diff -pruN 4.8.1-1/debian/changelog 4.11.0-2/debian/changelog
--- 4.8.1-1/debian/changelog	2022-01-06 13:18:48.000000000 +0000
+++ 4.11.0-2/debian/changelog	2022-03-25 09:45:57.000000000 +0000
@@ -1,3 +1,16 @@
+python-ironicclient (4.11.0-2) unstable; urgency=medium
+
+  * Uploading to unstable.
+
+ -- Thomas Goirand <zigo@debian.org>  Fri, 25 Mar 2022 10:45:57 +0100
+
+python-ironicclient (4.11.0-1) experimental; urgency=medium
+
+  * New upstream release.
+  * Add autopkgtest.
+
+ -- Thomas Goirand <zigo@debian.org>  Fri, 25 Feb 2022 15:40:29 +0100
+
 python-ironicclient (4.8.1-1) unstable; urgency=medium
 
   * New usptream release.
diff -pruN 4.8.1-1/debian/tests/control 4.11.0-2/debian/tests/control
--- 4.8.1-1/debian/tests/control	1970-01-01 00:00:00.000000000 +0000
+++ 4.11.0-2/debian/tests/control	2022-03-25 09:45:57.000000000 +0000
@@ -0,0 +1,5 @@
+Tests: unittests
+Depends:
+ @,
+ @builddeps@,
+Restrictions: allow-stderr needs-root
diff -pruN 4.8.1-1/debian/tests/unittests 4.11.0-2/debian/tests/unittests
--- 4.8.1-1/debian/tests/unittests	1970-01-01 00:00:00.000000000 +0000
+++ 4.11.0-2/debian/tests/unittests	2022-03-25 09:45:57.000000000 +0000
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+set -e
+
+pkgos-dh_auto_test --no-py2 'ironicclient\.tests.*'
diff -pruN 4.8.1-1/doc/source/api_v1.rst 4.11.0-2/doc/source/api_v1.rst
--- 4.8.1-1/doc/source/api_v1.rst	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/doc/source/api_v1.rst	2022-01-04 20:58:06.000000000 +0000
@@ -8,24 +8,26 @@ The ironicclient python API lets you acc
 Bare Metal Provisioning Service.
 
 For example, to manipulate nodes, you interact with an
-`ironicclient.v1.node`_ object.
+:py:class:`ironicclient.v1.node.Node` object.
 You obtain access to nodes via attributes of the
-`ironicclient.v1.client.Client`_ object.
+:py:class:`ironicclient.v1.client.Client` object.
 
 Usage
 =====
 
 Get a Client object
 -------------------
-First, create an `ironicclient.v1.client.Client`_ instance by passing your
-credentials to `ironicclient.client.get_client()`_. By default, the
-Bare Metal Provisioning system is configured so that only administrators
+First, create an :py:class:`ironicclient.v1.client.Client` instance by passing
+your credentials to :py:meth:`ironicclient.client.get_client()`. By default,
+the Bare Metal Provisioning system is configured so that only administrators
 (users with 'admin' role) have access.
 
 .. note::
-    Explicit instantiation of `ironicclient.v1.client.Client`_ may cause
-    errors since it doesn't verify provided arguments, using
-    `ironicclient.client.get_client()` is preferred way to get client object.
+
+   Explicit instantiation of :py:class:`ironicclient.v1.client.Client` may
+   cause errors since it doesn't verify provided arguments, using
+   :py:meth:`ironicclient.client.get_client()` is preferred way to get client
+   object.
 
 There are two different sets of credentials that can be used::
 
@@ -81,15 +83,16 @@ To create a client, you can use the API
 Perform ironic operations
 -------------------------
 
-Once you have an ironic `Client`_, you can perform various tasks::
+Once you have an :py:class:`ironicclient.v1.client.Client`, you can perform
+various tasks::
 
    >>> ironic.driver.list()  # list of drivers
    >>> ironic.node.list()  # list of nodes
    >>> ironic.node.get(node_uuid)  # information about a particular node
 
