/*
Copyright (c) 2012 LinkedIn Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* $Id: $
*/
package com.linkedin.d2.balancer.servers;
import com.linkedin.d2.balancer.properties.UriProperties;
import com.linkedin.d2.balancer.zkfs.ZKFSUtil;
import com.linkedin.d2.discovery.stores.zk.ZKConnection;
import com.linkedin.d2.discovery.stores.zk.ZKPersistentConnection;
import com.linkedin.d2.discovery.stores.zk.ZooKeeperEphemeralStore;
import com.linkedin.d2.discovery.stores.zk.ZooKeeperStore;
import com.linkedin.common.callback.Callback;
import com.linkedin.common.callback.CallbackAdapter;
import com.linkedin.common.callback.Callbacks;
import com.linkedin.common.util.None;
import java.util.Collections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicReference;
/**
* Manages a ZooKeeper connection and one or more Announcers. Upon being started, tells the
* announcers to announce themselves after the connection is ready.
* @author Steven Ihde
* @version $Revision: $
*/
public class ZooKeeperConnectionManager
{
private static final Logger LOG = LoggerFactory.getLogger(ZooKeeperConnectionManager.class);
private final String _zkConnectString;
private final int _zkSessionTimeout;
private final String _zkBasePath;
private final ZKStoreFactory<UriProperties,ZooKeeperEphemeralStore<UriProperties>> _factory;
private final ZooKeeperAnnouncer[] _servers;
private final AtomicReference<Callback<None>> _startupCallback = new AtomicReference<Callback<None>>();
private final ZKPersistentConnection _zkConnection;
private volatile ZooKeeperEphemeralStore<UriProperties> _store;
public ZooKeeperConnectionManager(String zkConnectString, int zkSessionTimeout,
String zkBasePath,
ZKStoreFactory<UriProperties,ZooKeeperEphemeralStore<UriProperties>> factory,
ZooKeeperAnnouncer... servers)
{
_zkConnectString = zkConnectString;
_zkSessionTimeout = zkSessionTimeout;
_zkBasePath = zkBasePath;
_factory = factory;
_servers = servers;
_zkConnection = new ZKPersistentConnection(_zkConnectString, _zkSessionTimeout, Collections.singletonList(new Listener()));
}
/**
* @deprecated Use {@link #ZooKeeperConnectionManager(String, int, String, ZKStoreFactory, ZooKeeperAnnouncer...)} instead.
*/
@Deprecated
public ZooKeeperConnectionManager(String zkConnectString, int zkSessionTimeout,
String zkBasePath,
ZKStoreFactory<UriProperties,ZooKeeperEphemeralStore<UriProperties>> factory,
int retryLimit,
ZooKeeperAnnouncer... servers)
{
this(zkConnectString, zkSessionTimeout, zkBasePath, factory, servers);
}
/**
* @deprecated Use {@link #ZooKeeperConnectionManager(String, int, String, ZKStoreFactory, ZooKeeperAnnouncer...)} instead.
*/
@Deprecated
public ZooKeeperConnectionManager(String zkConnectString, int zkSessionTimeout,
String zkBasePath,
ZKStoreFactory<UriProperties,ZooKeeperEphemeralStore<UriProperties>> factory,
int retryLimit,
boolean exponentialBackoff,
ScheduledExecutorService scheduler,
long initInterval,
ZooKeeperAnnouncer... servers)
{
this(zkConnectString, zkSessionTimeout, zkBasePath, factory, servers);
}
public void start(Callback<None> callback)
{
if (!_startupCallback.compareAndSet(null, callback))
{
throw new IllegalStateException("Already starting");
}
try
{
_zkConnection.start();
LOG.info("Started ZooKeeper connection to {}", _zkConnectString);
}
catch (Exception e)
{
_startupCallback.set(null);
callback.onError(e);
}
}
public void shutdown(final Callback<None> callback)
{
Callback<None> zkCloseCallback = new CallbackAdapter<None,None>(callback)
{
@Override
protected None convertResponse(None none) throws Exception
{
_zkConnection.shutdown();
return none;
}
};
if (_store != null)
{
_store.shutdown(zkCloseCallback);
}
else
{
zkCloseCallback.onSuccess(None.none());
}
}
public void markDownAllServers(final Callback<None> callback)
{
Callback<None> markDownCallback;
if (callback != null)
{
markDownCallback = callback;
}
else
{
markDownCallback = new Callback<None>()
{
@Override
public void onError(Throwable e)
{
LOG.error("failed to mark down servers", e);
}
@Override
public void onSuccess(None result)
{
LOG.info("mark down all servers successful");
}
};
}
Callback<None> multiCallback = Callbacks.countDown(markDownCallback, _servers.length);
for (ZooKeeperAnnouncer server : _servers)
{
server.markDown(multiCallback);
}
}
public void markUpAllServers(final Callback<None> callback)
{
Callback<None> markUpCallback;
if (callback != null)
{
markUpCallback = callback;
}
else
{
markUpCallback = new Callback<None>()
{
@Override
public void onError(Throwable e)
{
LOG.error("failed to mark up servers", e);
}
@Override
public void onSuccess(None result)
{
LOG.info("mark up all servers successful");
}
};
}
Callback<None> multiCallback = Callbacks.countDown(markUpCallback, _servers.length);
for (ZooKeeperAnnouncer server : _servers)
{
server.markUp(multiCallback);
}
}
private class Listener implements ZKPersistentConnection.EventListener
{
/**
* a boolean flag to indicate whether _store is successfully started or not
*/
private volatile boolean _storeStarted = false;
@Override
public void notifyEvent(ZKPersistentConnection.Event event)
{
LOG.info("Received ZKPersistentConnection Event {}", event);
switch (event)
{
case SESSION_ESTABLISHED:
{
_store = _factory.createStore(_zkConnection.getZKConnection(), ZKFSUtil.uriPath(_zkBasePath));
startStore();
break;
}
case SESSION_EXPIRED:
{
_store.shutdown(Callbacks.<None>empty());
_storeStarted = false;
break;
}
case CONNECTED:
{
if (!_storeStarted)
{
startStore();
}
else
{
for (ZooKeeperAnnouncer server : _servers)
{
server.retry(Callbacks.<None>empty());
}
}
break;
}
case DISCONNECTED:
// do nothing
break;
}
}
private void startStore()
{
final Callback<None> callback = _startupCallback.getAndSet(null);
final Callback<None> multiCallback = callback != null ?
Callbacks.countDown(callback, _servers.length) :
Callbacks.<None>empty();
_store.start(new Callback<None>()
{
@Override
public void onError(Throwable e)
{
LOG.error("Failed to start ZooKeeperEphemeralStore", e);
if (callback != null)
{
callback.onError(e);
}
}
@Override
public void onSuccess(None result)
{
/* mark store as started */
_storeStarted = true;
for (ZooKeeperAnnouncer server : _servers)
{
server.setStore(_store);
server.start(new Callback<None>()
{
@Override
public void onError(Throwable e)
{
LOG.error("Failed to start server", e);
multiCallback.onError(e);
}
@Override
public void onSuccess(None result)
{
LOG.info("Started an announcer");
multiCallback.onSuccess(result);
}
});
}
LOG.info("Starting {} announcers", (_servers.length));
}
});
}
}
public interface ZKStoreFactory<P, Z extends ZooKeeperStore<P>>
{
Z createStore(ZKConnection connection, String path);
}
public ZooKeeperAnnouncer[] getAnnouncers()
{
return _servers;
}
}