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 s'authentifier auprès de l'API. Pour savoir comment gérer l'authentification auprès de l'API, consultez la page S'authentifier sur Compute Engine.

Avant de commencer

  • Familiarisez-vous avec les API REST.
  • Découvrez comment vous authentifier auprès de l'API Compute Engine.

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 effectuer une requête HTTP directe à l'aide d'outils tels que curl ou httplib2, ou utiliser l'une des bibliothèques clientes disponibles.

Lorsque vous effectuez une requête API nécessitant un corps de requête, telle que POST, UPDATE ou PATCH, le corps de la requête contient les propriétés de ressource que vous souhaitez définir dans requête. Par exemple, la commande curl suivante permet d'envoyer une requête POST à l'URI de ressource d'instances. La requête crée une instance avec les propriétés définies dans le corps de la requête. Le corps de la requête est indiqué par l'option -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"
    }
  ]
}'

L'URI d'image possède un ID de projet différent (debian-cloud) de votre ID de projet, car 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.

Lorsque vous référencez une autre ressource, utilisez l'URI complet de la ressource. Par exemple, la propriété network utilise un URI complet du réseau default.

Exemples de requêtes 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();
}

Créer des URI de ressources

Dans l'API Compute Engine, une référence à une autre ressource Google Cloud est attribuée en tant qu'URI complet :

https://compute.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 Google Cloud CLI et la Google Cloud Console 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 :

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

Les ressources régionales remplacent la spécification zone par une spécification region :

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

De même, les ressources globales ont la spécification global :

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

L'API Compute Engine accepte également des URI partiels, car le service peut déduire des informations telles que l'ID de projet. Par conséquent, les versions partielles suivantes des URI précédents sont également acceptables :

zones/ZONE/machineTypes/e2-standard-2
regions/REGION/addresses/ADDRESS_NAME
project/PROJECT_ID/global/images/VM_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 proposées 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. Avant de pouvoir utiliser ces images, vous devez fournir l'ID de projet approprié. Si vous omettez l'ID du projet pour les images, Compute Engine tente de trouver l'image dans votre projet. Étant donné qu'elle n'existe pas, la requête échoue.

Toutefois, si vous utilisez une image personnalisée appartenant à votre projet (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 de l'API Compute Engine, 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 (indiqué par la mention [Output Only] dans la description des propriétés). 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 Créer une instance à partir d'une image pour afficher les propriétés d'API requises pour la requête. Si vous souhaitez créer une adresse IP externe statique dans l'API, consultez la documentation Adresses IP externes statiques.

Valider des requêtes API

Pour valider vos requêtes API, procédez comme suit :

  1. Dans la documentation de référence de l'API Compute Engine, recherchez la méthode appelée par votre code. Par exemple, v1/compute.instances.insert.
  2. Dans le menu "Contenu", cliquez sur Essayer. La fenêtre Essayer cette API s'ouvre.

    Bouton "Essayer" dans le menu "Contenu"

  3. Sous Paramètres de requête, vous n'avez pas besoin de spécifier de projet ni de zone, car la validation ne nécessite pas d'envoyer la requête.

  4. Sous Corps de la requête, collez votre requête.

    Fenêtre "Essayer cette API" affichant le champ "Corps de la requête" pour indiquer où coller une requête de validation

Les éléments non valides de la requête sont soulignés en bleu. Cliquez sur chaque section soulignée pour en savoir plus sur le problème à résoudre.

Gérer les réponses de l'API

Si vous effectuez une requête qui modifie des données, Compute Engine renvoie un objet Operation que vous pouvez interroger pour obtenir l'état des opérations de votre requête. La ressource Operation ressemble à ceci :

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

Si la requête d'origine consiste à modifier une ressource zonale (par exemple, pour créer un instantané d'un disque ou pour arrêter une instance), Compute Engine renvoie un objet zoneOperations. De même, les ressources régionales et globales renvoient respectivement un objet regionOperations ou globalOperations. Vous pouvez obtenir l'état d'une opération en effectuant une requête qui utilise la méthode get ou wait pour la ressource Operation spécifique, en fournissant le 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 que l'état DONE de la ressource Operation 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 indique que l'opération précédente est maintenant terminée, spécifiée par l'état 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

Pour confirmer, envoyez une requête get à la ressource pour vérifier qu'elle existe et qu'elle est en cours d'exécution. Exemple :

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

Interroger les opérations

Vous pouvez écrire du code pour interroger périodiquement l'opération avec une requête get ou wait qui renvoie l'état DONE.

Avec une requête get, l'opération est renvoyée immédiatement, quel que soit son état. Vous devez interroger périodiquement l'opération pour savoir quand elle est terminée.

Si vous envoyez une requête wait, la requête renvoie une réponse lorsque l'opération passe à l'état DONE ou lorsque le délai de deux minutes approche. Vous pouvez choisir d'utiliser wait ou get pour interroger vos opérations, mais la méthode wait offre certains avantages par rapport à la méthode get :

  • Vous pouvez configurer vos clients pour interroger l'état de l'opération moins fréquemment, ce qui réduit votre utilisation des RPS de l'API Compute Engine.
  • La latence moyenne entre la fin de l'opération et le moment où le client est informé que l'opération est terminée est considérablement réduite, car le serveur répond dès que l'opération est terminée.
  • Cette méthode fournit des temps d'attente limités. La méthode n'attend pas plus que le délai d'expiration HTTP par défaut (2 minutes), puis renvoie l'état actuel de l'opération, qui peut être DONE ou toujours en cours.

La méthode wait est une API best-effort (optimisée au mieux). Si le serveur est surchargé, la requête peut envoyer une réponse avant l'expiration du délai par défaut ou après seulement quelques secondes. La méthode ne garantit pas non plus qu'une réponse ne soit renvoyée que lorsque l'opération est à l'état DONE. Par exemple, si la requête approche du délai de 2 minutes, la méthode renvoie une réponse même si l'opération n'est pas terminée. Pour vérifier vos opérations, nous vous recommandons d'utiliser la méthode wait ou get, dans une boucle de nouvelle tentative dotée d'un veille intermédiaire, pour interroger périodiquement l'état de l'opération. L'intervalle maximal entre les tentatives ne doit pas dépasser la durée minimale de conservation des opérations.

Exemple d'interrogation

Les exemples suivants utilisent la méthode get. Vous pouvez remplacer la méthode get par la méthode 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();
}