Crear solicitudes de API y administrar respuestas

En este documento, se describe cómo crear solicitudes a la API y controlar sus respuestas en la API de Compute Engine. Se abarcan los siguientes temas:

  • Crea un cuerpo de solicitud.
  • Determina los URI de recursos necesarios para una solicitud.
  • Controla las respuestas de la API.
  • Determina si la solicitud a la API se realizó correctamente.

En este documento, no se abarca cómo autenticar en la API. Para saber cómo autenticar en la API, lee Autentícate en Compute Engine.

Antes de comenzar

Crea una solicitud a la API

La API de Compute Engine espera que las solicitudes a la API estén en formato JSON. Para realizar una solicitud a la API, puedes realizar una solicitud HTTP directa mediante herramientas como curl o httplib2, o puedes usar una de las bibliotecas cliente disponibles.

Cuando realizas una solicitud a la API que requiere un cuerpo de solicitud, como una solicitud POST, UPDATE o PATCH, el cuerpo de la solicitud contiene las propiedades del recurso que deseas configurar en ella. Por ejemplo, con el siguiente comando de curl se realiza una solicitud POST al URI del recurso Instances. La solicitud crea una instancia con las propiedades definidas en el cuerpo de la solicitud. El cuerpo de la solicitud se indica con la marca -d:

curl -X POST -H "Authorization: Bearer [OAUTH_TOKEN]" -H "Content-Type: application/json" \
https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/instances -d \
'{
  "disks":[
    {
      "boot":"true",
      "initializeParams":{
        "sourceImage":"https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-10-buster-v20210122"
      }
    }
  ],
  "machineType":"https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/machineTypes/e2-standard-2",
  "name":"VM_NAME",
  "networkInterfaces":[
    {
      "accessConfigs":[
        {
          "name":"external-nat",
          "type":"ONE_TO_ONE_NAT"
        }
      ],
      "network":"https://www.googleapis.com/compute/v1/projects/PROJECT_ID/global/networks/default"
    }
  ]
}'

El URI de la imagen tiene un ID de proyecto diferente (debian-cloud) del ID de tu proyecto, ya que las imágenes pertenecen a proyectos diferentes, según su tipo. Por ejemplo, todas las imágenes de Debian disponibles de forma pública que ofrece Compute Engine se alojan en el proyecto debian-cloud.

Cuando hagas referencia a otro recurso, usa el URI de recurso completamente calificado. Por ejemplo, la propiedad network usa un URI completamente calificado para la red default.

Ejemplos de solicitudes a la API

Python

from __future__ import annotations

import re
import sys
from typing import Any
import warnings

from google.api_core.extended_operation import ExtendedOperation
from google.cloud import compute_v1


def get_image_from_family(project: str, family: str) -> compute_v1.Image:
    """
    Retrieve the newest image that is part of a given family in a project.

    Args:
        project: project ID or project number of the Cloud project you want to get image from.
        family: name of the image family you want to get image from.

    Returns:
        An Image object.
    """
    image_client = compute_v1.ImagesClient()
    # List of public operating system (OS) images: https://cloud.google.com/compute/docs/images/os-details
    newest_image = image_client.get_from_family(project=project, family=family)
    return newest_image


def disk_from_image(
    disk_type: str,
    disk_size_gb: int,
    boot: bool,
    source_image: str,
    auto_delete: bool = True,
) -> compute_v1.AttachedDisk:
    """
    Create an AttachedDisk object to be used in VM instance creation. Uses an image as the
    source for the new disk.

    Args:
         disk_type: the type of disk you want to create. This value uses the following format:
            "zones/{zone}/diskTypes/(pd-standard|pd-ssd|pd-balanced|pd-extreme)".
            For example: "zones/us-west3-b/diskTypes/pd-ssd"
        disk_size_gb: size of the new disk in gigabytes
        boot: boolean flag indicating whether this disk should be used as a boot disk of an instance
        source_image: source image to use when creating this disk. You must have read access to this disk. This can be one
            of the publicly available images or an image from one of your projects.
            This value uses the following format: "projects/{project_name}/global/images/{image_name}"
        auto_delete: boolean flag indicating whether this disk should be deleted with the VM that uses it

    Returns:
        AttachedDisk object configured to be created using the specified image.
    """
    boot_disk = compute_v1.AttachedDisk()
    initialize_params = compute_v1.AttachedDiskInitializeParams()
    initialize_params.source_image = source_image
    initialize_params.disk_size_gb = disk_size_gb
    initialize_params.disk_type = disk_type
    boot_disk.initialize_params = initialize_params
    # Remember to set auto_delete to True if you want the disk to be deleted when you delete
    # your VM instance.
    boot_disk.auto_delete = auto_delete
    boot_disk.boot = boot
    return boot_disk


