Crear solicitudes de API y manejar respuestas


Este documento describe cómo construir solicitudes API y manejar respuestas API desde la API de Compute Engine. Cubre cómo:

  • Construya un cuerpo de solicitud.
  • Determine los URI de recursos necesarios para una solicitud.
  • Manejar respuestas API.
  • Determine si una solicitud de API se realizó correctamente.

Este documento no cubre cómo autenticarse en la API. Para aprender cómo autenticarse en la API, lea Autenticarse en Compute Engine .

Antes de comenzar

Creando una solicitud API

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

Cuando realiza una solicitud de API que requiere un cuerpo de solicitud, como una solicitud POST , UPDATE o PATCH , el cuerpo de la solicitud contiene propiedades de recursos que desea establecer en esta solicitud. Por ejemplo, el siguiente comando curl realiza una solicitud POST al URI del recurso de Instancias. La solicitud crea una instancia con las propiedades definidas en el cuerpo de la solicitud. El cuerpo de la solicitud se indica con el indicador -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 su proyecto porque las imágenes pertenecen a proyectos diferentes, según el 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 utiliza un URI completo para la red default .

Ejemplos de solicitudes de API

Pitón

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 URI de recursos

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

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

Siempre que especifique una imagen, un tipo de máquina, una red o cualquier otro recurso, debe proporcionar el URI del recurso cuando utilice la API. Las herramientas cliente como la CLI de Google Cloud y la consola de Google Cloud ocultan esta complejidad y se encargan de crear estos URI de recursos por usted, pero al interactuar directamente con la API, debe crear estos URI de recursos usted mismo.

Hay URI de recursos ligeramente diferentes para diferentes 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 con una especificación region :

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

De manera similar, 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 porque el servicio puede inferir información como el ID del proyecto. Por lo tanto, las siguientes versiones parciales de los URI anteriores también son aceptables:

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

En los URI parciales, tanto el URI zonal como el regional omitieron 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 están alojadas 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 utilizar estas imágenes, debe 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 utiliza una imagen personalizada que pertenece a su proyecto (el mismo proyecto en el que está creando este recurso), puede omitir la especificación del proyecto al proporcionar un URI de imagen.

Determinar las propiedades requeridas

La documentación de referencia de la API de Compute Engine, disponible para las API v1 y beta , describe todas las propiedades posibles que puede configurar para un recurso específico. La documentación de referencia hace una distinción entre propiedades mutables e inmutables (marcadas con [Output Only] en la descripción de la propiedad), pero para determinar las propiedades requeridas para un recurso, debe revisar la documentación específica de esa tarea.

Por ejemplo, si está creando una instancia, lea la documentación Creación de una instancia a partir de una imagen para ver las propiedades de API necesarias para la solicitud. Si desea crear una dirección IP externa estática en la API, lea la documentación sobre Direcciones IP externas estáticas .

Validar solicitudes de API

Para validar sus solicitudes de API:

  1. En la referencia de la API de Compute Engine , busca el método que llama tu código. Por ejemplo, v1/compute.instances.insert .
  2. En el menú de contenidos, haz clic en ¡Pruébalo! Esto abre la ventana Pruebe esta API .

    ¡Pruébalo! botón en el menú de contenidos.

  3. En Parámetros de solicitud , no es necesario proporcionar un proyecto o zona porque la validación no requiere enviar la solicitud.

  4. En Cuerpo de la solicitud , pegue su solicitud.

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

Los elementos mal formados de la solicitud están subrayados en azul. Haga clic en cada sección subrayada para obtener más información sobre el problema que se va a abordar.

Manejo de respuestas API

Si realizas una solicitud que muta (altera) datos, Compute Engine devuelve un objeto Operation que luego puedes sondear para obtener el estado de las operaciones de tu 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, tomar una instantánea de un disco o detener una instancia), Compute Engine devuelve un objeto zoneOperations . De manera similar, los recursos regionales y globales devuelven un objeto regionOperations o globalOperations , respectivamente. Puede obtener el estado de una operación realizando una solicitud que utilice el método get o wait para el recurso Operation específico y proporcionando el name de la operación.

Su solicitud no estará completa hasta que el estado del recurso Operation vuelva a ser DONE . Esto puede llevar algún tiempo dependiendo de la naturaleza de su solicitud. Luego, después de que el estado del recurso Operation regrese a DONE , puede verificar si la operación se realizó correctamente y si hubo algún error.

Por ejemplo, la siguiente respuesta indica que la operación anterior ya 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, realice una solicitud get al recurso para comprobar que existe y 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

Puede escribir código para sondear periódicamente la operación con una solicitud get o wait que regresa cuando el estado de la operación es DONE .

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

Si realiza una solicitud wait , la solicitud regresa cuando la operación esté DONE o si la solicitud se acerca al plazo de 2 minutos. Puede optar por utilizar wait o get para sondear sus operaciones, pero el método wait proporciona ciertos beneficios sobre el método get :

  • Puedes configurar tus clientes para que sondeen 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 realiza la operación y el momento en que se informa al cliente que la operación se realiza se reduce significativamente porque el servidor responde tan pronto como se realiza la operación.
  • El método proporciona esperas limitadas. El método no espera más que el tiempo de espera HTTP predeterminado (2 minutos) y luego devuelve el estado actual de la operación, que puede estar DONE o aún en progreso.

El método wait es una API de mejor esfuerzo . Si el servidor está sobrecargado, la solicitud podría regresar antes de que llegue la fecha límite predeterminada o después de esperar cero segundos. Tampoco se garantiza que el método regrese solo cuando la operación esté DONE . Por ejemplo, si la solicitud se acerca al plazo de 2 minutos, el método regresa incluso si la operación no se realiza. Para comprobar sus operaciones, le recomendamos que utilice el método wait o get (en un ciclo de reintento con suspensión intermedia) para sondear periódicamente el estado de la operación. El intervalo máximo de reintento no debe exceder el período mínimo de retención de la operación .

Ejemplo de encuesta

Los siguientes ejemplos utilizan el método get . Puedes reemplazar el método get con el método wait :

Pitón

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