Java tutorial
package io.urmia.naming.service; /** * * Copyright 2014 by Amin Abbaspour * * This file is part of Urmia.io * * Urmia.io is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Urmia.io is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Urmia.io. If not, see <http://www.gnu.org/licenses/>. */ import com.google.common.base.*; import com.google.common.collect.FluentIterable; import com.google.common.collect.Lists; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.api.UnhandledErrorListener; import org.apache.curator.framework.state.ConnectionState; import org.apache.curator.framework.state.ConnectionStateListener; import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.curator.x.discovery.*; import org.apache.curator.x.discovery.details.JsonInstanceSerializer; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.urmia.naming.model.NodeType; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; /** * this class supposed to cache values and reflect changes in almost real time. * * todo: need a constructor/builder which accepts args[] and checks for '-z <URL>' and '-a <AZ>' */ public class ZkNamingServiceImpl implements NamingService { private static final Logger log = LoggerFactory.getLogger(ZkNamingServiceImpl.class); private final ServiceDiscovery<NodeType> naming; private final ServiceDiscovery<NodeType> discovery; private final CuratorFramework client; public static final JsonInstanceSerializer<NodeType> serializer = new JsonInstanceSerializer<NodeType>( NodeType.class); private static CuratorFramework mkClient(String address) throws InterruptedException { log.info("zk mkClient: {}", address); final RetryPolicy retryPolicy; retryPolicy = new ExponentialBackoffRetry(1000, 3); //retryPolicy = new org.apache.curator.retry.RetryNTimes(3, 1000); CuratorFramework client = CuratorFrameworkFactory.newClient(address, retryPolicy); client.getUnhandledErrorListenable().addListener(new MyUnhandledErrorListener()); client.getConnectionStateListenable().addListener(new MyConnectionStateListener()); log.info("zk mkClient start..."); client.start(); Thread.sleep(1000); if (!client.getZookeeperClient().isConnected()) throw new ExceptionInInitializerError("unable to get initial zk connection..."); return client; } private static class MyUnhandledErrorListener implements UnhandledErrorListener { @Override public void unhandledError(String message, Throwable e) { log.error("unhandledError: {}", e.getMessage()); System.exit(-1); } } private static class MyConnectionStateListener implements ConnectionStateListener { @Override public void stateChanged(CuratorFramework client, ConnectionState newState) { log.info("connection stateChanged: {}", newState); } } public ZkNamingServiceImpl(String address, int az) throws Exception { this(mkClient(address), az); } public ZkNamingServiceImpl(CuratorFramework root, int az) throws Exception { this.client = root; try { root.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT) .forPath("/urmia/" + az + "/naming"); } catch (KeeperException.NodeExistsException nee) { //log.info("skipping to parent create. already exists: {}", "/urmia/" + az + "/naming"); } try { root.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT) .forPath("/urmia/" + az + "/discovery"); } catch (KeeperException.NodeExistsException nee) { //log.info("skipping to parent create. already exists: {}", "/urmia/" + az + "/discovery"); } naming = ServiceDiscoveryBuilder.builder(NodeType.class).client(root).basePath("/urmia/" + az + "/naming") .serializer(serializer).build(); naming.start(); discovery = ServiceDiscoveryBuilder.builder(NodeType.class).client(root) .basePath("/urmia/" + az + "/discovery").serializer(serializer).build(); discovery.start(); } static final Comparator<ServiceInstance<NodeType>> nodeTypeComparator = new Comparator<ServiceInstance<NodeType>>() { public int compare(ServiceInstance<NodeType> o1, ServiceInstance<NodeType> o2) { int c; // type c = o1.getPayload().ordinal() - o2.getPayload().ordinal(); if (c != 0) return c; // host c = o1.getAddress().compareTo(o2.getAddress()); if (c != 0) return c; // port c = o1.getPort() - o2.getPort(); if (c != 0) return c; return o1.getId().compareTo(o2.getId()); } }; @Override public final List<ServiceInstance<NodeType>> list(NodeType type, Predicate<ServiceInstance<NodeType>> p) throws Exception { return list(type).filter(p).toSortedList(nodeTypeComparator); } @Override public final ServiceInstance<NodeType> get(NodeType type, String id) throws Exception { return naming.queryForInstance(type.name(), id); } private FluentIterable<ServiceInstance<NodeType>> list(NodeType type) throws Exception { return FluentIterable.from(naming.queryForInstances(type.name())); //.filter(fixed()); } private int suggestPort(NodeType type, String host) throws Exception { List<ServiceInstance<NodeType>> l = list(type, onHost(host)); int port = type.defaultPort + l.size(); log.info("suggestPort of type: {}, on host: {}, size: {} --> {}", type, host, l.size(), port); return port; } @Override public ServiceInstanceBuilder<NodeType> builder(NodeType type, ServiceType serviceType) throws Exception { return ServiceInstance.<NodeType>builder().address(getLocalHostName()).payload(type).name(type.name()) .port(suggestPort(type, getLocalHostName())).serviceType(serviceType); } @Override public List<NodeType> queryTypes() throws Exception { return FluentIterable.from(naming.queryForNames()).transform(nameToType()).filter(Predicates.notNull()) .toList(); } private Function<String, NodeType> nameToType() { return new Function<String, NodeType>() { public NodeType apply(String input) { try { return NodeType.valueOf(input); } catch (IllegalArgumentException e) { return null; } } }; } private Predicate<ServiceInstance<NodeType>> onHost(final String host) { return new Predicate<ServiceInstance<NodeType>>() { public boolean apply(ServiceInstance<NodeType> input) { return host.equalsIgnoreCase(input.getAddress()); } }; } @Override public void remove(ServiceInstance<NodeType> si) throws Exception { log.info("remove: {}", si); Preconditions.checkArgument(ServiceType.DYNAMIC != si.getServiceType()); naming.unregisterService(si); } @Override public void add(ServiceInstance<NodeType> si) throws Exception { log.info("add: {}", si); Preconditions.checkArgument(ServiceType.DYNAMIC != si.getServiceType()); naming.registerService(si); } @Override public int getRegisteredOnHostCount(NodeType type, String host) throws Exception { return list(type, onHost(host)).size(); } // -- discovery @Override public ServiceInstance<NodeType> discover(NodeType type, String id) throws Exception { return discovery.queryForInstance(type.name(), id); } static <T> List<T> shuffle(List<T> input, int count) { if (input == null || count >= input.size()) return input; final List<T> shuffled = new LinkedList<T>(input); Collections.shuffle(shuffled); return shuffled.subList(0, count); } @Override public List<ServiceInstance<NodeType>> suggestStorage(int durability) throws Exception { return shuffle(Lists.newArrayList(discovery.queryForInstances(NodeType.ODS.name())), durability); } private ServiceInstance<NodeType> dynamic(ServiceInstance<NodeType> si) throws Exception { if (si.getServiceType() == ServiceType.DYNAMIC) return si; return ServiceInstance.<NodeType>builder().serviceType(ServiceType.DYNAMIC).port(si.getPort()) .id(si.getId()).payload(si.getPayload()).name(si.getName()).address(si.getAddress()) .uriSpec(si.getUriSpec()).build(); } @Override public void register(ServiceInstance<NodeType> si) throws Exception { log.info("register: {}", si); if (si == null) return; deregister(si); discovery.registerService(dynamic(si)); } @Override public void deregister(ServiceInstance<NodeType> si) throws Exception { log.info("deregister: {}", si); if (si == null) return; if (client == null) return; if (client.getZookeeperClient().isConnected()) //Preconditions.checkArgument(ServiceType.DYNAMIC == si.getServiceType()); discovery.unregisterService(si); } private boolean isUp(ServiceInstance<NodeType> si) throws Exception { return discover(si.getPayload(), si.getId()) != null; } @Override public Optional<ServiceInstance<NodeType>> getOfType(NodeType t, String host, int order) throws Exception { List<ServiceInstance<NodeType>> l = list(t, onHost(host)); if (l == null || l.isEmpty() || l.size() <= order) return Optional.absent(); return Optional.fromNullable(l.get(order)); } public int getRunningCount(ServiceInstance si) throws Exception { FluentIterable<ServiceInstance<NodeType>> onMyHost = FluentIterable .from(discovery.queryForInstances(si.getName())).filter(onHost(si.getAddress())); log.info("getRunningCount for {} onMyHost: {}", si, onMyHost); return onMyHost.isEmpty() ? 0 : onMyHost.size() /*- 1*/; } private static String getLocalHostName() throws UnknownHostException { return InetAddress.getLocalHost().getHostName(); /* // todo: make a gethostname(3) syscall if (System.getProperty("os.name").startsWith("Windows")) return System.getenv("COMPUTERNAME"); return System.getenv("HOSTNAME"); */ } @Override public Optional<ServiceInstance<NodeType>> whoAmI(NodeType t, boolean autoRegister) throws Exception { String host = getLocalHostName(); log.info("my host: {}", host); List<ServiceInstance<NodeType>> onMyHost = list(t, onHost(host)); if (onMyHost.isEmpty()) // todo: register if autoRegister return Optional.absent(); /* if(onMyHost.size() == 1) return Optional.fromNullable(onMyHost.get(0)); */ log.debug("checking whoAmI against: {}", onMyHost); for (ServiceInstance<NodeType> si : onMyHost) { if (!isUp(si)) { log.info("good. this instance is not up: {}", si); return Optional.of(si); } else { log.info("trying next one. this instance is up: {}", si); } } return Optional.absent(); } }