package org.lokra.weed; import feign.Feign; import feign.jackson.JacksonDecoder; import feign.okhttp.OkHttpClient; import org.lokra.weed.content.Cluster; import org.lokra.weed.content.Locations; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; public class WeedManager { private final Logger log = LoggerFactory.getLogger(WeedManager.class); private ConcurrentMap<String, WeedVolumeClient> volumeClients = new ConcurrentHashMap<>(); private ConcurrentMap<String, Set<String>> volumeCollectionIds = new ConcurrentHashMap<>(); private ConcurrentMap<String, WeedMasterClient> peerMasters = new ConcurrentHashMap<>(); private final MasterHealthCheckThread masterHealthCheckThread = new MasterHealthCheckThread(); private final VolumeHealthCheckThread volumeHealthCheckThread = new VolumeHealthCheckThread(); private String leaderMasterUrl; private WeedMasterClient leaderMaster; private String host; private int port; public WeedManager() { } /** * Construction. * * @param host Master server host. * @param port Master server port. */ public WeedManager(String host, int port) { this.host = host; this.port = port; } /** * Startup weed manager. */ public void start() { leaderMasterUrl = assembleUrl(host, port); leaderMaster = Feign.builder() .client(new OkHttpClient()) .decoder(new JacksonDecoder()) .target(WeedMasterClient.class, String.format("http://%s", leaderMasterUrl)); fetchMasters(leaderMaster.cluster()); masterHealthCheckThread.start(); volumeHealthCheckThread.start(); } /** * Shutdown weed manager. */ public void shutdown() { masterHealthCheckThread.interrupt(); volumeHealthCheckThread.interrupt(); } /** * Get master server client. * * @return client. */ public WeedMasterClient getMasterClient() { return this.leaderMaster; } /** * Get volume server client. * * @return client. */ public WeedVolumeClient getVolumeClient(int volumeId, String collection) { Set<String> volumeIds = volumeCollectionIds.get(String.format("%d#%s", volumeId, collection != null ? collection : "")); if (volumeIds == null || volumeIds.size() == 0) volumeIds = fetchVolume(volumeId, collection); final long millis = System.currentTimeMillis(); final int index = (int) (millis % volumeIds.size()); final String[] ids = new String[volumeIds.size()]; return volumeClients.get(volumeIds.toArray(ids)[index]); } /** * Fetch volume server. * * @param volumeId Volume id. * @param collection Collection. * @return ids. */ private Set<String> fetchVolume(int volumeId, String collection) { Locations locations = getMasterClient().lookup(volumeId, collection); Set<String> ids = new HashSet<>(); locations.getLocations().forEach(location -> { final String id = String.format("%s#%s", location.getUrl(), location.getUrl()); ids.add(id); volumeClients.putIfAbsent(id, Feign.builder() .client(new OkHttpClient()) .decoder(new JacksonDecoder()) .target(WeedVolumeClient.class, String.format("http://%s", location.getPublicUrl()))); }); return ids; } /** * Master server host. * * @return host. */ public String getHost() { return host; } /** * Master server port. * * @return port. */ public int getPort() { return port; } /** * Assemble URL. * * @param host Host. * @param port Port. * @return URL. */ private String assembleUrl(String host, int port) { return String.format("%s:%d", host, port); } /** * Fetch master cluster information. * * @param cluster Master cluster information. */ private void fetchMasters(Cluster cluster) { if (!leaderMasterUrl.equals(cluster.getLeader())) { leaderMasterUrl = cluster.getLeader(); log.info("Weed leader master server is change to [{}]", leaderMasterUrl); leaderMaster = Feign.builder() .client(new OkHttpClient()) .decoder(new JacksonDecoder()) .target(WeedMasterClient.class, String.format("http://%s", leaderMaster)); } // Cleanup peer master Set<String> removeSet = peerMasters.keySet().stream().filter( key -> !cluster.getPeers().contains(key) && !cluster.getLeader().equals(key)) .collect(Collectors.toSet()); peerMasters.remove(leaderMasterUrl); removeSet.forEach(key -> peerMasters.remove(key)); cluster.getPeers().forEach(url -> { if (!peerMasters.containsKey(url)) { peerMasters.put(url, Feign.builder() .client(new OkHttpClient()) .decoder(new JacksonDecoder()) .target(WeedMasterClient.class, String.format("http://%s", url))); } }); } /** * Master server health check thread */ class MasterHealthCheckThread extends Thread { @Override public void run() { log.info("Started weed leader master server health check"); //noinspection InfiniteLoopStatement while (true) { try { Thread.sleep(3000); } catch (InterruptedException e) { log.info("Stop weed leader master server health check"); return; } try { fetchMasters(leaderMaster.cluster()); } catch (Exception e) { log.warn("Weed leader master server [{}] is down", leaderMasterUrl); for (String url : peerMasters.keySet()) { try { fetchMasters(peerMasters.get(url).cluster()); } catch (Exception ignored) { log.warn("Weed peer master server [{}] is down", url); continue; } break; } } } } } /** * Volume server health check thread */ class VolumeHealthCheckThread extends Thread { @Override public void run() { log.info("Started weed leader master server health check"); //noinspection InfiniteLoopStatement while (true) { try { Thread.sleep(3000); } catch (InterruptedException e) { log.info("Stop weed leader master server health check"); return; } try { fetchMasters(leaderMaster.cluster()); } catch (Exception e) { log.warn("Weed leader master server [{}] is down", leaderMasterUrl); for (String url : peerMasters.keySet()) { try { fetchMasters(peerMasters.get(url).cluster()); } catch (Exception ignored) { log.warn("Weed peer master server [{}] is down", url); continue; } break; } } } } } }