Skip to content

Commit 447d5d9

Browse files
committed
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>
1 parent 3f99dba commit 447d5d9

3 files changed

Lines changed: 208 additions & 0 deletions

File tree

openstackclient/compute/v2/server.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,12 @@ def get_parser(self, prog_name):
442442
metavar='<image>',
443443
help=_('Create server boot disk from this image (name or ID)'),
444444
)
445+
disk_group.add_argument(
446+
'--image-property',
447+
metavar='<key=value>',
448+
action=parseractions.KeyValueAction,
449+
help=_("Image property to be matched"),
450+
)
445451
disk_group.add_argument(
446452
'--volume',
447453
metavar='<volume>',
@@ -610,6 +616,45 @@ def take_action(self, parsed_args):
610616
parsed_args.image,
611617
)
612618

619+
if not image and parsed_args.image_property:
620+
def emit_duplicated_warning(img, image_property):
621+
img_uuid_list = [str(image.id) for image in img]
622+
LOG.warning(_('Multiple matching images: %(img_uuid_list)s\n'
623+
'Using image: %(chosen_one)s') %
624+
{'img_uuid_list': img_uuid_list,
625+
'chosen_one': img_uuid_list[0]})
626+
627+
def _match_image(image_api, wanted_properties):
628+
image_list = image_api.image_list()
629+
images_matched = []
630+
for img in image_list:
631+
img_dict = {}
632+
# exclude any unhashable entries
633+
for key, value in img.items():
634+
try:
635+
set([key, value])
636+
except TypeError:
637+
pass
638+
else:
639+
img_dict[key] = value
640+
if all(k in img_dict and img_dict[k] == v
641+
for k, v in wanted_properties.items()):
642+
images_matched.append(img)
643+
else:
644+
return []
645+
return images_matched
646+
647+
images = _match_image(image_client.api, parsed_args.image_property)
648+
if len(images) > 1:
649+
emit_duplicated_warning(images,
650+
parsed_args.image_property)
651+
if images:
652+
image = images[0]
653+
else:
654+
raise exceptions.CommandError(_("No images match the "
655+
"property expected by "
656+
"--image-property"))
657+
613658
# Lookup parsed_args.volume
614659
volume = None
615660
if parsed_args.volume:

openstackclient/tests/unit/compute/v2/test_server.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1528,6 +1528,164 @@ def test_server_create_with_block_device_mapping_no_uuid(self):
15281528
self.cmd.take_action,
15291529
parsed_args)
15301530

