Créer des requêtes API et gérer les réponses

Ce document explique comment créer des requêtes API et gérer les réponses de l'API Compute Engine. Il décrit comment effectuer les tâches suivantes :

  • Créer le corps d'une requête
  • Identifier les URI de ressources nécessaires pour une requête
  • Gérer les réponses de l'API
  • Déterminer si une requête API a abouti

Ce document n'explique pas comment autoriser l'envoi de requêtes à l'API. Pour savoir comment effectuer cette opération, consultez la page Autoriser les requêtes.

Avant de commencer

Créer une requête API

L'API Compute Engine requiert que les requêtes API soient au format JSON. Pour effectuer une requête API, vous pouvez créer une requête HTTP directe au moyen d'outils tels que curl ou httplib2, ou bien utiliser l'une des bibliothèques clientes disponibles.

Lorsque vous effectuez une requête API nécessitant un corps de requête (POST, UPDATE ou PATCH, par exemple), ce dernier contient les propriétés de ressources que vous souhaitez définir. Ainsi, la commande curl ci-dessous permet d'envoyer une requête POST à l'URI d'une ressource d'instances. Une instance est créée avec les propriétés définies dans le corps de la requête :

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

Dans ce cas, le corps de la requête est spécifié par l'indicateur -d et se présente comme suit (après avoir été mis en forme pour une meilleure lisibilité) :

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

Remarques :

  • Lors du référencement d'une autre ressource, les URI de ressources complets sont utilisés. Par exemple, la propriété network utilise un URI complet du réseau default.

  • L'URI d'image utilise un ID de projet (debian-cloud) différent de celui de votre projet. En effet, les images appartiennent à différents projets selon leur type. Par exemple, toutes les images Debian publiques fournies par Compute Engine sont hébergées dans le projet debian-cloud.

D'autres exemples de requêtes API utilisant les bibliothèques clientes Python et Java sont présentés ci-dessous.

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

Créer des URI de ressources

Dans les exemples ci-dessus, chaque fois qu'il était nécessaire de référencer une autre ressource Google Cloud Platform, un URI complet semblable au suivant était fourni :

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

Chaque fois que vous spécifiez une image, un type de machine, un réseau ou toute autre ressource, vous devez indiquer l'URI de la ressource lorsque vous utilisez l'API. Des outils clients tels que gcloud et la console Google Cloud Platform masquent cette complexité et gèrent la création de ces URI de ressources à votre place. Toutefois, lorsque vous interagissez directement avec l'API, vous devez créer ces URI de ressources vous-même.

Les URI de ressources varient légèrement en fonction des types de ressources. Par exemple, l'URI d'une ressource zonale inclut la spécification zone. Exemple :

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

Dans les URI de ressources régionales, la spécification zone est remplacée par region. Exemple :

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

Enfin, les ressources globales utilisent la spécification global. Exemple :

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

L'API Compute Engine accepte également des URI partiels, car le service peut déduire des informations telles que l'ID de projet. Voici des versions partielles acceptables des URI présentées ci-dessus :

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

Dans les URI partiels ci-dessus, l'ID de projet est omis pour les URI des ressources zonale et régionale, mais pas pour l'URI d'image. En effet, les images publiques fournies par Compute Engine sont hébergées dans d'autres projets tels que debian-cloud pour toutes les images Debian et ubuntu-os-cloud pour toutes les images Ubuntu. Pour utiliser ces images, vous devez spécifier l'ID de projet approprié. Si vous avez omis l'ID de projet pour les images, Compute Engine tente de trouver les images dans votre projet. Étant donné qu'elles n'existent pas, la requête échoue.

Par ailleurs, si vous utilisez une image personnalisée appartenant à votre projet (c'est-à-dire le projet dans lequel vous créez la ressource), vous pouvez omettre la spécification de projet lorsque vous indiquez un URI d'image.

Identifier les propriétés requises

La documentation de référence disponible pour les API v1 et bêta décrit toutes les propriétés que vous pouvez définir pour une ressource spécifique. Elle distingue les propriétés modifiables de celles qui sont non modifiables (et dont la description comporte la mention [Output Only]). Toutefois, pour déterminer les propriétés requises pour une ressource, vous devez examiner la documentation propre à la tâche concernée.

Par exemple, si vous créez une instance, consultez la documentation sur la création et le démarrage d'une instance pour connaître les propriétés d'API requises pour la requête. Si vous souhaitez créer une adresse IP externe statique dans l'API, reportez-vous à la documentation relative aux adresses IP externes statiques.

Vous pouvez également valider vos requêtes API dans l'explorateur d'API, ce qui vous permet de vérifier votre code rapidement et facilement.

Gérer les réponses de l'API

Si vous effectuez une requête qui modifie les données, Compute Engine renvoie un objet Operations que vous pouvez ensuite interroger pour obtenir l'état de votre requête. La ressource Operation se présente comme suit :

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

Si la requête d'origine consistait à modifier une ressource zonale, telle qu'une instance ou un disque, Compute Engine renvoie un objet zoneOperations. De même, un objet regionOperations ou globalOperations est respectivement renvoyé pour les ressources régionales ou globales. Vous pouvez obtenir l'état d'une opération en effectuant une requête GET sur la ressource "Operation" concernée. Pour ce faire, vous devez indiquer la valeur name de l'opération.

Votre requête n'est pas terminée tant que l'état renvoyé pour la ressource Operation n'est pas DONE. Cela peut prendre un certain temps selon la nature de la requête. Dès lors que l'état DONE est renvoyé, vous devez vérifier si l'opération a abouti et si des erreurs se sont produites.

Par exemple, la réponse suivante, qui spécifie l'état DONE, indique que l'opération ci-dessus est désormais terminée :

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]

Vous devez effectuer une requête GET sur la ressource afin de vous assurer qu'elle existe et/ou qu'elle est en cours d'exécution. Exemple :

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

Interroger les opérations

Vous ne souhaitez probablement pas passer votre temps à effectuer des requêtes individuelles pour obtenir l'état d'une opération en espérant qu'elle aboutisse. Il est préférable d'écrire du code permettant d'interroger régulièrement l'opération et renvoyant l'état DONE lorsque cette dernière est terminée. Différents exemples de requêtes d'interrogation en Python et Java sont présentés ci-dessous.

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