You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by er...@apache.org on 2016/10/13 15:14:23 UTC

[1/4] libcloud git commit: [GCE] Added support for HTTP(S) proxies with BackendServices

Repository: libcloud
Updated Branches:
  refs/heads/trunk 065d1919d -> c873a0d34


http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/aggregated_instanceGroupManagers.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/aggregated_instanceGroupManagers.json b/libcloud/test/compute/fixtures/gce/aggregated_instanceGroupManagers.json
index f42644e..0f42511 100644
--- a/libcloud/test/compute/fixtures/gce/aggregated_instanceGroupManagers.json
+++ b/libcloud/test/compute/fixtures/gce/aggregated_instanceGroupManagers.json
@@ -85,7 +85,7 @@
      "name": "myinstancegroup",
      "description": "my description for myinstancegroup",
      "instanceTemplate": "https://content.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my-instance-template1",
-     "instanceGroup": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroups/myinstancegroup",
+     "instanceGroup": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroups/myinstancegroup",
      "baseInstanceName": "base-foo",
      "fingerprint": "Q21hYveq9do=",
      "currentActions": {
@@ -98,7 +98,7 @@
       "refreshing": 0
      },
      "targetSize": 4,
-     "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroupManagers/myinstancegroup"
+     "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroupManagers/myinstancegroup"
     }
    ]
   },
@@ -232,7 +232,7 @@
      "zone": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b",
      "name": "myinstancegroup",
      "instanceTemplate": "https://content.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my-instance-template1",
-     "instanceGroup": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroups/myinstancegroup",
+     "instanceGroup": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroups/myinstancegroup",
      "baseInstanceName": "myinstancegroup",
      "fingerprint": "5bKcxzAnGOg=",
      "currentActions": {
@@ -245,7 +245,7 @@
       "refreshing": 0
      },
      "targetSize": 2,
-     "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroupManagers/myinstancegroup"
+     "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroupManagers/myinstancegroup"
     }
    ]
   },

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/global_backendServices_web_service.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/global_backendServices_web_service.json b/libcloud/test/compute/fixtures/gce/global_backendServices_web_service.json
index e9af3b4..4e0e5b1 100644
--- a/libcloud/test/compute/fixtures/gce/global_backendServices_web_service.json
+++ b/libcloud/test/compute/fixtures/gce/global_backendServices_web_service.json
@@ -7,14 +7,14 @@
  "backends": [
   {
    "description": "",
-   "group": "https://www.googleapis.com/resourceviews/v1beta1/projects/project_name/zones/us-central1-b/resourceViews/us-resources",
+   "group": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup",
    "balancingMode": "RATE",
    "maxRate": 100,
    "capacityScaler": 1.0
   },
   {
    "description": "",
-   "group": "https://www.googleapis.com/resourceviews/v1beta1/projects/project_name/zones/europe-west1-b/resourceViews/eu-resources",
+   "group": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup",
    "balancingMode": "RATE",
    "maxRate": 150,
    "capacityScaler": 1.0

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/global_instanceTemplates.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/global_instanceTemplates.json b/libcloud/test/compute/fixtures/gce/global_instanceTemplates.json
index d2b0225..1614824 100644
--- a/libcloud/test/compute/fixtures/gce/global_instanceTemplates.json
+++ b/libcloud/test/compute/fixtures/gce/global_instanceTemplates.json
@@ -1,5 +1,5 @@
 {
-    "id": "projects/supertom-graphite/global/instanceTemplates",
+    "id": "projects/project_name/global/instanceTemplates",
     "items": [
         {
             "creationTimestamp": "2016-07-18T09:53:22.323-07:00",
@@ -17,7 +17,7 @@
                         "initializeParams": {
                             "diskSizeGb": "10",
                             "diskType": "pd-standard",
-                            "sourceImage": "projects/supertom-graphite/global/images/my-new-image1"
+                            "sourceImage": "projects/project_name/global/images/my-new-image1"
                         },
                         "kind": "compute#attachedDisk",
                         "mode": "READ_WRITE",
@@ -38,7 +38,7 @@
                                 "type": "ONE_TO_ONE_NAT"
                             }
                         ],
-                        "network": "https://content.googleapis.com/compute/v1/projects/supertom-graphite/global/networks/default"
+                        "network": "https://content.googleapis.com/compute/v1/projects/project_name/global/networks/default"
                     }
                 ],
                 "scheduling": {
@@ -59,9 +59,9 @@
                     }
                 ]
             },
-            "selfLink": "https://content.googleapis.com/compute/v1/projects/supertom-graphite/global/instanceTemplates/my-instance-template1"
+            "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my-instance-template1"
         }
     ],
     "kind": "compute#instanceTemplateList",
