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

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

Crea URI de recursos

En la API de Compute Engine, una referencia a otro recurso de 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 URI de recursos por ti, pero cuando interactúas directamente con la API, debes crear estos URI 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

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