From d1912624139eaf40f522666da3fdb840a98ab020 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 3 Apr 2012 16:00:49 -0700 Subject: [PATCH] Image update works --- glanceclient/v1/images.py | 32 ++++++++++++++++-------- glanceclient/v1/shell.py | 52 ++++++++++++++++++++++++++++++++++++++- tests/v1/test_images.py | 35 +++++++++++++++++++++++--- tests/v1/utils.py | 20 +++++++++------ 4 files changed, 118 insertions(+), 21 deletions(-) diff --git a/glanceclient/v1/images.py b/glanceclient/v1/images.py index 459fde08..7af68ce3 100644 --- a/glanceclient/v1/images.py +++ b/glanceclient/v1/images.py @@ -18,9 +18,11 @@ import urllib from glanceclient.common import base -CREATE_PARAMS = ['id', 'name', 'disk_format', 'container_format', 'min_disk', +UPDATE_PARAMS = ('name', 'disk_format', 'container_format', 'min_disk', 'min_ram', 'owner', 'size', 'is_public', 'protected', - 'location', 'checksum', 'copy_from', 'properties'] + 'location', 'checksum', 'copy_from', 'properties') + +CREATE_PARAMS = UPDATE_PARAMS + ('id',) class Image(base.Resource): @@ -91,6 +93,8 @@ class ImageManager(base.Manager): TODO(bcwaldon): document accepted params """ + image_data = kwargs.pop('data', None) + fields = {} for field in kwargs: if field in CREATE_PARAMS: @@ -104,16 +108,24 @@ class ImageManager(base.Manager): if copy_from is not None: hdrs['x-glance-api-copy-from'] = copy_from - resp, body = self.api.post('/v1/images', headers=hdrs) + resp, body = self.api.post('/v1/images', headers=hdrs, body=image_data) return Image(self, body['image']) def update(self, image, **kwargs): """Update an image""" fields = {} - if 'name' in kwargs: - fields['name'] = kwargs['name'] - send_meta = self._image_meta_to_headers(fields) - url = '/v1/images/%s' % base.getid(image) - resp, body = self.api.put(url, headers=send_meta) - recv_meta = self._image_meta_from_headers(resp) - return Image(self, recv_meta) + for field in kwargs: + if field in UPDATE_PARAMS: + fields[field] = kwargs[field] + else: + msg = 'update() got an unexpected keyword argument \'%s\'' + raise TypeError(msg % field) + + copy_from = fields.pop('copy_from', None) + hdrs = self._image_meta_to_headers(fields) + if copy_from is not None: + hdrs['x-glance-api-copy-from'] = copy_from + + image_id = base.getid(image) + resp, body = self.api.put('/v1/images/%s' % image_id, headers=hdrs) + return Image(self, body['image']) diff --git a/glanceclient/v1/shell.py b/glanceclient/v1/shell.py index be32ffff..48302c2f 100644 --- a/glanceclient/v1/shell.py +++ b/glanceclient/v1/shell.py @@ -22,7 +22,7 @@ import glanceclient.v1.images def do_image_list(gc, args): """List images.""" images = gc.images.list() - columns = ['ID', 'Name', 'Disk Format', 'Container Format', 'Size'] + columns = ['ID', 'Name', 'Status'] utils.print_list(images, columns) @@ -96,6 +96,56 @@ def do_image_create(gc, args): _image_show(image) +@utils.arg('--name', metavar='', + help='Name of image.') +@utils.arg('--disk_format', metavar='', + help='Disk format of image.') +@utils.arg('--container_format', metavar='', + help='Container format of image.') +@utils.arg('--owner', metavar='', + help='Tenant who should own image.') +@utils.arg('--size', metavar='', + help='Size of image data (in bytes).') +@utils.arg('--min_disk', metavar='', + help='Minimum size of disk needed to boot image (in gigabytes).') +@utils.arg('--min_ram', metavar='', + help='Minimum amount of ram needed to boot image (in megabytes).') +@utils.arg('--location', metavar='', + help=('URL where the data for this image already resides.' + ' For example, if the image data is stored in the filesystem' + ' local to the glance server at \'/usr/share/image.tar.gz\',' + ' you would specify \'file:///usr/share/image.tar.gz\'.')) +@utils.arg('--checksum', metavar='', + help='Hash of image data used Glance can use for verification.') +@utils.arg('--copy_from', metavar='', + help=('Similar to \'--location\' in usage, but this indicates that' + ' the Glance server should immediately copy the data and' + ' store it in its configured image store.')) +@utils.arg('--is_public', type=bool, + help='Make image accessible to the public.') +@utils.arg('--is_protected', type=bool, + help='Prevent image from being deleted.') +@utils.arg('--property', metavar="", action='append', default=[], + help=("Arbitrary property to associate with image. " + "May be used multiple times.")) +def do_image_update(gc, args): + # Filter out None values + fields = dict(filter(lambda x: x[1] is not None, vars(args).items())) + + raw_properties = fields.pop('property') + fields['properties'] = {} + for datum in raw_properties: + key, value = datum.split('=', 1) + fields['properties'][key] = value + + # Filter out values we can't use + UPDATE_PARAMS = glanceclient.v1.images.UPDATE_PARAMS + fields = dict(filter(lambda x: x[0] in UPDATE_PARAMS, fields.items())) + + image = gc.images.create(**fields) + _image_show(image) + + @utils.arg('id', metavar='', help='ID of image to delete.') def do_image_delete(gc, args): """Delete a specific image.""" diff --git a/tests/v1/test_images.py b/tests/v1/test_images.py index 1c51fabc..ab6f57b6 100644 --- a/tests/v1/test_images.py +++ b/tests/v1/test_images.py @@ -1,4 +1,5 @@ +import StringIO import unittest from tests.v1 import utils @@ -43,7 +44,7 @@ class ImageManagerTest(unittest.TestCase): expect = [('DELETE', '/v1/images/1', {}, None)] self.assertEqual(self.api.calls, expect) - def test_create_no_data(self): + def test_create_without_data(self): params = { 'id': '1', 'name': 'image-1', @@ -82,9 +83,37 @@ class ImageManagerTest(unittest.TestCase): self.assertEqual(image.min_disk, '10') self.assertEqual(image.properties, {'a': 'b', 'c': 'd'}) + def test_create_with_data(self): + image_data = StringIO.StringIO('XXX') + self.mgr.create(data=image_data) + expect = [('POST', '/v1/images', {}, image_data)] + self.assertEqual(self.api.calls, expect) + def test_update(self): - image = self.mgr.update('1', name='image-2') - expect_hdrs = {'x-image-meta-name': 'image-2'} + fields = { + 'name': 'image-2', + 'container_format': 'ovf', + 'disk_format': 'vhd', + 'owner': 'asdf', + 'size': 1024, + 'min_ram': 512, + 'min_disk': 10, + 'copy_from': 'http://example.com', + 'properties': {'a': 'b', 'c': 'd'}, + } + image = self.mgr.update('1', **fields) + expect_hdrs = { + 'x-image-meta-name': 'image-2', + 'x-image-meta-container_format': 'ovf', + 'x-image-meta-disk_format': 'vhd', + 'x-image-meta-owner': 'asdf', + 'x-image-meta-size': '1024', + 'x-image-meta-min_ram': '512', + 'x-image-meta-min_disk': '10', + 'x-glance-api-copy-from': 'http://example.com', + 'x-image-meta-property-a': 'b', + 'x-image-meta-property-c': 'd', + } expect = [('PUT', '/v1/images/1', expect_hdrs, None)] self.assertEqual(self.api.calls, expect) self.assertEqual(image.id, '1') diff --git a/tests/v1/utils.py b/tests/v1/utils.py index 7598aa5c..d67df3a1 100644 --- a/tests/v1/utils.py +++ b/tests/v1/utils.py @@ -29,7 +29,6 @@ fixtures = { 'min_ram': '512', 'min_disk': '10', 'properties': {'a': 'b', 'c': 'd'}, - }}), }, '/v1/images/detail': { @@ -53,12 +52,19 @@ fixtures = { }, None), 'PUT': ( - { - 'x-image-meta-id': '1', - 'x-image-meta-name': 'image-2', - 'x-image-meta-property-arch': 'x86_64', - }, - None), + {}, + {'image': { + 'id': '1', + 'name': 'image-2', + 'container_format': 'ovf', + 'disk_format': 'vhd', + 'owner': 'asdf', + 'size': '1024', + 'min_ram': '512', + 'min_disk': '10', + 'properties': {'a': 'b', 'c': 'd'}, + }}, + ), 'DELETE': ({}, None), }, '/v1/images/1/members': {