-When the `Client`_ needs to propagate an exception, it will usually
-raise an instance subclassed from
-`ironicclient.exc.BaseException`_ or `ironicclient.exc.ClientException`_.
+When the Client needs to propagate an exception, it will usually raise an
+instance subclassed from
+:py:class:`ironicclient.common.apiclient.exceptions.ClientException`.
 
 Refer to the modules themselves, for more details.
 
@@ -97,10 +100,3 @@ ironicclient Modules
 ====================
 
 * :ref:`modindex`
-
-.. _ironicclient.v1.node: api/ironicclient.v1.node.html#ironicclient.v1.node.Node
-.. _ironicclient.v1.client.Client: api/ironicclient.v1.client.html#ironicclient.v1.client.Client
-.. _Client: api/ironicclient.v1.client.html#ironicclient.v1.client.Client
-.. _ironicclient.client.get_client(): api/ironicclient.client.html#ironicclient.client.get_client
-.. _ironicclient.exc.BaseException: api/ironicclient.exc.html#ironicclient.exc.BaseException
-.. _ironicclient.exc.ClientException: api/ironicclient.exc.html#ironicclient.exc.ClientException
diff -pruN 4.8.1-1/.gitreview 4.11.0-2/.gitreview
--- 4.8.1-1/.gitreview	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/.gitreview	2022-01-04 20:58:06.000000000 +0000
@@ -2,4 +2,3 @@
 host=review.opendev.org
 port=29418
 project=openstack/python-ironicclient.git
-defaultbranch=stable/xena
diff -pruN 4.8.1-1/ironicclient/common/http.py 4.11.0-2/ironicclient/common/http.py
--- 4.8.1-1/ironicclient/common/http.py	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/ironicclient/common/http.py	2022-01-04 20:58:06.000000000 +0000
@@ -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 = 78
 LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
 
 LOG = logging.getLogger(__name__)
diff -pruN 4.8.1-1/ironicclient/osc/v1/baremetal_driver.py 4.11.0-2/ironicclient/osc/v1/baremetal_driver.py
--- 4.8.1-1/ironicclient/osc/v1/baremetal_driver.py	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/ironicclient/osc/v1/baremetal_driver.py	2022-01-04 20:58:06.000000000 +0000
@@ -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.ShowOn
             '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
