package eu.europeana.cloud.service.coordination.registration; import com.google.common.base.Throwables; import eu.europeana.cloud.service.coordination.ServiceProperties; import eu.europeana.cloud.service.coordination.ZookeeperService; import org.apache.curator.x.discovery.ServiceDiscovery; import org.apache.curator.x.discovery.ServiceDiscoveryBuilder; import org.apache.curator.x.discovery.ServiceInstance; import org.apache.curator.x.discovery.ServiceInstanceBuilder; import org.apache.curator.x.discovery.details.InstanceSerializer; import org.apache.curator.x.discovery.details.JsonInstanceSerializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.PostConstruct; import javax.servlet.ServletContext; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Collection; import java.util.Iterator; /** * Registers services as available in Zookeeper. * <p/> * ZooKeeper is used for service discovery: * <p/> * Services are registered on a common znode, and any client can query Zookeeper * for a list of available services. * * @author emmanouil.koufakis@theeuropeanlibrary.org */ public final class ZookeeperServiceAdvertiser implements EcloudServiceAdvertiser { @Autowired ServletContext servletContext; /** * Logging */ private static final Logger LOGGER = LoggerFactory .getLogger(ZookeeperServiceAdvertiser.class); /** * List of properties that allow a client to connect to this UIS REST * Service. */ private ServiceProperties currentlyAdvertisedServiceProperties; /** * Id of currenly advertised service. * Autogenerated at runtime. * <p/> * Example: "1b96c813-0ec2-4038-ab49-1ef6a1a73083" */ private String currentlyAdvertisedServiceID; /** * Used to serialize the {@link ServiceProperties} before sending them to * Zookeeper. */ private final InstanceSerializer<ServiceProperties> serializer = new JsonInstanceSerializer<ServiceProperties>( ServiceProperties.class); /** * Service that actually performs the advertisement. */ private final ServiceDiscovery<ServiceProperties> discovery; /** * List of properties that allow a client to connect to some UIS REST * Service. */ private ServiceProperties serviceProperties; ZookeeperServiceAdvertiser(final ZookeeperService zookeeper, final String discoveryPath, final ServiceProperties serviceProperties) { LOGGER.info("ZookeeperServiceAdvertiser starting..."); this.serviceProperties = serviceProperties; discovery = ServiceDiscoveryBuilder.builder(ServiceProperties.class) .basePath(zookeeper.getZookeeperPath() + discoveryPath) .client(zookeeper.getClient()).serializer(serializer).build(); try { discovery.start(); } catch (Exception e) { throw Throwables.propagate(e); } LOGGER.info("ZookeeperServiceAdvertiser started successfully."); } /** * Registers this service as available. * <p/> * Other clients querying Zookeeper for available services will receive this * service on their list. * * @param serviceProperties List of properties required to connect to this Service. */ @Override public void startAdvertising(final ServiceProperties serviceProperties) { LOGGER.info("ZookeeperServiceAdvertiser starting advertising process '{}' ...", serviceProperties); try { ServiceInstance<ServiceProperties> propertiesToBeRegisteredInZoo = convert(serviceProperties); discovery.registerService(propertiesToBeRegisteredInZoo); this.currentlyAdvertisedServiceProperties = serviceProperties; this.currentlyAdvertisedServiceID = propertiesToBeRegisteredInZoo.getId(); LOGGER.info("ZookeeperServiceAdvertiser has advertised the service successfully."); } catch (final Exception e) { this.currentlyAdvertisedServiceProperties = null; this.currentlyAdvertisedServiceID = null; LOGGER.error(e.getMessage()); throw Throwables.propagate(e); } } @Override public void stopAdvertising() { LOGGER.info("ZookeeperServiceAdvertiser unregistering process '{}' ...", serviceProperties); try { discovery.unregisterService(convert(this.currentlyAdvertisedServiceProperties)); this.currentlyAdvertisedServiceProperties = null; this.currentlyAdvertisedServiceID = null; } catch (Exception e) { LOGGER.error(e.getMessage()); throw Throwables.propagate(e); } } private static ServiceInstance<ServiceProperties> convert( final ServiceProperties p) throws Exception { return ServiceInstance.<ServiceProperties>builder() .name(p.getServiceName()).payload(p) .address(p.getListenAddress()).build(); } /** * Used if no IP address is specified by the user. * * @return tries to autodetect server's ip address using ServiceInstanceBuilder.getAllLocalIPs() */ private String getAutoListenAddress() throws UnknownHostException, IOException { Collection<InetAddress> localIps = ServiceInstanceBuilder.getAllLocalIPs(); Iterator<InetAddress> localIpsIter = localIps.iterator(); LOGGER.info( "ZookeeperServiceAdvertiser: autodetecting ip address.. found {} localIpAddresses", localIps.size()); while (localIpsIter.hasNext()) { InetAddress localIp = localIpsIter.next(); if (localIp.isReachable(5000)) { String address = localIp.getLocalHost().getHostAddress(); LOGGER.info("ZookeeperServiceAdvertiser: autodetected {} as listen address", address); return address; } } return null; } /** * @return Servlet's context path. * <p/> * (Appended to the ip address, to construct the listen address for this service) */ private String getServletContextPath() { String servletPath = servletContext.getContextPath(); LOGGER.info("ZookeeperServiceAdvertiser detecting ContextPath: {}", servletPath); return servletPath; } /** * TODO * <p/> * Used if no port is specified by the user. * * @return Currently hardcoded port number. * <p/> * How this port is configured will depend * on how the services are going to be deployed in the future. */ private String getPort() { return "8080"; } @PostConstruct public void postConstruct() { try { if (this.serviceProperties.getListenAddress().isEmpty()) { String autodectedIpaddress = getAutoListenAddress(); if (autodectedIpaddress != null) { this.serviceProperties.setListenAddress("http://" + getAutoListenAddress() + ":" + getPort() + getServletContextPath()); } } else { this.serviceProperties.setListenAddress(serviceProperties.getListenAddress() + getServletContextPath()); } } catch (Exception e) { LOGGER.warn("ZookeeperServiceAdvertiser: Error while setting service address.. {}", e.getMessage()); } this.startAdvertising(serviceProperties); LOGGER.info("ZookeeperServiceAdvertiser advertising process successfull."); } @Override public String getCurrentlyAdvertisedServiceID() { return currentlyAdvertisedServiceID; } @Override public String getCurrentlyAdvertisedServiceAddress() { if (currentlyAdvertisedServiceProperties != null) { return currentlyAdvertisedServiceProperties.getListenAddress(); } return null; } public ServiceProperties getServiceProperties() { return serviceProperties; } }