/*
* 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.utils;
import generated.oasis.tosca.TDefinitions;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import org.apache.commons.io.FileUtils;
import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
import org.slf4j.Logger;
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.ServiceInstance.Capabilities;
import at.ac.tuwien.dsg.cloud.salsa.common.cloudservice.model.ServiceUnit;
import at.ac.tuwien.dsg.cloud.salsa.common.cloudservice.model.ServiceUnitRelationship;
import at.ac.tuwien.dsg.cloud.salsa.common.cloudservice.model.enums.SalsaEntityState;
import at.ac.tuwien.dsg.cloud.salsa.common.cloudservice.model.enums.SalsaInstanceState;
import at.ac.tuwien.dsg.cloud.salsa.engine.dataprocessing.SalsaXmlDataProcess;
import at.ac.tuwien.dsg.cloud.salsa.engine.capabilityinterface.SalsaEngineServiceIntenal;
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.exceptions.ServicedataProcessingException;
import at.ac.tuwien.dsg.cloud.salsa.tosca.extension.SalsaCapaReqString;
import at.ac.tuwien.dsg.cloud.salsa.tosca.extension.SalsaInstanceDescription_Docker;
import at.ac.tuwien.dsg.cloud.salsa.tosca.extension.SalsaInstanceDescription_VM;
import at.ac.tuwien.dsg.cloud.salsa.engine.dataprocessing.ToscaXmlProcess;
/**
* This class is for connecting to the SalsaCenter. Each of this instance target to a specific service Id, then the serviceId must be provide to the
* construction to ensure that the serviceId is available.
*
* This class is referred to the ControlService of Salsa-center-services
*
* @author Duc-Hung Le
*
*/
public class SalsaCenterConnector {
Logger logger;
String centerRestfulEndpoint;
//String serviceId;
String workingDir;
SalsaEngineServiceIntenal engineInternal;
/**
* Create a connector to Salsa service
*
* @param centerServiceEndpoint The endpoint. E.g: ip:port/path
* @param workingDir
* @param logger Logger
* @throws at.ac.tuwien.dsg.cloud.salsa.engine.exceptions.EngineConnectionException
*/
public SalsaCenterConnector(String centerServiceEndpoint, //String serviceId,
String workingDir, Logger logger) throws EngineConnectionException {
this.centerRestfulEndpoint = centerServiceEndpoint + "/rest";
this.logger = logger;
this.workingDir = workingDir;
this.engineInternal = JAXRSClientFactory.create(this.centerRestfulEndpoint, SalsaEngineServiceIntenal.class);
if (engineInternal == null) {
logger.error("Cannot create SalsaConnector object !");
throw new EngineConnectionException(this.centerRestfulEndpoint);
}
}
/**
* Deregister the service on Salsa Center
*
* @param serviceId
* @return
* @throws at.ac.tuwien.dsg.cloud.salsa.common.interfaces.SalsaException
*/
public String deregisterService(String serviceId) throws SalsaException {
//String url = centerRestfulEndpoint + "/services/" + serviceId;
//logger.debug("Salsa Connector query: " + url);
logger.debug("Deregister service: " + serviceId);
Response res = engineInternal.undeployService(serviceId);
return inputStreamToString((InputStream) res.getEntity());
}
/**
* Set the state of a node instances.
*
* @param serviceId
* @param topologyId The topology of node
* @param nodeId The node
* @param instanceId
* @param extra
* @param state The state
* @return
* @throws at.ac.tuwien.dsg.cloud.salsa.common.interfaces.SalsaException
*/
public String updateNodeState(String serviceId, String topologyId, String nodeId, int instanceId, SalsaEntityState state, String extra) throws SalsaException {
Response res = engineInternal.updateNodeState(serviceId, topologyId, nodeId, instanceId, state.getNodeStateString(), extra);
return res.getEntity().toString();
}
public String updateNodeMetadata(String metadata, String serviceId, String topologyId, String nodeId) throws SalsaException {
logger.debug("inside the center connection to update metadata");
Response res = engineInternal.updateNodeMetadata(metadata, serviceId, topologyId, nodeId);
return res.getEntity().toString();
}
public String removeInstanceMetadata(String serviceId, String nodeId, int instanceId) throws SalsaException {
Response res = engineInternal.removeInstanceMetadata(serviceId, nodeId, instanceId);
return res.getEntity().toString();
}
public SalsaInstanceState getInstanceStatus(String serviceId, String nodeId, int instanceId) throws SalsaException {
Response res = engineInternal.getInstanceStatus(serviceId, nodeId, instanceId);
return SalsaInstanceState.fromString(res.getEntity().toString());
}
// public String updateInstanceState(String serviceId, String topologyId, String nodeId, int instanceId, SalsaEntityState state) {
// System.out.println("SalsaConnector: Updating instance state: " + serviceId +"/" + topologyId + "/" + nodeId +"/"+instanceId+"/"+ state.getNodeStateString());
// Response res = engineInternal.updateInstanceState(serviceId, topologyId, nodeId, instanceId, state.getNodeStateString());
// return inputStreamToString((InputStream)res.getEntity());
// }
public String getInstanceState(String serviceId, String nodeId, int instanceId) throws SalsaException {
System.out.println("getInstanceState: " + serviceId + "/" + nodeId + "/" + instanceId);
CloudService service = getUpdateCloudServiceRuntime(serviceId);
ServiceInstance instance = service.getInstanceById(nodeId, instanceId);
if (instance == null) {
return null;
}
return instance.getState().getNodeStateString();
}
/**
* Get capability value of a instance.
*
* @param topoId
* @param nodeId node of the capability
* @param replica instanceId
* @param capaId ID of capa
* @return capability value
*/
public String getCapabilityValue(String serviceId, String topoId, String nodeId, int replica,
String capaId) throws SalsaException {
System.out.println("Try to get capability value of capaid: " + capaId);
CloudService service = getUpdateCloudServiceRuntime(serviceId);
System.out.println("Checking topo/node/inst-id: " + nodeId + "/" + replica);
ServiceUnit unit = service.getComponentById(nodeId);
// assume that the instance is up.
ServiceInstance rep = unit.getInstancesList().get(0);
System.out.println("Get this instance: " + rep.getInstanceId());
Capabilities capas = rep.getCapabilities();
if (capas != null) {
System.out.println("Capa is not null !");
List<SalsaCapaReqString> capaLst = capas.getCapability();
for (SalsaCapaReqString capa : capaLst) {
System.out.println("Checking capa: " + capa.getId() + " if it equals to " + capaId);
if (capa.getId().equals(capaId)) {
System.out.println("OK, return the capa value: " + capa.getValue());
return capa.getValue();
}
}
} else {
System.out.println("capa is null");
}
return null;
}
/**
* Download latest Tosca.
*
* @return Tosca object
*/
public TDefinitions getToscaDescription(String serviceId) throws ServicedataProcessingException {
try {
String url = centerRestfulEndpoint + "/services/tosca/" + serviceId;
String toscaFile = workingDir + "/" + serviceId;
FileUtils.copyURLToFile(new URL(url), new File(toscaFile));
TDefinitions def = ToscaXmlProcess.readToscaFile(toscaFile);
return def;
} catch (IOException | JAXBException e) {
throw new ServicedataProcessingException(serviceId, e);
}
}
public void addInstanceUnitMetaData(String serviceId, String topologyId,
String nodeId, ServiceInstance data) throws SalsaException {
engineInternal.addInstanceUnitMetaData(data, serviceId, topologyId, nodeId);
}
public void unqueueActions(String serviceID, String nodeId, int instnanceId) throws SalsaException {
engineInternal.unqueueAction(serviceID, nodeId, instnanceId);
}
public void queueActions(String serviceID, String nodeId, int instnanceId, String actionName) throws SalsaException {
engineInternal.queueAction(serviceID, nodeId, instnanceId, actionName);
}
public void addRelationship(String serviceId, String topologyId, ServiceUnitRelationship rela) throws SalsaException {
engineInternal.addRelationship(rela, serviceId, topologyId);
}
/**
* Query the Cloud Service Object, contain all runtime replicas of the service.
*
* @param serviceId
* @return the CloudService instance.
* @throws at.ac.tuwien.dsg.cloud.salsa.common.interfaces.SalsaException
*/
public CloudService getUpdateCloudServiceRuntime(String serviceId) throws SalsaException {
// some time it's false to get the Cloud Service because of error, retry 10 time:
for (int i = 0; i < 10; i++) {
try {
String xml = getUpdateCloudServiceRuntimeXML(serviceId);
if (xml != null) {
return SalsaXmlDataProcess.readSalsaServiceXml(xml);
}
} catch (IOException e) {
if (i < 9) {
logger.debug("Cannot read the service data: " + serviceId + ". Try:" + (i + 1) + "/10");
} else {
throw new ServicedataProcessingException(serviceId, e);
}
} catch (JAXBException e1) {
throw new ServicedataProcessingException(serviceId, e1);
}
sleep(1000);
}
throw new ServicedataProcessingException(serviceId);
}
public void sleep(int minisec) {
try {
Thread.sleep(minisec);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public ServiceUnit getUpdateServiceUnit(String serviceId, String nodeId) throws SalsaException {
CloudService service = getUpdateCloudServiceRuntime(serviceId);
System.out.println("Update service: " + service.getId());
return service.getComponentById(nodeId);
}
/**
* Query the Cloud Service Object, contain all runtime replicas of the service.
*
* @param serviceId
* @return XML String of the object.
* @throws at.ac.tuwien.dsg.cloud.salsa.common.interfaces.SalsaException
*/
public String getUpdateCloudServiceRuntimeXML(String serviceId) throws SalsaException {
Response res = engineInternal.getService(serviceId);
return inputStreamToString((InputStream) res.getEntity());
}
/*
* Get the json contain a list of deployed service Id
*/
public String getServiceListJson() {
String url = centerRestfulEndpoint + "/viewgenerator/cloudservice/json/list";
return queryDataToCenter1(url, HttpVerb.GET, "", "", MediaType.TEXT_PLAIN);
}
/*
* Get the json of running service to generate the tree
*/
public String getserviceruntimejsontree(String serviceId) {
String url = centerRestfulEndpoint + "/viewgenerator/cloudservice/json/compact/" + serviceId;
return queryDataToCenter1(url, HttpVerb.GET, "", "", MediaType.TEXT_PLAIN);
}
/*
* Get the json of running serviceto generate the tree
*/
public String getserviceruntimejsontreecompact(String serviceId) {
String url = centerRestfulEndpoint + "/viewgenerator/cloudservice/json/full/"
+ serviceId;
return queryDataToCenter1(url, HttpVerb.GET, "", "", MediaType.TEXT_PLAIN);
}
public String getRequirementValue(String serviceId, String topologyId,
String nodeId, int instanceId, String reqId) {
String url = centerRestfulEndpoint
+ "/services/" + serviceId
+ "/topologies/" + topologyId
+ "/nodes/" + nodeId
+ "/instances" + instanceId
+ "/requirement/" + reqId;
return queryDataToCenter1(url, HttpVerb.GET, "", "", MediaType.TEXT_PLAIN);
}
/**
* Update the topology for a replica. As the property is AnyType, the property can be any Jaxb object
*
* @param serviceId
* @param topologyId
* @param nodeId
* @param instanceId
* @param property
*/
public void updateInstanceUnitProperty(String serviceId, String topologyId, String nodeId,
int instanceId, Object property) throws SalsaException {
try {
logger.debug("Endpoint is: " + centerRestfulEndpoint);
logger.debug("Start to convert property data for node: " + nodeId);
String data = convertToXML(property);
logger.debug("Updating for node: " + nodeId + ", with data:" + data);
logger.debug("engine health: " + engineInternal.health());
Response res = engineInternal.updateInstanceUnitProperties(data, serviceId, topologyId, nodeId, instanceId);
logger.debug(res.readEntity(String.class));
logger.debug("Update done !");
} catch (JAXBException e) {
logger.debug(e.toString());
}
}
/**
* Update the capability for a node replica.
*
* @param topologyId
* @param nodeId
* @param instanceId
* @param value
*/
public void updateInstanceUnitCapability(String serviceId, String topologyId, String nodeId,
int instanceId, SalsaCapaReqString capa) throws SalsaException {
engineInternal.updateInstanceUnitCapability(capa, serviceId, topologyId, nodeId, instanceId);
}
/**
* Update the node ID counter which is use to calculate the id of multiple instances of one application node
*
* @param serviceId
* @param topoId
* @param nodeId
* @param value
*/
public void updateNodeIdCounter(String serviceId, String topologyId, String nodeId, Integer value) {
// /services/{serviceId}/topologies/{topologyId}/nodes/{nodeId}/instance-counter/{value}
String url = centerRestfulEndpoint
+ "/services/" + serviceId
+ "/topologies/" + topologyId
+ "/nodes/" + nodeId
+ "/instance-counter/" + value;
System.out.println(url);
queryDataToCenter1(url, HttpVerb.POST, value.toString(), "", "");
}
private String queryDataToCenter1(String input_url, HttpVerb method, String data, String type, String accept) {
try {
URL url = new URL(input_url);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(method.toString());
if (accept.equals("")) {
conn.setRequestProperty("Accept", MediaType.TEXT_PLAIN);
} else {
conn.setRequestProperty("Accept", accept);
}
if (type.equals("")) {
conn.setRequestProperty("Type", MediaType.TEXT_PLAIN);
} else {
conn.setRequestProperty("Type", type);
}
logger.debug("Execute a query. URL: " + url + ". Method: " + method + ". Data: " + data + ". Sending type:" + type + ". Recieving type: " + accept);
BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream())));
String output;
String result = "";
while ((output = br.readLine()) != null) {
System.out.println(output);
result += output;
}
conn.disconnect();
return result;
} catch (Exception e) {
logger.error("Error when executing the query. Error: " + e);
return null;
}
}
private String inputStreamToString(InputStream input) {
BufferedReader br = new BufferedReader(new InputStreamReader(input));
try {
String output;
String result = "";
while ((output = br.readLine()) != null) {
//System.out.println(output);
result += output;
}
return result;
} catch (IOException e) {
logger.error("Error when reading the web service: " + e);
return "";
}
}
public String getservicetemplatejsonlist() {
String url = centerRestfulEndpoint + "/app/getservicetemplatejsonlist";
return queryDataToCenter1(url, HttpVerb.GET, "", "", "");
}
public String getartifactjsonlist() {
String url = centerRestfulEndpoint + "/app/getartifactjsonlist";
return queryDataToCenter1(url, HttpVerb.GET, "", "", "");
}
public String removeOneInstance(String serviceId, String nodeId, int instanceId) {
String url = centerRestfulEndpoint + "/services/" + serviceId + "/nodes/" + nodeId + "/instances/" + instanceId;
return queryDataToCenter1(url, HttpVerb.DELETE, "", "", "");
}
public String logMessage(String data) {
logger.debug("Sending log message to salsa-engine: " + data);
engineInternal.logMessage(data);
return "Logged";
}
public static enum HttpVerb {
GET, POST, PUT, DELETE, OTHER;
public static HttpVerb fromString(String method) {
try {
return HttpVerb.valueOf(method.toUpperCase());
} catch (Exception e) {
return OTHER;
}
}
}
private String convertToXML(Object data) throws JAXBException {
JAXBContext jaxbContext = JAXBContext
.newInstance(data.getClass(),
SalsaInstanceDescription_VM.class,
SalsaInstanceDescription_Docker.class,
SalsaCapaReqString.class);
Marshaller msl = jaxbContext.createMarshaller();
msl.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter result = new StringWriter();
msl.marshal(data, result);
return result.toString();
}
}