diff --git a/storm/services/nova/json/images_client.py b/storm/services/nova/json/images_client.py index 70902c88a2..e8a1326786 100644 --- a/storm/services/nova/json/images_client.py +++ b/storm/services/nova/json/images_client.py @@ -70,11 +70,15 @@ class ImagesClient(object): """Deletes the provided image""" return self.client.delete("images/%s" % str(image_id)) - def wait_for_image_exists(self, image_id): + def wait_for_image_resp_code(self, image_id, code): + """ + Waits until the HTTP response code for the request matches the + expected value + """ resp, body = self.client.get("images/%s" % str(image_id)) start = int(time.time()) - while resp.status != 200: + while resp.status != code: time.sleep(self.build_interval) resp, body = self.client.get("images/%s" % str(image_id)) @@ -97,39 +101,45 @@ class ImagesClient(object): raise exceptions.BuildErrorException def list_image_metadata(self, image_id): + """Lists all metadata items for an image""" resp, body = self.client.get("images/%s/metadata" % str(image_id)) body = json.loads(body) - return resp, body + return resp, body['metadata'] def set_image_metadata(self, image_id, meta): + """Sets the metadata for an image""" post_body = json.dumps({'metadata': meta}) resp, body = self.client.put('images/%s/metadata' % str(image_id), post_body, self.headers) body = json.loads(body) - return resp, body + return resp, body['metadata'] def update_image_metadata(self, image_id, meta): + """Updates the metadata for an image""" post_body = json.dumps({'metadata': meta}) resp, body = self.client.post('images/%s/metadata' % str(image_id), post_body, self.headers) body = json.loads(body) - return resp, body + return resp, body['metadata'] def get_image_metadata_item(self, image_id, key): + """Returns the value for a specific image metadata key""" resp, body = self.client.get("images/%s/metadata/%s" % (str(image_id), key)) body = json.loads(body) - return resp, body + return resp, body['meta'] def set_image_metadata_item(self, image_id, key, meta): + """Sets the value for a specific image metadata key""" post_body = json.dumps({'meta': meta}) - resp, body = self.client.put('images/%s/metdata/%s' % - (str(image_id), key), - post_body, self.headers) + resp, body = self.client.put('images/%s/metadata/%s' % + (str(image_id), key), post_body, + self.headers) body = json.loads(body) - return resp, body + return resp, body['meta'] def delete_image_metadata_item(self, image_id, key): + """Deletes a single image metadata key/value pair""" resp, body = self.client.delete("images/%s/metadata/%s" % (str(image_id), key)) return resp, body diff --git a/storm/services/nova/json/servers_client.py b/storm/services/nova/json/servers_client.py index 6bad76e385..e65173fad9 100644 --- a/storm/services/nova/json/servers_client.py +++ b/storm/services/nova/json/servers_client.py @@ -285,7 +285,7 @@ class ServersClient(object): def set_server_metadata_item(self, server_id, key, meta): post_body = json.dumps({'meta': meta}) - resp, body = self.client.put('servers/%s/metdata/%s' % + resp, body = self.client.put('servers/%s/metadata/%s' % (str(server_id), key), post_body, self.headers) body = json.loads(body) diff --git a/storm/tests/test_image_metadata.py b/storm/tests/test_image_metadata.py new file mode 100644 index 0000000000..263a7711dd --- /dev/null +++ b/storm/tests/test_image_metadata.py @@ -0,0 +1,158 @@ +from nose.plugins.attrib import attr +from storm import openstack +from storm.common.utils.data_utils import rand_name +import storm.config +import unittest2 as unittest + + +class ImagesMetadataTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.os = openstack.Manager() + cls.servers_client = cls.os.servers_client + cls.client = cls.os.images_client + cls.config = storm.config.StormConfig() + cls.image_ref = cls.config.env.image_ref + cls.flavor_ref = cls.config.env.flavor_ref + cls.ssh_timeout = cls.config.nova.ssh_timeout + + name = rand_name('server') + resp, cls.server = cls.servers_client.create_server(name, + cls.image_ref, + cls.flavor_ref) + #Wait for the server to become active + cls.servers_client.wait_for_server_status(cls.server['id'], 'ACTIVE') + + #Create an image from the server + name = rand_name('image') + cls.meta = {'key1': 'value1', 'key2': 'value2'} + resp, body = cls.client.create_image(cls.server['id'], name, cls.meta) + image_ref = resp['location'] + temp = image_ref.rsplit('/') + image_id = temp[len(temp) - 1] + + cls.client.wait_for_image_resp_code(image_id, 200) + cls.client.wait_for_image_status(image_id, 'ACTIVE') + resp, cls.image = cls.client.get_image(image_id) + + @classmethod + def tearDownClass(cls): + cls.servers_client.delete_server(cls.server['id']) + cls.client.delete_image(cls.image['id']) + + def _parse_image_id(self, image_ref): + temp = image_ref.rsplit('/') + return len(temp) - 1 + + def test_list_image_metadata(self): + """All metadata key/value pairs for an image should be returned""" + resp, metadata = self.client.list_image_metadata(self.image['id']) + self.assertEqual('value1', metadata['key1']) + self.assertEqual('value2', metadata['key2']) + + def test_set_image_metadata(self): + """The metadata for the image should match the new values""" + meta = {'meta1': 'data1'} + name = rand_name('server') + resp, server = self.servers_client.create_server(name, self.image_ref, + self.flavor_ref) + self.servers_client.wait_for_server_status(server['id'], 'ACTIVE') + + name = rand_name('image') + resp, body = self.client.create_image(server['id'], name, meta) + image_id = self._parse_image_id(resp['location']) + self.client.wait_for_image_resp_code(image_id, 200) + self.client.wait_for_image_status(image_id, 'ACTIVE') + resp, image = self.client.get_image(image_id) + + meta = {'meta2': 'data2', 'meta3': 'data3'} + resp, body = self.client.set_image_metadata(image['id'], meta) + + resp, metadata = self.client.list_image_metadata(image['id']) + self.assertEqual('data2', metadata['meta2']) + self.assertEqual('data3', metadata['meta3']) + self.assertTrue('meta1' not in metadata) + + self.servers_client.delete_server(server['id']) + self.client.delete_image(image['id']) + + def test_update_image_metadata(self): + """The metadata for the image should match the updated values""" + meta = {'key1': 'value1', 'key2': 'value2'} + name = rand_name('server') + resp, server = self.servers_client.create_server(name, self.image_ref, + self.flavor_ref) + self.servers_client.wait_for_server_status(server['id'], 'ACTIVE') + + name = rand_name('image') + resp, body = self.client.create_image(server['id'], name, meta) + image_id = self._parse_image_id(resp['location']) + self.client.wait_for_image_resp_code(image_id, 200) + self.client.wait_for_image_status(image_id, 'ACTIVE') + resp, image = self.client.get_image(image_id) + + meta = {'key1': 'alt1', 'key2': 'alt2'} + resp, metadata = self.client.update_image_metadata(image['id'], meta) + + resp, metadata = self.client.list_image_metadata(image['id']) + self.assertEqual('alt1', metadata['key1']) + self.assertEqual('alt2', metadata['key2']) + + self.servers_client.delete_server(server['id']) + self.client.delete_image(image['id']) + + def test_get_image_metadata_item(self): + """The value for a specic metadata key should be returned""" + resp, meta = self.client.get_image_metadata_item(self.image['id'], + 'key2') + self.assertTrue('value2', meta['key2']) + + def test_set_image_metadata_item(self): + """ + The value provided for the given meta item should be set for the image + """ + meta = {'nova': 'server'} + name = rand_name('server') + resp, server = self.servers_client.create_server(name, self.image_ref, + self.flavor_ref) + self.servers_client.wait_for_server_status(server['id'], 'ACTIVE') + + name = rand_name('image') + resp, body = self.client.create_image(server['id'], name, meta) + image_id = self._parse_image_id(resp['location']) + self.client.wait_for_image_resp_code(image_id, 200) + self.client.wait_for_image_status(image_id, 'ACTIVE') + resp, image = self.client.get_image(image_id) + + meta = {'nova': 'alt'} + resp, body = self.client.set_image_metadata_item(image['id'], + 'nova', meta) + resp, metadata = self.client.list_image_metadata(image['id']) + self.assertEqual('alt', metadata['nova']) + + self.servers_client.delete_server(server['id']) + self.client.delete_image(image['id']) + + def test_delete_image_metadata_item(self): + """The metadata value/key pair should be deleted from the image""" + meta = {'delkey': 'delvalue'} + name = rand_name('server') + resp, server = self.servers_client.create_server(name, self.image_ref, + self.flavor_ref) + self.servers_client.wait_for_server_status(server['id'], 'ACTIVE') + + name = rand_name('image') + resp, body = self.client.create_image(server['id'], name, meta) + image_id = self._parse_image_id(resp['location']) + self.client.wait_for_image_resp_code(image_id, 200) + self.client.wait_for_image_status(image_id, 'ACTIVE') + resp, image = self.client.get_image(image_id) + + resp, body = self.client.delete_image_metadata_item(image['id'], + 'delkey') + resp, metadata = self.client.list_image_metadata(image['id']) + self.assertTrue('delkey' not in metadata) + + self.servers_client.delete_server(server['id']) + self.client.delete_image(image['id']) diff --git a/storm/tests/test_images.py b/storm/tests/test_images.py index e11dc4723d..635dc62b03 100644 --- a/storm/tests/test_images.py +++ b/storm/tests/test_images.py @@ -35,7 +35,7 @@ class ImagesTest(unittest.TestCase): name = rand_name('image') resp, body = self.client.create_image(server['id'], name) image_id = self._parse_image_id(resp['location']) - self.client.wait_for_image_exists(image_id) + self.client.wait_for_image_resp_code(image_id, 200) self.client.wait_for_image_status(image_id, 'ACTIVE') #Verify the image was created correctly