def wait_for_extended_operation(
    operation: ExtendedOperation, verbose_name: str = "operation", timeout: int = 300
) -> Any:
    """
    Waits for the extended (long-running) operation to complete.

    If the operation is successful, it will return its result.
    If the operation ends with an error, an exception will be raised.
    If there were any warnings during the execution of the operation
    they will be printed to sys.stderr.

    Args:
        operation: a long-running operation you want to wait on.
        verbose_name: (optional) a more verbose name of the operation,
            used only during error and warning reporting.
        timeout: how long (in seconds) to wait for operation to finish.
            If None, wait indefinitely.

    Returns:
        Whatever the operation.result() returns.

    Raises:
        This method will raise the exception received from `operation.exception()`
        or RuntimeError if there is no exception set, but there is an `error_code`
        set for the `operation`.

        In case of an operation taking longer than `timeout` seconds to complete,
        a `concurrent.futures.TimeoutError` will be raised.
    """
    result = operation.result(timeout=timeout)

    if operation.error_code:
        print(
            f"Error during {verbose_name}: [Code: {operation.error_code}]: {operation.error_message}",
            file=sys.stderr,
            flush=True,
        )
        print(f"Operation ID: {operation.name}", file=sys.stderr, flush=True)
        raise operation.exception() or RuntimeError(operation.error_message)

    if operation.warnings:
        print(f"Warnings during {verbose_name}:\n", file=sys.stderr, flush=True)
        for warning in operation.warnings:
            print(f" - {warning.code}: {warning.message}", file=sys.stderr, flush=True)

    return result


