/*
* 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.impl.base;
import java.io.File;
import java.util.List;
import at.ac.tuwien.dsg.cloud.salsa.cloudconnector.CloudInterface;
import at.ac.tuwien.dsg.cloud.salsa.cloudconnector.InstanceDescription;
import at.ac.tuwien.dsg.cloud.salsa.cloudconnector.VMStates;
import at.ac.tuwien.dsg.cloud.salsa.cloudconnector.multiclouds.MultiCloudConnector;
import at.ac.tuwien.dsg.cloud.salsa.cloudconnector.multiclouds.SalsaCloudProviders;
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.utils.SalsaCenterConnector;
import at.ac.tuwien.dsg.cloud.salsa.common.interfaces.SalsaException;
import at.ac.tuwien.dsg.cloud.salsa.engine.capabilityinterface.UnitCapabilityInterface;
import at.ac.tuwien.dsg.cloud.salsa.engine.exceptions.EngineConnectionException;
import at.ac.tuwien.dsg.cloud.salsa.engine.exceptions.IllegalConfigurationAPICallException;
import at.ac.tuwien.dsg.cloud.salsa.engine.exceptions.VMProvisionException;
import at.ac.tuwien.dsg.cloud.salsa.engine.exceptions.VMRemoveException;
import at.ac.tuwien.dsg.cloud.salsa.engine.services.InternalManagement;
import at.ac.tuwien.dsg.cloud.salsa.engine.utils.EngineLogger;
import at.ac.tuwien.dsg.cloud.salsa.engine.utils.EventPublisher;
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.model.Salsa.INFOMessage;
import at.ac.tuwien.dsg.cloud.salsa.tosca.extension.SalsaInstanceDescription_VM;
import com.google.common.base.Joiner;
import java.io.IOException;
import java.util.Scanner;
/**
* This class contain methods for manage virtual machine stack. The class still requires some information from SALSA
*
* @author Duc-Hung Le TODO: - Instances management - Running information model
*/
public class VMCapabilityBase implements UnitCapabilityInterface {
CloudInterface cloud;
File configFile;
SalsaCenterConnector centerCon;
{
try {
centerCon = new SalsaCenterConnector(SalsaConfiguration.getSalsaCenterEndpointLocalhost(), "/tmp", EngineLogger.logger);
} catch (EngineConnectionException ex) {
EngineLogger.logger.error("Cannot connect to SALSA service in localhost: " + SalsaConfiguration.getSalsaCenterEndpointLocalhost() + ". This is a fatal error !");
}
}
public VMCapabilityBase() {
this.configFile = SalsaConfiguration.getCloudUserParametersFile();
}
static final long cooldown = 2000;
static long lastDeploymentTime = (new java.util.Date()).getTime();
// This assume that a docker container will always created at least 2 seconds after previous one, on a same VM
private void waitForCooledDown() {
long currentTime = (new java.util.Date()).getTime();
long between = currentTime - lastDeploymentTime;
if (between < cooldown) {
EngineLogger.logger.debug("Waiting a " + (cooldown - between) + " miliseconds to reduce the cloud failure when create many VM at a time");
try {
Thread.sleep(cooldown - between);
} catch (InterruptedException ex) {
lastDeploymentTime = currentTime;
}
}
lastDeploymentTime = currentTime;
}
@Override
public ServiceInstance deploy(String serviceId, String nodeId, int instanceId) throws SalsaException {
waitForCooledDown();
EventPublisher.publishInstanceEvent(Joiner.on("/").join(serviceId, nodeId, instanceId), INFOMessage.ACTION_TYPE.DEPLOY, INFOMessage.ACTION_STATUS.STARTED, "VMCapabilityBase", "Start to deploy new VM");
//TDefinitions def = centerCon.getToscaDescription(serviceId);
CloudService service = centerCon.getUpdateCloudServiceRuntime(serviceId);
String topologyId = service.getTopologyOfNode(nodeId).getId();
ServiceUnit unit = service.getComponentById(nodeId);
SalsaInstanceDescription_VM instanceDesc = (SalsaInstanceDescription_VM) unit.getProperties().getAny();
//EngineLogger.logger.debug("Creating this VM node: " + nodeId + ". Tosca ID:" + def.getId());
//TNodeTemplate enhancedNode = (TNodeTemplate) ToscaStructureQuery.getNodetemplateById(nodeId, def);
ServiceInstance repData = centerCon.getUpdateServiceUnit(serviceId, nodeId).getInstanceById(instanceId);
EngineLogger.logger.debug("YOUR ARE HERE TO DEPLOY 4");
// get the static information of TOSCA and put into SalsaComponentInstanceData property
// SalsaInstanceDescription_VM instanceDesc = new SalsaInstanceDescription_VM();
//
// SalsaMappingProperties mapProp = (SalsaMappingProperties) enhancedNode.getProperties().getAny();
// instanceDesc.updateFromMappingProperties(mapProp);
// EngineLogger.logger.debug("YOUR ARE HERE TO DEPLOY 5");
String userData = prepareUserData(SalsaConfiguration.getUserName(), serviceId, topologyId, nodeId, instanceId, instanceDesc);
MultiCloudConnector mcc = new MultiCloudConnector(EngineLogger.logger, configFile);
EngineLogger.logger.debug("DEBUG: " + nodeId + " --- " + instanceDesc.getInstanceType());
EngineLogger.logger.debug("CLOUD PROVIDER = " + instanceDesc.getProvider() + "//" + SalsaCloudProviders.fromString(instanceDesc.getProvider()));
// start the VM
InstanceDescription indes = mcc.launchInstance(nodeId + "_" + instanceId,
SalsaCloudProviders.fromString(instanceDesc.getProvider()),
instanceDesc.getBaseImage(),
"", // this is the sshKeyGen, but not need anymore. When create mcc, we pass the configFile
userData,
instanceDesc.getInstanceType(),
1, 1); // deploy min instance number of node
// update the instance property from cloud specific to SALSA format
if (indes == null) {
throw new VMProvisionException(instanceDesc.getProvider(), serviceId, nodeId, instanceId, VMProvisionException.VMProvisionError.CLOUD_FAILURE, "Cloud connector does not send back the VM information after called, the instance description is null.");
} else if (indes.getPrivateIp() == null || indes.getPrivateIp().toString().isEmpty()) {
throw new VMProvisionException(instanceDesc.getProvider(), serviceId, nodeId, instanceId, VMProvisionException.VMProvisionError.CLOUD_FAILURE, "The VM does not have an IP");
}
if (indes.getState().equals(VMStates.Failed)) {
throw new VMProvisionException(instanceDesc.getProvider(), serviceId, nodeId, instanceId, VMProvisionException.VMProvisionError.CLOUD_FAILURE, "Cloud is failure to create a VM");
}
updateVMProperties(instanceDesc, indes);
EngineLogger.logger.debug(instanceDesc.getProvider() + " -- " + instanceDesc.getBaseImage() + " -- " + instanceDesc.getInstanceType() + " -- " + unit.getMin());
EngineLogger.logger.debug("A VM for " + nodeId + " has been created.");
centerCon.updateInstanceUnitProperty(serviceId, topologyId, nodeId, instanceId, instanceDesc);
EngineLogger.logger.debug("Updated VM info for node: " + nodeId);
EventPublisher.publishInstanceEvent(Joiner.on("/").join(serviceId, nodeId, instanceId), INFOMessage.ACTION_TYPE.DEPLOY, INFOMessage.ACTION_STATUS.DONE, "VMCapabilityBase", "A new VM is created with IP: " + instanceDesc.getPrivateIp());
return repData;
}
private static void updateVMProperties(SalsaInstanceDescription_VM sid, InstanceDescription inst) throws SalsaException {
//if (inst.getReplicaFQN()!=null){ sid.setReplicaNumber(inst.getReplicaFQN().getReplicaNum());}
if (inst.getPrivateIp() != null) {
sid.setPrivateIp(inst.getPrivateIp().getHostName());
} else {
throw new VMProvisionException(VMProvisionException.VMProvisionError.CLOUD_FAILURE, "Cannot get the IP address of the VM in the VM description.");
}
if (inst.getPublicIp() != null) {
sid.setPublicIp(inst.getPublicIp().getHostName());
}
if (inst.getPrivateDNS() != null) {
sid.setPrivateDNS(inst.getPrivateDNS());
}
if (inst.getPublicDNS() != null) {
sid.setPublicDNS(inst.getPublicDNS());
}
//if (inst.getState() !=null){ sid.setState(inst.getState()); }
if (inst.getInstanceId() != null) {
sid.setInstanceId(inst.getInstanceId());
} else {
throw new VMProvisionException(VMProvisionException.VMProvisionError.CLOUD_FAILURE, "Cannot get the ID of the VM in the description. The VM may be created or not but we cannot manage it.");
}
sid.setQuota(inst.getQuota());
}
private static String prepareUserData(String userName, String serviceId, String topologyId, String nodeId, int replica, SalsaInstanceDescription_VM instanceDesc) {
StringBuilder userDataBuffer = new StringBuilder();
userDataBuffer.append("#!/bin/bash \n");
userDataBuffer.append("echo \"Running the customization scripts\" \n\n");
// add the code to check and install java for pioneer
File java_checking = new File(SystemFunctions.class.getResource("/scripts/java1.8_update.sh").getFile());
try (Scanner scanner = new Scanner(java_checking)) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
userDataBuffer.append(line).append("\n");
}
scanner.close();
} catch (IOException e) {
e.printStackTrace();
}
// at this point, we have an installing java. Please see the script in resources folder.
// working dir should be specific for nodes.
String specificWorkingDir = SalsaConfiguration.getPioneerWorkingDir() + "/" + serviceId + "." + nodeId + "." + replica;
EngineLogger.logger.debug("Preparing user data. Working dir for pioneer for: " + serviceId + "/" + nodeId + "/" + replica + ": " + specificWorkingDir);
String specificVariableFile = specificWorkingDir + "/" + SalsaConfiguration.getSalsaVariableFile();
EngineLogger.logger.debug("Preparing user data. Variable file for pioneer for: " + serviceId + "/" + nodeId + "/" + replica + ": " + specificVariableFile);
userDataBuffer.append("mkdir -p ").append(specificWorkingDir).append(" \n");
userDataBuffer.append("cd ").append(specificWorkingDir).append(" \n");
// set some variable put in variable.properties
userDataBuffer.append("echo '# Generate salsa properties file. This code is generated at deployment time.' > ").append(specificVariableFile).append(" \n");
userDataBuffer.append("echo 'SALSA_USER_NAME=").append(userName).append("' >> ").append(specificVariableFile).append(" \n");
userDataBuffer.append("echo 'SALSA_SERVICE_ID=").append(serviceId).append("' >> ").append(specificVariableFile).append(" \n");
userDataBuffer.append("echo 'SALSA_TOPOLOGY_ID=").append(topologyId).append("' >> ").append(specificVariableFile).append(" \n");
userDataBuffer.append("echo 'SALSA_REPLICA=").append(replica).append("' >> ").append(specificVariableFile).append(" \n");
userDataBuffer.append("echo 'SALSA_NODE_ID=").append(nodeId).append("' >> ").append(specificVariableFile).append(" \n");
userDataBuffer.append("echo 'SALSA_TOSCA_FILE=").append(specificWorkingDir).append("/").append(serviceId).append("' >> ").append(specificVariableFile).append(" \n");
userDataBuffer.append("echo 'SALSA_WORKING_DIR=").append(specificWorkingDir).append("' >> ").append(specificVariableFile).append(" \n");
userDataBuffer.append("echo 'SALSA_PIONEER_WEB=").append(SalsaConfiguration.getPioneerArtifact()).append("' >> ").append(specificVariableFile).append(" \n");
userDataBuffer.append("echo 'SALSA_PIONEER_RUN=").append(SalsaConfiguration.getPioneerRun()).append("' >> ").append(specificVariableFile).append(" \n");
userDataBuffer.append("echo 'SALSA_CENTER_ENDPOINT=").append(SalsaConfiguration.getSalsaCenterEndpoint()).append("' >> ").append(specificVariableFile).append(" \n");
userDataBuffer.append("echo 'BROKER=").append(SalsaConfiguration.getBroker()).append("' >> ").append(specificVariableFile).append(" \n");
userDataBuffer.append("echo 'BROKER_TYPE=").append(SalsaConfiguration.getBrokerType()).append("' >> ").append(specificVariableFile).append(" \n");
userDataBuffer.append("echo 'ELISE_CONDUCTOR_URL=").append(SalsaConfiguration.getConductorWeb()).append("' >> ").append(specificVariableFile).append(" \n");
// download salsa-pioneer.jar
userDataBuffer.append("wget -qN --content-disposition ").append(SalsaConfiguration.getPioneerArtifact()).append(" \n");
// install all package dependencies and ganglia
SalsaInstanceDescription_VM tprop = instanceDesc;
if (tprop.getPackagesDependenciesList() != null) {
List<String> lstPkgs = tprop.getPackagesDependenciesList().getPackageDependency();
if (!lstPkgs.isEmpty()) {
for (String pkg : lstPkgs) {
if (!pkg.trim().isEmpty()) {
EventPublisher.publishInstanceEvent(Joiner.on("/").join(serviceId, nodeId, replica), INFOMessage.ACTION_TYPE.DEPLOY, INFOMessage.ACTION_STATUS.PROCESSING, "VMCapabilityBase", "Installing package " + pkg);
// TODO: should change, now just support Ubuntu image
userDataBuffer.append("apt-get -q -y install ").append(pkg).append(" \n");
}
}
}
}
// install ganglia client for monitoring this VM
// userDataBuffer.append("apt-get -y install ganglia-monitor gmetad \n");
// execute Pioneer
userDataBuffer.append("echo Current dir `pwd` \n");
// userDataBuffer.append("java -jar ").append(fileLst.get(0)).append(" setnodestate ").append(node.getId()).append(" ready \n");
// userDataBuffer.append("curl -sL ").append(SalsaConfiguration.getPioneerBootstrapScript()).append(" | sudo bash - \n");
// userDataBuffer.append("java -Xmx1024M -Xms512M -XX:MaxPermSize=256m -Xss4m -jar salsa-pioneer.jar").append(" startserver \n");
userDataBuffer.append("java -jar salsa-pioneer.jar").append(" startserver \n");
return userDataBuffer.toString();
}
@Override
public void remove(String serviceId, String nodeId, int instanceId) throws SalsaException {
EngineLogger.logger.debug("Removing VM: {}/{}/{}....", serviceId, nodeId, instanceId);
CloudService service = centerCon.getUpdateCloudServiceRuntime(serviceId);
EngineLogger.logger.debug("Get Service info when undeploying: {}/{}/{}....", serviceId, nodeId, instanceId);
ServiceUnit node = service.getComponentById(nodeId);
EngineLogger.logger.debug("Get node info when undeploying: {}/{}/{}....", serviceId, nodeId, instanceId);
if (!node.getType().equals(SalsaEntityType.OPERATING_SYSTEM.getEntityTypeString())) {
EngineLogger.logger.error("Remove VM on a non VM node: " + serviceId + "/" + nodeId + "/" + instanceId);
throw new IllegalConfigurationAPICallException("Remove VM on a non VM node: " + serviceId + "/" + nodeId + "/" + instanceId);
}
ServiceInstance vm = node.getInstanceById(instanceId);
if (vm.getProperties() == null) {
EngineLogger.logger.error("VM properties is null: " + serviceId + "/" + nodeId + "/" + instanceId);
throw new VMRemoveException(VMRemoveException.Cause.VM_DATA_NOT_FOUND);
}
SalsaInstanceDescription_VM vmProps = (SalsaInstanceDescription_VM) vm.getProperties().getAny();
if (vmProps == null) {
EngineLogger.logger.error("VM properties is null: " + serviceId + "/" + nodeId + "/" + instanceId);
throw new VMRemoveException(VMRemoveException.Cause.VM_DATA_NOT_FOUND);
}
EngineLogger.logger.debug("Sending request to shutdown the pioneer");
// send message to shutdown pioneer
String pioneerID = PioneerManager.getPioneerID(SalsaConfiguration.getUserName(), serviceId, nodeId, instanceId);
EngineLogger.logger.debug("Pioneer ID is: " + pioneerID);
(new InternalManagement()).shutdownPioneer(pioneerID);
// maybe the pioneer is not shutdown yet, but it is not the problem :)
MultiCloudConnector cloudCon = new MultiCloudConnector(EngineLogger.logger, configFile);
String providerName = vmProps.getProvider();
String cloudInstanceId = vmProps.getInstanceId();
EngineLogger.logger.debug("Removing virtual machine. Provider: " + providerName + "InstanceId: " + instanceId);
cloudCon.removeInstance(SalsaCloudProviders.fromString(providerName), cloudInstanceId);
centerCon.removeInstanceMetadata(serviceId, nodeId, instanceId);
}
}