Extends driver-list, driver-show supporting new hardware types

- Extend the driver-list command with --type argument, which, if supplied,
limits the driver list to only classic drivers (classic value) or
hardware types (dynamic value), and --detail to show detailed info
of drivers.

- Extend the output of the driver-show command with more info of driver.

Change-Id: I5f72c47805ae9f761250f500098bfef4d502e419
Partial-Bug: #1524745
This commit is contained in:
Dao Cong Tien 2017-01-12 12:04:17 +07:00
parent 3d2389ed1a
commit 9fbcadfff3
11 changed files with 396 additions and 30 deletions

View File

@ -27,6 +27,7 @@ import tempfile
from oslo_serialization import base64
from oslo_utils import strutils
import six
from ironicclient.common.i18n import _
from ironicclient import exc
@ -145,6 +146,27 @@ def args_array_to_patch(op, attributes):
return patch
def convert_list_props_to_comma_separated(data, props=None):
"""Convert the list-type properties to comma-separated strings
:param data: the input dict object.
:param props: the properties whose values will be converted.
Default to None to convert all list-type properties of the input.
:returns: the result dict instance.
"""
result = dict(data)
if props is None:
props = data.keys()
for prop in props:
val = data.get(prop, None)
if isinstance(val, list):
result[prop] = ', '.join(map(six.text_type, val))
return result
def common_params_for_list(args, fields, field_labels):
"""Generate 'params' dict that is common for every 'list' command.

View File

@ -32,22 +32,43 @@ class ListBaremetalDriver(command.Lister):
def get_parser(self, prog_name):
parser = super(ListBaremetalDriver, self).get_parser(prog_name)
parser.add_argument(
'--type',
metavar='<type>',
choices=["classic", "dynamic"],
help='Type of driver ("classic" or "dynamic"). '
'The default is to list all of them.'
)
parser.add_argument(
'--long',
action='store_true',
default=None,
help="Show detailed information about the drivers.")
return parser
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
client = self.app.client_manager.baremetal
params = {'driver_type': parsed_args.type,
'detail': parsed_args.long}
if parsed_args.long:
labels = res_fields.DRIVER_DETAILED_RESOURCE.labels
columns = res_fields.DRIVER_DETAILED_RESOURCE.fields
else:
labels = res_fields.DRIVER_RESOURCE.labels
columns = res_fields.DRIVER_RESOURCE.fields
drivers = client.driver.list()
drivers = client.driver.list(**params)
drivers = oscutils.sort_items(drivers, 'name')
for d in drivers:
d.hosts = ', '.join(d.hosts)
# For list-type properties, show the values as comma separated
# strings. It's easier to read.
data = [utils.convert_list_props_to_comma_separated(d._info)
for d in drivers]
return (labels,
(oscutils.get_item_properties(s, columns) for s in drivers))
(oscutils.get_dict_properties(s, columns) for s in data))
class PassthruCallBaremetalDriver(command.ShowOne):
@ -154,8 +175,7 @@ class ShowBaremetalDriver(command.ShowOne):
driver = baremetal_client.driver.get(parsed_args.driver)._info
driver.pop("links", None)
driver.pop("properties", None)
# NOTE(rloo): this will show the hosts as a comma-separated string
# whereas 'ironic driver show' displays this as a list
# of hosts (eg "host1, host2" vs "[u'host1', u'host2']"
driver['hosts'] = ', '.join(driver.get('hosts', ()))
# For list-type properties, show the values as comma separated
# strings. It's easier to read.
driver = utils.convert_list_props_to_comma_separated(driver)
return zip(*sorted(driver.items()))

View File

@ -139,6 +139,28 @@ class UtilsTest(test_utils.BaseTestCase):
self.assertRaises(exc.CommandError, utils.check_for_invalid_fields,
['a', 'd'], ['a', 'b', 'c'])
def test_convert_list_props_to_comma_separated_strings(self):
data = {'prop1': 'val1',
'prop2': ['item1', 'item2', 'item3']}
result = utils.convert_list_props_to_comma_separated(data)
self.assertEqual('val1', result['prop1'])
self.assertEqual('item1, item2, item3', result['prop2'])
def test_convert_list_props_to_comma_separated_mix(self):
data = {'prop1': 'val1',
'prop2': [1, 2.5, 'item3']}
result = utils.convert_list_props_to_comma_separated(data)
self.assertEqual('val1', result['prop1'])
self.assertEqual('1, 2.5, item3', result['prop2'])
def test_convert_list_props_to_comma_separated_partial(self):
data = {'prop1': [1, 2, 3],
'prop2': [1, 2.5, 'item3']}
result = utils.convert_list_props_to_comma_separated(
data, props=['prop2'])
self.assertEqual([1, 2, 3], result['prop1'])
self.assertEqual('1, 2.5, item3', result['prop2'])
class CommonParamsForListTest(test_utils.BaseTestCase):
def setUp(self):

View File

@ -61,10 +61,48 @@ BAREMETAL_PORT = {
baremetal_driver_hosts = ['fake-host1', 'fake-host2']
baremetal_driver_name = 'fakedrivername'
baremetal_driver_type = 'classic'
baremetal_driver_default_boot_if = 'boot'
baremetal_driver_default_console_if = 'console'
baremetal_driver_default_deploy_if = 'deploy'
baremetal_driver_default_inspect_if = 'inspect'
baremetal_driver_default_management_if = 'management'
baremetal_driver_default_network_if = 'network'
baremetal_driver_default_power_if = 'power'
baremetal_driver_default_raid_if = 'raid'
baremetal_driver_default_vendor_if = 'vendor'
baremetal_driver_enabled_boot_ifs = ['boot', 'boot2']
baremetal_driver_enabled_console_ifs = ['console', 'console2']
baremetal_driver_enabled_deploy_ifs = ['deploy', 'deploy2']
baremetal_driver_enabled_inspect_ifs = ['inspect', 'inspect2']
baremetal_driver_enabled_management_ifs = ['management', 'management2']
baremetal_driver_enabled_network_ifs = ['network', 'network2']
baremetal_driver_enabled_power_ifs = ['power', 'power2']
baremetal_driver_enabled_raid_ifs = ['raid', 'raid2']
baremetal_driver_enabled_vendor_ifs = ['vendor', 'vendor2']
BAREMETAL_DRIVER = {
'hosts': baremetal_driver_hosts,
'name': baremetal_driver_name,
'type': baremetal_driver_type,
'default_boot_interface': baremetal_driver_default_boot_if,
'default_console_interface': baremetal_driver_default_console_if,
'default_deploy_interface': baremetal_driver_default_deploy_if,
'default_inspect_interface': baremetal_driver_default_inspect_if,
'default_management_interface': baremetal_driver_default_management_if,
'default_network_interface': baremetal_driver_default_network_if,
'default_power_interface': baremetal_driver_default_power_if,
'default_raid_interface': baremetal_driver_default_raid_if,
'default_vendor_interface': baremetal_driver_default_vendor_if,
'enabled_boot_interfaces': baremetal_driver_enabled_boot_ifs,
'enabled_console_interfaces': baremetal_driver_enabled_console_ifs,
'enabled_deploy_interfaces': baremetal_driver_enabled_deploy_ifs,
'enabled_inspect_interfaces': baremetal_driver_enabled_inspect_ifs,
'enabled_management_interfaces': baremetal_driver_enabled_management_ifs,
'enabled_network_interfaces': baremetal_driver_enabled_network_ifs,
'enabled_power_interfaces': baremetal_driver_enabled_power_ifs,
'enabled_raid_interfaces': baremetal_driver_enabled_raid_ifs,
'enabled_vendor_interfaces': baremetal_driver_enabled_vendor_ifs,
}
baremetal_driver_passthru_method = 'lookup'

View File

@ -60,6 +60,80 @@ class TestListBaremetalDriver(TestBaremetalDriver):
', '.join(baremetal_fakes.baremetal_driver_hosts)), )
self.assertEqual(datalist, tuple(data))
def test_baremetal_driver_list_with_type(self):
arglist = ['--type', baremetal_fakes.baremetal_driver_type]
verifylist = [('type', baremetal_fakes.baremetal_driver_type)]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
collist = (
"Supported driver(s)",
"Active host(s)")
self.assertEqual(collist, tuple(columns))
datalist = ((
baremetal_fakes.baremetal_driver_name,
', '.join(baremetal_fakes.baremetal_driver_hosts)),)
self.assertEqual(datalist, tuple(data))
def test_baremetal_driver_list_with_detail(self):
arglist = ['--long']
verifylist = [('long', True)]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
collist = (
"Supported driver(s)",
"Type",
"Active host(s)",
'Default Boot Interface',
'Default Console Interface',
'Default Deploy Interface',
'Default Inspect Interface',
'Default Management Interface',
'Default Network Interface',
'Default Power Interface',
'Default RAID Interface',
'Default Vendor Interface',
'Enabled Boot Interfaces',
'Enabled Console Interfaces',
'Enabled Deploy Interfaces',
'Enabled Inspect Interfaces',
'Enabled Management Interfaces',
'Enabled Network Interfaces',
'Enabled Power Interfaces',
'Enabled RAID Interfaces',
'Enabled Vendor Interfaces'
)
self.assertEqual(collist, tuple(columns))
datalist = ((
baremetal_fakes.baremetal_driver_name,
baremetal_fakes.baremetal_driver_type,
', '.join(baremetal_fakes.baremetal_driver_hosts),
baremetal_fakes.baremetal_driver_default_boot_if,
baremetal_fakes.baremetal_driver_default_console_if,
baremetal_fakes.baremetal_driver_default_deploy_if,
baremetal_fakes.baremetal_driver_default_inspect_if,
baremetal_fakes.baremetal_driver_default_management_if,
baremetal_fakes.baremetal_driver_default_network_if,
baremetal_fakes.baremetal_driver_default_power_if,
baremetal_fakes.baremetal_driver_default_raid_if,
baremetal_fakes.baremetal_driver_default_vendor_if,
', '.join(baremetal_fakes.baremetal_driver_enabled_boot_ifs),
', '.join(baremetal_fakes.baremetal_driver_enabled_console_ifs),
', '.join(baremetal_fakes.baremetal_driver_enabled_deploy_ifs),
', '.join(baremetal_fakes.baremetal_driver_enabled_inspect_ifs),
', '.join(baremetal_fakes.baremetal_driver_enabled_management_ifs),
', '.join(baremetal_fakes.baremetal_driver_enabled_network_ifs),
', '.join(baremetal_fakes.baremetal_driver_enabled_power_ifs),
', '.join(baremetal_fakes.baremetal_driver_enabled_raid_ifs),
', '.join(baremetal_fakes.baremetal_driver_enabled_vendor_ifs),
),)
self.assertEqual(datalist, tuple(data))
class TestPassthruCallBaremetalDriver(TestBaremetalDriver):
@ -205,12 +279,41 @@ class TestShowBaremetalDriver(TestBaremetalDriver):
self.baremetal_mock.driver.get.assert_called_with(*args)
self.assertFalse(self.baremetal_mock.driver.properties.called)
collist = ('hosts', 'name')
collist = ('default_boot_interface', 'default_console_interface',
'default_deploy_interface', 'default_inspect_interface',
'default_management_interface', 'default_network_interface',
'default_power_interface', 'default_raid_interface',
'default_vendor_interface', 'enabled_boot_interfaces',
'enabled_console_interfaces', 'enabled_deploy_interfaces',
'enabled_inspect_interfaces',
'enabled_management_interfaces',
'enabled_network_interfaces', 'enabled_power_interfaces',
'enabled_raid_interfaces', 'enabled_vendor_interfaces',
'hosts', 'name', 'type')
self.assertEqual(collist, columns)
datalist = (
baremetal_fakes.baremetal_driver_default_boot_if,
baremetal_fakes.baremetal_driver_default_console_if,
baremetal_fakes.baremetal_driver_default_deploy_if,
baremetal_fakes.baremetal_driver_default_inspect_if,
baremetal_fakes.baremetal_driver_default_management_if,
baremetal_fakes.baremetal_driver_default_network_if,
baremetal_fakes.baremetal_driver_default_power_if,
baremetal_fakes.baremetal_driver_default_raid_if,
baremetal_fakes.baremetal_driver_default_vendor_if,
', '.join(baremetal_fakes.baremetal_driver_enabled_boot_ifs),
', '.join(baremetal_fakes.baremetal_driver_enabled_console_ifs),
', '.join(baremetal_fakes.baremetal_driver_enabled_deploy_ifs),
', '.join(baremetal_fakes.baremetal_driver_enabled_inspect_ifs),
', '.join(baremetal_fakes.baremetal_driver_enabled_management_ifs),
', '.join(baremetal_fakes.baremetal_driver_enabled_network_ifs),
', '.join(baremetal_fakes.baremetal_driver_enabled_power_ifs),
', '.join(baremetal_fakes.baremetal_driver_enabled_raid_ifs),
', '.join(baremetal_fakes.baremetal_driver_enabled_vendor_ifs),
', '.join(baremetal_fakes.baremetal_driver_hosts),
baremetal_fakes.baremetal_driver_name)
baremetal_fakes.baremetal_driver_name,
baremetal_fakes.baremetal_driver_type)
self.assertEqual(datalist, tuple(data))

View File

@ -22,8 +22,34 @@ from ironicclient.tests.unit import utils
from ironicclient.v1 import driver
DRIVER1 = {'name': 'fake', 'hosts': ['fake-host1', 'fake-host2']}
DRIVER2 = {'name': 'pxe_ipminative', 'hosts': ['fake-host1', 'fake-host2']}
DRIVER1 = {
'name': 'fake',
'type': 'dynamic',
'hosts': ['fake-host1', 'fake-host2'],
'default_boot_interface': 'boot',
'default_console_interface': 'console',
'default_deploy_interface': 'deploy',
'default_inspect_interface': 'inspect',
'default_management_interface': 'management',
'default_network_interface': 'network',
'default_power_interface': 'power',
'default_raid_interface': 'raid',
'default_vendor_interface': 'vendor',
'enabled_boot_interfaces': ['boot', 'boot2'],
'enabled_console_interfaces': ['console', 'console2'],
'enabled_deploy_interfaces': ['deploy', 'deploy2'],
'enabled_inspect_interfaces': ['inspect', 'inspect2'],
'enabled_management_interfaces': ['management', 'management2'],
'enabled_network_interfaces': ['network', 'network2'],
'enabled_power_interfaces': ['power', 'power2'],
'enabled_raid_interfaces': ['raid', 'raid2'],
'enabled_vendor_interfaces': ['vendor', 'vendor2'],
}
DRIVER2 = {
'name': 'pxe_ipminative',
'type': 'classic',
'hosts': ['fake-host1', 'fake-host2'],
}
DRIVER2_PROPERTIES = {
"username": "username. Required.",
@ -100,8 +126,12 @@ class DriverManagerTest(testtools.TestCase):
('GET', '/v1/drivers/%s' % DRIVER1['name'], {}, None)
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(DRIVER1['name'], driver_.name)
self.assertEqual(DRIVER1['hosts'], driver_.hosts)
driver_attr = {}
for attr in DRIVER1.keys():
driver_attr[attr] = getattr(driver_, attr)
self.assertEqual(DRIVER1, driver_attr)
def test_driver_properties(self):
properties = self.mgr.properties(DRIVER2['name'])

View File

@ -34,7 +34,17 @@ class DriverShellTest(utils.BaseTestCase):
with mock.patch.object(cliutils, 'print_dict', fake_print_dict):
driver = object()
d_shell._print_driver_show(driver)
exp = ['hosts', 'name']
exp = ['hosts', 'name', 'type',
'default_boot_interface', 'default_console_interface',
'default_deploy_interface', 'default_inspect_interface',
'default_management_interface', 'default_network_interface',
'default_power_interface', 'default_raid_interface',
'default_vendor_interface',
'enabled_boot_interfaces', 'enabled_console_interfaces',
'enabled_deploy_interfaces', 'enabled_inspect_interfaces',
'enabled_management_interfaces', 'enabled_network_interfaces',
'enabled_power_interfaces', 'enabled_raid_interfaces',
'enabled_vendor_interfaces']
act = actual.keys()
self.assertEqual(sorted(exp), sorted(act))
@ -145,9 +155,35 @@ class DriverShellTest(utils.BaseTestCase):
def test_do_driver_list(self):
client_mock = self.client_mock
args = mock.MagicMock()
args.type = None
args.detail = None
args.json = False
d_shell.do_driver_list(client_mock, args)
client_mock.driver.list.assert_called_once_with()
client_mock.driver.list.assert_called_once_with(driver_type=None,
detail=None)
def test_do_driver_list_with_type_and_no_detail(self):
client_mock = self.client_mock
args = mock.MagicMock()
args.type = 'classic'
args.detail = False
args.json = False
d_shell.do_driver_list(client_mock, args)
client_mock.driver.list.assert_called_once_with(driver_type='classic',
detail=False)
def test_do_driver_list_with_detail(self):
client_mock = self.client_mock
args = mock.MagicMock()
args.type = None
args.detail = True
args.json = False
d_shell.do_driver_list(client_mock, args)
client_mock.driver.list.assert_called_once_with(driver_type=None,
detail=True)
def test_do_driver_get_vendor_passthru_methods(self):
client_mock = mock.MagicMock()

View File

@ -27,8 +27,28 @@ class DriverManager(base.Manager):
resource_class = Driver
_resource_name = 'drivers'
def list(self):
return self._list('/v1/drivers', self._resource_name)
def list(self, driver_type=None, detail=None):
"""Retrieve a list of drivers.
:param driver_type: Optional, string to filter the drivers by type.
Value should be 'classic' or 'dynamic'.
:param detail: Optional, flag whether to return detailed information
about drivers. Default is None means not to send the arg
to the server due to older versions of the server cannot
handle filtering on detail.
:returns: A list of drivers.
"""
filters = []
if driver_type is not None:
filters.append('type=%s' % driver_type)
if detail is not None:
filters.append('detail=%s' % detail)
path = ''
if filters:
path = '?' + '&'.join(filters)
return self._list(self._path(path), self._resource_name)
def get(self, driver_name):
return self._get(resource_id=driver_name)

View File

@ -22,21 +22,37 @@ from ironicclient.v1 import utils as v1_utils
def _print_driver_show(driver, json=False):
fields = res_fields.DRIVER_RESOURCE.fields
fields = res_fields.DRIVER_DETAILED_RESOURCE.fields
data = dict([(f, getattr(driver, f, '')) for f in fields])
cliutils.print_dict(data, wrap=72, json_flag=json)
@cliutils.arg('-t', '--type',
metavar='<type>',
choices=["classic", "dynamic"],
help='Type of driver ("classic" or "dynamic"). '
'The default is to list all of them.')
@cliutils.arg('--detail',
dest='detail',
action='store_true',
default=None,
help="Show detailed information about the drivers.")
def do_driver_list(cc, args):
"""List the enabled drivers."""
drivers = cc.driver.list()
# NOTE(lucasagomes): Separate each host by a comma.
# It's easier to read.
for d in drivers:
d.hosts = ', '.join(d.hosts)
drivers = cc.driver.list(driver_type=args.type, detail=args.detail)
# NOTE(lucasagomes): For list-type properties, show the values as
# comma separated strings. It's easier to read.
data = [utils.convert_list_props_to_comma_separated(d._info)
for d in drivers]
if args.detail:
field_labels = res_fields.DRIVER_DETAILED_RESOURCE.labels
fields = res_fields.DRIVER_DETAILED_RESOURCE.fields
else:
field_labels = res_fields.DRIVER_RESOURCE.labels
fields = res_fields.DRIVER_RESOURCE.fields
cliutils.print_list(drivers, fields, field_labels=field_labels,
cliutils.print_list(data, fields, field_labels=field_labels,
json_flag=args.json)

View File

@ -38,10 +38,28 @@ class Resource(object):
'clean_step': 'Clean Step',
'console_enabled': 'Console Enabled',
'created_at': 'Created At',
'default_boot_interface': 'Default Boot Interface',
'default_console_interface': 'Default Console Interface',
'default_deploy_interface': 'Default Deploy Interface',
'default_inspect_interface': 'Default Inspect Interface',
'default_management_interface': 'Default Management Interface',
'default_network_interface': 'Default Network Interface',
'default_power_interface': 'Default Power Interface',
'default_raid_interface': 'Default RAID Interface',
'default_vendor_interface': 'Default Vendor Interface',
'description': 'Description',
'driver': 'Driver',
'driver_info': 'Driver Info',
'driver_internal_info': 'Driver Internal Info',
'enabled_boot_interfaces': 'Enabled Boot Interfaces',
'enabled_console_interfaces': 'Enabled Console Interfaces',
'enabled_deploy_interfaces': 'Enabled Deploy Interfaces',
'enabled_inspect_interfaces': 'Enabled Inspect Interfaces',
'enabled_management_interfaces': 'Enabled Management Interfaces',
'enabled_network_interfaces': 'Enabled Network Interfaces',
'enabled_power_interfaces': 'Enabled Power Interfaces',
'enabled_raid_interfaces': 'Enabled RAID Interfaces',
'enabled_vendor_interfaces': 'Enabled Vendor Interfaces',
'extra': 'Extra',
'hosts': 'Active host(s)',
'http_methods': 'Supported HTTP methods',
@ -66,6 +84,7 @@ class Resource(object):
'target_power_state': 'Target Power State',
'target_provision_state': 'Target Provision State',
'target_raid_config': 'Target RAID configuration',
'type': 'Type',
'updated_at': 'Updated At',
'uuid': 'UUID',
'local_link_connection': 'Local Link Connection',
@ -158,7 +177,6 @@ CHASSIS_RESOURCE = Resource(
'description',
])
# Nodes
NODE_DETAILED_RESOURCE = Resource(
['chassis_uuid',
@ -292,6 +310,31 @@ VIF_RESOURCE = Resource(
)
# Drivers
DRIVER_DETAILED_RESOURCE = Resource(
['name',
'type',
'hosts',
'default_boot_interface',
'default_console_interface',
'default_deploy_interface',
'default_inspect_interface',
'default_management_interface',
'default_network_interface',
'default_power_interface',
'default_raid_interface',
'default_vendor_interface',
'enabled_boot_interfaces',
'enabled_console_interfaces',
'enabled_deploy_interfaces',
'enabled_inspect_interfaces',
'enabled_management_interfaces',
'enabled_network_interfaces',
'enabled_power_interfaces',
'enabled_raid_interfaces',
'enabled_vendor_interfaces'
],
override_labels={'name': 'Supported driver(s)'}
)
DRIVER_RESOURCE = Resource(
['name',
'hosts',

View File

@ -0,0 +1,16 @@
---
features:
- |
To support dynamic drivers (available starting with ironic API
microversion 1.30):
* ironic driver-list has two new optional arguments, ``--type <type>``
for the type of driver ('classic' or 'dynamic') to list, and
``--detail`` to show detailed information about the drivers.
* ironic driver-show returns a lot more information, including the type
of driver and the default and enabled interfaces.
* openstack baremetal driver list has two new optional arguments,
``--type <type>`` for the type of driver ('classic' or 'dynamic')
to list, and ``--long`` to show detailed information about the drivers.
* openstack baremetal driver show returns a lot more information,
including the type of driver and the default and enabled interfaces.