com.vmware.photon.controller.model.adapters.gcp.GCPTestUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.photon.controller.model.adapters.gcp.GCPTestUtil.java

Source

/*
 * Copyright (c) 2015-2016 VMware, 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 com.vmware.photon.controller.model.adapters.gcp;

import static com.vmware.photon.controller.model.adapters.gcp.constants.GCPConstants.CPU_PLATFORM;
import static com.vmware.photon.controller.model.adapters.gcp.constants.GCPConstants.DEFAULT_AUTH_TYPE;
import static com.vmware.photon.controller.model.adapters.gcp.constants.GCPConstants.DEFAULT_CPU_PLATFORM;
import static com.vmware.photon.controller.model.adapters.gcp.constants.GCPConstants.DEFAULT_IMAGE_REFERENCE;
import static com.vmware.photon.controller.model.adapters.gcp.constants.GCPConstants.DISK_TYPE_PERSISTENT;
import static com.vmware.photon.controller.model.adapters.gcp.constants.GCPConstants.OPERATION_STATUS_DONE;
import static com.vmware.photon.controller.model.adapters.gcp.utils.GCPUtils.privateKeyFromPkcs8;
import static com.vmware.photon.controller.model.resources.ComputeDescriptionService.ComputeDescription.ENVIRONMENT_NAME_GCP;
import static com.vmware.photon.controller.model.tasks.ProvisioningUtils.createServiceURI;
import static com.vmware.photon.controller.model.tasks.ProvisioningUtils.getVMCount;

import java.io.IOException;
import java.net.URI;
import java.security.GeneralSecurityException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.compute.Compute;
import com.google.api.services.compute.Compute.Instances;
import com.google.api.services.compute.ComputeScopes;
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.Instance;
import com.google.api.services.compute.model.InstanceList;
import com.google.api.services.compute.model.NetworkInterface;
import com.google.api.services.compute.model.ServiceAccount;

import com.vmware.photon.controller.model.adapters.gcp.constants.GCPConstants;
import com.vmware.photon.controller.model.adapters.gcp.enumeration.GCPEnumerationAdapterService;
import com.vmware.photon.controller.model.resources.ComputeDescriptionService;
import com.vmware.photon.controller.model.resources.ComputeService;
import com.vmware.photon.controller.model.resources.ComputeService.ComputeState;
import com.vmware.photon.controller.model.resources.ComputeService.PowerState;
import com.vmware.photon.controller.model.resources.DiskService;
import com.vmware.photon.controller.model.resources.ResourceGroupService;
import com.vmware.photon.controller.model.resources.ResourceGroupService.ResourceGroupState;
import com.vmware.photon.controller.model.resources.ResourcePoolService;
import com.vmware.photon.controller.model.resources.ResourcePoolService.ResourcePoolState;
import com.vmware.photon.controller.model.tasks.ResourceEnumerationTaskService;
import com.vmware.photon.controller.model.tasks.ResourceEnumerationTaskService.ResourceEnumerationTaskState;
import com.vmware.photon.controller.model.tasks.TestUtils;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.ServiceDocument;
import com.vmware.xenon.common.ServiceStats;
import com.vmware.xenon.common.UriUtils;
import com.vmware.xenon.common.Utils;
import com.vmware.xenon.common.test.VerificationHost;
import com.vmware.xenon.services.common.AuthCredentialsService;
import com.vmware.xenon.services.common.QueryTask;
import com.vmware.xenon.services.common.QueryTask.Query;
import com.vmware.xenon.services.common.ServiceUriPaths;

public class GCPTestUtil {
    private static final String ADAPTER_TEST_INSTANCE = "adapter-test-instance-";
    private static final String ENUMERATION_TEST_MACHINE_TYPE = "https://www.googleapis"
            + ".com/compute/v1/projects/%s/zones/%s/machineTypes/f1-micro";
    private static final String NETWORK_INTERFACE = "https://www.googleapis"
            + ".com/compute/v1/projects/%s/global/networks/default";
    private static final String NETWORK_INTERFACE_CONFIG = "ONE_TO_ONE_NAT";
    private static final String NETWORK_ACCESS_CONFIG = "External NAT";
    private static final String SOURCE_IMAGE = "https://www.googleapis.com/compute/v1/"
            + "projects/ubuntu-os-cloud/global/images/family/ubuntu-1404-lts";
    private static final String BOOT_DISK_NAME_SUFFIX = "-boot-disk";
    private static final String DISK_TYPE = "https://www.googleapis"
            + ".com/compute/v1/projects/%s/zones/%s/diskTypes/pd-standard";
    private static final long ONE_HOUR_DIFFERENCE_MICROS = TimeUnit.HOURS.toMicros(1);
    private static final long WAIT_INTERVAL = 1000;
    private static final long MIN_CPU_COUNT = 1;
    private static final long MIN_MEMORY_BYTES = 1024;
    private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();

    /**
     * Create a resource pool where the VM will be housed.
     * @param host The test host service.
     * @return The default resource pool.
     * @throws Throwable The exception during creating resource pool.
     */
    public static ResourcePoolService.ResourcePoolState createDefaultResourcePool(VerificationHost host)
            throws Throwable {
        ResourcePoolService.ResourcePoolState inPool = new ResourcePoolService.ResourcePoolState();

        inPool.name = UUID.randomUUID().toString();
        inPool.id = inPool.name;
        inPool.minCpuCount = MIN_CPU_COUNT;
        inPool.minMemoryBytes = MIN_MEMORY_BYTES;

        return TestUtils.doPost(host, inPool, ResourcePoolService.ResourcePoolState.class,
                UriUtils.buildUri(host, ResourcePoolService.FACTORY_LINK));
    }

    /**
     * Create a resource group for a GCP project.
     * @param host The test host service.
     * @param projectId The GCP project ID.
     * @return The default resource group.
     * @throws Throwable The exception during creating compute host.
     */
    public static ResourceGroupState createDefaultResourceGroup(VerificationHost host, String projectId)
            throws Throwable {
        ResourceGroupState resourceGroup = new ResourceGroupState();

        resourceGroup.name = projectId;

        return TestUtils.doPost(host, resourceGroup, ResourceGroupState.class,
                UriUtils.buildUri(host, ResourceGroupService.FACTORY_LINK));
    }

    /**
     * Create a compute host description for a GCP instance
     * @param host The test host service.
     * @param userEmail The service account's client email.
     * @param privateKey The service account's private key.
     * @param zoneId The GCP project's zone ID.
     * @param resourcePoolLink The default resource pool's link.
     * @param resourceGroupLink The default resource group's link.
     * @return The default compute host.
     * @throws Throwable The exception during creating compute host.
     */
    public static ComputeService.ComputeState createDefaultComputeHost(VerificationHost host, String userEmail,
            String privateKey, String zoneId, String resourcePoolLink, String resourceGroupLink) throws Throwable {
        AuthCredentialsService.AuthCredentialsServiceState auth = new AuthCredentialsService.AuthCredentialsServiceState();

        auth.type = DEFAULT_AUTH_TYPE;
        auth.userEmail = userEmail;
        auth.privateKey = privateKey;
        auth.documentSelfLink = UUID.randomUUID().toString();

        TestUtils.doPost(host, auth, AuthCredentialsService.AuthCredentialsServiceState.class,
                UriUtils.buildUri(host, AuthCredentialsService.FACTORY_LINK));
        String authLink = UriUtils.buildUriPath(AuthCredentialsService.FACTORY_LINK, auth.documentSelfLink);

        ComputeDescriptionService.ComputeDescription gcpHostDescription = new ComputeDescriptionService.ComputeDescription();
        gcpHostDescription.id = UUID.randomUUID().toString();
        gcpHostDescription.name = gcpHostDescription.id;
        gcpHostDescription.documentSelfLink = gcpHostDescription.id;
        gcpHostDescription.enumerationAdapterReference = UriUtils.buildUri(host,
                GCPUriPaths.GCP_ENUMERATION_ADAPTER);
        gcpHostDescription.statsAdapterReference = UriUtils.buildUri(host, GCPUriPaths.GCP_STATS_ADAPTER);
        gcpHostDescription.zoneId = zoneId;
        gcpHostDescription.authCredentialsLink = authLink;
        gcpHostDescription.groupLinks = new HashSet<>();
        gcpHostDescription.groupLinks.add(resourceGroupLink);

        TestUtils.doPost(host, gcpHostDescription, ComputeDescriptionService.ComputeDescription.class,
                UriUtils.buildUri(host, ComputeDescriptionService.FACTORY_LINK));

        ComputeService.ComputeState gcpComputeHost = new ComputeService.ComputeState();
        gcpComputeHost.id = UUID.randomUUID().toString();
        gcpComputeHost.name = gcpHostDescription.name;
        gcpComputeHost.documentSelfLink = gcpComputeHost.id;
        gcpComputeHost.descriptionLink = UriUtils.buildUriPath(ComputeDescriptionService.FACTORY_LINK,
                gcpHostDescription.id);
        gcpComputeHost.resourcePoolLink = resourcePoolLink;

        return TestUtils.doPost(host, gcpComputeHost, ComputeService.ComputeState.class,
                UriUtils.buildUri(host, ComputeService.FACTORY_LINK));
    }

    /**
     * Create a GCP VM compute resource
     * @param host The test host service.
     * @param userEmail The service account's client email.
     * @param privateKey The service account's private key.
     * @param zoneId The GCP project's zone ID.
     * @param gcpVMName The default name of the VM.
     * @param parentLink The default compute host's link.
     * @param resourcePoolLink The default resource pool's link.
     * @return The default VM.
     * @throws Throwable The exception during creating vm.
     */
    public static ComputeService.ComputeState createDefaultVMResource(VerificationHost host, String userEmail,
            String privateKey, String zoneId, String gcpVMName, String parentLink, String resourcePoolLink)
            throws Throwable {
        AuthCredentialsService.AuthCredentialsServiceState auth = new AuthCredentialsService.AuthCredentialsServiceState();

        auth.type = DEFAULT_AUTH_TYPE;
        auth.userEmail = userEmail;
        auth.privateKey = privateKey;
        auth.documentSelfLink = UUID.randomUUID().toString();

        TestUtils.doPost(host, auth, AuthCredentialsService.AuthCredentialsServiceState.class,
                UriUtils.buildUri(host, AuthCredentialsService.FACTORY_LINK));
        String authLink = UriUtils.buildUriPath(AuthCredentialsService.FACTORY_LINK, auth.documentSelfLink);

        // Create a VM desc
        ComputeDescriptionService.ComputeDescription gcpVMDesc = new ComputeDescriptionService.ComputeDescription();
        gcpVMDesc.id = UUID.randomUUID().toString();
        gcpVMDesc.name = gcpVMDesc.id;
        gcpVMDesc.documentSelfLink = gcpVMDesc.id;
        gcpVMDesc.zoneId = zoneId;
        gcpVMDesc.authCredentialsLink = authLink;
        gcpVMDesc.environmentName = ENVIRONMENT_NAME_GCP;
        gcpVMDesc.customProperties = new HashMap<>();
        gcpVMDesc.customProperties.put(CPU_PLATFORM, DEFAULT_CPU_PLATFORM);

        ComputeDescriptionService.ComputeDescription vmComputeDesc = TestUtils.doPost(host, gcpVMDesc,
                ComputeDescriptionService.ComputeDescription.class,
                UriUtils.buildUri(host, ComputeDescriptionService.FACTORY_LINK));

        List<String> vmDisks = new ArrayList<>();
        DiskService.DiskState rootDisk = new DiskService.DiskState();
        rootDisk.name = gcpVMName + BOOT_DISK_NAME_SUFFIX;
        rootDisk.id = UUID.randomUUID().toString();
        rootDisk.documentSelfLink = rootDisk.id;
        rootDisk.type = DiskService.DiskType.HDD;
        rootDisk.sourceImageReference = URI.create(DEFAULT_IMAGE_REFERENCE);
        rootDisk.bootOrder = 1;
        rootDisk.documentSelfLink = rootDisk.id;

        TestUtils.doPost(host, rootDisk, DiskService.DiskState.class,
                UriUtils.buildUri(host, DiskService.FACTORY_LINK));
        vmDisks.add(UriUtils.buildUriPath(DiskService.FACTORY_LINK, rootDisk.id));

        ComputeService.ComputeState resource = new ComputeService.ComputeState();
        resource.id = String.valueOf(new Random().nextLong());
        resource.name = gcpVMName;
        resource.documentUpdateTimeMicros = Utils.getNowMicrosUtc();
        resource.parentLink = parentLink;
        resource.descriptionLink = vmComputeDesc.documentSelfLink;
        resource.resourcePoolLink = resourcePoolLink;
        resource.diskLinks = vmDisks;
        resource.documentSelfLink = resource.id;
        resource.customProperties = new HashMap<>();

        return TestUtils.doPost(host, resource, ComputeService.ComputeState.class,
                UriUtils.buildUri(host, ComputeService.FACTORY_LINK));
    }

    /**
     * Delete the document by specified docuemnt link.
     * @param host The test host service.
     * @param documentToDelete The document link of the document to be deleted.
     * @throws Throwable The exception during deleting document.
     */
    public static void deleteDocument(VerificationHost host, String documentToDelete) throws Throwable {
        host.testStart(1);
        host.send(Operation.createDelete(UriUtils.buildUri(host, documentToDelete)).setBody(new ServiceDocument())
                .setCompletion(host.getCompletion()));
        host.testWait();
    }

    /**
     * Stop the instances of specified project and zone.
     * @param host The test service host.
     * @param compute The Google Compute Engine client.
     * @param projectId The GCP project ID.
     * @param zoneId The GCP project's zone ID.
     * @param instanceNames The list of instances' names to be stopped.
     * @throws IOException The IO exception during stopping remote instances.
     */
    public static void stopInstances(VerificationHost host, Compute compute, String projectId, String zoneId,
            List<String> instanceNames, int batchSize, long interval) throws Throwable {
        if (batchSize <= 0) {
            throw new Exception("batch size cannot be less or equal to zero.");
        }
        if (interval <= 0) {
            throw new Exception("waiting interval cannot be less or equal to zero");
        }
        if (instanceNames != null) {
            int num = instanceNames.size();
            com.google.api.services.compute.model.Operation[] ops = new com.google.api.services.compute.model.Operation[num];
            String[] zones = new String[num];
            String[] opIds = new String[num];
            for (int i = 0; i < num; i++) {
                String instanceName = instanceNames.get(i);
                try {
                    ops[i] = compute.instances().stop(projectId, zoneId, instanceName).execute();
                    zones[i] = ops[i].getZone();
                    zones[i] = extractZoneFromZoneUri(zones[i]);
                    opIds[i] = ops[i].getName();
                    // There is an upper bound for the frequency of making Google API calls.
                    // This is to prevent making too much API calls in a very short time.
                    if ((i + 1) % batchSize == 0) {
                        TimeUnit.MILLISECONDS.sleep(interval);
                    }
                } catch (Exception e) {
                    host.log(Level.WARNING, "Error when stopping instances: " + e.getMessage());
                }
            }
            waitForOperationsDone(host, compute, projectId, ops, zones, opIds);
        }
    }

    /**
     * Delete the instances of specified project and zone.
     * @param host The test service host.
     * @param compute The Google Compute Engine client.
     * @param projectId The GCP project ID.
     * @param zoneId The GCP project's zone ID.
     * @param instanceNames The list of instances' names to be deleted.
     * @param batchSize The batch size.
     * @param interval The waiting interval.
     * @return The list of instances to be cleaned up.
     * @throws IOException The IO exception during deleting remote instances.
     */
    public static List<String> deleteInstances(VerificationHost host, Compute compute, String projectId,
            String zoneId, List<String> instanceNames, int batchSize, long interval) throws Throwable {
        if (batchSize <= 0) {
            throw new Exception("batch size cannot be less or equal to zero.");
        }
        if (interval <= 0) {
            throw new Exception("waiting interval cannot be less or equal to zero");
        }
        List<String> instancesToCleanUp = new ArrayList<>();
        if (instanceNames != null) {
            int num = instanceNames.size();
            com.google.api.services.compute.model.Operation[] ops = new com.google.api.services.compute.model.Operation[num];
            String[] zones = new String[num];
            String[] opIds = new String[num];
            for (int i = 0; i < num; i++) {
                String instanceName = instanceNames.get(i);
                try {
                    ops[i] = compute.instances().delete(projectId, zoneId, instanceName).execute();
                    zones[i] = ops[i].getZone();
                    zones[i] = extractZoneFromZoneUri(zones[i]);
                    opIds[i] = ops[i].getName();
                    // There is an upper bound for the frequency of making Google API calls.
                    // This is to prevent making too much API calls in a very short time.
                    if ((i + 1) % batchSize == 0) {
                        TimeUnit.MILLISECONDS.sleep(interval);
                    }
                } catch (Exception e) {
                    host.log(Level.WARNING, "Error when deleting instances: " + e.getMessage());
                    e.printStackTrace();
                    instancesToCleanUp.add(instanceName);
                }
            }
            waitForOperationsDone(host, compute, projectId, ops, zones, opIds);
        }
        return instancesToCleanUp;
    }

    /**
     * Provision the given number of instances in specified project and zone.
     * @param host The test service host.
     * @param compute The Google Compute Engine client.
     * @param userEmail The service account's client email.
     * @param projectId The GCP project ID.
     * @param zoneId The GCP project's zone ID.
     * @param n The number of instances to provision.
     * @param batchSize The batch size.
     * @param interval The waiting interval.
     * @throws Throwable The exception during provisioning remote instances.
     */
    public static List<String> provisionInstances(VerificationHost host, Compute compute, String userEmail,
            String projectId, String zoneId, int n, int batchSize, long interval) throws Throwable {
        if (n < 0) {
            throw new IllegalArgumentException("the number of instances to be provisioned cannot be negative.");
        }
        if (batchSize <= 0) {
            throw new IllegalArgumentException("batch size cannot be less or equal to zero.");
        }
        if (interval <= 0) {
            throw new IllegalArgumentException("waiting interval cannot be less or equal to zero");
        }
        List<String> scopes = Collections.singletonList(ComputeScopes.CLOUD_PLATFORM);
        List<String> instanceNames = new ArrayList<>();
        com.google.api.services.compute.model.Operation[] ops = new com.google.api.services.compute.model.Operation[n];
        String[] zones = new String[n];
        String[] opIds = new String[n];
        Instance instance = createInstanceTemplate(userEmail, projectId, zoneId, scopes);

        for (int i = 0; i < n; i++) {
            String instanceName = ADAPTER_TEST_INSTANCE + UUID.randomUUID().toString();
            instanceNames.add(instanceName);
            ops[i] = provisionOneInstance(compute, instance, instanceName, projectId, zoneId);
            zones[i] = ops[i].getZone();
            zones[i] = extractZoneFromZoneUri(zones[i]);
            opIds[i] = ops[i].getName();
            // There is an upper bound for the frequency of making Google API calls.
            // This is to prevent making too much API calls in a very short time.
            if ((i + 1) % batchSize == 0) {
                TimeUnit.MILLISECONDS.sleep(interval);
            }
        }

        waitForOperationsDone(host, compute, projectId, ops, zones, opIds);

        return instanceNames;
    }

    /**
     * Provision one instance of the specified project and zone.
     * @param compute The compute service used to call GCE APIs.
     * @param projectId The service account's project ID.
     * @param zoneId The GCP project's zone ID.
     * @return The Operation of provisioning the instance.
     * @throws Throwable The exception during creating the instance.
     */
    private static com.google.api.services.compute.model.Operation provisionOneInstance(Compute compute,
            Instance instance, String instanceName, String projectId, String zoneId) throws Throwable {
        instance.setName(instanceName);
        instance.getDisks().get(0).getInitializeParams().setDiskName(instanceName);
        Compute.Instances.Insert insert = compute.instances().insert(projectId, zoneId, instance);
        return insert.execute();
    }

    /**
     * Create an instance template for later provisioning.
     * @param userEmail The service account's client email.
     * @param projectId The project id.
     * @param zoneId The zone id.
     * @param scopes The priority scopes.
     * @return The instance template.
     */
    private static Instance createInstanceTemplate(String userEmail, String projectId, String zoneId,
            List<String> scopes) {
        Instance instance = new Instance();
        instance.setMachineType(String.format(ENUMERATION_TEST_MACHINE_TYPE, projectId, zoneId));

        NetworkInterface ifc = new NetworkInterface();
        ifc.setNetwork(String.format(NETWORK_INTERFACE, projectId));
        List<AccessConfig> configs = new ArrayList<>();
        AccessConfig config = new AccessConfig();
        config.setType(NETWORK_INTERFACE_CONFIG);
        config.setName(NETWORK_ACCESS_CONFIG);
        configs.add(config);
        ifc.setAccessConfigs(configs);
        instance.setNetworkInterfaces(Collections.singletonList(ifc));

        AttachedDisk disk = new AttachedDisk();
        disk.setBoot(true);
        disk.setAutoDelete(true);
        disk.setType(DISK_TYPE_PERSISTENT);
        AttachedDiskInitializeParams params = new AttachedDiskInitializeParams();
        params.setSourceImage(SOURCE_IMAGE);
        params.setDiskType(String.format(DISK_TYPE, projectId, zoneId));
        disk.setInitializeParams(params);
        instance.setDisks(Collections.singletonList(disk));

        ServiceAccount account = new ServiceAccount();
        account.setEmail(userEmail);
        account.setScopes(scopes);
        instance.setServiceAccounts(Collections.singletonList(account));

        return instance;
    }

    /**
     * Generate a random name with the specified prefix.
     * @param prefix The prefix of the random name.
     * @return The generated name.
     */
    public static String generateRandomName(String prefix) {
        StringBuilder sb = new StringBuilder();
        Random random = new Random();

        sb.append(prefix);

        for (int i = 0; i < 5; i++) {
            sb.append('a' + random.nextInt(26));
        }

        return sb.toString();
    }

    /**
     * Get a Google Compute Engine client object.
     * @param userEmail The service account's client email.
     * @param privateKey The service account's private key.
     * @param scopes The scopes used in the compute client object.
     * @param applicationName The application name.
     * @return The created Compute Engine client object.
     * @throws GeneralSecurityException Exception when creating http transport.
     * @throws IOException Exception when creating http transport.
     */
    public static Compute getGoogleComputeClient(String userEmail, String privateKey, List<String> scopes,
            String applicationName) throws GeneralSecurityException, IOException {
        HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
        GoogleCredential credential = new GoogleCredential.Builder().setTransport(httpTransport)
                .setJsonFactory(JSON_FACTORY).setServiceAccountId(userEmail).setServiceAccountScopes(scopes)
                .setServiceAccountPrivateKey(privateKeyFromPkcs8(privateKey)).build();
        return new Compute.Builder(httpTransport, JSON_FACTORY, credential).setApplicationName(applicationName)
                .build();
    }

    /**
     * Method to perform compute resource enumeration on the GCP endpoint.
     * @param host The test service host.
     * @param peerURI The peer uri.
     * @param enumTask The enumeration task.
     * @return The enumeration task state.
     * @throws Throwable Exception when sending enumeration request.
     */
    public static ResourceEnumerationTaskState performResourceEnumeration(VerificationHost host, URI peerURI,
            ResourceEnumerationTaskState enumTask) throws Throwable {
        URI uri = createServiceURI(host, peerURI, ResourceEnumerationTaskService.FACTORY_LINK);
        return TestUtils.doPost(host, enumTask, ResourceEnumerationTaskState.class, uri);
    }

    /**
     * Get the number of instances on specified GCP project and zone.
     * @param compute The GCE client object.
     * @param projectId The project id.
     * @param zoneId The zone id.
     * @return The number of instances.
     * @throws Throwable Exception during querying the instances
     */
    public static int getInstanceNumber(Compute compute, String projectId, String zoneId) throws Throwable {
        Instances instanceList = compute.instances();
        Instances.List list = instanceList.list(projectId, zoneId);
        InstanceList ins = list.execute();
        List<Instance> instances = ins.getItems();
        if (instances == null) {
            return 0;
        }
        return instances.size();
    }

    /**
     * Get the names of all stale instances remaining from previous runs due to read timeout
     * failures.
     * @param compute The GCE client object.
     * @param projectId The project id.
     * @param zoneId The zone id.
     * @return Names of stale instances
     * @throws Throwable Exception during querying the instances
     */
    public static List<String> getStaleInstanceNames(Compute compute, String projectId, String zoneId)
            throws Throwable {
        Instances instanceList = compute.instances();
        Instances.List list = instanceList.list(projectId, zoneId);
        InstanceList ins = list.execute();
        List<Instance> instances = ins.getItems();
        List<String> names = new ArrayList<String>();

        if (instances == null) {
            return null;
        }

        SimpleDateFormat dateFormat = new SimpleDateFormat(GCPConstants.VM_CREATION_TIMESTAMP_FORMAT);
        dateFormat.setTimeZone(TimeZone.getTimeZone(GCPConstants.UTC_TIMEZONE_ID));

        for (Instance i : instances) {
            Date date = dateFormat.parse(i.getCreationTimestamp());
            long time = TimeUnit.MILLISECONDS.toMicros(date.getTime());
            if (Utils.getNowMicrosUtc() - time > ONE_HOUR_DIFFERENCE_MICROS
                    && i.getName().startsWith("adapter-test-instance")) {
                names.add(i.getName());
            }
        }

        return names;
    }

    /**
     * Method to run enumeration on GCP endpoint.
     * @param host The test service host.
     * @param peerURI The peer uri.
     * @param enumTask The enumeration task to run.
     * @param testCase The test case name.
     * @throws Throwable Exception when running enumeration tasks.
     */
    public static void enumerateResources(VerificationHost host, URI peerURI, ResourceEnumerationTaskState enumTask,
            String testCase) throws Throwable {
        // Perform resource enumeration on the GCP end point.
        // Pass the references to the GCP compute host.
        host.log("Performing resource enumeration");
        ResourceEnumerationTaskState enumTaskState = performResourceEnumeration(host, peerURI, enumTask);

        // Wait for the enumeration task to be completed.
        host.waitForFinishedTask(ResourceEnumerationTaskState.class,
                createServiceURI(host, peerURI, enumTaskState.documentSelfLink));

        host.log("\n==%s==Total Time Spent in Enumeration==\n", testCase + getVMCount(host, peerURI));
        ServiceStats enumerationStats = host.getServiceState(null, ServiceStats.class,
                UriUtils.buildStatsUri(createServiceURI(host, peerURI, GCPEnumerationAdapterService.SELF_LINK)));
        host.log(Utils.toJsonHtml(enumerationStats));
    }

    /**
     * Query if there are desired number of compute states
     * with desired power states.
     * @param host The host server of the test.
     * @param resourcePool The default resource pool.
     * @param parentCompute The default compute host.
     * @param powerState The desired power state.
     * @param instanceNames The desired names of compute states.
     * @throws Throwable When reach the time limit.
     */
    public static void syncQueryComputeStatesWithPowerState(VerificationHost host, ResourcePoolState resourcePool,
            ComputeState parentCompute, PowerState powerState, Set<String> instanceNames) throws Throwable {
        host.waitFor("Waiting for changes of power statuses", () -> {
            queryComputeStatesWithPowerState(host, resourcePool, parentCompute, powerState, instanceNames);
            return instanceNames.isEmpty();
        });
    }

    /**
     * Method to query the number of local compute states with given power state.
     * @param host The test service host.
     * @param resourcePool The default resource pool.
     * @param parentCompute The default compute host.
     * @param powerState The given power state.
     * @param instanceNames The assumed names of compute states.
     */
    private static void queryComputeStatesWithPowerState(VerificationHost host, ResourcePoolState resourcePool,
            ComputeState parentCompute, PowerState powerState, Set<String> instanceNames) {
        Query query = QueryTask.Query.Builder.create().addKindFieldClause(ComputeState.class)
                .addFieldClause(ComputeState.FIELD_NAME_RESOURCE_POOL_LINK, resourcePool.documentSelfLink)
                .addFieldClause(ComputeState.FIELD_NAME_PARENT_LINK, parentCompute.documentSelfLink).build();

        QueryTask q = QueryTask.Builder.createDirectTask()
                .addOption(QueryTask.QuerySpecification.QueryOption.EXPAND_CONTENT).setQuery(query).build();

        host.sendRequest(Operation.createPost(host, ServiceUriPaths.CORE_QUERY_TASKS).setBody(q)
                .setReferer(host.getUri()).setCompletion((o, e) -> {
                    if (e != null) {
                        host.log(Level.WARNING, String.format("Error: %s", e.getMessage()));
                        return;
                    }
                    QueryTask queryTask = o.getBody(QueryTask.class);
                    if (queryTask.results.documentCount > 0) {
                        queryTask.results.documents.values().forEach(s -> {
                            ComputeState computeState = Utils.fromJson(s, ComputeState.class);
                            if (computeState.powerState == powerState) {
                                instanceNames.remove(computeState.name);
                            }
                        });
                    }
                }));
    }

    /**
     * Wait for all the operations to be done.
     * @param host The test service host.
     * @param compute The Google Compute Engine client.
     * @param projectId The project id.
     * @param ops The operations.
     * @param zones The zones.
     * @param opIds The operation ids.
     * @throws IOException When operation fails on fetch status.
     */
    private static void waitForOperationsDone(VerificationHost host, Compute compute, String projectId,
            com.google.api.services.compute.model.Operation[] ops, String[] zones, String[] opIds)
            throws IOException {
        for (int i = 0; i < ops.length; i++) {
            while (ops[i] != null && !ops[i].getStatus().equals(OPERATION_STATUS_DONE)) {
                try {
                    Thread.sleep(WAIT_INTERVAL);
                } catch (Exception e) {
                    host.log(Level.WARNING, String.format("Error: %s", e.getMessage()));
                }

                if (zones[i] != null) {
                    Compute.ZoneOperations.Get get = compute.zoneOperations().get(projectId, zones[i], opIds[i]);
                    ops[i] = get.execute();
                    continue;
                }
                Compute.GlobalOperations.Get get = compute.globalOperations().get(projectId, opIds[i]);
                ops[i] = get.execute();
            }
        }
    }

    /**
     * Extract the zone name from the zone uri.
     * @param zoneUri The zone uri in response.
     * @return The zone name.
     */
    private static String extractZoneFromZoneUri(String zoneUri) {
        if (zoneUri != null) {
            String[] bits = zoneUri.split("/");
            zoneUri = bits[bits.length - 1];
        }
        return zoneUri;
    }
}