1531+
def test_server_create_image_property(self):
1532+
arglist = [
1533+
'--image-property', 'hypervisor_type=qemu',
1534+
'--flavor', 'flavor1',
1535+
'--nic', 'none',
1536+
self.new_server.name,
1537+
]
1538+
verifylist = [
1539+
('image_property', {'hypervisor_type': 'qemu'}),
1540+
('flavor', 'flavor1'),
1541+
('nic', ['none']),
1542+
('config_drive', False),
1543+
('server_name', self.new_server.name),
1544+
]
1545+
_image = image_fakes.FakeImage.create_one_image()
1546+
# create a image_info as the side_effect of the fake image_list()
1547+
image_info = {
1548+
'id': _image.id,
1549+
'name': _image.name,
1550+
'owner': _image.owner,
1551+
'hypervisor_type': 'qemu',
1552+
}
1553+
self.api_mock = mock.Mock()
1554+
self.api_mock.image_list.side_effect = [
1555+
[image_info], [],
1556+
]
1557+
self.app.client_manager.image.api = self.api_mock
1558+
1559+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
1560+
1561+
columns, data = self.cmd.take_action(parsed_args)
1562+
1563+
# Set expected values
1564+
kwargs = dict(
1565+
files={},
1566+
reservation_id=None,
1567+
min_count=1,
1568+
max_count=1,
1569+
security_groups=[],
1570+
userdata=None,
1571+
key_name=None,
1572+
availability_zone=None,
1573+
block_device_mapping_v2=[],
1574+
nics='none',
1575+
meta=None,
1576+
scheduler_hints={},
1577+
config_drive=None,
1578+
)
1579+
# ServerManager.create(name, image, flavor, **kwargs)
1580+
self.servers_mock.create.assert_called_with(
1581+
self.new_server.name,
1582+
image_info,
1583+
self.flavor,
1584+
**kwargs
1585+
)
1586+
1587+
self.assertEqual(self.columns, columns)
1588+
self.assertEqual(self.datalist(), data)
1589+
1590+
def test_server_create_image_property_multi(self):
1591+
arglist = [
1592+
'--image-property', 'hypervisor_type=qemu',
1593+
'--image-property', 'hw_disk_bus=ide',
1594+
'--flavor', 'flavor1',
1595+
'--nic', 'none',
1596+
self.new_server.name,
1597+
]
1598+
verifylist = [
1599+
('image_property', {'hypervisor_type': 'qemu',
1600+
'hw_disk_bus': 'ide'}),
1601+
('flavor', 'flavor1'),
1602+
('nic', ['none']),
1603+
('config_drive', False),
1604+
('server_name', self.new_server.name),
1605+
]
1606+
_image = image_fakes.FakeImage.create_one_image()
1607+
# create a image_info as the side_effect of the fake image_list()
1608+
image_info = {
1609+
'id': _image.id,
1610+
'name': _image.name,
1611+
'owner': _image.owner,
1612+
'hypervisor_type': 'qemu',
1613+
'hw_disk_bus': 'ide',
1614+
}
1615+
self.api_mock = mock.Mock()
1616+
self.api_mock.image_list.side_effect = [
1617+
[image_info], [],
1618+
]
1619+
self.app.client_manager.image.api = self.api_mock
1620+
1621+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
1622+
1623+
columns, data = self.cmd.take_action(parsed_args)
1624+
1625+
# Set expected values
1626+
kwargs = dict(
1627+
files={},
1628+
reservation_id=None,
1629+
min_count=1,
1630+
max_count=1,
1631+
security_groups=[],
1632+
userdata=None,
1633+
key_name=None,
1634+
availability_zone=None,
1635+
block_device_mapping_v2=[],
1636+
nics='none',
1637+
meta=None,
1638+
scheduler_hints={},
1639+
config_drive=None,
1640+
)
1641+
# ServerManager.create(name, image, flavor, **kwargs)
1642+
self.servers_mock.create.assert_called_with(
1643+
self.new_server.name,
1644+
image_info,
1645+
self.flavor,
1646+
**kwargs
1647+
)
1648+
1649+
self.assertEqual(self.columns, columns)
1650+
self.assertEqual(self.datalist(), data)
1651+
1652+
def test_server_create_image_property_missed(self):
1653+
arglist = [
1654+
'--image-property', 'hypervisor_type=qemu',
1655+
'--image-property', 'hw_disk_bus=virtio',
1656+
'--flavor', 'flavor1',
1657+
'--nic', 'none',
1658+
self.new_server.name,
1659+
]
1660+
verifylist = [
1661+
('image_property', {'hypervisor_type': 'qemu',
1662+
'hw_disk_bus': 'virtio'}),
1663+
('flavor', 'flavor1'),
1664+
('nic', ['none']),
1665+
('config_drive', False),
1666+
('server_name', self.new_server.name),
1667+
]
1668+
_image = image_fakes.FakeImage.create_one_image()
1669+
# create a image_info as the side_effect of the fake image_list()
1670+
image_info = {
1671+
'id': _image.id,
1672+
'name': _image.name,
1673+
'owner': _image.owner,
1674+
'hypervisor_type': 'qemu',
1675+
'hw_disk_bus': 'ide',
1676+
}
1677+
self.api_mock = mock.Mock()
1678+
self.api_mock.image_list.side_effect = [
1679+
[image_info], [],
1680+
]
1681+
self.app.client_manager.image.api = self.api_mock
1682+
1683+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
1684+
1685+
self.assertRaises(exceptions.CommandError,
1686+
self.cmd.take_action,
1687+
parsed_args)
1688+
15311689

15321690
class TestServerDelete(TestServer):
15331691

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
features:
3+
- |
4+
Add a parameter ``--image-property`` to ``server create`` command.
5+
This parameter will filter a image which properties that are matching.

0 commit comments

Comments
 (0)