def create_instance(
    project_id: str,
    zone: str,
    instance_name: str,
    disks: list[compute_v1.AttachedDisk],
    machine_type: str = "n1-standard-1",
    network_link: str = "global/networks/default",
    subnetwork_link: str = None,
    internal_ip: str = None,
    external_access: bool = False,
    external_ipv4: str = None,
    accelerators: list[compute_v1.AcceleratorConfig] = None,
    preemptible: bool = False,
    spot: bool = False,
    instance_termination_action: str = "STOP",
    custom_hostname: str = None,
    delete_protection: bool = False,
) -> compute_v1.Instance:
    """
    Send an instance creation request to the Compute Engine API and wait for it to complete.

    Args:
        project_id: project ID or project number of the Cloud project you want to use.
        zone: name of the zone to create the instance in. For example: "us-west3-b"
        instance_name: name of the new virtual machine (VM) instance.
        disks: a list of compute_v1.AttachedDisk objects describing the disks
            you want to attach to your new instance.
        machine_type: machine type of the VM being created. This value uses the
            following format: "zones/{zone}/machineTypes/{type_name}".
            For example: "zones/europe-west3-c/machineTypes/f1-micro"
        network_link: name of the network you want the new instance to use.
            For example: "global/networks/default" represents the network
            named "default", which is created automatically for each project.
        subnetwork_link: name of the subnetwork you want the new instance to use.
            This value uses the following format:
            "regions/{region}/subnetworks/{subnetwork_name}"
        internal_ip: internal IP address you want to assign to the new instance.
            By default, a free address from the pool of available internal IP addresses of
            used subnet will be used.
        external_access: boolean flag indicating if the instance should have an external IPv4
            address assigned.
        external_ipv4: external IPv4 address to be assigned to this instance. If you specify
            an external IP address, it must live in the same region as the zone of the instance.
            This setting requires `external_access` to be set to True to work.
        accelerators: a list of AcceleratorConfig objects describing the accelerators that will
            be attached to the new instance.
        preemptible: boolean value indicating if the new instance should be preemptible
            or not. Preemptible VMs have been deprecated and you should now use Spot VMs.
        spot: boolean value indicating if the new instance should be a Spot VM or not.
        instance_termination_action: What action should be taken once a Spot VM is terminated.
            Possible values: "STOP", "DELETE"
        custom_hostname: Custom hostname of the new VM instance.
            Custom hostnames must conform to RFC 1035 requirements for valid hostnames.
        delete_protection: boolean value indicating if the new virtual machine should be
            protected against deletion or not.
    Returns:
        Instance object.
    """
    instance_client = compute_v1.InstancesClient()

    # Use the network interface provided in the network_link argument.
    network_interface = compute_v1.NetworkInterface()
    network_interface.network = network_link
    if subnetwork_link:
        network_interface.subnetwork = subnetwork_link

    if internal_ip:
        network_interface.network_i_p = internal_ip

    if external_access:
        access = compute_v1.AccessConfig()
        access.type_ = compute_v1.AccessConfig.Type.ONE_TO_ONE_NAT.name
        access.name = "External NAT"
        access.network_tier = access.NetworkTier.PREMIUM.name
        if external_ipv4:
            access.nat_i_p = external_ipv4
        network_interface.access_configs = [access]

    # Collect information into the Instance object.
    instance = compute_v1.Instance()
    instance.network_interfaces = [network_interface]
    instance.name = instance_name
    instance.disks = disks
    if re.match(r"^zones/[a-z\d\-]+/machineTypes/[a-z\d\-]+$", machine_type):
        instance.machine_type = machine_type
    else:
        instance.machine_type = f"zones/{zone}/machineTypes/{machine_type}"

    instance.scheduling = compute_v1.Scheduling()
    if accelerators:
        instance.guest_accelerators = accelerators
        instance.scheduling.on_host_maintenance = (
            compute_v1.Scheduling.OnHostMaintenance.TERMINATE.name
        )

    if preemptible:
        # Set the preemptible setting
        warnings.warn(
            "Preemptible VMs are being replaced by Spot VMs.", DeprecationWarning
        )
        instance.scheduling = compute_v1.Scheduling()
        instance.scheduling.preemptible = True

    if spot:
        # Set the Spot VM setting
        instance.scheduling.provisioning_model = (
            compute_v1.Scheduling.ProvisioningModel.SPOT.name
        )
        instance.scheduling.instance_termination_action = instance_termination_action

    if custom_hostname is not None:
        # Set the custom hostname for the instance
        instance.hostname = custom_hostname

    if delete_protection:
        # Set the delete protection bit
        instance.deletion_protection = True

    # Prepare the request to insert an instance.
    request = compute_v1.InsertInstanceRequest()
    request.zone = zone
    request.project = project_id
    request.instance_resource = instance

    # Wait for the create operation to complete.
    print(f"Creating the {instance_name} instance in {zone}...")

    operation = instance_client.insert(request=request)

    wait_for_extended_operation(operation, "instance creation")

    print(f"Instance {instance_name} created.")
    return instance_client.get(project=project_id, zone=zone, instance=instance_name)

Java

