Add support for fields in drivers CLI
This commit add support for fields selector to the driver CLI. * ``openstack baremetal driver list --fields <field> [<field> ...]`` * ``openstack baremetal driver show <driver_name> --fields <field> [<field> ...]`` Depends-On: https://review.opendev.org/c/openstack/ironic/+/804416 Story: 1674775 Task: 43043 Change-Id: I2d691feec876f6978d5075e779ea465ed660f09e
This commit is contained in:
parent
b5df386022
commit
920f5102cb
@ -37,7 +37,7 @@ from ironicclient import exc
|
|||||||
# http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa
|
# http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa
|
||||||
# for full details.
|
# for full details.
|
||||||
DEFAULT_VER = '1.9'
|
DEFAULT_VER = '1.9'
|
||||||
LAST_KNOWN_API_VERSION = 76
|
LAST_KNOWN_API_VERSION = 77
|
||||||
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
|
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
@ -12,8 +12,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
|
import itertools
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from osc_lib.command import command
|
from osc_lib.command import command
|
||||||
@ -39,11 +38,23 @@ class ListBaremetalDriver(command.Lister):
|
|||||||
help='Type of driver ("classic" or "dynamic"). '
|
help='Type of driver ("classic" or "dynamic"). '
|
||||||
'The default is to list all of them.'
|
'The default is to list all of them.'
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
display_group = parser.add_mutually_exclusive_group()
|
||||||
|
display_group.add_argument(
|
||||||
'--long',
|
'--long',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
default=None,
|
default=None,
|
||||||
help="Show detailed information about the drivers.")
|
help="Show detailed information about the drivers.")
|
||||||
|
display_group.add_argument(
|
||||||
|
'--fields',
|
||||||
|
nargs='+',
|
||||||
|
dest='fields',
|
||||||
|
metavar='<field>',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
choices=res_fields.DRIVER_DETAILED_RESOURCE.fields,
|
||||||
|
help=_("One or more node fields. Only these fields will be "
|
||||||
|
"fetched from the server. Can not be used when '--long' "
|
||||||
|
"is specified."))
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
@ -55,6 +66,12 @@ class ListBaremetalDriver(command.Lister):
|
|||||||
if parsed_args.long:
|
if parsed_args.long:
|
||||||
labels = res_fields.DRIVER_DETAILED_RESOURCE.labels
|
labels = res_fields.DRIVER_DETAILED_RESOURCE.labels
|
||||||
columns = res_fields.DRIVER_DETAILED_RESOURCE.fields
|
columns = res_fields.DRIVER_DETAILED_RESOURCE.fields
|
||||||
|
elif parsed_args.fields:
|
||||||
|
fields = itertools.chain.from_iterable(parsed_args.fields)
|
||||||
|
resource = res_fields.Resource(list(fields))
|
||||||
|
columns = resource.fields
|
||||||
|
labels = resource.labels
|
||||||
|
params['fields'] = columns
|
||||||
else:
|
else:
|
||||||
labels = res_fields.DRIVER_RESOURCE.labels
|
labels = res_fields.DRIVER_RESOURCE.labels
|
||||||
columns = res_fields.DRIVER_RESOURCE.fields
|
columns = res_fields.DRIVER_RESOURCE.fields
|
||||||
@ -213,13 +230,26 @@ class ShowBaremetalDriver(command.ShowOne):
|
|||||||
'driver',
|
'driver',
|
||||||
metavar='<driver>',
|
metavar='<driver>',
|
||||||
help=_('Name of the driver.'))
|
help=_('Name of the driver.'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--fields',
|
||||||
|
nargs='+',
|
||||||
|
dest='fields',
|
||||||
|
metavar='<field>',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
choices=res_fields.DRIVER_DETAILED_RESOURCE.fields,
|
||||||
|
help=_("One or more node fields. Only these fields will be "
|
||||||
|
"fetched from the server."))
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
self.log.debug("take_action(%s)", parsed_args)
|
self.log.debug("take_action(%s)", parsed_args)
|
||||||
baremetal_client = self.app.client_manager.baremetal
|
baremetal_client = self.app.client_manager.baremetal
|
||||||
|
|
||||||
driver = baremetal_client.driver.get(parsed_args.driver)._info
|
fields = list(itertools.chain.from_iterable(parsed_args.fields))
|
||||||
|
fields = fields if fields else None
|
||||||
|
driver = baremetal_client.driver.get(parsed_args.driver,
|
||||||
|
fields=fields)._info
|
||||||
driver.pop("links", None)
|
driver.pop("links", None)
|
||||||
driver.pop("properties", None)
|
driver.pop("properties", None)
|
||||||
# For list-type properties, show the values as comma separated
|
# For list-type properties, show the values as comma separated
|
||||||
|
@ -146,6 +146,69 @@ class TestListBaremetalDriver(TestBaremetalDriver):
|
|||||||
),)
|
),)
|
||||||
self.assertEqual(datalist, tuple(data))
|
self.assertEqual(datalist, tuple(data))
|
||||||
|
|
||||||
|
def test_baremetal_driver_list_fields(self):
|
||||||
|
arglist = [
|
||||||
|
'--fields', 'name', 'hosts'
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('fields', [['name', 'hosts']])
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
kwargs = {
|
||||||
|
'driver_type': None,
|
||||||
|
'detail': None,
|
||||||
|
'fields': ('name', 'hosts')
|
||||||
|
}
|
||||||
|
|
||||||
|
self.baremetal_mock.driver.list.assert_called_with(**kwargs)
|
||||||
|
|
||||||
|
def test_baremetal_driver_list_fields_multiple(self):
|
||||||
|
arglist = [
|
||||||
|
'--fields', 'name',
|
||||||
|
'--fields', 'hosts', 'type'
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('fields', [['name'], ['hosts', 'type']])
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
kwargs = {
|
||||||
|
'driver_type': None,
|
||||||
|
'detail': None,
|
||||||
|
'fields': ('name', 'hosts', 'type')
|
||||||
|
}
|
||||||
|
|
||||||
|
self.baremetal_mock.driver.list.assert_called_with(**kwargs)
|
||||||
|
|
||||||
|
def test_baremetal_driver_list_invalid_fields(self):
|
||||||
|
arglist = [
|
||||||
|
'--fields', 'name', 'invalid'
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('fields', [['name', 'invalid']])
|
||||||
|
]
|
||||||
|
self.assertRaises(oscutils.ParserException,
|
||||||
|
self.check_parser,
|
||||||
|
self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
def test_baremetal_driver_list_fields_with_long(self):
|
||||||
|
arglist = [
|
||||||
|
'--fields', 'name', 'hosts',
|
||||||
|
'--long'
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('fields', [['name', 'invalid']]),
|
||||||
|
('long', True)
|
||||||
|
]
|
||||||
|
self.assertRaises(oscutils.ParserException,
|
||||||
|
self.check_parser,
|
||||||
|
self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
|
||||||
class TestListBaremetalDriverProperty(TestBaremetalDriver):
|
class TestListBaremetalDriverProperty(TestBaremetalDriver):
|
||||||
|
|
||||||
@ -362,7 +425,7 @@ class TestShowBaremetalDriver(TestBaremetalDriver):
|
|||||||
columns, data = self.cmd.take_action(parsed_args)
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
args = ['fakedrivername']
|
args = ['fakedrivername']
|
||||||
self.baremetal_mock.driver.get.assert_called_with(*args)
|
self.baremetal_mock.driver.get.assert_called_with(*args, fields=None)
|
||||||
self.assertFalse(self.baremetal_mock.driver.properties.called)
|
self.assertFalse(self.baremetal_mock.driver.properties.called)
|
||||||
|
|
||||||
collist = ('default_bios_interface', 'default_boot_interface',
|
collist = ('default_bios_interface', 'default_boot_interface',
|
||||||
@ -420,3 +483,54 @@ class TestShowBaremetalDriver(TestBaremetalDriver):
|
|||||||
self.assertRaises(oscutils.ParserException,
|
self.assertRaises(oscutils.ParserException,
|
||||||
self.check_parser,
|
self.check_parser,
|
||||||
self.cmd, arglist, verifylist)
|
self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
def test_baremetal_driver_show_fields(self):
|
||||||
|
arglist = [
|
||||||
|
'fakedrivername',
|
||||||
|
'--fields', 'name', 'hosts'
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('driver', 'fakedrivername'),
|
||||||
|
('fields', [['name', 'hosts']])
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
args = ['fakedrivername']
|
||||||
|
fields = ['name', 'hosts']
|
||||||
|
self.baremetal_mock.driver.get.assert_called_with(*args, fields=fields)
|
||||||
|
self.assertFalse(self.baremetal_mock.driver.properties.called)
|
||||||
|
|
||||||
|
def test_baremetal_driver_show_fields_multiple(self):
|
||||||
|
arglist = [
|
||||||
|
'fakedrivername',
|
||||||
|
'--fields', 'name',
|
||||||
|
'--fields', 'hosts', 'type'
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('driver', 'fakedrivername'),
|
||||||
|
('fields', [['name'], ['hosts', 'type']])
|
||||||
|
]
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
args = ['fakedrivername']
|
||||||
|
fields = ['name', 'hosts', 'type']
|
||||||
|
self.baremetal_mock.driver.get.assert_called_with(*args, fields=fields)
|
||||||
|
self.assertFalse(self.baremetal_mock.driver.properties.called)
|
||||||
|
|
||||||
|
def test_baremetal_driver_show_invalid_fields(self):
|
||||||
|
arglist = [
|
||||||
|
'fakedrivername',
|
||||||
|
'--fields', 'name', 'invalid'
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('driver', 'fakedrivername'),
|
||||||
|
('fields', [['name', 'invalid']])
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertRaises(oscutils.ParserException,
|
||||||
|
self.check_parser,
|
||||||
|
self.cmd, arglist, verifylist)
|
||||||
|
@ -77,6 +77,13 @@ fake_responses = {
|
|||||||
{'drivers': [DRIVER1]},
|
{'drivers': [DRIVER1]},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
'/v1/drivers/?fields=name,hosts':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'drivers': [DRIVER1]}
|
||||||
|
)
|
||||||
|
},
|
||||||
'/v1/drivers/%s' % DRIVER1['name']:
|
'/v1/drivers/%s' % DRIVER1['name']:
|
||||||
{
|
{
|
||||||
'GET': (
|
'GET': (
|
||||||
@ -84,6 +91,13 @@ fake_responses = {
|
|||||||
DRIVER1
|
DRIVER1
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
'/v1/drivers/%s?fields=name,hosts' % DRIVER1['name']:
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
DRIVER1,
|
||||||
|
),
|
||||||
|
},
|
||||||
'/v1/drivers/%s/properties' % DRIVER2['name']:
|
'/v1/drivers/%s/properties' % DRIVER2['name']:
|
||||||
{
|
{
|
||||||
'GET': (
|
'GET': (
|
||||||
@ -136,6 +150,24 @@ class DriverManagerTest(testtools.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(DRIVER1, driver_attr)
|
self.assertEqual(DRIVER1, driver_attr)
|
||||||
|
|
||||||
|
def test_driver_list_fields(self):
|
||||||
|
drivers = self.mgr.list(fields=['name', 'hosts'])
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/drivers/?fields=name,hosts', {}, None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertThat(drivers, matchers.HasLength(1))
|
||||||
|
|
||||||
|
def test_driver_show_fields(self):
|
||||||
|
driver_ = self.mgr.get(DRIVER1['name'], fields=['name', 'hosts'])
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/drivers/%s?fields=name,hosts' %
|
||||||
|
DRIVER1['name'], {}, None)
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(DRIVER1['name'], driver_.name)
|
||||||
|
self.assertEqual(DRIVER1['hosts'], driver_.hosts)
|
||||||
|
|
||||||
def test_driver_properties(self):
|
def test_driver_properties(self):
|
||||||
properties = self.mgr.properties(DRIVER2['name'])
|
properties = self.mgr.properties(DRIVER2['name'])
|
||||||
expect = [
|
expect = [
|
||||||
|
@ -28,7 +28,7 @@ class DriverManager(base.Manager):
|
|||||||
_resource_name = 'drivers'
|
_resource_name = 'drivers'
|
||||||
|
|
||||||
def list(self, driver_type=None, detail=None, os_ironic_api_version=None,
|
def list(self, driver_type=None, detail=None, os_ironic_api_version=None,
|
||||||
global_request_id=None):
|
global_request_id=None, fields=None):
|
||||||
"""Retrieve a list of drivers.
|
"""Retrieve a list of drivers.
|
||||||
|
|
||||||
:param driver_type: Optional, string to filter the drivers by type.
|
:param driver_type: Optional, string to filter the drivers by type.
|
||||||
@ -41,13 +41,22 @@ class DriverManager(base.Manager):
|
|||||||
the request. If not specified, the client's default is used.
|
the request. If not specified, the client's default is used.
|
||||||
:param global_request_id: String containing global request ID header
|
:param global_request_id: String containing global request ID header
|
||||||
value (in form "req-<UUID>") to use for the request.
|
value (in form "req-<UUID>") to use for the request.
|
||||||
|
:param fields: Optional, a list with a specified set of fields
|
||||||
|
of the resource to be returned. Can not be used
|
||||||
|
when 'detail' is set.
|
||||||
:returns: A list of drivers.
|
:returns: A list of drivers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
filters = []
|
filters = []
|
||||||
|
if detail and fields:
|
||||||
|
raise exc.InvalidAttribute(_("Can't fetch a subset of fields "
|
||||||
|
"with 'detail' set"))
|
||||||
if driver_type is not None:
|
if driver_type is not None:
|
||||||
filters.append('type=%s' % driver_type)
|
filters.append('type=%s' % driver_type)
|
||||||
if detail is not None:
|
if detail is not None:
|
||||||
filters.append('detail=%s' % detail)
|
filters.append('detail=%s' % detail)
|
||||||
|
if fields is not None:
|
||||||
|
filters.append('fields=%s' % ','.join(fields))
|
||||||
|
|
||||||
path = ''
|
path = ''
|
||||||
if filters:
|
if filters:
|
||||||
@ -57,8 +66,8 @@ class DriverManager(base.Manager):
|
|||||||
global_request_id=global_request_id)
|
global_request_id=global_request_id)
|
||||||
|
|
||||||
def get(self, driver_name, os_ironic_api_version=None,
|
def get(self, driver_name, os_ironic_api_version=None,
|
||||||
global_request_id=None):
|
global_request_id=None, fields=None):
|
||||||
return self._get(resource_id=driver_name,
|
return self._get(resource_id=driver_name, fields=fields,
|
||||||
os_ironic_api_version=os_ironic_api_version,
|
os_ironic_api_version=os_ironic_api_version,
|
||||||
global_request_id=global_request_id)
|
global_request_id=global_request_id)
|
||||||
|
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds support for fields selector in driver cli.
|
||||||
|
See `story 1674775
|
||||||
|
<https://storyboard.openstack.org/#!/story/1674775>`_.
|
||||||
|
|
||||||
|
* ``openstack baremetal driver list --fields <field> [<field> ...]``
|
||||||
|
* ``openstack baremetal driver show <driver_name> --fields <field> [<field> ...]``
|
Loading…
Reference in New Issue
Block a user