diff -pruN 4.8.1-1/ironicclient/osc/v1/baremetal_node.py 4.11.0-2/ironicclient/osc/v1/baremetal_node.py
--- 4.8.1-1/ironicclient/osc/v1/baremetal_node.py	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/ironicclient/osc/v1/baremetal_node.py	2022-01-04 20:58:06.000000000 +0000
@@ -243,7 +243,7 @@ class BootdeviceShowBaremetalNode(comman
 
 
 class BootmodeSetBaremetalNode(command.Command):
-    """Set boot mode for baremetal node"""
+    """Set the boot mode for the next baremetal node deployment"""
 
     log = logging.getLogger(__name__ + ".BootmodeSetBaremetalNode")
 
@@ -2131,3 +2131,77 @@ class BIOSSettingShowBaremetalNode(comma
             parsed_args.node, parsed_args.setting_name)
         setting.pop("links", None)
         return self.dict2columns(setting)
+
+
+class NodeHistoryList(command.Lister):
+    """Get history events for a baremetal node."""
+
+    log = logging.getLogger(__name__ + ".NodeHistoryList")
+
+    def get_parser(self, prog_name):
+        parser = super(NodeHistoryList, self).get_parser(prog_name)
+
+        parser.add_argument(
+            'node',
+            metavar='<node>',
+            help=_("Name or UUID of the node.")
+        )
+        parser.add_argument(
+            '--long',
+            default=False,
+            help=_("Show detailed information about the BIOS settings."),
+            action='store_true')
+        return parser
+
+    def take_action(self, parsed_args):
+        self.log.debug("take_action(%s)", parsed_args)
+
+        baremetal_client = self.app.client_manager.baremetal
+        if parsed_args.long:
+            labels = res_fields.NODE_HISTORY_DETAILED_RESOURCE.labels
+            fields = res_fields.NODE_HISTORY_DETAILED_RESOURCE.fields
+        else:
+            labels = res_fields.NODE_HISTORY_RESOURCE.labels
+            fields = res_fields.NODE_HISTORY_RESOURCE.fields
+
+        data = baremetal_client.node.get_history_list(
+            parsed_args.node,
+            parsed_args.long)
+
+        return (labels,
+                (oscutils.get_dict_properties(s, fields) for s in data))
+
+
+class NodeHistoryEventGet(command.ShowOne):
+    """Get history event for a baremetal node."""
+
+    log = logging.getLogger(__name__ + ".NodeHistoryEventGet")
+
+    def get_parser(self, prog_name):
+        parser = super(NodeHistoryEventGet, self).get_parser(prog_name)
+
+        parser.add_argument(
+            'node',
+            metavar='<node>',
+            help=_("Name or UUID of the node.")
+        )
+
+        parser.add_argument(
+            'event',
+            metavar='<event>',
+            help=_("UUID of the event.")
+        )
+
+        return parser
+
+    def take_action(self, parsed_args):
+        self.log.debug("take_action(%s)", parsed_args)
+
+        baremetal_client = self.app.client_manager.baremetal
+
+        data = baremetal_client.node.get_history_event(
+            parsed_args.node,
+            parsed_args.event)
+        data.pop('links')
+
+        return self.dict2columns(data)
diff -pruN 4.8.1-1/ironicclient/shell.py 4.11.0-2/ironicclient/shell.py
--- 4.8.1-1/ironicclient/shell.py	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/ironicclient/shell.py	2022-01-04 20:58:06.000000000 +0000
@@ -137,7 +137,8 @@ class App(app.App):
         self.config = os_config.OpenStackConfig(override_defaults=_DEFAULTS)
         super(App, self).__init__(description=_DESCRIPTION,
                                   version=str(version_info),
-                                  command_manager=mgr)
+                                  command_manager=mgr,
+                                  deferred_help=True)
 
     def build_option_parser(self, description, version, argparse_kwargs=None):
         parser = super(App, self).build_option_parser(
diff -pruN 4.8.1-1/ironicclient/tests/unit/osc/v1/fakes.py 4.11.0-2/ironicclient/tests/unit/osc/v1/fakes.py
--- 4.8.1-1/ironicclient/tests/unit/osc/v1/fakes.py	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/ironicclient/tests/unit/osc/v1/fakes.py	2022-01-04 20:58:06.000000000 +0000
@@ -229,6 +229,18 @@ DEPLOY_TEMPLATE = {
     'steps': baremetal_deploy_template_steps,
     'extra': baremetal_deploy_template_extra,
 }
+NODE_HISTORY = [
+    {
+        'uuid': 'abcdef1',
+        'created_at': 'time',
+        'severity': 'info',
+        'event': 'meow',
+        'event_type': 'purring',
+        'conductor': 'lap-conductor',
+        'user': '0191',
+        'links': {'href': 'url', 'rel': 'self'},
+    }
+]
 
 
 class TestBaremetal(utils.TestCommand):
diff -pruN 4.8.1-1/ironicclient/tests/unit/osc/v1/test_baremetal_driver.py 4.11.0-2/ironicclient/tests/unit/osc/v1/test_baremetal_driver.py
--- 4.8.1-1/ironicclient/tests/unit/osc/v1/test_baremetal_driver.py	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/ironicclient/tests/unit/osc/v1/test_baremetal_driver.py	2022-01-04 20:58:06.000000000 +0000
@@ -146,6 +146,69 @@ class TestListBaremetalDriver(TestBareme
         ),)
         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(TestBareme
         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',
@@ -419,4 +482,55 @@ class TestShowBaremetalDriver(TestBareme
 
         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)
diff -pruN 4.8.1-1/ironicclient/tests/unit/osc/v1/test_baremetal_node.py 4.11.0-2/ironicclient/tests/unit/osc/v1/test_baremetal_node.py
--- 4.8.1-1/ironicclient/tests/unit/osc/v1/test_baremetal_node.py	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/ironicclient/tests/unit/osc/v1/test_baremetal_node.py	2022-01-04 20:58:06.000000000 +0000
@@ -4188,3 +4188,59 @@ class TestBIOSSettingShow(TestBaremetal)
             'node_uuid', 'bios_name_1')
         expected_data = ('bios_name_1', 'bios_value_1')
         self.assertEqual(expected_data, tuple(data))
