package org.kevoree.core; import org.kevoree.*; import org.kevoree.api.BootstrapService; import org.kevoree.api.KevScriptService; import org.kevoree.api.NodeType; import org.kevoree.api.PlatformService; import org.kevoree.api.adaptation.AdaptationModel; import org.kevoree.api.handler.*; import org.kevoree.api.telemetry.TelemetryEvent; import org.kevoree.api.telemetry.TelemetryListener; import org.kevoree.core.deploy.PrimitiveCommandExecutionHelper; import org.kevoree.core.deploy.PrimitiveExecute; import org.kevoree.factory.KevoreeFactory; import org.kevoree.kcl.api.FlexyClassLoader; import org.kevoree.log.Log; import org.kevoree.pmodeling.api.trace.TraceSequence; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.LinkedList; import java.util.UUID; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicReference; /** * * Created by duke on 9/26/14. */ public class KevoreeCoreBean implements ContextAwareModelService, PlatformService { private ArrayList<TelemetryListener> telemetryListeners = new ArrayList<TelemetryListener>(); private String nodeName; private KevScriptService kevscript; private OnStopHandler onStopHandler; public KevoreeCoreBean(KevScriptService kevscript) { this.kevscript = kevscript; } @Override public void addTelemetryListener(TelemetryListener l) { telemetryListeners.add(l); } @Override public void removeTelemetryListener(TelemetryListener l) { telemetryListeners.remove(l); } public boolean isAnyTelemetryListener() { return !telemetryListeners.isEmpty(); } @Override public String getNodeName() { return nodeName; } public void setNodeName(String nodeName) { this.nodeName = nodeName; } public KevoreeFactory getKevoreeFactory() { return kevoreeFactory; } public KevoreeFactory getFactory() { return kevoreeFactory; } public void broadcastTelemetry(TelemetryEvent.Type typeMessage, String message, Throwable stack) { if (isAnyTelemetryListener()) { String txt_stack = ""; if (stack != null) { try { ByteArrayOutputStream boo = new ByteArrayOutputStream(); PrintStream writer = new PrintStream(boo); stack.printStackTrace(writer); boo.flush(); boo.close(); txt_stack = new String(boo.toByteArray()); } catch (Exception e) { e.printStackTrace(); } } TelemetryEvent event = TelemetryEventImpl.build(getNodeName(), typeMessage, message, txt_stack); for (TelemetryListener tl : telemetryListeners) { tl.notify(event); } } } ContainerRoot pending = null; @Override public ContainerRoot getPendingModel() { return pending; } KevoreeListeners modelListeners = new KevoreeListeners(this); public BootstrapService getBootstrapService() { return bootstrapService; } public void setBootstrapService(BootstrapService bootstrapService) { this.bootstrapService = bootstrapService; } private BootstrapService bootstrapService = null; private NodeType nodeInstance; private MethodAnnotationResolver resolver; private LinkedList<UUIDModel> models = new LinkedList<UUIDModel>(); private KevoreeFactory kevoreeFactory = new org.kevoree.factory.DefaultKevoreeFactory(); private AtomicReference<UUIDModel> model = new AtomicReference<UUIDModel>(); private ExecutorService scheduler; private ScheduledExecutorService lockWatchDog; private ScheduledFuture futurWatchDog; private TupleLockCallBack currentLock; private ContainerRoot cloneCurrentModel(ContainerRoot pmodel) { return kevoreeFactory.createModelCloner().clone(pmodel, true); } @Override public void registerModelListener(ModelListener listener, String callerPath) { modelListeners.addListener(listener); } @Override public void unregisterModelListener(ModelListener listener, String callerPath) { modelListeners.removeListener(listener); } private class UpdateModelRunnable implements Runnable { ContainerRoot targetModel; UUID uuid; UpdateCallback callback; String callerPath; public UpdateModelRunnable(ContainerRoot targetModel, UUID uuid, UpdateCallback callback, String callerPath) { this.targetModel = targetModel; this.uuid = uuid; this.callback = callback; this.callerPath = callerPath; } @Override public void run() { boolean res; if (currentLock != null) { if (uuid.compareTo(currentLock.getUuid()) == 0) { res = internal_update_model(targetModel, callerPath); } else { Log.debug("Core Locked , bad UUID {}", uuid); res = false; //LOCK REFUSED ! } } else { //COMMON CHECK if (uuid != null) { if (uuid.compareTo(model.get().getUUID()) == 0) { res = internal_update_model(targetModel, callerPath); } else { res = false; } } else { res = internal_update_model(targetModel, callerPath); } } final boolean finalRes = res; if (callback!= null) { callback.run(finalRes); } } } @Override public UUIDModel getCurrentModel() { return model.get(); } @Override public void compareAndSwap(ContainerRoot model, UUID uuid, UpdateCallback callback, String callerPath) { scheduler.submit(new UpdateModelRunnable(cloneCurrentModel(model), uuid, callback, callerPath)); } @Override public void update(ContainerRoot model, UpdateCallback callback, String callerPath) { scheduler.submit(new UpdateModelRunnable(cloneCurrentModel(model), null, callback, callerPath)); } private class UpdateScriptRunnable implements Runnable { private String script; private UpdateCallback callback; private String callerPath; private UpdateScriptRunnable(String script, UpdateCallback callback, String callerPath) { this.script = script; this.callback = callback; this.callerPath = callerPath; } @Override public void run() { // TODO throwable should be returned in UpdateCallback as an error parameter to distinguish error from "false" result try { ContainerRoot newModel = kevoreeFactory.createModelCloner().clone(model.get().getModel(), false); kevscript.execute(script, newModel); callback.run(internal_update_model(cloneCurrentModel(newModel), callerPath)); } catch (Throwable e) { Log.error("Error while applying submitted KevScript", e); callback.run(false); } } } @Override public void submitScript(String script, UpdateCallback callback, String callerPath) { scheduler.submit(new UpdateScriptRunnable(script, callback, callerPath)); } private class UpdateSequenceRunnable implements Runnable { private TraceSequence sequence; private UpdateSequenceRunnable(TraceSequence sequence, UpdateCallback callback, String callerPath) { this.sequence = sequence; this.callback = callback; this.callerPath = callerPath; } private UpdateCallback callback; private String callerPath; @Override public void run() { try { ContainerRoot newModel = kevoreeFactory.createModelCloner().clone(model.get().getModel(), false); sequence.applyOn(newModel); callback.run(internal_update_model(cloneCurrentModel(newModel), callerPath)); } catch (Throwable e) { broadcastTelemetry(TelemetryEvent.Type.LOG_ERROR, "Error while applying trace sequence", e); //Log.error("error while apply trace sequence", e) Log.error("Error while applying submitted traces sequence", e); callback.run(false); } } } public void submitSequence(TraceSequence sequence, UpdateCallback callback, String callerPath) { scheduler.submit(new UpdateSequenceRunnable(sequence, callback, callerPath)); } private void switchToNewModel(ContainerRoot c) { ContainerRoot cc = c; if (!c.isReadOnly()) { broadcastTelemetry(TelemetryEvent.Type.LOG_WARNING, "It is not safe to store ReadWrite model!", null); //Log.error("It is not safe to store ReadWrite model") cc = kevoreeFactory.createModelCloner().clone(c, true); } //current model is backed-up UUIDModel previousModel = model.get(); if (previousModel != null) { models.add(previousModel); } // TODO : MAGIC NUMBER ;-) , ONLY KEEP 10 PREVIOUS MODEL if (models.size() > 15) { models.removeFirst(); Log.debug("Garbage old previous model"); } //Changes the current model by the new model if (cc != null) { UUIDModel uuidModel = new UUIDModelImpl(UUID.randomUUID(), cc); model.set(uuidModel); //Fires the update to listeners modelListeners.notifyAllListener(); } } public boolean internal_update_model(ContainerRoot proposedNewModel, String callerPath) { if (proposedNewModel.findNodesByID(getNodeName()) == null) { broadcastTelemetry(TelemetryEvent.Type.LOG_ERROR, "Asking for update with a NULL model or node name (" + getNodeName() + ") was not found in target model !", null); return false; } try { pending = proposedNewModel; ContainerRoot currentModel = model.get().getModel(); Log.trace("Before listeners PreCheck !"); UpdateContext updateContext = new UpdateContext(currentModel, proposedNewModel, callerPath); boolean preCheckResult = modelListeners.preUpdate(updateContext); Log.trace("PreCheck result = " + preCheckResult); Log.trace("Before listeners InitUpdate !"); boolean initUpdateResult = modelListeners.initUpdate(updateContext); Log.debug("InitUpdate result = " + initUpdateResult); if (preCheckResult && initUpdateResult) { // Checks and bootstrap the node checkBootstrapNode(proposedNewModel); currentModel = model.get().getModel(); long milli = System.currentTimeMillis(); if (Log.DEBUG) { Log.debug("Begin update model {}", milli); } boolean deployResult; AdaptationModel adaptationModel = null; try { if (nodeInstance != null) { // Compare the two models and plan the adaptation Log.info("Comparing models and planning adaptation"); broadcastTelemetry(TelemetryEvent.Type.MODEL_COMPARE_AND_PLAN, "Comparing models and planning adaptation", null); adaptationModel = nodeInstance.plan(currentModel, proposedNewModel); //Execution of the adaptation Log.info("Launching adaptation of the system"); broadcastTelemetry(TelemetryEvent.Type.PLATFORM_UPDATE_START, "Launching adaptation of the system", null); updateContext = new UpdateContext(currentModel, proposedNewModel, callerPath); final UpdateContext final_updateContext = updateContext; PrimitiveExecute afterUpdateTest = new PrimitiveExecute() { @Override public boolean exec() { return modelListeners.afterUpdate(final_updateContext); } }; PrimitiveExecute postRollbackTest = new PrimitiveExecute() { @Override public boolean exec() { modelListeners.postRollback(final_updateContext); return true; } }; PreCommand preCmd = new PreCommand(updateContext, modelListeners); ContainerNode rootNode = proposedNewModel.findNodesByID(getNodeName()); deployResult = PrimitiveCommandExecutionHelper.execute(this, rootNode, adaptationModel, nodeInstance, afterUpdateTest, preCmd.preRollbackTest, postRollbackTest); } else { broadcastTelemetry(TelemetryEvent.Type.LOG_ERROR, "Unable to initialize platform node " + getNodeName(), null); Log.error("Unable to initialize platform node " + getNodeName()); deployResult = false; } } catch (Exception e) { broadcastTelemetry(TelemetryEvent.Type.LOG_ERROR, "Error while updating", e); Log.error("Error while updating", e); deployResult = false; } if (deployResult) { switchToNewModel(proposedNewModel); broadcastTelemetry(TelemetryEvent.Type.PLATFORM_UPDATE_SUCCESS, "Update successfully completed", null); Log.info("Update successfully completed"); } else { //KEEP FAIL MODEL, TODO //Log.warn("Update failed") broadcastTelemetry(TelemetryEvent.Type.PLATFORM_UPDATE_FAIL, "Update failed!", null); //IF HARAKIRI if (adaptationModel != null) { } // if (previousHaraKiriModel != null) { // internal_update_model(previousHaraKiriModel, callerPath); // previousHaraKiriModel = null; //CLEAR // } } long milliEnd = System.currentTimeMillis() - milli; Log.info("End deploy result={} ({}ms)", deployResult, milliEnd); pending = null; return deployResult; } else { Log.warn("PreCheck or InitUpdate Step was refused, update aborded !"); return false; } } catch (Throwable e) { broadcastTelemetry(TelemetryEvent.Type.LOG_ERROR, "Error while updating", e); Log.error("Error while update", e); return false; } } private NodeType bootstrapNodeType(ContainerRoot model, String nodeName) { ContainerNode node = model.findNodesByID(nodeName); if (node != null) { FlexyClassLoader kcl = bootstrapService.installTypeDefinition(node); try { NodeType nodeObject = (NodeType) bootstrapService.createInstance(node, kcl); bootstrapService.injectDictionary(node, nodeObject, false); return nodeObject; } catch (ClassCastException e) { Log.error("You are trying to load a NodeType that does not target this runtime version"); } } else { broadcastTelemetry(TelemetryEvent.Type.LOG_ERROR, "Node not found using name " + nodeName, null); Log.error("Unable to find a node named \"" + nodeName + "\" in model"); } return null; } private void checkBootstrapNode(ContainerRoot currentModel) { try { if (nodeInstance == null) { Log.debug("Bootstrapping platform node \"{}\"", getNodeName()); nodeInstance = bootstrapNodeType(currentModel, getNodeName()); if (nodeInstance != null) { resolver = new MethodAnnotationResolver(nodeInstance.getClass()); Method met = resolver.resolve(org.kevoree.annotation.Start.class); if (met != null) { try { met.invoke(nodeInstance); } catch (Throwable ee) { Log.error("Error while invoking platform node @Start method", ee); } } UUIDModelImpl uuidModel = new UUIDModelImpl(UUID.randomUUID(), kevoreeFactory.createContainerRoot()); model.set(uuidModel); } else { broadcastTelemetry(TelemetryEvent.Type.LOG_ERROR, "Unable to initialize platform node " + getNodeName(), null); Log.error("Unable to initialize platform node " + getNodeName()); this.stop(); } } } catch (Throwable e) { broadcastTelemetry(TelemetryEvent.Type.LOG_ERROR, "Error while bootstrapping node instance " + getNodeName(), e); Log.error("Error while bootstrapping node instance " + getNodeName(), e); if (nodeInstance != null) { Method met = resolver.resolve(org.kevoree.annotation.Stop.class); if (met != null) { try { met.invoke(nodeInstance); } catch (Throwable ee) { Log.error("Error while invoking platform node @Stop method" , ee); } } } nodeInstance = null; resolver = null; } } private class PreCommand { private UpdateContext context; private KevoreeListeners modelListeners; private PreCommand(UpdateContext context, KevoreeListeners modelListeners) { this.context = context; this.modelListeners = modelListeners; } boolean alreadyCall = false; PrimitiveExecute preRollbackTest = new PrimitiveExecute() { @Override public boolean exec() { if (!alreadyCall) { modelListeners.preRollback(context); alreadyCall = true; } return true; } }; } public void start() { if (getNodeName() == null || getNodeName().equals("")) { setNodeName("node0"); } modelListeners.start(getNodeName()); broadcastTelemetry(TelemetryEvent.Type.PLATFORM_START, "Kevoree Start event : node name = " + getNodeName(), null); scheduler = Executors.newSingleThreadExecutor(new KevoreeCoreThreadFactory(getNodeName())); UUIDModelImpl uuidModel = new UUIDModelImpl(UUID.randomUUID(), kevoreeFactory.createContainerRoot()); model.set(uuidModel); } public boolean isStarted() { return scheduler != null && !scheduler.isShutdown(); } public void stop() { Log.info("Stopping Kevoree..."); broadcastTelemetry(TelemetryEvent.Type.PLATFORM_STOP, "Stopping Kevoree...", null); modelListeners.stop(); if (scheduler != null) { scheduler.shutdownNow(); scheduler = null; } if (nodeInstance != null) { ContainerRoot modelCurrent = model.get().getModel(); ContainerRoot stopModel = kevoreeFactory.createModelCloner().clone(modelCurrent); ContainerNode currentNode = stopModel.findNodesByID(getNodeName()); if (currentNode != null) { for (ContainerNode childNode : currentNode.getHosts()) { childNode.setStarted(false); } //TEST only stop local for (Group group : stopModel.getGroups()) { group.setStarted(false); } for (Channel hub : stopModel.getHubs()) { hub.setStarted(false); } for (ComponentInstance childComponent : currentNode.getComponents()) { childComponent.setStarted(false); } //val stopModel = factory.createContainerRoot() AdaptationModel adaptationModel = nodeInstance.plan(modelCurrent, stopModel); PrimitiveExecute afterUpdateTest = new PrimitiveExecute() { public boolean exec() { return true; } }; try { PrimitiveCommandExecutionHelper.execute(this, currentNode, adaptationModel, nodeInstance, afterUpdateTest, afterUpdateTest, afterUpdateTest); } catch (Exception e) { Log.error("Error while stopping platform", e); } } else { Log.error("Unable to find node platform \""+getNodeName()+"\" in current model. Cannot correctly stop platform"); } try { Log.trace("Call instance stop"); if (nodeInstance != null) { Method met = resolver.resolve(org.kevoree.annotation.Stop.class); if (met != null) { met.invoke(nodeInstance); } nodeInstance = null; resolver = null; } } catch (Exception e) { broadcastTelemetry(TelemetryEvent.Type.LOG_ERROR, "Error while stopping node instance", e); } } broadcastTelemetry(TelemetryEvent.Type.LOG_INFO, "Kevoree Stopped", null); if (this.onStopHandler != null) { this.onStopHandler.execute(); } } public void onStop(OnStopHandler onStopHandler) { this.onStopHandler = onStopHandler; } public interface OnStopHandler { void execute(); } private class AcquireLock implements Runnable { private LockCallBack callBack; private AcquireLock(LockCallBack callBack, Long timeout) { this.callBack = callBack; this.timeout = timeout; } private Long timeout; public void run() { if (currentLock != null) { try { callBack.run(null, true); } catch (Throwable t) { broadcastTelemetry(TelemetryEvent.Type.LOG_ERROR, "Exception inside a LockCallback with argument", t); //Log.error("Exception inside a LockCallback with argument {}, {}", t, null, true) } } else { UUID lockUUID = UUID.randomUUID(); currentLock = new TupleLockCallBack(lockUUID, callBack); lockWatchDog = java.util.concurrent.Executors.newSingleThreadScheduledExecutor(); futurWatchDog = lockWatchDog.schedule(new WatchDogCallable(), timeout, TimeUnit.MILLISECONDS); try { callBack.run(lockUUID, false); } catch (Throwable t) { broadcastTelemetry(TelemetryEvent.Type.LOG_ERROR, "Exception inside a LockCallback with argument. uuid:" + lockUUID.toString(), t); //Log.error("Exception inside a LockCallback with argument {}, {}", t, lockUUID.toString(), false) } } } } private class WatchDogCallable implements Runnable { public void run() { lockTimeout(); } } public void releaseLock(UUID uuid, String callerPath) { if (uuid != null) { if (scheduler != null) { scheduler.submit(new ReleaseLockCallable(uuid)); } } else { broadcastTelemetry(TelemetryEvent.Type.LOG_ERROR, "ReleaseLock method of Kevoree Core called with null argument, can release any lock", null); } } public void acquireLock(LockCallBack callBack, Long timeout, String callerPath) { scheduler.submit(new AcquireLock(callBack, timeout)); } private class ReleaseLockCallable implements Runnable { private ReleaseLockCallable(UUID uuid) { this.uuid = uuid; } private UUID uuid; public void run() { if (currentLock != null) { if (currentLock.getUuid().compareTo(uuid) == 0) { currentLock = null; futurWatchDog.cancel(true); futurWatchDog = null; lockWatchDog.shutdownNow(); lockWatchDog = null; } } } } private void lockTimeout() { if (scheduler != null) { scheduler.submit(new LockTimeoutCallable()); } } private class LockTimeoutCallable implements Runnable { public void run() { if (currentLock != null) { try { currentLock.getCallback().run(null, true); } catch (Throwable t) { broadcastTelemetry(TelemetryEvent.Type.LOG_ERROR, "Exception inside a LockCallback when it is called from the timeout trigger", t); } currentLock = null; lockWatchDog.shutdownNow(); lockWatchDog = null; futurWatchDog = null; } } } }