/** * Copyright 2014-2017 Linagora, Université Joseph Fourier, Floralis * * The present code is developed in the scope of the joint LINAGORA - * Université Joseph Fourier - Floralis research program and is designated * as a "Result" pursuant to the terms and conditions of the LINAGORA * - Université Joseph Fourier - Floralis research program. Each copyright * holder of Results enumerated here above fully & independently holds complete * ownership of the complete Intellectual Property rights applicable to the whole * of said Results, and may freely exploit it in any manner which does not infringe * the moral rights of the other copyright holders. * * 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 net.roboconf.target.occi.internal; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import java.util.Map; import java.util.logging.Logger; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; import net.roboconf.core.utils.Utils; import net.roboconf.target.api.TargetException; /** * Utilitary class to create and manage VMs using OCCI. * @author Pierre-Yves Gibello - Linagora */ public class OcciVMUtils { public static Logger logger = Logger.getLogger(OcciVMUtils.class.getName()); /** * Creates a VM (OCCI / VMWare) using HTTP rendering. * @param hostIpPort IP and port of OCCI server (eg. "172.16.225.91:8080") * @param id Unique VM ID * @param template VM image ID (null means no image specified) * @param title VM title * @param summary VM summary * @param userData User data for the VM * @param user VM credentials (user name) * @param password VM credentials (password) * @param config A map of parameters (eg. mixin attributes) * @return The VM ID */ public static String createVM(String hostIpPort, String id, String template, String title, String summary, String userData, String user, String password, Map<String, String> config) throws TargetException { //TODO This is a HACK for CloudAutomation APIs. Expecting interoperable implementation ! if(hostIpPort.contains("multi-language-connector")) { return createCloudAutomationVM(hostIpPort, template, title, userData, config, false); } String ret = null; URL url = null; try { CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL)); url = new URL("http://" + hostIpPort + "/compute/" + id); } catch (MalformedURLException e) { throw new TargetException(e); } if(Utils.isEmptyOrWhitespaces(title)) title = "Roboconf"; if(Utils.isEmptyOrWhitespaces(summary)) summary = "Generated by Roboconf"; HttpURLConnection httpURLConnection = null; DataInputStream in = null; try { httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setRequestMethod("PUT"); httpURLConnection.setRequestProperty("Content-Type", "text/occi"); httpURLConnection.setRequestProperty("Accept", "*/*"); StringBuffer category = new StringBuffer("compute; scheme=\"http://schemas.ogf.org/occi/infrastructure#\"; class=\"kind\""); if(template != null) { category.append(", medium; scheme=\"http://schemas.ogf.org/occi/infrastructure/compute/template/1.1#\"; class=\"mixin\"" + ", vmimage; scheme=\"http://occiware.org/occi/infrastructure/crtp/backend#vmimage\"; class=\"mixin\"" + ", vmwarefolders; scheme=\"http://occiware.org/occi/infrastructure/crtp/backend#vmwarefolders\"; class=\"mixin\"" + ", user_data; scheme=\"http://occiware.org/occi/infrastructure/compute#user_data\"; class=\"mixin\"" + ", credential; scheme=\"http://occiware.org/occi/infrastructure/crtp/backend#credential\"; class=\"mixin\""); // OLD API: comment 4 lines above and uncomment 2 below... //+ ", vmaddon; scheme=\"http://occiware.org/occi/vmwarecrtp#\"; class=\"mixin\"" //+ ", vmwarefolders; scheme=\"http://occiware.org/occi/vmwarecrtp#\"; class=\"mixin\""); } category.append(";"); httpURLConnection.setRequestProperty("Category", category.toString()); httpURLConnection.addRequestProperty("X-OCCI-Attribute", "occi.core.id=\"" + id + "\""); httpURLConnection.addRequestProperty("X-OCCI-Attribute", "occi.core.title=\"" + title + "\""); httpURLConnection.addRequestProperty("X-OCCI-Attribute", "occi.core.summary=\"" + summary + "\""); httpURLConnection.addRequestProperty("X-OCCI-Attribute", "occi.compute.architecture=\"x64\""); httpURLConnection.addRequestProperty("X-OCCI-Attribute", "occi.compute.cores=2"); httpURLConnection.addRequestProperty("X-OCCI-Attribute", "occi.compute.memory=2"); httpURLConnection.addRequestProperty("X-OCCI-Attribute", "occi.compute.state=\"active\""); if(template != null) { httpURLConnection.addRequestProperty("X-OCCI-Attribute", "imagename=\"" + template + "\""); httpURLConnection.addRequestProperty("X-OCCI-Attribute", "datacentername=\"" + config.get(VmwareFoldersMixin.DATACENTERNAME) + "\""); httpURLConnection.addRequestProperty("X-OCCI-Attribute", "datastorename=\"" + config.get(VmwareFoldersMixin.DATASTORENAME) + "\""); httpURLConnection.addRequestProperty("X-OCCI-Attribute", "clustername=\"" + config.get(VmwareFoldersMixin.CLUSTERNAME) + "\""); httpURLConnection.addRequestProperty("X-OCCI-Attribute", "hostsystemname=\"" + config.get(VmwareFoldersMixin.HOSTSYSTEMNAME) + "\""); httpURLConnection.addRequestProperty("X-OCCI-Attribute", "inventorypath=\"" + config.get(VmwareFoldersMixin.INVENTORYPATH) + "\""); } if(! Utils.isEmptyOrWhitespaces(userData)) { String userDataScript = "printf \'" + userData.replaceAll("\n\r", "\\\\n") .replaceAll("\n", "\\\\n") .replaceAll(System.lineSeparator(), "\\\\n") + "\' > /tmp/roboconf.properties"; httpURLConnection.addRequestProperty("X-OCCI-Attribute", "occi.compute.userdata=\"" + userDataScript + "\""); if(Utils.isEmptyOrWhitespaces(user)) user=""; httpURLConnection.addRequestProperty("X-OCCI-Attribute", "user=\"" + user + "\""); if(Utils.isEmptyOrWhitespaces(password)) password=""; httpURLConnection.addRequestProperty("X-OCCI-Attribute", "password=\"" + password + "\""); } in = new DataInputStream(httpURLConnection.getInputStream()); ByteArrayOutputStream out = new ByteArrayOutputStream(); Utils.copyStreamSafely(in, out); ret = out.toString( "UTF-8" ); } catch (IOException e) { throw new TargetException(e); } finally { Utils.closeQuietly(in); if (httpURLConnection != null) { httpURLConnection.disconnect(); } } return ("OK".equalsIgnoreCase(ret.trim()) ? id : null); } /** * Creates a VM (OCCI / VMWare) using JSON rendering. * @param hostIpPort IP and port of OCCI server (eg. "172.16.225.91:8080") * @param id Unique VM ID * @param template VM image ID (null means no image specified) * @param title VM title * @param summary VM summary * @param userData User data for the VM * @param user VM credentials (user name) * @param password VM credentials (password) * @param config A map of parameters (eg. mixin attributes) * @param waitForActive If true, wait until VM is active * @return The VM ID */ public static String createVMJson(String hostIpPort, String id, String template, String title, String summary, String userData, String user, String password, Map<String, String> config, boolean waitForActive) throws TargetException { //TODO This is a HACK for CloudAutomation APIs. Expecting interoperable implementation ! if(hostIpPort.contains("multi-language-connector")) { return createCloudAutomationVM(hostIpPort, template, title, userData, config, false); } else { String vmId = null; URL url = null; try { CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL)); url = new URL("http://" + hostIpPort + "/vm/"); } catch (MalformedURLException e) { throw new TargetException(e); } HttpURLConnection httpURLConnection = null; DataInputStream in = null; DataOutputStream output = null; try { httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setRequestMethod("PUT"); httpURLConnection.setRequestProperty("Content-Type", "application/json"); httpURLConnection.setRequestProperty("Accept", "application/json"); httpURLConnection.setDoInput(true); httpURLConnection.setDoOutput(true); String userDataString = "name: value"; if(userData != null) { userDataString = userData.replaceAll("\n\r", "\\\\n") .replaceAll("\n", "\\\\n") .replaceAll(System.lineSeparator(), "\\\\n"); } String request = "{\n" + "\"id\": \"" + id + "\",\n" + "\"title\": \"" + title + "\",\n" + "\"summary\": \"" + summary + "\",\n" + "\"kind\": \"http://schemas.ogf.org/occi/infrastructure#compute\",\n" + "\"mixins\": [" + "\"http://occiware.org/occi/infrastructure/crtp/backend#vmimage\",\n" + "\"http://occiware.org/occi/infrastructure/crtp/backend#vmwarefolders\",\n" + "\"http://schemas.ogf.org/occi/infrastructure/compute#user_data\",\n" + "\"http://occiware.org/occi/infrastructure/crtp/backend#credential\"\n" + "],\n" + "\"attributes\": {\n" + "\"occi.compute.state\": \"" + "active" + "\",\n" + "\"occi.compute.speed\": " + 3 + ",\n" + "\"occi.compute.memory\": " + 2 + ",\n" + "\"occi.compute.cores\": " + 2 + ",\n" + "\"occi.compute.architecture\": \"" + "x64" + "\",\n" + "\"imagename\": \"" + template + "\",\n" + "\"datacentername\": \"" + config.get(VmwareFoldersMixin.DATACENTERNAME) + "\",\n" + "\"datastorename\": \"" + config.get(VmwareFoldersMixin.DATASTORENAME) + "\",\n" + "\"clustername\": \"" + config.get(VmwareFoldersMixin.CLUSTERNAME) + "\",\n" + "\"hostsystemname\": \"" + config.get(VmwareFoldersMixin.HOSTSYSTEMNAME) + "\",\n" + "\"inventorypath\": \"" + config.get(VmwareFoldersMixin.INVENTORYPATH) + "\",\n" + "\"occi.compute.userdata\": \"" + userDataString + "\",\n" + "\"user\": \"" + user + "\",\n" + "\"password\": \"" + password + "\"\n" + "}\n}"; logger.finest(request); httpURLConnection.setRequestProperty("Content-Length", "" + Integer.toString(request.getBytes().length)); output = new DataOutputStream(httpURLConnection.getOutputStream()); output.writeBytes(request); output.flush(); Utils.closeQuietly(output); output = null; in = new DataInputStream(httpURLConnection.getInputStream()); ByteArrayOutputStream out = new ByteArrayOutputStream(); Utils.copyStreamSafely(in, out); // Parse JSON response to extract VM ID ObjectMapper objectMapper = new ObjectMapper(); JsonResponse rsp = objectMapper.readValue(out.toString(), JsonResponse.class); vmId = rsp.getId(); if(! Utils.isEmptyOrWhitespaces(vmId)) { if(vmId.startsWith("urn:uuid:")) vmId = vmId.substring(9); // Wait until VM is active, if requested if(waitForActive) { int retries = 15; boolean active = false; while(! active && retries-- > 0) { logger.finest("retry: " + retries); try { Thread.sleep(10000); // 10 seconds } catch (InterruptedException e) { // ignore } active = !Utils.isEmptyOrWhitespaces(getVMIP(hostIpPort, vmId)); //active = "ACTIVE".equalsIgnoreCase(getVMStatus(hostIpPort, ret)); } } } } catch (IOException e) { throw new TargetException(e); } finally { Utils.closeQuietly(in); Utils.closeQuietly(output); if (httpURLConnection != null) { httpURLConnection.disconnect(); } } return (vmId); } } /** * Creates a VM from an image on ActiveEon Proactive Cloud Automation. * @param hostIpPort * @param image The image ID (eg. when backed by OpenStack, the OpenStack image ID). * @param title * @param waitForActive If true, return only when VM is active. * @return The VM ID * @throws TargetException */ public static String createCloudAutomationVM(String hostIpPort, String image, String title, String userData, Map<String, String> config, boolean waitForActive) throws TargetException { String vmId = null; URL url = null; try { CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL)); url = new URL("http://" + hostIpPort + "/compute/"); } catch (MalformedURLException e) { throw new TargetException(e); } HttpURLConnection httpURLConnection = null; DataInputStream in = null; DataOutputStream output = null; try { httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setRequestMethod("POST"); httpURLConnection.setRequestProperty("Content-Type", "application/json"); httpURLConnection.setRequestProperty("Accept", "application/json"); httpURLConnection.setDoInput(true); httpURLConnection.setDoOutput(true); String userDataScript = "printf test > /tmp/roboconf.properties"; if(userData != null) { userDataScript = "printf \'" + userData.replaceAll("\n\r", "\\\\n") .replaceAll("\n", "\\\\n") .replaceAll(System.lineSeparator(), "\\\\n") + "\' > /tmp/roboconf.properties"; } String request = "{\n" + "\"attributes\": {\n" + "\"occi.entity.title\": \"" + title + "\",\n" + "\"vmimage\": {\n" + "\"imagename\": \"" + image + "\",\n" + "\"occi.category.title\": \"ubuntuMixin\"\n" + "},\n" + "\"user_data\": {\n" + "\"occi.compute.userdata\": \"" + userDataScript + "\",\n" + "\"occi.category.title\": \"scriptMixin\"\n" + "}\n}\n}"; logger.finest(request); httpURLConnection.setRequestProperty("Content-Length", "" + Integer.toString(request.getBytes().length)); output = new DataOutputStream(httpURLConnection.getOutputStream()); output.writeBytes(request); output.flush(); Utils.closeQuietly(output); output = null; in = new DataInputStream(httpURLConnection.getInputStream()); ByteArrayOutputStream out = new ByteArrayOutputStream(); Utils.copyStreamSafely(in, out); // Parse JSON response to extract VM ID ObjectMapper objectMapper = new ObjectMapper(); JsonResponse rsp = objectMapper.readValue(out.toString(), JsonResponse.class); vmId = rsp.getId(); // Wait until VM is active if(waitForActive && !Utils.isEmptyOrWhitespaces(vmId)) { int retries = 15; boolean active = false; while(! active && retries-- > 0) { logger.finest("retry: " + retries); try { Thread.sleep(10000); // 10 seconds } catch (InterruptedException e) { // ignore } active = !Utils.isEmptyOrWhitespaces(getVMIP(hostIpPort, vmId)); //active = "ACTIVE".equalsIgnoreCase(getVMStatus(hostIpPort, ret)); } } } catch (IOException e) { throw new TargetException(e); } finally { Utils.closeQuietly(in); Utils.closeQuietly(output); if (httpURLConnection != null) { httpURLConnection.disconnect(); } } return (vmId); } /** * Retrieves VM status (tested on CA only). * @param hostIpPort IP and port of OCCI server (eg. "172.16.225.91:8080") * @param id Unique VM ID * @return The VM's status * @throws TargetException */ public static String getVMStatus(String hostIpPort, String id) throws TargetException { String status = null; URL url = null; try { CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL)); url = new URL("http://" + hostIpPort + "/compute/" + id); } catch (MalformedURLException e) { throw new TargetException(e); } HttpURLConnection httpURLConnection = null; DataInputStream in = null; try { httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setRequestMethod("GET"); httpURLConnection.setRequestProperty("Accept", "application/json"); in = new DataInputStream(httpURLConnection.getInputStream()); ByteArrayOutputStream out = new ByteArrayOutputStream(); Utils.copyStreamSafely(in, out); // Parse JSON response to extract VM status ObjectMapper objectMapper = new ObjectMapper(); JsonResponse rsp = objectMapper.readValue(out.toString(), JsonResponse.class); status = rsp.getState(); } catch (IOException e) { throw new TargetException(e); } finally { Utils.closeQuietly(in); if (httpURLConnection != null) { httpURLConnection.disconnect(); } } return status; } /** * Deletes a VM (OCCI / VMWare). * @param hostIpPort IP and port of OCCI server (eg. "172.16.225.91:8080") * @param id Unique VM ID * @return true if deletion OK, false otherwise */ public static boolean deleteVM(String hostIpPort, String id) throws TargetException { String ret = null; URL url = null; try { CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL)); //TODO This is a HACK for CloudAutomation APIs. Expecting interoperable implementation ! if(hostIpPort.contains("multi-language-connector")) { url = new URL("http://" + hostIpPort + "/compute/" + id); } else { url = new URL("http://" + hostIpPort + "/" + id); } } catch (MalformedURLException e) { throw new TargetException(e); } HttpURLConnection httpURLConnection = null; DataInputStream in = null; try { httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setRequestMethod("DELETE"); httpURLConnection.setRequestProperty("Content-Type", "text/occi"); httpURLConnection.setRequestProperty("Accept", "*/*"); in = new DataInputStream(httpURLConnection.getInputStream()); ByteArrayOutputStream out = new ByteArrayOutputStream(); Utils.copyStreamSafely(in, out); ret = out.toString( "UTF-8" ); } catch (IOException e) { throw new TargetException(e); } finally { Utils.closeQuietly(in); if (httpURLConnection != null) { httpURLConnection.disconnect(); } } return ("OK".equalsIgnoreCase(ret)); } /** * Retrieves VM IP. * @param hostIpPort IP and port of OCCI server (eg. "172.16.225.91:8080") * @param id Unique VM ID * @return The VM's IP address * @throws TargetException */ public static String getVMIP(String hostIpPort, String id) throws TargetException { String vmIp = null; URL url = null; try { CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL)); url = new URL("http://" + hostIpPort + "/compute/" + id); } catch (MalformedURLException e) { throw new TargetException(e); } HttpURLConnection httpURLConnection = null; DataInputStream in = null; try { httpURLConnection = (HttpURLConnection) url.openConnection(); httpURLConnection.setRequestMethod("GET"); httpURLConnection.setRequestProperty("Accept", "application/json"); in = new DataInputStream(httpURLConnection.getInputStream()); ByteArrayOutputStream out = new ByteArrayOutputStream(); Utils.copyStreamSafely(in, out); // Parse JSON response to extract VM IP ObjectMapper objectMapper = new ObjectMapper(); JsonResponse rsp = objectMapper.readValue(out.toString(), JsonResponse.class); vmIp = rsp.getHostsystemname(); if(Utils.isEmptyOrWhitespaces(vmIp)) vmIp = rsp.getHostname(); } catch (IOException e) { throw new TargetException(e); } finally { Utils.closeQuietly(in); if (httpURLConnection != null) { httpURLConnection.disconnect(); } } return vmIp; } /** * Checks if VM is running. * @param hostIpPort * @param id * @return * @throws TargetException */ public static boolean isVMRunning(String hostIpPort, String id) throws TargetException { boolean result = false; String ip = OcciVMUtils.getVMIP(hostIpPort, id); try { InetAddress inet = InetAddress.getByName(ip); result = inet.isReachable(5000); } catch (Exception e) { result = false; logger.info(e.getMessage()); } return result; } /** * Test main program. * @param args * @throws InterruptedException */ /* public static void main(String[] args) throws Exception { //String id = createCloudAutomationVM("81.200.35.140:8080/multi-language-connector/occi", "aab7ea48-0585-44b2-afd4-19e99b6581e7", "testCAjava", true); //System.out.println("Created VM on CA:" + id); //System.out.println("VM IP:" + getVMIP("172.16.225.91:8080", "6157c4d2-08b3-4204-be85-d1828df74c22")); //System.out.println("VM status:" + getVMStatus("172.16.225.91:8080", "6157c4d2-08b3-4204-be85-d1828df74c22")); //System.out.println("Delete VM: " + deleteVM("172.16.225.91:8080", "6157c4d2-08b3-4204-be85-d1828df74c22")); java.util.Properties p = new java.util.Properties(); p.setProperty("key1", "value1"); p.setProperty("key2", "value2"); p.setProperty("key3", "value3"); p.setProperty("key4", "value4"); java.io.StringWriter writer = new java.io.StringWriter(); p.store( writer, "" ); String s = writer.toString(); //System.out.println(s); String userdata = s.replaceAll("\n\r", "\\\\n") .replaceAll("\n", "\\\\n") .replaceAll(System.lineSeparator(), "\\\\n"); System.out.println(userdata); System.out.println("Create VM (JSON): " + createVMJson("172.16.225.80:8080", "6157c4d2-08b3-4204-be85-d1828df74c25", "RoboconfAgentOcciware090117", "javaTest", "Java Test", userdata, "ubuntu", "ubuntu", null, false)); //System.out.println("IP: " + getVMIP("172.16.225.80:8080", "6157c4d2-08b3-4204-be85-d1828df74c25")); // curl -v -X DELETE http://172.16.225.80:8080/6157c4d2-08b3-4204-be85-d1828df74c25 System.exit(0); //System.out.println("Create VM: " + // createVM("81.200.35.140:8080/multi-language-connector/occi", //CA/OW2Stack // "", "e3161161-02a4-4685-ad99-8ac36b3e66ea", "UbuntuTest", "Ubuntu Test", null, null, null)); //System.out.println("Create VM: " + //createVM("81.200.35.140:8080/multi-language-connector/occi", //CA/OW2Stack // "", "e906f16e-a3cb-414c-9a7e-c308dff4897d", "javaTest", "Java Test", userdata, null, null)); //createVM("172.16.225.91:8080", //VMWare //"6157c4d2-08b3-4204-be85-d1828df74c22", "RoboconfAgentOcciware090117", "javaTest", "Java Test", null, "occiware", "Occiware1234", null)); //Thread.sleep(40000); //System.out.println("Delete VM: " + deleteVM("81.200.35.151:8080", "8e0cb600-4478-4687-9fa4-135f5985efdf")); //System.out.println("Delete VM: " + deleteVM("172.16.225.91:8080", "6157c4d2-08b3-4204-be85-d1828df74c22")); } */ } /** * Bean for JSON parsing (Jackson ObjectMapper). * @author Pierre-Yves Gibello - Linagora */ @JsonIgnoreProperties(ignoreUnknown = true) class JsonResponse { private String id; private String hostsystemname; @JsonProperty("occi.compute.hostname") private String hostname; @JsonProperty("occi.compute.state") private String state; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getHostsystemname() { return hostsystemname; } public void setHostsystemname(String hostsystemname) { this.hostsystemname = hostsystemname; } public String getHostname() { return hostname; } public void setHostname(String hostname) { this.hostname = hostname; } public String getState() { return state; } public void setState(String state) { this.state = state; } }