/* * Copyright (c) 2013 Technische Universitat Wien (TUW), Distributed Systems Group. http://dsg.tuwien.ac.at * * This work was partially supported by the European Commission in terms of the CELAR FP7 project (FP7-ICT-2011-8 #317790), http://www.celarcloud.eu/ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package at.ac.tuwien.dsg.cloud.salsa.engine.services; import at.ac.tuwien.dsg.cloud.elise.collectorinterfaces.models.CollectorDescription; import at.ac.tuwien.dsg.cloud.elise.collectorinterfaces.models.ConductorDescription; import at.ac.tuwien.dsg.cloud.elise.master.RESTService.EliseManager; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.xml.bind.JAXBException; import org.springframework.stereotype.Service; import at.ac.tuwien.dsg.cloud.salsa.common.cloudservice.model.CloudService; import at.ac.tuwien.dsg.cloud.salsa.common.cloudservice.model.ServiceInstance; import at.ac.tuwien.dsg.cloud.salsa.common.cloudservice.model.ServiceUnit; import at.ac.tuwien.dsg.cloud.salsa.common.cloudservice.model.enums.SalsaEntityType; import at.ac.tuwien.dsg.cloud.salsa.engine.dataprocessing.SalsaXmlDataProcess; import at.ac.tuwien.dsg.cloud.salsa.engine.exceptions.EngineMisconfiguredException; import at.ac.tuwien.dsg.cloud.salsa.common.interfaces.SalsaException; import at.ac.tuwien.dsg.cloud.salsa.engine.services.jsondata.ServiceJsonList; import at.ac.tuwien.dsg.cloud.salsa.engine.services.jsondata.ServiceJsonList.ServiceInfo; import at.ac.tuwien.dsg.cloud.salsa.engine.utils.ActionIDManager; import at.ac.tuwien.dsg.cloud.salsa.engine.utils.EngineLogger; import at.ac.tuwien.dsg.cloud.salsa.engine.utils.PioneerManager; import at.ac.tuwien.dsg.cloud.salsa.engine.utils.SalsaConfiguration; import at.ac.tuwien.dsg.cloud.salsa.engine.utils.SystemFunctions; import at.ac.tuwien.dsg.cloud.salsa.messaging.messageInterface.MessageClientFactory; import at.ac.tuwien.dsg.cloud.salsa.messaging.messageInterface.MessagePublishInterface; import at.ac.tuwien.dsg.cloud.salsa.messaging.protocol.SalsaMessage; import at.ac.tuwien.dsg.cloud.salsa.messaging.protocol.SalsaMessageTopic; import at.ac.tuwien.dsg.cloud.salsa.tosca.extension.SalsaInstanceDescription_VM; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; import java.io.File; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.io.FileUtils; import org.apache.cxf.jaxrs.client.JAXRSClientFactory; import org.slf4j.Logger; @Service @Path("/manager") public class InternalManagement { Logger LOGGER = EngineLogger.logger; /** * Get the list of all pioneers which are engaged with this salsa-engine * * @return The human-readable text of pioneers */ @GET @Path("/pioneers/cache") public String getPioneers() { EngineLogger.logger.debug("Getting pioneer"); return PioneerManager.describe(); } @GET @Path("/pioneer/shutdown/{pioneerID}") public String shutdownPioneer(@PathParam("pioneerID") String pioneerID) { SalsaMessage msg = new SalsaMessage(SalsaMessage.MESSAGE_TYPE.salsa_shutdownPioneer, SalsaConfiguration.getSalsaCenterEndpoint(), SalsaMessageTopic.getPioneerTopicByID(pioneerID), "", ""); MessageClientFactory factory = MessageClientFactory.getFactory(SalsaConfiguration.getBroker(), SalsaConfiguration.getBrokerType()); MessagePublishInterface publish = factory.getMessagePublisher(); publish.pushMessage(msg); return "Sent shutdown message to pioneer: " + pioneerID; } /** * Get the list of pending actions * * @return */ @GET @Path("/actions/cache") public String getActions() { return ActionIDManager.describe(); } /** * Get meta information of salsa * * @return */ @GET @Path("/meta") public String getMetadata() { Map<String, Object> map = new HashMap<>(); map.put("endpoint", SalsaConfiguration.getSalsaCenterEndpoint()); map.put("version", SalsaConfiguration.getSalsaVersion()); map.put("build_time", SalsaConfiguration.getBuildTime()); map.put("broker", SalsaConfiguration.getBroker()); map.put("broker_type", SalsaConfiguration.getBrokerType()); map.put("user", SalsaConfiguration.getUserName()); map.put("pioneer_number", PioneerManager.count()); map.put("pioneer_description", PioneerManager.describeShort()); EliseManager eliseManager = ((EliseManager) JAXRSClientFactory.create(SalsaConfiguration.getSalsaCenterEndpointLocalhost() + "/rest/elise", EliseManager.class, Collections.singletonList(new JacksonJaxbJsonProvider()))); List<ConductorDescription> conductors = eliseManager.getConductorList(); map.put("conductor_number", conductors.size()); if (conductors.size() > 0) { Map<String, String> conductorMap = new HashMap<>(); for (ConductorDescription c : conductors) { String s = ""; for (CollectorDescription cl : c.getCollectors()) { s += cl.getName() + " "; } conductorMap.put(c.getId(), c.getIp() + "," + s.trim()); } map.put("conductor_description", conductorMap); } ServiceJsonList serviceList = new ServiceJsonList(SalsaConfiguration.getServiceStorageDir()); map.put("managed_cloudservices", serviceList.getServicesList().size()); if (serviceList.getServicesList().size() > 0) { Map<String, String> serviceMap = new HashMap<>(); for (ServiceInfo sv : serviceList.getServicesList()) { serviceMap.put(sv.getServiceId(), "deployed:" + sv.getDeployTime()); } map.put("managed_cloudservices_description", serviceMap); } ObjectMapper mapper = new ObjectMapper(); try { return mapper.writer().withDefaultPrettyPrinter().writeValueAsString(map); } catch (IOException ex) { return null; } } @GET @Path("/syn") public String synPioneer() throws SalsaException { PioneerManager.removeAllPioneerInfo(); SalsaMessage msg = new SalsaMessage(SalsaMessage.MESSAGE_TYPE.discover, SalsaConfiguration.getSalsaCenterEndpoint(), SalsaMessageTopic.PIONEER_REGISTER_AND_HEARBEAT, "", "toDiscoverPioneer"); MessagePublishInterface publish = SalsaConfiguration.getMessageClientFactory().getMessagePublisher(); publish.pushMessage(msg); return "Syn message published to the queue."; } /** * @return @throws SalsaException */ @GET @Path("/artifacts/pioneer") @Produces(MediaType.APPLICATION_OCTET_STREAM) public Response getPioneerArtifact() throws SalsaException { LOGGER.debug("Getting pioneer artifact and return: " + SalsaConfiguration.getPioneerLocalFile()); File file = new File(SalsaConfiguration.getPioneerLocalFile()); String fileName = file.getName(); if (file.exists()) { return Response.ok(file, MediaType.APPLICATION_OCTET_STREAM) .header("Content-Disposition", "attachment; filename=\"" + fileName + "\"") .build(); } throw new EngineMisconfiguredException(fileName, "Not found the pioneer.jar artifact: " + SalsaConfiguration.getPioneerLocalFile()); } /** * * @return @throws SalsaException */ @GET @Path("/artifacts/conductor") @Produces(MediaType.APPLICATION_OCTET_STREAM) public Response getConductorArtifact() throws SalsaException { LOGGER.debug("Getting conductor artifact and return: " + SalsaConfiguration.getConductorLocalFile()); File file = new File(SalsaConfiguration.getConductorLocalFile()); String fileName = file.getName(); if (file.exists()) { return Response.ok(file, MediaType.APPLICATION_OCTET_STREAM) .header("Content-Disposition", "attachment; filename=\"" + fileName + "\"") .build(); } throw new EngineMisconfiguredException(fileName, "Not found the conductor.jar artifact at: " + SalsaConfiguration.getConductorLocalFile()); } /** * This return the bootstrap script for the piooner. The script usually contains environment preparation, e.g. install suitable JRE * * @return The content of the script file * @throws SalsaException */ @GET @Path("/artifacts/pioneerbootstrap") @Produces(MediaType.APPLICATION_OCTET_STREAM) public Response getPioneerBootstrapScript() throws SalsaException { LOGGER.debug("Getting conductor artifact and return: " + SalsaConfiguration.getConductorLocalFile()); File file = new File(SalsaConfiguration.getPioneerBootstrapScriptLocalFile()); String fileName = file.getName(); if (file.exists()) { return Response.ok(file, MediaType.APPLICATION_OCTET_STREAM) .header("Content-Disposition", "attachment; filename=\"" + fileName + "\"") .build(); } throw new EngineMisconfiguredException(fileName, "Not found the bootstrap script artifact at: " + SalsaConfiguration.getPioneerBootstrapScript()); } // this should be removed because ELISE is now involved @Deprecated @GET @Path("/services/{serviceId}/topologies/{topologyId}/nodes/{nodeId}/instances/{instanceId}") @Produces(MediaType.APPLICATION_XML) public Response getMonitorOfInstance(@PathParam("serviceId") String serviceId, @PathParam("topologyId") String topologyId, @PathParam("nodeId") String nodeId, @PathParam("instanceId") String instanceId) { try { String serviceFile = SalsaConfiguration.getServiceStorageDir() + "/" + serviceId + ".data"; CloudService service = SalsaXmlDataProcess.readSalsaServiceFile(serviceFile); ServiceUnit node = service.getComponentById(topologyId, nodeId); ServiceInstance instance = service.getInstanceById(topologyId, nodeId, Integer.parseInt(instanceId)); // in case of VM, fetch ganglia information and show up if (node.getType().equals(SalsaEntityType.OPERATING_SYSTEM.getEntityTypeString())) { SalsaInstanceDescription_VM pros = (SalsaInstanceDescription_VM) instance.getProperties().getAny(); return Response.status(200).entity(getVMInformation(pros)).build(); } else { } } catch (IOException e1) { EngineLogger.logger.debug(e1.toString()); return Response.status(500).entity("<Error>Internal error. Cannot read the service file !</Error>").build(); } catch (JAXBException e2) { EngineLogger.logger.debug(e2.toString()); return Response.status(500).entity("<Error>Internal error. Cannot parse the service file !</Error>").build(); } return null; } protected String getVMInformation(SalsaInstanceDescription_VM pros) { String ip = pros.getPrivateIp(); EngineLogger.logger.debug("Querying ganglia information and return: " + ip); //ProcessBuilder pb = new ProcessBuilder("/usr/bin/telnet",ip,"8649"); EngineLogger.logger.debug("Debug ganglia 1"); try { //Process p = pb.start(); Process p = Runtime.getRuntime().exec("/usr/bin/telnet " + ip + " 8649"); EngineLogger.logger.debug("Debug ganglia 2"); p.waitFor(); EngineLogger.logger.debug("Debug ganglia 3"); BufferedReader reader = new BufferedReader( new InputStreamReader(p.getInputStream())); String output = ""; boolean writing = false; String line = reader.readLine(); EngineLogger.logger.debug("Debug ganglia 4"); EngineLogger.logger.debug(line); //line = reader.readLine(); line = reader.readLine(); line = reader.readLine(); // 3 first line of telnet command // we write out the HOST information of GANGLIA_XML while (line != null) { if (line.contains("<HOST") && line.contains("IP=\"" + pros.getPrivateIp() + "\"")) { writing = true; } if (writing) { output += line; } if (writing && line.contains("</HOST>")) { writing = false; } line = reader.readLine(); } EngineLogger.logger.debug(output); return output; } catch (IOException e) { EngineLogger.logger.debug(e.toString()); return "<Error>Error when query monitoring information !</Error>"; } catch (InterruptedException e1) { EngineLogger.logger.debug(e1.toString()); return "Error when execute command to query monitoring information !"; } } //run and manage a single conductor on the main service static Process myConductor; boolean isRunning(Process process) { try { process.exitValue(); return false; } catch (Exception e) { return true; } } /** * Run a conductor. By default just copy all to /tmp and run * * @return */ @GET @Path("/conductor/start") public boolean startConductor() { if (myConductor != null && isRunning(myConductor)) { EngineLogger.logger.debug("Tend to start a conductor, but it is already running... Salsa will do nothing!"); return false; } String fileName = "conductor.jar"; String workingFolderName = "/tmp/conductor/"; String extensionsFolderName = workingFolderName + "extensions/"; (new File(extensionsFolderName)).mkdirs(); File localfile = new File(SalsaConfiguration.getConductorLocalFile()); File configFile = new File(SalsaConfiguration.getConfigurationFile()); File runFile = new File(workingFolderName + fileName); try { FileUtils.copyFile(localfile, runFile); EngineLogger.logger.debug("Copying configuration file from: {} to {}", configFile.getPath(), workingFolderName + "salsa.engine.properties"); FileUtils.copyFile(configFile, new File(workingFolderName + "salsa.engine.properties")); EngineLogger.logger.debug("Starting a conductor ..."); myConductor = SystemFunctions.executeCommandAndForget("java -jar " + fileName, workingFolderName, SalsaConfiguration.getSalsaCenterEndpoint()); } catch (IOException ex) { LOGGER.error("Error when starting conductor: {}", ex.getMessage(), ex); return false; } return true; } }