/* * Copyright (c) 2008-2011 EMC Corporation * All Rights Reserved */ package com.emc.storageos.coordinator.client.beacon.impl; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.data.Stat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.coordinator.client.beacon.ServiceBeacon; import com.emc.storageos.coordinator.common.Service; import com.emc.storageos.coordinator.common.impl.ServiceImpl; import com.emc.storageos.coordinator.common.impl.ZkConnection; import com.emc.storageos.coordinator.common.impl.ZkPath; import com.emc.storageos.coordinator.exceptions.CoordinatorException; import com.emc.storageos.services.util.NamedThreadPoolExecutor; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.state.ConnectionState; import org.apache.curator.framework.state.ConnectionStateListener; import org.apache.curator.utils.EnsurePath; /** * Default ServiceBeacon implementation */ public class ServiceBeaconImpl implements ServiceBeacon { private static final Logger _log = LoggerFactory.getLogger(ServiceBeaconImpl.class); // service path protected String _servicePath; // service parent path protected String _serviceParentPath; // service information protected ServiceImpl _service; // zk connection protected ZkConnection _zkConnection; private final ExecutorService _executor = new NamedThreadPoolExecutor(ServiceBeaconImpl.class.getSimpleName(), 1); // initialization flag private volatile boolean _bInitialized = false; private volatile boolean _bStarted = false; // Service beacon should be created to site specific area(/sites/<uuid>/service) since yoda. But in order to make sure // rolling upgrade could work, we need temporarily create beacons at global area(/service). So we add this flag here // to indicate where the beacon should be created - site specific aread(default), or global area in zk private boolean siteSpecific = true; /** * Reacts to connect/reconnect events by registering if necessary */ private final ConnectionStateListener _connectionListener = new ConnectionStateListener() { @Override public void stateChanged(CuratorFramework client, ConnectionState newState) { switch (newState) { case CONNECTED: case RECONNECTED: { _executor.submit(new Callable<Object>() { @Override public Object call() throws Exception { if (_bStarted) { // We should not register beacon before start() // zkConn is connected in init(). // The reconnection might happen before start() invoked, // which means the service is not fully started yet. register(); } return null; } }); break; } } } }; /** * Service setter * * @param service service info */ public void setService(ServiceImpl service) { _service = service; } public ServiceImpl getService() { return _service; } /** * Set ZK cluster connection. Connection must be built but not started when * this setter is called * * @param zkConnection ZK cluster connection */ public void setZkConnection(ZkConnection zkConnection) { _zkConnection = zkConnection; } public ZkConnection getZkConnection() { return _zkConnection; } public void setSiteSpecific(boolean siteSpecific) { this.siteSpecific = siteSpecific; } /** * Init method. * Add state change listener * Connect to zk cluster * Remove stale service registration from zk */ public void init() { _zkConnection.curator().getConnectionStateListenable().addListener(_connectionListener); _zkConnection.connect(); if (siteSpecific) { _serviceParentPath = String.format("%1$s/%2$s%3$s/%4$s/%5$s", ZkPath.SITES, _zkConnection.getSiteId(), ZkPath.SERVICE, _service.getName(), _service.getVersion()); } else { _serviceParentPath = String.format("%1$s/%2$s/%3$s", ZkPath.SERVICE, _service.getName(), _service.getVersion()); } _servicePath = String.format("%1$s/%2$s", _serviceParentPath, _service.getId()); try { checkStaleRegistration(); } catch (Exception ex) { _log.warn("Unable to remove stale service registration", ex); } _bInitialized = true; } /** * Check stale service registration that may exist in zk because of unclean shutdown. * Remove stale registration if there is. Return zk Stat object if the service has been * successfully registered before. * * @return null if service registration does not exist, otherwise a stat instance * to indicate zk path */ private Stat checkStaleRegistration() throws Exception { Stat stat = _zkConnection.curator().checkExists().forPath(_servicePath); if (stat != null && stat.getEphemeralOwner() != _zkConnection.curator(). getZookeeperClient().getZooKeeper().getSessionId()) { _zkConnection.curator().delete().forPath(_servicePath); _log.info("Deleted stale service registration from previous session"); stat = null; } return stat; } protected boolean register() throws Exception { // whenever we get into connected state (implies we previously moved out of // connected state for some reason), we check service registration and // update if necessary try { _log.info("Registering Service in path: {}", this._servicePath); Stat stat = checkStaleRegistration(); if (stat == null) { _zkConnection.curator().create().withMode(CreateMode.EPHEMERAL). forPath(_servicePath, _service.serialize()); _log.info("Service info registered"); return true; } } catch (Exception e) { _log.error("register service error", e); } return false; } @Override public Service info() { return _service; } @Override public void start() { // make sure ServiceBeacon is initialized. if (!_bInitialized) { init(); } try { EnsurePath path = new EnsurePath(_serviceParentPath); path.ensure(_zkConnection.curator().getZookeeperClient()); } catch (Exception e) { throw CoordinatorException.fatals .failedToConnectToServiceRegistrationEndpoint(e); } _executor.submit(new Callable<Object>() { @Override public Object call() throws Exception { register(); _bStarted = true; return null; } }); } @Override public void stop() { try { _zkConnection.curator().delete().forPath(_servicePath); } catch (Exception e) { _log.warn("Unable to delete service registration", e); } _executor.shutdown(); // shouldn't close connection since it is likely shared with some other service } }