diff options
Diffstat (limited to 'openstackclient')
| -rw-r--r-- | openstackclient/compute/v2/server.py | 8 | ||||
| -rw-r--r-- | openstackclient/image/v2/image.py | 46 | ||||
| -rw-r--r-- | openstackclient/tests/image/v2/fakes.py | 133 |
3 files changed, 178 insertions, 9 deletions
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index d58ebacd..adab29c9 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -276,25 +276,25 @@ class CreateServer(show.ShowOne): disk_group.add_argument( '--image', metavar='<image>', - help=_('Create server from this image'), + help=_('Create server from this image (name or ID)'), ) disk_group.add_argument( '--volume', metavar='<volume>', - help=_('Create server from this volume'), + help=_('Create server from this volume (name or ID)'), ) parser.add_argument( '--flavor', metavar='<flavor>', required=True, - help=_('Create server with this flavor'), + help=_('Create server with this flavor (name or ID)'), ) parser.add_argument( '--security-group', metavar='<security-group-name>', action='append', default=[], - help=_('Security group to assign to this server ' + help=_('Security group to assign to this server (name or ID) ' '(repeat for multiple groups)'), ) parser.add_argument( diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index c0fb5b58..a3c1a99d 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -116,7 +116,6 @@ class CreateImage(show.ShowOne): def get_parser(self, prog_name): parser = super(CreateImage, self).get_parser(prog_name) - # TODO(mordred): add --volume and --force parameters and support # TODO(bunting): There are additional arguments that v1 supported # that v2 either doesn't support or supports weirdly. # --checksum - could be faked clientside perhaps? @@ -170,6 +169,19 @@ class CreateImage(show.ShowOne): metavar="<file>", help="Upload image from local file", ) + parser.add_argument( + "--volume", + metavar="<volume>", + help="Create image from a volume", + ) + parser.add_argument( + "--force", + dest='force', + action='store_true', + default=False, + help="Force image creation if volume is in use " + "(only meaningful with --volume)", + ) protected_group = parser.add_mutually_exclusive_group() protected_group.add_argument( "--protected", @@ -245,6 +257,7 @@ class CreateImage(show.ShowOne): if getattr(parsed_args, 'properties', None): for k, v in six.iteritems(parsed_args.properties): kwargs[k] = str(v) + # Handle exclusive booleans with care # Avoid including attributes in kwargs if an option is not # present on the command line. These exclusive booleans are not @@ -263,12 +276,33 @@ class CreateImage(show.ShowOne): # open the file first to ensure any failures are handled before the # image is created fp = gc_utils.get_data_file(parsed_args) + info = {} + if fp is not None and parsed_args.volume: + raise exceptions.CommandError("Uploading data and using container " + "are not allowed at the same time") if fp is None and parsed_args.file: self.log.warning("Failed to get an image file.") return {}, {} - image = image_client.images.create(**kwargs) + # If a volume is specified. + if parsed_args.volume: + volume_client = self.app.client_manager.volume + source_volume = utils.find_resource( + volume_client.volumes, + parsed_args.volume, + ) + response, body = volume_client.volumes.upload_to_image( + source_volume.id, + parsed_args.force, + parsed_args.name, + parsed_args.container_format, + parsed_args.disk_format, + ) + info = body['os-volume_upload_image'] + info['volume_type'] = info['volume_type']['name'] + else: + image = image_client.images.create(**kwargs) if fp is not None: with fp: @@ -289,7 +323,9 @@ class CreateImage(show.ShowOne): # update the image after the data has been uploaded image = image_client.images.get(image.id) - info = _format_image(image) + if not info: + info = _format_image(image) + return zip(*sorted(six.iteritems(info))) @@ -539,8 +575,8 @@ class SetImage(command.Command): # --location - maybe location add? # --copy-from - does not exist in v2 # --file - should be able to upload file - # --volume - needs adding - # --force - needs adding + # --volume - not possible with v2 as can't change id + # --force - see `--volume` # --checksum - maybe could be done client side # --stdin - could be implemented parser.add_argument( diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 11ad455d..692ef104 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -15,7 +15,10 @@ import copy import mock +import random +import uuid +from openstackclient.common import utils as common_utils from openstackclient.tests import fakes from openstackclient.tests import utils @@ -167,3 +170,133 @@ class TestImagev2(utils.TestCommand): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + + +class FakeImage(object): + """Fake one or more images. + + TODO(xiexs): Currently, only image API v2 is supported by this class. + """ + + @staticmethod + def create_one_image(attrs={}): + """Create a fake image. + + :param Dictionary attrs: + A dictionary with all attrbutes of image + :retrun: + A FakeResource object with id, name, owner, protected, + visibility and tags attrs + """ + # Set default attribute + image_info = { + 'id': 'image-id' + uuid.uuid4().hex, + 'name': 'image-name' + uuid.uuid4().hex, + 'owner': 'image-owner' + uuid.uuid4().hex, + 'protected': bool(random.choice([0, 1])), + 'visibility': random.choice(['public', 'private']), + 'tags': [uuid.uuid4().hex for r in range(random.randint(1, 5))], + } + + # Overwrite default attributes if there are some attributes set + image_info.update(attrs) + + image = fakes.FakeResource( + None, + image_info, + loaded=True) + return image + + @staticmethod + def create_images(attrs={}, count=2): + """Create multiple fake images. + + :param Dictionary attrs: + A dictionary with all attributes of image + :param Integer count: + The number of images to be faked + :return: + A list of FakeResource objects + """ + images = [] + for n in range(0, count): + images.append(FakeImage.create_one_image(attrs)) + + return images + + @staticmethod + def get_images(images=None, count=2): + """Get an iterable MagicMock object with a list of faked images. + + If images list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List images: + A list of FakeResource objects faking images + :param Integer count: + The number of images to be faked + :return + An iterable Mock object with side_effect set to a list of faked + images + """ + if images is None: + images = FakeImage.create_images(count) + + return mock.MagicMock(side_effect=images) + + @staticmethod + def get_image_info(image=None): + """Get the image info from a faked image object. + + :param image: + A FakeResource objects faking image + :return + A dictionary which includes the faked image info as follows: + { + 'id': image_id, + 'name': image_name, + 'owner': image_owner, + 'protected': image_protected, + 'visibility': image_visibility, + 'tags': image_tags + } + """ + if image is not None: + return image._info + return {} + + @staticmethod + def get_image_columns(image=None): + """Get the image columns from a faked image object. + + :param image: + A FakeResource objects faking image + :return + A tuple which may include the following keys: + ('id', 'name', 'owner', 'protected', 'visibility', 'tags') + """ + if image is not None: + return tuple(k for k in sorted( + FakeImage.get_image_info(image).keys())) + return tuple([]) + + @staticmethod + def get_image_data(image=None): + """Get the image data from a faked image object. + + :param image: + A FakeResource objects faking image + :return + A tuple which may include the following values: + ('image-123', 'image-foo', 'admin', False, 'public', 'bar, baz') + """ + data_list = [] + if image is not None: + for x in sorted(FakeImage.get_image_info(image).keys()): + if x == 'tags': + # The 'tags' should be format_list + data_list.append( + common_utils.format_list(getattr(image, x))) + else: + data_list.append(getattr(image, x)) + return tuple(data_list) |
