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;
  • determinar se uma solicitação de API foi bem-sucedida.

Neste documento, não há informações sobre como fazer autorização para a API. Para isso, leia Como autorizar solicitações.

Antes de começar

Como criar uma solicitação de API

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

Quando você faz uma solicitação de API que requer um corpo de solicitação, como POST, UPDATE ou PATCH, esse corpo contém propriedades de recurso necessárias para definir a solicitação. Por exemplo, o seguinte comando curl faz uma solicitação POST ao URI de recursos de instâncias. A solicitação cria uma nova instância com as propriedades definidas no corpo da solicitação:

curl -X POST -H "Authorization: Bearer [OAUTH_TOKEN]" -H "Content-Type: application/json"
https://www.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-8-jessie-v20160301"
      }
    }
  ],
  "machineType":"https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/[ZONE]/machineTypes/n1-standard-1",
  "name":"[INSTANCE_NAME]",
  "networkInterfaces":[
    {
      "accessConfigs":[
        {
          "name":"external-nat",
          "type":"ONE_TO_ONE_NAT"
        }
      ],
      "network":"https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/global/networks/default"
    }
  ]
}'

Neste caso, o corpo da solicitação é indicado pela sinalização -d e tem esta aparência, formatada para facilitar a visualização:

{
  "disks":[
    {
      "boot":"true",
      "initializeParams":{
        "sourceImage":"https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-8-jessie-v20160301"
      }
    }
  ],
  "machineType":"https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/[ZONE]/machineTypes/n1-standard-1",
  "name":"[EXAMPLE_INSTANCE]",
  "networkInterfaces":[
    {
      "accessConfigs":[
        {
          "name":"external-nat",
          "type":"ONE_TO_ONE_NAT"
        }
      ],
      "network":"https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/global/networks/default"
    }
  ]
}

Observações importantes:

  • Quando você faz referência a outro recurso, são utilizados os URIs de recursos totalmente qualificados. Por exemplo, a propriedade network usa um URI totalmente qualificado para a rede default.

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

Aqui estão outros exemplos de solicitações de API que usam as bibliotecas de cliente Python e Java.

Python

def create_instance(compute, project, zone, name, bucket):
    # Get the latest Debian Jessie image.
    image_response = compute.images().getFromFamily(
        project='debian-cloud', family='debian-9').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'), 'r').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(
      "https://www.googleapis.com/compute/v1/projects/"
      + PROJECT_ID + "/zones/" + ZONE_NAME + "/machineTypes/n1-standard-1");

  // Add Network Interface to be used by VM Instance.
  NetworkInterface ifc = new NetworkInterface();
  ifc.setNetwork("https://www.googleapis.com/compute/v1/projects/" + PROJECT_ID + "/global/networks/default");
  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("https://www.googleapis.com/compute/v1/projects/" + PROJECT_ID + "/zones/"
                     + ZONE_NAME + "/diskTypes/pd-standard");
  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("gs://" + PROJECT_ID + "/vm-startup.sh");
  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

Nos exemplos acima, para fazer referência a outro recurso do Google Cloud Platform, você forneceu um URI totalmente qualificado com este formato:

https://www.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 gcloud e o Console do Google Cloud Platform 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, da seguinte forma:

https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/[ZONE]/machineTypes/n1-standard-1

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

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

Por último, recursos globais têm a especificação global:

https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/global/images/[IMAGE_NAME]

A API do Compute Engine também aceita URIs parciais porque o serviço pode deduzir informações, como o código do projeto. Assim, o que vem a seguir são versões parciais aceitáveis dos mesmos URIs citados acima:

zones/[ZONE]/machineTypes/n1-standard-1
regions/[REGION]/addresses/[ADDRESS_NAME]
project/[PROJECT_ID]/global/images/[IMAGE_NAME]

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

Por outro lado, se você estiver usando uma imagem personalizada que pertence ao seu projeto, ou seja, o mesmo projeto no qual 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

Na documentação de referência da API, disponível para as versões v1 e Beta, há uma descrição de todas as propriedades que podem ser configuradas em 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 nova instância, leia a documentação Como criar e iniciar uma instância para ver as propriedades da API necessárias para a solicitação. Para criar um endereço IP externo estático na API, leia a documentação Endereços IP externos estáticos.

Também é possível validar suas solicitações de API no explorador de API, como uma maneira rápida e fácil de verificar seu código.

Como manipular as respostas de API

Se você faz uma solicitação que transforma dados, o Compute Engine retorna um objeto de operações que pode ser pesquisado para receber o status da solicitação. O recurso "Operação" tem este formato:

{
 "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_INSTANCE]",
 "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 era transformar um recurso por zona, como uma instância ou um disco, um objeto zoneOperations é retornado pelo Compute Engine. Da mesma maneira, recursos regionais e globais retornam objetos regionOperations e globalOperations, respectivamente. Para receber o status de uma operação, faça uma solicitação GET ao recurso "Operação" específico e informe o name da operação.

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

Por exemplo, a resposta a seguir indica que a mesma operação que aparece acima está completa, com indicação de 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://www.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://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/[ZONE]/instances/[EXAMPLE_INSTANCE]
user: phunl@google.com
zone: https://www.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/ou está em execução. Exemplo:

GET /compute/v1/projects/[PROJECT_ID]/zones/[ZONE]/instances/[EXAMPLE_INSTANCE]

{
  "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_INSTANCE]",
  "status": "RUNNING",
  "tags": {
    "fingerprint": "42WmSpB8rSM="
  },
  "zone": "https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/zones/[ZONE]"
}

Como pesquisar operações

Provavelmente, você não quer passar seu tempo fazendo solicitações individuais para buscar o status de uma operação com a esperança de que ela seja retornada com sucesso. Em vez disso, escreva alguns códigos para pesquisar a operação periodicamente e retornar quando o status da operação aparecer como DONE. Aqui estão alguns exemplos de pesquisa em Python e Java.

Python

def wait_for_operation(compute, project, zone, 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 = operation.getZone();  // null for global/regional operations
  if (zone != null) {
    String[] bits = zone.split("/");
    zone = bits[bits.length - 1];
  }
  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 {
      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();
}
Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Documentação do Compute Engine