From 447d5d9e344060be4f284ad56b430b20eb190c2b Mon Sep 17 00:00:00 2001 From: Chen Hanxiao <chenhx@certusnet.com.cn> Date: Fri, 19 Jan 2018 14:30:18 +0800 Subject: [PATCH] Add --image-property parameter in 'server create' add --image-property option, just like --image-with of novaclient did. Change-Id: Ic1a8976559255529a8785b1b301a0307812433cb Signed-off-by: Chen Hanxiao <chenhx@certusnet.com.cn> --- openstackclient/compute/v2/server.py | 45 +++++ .../tests/unit/compute/v2/test_server.py | 158 ++++++++++++++++++ ...reate-image-property-ef76af26233b472b.yaml | 5 + 3 files changed, 208 insertions(+) create mode 100644 releasenotes/notes/add-server-create-image-property-ef76af26233b472b.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 85c20aee6d..f60cbe56a5 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -442,6 +442,12 @@ class CreateServer(command.ShowOne): metavar='<image>', help=_('Create server boot disk from this image (name or ID)'), ) + disk_group.add_argument( + '--image-property', + metavar='<key=value>', + action=parseractions.KeyValueAction, + help=_("Image property to be matched"), + ) disk_group.add_argument( '--volume', metavar='<volume>', @@ -610,6 +616,45 @@ class CreateServer(command.ShowOne): parsed_args.image, ) + if not image and parsed_args.image_property: + def emit_duplicated_warning(img, image_property): + img_uuid_list = [str(image.id) for image in img] + LOG.warning(_('Multiple matching images: %(img_uuid_list)s\n' + 'Using image: %(chosen_one)s') % + {'img_uuid_list': img_uuid_list, + 'chosen_one': img_uuid_list[0]}) + + def _match_image(image_api, wanted_properties): + image_list = image_api.image_list() + images_matched = [] + for img in image_list: + img_dict = {} + # exclude any unhashable entries + for key, value in img.items(): + try: + set([key, value]) + except TypeError: + pass + else: + img_dict[key] = value + if all(k in img_dict and img_dict[k] == v + for k, v in wanted_properties.items()): + images_matched.append(img) + else: + return [] + return images_matched + + images = _match_image(image_client.api, parsed_args.image_property) + if len(images) > 1: + emit_duplicated_warning(images, + parsed_args.image_property) + if images: + image = images[0] + else: + raise exceptions.CommandError(_("No images match the " + "property expected by " + "--image-property")) + # Lookup parsed_args.volume volume = None if parsed_args.volume: diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 87c9a98514..feb9d1f8a2 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1528,6 +1528,164 @@ class TestServerCreate(TestServer): self.cmd.take_action, parsed_args) + def test_server_create_image_property(self): + arglist = [ + '--image-property', 'hypervisor_type=qemu', + '--flavor', 'flavor1', + '--nic', 'none', + self.new_server.name, + ] + verifylist = [ + ('image_property', {'hypervisor_type': 'qemu'}), + ('flavor', 'flavor1'), + ('nic', ['none']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + _image = image_fakes.FakeImage.create_one_image() + # create a image_info as the side_effect of the fake image_list() + image_info = { + 'id': _image.id, + 'name': _image.name, + 'owner': _image.owner, + 'hypervisor_type': 'qemu', + } + self.api_mock = mock.Mock() + self.api_mock.image_list.side_effect = [ + [image_info], [], + ] + self.app.client_manager.image.api = self.api_mock + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='none', + meta=None, + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + image_info, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_image_property_multi(self): + arglist = [ + '--image-property', 'hypervisor_type=qemu', + '--image-property', 'hw_disk_bus=ide', + '--flavor', 'flavor1', + '--nic', 'none', + self.new_server.name, + ] + verifylist = [ + ('image_property', {'hypervisor_type': 'qemu', + 'hw_disk_bus': 'ide'}), + ('flavor', 'flavor1'), + ('nic', ['none']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + _image = image_fakes.FakeImage.create_one_image() + # create a image_info as the side_effect of the fake image_list() + image_info = { + 'id': _image.id, + 'name': _image.name, + 'owner': _image.owner, + 'hypervisor_type': 'qemu', + 'hw_disk_bus': 'ide', + } + self.api_mock = mock.Mock() + self.api_mock.image_list.side_effect = [ + [image_info], [], + ] + self.app.client_manager.image.api = self.api_mock + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = dict( + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping_v2=[], + nics='none', + meta=None, + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + image_info, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + + def test_server_create_image_property_missed(self): + arglist = [ + '--image-property', 'hypervisor_type=qemu', + '--image-property', 'hw_disk_bus=virtio', + '--flavor', 'flavor1', + '--nic', 'none', + self.new_server.name, + ] + verifylist = [ + ('image_property', {'hypervisor_type': 'qemu', + 'hw_disk_bus': 'virtio'}), + ('flavor', 'flavor1'), + ('nic', ['none']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + _image = image_fakes.FakeImage.create_one_image() + # create a image_info as the side_effect of the fake image_list() + image_info = { + 'id': _image.id, + 'name': _image.name, + 'owner': _image.owner, + 'hypervisor_type': 'qemu', + 'hw_disk_bus': 'ide', + } + self.api_mock = mock.Mock() + self.api_mock.image_list.side_effect = [ + [image_info], [], + ] + self.app.client_manager.image.api = self.api_mock + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + class TestServerDelete(TestServer): diff --git a/releasenotes/notes/add-server-create-image-property-ef76af26233b472b.yaml b/releasenotes/notes/add-server-create-image-property-ef76af26233b472b.yaml new file mode 100644 index 0000000000..9d71446fbc --- /dev/null +++ b/releasenotes/notes/add-server-create-image-property-ef76af26233b472b.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add a parameter ``--image-property`` to ``server create`` command. + This parameter will filter a image which properties that are matching.