From e67b306c10be6358399d0e8a71350422394477e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rio=20Santos?= Date: Fri, 20 May 2016 16:56:20 +0100 Subject: [PATCH] Add support for changing metadata of compute instances Change-Id: I2301e0ab2024c51e7482ce0f05b706e8a395d676 --- ...ture-server-metadata-50caf18cec532160.yaml | 3 + shade/_tasks.py | 10 +++ shade/openstackcloud.py | 41 ++++++++++++ shade/tests/functional/test_compute.py | 35 ++++++++++ .../tests/unit/test_server_delete_metadata.py | 66 ++++++++++++++++++ shade/tests/unit/test_server_set_metadata.py | 67 +++++++++++++++++++ 6 files changed, 222 insertions(+) create mode 100644 releasenotes/notes/feature-server-metadata-50caf18cec532160.yaml create mode 100644 shade/tests/unit/test_server_delete_metadata.py create mode 100644 shade/tests/unit/test_server_set_metadata.py diff --git a/releasenotes/notes/feature-server-metadata-50caf18cec532160.yaml b/releasenotes/notes/feature-server-metadata-50caf18cec532160.yaml new file mode 100644 index 000000000..e0a3f6c83 --- /dev/null +++ b/releasenotes/notes/feature-server-metadata-50caf18cec532160.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add new APIs, OpenStackCloud.set_server_metadata() and OpenStackCloud.delete_server_metadata() to manage metadata of existing nova compute instances diff --git a/shade/_tasks.py b/shade/_tasks.py index 0f4502969..4cc07e203 100644 --- a/shade/_tasks.py +++ b/shade/_tasks.py @@ -172,6 +172,16 @@ class ServerRebuild(task_manager.Task): return client.nova_client.servers.rebuild(**self.args) +class ServerSetMetadata(task_manager.Task): + def main(self, client): + return client.nova_client.servers.set_meta(**self.args) + + +class ServerDeleteMetadata(task_manager.Task): + def main(self, client): + return client.nova_client.servers.delete_meta(**self.args) + + class ServerGroupList(task_manager.Task): def main(self, client): return client.nova_client.server_groups.list(**self.args) diff --git a/shade/openstackcloud.py b/shade/openstackcloud.py index 24fc10653..e546533c6 100644 --- a/shade/openstackcloud.py +++ b/shade/openstackcloud.py @@ -4302,6 +4302,47 @@ class OpenStackCloud(object): extra_data=dict(server=server)) return server + def set_server_metadata(self, name_or_id, metadata): + """Set metadata in a server instance. + + :param str name_or_id: The name or id of the server instance + to update. + :param dict metadata: A dictionary with the key=value pairs + to set in the server instance. It only updates the key=value + pairs provided. Existing ones will remain untouched. + + :raises: OpenStackCloudException on operation error. + """ + try: + self.manager.submitTask( + _tasks.ServerSetMetadata(server=self.get_server(name_or_id), + metadata=metadata)) + except OpenStackCloudException: + raise + except Exception as e: + raise OpenStackCloudException( + "Error updating metadata: {0}".format(e)) + + def delete_server_metadata(self, name_or_id, metadata_keys): + """Delete metadata from a server instance. + + :param str name_or_id: The name or id of the server instance + to update. + :param list metadata_keys: A list with the keys to be deleted + from the server instance. + + :raises: OpenStackCloudException on operation error. + """ + try: + self.manager.submitTask( + _tasks.ServerDeleteMetadata(server=self.get_server(name_or_id), + keys=metadata_keys)) + except OpenStackCloudException: + raise + except Exception as e: + raise OpenStackCloudException( + "Error deleting metadata: {0}".format(e)) + def delete_server( self, name_or_id, wait=False, timeout=180, delete_ips=False, delete_ip_retry=1): diff --git a/shade/tests/functional/test_compute.py b/shade/tests/functional/test_compute.py index 78ed58c91..287806685 100644 --- a/shade/tests/functional/test_compute.py +++ b/shade/tests/functional/test_compute.py @@ -19,6 +19,7 @@ test_compute Functional tests for `shade` compute methods. """ +from shade import exc from shade.tests.functional import base from shade.tests.functional.util import pick_flavor, pick_image @@ -215,3 +216,37 @@ class TestCompute(base.BaseFunctionalTestCase): wait=True) self.addCleanup(self.demo_cloud.delete_image, image['id']) self.assertEqual('active', image['status']) + + def test_set_and_delete_metadata(self): + self.addCleanup(self._cleanup_servers_and_volumes, self.server_name) + self.demo_cloud.create_server( + name=self.server_name, + image=self.image, + flavor=self.flavor, + wait=True) + self.demo_cloud.set_server_metadata(self.server_name, + {'key1': 'value1', + 'key2': 'value2'}) + updated_server = self.demo_cloud.get_server(self.server_name) + self.assertEqual(set(updated_server.metadata.items()), + set({'key1': 'value1', 'key2': 'value2'}.items())) + + self.demo_cloud.set_server_metadata(self.server_name, + {'key2': 'value3'}) + updated_server = self.demo_cloud.get_server(self.server_name) + self.assertEqual(set(updated_server.metadata.items()), + set({'key1': 'value1', 'key2': 'value3'}.items())) + + self.demo_cloud.delete_server_metadata(self.server_name, ['key2']) + updated_server = self.demo_cloud.get_server(self.server_name) + self.assertEqual(set(updated_server.metadata.items()), + set({'key1': 'value1'}.items())) + + self.demo_cloud.delete_server_metadata(self.server_name, ['key1']) + updated_server = self.demo_cloud.get_server(self.server_name) + self.assertEqual(set(updated_server.metadata.items()), set([])) + + self.assertRaises( + exc.OpenStackCloudException, + self.demo_cloud.delete_server_metadata, + self.server_name, ['key1']) diff --git a/shade/tests/unit/test_server_delete_metadata.py b/shade/tests/unit/test_server_delete_metadata.py new file mode 100644 index 000000000..d734d7114 --- /dev/null +++ b/shade/tests/unit/test_server_delete_metadata.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +test_server_delete_metadata +---------------------------------- + +Tests for the `delete_server_metadata` command. +""" + +from mock import patch, Mock +import os_client_config +from shade import OpenStackCloud +from shade.exc import OpenStackCloudException +from shade.tests import base + + +class TestServerDeleteMetadata(base.TestCase): + def setUp(self): + super(TestServerDeleteMetadata, self).setUp() + config = os_client_config.OpenStackConfig() + self.client = OpenStackCloud( + cloud_config=config.get_one_cloud(validate=False)) + self.client._SERVER_AGE = 0 + + def test_server_delete_metadata_with_delete_meta_exception(self): + """ + Test that a generic exception in the novaclient delete_meta raises + an exception in delete_server_metadata. + """ + with patch("shade.OpenStackCloud"): + config = { + "servers.delete_meta.side_effect": Exception("exception"), + } + OpenStackCloud.nova_client = Mock(**config) + + self.assertRaises( + OpenStackCloudException, self.client.delete_server_metadata, + {'id': 'server-id'}, ['key']) + + def test_server_delete_metadata_with_exception_reraise(self): + """ + Test that an OpenStackCloudException exception gets re-raised + in delete_server_metadata. + """ + with patch("shade.OpenStackCloud"): + config = { + "servers.delete_meta.side_effect": + OpenStackCloudException("exception"), + } + OpenStackCloud.nova_client = Mock(**config) + + self.assertRaises( + OpenStackCloudException, self.client.delete_server_metadata, + 'server-id', ['key']) diff --git a/shade/tests/unit/test_server_set_metadata.py b/shade/tests/unit/test_server_set_metadata.py new file mode 100644 index 000000000..b0c0a2d88 --- /dev/null +++ b/shade/tests/unit/test_server_set_metadata.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +test_server_set_metadata +---------------------------------- + +Tests for the `set_server_metadata` command. +""" + +from mock import patch, Mock +import os_client_config +from shade import OpenStackCloud +from shade.exc import OpenStackCloudException +from shade.tests import base + + +class TestServerSetMetadata(base.TestCase): + + def setUp(self): + super(TestServerSetMetadata, self).setUp() + config = os_client_config.OpenStackConfig() + self.client = OpenStackCloud( + cloud_config=config.get_one_cloud(validate=False)) + self.client._SERVER_AGE = 0 + + def test_server_set_metadata_with_set_meta_exception(self): + """ + Test that a generic exception in the novaclient set_meta raises + an exception in set_server_metadata. + """ + with patch("shade.OpenStackCloud"): + config = { + "servers.set_meta.side_effect": Exception("exception"), + } + OpenStackCloud.nova_client = Mock(**config) + + self.assertRaises( + OpenStackCloudException, self.client.set_server_metadata, + {'id': 'server-id'}, {'meta': 'data'}) + + def test_server_set_metadata_with_exception_reraise(self): + """ + Test that an OpenStackCloudException exception gets re-raised + in set_server_metadata. + """ + with patch("shade.OpenStackCloud"): + config = { + "servers.set_meta.side_effect": + OpenStackCloudException("exception"), + } + OpenStackCloud.nova_client = Mock(**config) + + self.assertRaises( + OpenStackCloudException, self.client.set_server_metadata, + 'server-id', {'meta': 'data'})