package com.workshare.msnos.usvc; import static com.workshare.msnos.core.Message.Type.PRS; import static com.workshare.msnos.core.Message.Type.QNE; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import net.jodah.expiringmap.ExpiringMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.workshare.msnos.core.Cloud; import com.workshare.msnos.core.Cloud.Listener; import com.workshare.msnos.core.Iden; import com.workshare.msnos.core.LocalAgent; import com.workshare.msnos.core.Message; import com.workshare.msnos.core.MessageBuilder; import com.workshare.msnos.core.MsnosException; import com.workshare.msnos.core.PassiveAgent; import com.workshare.msnos.core.Receipt; import com.workshare.msnos.core.RemoteAgent; import com.workshare.msnos.core.payloads.FltPayload; import com.workshare.msnos.core.payloads.HealthcheckPayload; import com.workshare.msnos.core.payloads.Presence; import com.workshare.msnos.core.payloads.QnePayload; import com.workshare.msnos.core.protocols.ip.Endpoint; import com.workshare.msnos.core.protocols.ip.HttpEndpoint; import com.workshare.msnos.soup.json.Json; import com.workshare.msnos.soup.threading.ExecutorServices; import com.workshare.msnos.usvc.api.RestApi; import com.workshare.msnos.usvc.api.RestApi.Type; import com.workshare.msnos.usvc.api.routing.ApiRepository; public class Microcloud { private static final ScheduledExecutorService DEFAULT_EXECUTOR = ExecutorServices.newSingleThreadScheduledExecutor(); private static final Long ENQUIRY_EXPIRE = Long.getLong("com.ws.msnos.microservice.enquiry.timeout", 60); private static final Logger log = LoggerFactory.getLogger("STANDARD"); private final Map<Iden, RemoteMicroservice> remoteServices; private final Map<UUID, PassiveService> passiveServices; private final ApiRepository apis; private final Cloud cloud; private final Map<UUID, Iden> enquiries; private final ScheduledExecutorService executor; public Microcloud(Cloud cloud) { this(cloud, DEFAULT_EXECUTOR); } public Microcloud(Cloud cloud, ScheduledExecutorService executor) { this.cloud = cloud; this.cloud.addSynchronousListener(new Cloud.Listener() { @Override public void onMessage(Message message) { try { doProcess(message); } catch (MsnosException e) { log.error("Error processing message {}", e); } } }); remoteServices = new ConcurrentHashMap<Iden, RemoteMicroservice>(); passiveServices = new ConcurrentHashMap<UUID, PassiveService>(); apis = new ApiRepository(); this.executor = executor; this.enquiries = ExpiringMap.builder().expiration(ENQUIRY_EXPIRE, TimeUnit.SECONDS).build(); Healthchecker healthcheck = new Healthchecker(this, executor); healthcheck.start(); } public Receipt send(Message message) throws MsnosException { return cloud.send(message); } public void process(Message message, Endpoint.Type endpoint) { cloud.process(message, endpoint.toString()); } public Listener addListener(Listener listener) { return cloud.addListener(listener); } public void removeListener(Listener listener) { cloud.removeListener(listener); } public Cloud getCloud() { return cloud; } public ApiRepository getApis() { return apis; } public PassiveService searchPassives(UUID search) { return passiveServices.get(search); } public boolean canServe(String path) { return getApis().canServe(path); } public RestApi searchApi(IMicroservice microservice, String path) { return getApis().searchApi(microservice, path); } public RestApi searchApiById(long id) { return getApis().searchApiById(id); } public List<RemoteMicroservice> getMicroServices() { return Collections.unmodifiableList(new ArrayList<RemoteMicroservice>(remoteServices.values())); } public List<PassiveService> getPassiveServices() { return Collections.unmodifiableList(new ArrayList<PassiveService>(passiveServices.values())); } void onJoin(Microservice microservice) throws MsnosException { LocalAgent agent = microservice.getAgent(); agent.join(cloud); Message message = new MessageBuilder(Message.Type.ENQ, agent, cloud).make(); agent.send(message); } public void onLeave(Microservice microservice) throws MsnosException { LocalAgent agent = microservice.getAgent(); agent.leave(); } private void doProcess(Message message) throws MsnosException { log.debug("Handling message {}", message); switch (message.getType()) { case QNE: processQNE(message); break; case FLT: processFault(message); break; case PRS: processPresence(message); break; case HCK: processHealthcheck(message); break; default: break; } enquiryMicroserviceIfUnknown(message); } private void enquiryMicroserviceIfUnknown(final Message message) { final Iden from = message.getFrom(); if (!remoteServices.containsKey(from)) { if (from.getType() == Iden.Type.AGT && message.getType() != PRS) if (enquiries.get(from.getUUID()) == null) { enquiries.put(from.getUUID(), from); executor.schedule(new Runnable(){ @Override public void run() { if (!remoteServices.containsKey(from)) { log.warn("Enquiring unknown microservice {} on message {} received", from, message.getType()); try { send(new MessageBuilder(Message.Type.ENQ, cloud, from).make()); } catch (MsnosException e) { log.warn("Unexpected exception while enquiring "+from, e); } } else { log.debug("No need to enquiry microservice {} on message {} received", from, message.getType()); } }}, ENQUIRY_EXPIRE/5, TimeUnit.SECONDS); } } } private void processHealthcheck(Message message) { final HealthcheckPayload payload = ((HealthcheckPayload) message.getData()); final Iden iden = payload.getIden(); final RemoteMicroservice remote = remoteServices.get(iden); if (remote == null) { if (!cloud.containsAgent(iden) && !passiveServices.containsKey(iden)) { log.warn("Received health report on service {} not present in the cloud", iden); } return; } if (payload.isWorking()) { log.debug("Marking remote {} as working after cloud message received", remote.getName()); remote.markWorking(); } else { log.info("Marking remote {} as faulty after cloud message received", remote.getName()); remote.markFaulty(); } } private void processQNE(Message message) throws MsnosException { QnePayload qnePayload = ((QnePayload) message.getData()); final Iden iden = message.getFrom(); RemoteAgent remoteAgent = cloud.find(iden); if (remoteAgent != null) { RemoteMicroservice remote; Iden remoteKey = remoteAgent.getIden(); if (remoteServices.containsKey(remoteKey)) { remote = remoteServices.get(remoteKey); remote.setApis(qnePayload.getApis()); } else { remote = new RemoteMicroservice(qnePayload.getName(), remoteAgent, new HashSet<RestApi>(qnePayload.getApis())); remoteServices.put(remoteKey, remote); } registerRemoteMsnosEndpoints(remote); apis.register(remote); } } private void registerRemoteMsnosEndpoints(RemoteMicroservice remote) throws MsnosException { Set<RestApi> remoteApis = remote.getApis(); for (RestApi restApi : remoteApis) { if (restApi.getType() == RestApi.Type.MSNOS_HTTP) { final HttpEndpoint endpoint = new HttpEndpoint(remote, restApi); cloud.registerRemoteMsnosEndpoint(endpoint); } } } private void processFault(Message message) { Iden about = ((FltPayload) message.getData()).getAbout(); removeMicroservice(about); } private void processPresence(Message message) { boolean leaving = (false == ((Presence) message.getData()).isPresent()); if (leaving) removeMicroservice(message.getFrom()); } private void removeMicroservice(Iden about) { if (remoteServices.containsKey(about)) { apis.unregister(remoteServices.get(about)); remoteServices.remove(about); } } public RemoteMicroservice getRemoteMicroService(Iden iden) { return remoteServices.get(iden); } void publish(Microservice microservice, RestApi... apis) throws MsnosException { LocalAgent agent = microservice.getAgent(); Message message = new MessageBuilder(QNE, agent, cloud).with(new QnePayload(microservice.getName(), apis)).make(); cloud.send(message); handleMsnosApis(microservice, apis); } private void handleMsnosApis(Microservice microservice, RestApi... apis) throws MsnosException { Set<RestApi> msnosApis = new HashSet<RestApi>(); for (RestApi api : apis) { if (api.getType() == Type.MSNOS_HTTP) { msnosApis.add(api); } } if (msnosApis.size() == 0) return; final LocalAgent agent = microservice.getAgent(); msnosApis = RestApi.ensureHostIsPresent(agent, msnosApis); log.debug("Registering msnos apis {} for agent {}", msnosApis, agent.getIden().getUUID()); for (RestApi api : msnosApis) { cloud.registerLocalMsnosEndpoint(new HttpEndpoint(microservice, api)); } cloud.send(new MessageBuilder(PRS, agent, cloud).with(new Presence(true, agent)).make()); } void onJoin(PassiveService passive) throws MsnosException { passiveServices.put(passive.getUuid(), passive); RestApi restApi = new RestApi(passive.getHealthCheckUri(), passive.getPort(), passive.getHost(), RestApi.Type.HEALTHCHECK, false); publish(passive, restApi); } void publish(PassiveService passiveService, RestApi... apis) throws MsnosException { if (!passiveServices.containsKey(passiveService.getUuid())) throw new IllegalArgumentException("Cannot publish passive restApis that are from services which are not joined to the Cloud! "); PassiveAgent agent = passiveService.getAgent(); Message message = new MessageBuilder(Message.Type.QNE, agent, cloud).with(new QnePayload(passiveService.getName(), apis)).make(); cloud.send(message); } @Override public String toString() { try { return Json.toJsonString(this); } catch (Throwable any) { return super.toString(); } } public void update(final long amount, final TimeUnit unit) throws MsnosException { final Message[] messages = new Message[] { new MessageBuilder(Message.Type.DSC, cloud, cloud).make(), new MessageBuilder(Message.Type.ENQ, cloud, cloud).make(), }; long allotted = amount/messages.length; for (Message message : messages) { Receipt receipt = cloud.sendSync(message); try { receipt.waitForDelivery(allotted, unit); } catch (InterruptedException e) { Thread.interrupted(); } } } }