Criação de solicitações de API e tratamento de respostas


Este documento descreve como criar solicitações de API e processar respostas de API da API Compute Engine. Abrange como:

  • Construa um corpo de solicitação.
  • Determine os URIs de recursos necessários para uma solicitação.
  • Lidar com respostas da API.
  • Determine se uma solicitação de API foi bem-sucedida.

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

Antes de começar

Criando uma solicitação de API

A API Compute Engine espera que as solicitações de API estejam no formato JSON. Para fazer uma solicitação de API, você pode fazer uma solicitação HTTP direta, usando ferramentas como curl ou httplib2 , ou pode usar uma das bibliotecas cliente disponíveis .

Quando você faz uma solicitação de API que requer um corpo de solicitação, como uma solicitação POST , UPDATE ou PATCH , o corpo da solicitação contém propriedades de recursos que você deseja definir nessa solicitação. Por exemplo, o comando curl a seguir faz uma solicitação POST para o 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 pelo sinalizador -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 possui um ID de projeto diferente ( debian-cloud ) do ID do seu projeto porque as imagens pertencem a projetos diferentes, dependendo do tipo de imagem. Por exemplo, todas as imagens Debian disponíveis publicamente oferecidas pelo Compute Engine são hospedadas no projeto debian-cloud .

Ao fazer referência a outro recurso, utilize o URI de recurso totalmente qualificado. Por exemplo, a propriedade network utiliza um URI totalmente qualificado para a rede default .

Exemplos de solicitações de API

Pitão

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

Criando URIs de recursos

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

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

Sempre que você especificar uma imagem, um tipo de máquina, uma rede ou qualquer outro recurso, deverá fornecer o URI ao recurso ao usar a API. Ferramentas de cliente como a CLI do Google Cloud e o console do Google Cloud ocultam essa complexidade e cuidam da criação desses URIs de recursos para você, mas ao interagir diretamente com a API, você mesmo deve criar esses URIs de recursos.

Existem URIs de recursos ligeiramente diferentes para diferentes tipos de recursos. Por exemplo, um recurso zonal possui 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, tanto os URIs zonais quanto os regionais omitiram o ID 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 poder 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 projeto no qual você está criando esse recurso), poderá omitir a especificação do projeto ao fornecer um URI de imagem.

Determinando propriedades necessárias

A documentação de referência da API Compute Engine, disponível para APIs v1 e beta , descreve todas as propriedades possíveis que você 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 por [Output Only] na descrição da propriedade), mas para determinar as propriedades necessárias para um recurso, é necessário revisar a documentação específica para essa tarefa.

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

Validando solicitações de API

Para validar suas solicitações de API:

  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 Experimente! Isso abre a janela Experimente esta API .

    O Experimente! botão no menu de conteúdo.

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

  4. Em Request body , cole sua solicitação.

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

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

Tratamento de respostas da API

Se você fizer uma solicitação que altere (altere) os dados, o Compute Engine retornará um objeto Operation que você poderá pesquisar para obter o status das operações da sua solicitação. O recurso Operation é assim:

{
 "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 para modificar (alterar) um recurso zonal (por exemplo, para capturar um instantâneo 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. Você pode obter o status de uma operação executando uma solicitação que usa o método get ou wait para o recurso Operation específico e fornecendo o name da operação.

Sua solicitação não será concluída até que o status do recurso Operation retorne como DONE . Isso pode levar algum tempo dependendo da natureza da sua solicitação. Então, depois que o status do recurso Operation retornar como DONE , você poderá verificar se a operação foi bem-sucedida 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. 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 votação

Você pode escrever algum código para pesquisar periodicamente a operação com uma solicitação get ou wait que retorna quando o status da operação for DONE .

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

Se você fizer uma solicitação wait , a solicitação retornará quando a operação for DONE ou se a solicitação estiver se aproximando do prazo de 2 minutos. Você pode optar por usar wait ou get para pesquisar suas operações, mas o método wait oferece alguns benefícios em relação ao método 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 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 fornece esperas limitadas. O método não espera mais do que o tempo limite HTTP padrão (2 minutos) e então 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 após esperar apenas zero segundos. Também não é garantido que o método retorne apenas quando a operação for DONE . Por exemplo, se a requisição se aproximar do prazo de 2 minutos, o método retornará mesmo que a operação não seja realizada. Para verificar suas operações, recomendamos que você use o método wait ou get — em um loop de nova tentativa com suspensão intermediária — para pesquisar periodicamente o status 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 pesquisa

Os exemplos a seguir usam o método get . Você pode substituir o método get pelo método wait :

Pitão

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