Crear solicitudes de API y gestionar las respuestas


En este documento se describe cómo crear solicitudes de API y gestionar respuestas de la API de Compute Engine. En él se explica cómo:

  • Crea un cuerpo de solicitud.
  • Determina los URIs de recursos necesarios para una solicitud.
  • Gestionar las respuestas de la API.
  • Determina si una solicitud de API se ha realizado correctamente.

En este documento no se explica cómo autenticarse en la API. Para saber cómo autenticarte en la API, consulta el artículo Autenticación en Compute Engine.

Antes de empezar

Crear una solicitud de API

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

Cuando haces una solicitud a la API que requiere un cuerpo de solicitud, como una solicitud POST, UPDATE o PATCH, el cuerpo de la solicitud contiene propiedades de recursos que quieres definir en esta solicitud. Por ejemplo, el siguiente comando curl envía 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 tuyo, ya que las imágenes pertenecen a proyectos distintos en función del tipo de imagen. Por ejemplo, todas las imágenes de Debian disponibles públicamente que ofrece Compute Engine están alojadas en el proyecto debian-cloud.

Cuando haga referencia a otro recurso, utilice el URI de recurso completo. Por ejemplo, la propiedad network usa un URI completo para la red default.

Solicitudes de ejemplo a la API

Python

def create_instance(
    compute: object,
    project: str,
    zone: str,
    name: str,
    bucket: str,
) -> str:
    """Creates an instance in the specified zone.

    Args:
      compute: an initialized compute service object.
      project: the Google Cloud project ID.
      zone: the name of the zone in which the instances should be created.
      name: the name of the instance.
      bucket: the name of the bucket in which the image should be written.

    Returns:
      The instance object.
    """
    # Get the latest Debian Jessie image.
    image_response = (
        compute.images()
        .getFromFamily(project="debian-cloud", family="debian-11")
        .execute()
    )
    source_disk_image = image_response["selfLink"]

    # Configure the machine
    machine_type = "zones/%s/machineTypes/n1-standard-1" % zone
    startup_script = open(
        os.path.join(os.path.dirname(__file__), "startup-script.sh")
    ).read()
    image_url = "http://storage.googleapis.com/gce-demo-input/photo.jpg"
    image_caption = "Ready for dessert?"

    config = {
        "name": name,
        "machineType": machine_type,
        # Specify the boot disk and the image to use as a source.
        "disks": [
            {
                "boot": True,
                "autoDelete": True,
                "initializeParams": {
                    "sourceImage": source_disk_image,
                },
            }
        ],
        # Specify a network interface with NAT to access the public
        # internet.
        "networkInterfaces": [
            {
                "network": "global/networks/default",
                "accessConfigs": [{"type": "ONE_TO_ONE_NAT", "name": "External NAT"}],
            }
        ],
        # Allow the instance to access cloud storage and logging.
        "serviceAccounts": [
            {
                "email": "default",
                "scopes": [
                    "https://www.googleapis.com/auth/devstorage.read_write",
                    "https://www.googleapis.com/auth/logging.write",
                ],
            }
        ],
        # Metadata is readable from the instance and allows you to
        # pass configuration from deployment scripts to instances.
        "metadata": {
            "items": [
                {
                    # Startup script is automatically executed by the
                    # instance upon startup.
                    "key": "startup-script",
                    "value": startup_script,
                },
                {"key": "url", "value": image_url},
                {"key": "text", "value": image_caption},
                {"key": "bucket", "value": bucket},
            ]
        },
    }

    return compute.instances().insert(project=project, zone=zone, body=config).execute()

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();
}

Crear URIs de recursos

En la API de Compute Engine, se hace referencia a otro Google Cloud recurso mediante un URI completo:

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

Cada vez que especifiques una imagen, un tipo de máquina, una red o cualquier otro recurso, debes proporcionar el URI del recurso al usar la API. Las herramientas de cliente, como la CLI de Google Cloud y la consola, ocultan esta complejidad y se encargan de crear estos URIs de recursos por ti, pero, cuando interactúas directamente con la API, debes crear estos URIs de recursos tú mismo. Google Cloud

Los URIs de los recursos son ligeramente diferentes para los 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 sustituyen 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 Compute Engine también acepta URIs parciales porque el servicio puede inferir información como el ID del proyecto. Por lo tanto, también se aceptan las siguientes versiones parciales de los URIs anteriores:

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

En los URIs parciales, tanto el URI zonal como el regional omitían el ID del proyecto, pero el URI de la imagen no. Esto se debe a que las imágenes disponibles públicamente 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. Para poder usar estas imágenes, debes proporcionar el ID de proyecto adecuado. Si omite el ID de proyecto de las imágenes, Compute Engine intentará encontrar la imagen en su proyecto y la solicitud fallará porque la imagen no existe.

Sin embargo, si usas una imagen personalizada que pertenece a tu proyecto (el mismo proyecto en el que estás creando este recurso), puedes omitir la especificación del proyecto al proporcionar un URI de imagen.