public static Operation startInstance(Compute compute, String instanceName) throws IOException {
  System.out.println("================== Starting New Instance ==================");

  // Create VM Instance object with the required properties.
  Instance instance = new Instance();
  instance.setName(instanceName);
  instance.setMachineType(
      String.format(
          "https://www.googleapis.com/compute/v1/projects/%s/zones/%s/machineTypes/e2-standard-1",
          PROJECT_ID, ZONE_NAME));
  // Add Network Interface to be used by VM Instance.
  NetworkInterface ifc = new NetworkInterface();
  ifc.setNetwork(
      String.format(
          "https://www.googleapis.com/compute/v1/projects/%s/global/networks/default",
          PROJECT_ID));
  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));

  // Add attached Persistent Disk to be used by VM Instance.
  AttachedDisk disk = new AttachedDisk();
  disk.setBoot(true);
  disk.setAutoDelete(true);
  disk.setType("PERSISTENT");
  AttachedDiskInitializeParams params = new AttachedDiskInitializeParams();
  // Assign the Persistent Disk the same name as the VM Instance.
  params.setDiskName(instanceName);
  // Specify the source operating system machine image to be used by the VM Instance.
  params.setSourceImage(SOURCE_IMAGE_PREFIX + SOURCE_IMAGE_PATH);
  // Specify the disk type as Standard Persistent Disk
  params.setDiskType(
      String.format(
          "https://www.googleapis.com/compute/v1/projects/%s/zones/%s/diskTypes/pd-standard",
          PROJECT_ID, ZONE_NAME));
  disk.setInitializeParams(params);
  instance.setDisks(Collections.singletonList(disk));

  // Initialize the service account to be used by the VM Instance and set the API access scopes.
  ServiceAccount account = new ServiceAccount();
  account.setEmail("default");
  List<String> scopes = new ArrayList<>();
  scopes.add("https://www.googleapis.com/auth/devstorage.full_control");
  scopes.add("https://www.googleapis.com/auth/compute");
  account.setScopes(scopes);
  instance.setServiceAccounts(Collections.singletonList(account));

  // Optional - Add a startup script to be used by the VM Instance.
  Metadata meta = new Metadata();
  Metadata.Items item = new Metadata.Items();
  item.setKey("startup-script-url");
  // If you put a script called "vm-startup.sh" in this Google Cloud Storage
  // bucket, it will execute on VM startup.  This assumes you've created a
  // bucket named the same as your PROJECT_ID.
  // For info on creating buckets see:
  // https://cloud.google.com/storage/docs/cloud-console#_creatingbuckets
  item.setValue(String.format("gs://%s/vm-startup.sh", PROJECT_ID));
  meta.setItems(Collections.singletonList(item));
  instance.setMetadata(meta);

  System.out.println(instance.toPrettyString());
  Compute.Instances.Insert insert = compute.instances().insert(PROJECT_ID, ZONE_NAME, instance);
  return insert.execute();
}

Crea URI de recursos

En la API de Compute Engine, una referencia a otro recurso Google Cloud se muestra como un URI completamente calificado:

https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/RESOURCE_TYPE/SPECIFIC_RESOURCE

Siempre que especifiques una imagen, un tipo de máquina, una red o cualquier otro recurso, debes proporcionar el URI al recurso cuando uses la API. Las herramientas cliente, como Google Cloud CLI y la consola de Google Cloud , ocultan esta complejidad y controlan la creación de estos URIs de recursos por ti, pero cuando interactúas directamente con la API, debes crear estos URIs de recursos.

Hay URI de recursos ligeramente diferentes para distintos tipos de recursos. Por ejemplo, un recurso zonal tiene la especificación zone en el URI:

https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/machineTypes/e2-standard-2

Los recursos regionales reemplazan la especificación zone por una especificación region:

https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/regions/REGION/addresses/ADDRESS_NAME

Del mismo modo, los recursos globales tienen la especificación global:

https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/global/images/VM_NAME

La API de Compute Engine también acepta URI parciales, ya que el servicio puede inferir información como el ID del proyecto. Por lo tanto, también se aceptan las siguientes versiones parciales de los URI más antiguos:

zones/ZONE/machineTypes/e2-standard-2
regions/REGION/addresses/ADDRESS_NAME
project/PROJECT_ID/global/images/VM_NAME

En el caso de los URI parciales, los URI zonales y regionales omitieron el ID del proyecto, pero el URI de la imagen no lo hizo. Esto se debe a que las imágenes disponibles a nivel público que ofrece Compute Engine se alojan en otros proyectos, como debian-cloud para todas las imágenes de Debian y ubuntu-os-cloud para todas las imágenes de Ubuntu. Antes de poder usar estas imágenes, debes proporcionar el ID del proyecto adecuado. Si omites el ID del proyecto para las imágenes, Compute Engine intenta encontrar la imagen en tu proyecto y la solicitud falla porque la imagen no existe.

Sin embargo, si usas una imagen personalizada que pertenece a tu proyecto (el mismo proyecto en el que creas este recurso), puedes omitir la especificación del proyecto cuando proporciones un URI de la imagen.

Determina las propiedades obligatorias

En la documentación de referencia de la API de Compute Engine, disponible para las API v1 y Beta, se describen todas las propiedades posibles que puedes establecer. para un recurso específico. En la documentación de referencia, se hace una distinción entre propiedades inmutables y mutables (marcadas por un [Output Only] en la descripción de la propiedad), pero para determinar las propiedades obligatorias de un recurso, debes revisar la documentación específica de esa tarea.

Por ejemplo, si estás creando una instancia, lee la documentación Crea una instancia a partir de una imagen para ver las propiedades de API que se requieren para la solicitud. Si deseas crear una dirección IP externa estática en la API, consulta la documentación Direcciones IP externas estáticas.