+
+
+class TestNodeHistoryEventList(TestBaremetal):
+    def setUp(self):
+        super(TestNodeHistoryEventList, self).setUp()
+
+        self.baremetal_mock.node.get_history_list.return_value = (
+            baremetal_fakes.NODE_HISTORY)
+
+        # Get the command object to test
+        self.cmd = baremetal_node.NodeHistoryList(self.app, None)
+
+    def test_baremetal_node_history_list(self):
+        arglist = ['node_uuid', '--long']
+        verifylist = [('node', 'node_uuid'), ('long', True)]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+        self.baremetal_mock.node.get_history_list.assert_called_once_with(
+            'node_uuid', True)
+        expected_columns = ('UUID', 'Created At', 'Severity',
+                            'Event Origin Type', 'Description of the event',
+                            'Conductor', 'User')
+        expected_data = (('abcdef1', 'time', 'info', 'purring', 'meow',
+                          'lap-conductor', '0191'),)
+        self.assertEqual(expected_columns, columns)
+        self.assertEqual(expected_data, tuple(data))
+
+
+class TestNodeHistoryEventGet(TestBaremetal):
+    def setUp(self):
+        super(TestNodeHistoryEventGet, self).setUp()
+
+        self.baremetal_mock.node.get_history_event.return_value = (
+            baremetal_fakes.NODE_HISTORY[0])
+
+        # Get the command object to test
+        self.cmd = baremetal_node.NodeHistoryEventGet(self.app, None)
+
+    def test_baremetal_node_history_list(self):
+        arglist = ['node_uuid', 'event_uuid']
+        verifylist = [('node', 'node_uuid'), ('event', 'event_uuid')]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        columns, data = self.cmd.take_action(parsed_args)
+        self.baremetal_mock.node.get_history_event.assert_called_once_with(
+            'node_uuid', 'event_uuid')
+        expected_columns = ('conductor', 'created_at', 'event', 'event_type',
+                            'severity', 'user', 'uuid')
+        expected_data = ('lap-conductor', 'time', 'meow', 'purring', 'info',
+                         '0191', 'abcdef1')
+
+        self.assertEqual(expected_columns, columns)
+        self.assertEqual(expected_data, tuple(data))
diff -pruN 4.8.1-1/ironicclient/tests/unit/v1/test_driver.py 4.11.0-2/ironicclient/tests/unit/v1/test_driver.py
--- 4.8.1-1/ironicclient/tests/unit/v1/test_driver.py	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/ironicclient/tests/unit/v1/test_driver.py	2022-01-04 20:58:06.000000000 +0000
@@ -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.TestCa
 
         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 = [
diff -pruN 4.8.1-1/ironicclient/tests/unit/v1/test_node.py 4.11.0-2/ironicclient/tests/unit/v1/test_node.py
--- 4.8.1-1/ironicclient/tests/unit/v1/test_node.py	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/ironicclient/tests/unit/v1/test_node.py	2022-01-04 20:58:06.000000000 +0000
@@ -1556,7 +1556,7 @@ class NodeManagerTest(testtools.TestCase
     def test_node_set_provision_state_with_configdrive(self):
         target_state = 'active'
         self.mgr.set_provision_state(NODE1['uuid'], target_state,
-                                     configdrive='foo')
+                                     configdrive=b'foo')
         body = {'target': target_state, 'configdrive': 'foo'}
         expect = [
             ('PUT', '/v1/nodes/%s/states/provision' % NODE1['uuid'], {}, body),
@@ -1616,6 +1616,16 @@ class NodeManagerTest(testtools.TestCase
         ]
         self.assertEqual(expect, self.api.calls)
 
+    def test_node_set_provision_state_fails_missing_dir_or_file(self):
+        target_state = 'active'
+
+        with common_utils.tempdir() as dirname:
+            self.assertRaisesRegex(ValueError,
+                                   'Config drive',
+                                   self.mgr.set_provision_state,
+                                   NODE1['uuid'], target_state,
+                                   configdrive=dirname + "/thisdoesnotexist")
+
     def test_node_set_provision_state_with_cleansteps(self):
         cleansteps = [{"step": "upgrade", "interface": "deploy"}]
         target_state = 'clean'
diff -pruN 4.8.1-1/ironicclient/v1/driver.py 4.11.0-2/ironicclient/v1/driver.py
--- 4.8.1-1/ironicclient/v1/driver.py	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/ironicclient/v1/driver.py	2022-01-04 20:58:06.000000000 +0000
@@ -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)
 
diff -pruN 4.8.1-1/ironicclient/v1/node.py 4.11.0-2/ironicclient/v1/node.py
--- 4.8.1-1/ironicclient/v1/node.py	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/ironicclient/v1/node.py	2022-01-04 20:58:06.000000000 +0000
@@ -714,12 +714,16 @@ class NodeManager(base.CreateManager):
         path = "%s/states/provision" % node_uuid
         body = {'target': state}
         if configdrive:
-            if not isinstance(configdrive, dict):
+            if isinstance(configdrive, str):
                 if os.path.isfile(configdrive):
                     with open(configdrive, 'rb') as f:
                         configdrive = f.read()
-                if os.path.isdir(configdrive):
+                elif os.path.isdir(configdrive):
                     configdrive = utils.make_configdrive(configdrive)
+                else:
+                    raise ValueError('Config drive seems to refer to a file '
+                                     'or directory but this file/directory '
+                                     'does not exist: %s.' % configdrive)
 
             if isinstance(configdrive, bytes):
                 try:
@@ -1008,3 +1012,56 @@ class NodeManager(base.CreateManager):
                       '%(state)s, the current state is %(actual)s',
                       {'node': node_ident, 'state': expected_state,
                        'actual': node.provision_state})
