diff --git a/src/main/java/org/activeeon/morphemic/PAGateway.java b/src/main/java/org/activeeon/morphemic/PAGateway.java index 8863ac09c5fded9edf3d904d43e62e65896e1bf7..6420185d22d6f9eacb7bc437387badb63796421e 100644 --- a/src/main/java/org/activeeon/morphemic/PAGateway.java +++ b/src/main/java/org/activeeon/morphemic/PAGateway.java @@ -32,7 +32,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.security.KeyException; import java.util.*; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; public class PAGateway { @@ -242,6 +242,38 @@ public class PAGateway { schedulerGateway.submit(fXmlFile, variables); } + /** + * Add an EMS deployment to a defined job + * @param authorizationBearer The authorization bearer used by upperware's components to authenticate with each other. Needed by the EMS. + * @return return 0 if the deployment task is properly added. + */ + public int addEmsDeployment(List nodeNames, String authorizationBearer) { + Validate.notNull(authorizationBearer,"The provided authorization bearer cannot be empty"); + em.getTransaction().begin(); + + AtomicInteger failedDeployementIdentification = new AtomicInteger(); + // TODO Info to fetch from a config file and from nodeCandidate, when the feature will be available + String baguetteIp = "ems"; + int baguettePort = 22; + String operatingSystem = "UBUNTU"; + String targetType = "IAAS"; + boolean isUsingHttps = true; + + // For supplied node ... + nodeNames.forEach(node -> { + Deployment deployment = em.find(Deployment.class,node); + PACloud cloud = deployment.getPaCloud(); + EmsDeploymentRequest req = new EmsDeploymentRequest(authorizationBearer, baguetteIp, baguettePort, OperatingSystemFamily.fromValue(operatingSystem), targetType, deployment.getNodeName(), EmsDeploymentRequest.TargetProvider.valueOf(cloud.getCloudProviderName()), deployment.getLocationName(), isUsingHttps, deployment.getNodeName()); + deployment.setEmsDeployment(req); + em.persist(deployment); + }); + + em.getTransaction().commit(); + + LOGGER.info("EMS deployment definition finished."); + return failedDeployementIdentification.get(); + } + /** * Add nodes to the tasks of a defined job * @param nodes An array of nodes information in JSONObject format @@ -451,6 +483,15 @@ public class PAGateway { return deployNodeTask; } + private ScriptTask createEmsDeploymentTask(EmsDeploymentRequest emsDeploymentRequest, String taskNameSuffix, String nodeToken) { + LOGGER.debug("Preparing EMS deployment task"); + ScriptTask emsDeploymentTask = PAFactory.createComplexScriptTaskFromFiles("emsDeployment" + taskNameSuffix,"emsdeploy_mainscript.groovy","groovy","emsdeploy_prescript.sh","bash","emsdeploy_postscript.sh","bash"); + Map variablesMap = emsDeploymentRequest.getWorkflowMap(); + emsDeploymentTask.addGenericInformation("NODE_ACCESS_TOKEN", nodeToken); + emsDeploymentTask.setVariables(variablesMap); + return emsDeploymentTask; + } + /** * Translate a Morphemic task skeleton into a list of ProActive tasks * @param task A Morphemic task skeleton @@ -469,6 +510,8 @@ public class PAGateway { String token = task.getTaskId() + tasksTokens.size(); String suffix = "_" + tasksTokens.size(); scriptTasks.add(createInfraTask(task, deployment, suffix, token)); + // If the infrastructure comes with the deployment of the EMS, we set it up. + Optional.ofNullable(deployment.getEmsDeployment()).ifPresent(emsDeploymentRequest -> scriptTasks.add(createEmsDeploymentTask(emsDeploymentRequest,suffix,token))); LOGGER.debug("Token added: " + token); tasksTokens.add(token); }); diff --git a/src/main/java/org/activeeon/morphemic/application/deployment/PAFactory.java b/src/main/java/org/activeeon/morphemic/application/deployment/PAFactory.java index 2055783cd200a23a7f7767fd14af8d4cfa66c07c..48b5b693091e958f5f1bec8a83237ca36c66e6b6 100644 --- a/src/main/java/org/activeeon/morphemic/application/deployment/PAFactory.java +++ b/src/main/java/org/activeeon/morphemic/application/deployment/PAFactory.java @@ -149,6 +149,38 @@ public class PAFactory { return scriptTask; } + /** + * Create a script task + * @param taskName The name of the task + * @param scriptFileName The script implementation file name + * @param scriptLanguage The script language + * @param preScriptFileName The pre-script implementation file name + * @param preScriptLanguage The pre-script language + * @param postScriptFileName The post-script implementation file name + * @param postScriptLanguage The post-script language + * @return A ProActive ScriptTask instance + */ + public static ScriptTask createComplexScriptTaskFromFiles(String taskName, String scriptFileName, String scriptLanguage, String preScriptFileName, String preScriptLanguage, String postScriptFileName, String postScriptLanguage ) { + ScriptTask scriptTask = new ScriptTask(); + scriptTask.setName(taskName); + TaskScript taskScript = null; + TaskScript taskPreScript = null; + TaskScript taskPostScript = null; + LOGGER.debug("Creating a script task from the files : scriptFileName=" + scriptFileName + " preScriptFileName=" + preScriptFileName + " postScriptFileName=" + postScriptFileName); + try { + taskScript = new TaskScript(createSimpleScriptFromFIle(scriptFileName, scriptLanguage)); + taskPreScript = new TaskScript(createSimpleScriptFromFIle(preScriptFileName, preScriptLanguage)); + taskPostScript = new TaskScript(createSimpleScriptFromFIle(postScriptFileName, postScriptLanguage)); + } catch (InvalidScriptException ie) { + LOGGER.error("ERROR: Task " + taskName + " script not created due to an InvalidScriptException: " + ie.toString()); + } + LOGGER.debug("Bash script task created."); + scriptTask.setScript(taskScript); + scriptTask.setPreScript(taskPreScript); + scriptTask.setPostScript(taskPostScript); + return scriptTask; + } + /** * Create a Groovy node selection script * @param scriptFileName The script implementation file name diff --git a/src/main/java/org/activeeon/morphemic/model/Deployment.java b/src/main/java/org/activeeon/morphemic/model/Deployment.java index cae65332bd2a77d433a0c886cf349d247e458451..783735cb2817c9b0f79aec2fb51fdb8366148690 100644 --- a/src/main/java/org/activeeon/morphemic/model/Deployment.java +++ b/src/main/java/org/activeeon/morphemic/model/Deployment.java @@ -12,11 +12,8 @@ import java.io.Serializable; @Entity @Table(name = "DEPLOYMENT") public class Deployment implements Serializable { - @Id - @GeneratedValue(strategy = GenerationType.AUTO) - @Column(name = "DEPLOYMENT_ID") - private int deploymentId; + @Id @Column(name = "NODE_NAME") private String nodeName; @@ -29,13 +26,15 @@ public class Deployment implements Serializable { @Column(name = "HARDWARE_PROVIDER_ID") private String hardwareProviderId; + @OneToOne(fetch = FetchType.EAGER) + private EmsDeploymentRequest emsDeployment; + @ManyToOne(fetch = FetchType.EAGER) private PACloud paCloud; @Override public String toString() { return "Deployment{" + - "deploymentId=" + deploymentId + ", nodeName='" + nodeName + '\'' + ", locationName='" + locationName + '\'' + ", imageProviderId='" + imageProviderId + '\'' + diff --git a/src/main/java/org/activeeon/morphemic/model/EmsDeploymentRequest.java b/src/main/java/org/activeeon/morphemic/model/EmsDeploymentRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..4c7fc4ce18124b48b016c55a85bab2eb8ded6ff7 --- /dev/null +++ b/src/main/java/org/activeeon/morphemic/model/EmsDeploymentRequest.java @@ -0,0 +1,118 @@ +package org.activeeon.morphemic.model; + +import org.ow2.proactive.scheduler.common.task.TaskVariable; + +import javax.persistence.*; +import java.io.Serializable; +import java.net.InetAddress; +import java.util.HashMap; +import java.util.Map; + +@Entity +@Table(name = "EMSDEPLOYMENTREQUEST") +public class EmsDeploymentRequest implements Serializable { + + public enum TargetType { + vm("IAAS"), + container("PAAS"), + edge("EDGE"), + baremetal("BYON"), + faas("FAAS"); + + TargetType(String adapterVal) { + this.adapterVal = adapterVal; + } + + String adapterVal; + + } + + public enum TargetProvider { + // Amazon Web Service Elastic Compute Cloud + AWSEC2("aws-ec2"), + // Azure VM + AZUREVM("azure"), + // Google CLoud Engine + GCE("gce"), + // OpenStack NOVA + OPENSTACKNOVA("openstack"), + // BYON, to be used for on-premise baremetal & Edge + BYON("byon"); + + TargetProvider(String upperwareVal) { + this.upperwareValue = upperwareVal; + } + + String upperwareValue; + } + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private String id; + + @Column(name = "AUTHORIZATIONBEARER") + private String authorizationBearer; + + @Column(name = "BAGUETTEIP") + private String baguetteIp; + + @Column(name = "BAGUETTEPORT") + private int baguette_port; + + @Column(name = "TARGETOS") + private OperatingSystemFamily targetOs; + + @Column(name = "TARGETTYPE") + private TargetType targetType; + + @Column(name = "TARGETNAME") + private String targetName; + + @Column(name = "TARGETPROVIDER") + private TargetProvider targetProvider; + + @Column(name = "LOCATION") + private String location; + + @Column(name = "ISUSINGHTTP") + private boolean isUsingHttps; + + @Column(name = "NODEID") + private String nodeId; + + + public EmsDeploymentRequest(String authorizationBearer, String baguetteIp, int baguette_port, OperatingSystemFamily targetOs, + String targetType, String targetName, + TargetProvider targetProvider, String location, boolean isUsingHttps, String id) { + this.authorizationBearer = authorizationBearer; + this.baguetteIp = baguetteIp; + this.baguette_port = baguette_port; + this.targetOs = targetOs; + this.targetType = TargetType.valueOf(targetType); + this.targetName = targetName; + this.targetProvider = targetProvider; + this.location = location; + this.isUsingHttps = isUsingHttps; + this.nodeId = id; + } + + /** + * Provide the variable Array to be used in the EMS deployment workflow, structured to be ysed with the submit PA API + * @return The structured map. + */ + public Map getWorkflowMap() { + Map result = new HashMap<>(); + result.put("authorization_bearer", new TaskVariable("authorization_bearer",this.authorizationBearer,"",false)); + result.put("baguette_ip", new TaskVariable("baguette_ip",baguetteIp.toString(),"",false)); + result.put("baguette_port",new TaskVariable("baguette_port",String.valueOf(baguette_port),"",false)); + result.put("target_operating_system",new TaskVariable("target_operating_system",targetOs.name(),"",false)); + result.put("target_type", new TaskVariable("target_type",targetType.name(),"",false)); + result.put("target_name",new TaskVariable("target_name",targetName,"",false)); + result.put("target_provider",new TaskVariable("target_provider",targetProvider.name(),"",false)); + result.put("location", new TaskVariable("location",location,"",false)); + result.put("using_https", new TaskVariable("using_https",isUsingHttps + "","PA:Boolean",false)); + result.put("id",new TaskVariable("id", nodeId,"",false)); + return result; + } + +} diff --git a/src/main/resources/emsdeploy_mainscript.groovy b/src/main/resources/emsdeploy_mainscript.groovy new file mode 100644 index 0000000000000000000000000000000000000000..f294e5dad42eaccdac9af233c4f03ad36d841119 --- /dev/null +++ b/src/main/resources/emsdeploy_mainscript.groovy @@ -0,0 +1,93 @@ +// Calculing privatekey fingerprint +println "== Baguette request preparation" +println "-- Getting parameters" +def password = credentials.containsKey('target_password') ? credentials.get('target_password') : "" +File privateKeyFile = new File("/tmp/ems-keypair") +File ipFile = new File("/tmp/ip.txt") +if (!privateKeyFile.exists()) { + println "ERR: PrivateKey file doesn't exist. Exiting" + return 255 +} +if (!ipFile.exists()) { + println "ERR: Public ip file doesn't exist. Exiting" + return 255 +} +def privateKey = privateKeyFile.text +def ip = ipFile.text + +def fingerprint = '' // TODO: + +println "-- Validating parameters" +// Chacking parameters values +def baguetteBearer = variables.get('authorization_bearer'); +def baguetteIp = variables.get('baguette_ip'); +def baguettePort = variables.get('baguette_port'); +def usingHttps = variables.get("using_https").toBoolean() +def emsUrl = String.format("%s://%s:%s/baguette/registerNode", usingHttps ? "https" : "http",baguetteIp,baguettePort) +def os = variables.get("target_operating_system") +//def ip = variables.get("target_ip") +def port = 22 //variables.get("target_port") +def username = "whoami".execute().text[0..-2] // We capture the output of whoami command & remove the \n char +def type = variables.get("target_type") +def name = variables.get("target_name") +def provider = variables.get("target_provider") +def location = variables.get("location") +def id = variables.get("id") + +// Request preparation +def isInputValid = true; +isInputValid &= (baguetteBearer != null); +isInputValid &= (baguetteIp != null); +isInputValid &= (baguettePort != null); +isInputValid &= (emsUrl != null); +isInputValid &= (os != null); +isInputValid &= (ip != null); +isInputValid &= (port != null); +isInputValid &= (username != null); +isInputValid &= (type != null); +isInputValid &= (name != null); +isInputValid &= (provider != null); + +if (!isInputValid) { + println "ERR: One or many provided parameters are invalid." + return 255; +} + +println "-- payload preparation" +def requestPayload = [:] +requestPayload.operatingSystem = os; +requestPayload.address = ip; +requestPayload.ssh = [:]; +requestPayload.ssh.port = port; +requestPayload.ssh.username = username; +requestPayload.type = type; +requestPayload.name = name; +requestPayload.provider = provider; +requestPayload.timestamp = new Date().getTime(); +requestPayload.location = location; +requestPayload.id = id; + +//if (password != "") { +// println "INFO: Using provided password" +requestPayload.ssh.password = password; +//} +requestPayload.ssh.key = privateKey; +requestPayload.ssh.fingerprint = fingerprint; + +def requestContent = groovy.json.JsonOutput.toJson(requestPayload); +println requestContent +// Request execution +println "== Requesting baguette server for EMS deployment" +def emsConnection = new URL(emsUrl).openConnection(); +emsConnection.setRequestMethod("POST") +emsConnection.setDoOutput(true) +emsConnection.setRequestProperty("Content-Type", "application/json") +emsConnection.setRequestProperty("Authorization", "Bearer " + baguetteBearer) +emsConnection.getOutputStream().write(requestContent.getBytes("UTF-8")); +def responseCode = emsConnection.getResponseCode(); +def responseContent = emsConnection.getInputStream().getText(); + +// Feedback analysis +println "== Obtaining result:" +println ">> Result: Code=" + responseCode + " Content=" + responseContent +result = '{"Code"="' + responseCode + '","Content"="' + responseContent + '"}' \ No newline at end of file diff --git a/src/main/resources/emsdeploy_postscript.sh b/src/main/resources/emsdeploy_postscript.sh new file mode 100644 index 0000000000000000000000000000000000000000..eeec924be6c86909af52eb1f62fe618c00981f7d --- /dev/null +++ b/src/main/resources/emsdeploy_postscript.sh @@ -0,0 +1,2 @@ +echo "== Removing generated keypair" +rm -f /tmp/ems-keypair /tmp/ems-keypair.pup /tmp/ip.txt \ No newline at end of file diff --git a/src/main/resources/emsdeploy_prescript.sh b/src/main/resources/emsdeploy_prescript.sh new file mode 100644 index 0000000000000000000000000000000000000000..2d3e6438bb17f9db5e6c85abde2cf451a56a214f --- /dev/null +++ b/src/main/resources/emsdeploy_prescript.sh @@ -0,0 +1,9 @@ +echo "== Configuring infrastructure resource" +echo "-- Generating EMS-Specific keypair" +ssh-keygen -t rsa -m pkcs8 -f /tmp/ems-keypair +echo "-- Getting the public IP address of the infrastructure" +curl -o /tmp/ip.txt ifconfig.me +echo Found IP: +cat /tmp/ip.txt +echo "-- Adding generated public key" +cat /tmp/ems-keypair.pub >> ~/.ssh/authorized_keys \ No newline at end of file