/** * This file is part of CloudML [ http://cloudml.org ] * * Copyright (C) 2012 - SINTEF ICT * Contact: Franck Chauvel <franck.chauvel@sintef.no> * * Module: root * * CloudML is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * CloudML 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with CloudML. If not, see * <http://www.gnu.org/licenses/>. */ package org.cloudml.facade; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.util.*; import java.util.concurrent.*; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.io.FilenameUtils; import org.cloudml.codecs.DrawnIconVertexDemo; import org.cloudml.codecs.JsonCodec; import org.cloudml.codecs.Vertex; import org.cloudml.codecs.XmiCodec; import org.cloudml.codecs.commons.Codec; import org.cloudml.connectors.Connector; import org.cloudml.connectors.ConnectorFactory; import org.cloudml.core.*; import org.cloudml.deployer.CloudAppDeployer; import org.cloudml.connectors.JCloudsConnector; import org.cloudml.core.credentials.Credentials; import org.cloudml.core.samples.SensApp; import org.cloudml.core.validation.DeploymentValidator; import org.cloudml.core.validation.Report; import org.cloudml.deployer.CloudMLModelComparator; import org.cloudml.deployer.DataMigration; import org.cloudml.facade.commands.*; import org.cloudml.facade.events.*; import org.cloudml.facade.events.Message.Category; import org.cloudml.indicators.Robustness; import org.cloudml.mrt.Coordinator; import org.cloudml.mrt.SimpleModelRepo; import org.jclouds.compute.domain.ComputeMetadata; /** * This class implements an easier access to typical CloudML features, such as * deploying an application described in a JSON file for instance. * * It holds a list of clients and may send events to them. The facade * communicate with its clients using events. */ class Facade implements CloudML, CommandHandler { private static final Logger journal = Logger.getLogger(Facade.class.getName()); private final List<EventHandler> handlers = Collections.synchronizedList(new ArrayList<EventHandler>()); private final ExecutorService executor = Executors.newSingleThreadExecutor(); private Deployment deploy; private boolean stopOnTimeout = false; private final CloudAppDeployer deployer; private CloudMLModelComparator diff = null; public Coordinator getCoordinator() { return coordinator; } private Coordinator coordinator; private static final String JSON_STRING_PREFIX = "json-string:"; public Facade() { XmiCodec.init(); JsonCodec.init(); this.deployer = new CloudAppDeployer(); } @Override public void terminate() { executor.shutdown(); try { executor.awaitTermination(2, TimeUnit.SECONDS); } catch (InterruptedException ex) { Logger.getLogger(Facade.class.getName()).log(Level.SEVERE, null, ex); } finally { executor.shutdownNow(); } } @Override public Execution fireAndForget(CloudMlCommand command) { final Execution execution = new Execution(command, this); executor.submit(execution); return execution; } @Override public Execution fireAndWait(CloudMlCommand command) { final Execution execution = new Execution(command, this); final Future<Boolean> done = executor.submit(execution, true); try { if (execution.getTimeout() > -1) { dispatch(new Message(command, Category.INFORMATION, "Will wait (max) for " + execution.getTimeout() + " ms...")); done.get(execution.getTimeout(), TimeUnit.MILLISECONDS); dispatch(new Message(command, Category.INFORMATION, "OK!")); } else { dispatch(new Message(command, Category.INFORMATION, "Will wait until completion")); done.get(); } } catch (InterruptedException ex) { Logger.getLogger(Facade.class.getName()).log(Level.SEVERE, null, ex); } catch (ExecutionException ex) { Logger.getLogger(Facade.class.getName()).log(Level.SEVERE, null, ex); } catch (TimeoutException ex) { //Logger.getLogger(Facade.class.getName()).log(Level.SEVERE, null, ex); if (stopOnTimeout) { terminate(); final String text = "Sequence interrupted because of timeout on last command: " + command; final Message failure = new Message(command, Category.ERROR, text); dispatch(failure); } else { final String text = "Timeout on command: " + command; final Message message = new Message(command, Category.INFORMATION, text); dispatch(message); } } return execution; } @Override public void register(EventHandler handler) { this.handlers.add(handler); } /** * Dispatch the given event to those which have registered a handler for * this type of event * * @param event the event to dispatch */ public void dispatch(Event event) { for (EventHandler handler: handlers) { event.accept(handler); } } ////////LEGACY CODE TO BE MIGRATED INTO NEW COMMANDS///////////////////// /** * Create the VM described by the given VMInstance object * * @param a */ public void createVM(VMInstance a) { Provider provider = a.getType().getProvider(); JCloudsConnector jc = new JCloudsConnector(provider.getName(), provider.getCredentials().getLogin(), provider.getCredentials().getPassword()); jc.createInstance(a); jc.closeConnection(); } /** * Execute a given command on an component * * @param a the component on which the command will be executed * @param command the related shell command as a String * @param user the user associated */ public void executeOnVM(ComponentInstance a, String command, String user) {//TODO: use the connector factory VM ownerVM = (VM) deployer.getDestination(a).getType();//TODO: generics Provider provider = ownerVM.getProvider(); final Credentials credentials = provider.getCredentials(); JCloudsConnector jc = new JCloudsConnector(provider.getName(), credentials.getLogin(), credentials.getPassword()); jc.execCommand(ownerVM.getGroupName(), command, user, ownerVM.getPrivateKey()); jc.closeConnection(); } public ComputeMetadata findVMByName(String name, Provider p) {//TODO: use the connector factory JCloudsConnector jc = new JCloudsConnector(p.getName(), p.getCredentials().getLogin(), p.getCredentials().getPassword()); ComputeMetadata cm = jc.getVMByName(name); jc.closeConnection(); return cm; } public Set<? extends ComputeMetadata> listOfVMs(Provider p) {//TODO: use the connector factory JCloudsConnector jc = new JCloudsConnector(p.getName(), p.getCredentials().getLogin(), p.getCredentials().getPassword()); Set<? extends ComputeMetadata> list = jc.listOfVMs(); jc.closeConnection(); return list; } ////////END OF LEGACY CODE TO BE MIGRATED INTO NEW COMMANDS///////////////////// @Override public Deployment getDeploymentModel() { return deploy; } /* * COMMAND HANDLERS */ @Override public void handle(AnalyseRobustness command) { if (isDeploymentLoaded()) { robustness(command); robustnessWithSelfRepair(command); } else { reportNoDeploymentLoaded(command); } } private void robustnessWithSelfRepair(AnalyseRobustness command) { final Robustness robustness = Robustness.ofSelfRepairing(deploy, command.getToObserve(), command.getToControl()); final String content = String.format("Robustness (with self-repair): %.2f percent", robustness.value()); dispatch(new Message(command, Category.INFORMATION, content)); } private void robustness(AnalyseRobustness command) { final Robustness robustness = Robustness.of(deploy, command.getToObserve(), command.getToControl()); final String content = String.format("Robustness: %.2f percent", robustness.value()); dispatch(new Message(command, Category.INFORMATION, content)); } @Override public void handle(StartComponent command) { ArrayList<Thread> ts=new ArrayList<Thread>(); if (isDeploymentLoaded()) { dispatch(new Message(command, Category.INFORMATION, "Starting VM: " + command.getComponentId())); for(int i = 0; i <= command.getComponentId().size();i++) { final String id=command.getComponentId().get(i); ts.add(new Thread(){ public void run() { VMInstance vmi = deploy.getComponentInstances().onlyVMs().withID(id); if (vmi != null) { Provider provider = vmi.getType().getProvider(); Connector c = ConnectorFactory.createIaaSConnector(provider); c.startVM(vmi); coordinator.updateStatus(vmi.getName(), ComponentInstance.State.RUNNING, Facade.class.getName()); for(InternalComponentInstance ici : vmi.hostedComponents()){ InternalComponent ic=ici.getType(); for(Resource r : ic.getResources()){ c.execCommand(vmi.getId(),r.getStartCommand(),"ubuntu",vmi.getType().getPrivateKey()); } } c.closeConnection(); } }}); ts.get(i).start(); } for(Thread t: ts){ try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * Report to the user that the given command is not yet supported. * * @param command the command which is not yet supported! */ private void reportCommandNotYetSupported(CloudMlCommand command) { dispatch(new Message(command, Category.ERROR, "Not yet implemented")); } /** * Report to the client that the given command cannot be performed because * there is not yet any deployment model loaded. * * @param command the command that triggered this message */ private void reportNoDeploymentLoaded(CloudMlCommand command) { final String text = "No deployment model. Please first load a deployment model"; final Message message = new Message(command, Category.ERROR, text); dispatch(message); } @Override public void handle(StopComponent command) { ArrayList<Thread> ts = new ArrayList<Thread>(); if (isDeploymentLoaded()) { dispatch(new Message(command, Category.INFORMATION, "Stopping VM: " + command.getComponentId())); for (int i = 0; i <= command.getComponentId().size(); i++) { final String id = command.getComponentId().get(i); ts.add(new Thread() { public void run() { VMInstance vmi = deploy.getComponentInstances().onlyVMs().withID(id); if (vmi != null) { Provider provider = vmi.getType().getProvider(); Connector c = ConnectorFactory.createIaaSConnector(provider); for(InternalComponentInstance ici : vmi.hostedComponents()){ InternalComponent ic=ici.getType(); for(Resource r : ic.getResources()){ c.execCommand(vmi.getId(),r.getStopCommand(),"ubuntu",vmi.getType().getPrivateKey()); } } c.stopVM(vmi); coordinator.updateStatus(vmi.getName(), ComponentInstance.State.STOPPED, Facade.class.getName()); c.closeConnection(); } } }); ts.get(i).start(); } for (Thread t : ts) { try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } @Override public void handle(Attach command) { if (isDeploymentLoaded()) { dispatch(new Message(command, Category.INFORMATION, "Relationship created!")); } else { reportNoDeploymentLoaded(command); } } @Override public void handle(Detach command) { reportCommandNotYetSupported(command); } @Override public void handle(Install command) { reportCommandNotYetSupported(command); } @Override public void handle(Uninstall command) { reportCommandNotYetSupported(command); } @Override public void handle(Instantiate command) { reportCommandNotYetSupported(command); } @Override public void handle(Destroy command) { if (isDeploymentLoaded()) { final VMInstance instance = deploy.getComponentInstances().onlyVMs().firstNamed(command.getInstanceId()); if (instance == null) { final String text = String.format("No VM with ID=\"%s\"", command.getInstanceId()); final Message message = new Message(command, Category.ERROR, text); dispatch(message); } else { Provider p = instance.getType().getProvider(); JCloudsConnector jc = new JCloudsConnector(p.getName(), p.getCredentials().getLogin(), p.getCredentials().getPassword()); jc.destroyVM(instance.getId()); dispatch(new Message(command, Category.INFORMATION, "VM instance terminated")); } } else { reportNoDeploymentLoaded(command); } } @Override public void handle(Upload command) { if (isDeploymentLoaded()) { ExternalComponentInstance ownerVM = null; for (ExternalComponentInstance ni: deploy.getComponentInstances().onlyExternals()) { if (ni.getName().equals(command.getArtifactId())) { ownerVM = ni; } } if (ownerVM != null && ownerVM instanceof VMInstance) { Provider p = ((VMInstance) ownerVM).getType().getProvider(); JCloudsConnector jc = new JCloudsConnector(p.getName(), p.getCredentials().getLogin(), p.getCredentials().getPassword()); ComputeMetadata c = jc.getVMByName(command.getArtifactId()); jc.uploadFile(command.getLocalPath(), command.getRemotePath(), c.getId(), "ubuntu", ((VM) ownerVM.getType()).getPrivateKey()); } else { final String text = "There is no VM with this ID!"; final Message message = new Message(command, Category.ERROR, text); dispatch(message); } } else { reportNoDeploymentLoaded(command); } } private void saveMetadata(Deployment deploy2) { if (isDeploymentLoaded()) { diff = new CloudMLModelComparator(deploy, deploy2); diff.compareCloudMLModel(); deploy.setName(deploy2.getName()); deploy.getComponents().addAll(deploy2.getComponents()); deploy.getRelationships().addAll(deploy2.getRelationships()); deploy.getRelationshipInstances().removeAll(diff.getRemovedRelationships()); deploy.getExecuteInstances().removeAll(diff.getRemovedExecutes()); deploy.getComponentInstances().removeAll(diff.getRemovedECs().keySet()); deploy.getComponentInstances().removeAll(diff.getRemovedComponents()); deploy.getRelationshipInstances().replaceAll(diff.getAddedRelationships()); deploy.getComponentInstances().replaceAll(diff.getAddedECs()); deploy.getExecuteInstances().replaceAll(diff.getAddedExecutes()); deploy.getComponentInstances().replaceAll(diff.getAddedComponents()); } else { deploy = deploy2; } } public Deployment merge(String path){ InputStream instream = null; try { instream = new FileInputStream(path); } catch (FileNotFoundException e) { e.printStackTrace(); } Deployment target = (Deployment) new JsonCodec().load(instream); if (isDeploymentLoaded()) { deploy.getComponents().replaceAll(target.getComponents()); deploy.getRelationships().replaceAll(target.getRelationships()); //TODO: Check if names are unique deploy.getComponentInstances().replaceAll(target.getComponentInstances()); deploy.getExecuteInstances().replaceAll(target.getExecuteInstances()); deploy.getProviders().replaceAll(target.getProviders()); deploy.getRelationshipInstances().replaceAll(target.getRelationshipInstances()); }else{ deploy=target; } initCoordinator(); return deploy; } @Override public void handle(LoadDeployment command) { String path = command.getPathToModel(); if ("sample://sensapp".equals(path.toLowerCase())) { deploy = SensApp.completeSensApp().build(); final Message message = new Message(command, Category.INFORMATION, "Loading Complete."); dispatch(message); }else if (path.trim().startsWith(JSON_STRING_PREFIX)) { String content = path.trim().substring(JSON_STRING_PREFIX.length()).trim(); InputStream instream = new ByteArrayInputStream(content.getBytes()); Deployment deploy2 = (Deployment) new JsonCodec().load(instream); saveMetadata(deploy2); final Message message = new Message(command, Category.INFORMATION, "Loading Complete."); dispatch(message); } else { final String extension = canHandle(command.getPathToModel()); if (extension != null) { final File f = new File(command.getPathToModel()); try { deploy = (Deployment) new JsonCodec().load(new FileInputStream(f)); final Message message = new Message(command, Category.INFORMATION, "Loading Complete."); dispatch(message); } catch (FileNotFoundException ex) { final Message message = new Message(command, Category.ERROR, "Unable to find file: " + command.getPathToModel()); dispatch(message); } catch (Exception e) { final Message message = new Message(command, Category.ERROR, "Error while loading model: " + e.getLocalizedMessage()); dispatch(message); } } else { wrongFileFormat(command.getPathToModel(), command); } } initCoordinator(); } private void initCoordinator(){ if (coordinator == null) { if(Coordinator.SINGLE_INSTANCE == null){ //only if there is no WebSocket server running. coordinator = new Coordinator(); SimpleModelRepo modelRepo = new SimpleModelRepo(deploy); coordinator.setModelRepo(modelRepo); coordinator.start(); } else coordinator = Coordinator.SINGLE_INSTANCE; } deployer.setCoordinator(coordinator); } @Override public void handle(StoreDeployment command) { final String extension = canHandle(command.getDestination()); if (extension != null) { final File f = new File(command.getDestination()); try { getCodec(extension).save(deploy, new FileOutputStream(f)); final Message message = new Message(command, Category.INFORMATION, "Serialisation Complete."); dispatch(message); } catch (FileNotFoundException ex) { final Message message = new Message(command, Category.ERROR, "Cannot save model to specified destination."); dispatch(message); } catch (Exception e) { final Message message = new Message(command, Category.ERROR, "Error while saving model: " + e.getLocalizedMessage()); dispatch(message); } } else { wrongFileFormat(command.getDestination(), command); } } @Override public void handle(Deploy command) { if (isDeploymentLoaded()) { if (diff != null) { deployer.deploy(deploy, diff); } else { deployer.deploy(deploy); } final Message success = new Message(command, Category.INFORMATION, "Deployment Complete."); dispatch(success); } else { reportNoDeploymentLoaded(command); } } public void handle(DebugMode command){ final Message success; if(command.getDebug()) { deployer.activeDebug(); success = new Message(command, Category.INFORMATION, "Debug mode activated."); }else{ deployer.stopDebug(); success = new Message(command, Category.INFORMATION, "Debug mode stopped."); } dispatch(success); } @Override public void handle(Burst command){ dispatch(new Message(command, Category.INFORMATION, "Bursting out External Component: " + command.getEcId()+" to "+ command.getProviderID())); VMInstance vmi = deploy.getComponentInstances().onlyVMs().withID(command.getEcId()); Provider p=deploy.getProviders().firstNamed(command.getProviderID()); if(p == null){ dispatch(new Message(command, Category.ERROR, "Cannot find a Provider with this ID!")); return; } if (vmi == null) { ExternalComponentInstance eci=deploy.getComponentInstances().onlyExternals().firstNamed(command.getEcId()); if(eci == null){ dispatch(new Message(command, Category.ERROR, "Cannot find a External component with this ID!")); }else{ deployer.scaleOut(eci,p); } } else { deployer.scaleOut(vmi,p); } } @Override public void handle(ListComponents command) { if (isDeploymentLoaded()) { final ComponentList data = new ComponentList(command, deploy.getComponents()); dispatch(new Message(command, Category.INFORMATION, data.toString())); dispatch(data); } else { reportNoDeploymentLoaded(command); } } @Override public void handle(ListComponentInstances command) { if (isDeploymentLoaded()) { final Collection<ComponentInstance<? extends Component>> instances = deploy.getComponentInstances().toList(); final ComponentInstanceList data = new ComponentInstanceList(command, instances); dispatch(new Message(command, Category.INFORMATION, data.toString())); dispatch(data); } else { reportNoDeploymentLoaded(command); } } @Override public void handle(ViewComponent command) { if (isDeploymentLoaded()) { final Component type = deploy.getComponents().firstNamed(command.getComponentId()); if (type == null) { final String text = String.format("No artefact type with ID \"%s\"", command.getComponentId()); final Message message = new Message(command, Category.ERROR, text); dispatch(message); } else { final ComponentData data = new ComponentData(command, type); dispatch(data); } } else { reportNoDeploymentLoaded(command); } } @Override public void handle(ViewComponentInstance command) { if (isDeploymentLoaded()) { final ComponentInstance instance = deploy.getComponentInstances().onlyInternals().firstNamed(command.getComponentId()); if (instance == null) { final String text = String.format("No artefact instance with ID \"%s\"", command.getComponentId()); final Message message = new Message(command, Category.ERROR, text); dispatch(message); } else { final ComponentInstanceData data = new ComponentInstanceData(command, instance); dispatch(data); } } else { reportNoDeploymentLoaded(command); } } @Override public void handle(LoadCredentials command) { reportCommandNotYetSupported(command); } @Override public void handle(StoreCredentials command) { reportCommandNotYetSupported(command); } @Override public void handle(ShotImage command) { if (isDeploymentLoaded()) { dispatch(new Message(command, Category.INFORMATION, "Generating picture ...")); DrawnIconVertexDemo g = new DrawnIconVertexDemo(deploy); ArrayList<Vertex> v = g.drawFromDeploymentModel(); File f = new File(command.getPathToSnapshot()); g.writeServerJPEGImage(f); } else { reportNoDeploymentLoaded(command); } } @Override public void handle(Snapshot command) { if (isDeploymentLoaded()) { dispatch(new Message(command, Category.INFORMATION, "Generating snapshot ...")); VMInstance vmi = deploy.getComponentInstances().onlyVMs().withID(command.getVmId()); Connector c = ConnectorFactory.createIaaSConnector(vmi.getType().getProvider()); c.createSnapshot(vmi); } else { reportNoDeploymentLoaded(command); } } @Override public void handle(Image command) { if (isDeploymentLoaded()) { dispatch(new Message(command, Category.INFORMATION, "Generating an image ...")); VMInstance vmi = deploy.getComponentInstances().onlyVMs().withID(command.getVmId()); Connector c = ConnectorFactory.createIaaSConnector(vmi.getType().getProvider()); c.createImage(vmi); } else { reportNoDeploymentLoaded(command); } } @Override public void handle(Reset command) { dispatch(new Message(command, Category.INFORMATION, "The deployment engine has been reset ...")); this.deploy = null; this.deployer.reset(); } @Override public void handle(offlineMigration command) { dispatch(new Message(command, Category.INFORMATION, "Start offline data migration...")); DataMigration dm= new DataMigration(); dm.switchOver(command.getSource(),command.getDestination(),command.getNbThread()); } @Override public void handle(onlineMigration command) { dispatch(new Message(command, Category.INFORMATION, "Start online data migration...")); DataMigration dm= new DataMigration(); dm.switchOverPartitioned(command.getSource(),command.getDestination(),command.getNbThread(), command.getVdpSize()); } @Override public void handle(GetDeployment command) { } @Override public void handle(ValidateCommand command) { assert command != null: "Unable to handle a 'null' validate command!"; if (isDeploymentLoaded()) { final Report validation = new DeploymentValidator().validate(deploy); for (org.cloudml.core.validation.Message eachError: validation.getErrors()) { final Message error = new Message(command, Category.ERROR, eachError.toString()); dispatch(error); } if (command.mustReportWarnings()) { for (org.cloudml.core.validation.Message eachWarning: validation.getWarnings()) { final Message warning = new Message(command, Category.WARNING, eachWarning.toString()); dispatch(warning); } } final String summaryText = String.format("%d error(s) ; %d warning(s).", validation.getErrors().size(), validation.getWarnings().size()); final Message summary = new Message(command, Category.INFORMATION, summaryText); dispatch(summary); } else { reportNoDeploymentLoaded(command); } } /** * @return if a deployment model has been loaded, and is available for * further commands, false otherwise. */ private boolean isDeploymentLoaded() { return deploy != null; } @Override public void handle(ScaleOut command) { dispatch(new Message(command, Category.INFORMATION, "Scaling out VM: " + command.getVmId())); VMInstance vmi = deploy.getComponentInstances().onlyVMs().withID(command.getVmId()); if (vmi == null) { dispatch(new Message(command, Category.ERROR, "Cannot find a VM with this ID!")); } else { Boolean success=true; if(command.getNb() > 1) { success=deployer.scaleOut(vmi, command.getNb()); } else { success=deployer.scaleOut(vmi); } /*if(!success){ if (coordinator != null) { coordinator.ack("MaxVMsReached", command.getClass().getName()); } }*/ } } /** * * @param pathName * @return null if we cannot manage this type of files. Returns the * extension if we can. */ private String canHandle(String pathName) { final String extension = FilenameUtils.getExtension(pathName); if (Codec.extensions.keySet().contains(extension)) { return extension; } else { return null; } } /** * Formats and emits a proper error message if the model located at * 'pathName' cannot be handled by 'command' (i.e. if canHandle(pathName) == * null) * * @param pathName * @param command */ private void wrongFileFormat(String pathName, CloudMlCommand command) { final StringBuilder ext = new StringBuilder(); int i = 0; for (String e: Codec.extensions.keySet()) { if (i > 0) { ext.append(", "); } ext.append(e); i++; } final String text = "Cannot handle this type of file (" + FilenameUtils.getExtension(pathName) + ").\nPlease provide file with an extension CloudML codecs can manage (" + ext.toString() + ")"; final Message message = new Message(command, Category.ERROR, text); dispatch(message); } /** * * @param extension * @return the codec able to manage the type of files specified by extension */ private Codec getCodec(String extension) { return Codec.extensions.get(extension); } }