API-Anfragen erstellen und Antworten verarbeiten


In diesem Dokument wird beschrieben, wie API-Anfragen formuliert und API-Antworten der Compute Engine API behandelt werden. Folgende Themen werden abgedeckt:

  • Formulierung eines Anfragetextes.
  • Bestimmung der Ressource-URIs, die für die Anfrage erforderlich sind.
  • Verarbeitung von API-Antworten.
  • Prüfen, ob eine API-Anfrage erfolgreich war.

Die Authentifizierung bei der API wird in diesem Dokument nicht behandelt. Weitere Informationen zur Authentifizierung bei der API finden Sie unter Bei Compute Engine authentifizieren.

Vorbereitung

API-Anfrage erstellen

Die Compute Engine API erwartet API-Anfragen im JSON-Format. Um eine API-Anfrage zu senden, können Sie entweder eine direkte HTTP-Anfrage stellen, indem Sie Tools wie curl oder httplib2 verwenden, oder Sie können eine der verfügbaren Clientbibliotheken verwenden.

Wenn Sie eine API-Anfrage stellen, die einen Anfragetext erfordert, z. B. eine POST-, UPDATE- oder PATCH-Anfrage, enthält der Anfragetext Ressourcenattribute, die Sie in der Anfrage festlegen sollten. Mit dem folgenden curl-Befehl wird beispielsweise eine POST-Anfrage an den URI der Instanzressource gesendet. Die Anfrage erstellt eine Instanz mit den im Anfragetext definierten Attributen. Der Anfragetext wird durch das Flag -d angezeigt:

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

Der Image-URI hat eine andere Projekt-ID (debian-cloud) als Ihre Projekt-ID, da die Images je nach Image-Typ zu unterschiedlichen Projekten gehören. So werden beispielsweise alle öffentlich verfügbaren Debian-Images, die von Compute Engine angeboten werden, im Projekt debian-cloud gehostet.

Verwenden Sie beim Verweis auf eine andere Ressource den voll qualifizierten Ressourcen-URI. Das Attribut network verwendet beispielsweise einen voll qualifizierten URI zum default-Netzwerk.

Beispiel: API-Anfragen

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

Ressourcen-URIs erstellen

In der Compute Engine API wird ein Verweis auf eine andere Google Cloud-Ressource als voll qualifizierter URI angegeben:

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

Jedes Mal, wenn Sie ein Image, einen Maschinentyp, ein Netzwerk oder irgendeine andere Ressource angeben, müssen Sie den URI zu der Ressource angeben, wenn Sie die API benutzen. Bei Client-Tools wie der Google Cloud CLI und der Google Cloud Console bleibt diese Komplexität unsichtbar, denn diese übernehmen das Erstellen dieser Ressourcen-URIs für Sie, aber wenn Sie direkt mit der API interagieren, müssen Sie diese Ressourcen-URIs selbst erstellen.

Bei unterschiedlichen Arten von Ressourcen kann es Abweichungen bezüglich der Ressourcen-URIs geben. Eine zonale Ressource hat beispielsweise die Spezifikation zone im URI:

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

Regionale Ressourcen ersetzen die Spezifikation zone durch die Spezifikation region:

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

Globale Ressourcen haben ebenfalls die Spezifikation global:

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

Die Compute Engine API akzeptiert auch Teil-URIs, da der Dienst Informationen wie die Projekt-ID ableiten kann. Daher sind auch die folgenden Teilversionen der früheren URIs akzeptabel:

zones/ZONE/machineTypes/e2-standard-2
regions/REGION/addresses/ADDRESS_NAME
project/PROJECT_ID/global/images/VM_NAME

In den Teil-URIs haben die zonalen und regionalen URIs die Projekt-ID jeweils weggelassen, die Image-URI jedoch nicht. Dies liegt daran, dass öffentlich verfügbare Compute Engine-Images in anderen Projekten gehostet werden, z. B. debian-cloud für alle Debian-Images und ubuntu-os-cloud für alle Ubuntu-Images. Bevor Sie diese Images verwenden können, müssen Sie die entsprechende Projekt-ID angeben. Wenn Sie die Projekt-ID für Images weglassen, versucht Compute Engine, das Image in Ihrem Projekt zu finden, und die Anfrage schlägt fehl, da das Image nicht vorhanden ist.

Wenn Sie jedoch ein benutzerdefiniertes Image verwenden, das zu Ihrem Projekt gehört (demselben Projekt, in dem Sie diese Ressource erstellen), können Sie die Projektspezifikation weglassen, wenn Sie ein Image-URI bereitstellen.

Erforderliche Properties ermitteln

In der Referenzdokumentation zur Compute Engine API, die sowohl für die v1 als auch für die beta APIs verfügbar ist, werden alle möglichen Attribute beschrieben, die Sie für eine bestimmte Ressource festlegen können. Die Referenzdokumentation unterscheidet zwischen veränderlichen und nicht veränderlichen Attributen (in der Attributbeschreibung mit [Output Only] gekennzeichnet). Um die erforderlichen Attribute für eine Ressource zu bestimmen, müssen Sie jedoch die spezifische Dokumentation für diese Aufgabe nachschlagen.

Wenn Sie beispielsweise eine Instanz erstellen, lesen Sie die Dokumentation Instanz aus einem Image erstellen, um zu sehen, welche API-Attribute für die Anfrage erforderlich sind. Wenn Sie in der API eine statische externe IP-Adresse erstellen möchten, lesen Sie die Dokumentation Statische externe IP-Adressen.

