diff --git a/ironicclient/osc/v1/baremetal_driver.py b/ironicclient/osc/v1/baremetal_driver.py new file mode 100644 index 000000000..07cb72ba1 --- /dev/null +++ b/ironicclient/osc/v1/baremetal_driver.py @@ -0,0 +1,164 @@ +# Copyright (c) 2016 Mirantis, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + + +import logging + +from osc_lib.command import command +from osc_lib import utils as oscutils + +from ironicclient.common import utils +from ironicclient.v1 import resource_fields as res_fields + + +class ListBaremetalDriver(command.Lister): + """List the enabled drivers.""" + + log = logging.getLogger(__name__ + ".ListBaremetalDriver") + + def get_parser(self, prog_name): + parser = super(ListBaremetalDriver, self).get_parser(prog_name) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + client = self.app.client_manager.baremetal + + labels = ['Supported driver(s)', 'Active host(s)'] + columns = ['name', 'hosts'] + + drivers = client.driver.list() + drivers = oscutils.sort_items(drivers, 'name') + for d in drivers: + d.hosts = ', '.join(d.hosts) + + return (labels, + (oscutils.get_item_properties(s, columns) for s in drivers)) + + +class PassthruCallBaremetalDriver(command.ShowOne): + """Call a vendor passthru method for a driver.""" + + log = logging.getLogger(__name__ + ".PassthruCallBaremetalDriver") + http_methods = ['POST', 'PUT', 'GET', 'DELETE', 'PATCH'] + + def get_parser(self, prog_name): + parser = super(PassthruCallBaremetalDriver, self).get_parser(prog_name) + parser.add_argument( + 'driver', + metavar='', + help='Name of the driver.' + ) + parser.add_argument( + 'method', + metavar='', + help="Vendor passthru method to be called." + ) + parser.add_argument( + '--arg', + metavar='', + action='append', + help="Argument to pass to the passthru method (repeat option " + "to specify multiple arguments)." + ) + parser.add_argument( + '--http-method', + dest='http_method', + metavar='', + choices=self.http_methods, + default='POST', + help="The HTTP method to use in the passthru request. One of " + "%s. Defaults to 'POST'." % + oscutils.format_list(self.http_methods) + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + baremetal_client = self.app.client_manager.baremetal + + arguments = utils.args_array_to_dict( + {'args': parsed_args.arg}, 'args')['args'] + if not arguments: + arguments = {} + + response = (baremetal_client.driver. + vendor_passthru(parsed_args.driver, + parsed_args.method, + http_method=parsed_args.http_method, + args=arguments)) + + return self.dict2columns(response) + + +class PassthruListBaremetalDriver(command.Lister): + """List available vendor passthru methods for a driver.""" + + log = logging.getLogger(__name__ + ".PassthruListBaremetalDriver") + + def get_parser(self, prog_name): + parser = super(PassthruListBaremetalDriver, self).get_parser(prog_name) + parser.add_argument( + 'driver', + metavar='', + help='Name of the driver.') + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + baremetal_client = self.app.client_manager.baremetal + + columns = res_fields.VENDOR_PASSTHRU_METHOD_RESOURCE.fields + labels = res_fields.VENDOR_PASSTHRU_METHOD_RESOURCE.labels + + methods = baremetal_client.driver.get_vendor_passthru_methods( + parsed_args.driver) + + params = [] + for method, response in methods.items(): + response['name'] = method + http_methods = ', '.join(response['http_methods']) + response['http_methods'] = http_methods + params.append(response) + + return (labels, + (oscutils.get_dict_properties(s, columns) for s in params)) + + +class ShowBaremetalDriver(command.ShowOne): + """Show information about a driver.""" + + log = logging.getLogger(__name__ + ".ShowBaremetalDriver") + + def get_parser(self, prog_name): + parser = super(ShowBaremetalDriver, self).get_parser(prog_name) + parser.add_argument( + 'driver', + metavar='', + help='Name of the driver.') + 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 + 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', ())) + return zip(*sorted(driver.items())) diff --git a/ironicclient/tests/unit/osc/v1/fakes.py b/ironicclient/tests/unit/osc/v1/fakes.py index 2f8111e01..a08b4ac2e 100644 --- a/ironicclient/tests/unit/osc/v1/fakes.py +++ b/ironicclient/tests/unit/osc/v1/fakes.py @@ -47,6 +47,21 @@ BAREMETAL_PORT = { 'node_uuid': baremetal_uuid, } +baremetal_driver_hosts = ['fake-host1', 'fake-host2'] +baremetal_driver_name = 'fakedrivername' + +BAREMETAL_DRIVER = { + 'hosts': baremetal_driver_hosts, + 'name': baremetal_driver_name, +} + +baremetal_driver_passthru_method = 'lookup' + +BAREMETAL_DRIVER_PASSTHRU = {"lookup": {"attach": "false", + "http_methods": ["POST"], + "description": "", + "async": "false"}} + class TestBaremetal(utils.TestCommand): diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_driver.py b/ironicclient/tests/unit/osc/v1/test_baremetal_driver.py new file mode 100644 index 000000000..f2bb45114 --- /dev/null +++ b/ironicclient/tests/unit/osc/v1/test_baremetal_driver.py @@ -0,0 +1,223 @@ +# Copyright (c) 2016 Mirantis, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import copy + +from osc_lib.tests import utils as oscutils + +from ironicclient.osc.v1 import baremetal_driver +from ironicclient.tests.unit.osc.v1 import fakes as baremetal_fakes + + +class TestBaremetalDriver(baremetal_fakes.TestBaremetal): + + def setUp(self): + super(TestBaremetalDriver, self).setUp() + + self.baremetal_mock = self.app.client_manager.baremetal + self.baremetal_mock.reset_mock() + + +class TestListBaremetalDriver(TestBaremetalDriver): + + def setUp(self): + super(TestListBaremetalDriver, self).setUp() + + self.baremetal_mock.driver.list.return_value = [ + baremetal_fakes.FakeBaremetalResource( + None, + copy.deepcopy(baremetal_fakes.BAREMETAL_DRIVER), + loaded=True) + ] + self.cmd = baremetal_driver.ListBaremetalDriver(self.app, None) + + def test_baremetal_driver_list(self): + arglist = [] + verifylist = [] + + 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)) + + +class TestPassthruCallBaremetalDriver(TestBaremetalDriver): + + def setUp(self): + super(TestPassthruCallBaremetalDriver, self).setUp() + + self.baremetal_mock.driver.vendor_passthru.return_value = ( + baremetal_fakes.BAREMETAL_DRIVER_PASSTHRU + ) + + self.cmd = baremetal_driver.PassthruCallBaremetalDriver(self.app, None) + + def test_baremetal_driver_passthru_call_with_min_args(self): + + arglist = [ + baremetal_fakes.baremetal_driver_name, + baremetal_fakes.baremetal_driver_passthru_method, + ] + + verifylist = [ + ('driver', baremetal_fakes.baremetal_driver_name), + ('method', baremetal_fakes.baremetal_driver_passthru_method), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + # Set expected values + args = [ + baremetal_fakes.baremetal_driver_name, + baremetal_fakes.baremetal_driver_passthru_method, + ] + kwargs = { + 'http_method': 'POST', + 'args': {} + } + (self.baremetal_mock.driver.vendor_passthru. + assert_called_once_with(*args, **kwargs)) + + def test_baremetal_driver_passthru_call_with_all_args(self): + + arglist = [ + baremetal_fakes.baremetal_driver_name, + baremetal_fakes.baremetal_driver_passthru_method, + '--arg', 'arg1=val1', '--arg', 'arg2=val2', + '--http-method', 'POST' + ] + + verifylist = [ + ('driver', baremetal_fakes.baremetal_driver_name), + ('method', baremetal_fakes.baremetal_driver_passthru_method), + ('arg', ['arg1=val1', 'arg2=val2']), + ('http_method', 'POST') + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + # Set expected values + args = [ + baremetal_fakes.baremetal_driver_name, + baremetal_fakes.baremetal_driver_passthru_method, + ] + kwargs = { + 'http_method': 'POST', + 'args': {'arg1': 'val1', 'arg2': 'val2'} + } + (self.baremetal_mock.driver.vendor_passthru. + assert_called_once_with(*args, **kwargs)) + + def test_baremetal_driver_passthru_call_no_arg(self): + arglist = [] + verifylist = [] + + self.assertRaises(oscutils.ParserException, + self.check_parser, + self.cmd, arglist, verifylist) + + +class TestPassthruListBaremetalDriver(TestBaremetalDriver): + + def setUp(self): + super(TestPassthruListBaremetalDriver, self).setUp() + + self.baremetal_mock.driver.get_vendor_passthru_methods.return_value = ( + baremetal_fakes.BAREMETAL_DRIVER_PASSTHRU + ) + self.cmd = baremetal_driver.PassthruListBaremetalDriver(self.app, None) + + def test_baremetal_driver_passthru_list(self): + arglist = ['fakedrivername'] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + args = ['fakedrivername'] + (self.baremetal_mock.driver.get_vendor_passthru_methods. + assert_called_with(*args)) + + collist = ( + "Name", + "Supported HTTP methods", + "Async", + "Description", + "Response is attachment", + ) + self.assertEqual(collist, tuple(columns)) + + datalist = (('lookup', 'POST', 'false', '', 'false'),) + + self.assertEqual(datalist, tuple(data)) + + def test_baremetal_driver_passthru_list_no_arg(self): + arglist = [] + verifylist = [] + + self.assertRaises(oscutils.ParserException, + self.check_parser, + self.cmd, arglist, verifylist) + + +class TestShowBaremetalDriver(TestBaremetalDriver): + + def setUp(self): + super(TestShowBaremetalDriver, self).setUp() + + self.baremetal_mock.driver.get.return_value = ( + baremetal_fakes.FakeBaremetalResource( + None, + copy.deepcopy(baremetal_fakes.BAREMETAL_DRIVER), + loaded=True)) + self.cmd = baremetal_driver.ShowBaremetalDriver(self.app, None) + + def test_baremetal_driver_show(self): + arglist = ['fakedrivername'] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + args = ['fakedrivername'] + self.baremetal_mock.driver.get.assert_called_with(*args) + self.assertFalse(self.baremetal_mock.driver.properties.called) + + collist = ('hosts', 'name') + self.assertEqual(collist, columns) + + datalist = ( + ', '.join(baremetal_fakes.baremetal_driver_hosts), + baremetal_fakes.baremetal_driver_name) + + self.assertEqual(datalist, tuple(data)) + + def test_baremetal_driver_show_no_arg(self): + arglist = [] + verifylist = [] + + self.assertRaises(oscutils.ParserException, + self.check_parser, + self.cmd, arglist, verifylist) diff --git a/releasenotes/notes/osc-plugin-b82e1edd55fb6185.yaml b/releasenotes/notes/osc-plugin-b82e1edd55fb6185.yaml new file mode 100644 index 000000000..17f43cced --- /dev/null +++ b/releasenotes/notes/osc-plugin-b82e1edd55fb6185.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Extend the OpenStackClient plugin with new commands: + + * openstack baremetal driver list + * openstack baremetal driver passthru call + * openstack baremetal driver passthru list + * openstack baremetal driver show + diff --git a/setup.cfg b/setup.cfg index 54e91f72d..41efd1973 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,6 +31,10 @@ openstack.cli.extension = openstack.baremetal.v1 = baremetal_create = ironicclient.osc.v1.baremetal_create:CreateBaremetal baremetal_delete = ironicclient.osc.v1.baremetal_node:DeleteBaremetal + baremetal_driver_list = ironicclient.osc.v1.baremetal_driver:ListBaremetalDriver + baremetal_driver_passthru_call = ironicclient.osc.v1.baremetal_driver:PassthruCallBaremetalDriver + baremetal_driver_passthru_list = ironicclient.osc.v1.baremetal_driver:PassthruListBaremetalDriver + baremetal_driver_show = ironicclient.osc.v1.baremetal_driver:ShowBaremetalDriver baremetal_list = ironicclient.osc.v1.baremetal_node:ListBaremetal baremetal_node_abort = ironicclient.osc.v1.baremetal_node:AbortBaremetalNode baremetal_node_adopt = ironicclient.osc.v1.baremetal_node:AdoptBaremetalNode