Criar pedidos de API e processar respostas

Este documento descreve como criar pedidos de API e processar respostas de API da API Compute Engine. Explica como:

  • Construa um corpo do pedido.
  • Determine os URIs de recursos necessários para um pedido.
  • Processar respostas da API.
  • Determinar se um pedido de API foi bem-sucedido.

Este documento não aborda a autenticação na API. Para saber como se autenticar na API, leia o artigo Autenticar-se no Compute Engine.

Antes de começar

Criar um pedido de API

A API Compute Engine espera que os pedidos de API estejam no formato JSON. Para fazer um pedido de API, pode fazer um pedido HTTP direto através de ferramentas como curl ou httplib2, ou pode usar uma das bibliotecas cliente disponíveis.

Quando faz um pedido de API que requer um corpo do pedido, como um pedido POST, UPDATE ou PATCH, o corpo do pedido contém propriedades de recursos que quer definir neste pedido. Por exemplo, o seguinte comando curl faz um pedido POST ao URI do recurso Instances. O pedido cria uma instância com as propriedades definidas no corpo do pedido. O corpo do pedido é indicado pela flag -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 do projeto (debian-cloud) diferente do ID do seu projeto, porque as imagens pertencem a projetos diferentes, consoante o tipo de imagem. Por exemplo, todas as imagens Debian disponíveis publicamente oferecidas pelo Compute Engine estão alojadas no projeto debian-cloud.

Quando fizer 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 pedidos de 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();
}

Criar URIs de recursos

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

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

Sempre que especificar uma imagem, um tipo de máquina, uma rede ou qualquer outro recurso, tem de fornecer o URI do recurso quando usar a API. As ferramentas de cliente, como a CLI Google Cloud e a Trusted Cloud consola, ocultam esta complexidade e processam a criação destes URIs de recursos por si, mas quando interage diretamente com a API, tem de criar estes URIs de recursos.

Existem URIs de recursos ligeiramente diferentes para diferentes tipos de recursos. 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. Assim, as seguintes versões parciais dos URI 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 ID do projeto, mas o URI da imagem não. Isto deve-se ao facto de as imagens disponíveis publicamente oferecidas pelo Compute Engine estarem alojadas noutros projetos, como debian-cloud para todas as imagens Debian e ubuntu-os-cloud para todas as imagens Ubuntu. Antes de poder usar estas imagens, tem de indicar o ID do projeto adequado. Se omitir o ID do projeto para imagens, o Compute Engine tenta encontrar a imagem no seu projeto e o pedido falha porque a imagem não existe.

No entanto, se usar uma imagem personalizada que pertence ao seu projeto (o mesmo projeto no qual está a criar este recurso), pode omitir a especificação do projeto quando fornecer um URI de imagem.

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 pode definir para um recurso específico. A documentação de referência faz uma distinção entre propriedades mutáveis e imutáveis (marcadas com um [Output Only] na descrição da propriedade), mas para determinar as propriedades necessárias para um recurso, tem de rever a documentação específica dessa tarefa.

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

Validar pedidos de API

Para validar os pedidos da API:

  1. Na referência da API Compute Engine, encontre o método que o seu código está a chamar. Por exemplo, v1/compute.instances.insert.
  2. No menu de conteúdos, clique em Experimentar! Esta ação abre a janela Experimentar esta API.

    O botão Experimentar! no menu de conteúdos.

  3. Em Parâmetros de pedido, não tem de fornecer um projeto nem uma zona porque a validação não requer o envio do pedido.

  4. Em Corpo do pedido, cole o seu pedido.

    A janela Experimentar esta API apresenta o campo Corpo do pedido para mostrar onde colar um pedido de validação.

Os elementos com formato incorreto do pedido são sublinhados a azul. Clique em cada secção sublinhada para ver mais informações sobre o problema a resolver.

Processamento de respostas da API

Se fizer um pedido que altere os dados, o Compute Engine devolve um objeto Operation que pode sondar para obter o estado das operações do seu pedido. O recurso Operation tem o seguinte aspeto:

{
 "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 o pedido original for para alterar um recurso zonal, por exemplo, para criar uma captura de ecrã de um disco ou parar uma instância, o Compute Engine devolve um objeto zoneOperations. Da mesma forma, os recursos regionais e globais devolvem um objeto regionOperations ou globalOperations, respetivamente. Pode obter o estado de uma operação fazendo um pedido que use o método get ou wait para o recurso Operation específico e fornecendo o name da operação.

O seu pedido não está concluído até que o estado do recurso Operation seja devolvido como DONE. Este processo pode demorar algum tempo, consoante a natureza do seu pedido. Em seguida, depois de o estado do recurso Operation ser devolvido como DONE, pode verificar se a operação foi bem-sucedida e se ocorreram erros.

Por exemplo, a seguinte resposta indica que a operação anterior está agora concluída, especificada pelo 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, faça um pedido get ao recurso para verificar se existe e está em execução. Por 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"
}

Operações de sondagem

Pode escrever algum código para sondar periodicamente a operação com um pedido get ou wait que é devolvido quando o estado da operação é DONE.

Com um pedido get, a operação é devolvida imediatamente, independentemente do estado da operação. Tem de sondar a operação periodicamente para saber quando a operação é concluída.

Se fizer um pedido wait, o pedido é devolvido quando a operação é DONE ou se o pedido estiver a aproximar-se do prazo de 2 minutos. Pode optar por usar wait ou get para sondar as suas operações, mas o método wait oferece determinadas vantagens em relação ao método get:

  • Pode configurar os seus clientes para sondarem o estado da operação com menor frequência, reduzindo a sua utilização de QPS da API Compute Engine.
  • A latência média entre o momento em que a operação é concluída e o momento em que o cliente é informado de que a operação foi concluída é significativamente reduzida porque o servidor responde assim que a operação é concluída.
  • O método oferece esperas limitadas. O método aguarda, no máximo, o tempo limite HTTP predefinido (2 minutos) e, em seguida, devolve o estado atual da operação, que pode ser DONE ou ainda estar em curso.

O método wait é uma API de melhor esforço. Se o servidor estiver sobrecarregado, o pedido pode ser devolvido antes de atingir o prazo predefinido ou após esperar apenas zero segundos. Além disso, não é garantido que o método seja devolvido apenas quando a operação for DONE. Por exemplo, se o pedido se aproximar do prazo de 2 minutos, o método é devolvido mesmo que a operação não esteja concluída. Para verificar as suas operações, recomendamos que use o método wait ou get, num ciclo de repetição com um intervalo de espera, para sondar periodicamente o estado da operação. O intervalo máximo de novas tentativas não deve exceder o período mínimo de retenção da operação.

Exemplo de sondagem

Os exemplos seguintes usam o método get. 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();
}