Determinar las propiedades obligatorias

En la documentación de referencia de la API de Compute Engine, disponible para las APIs v1 y beta, se describen todas las propiedades que puedes definir para un recurso concreto. En la documentación de referencia se distingue entre propiedades mutables e inmutables (marcadas con un [Output Only] en la descripción de la propiedad), pero para determinar las propiedades obligatorias de un recurso, debes consultar la documentación específica de esa tarea.

Por ejemplo, si vas a crear una instancia, consulta la documentación sobre cómo crear una instancia a partir de una imagen para ver las propiedades de la API necesarias para la solicitud. Si quieres crear una dirección IP externa estática en la API, consulta la documentación sobre direcciones IP externas estáticas.

Validar solicitudes a la API

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

  1. En la referencia de la API Compute Engine, busca el método al que llama tu código. 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ú de contenido.

  3. En Parámetros de solicitud, no es necesario que proporcione un proyecto ni una zona, ya que la validación no requiere que envíe la solicitud.

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

    La ventana Probar esta API, que muestra el campo Cuerpo de la solicitud para indicar dónde pegar una solicitud de validación.

Los elementos con formato incorrecto de la solicitud se subrayan en azul. Haga clic en cada sección subrayada para obtener más información sobre el problema que debe solucionar.

Procesamiento de las respuestas del API

Si haces una solicitud que muta (modifica) los datos, Compute Engine devuelve un objeto Operation que puedes sondear para obtener el estado de las operaciones de tu solicitud. El recurso Operation tiene este aspecto:

{
 "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 para mutar (alterar) un recurso zonal (por ejemplo, para crear una instantánea de un disco o detener una instancia), Compute Engine devuelve un objeto zoneOperations. Del mismo modo, los recursos regionales y globales devuelven un objeto regionOperations o globalOperations, respectivamente. Para obtener el estado de una operación, debes enviar una solicitud que utilice el método get o wait del recurso Operation específico y proporcionar el name de la operación.

Tu solicitud no se completará hasta que el estado del recurso Operation sea DONE. Este proceso puede llevar algún tiempo, en función de la naturaleza de tu solicitud. Después de que el estado del recurso Operation sea DONE, puedes comprobar si la operación se ha realizado correctamente y si ha habido algún error.

Por ejemplo, la siguiente respuesta indica que la operación anterior se ha completado, tal como se especifica en 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 confirmarlo, haz una solicitud get al recurso para comprobar que existe y que está en ejecución. 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 código para sondear periódicamente la operación con una solicitud get o wait que se devuelva cuando el estado de la operación sea DONE.

Con una solicitud get, la operación se devuelve inmediatamente, independientemente del estado de la operación. Debes sondear la operación periódicamente para saber cuándo se ha completado.

Si haces una solicitud wait, esta se devuelve cuando la operación se 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 ofrece ciertas ventajas con respecto al método get:

  • Puedes configurar tus clientes para que consulten el estado de la operación con menos frecuencia, lo que reducirá el uso de QPS de la API Compute Engine.
  • La latencia media entre el momento en que se completa la operación y el momento en que se informa al cliente de que se ha completado se reduce significativamente porque el servidor responde en cuanto se completa la operación.
  • El método proporciona esperas acotadas. El método espera un máximo de 2 minutos (el tiempo de espera HTTP predeterminado) y, a continuación, devuelve el estado actual de la operación, que puede ser DONE o seguir en curso.

El método wait es una API de máxima capacidad. Si el servidor está sobrecargado, es posible que la solicitud se devuelva antes de que se alcance el plazo predeterminado o después de esperar cero segundos. Tampoco se garantiza que el método devuelva un valor solo cuando la operación sea DONE. Por ejemplo, si la solicitud se acerca al límite de 2 minutos, el método devuelve un valor aunque la operación no se haya completado. Para comprobar el estado de tus operaciones, te recomendamos que utilices el método wait o el get en un bucle de reintentos con un tiempo de espera entre ellos para sondear periódicamente el estado de la operación. El intervalo máximo de reintentos no debe superar el periodo de conservación mínimo de la operación.

Ejemplo de sondeo

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

Python

def wait_for_operation(
    compute: object,
    project: str,
    zone: str,
    operation: str,
) -> dict:
    """Waits for the given operation to complete.

    Args:
      compute: an initialized compute service object.
      project: the Google Cloud project ID.
      zone: the name of the zone in which the operation should be executed.
      operation: the operation ID.

    Returns:
      The result of the operation.
    """
    print("Waiting for operation to finish...")
    while True:
        result = (
            compute.zoneOperations()
            .get(project=project, zone=zone, operation=operation)
            .execute()
        )

        if result["status"] == "DONE":
            print("done.")
            if "error" in result:
                raise Exception(result["error"])
            return result

        time.sleep(1)

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();
}