+
+    def get_history_list(self,
+                         node_ident,
+                         detail=False,
+                         os_ironic_api_version=None,
+                         global_request_id=None):
+        """Get node history event list.
+
+        Provides the ability to query a node event history list from
+        the API and return the API response to the caller.
+
+        Requires API version 1.78.
+
+        :param node_ident: The name or UUID of the node.
+        :param detail: If detailed data should be returned in the
+                       event list entry. Default False.
+        :param os_ironic_api_version: String version (e.g. "1.35") to use for
+            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.
+        """
+        path = "%s/history" % node_ident
+
+        if detail:
+            path = path + '/detail'
+
+        return self._list_primitives(
+            self._path(path), 'history',
+            os_ironic_api_version=os_ironic_api_version,
+            global_request_id=global_request_id)
+
+    def get_history_event(self,
+                          node_ident,
+                          event,
+                          os_ironic_api_version=None,
+                          global_request_id=None):
+        """Get a single event record for a node.
+
+        Provides the ability to request, and return
+        a node's single vent hisotyr entry.
+
+        :param node_ident: The name or UUID of the node.
+        :param event: The UUID of the event entry as listed
+                      in the node event history list.
+        :param os_ironic_api_version: String version (e.g. "1.35") to use for
+            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.
+        """
+        path = "%s/history/%s" % (node_ident, event)
+        return self._get_as_dict(
+            path, os_ironic_api_version=os_ironic_api_version,
+            global_request_id=global_request_id)
diff -pruN 4.8.1-1/ironicclient/v1/resource_fields.py 4.11.0-2/ironicclient/v1/resource_fields.py
--- 4.8.1-1/ironicclient/v1/resource_fields.py	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/ironicclient/v1/resource_fields.py	2022-01-04 20:58:06.000000000 +0000
@@ -79,6 +79,8 @@ class Resource(object):
         'enabled_storage_interfaces': 'Enabled Storage Interfaces',
         'enabled_vendor_interfaces': 'Enabled Vendor Interfaces',
         'extra': 'Extra',
