libcloud-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From erjoh...@apache.org
Subject libcloud git commit: [google compute] Projects support common instance metadata, usage buckets Closes #409
Date Thu, 11 Dec 2014 19:17:15 GMT
Repository: libcloud
Updated Branches:
  refs/heads/trunk 4d11377bd -> 791ff6a46


[google compute] Projects support common instance metadata, usage buckets
Closes #409

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


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

Branch: refs/heads/trunk
Commit: 791ff6a469397de2973ce16cdbbc10f20cbd1659
Parents: 4d11377
Author: Eric Johnson <erjohnso@google.com>
Authored: Tue Dec 2 22:13:57 2014 +0000
Committer: Eric Johnson <erjohnso@google.com>
Committed: Thu Dec 11 19:16:22 2014 +0000

----------------------------------------------------------------------
 CHANGES.rst                                     |   4 +
 libcloud/compute/drivers/gce.py                 | 184 +++++++++++++++++++
 ...ons_operation_setCommonInstanceMetadata.json |  15 ++
 ...erations_operation_setUsageExportBucket.json |  14 ++
 libcloud/test/compute/fixtures/gce/project.json | 155 ++++++++++------
 .../gce/setCommonInstanceMetadata_post.json     |  15 ++
 .../fixtures/gce/setUsageExportBucket_post.json |  14 ++
 libcloud/test/compute/test_gce.py               | 117 +++++++++++-
 8 files changed, 458 insertions(+), 60 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/791ff6a4/CHANGES.rst
----------------------------------------------------------------------
diff --git a/CHANGES.rst b/CHANGES.rst
index 39d3a54..c7a0aba 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -16,6 +16,10 @@ General
 Compute
 ~~~~~~~
 
+- GCE Projects support common instance metadata and usage export buckets
+  (GITHUB-409)
+  [Eric Johnson]
+
 - Improvements to TargetPool resource in GCE driver.
   (GITHUB-414)
   [Eric Johnson]

http://git-wip-us.apache.org/repos/asf/libcloud/blob/791ff6a4/libcloud/compute/drivers/gce.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py
index 6ee5c6e..f44945e 100644
--- a/libcloud/compute/drivers/gce.py
+++ b/libcloud/compute/drivers/gce.py
@@ -363,6 +363,54 @@ class GCEProject(UuidMixin):
         self.extra = extra
         UuidMixin.__init__(self)
 
+    def set_common_instance_metadata(self, metadata=None, force=False):
+        """
+        Set common instance metadata for the project. Common uses
+        are for setting 'sshKeys', or setting a project-wide
+        'startup-script' for all nodes (instances).  Passing in
+        ``None`` for the 'metadata' parameter will clear out all common
+        instance metadata *except* for 'sshKeys'. If you also want to
+        update 'sshKeys', set the 'force' paramater to ``True``.
+
+        :param  metadata: Dictionay of metadata. Can be either a standard
+                          python dictionary, or the format expected by
+                          GCE (e.g. {'items': [{'key': k1, 'value': v1}, ...}]
+        :type   metadata: ``dict`` or ``None``
+
+        :param  force: Force update of 'sshKeys'. If force is ``False`` (the
+                       default), existing sshKeys will be retained. Setting
+                       force to ``True`` will either replace sshKeys if a new
+                       a new value is supplied, or deleted if no new value
+                       is supplied.
+        :type   force: ``bool``
+
+        :return: True if successful
+        :rtype:  ``bool``
+        """
+        return self.driver.ex_set_common_instance_metadata(self, metadata)
+
+    def set_usage_export_bucket(self, bucket, prefix=None):
+        """
+        Used to retain Compute Engine resource usage, storing the CSV data in
+        a Google Cloud Storage bucket. See the
+        `docs <https://cloud.google.com/compute/docs/usage-export>`_ for more
+        information. Please ensure you have followed the necessary setup steps
+        prior to enabling this feature (e.g. bucket exists, ACLs are in place,
+        etc.)
+
+        :param  bucket: Name of the Google Cloud Storage bucket. Specify the
+                        name in either 'gs://<bucket_name>' or the full URL
+                        'https://storage.googleapis.com/<bucket_name>'.
+        :type   bucket: ``str``
+
+        :param  prefix: Optional prefix string for all reports.
+        :type   prefix: ``str`` or ``None``
+
+        :return: True if successful
+        :rtype:  ``bool``
+        """
+        return self.driver.ex_set_usage_export_bucket(self, bucket, prefix)
+
     def __repr__(self):
         return '<GCEProject id="%s" name="%s">' % (self.id, self.name)
 
