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üfung, ob eine API-Anfrage erfolgreich war.

Die Autorisierung bei der API wird in diesem Dokument nicht behandelt. Informationen zur Autorisierung bei der API finden Sie unter Autorisierung von Anfragen.

Vorbereitung

Formulierung einer API-Anfrage

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 und hierzu ein Tool wie curl oder httplib2 verwenden oder Sie können eine der verfügbaren Clientbibliotheken nutzen.

Wenn Sie eine API-Anfrage senden, die einen Anfragetext erfordert, zum Beispiel eine POST-, UPDATE-, oder PATCH-Anfrage, dann enthält der Anfragetext Properties, die Sie in dieser Anfrage verwenden wollen. Zum Beispiel führt der folgende curl-Befehl eine POST-Anfrage an den Ressourcen-URI "Instances" aus. Die Anfrage erzeugt eine neue Instanz mit den im Anfragetext definierten Properties:

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

In diesem Fall wird der Anfragetext durch das Flag -d angezeigt und sieht so aus (zum leichteren Lesen formatiert):

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

Beachten Sie:

  • Wenn eine andere Ressource referenziert wird, werden voll qualifizierte Ressourcen-URIs verwendet. Das Attribut network nutzt beispielsweise einen voll qualifizierten URI zum Standardnetzwerk default.

  • Der Image-URI hat eine andere Projekt-ID (debian-cloud) als Ihre Projekt-ID. Images gehören nämlich je nach Art des Images zu unterschiedlichen Projekten. So werden zum Beispiel alle öffentlich verfügbaren Debian-Images, die von Compute Engine angeboten werden, auf dem Projekt debian-cloud gehostet.

Hier einige weitere Beispiele für API-Anfragen unter Verwendung der Python- und Java-Clientbibliotheken.

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

Ressourcen-URIs erstellen

In den obigen Beispielen haben Sie jedes Mal, wenn Sie eine andere Google Cloud Platform-Ressource referenzieren mussten, einen voll qualifizierten URI angegeben, der so aussah:

https://www.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 gcloud und der Google Cloud Platform 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 verschiedenen Arten von Ressourcen sind die Ressourcen-URIs etwas unterschiedlich. So hat zum Beispiel eine zonale Ressource die Spezifikation zone im URI, in dieser Form:

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

Bei regionalen Ressourcen wird die Spezifikation zone durch die Spezifikation region ersetzt:

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

Globale Ressourcen schließlich haben die Spezifikation global:

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

Die Compute Engine API akzeptiert auch Teil-URIs, denn der Dienst kann Informationen wie die Projekt-ID auch ableiten. Die folgenden Möglichkeiten sind demnach akzeptable Teilversionen der oben angegebenen URIs:

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

In den obigen Teil-URIs haben die zonalen und regionalen URIs die Projekt-ID jeweils weggelassen, die Image-URI jedoch nicht. Der Grund dafür ist, dass die von Compute Engine angebotenen, öffentlich verfügbaren Images in anderen Projekten gehostet werden, wie debian-cloud für alle Debian-Images und ubuntu-os-cloud für alle Ubuntu-Images. Um diese Images nutzen zu können, müssen Sie die passende Projekt-ID angeben. Wenn Sie bei Images die Projekt-ID weglassen, versucht Compute Engine das Image in Ihrem Projekt zu finden, und die Anfrage schlägt fehl, weil das Image nicht vorhanden ist.

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

Erforderliche Properties ermitteln

In der API-Referenzdokumentation für die v1 API und die Beta API sind alle Attribute beschrieben, die Sie für eine bestimmte Ressource angeben können. Die Referenzdokumentation unterscheidet zwischen veränderbaren und nicht veränderbaren Properties (in der Propertybeschreibung mit [Output Only] gekennzeichnet), aber um die erforderlichen Properties für eine Ressource zu bestimmen, müssen Sie die spezifische Dokumentation für diese Aufgabe nachschlagen.

Wenn Sie beispielsweise eine neue Instanz erstellen, können Sie unter Instanz erstellen und starten nachlesen, welche API-Attribute für die Anfrage erforderlich sind. Informationen zum Erstellen einer statischen externen IP-Adresse in der API finden Sie unter Statische externe IP-Adressen.

Alternativ dazu können Sie Ihre API-Anfragen im API-Explorer validieren und Ihren Code dort schnell und einfach überprüfen.

Behandlung von API-Antworten

Wenn Sie eine Anfrage senden, die Daten ändert, gibt Compute Engine ein Vorgangsobjekt zurück, das Sie dann abfragen können, um den Status Ihrer Anfrage zu erfahren. Die Vorgangsressource 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_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"
}

Wenn in der ursprünglichen Anfrage die Änderung einer zonalen Ressource wie einer Instanz oder eines Laufwerks angefordert wurde, gibt Compute Engine ein zoneOperations-Objekt zurück. Analog geben regionale und globale Ressourcen ein regionOperations- bzw. globalOperations-Objekt zurück. Sie können den Status eines Vorgangs mit einer GET-Anfrage an die entsprechende Ressource abrufen, dabei müssen Sie den name des Vorgangs angeben.

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

So zeigt die folgende Antwort zum Beispiel an, dass derselbe Vorgang, der oben beschrieben ist, jetzt abgeschlossen ist, wie der Status DONE angibt:

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]

Zur Bestätigung sollten Sie eine GET-Anfrage an die Ressource senden, um zu prüfen, dass diese existiert bzw. ausgeführt wird. Beispiel:

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

Vorgänge abfragen

Sie wollen wahrscheinlich Ihre Zeit nicht damit verbringen, einzelne Anfragen zu senden, um den Status eines Vorgangs zu erfahren, in der Hoffnung, dass der Vorgang erfolgreich beendet wurde. Sie sollten stattdessen Code schreiben, der in regelmäßigen Abständen den Vorgang abfragt und beendet wird, wenn der Status des Vorgangs DONE ist. Hier einige Beispiele für solche Abfragen in Python und 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();
}