Browse Source

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
changes/78/805978/4
Tadeas Kot 4 months ago
parent
commit
920f5102cb
  1. 2
      ironicclient/common/http.py
  2. 38
      ironicclient/osc/v1/baremetal_driver.py
  3. 116
      ironicclient/tests/unit/osc/v1/test_baremetal_driver.py
  4. 32
      ironicclient/tests/unit/v1/test_driver.py
  5. 15
      ironicclient/v1/driver.py
  6. 9
      releasenotes/notes/add-driver-cli-fields-selector-b0f527eb5f6fb2a9.yaml

2
ironicclient/common/http.py

@ -37,7 +37,7 @@ from ironicclient import exc
# http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa
# for full details.
DEFAULT_VER = '1.9'
LAST_KNOWN_API_VERSION = 76
LAST_KNOWN_API_VERSION = 77
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
LOG = logging.getLogger(__name__)

38
ironicclient/osc/v1/baremetal_driver.py

@ -12,8 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
import itertools
import logging
from osc_lib.command import command
@ -39,11 +38,23 @@ class ListBaremetalDriver(command.Lister):
help='Type of driver ("classic" or "dynamic"). '
'The default is to list all of them.'
)
parser.add_argument(
display_group = parser.add_mutually_exclusive_group()
display_group.add_argument(
'--long',
action='store_true',
default=None,
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
def take_action(self, parsed_args):
@ -55,6 +66,12 @@ class ListBaremetalDriver(command.Lister):
if parsed_args.long:
labels = res_fields.DRIVER_DETAILED_RESOURCE.labels
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:
labels = res_fields.DRIVER_RESOURCE.labels
columns = res_fields.DRIVER_RESOURCE.fields
@ -213,13 +230,26 @@ class ShowBaremetalDriver(command.ShowOne):
'driver',
metavar='<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
def take_action(self, parsed_args):
self.log.debug("take_action(%s)", parsed_args)
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("properties", None)
# For list-type properties, show the values as comma separated

116
ironicclient/tests/unit/osc/v1/test_baremetal_driver.py

@ -146,6 +146,69 @@ class TestListBaremetalDriver(TestBaremetalDriver):
),)
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):
@ -362,7 +425,7 @@ class TestShowBaremetalDriver(TestBaremetalDriver):
columns, data = self.cmd.take_action(parsed_args)
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)
collist = ('default_bios_interface', 'default_boot_interface',
@ -420,3 +483,54 @@ class TestShowBaremetalDriver(TestBaremetalDriver):
self.assertRaises(oscutils.ParserException,
self.check_parser,
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)

32
ironicclient/tests/unit/v1/test_driver.py

@ -77,6 +77,13 @@ fake_responses = {
{'drivers': [DRIVER1]},
),
},
'/v1/drivers/?fields=name,hosts':
{
'GET': (
{},
{'drivers': [DRIVER1]}
)
},
'/v1/drivers/%s' % DRIVER1['name']:
{
'GET': (
@ -84,6 +91,13 @@ fake_responses = {
DRIVER1
),
},
'/v1/drivers/%s?fields=name,hosts' % DRIVER1['name']:
{
'GET': (
{},
DRIVER1,
),
},
'/v1/drivers/%s/properties' % DRIVER2['name']:
{
'GET': (
@ -136,6 +150,24 @@ class DriverManagerTest(testtools.TestCase):
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):
properties = self.mgr.properties(DRIVER2['name'])
expect = [

15
ironicclient/v1/driver.py

@ -28,7 +28,7 @@ class DriverManager(base.Manager):
_resource_name = 'drivers'
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.
: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.
:param global_request_id: String containing global request ID header
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.
"""
filters = []
if detail and fields:
raise exc.InvalidAttribute(_("Can't fetch a subset of fields "
"with 'detail' set"))
if driver_type is not None:
filters.append('type=%s' % driver_type)
if detail is not None:
filters.append('detail=%s' % detail)
if fields is not None:
filters.append('fields=%s' % ','.join(fields))
path = ''
if filters:
@ -57,8 +66,8 @@ class DriverManager(base.Manager):
global_request_id=global_request_id)
def get(self, driver_name, os_ironic_api_version=None,
global_request_id=None):
return self._get(resource_id=driver_name,
global_request_id=None, fields=None):
return self._get(resource_id=driver_name, fields=fields,
os_ironic_api_version=os_ironic_api_version,
global_request_id=global_request_id)

9
releasenotes/notes/add-driver-cli-fields-selector-b0f527eb5f6fb2a9.yaml

@ -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…
Cancel
Save