diff --git a/infrastructures/infrastructure-common/src/main/java/org/ow2/proactive/resourcemanager/nodesource/infrastructure/ConnectorIaasController.java b/infrastructures/infrastructure-common/src/main/java/org/ow2/proactive/resourcemanager/nodesource/infrastructure/ConnectorIaasController.java index fde62055..d6a440f8 100644 --- a/infrastructures/infrastructure-common/src/main/java/org/ow2/proactive/resourcemanager/nodesource/infrastructure/ConnectorIaasController.java +++ b/infrastructures/infrastructure-common/src/main/java/org/ow2/proactive/resourcemanager/nodesource/infrastructure/ConnectorIaasController.java @@ -31,6 +31,8 @@ import org.apache.log4j.Logger; import org.json.JSONObject; +import com.google.common.collect.Lists; + public class ConnectorIaasController { @@ -100,6 +102,25 @@ public String createAzureInfrastructure(String infrastructureId, String clientId return infrastructureId; } + public String createMaasInfrastructure(String infrastructureId, String apiToken, String endpoint, + boolean ignoreCertificateCheck, boolean destroyOnShutdown) { + + String infrastructureJson = ConnectorIaasJSONTransformer.getMaasInfrastructureJSON(infrastructureId, + infrastructureType, + apiToken, + endpoint, + ignoreCertificateCheck, + destroyOnShutdown); + + logger.info("Creating MAAS infrastructure : " + infrastructureJson); + + connectorIaasClient.createInfrastructure(infrastructureId, infrastructureJson); + + logger.info("MAAS infrastructure created"); + + return infrastructureId; + } + public Set createInstances(String infrastructureId, String instanceTag, String image, int numberOfInstances, int cores, int ram) { @@ -135,6 +156,20 @@ public Set createAzureInstances(String infrastructureId, String instance return createInstance(infrastructureId, instanceTag, instanceJson); } + public Set createMaasInstances(String infrastructureId, String instanceTag, String image, + int numberOfInstances, String systemId, String minCpu, String minMem, List scripts) { + + String instanceJson = ConnectorIaasJSONTransformer.getMaasInstanceJSON(instanceTag, + image, + "" + numberOfInstances, + systemId, + minCpu, + minMem, + scripts); + + return createInstance(infrastructureId, instanceTag, instanceJson); + } + public Set createInstancesWithOptions(String infrastructureId, String instanceTag, String image, int numberOfInstances, int cores, int ram, String spotPrice, String securityGroupNames, String subnetId, String macAddresses) { diff --git a/infrastructures/infrastructure-common/src/main/java/org/ow2/proactive/resourcemanager/nodesource/infrastructure/ConnectorIaasJSONTransformer.java b/infrastructures/infrastructure-common/src/main/java/org/ow2/proactive/resourcemanager/nodesource/infrastructure/ConnectorIaasJSONTransformer.java index 8da8c50a..38465f0c 100644 --- a/infrastructures/infrastructure-common/src/main/java/org/ow2/proactive/resourcemanager/nodesource/infrastructure/ConnectorIaasJSONTransformer.java +++ b/infrastructures/infrastructure-common/src/main/java/org/ow2/proactive/resourcemanager/nodesource/infrastructure/ConnectorIaasJSONTransformer.java @@ -33,6 +33,8 @@ import org.json.JSONArray; import org.json.JSONObject; +import com.google.common.collect.Lists; + public class ConnectorIaasJSONTransformer { @@ -85,6 +87,18 @@ public static String getAzureInfrastructureJSON(String infrastructureId, String return infrastructure.toString(); } + public static String getMaasInfrastructureJSON(String infrastructureId, String type, String apiToken, + String endpoint, boolean ignoreCertificateCheck, boolean toBeRemovedOnShutdown) { + JSONObject credentials = new JSONObject().put("password", apiToken).put("allowSelfSignedSSLCertificate", + ignoreCertificateCheck); + JSONObject infrastructure = new JSONObject().put("id", infrastructureId).put("type", type).put("credentials", + credentials); + infrastructure.put("endpoint", endpoint); + infrastructure.put("toBeRemovedOnShutdown", toBeRemovedOnShutdown); + + return infrastructure.toString(); + } + public static String getInstanceJSON(String tag, String image, String number, String cpu, String ram, String spotPrice, String securityGroupNames, String subnetId, String macAddresses) { JSONObject hardware = new JSONObject(); @@ -165,6 +179,31 @@ public static String getAzureInstanceJSON(String instanceTag, String image, Stri return instance.toString(); } + public static String getMaasInstanceJSON(String instanceTag, String image, String number, String systemId, + String minCpu, String minMem, List scripts) { + JSONObject hardware = new JSONObject(); + if (minMem != null && !minMem.isEmpty()) { + hardware.put("minRam", minMem); + } + if (minCpu != null && !minCpu.isEmpty()) { + hardware.put("minCores", minCpu); + } + JSONObject script = new JSONObject(); + script.put("scripts", new JSONArray(scripts)); + JSONObject instance = new JSONObject().put("tag", instanceTag).put("image", image).put("number", number); + if (systemId != null && !systemId.isEmpty()) { + instance.put("id", systemId); + } + if (hardware.length() > 0) { + instance.put("hardware", hardware); + } + if (script.length() > 0) { + instance.put("initScript", script); + } + + return instance.toString(); + } + public static String getInstanceJSONWithPublicKeyAndScripts(String tag, String image, String number, String publicKeyName, String type, List scripts) { JSONObject credentials = new JSONObject(); diff --git a/infrastructures/infrastructure-maas/build.gradle b/infrastructures/infrastructure-maas/build.gradle new file mode 100644 index 00000000..9bcd7355 --- /dev/null +++ b/infrastructures/infrastructure-maas/build.gradle @@ -0,0 +1,46 @@ +apply plugin: 'java' +apply plugin: 'com.github.johnrengelman.shadow' + +shadowJar { + +} + +sourceCompatibility = JavaVersion.VERSION_1_7 +targetCompatibility = JavaVersion.VERSION_1_7 + + +dependencies { + compile project(':infrastructures:infrastructure-common') + compile "org.ow2.proactive:rm-server:${rmVersion}" + compile "org.objectweb.proactive:programming-core:${programmingVersion}" + + testCompile files("${System.properties['java.home']}/../lib/tools.jar") + testCompile 'junit:junit:4.12' + testCompile 'org.hamcrest:hamcrest-junit:2.0.0.0' + testCompile 'org.mockito:mockito-core:1.10.19' + +} + +configurations { + shadowJar + + runtime.extendsFrom = [compile] + runtime.exclude module: 'rm-server' + runtime.exclude module: 'programming-core' + runtime.exclude module: 'guava' + runtime.exclude module: 'log4j' + + testRuntime.extendsFrom = [testCompile] +} + + +jar { + manifest { + attributes("Implementation-Title": "ProActive", + "Implementation-Version": version, + "Specification-Version": version, + "Implementation-Vendor": "Activeeon - OASIS - INRIA Sophia Antipolis", + "Implementation-URL": "http://proactive.inria.fr" + ) + } +} diff --git a/infrastructures/infrastructure-maas/src/main/java/org/ow2/proactive/resourcemanager/nodesource/infrastructure/MaasInfrastructure.java b/infrastructures/infrastructure-maas/src/main/java/org/ow2/proactive/resourcemanager/nodesource/infrastructure/MaasInfrastructure.java new file mode 100644 index 00000000..fa230f4a --- /dev/null +++ b/infrastructures/infrastructure-maas/src/main/java/org/ow2/proactive/resourcemanager/nodesource/infrastructure/MaasInfrastructure.java @@ -0,0 +1,378 @@ +/* + * ProActive Parallel Suite(TM): + * The Open Source library for parallel and distributed + * Workflows & Scheduling, Orchestration, Cloud Automation + * and Big Data Analysis on Enterprise Grids & Clouds. + * + * Copyright (c) 2007 - 2017 ActiveEon + * Contact: contact@activeeon.com + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation: version 3 of + * the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * If needed, contact us to obtain a release under GPL Version 2 or 3 + * or a different license than the AGPL. + */ +package org.ow2.proactive.resourcemanager.nodesource.infrastructure; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.log4j.Logger; +import org.objectweb.proactive.core.ProActiveException; +import org.objectweb.proactive.core.node.Node; +import org.ow2.proactive.resourcemanager.exception.RMException; +import org.ow2.proactive.resourcemanager.nodesource.common.Configurable; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + + +public class MaasInfrastructure extends InfrastructureManager { + + private static final Logger LOGGER = Logger.getLogger(MaasInfrastructure.class); + + public static final String INSTANCE_ID_NODE_PROPERTY = "instanceId"; + + public static final String INFRASTRUCTURE_TYPE = "maas"; + + private static final String DEFAULT_RM_HOSTNAME = "localhost"; + + private final static int PARAMETERS_NUMBER = 13; + + // Indexes of parameters + private final static int API_TOKEN_INDEX = 0; + + private final static int ENDPOINT_INDEX = 1; + + private final static int RM_HOSTNAME_INDEX = 2; + + private final static int CONNECTOR_IAAS_URL_INDEX = 3; + + private final static int IMAGE_INDEX = 4; + + private final static int SYSTEM_ID_INDEX = 5; + + private final static int MIN_CPU_INDEX = 6; + + private final static int MIN_MEM_INDEX = 7; + + private final static int NUMBER_OF_INSTANCES_INDEX = 8; + + private final static int NUMBER_OF_NODES_PER_INSTANCE_INDEX = 9; + + private final static int DOWNLOAD_COMMAND_INDEX = 10; + + private final static int IGNORE_CERTIFICATE_CHECK_INDEX = 11; + + private final static int ADDITIONAL_PROPERTIES_INDEX = 12; + + // Command lines patterns + private static final CharSequence RM_HOSTNAME_PATTERN = ""; + + private static final CharSequence RM_URL_PATTERN = ""; + + private static final CharSequence COMMUNICATION_PROTOCOL_PATTERN = ""; + + private static final CharSequence INSTANCE_ID_PATTERN = ""; + + private static final CharSequence ADDITIONAL_PROPERTIES_PATTERN = ""; + + private static final CharSequence NODESOURCE_NAME_PATTERN = ""; + + private static final CharSequence NUMBER_OF_NODES_PATTERN = ""; + + // Command lines definition + private static final String POWERSHELL_DOWNLOAD_CMD = "powershell -command \"& { (New-Object Net.WebClient).DownloadFile('http://" + + RM_HOSTNAME_PATTERN + ":8080/rest/node.jar" + + "', 'node.jar') }\""; + + private static final String WGET_DOWNLOAD_CMD = "wget -nv http://" + RM_HOSTNAME_PATTERN + ":8080/rest/node.jar"; + + private static final String START_NODE_CMD = "java -jar node.jar -Dproactive.communication.protocol=" + + COMMUNICATION_PROTOCOL_PATTERN + " -Dproactive.pamr.router.address=" + + RM_HOSTNAME_PATTERN + " -D" + INSTANCE_ID_NODE_PROPERTY + "=" + + INSTANCE_ID_PATTERN + " " + ADDITIONAL_PROPERTIES_PATTERN + " -r " + + RM_URL_PATTERN + " -s " + NODESOURCE_NAME_PATTERN + " -w " + + NUMBER_OF_NODES_PATTERN; + + private static final String START_NODE_FALLBACK_CMD = "java -jar node.jar -D" + INSTANCE_ID_NODE_PROPERTY + "=" + + INSTANCE_ID_PATTERN + " " + ADDITIONAL_PROPERTIES_PATTERN + + " -r " + RM_URL_PATTERN + " -s " + NODESOURCE_NAME_PATTERN + + " -w " + NUMBER_OF_NODES_PATTERN; + + @Configurable(description = "The MAAS API token") + protected String apiToken = null; + + @Configurable(description = "The MAAS endpoint") + protected String endpoint = null; + + @Configurable(description = "Resource manager hostname or ip address") + protected String rmHostname = generateDefaultRMHostname(); + + @Configurable(description = "Connector-iaas URL") + protected String connectorIaasURL = "http://" + generateDefaultRMHostname() + ":8080/connector-iaas"; + + @Configurable(description = "Image to deploy (if not specify the default image of the platform will be used)") + protected String image = null; + + @Configurable(description = "System ID of the Machine to deploy (if set, minimal resources constraints below will be ignored and only a single machine will be deployed)") + protected String systemId = null; + + @Configurable(description = "Minimal amount of CPU") + protected String vmMinCpu = null; + + @Configurable(description = "Minimal amount of Memory (MB)") + protected String vmMinMem = null; + + @Configurable(description = "Total instance to create") + protected int numberOfInstances = 1; + + @Configurable(description = "Total nodes to create per instance") + protected int numberOfNodesPerInstance = 1; + + @Configurable(description = "Command used to download the worker jar") + protected String downloadCommand = generateDefaultDownloadCommand(); + + @Configurable(description = "Optional flag to specify if untrusted (eg. self-signed) certificate are allowed") + protected boolean ignoreCertificateCheck = false; + + @Configurable(description = "Additional Java command properties (e.g. \"-Dpropertyname=propertyvalue\")") + //protected String additionalProperties = "-Dproactive.useIPaddress=true -Dproactive.net.public_address=$(wget -qO- ipinfo.io/ip) -Dproactive.communication.protocol=pnp -Dproactive.pnp.port=64738 -Dproactive.pamr.router.port="; + protected String additionalProperties = "-Dproactive.useIPaddress=true"; + + protected ConnectorIaasController connectorIaasController = null; + + protected final Map> nodesPerInstances; + + /** + * Default constructor + */ + public MaasInfrastructure() { + nodesPerInstances = Maps.newConcurrentMap(); + } + + @Override + public void configure(Object... parameters) { + + LOGGER.info("Validating parameters : " + Arrays.toString(parameters)); + validate(parameters); + + this.apiToken = getParameter(parameters, API_TOKEN_INDEX); + this.endpoint = getParameter(parameters, ENDPOINT_INDEX); + this.rmHostname = getParameter(parameters, RM_HOSTNAME_INDEX); + this.connectorIaasURL = getParameter(parameters, CONNECTOR_IAAS_URL_INDEX); + this.image = getParameter(parameters, IMAGE_INDEX); + this.systemId = getParameter(parameters, SYSTEM_ID_INDEX); + this.vmMinCpu = getParameter(parameters, MIN_CPU_INDEX); + this.vmMinMem = getParameter(parameters, MIN_MEM_INDEX); + this.numberOfInstances = Integer.parseInt(getParameter(parameters, NUMBER_OF_INSTANCES_INDEX)); + this.numberOfNodesPerInstance = Integer.parseInt(getParameter(parameters, NUMBER_OF_NODES_PER_INSTANCE_INDEX)); + this.downloadCommand = getParameter(parameters, DOWNLOAD_COMMAND_INDEX); + this.ignoreCertificateCheck = Boolean.parseBoolean(getParameter(parameters, IGNORE_CERTIFICATE_CHECK_INDEX)); + this.additionalProperties = getParameter(parameters, ADDITIONAL_PROPERTIES_INDEX); + + connectorIaasController = new ConnectorIaasController(connectorIaasURL, INFRASTRUCTURE_TYPE); + } + + private String getParameter(Object[] parameters, int index) { + return parameters[index].toString().trim(); + } + + private void validate(Object[] parameters) { + if (parameters == null || parameters.length < PARAMETERS_NUMBER) { + throw new IllegalArgumentException("Invalid parameters for AzureInfrastructure creation"); + } + + throwIllegalArgumentExceptionIfNull(parameters[API_TOKEN_INDEX], "MAAS API apiToken must be specified"); + throwIllegalArgumentExceptionIfNull(parameters[ENDPOINT_INDEX], "MAAS endpoint must be specified"); + throwIllegalArgumentExceptionIfNull(parameters[RM_HOSTNAME_INDEX], + "The Resource manager hostname must be specified"); + throwIllegalArgumentExceptionIfNull(parameters[CONNECTOR_IAAS_URL_INDEX], + "The connector-iaas URL must be specified"); + if (parameters[MIN_CPU_INDEX] == null && parameters[MIN_MEM_INDEX] == null) { + throwIllegalArgumentExceptionIfNull(parameters[SYSTEM_ID_INDEX], + "Wether minimal resources (CPU and/or Memory) or a system ID must be specified."); + } + throwIllegalArgumentExceptionIfNull(parameters[NUMBER_OF_INSTANCES_INDEX], + "The number of instances to create must be specified"); + throwIllegalArgumentExceptionIfNull(parameters[NUMBER_OF_NODES_PER_INSTANCE_INDEX], + "The number of nodes per instance to deploy must be specified"); + throwIllegalArgumentExceptionIfNull(parameters[DOWNLOAD_COMMAND_INDEX], + "The download node.jar command must be specified"); + + if (parameters[ADDITIONAL_PROPERTIES_INDEX] == null) { + parameters[ADDITIONAL_PROPERTIES_INDEX] = ""; + } + } + + private void throwIllegalArgumentExceptionIfNull(Object parameter, String error) { + if (parameter == null) { + throw new IllegalArgumentException(error); + } + } + + @Override + public void acquireNode() { + + connectorIaasController.waitForConnectorIaasToBeUP(); + + // Create MAAS infrastructure & instances + connectorIaasController.createMaasInfrastructure(getInfrastructureId(), + apiToken, + endpoint, + ignoreCertificateCheck, + false); + String instanceTag = getInfrastructureId(); + Set instancesIds; + instancesIds = connectorIaasController.createMaasInstances(getInfrastructureId(), + instanceTag, + image, + numberOfInstances, + systemId, + vmMinCpu, + vmMinMem, + Lists.newArrayList(generateScriptWithoutInstanceId())); + LOGGER.info("Instances ids created : " + instancesIds); + } + + @Override + public void acquireAllNodes() { + acquireNode(); + } + + @Override + public void removeNode(Node node) throws RMException { + + String instanceId = getInstanceIdProperty(node); + + try { + node.getProActiveRuntime().killNode(node.getNodeInformation().getName()); + + } catch (Exception e) { + LOGGER.warn("Unable to remove the node '" + node.getNodeInformation().getName() + "' with error: " + e); + } + + synchronized (this) { + nodesPerInstances.get(instanceId).remove(node.getNodeInformation().getName()); + LOGGER.info("Removed node : " + node.getNodeInformation().getName()); + + if (nodesPerInstances.get(instanceId).isEmpty()) { + connectorIaasController.terminateInstance(getInfrastructureId(), instanceId); + nodesPerInstances.remove(instanceId); + LOGGER.info("Removed instance : " + instanceId); + } + } + } + + @Override + public void notifyAcquiredNode(Node node) throws RMException { + + String instanceId = getInstanceIdProperty(node); + + synchronized (this) { + if (!nodesPerInstances.containsKey(instanceId)) { + nodesPerInstances.put(instanceId, new HashSet()); + } + nodesPerInstances.get(instanceId).add(node.getNodeInformation().getName()); + } + } + + @Override + public String getDescription() { + return "Handles nodes from Microsoft Azure."; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return getDescription(); + } + + private String generateDefaultRMHostname() { + try { + // best effort, may not work for all machines + return InetAddress.getLocalHost().getCanonicalHostName(); + } catch (UnknownHostException e) { + LOGGER.warn("Unable to retrieve local canonical hostname with error: " + e); + return DEFAULT_RM_HOSTNAME; + } + } + + private String generateScriptWithoutInstanceId() { + String startNodeCommand = generateStartNodeCommand(); + if (System.getProperty("os.name").contains("Windows")) { + return this.downloadCommand + "; Start-Process -NoNewWindow " + startNodeCommand; + } else { + return "/bin/bash -c '" + this.downloadCommand + "; nohup " + startNodeCommand + " &'"; + } + } + + private String generateDefaultDownloadCommand() { + if (System.getProperty("os.name").contains("Windows")) { + return POWERSHELL_DOWNLOAD_CMD.replace(RM_HOSTNAME_PATTERN, this.rmHostname); + } else { + return WGET_DOWNLOAD_CMD.replace(RM_HOSTNAME_PATTERN, this.rmHostname); + } + } + + private String generateStartNodeCommand() { + try { + String communicationProtocol = rmUrl.split(":")[0]; + String startNodeCommand = START_NODE_CMD.replace(COMMUNICATION_PROTOCOL_PATTERN, communicationProtocol) + .replace(RM_HOSTNAME_PATTERN, rmHostname) + .replace(ADDITIONAL_PROPERTIES_PATTERN, additionalProperties) + .replace(RM_URL_PATTERN, rmUrl) + .replace(NODESOURCE_NAME_PATTERN, nodeSource.getName()) + .replace(NUMBER_OF_NODES_PATTERN, + String.valueOf(numberOfNodesPerInstance)); + if (systemId != null && !systemId.isEmpty()) { + startNodeCommand = startNodeCommand.replace(INSTANCE_ID_PATTERN, systemId); + } + return startNodeCommand; + + } catch (Exception e) { + LOGGER.error("Exception when generating the command, fallback on default value", e); + String startNodeFallbackCommand = START_NODE_FALLBACK_CMD.replace(ADDITIONAL_PROPERTIES_PATTERN, + additionalProperties) + .replace(RM_URL_PATTERN, rmUrl) + .replace(NODESOURCE_NAME_PATTERN, + nodeSource.getName()) + .replace(NUMBER_OF_NODES_PATTERN, + String.valueOf(numberOfNodesPerInstance)); + if (systemId != null && !systemId.isEmpty()) { + startNodeFallbackCommand = startNodeFallbackCommand.replace(INSTANCE_ID_PATTERN, systemId); + } + return startNodeFallbackCommand; + } + } + + private String getInstanceIdProperty(Node node) throws RMException { + try { + return node.getProperty(INSTANCE_ID_NODE_PROPERTY); + } catch (ProActiveException e) { + throw new RMException(e); + } + } + + private String getInfrastructureId() { + return nodeSource.getName().trim().replace(" ", "_").toLowerCase(); + } + +} diff --git a/infrastructures/infrastructure-maas/src/test/java/org/ow2/proactive/resourcemanager/nodesource/infrastructure/MaasInfrastructureTest.java b/infrastructures/infrastructure-maas/src/test/java/org/ow2/proactive/resourcemanager/nodesource/infrastructure/MaasInfrastructureTest.java new file mode 100644 index 00000000..ee9626d4 --- /dev/null +++ b/infrastructures/infrastructure-maas/src/test/java/org/ow2/proactive/resourcemanager/nodesource/infrastructure/MaasInfrastructureTest.java @@ -0,0 +1,292 @@ +/* + * ProActive Parallel Suite(TM): + * The Open Source library for parallel and distributed + * Workflows & Scheduling, Orchestration, Cloud Automation + * and Big Data Analysis on Enterprise Grids & Clouds. + * + * Copyright (c) 2007 - 2017 ActiveEon + * Contact: contact@activeeon.com + * + * This library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation: version 3 of + * the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * If needed, contact us to obtain a release under GPL Version 2 or 3 + * or a different license than the AGPL. + */ +package org.ow2.proactive.resourcemanager.nodesource.infrastructure; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.objectweb.proactive.core.ProActiveException; +import org.objectweb.proactive.core.node.Node; +import org.objectweb.proactive.core.node.NodeInformation; +import org.objectweb.proactive.core.runtime.ProActiveRuntime; +import org.ow2.proactive.resourcemanager.exception.RMException; +import org.ow2.proactive.resourcemanager.nodesource.NodeSource; +import org.python.google.common.collect.Sets; + +import com.google.common.collect.Lists; + + +public class MaasInfrastructureTest { + + private MaasInfrastructure maasInfrastructure; + + @Mock + private ConnectorIaasController connectorIaasController; + + @Mock + private NodeSource nodeSource; + + @Mock + private Node node; + + @Mock + private ProActiveRuntime proActiveRuntime; + + @Mock + private NodeInformation nodeInformation; + + @Before + public void init() { + MockitoAnnotations.initMocks(this); + maasInfrastructure = new MaasInfrastructure(); + } + + @Test + public void testDefaultValuesOfAllParameters() { + assertThat(maasInfrastructure.endpoint, is(nullValue())); + assertThat(maasInfrastructure.apiToken, is(nullValue())); + assertThat(maasInfrastructure.rmHostname, is(not(nullValue()))); + assertThat(maasInfrastructure.connectorIaasURL, + is("http://" + maasInfrastructure.rmHostname + ":8080/connector-iaas")); + assertThat(maasInfrastructure.image, is(nullValue())); + assertThat(maasInfrastructure.systemId, is(nullValue())); + assertThat(maasInfrastructure.vmMinMem, is(nullValue())); + assertThat(maasInfrastructure.vmMinCpu, is(nullValue())); + assertThat(maasInfrastructure.ignoreCertificateCheck, is(false)); + assertThat(maasInfrastructure.numberOfInstances, is(1)); + assertThat(maasInfrastructure.numberOfNodesPerInstance, is(1)); + if (System.getProperty("os.name").contains("Windows")) { + assertThat(maasInfrastructure.downloadCommand, + is("powershell -command \"& { (New-Object Net.WebClient).DownloadFile('http://" + + maasInfrastructure.rmHostname + ":8080/rest/node.jar', 'node.jar') }\"")); + } else { + assertThat(maasInfrastructure.downloadCommand, + is("wget -nv http://" + maasInfrastructure.rmHostname + ":8080/rest/node.jar")); + + } + assertThat(maasInfrastructure.additionalProperties, is("-Dproactive.useIPaddress=true")); + } + + @Test + public void testConfigureDoNotThrowIllegalArgumentExceptionWithValidParameters() { + when(nodeSource.getName()).thenReturn("Node source Name"); + maasInfrastructure.nodeSource = nodeSource; + + try { + maasInfrastructure.configure("apiToken", + "endpoint", + "test.activeeon.com", + "http://localhost:8088/connector-iaas", + "image", + "systemId", + "minCpu", + "minMem", + "2", + "3", + "wget -nv test.activeeon.com/rest/node.jar", + true, + "-Dnew=value"); + Assert.assertTrue(Boolean.TRUE); + } catch (IllegalArgumentException e) { + fail("NPE not thrown"); + } + } + + @Test(expected = IllegalArgumentException.class) + public void tesConfigureNotEnoughParameters() { + + when(nodeSource.getName()).thenReturn("Node source Name"); + maasInfrastructure.nodeSource = nodeSource; + + maasInfrastructure.configure("apiToken", "really:secret:information"); + } + + @Test(expected = IllegalArgumentException.class) + public void tesConfigureWithANullArgument() { + + when(nodeSource.getName()).thenReturn("Node source Name"); + maasInfrastructure.nodeSource = nodeSource; + maasInfrastructure.configure(null, + "endpoint", + "test.activeeon.com", + "http://localhost:8088/connector-iaas", + "image", + "systemId", + "minCpu", + "minMem", + "2", + "3", + "wget -nv test.activeeon.com/rest/node.jar", + true, + "-Dnew=value"); + } + + @Test + public void testAcquiringTwoNodesByRegisteringInfrastructureCreatingInstancesAndInjectingScriptOnThem() { + + when(nodeSource.getName()).thenReturn("Node source Name"); + maasInfrastructure.nodeSource = nodeSource; + + maasInfrastructure.configure("apiToken", + "endpoint", + "test.activeeon.com", + "http://localhost:8088/connector-iaas", + "image", + "systemId", + "minCpu", + "minMem", + "2", + "3", + "wget -nv test.activeeon.com/rest/node.jar", + false, + "-Dnew=value"); + + maasInfrastructure.connectorIaasController = connectorIaasController; + + maasInfrastructure.rmUrl = "http://test.activeeon.com"; + + when(connectorIaasController.createMaasInfrastructure("node_source_name", + "apiToken", + "endpoint", + false, + false)).thenReturn("node_source_name"); + + when(connectorIaasController.createMaasInstances("node_source_name", + "node_source_name", + "image", + 2, + "systemId", + "minCpu", + "minMem", + Lists.newArrayList("/bin/bash -c 'wget -nv test.activeeon.com/rest/node.jar; nohup java -jar node.jar -Dproactive.communication.protocol=http -Dproactive.pamr.router.address=test.activeeon.com -DinstanceId=systemId -Dnew=value -r http://test.activeeon.com -s Node source Name -w 3 &'"))).thenReturn(Sets.newHashSet("123", + "456")); + + maasInfrastructure.acquireNode(); + + verify(connectorIaasController, times(1)).waitForConnectorIaasToBeUP(); + + verify(connectorIaasController).createMaasInfrastructure("node_source_name", + "apiToken", + "endpoint", + false, + false); + + verify(connectorIaasController).createMaasInstances("node_source_name", + "node_source_name", + "image", + 2, + "systemId", + "minCpu", + "minMem", + Lists.newArrayList("/bin/bash -c 'wget -nv test.activeeon.com/rest/node.jar; nohup java -jar node.jar -Dproactive.communication.protocol=http -Dproactive.pamr.router.address=test.activeeon.com -DinstanceId=systemId -Dnew=value -r http://test.activeeon.com -s Node source Name -w 3 &'")); + } + + @Test + public void testRemoveNode() throws ProActiveException, RMException { + when(nodeSource.getName()).thenReturn("Node source Name"); + maasInfrastructure.nodeSource = nodeSource; + + maasInfrastructure.configure("apiToken", + "endpoint", + "test.activeeon.com", + "http://localhost:8088/connector-iaas", + "image", + "systemId", + "minCpu", + "minMem", + "2", + "3", + "wget -nv test.activeeon.com/rest/node.jar", + true, + "-Dnew=value"); + + maasInfrastructure.connectorIaasController = connectorIaasController; + + when(node.getProperty(MaasInfrastructure.INSTANCE_ID_NODE_PROPERTY)).thenReturn("123"); + + when(node.getNodeInformation()).thenReturn(nodeInformation); + + when(node.getProActiveRuntime()).thenReturn(proActiveRuntime); + + when(nodeInformation.getName()).thenReturn("nodename"); + + maasInfrastructure.nodesPerInstances.put("123", Sets.newHashSet("nodename")); + + maasInfrastructure.removeNode(node); + + verify(proActiveRuntime).killNode("nodename"); + + verify(connectorIaasController).terminateInstance("node_source_name", "123"); + + assertThat(maasInfrastructure.nodesPerInstances.isEmpty(), is(true)); + + } + + @Test + public void testThatNotifyAcquiredNodeMethodFillsTheNodesMapCorrectly() throws ProActiveException, RMException { + + when(nodeSource.getName()).thenReturn("Node source Name"); + maasInfrastructure.nodeSource = nodeSource; + maasInfrastructure.configure("apiToken", + "endpoint", + "test.activeeon.com", + "http://localhost:8088/connector-iaas", + "image", + "systemId", + "minCpu", + "minMem", + "2", + "3", + "wget -nv test.activeeon.com/rest/node.jar", + true, + "-Dnew=value"); + + maasInfrastructure.connectorIaasController = connectorIaasController; + + when(node.getProperty(MaasInfrastructure.INSTANCE_ID_NODE_PROPERTY)).thenReturn("123"); + + when(node.getNodeInformation()).thenReturn(nodeInformation); + + when(nodeInformation.getName()).thenReturn("nodename"); + + maasInfrastructure.notifyAcquiredNode(node); + + assertThat(maasInfrastructure.nodesPerInstances.get("123").isEmpty(), is(false)); + assertThat(maasInfrastructure.nodesPerInstances.get("123").size(), is(1)); + assertThat(maasInfrastructure.nodesPerInstances.get("123").contains("nodename"), is(true)); + } +} diff --git a/settings.gradle b/settings.gradle index 72cee923..f8f3d4fc 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,4 +3,5 @@ include 'infrastructures:infrastructure-aws-ec2' include 'infrastructures:infrastructure-openstack' include 'infrastructures:infrastructure-vmware' include 'infrastructures:infrastructure-azure' +include 'infrastructures:infrastructure-maas' include 'infrastructures:infrastructure-common'