org.dasein.cloud.cloudstack.compute.VirtualMachines.java Source code

Java tutorial

Introduction

Here is the source code for org.dasein.cloud.cloudstack.compute.VirtualMachines.java

Source

/**
 * Copyright (C) 2009-2015 Dell, Inc.
 *
 * ====================================================================
 * 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 org.dasein.cloud.cloudstack.compute;

import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import org.dasein.cloud.*;
import org.dasein.cloud.cloudstack.*;
import org.dasein.cloud.cloudstack.network.Network;
import org.dasein.cloud.cloudstack.network.SecurityGroup;
import org.dasein.cloud.compute.*;
import org.dasein.cloud.network.RawAddress;
import org.dasein.cloud.util.APITrace;
import org.dasein.cloud.util.Cache;
import org.dasein.cloud.util.CacheLevel;
import org.dasein.util.CalendarWrapper;
import org.dasein.util.uom.storage.Gigabyte;
import org.dasein.util.uom.storage.Megabyte;
import org.dasein.util.uom.storage.Storage;
import org.dasein.util.uom.time.Hour;
import org.dasein.util.uom.time.TimePeriod;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

public class VirtualMachines extends AbstractVMSupport<CSCloud> {
    static public final Logger logger = Logger.getLogger(VirtualMachines.class);

    static private final String DEPLOY_VIRTUAL_MACHINE = "deployVirtualMachine";
    static private final String DESTROY_VIRTUAL_MACHINE = "destroyVirtualMachine";
    static private final String GET_VIRTUAL_MACHINE_PASSWORD = "getVMPassword";
    static private final String LIST_VIRTUAL_MACHINES = "listVirtualMachines";
    static private final String LIST_SERVICE_OFFERINGS = "listServiceOfferings";
    static private final String REBOOT_VIRTUAL_MACHINE = "rebootVirtualMachine";
    static private final String RESET_VIRTUAL_MACHINE_PASSWORD = "resetPasswordForVirtualMachine";
    static private final String RESIZE_VIRTUAL_MACHINE = "scaleVirtualMachine";
    static private final String START_VIRTUAL_MACHINE = "startVirtualMachine";
    static private final String STOP_VIRTUAL_MACHINE = "stopVirtualMachine";

    static private Properties cloudMappings;
    static private Map<String, Map<String, String>> customNetworkMappings;
    static private Map<String, Map<String, Set<String>>> customServiceMappings;

    public VirtualMachines(CSCloud provider) {
        super(provider);
    }

    @Override
    public VirtualMachine alterVirtualMachineSize(@Nonnull String vmId, @Nullable String cpuCount,
            @Nullable String ramInMB) throws InternalException, CloudException {
        APITrace.begin(getProvider(), "VM.alterVirtualMachineSize");
        try {
            CSMethod method = new CSMethod(getProvider());

            VirtualMachine vm = getVirtualMachine(vmId);

            boolean restart = false;
            if (!vm.getCurrentState().equals(VmState.STOPPED)) {
                restart = true;
                stop(vmId, true);
            }

            long timeout = System.currentTimeMillis() + (CalendarWrapper.MINUTE * 20L);
            while (System.currentTimeMillis() < timeout) {
                if (!vm.getCurrentState().equals(VmState.STOPPED)) {
                    try {
                        Thread.sleep(15000L);
                        vm = getVirtualMachine(vmId);
                    } catch (InterruptedException ignore) {
                    }
                } else {
                    break;
                }
            }
            vm = getVirtualMachine(vmId);
            if (!vm.getCurrentState().equals(VmState.STOPPED)) {
                throw new CloudException("Unable to stop vm for scaling");
            }
            List<Param> params = new ArrayList<Param>();
            params.add(new Param("id", vmId));
            params.add(new Param("serviceOfferingId", vm.getProductId()));
            int index = 0;
            if (cpuCount != null) {
                params.add(new Param("details[" + index + "].cpunumber", cpuCount));
                index++;
            }
            if (ramInMB != null) {
                params.add(new Param("details[" + index + "].memory", ramInMB));
            }
            Document doc = method.get(method.buildUrl(RESIZE_VIRTUAL_MACHINE, params), RESIZE_VIRTUAL_MACHINE);

            NodeList matches = doc.getElementsByTagName("scalevirtualmachineresponse");
            String jobId = null;

            for (int i = 0; i < matches.getLength(); i++) {
                NodeList attrs = matches.item(i).getChildNodes();

                for (int j = 0; j < attrs.getLength(); j++) {
                    Node node = attrs.item(j);

                    if (node != null && node.getNodeName().equalsIgnoreCase("jobid")) {
                        jobId = node.getFirstChild().getNodeValue();
                    }
                }
            }
            if (jobId == null) {
                throw new CloudException("Could not scale server");
            }
            Document responseDoc = getProvider().waitForJob(doc, "Scale Server");

            if (responseDoc != null) {
                NodeList nodeList = responseDoc.getElementsByTagName("virtualmachine");
                if (nodeList.getLength() > 0) {
                    Node virtualMachine = nodeList.item(0);
                    vm = toVirtualMachine(virtualMachine);
                    if (vm != null) {
                        if (restart) {
                            start(vmId);
                        }
                        return vm;
                    }
                }
            }
            if (restart) {
                start(vmId);
            }
            return getVirtualMachine(vmId);
        } finally {
            APITrace.end();
        }
    }

    @Override
    public VirtualMachine alterVirtualMachineProduct(@Nonnull String vmId, @Nonnull String productId)
            throws InternalException, CloudException {
        APITrace.begin(getProvider(), "VM.alterVirtualMachineProduct");
        try {
            CSMethod method = new CSMethod(getProvider());

            VirtualMachine vm = getVirtualMachine(vmId);
            if (vm.getProductId().equals(productId)) {
                return vm;
            }

            boolean restart = false;
            if (!vm.getCurrentState().equals(VmState.STOPPED)) {
                restart = true;
                stop(vmId, true);
            }

            long timeout = System.currentTimeMillis() + (CalendarWrapper.MINUTE * 20L);
            while (System.currentTimeMillis() < timeout) {
                if (!vm.getCurrentState().equals(VmState.STOPPED)) {
                    try {
                        Thread.sleep(15000L);
                        vm = getVirtualMachine(vmId);
                    } catch (InterruptedException ignore) {
                    }
                } else {
                    break;
                }
            }
            vm = getVirtualMachine(vmId);
            if (!vm.getCurrentState().equals(VmState.STOPPED)) {
                throw new CloudException("Unable to stop vm for scaling");
            }
            Document doc = method.get(method.buildUrl(RESIZE_VIRTUAL_MACHINE, new Param("id", vmId),
                    new Param("serviceOfferingId", productId)), RESIZE_VIRTUAL_MACHINE);

            NodeList matches = doc.getElementsByTagName("scalevirtualmachineresponse");
            String jobId = null;

            for (int i = 0; i < matches.getLength(); i++) {
                NodeList attrs = matches.item(i).getChildNodes();

                for (int j = 0; j < attrs.getLength(); j++) {
                    Node node = attrs.item(j);

                    if (node != null && node.getNodeName().equalsIgnoreCase("jobid")) {
                        jobId = node.getFirstChild().getNodeValue();
                    }
                }
            }
            if (jobId == null) {
                throw new CloudException("Could not scale server");
            }
            Document responseDoc = getProvider().waitForJob(doc, "Scale Server");

            if (responseDoc != null) {
                NodeList nodeList = responseDoc.getElementsByTagName("virtualmachine");
                if (nodeList.getLength() > 0) {
                    Node virtualMachine = nodeList.item(0);
                    vm = toVirtualMachine(virtualMachine);
                    if (vm != null) {
                        if (restart) {
                            start(vmId);
                        }
                        return vm;
                    }
                }
            }
            if (restart) {
                start(vmId);
            }
            return getVirtualMachine(vmId);
        } finally {
            APITrace.end();
        }
    }

    private transient volatile VMCapabilities capabilities;

    @Nonnull
    @Override
    public VirtualMachineCapabilities getCapabilities() throws InternalException, CloudException {
        if (capabilities == null) {
            capabilities = new VMCapabilities(getProvider());
        }
        return capabilities;
    }

    @Nullable
    @Override
    public VMScalingCapabilities describeVerticalScalingCapabilities() throws CloudException, InternalException {
        return VMScalingCapabilities.getInstance(false, true, Requirement.NONE, Requirement.NONE);
    }

    @Nullable
    @Override
    public String getPassword(@Nonnull String vmId) throws InternalException, CloudException {
        return getRootPassword(vmId);
    }

    private String getRootPassword(@Nonnull String serverId) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "VM.getPassword");
        try {
            ProviderContext ctx = getContext();

            if (ctx == null) {
                throw new CloudException("No context was specified for this request");
            }

            CSMethod method = new CSMethod(getProvider());
            Document doc = method.get(method.buildUrl(GET_VIRTUAL_MACHINE_PASSWORD, new Param("id", serverId)),
                    GET_VIRTUAL_MACHINE_PASSWORD);

            if (doc != null) {
                NodeList matches = doc.getElementsByTagName("getvmpasswordresponse");

                for (int i = 0; i < matches.getLength(); i++) {
                    Node node = matches.item(i);

                    if (node != null) {
                        NodeList attributes = node.getChildNodes();
                        for (int j = 0; j < attributes.getLength(); j++) {
                            Node attribute = attributes.item(j);
                            String name = attribute.getNodeName().toLowerCase();
                            String value;

                            if (attribute.getChildNodes().getLength() > 0) {
                                value = attribute.getFirstChild().getNodeValue();
                            } else {
                                value = null;
                            }
                            if (name.equals("password")) {
                                NodeList nodes = attribute.getChildNodes();
                                for (int k = 0; k < nodes.getLength(); k++) {
                                    Node password = nodes.item(k);
                                    name = password.getNodeName().toLowerCase();

                                    if (password.getChildNodes().getLength() > 0) {
                                        value = password.getFirstChild().getNodeValue();
                                    } else {
                                        value = null;
                                    }
                                    if (name.equals("encryptedpassword")) {
                                        return value;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            logger.warn("Unable to find password for vm with id " + serverId);
            return null;
        } catch (CSException e) {
            if (e.getHttpCode() == 431) {
                logger.warn("No password found for vm " + serverId);
            }
            return null;
        } finally {
            APITrace.end();
        }
    }

    private String getRootPassword(@Nonnull String serverId) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "VM.getPassword");
        try {
            ProviderContext ctx = provider.getContext();

            if (ctx == null) {
                throw new CloudException("No context was specified for this request");
            }

            CSMethod method = new CSMethod(provider);
            Document doc = method.get(method.buildUrl(GET_VIRTUAL_MACHINE_PASSWORD, new Param("id", serverId)),
                    GET_VIRTUAL_MACHINE_PASSWORD);

            if (doc != null) {
                NodeList matches = doc.getElementsByTagName("getvmpasswordresponse");

                for (int i = 0; i < matches.getLength(); i++) {
                    Node node = matches.item(i);

                    if (node != null) {
                        NodeList attributes = node.getChildNodes();
                        for (int j = 0; j < attributes.getLength(); j++) {
                            Node attribute = attributes.item(j);
                            String name = attribute.getNodeName().toLowerCase();
                            String value;

                            if (attribute.getChildNodes().getLength() > 0) {
                                value = attribute.getFirstChild().getNodeValue();
                            } else {
                                value = null;
                            }
                            if (name.equals("password")) {
                                NodeList nodes = attribute.getChildNodes();
                                for (int k = 0; k < nodes.getLength(); k++) {
                                    Node password = nodes.item(k);
                                    name = password.getNodeName().toLowerCase();

                                    if (password.getChildNodes().getLength() > 0) {
                                        value = password.getFirstChild().getNodeValue();
                                    } else {
                                        value = null;
                                    }
                                    if (name.equals("encryptedpassword")) {
                                        return value;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            logger.warn("Unable to find password for vm with id " + serverId);
            return null;
        } catch (CSException e) {
            if (e.getHttpCode() == 431) {
                logger.warn("No password found for vm " + serverId);
            }
            return null;
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nullable VirtualMachine getVirtualMachine(@Nonnull String serverId)
            throws InternalException, CloudException {
        APITrace.begin(getProvider(), "VM.getVirtualMachine");
        try {

            CSMethod method = new CSMethod(getProvider());

            try {
                Document doc = method.get(method.buildUrl(LIST_VIRTUAL_MACHINES, new Param("id", serverId)),
                        LIST_VIRTUAL_MACHINES);
                NodeList matches = doc.getElementsByTagName("virtualmachine");

                if (matches.getLength() < 1) {
                    return null;
                }
                for (int i = 0; i < matches.getLength(); i++) {
                    VirtualMachine s = toVirtualMachine(matches.item(i));

                    if (s != null && s.getProviderVirtualMachineId().equals(serverId)) {
                        return s;
                    }
                }
            } catch (CloudException e) {
                if (e.getMessage().contains("does not exist")) {
                    return null;
                }
                throw e;
            }
            return null;
        } finally {
            APITrace.end();
        }
    }

    @Override
    public boolean isSubscribed() throws CloudException, InternalException {
        APITrace.begin(getProvider(), "VM.isSubscribed");
        try {
            CSMethod method = new CSMethod(getProvider());

            try {
                method.get(method.buildUrl(CSTopology.LIST_ZONES, new Param("available", "true")),
                        CSTopology.LIST_ZONES);
                return true;
            } catch (CSException e) {
                int code = e.getHttpCode();

                if (code == HttpServletResponse.SC_FORBIDDEN || code == 401 || code == 531) {
                    return false;
                }
                throw e;
            } catch (CloudException e) {
                int code = e.getHttpCode();

                if (code == HttpServletResponse.SC_FORBIDDEN || code == HttpServletResponse.SC_UNAUTHORIZED) {
                    return false;
                }
                throw e;
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull VirtualMachine launch(@Nonnull VMLaunchOptions withLaunchOptions)
            throws CloudException, InternalException {
        APITrace.begin(getProvider(), "VM.launch");
        try {
            String id = withLaunchOptions.getStandardProductId();

            VirtualMachineProduct product = getProduct(id);

            if (product == null) {
                throw new CloudException("Invalid product ID: " + id);
            }
            if (getProvider().getVersion().greaterThan(CSVersion.CS21)) {
                return launch22(withLaunchOptions.getMachineImageId(), product, withLaunchOptions.getDataCenterId(),
                        withLaunchOptions.getFriendlyName(), withLaunchOptions.getBootstrapKey(),
                        withLaunchOptions.getVlanId(), withLaunchOptions.getFirewallIds(),
                        withLaunchOptions.getUserData());
            } else {
                return launch21(withLaunchOptions.getMachineImageId(), product, withLaunchOptions.getDataCenterId(),
                        withLaunchOptions.getFriendlyName());
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    @Deprecated
    @SuppressWarnings("deprecation")
    public @Nonnull VirtualMachine launch(@Nonnull String imageId, @Nonnull VirtualMachineProduct product,
            @Nonnull String inZoneId, @Nonnull String name, @Nonnull String description, @Nullable String usingKey,
            @Nullable String withVlanId, boolean withMonitoring, boolean asSandbox,
            @Nullable String[] protectedByFirewalls, @Nullable Tag... tags)
            throws InternalException, CloudException {
        if (getProvider().getVersion().greaterThan(CSVersion.CS21)) {
            StringBuilder userData = new StringBuilder();

            if (tags != null && tags.length > 0) {
                for (Tag tag : tags) {
                    userData.append(tag.getKey());
                    userData.append("=");
                    userData.append(tag.getValue());
                    userData.append("\n");
                }
            } else {
                userData.append("created=Dasein Cloud\n");
            }
            return launch22(imageId, product, inZoneId, name, usingKey, withVlanId, protectedByFirewalls,
                    userData.toString());
        } else {
            return launch21(imageId, product, inZoneId, name);
        }
    }

    private VirtualMachine launch21(String imageId, VirtualMachineProduct product, String inZoneId, String name)
            throws InternalException, CloudException {
        CSMethod method = new CSMethod(getProvider());
        return launch(
                method.get(
                        method.buildUrl(DEPLOY_VIRTUAL_MACHINE, new Param("zoneId", getContext().getRegionId()),
                                new Param("serviceOfferingId", product.getProviderProductId()),
                                new Param("templateId", imageId), new Param("displayName", name)),
                        DEPLOY_VIRTUAL_MACHINE));
    }

    private void load() {
        try {
            InputStream input = VirtualMachines.class.getResourceAsStream("/cloudMappings.cfg");
            BufferedReader reader = new BufferedReader(new InputStreamReader(input));
            Properties properties = new Properties();
            String line;

            while ((line = reader.readLine()) != null) {
                if (line.startsWith("#")) {
                    continue;
                }
                int idx = line.indexOf('=');
                if (idx < 0 || line.endsWith("=")) {
                    continue;
                }
                String cloudUrl = line.substring(0, idx);
                String cloudId = line.substring(idx + 1);
                properties.put(cloudUrl, cloudId);
            }
            cloudMappings = properties;
        } catch (Throwable ignore) {
            // ignore
        }
        try {
            InputStream input = VirtualMachines.class.getResourceAsStream("/customNetworkMappings.cfg");
            Map<String, Map<String, String>> mapping = new HashMap<String, Map<String, String>>();
            Properties properties = new Properties();

            properties.load(input);
            for (Object key : properties.keySet()) {
                String[] trueKey = ((String) key).split(",");
                Map<String, String> current = mapping.get(trueKey[0]);

                if (current == null) {
                    current = new HashMap<String, String>();
                    mapping.put(trueKey[0], current);
                }
                current.put(trueKey[1], (String) properties.get(key));
            }
            customNetworkMappings = mapping;
        } catch (Throwable ignore) {
            // ignore
        }
        try {
            InputStream input = VirtualMachines.class.getResourceAsStream("/customServiceMappings.cfg");
            Map<String, Map<String, Set<String>>> mapping = new HashMap<String, Map<String, Set<String>>>();
            Properties properties = new Properties();

            properties.load(input);

            for (Object key : properties.keySet()) {
                String value = (String) properties.get(key);

                if (value != null) {
                    String[] trueKey = ((String) key).split(",");
                    Map<String, Set<String>> tmp = mapping.get(trueKey[0]);

                    if (tmp == null) {
                        tmp = new HashMap<String, Set<String>>();
                        mapping.put(trueKey[0], tmp);
                    }
                    TreeSet<String> m = new TreeSet<String>();
                    String[] offerings = value.split(",");

                    if (offerings == null || offerings.length < 1) {
                        m.add(value);
                    } else {
                        Collections.addAll(m, offerings);
                    }
                    tmp.put(trueKey[1], m);
                }
            }
            customServiceMappings = mapping;
        } catch (Throwable ignore) {
            // ignore
        }
    }

    private @Nonnull VirtualMachine launch22(@Nonnull String imageId, @Nonnull VirtualMachineProduct product,
            @Nullable String inZoneId, @Nonnull String name, @Nullable String withKeypair,
            @Nullable String targetVlanId, @Nullable String[] protectedByFirewalls, @Nullable String userData)
            throws InternalException, CloudException {
        ProviderContext ctx = getContext();
        List<String> vlans = null;

        if (ctx == null) {
            throw new InternalException("No context was provided for this request");
        }
        String regionId = ctx.getRegionId();

        if (inZoneId == null || inZoneId.isEmpty()) {
            inZoneId = regionId;
        }

        if (inZoneId == null || inZoneId.isEmpty()) {
            throw new InternalException("No region is established for this request");
        }

        String prdId = product.getProviderProductId();

        if (customNetworkMappings == null) {
            load();
        }
        if (customNetworkMappings != null) {
            String cloudId = cloudMappings.getProperty(ctx.getCloud().getEndpoint());

            if (cloudId != null) {
                Map<String, String> map = customNetworkMappings.get(cloudId);

                if (map != null) {
                    String id = map.get(prdId);

                    if (id != null) {
                        targetVlanId = id;
                    }
                }
            }
        }
        if (targetVlanId != null && targetVlanId.length() < 1) {
            targetVlanId = null;
        }
        if (userData == null) {
            userData = "";
        }
        String securityGroupIds = null;

        if (protectedByFirewalls != null && protectedByFirewalls.length > 0) {
            StringBuilder str = new StringBuilder();
            int idx = 0;

            for (String fw : protectedByFirewalls) {
                fw = fw.trim();
                if (!fw.equals("")) {
                    str.append(fw);
                    if ((idx++) < protectedByFirewalls.length - 1) {
                        str.append(",");
                    }
                }
            }
            securityGroupIds = str.toString();
        }
        if (targetVlanId == null) {
            Network vlan = getProvider().getNetworkServices().getVlanSupport();

            if (vlan != null && vlan.isSubscribed()) {
                if (getCapabilities().identifyVlanRequirement().equals(Requirement.REQUIRED)) {
                    vlans = vlan.findFreeNetworks();
                }
            }
        } else {
            vlans = new ArrayList<String>();
            vlans.add(targetVlanId);
        }
        if (securityGroupIds != null && securityGroupIds.length() > 0) {
            // TODO: shouldn't we throw OpNotSupported if firewalls aren't supported but still requested?
            // otherwise it's like a confusion, no?
            if (!getProvider().getDataCenterServices().supportsSecurityGroups(regionId,
                    vlans == null || vlans.size() < 1)) {
                securityGroupIds = null;
            } else {
                if (!getProvider().getServiceProvider().equals(CSServiceProvider.DATAPIPE)) {
                    securityGroupIds = null;
                }
            }
        } else if (getProvider().getDataCenterServices().supportsSecurityGroups(regionId,
                vlans == null || vlans.size() < 1)) {
            /*
            String sgId = null;
                
            if( withVlanId == null ) {
            Collection<Firewall> firewalls = getProvider().getNetworkServices().getFirewallSupport().list();
                
            for( Firewall fw : firewalls ) {
                if( fw.getName().equalsIgnoreCase("default") && fw.getProviderVlanId() == null ) {
                    sgId = fw.getProviderFirewallId();
                    break;
                }
            }
            if( sgId == null ) {
                try {
                    sgId = getProvider().getNetworkServices().getFirewallSupport().create("default", "Default security group");
                }
                catch( Throwable t ) {
                    logger.warn("Unable to create a default security group, gonna try anyways: " + t.getMessage());
                }
            }
            if( sgId != null ) {
                securityGroupIds = sgId;
            }
            }
            else {
            Collection<Firewall> firewalls = getProvider().getNetworkServices().getFirewallSupport().list();
                
            for( Firewall fw : firewalls ) {
                if( (fw.getName().equalsIgnoreCase("default") || fw.getName().equalsIgnoreCase("default-" + withVlanId)) && withVlanId.equals(fw.getProviderVlanId()) ) {
                    sgId = fw.getProviderFirewallId();
                    break;
                }
            }
            if( sgId == null ) {
                try {
                    sgId = getProvider().getNetworkServices().getFirewallSupport().createInVLAN("default-" + withVlanId, "Default " + withVlanId + " security group", withVlanId);
                }
                catch( Throwable t ) {
                    logger.warn("Unable to create a default security group, gonna try anyways: " + t.getMessage());
                }
            }
            }
            if( sgId != null ) {
            securityGroupIds = sgId;
            count++;
            }    
            */
        }
        List<Param> params = new ArrayList<Param>();

        params.add(new Param("zoneId", inZoneId));
        params.add(new Param("serviceOfferingId", prdId));
        params.add(new Param("templateId", imageId));
        params.add(new Param("displayName", name));
        if (userData != null && userData.length() > 0) {
            try {
                params.add(new Param("userdata",
                        new String(Base64.encodeBase64(userData.getBytes("utf-8")), "utf-8")));
            } catch (UnsupportedEncodingException e) {
                //                e.printStackTrace();
            }
        }
        if (withKeypair != null) {
            params.add(new Param("keypair", withKeypair));
        }
        if (securityGroupIds != null && securityGroupIds.length() > 0) {
            params.add(new Param("securitygroupids", securityGroupIds));
        }
        if (vlans != null && vlans.size() > 0) {
            CloudException lastError = null;

            for (String withVlanId : vlans) {
                params.add(new Param("networkIds", withVlanId));

                try {
                    CSMethod method = new CSMethod(getProvider());

                    return launch(method.get(
                            method.buildUrl(DEPLOY_VIRTUAL_MACHINE, params.toArray(new Param[params.size()])),
                            DEPLOY_VIRTUAL_MACHINE));
                } catch (CloudException e) {
                    if (e.getMessage().contains("sufficient address capacity")) {
                        lastError = e;
                        continue;
                    }
                    throw e;
                }
            }
            if (lastError == null) {
                throw lastError;
            }
            throw new CloudException("Unable to identify a network into which a VM can be launched");
        } else {
            CSMethod method = new CSMethod(getProvider());

            return launch(
                    method.get(method.buildUrl(DEPLOY_VIRTUAL_MACHINE, params.toArray(new Param[params.size()])),
                            DEPLOY_VIRTUAL_MACHINE));
        }
    }

    private @Nonnull VirtualMachine launch(@Nonnull Document doc) throws InternalException, CloudException {
        NodeList matches = doc.getElementsByTagName("deployvirtualmachineresponse");
        String serverId = null;
        String jobId = null;

        for (int i = 0; i < matches.getLength(); i++) {
            NodeList attrs = matches.item(i).getChildNodes();

            for (int j = 0; j < attrs.getLength(); j++) {
                Node node = attrs.item(j);

                if (node != null && (node.getNodeName().equalsIgnoreCase("virtualmachineid")
                        || node.getNodeName().equalsIgnoreCase("id"))) {
                    serverId = node.getFirstChild().getNodeValue();
                    break;
                } else if (node != null && node.getNodeName().equalsIgnoreCase("jobid")) {
                    jobId = node.getFirstChild().getNodeValue();
                }
            }
            if (serverId != null) {
                break;
            }
        }
        if (serverId == null && jobId == null) {
            throw new CloudException("Could not launch server");
        }
        // TODO: very odd logic below; figure out what it thinks it is doing

        VirtualMachine vm = null;

        // have to wait on jobs as sometimes they fail and we need to bubble error message up
        Document responseDoc = getProvider().waitForJob(doc, "Launch Server");

        //parse vm from job completion response to capture vm passwords on initial launch.
        if (responseDoc != null) {
            NodeList nodeList = responseDoc.getElementsByTagName("virtualmachine");
            if (nodeList.getLength() > 0) {
                Node virtualMachine = nodeList.item(0);
                vm = toVirtualMachine(virtualMachine);
                if (vm != null) {
                    return vm;
                }
            }
        }

        if (vm == null) {
            vm = getVirtualMachine(serverId);
        }
        if (vm == null) {
            throw new CloudException("No virtual machine provided: " + serverId);
        }
        return vm;
    }

    @Override
    public @Nonnull Iterable<String> listFirewalls(@Nonnull String vmId) throws InternalException, CloudException {
        APITrace.begin(getProvider(), "VM.listFirewalls");
        try {
            SecurityGroup support = getProvider().getNetworkServices().getFirewallSupport();

            if (support == null) {
                return Collections.emptyList();
            }
            return support.listFirewallsForVM(vmId);
        } finally {
            APITrace.end();
        }
    }

    private void setFirewalls(@Nonnull VirtualMachine vm) throws InternalException, CloudException {
        APITrace.begin(getProvider(), "VM.setFirewalls");
        try {
            SecurityGroup support = getProvider().getNetworkServices().getFirewallSupport();

            if (support == null) {
                return;
            }
            ArrayList<String> ids = new ArrayList<String>();

            Iterable<String> firewalls;
            try {
                firewalls = support.listFirewallsForVM(vm.getProviderVirtualMachineId());
            } catch (Throwable t) {
                logger.error("Problem listing firewalls (listSecurityGroups) for '"
                        + vm.getProviderVirtualMachineId() + "': " + t.getMessage());
                return;
            }

            for (String id : firewalls) {
                ids.add(id);
            }
            vm.setProviderFirewallIds(ids.toArray(new String[ids.size()]));
        } finally {
            APITrace.end();
        }
    }

    @Override
    public Iterable<VirtualMachineProduct> listProducts(VirtualMachineProductFilterOptions options,
            Architecture architecture) throws InternalException, CloudException {
        APITrace.begin(getProvider(), "VM.listProducts");
        try {
            ProviderContext ctx = getContext();

            if (ctx == null) {
                throw new CloudException("No context was configured for this request");
            }

            Cache<VirtualMachineProduct> cache = Cache.getInstance(getProvider(), "ServerProducts",
                    VirtualMachineProduct.class, CacheLevel.REGION_ACCOUNT,
                    new TimePeriod<Hour>(4, TimePeriod.HOUR));
            Collection<VirtualMachineProduct> products = (Collection<VirtualMachineProduct>) cache
                    .get(getContext());
            if (products == null) {
                Set<String> mapping = null;

                if (customServiceMappings == null) {
                    load();
                }
                if (customServiceMappings != null) {
                    String cloudId = cloudMappings.getProperty(getContext().getCloud().getEndpoint());

                    if (cloudId != null) {
                        Map<String, Set<String>> map = customServiceMappings.get(cloudId);

                        if (map != null) {
                            mapping = map.get(getContext().getRegionId());
                        }
                    }
                }
                products = new ArrayList<VirtualMachineProduct>();

                CSMethod method = new CSMethod(getProvider());
                Document doc = method.get(
                        method.buildUrl(LIST_SERVICE_OFFERINGS, new Param("zoneId", ctx.getRegionId())),
                        LIST_SERVICE_OFFERINGS);
                NodeList matches = doc.getElementsByTagName("serviceoffering");

                for (int i = 0; i < matches.getLength(); i++) {
                    String id = null, name = null;
                    Node node = matches.item(i);
                    NodeList attributes;
                    int memory = 0;
                    int cpu = 0;
                    Boolean customized = null;
                    attributes = node.getChildNodes();
                    for (int j = 0; j < attributes.getLength(); j++) {
                        Node n = attributes.item(j);
                        String value;

                        if (n.getChildNodes().getLength() > 0) {
                            value = n.getFirstChild().getNodeValue();
                        } else {
                            value = null;
                        }
                        if (n.getNodeName().equals("id")) {
                            id = value;
                        } else if (n.getNodeName().equals("name")) {
                            name = value;
                        } else if (n.getNodeName().equals("cpunumber")) {
                            cpu = Integer.parseInt(value);
                        } else if (n.getNodeName().equals("memory")) {
                            memory = Integer.parseInt(value);
                        } else if (n.getNodeName().equals("iscustomized")) {
                            customized = Boolean.valueOf(value);
                        }
                        if (id != null && name != null && cpu > 0 && memory > 0 && customized != null) {
                            break;
                        }
                    }
                    if (id != null && name != null && cpu > 0 && memory > 0 && !customized) {
                        if (mapping == null || mapping.contains(id)) {
                            VirtualMachineProduct product;

                            product = new VirtualMachineProduct();
                            product.setProviderProductId(id);
                            product.setName(name + " (" + cpu + " CPU/" + memory + "MB RAM)");
                            product.setDescription(name + " (" + cpu + " CPU/" + memory + "MB RAM)");
                            product.setRamSize(new Storage<Megabyte>(memory, Storage.MEGABYTE));
                            product.setCpuCount(cpu);
                            product.setRootVolumeSize(new Storage<Gigabyte>(1, Storage.GIGABYTE));
                            if (options != null) {
                                if (options.matches(product)) {
                                    products.add(product);
                                }
                            } else {
                                products.add(product);
                            }
                        }
                    }
                }
                cache.put(getContext(), products);
            }
            return products;
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull Iterable<ResourceStatus> listVirtualMachineStatus() throws InternalException, CloudException {
        APITrace.begin(getProvider(), "VM.listVirtualMachineStatus");
        try {
            ProviderContext ctx = getContext();

            if (ctx == null) {
                throw new CloudException("No context was specified for this request");
            }
            CSMethod method = new CSMethod(getProvider());
            Document doc = method.get(
                    method.buildUrl(LIST_VIRTUAL_MACHINES, new Param("zoneId", ctx.getRegionId())),
                    LIST_VIRTUAL_MACHINES);
            ArrayList<ResourceStatus> servers = new ArrayList<ResourceStatus>();

            int numPages = 1;
            NodeList nodes = doc.getElementsByTagName("count");
            Node n = nodes.item(0);
            if (n != null) {
                String value = n.getFirstChild().getNodeValue().trim();
                int count = Integer.parseInt(value);
                numPages = count / 500;
                int remainder = count % 500;
                if (remainder > 0) {
                    numPages++;
                }
            }

            for (int page = 1; page <= numPages; page++) {
                if (page > 1) {
                    String nextPage = String.valueOf(page);
                    doc = method.get(
                            method.buildUrl(LIST_VIRTUAL_MACHINES, new Param("zoneId", ctx.getRegionId()),
                                    new Param("pagesize", "500"), new Param("page", nextPage)),
                            LIST_VIRTUAL_MACHINES);
                }
                NodeList matches = doc.getElementsByTagName("virtualmachine");

                for (int i = 0; i < matches.getLength(); i++) {
                    Node node = matches.item(i);

                    if (node != null) {
                        ResourceStatus vm = toStatus(node);

                        if (vm != null) {
                            servers.add(vm);
                        }
                    }
                }
            }
            return servers;
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull Iterable<VirtualMachine> listVirtualMachines() throws InternalException, CloudException {
        APITrace.begin(getProvider(), "VM.listVirtualMachines");
        try {
            ProviderContext ctx = getContext();

            if (ctx == null) {
                throw new CloudException("No context was specified for this request");
            }
            CSMethod method = new CSMethod(getProvider());
            Document doc = method.get(
                    method.buildUrl(LIST_VIRTUAL_MACHINES, new Param("zoneId", ctx.getRegionId())),
                    LIST_VIRTUAL_MACHINES);
            ArrayList<VirtualMachine> servers = new ArrayList<VirtualMachine>();

            int numPages = 1;
            NodeList nodes = doc.getElementsByTagName("count");
            Node n = nodes.item(0);
            if (n != null) {
                String value = n.getFirstChild().getNodeValue().trim();
                int count = Integer.parseInt(value);
                numPages = count / 500;
                int remainder = count % 500;
                if (remainder > 0) {
                    numPages++;
                }
            }

            for (int page = 1; page <= numPages; page++) {
                if (page > 1) {
                    String nextPage = String.valueOf(page);
                    doc = method.get(
                            method.buildUrl(LIST_VIRTUAL_MACHINES, new Param("zoneId", ctx.getRegionId()),
                                    new Param("pagesize", "500"), new Param("page", nextPage)),
                            LIST_VIRTUAL_MACHINES);
                }
                NodeList matches = doc.getElementsByTagName("virtualmachine");

                for (int i = 0; i < matches.getLength(); i++) {
                    Node node = matches.item(i);

                    if (node != null) {
                        VirtualMachine vm = toVirtualMachine(node);

                        if (vm != null) {
                            servers.add(vm);
                        }
                    }
                }
            }
            return servers;
        } finally {
            APITrace.end();
        }
    }

    private String resetPassword(@Nonnull String serverId) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "VM.resetPassword");
        try {
            ProviderContext ctx = getContext();

            if (ctx == null) {
                throw new CloudException("No context was specified for this request");
            }

            CSMethod method = new CSMethod(getProvider());
            Document doc = method.get(method.buildUrl(RESET_VIRTUAL_MACHINE_PASSWORD, new Param("id", serverId)),
                    RESET_VIRTUAL_MACHINE_PASSWORD);

            Document responseDoc = getProvider().waitForJob(doc, "reset vm password");

            if (responseDoc != null) {
                NodeList matches = responseDoc.getElementsByTagName("virtualmachine");

                for (int i = 0; i < matches.getLength(); i++) {
                    Node node = matches.item(i);

                    if (node != null) {
                        NodeList attributes = node.getChildNodes();
                        for (int j = 0; j < attributes.getLength(); j++) {
                            Node attribute = attributes.item(j);
                            String name = attribute.getNodeName().toLowerCase();
                            String value;

                            if (attribute.getChildNodes().getLength() > 0) {
                                value = attribute.getFirstChild().getNodeValue();
                            } else {
                                value = null;
                            }
                            if (name.equals("password")) {
                                return value;
                            }
                        }
                    }
                }
            }

            logger.warn("Unable to find password for vm with id " + serverId);
            return null;
        } finally {
            APITrace.end();
        }
    }

    @Override
    public void reboot(@Nonnull String serverId) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "VM.reboot");
        try {
            CSMethod method = new CSMethod(getProvider());

            method.get(method.buildUrl(REBOOT_VIRTUAL_MACHINE, new Param("id", serverId)), REBOOT_VIRTUAL_MACHINE);
        } finally {
            APITrace.end();
        }
    }

    @Override
    public void start(@Nonnull String serverId) throws InternalException, CloudException {
        APITrace.begin(getProvider(), "VM.start");
        try {
            CSMethod method = new CSMethod(getProvider());

            method.get(method.buildUrl(START_VIRTUAL_MACHINE, new Param("id", serverId)), START_VIRTUAL_MACHINE);
        } finally {
            APITrace.end();
        }
    }

    @Override
    public void stop(@Nonnull String vmId, boolean force) throws InternalException, CloudException {
        APITrace.begin(getProvider(), "VM.stop");
        try {
            CSMethod method = new CSMethod(getProvider());

            method.get(method.buildUrl(STOP_VIRTUAL_MACHINE, new Param("id", vmId),
                    new Param("forced", String.valueOf(force))), STOP_VIRTUAL_MACHINE);
        } finally {
            APITrace.end();
        }
    }

    @Override
    public void terminate(@Nonnull String serverId, @Nullable String explanation)
            throws InternalException, CloudException {
        APITrace.begin(getProvider(), "VM.terminate");
        try {
            CSMethod method = new CSMethod(getProvider());
            List<Param> params = new ArrayList<Param>();
            params.add(new Param("id", serverId));
            if (getProvider().isAdminAccount()) {
                params.add(new Param("expunge", "true"));
            }
            method.get(method.buildUrl(DESTROY_VIRTUAL_MACHINE, params), DESTROY_VIRTUAL_MACHINE);
        } finally {
            APITrace.end();
        }
    }

    private @Nullable ResourceStatus toStatus(@Nullable Node node) throws CloudException, InternalException {
        if (node == null) {
            return null;
        }
        NodeList attributes = node.getChildNodes();
        VmState state = null;
        String serverId = null;

        for (int i = 0; i < attributes.getLength(); i++) {
            Node attribute = attributes.item(i);
            String name = attribute.getNodeName().toLowerCase();
            String value;

            if (attribute.getChildNodes().getLength() > 0) {
                value = attribute.getFirstChild().getNodeValue();
            } else {
                value = null;
            }
            if (name.equals("virtualmachineid") || name.equals("id")) {
                serverId = value;
            } else if (name.equals("state")) {
                if (value == null) {
                    state = VmState.PENDING;
                } else if (value.equalsIgnoreCase("stopped")) {
                    state = VmState.STOPPED;
                } else if (value.equalsIgnoreCase("running")) {
                    state = VmState.RUNNING;
                } else if (value.equalsIgnoreCase("stopping")) {
                    state = VmState.STOPPING;
                } else if (value.equalsIgnoreCase("starting")) {
                    state = VmState.PENDING;
                } else if (value.equalsIgnoreCase("creating")) {
                    state = VmState.PENDING;
                } else if (value.equalsIgnoreCase("migrating")) {
                    state = VmState.REBOOTING;
                } else if (value.equalsIgnoreCase("destroyed")) {
                    state = VmState.TERMINATED;
                } else if (value.equalsIgnoreCase("error")) {
                    logger.warn("VM is in an error state.");
                    return null;
                } else if (value.equalsIgnoreCase("expunging")) {
                    state = VmState.TERMINATED;
                } else if (value.equalsIgnoreCase("ha")) {
                    state = VmState.REBOOTING;
                } else {
                    throw new CloudException("Unexpected server state: " + value);
                }
            }
            if (serverId != null && state != null) {
                break;
            }
        }
        if (serverId == null) {
            return null;
        }
        if (state == null) {
            state = VmState.PENDING;
        }
        return new ResourceStatus(serverId, state);
    }

    private @Nullable VirtualMachine toVirtualMachine(@Nullable Node node)
            throws CloudException, InternalException {
        if (node == null) {
            return null;
        }
        HashMap<String, String> properties = new HashMap<String, String>();
        VirtualMachine server = new VirtualMachine();
        NodeList attributes = node.getChildNodes();
        String productId = null;

        server.setProviderOwnerId(getContext().getAccountNumber());
        server.setClonable(false);
        server.setImagable(false);
        server.setPausable(true);
        server.setPersistent(true);
        server.setArchitecture(Architecture.I64);
        for (int i = 0; i < attributes.getLength(); i++) {
            Node attribute = attributes.item(i);
            String name = attribute.getNodeName().toLowerCase();
            String value;

            if (attribute.getChildNodes().getLength() > 0) {
                value = attribute.getFirstChild().getNodeValue();
            } else {
                value = null;
            }
            if (name.equals("virtualmachineid") || name.equals("id")) {
                server.setProviderVirtualMachineId(value);
                logger.info("Processing VM id '" + value + "'");
            } else if (name.equals("name")) {
                server.setDescription(value);
            }
            /*
            else if( name.equals("haenable") ) {
            server.setPersistent(value != null && value.equalsIgnoreCase("true"));
            }
            */
            else if (name.equals("displayname")) {
                server.setName(value);
            } else if (name.equals("ipaddress")) { // v2.1
                if (value != null) {
                    server.setPrivateAddresses(new RawAddress(value));
                }
                server.setPrivateDnsAddress(value);
            } else if (name.equals("password")) {
                server.setRootPassword(value);
            } else if (name.equals("nic")) { // v2.2
                if (attribute.hasChildNodes()) {
                    NodeList parts = attribute.getChildNodes();
                    String addr = null;

                    for (int j = 0; j < parts.getLength(); j++) {
                        Node part = parts.item(j);

                        if (part.getNodeName().equalsIgnoreCase("ipaddress")) {
                            if (part.hasChildNodes()) {
                                addr = part.getFirstChild().getNodeValue();
                                if (addr != null) {
                                    addr = addr.trim();
                                }
                            }
                        } else if (part.getNodeName().equalsIgnoreCase("networkid")) {
                            server.setProviderVlanId(part.getFirstChild().getNodeValue().trim());
                        }
                    }
                    if (addr != null) {
                        boolean pub = false;

                        if (!addr.startsWith("10.") && !addr.startsWith("192.168.")) {
                            if (addr.startsWith("172.")) {
                                String[] nums = addr.split("\\.");

                                if (nums.length != 4) {
                                    pub = true;
                                } else {
                                    try {
                                        int x = Integer.parseInt(nums[1]);

                                        if (x < 16 || x > 31) {
                                            pub = true;
                                        }
                                    } catch (NumberFormatException ignore) {
                                        // ignore
                                    }
                                }
                            } else {
                                pub = true;
                            }
                        }
                        if (pub) {
                            server.setPublicAddresses(new RawAddress(addr));
                            if (server.getPublicDnsAddress() == null) {
                                server.setPublicDnsAddress(addr);
                            }
                        } else {
                            server.setPrivateAddresses(new RawAddress(addr));
                            if (server.getPrivateDnsAddress() == null) {
                                server.setPrivateDnsAddress(addr);
                            }
                        }
                    }
                }
            } else if (name.equals("osarchitecture")) {
                if (value != null && value.equals("32")) {
                    server.setArchitecture(Architecture.I32);
                } else {
                    server.setArchitecture(Architecture.I64);
                }
            } else if (name.equals("created")) {
                DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); //2009-02-03T05:26:32.612278

                try {
                    server.setCreationTimestamp(df.parse(value).getTime());
                } catch (ParseException e) {
                    logger.warn("Invalid date: " + value);
                    server.setLastBootTimestamp(0L);
                }
            } else if (name.equals("state")) {
                VmState state;

                //(Running, Stopped, Stopping, Starting, Creating, Migrating, HA).
                if (value.equalsIgnoreCase("stopped")) {
                    state = VmState.STOPPED;
                    server.setImagable(true);
                } else if (value.equalsIgnoreCase("running")) {
                    state = VmState.RUNNING;
                } else if (value.equalsIgnoreCase("stopping")) {
                    state = VmState.STOPPING;
                } else if (value.equalsIgnoreCase("starting")) {
                    state = VmState.PENDING;
                } else if (value.equalsIgnoreCase("creating")) {
                    state = VmState.PENDING;
                } else if (value.equalsIgnoreCase("migrating")) {
                    state = VmState.REBOOTING;
                } else if (value.equalsIgnoreCase("destroyed")) {
                    state = VmState.TERMINATED;
                } else if (value.equalsIgnoreCase("error")) {
                    state = VmState.ERROR;
                } else if (value.equalsIgnoreCase("expunging")) {
                    state = VmState.TERMINATED;
                } else if (value.equalsIgnoreCase("ha")) {
                    state = VmState.REBOOTING;
                } else {
                    throw new CloudException("Unexpected server state: " + value);
                }
                server.setCurrentState(state);
            } else if (name.equals("zoneid")) {
                server.setProviderRegionId(value);
                server.setProviderDataCenterId(value);
            } else if (name.equals("templateid")) {
                server.setProviderMachineImageId(value);
            } else if (name.equals("templatename")) {
                Platform platform = Platform.guess(value);
                if (platform.equals(Platform.UNKNOWN)) {
                    platform = guessForWindows(value);
                }
                server.setPlatform(platform);
            } else if (name.equals("serviceofferingid")) {
                productId = value;
            } else if (value != null) {
                properties.put(name, value);
            }
        }
        if (server.getName() == null) {
            server.setName(server.getProviderVirtualMachineId());
        }
        if (server.getDescription() == null) {
            server.setDescription(server.getName());
        }
        server.setProviderAssignedIpAddressId(null);
        if (server.getProviderRegionId() == null) {
            server.setProviderRegionId(getContext().getRegionId());
        }
        if (server.getProviderDataCenterId() == null) {
            server.setProviderDataCenterId(getContext().getRegionId());
        }
        if (productId != null) {
            server.setProductId(productId);
        }

        /*final String finalServerId = server.getProviderVirtualMachineId();
        // commenting out for now until we can find a way to return plain text rather than encrypted
        server.setPasswordCallback(new Callable<String>() {
        @Override
        public String call() throws Exception {
            return getRootPassword(finalServerId);
        }
        }
        );  */
        server.setTags(properties);
        return server;
    }

    private Platform guessForWindows(String name) {
        if (name == null) {
            return Platform.UNKNOWN;
        }
        String platform = name.toLowerCase();
        if (platform.contains("windows") || platform.contains("win")) {
            return Platform.WINDOWS;
        }
        return Platform.UNKNOWN;
    }
}