package org.cloudifysource.esc.byon; import java.text.MessageFormat; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Logger; import org.apache.commons.lang.StringUtils; import org.cloudifysource.domain.cloud.compute.ComputeTemplate; import org.cloudifysource.dsl.utils.IPUtils; import org.cloudifysource.esc.driver.provisioning.CloudProvisioningException; import org.cloudifysource.esc.driver.provisioning.CustomNode; import org.cloudifysource.esc.driver.provisioning.byon.CustomNodeImpl; public class ByonUtils { private static final String PROVIDER_ID = "BYON"; private static final String NODES_LIST = "nodesList"; private static final String NODE_ID = "id"; private static final String NODE_HOST_LIST = "host-list"; private static final String NODE_HOST_RANGE = "host-range"; private static final String NODE_USERNAME = "username"; private static final String NODE_KEY_FILE = "keyFile"; private static final String NODE_CREDENTIAL = "credential"; private static final String EMPTY_ID_ERR_MESSAGE = "Failed to parse cloud nodes, empty ID configuration"; private static final String EMPTY_HOSTS_ERR_MESSAGE = "Failed to parse cloud nodes, host list or range not set"; private static final String INVALID_HOSTS_ERR_MESSAGE = "Failed to parse cloud nodes, invalid hosts configuration"; private static final String EMPTY_IP_RANGE_ERR_MESSAGE = "Failed to parse cloud nodes, invalid IP range " + "configuration: missing \"-\""; private static final Logger logger = Logger.getLogger(ByonUtils.class.getName()); /** * Parse the templates's nodes list and return a list of nodes, as Strings. * @param template The template object to parse * @return a list of nodes, as Strings, as expected by the parseCloudNodes. * @throws CloudProvisioningException Indicates a failure to parse the given template. */ public static List<Map<String, String>> getTemplateNodesList(final ComputeTemplate template) throws CloudProvisioningException { List<Map<Object, Object>> originalNodesList = null; final Map<String, Object> customSettings = template.getCustom(); if (customSettings != null) { originalNodesList = (List<Map<Object, Object>>) customSettings.get(NODES_LIST); } if(originalNodesList == null || originalNodesList.isEmpty()) { logger.warning("Nodes list is missing. custom = " + customSettings); throw new CloudProvisioningException("Nodes list not set"); } return ByonUtils.convertToStringMap(originalNodesList); } /** * Parses the nodes defined in the given {@link ComputeTemplate} object. * @param template The template to parse. * @return a list of {@link CustomNode} objects. * @throws CloudProvisioningException Indicates a failure to parse the given template. */ public static List<CustomNode> parseCloudNodes(final ComputeTemplate template) throws CloudProvisioningException { List<Map<String, String>> nodesList = ByonUtils.getTemplateNodesList(template); return parseCloudNodes(nodesList); } /** * Parses the nodes defined in the given list to create a list of {@link CustomNode} objects. * @param nodesMapList The list of nodes to parse, as specified in the cloud configuration file. * @return a list of {@link CustomNode} objects. * @throws CloudProvisioningException Indicates a failure to parse the given nodes list. */ public static List<CustomNode> parseCloudNodes( final List<Map<String, String>> nodesMapList) throws CloudProvisioningException { final List<CustomNode> cloudNodes = new ArrayList<CustomNode>(); for (final Map<String, String> nodeMap : nodesMapList) { String nodeId = nodeMap.get(NODE_ID); String hostList = nodeMap.get(NODE_HOST_LIST); String hostRange = nodeMap.get(NODE_HOST_RANGE); if (StringUtils.isBlank(nodeId)) { throw new CloudProvisioningException(EMPTY_ID_ERR_MESSAGE); } if (StringUtils.isNotBlank(hostList)) { if (isIPList(hostList.trim())) { cloudNodes.addAll(parseNodeList(nodeMap)); } else { cloudNodes.add(parseOneNode(nodeMap)); } } else if (StringUtils.isNotBlank(hostRange)) { if (isIPRange(hostRange.trim())) { cloudNodes.addAll(parseNodeRange(nodeMap)); } else if (isIPCIDR(hostRange.trim())) { cloudNodes.addAll(parseNodeCIDR(nodeMap)); } else { throw new CloudProvisioningException( INVALID_HOSTS_ERR_MESSAGE + ": " + hostRange); } } else { //host list or range not set throw new CloudProvisioningException(EMPTY_HOSTS_ERR_MESSAGE); } } return cloudNodes; } /** * Parses a single byon cloud node. * * @param nodeMap * The map of attributes related to this node (ID, IP or host name) * @return A {@link CustomNode} object */ public static CustomNode parseOneNode(final Map<String, String> nodeMap) { // Handle the edge case scenario where a host-list defines a single node String nodeId = nodeMap.get(NODE_ID); if (nodeId.contains("{")) { nodeId = MessageFormat.format(nodeId, 1); } String host = nodeMap.get(NODE_HOST_LIST).trim(); String ip = null; String hostName = null; if (IPUtils.validateIPAddress(host)) { // the host is an ip address ip = host; } else { hostName = host; } return new CustomNodeImpl(PROVIDER_ID, nodeId.trim(), ip, hostName, nodeMap.get(NODE_USERNAME), nodeMap.get(NODE_CREDENTIAL), nodeMap.get(NODE_KEY_FILE), nodeId.trim()); } /** * Parses a list of nodes (comma-separated IPs or host names). * * @param nodeMap * The map of attributes related to this node (ID, IPs or hosts list) * @return A list of {@link CustomNode} objects */ public static List<CustomNode> parseNodeList(final Map<String, String> nodeMap) { final List<CustomNode> cloudNodes = new ArrayList<CustomNode>(); boolean useIdAsTemplate = false; boolean useIdAsPrefix = false; int index = 1; String currnentId; final String nodeId = nodeMap.get(NODE_ID).trim(); final String hostsList = nodeMap.get(NODE_HOST_LIST).trim(); final String[] hosts = hostsList.split(","); if (hosts.length > 1) { if (isIdTemplate(nodeId)) { useIdAsTemplate = true; } else { useIdAsPrefix = true; } } for (String host : hosts) { // validate the IP host = host.trim(); if (host.isEmpty()) { continue; } // set the id if (useIdAsTemplate) { currnentId = MessageFormat.format(nodeId, index); } else if (useIdAsPrefix) { currnentId = nodeId + index; } else { // just one IPs currnentId = nodeId; } String ip = null; String hostName = null; if (IPUtils.validateIPAddress(host)) { // the host is an ip address ip = host; } else { hostName = host; } // create a new node cloudNodes.add(new CustomNodeImpl(PROVIDER_ID, currnentId, ip, hostName, nodeMap.get(NODE_USERNAME), nodeMap.get(NODE_CREDENTIAL), nodeMap.get(NODE_KEY_FILE), currnentId)); index++; } return cloudNodes; } /** * Parses a range of nodes (e.g. 192.168.9.1-192.168.9.8) * * @param nodeMap * The map of attributes related to this node (ID, IP range) * @return A list of {@link CustomNode} objects * @throws CloudProvisioningException * Indicated an invalid IP address is used */ public static List<CustomNode> parseNodeRange(final Map<String, String> nodeMap) throws CloudProvisioningException { final List<CustomNode> cloudNodes = new ArrayList<CustomNode>(); boolean useIdAsTemplate = false; boolean useIdAsPrefix = false; int index = 1; String currnentId; final String nodeId = nodeMap.get(NODE_ID).trim(); final String ipRange = nodeMap.get(NODE_HOST_RANGE).trim(); // syntax validation (IPs are validated later, through IPUtils) final int ipDashIndex = ipRange.indexOf('-'); if (ipDashIndex < 0) { throw new CloudProvisioningException(EMPTY_IP_RANGE_ERR_MESSAGE); } // run through the range of IPs final String ipRangeStart = ipRange.substring(0, ipRange.indexOf('-')); final String ipRangeEnd = ipRange.substring(ipRange.indexOf('-') + 1); if (IPUtils.ip2Long(ipRangeStart) < IPUtils.ip2Long(ipRangeEnd)) { if (isIdTemplate(nodeId)) { useIdAsTemplate = true; } else { useIdAsPrefix = true; } } String ip = ipRangeStart; while (IPUtils.ip2Long(ip) <= IPUtils.ip2Long(ipRangeEnd)) { // validate the IP ip = ip.trim(); if (!IPUtils.validateIPAddress(ip)) { throw new CloudProvisioningException("Invalid IP address: " + ip); } // set the id if (useIdAsTemplate) { currnentId = MessageFormat.format(nodeId, index); } else if (useIdAsPrefix) { currnentId = nodeId + index; } else { // just one IPs currnentId = nodeId; } cloudNodes.add(new CustomNodeImpl(PROVIDER_ID, currnentId, ip, null, nodeMap.get(NODE_USERNAME), nodeMap.get(NODE_CREDENTIAL), nodeMap.get(NODE_KEY_FILE), currnentId)); index++; ip = IPUtils.getNextIP(ip); } return cloudNodes; } /** * Parses a range of nodes formatted as a CIDR (e.g. 192.168.9.60/31) * * @param nodeMap * The map of attributes related to this node (ID, IPs as CIDR) * @return A list of {@link CustomNode} objects * @throws CloudProvisioningException * Indicated an invalid IP address is used */ public static List<CustomNode> parseNodeCIDR(final Map<String, String> nodeMap) throws CloudProvisioningException { try { nodeMap.put(NODE_HOST_RANGE, IPUtils.ipCIDR2Range(nodeMap .get(NODE_HOST_RANGE).trim())); } catch (final Exception e) { throw new CloudProvisioningException( "Failed to start cloud machine.", e); } return parseNodeRange(nodeMap); } /******* * It is easy to accidentally create GStrings instead of String in a groovy file. This will auto correct the problem * for byon node definitions by calling the toString() methods for map keys and values. * * @param originalNodesList * . * @return the */ public static List<Map<String, String>> convertToStringMap(final List<Map<Object, Object>> originalNodesList) { List<Map<String, String>> nodesList; nodesList = new LinkedList<Map<String, String>>(); for (final Map<Object, Object> originalMap : originalNodesList) { final Map<String, String> newMap = new LinkedHashMap<String, String>(); final Set<Entry<Object, Object>> entries = originalMap.entrySet(); for (final Entry<Object, Object> entry : entries) { newMap.put(entry.getKey().toString(), entry.getValue().toString()); } nodesList.add(newMap); } return nodesList; } /** * Checks if the specified id is a single-valued id or a template for multiple id-s. * * @param nodeId * The id to examine * @return true if this id is a template, false if it's a single-values id. */ private static boolean isIdTemplate(final String nodeId) { boolean result = false; if (nodeId.contains("{0}")) { result = true; } return result; } private static boolean isIPList(final String hostList) { boolean result = false; if (hostList != null && hostList.contains(",")) { result = true; } return result; } private static boolean isIPRange(final String hostRange) throws CloudProvisioningException { boolean result = false; if (hostRange.contains("-")) { final String ipRangeStart = hostRange.substring(0, hostRange.indexOf('-')); if (IPUtils.validateIPAddress(ipRangeStart)) { result = true; } } return result; } private static boolean isIPCIDR(final String hostRange) { boolean result = false; if (hostRange.contains("/")) { result = true; } return result; } }