-    "selfLink": "https://www.googleapis.com/compute/v1/projects/supertom-graphite/global/instanceTemplates"
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates"
 }

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/global_instanceTemplates_insert.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/global_instanceTemplates_insert.json b/libcloud/test/compute/fixtures/gce/global_instanceTemplates_insert.json
new file mode 100644
index 0000000..fe35dcb
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_instanceTemplates_insert.json
@@ -0,0 +1,12 @@
+{
+    "status": "PENDING", 
+    "kind": "compute#operation", 
+    "name": "my_instance_template1", 
+    "insertTime": "2016-09-02T09:31:52.285-07:00", 
+    "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my_instance_template1", 
+    "operationType": "compute.instanceTemplates.insert", 
+    "progress": 50, 
+    "id": 123456, 
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation-global_instanceTemplates_my_instance_template1_insert", 
+    "user": "1264195755357-compute@developer.gserviceaccount.com"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/global_sslcertificates.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/global_sslcertificates.json b/libcloud/test/compute/fixtures/gce/global_sslcertificates.json
new file mode 100644
index 0000000..170c1cb
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_sslcertificates.json
@@ -0,0 +1,16 @@
+{
+ "kind": "compute#sslCertificateList",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/sslCertificates",
+ "id": "projects/project_name/global/sslCertificates",
+ "items": [
+  {
+   "kind": "compute#sslCertificate",
+   "id": "2064539516762881220",
+   "creationTimestamp": "2016-08-30T10:28:11.926-07:00",
+   "name": "example",
+   "description": "my example ssl cert.",
+   "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/sslCertificates/example-cert",
+   "certificate": "-----BEGIN CERTIFICATE-----\nfoobar==\n-----END CERTIFICATE-----\n"
+  }
+ ]
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/global_sslcertificates_example.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/global_sslcertificates_example.json b/libcloud/test/compute/fixtures/gce/global_sslcertificates_example.json
new file mode 100644
index 0000000..c6a2985
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_sslcertificates_example.json
@@ -0,0 +1,10 @@
+{
+ "kind": "compute#sslCertificate",
+ "id": "2064539516762881220",
+ "creationTimestamp": "2016-08-30T10:28:11.926-07:00",
+ "name": "example",
+ "description": "my example ssl cert.",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/sslCertificates/example",
+ "certificate": "-----BEGIN CERTIFICATE-----\nfoobar==\n-----END CERTIFICATE-----\n"
+
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/global_sslcertificates_post.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/global_sslcertificates_post.json b/libcloud/test/compute/fixtures/gce/global_sslcertificates_post.json
new file mode 100644
index 0000000..23a9542
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/global_sslcertificates_post.json
@@ -0,0 +1,13 @@
+{
+ "kind": "compute#operation",
+ "id": "5564862567931215044",
+ "name": "operation-1472578091714-53b4d4e0f85d1-cf587a68-9d7a9200",
+ "operationType": "insert",
+ "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/sslCertificates/example",
+ "targetId": "2064539516762881220",
+ "status": "PENDING",
+ "user": "1294195755358-compute@developer.gserviceaccount.com",
+ "progress": 0,
+ "insertTime": "2016-08-30T10:28:11.948-07:00",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation_global_sslcertificates_post"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/operations_operation_global_instanceTemplates_insert.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_global_instanceTemplates_insert.json b/libcloud/test/compute/fixtures/gce/operations_operation_global_instanceTemplates_insert.json
new file mode 100644
index 0000000..274cc4f
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_global_instanceTemplates_insert.json
@@ -0,0 +1,12 @@
+{
+    "status": "DONE", 
+    "kind": "compute#operation", 
+    "name": "my_instance_template1", 
+    "insertTime": "2016-09-02T09:31:52.285-07:00", 
+    "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my_instance_template1", 
+    "operationType": "compute.instanceTemplates.insert", 
+    "progress": 100, 
+    "id": 123456, 
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation-global_instanceTemplates_my_instance_template1_insert", 
+    "user": "1264195755357-compute@developer.gserviceaccount.com"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/operations_operation_global_sslcertificates_post.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_global_sslcertificates_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_global_sslcertificates_post.json
new file mode 100644
index 0000000..73e5fbd
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_global_sslcertificates_post.json
@@ -0,0 +1,13 @@
+{
+ "kind": "compute#operation",
+ "id": "5564862567931215044",
+ "name": "operation-1472578091714-53b4d4e0f85d1-cf587a68-9d7a9200",
+ "operationType": "insert",
+ "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/sslCertificates/example",
+ "targetId": "2064539516762881220",
+ "status": "DONE",
+ "user": "1294195755358-compute@developer.gserviceaccount.com",
+ "progress": 100,
+ "insertTime": "2016-08-30T10:28:11.948-07:00",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation_global_sslcertificates_post"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_insert.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_insert.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_insert.json
new file mode 100644
index 0000000..2b6c12f
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_insert.json
@@ -0,0 +1,13 @@
+{
+    "status": "DONE", 
+    "kind": "compute#operation", 
+    "name": "myname", 
+    "zone": "us-central1-a", 
+    "insertTime": "2016-09-02T09:31:52.285-07:00", 
+    "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones_us_central1_a/instanceGroups/myname", 
+    "operationType": "compute.instanceGroups.insert", 
+    "progress": 100, 
+    "id": 123456, 
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_insert", 
+    "user": "1264195755357-compute@developer.gserviceaccount.com"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_addInstances.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_addInstances.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_addInstances.json
new file mode 100644
index 0000000..9e21253
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_addInstances.json
@@ -0,0 +1,13 @@
+{
+    "status": "DONE", 
+    "kind": "compute#operation", 
+    "name": "myname", 
+    "zone": "us-central1-a", 
+    "insertTime": "2016-09-02T09:31:52.285-07:00", 
+    "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", 
+    "operationType": "compute.instanceGroups.addInstances", 
+    "progress": 100, 
+    "id": 123456, 
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_addInstances", 
+    "user": "1264195755357-compute@developer.gserviceaccount.com"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_delete.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_delete.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_delete.json
new file mode 100644
index 0000000..e13d017
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_delete.json
@@ -0,0 +1,13 @@
+{
+    "status": "DONE", 
+    "kind": "compute#operation", 
+    "name": "myname", 
+    "zone": "us-central1-a", 
+    "insertTime": "2016-09-02T09:31:52.285-07:00", 
+    "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", 
+    "operationType": "compute.instanceGroups.delete", 
+    "progress": 100, 
+    "id": 123456, 
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_delete", 
+    "user": "1264195755357-compute@developer.gserviceaccount.com"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_removeInstances.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_removeInstances.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_removeInstances.json
new file mode 100644
index 0000000..a994784
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_removeInstances.json
@@ -0,0 +1,13 @@
+{
+    "status": "DONE", 
+    "kind": "compute#operation", 
+    "name": "myname", 
+    "zone": "us-central1-a", 
+    "insertTime": "2016-09-02T09:31:52.285-07:00", 
+    "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", 
+    "operationType": "compute.instanceGroups.removeInstances", 
+    "progress": 100, 
+    "id": 123456, 
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_removeInstances", 
+    "user": "1264195755357-compute@developer.gserviceaccount.com"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_setNamedPorts.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_setNamedPorts.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_setNamedPorts.json
new file mode 100644
index 0000000..e7b869e
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us_central1_a_instanceGroups_myname_setNamedPorts.json
@@ -0,0 +1,13 @@
+{
+    "status": "DONE", 
+    "kind": "compute#operation", 
+    "name": "myname", 
+    "zone": "us-central1-a", 
+    "insertTime": "2016-09-02T09:31:52.285-07:00", 
+    "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", 
+    "operationType": "compute.instanceGroups.setNamedPorts", 
+    "progress": 100, 
+    "id": 123456, 
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_setNamedPorts", 
+    "user": "1264195755357-compute@developer.gserviceaccount.com"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/regions_us-east1_subnetworks_cf_972cf02e6ad49113.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/regions_us-east1_subnetworks_cf_972cf02e6ad49113.json b/libcloud/test/compute/fixtures/gce/regions_us-east1_subnetworks_cf_972cf02e6ad49113.json
new file mode 100644
index 0000000..79ac9e4
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/regions_us-east1_subnetworks_cf_972cf02e6ad49113.json
@@ -0,0 +1,11 @@
+{
+ "kind": "compute#subnetwork",
+ "id": "4297043163355844284",
+ "creationTimestamp": "2016-10-01T05:34:27.209-07:00",
+ "gatewayAddress": "10.128.0.1",
+ "name": "cf-972cf02e6ad49113",
+ "network": "https://www.googleapis.com/compute/v1/projects/project_name/global/networks/cf",
+ "ipCidrRange": "10.128.0.0/20",
+ "region": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-east1",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49113"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers.json
index 8f6c569..c451bd6 100644
--- a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers.json
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers.json
@@ -11,7 +11,7 @@
    "zone": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a",
    "name": "myinstancegroup",
    "instanceTemplate": "https://content.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my-instance-template1",
-   "instanceGroup": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup",
+   "instanceGroup": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup",
    "baseInstanceName": "myinstancegroup",
    "fingerprint": "5bKcxzAnGOg=",
    "currentActions": {
@@ -24,7 +24,7 @@
     "refreshing": 0
    },
    "targetSize": 2,
-   "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroupManagers/myinstancegroup"
+   "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroupManagers/myinstancegroup"
   }
  ]
 }

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers_myinstancegroup.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers_myinstancegroup.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers_myinstancegroup.json
index be0f0e7..823b5b3 100644
--- a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers_myinstancegroup.json
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroupManagers_myinstancegroup.json
@@ -7,7 +7,7 @@
     "name": "myinstancegroup",
     "description": "my description for myinstancegroup",
     "instanceTemplate": "https://content.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my-instance-template1",
-    "instanceGroup": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup",
+    "instanceGroup": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup",
     "baseInstanceName": "base-foo",
     "fingerprint": "Q21hYveq9do=",
     "currentActions": {
@@ -20,5 +20,5 @@
 	"refreshing": 0
     },
     "targetSize": 4,
-    "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroupManagers/myinstancegroup"
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroupManagers/myinstancegroup"
 }

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroup_myinstancegroup.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroup_myinstancegroup.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroup_myinstancegroup.json
index ee36755..68f06e6 100644
--- a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroup_myinstancegroup.json
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroup_myinstancegroup.json
@@ -4,11 +4,11 @@
     "id": "1968709502073089769",
     "creationTimestamp": "2016-08-11T16:53:42.413-07:00",
     "zone": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a",
-    "name": "managed-instance-group-1",
+    "name": "myinstancegroup",
     "description": "This instance group is controlled by Instance Group Manager 'myinstancegroup'. To modify instances in this group, use the Instance Group Manager API: https://cloud.google.com/compute/docs/reference/latest/instanceGroupManagers",
     "network": "https://content.googleapis.com/compute/v1/projects/project_name/global/networks/default",
     "fingerprint": "42WmSpB8rSM=",
-    "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup",
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup",
     "size": 4,
     "subnetwork": "https://content.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49112"
 }

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroup_myinstancegroup2.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroup_myinstancegroup2.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroup_myinstancegroup2.json
new file mode 100644
index 0000000..f853bb7
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_instanceGroup_myinstancegroup2.json
@@ -0,0 +1,14 @@
+{
+
+    "kind": "compute#instanceGroup",
+    "id": "1968709502073089768",
+    "creationTimestamp": "2016-08-26T16:53:42.413-07:00",
+    "zone": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a",
+    "name": "myinstancegroup2",
+    "description": "myinstancegroup2",
+    "network": "https://content.googleapis.com/compute/v1/projects/project_name/global/networks/default",
+    "fingerprint": "42WmSpB8rSN=",
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myinstancegroup2",
+    "size": 4,
+    "subnetwork": "https://content.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49112"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroupManagers_myinstancegroup.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroupManagers_myinstancegroup.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroupManagers_myinstancegroup.json
index 2385aa3..863f664 100644
--- a/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroupManagers_myinstancegroup.json
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroupManagers_myinstancegroup.json
@@ -7,7 +7,7 @@
     "name": "myinstancegroup",
     "description": "my description for myinstancegroup",
     "instanceTemplate": "https://content.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my-instance-template1",
-    "instanceGroup": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroups/myinstancegroup",
+    "instanceGroup": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroups/myinstancegroup",
     "baseInstanceName": "base-foo",
     "fingerprint": "Q21hYveq9do=",
     "currentActions": {
@@ -20,5 +20,5 @@
 	"refreshing": 0
     },
     "targetSize": 4,
-    "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroupManagers/myinstancegroup"
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroupManagers/myinstancegroup"
 }

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroup_myinstancegroup.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroup_myinstancegroup.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroup_myinstancegroup.json
index c4330a8..94815c4 100644
--- a/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroup_myinstancegroup.json
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-b_instanceGroup_myinstancegroup.json
@@ -10,5 +10,5 @@
     "fingerprint": "42WmSpB8rSM=",
     "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-b/instanceGroups/myinstancegroup",
     "size": 4,
-    "subnetwork": "https://content.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49112"
+    "subnetwork": "https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/subnetworks/cf-972cf02e6ad49112"
 }

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroupManagers.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroupManagers.json b/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroupManagers.json
index 1bf7014..1ac5d9f 100644
--- a/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroupManagers.json
+++ b/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroupManagers.json
@@ -11,7 +11,7 @@
    "zone": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b",
    "name": "myinstancegroup",
    "instanceTemplate": "https://content.googleapis.com/compute/v1/projects/project_name/global/instanceTemplates/my-instance-template1",
-   "instanceGroup": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroups/myinstancegroup",
+   "instanceGroup": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroups/myinstancegroup",
    "baseInstanceName": "myinstancegroup",
    "fingerprint": "5bKcxzAnGOg=",
    "currentActions": {
@@ -24,7 +24,7 @@
     "refreshing": 0
    },
    "targetSize": 2,
-   "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroupManagers/myinstancegroup"
+   "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroupManagers/myinstancegroup"
   }
  ]
 }

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroup_myinstancegroup.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroup_myinstancegroup.json b/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroup_myinstancegroup.json
index 3726373..55e9ff5 100644
--- a/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroup_myinstancegroup.json
+++ b/libcloud/test/compute/fixtures/gce/zones_us-east1-b_instanceGroup_myinstancegroup.json
@@ -8,7 +8,7 @@
     "description": "This instance group is controlled by Instance Group Manager 'myinstancegroup'. To modify instances in this group, use the Instance Group Manager API: https://cloud.google.com/compute/docs/reference/latest/instanceGroupManagers",
     "network": "https://content.googleapis.com/compute/v1/projects/project_name/global/networks/default",
     "fingerprint": "42WmSpB8rSM=",
-    "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroups/myinstancegroup",
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-east1-b/instanceGroups/myinstancegroup",
     "size": 4,
-    "subnetwork": "https://content.googleapis.com/compute/v1/projects/project_name/regions/us-east1/subnetworks/cf-972cf02e6ad49112"
+    "subnetwork": "https://content.googleapis.com/compute/v1/projects/project_name/regions/us-east1/subnetworks/cf-972cf02e6ad49113"
 }

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups.json b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups.json
new file mode 100644
index 0000000..33a837b
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups.json
@@ -0,0 +1,29 @@
+{
+    "id": "projects/project_name/zones/us-central1-a/instanceGroups",
+    "items": [
+        {
+            "creationTimestamp": "2016-09-09T13:48:39.700-07:00",
+            "description": "",
+            "fingerprint": "42WmSpB8rSM=",
+            "id": "5837905299775594184",
+            "kind": "compute#instanceGroup",
+            "name": "myname",
+            "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname",
+            "size": 0,
+            "zone": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a"
+        },
+        {
+            "creationTimestamp": "2016-09-09T13:54:30.857-07:00",
+            "description": "",
+            "fingerprint": "42WmSpB8rSM=",
+            "id": "6825641674983513961",
+            "kind": "compute#instanceGroup",
+            "name": "myname2",
+            "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname2",
+            "size": 0,
+            "zone": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a"
+        }
+    ],
+    "kind": "compute#instanceGroupList",
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_insert.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_insert.json b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_insert.json
new file mode 100644
index 0000000..2b6c12f
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_insert.json
@@ -0,0 +1,13 @@
+{
+    "status": "DONE", 
+    "kind": "compute#operation", 
+    "name": "myname", 
+    "zone": "us-central1-a", 
+    "insertTime": "2016-09-02T09:31:52.285-07:00", 
+    "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones_us_central1_a/instanceGroups/myname", 
+    "operationType": "compute.instanceGroups.insert", 
+    "progress": 100, 
+    "id": 123456, 
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_insert", 
+    "user": "1264195755357-compute@developer.gserviceaccount.com"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname.json b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname.json
new file mode 100644
index 0000000..7114fac
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname.json
@@ -0,0 +1,12 @@
+{
+
+    "kind": "compute#instanceGroup",
+    "id": "5837905299775594184",
+    "creationTimestamp": "2016-09-09T13:48:39.700-07:00",
+    "name": "myname",
+    "description": "",
+    "fingerprint": "42WmSpB8rSM=",
+    "zone": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a",
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname",
+    "size": 0
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_addInstances.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_addInstances.json b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_addInstances.json
new file mode 100644
index 0000000..9e21253
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_addInstances.json
@@ -0,0 +1,13 @@
+{
+    "status": "DONE", 
+    "kind": "compute#operation", 
+    "name": "myname", 
+    "zone": "us-central1-a", 
+    "insertTime": "2016-09-02T09:31:52.285-07:00", 
+    "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", 
+    "operationType": "compute.instanceGroups.addInstances", 
+    "progress": 100, 
+    "id": 123456, 
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_addInstances", 
+    "user": "1264195755357-compute@developer.gserviceaccount.com"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_delete.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_delete.json b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_delete.json
new file mode 100644
index 0000000..e13d017
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_delete.json
@@ -0,0 +1,13 @@
+{
+    "status": "DONE", 
+    "kind": "compute#operation", 
+    "name": "myname", 
+    "zone": "us-central1-a", 
+    "insertTime": "2016-09-02T09:31:52.285-07:00", 
+    "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", 
+    "operationType": "compute.instanceGroups.delete", 
+    "progress": 100, 
+    "id": 123456, 
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_delete", 
+    "user": "1264195755357-compute@developer.gserviceaccount.com"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_listInstances.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_listInstances.json b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_listInstances.json
new file mode 100644
index 0000000..94c6cbf
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_listInstances.json
@@ -0,0 +1,15 @@
+{
+    "id": "projects/project_name/zones/us-central1-a/instanceGroups/myname/listInstances",
+    "items": [
+        {
+            "instance": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instances/node-name",
+            "status": "RUNNING"
+        },
+	{
+            "instance": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instances/lcnode-001",
+            "status": "RUNNING"
+        }
+    ],
+    "kind": "compute#instanceGroupsListInstances",
+    "selfLink": "https://content.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname/listInstances"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_removeInstances.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_removeInstances.json b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_removeInstances.json
new file mode 100644
index 0000000..a994784
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_removeInstances.json
@@ -0,0 +1,13 @@
+{
+    "status": "DONE", 
+    "kind": "compute#operation", 
+    "name": "myname", 
+    "zone": "us-central1-a", 
+    "insertTime": "2016-09-02T09:31:52.285-07:00", 
+    "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", 
+    "operationType": "compute.instanceGroups.removeInstances", 
+    "progress": 100, 
+    "id": 123456, 
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_removeInstances", 
+    "user": "1264195755357-compute@developer.gserviceaccount.com"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_setNamedPorts.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_setNamedPorts.json b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_setNamedPorts.json
new file mode 100644
index 0000000..e7b869e
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us_central1_a_instanceGroups_myname_setNamedPorts.json
@@ -0,0 +1,13 @@
+{
+    "status": "DONE", 
+    "kind": "compute#operation", 
+    "name": "myname", 
+    "zone": "us-central1-a", 
+    "insertTime": "2016-09-02T09:31:52.285-07:00", 
+    "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instanceGroups/myname", 
+    "operationType": "compute.instanceGroups.setNamedPorts", 
+    "progress": 100, 
+    "id": 123456, 
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us_central1_a_instanceGroups_myname_setNamedPorts", 
+    "user": "1264195755357-compute@developer.gserviceaccount.com"
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/test/compute/test_gce.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_gce.py b/libcloud/test/compute/test_gce.py
index 75a53f1..9ec97e0 100644
--- a/libcloud/test/compute/test_gce.py
+++ b/libcloud/test/compute/test_gce.py
@@ -22,7 +22,7 @@ import unittest
 
 from libcloud.utils.py3 import httplib
 from libcloud.compute.drivers.gce import (
-    GCENodeDriver, API_VERSION, timestamp_to_datetime, GCEAddress,
+    GCENodeDriver, API_VERSION, timestamp_to_datetime, GCEAddress, GCEBackend,
     GCEBackendService, GCEFirewall, GCEForwardingRule, GCEHealthCheck,
     GCENetwork, GCENodeImage, GCERoute, GCERegion, GCETargetHttpProxy,
     GCEUrlMap, GCEZone, GCESubnetwork)
@@ -108,6 +108,125 @@ class GCENodeDriverTest(GoogleTestCase, TestCaseMixin):
         image = self.driver._match_images(project, 'backports')
         self.assertEqual(image.name, 'backports-debian-7-wheezy-v20131127')
 
+    def test_build_disk_gce_struct(self):
+        device_name = 'disk_name'
+        disk_name = None
+        source = self.driver.ex_get_volume('lcdisk')
+        is_boot = True
+        # source as input
+        d = self.driver._build_disk_gce_struct(
+            device_name=device_name, source=source, disk_name=disk_name,
+            is_boot=is_boot)
+        self.assertEqual(source.extra['selfLink'], d['source'])
+        self.assertTrue(d['boot'])
+        self.assertTrue(d['autoDelete'])
+        self.assertEqual('READ_WRITE', d['mode'])
+        self.assertFalse('initializeParams' in d)
+
+        # image as input
+        device_name = 'disk_name'
+        disk_type = self.driver.ex_get_disktype('pd-ssd', 'us-central1-a')
+        image = self.driver.ex_get_image('debian-7')
+        source = None
+        is_boot = True
+        d = self.driver._build_disk_gce_struct(device_name=device_name,
+                                               disk_type=disk_type,
+                                               image=image, is_boot=is_boot)
+        self.assertEqual('READ_WRITE', d['mode'])
+        self.assertEqual('PERSISTENT', d['type'])
+        self.assertTrue('initializeParams' in d and
+                        isinstance(d['initializeParams'], dict))
+        self.assertTrue(
+            all(k in d['initializeParams']
+                for k in ['sourceImage', 'diskType', 'diskName']))
+        self.assertTrue(d['initializeParams']['sourceImage'].startswith(
+            'https://'))
+        self.assertTrue(d['autoDelete'])
+        self.assertTrue(d['boot'])
+
+    def test_build_network_gce_struct(self):
+        network = self.driver.ex_get_network('lcnetwork')
+        address = self.driver.ex_get_address('lcaddress')
+        subnetwork_name = 'cf-972cf02e6ad49112'
+        subnetwork = self.driver.ex_get_subnetwork(subnetwork_name)
+        d = self.driver._build_network_gce_struct(network, subnetwork, address)
+        self.assertTrue('network' in d)
+        self.assertTrue('subnetwork' in d)
+        self.assertTrue('kind' in d and
+                        d['kind'] == 'compute#instanceNetworkInterface')
+
+        network = self.driver.ex_get_network('default')
+        d = self.driver._build_network_gce_struct(network)
+        self.assertTrue('network' in d)
+        self.assertFalse('subnetwork' in d)
+        self.assertTrue('kind' in d and
+                        d['kind'] == 'compute#instanceNetworkInterface')
+
+    def test_build_scheduling_gce_struct(self):
+        self.assertFalse(
+            self.driver._build_scheduling_gce_struct(None, None, None))
+        # on_host_maintenance bad value should raise a Valueerror
+        self.assertRaises(ValueError,
+                          self.driver._build_service_account_gce_struct,
+                          'on_host_maintenance="foobar"')
+        # on_host_maintenance is 'MIGRATE' and prempt is True
+        self.assertRaises(ValueError,
+                          self.driver._build_service_account_gce_struct,
+                          'on_host_maintenance="MIGRATE"', 'preemptible=True')
+        # automatic_restart is True and prempt is  True
+        self.assertRaises(ValueError,
+                          self.driver._build_service_account_gce_struct,
+                          'automatic_restart="True"', 'preemptible=True')
+
+        actual = self.driver._build_scheduling_gce_struct('TERMINATE', True,
+                                                          False)
+        self.assertTrue('automaticRestart' in actual and
+                        actual['automaticRestart'] is True)
+        self.assertTrue('onHostMaintenance' in actual and
+                        actual['onHostMaintenance'] == 'TERMINATE')
+        self.assertTrue('preemptible' in actual)
+        self.assertFalse(actual['preemptible'])
+
+    def test_build_service_account_gce_struct(self):
+        self.assertRaises(ValueError,
+                          self.driver._build_service_account_gce_struct, None)
+        input = {'scopes': ['compute-ro']}
+        actual = self.driver._build_service_account_gce_struct(input)
+        self.assertTrue('email' in actual)
+        self.assertTrue('scopes' in actual)
+
+    def test_build_service_account_gce_list(self):
+        # ensure we have a list
+        self.assertRaises(ValueError,
+                          self.driver._build_service_accounts_gce_list, 'foo')
+        # no input
+        actual = self.driver._build_service_accounts_gce_list()
+        self.assertTrue(len(actual) == 1)
+        self.assertTrue('email' in actual[0])
+        self.assertTrue('scopes' in actual[0])
+
+    def test_get_selflink_or_name(self):
+        network = self.driver.ex_get_network('lcnetwork')
+
+        # object as input
+        actual = self.driver._get_selflink_or_name(network, False, 'network')
+        self.assertEqual('lcnetwork', actual)
+        actual = self.driver._get_selflink_or_name(network, True, 'network')
+        self.assertTrue(actual.startswith('https://'))
+
+        # name-only as input
+        actual = self.driver._get_selflink_or_name('lcnetwork', True,
+                                                   'network')
+        self.assertTrue(actual.startswith('https://'))
+
+        actual = self.driver._get_selflink_or_name('lcnetwork', False,
+                                                   'network')
+        self.assertTrue('lcnetwork', actual)
+
+        # if selflinks is true, we need objname
+        self.assertRaises(ValueError, self.driver._get_selflink_or_name,
+                          'lcnetwork', True)
+
     def test_ex_get_serial_output(self):
         self.assertRaises(ValueError, self.driver.ex_get_serial_output, 'foo')
         node = self.driver.ex_get_node('node-name', 'us-central1-a')
@@ -213,6 +332,86 @@ class GCENodeDriverTest(GoogleTestCase, TestCaseMixin):
         self.assertEqual(local_images[0].name, 'aws-ubuntu')
         self.assertEqual(debian_images[1].name, 'debian-7-wheezy-v20131120')
 
+    def test_ex_destroy_instancegroup(self):
+        name = 'myname'
+        zone = 'us-central1-a'
+        uig = self.driver.ex_get_instancegroup(name, zone)
+        self.assertTrue(self.driver.ex_destroy_instancegroup(uig))
+
+    def test_ex_get_instancegroup(self):
+        name = 'myname'
+        loc = 'us-central1-a'
+        actual = self.driver.ex_get_instancegroup(name, loc)
+        self.assertEqual(actual.name, name)
+        self.assertEqual(actual.zone.name, loc)
+
+    def test_ex_create_instancegroup(self):
+        name = 'myname'
+        loc = 'us-central1-a'
+        actual = self.driver.ex_create_instancegroup(name, loc)
+        self.assertEqual(actual.name, name)
+        self.assertEqual(actual.zone.name, loc)
+
+    def test_ex_list_instancegroups(self):
+        loc = 'us-central1-a'
+        actual = self.driver.ex_list_instancegroups(loc)
+        self.assertTrue(len(actual) == 2)
+        self.assertEqual(actual[0].name, 'myname')
+        self.assertEqual(actual[1].name, 'myname2')
+
+    def test_ex_instancegroup_list_instances(self):
+        name = 'myname'
+        loc = 'us-central1-a'
+        gceobj = self.driver.ex_get_instancegroup(name, loc)
+        actual = self.driver.ex_instancegroup_list_instances(gceobj)
+        self.assertTrue(len(actual) == 2)
+        for node in actual:
+            self.assertTrue(isinstance(node, Node))
+            self.assertEqual(loc, node.extra['zone'].name)
+
+    def test_ex_instancegroup_add_instances(self):
+        name = 'myname'
+        loc = 'us-central1-a'
+        gceobj = self.driver.ex_get_instancegroup(name, loc)
+        node_name = self.driver.ex_get_node('node-name', loc)
+        lcnode = self.driver.ex_get_node('lcnode-001', loc)
+        node_list = [node_name, lcnode]
+        self.assertTrue(
+            self.driver.ex_instancegroup_add_instances(gceobj, node_list))
+
+    def test_ex_instancegroup_remove_instances(self):
+        name = 'myname'
+        loc = 'us-central1-a'
+        gceobj = self.driver.ex_get_instancegroup(name, loc)
+        node_name = self.driver.ex_get_node('node-name', loc)
+        lcnode = self.driver.ex_get_node('lcnode-001', loc)
+        node_list = [node_name, lcnode]
+        self.assertTrue(
+            self.driver.ex_instancegroup_remove_instances(gceobj, node_list))
+
+    def test_ex_instancegroup_set_named_ports(self):
+        name = 'myname'
+        loc = 'us-central1-a'
+        gceobj = self.driver.ex_get_instancegroup(name, loc)
+        named_ports = [{'name': 'foo', 'port': 4444}]
+        # base case
+        self.assertTrue(
+            self.driver.ex_instancegroup_set_named_ports(gceobj, named_ports))
+        # specify nothing, default is empty list
+        self.assertTrue(self.driver.ex_instancegroup_set_named_ports(gceobj))
+        # specify empty list
+        self.assertTrue(
+            self.driver.ex_instancegroup_set_named_ports(gceobj, []))
+        # raise valueerror if string is passed in
+        self.assertRaises(ValueError,
+                          self.driver.ex_instancegroup_set_named_ports, gceobj,
+                          'foobar')
+        # raise valueerror if dictionary is passed in
+        self.assertRaises(ValueError,
+                          self.driver.ex_instancegroup_set_named_ports, gceobj,
+                          {'name': 'foo',
+                           'port': 4444})
+
     def test_ex_create_instancegroupmanager(self):
         name = 'myinstancegroup'
         zone = 'us-central1-a'
@@ -226,6 +425,14 @@ class GCENodeDriverTest(GoogleTestCase, TestCaseMixin):
         self.assertEqual(mig.size, size)
         self.assertEqual(mig.zone.name, zone)
 
+    def test_ex_create_instancetemplate(self):
+        name = 'my-instance-template1'
+        actual = self.driver.ex_create_instancetemplate(
+            name, size='n1-standard-1', image='debian-7', network='default')
+        self.assertEqual(actual.name, name)
+        self.assertEqual(actual.extra['properties']['machineType'],
+                         'n1-standard-1')
+
     def test_list_locations(self):
         locations = self.driver.list_locations()
         self.assertEqual(len(locations), 6)
@@ -236,6 +443,12 @@ class GCENodeDriverTest(GoogleTestCase, TestCaseMixin):
         self.assertEqual(len(routes), 3)
         self.assertTrue('lcdemoroute' in [route.name for route in routes])
 
+    def test_ex_list_sslcertificate(self):
+        ssl_name = 'example'
+        certs = self.driver.ex_list_sslcertificates()
+        self.assertEqual(certs[0].name, ssl_name)
+        self.assertTrue(len(certs) == 1)
+
     def test_ex_list_subnetworks(self):
         subnetworks = self.driver.ex_list_subnetworks()
         self.assertEqual(len(subnetworks), 1)
@@ -244,6 +457,15 @@ class GCENodeDriverTest(GoogleTestCase, TestCaseMixin):
         subnetworks = self.driver.ex_list_subnetworks('all')
         self.assertEqual(len(subnetworks), 4)
 
+    def test_ex_create_sslcertificate(self):
+        ssl_name = 'example'
+        private_key = '-----BEGIN RSA PRIVATE KEY-----\nfoobar==\n-----END RSA PRIVATE KEY-----\n'
+        certificate = '-----BEGIN CERTIFICATE-----\nfoobar==\n-----END CERTIFICATE-----\n'
+        ssl = self.driver.ex_create_sslcertificate(
+            ssl_name, certificate=certificate, private_key=private_key)
+        self.assertEqual(ssl_name, ssl.name)
+        self.assertEqual(certificate, ssl.certificate)
+
     def test_ex_create_subnetwork(self):
         name = 'cf-972cf02e6ad49112'
         cidr = '10.128.0.0/20'
@@ -278,6 +500,13 @@ class GCENodeDriverTest(GoogleTestCase, TestCaseMixin):
         # delete with region object
         self.assertTrue(self.driver.ex_destroy_subnetwork(name, region))
 
+    def test_ex_get_sslcertificate(self):
+        ssl_name = 'example'
+        ssl = self.driver.ex_get_sslcertificate(ssl_name)
+        self.assertEqual(ssl.name, ssl_name)
+        self.assertTrue(hasattr(ssl, 'certificate'))
+        self.assertTrue(len(ssl.certificate))
+
     def test_ex_get_subnetwork(self):
         name = 'cf-972cf02e6ad49112'
         region_name = 'us-central1'
@@ -403,7 +632,7 @@ class GCENodeDriverTest(GoogleTestCase, TestCaseMixin):
         instances = mig.list_managed_instances()
         self.assertTrue(all([x['currentAction'] == 'NONE' for x in instances]))
         self.assertTrue('base-foo-2vld' in [x['name'] for x in instances])
-        self.assertEquals(len(instances), 4)
+        self.assertEqual(len(instances), 4)
 
     def test_ex_list_instancetemplates(self):
         instancetemplates = self.driver.ex_list_instancetemplates()
@@ -453,12 +682,40 @@ class GCENodeDriverTest(GoogleTestCase, TestCaseMixin):
         self.assertTrue(isinstance(address, GCEAddress))
         self.assertEqual(address.name, address_name)
 
+    def test_ex_create_backend(self):
+        # Note: this is an internal object, no API call is made
+        # and no fixture is needed specifically for GCEBackend, however
+        # it does rely on an InstanceGroup object.
+        ig = self.driver.ex_get_instancegroup('myinstancegroup',
+                                              'us-central1-a')
+
+        backend = self.driver.ex_create_backend(ig)
+
+        self.assertTrue(isinstance(backend, GCEBackend))
+        self.assertEqual(backend.name,
+                         '%s/instanceGroups/%s' % (ig.zone.name, ig.name))
+        self.assertEqual(backend.instance_group.name, ig.name)
+        self.assertEqual(backend.balancing_mode, 'UTILIZATION')
+
     def test_ex_create_backendservice(self):
         backendservice_name = 'web-service'
+
+        ig1 = self.driver.ex_get_instancegroup('myinstancegroup',
+                                               'us-central1-a')
+        backend1 = self.driver.ex_create_backend(ig1)
+        ig2 = self.driver.ex_get_instancegroup('myinstancegroup2',
+                                               'us-central1-a')
+        backend2 = self.driver.ex_create_backend(ig2)
+
         backendservice = self.driver.ex_create_backendservice(
-            name=backendservice_name, healthchecks=['lchealthcheck'])
+            name=backendservice_name, healthchecks=['lchealthcheck'],
+            backends=[backend1, backend2])
         self.assertTrue(isinstance(backendservice, GCEBackendService))
         self.assertEqual(backendservice.name, backendservice_name)
+        self.assertEqual(len(backendservice.backends), 2)
+        ig_links = [ig1.extra['selfLink'], ig2.extra['selfLink']]
+        for be in backendservice.backends:
+            self.assertTrue(be['group'] in ig_links)
 
     def test_ex_create_healthcheck(self):
         healthcheck_name = 'lchealthcheck'
@@ -486,8 +743,8 @@ class GCENodeDriverTest(GoogleTestCase, TestCaseMixin):
         family = 'coreos'
         guest_os_features = ['VIRTIO_SCSI_MULTIQUEUE', 'WINDOWS']
         expected_features = [
-            {'type': 'VIRTIO_SCSI_MULTIQUEUE'},
-            {'type': 'WINDOWS'}]
+            {'type': 'VIRTIO_SCSI_MULTIQUEUE'}, {'type': 'WINDOWS'}
+        ]
         mock_request = mock.Mock()
         mock_request.side_effect = self.driver.connection.async_request
         self.driver.connection.async_request = mock_request
@@ -516,8 +773,8 @@ class GCENodeDriverTest(GoogleTestCase, TestCaseMixin):
         family = 'coreos'
         guest_os_features = ['VIRTIO_SCSI_MULTIQUEUE', 'WINDOWS']
         expected_features = [
-            {'type': 'VIRTIO_SCSI_MULTIQUEUE'},
-            {'type': 'WINDOWS'}]
+            {'type': 'VIRTIO_SCSI_MULTIQUEUE'}, {'type': 'WINDOWS'}
+        ]
         image = self.driver.ex_copy_image(name, url, description=description,
                                           family=family,
                                           guest_os_features=guest_os_features)
@@ -676,6 +933,9 @@ class GCENodeDriverTest(GoogleTestCase, TestCaseMixin):
         self.assertEqual(node_data['serviceAccounts'][0]['email'], 'default')
         self.assertIsInstance(node_data['serviceAccounts'][0]['scopes'], list)
         self.assertEqual(len(node_data['serviceAccounts'][0]['scopes']), 1)
+        self.assertEqual(len(node_data['networkInterfaces']), 1)
+        self.assertTrue(node_data['networkInterfaces'][0][
+            'network'].startswith('https://'))
 
     def test_create_node_network_opts(self):
         node_name = 'node-name'
@@ -2206,6 +2466,12 @@ class GCEMockHttp(MockHttpTestCase):
             body = self.fixtures.load('global_urlMaps_web_map.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _regions_us_east1_subnetworks_cf_972cf02e6ad49113(self, method, url,
+                                                          body, headers):
+        body = self.fixtures.load(
+            'regions_us-east1_subnetworks_cf_972cf02e6ad49113.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
     def _regions_us_central1_subnetworks_cf_972cf02e6ad49112(self, method, url,
                                                              body, headers):
         body = self.fixtures.load(
@@ -2939,6 +3205,12 @@ class GCEMockHttp(MockHttpTestCase):
             'zones_us-central1-a_instanceGroup_myinstancegroup.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _zones_us_central1_a_instanceGroups_myinstancegroup2(self, method, url,
+                                                             body, headers):
+        body = self.fixtures.load(
+            'zones_us-central1-a_instanceGroup_myinstancegroup2.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
     def _zones_us_central1_b_instanceGroups_myinstancegroup(self, method, url,
                                                             body, headers):
         body = self.fixtures.load(
@@ -2975,7 +3247,6 @@ class GCEMockHttp(MockHttpTestCase):
             'zones_us-east1-b_instanceGroupManagers.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
-    # TODO(supertom): this one
     def _zones_us_central1_a_instanceGroupManagers(self, method, url, body,
                                                    headers):
         # do an insert.  Returns an operations link, which then
@@ -2995,7 +3266,19 @@ class GCEMockHttp(MockHttpTestCase):
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
     def _global_instanceTemplates(self, method, url, body, headers):
-        body = self.fixtures.load('global_instanceTemplates.json')
+        if method == 'POST':
+            # insert
+            body = self.fixtures.load('global_instanceTemplates_insert.json')
+        else:
+            # get or list call
+            body = self.fixtures.load('global_instanceTemplates.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_operations_operation_global_instanceTemplates_my_instance_template1_insert(
+            self, method, url, body, headers):
+        """ Redirects from _global_instanceTemplates """
+        body = self.fixtures.load(
+            'operations_operation_global_instanceTemplates_insert.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
     def _global_instanceTemplates_my_instance_template1(self, method, url,
@@ -3008,6 +3291,109 @@ class GCEMockHttp(MockHttpTestCase):
         body = self.fixtures.load('aggregated_autoscalers.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _global_sslCertificates(self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load('global_sslcertificates_post.json')
+        else:
+            body = self.fixtures.load('global_sslcertificates.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_sslCertificates_example(self, method, url, body, headers):
+        body = self.fixtures.load('global_sslcertificates_example.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_operations_operation_global_sslcertificates_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_global_sslcertificates_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_instanceGroups_myname(self, method, url, body,
+                                                   headers):
+        if method == 'DELETE':
+            # delete
+            body = self.fixtures.load(
+                'zones_us_central1_a_instanceGroups_myname_delete.json')
+        else:
+            # get or list call
+            body = self.fixtures.load(
+                'zones_us_central1_a_instanceGroups_myname.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_instanceGroups_myname_delete(
+            self, method, url, body, headers):
+        """ Redirects from _zones_us_central1_a_instanceGroups_myname """
+        body = self.fixtures.load(
+            'operations_operation_zones_us_central1_a_instanceGroups_myname_delete.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_instanceGroups(self, method, url, body, headers):
+        if method == 'POST':
+            # insert
+            body = self.fixtures.load(
+                'zones_us_central1_a_instanceGroups_insert.json')
+        else:
+            # get or list call
+            body = self.fixtures.load(
+                'zones_us_central1_a_instanceGroups.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_instanceGroups_myname_insert(
+            self, method, url, body, headers):
+        """ Redirects from _zones_us_central1_a_instanceGroups """
+        body = self.fixtures.load(
+            'operations_operation_zones_us_central1_a_instanceGroups_insert.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_instanceGroups_myname_listInstances(
+            self, method, url, body, headers):
+        # POST
+        body = self.fixtures.load(
+            'zones_us_central1_a_instanceGroups_myname_listInstances.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_instanceGroups_myname_addInstances(
+            self, method, url, body, headers):
+        # POST
+        body = self.fixtures.load(
+            'zones_us_central1_a_instanceGroups_myname_addInstances.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_instanceGroups_myname_addInstances(
+            self, method, url, body, headers):
+        """ Redirects from _zones_us_central1_a_instanceGroups_myname_addInstances """
+        body = self.fixtures.load(
+            'operations_operation_zones_us_central1_a_instanceGroups_myname_addInstances.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_instanceGroups_myname_removeInstances(
+            self, method, url, body, headers):
+        # POST
+        body = self.fixtures.load(
+            'zones_us_central1_a_instanceGroups_myname_removeInstances.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_instanceGroups_myname_removeInstances(
+            self, method, url, body, headers):
+        """ Redirects from _zones_us_central1_a_instanceGroups_myname_removeInstances """
+        body = self.fixtures.load(
+            'operations_operation_zones_us_central1_a_instanceGroups_myname_removeInstances.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_instanceGroups_myname_setNamedPorts(
+            self, method, url, body, headers):
+        # POST
+        body = self.fixtures.load(
+            'zones_us_central1_a_instanceGroups_myname_setNamedPorts.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_instanceGroups_myname_setNamedPorts(
+            self, method, url, body, headers):
+        """ Redirects from _zones_us_central1_a_instanceGroups_myname_setNamedPorts """
+        body = self.fixtures.load(
+            'operations_operation_zones_us_central1_a_instanceGroups_myname_setNamedPorts.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
 
 if __name__ == '__main__':
     sys.exit(unittest.main())


[2/4] libcloud git commit: [GCE] Added support for HTTP(S) proxies with BackendServices

Posted by er...@apache.org.
http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/libcloud/compute/drivers/gce.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py
index 6b00491..7eaffd3 100644
--- a/libcloud/compute/drivers/gce.py
+++ b/libcloud/compute/drivers/gce.py
@@ -318,6 +318,77 @@ class GCEAddress(UuidMixin):
             (hasattr(self.region, "name") and self.region.name or self.region))
 
 
+class GCEBackend(UuidMixin):
+    """A GCE Backend.  Only used for creating Backend Services."""
+
+    def __init__(self, instance_group, balancing_mode='UTILIZATION',
+                 max_utilization=None, max_rate=None,
+                 max_rate_per_instance=None, capacity_scaler=1,
+                 description=None):
+
+        if isinstance(instance_group, GCEInstanceGroup):
+            self.instance_group = instance_group
+        elif isinstance(instance_group, GCEInstanceGroupManager):
+            self.instance_group = instance_group.instance_group
+        else:
+            raise ValueError('instance_group must be of type GCEInstanceGroup'
+                             'or of type GCEInstanceGroupManager')
+
+        self.instance_group = instance_group
+        self.balancing_mode = balancing_mode
+        self.max_utilization = max_utilization
+        self.max_rate = max_rate
+        self.max_rate_per_instance = max_rate_per_instance
+        self.capacity_scaler = capacity_scaler
+
+        # 'id' and 'name' aren't actually used or provided by the GCE API.
+        # We create them for convenience.
+        self.id = self._gen_id()
+        self.name = self.id
+
+        self.description = description or self.name
+        UuidMixin.__init__(self)
+
+    def _gen_id(self):
+        """
+        Use the Instance Group information to fill in name and id fields.
+
+        :return: id in the format of:
+                 ZONE/instanceGroups/INSTANCEGROUPNAME
+                 Ex: us-east1-c/instanceGroups/my-instance-group
+        :rtype:  ``str``
+        """
+        zone_name = self.instance_group.zone.name
+        return "%s/instanceGroups/%s" % (zone_name, self.instance_group.name)
+
+    def to_backend_dict(self):
+        """
+        Returns dict formatted for inclusion in Backend Service Request.
+
+        :return: dict formatted as a list entry for Backend Service 'backend'.
+        :rtype: ``dict``
+        """
+        d = {}
+        d['group'] = self.instance_group.extra['selfLink']
+
+        if self.balancing_mode:
+            d['balancingMode'] = self.balancing_mode
+        if self.max_utilization:
+            d['maxUtilization'] = self.max_utilization
+        if self.max_rate:
+            d['maxRate'] = self.max_rate
+        if self.max_rate_per_instance:
+            d['maxRatePerInstance'] = self.max_rate_per_instance
+        if self.capacity_scaler:
+            d['capacityScaler'] = self.capacity_scaler
+
+        return d
+
+    def __repr__(self):
+        return '<GCEBackend instancegroup="%s" balancing_mode="%s">' % (
+            self.id, self.balancing_mode)
+
+
 class GCEBackendService(UuidMixin):
     """A GCE Backend Service."""
 
@@ -525,6 +596,67 @@ class GCENodeImage(NodeImage):
                                               deprecated, obsolete, deleted)
 
 
+class GCESslCertificate(UuidMixin):
+    """ GCESslCertificate represents the SslCertificate resource. """
+
+    def __init__(self, id, name, certificate, driver, extra, private_key=None,
+                 description=None):
+        """
+        :param  name:  Name of the resource. Provided by the client when the
+                       resource is created. The name must be 1-63 characters
+                       long, and comply with RFC1035. Specifically, the name
+                       must be 1-63 characters long and match the regular
+                       expression [a-z]([-a-z0-9]*[a-z0-9])? which means the
+                       first character must be a lowercase letter, and all
+                       following characters must be a dash, lowercase letter,
+                       or digit, except the last character, which cannot be a
+                       dash.
+        :type   name: ``str``
+
+        :param  certificate:  A local certificate file. The certificate must
+                              be in PEM format. The certificate chain must be
+                              no greater than 5 certs long. The chain must
+                              include at least one intermediate cert.
+        :type   certificate: ``str``
+
+        :param  private_key:  A write-only private key in PEM format. Only
+                              insert RPCs will include this field.
+        :type   private_key: ``str``
+
+        :keyword  description:  An optional description of this resource.
+                              Provide this property when you create the
+                              resource.
+        :type   description: ``str``
+
+        :keyword  driver:  An initialized :class: `GCENodeDriver`
+        :type   driver: :class:`:class: `GCENodeDriver``
+
+        :keyword  extra:  A dictionary of extra information.
+        :type   extra: ``:class: ``dict````
+
+        """
+
+        self.name = name
+        self.certificate = certificate
+        self.private_key = private_key
+        self.description = description
+        self.driver = driver
+        self.extra = extra
+        UuidMixin.__init__(self)
+
+    def __repr__(self):
+        return '<GCESslCertificate name="%s">' % (self.name)
+
+    def destroy(self):
+        """
+        Destroy this SslCertificate.
+
+        :return:  Return True if successful.
+        :rtype: ``bool``
+        """
+        return self.driver.ex_destroy_sslcertificate(sslcertificate=self)
+
+
 class GCESubnetwork(UuidMixin):
     """A GCE Subnetwork object class."""
 
@@ -710,8 +842,8 @@ class GCESnapshot(VolumeSnapshot):
     def __init__(self, id, name, size, status, driver, extra=None,
                  created=None):
         self.status = status
-        super(GCESnapshot, self).__init__(id, driver, size, extra,
-                                          created, name=name)
+        super(GCESnapshot, self).__init__(id, driver, size, extra, created,
+                                          name=name)
 
 
 class GCETargetHttpProxy(UuidMixin):
@@ -736,6 +868,109 @@ class GCETargetHttpProxy(UuidMixin):
         return self.driver.ex_destroy_targethttpproxy(targethttpproxy=self)
 
 
+class GCETargetHttpsProxy(UuidMixin):
+    """ GCETargetHttpsProxy represents the TargetHttpsProxy resource. """
+
+    def __init__(self, id, name, description=None, sslcertificates=None,
+                 urlmap=None, driver=None, extra=None):
+        """
+        :param  name:  Name of the resource. Provided by the client when the
+                       resource is created. The name must be 1-63 characters
+                       long, and comply with RFC1035. Specifically, the name
+                       must be 1-63 characters long and match the regular
+                       expression [a-z]([-a-z0-9]*[a-z0-9])? which means the
+                       first character must be a lowercase letter, and all
+                       following characters must be a dash, lowercase letter,
+                       or digit, except the last character, which cannot be a
+                       dash.
+        :type   name: ``str``
+
+        :param  description:  An optional description of this resource.
+                              Provide this property when you create the
+                              resource.
+        :type   description: ``str``
+
+        :param  sslcertificates:  URLs to SslCertificate resources that are
+                                   used to authenticate connections between
+                                   users and the load balancer. Currently,
+                                   exactly one SSL certificate must be
+                                   specified.
+        :type   sslcertificates: ``list`` of :class:`GCESslcertificates`
+
+        :param  urlmap:  A fully-qualified or valid partial URL to the
+                          UrlMap resource that defines the mapping from URL
+                          to the BackendService. For example, the following
+                          are all valid URLs for specifying a URL map:   - ht
+                          tps://www.googleapis.compute/v1/projects/project/gl
+                          obal/urlMaps/url-map  -
+                          projects/project/global/urlMaps/url-map  -
+                          global/urlMaps/url-map
+        :type   urlmap: :class:`GCEUrlMap`
+
+        :keyword  driver:  An initialized :class: `GCENodeDriver`
+        :type   driver: :class:`:class: `GCENodeDriver``
+
+        :keyword  extra:  A dictionary of extra information.
+        :type   extra: ``:class: ``dict````
+
+        """
+
+        self.name = name
+        self.description = description
+        self.sslcertificates = sslcertificates
+        self.urlmap = urlmap
+        self.driver = driver
+        self.extra = extra
+        UuidMixin.__init__(self)
+
+    def __repr__(self):
+        return '<GCETargetHttpsProxy name="%s">' % (self.name)
+
+    def set_sslcertificates(self, sslcertificates):
+        """
+        Set the SSL Certificates for this TargetHTTPSProxy
+
+        :param  sslcertificates: SSL Certificates to set.
+        :type   sslcertificates: ``list`` of :class:`GCESslCertificate`
+
+        :return:  True if successful
+        :rtype:   ``bool``
+        """
+        return self.driver.ex_targethttpsproxy_set_sslcertificates(
+            targethttpsproxy=self, sslcertificates=sslcertificates)
+
+    def set_urlmap(self, urlmap):
+        """
+        Changes the URL map for TargetHttpsProxy.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  targethttpsproxy:  Name of the TargetHttpsProxy resource
+                                   whose URL map is to be set.
+        :type   targethttpsproxy: ``str``
+
+        :param  urlmap:  UrlMap to set.
+        :type   urlmap: :class:`GCEUrlMap`
+
+        :return:  True
+        :rtype: ``bool``
+        """
+
+        return self.driver.ex_targethttpsproxy_set_urlmap(
+            targethttpsproxy=self, urlmap=urlmap)
+
+    def destroy(self):
+        """
+        Destroy this TargetHttpsProxy.
+
+        :return:  Return True if successful.
+        :rtype: ``bool``
+        """
+        return self.driver.ex_destroy_targethttpsproxy(targethttpsproxy=self)
+
+
 class GCETargetInstance(UuidMixin):
     def __init__(self, id, name, zone, node, driver, extra=None):
         self.id = str(id)
@@ -803,12 +1038,21 @@ class GCEInstanceTemplate(UuidMixin):
             self.id, self.name, self.extra['properties'].get('machineType',
                                                              'UNKNOWN'))
 
+    def destroy(self):
+        """
+        Destroy this InstanceTemplate.
+
+        :return:  Return True if successful.
+        :rtype: ``bool``
+        """
+        return self.driver.ex_destroy_instancetemplate(instancetemplate=self)
+
 
 class GCEInstanceGroup(UuidMixin):
     """ GCEInstanceGroup represents the InstanceGroup resource. """
 
-    def __init__(self, id, name, zone, driver, extra=None, description=None,
-                 network=None, subnetwork=None, named_ports=None):
+    def __init__(self, id, name, zone, driver, extra=None, network=None,
+                 subnetwork=None, named_ports=None):
         """
         :param  name:  Required. The name of the instance group. The name
                        must be 1-63 characters long, and comply with RFC1035.
@@ -818,11 +1062,6 @@ class GCEInstanceGroup(UuidMixin):
                        located.
         :type   zone: :class:`GCEZone`
 
-        :param  description:  An optional description of this resource.
-                              Provide this property when you create the
-                              resource.
-        :type   description: ``str``
-
         :param  network:  The URL of the network to which all instances in
                           the instance group belong.
         :type   network: :class:`GCENetwork`
@@ -845,7 +1084,6 @@ class GCEInstanceGroup(UuidMixin):
 
         self.name = name
         self.zone = zone
-        self.description = description
         self.network = network
         self.subnetwork = subnetwork
         self.named_ports = named_ports
@@ -855,7 +1093,7 @@ class GCEInstanceGroup(UuidMixin):
 
     def __repr__(self):
         return '<GCEInstanceGroup name="%s" zone="%s">' % (self.name,
-                                                           self.zone)
+                                                           self.zone.name)
 
     def destroy(self):
         """
@@ -866,6 +1104,91 @@ class GCEInstanceGroup(UuidMixin):
         """
         return self.driver.ex_destroy_instancegroup(instancegroup=self)
 
+    def add_instances(self, node_list):
+        """
+        Adds a list of instances to the specified instance group. All of the
+        instances in the instance group must be in the same
+        network/subnetwork. Read  Adding instances for more information.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  instancegroup:  The Instance Group where you are
+                                adding instances.
+        :type   instancegroup: :class:``GCEInstanceGroup``
+
+        :param  node_list: List of nodes to add.
+        :type   node_list: ``list`` of :class:`Node` or ``list`` of
+                           :class:`GCENode`
+
+        :return:  Return True if successful.
+        :rtype: ``bool``
+        """
+        return self.driver.ex_instancegroup_add_instances(instancegroup=self,
+                                                          node_list=node_list)
+
+    def list_instances(self):
+        """
+        Lists the instances in the specified instance group.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+        * https://www.googleapis.com/auth/compute.readonly
+
+        :return:  List of :class:`GCENode` objects.
+        :rtype: ``list`` of :class:`GCENode` objects.
+        """
+        return self.driver.ex_instancegroup_list_instances(instancegroup=self)
+
+    def remove_instances(self, node_list):
+        """
+        Removes one or more instances from the specified instance group,
+        but does not delete those instances.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  instancegroup:  The Instance Group where you are
+                                removng instances.
+        :type   instancegroup: :class:``GCEInstanceGroup``
+
+        :param  node_list: List of nodes to add.
+        :type   node_list: ``list`` of :class:`Node` or ``list`` of
+                           :class:`GCENode`
+
+        :return:  Return True if successful.
+        :rtype: ``bool``
+        """
+        return self.driver.ex_instancegroup_remove_instances(
+            instancegroup=self, node_list=node_list)
+
+    def set_named_ports(self, named_ports):
+        """
+        Sets the named ports for the specified instance group.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  named_ports:  Assigns a name to a port number. For example:
+                              {name: "http", port: 80}  This allows the
+                              system to reference ports by the assigned name
+                              instead of a port number. Named ports can also
+                              contain multiple ports. For example: [{name:
+                              "http", port: 80},{name: "http", port: 8080}]
+                              Named ports apply to all instances in this
+                              instance group.
+        :type   named_ports: ``list`` of {'name': ``str``, 'port`: ``int``}
+
+        :return:  Return True if successful.
+        :rtype: ``bool``
+        """
+        return self.driver.ex_instancegroup_set_named_ports(
+            instancegroup=self, named_ports=named_ports)
+
 
 class GCEInstanceGroupManager(UuidMixin):
     """
@@ -982,6 +1305,30 @@ class GCEInstanceGroupManager(UuidMixin):
         return self.driver.ex_instancegroupmanager_resize(manager=self,
                                                           size=size)
 
+    def set_named_ports(self, named_ports):
+        """
+        Sets the named ports for the instance group controlled by this manager.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  named_ports:  Assigns a name to a port number. For example:
+                              {name: "http", port: 80}  This allows the
+                              system to reference ports by the assigned name
+                              instead of a port number. Named ports can also
+                              contain multiple ports. For example: [{name:
+                              "http", port: 80},{name: "http", port: 8080}]
+                              Named ports apply to all instances in this
+                              instance group.
+        :type   named_ports: ``list`` of {'name': ``str``, 'port`: ``int``}
+
+        :return:  Return True if successful.
+        :rtype: ``bool``
+        """
+        return self.driver.ex_instancegroup_set_named_ports(
+            instancegroup=self.instance_group, named_ports=named_ports)
+
     def __repr__(self):
         return '<GCEInstanceGroupManager name="%s" zone="%s" size="%d">' % (
             self.name, self.zone.name, self.size)
@@ -1297,6 +1644,7 @@ class GCENodeDriver(NodeDriver):
         "windows-cloud": ["windows"],
     }
 
+    BACKEND_SERVICE_PROTOCOLS = ['HTTP', 'HTTPS', 'HTTP2', 'TCP', 'SSL']
     GUEST_OS_FEATURES = ['VIRTIO_SCSI_MULTIQUEUE', 'WINDOWS']
 
     def __init__(self, user_id, key=None, datacenter=None, project=None,
@@ -1858,6 +2206,26 @@ class GCENodeDriver(NodeDriver):
         list_routes = [self._to_route(n) for n in response.get('items', [])]
         return list_routes
 
+    def ex_list_sslcertificates(self):
+        """
+        Retrieves the list of SslCertificate resources available to the
+        specified project.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+        * https://www.googleapis.com/auth/compute.readonly
+
+        :return: A list of SSLCertificate objects.
+        :rtype: ``list`` of :class:`GCESslCertificate`
+        """
+        list_data = []
+        request = '/global/sslCertificates'
+        response = self.connection.request(request, method='GET').object
+        list_data = [self._to_sslcertificate(a)
+                     for a in response.get('items', [])]
+        return list_data
+
     def ex_list_subnetworks(self, region=None):
         """
         Return the list of subnetworks.
@@ -2029,6 +2397,18 @@ class GCENodeDriver(NodeDriver):
         response = self.connection.request(request, method='GET').object
         return [self._to_targethttpproxy(u) for u in response.get('items', [])]
 
+    def ex_list_targethttpsproxies(self):
+        """
+        Return the list of target HTTPs proxies.
+
+        :return:  A list of target https proxy objects
+        :rtype:   ``list`` of :class:`GCETargetHttpsProxy`
+        """
+        request = '/global/targetHttpsProxies'
+        response = self.connection.request(request, method='GET').object
+        return [self._to_targethttpsproxy(x)
+                for x in response.get('items', [])]
+
     def ex_list_targetinstances(self, zone=None):
         """
         Return the list of target instances.
@@ -2337,28 +2717,160 @@ class GCENodeDriver(NodeDriver):
                                       data=autoscaler_data)
         return self.ex_get_autoscaler(name, zone)
 
-    def ex_create_backendservice(self, name, healthchecks):
+    def ex_create_backend(self, instance_group, balancing_mode='UTILIZATION',
+                          max_utilization=None, max_rate=None,
+                          max_rate_per_instance=None, capacity_scaler=1,
+                          description=None):
+        """
+        Helper Object to create a backend.
+
+        :param  instance_group: The Instance Group for this Backend.
+        :type   instance_group: :class: `GCEInstanceGroup`
+
+        :param  balancing_mode: Specifies the balancing mode for this backend.
+                                For global HTTP(S) load balancing, the valid
+                                values are UTILIZATION (default) and RATE.
+                                For global SSL load balancing, the valid
+                                values are UTILIZATION (default) and
+                                CONNECTION.
+        :type   balancing_mode: ``str``
+
+        :param  max_utilization: Used when balancingMode is UTILIZATION.
+                                 This ratio defines the CPU utilization
+                                 target for the group. The default is 0.8.
+                                 Valid range is [0.0, 1.0].
+        :type   max_utilization: ``float``
+
+        :param  max_rate: The max requests per second (RPS) of the group.
+                          Can be used with either RATE or UTILIZATION balancing
+                          modes, but required if RATE mode. For RATE mode,
+                          either maxRate or maxRatePerInstance must be set.
+        :type   max_rate: ``int``
+
+        :param  max_rate_per_instance: The max requests per second (RPS) that
+                                       a single backend instance can handle.
+                                       This is used to calculate the capacity
+                                       of the group. Can be used in either
+                                       balancing mode. For RATE mode, either
+                                       maxRate or maxRatePerInstance must be
+                                       set.
+        :type   max_rate_per_instance: ``float``
+
+        :param  capacity_scaler: A multiplier applied to the group's maximum
+                                 servicing capacity (based on UTILIZATION,
+                                 RATE, or CONNECTION). Default value is 1,
+                                 which means the group will serve up to 100%
+                                 of its configured capacity (depending on
+                                 balancingMode). A setting of 0 means the
+                                 group is completely drained, offering 0%
+                                 of its available capacity. Valid range is
+                                 [0.0,1.0].
+        :type   capacity_scaler: ``float``
+
+        :param  description: An optional description of this resource.
+                             Provide this property when you create the
+                             resource.
+        :type   description: ``str``
+
+        :return: A GCEBackend object.
+        :rtype: :class: `GCEBackend`
+        """
+
+        return GCEBackend(
+            instance_group=instance_group, balancing_mode=balancing_mode,
+            max_utilization=max_utilization, max_rate=max_rate,
+            max_rate_per_instance=max_rate_per_instance,
+            capacity_scaler=capacity_scaler, description=description)
+
+    def ex_create_backendservice(self, name, healthchecks, backends=[],
+                                 protocol=None, description=None,
+                                 timeout_sec=None, enable_cdn=False, port=None,
+                                 port_name=None):
         """
         Create a global Backend Service.
 
-        :param    name: Name of the Backend Service
-        :type     name: ``str``
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  name:  Name of the resource. Provided by the client when the
+                       resource is created. The name must be 1-63 characters
+                       long, and comply with RFC1035. Specifically, the name
+                       must be 1-63 characters long and match the regular
+                       expression [a-z]([-a-z0-9]*[a-z0-9])? which means the
+                       first character must be a lowercase letter, and all
+                       following characters must be a dash, lowercase letter,
+                       or digit, except the last character, which cannot be a
+                       dash.
+        :type   name: ``str``
 
         :param    healthchecks: A list of HTTP Health Checks to use for this
                                 service.  There must be at least one.
         :type     healthchecks: ``list`` of (``str`` or
                                 :class:`GCEHealthCheck`)
 
+        :keyword  backends:  The list of backends that serve this
+                             BackendService.
+        :type   backends: ``list`` of :class `GCEBackend` or list of ``dict``
+
+        :keyword  timeout_sec:  How many seconds to wait for the backend
+                                before considering it a failed request.
+                                Default is 30 seconds.
+        :type   timeout_sec: ``integer``
+
+        :keyword  enable_cdn:  If true, enable Cloud CDN for this
+                                 BackendService.  When the load balancing
+                                 scheme is INTERNAL, this field is not used.
+        :type   enable_cdn: ``bool``
+
+        :keyword  port:  Deprecated in favor of port_name. The TCP port to
+                         connect on the backend. The default value is 80.
+                         This cannot be used for internal load balancing.
+        :type   port: ``integer``
+
+        :keyword  port_name: Name of backend port. The same name should appear
+                             in the instance groups referenced by this service.
+        :type     port_name: ``str``
+
+        :keyword  protocol: The protocol this Backend Service uses to
+                            communicate with backends.
+                            Possible values are HTTP, HTTPS, HTTP2, TCP
+                            and SSL.
+        :type     protocol: ``str``
+
         :return:  A Backend Service object.
         :rtype:   :class:`GCEBackendService`
         """
-        backendservice_data = {'name': name, 'healthChecks': []}
+        backendservice_data = {'name': name,
+                               'healthChecks': [],
+                               'backends': [],
+                               'enableCDN': enable_cdn}
 
         for hc in healthchecks:
             if not hasattr(hc, 'extra'):
                 hc = self.ex_get_healthcheck(name=hc)
             backendservice_data['healthChecks'].append(hc.extra['selfLink'])
 
+        for be in backends:
+            if isinstance(be, GCEBackend):
+                backendservice_data['backends'].append(be.to_backend_dict())
+            else:
+                backendservice_data['backends'].append(be)
+        if port:
+            backendservice_data['port'] = port
+        if port_name:
+            backendservice_data['portName'] = port_name
+        if timeout_sec:
+            backendservice_data['timeoutSec'] = timeout_sec
+        if protocol:
+            if protocol in self.BACKEND_SERVICE_PROTOCOLS:
+                backendservice_data['protocol'] = protocol
+            else:
+                raise ValueError('Protocol must be one of %s' %
+                                 ','.join(self.BACKEND_SERVICE_PROTOCOLS))
+        if description:
+            backendservice_data['description'] = description
+
         request = '/global/backendServices'
         self.connection.async_request(request, method='POST',
                                       data=backendservice_data)
@@ -2715,6 +3227,72 @@ class GCENodeDriver(NodeDriver):
         self.connection.async_request(request, method='POST', data=image_data)
         return self.ex_get_image(name)
 
+    def ex_create_instancegroup(self, name, zone, description=None,
+                                network=None, subnetwork=None,
+                                named_ports=None):
+        """
+        Creates an instance group in the specified project using the
+        parameters that are included in the request.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  name:  Required. The name of the instance group. The name
+                       must be 1-63 characters long, and comply with RFC1035.
+        :type   name: ``str``
+
+        :param  zone:  The URL of the zone where the instance group is
+                       located.
+        :type   zone: :class:`GCEZone`
+
+        :keyword  description:  An optional description of this resource.
+                                Provide this property when you create the
+                                resource.
+        :type   description: ``str``
+
+        :keyword  network:  The URL of the network to which all instances in
+                            the instance group belong.
+        :type   network: :class:`GCENetwork`
+
+        :keyword  subnetwork:  The URL of the subnetwork to which all
+                               instances in the instance group belong.
+        :type   subnetwork: :class:`GCESubnetwork`
+
+        :keyword  named_ports:  Assigns a name to a port number. For example:
+                                {name: "http", port: 80}  This allows the
+                                system to reference ports by the assigned
+                                name instead of a port number. Named ports
+                                can also contain multiple ports. For example:
+                                [{name: "http", port: 80},{name: "http",
+                                port: 8080}]   Named ports apply to all
+                                instances in this instance group.
+        :type   named_ports: ``list`` of {'name': ``str``, 'port`: ``int``}
+
+        :return:  `GCEInstanceGroup` object.
+        :rtype: :class:`GCEInstanceGroup`
+        """
+        zone = zone or self.zone
+        if not hasattr(zone, 'name'):
+            zone = self.ex_get_zone(zone)
+        request = "/zones/%s/instanceGroups" % (zone.name)
+        request_data = {}
+        request_data['name'] = name
+        request_data['zone'] = zone.extra['selfLink']
+        if description:
+            request_data['description'] = description
+        if network:
+            request_data['network'] = network.extra['selfLink']
+        if subnetwork:
+            request_data['subnetwork'] = subnetwork.extra['selfLink']
+        if named_ports:
+            request_data['namedPorts'] = named_ports
+
+        self.connection.async_request(request, method='POST',
+                                      data=request_data)
+
+        return self.ex_get_instancegroup(name, zone)
+
     def ex_create_instancegroupmanager(self, name, zone, template, size,
                                        base_instance_name=None,
                                        description=None):
@@ -2830,7 +3408,61 @@ class GCENodeDriver(NodeDriver):
 
         return self.ex_get_route(name)
 
-    def ex_create_subnetwork(self, name, cidr=None, network=None, region=None,
+    def ex_create_sslcertificate(self, name, certificate=None,
+                                 private_key=None, description=None):
+        """
+        Creates a SslCertificate resource in the specified project using the
+        data included in the request.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  name:  Name of the resource. Provided by the client when the
+                       resource is created. The name must be 1-63 characters
+                       long, and comply with RFC1035. Specifically, the name
+                       must be 1-63 characters long and match the regular
+                       expression [a-z]([-a-z0-9]*[a-z0-9])? which means the
+                       first character must be a lowercase letter, and all
+                       following characters must be a dash, lowercase letter,
+                       or digit, except the last character, which cannot be a
+                       dash.
+        :type   name: ``str``
+
+        :param  certificate:  A string containing local certificate file in
+                              PEM format. The certificate chain
+                              must be no greater than 5 certs long. The
+                              chain must include at least one intermediate
+                              cert.
+        :type   certificate: ``str``
+
+        :param  private_key:  A string containing a write-only private key
+                              in PEM format. Only insert RPCs will include
+                              this field.
+        :type   private_key: ``str``
+
+        :keyword  description:  An optional description of this resource.
+                                Provide this property when you create the
+                                resource.
+        :type   description: ``str``
+
+        :return:  `GCESslCertificate` object.
+        :rtype: :class:`GCESslCertificate`
+        """
+
+        request = "/global/sslCertificates" % ()
+        request_data = {}
+        request_data['name'] = name
+        request_data['certificate'] = certificate
+        request_data['privateKey'] = private_key
+        request_data['description'] = description
+
+        self.connection.async_request(request, method='POST',
+                                      data=request_data)
+
+        return self.ex_get_sslcertificate(name)
+
+    def ex_create_subnetwork(self, name, cidr=None, network=None, region=None,
                              description=None):
         """
         Create a subnetwork.
@@ -3133,6 +3765,679 @@ class GCENodeDriver(NodeDriver):
         self.connection.async_request(request, method='POST', data=node_data)
         return self.ex_get_node(name, location.name)
 
+    def ex_create_instancetemplate(
+            self, name, size, source=None, image=None, disk_type='pd-standard',
+            disk_auto_delete=True, network='default', subnetwork=None,
+            can_ip_forward=None, external_ip='ephemeral',
+            service_accounts=None, on_host_maintenance=None,
+            automatic_restart=None, preemptible=None, tags=None, metadata=None,
+            description=None, disks_gce_struct=None, nic_gce_struct=None):
+        """
+        Creates an instance template in the specified project using the data
+        that is included in the request. If you are creating a new template to
+        update an existing instance group, your new instance template must
+        use the same network or, if applicable, the same subnetwork as the
+        original template.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  name: The name of the node to create.
+        :type   name: ``str``
+
+        :param  size: The machine type to use.
+        :type   size: ``str`` or :class:`GCENodeSize`
+
+        :param  image: The image to use to create the node (or, if attaching
+                       a persistent disk, the image used to create the disk)
+        :type   image: ``str`` or :class:`GCENodeImage` or ``None``
+
+        :keyword  network: The network to associate with the template.
+        :type     network: ``str`` or :class:`GCENetwork`
+
+        :keyword  subnetwork: The subnetwork to associate with the node.
+        :type     subnetwork: ``str`` or :class:`GCESubnetwork`
+
+        :keyword  tags: A list of tags to associate with the node.
+        :type     tags: ``list`` of ``str`` or ``None``
+
+        :keyword  metadata: Metadata dictionary for instance.
+        :type     metadata: ``dict`` or ``None``
+
+        :keyword  external_ip: The external IP address to use.  If 'ephemeral'
+                               (default), a new non-static address will be
+                               used.  If 'None', then no external address will
+                               be used.  To use an existing static IP address,
+                               a GCEAddress object should be passed in.
+        :type     external_ip: :class:`GCEAddress` or ``str`` or ``None``
+
+        :keyword  disk_type: Specify a pd-standard (default) disk or pd-ssd
+                                for an SSD disk.
+        :type     disk_type: ``str`` or :class:`GCEDiskType`
+
+        :keyword  disk_auto_delete: Indicate that the boot disk should be
+                                       deleted when the Node is deleted. Set to
+                                       True by default.
+        :type     disk_auto_delete: ``bool``
+
+        :keyword  service_accounts: Specify a list of serviceAccounts when
+                                       creating the instance. The format is a
+                                       list of dictionaries containing email
+                                       and list of scopes, e.g.
+                                       [{'email':'default',
+                                       'scopes':['compute', ...]}, ...]
+                                       Scopes can either be full URLs or short
+                                       names. If not provided, use the
+                                       'default' service account email and a
+                                       scope of 'devstorage.read_only'. Also
+                                       accepts the aliases defined in
+                                       'gcloud compute'.
+        :type     service_accounts: ``list``
+
+        :keyword  description: The description of the node (instance).
+        :type     description: ``str`` or ``None``
+
+        :keyword  can_ip_forward: Set to ``True`` to allow this node to
+                                  send/receive non-matching src/dst packets.
+        :type     can_ip_forward: ``bool`` or ``None``
+
+        :keyword  disks_gce_struct: Support for passing in the GCE-specific
+                                       formatted disks[] structure. No attempt
+                                       is made to ensure proper formatting of
+                                       the disks[] structure. Using this
+                                       structure obviates the need of using
+                                       other disk params like 'ex_boot_disk',
+                                       etc. See the GCE docs for specific
+                                       details.
+        :type     disks_gce_struct: ``list`` or ``None``
+
+        :keyword  nic_gce_struct: Support passing in the GCE-specific
+                                     formatted networkInterfaces[] structure.
+                                     No attempt is made to ensure proper
+                                     formatting of the networkInterfaces[]
+                                     data. Using this structure obviates the
+                                     need of using 'external_ip' and
+                                     'ex_network'.  See the GCE docs for
+                                     details.
+        :type     nic_gce_struct: ``list`` or ``None``
+
+        :keyword  on_host_maintenance: Defines whether node should be
+                                          terminated or migrated when host
+                                          machine goes down. Acceptable values
+                                          are: 'MIGRATE' or 'TERMINATE' (If
+                                          not supplied, value will be reset to
+                                          GCE default value for the instance
+                                          type.)
+        :type     ex_on_host_maintenance: ``str`` or ``None``
+
+        :keyword  automatic_restart: Defines whether the instance should be
+                                        automatically restarted when it is
+                                        terminated by Compute Engine. (If not
+                                        supplied, value will be set to the GCE
+                                        default value for the instance type.)
+        :type     automatic_restart: ``bool`` or ``None``
+
+        :keyword  preemptible: Defines whether the instance is preemptible.
+                                  (If not supplied, the instance will not be
+                                  preemptible)
+        :type     preemptible: ``bool`` or ``None``
+
+        :return:  An Instance Template object.
+        :rtype:   :class:`GCEInstanceTemplate`
+        """
+        request = "/global/instanceTemplates"
+
+        properties = self._create_instance_properties(
+            name, node_size=size, source=source, image=image,
+            disk_type='pd-standard', disk_auto_delete=True,
+            external_ip=external_ip, network=network, subnetwork=subnetwork,
+            can_ip_forward=can_ip_forward, service_accounts=service_accounts,
+            on_host_maintenance=on_host_maintenance,
+            automatic_restart=automatic_restart, preemptible=preemptible,
+            tags=tags, metadata=metadata, description=description,
+            disks_gce_struct=disks_gce_struct, nic_gce_struct=nic_gce_struct,
+            use_selflinks=False)
+
+        request_data = {'name': name,
+                        'description': description,
+                        'properties': properties}
+
+        self.connection.async_request(request, method='POST',
+                                      data=request_data)
+
+        return self.ex_get_instancetemplate(name)
+
+    def _create_instance_properties(
+            self, name, node_size, source=None, image=None,
+            disk_type='pd-standard', disk_auto_delete=True, network='default',
+            subnetwork=None, external_ip='ephemeral', can_ip_forward=None,
+            service_accounts=None, on_host_maintenance=None,
+            automatic_restart=None, preemptible=None, tags=None, metadata=None,
+            description=None, disks_gce_struct=None, nic_gce_struct=None,
+            use_selflinks=True):
+        """
+        Create the GCE instance properties needed for instance templates.
+
+        :param    node_size: The machine type to use.
+        :type     node_size: ``str`` or :class:`GCENodeSize`
+
+        :keyword  source: A source disk to attach to the instance. Cannot
+                          specify both 'image' and 'source'.
+        :type     source: :class:`StorageVolume` or ``str`` or ``None``
+
+        :param    image: The image to use to create the node. Cannot specify
+                         both 'image' and 'source'.
+        :type     image: ``str`` or :class:`GCENodeImage` or ``None``
+
+        :keyword  disk_type: Specify a pd-standard (default) disk or pd-ssd
+                             for an SSD disk.
+        :type     disk_type: ``str`` or :class:`GCEDiskType`
+
+        :keyword  disk_auto_delete: Indicate that the boot disk should be
+                                    deleted when the Node is deleted. Set to
+                                    True by default.
+        :type     disk_auto_delete: ``bool``
+
+        :keyword  network: The network to associate with the node.
+        :type     network: ``str`` or :class:`GCENetwork`
+
+        :keyword  subnetwork: The Subnetwork resource for this instance. If
+                              the network resource is in legacy mode, do not
+                              provide this property. If the network is in auto
+                              subnet mode, providing the subnetwork is
+                              optional. If the network is in custom subnet
+                              mode, then this field should be specified.
+        :type     subnetwork: :class: `GCESubnetwork` or None
+
+        :keyword  external_ip: The external IP address to use.  If 'ephemeral'
+                               (default), a new non-static address will be
+                               used.  If 'None', then no external address will
+                               be used.  To use an existing static IP address,
+                               a GCEAddress object should be passed in.
+        :type     external_ip: :class:`GCEAddress` or ``str`` or ``None``
+
+        :keyword  can_ip_forward: Set to ``True`` to allow this node to
+                                  send/receive non-matching src/dst packets.
+        :type     can_ip_forward: ``bool`` or ``None``
+
+        :keyword  service_accounts: Specify a list of serviceAccounts when
+                                    creating the instance. The format is a
+                                    list of dictionaries containing email
+                                    and list of scopes, e.g.
+                                    [{'email':'default',
+                                    'scopes':['compute', ...]}, ...]
+                                    Scopes can either be full URLs or short
+                                    names. If not provided, use the
+                                    'default' service account email and a
+                                    scope of 'devstorage.read_only'. Also
+                                    accepts the aliases defined in
+                                    'gcloud compute'.
+        :type     service_accounts: ``list``
+
+        :keyword  on_host_maintenance: Defines whether node should be
+                                       terminated or migrated when host
+                                       machine goes down. Acceptable values
+                                       are: 'MIGRATE' or 'TERMINATE' (If
+                                       not supplied, value will be reset to
+                                       GCE default value for the instance
+                                       type.)
+        :type     on_host_maintenance: ``str`` or ``None``
+
+        :keyword  automatic_restart: Defines whether the instance should be
+                                     automatically restarted when it is
+                                     terminated by Compute Engine. (If not
+                                     supplied, value will be set to the GCE
+                                     default value for the instance type.)
+        :type     automatic_restart: ``bool`` or ``None``
+
+        :keyword  preemptible: Defines whether the instance is preemptible.
+                               (If not supplied, the instance will not be
+                               preemptible)
+        :type     preemptible: ``bool`` or ``None``
+
+        :keyword  tags: A list of tags to associate with the node.
+        :type     tags: ``list`` of ``str`` or ``None``
+
+        :keyword  metadata: Metadata dictionary for instance.
+        :type     metadata: ``dict`` or ``None``
+
+        :keyword  description: The description of the node (instance).
+        :type     description: ``str`` or ``None``
+
+        :keyword  disks_gce_struct: Support for passing in the GCE-specific
+                                    formatted disks[] structure. No attempt
+                                    is made to ensure proper formatting of
+                                    the disks[] structure. Using this
+                                    structure obviates the need of using
+                                    other disk params like 'boot_disk',
+                                    etc. See the GCE docs for specific
+                                    details.
+        :type     disks_gce_struct: ``list`` or ``None``
+
+        :keyword  nic_gce_struct: Support passing in the GCE-specific
+                                  formatted networkInterfaces[] structure.
+                                  No attempt is made to ensure proper
+                                  formatting of the networkInterfaces[]
+                                  data. Using this structure obviates the
+                                  need of using 'external_ip' and
+                                  'network'.  See the GCE docs for
+                                  details.
+        :type     nic_gce_struct: ``list`` or ``None``
+
+        :return:  A dictionary formatted for use with the GCE API.
+        :rtype:   ``dict``
+        """
+        instance_properties = {}
+
+        # build disks
+        if not image and not source and not disks_gce_struct:
+            raise ValueError("Missing root device or image. Must specify an "
+                             "'image', source, or use the "
+                             "'disks_gce_struct'.")
+
+        if source and disks_gce_struct:
+            raise ValueError("Cannot specify both 'source' and "
+                             "'disks_gce_struct'. Use one or the other.")
+
+        if disks_gce_struct:
+            instance_properties['disks'] = disks_gce_struct
+        else:
+            disk_name = None
+            device_name = None
+            if source:
+                disk_name = source.name
+                # TODO(supertom): what about device name?
+                device_name = source.name
+                image = None
+
+            instance_properties['disks'] = [self._build_disk_gce_struct(
+                device_name, source=source, disk_type=disk_type, image=image,
+                disk_name=disk_name, usage_type='PERSISTENT',
+                mount_mode='READ_WRITE', auto_delete=disk_auto_delete,
+                is_boot=True, use_selflinks=use_selflinks)]
+
+        # build network interfaces
+        if nic_gce_struct is not None:
+            if hasattr(external_ip, 'address'):
+                raise ValueError("Cannot specify both a static IP address "
+                                 "and 'nic_gce_struct'. Use one or the "
+                                 "other.")
+            if hasattr(network, 'name'):
+                if network.name == 'default':
+                    # assume this is just the default value from create_node()
+                    # and since the user specified ex_nic_gce_struct, the
+                    # struct should take precedence
+                    network = None
+                else:
+                    raise ValueError("Cannot specify both 'network' and "
+                                     "'nic_gce_struct'. Use one or the "
+                                     "other.")
+            instance_properties['networkInterfaces'] = nic_gce_struct
+        else:
+            instance_properties['networkInterfaces'] = [
+                self._build_network_gce_struct(
+                    network=network, subnetwork=subnetwork,
+                    external_ip=external_ip, use_selflinks=True)
+            ]
+
+        # build scheduling
+        scheduling = self._build_scheduling_gce_struct(
+            on_host_maintenance, automatic_restart, preemptible)
+        if scheduling:
+            instance_properties['scheduling'] = scheduling
+
+        # build service accounts/scopes
+        instance_properties[
+            'serviceAccounts'] = self._build_service_accounts_gce_list(
+                service_accounts)
+
+        # include general properties
+        if description:
+            instance_properties['description'] = str(description)
+        if tags:
+            instance_properties['tags'] = {'items': tags}
+        if metadata:
+            instance_properties['metadata'] = self._format_metadata(
+                fingerprint='na', metadata=metadata)
+        if can_ip_forward:
+            instance_properties['canIpForward'] = True
+
+        instance_properties['machineType'] = self._get_selflink_or_name(
+            obj=node_size, get_selflinks=use_selflinks, objname='size')
+
+        return instance_properties
+
+    def _build_disk_gce_struct(
+            self, device_name, source=None, disk_type=None, disk_size=None,
+            image=None, disk_name=None, is_boot=True, mount_mode='READ_WRITE',
+            usage_type='PERSISTENT', auto_delete=True, use_selflinks=True):
+        """
+        Generates the GCP dict for a disk.
+
+        :param    device_name: Specifies a unique device name of your
+                               choice that is reflected into the
+                               /dev/disk/by-id/google-* tree
+                               of a Linux operating system running within the
+                               instance. This name can be used to reference the
+                               device for mounting, resizing, and so on, from
+                               within the instance.  Defaults to disk_name.
+        :type      device_name: ``str``
+
+        :keyword   source: The disk to attach to the instance.
+        :type      source: ``str`` of selfLink, :class:`StorageVolume` or None
+
+        :keyword   disk_type: Specify a URL or DiskType object.
+        :type      disk_type: ``str`` or :class:`GCEDiskType` or ``None``
+
+        :keyword   image: The image to use to create the disk.
+        :type      image: :class:`GCENodeImage` or ``None``
+
+        :keyword   disk_size: Integer in gigabytes.
+        :type      disk_size: ``int``
+
+        :param     disk_name: Specifies the disk name. If not specified, the
+                              default is to use the device_name.
+        :type      disk_name: ``str``
+
+        :keyword   mount_mode: The mode in which to attach this disk, either
+                               READ_WRITE or READ_ONLY. If not specified,
+                               the default is to attach the disk in READ_WRITE
+                               mode.
+        :type      mount_mode: ``str``
+
+        :keyword   usage_type: Specifies the type of the disk, either SCRATCH
+                               or PERSISTENT. If not specified, the default
+                               is PERSISTENT.
+        :type      usage_type: ``str``
+
+        :keyword   auto_delete: Indicate that the boot disk should be
+                                deleted when the Node is deleted. Set to
+                                True by default.
+        :type      auto_delete: ``bool``
+
+        :return:   Dictionary to be used in disk-portion of
+                   instance API call.
+        :rtype:    ``dict``
+        """
+        # validation
+        if source is None and image is None:
+            raise ValueError(
+                "Either the 'source' or 'image' argument must be specified.")
+
+        if not isinstance(auto_delete, bool):
+            raise ValueError("auto_delete field is not a bool.")
+
+        if disk_size is not None and not disk_size.isdigit():
+            raise ValueError("disk_size must be a digit, '%s' provided." %
+                             (disk_size))
+
+        mount_modes = ['READ_WRITE', 'READ_ONLY']
+        if mount_mode not in mount_modes:
+            raise ValueError("mount mode must be one of: %s." %
+                             (','.join(mount_modes)))
+        usage_types = ['PERSISTENT', 'SCRATCH']
+        if usage_type not in usage_types:
+            raise ValueError("usage type must be one of: %s." %
+                             (','.join(usage_types)))
+
+        disk = {}
+        if not disk_name:
+            disk_name = device_name
+
+        if source is not None:
+            disk['source'] = self._get_selflink_or_name(
+                obj=source, get_selflinks=use_selflinks, objname='volume')
+
+        else:
+            # create new disk
+            # we need the URL of the image, always.
+            image = self._get_selflink_or_name(obj=image, get_selflinks=True,
+                                               objname='image')
+            disk_type = self._get_selflink_or_name(
+                obj=disk_type, get_selflinks=use_selflinks, objname='disktype')
+
+            disk['initializeParams'] = {
+                'diskName': disk_name,
+                'diskType': disk_type,
+                'sourceImage': image,
+            }
+            if disk_size is not None:
+                disk['initializeParams']['diskSizeGb'] = disk_size
+
+        # add in basic attributes
+        disk.update({'boot': is_boot,
+                     'type': usage_type,
+                     'mode': mount_mode,
+                     'deviceName': device_name,
+                     'autoDelete': auto_delete})
+        return disk
+
+    def _get_selflink_or_name(self, obj, get_selflinks=True, objname=None):
+        """
+        Return the selflink or name, given a name or object.
+
+        Will try to fetch the appropriate object if necessary (assumes
+        we only need one parameter to fetch the object, no introspection
+        is performed).
+
+        :param    obj: object to test.
+        :type     obj: ``str`` or ``object``
+
+        :param    get_selflinks: Inform if we should return selfLinks or just
+                              the name.  Default is True.
+        :param    get_selflinks: ``bool``
+
+        :param    objname: string to use in constructing method call
+        :type     objname: ``str`` or None
+
+        :return:  URL from extra['selfLink'] or name
+        :rtype:   ``str``
+        """
+        if get_selflinks:
+            if not hasattr(obj, 'name'):
+                if objname:
+                    getobj = getattr(self, 'ex_get_%s' % (objname))
+                    obj = getobj(obj)
+                else:
+                    raise ValueError(
+                        "objname must be set if selflinks is True.")
+            return obj.extra['selfLink']
+        else:
+            if not hasattr(obj, 'name'):
+                return obj
+            else:
+                return obj.name
+
+    def _build_network_gce_struct(self, network, subnetwork=None,
+                                  external_ip=None, use_selflinks=True):
+        """
+        Build network interface dict for use in the GCE API.
+
+        Note: Must be wrapped in a list before passing to the GCE API.
+
+        :param    network: The network to associate with the node.
+        :type     network: :class:`GCENetwork`
+
+        :keyword  subnetwork: The subnetwork to include.
+        :type     subnetwork: :class:`GCESubNetwork`
+
+        :keyword  external_ip: The external IP address to use.  If 'ephemeral'
+                               (default), a new non-static address will be
+                               used.  If 'None', then no external address will
+                               be used.  To use an existing static IP address,
+                               a GCEAddress object should be passed in.
+        :type     external_ip: :class:`GCEAddress`
+
+        :return:  network interface dict
+        :rtype:   ``dict``
+        """
+        ni = {}
+        ni = {'kind': 'compute#instanceNetworkInterface'}
+        if network is None:
+            network = 'default'
+
+        ni['network'] = self._get_selflink_or_name(
+            obj=network, get_selflinks=use_selflinks, objname='network')
+
+        if subnetwork:
+            ni['subnetwork'] = self._get_selflink_or_name(
+                obj=subnetwork, get_selflinks=use_selflinks,
+                objname='subnetwork')
+
+        if external_ip:
+            access_configs = [{'name': 'External NAT',
+                               'type': 'ONE_TO_ONE_NAT'}]
+            if hasattr(external_ip, 'address'):
+                access_configs[0]['natIP'] = external_ip.address
+            ni['accessConfigs'] = access_configs
+
+        return ni
+
+    def _build_service_account_gce_struct(
+            self, service_account, default_email='default',
+            default_scope='devstorage.read_only'):
+        """
+        Helper to create Service Account dict.  Use
+        _build_service_accounts_gce_list to create a list ready for the
+        GCE API.
+
+        :param: service_account: dictionarie containing email
+                                 and list of scopes, e.g.
+                                 [{'email':'default',
+                                 'scopes':['compute', ...]}, ...]
+                                 Scopes can either be full URLs or short
+                                 names. If not provided, use the
+                                 'default' service account email and a
+                                 scope of 'devstorage.read_only'. Also
+                                 accepts the aliases defined in
+                                 'gcloud compute'.
+       :type    service_account: ``dict`` or None
+
+       :return: dict usable in GCE API call.
+       :rtype:  ``dict``
+       """
+        if not isinstance(service_account, dict):
+            raise ValueError(
+                "service_account not in the correct format,"
+                "'%s - %s'" %
+                (str(type(service_account)), str(service_account)))
+        sa = {}
+        if 'email' not in service_account:
+            sa['email'] = default_email
+
+        if 'scopes' not in service_account:
+            sa['scopes'] = [self.AUTH_URL + default_scope]
+        else:
+            ps = []
+            for scope in service_account['scopes']:
+                if scope.startswith(self.AUTH_URL):
+                    ps.append(scope)
+                elif scope in self.SA_SCOPES_MAP:
+                    ps.append(self.AUTH_URL + self.SA_SCOPES_MAP[scope])
+                else:
+                    ps.append(self.AUTH_URL + scope)
+            sa['scopes'] = ps
+
+        return sa
+
+    def _build_service_accounts_gce_list(self, service_accounts=None,
+                                         default_email='default',
+                                         default_scope='devstorage.read_only'):
+        """
+        Helper to create service account list for GCE API.
+
+        :keyword  service_accounts: Specify a list of serviceAccounts when
+                                       creating the instance. The format is a
+                                       list of dictionaries containing email
+                                       and list of scopes, e.g.
+                                       [{'email':'default',
+                                       'scopes':['compute', ...]}, ...]
+                                       Scopes can either be full URLs or short
+                                       names. If not provided, use the
+                                       'default' service account email and a
+                                       scope of 'devstorage.read_only'. Also
+                                       accepts the aliases defined in
+                                       'gcloud compute'.
+
+        :type     service_accounts: ``list`` of ``dict`` or None
+
+        :return:  list of dictionaries usable in the GCE API.
+        :rtype:   ``list`` of ``dict``
+        """
+        gce_service_accounts = []
+        if not service_accounts:
+            gce_service_accounts = [{
+                'email': default_email,
+                'scopes': [self.AUTH_URL + default_scope]
+            }]
+        elif not isinstance(service_accounts, list):
+            raise ValueError("service_accounts field is not a list.")
+        else:
+            for sa in service_accounts:
+                gce_service_accounts.append(
+                    self._build_service_account_gce_struct(service_account=sa))
+
+        return gce_service_accounts
+
+    def _build_scheduling_gce_struct(self, on_host_maintenance=None,
+                                     automatic_restart=None, preemptible=None):
+        """
+        Build the scheduling dict suitable for use with the GCE API.
+
+        :param    on_host_maintenance: Defines whether node should be
+                                          terminated or migrated when host
+                                          machine goes down. Acceptable values
+                                          are: 'MIGRATE' or 'TERMINATE' (If
+                                          not supplied, value will be reset to
+                                          GCE default value for the instance
+                                          type.)
+        :type     on_host_maintenance: ``str`` or ``None``
+
+        :param    automatic_restart: Defines whether the instance should be
+                                        automatically restarted when it is
+                                        terminated by Compute Engine. (If not
+                                        supplied, value will be set to the GCE
+                                        default value for the instance type.)
+        :type     automatic_restart: ``bool`` or ``None``
+
+        :param    preemptible: Defines whether the instance is preemptible.
+                                        (If not supplied, the instance will
+                                         not be preemptible)
+        :type     preemptible: ``bool`` or ``None``
+
+        :return:  A dictionary of scheduling options for the GCE API.
+        :rtype:   ``dict``
+        """
+        scheduling = {}
+        if preemptible is not None:
+            if isinstance(preemptible, bool):
+                scheduling['preemptible'] = preemptible
+            else:
+                raise ValueError("boolean expected for preemptible")
+        if on_host_maintenance is not None:
+            maint_opts = ['MIGRATE', 'TERMINATE']
+            if isinstance(on_host_maintenance,
+                          str) and on_host_maintenance in maint_opts:
+                if preemptible is True and on_host_maintenance is 'MIGRATE':
+                    raise ValueError(("host maintenance cannot be 'MIGRATE' "
+                                      "if instance is preemptible."))
+                scheduling['onHostMaintenance'] = on_host_maintenance
+            else:
+                raise ValueError("host maintenance must be one of %s" %
+                                 (','.join(maint_opts)))
+        if automatic_restart is not None:
+            if isinstance(automatic_restart, bool):
+                if automatic_restart is True and preemptible is True:
+                    raise ValueError(
+                        "instance cannot be restarted if it is preemptible.")
+                scheduling['automaticRestart'] = automatic_restart
+
+            else:
+                raise ValueError("boolean expected for automatic")
+
+        return scheduling
+
     def ex_create_multiple_nodes(
             self, base_name, size, image, number, location=None,
             ex_network='default', ex_tags=None, ex_metadata=None,
@@ -3376,6 +4681,61 @@ class GCENodeDriver(NodeDriver):
 
         return self.ex_get_targethttpproxy(name)
 
+    def ex_create_targethttpsproxy(self, name, urlmap, sslcertificates,
+                                   description=None):
+        """
+        Creates a TargetHttpsProxy resource in the specified project
+        using the data included in the request.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  name:  Name of the resource. Provided by the client when the
+                       resource is created. The name must be 1-63 characters
+                       long, and comply with RFC1035. Specifically, the name
+                       must be 1-63 characters long and match the regular
+                       expression [a-z]([-a-z0-9]*[a-z0-9])? which means the
+                       first character must be a lowercase letter, and all
+                       following characters must be a dash, lowercase letter,
+                       or digit, except the last character, which cannot be a
+                       dash.
+        :type   name: ``str``
+
+        :param  sslcertificates:  URLs to SslCertificate resources that
+                                     are used to authenticate connections
+                                     between users and the load balancer.
+                                     Currently, exactly one SSL certificate
+                                     must be specified.
+        :type   sslcertificates: ``list`` of :class:`GCESslcertificates`
+
+        :param  urlmap:  A fully-qualified or valid partial URL to the
+                            UrlMap resource that defines the mapping from URL
+                            to the BackendService.
+        :type   urlmap: :class:`GCEUrlMap`
+
+        :keyword  description:  An optional description of this resource.
+                                Provide this property when you create the
+                                resource.
+        :type   description: ``str``
+
+        :return:  `GCETargetHttpsProxy` object.
+        :rtype: :class:`GCETargetHttpsProxy`
+        """
+
+        request = "/global/targetHttpsProxies" % ()
+        request_data = {}
+        request_data['name'] = name
+        request_data['description'] = description
+        request_data['sslCertificates'] = [x.extra['selfLink']
+                                           for x in sslcertificates]
+        request_data['urlMap'] = urlmap.extra['selfLink']
+
+        self.connection.async_request(request, method='POST',
+                                      data=request_data)
+
+        return self.ex_get_targethttpsproxy(name)
+
     def ex_create_targetinstance(self, name, zone=None, node=None,
                                  description=None, nat_policy="NO_NAT"):
         """
@@ -3705,6 +5065,61 @@ class GCENodeDriver(NodeDriver):
 
         return self.ex_get_firewall(firewall.name)
 
+    def ex_targethttpsproxy_set_sslcertificates(self, targethttpsproxy,
+                                                sslcertificates):
+        """
+        Replaces SslCertificates for TargetHttpsProxy.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  targethttpsproxy:  Name of the TargetHttpsProxy resource to
+                                   set an SslCertificates resource for.
+        :type   targethttpsproxy: ``str``
+
+        :param  sslcertificates:  sslcertificates to set.
+        :type   sslcertificates: ``list`` of :class:`GCESslCertificates`
+
+        :return:  True
+        :rtype: ``bool``
+        """
+
+        request = "/targetHttpsProxies/%s/setSslCertificates" % (
+            targethttpsproxy.name)
+        request_data = {'sslCertificates': [x.extra['selfLink']
+                                            for x in sslcertificates]}
+        self.connection.async_request(request, method='POST',
+                                      data=request_data)
+
+        return True
+
+    def ex_targethttpsproxy_set_urlmap(self, targethttpsproxy, urlmap):
+        """
+        Changes the URL map for TargetHttpsProxy.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  targethttpsproxy:  Name of the TargetHttpsProxy resource
+                                   whose URL map is to be set.
+        :type   targethttpsproxy: ``str``
+
+        :param  urlmap:  urlmap to set.
+        :type   urlmap: :class:`GCEUrlMap`
+
+        :return:  True
+        :rtype: ``bool``
+        """
+
+        request = "/targetHttpsProxies/%s/setUrlMap" % (targethttpsproxy.name)
+        request_data = {'urlMap': urlmap.extra['selfLink']}
+        self.connection.async_request(request, method='POST',
+                                      data=request_data)
+
+        return True
+
     def ex_targetpool_get_health(self, targetpool, node=None):
         """
         Return a hash of target pool instances and their health.
@@ -3920,6 +5335,158 @@ class GCENodeDriver(NodeDriver):
             targetpool.healthchecks.pop(index)
         return True
 
+    def ex_instancegroup_add_instances(self, instancegroup, node_list):
+        """
+        Adds a list of instances to the specified instance group. All of the
+        instances in the instance group must be in the same
+        network/subnetwork. Read  Adding instances for more information.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  instancegroup:  The Instance Group where you are
+                                adding instances.
+        :type   instancegroup: :class:``GCEInstanceGroup``
+
+        :param  node_list: List of nodes to add.
+        :type   node_list: ``list`` of :class:`Node` or ``list`` of
+                           :class:`GCENode`
+
+        :return:  Return True if successful.
+        :rtype: ``bool``
+        """
+        request = "/zones/%s/instanceGroups/%s/addInstances" % (
+            instancegroup.zone.name, instancegroup.name)
+        request_data = {'instances': [{'instance': x.extra['selfLink']}
+                                      for x in node_list]}
+        self.connection.async_request(request, method='POST',
+                                      data=request_data)
+        return True
+
+    def ex_instancegroup_remove_instances(self, instancegroup, node_list):
+        """
+        Removes one or more instances from the specified instance group,
+        but does not delete those instances.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  instancegroup:  The Instance Group where the
+                                specified instances will be removed.
+        :type   instancegroup: :class:``GCEInstanceGroup``
+
+        :param  node_list: List of nodes to add.
+        :type   node_list: ``list`` of :class:`Node` or ``list`` of
+                           :class:`GCENode`
+
+        :return:  True if successful.
+        :rtype: ``bool``
+        """
+        request = "/zones/%s/instanceGroups/%s/removeInstances" % (
+            instancegroup.zone.name, instancegroup.name)
+        request_data = {'instances': [{'instance': x.extra['selfLink']}
+                                      for x in node_list]}
+        self.connection.async_request(request, method='POST',
+                                      data=request_data)
+        return True
+
+    def ex_instancegroup_list_instances(self, instancegroup):
+        """
+        Lists the instances in the specified instance group.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+        * https://www.googleapis.com/auth/compute.readonly
+
+        :param  instancegroup:  The Instance Group where from which you
+                                want to generate a list of included
+                                instances.
+        :type   instancegroup: :class:``GCEInstanceGroup``
+
+        :return:  List of :class:`GCENode` objects.
+        :rtype: ``list`` of :class:`GCENode` objects.
+        """
+        request = "/zones/%s/instanceGroups/%s/listInstances" % (
+            instancegroup.zone.name, instancegroup.name)
+
+        # Note: This API requires a 'POST'.
+        response = self.connection.request(request, method='POST').object
+
+        list_data = []
+        if 'items' in response:
+            for v in response['items']:
+                instance_info = self._get_components_from_path(v['instance'])
+                list_data.append(
+                    self.ex_get_node(instance_info['name'], instance_info[
+                        'zone']))
+        return list_data
+
+    def ex_instancegroup_set_named_ports(self, instancegroup, named_ports=[]):
+        """
+        Sets the named ports for the specified instance group.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  instancegroup:  The Instance Group where where the
+                                named ports are updated.
+        :type   instancegroup: :class:`GCEInstanceGroup`
+
+        :param  named_ports:  Assigns a name to a port number. For example:
+                              {name: "http", port: 80}  This allows the
+                              system to reference ports by the assigned name
+                              instead of a port number. Named ports can also
+                              contain multiple ports. For example: [{name:
+                              "http", port: 80},{name: "http", port: 8080}]
+                              Named ports apply to all instances in this
+                              instance group.
+        :type   named_ports: ``list`` of {'name': ``str``, 'port`: ``int``}
+
+        :return:  Return True if successful.
+        :rtype: ``bool``
+        """
+
+        if not isinstance(named_ports, list):
+            raise ValueError("'named_ports' must be a list of name/port"
+                             " dictionaries.")
+
+        request = "/zones/%s/instanceGroups/%s/setNamedPorts" % (
+            instancegroup.zone.name, instancegroup.name)
+        request_data = {'namedPorts': named_ports,
+                        'fingerprint': instancegroup.extra['fingerprint']}
+        self.connection.async_request(request, method='POST',
+                                      data=request_data)
+        return True
+
+    def ex_destroy_instancegroup(self, instancegroup):
+        """
+        Deletes the specified instance group. The instances in the group
+        are not deleted. Note that instance group must not belong to a backend
+        service. Read  Deleting an instance group for more information.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  instancegroup:  The name of the instance group to delete.
+        :type   instancegroup: :class:`GCEInstanceGroup`
+
+        :return:  Return True if successful.
+        :rtype: ``bool``
+        """
+
+        request = "/zones/%s/instanceGroups/%s" % (instancegroup.zone.name,
+                                                   instancegroup.name)
+        request_data = {}
+        self.connection.async_request(request, method='DELETE',
+                                      data=request_data)
+
+        return True
+
     def ex_instancegroupmanager_list_managed_instances(self, manager):
         """
         Lists all of the instances in the Managed Instance Group.
@@ -4479,8 +6046,8 @@ class GCENodeDriver(NodeDriver):
                 try:
                     timestamp_to_datetime(value)
                 except:
-                    raise ValueError('%s must be an RFC3339 timestamp'
-                                     % attribute)
+                    raise ValueError('%s must be an RFC3339 timestamp' %
+                                     attribute)
                 image_data[attribute] = value
 
         request = '/global/images/%s/deprecate' % (image.name)
@@ -4634,6 +6201,33 @@ class GCENodeDriver(NodeDriver):
         self.connection.async_request(request, method='DELETE')
         return True
 
+    def ex_destroy_instancetemplate(self, instancetemplate):
+        """
+        Deletes the specified instance template. If you delete an instance
+        template that is being referenced from another instance group, the
+        instance group will not be able to create or recreate virtual machine
+        instances. Deleting an instance template is permanent and cannot be
+        undone.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  instancetemplate:  The name of the instance template to
+                                   delete.
+        :type   instancetemplate: ``str``
+
+        :return  instanceTemplate:  Return True if successful.
+        :rtype   instanceTemplate: ````bool````
+        """
+
+        request = "/global/instanceTemplates/%s" % (instancetemplate.name)
+        request_data = {}
+        self.connection.async_request(request, method='DELETE',
+                                      data=request_data)
+
+        return True
+
     def ex_destroy_autoscaler(self, autoscaler):
         """
         Destroy an Autoscaler.
@@ -4792,6 +6386,29 @@ class GCENodeDriver(NodeDriver):
         self.connection.async_request(request, method='DELETE')
         return True
 
+    def ex_destroy_targethttpsproxy(self, targethttpsproxy):
+        """
+        Deletes the specified TargetHttpsProxy resource.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  targethttpsproxy:  Name of the TargetHttpsProxy resource to
+                                   delete.
+        :type   targethttpsproxy: ``str``
+
+        :return  targetHttpsProxy:  Return True if successful.
+        :rtype   targetHttpsProxy: ````bool````
+        """
+
+        request = "/global/targetHttpsProxies/%s" % (targethttpsproxy.name)
+        request_data = {}
+        self.connection.async_request(request, method='DELETE',
+                                      data=request_data)
+
+        return True
+
     def ex_destroy_targetinstance(self, targetinstance):
         """
         Destroy a target instance.
@@ -5109,6 +6726,29 @@ class GCENodeDriver(NodeDriver):
         response = self.connection.request(request, method='GET').object
         return self._to_route(response)
 
+    def ex_destroy_sslcertificate(self, sslcertificate):
+        """
+        Deletes the specified SslCertificate resource.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+
+        :param  sslcertificate:  Name of the SslCertificate resource to
+                                 delete.
+        :type   sslcertificate: ``str``
+
+        :return  sslCertificate:  Return True if successful.
+        :rtype   sslCertificate: ````bool````
+        """
+
+        request = "/global/sslCertificates/%s" % (sslcertificate.name)
+        request_data = {}
+        self.connection.async_request(request, method='DELETE',
+                                      data=request_data)
+
+        return True
+
     def ex_destroy_subnetwork(self, name, region=None):
         """
         Delete a Subnetwork object based on name and region.
@@ -5318,6 +6958,29 @@ class GCENodeDriver(NodeDriver):
         response = self.connection.request(request, method='GET').object
         return self._to_region(response)
 
+    def ex_get_sslcertificate(self, name):
+        """
+        Returns the specified SslCertificate resource. Get a list of available
+        SSL certificates by making a list() request.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+        * https://www.googleapis.com/auth/compute.readonly
+
+        :param  name:  Name of the SslCertificate resource to
+                                 return.
+        :type   name: ``str``
+
+        :return:  `GCESslCertificate` object.
+        :rtype: :class:`GCESslCertificate`
+        """
+
+        request = "/global/sslCertificates/%s" % (name)
+        response = self.connection.request(request, method='GET').object
+
+        return self._to_sslcertificate(response)
+
     def ex_get_targethttpproxy(self, name):
         """
         Return a Target HTTP Proxy object based on its name.
@@ -5332,6 +6995,29 @@ class GCENodeDriver(NodeDriver):
         response = self.connection.request(request, method='GET').object
         return self._to_targethttpproxy(response)
 
+    def ex_get_targethttpsproxy(self, name):
+        """
+        Returns the specified TargetHttpsProxy resource. Get a list of
+        available target HTTPS proxies by making a list() request.
+
+        Scopes needed - one of the following:
+        * https://www.googleapis.com/auth/cloud-platform
+        * https://www.googleapis.com/auth/compute
+        * https://www.googleapis.com/auth/compute.readonly
+
+        :param  name:  Name of the TargetHttpsProxy resource to
+                                   return.
+        :type   name: ``str``
+
+        :return:  `GCETargetHttpsProxy` object.
+        :rtype: :class:`GCETargetHttpsProxy`
+        """
+
+        request = "/global/targetHttpsProxies/%s" % (name)
+        response = self.connection.request(request, method='GET').object
+
+        return self._to_targethttpsproxy(response)
+
     def ex_get_targetinstance(self, name, zone=None):
         """
         Return a TargetInstance object based on a name and optional zone.
@@ -5700,8 +7386,9 @@ class GCENodeDriver(NodeDriver):
             ex_on_host_maintenance=None, ex_automatic_restart=None,
             ex_preemptible=None, ex_subnetwork=None):
         """
-        Returns a request and body to create a new node.  This is a helper
-        method to support both :class:`create_node` and
+        Returns a request and body to create a new node.
+
+        This is a helper method to support both :class:`create_node` and
         :class:`ex_create_multiple_nodes`.
 
         :param  name: The name of the node to create.
@@ -5815,140 +7502,34 @@ class GCENodeDriver(NodeDriver):
         :return:  A tuple containing a request string and a node_data dict.
         :rtype:   ``tuple`` of ``str`` and ``dict``
         """
-        node_data = {}
-        node_data['machineType'] = size.extra['selfLink']
-        node_data['name'] = name
-        if tags:
-            node_data['tags'] = {'items': tags}
-        if metadata:
-            node_data['metadata'] = self._format_metadata(fingerprint='na',
-                                                          metadata=metadata)
-
-        # by default, new instances will match the same serviceAccount and
-        # scope set in the Developers Console and Cloud SDK
-        if not ex_service_accounts:
-            set_scopes = [{
-                'email': 'default',
-                'scopes': [self.AUTH_URL + 'devstorage.read_only']
-            }]
-        elif not isinstance(ex_service_accounts, list):
-            raise ValueError("ex_service_accounts field is not a list.")
-        else:
-            set_scopes = []
-            for sa in ex_service_accounts:
-                if not isinstance(sa, dict):
-                    raise ValueError("ex_service_accounts needs to be a list "
-                                     "of dicts, got: '%s - %s'" %
-                                     (str(type(sa)), str(sa)))
-                if 'email' not in sa:
-                    sa['email'] = 'default'
-                if 'scopes' not in sa:
-                    sa['scopes'] = [self.AUTH_URL + 'devstorage.read_only']
-                ps = []
-                for scope in sa['scopes']:
-                    if scope.startswith(self.AUTH_URL):
-                        ps.append(scope)
-                    elif scope in self.SA_SCOPES_MAP:
-                        ps.append(self.AUTH_URL + self.SA_SCOPES_MAP[scope])
-                    else:
-                        ps.append(self.AUTH_URL + scope)
-                sa['scopes'] = ps
-                set_scopes.append(sa)
-        node_data['serviceAccounts'] = set_scopes
-
-        if boot_disk and ex_disks_gce_struct:
-            raise ValueError("Cannot specify both 'boot_disk' and "
-                             "'ex_disks_gce_s

<TRUNCATED>

[4/4] libcloud git commit: update changes

Posted by er...@apache.org.
update changes


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/c873a0d3
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/c873a0d3
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/c873a0d3

Branch: refs/heads/trunk
Commit: c873a0d34a8188fd5e2475023f1b26fb063ef51e
Parents: cffd964
Author: Eric Johnson <er...@google.com>
Authored: Thu Oct 13 15:13:59 2016 +0000
Committer: Eric Johnson <er...@google.com>
Committed: Thu Oct 13 15:13:59 2016 +0000

----------------------------------------------------------------------
 CHANGES.rst | 4 ++++
 1 file changed, 4 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/c873a0d3/CHANGES.rst
----------------------------------------------------------------------
diff --git a/CHANGES.rst b/CHANGES.rst
index 38fd531..9d834c0 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -12,6 +12,10 @@ Compute
   (GITHUB-897)
   [Anthony Monthe]
 
+- [GCE] Support for HTTP(S) proxies with BackendServices
+  (GITHUB-856
+  [Tom Melendez]
+
 Changes in Apache Libcloud 1.3.0
 --------------------------------
 


[3/4] libcloud git commit: [GCE] Added support for HTTP(S) proxies with BackendServices

Posted by er...@apache.org.
[GCE] Added support for HTTP(S) proxies with BackendServices

Closes #856

Signed-off-by: Eric Johnson <er...@google.com>


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/cffd9642
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/cffd9642
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/cffd9642

Branch: refs/heads/trunk
Commit: cffd96420e9e86f213b7c08d00d548d9096fa7cb
Parents: 065d191
Author: Tom Melendez <su...@google.com>
Authored: Thu Aug 25 23:57:27 2016 +0000
Committer: Eric Johnson <er...@google.com>
Committed: Thu Oct 13 15:12:39 2016 +0000

----------------------------------------------------------------------
 demos/gce_demo.py                               |  237 ++-
 libcloud/compute/drivers/gce.py                 | 1947 ++++++++++++++++--
 .../gce/aggregated_instanceGroupManagers.json   |    8 +-
 .../gce/global_backendServices_web_service.json |    4 +-
 .../fixtures/gce/global_instanceTemplates.json  |   10 +-
 .../gce/global_instanceTemplates_insert.json    |   12 +
 .../fixtures/gce/global_sslcertificates.json    |   16 +
 .../gce/global_sslcertificates_example.json     |   10 +
 .../gce/global_sslcertificates_post.json        |   13 +
 ...eration_global_instanceTemplates_insert.json |   12 +
 ...s_operation_global_sslcertificates_post.json |   13 +
 ...nes_us_central1_a_instanceGroups_insert.json |   13 +
 ...l1_a_instanceGroups_myname_addInstances.json |   13 +
 ...central1_a_instanceGroups_myname_delete.json |   13 +
 ...a_instanceGroups_myname_removeInstances.json |   13 +
 ...1_a_instanceGroups_myname_setNamedPorts.json |   13 +
 ...s-east1_subnetworks_cf_972cf02e6ad49113.json |   11 +
 ...nes_us-central1-a_instanceGroupManagers.json |    4 +-
 ...a_instanceGroupManagers_myinstancegroup.json |    4 +-
 ...entral1-a_instanceGroup_myinstancegroup.json |    4 +-
 ...ntral1-a_instanceGroup_myinstancegroup2.json |   14 +
 ...b_instanceGroupManagers_myinstancegroup.json |    4 +-
 ...entral1-b_instanceGroup_myinstancegroup.json |    2 +-
 .../zones_us-east1-b_instanceGroupManagers.json |    4 +-
 ...s-east1-b_instanceGroup_myinstancegroup.json |    4 +-
 .../gce/zones_us_central1_a_instanceGroups.json |   29 +
 ...nes_us_central1_a_instanceGroups_insert.json |   13 +
 ...nes_us_central1_a_instanceGroups_myname.json |   12 +
 ...l1_a_instanceGroups_myname_addInstances.json |   13 +
 ...central1_a_instanceGroups_myname_delete.json |   13 +
 ...1_a_instanceGroups_myname_listInstances.json |   15 +
 ...a_instanceGroups_myname_removeInstances.json |   13 +
 ...1_a_instanceGroups_myname_setNamedPorts.json |   13 +
 libcloud/test/compute/test_gce.py               |  404 +++-
 34 files changed, 2697 insertions(+), 226 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/cffd9642/demos/gce_demo.py
----------------------------------------------------------------------
diff --git a/demos/gce_demo.py b/demos/gce_demo.py
index 6322c9e..c49e5e6 100755
--- a/demos/gce_demo.py
+++ b/demos/gce_demo.py
@@ -14,7 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
 # This example performs several tasks on Google Compute Platform.  It can be
 # run directly or can be imported into an interactive python session.  This
 # can also serve as live integration tests.
@@ -66,8 +65,8 @@ except ImportError:
     sys.exit(1)
 
 # Add parent dir of this file's dir to sys.path (OS-agnostically)
-sys.path.append(os.path.normpath(os.path.join(os.path.dirname(__file__),
-                                 os.path.pardir)))
+sys.path.append(
+    os.path.normpath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
 
 from libcloud.compute.types import Provider
 from libcloud.compute.providers import get_driver
@@ -92,6 +91,7 @@ DEMO_BASE_NAME = 'lct'
 
 # Datacenter to create resources in
 DATACENTER = 'us-central1-f'
+BACKUP_DATACENTER = 'us-east1-c'
 
 # Clean up resources at the end (can be set to false in order to
 # inspect resources at the end of the run). Resources will be cleaned
@@ -136,6 +136,46 @@ def get_dns_driver(gce_driver=None):
     return driver
 
 
+def create_mig(gce, mig_base_name, zone, template, postfix, num_instances=2):
+    """
+    Creates MIG, sets named ports, modifies various text with 'postfix'.
+
+    :param  gce: An initalized GCE driver.
+    :type   gce: :class`GCENodeDriver`
+
+    :param  zone: Zone to create Managed Instance Group in.
+    :type   zone: :class:`GCEZone` or ``str``
+
+    :param  template: Instance Template to use in creating MIG.
+    :type   template: :class:`GCEInstanceTemplate`
+
+    :param  postfix: string to append to mig name, etc.  Example: 'east',
+                     'central'
+    :type   postfix: ``str``
+
+    :param  num_instances: number of instances to create in MIG.  Default is 2.
+    :type   num_instances: ``int``
+
+    :returns: initialized Managed Instance Group.
+    :rtype: :class:`GCEInstanceGroupManager`
+    """
+    mig_name = '%s-%s' % (mig_base_name, postfix)
+    mig = gce.ex_create_instancegroupmanager(
+        mig_name, zone, template, num_instances, base_instance_name=mig_name,
+        description='Demo for %s' % postfix)
+    display('    Managed Instance Group [%s] "%s" created' % (postfix.upper(),
+                                                              mig.name))
+    display('    ... MIG instances created: %s' %
+            ','.join([x['name'] for x in mig.list_managed_instances()]))
+
+    # set the named_ports on the Instance Group.
+    named_ports = [{'name': '%s-http' % DEMO_BASE_NAME, 'port': 80}]
+    mig.set_named_ports(named_ports=named_ports)
+    display('    ... MIG ports set: %s' % named_ports)
+
+    return mig
+
+
 def display(title, resource_list=[]):
     """
     Display a list of resources.
@@ -214,8 +254,32 @@ def cleanup_only():
     snapshots = gce.ex_list_snapshots()
     display('Snapshots:', snapshots)
 
+    gfrs = gce.ex_list_forwarding_rules(global_rules=True)
+    display("Global Forwarding Rules", gfrs)
+    targetproxies = gce.ex_list_targethttpproxies()
+    display("Target HTTP Proxies", targetproxies)
+    urlmaps = gce.ex_list_urlmaps()
+    display("URLMaps", urlmaps)
+    bes = gce.ex_list_backendservices()
+    display("Backend Services", bes)
+    migs = gce.ex_list_instancegroupmanagers(zone='all')
+    display("Instance Group Managers", migs)
+    its = gce.ex_list_instancetemplates()
+    display("Instance Templates", its)
+    hcs = gce.ex_list_healthchecks()
+    display("Health Checks", hcs)
+
     # == Clean up any old demo resources ==
     display('Cleaning up any "%s" resources' % DEMO_BASE_NAME)
+    clean_up(gce, DEMO_BASE_NAME, None,
+             gfrs + targetproxies + urlmaps + bes + hcs + migs + its)
+
+    # == Pause to let cleanup occur and repopulate volume and node lists ==
+    if len(migs):
+        time.sleep(10)
+        all_volumes = gce.list_volumes(ex_zone='all')
+        all_nodes = gce.list_nodes(ex_zone='all')
+
     clean_up(gce, DEMO_BASE_NAME, all_nodes,
              all_addresses + all_volumes + firewalls + networks + snapshots)
     volumes = gce.list_volumes()
@@ -260,6 +324,8 @@ def clean_up(gce, base_name, node_list=None, resource_list=None):
         if resrc.name.startswith(base_name):
             try:
                 resrc.destroy()
+                class_name = resrc.__class__.__name__
+                display('   Deleted %s (%s)' % (resrc.name, class_name))
             except ResourceNotFoundError:
                 display('   Not found: %s (%s)' % (resrc.name,
                                                    resrc.__class__.__name__))
@@ -380,8 +446,7 @@ def main_compute():
     name = '%s-subnet-node' % DEMO_BASE_NAME
     node_1 = gce.create_node(name, 'g1-small', 'debian-8',
                              ex_disk_auto_delete=True,
-                             ex_network=network_custom,
-                             ex_subnetwork=subnet)
+                             ex_network=network_custom, ex_subnetwork=subnet)
     display('  Node %s created' % name)
 
     # == Destroy instance in custom subnetwork ==
@@ -424,8 +489,7 @@ def main_compute():
                 },
                 "boot": True,
                 "autoDelete": True
-            },
-            {
+            }, {
                 "type": "SCRATCH",
                 "deviceName": '%s-gstruct-lssd' % DEMO_BASE_NAME,
                 "initializeParams": {
@@ -452,7 +516,7 @@ def main_compute():
         display('Stopping node, setting custom size, starting node:')
         name = '%s-np-node' % DEMO_BASE_NAME
         gce.ex_stop_node(node_1)
-        gce.ex_set_machine_type(node_1, 'custom-2-4096')   # 2 vCPU, 4GB RAM
+        gce.ex_set_machine_type(node_1, 'custom-2-4096')  # 2 vCPU, 4GB RAM
         gce.ex_start_node(node_1)
         node_1 = gce.ex_get_node(name)
         display('  %s: state=%s, size=%s' % (name, node_1.extra['status'],
@@ -471,8 +535,7 @@ def main_compute():
         if CLEANUP:
             # == Detach the disk ==
             if gce.detach_volume(volume, ex_node=node_1):
-                display('  Detached %s from %s' % (volume.name,
-                                                   node_1.name))
+                display('  Detached %s from %s' % (volume.name, node_1.name))
 
     # == Create Snapshot ==
     display('Creating a snapshot from existing disk:')
@@ -499,8 +562,7 @@ def main_compute():
     display('  Created %s from snapshot' % volume.name)
     # Create Node with Disk
     node_2 = gce.create_node(name, size, image, ex_tags=['libcloud'],
-                             ex_boot_disk=volume,
-                             ex_disk_auto_delete=False)
+                             ex_boot_disk=volume, ex_disk_auto_delete=False)
     display('  Node %s created with attached disk %s' % (node_2.name,
                                                          volume.name))
 
@@ -525,10 +587,9 @@ def main_compute():
     number = MAX_NODES - 2
     if number > 0:
         display('Creating Multiple Nodes (%s):' % number)
-        multi_nodes = gce.ex_create_multiple_nodes(base_name, size, image,
-                                                   number,
-                                                   ex_tags=['libcloud'],
-                                                   ex_disk_auto_delete=True)
+        multi_nodes = gce.ex_create_multiple_nodes(
+            base_name, size, image, number, ex_tags=['libcloud'],
+            ex_disk_auto_delete=True)
         for node in multi_nodes:
             display('  Node %s created' % node.name)
 
@@ -542,8 +603,7 @@ def main_compute():
     # == Create a Firewall ==
     display('Creating a Firewall:')
     name = '%s-firewall' % DEMO_BASE_NAME
-    allowed = [{'IPProtocol': 'tcp',
-                'ports': ['3141']}]
+    allowed = [{'IPProtocol': 'tcp', 'ports': ['3141']}]
     firewall_1 = gce.ex_create_firewall(name, allowed, network=network_1,
                                         source_tags=['libcloud'])
     display('  Firewall %s created' % firewall_1.name)
@@ -629,20 +689,16 @@ def main_load_balancer():
     size = gce.ex_get_size('n1-standard-1')
     number = 3
     display('Creating %d nodes' % number)
-    metadata = {'items': [{'key': 'startup-script',
-                           'value': startup_script}]}
-    lb_nodes = gce.ex_create_multiple_nodes(base_name, size, image,
-                                            number, ex_tags=[tag],
-                                            ex_metadata=metadata,
-                                            ex_disk_auto_delete=True,
-                                            ignore_errors=False)
+    metadata = {'items': [{'key': 'startup-script', 'value': startup_script}]}
+    lb_nodes = gce.ex_create_multiple_nodes(
+        base_name, size, image, number, ex_tags=[tag], ex_metadata=metadata,
+        ex_disk_auto_delete=True, ignore_errors=False)
     display('Created Nodes', lb_nodes)
 
     # == Create a Firewall for instances ==
     display('Creating a Firewall')
     name = '%s-firewall' % DEMO_BASE_NAME
-    allowed = [{'IPProtocol': 'tcp',
-                'ports': ['80']}]
+    allowed = [{'IPProtocol': 'tcp', 'ports': ['80']}]
     firewall = gce.ex_create_firewall(name, allowed, target_tags=[tag])
     display('    Firewall %s created' % firewall.name)
 
@@ -652,10 +708,9 @@ def main_load_balancer():
 
     # These are all the default values, but listed here as an example.  To
     # create a healthcheck with the defaults, only name is required.
-    hc = gcelb.ex_create_healthcheck(name, host=None, path='/', port='80',
-                                     interval=5, timeout=5,
-                                     unhealthy_threshold=2,
-                                     healthy_threshold=2)
+    hc = gcelb.ex_create_healthcheck(
+        name, host=None, path='/', port='80', interval=5, timeout=5,
+        unhealthy_threshold=2, healthy_threshold=2)
     display('Healthcheck %s created' % hc.name)
 
     # == Create Load Balancer ==
@@ -739,6 +794,114 @@ def main_load_balancer():
     display('Total runtime: %s' % str(end_time - start_time))
 
 
+# ==== BACKEND SERVICE LOAD BALANCER CODE STARTS HERE ====
+def main_backend_service():
+    start_time = datetime.datetime.now()
+    display('Backend Service w/Global Forwarding Rule demo/test start time: %s'
+            % str(start_time))
+    gce = get_gce_driver()
+    # Get project info and print name
+    project = gce.ex_get_project()
+    display('Project: %s' % project.name)
+
+    # Based on the instructions at:
+    # https://cloud.google.com/compute/docs/load-balancing/http/#overview
+
+    zone_central = DATACENTER
+    zone_east = BACKUP_DATACENTER
+    it_name = '%s-instancetemplate' % DEMO_BASE_NAME
+    mig_name = '%s-mig' % DEMO_BASE_NAME
+    hc_name = '%s-healthcheck' % DEMO_BASE_NAME
+    bes_name = '%s-bes' % DEMO_BASE_NAME
+    urlmap_name = '%s-urlmap' % DEMO_BASE_NAME
+    targethttpproxy_name = '%s-httptargetproxy' % DEMO_BASE_NAME
+    address_name = '%s-address' % DEMO_BASE_NAME
+    gfr_name = '%s-gfr' % DEMO_BASE_NAME
+    firewall_name = '%s-firewall' % DEMO_BASE_NAME
+
+    startup_script = ('apt-get -y update && '
+                      'apt-get -y install apache2 && '
+                      'echo "$(hostname)" > /var/www/html/index.html')
+    tag = '%s-mig-www' % DEMO_BASE_NAME
+    metadata = {'items': [{'key': 'startup-script', 'value': startup_script}]}
+
+    mig_central = None
+    mig_east = None
+    bes = None
+    urlmap = None
+    tp = None
+    address = None
+    gfr = None
+    firewall = None
+
+    display('Create a BackendService')
+    # == Create an Instance Template ==
+    it = gce.ex_create_instancetemplate(it_name, size='n1-standard-1',
+                                        image='debian-8', network='default',
+                                        metadata=metadata, tags=[tag])
+    display('    InstanceTemplate "%s" created' % it.name)
+
+    # == Create a MIG ==
+    mig_central = create_mig(gce, mig_name, zone_central, it, 'central')
+    mig_east = create_mig(gce, mig_name, zone_east, it, 'east')
+
+    # == Create a Health Check ==
+    hc = gce.ex_create_healthcheck(hc_name, host=None, path='/', port='80',
+                                   interval=30, timeout=10,
+                                   unhealthy_threshold=10, healthy_threshold=1)
+    display('    Healthcheck %s created' % hc.name)
+
+    # == Create a Backend Service ==
+    be_central = gce.ex_create_backend(
+        instance_group=mig_central.instance_group)
+    be_east = gce.ex_create_backend(instance_group=mig_east.instance_group)
+    bes = gce.ex_create_backendservice(
+        bes_name, [hc], backends=[be_central, be_east], port_name='%s-http' %
+        DEMO_BASE_NAME, protocol='HTTP', description='%s bes desc' %
+        DEMO_BASE_NAME, timeout_sec=60, enable_cdn=False)
+    display('    Backend Service "%s" created' % bes.name)
+
+    # == Create a URLMap ==
+    urlmap = gce.ex_create_urlmap(urlmap_name, default_service=bes)
+    display('    URLMap "%s" created' % urlmap.name)
+
+    # == Create a Target (HTTP) Proxy ==
+    tp = gce.ex_create_targethttpproxy(targethttpproxy_name, urlmap)
+    display('    TargetProxy "%s" created' % tp.name)
+
+    # == Create a Static Address ==
+    address = gce.ex_create_address(address_name, region='global')
+    display('    Address "%s" created with IP "%s"' % (address.name,
+                                                       address.address))
+    # == Create a Global Forwarding Rule ==
+    gfr = gce.ex_create_forwarding_rule(
+        gfr_name, target=tp, address=address, port_range='80',
+        description='%s libcloud forwarding rule http test' % DEMO_BASE_NAME,
+        global_rule=True)
+    display('    Global Forwarding Rule "%s" created' % (gfr.name))
+
+    # == Create a Firewall for instances ==
+    allowed = [{'IPProtocol': 'tcp', 'ports': ['80']}]
+    firewall = gce.ex_create_firewall(firewall_name, allowed,
+                                      target_tags=[tag])
+    display('    Firewall %s created' % firewall.name)
+
+    # TODO(supertom): launch instances to demostrate that it works
+    # take backends out of service.  Adding in this functionality
+    # will also add 10-15 minutes to the demo.
+    #    display("Sleeping for 10 minutes, starting at %s" %
+    #            str(datetime.datetime.now()))
+    #    time.sleep(600)
+
+    if CLEANUP:
+        display('Cleaning up %s resources created' % DEMO_BASE_NAME)
+        clean_up(gce, DEMO_BASE_NAME, None,
+                 resource_list=[firewall, gfr, address, tp, urlmap, bes, hc,
+                                mig_central, mig_east, it])
+    end_time = datetime.datetime.now()
+    display('Total runtime: %s' % str(end_time - start_time))
+
+
 # ==== GOOGLE DNS CODE STARTS HERE ====
 def main_dns():
     start_time = datetime.datetime.now()
@@ -768,17 +931,19 @@ def main_dns():
     end_time = datetime.datetime.now()
     display('Total runtime: %s' % str(end_time - start_time))
 
+
 if __name__ == '__main__':
     parser = argparse.ArgumentParser(
         description='Google Cloud Platform Demo / Live Test Script')
-    parser.add_argument("--compute",
-                        help="perform compute demo / live tests",
+    parser.add_argument("--compute", help="perform compute demo / live tests",
                         dest="compute", action="store_true")
     parser.add_argument("--load-balancer",
                         help="perform load-balancer demo / live tests",
                         dest="lb", action="store_true")
-    parser.add_argument("--dns",
-                        help="perform DNS demo / live tests",
+    parser.add_argument("--backend-service",
+                        help="perform backend-service demo / live tests",
+                        dest="bes", action="store_true")
+    parser.add_argument("--dns", help="perform DNS demo / live tests",
                         dest="dns", action="store_true")
     parser.add_argument("--cleanup-only",
                         help="perform clean-up (skips all tests)",
@@ -794,3 +959,5 @@ if __name__ == '__main__':
             main_load_balancer()
         if cl_args.dns:
             main_dns()
+        if cl_args.bes:
+            main_backend_service()