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 2014/12/09 23:25:29 UTC

libcloud git commit: [google compute] Add TargetInstance resource to GCE driver Closes #393

Repository: libcloud
Updated Branches:
  refs/heads/trunk 416a8fdfb -> 05bbd179b


[google compute] Add TargetInstance resource to GCE driver
Closes #393

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/05bbd179
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/05bbd179
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/05bbd179

Branch: refs/heads/trunk
Commit: 05bbd179bda2d550f91200fa31893e62051286b8
Parents: 416a8fd
Author: Eric Johnson <er...@google.com>
Authored: Tue Nov 18 22:55:29 2014 +0000
Committer: Eric Johnson <er...@google.com>
Committed: Tue Dec 9 22:24:08 2014 +0000

----------------------------------------------------------------------
 CHANGES.rst                                     |   4 +
 libcloud/compute/drivers/gce.py                 | 157 +++++++++++++++++++
 .../gce/aggregated_targetInstances.json         | 127 +++++++++++++++
 ...targetInstances_lctargetinstance_delete.json |  15 ++
 ...ones_us-central1-a_targetInstances_post.json |  14 ++
 .../zones_us-central1-a_targetInstances.json    |  27 ++++
 ...ral1-a_targetInstances_lctargetinstance.json |  10 ++
 ...targetInstances_lctargetinstance_delete.json |  15 ++
 ...ones_us-central1-a_targetInstances_post.json |  14 ++
 libcloud/test/compute/test_gce.py               |  68 ++++++++
 10 files changed, 451 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/05bbd179/CHANGES.rst
----------------------------------------------------------------------
diff --git a/CHANGES.rst b/CHANGES.rst
index 2467a3b..f9c2ffe 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -16,6 +16,10 @@ General
 Compute
 ~~~~~~~
 
+- Adding TargetInstances resource to GCE driver.
+  (GITHUB-393)
+  [Eric Johnson]
+
 - Adding DiskTypes resource to GCE driver.
   (GITHUB-391)
   [Eric Johnson]

http://git-wip-us.apache.org/repos/asf/libcloud/blob/05bbd179/libcloud/compute/drivers/gce.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py
index 1b2851d..826e8c2 100644
--- a/libcloud/compute/drivers/gce.py
+++ b/libcloud/compute/drivers/gce.py
@@ -392,6 +392,31 @@ class GCESnapshot(VolumeSnapshot):
         super(GCESnapshot, self).__init__(id, driver, size, extra)
 
 
+class GCETargetInstance(UuidMixin):
+    def __init__(self, id, name, zone, node, driver, extra=None):
+        self.id = str(id)
+        self.name = name
+        self.zone = zone
+        self.node = node
+        self.driver = driver
+        self.extra = extra
+        UuidMixin.__init__(self)
+
+    def destroy(self):
+        """
+        Destroy this Target Instance
+
+        :return:  True if successful
+        :rtype:   ``bool``
+        """
+        return self.driver.ex_destroy_targetinstance(targetinstance=self)
+
+    def __repr__(self):
+        return '<GCETargetInstance id="%s" name="%s" zone="%s" node="%s">' % (
+            self.id, self.name, self.zone.name,
+            (hasattr(self.node, 'name') and self.node.name or self.node))
+
+
 class GCETargetPool(UuidMixin):
     def __init__(self, id, name, region, healthchecks, nodes, driver,
                  extra=None):
@@ -979,6 +1004,33 @@ class GCENodeDriver(NodeDriver):
                           response.get('items', [])]
         return list_snapshots
 