@@ -770,6 +818,92 @@ class GCENodeDriver(NodeDriver):
                                   response['items']]
         return list_disktypes
 
+    def ex_set_usage_export_bucket(self, bucket, prefix=None):
+        """
+        Used to retain Compute Engine resource usage, storing the CSV data in
+        a Google Cloud Storage bucket. See the
+        `docs <https://cloud.google.com/compute/docs/usage-export>`_ for more
+        information. Please ensure you have followed the necessary setup steps
+        prior to enabling this feature (e.g. bucket exists, ACLs are in place,
+        etc.)
+
+        :param  bucket: Name of the Google Cloud Storage bucket. Specify the
+                        name in either 'gs://<bucket_name>' or the full URL
+                        'https://storage.googleapis.com/<bucket_name>'.
+        :type   bucket: ``str``
+
+        :param  prefix: Optional prefix string for all reports.
+        :type   prefix: ``str`` or ``None``
+
+        :return: True if successful
+        :rtype:  ``bool``
+        """
+        if bucket.startswith('https://www.googleapis.com/') or \
+                bucket.startswith('gs://'):
+            data = {'bucketName': bucket}
+        else:
+            raise ValueError("Invalid bucket name: %s" % bucket)
+        if prefix:
+            data['reportNamePrefix'] = prefix
+
+        request = '/setUsageExportBucket'
+        self.connection.async_request(request, method='POST', data=data)
+        return True
+
+    def ex_set_common_instance_metadata(self, metadata=None, force=False):
+        """
+        Set common instance metadata for the project. Common uses
+        are for setting 'sshKeys', or setting a project-wide
+        'startup-script' for all nodes (instances).  Passing in
+        ``None`` for the 'metadata' parameter will clear out all common
+        instance metadata *except* for 'sshKeys'. If you also want to
+        update 'sshKeys', set the 'force' paramater to ``True``.
+
+        :param  metadata: Dictionay of metadata. Can be either a standard
+                          python dictionary, or the format expected by
+                          GCE (e.g. {'items': [{'key': k1, 'value': v1}, ...}]
+        :type   metadata: ``dict`` or ``None``
+
+        :param  force: Force update of 'sshKeys'. If force is ``False`` (the
+                       default), existing sshKeys will be retained. Setting
+                       force to ``True`` will either replace sshKeys if a new
+                       a new value is supplied, or deleted if no new value
+                       is supplied.
+        :type   force: ``bool``
+
+        :return: True if successful
+        :rtype:  ``bool``
+        """
+        if metadata:
+            if not isinstance(metadata, dict):
+                raise ValueError("Metadata must be a python dictionary.")
+
+            if 'items' not in metadata:
+                items = []
+                for k, v in metadata.items():
+                    items.append({'key': k, 'value': v})
+                metadata = {'items': items}
+            elif not isinstance(metadata['items'], list):
+                raise ValueError("Invalid GCE metadata format.")
+
+        request = '/setCommonInstanceMetadata'
+
+        project = self.ex_get_project()
+        current_metadata = project.extra['commonInstanceMetadata']
+        fingerprint = current_metadata['fingerprint']
+
+        # grab copy of current 'sshKeys' in case we want to retain them
+        current_keys = ""
+        for md in current_metadata['items']:
+            if md['key'] == 'sshKeys':
+                current_keys = md['value']
+
+        new_md = self._set_project_metadata(metadata, force, current_keys)
+
+        md = {'fingerprint': fingerprint, 'items': new_md}
+        self.connection.async_request(request, method='POST', data=md)
+        return True
+
     def ex_list_addresses(self, region=None):
         """
         Return a list of static addresses for a region, 'global', or all.
@@ -4142,6 +4276,11 @@ class GCENodeDriver(NodeDriver):
         extra['creationTimestamp'] = project.get('creationTimestamp')
         extra['description'] = project.get('description')
         metadata = project['commonInstanceMetadata'].get('items')
+        if 'commonInstanceMetadata' in project:
+            # add this struct to get 'fingerprint' too
+            extra['commonInstanceMetadata'] = project['commonInstanceMetadata']
+        if 'usageExportLocation' in project:
+            extra['usageExportLocation'] = project['usageExportLocation']
 
         return GCEProject(id=project['id'], name=project['name'],
                           metadata=metadata, quotas=project.get('quotas'),
@@ -4313,3 +4452,48 @@ class GCENodeDriver(NodeDriver):
         return GCEZone(id=zone['id'], name=zone['name'], status=zone['status'],
                        maintenance_windows=zone.get('maintenanceWindows'),
                        deprecated=deprecated, driver=self, extra=extra)
+
+    def _set_project_metadata(self, metadata=None, force=False,
+                              current_keys=""):
+        """
+        Return the GCE-friendly dictionary of metadata with/without an
+        entry for 'sshKeys' based on params for 'force' and 'current_keys'.
+        This method was added to simplify the set_common_instance_metadata
+        method and make it easier to test.
+
+        :param  metadata: The GCE-formatted dict (e.g. 'items' list of dicts)
+        :type   metadata: ``dict`` or ``None``
+
+        :param  force: Flag to specify user preference for keeping current_keys
+        :type   force: ``bool``
+
+        :param  current_keys: The value, if any, of existing 'sshKeys'
+        :type   current_keys: ``str``
+
+        :return: GCE-friendly metadata dict
+        :rtype:  ``dict``
+        """
+        if metadata is None:
+            # User wants to delete metdata, but if 'force' is False
+            # and we already have sshKeys, we should retain them.
+            # Otherwise, delete ALL THE THINGS!
+            if not force and current_keys:
+                new_md = [{'key': 'sshKeys', 'value': current_keys}]
+            else:
+                new_md = []
+        else:
+            # User is providing new metadata. If 'force' is False, they
+            # want to preserve existing sshKeys, otherwise 'force' is True
+            # and the user wants to add/replace sshKeys.
+            new_md = metadata['items']
+            if not force and current_keys:
+                # not sure how duplicate keys would be resolved, so ensure
+                # existing 'sshKeys' entry is removed.
+                updated_md = []
+                for d in new_md:
+                    if d['key'] != 'sshKeys':
+                        updated_md.append({'key': d['key'],
+                                          'value': d['value']})
+                new_md = updated_md
+                new_md.append({'key': 'sshKeys', 'value': current_keys})
+        return new_md

http://git-wip-us.apache.org/repos/asf/libcloud/blob/791ff6a4/libcloud/test/compute/fixtures/gce/operations_operation_setCommonInstanceMetadata.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_setCommonInstanceMetadata.json
b/libcloud/test/compute/fixtures/gce/operations_operation_setCommonInstanceMetadata.json
new file mode 100644
index 0000000..63f95ff
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_setCommonInstanceMetadata.json
@@ -0,0 +1,15 @@
+{
+  "endTime": "2013-06-26T10:05:07.630-07:00",
+  "id": "3681664092089171723",
+  "insertTime": "2013-06-26T10:05:03.271-07:00",
+  "kind": "compute#operation",
+  "name": "operation-setCommonInstanceMetadat",
+  "operationType": "insert",
+  "progress": 100,
+  "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation-setCommonInstanceMetadata",
+  "startTime": "2013-06-26T10:05:03.315-07:00",
+  "status": "DONE",
+  "targetId": "16211908079305042870",
+  "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/setCommonInstanceMetadata",
+  "user": "foo@developer.gserviceaccount.com"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/791ff6a4/libcloud/test/compute/fixtures/gce/operations_operation_setUsageExportBucket.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_setUsageExportBucket.json
b/libcloud/test/compute/fixtures/gce/operations_operation_setUsageExportBucket.json
new file mode 100644
index 0000000..5fa9902
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/operations_operation_setUsageExportBucket.json
@@ -0,0 +1,14 @@
+{
+ "kind": "compute#operation",
+ "id": "17203609782824174066",
+ "name": "operation-setUsageExportBucket",
+ "operationType": "setUsageExportBucket",
+ "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name",
+ "targetId": "8116069320260064853",
+ "status": "DONE",
+ "user": "erjohnso@google.com",
+ "progress": 100,
+ "insertTime": "2014-11-21T06:58:03.602-08:00",
+ "startTime": "2014-11-21T06:58:04.018-08:00",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation-setUsageExportBucket"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/791ff6a4/libcloud/test/compute/fixtures/gce/project.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/project.json b/libcloud/test/compute/fixtures/gce/project.json
index 6b65499..b50e40f 100644
--- a/libcloud/test/compute/fixtures/gce/project.json
+++ b/libcloud/test/compute/fixtures/gce/project.json
@@ -1,59 +1,98 @@
 {
-  "commonInstanceMetadata": {
-    "items": [
-      {
-        "key": "sshKeys",
-        "value": "ASDFASDF"
-      }
-    ],
-    "kind": "compute#metadata"
-  },
-  "creationTimestamp": "2013-02-05T16:19:20.516-08:00",
-  "description": "",
-  "id": "2193465259114366848",
-  "kind": "compute#project",
-  "name": "project_name",
-  "quotas": [
-    {
-      "limit": 1000.0,
-      "metric": "SNAPSHOTS",
-      "usage": 0.0
-    },
-    {
-      "limit": 5.0,
-      "metric": "NETWORKS",
-      "usage": 3.0
-    },
-    {
-      "limit": 100.0,
-      "metric": "FIREWALLS",
-      "usage": 5.0
-    },
-    {
-      "limit": 100.0,
-      "metric": "IMAGES",
-      "usage": 0.0
-    },
-    {
-      "limit": 100.0,
-      "metric": "ROUTES",
-      "usage": 6.0
-    },
-    {
-      "limit": 50.0,
-      "metric": "FORWARDING_RULES",
-      "usage": 0.0
-    },
-    {
-      "limit": 50.0,
-      "metric": "TARGET_POOLS",
-      "usage": 1.0
-    },
-    {
-      "limit": 50.0,
-      "metric": "HEALTH_CHECKS",
-      "usage": 1.0
-    }
-  ],
-  "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name"
-}
\ No newline at end of file
+ "kind": "compute#project",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name",
+ "id": "8116069320260064853",
+ "creationTimestamp": "2014-01-21T10:30:53.390-08:00",
+ "name": "project_name",
+ "description": "",
+ "commonInstanceMetadata": {
+  "kind": "compute#metadata",
+  "fingerprint": "3zEcGBxH6Vs=",
+  "items": [
+   {
+    "key": "sshKeys",
+    "value": "ABCDEF"
+   },
+   {
+    "key": "startup-script",
+    "value": "#!/bin/bash\n\nAUTO_SCRIPT=$(curl -s http://metadata/computeMetadata/v1/instance/attributes/my-auto-script
-H \"Metadata-Flavor: Google\")\nCHECK=${AUTO_SCRIPT:-disabled}\n\nif [ \"${CHECK}\" = \"enabled\"
-a -f /etc/debian_version ]; then\n    export DEBIAN_FRONTEND=noninteractive\n    apt-get
-q -y update\n    apt-get -q -y install git vim tmux\n    fi\nexit 0\n"
+   }
+  ]
+ },
+ "quotas": [
+  {
+   "metric": "SNAPSHOTS",
+   "limit": 1000,
+   "usage": 1
+  },
+  {
+   "metric": "NETWORKS",
+   "limit": 5,
+   "usage": 3
+  },
+  {
+   "metric": "FIREWALLS",
+   "limit": 100,
+   "usage": 6
+  },
+  {
+   "metric": "IMAGES",
+   "limit": 100,
+   "usage": 1
+  },
+  {
+   "metric": "STATIC_ADDRESSES",
+   "limit": 7,
+   "usage": 1
+  },
+  {
+   "metric": "ROUTES",
+   "limit": 100,
+   "usage": 2
+  },
+  {
+   "metric": "FORWARDING_RULES",
+   "limit": 50,
+   "usage": 0
+  },
+  {
+   "metric": "TARGET_POOLS",
+   "limit": 50,
+   "usage": 0
+  },
+  {
+   "metric": "HEALTH_CHECKS",
+   "limit": 50,
+   "usage": 1
+  },
+  {
+   "metric": "IN_USE_ADDRESSES",
+   "limit": 23,
+   "usage": 0
+  },
+  {
+   "metric": "TARGET_INSTANCES",
+   "limit": 50,
+   "usage": 3
+  },
+  {
+   "metric": "TARGET_HTTP_PROXIES",
+   "limit": 50,
+   "usage": 0
+  },
+  {
+   "metric": "URL_MAPS",
+   "limit": 50,
+   "usage": 1
+  },
+  {
+   "metric": "BACKEND_SERVICES",
+   "limit": 50,
+   "usage": 1
+  }
+ ],
+ "usageExportLocation": {
+  "bucketName": "gs://graphite-usage-reports",
+  "reportNamePrefix": "graphite-report"
+ }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/791ff6a4/libcloud/test/compute/fixtures/gce/setCommonInstanceMetadata_post.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/setCommonInstanceMetadata_post.json b/libcloud/test/compute/fixtures/gce/setCommonInstanceMetadata_post.json
new file mode 100644
index 0000000..1bbe33b
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/setCommonInstanceMetadata_post.json
@@ -0,0 +1,15 @@
+{
+  "endTime": "2013-06-26T10:05:07.630-07:00",
+  "id": "3681664092089171723",
+  "insertTime": "2013-06-26T10:05:03.271-07:00",
+  "kind": "compute#operation",
+  "name": "operation-setCommonInstanceMetadata",
+  "operationType": "insert",
+  "progress": 0,
+  "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation-setCommonInstanceMetadata",
+  "startTime": "2013-06-26T10:05:03.315-07:00",
+  "status": "PENDING",
+  "targetId": "16211908079305042870",
+  "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/setCommonInstanceMetadata",
+  "user": "foo@developer.gserviceaccount.com"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/791ff6a4/libcloud/test/compute/fixtures/gce/setUsageExportBucket_post.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/gce/setUsageExportBucket_post.json b/libcloud/test/compute/fixtures/gce/setUsageExportBucket_post.json
new file mode 100644
index 0000000..24df85c
--- /dev/null
+++ b/libcloud/test/compute/fixtures/gce/setUsageExportBucket_post.json
@@ -0,0 +1,14 @@
+{
+ "kind": "compute#operation",
+ "id": "17203609782824174066",
+ "name": "operation-setUsageExportBucket",
+ "operationType": "setUsageExportBucket",
+ "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name",
+ "targetId": "8116069320260064853",
+ "status": "PENDING",
+ "user": "erjohnso@google.com",
+ "progress": 0,
+ "insertTime": "2014-11-21T06:58:03.602-08:00",
+ "startTime": "2014-11-21T06:58:04.018-08:00",
+ "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation-setUsageExportBucket"
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/791ff6a4/libcloud/test/compute/test_gce.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_gce.py b/libcloud/test/compute/test_gce.py
index dccff82..8efeaa1 100644
--- a/libcloud/test/compute/test_gce.py
+++ b/libcloud/test/compute/test_gce.py
@@ -789,9 +789,100 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin):
         project = self.driver.ex_get_project()
         self.assertEqual(project.name, 'project_name')
         networks_quota = project.quotas[1]
-        self.assertEqual(networks_quota['usage'], 3.0)
-        self.assertEqual(networks_quota['limit'], 5.0)
+        self.assertEqual(networks_quota['usage'], 3)
+        self.assertEqual(networks_quota['limit'], 5)
         self.assertEqual(networks_quota['metric'], 'NETWORKS')
+        self.assertTrue('fingerprint' in project.extra['commonInstanceMetadata'])
+        self.assertTrue('items' in project.extra['commonInstanceMetadata'])
+        self.assertTrue('usageExportLocation' in project.extra)
+        self.assertTrue('bucketName' in project.extra['usageExportLocation'])
+        self.assertTrue(project.extra['usageExportLocation']['bucketName'], 'gs://graphite-usage-reports')
+
+    def test_ex_set_usage_export_bucket(self):
+        self.assertRaises(ValueError,
+                          self.driver.ex_set_usage_export_bucket, 'foo')
+        bucket_name = 'gs://foo'
+        self.driver.ex_set_usage_export_bucket(bucket_name)
+
+        bucket_name = 'https://www.googleapis.com/foo'
+        self.driver.ex_set_usage_export_bucket(bucket_name)
+
+    def test__set_project_metadata(self):
+        self.assertEqual(len(self.driver._set_project_metadata(None, False, "")), 0)
+
+        # 'delete' metadata, but retain current sshKeys
+        md = self.driver._set_project_metadata(None, False, "this is a test")
+        self.assertEqual(len(md), 1)
+        self.assertEqual(md[0]['key'], 'sshKeys')
+        self.assertEqual(md[0]['value'], 'this is a test')
+
+        # 'delete' metadata *and* any existing sshKeys
+        md = self.driver._set_project_metadata(None, True, "this is a test")
+        self.assertEqual(len(md), 0)
+
+        # add new metadata, keep existing sshKeys, since the new value also
+        # has 'sshKeys', we want the final struct to only have one ke/value
+        # of sshKeys and it should be the "current_keys"
+        gce_md = {'items': [{'key': 'foo', 'value': 'one'},
+                            {'key': 'sshKeys', 'value': 'another test'}]}
+        md = self.driver._set_project_metadata(gce_md, False, "this is a test")
+        self.assertEqual(len(md), 2, str(md))
+        sshKeys = ""
+        count = 0
+        for d in md:
+            if d['key'] == 'sshKeys':
+                count += 1
+                sshKeys = d['value']
+        self.assertEqual(sshKeys, 'this is a test')
+        self.assertEqual(count, 1)
+
+        # add new metadata, overwrite existing sshKeys, in this case, the
+        # existing 'sshKeys' value should be replaced
+        gce_md = {'items': [{'key': 'foo', 'value': 'one'},
+                            {'key': 'sshKeys', 'value': 'another test'}]}
+        md = self.driver._set_project_metadata(gce_md, True, "this is a test")
+        self.assertEqual(len(md), 2, str(md))
+        sshKeys = ""
+        count = 0
+        for d in md:
+            if d['key'] == 'sshKeys':
+                count += 1
+                sshKeys = d['value']
+        self.assertEqual(sshKeys, 'another test')
+        self.assertEqual(count, 1)
+
+        # add new metadata, remove existing sshKeys. in this case, we had an
+        # 'sshKeys' entry, but it will be removed entirely
+        gce_md = {'items': [{'key': 'foo', 'value': 'one'},
+                            {'key': 'nokeys', 'value': 'two'}]}
+        md = self.driver._set_project_metadata(gce_md, True, "this is a test")
+        self.assertEqual(len(md), 2, str(md))
+        sshKeys = ""
+        count = 0
+        for d in md:
+            if d['key'] == 'sshKeys':
+                count += 1
+                sshKeys = d['value']
+        self.assertEqual(sshKeys, '')
+        self.assertEqual(count, 0)
+
+    def test_ex_set_common_instance_metadata(self):
+        # test non-dict
+        self.assertRaises(ValueError,
+                          self.driver.ex_set_common_instance_metadata,
+                          ['bad', 'type'])
+        # test standard python dict
+        pydict = {'foo': 'pydict', 'one': 1}
+        self.driver.ex_set_common_instance_metadata(pydict)
+        # test GCE badly formatted dict
+        bad_gcedict = {'items': 'foo'}
+        self.assertRaises(ValueError,
+                          self.driver.ex_set_common_instance_metadata,
+                          bad_gcedict)
+        # test gce formatted dict
+        gcedict = {'items': [{'key': 'gcedict', 'value': 'v1'},
+                             {'key': 'gcedict', 'value': 'v2'}]}
+        self.driver.ex_set_common_instance_metadata(gcedict)
 
     def test_ex_get_region(self):
         region_name = 'us-central1'
@@ -878,6 +969,16 @@ class GCEMockHttp(MockHttpTestCase):
                                                                 qs, path)
         return method_name
 
+    def _setUsageExportBucket(self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load('setUsageExportBucket_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _setCommonInstanceMetadata(self, method, url, body, headers):
+        if method == 'POST':
+            body = self.fixtures.load('setCommonInstanceMetadata_post.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
     def _aggregated_addresses(self, method, url, body, headers):
         body = self.fixtures.load('aggregated_addresses.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
@@ -1033,6 +1134,18 @@ class GCEMockHttp(MockHttpTestCase):
             body = self.fixtures.load('global_snapshots_lcsnapshot.json')
         return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
 
+    def _global_operations_operation_setUsageExportBucket(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_setUsageExportBucket.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
+    def _global_operations_operation_setCommonInstanceMetadata(
+            self, method, url, body, headers):
+        body = self.fixtures.load(
+            'operations_operation_setCommonInstanceMetadata.json')
+        return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK])
+
     def _global_operations_operation_global_httpHealthChecks_lchealthcheck_delete(
             self, method, url, body, headers):
         body = self.fixtures.load(


Mime
View raw message