Como criar solicitações de API e processar respostas


Neste documento, há detalhes sobre como construir solicitações e administrar respostas de API do Compute Engine. Confira como:

  • construir um corpo de solicitação;
  • determinar os URIs de recursos necessários para uma solicitação;
  • processar respostas de API;
  • Determine se uma solicitação de API foi bem-sucedida.

Este documento não aborda como autenticar na API. Para saber como autenticar na API, leia Autenticar no Compute Engine.

Antes de começar

Como criar uma solicitação de API

A API Compute Engine exige solicitações de API em formato JSON. Para fazer uma solicitação de API, faça uma solicitação HTTP direta usando ferramentas como curl ou httplib2, ou use uma das bibliotecas de cliente disponíveis.

Quando você faz uma solicitação de API que requer um corpo, como uma solicitação POST, UPDATE ou PATCH, o corpo da solicitação contém as propriedades de recurso que você quer definir nesta solicitação. Por exemplo, o comando curl a seguir faz uma solicitação POST ao URI de recurso de instâncias. A solicitação cria uma instância com as propriedades definidas no corpo da solicitação. O corpo da solicitação é indicado pela sinalização -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"
    }
  ]
}'

O URI da imagem tem um ID de projeto diferente (debian-cloud) do ID do projeto, porque as imagens pertencem a diferentes projetos, dependendo do tipo de imagem. Por exemplo, todas as imagens do Debian disponíveis publicamente pelo Compute Engine são hospedadas no projeto debian-cloud.

Ao fazer referência a outro recurso, use o URI do recurso totalmente qualificado. Por exemplo, a propriedade network usa um URI totalmente qualificado para a rede default.

Exemplos de solicitações da 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();
}

Como criar URIs de recurso

Na API Compute Engine, uma referência a outro recurso do Google Cloud é fornecida como um URI totalmente qualificado:

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

Ao usar a API, sempre que especificar uma imagem, um tipo de máquina, uma rede ou qualquer outro recurso, você precisará fornecer o URI dele. Ferramentas de cliente como a Google Cloud CLI e o Console do Google Cloud ocultam essa complexidade e lidam com a criação dos URIs de recursos para você, mas ao interagir diretamente com a API, você precisa criá-los por conta própria.

Há URIs de recurso visivelmente diferentes para tipos de recursos distintos. Por exemplo, um recurso zonal tem a especificação zone no URI:

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

Os recursos regionais substituem a especificação zone por uma especificação region:

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

Da mesma forma, os recursos globais têm a especificação global:

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

A API Compute Engine também aceita URIs parciais porque o serviço pode inferir informações como o ID do projeto. Portanto, as seguintes versões parciais dos URIs anteriores também são aceitáveis:

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

Nos URIs parciais, os URIs zonais e regionais omitiram o código do projeto, mas o URI da imagem não. Isso ocorre porque as imagens disponíveis publicamente oferecidas pelo Compute Engine são hospedadas em outros projetos, como debian-cloud para todas as imagens do Debian e ubuntu-os-cloud para todas as imagens do Ubuntu. Antes de usar essas imagens, você precisa fornecer o ID do projeto apropriado. Se você omitir o ID do projeto para imagens, o Compute Engine tentará encontrar a imagem no seu projeto, e a solicitação falhará porque a imagem não existe.

No entanto, se você usar uma imagem personalizada que pertence ao seu projeto (o mesmo em que está criando esse recurso), será possível omitir a especificação do projeto ao fornecer um URI de imagem.

Como determinar as propriedades necessárias

A documentação de referência da API Compute Engine, disponível para as APIs v1 e beta, descreve todas as propriedades possíveis que podem ser definidas para um recurso específico. Na documentação de referência, há uma distinção entre as propriedades mutáveis e as imutáveis, marcadas com [Output Only] na descrição da propriedade. Entretanto, para determinar as propriedades necessárias para um recurso, é necessário analisar a documentação específica a essa tarefa.