+        'event': 'Description of the event',
+        'event_type': 'Event Origin Type',
         'hostname': 'Hostname',
         'hosts': 'Active host(s)',
         'http_methods': 'Supported HTTP methods',
@@ -140,8 +142,10 @@ class Resource(object):
         'raid_interface': 'RAID Interface',
         'rescue_interface': 'Rescue Interface',
         'storage_interface': 'Storage Interface',
+        'severity': 'Severity',
         'unique': 'Unique',
         'upper_bound': 'Upper Bound',
+        'user': 'User',
         'vendor_interface': 'Vendor Interface',
         'standalone_ports_supported': 'Standalone Ports Supported',
         'physical_network': 'Physical Network',
@@ -570,3 +574,21 @@ DEPLOY_TEMPLATE_RESOURCE = Resource(
      'name',
      ],
 )
+
+
+NODE_HISTORY_RESOURCE = Resource(
+    ['uuid',
+     'created_at',
+     'severity',
+     'event']
+)
+
+NODE_HISTORY_DETAILED_RESOURCE = Resource(
+    ['uuid',
+     'created_at',
+     'severity',
+     'event_type',
+     'event',
+     'conductor',
+     'user']
+)
diff -pruN 4.8.1-1/releasenotes/notes/add-driver-cli-fields-selector-b0f527eb5f6fb2a9.yaml 4.11.0-2/releasenotes/notes/add-driver-cli-fields-selector-b0f527eb5f6fb2a9.yaml
--- 4.8.1-1/releasenotes/notes/add-driver-cli-fields-selector-b0f527eb5f6fb2a9.yaml	1970-01-01 00:00:00.000000000 +0000
+++ 4.11.0-2/releasenotes/notes/add-driver-cli-fields-selector-b0f527eb5f6fb2a9.yaml	2022-01-04 20:58:06.000000000 +0000
@@ -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> ...]``
diff -pruN 4.8.1-1/releasenotes/notes/add-node-history-b9b9beeb0200f185.yaml 4.11.0-2/releasenotes/notes/add-node-history-b9b9beeb0200f185.yaml
--- 4.8.1-1/releasenotes/notes/add-node-history-b9b9beeb0200f185.yaml	1970-01-01 00:00:00.000000000 +0000
+++ 4.11.0-2/releasenotes/notes/add-node-history-b9b9beeb0200f185.yaml	2022-01-04 20:58:06.000000000 +0000
@@ -0,0 +1,9 @@
+---
+features:
+  - |
+    Adds support for API version ``1.78`` and "node history" functionality
+    allowing users to query a list of events, and retrieve a single history
+    event from a baremetal node.
+  - Adds ``get_history_list`` and ``get_history_event`` methods to the python
+    client library. These methods allow operators to query recorded node event
+    information from an ironic API, where supported and enabled.
diff -pruN 4.8.1-1/releasenotes/source/index.rst 4.11.0-2/releasenotes/source/index.rst
--- 4.8.1-1/releasenotes/source/index.rst	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/releasenotes/source/index.rst	2022-01-04 20:58:06.000000000 +0000
@@ -6,6 +6,7 @@
    :maxdepth: 1
 
    unreleased
+   xena
    wallaby
    victoria
    ussuri