Valida solicitudes a la API

Para validar tus solicitudes a la API, sigue estos pasos:

  1. En la referencia de la API de Compute Engine, busca el método que el código está llamando. Por ejemplo, v1/compute.instances.insert.
  2. En el menú de contenido, haz clic en Probar. Se abrirá la ventana Probar esta API.

    El botón Probar en el menú del contenido.

  3. En Parámetros de solicitud, no necesitas proporcionar un proyecto o zona porque la validación no requiere que se envíe la solicitud.

  4. En Cuerpo de la solicitud, pega la solicitud.

    La ventana Prueba esta API, en la que se muestra el campo Cuerpo de la solicitud a fin de mostrar dónde pegar la solicitud de validación.

Los elementos con formato incorrecto de la solicitud están subrayados en azul. Haz clic en cada sección subrayada para obtener más información sobre el problema que se abordará.

Controla las respuestas de la API

Si realizas una solicitud que muta (altera) los datos, Compute Engine muestra un objeto Operation que puedes sondear para obtener el estado de las operaciones de la solicitud. El recurso Operation se ve así:

{
 "kind": "compute#operation",
 "id": "7127550864823803662",
 "name": "operation-1458856416788-52ed27a803e22-1c3bd86a-9e95017b",
 "zone": "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE",
 "operationType": "insert",
 "targetLink": "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/instances/EXAMPLE_VM",
 "targetId": "4132355614508179214",
 "status": "PENDING",
 "user": "user@example.com",
 "progress": 0,
 "insertTime": "2016-03-24T14:53:37.788-07:00",
 "selfLink": "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/operations/operation-1458856416788-52ed27a803e22-1c3bd86a-9e95017b"
}

Si la solicitud original es mutar (alterar) un recurso zonal, por ejemplo, para tomar una instantánea de un disco o detener una instancia, Compute Engine muestra un objeto zoneOperations. Del mismo modo, los recursos regionales y globales muestran un objeto regionOperations o globalOperations, respectivamente. Puedes obtener el estado de una operación si realizas una solicitud que use los métodos get o wait para el recurso Operation específico y proporcionas el name de la operación.

Tu solicitud no se completará hasta que el estado del recurso Operation se muestre como DONE. Esto puede tomar un tiempo según la naturaleza de tu solicitud. Luego, después de que el estado del recurso Operation se muestre como DONE, puedes verificar si la operación se realizó de forma correcta y si hubo algún error.

Por ejemplo, la siguiente respuesta indica que la operación anterior ahora está completa, especificada por el estado DONE:

endTime: '2016-03-24T14:54:07.119-07:00'
id: '7127550864823803662'
insertTime: '2016-03-24T14:53:37.788-07:00'
kind: compute#operation
name: operation-1458856416788-52ed27a803e22-1c3bd86a-9e95017b
operationType: insert
progress: 100
selfLink: https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/operations/operation-1458856416788-52ed27a803e22-1c3bd86a-9e95017b
startTime: '2016-03-24T14:53:38.397-07:00'
status: DONE
targetId: '4132355614508179214'
targetLink: https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/instances/EXAMPLE_VM
user: user@example.com
zone: https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE

Para confirmar, realiza una solicitud get al recurso a fin de verificar que exista y que se esté ejecutando. Por ejemplo:

GET /compute/v1/projects/PROJECT_ID/zones/ZONE/instances/EXAMPLE_VM

