org.dasein.cloud.google.compute.server.ServerSupport.java Source code

Java tutorial

Introduction

Here is the source code for org.dasein.cloud.google.compute.server.ServerSupport.java

Source

/**
 * Copyright (C) 2012-2015 Dell, Inc
 * See annotations for authorship information
 *
 * ====================================================================
 * 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.
 * ====================================================================
 */

/* Contains code based on example code from https://cloud.google.com/compute/docs/instances/automate-pw-generation 
 * which is licensed under the following license.
 * 
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * 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.google.compute.server;

import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.spec.RSAPublicKeySpec;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.crypto.Cipher;

import org.apache.log4j.Logger;
import org.dasein.cloud.CloudErrorType;
import org.dasein.cloud.CloudException;
import org.dasein.cloud.InternalException;
import org.dasein.cloud.OperationNotSupportedException;
import org.dasein.cloud.ResourceStatus;
import org.dasein.cloud.Tag;
import org.dasein.cloud.VisibleScope;
import org.dasein.cloud.compute.AbstractVMSupport;
import org.dasein.cloud.compute.Architecture;
import org.dasein.cloud.compute.MachineImage;
import org.dasein.cloud.compute.Platform;
import org.dasein.cloud.compute.VMFilterOptions;
import org.dasein.cloud.compute.VMLaunchOptions;
import org.dasein.cloud.compute.VMScalingOptions;
import org.dasein.cloud.compute.VirtualMachine;
import org.dasein.cloud.compute.VirtualMachineProduct;
import org.dasein.cloud.compute.VirtualMachineProductFilterOptions;
import org.dasein.cloud.compute.VmState;
import org.dasein.cloud.compute.VolumeAttachment;
import org.dasein.cloud.compute.VolumeCreateOptions;
import org.dasein.cloud.google.Google;
import org.dasein.cloud.google.GoogleException;
import org.dasein.cloud.google.GoogleMethod;
import org.dasein.cloud.google.GoogleOperationType;
import org.dasein.cloud.google.capabilities.GCEInstanceCapabilities;
import org.dasein.cloud.network.RawAddress;
import org.dasein.cloud.network.VLAN;
import org.dasein.cloud.util.APITrace;
import org.dasein.cloud.util.Cache;
import org.dasein.cloud.util.CacheLevel;
import org.dasein.cloud.util.NamingConstraints;
import org.dasein.util.Jiterator;
import org.dasein.util.JiteratorPopulator;
import org.dasein.util.PopulatorThread;
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.Day;
import org.dasein.util.uom.time.TimePeriod;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.simple.parser.JSONParser;

import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.repackaged.org.apache.commons.codec.binary.Base64;
import com.google.api.services.compute.Compute;
import com.google.api.services.compute.model.AccessConfig;
import com.google.api.services.compute.model.AttachedDisk;
import com.google.api.services.compute.model.AttachedDiskInitializeParams;
import com.google.api.services.compute.model.Disk;
import com.google.api.services.compute.model.Image;
import com.google.api.services.compute.model.Instance;
import com.google.api.services.compute.model.InstanceAggregatedList;
import com.google.api.services.compute.model.MachineType;
import com.google.api.services.compute.model.MachineTypeAggregatedList;
import com.google.api.services.compute.model.MachineTypeList;
import com.google.api.services.compute.model.Metadata;
import com.google.api.services.compute.model.Metadata.Items;
import com.google.api.services.compute.model.NetworkInterface;
import com.google.api.services.compute.model.Operation;
import com.google.api.services.compute.model.Scheduling;
import com.google.api.services.compute.model.SerialPortOutput;
import com.google.api.services.compute.model.Tags;
import com.google.common.io.BaseEncoding;

public class ServerSupport extends AbstractVMSupport<Google> {

    private Google provider;
    static private final Logger logger = Google.getLogger(ServerSupport.class);
    private Cache<MachineTypeAggregatedList> machineTypesCache;

    public ServerSupport(Google provider) {
        super(provider);
        this.provider = provider;
        machineTypesCache = Cache.getInstance(provider, "MachineTypes", MachineTypeAggregatedList.class,
                CacheLevel.CLOUD, new TimePeriod<Day>(1, TimePeriod.DAY));
    }

    @Override
    public VirtualMachine alterVirtualMachine(@Nonnull String vmId, @Nonnull VMScalingOptions options)
            throws InternalException, CloudException {
        throw new OperationNotSupportedException("GCE does not support altering of existing instances.");
    }

    @Override
    public VirtualMachine modifyInstance(@Nonnull String vmId, @Nonnull String[] firewalls)
            throws InternalException, CloudException {
        throw new OperationNotSupportedException("GCE does not support altering of existing instances.");
    }

    @Override
    public @Nonnull VirtualMachine clone(@Nonnull String vmId, @Nonnull String intoDcId, @Nonnull String name,
            @Nonnull String description, boolean powerOn, String... firewallIds)
            throws InternalException, CloudException {
        throw new OperationNotSupportedException("GCE does not support cloning of instances via the API.");
    }

    @Override
    public void disableAnalytics(@Nonnull String vmId) throws InternalException, CloudException {
        throw new OperationNotSupportedException("GCE does not currently support analytics.");
    }

    @Override
    public void enableAnalytics(@Nonnull String vmId) throws InternalException, CloudException {
        throw new OperationNotSupportedException("GCE does not currently support analytics.");
    }

    private transient volatile GCEInstanceCapabilities capabilities;

    @Override
    public @Nonnull GCEInstanceCapabilities getCapabilities() {
        if (capabilities == null) {
            capabilities = new GCEInstanceCapabilities(provider);
        }
        return capabilities;
    }

    @Override
    public String getPassword(@Nonnull String vmId) throws InternalException, CloudException {
        throw new OperationNotSupportedException("GCE instances do not have passwords");
    }

    public @Nonnull String getVmNameFromId(String vmId) throws InternalException, CloudException {
        if (null == vmId) {
            throw new InternalException("vmId cannot be null");
        }

        if (vmId.contains("_")) {
            String[] parts = vmId.split("_");
            if (null == parts[0]) {
                throw new InternalException("vmId cannot begin with '_'");
            }
            return parts[0];
        } else {
            return vmId;
        }
    }

    public @Nonnull String getVmIdFromName(String vmName) throws InternalException, CloudException {
        if (null == vmName) {
            throw new InternalException("vmName cannot be null ");
        }
        VirtualMachine vm = getVirtualMachine(vmName);
        if ((null != vm) && (null != vm.getProviderVirtualMachineId())) {
            return vm.getProviderVirtualMachineId();
        } else {
            throw new CloudException("Unable to lookup vmId for vm named: " + vmName);
        }
    }

    @Override
    public @Nonnull String getConsoleOutput(@Nonnull String vmId) throws InternalException, CloudException {
        try {
            for (VirtualMachine vm : listVirtualMachines()) {
                if (vm.getProviderVirtualMachineId().equalsIgnoreCase(vmId)) {
                    Compute gce = provider.getGoogleCompute();
                    SerialPortOutput output = gce.instances()
                            .getSerialPortOutput(provider.getContext().getAccountNumber(),
                                    vm.getProviderDataCenterId(), getVmNameFromId(vmId))
                            .execute();
                    return output.getContents();
                }
            }
        } catch (IOException ex) {
            logger.error(ex.getMessage());
            if (ex.getClass() == GoogleJsonResponseException.class) {
                GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                        gjre.getDetails().getMessage());
            } else
                throw new CloudException(
                        "An error occurred when getting console output for VM: " + vmId + ": " + ex.getMessage());
        }
        throw new InternalException("The Virtual Machine: " + vmId + " could not be found.");
    }

    @Override
    public VirtualMachineProduct getProduct(@Nonnull String productId) throws InternalException, CloudException {
        try {
            Compute gce = provider.getGoogleCompute();
            String[] parts = productId.split("\\+");
            if ((parts != null) && (parts.length > 1)) {
                MachineTypeList types = gce.machineTypes().list(provider.getContext().getAccountNumber(), parts[1])
                        .setFilter("name eq " + parts[0]).execute();
                if ((null != types) && (null != types.getItems())) {
                    for (MachineType type : types.getItems()) {
                        if (parts[0].equals(type.getName()))
                            return toProduct(type);
                    }
                }
            }
            return null; // Tests indicate null should come back, rather than exception
            //throw new CloudException("The product: " + productId + " could not be found.");
        } catch (IOException ex) {
            logger.error(ex.getMessage());
            if (ex.getClass() == GoogleJsonResponseException.class) {
                GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                        gjre.getDetails().getMessage());
            } else
                throw new CloudException(
                        "An error occurred retrieving the product: " + productId + ": " + ex.getMessage());
        }
    }

    @Override
    public VirtualMachine getVirtualMachine(@Nonnull String vmId) throws InternalException, CloudException {
        APITrace.begin(getProvider(), "getVirtualMachine");
        try {
            try {
                Compute gce = provider.getGoogleCompute();
                InstanceAggregatedList instances = gce.instances()
                        .aggregatedList(provider.getContext().getAccountNumber())
                        .setFilter("name eq " + getVmNameFromId(vmId)).execute();
                Iterator<String> it = instances.getItems().keySet().iterator();
                while (it.hasNext()) {
                    String zone = it.next();
                    if (instances.getItems() != null && instances.getItems().get(zone) != null
                            && instances.getItems().get(zone).getInstances() != null) {
                        for (Instance instance : instances.getItems().get(zone).getInstances()) {
                            if (instance.getName().equals(getVmNameFromId(vmId))) {
                                return toVirtualMachine(instance);
                            }
                        }
                    }
                }
                return null; // not found
            } catch (IOException ex) {
                logger.error(ex.getMessage());
                if (ex.getClass() == GoogleJsonResponseException.class) {
                    GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                    throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                            gjre.getDetails().getMessage());
                } else
                    throw new CloudException("An error occurred retrieving VM: " + vmId + ": " + ex.getMessage());
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public boolean isSubscribed() throws CloudException, InternalException {
        listVirtualMachines();
        return true;
    }

    public void validateLaunchOptions(@Nonnull VMLaunchOptions withLaunchOptions)
            throws CloudException, InternalException {

        if (withLaunchOptions.getDataCenterId() == null || withLaunchOptions.getDataCenterId().equals("")) {
            throw new InternalException("A datacenter must be specified when launching an instance");
        }

        if (withLaunchOptions.getMachineImageId() == null || withLaunchOptions.getMachineImageId().equals("")) {
            throw new InternalException("A MachineImage must be specified when launching an instance");
        }

        if (withLaunchOptions.getHostName() == null || withLaunchOptions.getHostName().equals("")) {
            throw new InternalException("A hostname must be specified when launching an instance");
        }

        if (withLaunchOptions.getVlanId().equals("")) {
            throw new InternalException("A vlan must be specified when launching an instance");
        } else {
            VLAN vlan = provider.getNetworkServices().getVlanSupport().getVlan(withLaunchOptions.getVlanId());
            if ((null == vlan) || (null == vlan.getTag("contentLink"))) {
                throw new InternalException("Problem getting Vlan for " + withLaunchOptions.getVlanId());
            }
        }

        String hostName = getCapabilities().getVirtualMachineNamingConstraints()
                .convertToValidName(withLaunchOptions.getHostName(), Locale.US);
        if (null != provider.getComputeServices().getVolumeSupport().getVolume(hostName)) {
            throw new InternalException("Root disk " + hostName + " already exists.");
        }
    }

    @Override
    public @Nonnull VirtualMachine launch(@Nonnull VMLaunchOptions withLaunchOptions)
            throws CloudException, InternalException {
        APITrace.begin(getProvider(), "launchVM"); //  windows-cloud_windows-server-2012-r2-dc-v20150629

        validateLaunchOptions(withLaunchOptions); // this will exception out on problem.

        try {
            Compute gce = provider.getGoogleCompute();
            GoogleMethod method = new GoogleMethod(provider);

            String hostName = getCapabilities().getVirtualMachineNamingConstraints()
                    .convertToValidName(withLaunchOptions.getHostName(), Locale.US);
            Instance instance = new Instance();
            instance.setName(hostName);
            instance.setDescription(withLaunchOptions.getDescription());
            if (withLaunchOptions.getStandardProductId().contains("+")) {
                instance.setMachineType(getProduct(withLaunchOptions.getStandardProductId()).getDescription());
            } else {
                instance.setMachineType(getProduct(
                        withLaunchOptions.getStandardProductId() + "+" + withLaunchOptions.getDataCenterId())
                                .getDescription());
            }
            MachineImage image = provider.getComputeServices().getImageSupport()
                    .getImage(withLaunchOptions.getMachineImageId());

            AttachedDisk rootVolume = new AttachedDisk();
            rootVolume.setBoot(Boolean.TRUE);
            rootVolume.setType("PERSISTENT");
            rootVolume.setMode("READ_WRITE");
            AttachedDiskInitializeParams params = new AttachedDiskInitializeParams();
            // do not use withLaunchOptions.getFriendlyName() it is non compliant!!!
            params.setDiskName(hostName);
            // Not Optimum solution, update in core should come next release to have this be part of MachineImage
            try {
                String[] parts = withLaunchOptions.getMachineImageId().split("_");
                Image img = gce.images().get(parts[0], parts[1]).execute();
                Long size = img.getDiskSizeGb();
                String diskSizeGb = size.toString();
                if (null == diskSizeGb) {
                    diskSizeGb = img.getUnknownKeys().get("diskSizeGb").toString();
                }
                Long MinimumDiskSizeGb = Long.valueOf(diskSizeGb).longValue();
                params.setDiskSizeGb(MinimumDiskSizeGb);
            } catch (Exception e) {
                params.setDiskSizeGb(10L);
            }
            if ((image != null) && (image.getTag("contentLink") != null))
                params.setSourceImage((String) image.getTag("contentLink"));
            else
                throw new CloudException("Problem getting the contentLink tag value from the image for "
                        + withLaunchOptions.getMachineImageId());
            rootVolume.setInitializeParams(params);

            List<AttachedDisk> attachedDisks = new ArrayList<AttachedDisk>();
            attachedDisks.add(rootVolume);

            if (withLaunchOptions.getVolumes().length > 0) {
                for (VolumeAttachment volume : withLaunchOptions.getVolumes()) {
                    AttachedDisk vol = new AttachedDisk();
                    vol.setBoot(Boolean.FALSE);
                    vol.setType("PERSISTENT");
                    vol.setMode("READ_WRITE");
                    vol.setAutoDelete(Boolean.FALSE);
                    vol.setKind("compute#attachedDisk");
                    if (null != volume.getExistingVolumeId()) {
                        vol.setDeviceName(volume.getExistingVolumeId());
                        vol.setSource(provider.getComputeServices().getVolumeSupport()
                                .getVolume(volume.getExistingVolumeId()).getMediaLink());
                    } else {
                        VolumeCreateOptions volumeOptions = volume.getVolumeToCreate();
                        volumeOptions.setDataCenterId(withLaunchOptions.getDataCenterId());
                        String newDisk = provider.getComputeServices().getVolumeSupport()
                                .createVolume(volume.getVolumeToCreate());
                        vol.setDeviceName(newDisk);
                        vol.setSource(
                                provider.getComputeServices().getVolumeSupport().getVolume(newDisk).getMediaLink());
                    }
                    attachedDisks.add(vol);
                }
            }

            instance.setDisks(attachedDisks);

            AccessConfig nicConfig = new AccessConfig();
            nicConfig.setName("External NAT");
            nicConfig.setType("ONE_TO_ONE_NAT");//Currently the only type supported
            if (withLaunchOptions.getStaticIpIds().length > 0) {
                nicConfig.setNatIP(withLaunchOptions.getStaticIpIds()[0]);
            }
            List<AccessConfig> accessConfigs = new ArrayList<AccessConfig>();
            accessConfigs.add(nicConfig);

            NetworkInterface nic = new NetworkInterface();
            nic.setName("nic0");
            if (null != withLaunchOptions.getVlanId()) {
                VLAN vlan = provider.getNetworkServices().getVlanSupport().getVlan(withLaunchOptions.getVlanId());
                nic.setNetwork(vlan.getTag("contentLink"));
            } else {
                nic.setNetwork(
                        provider.getNetworkServices().getVlanSupport().getVlan("default").getTag("contentLink"));
            }
            nic.setAccessConfigs(accessConfigs);
            List<NetworkInterface> nics = new ArrayList<NetworkInterface>();
            nics.add(nic);
            instance.setNetworkInterfaces(nics);
            instance.setCanIpForward(Boolean.FALSE);

            Scheduling scheduling = new Scheduling();
            scheduling.setAutomaticRestart(Boolean.TRUE);
            scheduling.setOnHostMaintenance("TERMINATE");
            instance.setScheduling(scheduling);

            Map<String, String> keyValues = new HashMap<String, String>();
            if (withLaunchOptions.getBootstrapUser() != null && withLaunchOptions.getBootstrapKey() != null
                    && !withLaunchOptions.getBootstrapUser().equals("")
                    && !withLaunchOptions.getBootstrapKey().equals("")) {
                keyValues.put("sshKeys",
                        withLaunchOptions.getBootstrapUser() + ":" + withLaunchOptions.getBootstrapKey());
            }
            if (!withLaunchOptions.getMetaData().isEmpty()) {
                for (Map.Entry<String, Object> entry : withLaunchOptions.getMetaData().entrySet()) {
                    keyValues.put(entry.getKey(), (String) entry.getValue());
                }
            }
            if (!keyValues.isEmpty()) {
                Metadata metadata = new Metadata();
                ArrayList<Metadata.Items> items = new ArrayList<Metadata.Items>();

                for (Map.Entry<String, String> entry : keyValues.entrySet()) {
                    Metadata.Items item = new Metadata.Items();
                    item.set("key", entry.getKey());
                    if ((entry.getValue() == null) || (entry.getValue().isEmpty() == true)
                            || (entry.getValue().equals("")))
                        item.set("value", ""); // GCE HATES nulls...
                    else
                        item.set("value", entry.getValue());
                    items.add(item);
                }
                // https://github.com/GoogleCloudPlatform/compute-image-packages/tree/master/google-startup-scripts
                if (null != withLaunchOptions.getUserData()) {
                    Metadata.Items item = new Metadata.Items();
                    item.set("key", "startup-script");
                    item.set("value", withLaunchOptions.getUserData());
                    items.add(item);
                }
                metadata.setItems(items);
                instance.setMetadata(metadata);
            }

            Tags tags = new Tags();
            ArrayList<String> tagItems = new ArrayList<String>();
            tagItems.add(hostName); // Each tag must be 1-63 characters long, and comply with RFC1035
            tags.setItems(tagItems);
            instance.setTags(tags);

            String vmId = "";
            try {
                Operation job = gce.instances().insert(provider.getContext().getAccountNumber(),
                        withLaunchOptions.getDataCenterId(), instance).execute();
                vmId = method.getOperationTarget(provider.getContext(), job, GoogleOperationType.ZONE_OPERATION, "",
                        withLaunchOptions.getDataCenterId(), false);
            } catch (IOException ex) {
                if (ex.getClass() == GoogleJsonResponseException.class) {
                    GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                    throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                            gjre.getDetails().getMessage());
                } else
                    throw new CloudException("An error occurred launching the instance: " + ex.getMessage());
            } catch (Exception e) {
                if ((e.getMessage().contains("The resource")) && (e.getMessage().contains("disks"))
                        && (e.getMessage().contains("already exists"))) {
                    throw new CloudException(
                            "A disk named '" + withLaunchOptions.getFriendlyName() + "' already exists.");
                } else {
                    throw new CloudException(e);
                }
            }

            if (!vmId.equals("")) {
                VirtualMachine vm = getVirtualMachine(vmId);

                if (withLaunchOptions.getMachineImageId().toLowerCase().contains("windows")) {
                    // Generate the public/private key pair for encryption and decryption.
                    KeyPair keys = null;
                    try {
                        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
                        keyGen.initialize(2048);

                        keys = keyGen.genKeyPair();
                    } catch (NoSuchAlgorithmException e) {
                        throw new InternalException(e);
                    }

                    resetPassword(vmId, withLaunchOptions.getDataCenterId(), keys);

                    int retryCount = 20;
                    while (retryCount-- > 0) {
                        SerialPortOutput output = null;
                        try {
                            output = gce.instances().getSerialPortOutput(provider.getContext().getAccountNumber(),
                                    withLaunchOptions.getDataCenterId(), vmId).setPort(4).execute();
                        } catch (IOException e) {
                            throw new CloudException(e);
                        }
                        System.out.println(output);
                        // Get the last line - this will be a JSON string corresponding to the most recent password reset attempt.
                        String[] entries = output.getContents().split("\n");
                        String outputEntry = entries[entries.length - 1];

                        // Parse output using the json-simple library.
                        JSONParser parser = new JSONParser();
                        try {
                            org.json.simple.JSONObject passwordDict = (org.json.simple.JSONObject) parser
                                    .parse(outputEntry);
                            vm.setRootUser(passwordDict.get("userName").toString());
                            vm.setRootPassword(
                                    decryptPassword(passwordDict.get("encryptedPassword").toString(), keys));
                            break;
                        } catch (Exception e) {
                        } // ignore exception, just means metadata not yet avail.

                        try {
                            Thread.sleep(10000);
                        } catch (InterruptedException e) {
                        }
                    }
                }
                return vm;
            } else {
                throw new CloudException(
                        "Could not find the instance: " + withLaunchOptions.getFriendlyName() + " after launch.");
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull Iterable<String> listFirewalls(@Nonnull String vmId) throws InternalException, CloudException {
        ArrayList<String> firewalls = new ArrayList<String>();
        for (org.dasein.cloud.network.Firewall firewall : provider.getNetworkServices().getFirewallSupport()
                .list()) {
            for (String key : firewall.getTags().keySet()) {
                if (firewall.getTags().get(key).equals(getVmNameFromId(vmId))) {
                    firewalls.add(firewall.getName());
                }
            }
        }
        return firewalls;
    }

    public @Nonnull Iterable<VirtualMachineProduct> listProducts(@Nonnull Architecture architecture,
            String preferredDataCenterId) throws InternalException, CloudException {
        MachineTypeAggregatedList machineTypes = null;

        Compute gce = provider.getGoogleCompute();
        Iterable<MachineTypeAggregatedList> machineTypesCachedList = machineTypesCache.get(provider.getContext());

        if (machineTypesCachedList != null) {
            Iterator<MachineTypeAggregatedList> machineTypesCachedListIterator = machineTypesCachedList.iterator();
            if (machineTypesCachedListIterator.hasNext())
                machineTypes = machineTypesCachedListIterator.next();
        } else {
            try {
                machineTypes = gce.machineTypes().aggregatedList(provider.getContext().getAccountNumber())
                        .execute();
                machineTypesCache.put(provider.getContext(), Arrays.asList(machineTypes));
            } catch (IOException ex) {
                logger.error(ex.getMessage());
                if (ex.getClass() == GoogleJsonResponseException.class) {
                    GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                    throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                            gjre.getDetails().getMessage());
                } else
                    throw new CloudException("An error occurred listing VM products.");
            }
        }

        Collection<VirtualMachineProduct> products = new ArrayList<VirtualMachineProduct>();

        Iterator<String> it = machineTypes.getItems().keySet().iterator();
        while (it.hasNext()) {
            Object dataCenterId = it.next();
            if ((preferredDataCenterId == null) || (dataCenterId.toString().endsWith(preferredDataCenterId)))
                for (MachineType type : machineTypes.getItems().get(dataCenterId).getMachineTypes()) {
                    //TODO: Filter out deprecated states somehow
                    if ((preferredDataCenterId == null) || (type.getZone().equals(preferredDataCenterId))) {
                        VirtualMachineProduct product = toProduct(type);
                        products.add(product);
                    }
                }
        }

        return products;
    }

    @Override
    public @Nonnull Iterable<VirtualMachineProduct> listProducts(@Nonnull String machineImageId,
            @Nonnull VirtualMachineProductFilterOptions options) throws InternalException, CloudException {
        Collection<VirtualMachineProduct> products = new ArrayList<VirtualMachineProduct>();

        Iterable<VirtualMachineProduct> candidateProduct = listProducts(options, null);
        for (VirtualMachineProduct product : candidateProduct) {
            if (options == null || options.matches(product)) {
                products.add(product);
            }
        }
        return products;
    }

    @Override
    public Iterable<VirtualMachineProduct> listProducts(VirtualMachineProductFilterOptions options,
            Architecture architecture) throws InternalException, CloudException {
        if ((architecture == null) || (Architecture.I64 == architecture)) { // GCE only has I64 architecture
            String dataCenterId = null;
            if (options != null)
                dataCenterId = options.getDataCenterId();
            Iterable<VirtualMachineProduct> result = listProducts(Architecture.I64, dataCenterId);
            return result;
        } else
            return new ArrayList<VirtualMachineProduct>(); // empty!
    }

    @Override
    public @Nonnull Iterable<VirtualMachine> listVirtualMachines(VMFilterOptions options)
            throws InternalException, CloudException {
        APITrace.begin(getProvider(), "listVirtualMachines");
        try {
            try {
                ArrayList<VirtualMachine> vms = new ArrayList<VirtualMachine>();
                Compute gce = provider.getGoogleCompute();
                InstanceAggregatedList instances = gce.instances()
                        .aggregatedList(provider.getContext().getAccountNumber()).execute();
                Iterator<String> it = instances.getItems().keySet().iterator();
                while (it.hasNext()) {
                    String zone = it.next();
                    if (getContext().getRegionId()
                            .equals(provider.getDataCenterServices().getRegionFromZone(zone))) {
                        if (instances.getItems() != null && instances.getItems().get(zone) != null
                                && instances.getItems().get(zone).getInstances() != null) {
                            for (Instance instance : instances.getItems().get(zone).getInstances()) {
                                VirtualMachine vm = toVirtualMachine(instance);
                                if (options == null || options.matches(vm)) {
                                    vms.add(vm);
                                }
                            }
                        }
                    }
                }
                return vms;
            } catch (IOException ex) {
                logger.error(ex.getMessage());
                if (ex.getClass() == GoogleJsonResponseException.class) {
                    GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                    throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                            gjre.getDetails().getMessage());
                } else
                    throw new CloudException("An error occurred while listing Virtual Machines.");
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public @Nonnull Iterable<VirtualMachine> listVirtualMachines() throws InternalException, CloudException {
        VMFilterOptions options = VMFilterOptions.getInstance();
        return listVirtualMachines(options);
    }

    @Override
    public @Nonnull Iterable<ResourceStatus> listVirtualMachineStatus() throws InternalException, CloudException {
        ArrayList<ResourceStatus> vmStatuses = new ArrayList<ResourceStatus>();
        for (VirtualMachine vm : listVirtualMachines()) {
            ResourceStatus status = new ResourceStatus(vm.getProviderVirtualMachineId(), vm.getCurrentState());
            vmStatuses.add(status);
        }
        return vmStatuses;
    }

    @Override
    public void pause(@Nonnull String vmId) throws InternalException, CloudException {
        throw new OperationNotSupportedException("GCE does not support pausing vms.");
    }

    @Override
    public void reboot(@Nonnull String vmId) throws CloudException, InternalException {
        APITrace.begin(getProvider(), "rebootVM");
        try {
            try {
                Operation job = null;
                String zone = null;
                for (VirtualMachine vm : listVirtualMachines()) {
                    if (vm.getProviderVirtualMachineId().equalsIgnoreCase(vmId)) {
                        zone = vm.getProviderDataCenterId();
                        Compute gce = provider.getGoogleCompute();
                        job = gce.instances().reset(provider.getContext().getAccountNumber(),
                                vm.getProviderDataCenterId(), getVmNameFromId(vmId)).execute();
                        break;
                    }
                }
                if (job != null) {
                    GoogleMethod method = new GoogleMethod(provider);
                    method.getOperationComplete(provider.getContext(), job, GoogleOperationType.ZONE_OPERATION,
                            null, zone);
                }
            } catch (IOException ex) {
                logger.error(ex.getMessage());
                if (ex.getClass() == GoogleJsonResponseException.class) {
                    GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                    throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                            gjre.getDetails().getMessage());
                } else
                    throw new CloudException(
                            "An error occurred while rebooting VM: " + vmId + ": " + ex.getMessage());
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public void resume(@Nonnull String vmId) throws CloudException, InternalException {
        throw new OperationNotSupportedException("GCE does not support suspend/resume of instances.");
    }

    @Override
    public void suspend(@Nonnull String vmId) throws CloudException, InternalException {
        throw new OperationNotSupportedException("GCE does not support suspend/resume of instances.");
    }

    @Override
    public void start(@Nonnull String vmId) throws InternalException, CloudException {
        Compute gce = provider.getGoogleCompute();
        try {
            VirtualMachine vm = getVirtualMachine(vmId);
            gce.instances().start(provider.getContext().getAccountNumber(), vm.getProviderDataCenterId(),
                    getVmNameFromId(vmId)).execute();
        } catch (IOException ex) {
            logger.error(ex.getMessage());
            if (ex.getClass() == GoogleJsonResponseException.class) {
                GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                        gjre.getDetails().getMessage());
            } else
                throw new CloudException("An error occurred while rebooting VM: " + vmId + ": " + ex.getMessage());
        }
    }

    @Override
    public void stop(@Nonnull String vmId, boolean force) throws InternalException, CloudException {
        Compute gce = provider.getGoogleCompute();
        try {
            VirtualMachine vm = getVirtualMachine(vmId);
            gce.instances().stop(provider.getContext().getAccountNumber(), vm.getProviderDataCenterId(),
                    getVmNameFromId(vmId)).execute();
        } catch (IOException ex) {
            logger.error(ex.getMessage());
            if (ex.getClass() == GoogleJsonResponseException.class) {
                GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                        gjre.getDetails().getMessage());
            } else
                throw new CloudException("An error occurred while rebooting VM: " + vmId + ": " + ex.getMessage());
        }
    }

    @Override
    public void terminate(@Nonnull String vmId) throws InternalException, CloudException {
        VirtualMachine vm = getVirtualMachine(vmId);
        terminateVm(vmId);
        terminateVmDisk(getVmNameFromId(vmId), vm.getProviderDataCenterId());
    }

    @Override
    public void terminate(@Nonnull String vmId, String reason) throws InternalException, CloudException {
        VirtualMachine vm = getVirtualMachine(vmId);
        terminateVm(vmId, null);
        terminateVmDisk(vmId, vm.getProviderDataCenterId());
    }

    public void terminateVm(@Nonnull String vmId) throws InternalException, CloudException {
        terminateVm(vmId, null);
    }

    public void terminateVm(@Nonnull String vmId, String reason) throws InternalException, CloudException {
        try {
            APITrace.begin(getProvider(), "terminateVM");
            Operation job = null;
            GoogleMethod method = null;
            String zone = null;
            Compute gce = provider.getGoogleCompute();
            VirtualMachine vm = getVirtualMachine(vmId);

            if (null == vm) {
                throw new CloudException("Virtual Machine " + vmId + " was not found.");
            }

            try {
                zone = vm.getProviderDataCenterId();
                job = gce.instances().delete(provider.getContext().getAccountNumber(), zone, getVmNameFromId(vmId))
                        .execute();
                if (job != null) {
                    method = new GoogleMethod(provider);
                    if (false == method.getOperationComplete(provider.getContext(), job,
                            GoogleOperationType.ZONE_OPERATION, null, zone)) {
                        throw new CloudException(
                                "An error occurred while terminating the VM. Note: The root disk might also still exist");
                    }
                }
            } catch (IOException ex) {
                logger.error(ex.getMessage());
                if (ex.getClass() == GoogleJsonResponseException.class) {
                    GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                    throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                            gjre.getDetails().getMessage());
                } else
                    throw new CloudException(
                            "An error occurred while terminating VM: " + vmId + ": " + ex.getMessage());
            } catch (Exception ex) {
                throw new CloudException(ex); // catch exception from getOperationComplete
            }

        } finally {
            APITrace.end();
        }
    }

    public void terminateVmDisk(@Nonnull String diskName, String zone) throws InternalException, CloudException {
        try {
            APITrace.begin(getProvider(), "terminateVM");
            try {
                Compute gce = provider.getGoogleCompute();
                Operation job = gce.disks().delete(provider.getContext().getAccountNumber(), zone, diskName)
                        .execute();
                GoogleMethod method = new GoogleMethod(provider);
                method.getOperationComplete(provider.getContext(), job, GoogleOperationType.ZONE_OPERATION, null,
                        zone);
            } catch (IOException ex) {
                if (ex.getClass() == GoogleJsonResponseException.class) {
                    GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                    if ((404 == gjre.getStatusCode()) && (gjre.getStatusMessage().equals("Not Found"))) {
                        // remain silent. this happens when instance is created with delete root volume on terminate is selected.
                        //throw new CloudException("Virtual Machine disk image '" + vmId + "' was not found.");
                    } else {
                        throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                                gjre.getDetails().getMessage());
                    }
                } else
                    throw new CloudException(
                            "An error occurred while deleting VM disk: " + diskName + ": " + ex.getMessage());
            } catch (Exception ex) {
                throw new CloudException(ex); // catch exception from getOperationComplete
            }
        } finally {
            APITrace.end();
        }
    }

    @Override
    public void unpause(@Nonnull String vmId) throws CloudException, InternalException {
        throw new OperationNotSupportedException("GCE does not support unpausing vms.");
    }

    @Override
    public void updateTags(String vmId, Tag... tags) throws CloudException, InternalException {
        updateTags(new String[] { vmId }, tags);
    }

    @Override
    public void updateTags(String[] vmIds, Tag... tags) throws CloudException, InternalException {
        //TODO: Implement me
    }

    @Override
    public void removeTags(String vmId, Tag... tags) throws CloudException, InternalException {
        throw new OperationNotSupportedException("Google does not support removing meta data from vms");
    }

    @Override
    public void removeTags(String[] vmIds, Tag... tags) throws CloudException, InternalException {
        throw new OperationNotSupportedException("Google does not support removing meta data from vms");
    }

    private VirtualMachine toVirtualMachine(Instance instance) throws InternalException, CloudException {
        VirtualMachine vm = new VirtualMachine();
        vm.setProviderVirtualMachineId(instance.getName() + "_" + instance.getId().toString());
        vm.setName(instance.getName());
        if (instance.getDescription() != null) {
            vm.setDescription(instance.getDescription());
        } else {
            vm.setDescription(instance.getName());
        }
        vm.setProviderOwnerId(provider.getContext().getAccountNumber());

        VmState vmState = null;
        if (instance.getStatus().equalsIgnoreCase("provisioning")
                || instance.getStatus().equalsIgnoreCase("staging")) {
            if ((null != instance.getStatusMessage()) && (instance.getStatusMessage().contains("failed"))) {
                vmState = VmState.ERROR;
            } else {
                vmState = VmState.PENDING;
            }
        } else if (instance.getStatus().equalsIgnoreCase("stopping")) {
            vmState = VmState.STOPPING;
        } else if (instance.getStatus().equalsIgnoreCase("terminated")) {
            vmState = VmState.STOPPED;
        } else {
            vmState = VmState.RUNNING;
        }
        vm.setCurrentState(vmState);
        String regionId = "";
        try {
            regionId = provider.getDataCenterServices()
                    .getRegionFromZone(instance.getZone().substring(instance.getZone().lastIndexOf("/") + 1));
        } catch (Exception ex) {
            logger.error("An error occurred getting the region for the instance");
            return null;
        }
        vm.setProviderRegionId(regionId);
        String zone = instance.getZone();
        zone = zone.substring(zone.lastIndexOf("/") + 1);
        vm.setProviderDataCenterId(zone);

        DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
        DateTime dt = DateTime.parse(instance.getCreationTimestamp(), fmt);
        vm.setCreationTimestamp(dt.toDate().getTime());

        if (instance.getDisks() != null) {
            for (AttachedDisk disk : instance.getDisks()) {
                if (disk != null && disk.getBoot() != null && disk.getBoot()) {
                    String diskName = disk.getSource().substring(disk.getSource().lastIndexOf("/") + 1);
                    Compute gce = provider.getGoogleCompute();
                    try {
                        Disk sourceDisk = gce.disks().get(provider.getContext().getAccountNumber(), zone, diskName)
                                .execute();
                        if (sourceDisk != null && sourceDisk.getSourceImage() != null) {
                            String project = "";
                            Pattern p = Pattern.compile("/projects/(.*?)/");
                            Matcher m = p.matcher(sourceDisk.getSourceImage());
                            while (m.find()) {
                                project = m.group(1);
                                break;
                            }
                            vm.setProviderMachineImageId(project + "_" + sourceDisk.getSourceImage()
                                    .substring(sourceDisk.getSourceImage().lastIndexOf("/") + 1));
                        }
                    } catch (IOException ex) {
                        logger.error(ex.getMessage());
                        if (ex.getClass() == GoogleJsonResponseException.class) {
                            GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                            throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(),
                                    gjre.getContent(), gjre.getDetails().getMessage());
                        } else
                            throw new InternalException("IOException: " + ex.getMessage());
                    }
                }
            }
        }

        String machineTypeName = instance.getMachineType()
                .substring(instance.getMachineType().lastIndexOf("/") + 1);
        vm.setProductId(machineTypeName + "+" + zone);

        ArrayList<RawAddress> publicAddresses = new ArrayList<RawAddress>();
        ArrayList<RawAddress> privateAddresses = new ArrayList<RawAddress>();
        boolean firstPass = true;
        boolean isSet = false;
        String providerAssignedIpAddressId = null;
        for (NetworkInterface nic : instance.getNetworkInterfaces()) {
            if (firstPass) {
                vm.setProviderVlanId(nic.getNetwork().substring(nic.getNetwork().lastIndexOf("/") + 1));
                firstPass = false;
            }
            if (nic.getNetworkIP() != null) {
                privateAddresses.add(new RawAddress(nic.getNetworkIP()));
            }
            if (nic.getAccessConfigs() != null && !nic.getAccessConfigs().isEmpty()) {
                for (AccessConfig accessConfig : nic.getAccessConfigs()) {
                    if (accessConfig.getNatIP() != null) {
                        publicAddresses.add(new RawAddress(accessConfig.getNatIP()));
                        if (!isSet) {
                            try {
                                isSet = true;
                                providerAssignedIpAddressId = provider.getNetworkServices().getIpAddressSupport()
                                        .getIpAddressIdFromIP(accessConfig.getNatIP(), regionId);
                            } catch (InternalException ex) {
                                /*Likely to be an ephemeral IP*/
                            }
                        }
                    }
                }
            }
        }
        vm.setPublicAddresses(publicAddresses.toArray(new RawAddress[publicAddresses.size()]));
        vm.setPrivateAddresses(privateAddresses.toArray(new RawAddress[privateAddresses.size()]));
        vm.setProviderAssignedIpAddressId(providerAssignedIpAddressId);

        vm.setRebootable(true);
        vm.setPersistent(true);
        vm.setIpForwardingAllowed(true);
        vm.setImagable(false);
        vm.setClonable(false);

        vm.setPlatform(Platform.guess(instance.getName()));
        vm.setArchitecture(Architecture.I64);

        vm.setTag("contentLink", instance.getSelfLink());

        return vm;
    }

    private VirtualMachineProduct toProduct(MachineType machineType) {
        VirtualMachineProduct product = new VirtualMachineProduct();
        product.setProviderProductId(machineType.getName() + "+" + machineType.getZone());
        product.setName(machineType.getName());
        product.setDescription(machineType.getSelfLink());
        product.setCpuCount(machineType.getGuestCpus());
        product.setRamSize(new Storage<Megabyte>(machineType.getMemoryMb(), Storage.MEGABYTE));
        if (machineType.getImageSpaceGb() != null)
            product.setRootVolumeSize(new Storage<Gigabyte>(machineType.getImageSpaceGb(), Storage.GIGABYTE));
        else
            product.setRootVolumeSize(new Storage<Gigabyte>(0, Storage.GIGABYTE)); // defined at creation time by specified root volume size.
        product.setVisibleScope(VisibleScope.ACCOUNT_DATACENTER);
        return product;
    }

    // the default implementation does parallel launches and throws an exception only if it is unable to launch any virtual machines
    @Override
    public @Nonnull Iterable<String> launchMany(final @Nonnull VMLaunchOptions withLaunchOptions,
            final @Nonnegative int count) throws CloudException, InternalException {
        if (count < 1) {
            throw new InternalException(
                    "Invalid attempt to launch less than 1 virtual machine (requested " + count + ").");
        }
        if (count == 1) {
            return Collections.singleton(launch(withLaunchOptions).getProviderVirtualMachineId());
        }
        final List<Future<String>> results = new ArrayList<Future<String>>();

        // windows on GCE follows same naming constraints as regular instances, 1-62 lower and numbers, must begin with a letter.
        NamingConstraints c = NamingConstraints.getAlphaNumeric(1, 63).withNoSpaces()
                .withRegularExpression("(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)").lowerCaseOnly().constrainedBy('-');
        String baseHost = c.convertToValidName(withLaunchOptions.getHostName(), Locale.US);

        if (baseHost == null) {
            baseHost = withLaunchOptions.getHostName();
        }
        for (int i = 1; i <= count; i++) {
            String hostName = c.incrementName(baseHost, i);
            String friendlyName = withLaunchOptions.getFriendlyName() + "-" + i;
            VMLaunchOptions options = withLaunchOptions
                    .copy(hostName == null ? withLaunchOptions.getHostName() + "-" + i : hostName, friendlyName);

            results.add(launchAsync(options));
        }

        PopulatorThread<String> populator = new PopulatorThread<String>(new JiteratorPopulator<String>() {
            @Override
            public void populate(@Nonnull Jiterator<String> iterator) throws Exception {
                List<Future<String>> original = results;
                List<Future<String>> copy = new ArrayList<Future<String>>();
                Exception exception = null;
                boolean loaded = false;

                while (!original.isEmpty()) {
                    for (Future<String> result : original) {
                        if (result.isDone()) {
                            try {
                                iterator.push(result.get());
                                loaded = true;
                            } catch (Exception e) {
                                exception = e;
                            }
                        } else {
                            copy.add(result);
                        }
                    }
                    original = copy;
                    // copy has to be a new list else we'll get into concurrently modified list state
                    copy = new ArrayList<Future<String>>();
                }
                if (exception != null && !loaded) {
                    throw exception;
                }
            }
        });

        populator.populate();
        return populator.getResult();
    }

    @Override
    public @Nullable String getUserData(@Nonnull String vmId) throws InternalException, CloudException {
        APITrace.begin(getProvider(), "getVirtualMachine");
        try {
            try {
                Compute gce = provider.getGoogleCompute();
                InstanceAggregatedList instances = gce.instances()
                        .aggregatedList(provider.getContext().getAccountNumber())
                        .setFilter("name eq " + getVmNameFromId(vmId)).execute();
                Iterator<String> it = instances.getItems().keySet().iterator();
                while (it.hasNext()) {
                    String zone = it.next();
                    if (instances.getItems() != null && instances.getItems().get(zone) != null
                            && instances.getItems().get(zone).getInstances() != null) {
                        for (Instance instance : instances.getItems().get(zone).getInstances()) {
                            if (instance.getName().equals(getVmNameFromId(vmId))) {
                                Metadata metadata = instance.getMetadata();
                                if (null != metadata) {
                                    List<Items> items = metadata.getItems();
                                    if (null != items) {
                                        for (Items item : items) {
                                            if ("startup-script".equals(item.getKey())) {
                                                return item.getValue();
                                            }
                                        }
                                    }
                                }
                            }

                        }
                    }
                }
                return null; // not found
            } catch (IOException ex) {
                logger.error(ex.getMessage());
                if (ex.getClass() == GoogleJsonResponseException.class) {
                    GoogleJsonResponseException gjre = (GoogleJsonResponseException) ex;
                    throw new GoogleException(CloudErrorType.GENERAL, gjre.getStatusCode(), gjre.getContent(),
                            gjre.getDetails().getMessage());
                } else
                    throw new CloudException("An error occurred retrieving VM: " + vmId + ": " + ex.getMessage());
            }
        } finally {
            APITrace.end();
        }
    }

    private void resetPassword(String vmId, String dataCenterId, KeyPair keys)
            throws InternalException, CloudException {
        Compute gce = provider.getGoogleCompute();
        Instance inst;
        try {
            inst = gce.instances().get(provider.getContext().getAccountNumber(), dataCenterId, vmId).execute();
        } catch (IOException e) {
            throw new CloudException(e);
        }
        Metadata metadata = inst.getMetadata();

        replaceMetadata(metadata, buildKeyMetadata(keys, "admin", "")); // administrator appears to be reserved

        // Tell Compute Engine to update the instance metadata with our changes.
        try {
            gce.instances().setMetadata(provider.getContext().getAccountNumber(), dataCenterId, vmId, metadata)
                    .execute();
        } catch (IOException e) {
            throw new CloudException(e);
        }
        try {
            Thread.sleep(30000);
        } catch (InterruptedException e) {
        }
    }

    private String decryptPassword(String message, KeyPair keys) throws InternalException {
        try {
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

            Cipher rsaOAEPPadding = Cipher.getInstance("RSA/NONE/OAEPPadding", "BC");
            rsaOAEPPadding.init(Cipher.DECRYPT_MODE, keys.getPrivate());

            return new String(rsaOAEPPadding.doFinal(Base64.decodeBase64(message)), "UTF8");
        } catch (Exception e) {
            throw new InternalException(e);
        }
    }

    private void replaceMetadata(Metadata metadata, JSONObject newMetadataItem) {
        String newItemString = newMetadataItem.toString();

        List<Items> items = metadata.getItems();

        if (items == null) {
            items = new LinkedList<Items>();
            metadata.setItems(items);
        }

        // Find the "windows-keys" entry and update it.
        for (Items item : items) {
            if (item.getKey().compareTo("windows-keys") == 0) {
                item.setValue(newItemString);
                return;
            }
        }
        items.add(new Items().setKey("windows-keys").setValue(newItemString));
    }

    private JSONObject buildKeyMetadata(KeyPair pair, String userName, String email) throws InternalException {
        JSONObject metadataValues = jsonEncode(pair);

        for (String key : JSONObject.getNames(metadataValues)) {
            try {
                metadataValues.put(key, metadataValues.get(key));
            } catch (JSONException e) {
            }
        }

        // Create the date on which the new keys expire.
        Date now = new Date();
        Date expireDate = new Date(now.getTime() + 360000); // 6 minutes

        SimpleDateFormat rfc3339Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        rfc3339Format.setTimeZone(TimeZone.getTimeZone("UTC"));
        String dateString = rfc3339Format.format(expireDate);

        try {
            metadataValues.put("userName", userName);
            metadataValues.put("email", email);
            metadataValues.put("expireOn", dateString);
        } catch (JSONException e) {
            throw new InternalException(e);
        }

        return metadataValues;
    }

    private JSONObject jsonEncode(KeyPair keys) throws InternalException {
        JSONObject returnJson = new JSONObject();
        try {
            KeyFactory factory = KeyFactory.getInstance("RSA");

            RSAPublicKeySpec pubSpec = factory.getKeySpec(keys.getPublic(), RSAPublicKeySpec.class);

            BigInteger modulus = pubSpec.getModulus();
            BigInteger exponent = pubSpec.getPublicExponent();

            BaseEncoding stringEncoder = BaseEncoding.base64();

            // Strip out the leading 0 byte in the modulus.
            byte[] arr = Arrays.copyOfRange(modulus.toByteArray(), 1, modulus.toByteArray().length);

            returnJson.put("modulus", stringEncoder.encode(arr).replaceAll("\n", ""));
            returnJson.put("exponent", stringEncoder.encode(exponent.toByteArray()).replaceAll("\n", ""));
        } catch (Exception e) {
            throw new InternalException(e);
        }

        return returnJson;
    }
}