/*
* 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.ConductorDescription;
import at.ac.tuwien.dsg.cloud.elise.master.RESTService.EliseManager;
import at.ac.tuwien.dsg.cloud.elise.master.RESTService.EliseRepository;
import at.ac.tuwien.dsg.cloud.elise.model.generic.Capability;
import at.ac.tuwien.dsg.cloud.elise.model.generic.executionmodels.RestExecution;
import at.ac.tuwien.dsg.cloud.elise.model.runtime.UnitInstance;
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.SalsaEntityState;
import at.ac.tuwien.dsg.cloud.salsa.domainmodels.DomainEntity;
import at.ac.tuwien.dsg.cloud.salsa.domainmodels.IaaS.DockerInfo;
import at.ac.tuwien.dsg.cloud.salsa.engine.utils.SalsaCenterConnector;
import at.ac.tuwien.dsg.cloud.salsa.engine.exceptions.EngineConnectionException;
import at.ac.tuwien.dsg.cloud.salsa.common.interfaces.SalsaException;
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.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.messaging.messageInterface.MessageClientFactory;
import at.ac.tuwien.dsg.cloud.salsa.messaging.messageInterface.MessageSubscribeInterface;
import at.ac.tuwien.dsg.cloud.salsa.messaging.messageInterface.SalsaMessageHandling;
import at.ac.tuwien.dsg.cloud.salsa.messaging.model.Salsa.INFOMessage;
import at.ac.tuwien.dsg.cloud.salsa.messaging.model.Salsa.PioneerInfo;
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.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.tosca.extension.SalsaCapaReqString;
import at.ac.tuwien.dsg.cloud.salsa.tosca.extension.SalsaInstanceDescription_Docker;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import com.google.common.base.Joiner;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import javax.annotation.PostConstruct;
import org.apache.commons.io.FileUtils;
import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
import org.apache.log4j.spi.LoggingEvent;
/**
*
* @author Duc-Hung Le
*/
public class SalsaEngineListener {
MessageClientFactory factory = MessageClientFactory.getFactory(SalsaConfiguration.getBroker(), SalsaConfiguration.getBrokerType());
@PostConstruct
public void init() {
EngineLogger.logger.debug("Subscribing to the control topic : " + SalsaMessageTopic.PIONEER_REGISTER_AND_HEARBEAT + " and " + SalsaMessageTopic.PIONEER_UPDATE_CONFIGURATION_STATE);
MessageSubscribeInterface subscriber1 = factory.getMessageSubscriber(new SalsaMessageHandling() {
@Override
public void handleMessage(SalsaMessage msg) {
EngineLogger.logger.debug("Get a message from pioneer to update somethings....");
if (msg.getMsgType().equals(SalsaMessage.MESSAGE_TYPE.salsa_updateNodeMetadata)) {
EngineLogger.logger.debug(" --> The message is for updating metadata, msgtype: " + msg.getMsgType());
SalsaMsgUpdateMetadata metadataInfo = SalsaMsgUpdateMetadata.fromJson(msg.getPayload());
try {
SalsaCenterConnector centerCon = new SalsaCenterConnector(SalsaConfiguration.getSalsaCenterEndpointLocalhost(), "/tmp", EngineLogger.logger);
EngineLogger.logger.debug(" --> Is about calling salsa API to update");
EngineLogger.logger.debug(" --> msg.getPayload: {}", msg.getPayload());
EngineLogger.logger.debug(" --> metadataInfo.getService: {}", metadataInfo.getService());
EngineLogger.logger.debug(" --> metadataInfo.getTopology: {}", metadataInfo.getTopology());
EngineLogger.logger.debug(" --> metadataInfo.getUnit: {}", metadataInfo.getUnit());
centerCon.updateNodeMetadata(msg.getPayload(), metadataInfo.getService(), metadataInfo.getTopology(), metadataInfo.getUnit());
EngineLogger.logger.debug(" --> Seem to update done");
// update the capability to the instance in the DB. These API are executed by SALSA
EliseRepository mng = JAXRSClientFactory.create(SalsaConfiguration.getSalsaCenterEndpointLocalhost()+"/rest/elise", EliseRepository.class, Arrays.asList(new JacksonJaxbJsonProvider()));
ServiceUnit unit = centerCon.getUpdateServiceUnit(metadataInfo.getService(), metadataInfo.getUnit());
ServiceInstance instance = unit.getInstanceById(metadataInfo.getInstance());
UnitInstance unitInstance = mng.readUnitInstance(instance.getUuid().toString());
SalsaMsgUpdateMetadata data = SalsaMsgUpdateMetadata.fromJson(msg.getPayload());
for(String key: data.getActions().keySet()){
// in the database we store the rest, while in the salsa service XML we store the internal file reference
String endpoint = SalsaConfiguration.getSalsaCenterEndpoint() + "/rest/services/"+metadataInfo.getService()+"/nodes/"+metadataInfo.getUnit()+"/instances/"+instance.getInstanceId()+"/action_queue/"+key;
RestExecution restCall = new RestExecution(endpoint, RestExecution.RestMethod.POST, "");
Capability capa = new Capability(key, Capability.ExecutionMethod.REST, restCall);
capa.executedBy("SALSA");
unitInstance.hasCapability(capa);
}
mng.saveUnitInstance(unitInstance);
} catch (EngineConnectionException ex) {
EngineLogger.logger.error("Cannot connect to SALSA service in localhost: " + SalsaConfiguration.getSalsaCenterEndpointLocalhost() + ". This is a fatal error !");
} catch (SalsaException ex) {
EngineLogger.logger.error("Cannot update the metadata");
}
return;
}
EngineLogger.logger.debug(" --> The message is for updating configuration state, msgtype: " + msg.getMsgType());
// OTHER CASE ONLY: SalsaMessage.MESSAGE_TYPE.salsa_configurationStateUpdate
SalsaMsgConfigureState state = SalsaMsgConfigureState.fromJson(msg.getPayload());
SalsaCenterConnector centerCon = null;
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 !");
}
SalsaMsgConfigureArtifact fullID = ActionIDManager.getInstanceFullID(state.getActionID());
EngineLogger.logger.debug("Current actions is pending: " + ActionIDManager.describe());
if (fullID == null) {
EngineLogger.logger.error("Action is not found. ID: " + state.getActionID());
return;
}
SalsaEntityState salsaState = SalsaEntityState.UNDEPLOYED;
switch (state.getState()) {
case ERROR: {
EngineLogger.logger.error("Artifact configuration failed. Instance: {},{},{}. Details: {}", fullID.getService(), fullID.getUnit(), fullID.getInstance(), state.getDomainID());
salsaState = SalsaEntityState.ERROR;
break;
}
case SUCCESSFUL: {
if (fullID.getActionName().equals("deploy")) {
try {
EngineLogger.logger.debug("The deploy action for unit {}/{}/{}/{} is successful", fullID.getUser(), fullID.getService(), fullID.getUnit(), fullID.getInstance());
updateInstanceCapability(fullID, state);
} catch (SalsaException ex) {
EngineLogger.logger.error("Deployment action failed. ActionID: {}", fullID.getActionID(), ex);
}
salsaState = SalsaEntityState.DEPLOYED;
EventPublisher.publishInstanceEvent(Joiner.on("/").join(fullID.getService(), fullID.getUnit(), fullID.getInstance()), INFOMessage.ACTION_TYPE.DEPLOY, INFOMessage.ACTION_STATUS.DONE, "EventListener", "The instance deployment is finished");
} else if (fullID.getActionName().equals("undeploy")) {
EngineLogger.logger.debug("The undeploy action for unit {}/{}/{}/{} is successful", fullID.getUser(), fullID.getService(), fullID.getUnit(), fullID.getInstance());
salsaState = SalsaEntityState.UNDEPLOYED;
EventPublisher.publishInstanceEvent(Joiner.on("/").join(fullID.getService(), fullID.getUnit(), fullID.getInstance()), INFOMessage.ACTION_TYPE.REMOVE, INFOMessage.ACTION_STATUS.DONE, "EventListener", "The instance deployment is finished");
}
ActionIDManager.removeAction(state.getActionID());
break;
}
case PROCESSING: {
try {
centerCon = new SalsaCenterConnector(SalsaConfiguration.getSalsaCenterEndpointLocalhost(), "/tmp", EngineLogger.logger);
switch (fullID.getUnitType()) {
case docker: {
String dockerPropString = state.getDomainModel();
if (fullID.getActionName().equals("deploy") && dockerPropString != null) {
EngineLogger.logger.debug("Receive docker info:" + dockerPropString);
DockerInfo dockerInfo = (DockerInfo) DomainEntity.fromJson(dockerPropString);
if (dockerInfo == null) {
EngineLogger.logger.error("May get wrong DockerInfo");
} else {
EngineLogger.logger.error("Parsing DockerInfo");
SalsaInstanceDescription_Docker dockersalsainfo = new SalsaInstanceDescription_Docker(dockerInfo.getProvider(), dockerInfo.getInstanceId(), dockerInfo.getName());
dockersalsainfo.setPrivateIp(dockerInfo.getPrivateIp());
dockersalsainfo.setPublicIp(dockerInfo.getPublicIp());
dockersalsainfo.setBaseImage(dockerInfo.getBaseImageID());
dockersalsainfo.setPortmap(dockerInfo.getPortmap());
dockersalsainfo.setState(dockerInfo.getStatus());
centerCon.updateInstanceUnitProperty(fullID.getService(), fullID.getTopology(), fullID.getUnit(), fullID.getInstance(), dockersalsainfo);
}
}
}
}
} catch (SalsaException ex) {
EngineLogger.logger.error("Update node state fail: {}", ex.getMessage(), ex);
}
salsaState = SalsaEntityState.CONFIGURING;
break;
}
default: {
EngineLogger.logger.debug("The state cannot be understood. no state is updated");
break;
}
}
if (centerCon != null) {
try {
centerCon.updateNodeState(fullID.getService(), fullID.getTopology(), fullID.getUnit(), fullID.getInstance(), salsaState, state.getExtra());
} catch (SalsaException ex) {
EngineLogger.logger.error("An error when trying update node state", ex);
}
}
}
}
);
subscriber1.subscribe(SalsaMessageTopic.PIONEER_UPDATE_CONFIGURATION_STATE);
MessageSubscribeInterface subscriber2 = factory.getMessageSubscriber(new SalsaMessageHandling() {
@Override
public void handleMessage(SalsaMessage msg) {
switch (msg.getMsgType()) {
case salsa_pioneerActivated: {
PioneerInfo piInfo = PioneerInfo.fromJson(msg.getPayload());
if (piInfo.getUserName().equals(SalsaConfiguration.getUserName())) {
PioneerManager.addPioneer(piInfo.getId(), piInfo);
SalsaCenterConnector centerCon;
try {
centerCon = new SalsaCenterConnector(SalsaConfiguration.getSalsaCenterEndpointLocalhost(), "/tmp", EngineLogger.logger);
centerCon.updateNodeState(piInfo.getService(), piInfo.getTopology(), piInfo.getUnit(), piInfo.getInstance(), SalsaEntityState.DEPLOYED, "Pioneer is deployed");
} catch (SalsaException ex) {
EngineLogger.logger.error("Cannot connect to SALSA service in localhost: " + SalsaConfiguration.getSalsaCenterEndpointLocalhost() + ". This is a fatal error !", ex);
}
}
break;
}
case elise_conductorActivated: {
ConductorDescription conInfo = ConductorDescription.fromJson(msg.getPayload());
if (conInfo != null) {
EngineLogger.logger.debug("Registering a conductor: ", conInfo.toJson());
EliseManager eliseManager = ((EliseManager) JAXRSClientFactory.create(SalsaConfiguration.getSalsaCenterEndpointLocalhost() + "/rest/elise", EliseManager.class, Collections.singletonList(new JacksonJsonProvider())));
eliseManager.registerConductor(conInfo);
EngineLogger.logger.debug("Registering a conductor DONE !");
}
break;
}
default:
break;
}
}
});
subscriber2.subscribe(SalsaMessageTopic.PIONEER_REGISTER_AND_HEARBEAT);
MessageSubscribeInterface subscribe3 = factory.getMessageSubscriber(new SalsaMessageHandling() {
@Override
public void handleMessage(SalsaMessage msg) {
LoggingEvent event;
try {
String fileName = "./logs/salsa.pioneer.log." + msg.getFromSalsa();
String payload = msg.getPayload();
FileUtils.writeStringToFile(new File(fileName), payload.trim() + "\n", true);
} catch (IOException ex) {
EngineLogger.logger.warn("Cannot create log files for pioneer " + msg.getFromSalsa(), ex);
}
}
});
subscribe3.subscribe(SalsaMessageTopic.PIONEER_LOG);
// try {
// // SYN pioneers
// EngineLogger.logger.debug("Syn pioneers ...");
// (new InternalManagement()).synPioneer();
// } catch (SalsaException ex) {
// EngineLogger.logger.debug("Cannot syn pioneers ... !");
// }
// EngineLogger.logger.info("SalsaEngine started!");
}
private void updateInstanceCapability(SalsaMsgConfigureArtifact confRequest, SalsaMsgConfigureState confState) throws SalsaException {
SalsaCenterConnector centerCon = new SalsaCenterConnector(SalsaConfiguration.getSalsaCenterEndpointLocalhost(), "/tmp", EngineLogger.logger);
CloudService service = centerCon.getUpdateCloudServiceRuntime(confRequest.getService());
ServiceUnit unit = service.getComponentById(confRequest.getUnit());
if (!unit.getCapabilityVars().isEmpty()) { // it need to expose capability, so what ever, just expose
for (String capaVar : unit.getCapabilityVars()) {
String capaValue = null;
if (confState.getCapabilities() != null) {
capaValue = confState.getCapabilities().get(capaVar);
}
if (capaValue == null) {
capaValue = "salsa:localIP";
}
centerCon.updateInstanceUnitCapability(confRequest.getService(), confRequest.getTopology(), confRequest.getUnit(), confRequest.getInstance(), new SalsaCapaReqString(capaVar, capaValue));
}
} else {
EngineLogger.logger.debug("The unit {}/{} do not have capability variable, no capability is added.", unit.getId(), confRequest.getInstance());
}
}
}