API-Anfragen validieren

So validieren Sie API-Anfragen:

  1. Suchen Sie in der Referenz zur Compute Engine API nach der Methode, die Ihr Code aufruft. Beispiel: v1/compute.instances.insert.
  2. Klicken Sie im Inhaltsmenü auf Jetzt testen. Dadurch wird das Fenster API testen geöffnet.

    Die Schaltfläche &quot;Testen!&quot; im Inhaltsmenü.

  3. Unter Anfrageparameter müssen Sie kein Projekt oder keine Zone angeben, da die Validierung keine Übermittlung der Anfrage erfordert.

  4. Fügen Sie Ihre Anfrage unter Anfragetext ein.

    Das Fenster &quot;API testen&quot; mit dem Feld &quot;Anfragetext&quot;, um zu zeigen, wo eine Anfrage zur Validierung eingefügt werden sollte

Fehlerhafte Elemente der Anfrage sind blau unterstrichen. Klicken Sie auf jeden unterstrichenen Abschnitt, um weitere Informationen zu dem zu behebenden Problem zu erhalten.

Behandlung von API-Antworten

Wenn Sie eine Anfrage stellen, durch die Daten mutiert (geändert) werden, gibt Compute Engine ein Operation-Objekt zurück, das Sie abfragen können, um den Status der Vorgänge für Ihre Anfrage abzurufen. Die Operation-Ressource sieht so aus:

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

Wenn die ursprüngliche Anfrage eine zonale Ressource mutieren (ändern) soll, z. B. zum Erstellen eines Snapshots eines Laufwerks oder zum Beenden einer Instanz, gibt Compute Engine ein zoneOperations-Object zurück. Regionale und globale Ressourcen geben ein regionOperations- bzw. globalOperations-Objekt zurück. Sie können den Status eines Vorgangs abrufen, indem Sie eine Anfrage ausführen, die die Methode get oder wait für die jeweilige Ressource Operation verwendet und den name des Vorgangs angibt.

Ihre Anfrage ist erst abgeschlossen, wenn der Status der Vorgangsressource Operation als DONE zurückgegeben wird. Dies kann je nach Art Ihrer Anfrage etwas Zeit in Anspruch nehmen. Wenn der Status der Ressource Operation schließlich als DONE zurückgegeben wird, sollten Sie prüfen, ob der Vorgang erfolgreich war oder ob Fehler aufgetreten sind.

Die folgende Antwort gibt mit dem Status DONE beispielsweise an, dass der vorherige Vorgang jetzt abgeschlossen ist:

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

Stellen Sie zur Bestätigung eine get-Anfrage an die Ressource, um zu prüfen, ob sie vorhanden ist und ausgeführt wird. Beispiel:

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

Vorgänge abfragen

Sie können Code schreiben, um den Vorgang regelmäßig mit einer get- oder wait-Anfrage abzufragen, die zurückgegeben wird, wenn der Vorgangsstatus DONE lautet.

Bei einer get-Anfrage wird der Vorgang sofort zurückgegeben, unabhängig vom Status des Vorgangs. Sie müssen den Vorgang regelmäßig abfragen, um zu erfahren, wann er abgeschlossen ist.

Wenn Sie eine wait-Anfrage stellen, wird die Anfrage zurückgegeben, wenn der Vorgang DONE ist oder wenn sich die Anfrage der Frist von zwei Minuten nähert. Sie können wait oder get verwenden, um Ihre Vorgänge abzufragen. Die Methode wait bietet jedoch bestimmte Vorteile gegenüber der Methode get:

  • Sie können Ihre Clients so einrichten, dass sie den Vorgangsstatus weniger häufig abfragen, und somit die Abfragen pro Sekunde der Compute Engine API reduzieren.
  • Die durchschnittliche Latenz zwischen der Fertigstellung des Vorgangs und dem Zeitpunkt, zu dem der Client über die Fertigstellung des Vorgangs informiert wird, reduziert sich erheblich, da der Server antwortet, sobald der Vorgang abgeschlossen ist.
  • Die Methode bietet begrenzte Wartezeiten. Die Methode wartet nicht mehr als die standardmäßige HTTP-Zeitüberschreitung (2 Minuten) und gibt dann den aktuellen Status des Vorgangs zurück, der DONE oder "Noch in Bearbeitung" sein kann.

Die Methode wait ist eine Best-Effort-API. Wenn der Server überlastet ist, wird die Anfrage möglicherweise zurückgegeben, bevor sie die Standardfrist erreicht oder nachdem nur null Sekunden gewartet wurde. Es wird auch nicht garantiert, dass die Methode nur zurückgegeben wird, wenn der Vorgang den Status DONE hat. Wenn sich die Anfrage beispielsweise der Frist von 2 Minuten nähert, wird die Methode auch dann zurückgegeben, wenn der Vorgang nicht abgeschlossen ist. Zur Prüfung Ihrer Vorgänge empfehlen wir, die Methode wait oder get in einer Wiederholungsschleife mit zwischengeschaltetem Ruhemodus zu verwenden, um den Vorgangsstatus regelmäßig abzufragen. Das maximale Wiederholungsintervall sollte die Mindestaufbewahrungsdauer für Vorgänge nicht überschreiten.

Beispielabfragen

In den folgenden Beispielen wird die get-Methode verwendet. Sie können die get-Methode durch die wait-Methode ersetzen:

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