Java tutorial
/** * Copyright 2013-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.azure.internal; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.SecureRandom; import java.util.Map; import java.util.logging.Logger; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.apache.commons.codec.binary.Base64; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import net.roboconf.core.agents.DataHelpers; import net.roboconf.core.model.beans.Instance; import net.roboconf.core.model.helpers.InstanceHelpers; import net.roboconf.core.utils.Utils; import net.roboconf.target.api.TargetException; import net.roboconf.target.api.TargetHandler; import net.roboconf.target.api.TargetHandlerParameters; /** * @author Linh-Manh Pham - LIG */ public class AzureIaasHandler implements TargetHandler { public static final String TARGET_ID = "iaas-azure"; private final Logger logger = Logger.getLogger(getClass().getName()); /* * (non-Javadoc) * @see net.roboconf.target.api.TargetHandler#getTargetId() */ @Override public String getTargetId() { return TARGET_ID; } /* * (non-Javadoc) * @see net.roboconf.target.api.TargetHandler * #createMachine(net.roboconf.target.api.TargetHandlerParameters) */ @Override public String createMachine(TargetHandlerParameters parameters) throws TargetException { String instanceId; try { // For IaaS, we only expect root instance names to be passed if (InstanceHelpers.countInstances(parameters.getScopedInstancePath()) > 1) throw new TargetException("Only root instances can be passed in arguments."); String rootInstanceName = InstanceHelpers.findRootInstancePath(parameters.getScopedInstancePath()); final AzureProperties azureProperties = buildProperties(parameters.getTargetProperties()); // The following part enables to transmit data to the VM. // When the VM is up, it will be able to read this data. // TODO: Azure does not allow a VM name with spaces whereas graph configuration of Roboconf supports it. It conflicts. // channelName = channelName.replaceAll("\\s+","-").toLowerCase(); String userData = DataHelpers.writeUserDataAsString(parameters.getMessagingProperties(), parameters.getDomain(), parameters.getApplicationName(), rootInstanceName); String encodedUserData = new String(Base64.encodeBase64(userData.getBytes("UTF-8")), "UTF-8"); replaceValueOfTagInXMLFile(azureProperties.getCreateCloudServiceTemplate(), "ServiceName", rootInstanceName); replaceValueOfTagInXMLFile(azureProperties.getCreateCloudServiceTemplate(), "Location", azureProperties.getLocation()); replaceValueOfTagInXMLFile(azureProperties.getCreateDeploymentTemplate(), "CustomData", encodedUserData); replaceValueOfTagInXMLFile(azureProperties.getCreateDeploymentTemplate(), "Name", rootInstanceName); replaceValueOfTagInXMLFile(azureProperties.getCreateDeploymentTemplate(), "HostName", rootInstanceName); replaceValueOfTagInXMLFile(azureProperties.getCreateDeploymentTemplate(), "RoleName", rootInstanceName); replaceValueOfTagInXMLFile(azureProperties.getCreateDeploymentTemplate(), "RoleSize", azureProperties.getVMSize()); replaceValueOfTagInXMLFile(azureProperties.getCreateDeploymentTemplate(), "SourceImageName", azureProperties.getVMTemplate()); // Let send the request to Azure API to create a Cloud Service and a Deployment (a PersistentVMRole) String baseURL = String.format("https://management.core.windows.net/%s/services", azureProperties.getSubscriptionId()); String requestHeaderContentType = "application/xml"; byte[] requestBodyCreateCloudService = convertFileToByte( azureProperties.getCreateCloudServiceTemplate()); byte[] requestBodyCreateDeployment = convertFileToByte(azureProperties.getCreateDeploymentTemplate()); String checkCloudServiceURL = baseURL + "/hostedservices/operations/isavailable/" + rootInstanceName; String createCloudServiceURL = baseURL + "/hostedservices"; String createDeploymentURL = baseURL + "/hostedservices/" + rootInstanceName + "/deployments"; // Check if Cloud Service exist String responseCheckCloudService = processGetRequest(new URL(checkCloudServiceURL), azureProperties.getKeyStoreFile(), azureProperties.getKeyStorePassword()); boolean checkResult = getExistResutlFromXML(responseCheckCloudService, "Result"); // true means the name is still available this.logger.info("Response Result: Cloud Service Name is still available: " + checkResult); // Create Cloud Service, Deployment & Add a Role (Linux VM), maybe add a second Role (another Linux VM) int rescodeCreateCloudService = -1; if (checkResult) { rescodeCreateCloudService = processPostRequest(new URL(createCloudServiceURL), requestBodyCreateCloudService, requestHeaderContentType, azureProperties.getKeyStoreFile(), azureProperties.getKeyStorePassword()); // rescode shoud be 201 } this.logger.info("Create Cloud Service: Response Code: " + rescodeCreateCloudService); this.logger.info("Creating Azure VM in progress: " + rootInstanceName); if (rescodeCreateCloudService == 201) { int rescodeCreateDeployment = processPostRequest(new URL(createDeploymentURL), requestBodyCreateDeployment, requestHeaderContentType, azureProperties.getKeyStoreFile(), azureProperties.getKeyStorePassword()); // rescode shoud be 202 this.logger.info("Create VM: Response Code: " + rescodeCreateDeployment); } instanceId = rootInstanceName; // instanceID in this context should be rootInstanceName } catch (Exception e) { throw new TargetException(e); } return instanceId; } /* * (non-Javadoc) * @see net.roboconf.target.api.TargetHandler#configureMachine( * net.roboconf.target.api.TargetHandlerParameters, java.lang.String, net.roboconf.core.model.beans.Instance) */ @Override public void configureMachine(TargetHandlerParameters parameters, String machineId, Instance scopedInstance) throws TargetException { this.logger.fine("Configuring machine '" + machineId + "': nothing to configure."); } /* * (non-Javadoc) * @see net.roboconf.target.api.TargetHandler * #isMachineRunning(net.roboconf.target.api.TargetHandlerParameters, java.lang.String) */ @Override public boolean isMachineRunning(TargetHandlerParameters parameters, String machineId) throws TargetException { // TODO See #230 return false; } /* * (non-Javadoc) * @see net.roboconf.target.api.TargetHandler * #terminateMachine(net.roboconf.target.api.TargetHandlerParameters, java.lang.String) */ @Override public void terminateMachine(TargetHandlerParameters parameters, String instanceId) throws TargetException { // instanceID is CloudServiceName try { final AzureProperties azureProperties = buildProperties(parameters.getTargetProperties()); String baseURL = String.format("https://management.core.windows.net/%s/services", azureProperties.getSubscriptionId()); String deleteCloudServiceURL = baseURL + "/hostedservices/" + instanceId + "?comp=media"; // Delete Cloud Service, and also delete all the related things int rescodeDeleteCloudService = processDeleteRequest(new URL(deleteCloudServiceURL), azureProperties.getKeyStoreFile(), azureProperties.getKeyStorePassword()); // rescode shoud be 202 this.logger.info("Response Code: Delete VM: " + rescodeDeleteCloudService); } catch (Exception e) { throw new TargetException(e); } } /* * (non-Javadoc) * @see net.roboconf.target.api.TargetHandler * #retrievePublicIpAddress(net.roboconf.target.api.TargetHandlerParameters, java.lang.String) */ @Override public String retrievePublicIpAddress(TargetHandlerParameters parameters, String machineId) throws TargetException { // Most likely feasible but not implemented for the moment return null; } /** * Validates the received properties and builds a Java bean from them. * @param targetProperties the target properties * @return a non-null bean * @throws TargetException if properties are invalid */ static AzureProperties buildProperties(Map<String, String> targetProperties) throws TargetException { String[] properties = { AzureConstants.AZURE_SUBSCRIPTION_ID, AzureConstants.AZURE_KEY_STORE_FILE, AzureConstants.AZURE_KEY_STORE_PASSWORD, AzureConstants.AZURE_CREATE_CLOUD_SERVICE_TEMPLATE, AzureConstants.AZURE_CREATE_DEPLOYMENT_TEMPLATE, AzureConstants.AZURE_LOCATION, AzureConstants.AZURE_VM_SIZE, AzureConstants.AZURE_VM_TEMPLATE }; for (String property : properties) { if (Utils.isEmptyOrWhitespaces(targetProperties.get(property))) throw new TargetException("The value for " + property + " cannot be null or empty."); } // Create a bean AzureProperties azureProperties = new AzureProperties(); String s = targetProperties.get(AzureConstants.AZURE_SUBSCRIPTION_ID); azureProperties.setSubscriptionId(s.trim()); s = targetProperties.get(AzureConstants.AZURE_KEY_STORE_FILE); azureProperties.setKeyStoreFile(s.trim()); s = targetProperties.get(AzureConstants.AZURE_KEY_STORE_PASSWORD); azureProperties.setKeyStoreFile(s.trim()); s = targetProperties.get(AzureConstants.AZURE_CREATE_CLOUD_SERVICE_TEMPLATE); azureProperties.setKeyStoreFile(s.trim()); s = targetProperties.get(AzureConstants.AZURE_CREATE_DEPLOYMENT_TEMPLATE); azureProperties.setKeyStoreFile(s.trim()); s = targetProperties.get(AzureConstants.AZURE_LOCATION); azureProperties.setKeyStoreFile(s.trim()); s = targetProperties.get(AzureConstants.AZURE_VM_SIZE); azureProperties.setKeyStoreFile(s.trim()); s = targetProperties.get(AzureConstants.AZURE_VM_TEMPLATE); azureProperties.setKeyStoreFile(s.trim()); return azureProperties; } private KeyStore getKeyStore(String keyStoreName, String password) throws IOException { KeyStore ks = null; FileInputStream fis = null; try { ks = KeyStore.getInstance("JKS"); char[] passwordArray = password.toCharArray(); fis = new FileInputStream(keyStoreName); ks.load(fis, passwordArray); } catch (Exception e) { this.logger.severe(e.getMessage()); Utils.logException(this.logger, e); } finally { Utils.closeQuietly(fis); } return ks; } private SSLSocketFactory getSSLSocketFactory(String keyStoreName, String password) throws GeneralSecurityException, IOException { KeyStore ks = this.getKeyStore(keyStoreName, password); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); keyManagerFactory.init(ks, password.toCharArray()); SSLContext context = SSLContext.getInstance("TLS"); context.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom()); return context.getSocketFactory(); } private static boolean getExistResutlFromXML(String xmlStr, String nameOfNode) throws ParserConfigurationException, SAXException, IOException { DocumentBuilderFactory f = DocumentBuilderFactory.newInstance(); DocumentBuilder b; b = f.newDocumentBuilder(); Document doc; doc = b.parse(new ByteArrayInputStream(xmlStr.getBytes("UTF-8"))); NodeList nodes = doc.getElementsByTagName(nameOfNode); String result = "false"; for (int i = 0; i < nodes.getLength(); i++) { Element node = (Element) nodes.item(i); result = node.getTextContent(); } return Boolean.parseBoolean(result); } private String processGetRequest(URL url, String keyStore, String keyStorePassword) throws GeneralSecurityException, IOException { SSLSocketFactory sslFactory = this.getSSLSocketFactory(keyStore, keyStorePassword); HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); con.setSSLSocketFactory(sslFactory); con.setRequestMethod("GET"); con.addRequestProperty("x-ms-version", "2014-04-01"); InputStream responseStream = (InputStream) con.getContent(); ByteArrayOutputStream os = new ByteArrayOutputStream(); Utils.copyStreamSafely(responseStream, os); return os.toString("UTF-8"); } private int processPostRequest(URL url, byte[] data, String contentType, String keyStore, String keyStorePassword) throws GeneralSecurityException, IOException { SSLSocketFactory sslFactory = this.getSSLSocketFactory(keyStore, keyStorePassword); HttpsURLConnection con; con = (HttpsURLConnection) url.openConnection(); con.setSSLSocketFactory(sslFactory); con.setDoOutput(true); con.setRequestMethod("POST"); con.addRequestProperty("x-ms-version", "2014-04-01"); con.setRequestProperty("Content-Length", String.valueOf(data.length)); con.setRequestProperty("Content-Type", contentType); DataOutputStream requestStream = new DataOutputStream(con.getOutputStream()); requestStream.write(data); requestStream.flush(); requestStream.close(); return con.getResponseCode(); } private int processDeleteRequest(URL url, String keyStore, String keyStorePassword) throws GeneralSecurityException, IOException { SSLSocketFactory sslFactory = this.getSSLSocketFactory(keyStore, keyStorePassword); HttpsURLConnection con; con = (HttpsURLConnection) url.openConnection(); con.setSSLSocketFactory(sslFactory); con.setRequestMethod("DELETE"); con.addRequestProperty("x-ms-version", "2014-04-01"); return con.getResponseCode(); } private byte[] convertFileToByte(String xmlFilePath) { ByteArrayOutputStream os = new ByteArrayOutputStream(); try { Utils.copyStream(new File(xmlFilePath), os); } catch (IOException e) { this.logger.severe(e.getMessage()); } return os.toByteArray(); } private void replaceValueOfTagInXMLFile(String filePath, String tagName, String replacingValue) throws ParserConfigurationException, SAXException, IOException { File fXmlFile = new File(filePath); DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); Document doc = dBuilder.parse(fXmlFile); //optional, but recommended //read this - http://stackoverflow.com/questions/13786607/normalization-in-dom-parsing-with-java-how-does-it-work doc.getDocumentElement().normalize(); NodeList nList = doc.getElementsByTagName(tagName); Node nNode = nList.item(0); nNode.setTextContent(replacingValue); // write the modified content into xml file TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer transformer; try { transformer = transformerFactory.newTransformer(); DOMSource source = new DOMSource(doc); StreamResult result = new StreamResult(new File(filePath)); transformer.transform(source, result); } catch (TransformerException e) { this.logger.severe(e.getMessage()); } } }