/*
* 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 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.engine.dataprocessing.SalsaXmlDataProcess;
import at.ac.tuwien.dsg.cloud.salsa.engine.capabilityinterface.WholeAppCapabilityInterface;
import at.ac.tuwien.dsg.cloud.salsa.engine.exceptions.EngineConnectionException;
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.capabilityinterface.UnitCapabilityInterface;
import at.ac.tuwien.dsg.cloud.salsa.engine.exceptions.ServicedataProcessingException;
import at.ac.tuwien.dsg.cloud.salsa.engine.exceptions.AppDescriptionException;
import at.ac.tuwien.dsg.cloud.salsa.engine.impl.richInformationCapability.AsyncUnitCapability;
import at.ac.tuwien.dsg.cloud.salsa.engine.impl.genericCapability.InfoParser;
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.dataprocessing.ToscaXmlProcess;
import at.ac.tuwien.dsg.cloud.salsa.engine.utils.EventPublisher;
import at.ac.tuwien.dsg.cloud.salsa.messaging.model.Salsa.INFOMessage;
import com.google.common.base.Joiner;
import generated.oasis.tosca.TDefinitions;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.JAXBException;
/**
*
* @author Duc-Hung Le
*/
public class WholeAppCapabilityBase implements WholeAppCapabilityInterface {
SalsaCenterConnector centerCon;
UnitCapabilityInterface asynCapa = new AsyncUnitCapability();
{
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 !");
}
}
File configFile;
public WholeAppCapabilityBase() {
this.configFile = SalsaConfiguration.getCloudUserParametersFile();
}
@Override
public CloudService addService(String serviceName, TDefinitions def) throws SalsaException {
EventPublisher.publishCloudServiceEvent(serviceName, INFOMessage.ACTION_TYPE.DEPLOY, INFOMessage.ACTION_STATUS.STARTED, "WholeAppCapabilityBase", "Start to add a new cloud service with ID " + serviceName);
if (configFile == null) {
throw new EngineMisconfiguredException("./salsa.engine.properties", "The file is missing");
}
String deployID = serviceName;
String ogininalToscaFile = SalsaConfiguration.getServiceStorageDir() + "/" + deployID + ".original";
try {
ToscaXmlProcess.writeToscaDefinitionToFile(def, ogininalToscaFile);
} catch (JAXBException | IOException ex) {
throw new ServicedataProcessingException(serviceName, ex);
}
// register service, all state is INITIAL
String fullToscaFile = SalsaConfiguration.getServiceStorageDir() + "/" + deployID;
try {
ToscaXmlProcess.writeToscaDefinitionToFile(def, fullToscaFile);
} catch (JAXBException | IOException ex) {
throw new ServicedataProcessingException(serviceName, ex);
}
EngineLogger.logger.debug("debugggg Sep 8 - 1");
// register service running data
String fullSalsaDataFile = SalsaConfiguration.getServiceStorageDir() + "/" + deployID + ".data";
EngineLogger.logger.debug("debugggg Sep 8 - 2");
CloudService serviceData = null;
try {
serviceData = InfoParser.buildRuntimeDataFromTosca(def);
} catch (Exception e) {
throw new AppDescriptionException("TOSCA description", "Cannot build the cloud service model from input TOSCA. Please check: ID consistency, relationship orders.", e);
}
EngineLogger.logger.debug("debugggg Sep 8 - 3");
serviceData.setId(deployID);
serviceData.setName(def.getId());
SalsaXmlDataProcess.writeCloudServiceToFile(serviceData, fullSalsaDataFile);
EngineLogger.logger.debug("debugggg Sep 8 - 4");
EventPublisher.publishCloudServiceEvent(serviceName, INFOMessage.ACTION_TYPE.DEPLOY, INFOMessage.ACTION_STATUS.DONE, "WholeAppCapabilityBase", "Add new cloud service with ID " + serviceName);
return actualCreateNewService(serviceData);
}
@Override
public boolean cleanService(String serviceId) throws SalsaException {
EventPublisher.publishCloudServiceEvent(serviceId, INFOMessage.ACTION_TYPE.REMOVE, INFOMessage.ACTION_STATUS.STARTED, "WholeAppCapabilityBase", "Start to clean service with ID " + serviceId);
centerCon = new SalsaCenterConnector(SalsaConfiguration.getSalsaCenterEndpointLocalhost(), "/tmp", EngineLogger.logger);
CloudService service = centerCon.getUpdateCloudServiceRuntime(serviceId);
if (service == null) {
EngineLogger.logger.error("Cannot get the service information to delete its instances: {}", serviceId);
throw new ServicedataProcessingException(serviceId);
}
List<ServiceUnit> suList = service.getAllComponentByType(SalsaEntityType.OPERATING_SYSTEM);
if (suList == null){
EngineLogger.logger.error("Cannot get the list ofthe VMs for service: {}", serviceId);
return false;
}
EngineLogger.logger.debug("Trying to clean {} machine", suList.size());
int numberOfReferenceNodes = 0;
for (ServiceUnit su : suList) {
EngineLogger.logger.debug("Checking to undeploy all instance of service unit: {}", su.getId());
if (su.getReference() == null || su.getReference().isEmpty()) {
List<ServiceInstance> repLst = su.getInstancesList();
EngineLogger.logger.debug("The service unit {} has {} instances", su.getId(), repLst.size());
// Note: we must use this to ensure the dependency, so the higer stack will be removed before the VMs are removed
for(ServiceInstance i: repLst){
EngineLogger.logger.debug("Calling API to remove instance: {}", i.getInstanceId());
asynCapa.remove(serviceId, su.getId(), i.getInstanceId());
// centerCon.removeOneInstance(serviceId, su.getId(), i.getInstanceId());
}
} else {
EngineLogger.logger.debug("Node {} is a reference, to overpass it.", su.getId());
numberOfReferenceNodes += su.getInstancesList().size();
}
}
// wait until return true
int count = 30;
while (true){
service = centerCon.getUpdateCloudServiceRuntime(serviceId);
if (service == null){
break;
}
List<ServiceInstance> instances = service.getAllReplicaByType(SalsaEntityType.OPERATING_SYSTEM);
EngineLogger.logger.debug("Checking if all the instances of service {} are removed. There are {} left. Timeout in: {} times", serviceId, (instances.size() - numberOfReferenceNodes), count);
if (instances.isEmpty() || count<1 || (instances.size() - numberOfReferenceNodes <=0)){
break;
}
try {
// recheck every 3 second
count = count - 1;
Thread.sleep(3000);
} catch (InterruptedException ex) {
Logger.getLogger(WholeAppCapabilityBase.class.getName()).log(Level.SEVERE, null, ex);
}
}
// unregister pioneers
PioneerManager.removePioneerOfWholeService(SalsaConfiguration.getUserName(), serviceId);
EventPublisher.publishCloudServiceEvent(serviceId, INFOMessage.ACTION_TYPE.REMOVE, INFOMessage.ACTION_STATUS.DONE, "WholeAppCapabilityBase", "Clean service done: " + serviceId);
return true;
}
private CloudService actualCreateNewService(CloudService serviceData) throws SalsaException {
// here find all the TOP node
if (serviceData == null){
throw new ServicedataProcessingException("a new service");
}
EngineLogger.logger.debug("Start to process deployment with service data, id= {}", serviceData.getId());
List<ServiceUnit> nodes = serviceData.getAllComponent();
EngineLogger.logger.debug("Total number of nodes: {}", nodes.size());
List<ServiceUnit> topNodes = new ArrayList<>();
for (ServiceUnit node : nodes) {
boolean getIt = true;
for (ServiceUnit t : nodes) {
if (t.getHostedId().equals(node.getId())) {
getIt = false;
EngineLogger.logger.debug("Orchestating: Discard node: " + node.getId());
break;
}
}
if (getIt) {
EngineLogger.logger.debug("Orchestating: Get top node: " + node.getId());
topNodes.add(node);
}
}
InfoParser.cloneDataForReferenceNodes(serviceData);
// deploy new node by generate deployment threads for each service
for (ServiceUnit unit : topNodes) {
ServiceUnit refUnit = InfoParser.getReferenceServiceUnit(unit);
if (refUnit == null && unit.getMin() > 0) { // not a reference and min > 0
EngineLogger.logger.debug("Orchestating: Creating top node: " + unit.getId());
// try to create minimum instance of software
for (int i = 0; i < unit.getMin(); i++) {
asynCapa.deploy(serviceData.getId(), unit.getId(), i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
EngineLogger.logger.error("Thread interrupted !");
}
}
}
}
return serviceData;
}
}