{
  "cpuPlatform": "Intel Haswell",
  "creationTimestamp": "2016-03-24T14:53:37.170-07:00",
  "disks": [
    ..[snip]..
  "selfLink": "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/instances/EXAMPLE_VM",
  "status": "RUNNING",
  "tags": {
    "fingerprint": "42WmSpB8rSM="
  },
  "zone": "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE"
}

Operaciones de sondeo

Puedes escribir parte del código para sondear periódicamente la operación con una solicitud get o wait que se muestra cuando el estado de la operación es DONE.

Con una solicitud get, la operación se muestra de inmediato, sin importar el estado de la operación. Debes consultar la operación con regularidad para saber cuándo se completa.

Si realizas una solicitud wait, la solicitud se muestra cuando la operación es DONE o si la solicitud se acerca al plazo de 2 minutos. Puedes usar wait o get para sondear tus operaciones, pero el método wait proporciona ciertos beneficios sobre el método get:

  • Puedes configurar tus clientes para que consulten el estado de la operación con menos frecuencia, lo que reduce el uso de QPS de la API de Compute Engine.
  • La latencia promedio entre el momento en que se completa la operación y en el que se informa al cliente que se realizó esta operación se reduce significativamente porque el servidor responde en cuanto se completa la operación.
  • El método proporciona esperas delimitadas. El método espera solo el tiempo de espera de HTTP predeterminado (2 minutos) y, luego, muestra el estado actual de la operación, que puede ser DONE o que aún está en progreso.

El método wait es una API de mejor esfuerzo. Si el servidor está sobrecargado, la solicitud puede mostrarse antes de que alcance el plazo predeterminado o después de esperar cero segundos. Tampoco se garantiza que el método solo se muestre cuando la operación sea DONE. Por ejemplo, si la solicitud se acerca al plazo de 2 minutos, el método mostrará el resultado incluso si la operación no se realizó. A fin de verificar tus operaciones, te recomendamos que uses los métodos wait o get, en un bucle de reintentos con suspensión intermedia, para sondear periódicamente el estado de la operación. El intervalo de reintento máximo no debe exceder el período de retención de operación mínimo.

Ejemplo de sondeo

En los siguientes ejemplos, se usa el método get. Puedes reemplazar el método get por el método wait:

Python

import time

from google.cloud import compute_v1


def wait_for_operation(
    operation: compute_v1.Operation, project_id: str
) -> compute_v1.Operation:
    """
    This method waits for an operation to be completed. Calling this function
    will block until the operation is finished.

    Args:
        operation: The Operation object representing the operation you want to
            wait on.
        project_id: project ID or project number of the Cloud project you want to use.

    Returns:
        Finished Operation object.
    """
    kwargs = {"project": project_id, "operation": operation.name}
    if operation.zone:
        client = compute_v1.ZoneOperationsClient()
        # Operation.zone is a full URL address of a zone, so we need to extract just the name
        kwargs["zone"] = operation.zone.rsplit("/", maxsplit=1)[1]
    elif operation.region:
        client = compute_v1.RegionOperationsClient()
        # Operation.region is a full URL address of a region, so we need to extract just the name
        kwargs["region"] = operation.region.rsplit("/", maxsplit=1)[1]
    else:
        client = compute_v1.GlobalOperationsClient()

    while True:
        result = client.get(**kwargs)

        if result.status == compute_v1.Operation.Status.DONE:
            print("Operation finished.")
            if result.error:
                print(f"Error during operation: {result.error}")
            return result

        print("Waiting for operation to complete...")
        time.sleep(2)

Java

/**
 * Wait until {@code operation} is completed.
 *
 * @param compute the {@code Compute} object
 * @param operation the operation returned by the original request
 * @param timeout the timeout, in millis
 * @return the error, if any, else {@code null} if there was no error
 * @throws InterruptedException if we timed out waiting for the operation to complete
 * @throws IOException if we had trouble connecting
 */
public static Operation.Error blockUntilComplete(
    Compute compute, Operation operation, long timeout) throws Exception {
  long start = System.currentTimeMillis();
  final long pollInterval = 5 * 1000;
  String zone = getLastWordFromUrl(operation.getZone()); // null for global/regional operations
  String region = getLastWordFromUrl(operation.getRegion());
  String status = operation.getStatus();
  String opId = operation.getName();
  while (operation != null && !status.equals("DONE")) {
    Thread.sleep(pollInterval);
    long elapsed = System.currentTimeMillis() - start;
    if (elapsed >= timeout) {
      throw new InterruptedException("Timed out waiting for operation to complete");
    }
    System.out.println("waiting...");
    if (zone != null) {
      Compute.ZoneOperations.Get get = compute.zoneOperations().get(PROJECT_ID, zone, opId);
      operation = get.execute();
    } else if (region != null) {
      Compute.RegionOperations.Get get = compute.regionOperations().get(PROJECT_ID, region, opId);
      operation = get.execute();
    } else {
      Compute.GlobalOperations.Get get = compute.globalOperations().get(PROJECT_ID, opId);
      operation = get.execute();
    }
    if (operation != null) {
      status = operation.getStatus();
    }
  }
  return operation == null ? null : operation.getError();
}