+    def ex_list_targetinstances(self, zone=None):
+        """
+        Return the list of target instances.
+
+        :return:  A list of target instance objects
+        :rtype:   ``list`` of :class:`GCETargetInstance`
+        """
+        list_targetinstances = []
+        zone = self._set_zone(zone)
+        if zone is None:
+            request = '/aggregated/targetInstances'
+        else:
+            request = '/zones/%s/targetInstances' % (zone.name)
+        response = self.connection.request(request, method='GET').object
+
+        if 'items' in response:
+            # The aggregated result returns dictionaries for each region
+            if zone is None:
+                for v in response['items'].values():
+                    zone_targetinstances = [self._to_targetinstance(t) for t in
+                                            v.get('targetInstances', [])]
+                    list_targetinstances.extend(zone_targetinstances)
+            else:
+                list_targetinstances = [self._to_targetinstance(t) for t in
+                                        response['items']]
+        return list_targetinstances
+
     def ex_list_targetpools(self, region=None):
         """
         Return the list of target pools.
@@ -1721,6 +1773,49 @@ class GCENodeDriver(NodeDriver):
             node_list.append(status['node'])
         return node_list
 
+    def ex_create_targetinstance(self, name, zone=None, node=None,
+                                 description=None, nat_policy="NO_NAT"):
+        """
+        Create a target instance.
+
+        :param  name: Name of target instance
+        :type   name: ``str``
+
+        :keyword  region: Zone to create the target pool in. Defaults to
+                          self.zone
+        :type     region: ``str`` or :class:`GCEZone` or ``None``
+
+        :keyword  node: The actual instance to be used as the traffic target.
+        :type     node: ``str`` or :class:`Node`
+
+        :keyword  description: A text description for the target instance
+        :type     description: ``str`` or ``None``
+
+        :keyword  nat_policy: The NAT option for how IPs are NAT'd to the node.
+        :type     nat_policy: ``str``
+
+        :return:  Target Instance object
+        :rtype:   :class:`GCETargetInstance`
+        """
+        zone = zone or self.zone
+        targetinstance_data = {}
+        targetinstance_data['name'] = name
+        if not hasattr(zone, 'name'):
+            zone = self.ex_get_zone(zone)
+        targetinstance_data['zone'] = zone.extra['selfLink']
+        if node is not None:
+            if not hasattr(node, 'name'):
+                node = self.ex_get_node(node, zone)
+            targetinstance_data['instance'] = node.extra['selfLink']
+        targetinstance_data['natPolicy'] = nat_policy
+        if description:
+            targetinstance_data['description'] = description
+
+        request = '/zones/%s/targetInstances' % (zone.name)
+        self.connection.async_request(request, method='POST',
+                                      data=targetinstance_data)
+        return self.ex_get_targetinstance(name, zone)
+
     def ex_create_targetpool(self, name, region=None, healthchecks=None,
                              nodes=None, session_affinity=None):
         """
@@ -2646,6 +2741,21 @@ class GCENodeDriver(NodeDriver):
             success.append(s)
         return success
 
+    def ex_destroy_targetinstance(self, targetinstance):
+        """
+        Destroy a target instance.
+
+        :param  targetinstance: TargetInstance object to destroy
+        :type   targetinstance: :class:`GCETargetInstance`
+
+        :return:  True if successful
+        :rtype:   ``bool``
+        """
+        request = '/zones/%s/targetInstances/%s' % (targetinstance.zone.name,
+                                                    targetinstance.name)
+        self.connection.async_request(request, method='DELETE')
+        return True
+
     def ex_destroy_targetpool(self, targetpool):
         """
         Destroy a target pool.
@@ -2958,6 +3068,26 @@ class GCENodeDriver(NodeDriver):
         response = self.connection.request(request, method='GET').object
         return self._to_region(response)
 
+    def ex_get_targetinstance(self, name, zone=None):
+        """
+        Return a TargetInstance object based on a name and optional zone.
+
+        :param  name: The name of the target instance
+        :type   name: ``str``
+
+        :keyword  zone: The zone to search for the target instance in (set to
+                          'all' to search all zones).
+        :type     zone: ``str`` or :class:`GCEZone` or ``None``
+
+        :return:  A TargetInstance object for the instance
+        :rtype:   :class:`GCETargetInstance`
+        """
+        zone = self._set_zone(zone) or self._find_zone_or_region(
+            name, 'targetInstances', res_name='TargetInstance')
+        request = '/zones/%s/targetInstances/%s' % (zone.name, name)
+        response = self.connection.request(request, method='GET').object
+        return self._to_targetinstance(response)
+
     def ex_get_targetpool(self, name, region=None):
         """
         Return a TargetPool object based on a name and optional region.
