/* * 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.pioneer; import at.ac.tuwien.dsg.cloud.salsa.domainmodels.types.SalsaArtifactType; import static at.ac.tuwien.dsg.cloud.salsa.domainmodels.types.SalsaArtifactType.apt; import static at.ac.tuwien.dsg.cloud.salsa.domainmodels.types.SalsaArtifactType.chef; import static at.ac.tuwien.dsg.cloud.salsa.domainmodels.types.SalsaArtifactType.chefSolo; import static at.ac.tuwien.dsg.cloud.salsa.domainmodels.types.SalsaArtifactType.dockerfile; import static at.ac.tuwien.dsg.cloud.salsa.domainmodels.types.SalsaArtifactType.sh; import static at.ac.tuwien.dsg.cloud.salsa.domainmodels.types.SalsaArtifactType.shcont; import static at.ac.tuwien.dsg.cloud.salsa.domainmodels.types.SalsaArtifactType.war; import at.ac.tuwien.dsg.cloud.salsa.messaging.messageInterface.MessagePublishInterface; import at.ac.tuwien.dsg.cloud.salsa.messaging.messageInterface.MessageSubscribeInterface; import at.ac.tuwien.dsg.cloud.salsa.domainmodels.types.ServiceCategory; import at.ac.tuwien.dsg.cloud.salsa.messaging.messageInterface.MessageClientFactory; import at.ac.tuwien.dsg.cloud.salsa.messaging.messageInterface.SalsaMessageHandling; import at.ac.tuwien.dsg.cloud.salsa.messaging.model.Salsa.SalsaMsgConfigureArtifact; import at.ac.tuwien.dsg.cloud.salsa.messaging.model.Salsa.SalsaMsgConfigureState; import at.ac.tuwien.dsg.cloud.salsa.messaging.model.Salsa.SalsaMsgUpdateMetadata; 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.pioneer.elise.EliseConductorManager; import at.ac.tuwien.dsg.cloud.salsa.pioneer.instruments.AptGetInstrument; import at.ac.tuwien.dsg.cloud.salsa.pioneer.instruments.ArtifactConfigurationInterface; import at.ac.tuwien.dsg.cloud.salsa.pioneer.instruments.BashContinuousInstrument; import at.ac.tuwien.dsg.cloud.salsa.pioneer.instruments.BashContinuousManagement; import at.ac.tuwien.dsg.cloud.salsa.pioneer.instruments.BashInstrument; import at.ac.tuwien.dsg.cloud.salsa.pioneer.instruments.BinaryExecutionInstrument; import at.ac.tuwien.dsg.cloud.salsa.pioneer.instruments.ChefSoloInstrument; import at.ac.tuwien.dsg.cloud.salsa.pioneer.instruments.DockerConfigurator; import at.ac.tuwien.dsg.cloud.salsa.pioneer.instruments.WarInstrument; import at.ac.tuwien.dsg.cloud.salsa.pioneer.utils.PioneerConfiguration; import at.ac.tuwien.dsg.cloud.salsa.pioneer.utils.SystemFunctions; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Queue; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.slf4j.Logger; /** * * @author Duc-Hung Le */ public class Main { static Logger logger = PioneerConfiguration.logger; static Queue<SalsaMsgConfigureArtifact> taskQueue = new LinkedList<>(); static final MessageClientFactory factory = new MessageClientFactory(PioneerConfiguration.getBroker(), PioneerConfiguration.getBrokerType()); public static void main(String[] args) { logger.debug("Starting pioneer ..."); String command = "startserver"; if (args.length > 0) { command = args[0]; } MessageSubscribeInterface subscribeSalsaEngineRequests = factory.getMessageSubscriber(new SalsaMessageHandling() { @Override public void handleMessage(SalsaMessage msg) { switch (msg.getMsgType()) { case salsa_deploy: { SalsaMsgConfigureArtifact confInfo = SalsaMsgConfigureArtifact.fromJson(msg.getPayload()); if (!confInfo.getPioneerID().equals(PioneerConfiguration.getPioneerID())) { logger.debug("Received a message but not for me, it is for pioneer: " + confInfo.getPioneerID()); break; } taskQueue.add(confInfo); logger.debug("Received message for DEPLOYMENT: " + msg.toJson() + ", put in queue. QueueSize: " + taskQueue.size()); break; } case salsa_reconfigure: { SalsaMsgConfigureArtifact cmd = SalsaMsgConfigureArtifact.fromJson(msg.getPayload()); logger.debug("Received a reconfiguration command for: " + cmd.getUser() + "/" + cmd.getService() + "/" + cmd.getUnit() + "/" + cmd.getInstance() + ". ActionName: " + cmd.getActionName() + ", ActionID: " + cmd.getActionID()); // send back message that the pioneer received the command SalsaMsgConfigureState confState = new SalsaMsgConfigureState(cmd.getActionID(), SalsaMsgConfigureState.CONFIGURATION_STATE.PROCESSING, 0, "Pioneer received the request and is processing. Action ID: " + cmd.getActionID()); SalsaMessage notifyMsg = new SalsaMessage(SalsaMessage.MESSAGE_TYPE.salsa_messageReceived, PioneerConfiguration.getPioneerID(), SalsaMessageTopic.PIONEER_UPDATE_CONFIGURATION_STATE, "", confState.toJson()); MessagePublishInterface publish = factory.getMessagePublisher(); publish.pushMessage(notifyMsg); // now reconfigure: only support script now //int returnCode = SystemFunctions.executeCommandGetReturnCode(cmd.getRunByMe(), PioneerConfiguration.getWorkingDirOfInstance(cmd.getUnit(), cmd.getInstance()), PioneerConfiguration.getPioneerID()); int returnCode = executeLifecycleAction(cmd); // And update the configuration result SalsaMsgConfigureState confResult; if (returnCode == 0) { confResult = new SalsaMsgConfigureState(cmd.getActionID(), SalsaMsgConfigureState.CONFIGURATION_STATE.SUCCESSFUL, 0, "Action is successful: " + cmd.getRunByMe()); } else { confResult = new SalsaMsgConfigureState(cmd.getActionID(), SalsaMsgConfigureState.CONFIGURATION_STATE.SUCCESSFUL, 1, "Action is failed: " + cmd.getRunByMe() + ". Return code: " + returnCode); } MessagePublishInterface publish_conf = factory.getMessagePublisher(); SalsaMessage reply = new SalsaMessage(SalsaMessage.MESSAGE_TYPE.salsa_configurationStateUpdate, PioneerConfiguration.getPioneerID(), SalsaMessageTopic.PIONEER_UPDATE_CONFIGURATION_STATE, null, confResult.toJson()); publish_conf.pushMessage(reply); logger.debug("Result is published !"); break; } case salsa_shutdownPioneer: { logger.debug("Received a message to shutdown this pioneer. Bye!"); System.exit(0); } case salsa_configurationStateUpdate: { break; } case salsa_messageReceived: { break; } default: { logger.error("Message type is not support !" + msg.getMsgType()); break; } } } }); if (subscribeSalsaEngineRequests == null) { logger.error("Cannot subscribe to the message queue: {}, type: {}. Pioneer QUIT !", PioneerConfiguration.getBroker(), PioneerConfiguration.getBrokerType()); return; } subscribeSalsaEngineRequests.subscribe(SalsaMessageTopic.getPioneerTopicByID(PioneerConfiguration.getPioneerID())); MessageSubscribeInterface subscribeSynChannel = factory.getMessageSubscriber(new SalsaMessageHandling() { @Override public void handleMessage(SalsaMessage salsaMessage) { // send information of this pioneer when get Discover command if (salsaMessage.getMsgType().equals(SalsaMessage.MESSAGE_TYPE.discover)) { MessagePublishInterface publish = factory.getMessagePublisher(); publish.pushMessage(new SalsaMessage(SalsaMessage.MESSAGE_TYPE.salsa_pioneerActivated, PioneerConfiguration.getPioneerID(), SalsaMessageTopic.PIONEER_REGISTER_AND_HEARBEAT, null, PioneerConfiguration.getPioneerInfo().toJson())); } } }); subscribeSynChannel.subscribe(SalsaMessageTopic.PIONEER_REGISTER_AND_HEARBEAT); // send information of this pioneer MessagePublishInterface publish = factory.getMessagePublisher(); publish.pushMessage(new SalsaMessage(SalsaMessage.MESSAGE_TYPE.salsa_pioneerActivated, PioneerConfiguration.getPioneerID(), SalsaMessageTopic.PIONEER_REGISTER_AND_HEARBEAT, null, PioneerConfiguration.getPioneerInfo().toJson())); new Thread(new TaskHandleThread()).start(); // also start an elise conductor //new Thread(new EliseConductorThread()).start(); } private static class TaskHandleThread implements Runnable { @Override public void run() { int count = 0; while (true) { SalsaMsgConfigureArtifact confInfo = taskQueue.poll(); if (confInfo != null) { logger.debug("FULLED A TASK FROM TASKQUEUE. The queue how have: " + taskQueue.size()); handleConfigurationTask(confInfo); count = 0; } else { try { if (count <= 2) { count += 1; logger.debug("TASKQUEUE is empty (show {}/3).", count); } Thread.sleep(2000); } catch (InterruptedException ex) { logger.error("Task handling thread is interrupted, pioneer is no longer to process request!"); ex.printStackTrace(); } } } } } private static void handleConfigurationTask(SalsaMsgConfigureArtifact confInfo) { // feedback that Pioneer is PROCESSING the request logger.debug("Start to handle the task: {} of {}/{}", confInfo.getActionName(), confInfo.getUnit(), confInfo.getInstance()); SalsaMsgConfigureState confState = new SalsaMsgConfigureState(confInfo.getActionID(), SalsaMsgConfigureState.CONFIGURATION_STATE.PROCESSING, 0, "Pioneer received the request and is processing. Action ID: " + confInfo.getActionID()); SalsaMessage notifyMsg = new SalsaMessage(SalsaMessage.MESSAGE_TYPE.salsa_messageReceived, PioneerConfiguration.getPioneerID(), SalsaMessageTopic.PIONEER_UPDATE_CONFIGURATION_STATE, "", confState.toJson()); logger.debug("Start to publish notification message ..."); MessagePublishInterface publish_deploy = factory.getMessagePublisher(); publish_deploy.pushMessage(notifyMsg); logger.debug("The command is attracted: Target to pioneer {}, and mine is {}!", confInfo.getPioneerID(), PioneerConfiguration.getPioneerID()); if (confInfo.getPioneerID().equals(PioneerConfiguration.getPioneerID())) { SystemFunctions.writeSystemVariable(confInfo.getEnvironment()); logger.debug("Executing the first deployment ! ConfInfo: " + confInfo.toJson()); if (confInfo.getActionName().equals(CommonLifecycle.DEPLOY)) { logger.debug("Yes, the action name is: deploy"); SalsaMsgConfigureState downloadResult = downloadArtifact(confInfo); if (downloadResult.getState().equals(SalsaMsgConfigureState.CONFIGURATION_STATE.ERROR)) { MessagePublishInterface publish_conf = factory.getMessagePublisher(); SalsaMessage reply = new SalsaMessage(SalsaMessage.MESSAGE_TYPE.salsa_configurationStateUpdate, PioneerConfiguration.getPioneerID(), SalsaMessageTopic.PIONEER_UPDATE_CONFIGURATION_STATE, null, downloadResult.toJson()); publish_conf.pushMessage(reply); logger.error("Artifact download failed for unit: {}/{}/{}. The result is sent back to center." + confInfo.getService(), confInfo.getUnit(), confInfo.getInstance()); } else { logger.debug("Finished download artifacts !"); ArtifactConfigurationInterface confModule = selectConfigurationModule(confInfo); logger.debug("Select module done !"); if (confModule != null) { SalsaMsgConfigureState confResult = confModule.configureArtifact(confInfo); logger.debug("Configuration done ! Result: " + confResult.getState() + ", Info: " + confResult.getDomainID()); MessagePublishInterface publish_conf = factory.getMessagePublisher(); SalsaMessage reply = new SalsaMessage(SalsaMessage.MESSAGE_TYPE.salsa_configurationStateUpdate, PioneerConfiguration.getPioneerID(), SalsaMessageTopic.PIONEER_UPDATE_CONFIGURATION_STATE, null, confResult.toJson()); publish_conf.pushMessage(reply); logger.debug("Result is published !"); } else { SalsaMsgConfigureState confResult = new SalsaMsgConfigureState(confInfo.getActionID(), SalsaMsgConfigureState.CONFIGURATION_STATE.ERROR, 101, "Cannot find configuration module to execute action!"); MessagePublishInterface publish_conf = factory.getMessagePublisher(); SalsaMessage reply = new SalsaMessage(SalsaMessage.MESSAGE_TYPE.salsa_configurationStateUpdate, PioneerConfiguration.getPioneerID(), SalsaMessageTopic.PIONEER_UPDATE_CONFIGURATION_STATE, null, confResult.toJson()); publish_conf.pushMessage(reply); logger.debug("Result is published !"); } } } } } private static class EliseConductorThread implements Runnable { @Override public void run() { EliseConductorManager.runConductor(); } } private static SalsaMsgConfigureState downloadArtifact(SalsaMsgConfigureArtifact confInfo) { logger.debug("Inside downloadArtifact method"); logger.debug("Preparing artifact for node: " + confInfo.getUnit()); if (confInfo.getArtifacts() == null) { new File(PioneerConfiguration.getWorkingDirOfInstance(confInfo.getUnit(), confInfo.getInstance())).mkdirs(); return new SalsaMsgConfigureState(confInfo.getActionID(), SalsaMsgConfigureState.CONFIGURATION_STATE.SUCCESSFUL, 0, "No need to download artifact"); } logger.debug("Number of artifact: " + confInfo.getArtifacts().size()); for (SalsaMsgConfigureArtifact.DeploymentArtifact art : confInfo.getArtifacts()) { try { logger.debug("Downloading artifact for: " + confInfo.getUnit() + ". URL:" + art); URL url = new URL(art.getReference()); String filePath = PioneerConfiguration.getWorkingDirOfInstance(confInfo.getUnit(), confInfo.getInstance()) + File.separator + FilenameUtils.getName(url.getFile()); logger.debug("Download file from:" + url.toString() + "\nSave to file:" + filePath); FileUtils.copyURLToFile(url, new File(filePath)); (new File(filePath)).setExecutable(true); // if the artifact is an archieve, try to extract it extractFile(filePath, PioneerConfiguration.getWorkingDirOfInstance(confInfo.getUnit(), confInfo.getInstance())); // of artifact is META, parse new capabilities and publish a message to update capabilities if (art.getType().equals(SalsaArtifactType.metadata.getString())) { logger.debug("Found a meta artifact: " + filePath); HashMap<String, String> metadataActions = new HashMap<>(); try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) { logger.debug("Ok, now reading meta data file"); while (true) { String inputLine = reader.readLine(); logger.debug("Read a meta data line: " + inputLine); if (inputLine == null) { logger.debug("Reading meta data file completed"); break; } else if (inputLine.startsWith("action.")) { logger.debug(" -> The line starts with action. , parsing the action"); String newActionName = inputLine.substring(inputLine.indexOf(".") + 1, inputLine.indexOf("=")); logger.debug(" --> action name: " + newActionName); String newActionRef = inputLine.substring(inputLine.indexOf("=") + 1); logger.debug(" --> action ref: " + newActionRef); if (newActionName != null && newActionRef != null && !newActionName.trim().isEmpty() && !newActionRef.trim().isEmpty()) { metadataActions.put(newActionName, newActionRef); logger.debug("Parsing metadata action for {}/{}, action: {}, ref: {}", confInfo.getUnit(), confInfo.getInstance(), newActionName, newActionRef); } else { logger.debug("Parsing error: either action or action reference is empty"); } } else if (!inputLine.trim().isEmpty()) { logger.debug("Do not parse custom attributes yet, pass this: {}", inputLine); } } } MessagePublishInterface publish_deploy = factory.getMessagePublisher(); SalsaMsgUpdateMetadata updateMsg = new SalsaMsgUpdateMetadata(confInfo, metadataActions); SalsaMessage salsaMsg = new SalsaMessage(SalsaMessage.MESSAGE_TYPE.salsa_updateNodeMetadata, PioneerConfiguration.getPioneerID(), SalsaMessageTopic.PIONEER_UPDATE_CONFIGURATION_STATE, "", updateMsg.toJson()); publish_deploy.pushMessage(salsaMsg); } } catch (IOException e) { // in the case cannot create artifact logger.error("Error while downloading artifact for: " + confInfo.getUnit() + ". URL:" + art); return new SalsaMsgConfigureState(confInfo.getActionID(), SalsaMsgConfigureState.CONFIGURATION_STATE.ERROR, 0, "Failed to download artifact at: " + art.getReference()); } } return new SalsaMsgConfigureState(confInfo.getActionID(), SalsaMsgConfigureState.CONFIGURATION_STATE.SUCCESSFUL, 0, "All artifacts are download successfully."); } // support only .tar.gz at this time private static void extractFile(String filePath, String workingDir) { if (filePath.endsWith("tar.gz")) { SystemFunctions.executeCommandGetReturnCode("tar -xvzf " + filePath, workingDir, "ExtractFileModule"); } } private static ArtifactConfigurationInterface selectConfigurationModule(SalsaMsgConfigureArtifact confInfo) { logger.debug("Selecting configuration module for: " + confInfo.getActionID() + ", " + confInfo.getRunByMe()); if (confInfo.getArtifactType() == null) { // in this case, the runByMe contain a binary command if (!confInfo.getRunByMe().isEmpty()) { return new BinaryExecutionInstrument(); } else { return null; } } switch (confInfo.getArtifactType()) { case apt: logger.debug("Selected module apt !"); return new AptGetInstrument(); case chef: logger.debug("Selected module chef !"); return new ChefSoloInstrument(); case chefSolo: logger.debug("Selected module chef solo !"); return new ChefSoloInstrument(); case dockerfile: logger.debug("Selected module docker !"); return new DockerConfigurator(); case sh: logger.debug("Selected module sh !"); return new BashInstrument(); case shcont: logger.debug("Selected module sh cont !"); return new BashContinuousInstrument(); case war: logger.debug("Selected module war !"); return new WarInstrument(); default: logger.debug("Cannot select any module !"); return null; } } public static int executeLifecycleAction(SalsaMsgConfigureArtifact cmd) { logger.debug("Recieve command to executing action: " + cmd.getUnit() + "/" + cmd.getInstance() + "/" + cmd.getActionName()); String actionName = cmd.getActionName(); logger.debug("Found a custom action: " + actionName); if (cmd.getPreRunByMe() == null || !cmd.getPreRunByMe().trim().isEmpty()) { // something need to to be run first int code = SystemFunctions.executeCommandGetReturnCode(cmd.getPreRunByMe(), PioneerConfiguration.getWorkingDirOfInstance(cmd.getUnit(), cmd.getInstance()), PioneerConfiguration.getPioneerID()); if (code != 0) { logger.error("Error when execute the pre-action: " + cmd.getPreRunByMe() + ". The process still will be continued !"); } } // if this is an undeployment, execute it if (actionName.equals("undeploy")) { // if this is an undeploy action, remove node logger.debug("Recieve command to remove node: " + cmd.getUnit() + "/" + cmd.getInstance()); // TODO: more detail model for specific DOCKER. Here we assume that AppContainer is DOCKER if (cmd.getUnitType() == ServiceCategory.docker) { logger.debug("This node is a docker container, start to remove it !"); DockerConfigurator docker = new DockerConfigurator(); docker.removeDockerContainer(cmd.getRunByMe().trim()); } else { BashContinuousManagement.killProcessInstance(cmd.getActionID()); } return 0; } else { // other actions if (cmd.getRunByMe().trim().length() == 0) { logger.debug("Do not find any running command for the action: " + actionName); } SystemFunctions.executeCommandGetReturnCode(cmd.getRunByMe(), PioneerConfiguration.getWorkingDirOfInstance(cmd.getUnit(), cmd.getInstance()), PioneerConfiguration.getPioneerID()); } return 0; } }