diff -pruN 4.8.1-1/releasenotes/source/xena.rst 4.11.0-2/releasenotes/source/xena.rst
--- 4.8.1-1/releasenotes/source/xena.rst	1970-01-01 00:00:00.000000000 +0000
+++ 4.11.0-2/releasenotes/source/xena.rst	2022-01-04 20:58:06.000000000 +0000
@@ -0,0 +1,6 @@
+=========================
+Xena Series Release Notes
+=========================
+
+.. release-notes::
+   :branch: stable/xena
diff -pruN 4.8.1-1/setup.cfg 4.11.0-2/setup.cfg
--- 4.8.1-1/setup.cfg	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/setup.cfg	2022-01-04 20:58:06.000000000 +0000
@@ -19,6 +19,7 @@ classifier =
     Programming Language :: Python :: 3.6
     Programming Language :: Python :: 3.7
     Programming Language :: Python :: 3.8
+    Programming Language :: Python :: 3.9
 
 [files]
 packages = ironicclient
@@ -71,6 +72,8 @@ openstack.baremetal.v1 =
     baremetal_node_create = ironicclient.osc.v1.baremetal_node:CreateBaremetalNode
     baremetal_node_delete = ironicclient.osc.v1.baremetal_node:DeleteBaremetalNode
     baremetal_node_deploy = ironicclient.osc.v1.baremetal_node:DeployBaremetalNode
+    baremetal_node_history_list = ironicclient.osc.v1.baremetal_node:NodeHistoryList
+    baremetal_node_history_get = ironicclient.osc.v1.baremetal_node:NodeHistoryEventGet
     baremetal_node_inspect = ironicclient.osc.v1.baremetal_node:InspectBaremetalNode
     baremetal_node_list = ironicclient.osc.v1.baremetal_node:ListBaremetalNode
     baremetal_node_maintenance_set = ironicclient.osc.v1.baremetal_node:MaintenanceSetBaremetalNode
diff -pruN 4.8.1-1/tox.ini 4.11.0-2/tox.ini
--- 4.8.1-1/tox.ini	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/tox.ini	2022-01-04 20:58:06.000000000 +0000
@@ -14,7 +14,7 @@ setenv = VIRTUAL_ENV={envdir}
 usedevelop = True
 install_command = pip install {opts} {packages}
 deps =
-    -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/xena}
+    -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
     -r{toxinidir}/requirements.txt
     -r{toxinidir}/test-requirements.txt
 commands =
@@ -22,7 +22,7 @@ commands =
 
 [testenv:releasenotes]
 deps =
-  -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/xena}
+  -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
   -r{toxinidir}/doc/requirements.txt
 commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
 
@@ -49,7 +49,7 @@ commands =
 
 [testenv:venv]
 deps =
-  -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/xena}
+  -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
   -r{toxinidir}/test-requirements.txt
   -r{toxinidir}/requirements.txt
   -r{toxinidir}/doc/requirements.txt
@@ -63,7 +63,7 @@ setenv = TESTS_DIR=./ironicclient/tests/
 
 [testenv:docs]
 deps =
-  -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/xena}
+  -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
   -r{toxinidir}/doc/requirements.txt
 commands =
   sphinx-build -W -b html doc/source doc/build/html
diff -pruN 4.8.1-1/zuul.d/project.yaml 4.11.0-2/zuul.d/project.yaml
--- 4.8.1-1/zuul.d/project.yaml	2021-09-16 18:11:43.000000000 +0000
+++ 4.11.0-2/zuul.d/project.yaml	2022-01-04 20:58:06.000000000 +0000
@@ -3,7 +3,7 @@
       - check-requirements
       - openstack-cover-jobs
       - openstack-lower-constraints-master-branch-jobs
-      - openstack-python3-xena-jobs
+      - openstack-python3-yoga-jobs
       - openstackclient-plugin-jobs
       - publish-openstack-docs-pti
       - release-notes-jobs-python3