@@ -3971,6 +4101,33 @@ class GCENodeDriver(NodeDriver):
         return StorageVolume(id=volume['id'], name=volume['name'],
                              size=volume['sizeGb'], driver=self, extra=extra)
 
+    def _to_targetinstance(self, targetinstance):
+        """
+        Return a Target Instance object from the json-response dictionary.
+
+        :param  targetinstance: The dictionary describing the target instance.
+        :type   targetinstance: ``dict``
+
+        :return: Target Instance object
+        :rtype:  :class:`GCETargetInstance`
+        """
+        node = None
+        extra = {}
+        extra['selfLink'] = targetinstance.get('selfLink')
+        extra['description'] = targetinstance.get('description')
+        extra['natPolicy'] = targetinstance.get('natPolicy')
+        zone = self.ex_get_zone(targetinstance['zone'])
+        if 'instance' in targetinstance:
+            node_name = targetinstance['instance'].split('/')[-1]
+            try:
+                node = self.ex_get_node(node_name, zone)
+            except ResourceNotFoundError:
+                node = targetinstance['instance']
+
+        return GCETargetInstance(id=targetinstance['id'],
+                                 name=targetinstance['name'], zone=zone,
+                                 node=node, driver=self, extra=extra)
+
     def _to_targetpool(self, targetpool):
         """
         Return a Target Pool object from the json-response dictionary.

http://git-wip-us.apache.org/repos/asf/libcloud/blob/05bbd179/libcloud/test/compute/fixtures/gce/aggregated_targetInstances.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/aggregated_targetInstances.json b/libcloud/test/compute/fixtures/gce/aggregated_targetInstances.json
new file mode 100644
index 0000000..e289c44
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/aggregated_targetInstances.json
@@ -0,0 +1,127 @@
+{
+ "kind": "compute#targetInstanceAggregatedList",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/aggregated/targetInstances",
+ "id": "projects/project_name/aggregated/targetInstances",
+ "items": {
+  "zones/asia-east1-a": {
+   "warning": {
+    "code": "NO_RESULTS_ON_PAGE",
+    "message": "There are no results for scope 'zones/asia-east1-a' on this page.",
+    "data": [
+     {
+      "key": "scope",
+      "value": "zones/asia-east1-a"
+     }
+    ]
+   }
+  },
+  "zones/asia-east1-b": {
+   "warning": {
+    "code": "NO_RESULTS_ON_PAGE",
+    "message": "There are no results for scope 'zones/asia-east1-b' on this page.",
+    "data": [
+     {
+      "key": "scope",
+      "value": "zones/asia-east1-b"
+     }
+    ]
+   }
+  },
+  "zones/asia-east1-c": {
+   "warning": {
+    "code": "NO_RESULTS_ON_PAGE",
+    "message": "There are no results for scope 'zones/asia-east1-c' on this page.",
+    "data": [
+     {
+      "key": "scope",
+      "value": "zones/asia-east1-c"
+     }
+    ]
+   }
+  },
+  "zones/europe-west1-a": {
+   "warning": {
+    "code": "NO_RESULTS_ON_PAGE",
+    "message": "There are no results for scope 'zones/europe-west1-a' on this page.",
+    "data": [
+     {
+      "key": "scope",
+      "value": "zones/europe-west1-a"
+     }
+    ]
+   }
+  },
+  "zones/europe-west1-b": {
+   "warning": {
+    "code": "NO_RESULTS_ON_PAGE",
+    "message": "There are no results for scope 'zones/europe-west1-b' on this page.",
+    "data": [
+     {
+      "key": "scope",
+      "value": "zones/europe-west1-b"
+     }
+    ]
+   }
+  },
+  "zones/europe-west1-c": {
+   "warning": {
+    "code": "NO_RESULTS_ON_PAGE",
+    "message": "There are no results for scope 'zones/europe-west1-c' on this page.",
+    "data": [
+     {
+      "key": "scope",
+      "value": "zones/europe-west1-c"
+     }
+    ]
+   }
+  },
+  "zones/us-central1-a": {
+   "targetInstances": [
+    {
+     "kind": "compute#targetInstance",
+     "id": "8092539649535704539",
+     "creationTimestamp": "2014-08-07T12:46:10.372-07:00",
+     "name": "hello",
+     "zone": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a",
+     "natPolicy": "NO_NAT",
+     "instance": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instances/node-name",
+     "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/targetInstances/hello"
+    },
+    {
+     "kind": "compute#targetInstance",
+     "id": "9539205115599811578",
+     "creationTimestamp": "2014-08-07T13:09:19.634-07:00",
+     "name": "lctargetinstance",
+     "zone": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a",
+     "natPolicy": "NO_NAT",
+     "instance": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instances/node-name",
+     "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/targetInstances/lctargetinstance"
+    }
+   ]
+  },
+  "zones/us-central1-b": {
+   "warning": {
+    "code": "NO_RESULTS_ON_PAGE",
+    "message": "There are no results for scope 'zones/us-central1-b' on this page.",
+    "data": [
+     {
+      "key": "scope",
+      "value": "zones/us-central1-b"
+     }
+    ]
+   }
+  },
+  "zones/us-central1-f": {
+   "warning": {
+    "code": "NO_RESULTS_ON_PAGE",
+    "message": "There are no results for scope 'zones/us-central1-f' on this page.",
+    "data": [
+     {
+      "key": "scope",
+      "value": "zones/us-central1-f"
+     }
+    ]
+   }
+  }
+ }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/05bbd179/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_targetInstances_lctargetinstance_delete.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_targetInstances_lctargetinstance_delete.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_targetInstances_lctargetinstance_delete.json
new file mode 100644
index 0000000..6838d23
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_targetInstances_lctargetinstance_delete.json
@@ -0,0 +1,15 @@
+{
+ "kind": "compute#operation",
+ "id": "16305469717066123402",
+ "name": "operation-zones_us-central1-a_targetInstances_lctargetinstance_delete",
+ "zone": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a",
+ "operationType": "delete",
+ "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/targetInstances/lctargetinstance",
+ "targetId": "8092539649535704539",
+ "status": "DONE",
+ "user": "erjohnso@google.com",
+ "progress": 100,
+ "insertTime": "2014-11-14T13:05:18.564-08:00",
+ "startTime": "2014-11-14T13:05:18.868-08:00",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_targetInstances_lctargetinstance_delete"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/05bbd179/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_targetInstances_post.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_targetInstances_post.json b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_targetInstances_post.json
new file mode 100644
index 0000000..e8110f7
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_zones_us-central1-a_targetInstances_post.json
@@ -0,0 +1,14 @@
+{
+ "kind": "compute#operation",
+ "id": "17976948162128740230",
+ "name": "operation-zones_us-central1-a_targetInstances_post",
+ "zone": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a",
+ "operationType": "insert",
+ "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/targetInstances/lctargetinstance",
+ "status": "DONE",
+ "user": "erjohnso@google.com",
+ "progress": 100,
+ "insertTime": "2014-11-14T13:21:20.789-08:00",
+ "startTime": "2014-11-14T13:21:21.118-08:00",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_targetInstances_post"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/05bbd179/libcloud/test/compute/fixtures/gce/zones_us-central1-a_targetInstances.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_targetInstances.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_targetInstances.json
new file mode 100644
index 0000000..84ca186
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_targetInstances.json
@@ -0,0 +1,27 @@
+{
+ "kind": "compute#targetInstanceList",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/targetInstances",
+ "id": "projects/project_name/zones/us-central1-a/targetInstances",
+ "items": [
+  {
+   "kind": "compute#targetInstance",
+   "id": "8092539649535704539",
+   "creationTimestamp": "2014-08-07T12:46:10.372-07:00",
+   "name": "hello",
+   "zone": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a",
+   "natPolicy": "NO_NAT",
+   "instance": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instances/node-name",
+   "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/targetInstances/hello"
+  },
+  {
+   "kind": "compute#targetInstance",
+   "id": "9539205115599811578",
+   "creationTimestamp": "2014-08-07T13:09:19.634-07:00",
+   "name": "lctargetinstance",
+   "zone": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a",
+   "natPolicy": "NO_NAT",
+   "instance": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instances/node-name",
+   "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/targetInstances/lctargetinstance"
+  }
+ ]
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/05bbd179/libcloud/test/compute/fixtures/gce/zones_us-central1-a_targetInstances_lctargetinstance.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_targetInstances_lctargetinstance.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_targetInstances_lctargetinstance.json
new file mode 100644
index 0000000..8708886
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_targetInstances_lctargetinstance.json
@@ -0,0 +1,10 @@
+{
+ "kind": "compute#targetInstance",
+ "id": "9539205115599811578",
+ "creationTimestamp": "2014-08-07T13:09:19.634-07:00",
+ "name": "lctargetinstance",
+ "zone": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a",
+ "natPolicy": "NO_NAT",
+ "instance": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/instances/node-name",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/targetInstances/lctargetinstance"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/05bbd179/libcloud/test/compute/fixtures/gce/zones_us-central1-a_targetInstances_lctargetinstance_delete.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_targetInstances_lctargetinstance_delete.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_targetInstances_lctargetinstance_delete.json
new file mode 100644
index 0000000..38ea12a
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_targetInstances_lctargetinstance_delete.json
@@ -0,0 +1,15 @@
+{
+ "kind": "compute#operation",
+ "id": "16305469717066123402",
+ "name": "operation-zones_us-central1-a_targetInstances_lctargetinstance_delete",
+ "zone": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a",
+ "operationType": "delete",
+ "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/targetInstances/lctargetinstance",
+ "targetId": "8092539649535704539",
+ "status": "PENDING",
+ "user": "erjohnso@google.com",
+ "progress": 0,
+ "insertTime": "2014-11-14T13:05:18.564-08:00",
+ "startTime": "2014-11-14T13:05:18.868-08:00",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/operations/operation-zones_us-central1-a_targetInstances_lctargetinstance_delete"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/05bbd179/libcloud/test/compute/fixtures/gce/zones_us-central1-a_targetInstances_post.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/zones_us-central1-a_targetInstances_post.json b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_targetInstances_post.json
new file mode 100644
index 0000000..2a31802
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/zones_us-central1-a_targetInstances_post.json
@@ -0,0 +1,14 @@
+{
+  "id": "7487852523793007955",
+  "insertTime": "2013-09-03T00:51:05.064-07:00",
+  "kind": "compute#operation",
+  "name": "operation-zones_us-central1-a_targetInstances_post",
+  "operationType": "insert",
+  "progress": 0,
+  "region": "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/operations/operation-zones_us-central1-a_targetInstances_post",
+  "startTime": "2013-09-03T00:51:05.115-07:00",
+  "status": "PENDING",
+  "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/zones/us-central1-a/targetInstances/lctargetinstance",
+  "user": "user@gserviceaccount.com"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/05bbd179/libcloud/test/compute/test_gce.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_gce.py b/libcloud/test/compute/test_gce.py
index f0e399b..d63b79e 100644
--- a/libcloud/test/compute/test_gce.py
+++ b/libcloud/test/compute/test_gce.py
@@ -190,6 +190,18 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         self.assertEqual(len(snapshots), 2)
         self.assertEqual(snapshots[0].name, 'lcsnapshot')
 
+    def test_ex_list_targetinstances(self):
+        target_instances = self.driver.ex_list_targetinstances()
+        target_instances_all = self.driver.ex_list_targetinstances('all')
+        target_instances_uc1 = self.driver.ex_list_targetinstances('us-central1-a')
+        self.assertEqual(len(target_instances), 2)
+        self.assertEqual(len(target_instances_all), 2)
+        self.assertEqual(len(target_instances_uc1), 2)
+        self.assertEqual(target_instances[0].name, 'hello')
+        self.assertEqual(target_instances_uc1[0].name, 'hello')
+        names = [t.name for t in target_instances_all]
+        self.assertTrue('lctargetinstance' in names)
+
     def test_ex_list_targetpools(self):
         target_pools = self.driver.ex_list_targetpools()
         target_pools_all = self.driver.ex_list_targetpools('all')
@@ -428,6 +440,15 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         self.assertEqual(nodes[0].name, '%s-000' % base_name)
         self.assertEqual(nodes[1].name, '%s-001' % base_name)
 
+    def test_ex_create_targetinstance(self):
+        targetinstance_name = 'lctargetinstance'
+        zone = 'us-central1-a'
+        node = self.driver.ex_get_node('node-name', zone)
+        targetinstance = self.driver.ex_create_targetinstance(
+            targetinstance_name, zone=zone, node=node)
+        self.assertEqual(targetinstance.name, targetinstance_name)
+        self.assertEqual(targetinstance.zone.name, zone)
+
     def test_ex_create_targetpool(self):
         targetpool_name = 'lctargetpool'
         region = 'us-central1'
@@ -627,6 +648,12 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         for d in destroyed:
             self.assertTrue(d)
 
+    def test_destroy_targetinstance(self):
+        targetinstance = self.driver.ex_get_targetinstance('lctargetinstance')
+        self.assertEqual(targetinstance.name, 'lctargetinstance')
+        destroyed = targetinstance.destroy()
+        self.assertTrue(destroyed)
+
     def test_destroy_targetpool(self):
         targetpool = self.driver.ex_get_targetpool('lctargetpool')
         destroyed = targetpool.destroy()
@@ -763,6 +790,12 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         self.assertEqual(size.ram, 3840)
         self.assertEqual(size.extra['guestCpus'], 1)
 
+    def test_ex_get_targetinstance(self):
+        targetinstance_name = 'lctargetinstance'
+        targetinstance = self.driver.ex_get_targetinstance(targetinstance_name)
+        self.assertEqual(targetinstance.name, targetinstance_name)
+        self.assertEqual(targetinstance.zone.name, 'us-central1-a')
+
     def test_ex_get_targetpool(self):
         targetpool_name = 'lctargetpool'
         targetpool = self.driver.ex_get_targetpool(targetpool_name)
@@ -850,6 +883,10 @@ class GCEMockHttp(MockHttpTestCase):
         body = self.fixtures.load('aggregated_machineTypes.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _aggregated_targetInstances(self, method, url, body, headers):
+        body = self.fixtures.load('aggregated_targetInstances.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
     def _aggregated_targetPools(self, method, url, body, headers):
         body = self.fixtures.load('aggregated_targetPools.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
@@ -1087,12 +1124,24 @@ class GCEMockHttp(MockHttpTestCase):
             'operations_operation_regions_us-central1_forwardingRules_lcforwardingrule_delete.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_targetInstances_post(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_zones_us-central1-a_targetInstances_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
     def _regions_us_central1_operations_operation_regions_us_central1_targetPools_post(
             self, method, url, body, headers):
         body = self.fixtures.load(
             'operations_operation_regions_us-central1_targetPools_post.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _zones_us_central1_a_operations_operation_zones_us_central1_a_targetInstances_lctargetinstance_delete(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_zones_us-central1-a_targetInstances_lctargetinstance_delete.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
     def _regions_us_central1_operations_operation_regions_us_central1_targetPools_lctargetpool_delete(
             self, method, url, body, headers):
         body = self.fixtures.load(
@@ -1279,6 +1328,14 @@ class GCEMockHttp(MockHttpTestCase):
                 'regions_us-central1_forwardingRules_lcforwardingrule.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _zones_us_central1_a_targetInstances(self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load(
+                'zones_us-central1-a_targetInstances_post.json')
+        else:
+            body = self.fixtures.load('zones_us-central1-a_targetInstances.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
     def _regions_us_central1_targetPools(self, method, url, body, headers):
         if method == 'POST':
             body = self.fixtures.load(
@@ -1287,6 +1344,17 @@ class GCEMockHttp(MockHttpTestCase):
             body = self.fixtures.load('regions_us-central1_targetPools.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _zones_us_central1_a_targetInstances_lctargetinstance(self, method,
+                                                              url, body,
+                                                              headers):
+        if method == 'DELETE':
+            body = self.fixtures.load(
+                'zones_us-central1-a_targetInstances_lctargetinstance_delete.json')
+        else:
+            body = self.fixtures.load(
+                'zones_us-central1-a_targetInstances_lctargetinstance.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
     def _regions_us_central1_targetPools_lctargetpool(self, method, url,
                                                       body, headers):
         if method == 'DELETE':