Por exemplo, se você estiver criando uma instância, leia a documentação Como criar uma instância a partir de uma imagem para ver as propriedades da API necessárias para a solicitação. Se você quiser criar um endereço IP externo estático na API, leia a documentação Endereços IP externos estáticos.

Como validar solicitações de API

Para validar suas solicitações de API, faça o seguinte:

  1. Na referência da API Compute Engine, encontre o método que seu código está chamando. Por exemplo, v1/compute.instances.insert.
  2. No menu de conteúdo, clique em Testar. Isso abre a janela Testar esta API.

    Faça um teste. no menu de conteúdo.

  3. Em Parâmetros de solicitação, não é preciso fornecer um projeto ou zona, porque a validação não exige o envio da solicitação.

  4. Em Corpo da solicitação, cole sua solicitação.

    A janela "Testar esta API", exibindo o campo "Corpo da solicitação" para mostrar onde colar uma solicitação de validação.

Os elementos incorretos da solicitação estão sublinhados em azul. Clique em cada seção sublinhada para mais informações sobre o problema a ser resolvido.

Como manipular as respostas de API

Se você fizer uma solicitação que altere (altera) dados, o Compute Engine retornará um objeto Operation que pode ser pesquisado para receber o status das operações da solicitação. O recurso Operation tem esta aparência:

{
 "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"
}

Se a solicitação original for transformar (alterar) um recurso zonal, por exemplo, para capturar o snapshot de um disco ou interromper uma instância, o Compute Engine retornará um objeto zoneOperations. Da mesma forma, os recursos regionais e globais retornam um objeto regionOperations ou globalOperations, respectivamente. Para ver o status de uma operação, execute uma solicitação que use o método get ou wait no recurso específico Operation e forneça o name da operação.

Sua solicitação só está completa quando o status de recurso Operation é retornado como DONE. Isso pode levar algum tempo, dependendo da natureza da solicitação. Depois que o status do recurso Operation é retornado como DONE, verifique se a operação teve êxito e se houve algum erro.

Por exemplo, a resposta a seguir indica que a operação anterior foi concluída, especificada pelo status 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, faça uma solicitação get ao recurso para verificar se ele existe e está em execução. Exemplo:

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"
}

Como pesquisar operações

É possível escrever um código para pesquisar periodicamente a operação com uma solicitação get ou wait que é retornada quando o status da operação é DONE.

Com uma solicitação get, a operação é retornada imediatamente, independentemente do status dela. Você precisa pesquisar a operação periodicamente para saber quando ela for concluída.

Se você fizer uma solicitação wait, ela retornará quando a operação for DONE ou se a solicitação estiver se aproximando do prazo de dois minutos. É possível usar wait ou get para pesquisar suas operações, mas o método wait oferece determinados benefícios em relação ao get:

  • Você pode configurar seus clientes para pesquisar o status da operação com menos frequência, reduzindo o uso de QPS da API Compute Engine.
  • A latência média entre o momento em que a operação é concluída e quando o cliente é informado de que ela foi concluída é significativamente reduzida, porque o servidor responde assim que é concluído.
  • O método fornece esperas limitadas. O método aguarda no máximo o tempo limite HTTP padrão (2 minutos) e retorna o estado atual da operação, que pode ser DONE ou ainda em andamento.

O método wait é uma API de melhor esforço. Se o servidor estiver sobrecarregado, a solicitação poderá retornar antes de atingir o prazo padrão ou depois de aguardar apenas zero segundo. O método também não tem garantia de retornar somente quando a operação é DONE. Por exemplo, se a solicitação se aproximar do prazo de dois minutos, o método retornará mesmo que a operação não seja concluída. Para verificar suas operações, recomendamos usar o método wait ou get, em um loop de repetição com suspensão intermediária, para analisar periodicamente o status da operação. O intervalo máximo de tentativas não pode exceder o período mínimo de armazenamento da operação.

Exemplo de pesquisa

Os exemplos a seguir usam o método get. Você pode substituir o método get pelo 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();
}