diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index fd88e8736c..8707d111ac 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -561,6 +561,7 @@ configdrive: * ``network_data`` (optional) - JSON object with networking configuration. * ``user_data`` (optional) - user data. May be a string (which will be UTF-8 encoded); a JSON object, or a JSON array. + * ``vendor_data`` (optional) - JSON object with extra vendor data. This parameter is only accepted when setting the state to "active" or "rebuild". diff --git a/doc/source/contributor/webapi-version-history.rst b/doc/source/contributor/webapi-version-history.rst index 338539e9e3..6b18ba8d1d 100644 --- a/doc/source/contributor/webapi-version-history.rst +++ b/doc/source/contributor/webapi-version-history.rst @@ -2,6 +2,12 @@ REST API Version History ======================== +1.59 (Ussuri, master) + +Added the ability to specify a ``vendor_data`` dictionary field in the +``configdrive`` parameter submitted with the deployment of a node. The value +is a dictionary which is served as ``vendor_data2.json`` in the config drive. + 1.58 (Train, 12.2.0) -------------------- diff --git a/ironic/api/controllers/v1/utils.py b/ironic/api/controllers/v1/utils.py index 2949325fc0..98a6a1462f 100644 --- a/ironic/api/controllers/v1/utils.py +++ b/ironic/api/controllers/v1/utils.py @@ -616,7 +616,8 @@ _CONFIG_DRIVE_SCHEMA = { 'network_data': {'type': 'object'}, 'user_data': { 'type': ['object', 'array', 'string', 'null'] - } + }, + 'vendor_data': {'type': 'object'}, }, 'additionalProperties': False }, @@ -648,13 +649,22 @@ def check_allow_configdrive(target, configdrive=None): raise wsme.exc.ClientSideError( msg, status_code=http_client.BAD_REQUEST) - if isinstance(configdrive, dict) and not allow_build_configdrive(): - msg = _('Providing a JSON object for configdrive is only supported' - ' starting with API version %(base)s.%(opr)s') % { - 'base': versions.BASE_VERSION, - 'opr': versions.MINOR_56_BUILD_CONFIGDRIVE} - raise wsme.exc.ClientSideError( - msg, status_code=http_client.BAD_REQUEST) + if isinstance(configdrive, dict): + if not allow_build_configdrive(): + msg = _('Providing a JSON object for configdrive is only supported' + ' starting with API version %(base)s.%(opr)s') % { + 'base': versions.BASE_VERSION, + 'opr': versions.MINOR_56_BUILD_CONFIGDRIVE} + raise wsme.exc.ClientSideError( + msg, status_code=http_client.BAD_REQUEST) + if ('vendor_data' in configdrive and + not allow_configdrive_vendor_data()): + msg = _('Providing vendor_data in configdrive is only supported' + ' starting with API version %(base)s.%(opr)s') % { + 'base': versions.BASE_VERSION, + 'opr': versions.MINOR_59_CONFIGDRIVE_VENDOR_DATA} + raise wsme.exc.ClientSideError( + msg, status_code=http_client.BAD_REQUEST) def check_allow_filter_by_fault(fault): @@ -1163,6 +1173,15 @@ def allow_build_configdrive(): return api.request.version.minor >= versions.MINOR_56_BUILD_CONFIGDRIVE +def allow_configdrive_vendor_data(): + """Check if configdrive can contain a vendor_data key. + + Version 1.59 of the API added support for configdrive vendor_data. + """ + return (api.request.version.minor >= + versions.MINOR_59_CONFIGDRIVE_VENDOR_DATA) + + def allow_allocation_update(): """Check if updating an existing allocation is allowed or not. diff --git a/ironic/api/controllers/v1/versions.py b/ironic/api/controllers/v1/versions.py index cadd2b169b..f4ab84618b 100644 --- a/ironic/api/controllers/v1/versions.py +++ b/ironic/api/controllers/v1/versions.py @@ -96,6 +96,7 @@ BASE_VERSION = 1 # v1.56: Add support for building configdrives. # v1.57: Add support for updating an exisiting allocation. # v1.58: Add support for backfilling allocations. +# v1.59: Add support vendor data in configdrives. MINOR_0_JUNO = 0 MINOR_1_INITIAL_VERSION = 1 @@ -156,6 +157,7 @@ MINOR_55_DEPLOY_TEMPLATES = 55 MINOR_56_BUILD_CONFIGDRIVE = 56 MINOR_57_ALLOCATION_UPDATE = 57 MINOR_58_ALLOCATION_BACKFILL = 58 +MINOR_59_CONFIGDRIVE_VENDOR_DATA = 59 # When adding another version, update: # - MINOR_MAX_VERSION @@ -163,7 +165,7 @@ MINOR_58_ALLOCATION_BACKFILL = 58 # explanation of what changed in the new version # - common/release_mappings.py, RELEASE_MAPPING['master']['api'] -MINOR_MAX_VERSION = MINOR_58_ALLOCATION_BACKFILL +MINOR_MAX_VERSION = MINOR_59_CONFIGDRIVE_VENDOR_DATA # String representations of the minor and maximum versions _MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION) diff --git a/ironic/common/release_mappings.py b/ironic/common/release_mappings.py index 2f3e33e4cd..695e3d8978 100644 --- a/ironic/common/release_mappings.py +++ b/ironic/common/release_mappings.py @@ -197,7 +197,7 @@ RELEASE_MAPPING = { } }, 'master': { - 'api': '1.58', + 'api': '1.59', 'rpc': '1.48', 'objects': { 'Allocation': ['1.0'], diff --git a/ironic/conductor/utils.py b/ironic/conductor/utils.py index 1c6898c443..ebd75238f7 100644 --- a/ironic/conductor/utils.py +++ b/ironic/conductor/utils.py @@ -831,7 +831,7 @@ def build_configdrive(node, configdrive): :param node: an Ironic node object. :param configdrive: A configdrive as a dict with keys ``meta_data``, - ``network_data`` and ``user_data`` (all optional). + ``network_data``, ``user_data`` and ``vendor_data`` (all optional). :returns: A gzipped and base64 encoded configdrive as a string. """ meta_data = configdrive.setdefault('meta_data', {}) @@ -847,7 +847,8 @@ def build_configdrive(node, configdrive): LOG.debug('Building a configdrive for node %s', node.uuid) return os_configdrive.build(meta_data, user_data=user_data, - network_data=configdrive.get('network_data')) + network_data=configdrive.get('network_data'), + vendor_data=configdrive.get('vendor_data')) def fast_track_able(task): diff --git a/ironic/tests/unit/api/controllers/v1/test_node.py b/ironic/tests/unit/api/controllers/v1/test_node.py index f5c5440a8b..acd7c0df89 100644 --- a/ironic/tests/unit/api/controllers/v1/test_node.py +++ b/ironic/tests/unit/api/controllers/v1/test_node.py @@ -4162,11 +4162,12 @@ class TestPut(test_api_base.BaseApiTest): def test_provision_with_deploy_configdrive_as_dict_all_fields(self): fake_cd = {'user_data': {'serialize': 'me'}, 'meta_data': {'hostname': 'example.com'}, - 'network_data': {'links': []}} + 'network_data': {'links': []}, + 'vendor_data': {'foo': 'bar'}} ret = self.put_json('/nodes/%s/states/provision' % self.node.uuid, {'target': states.ACTIVE, 'configdrive': fake_cd}, - headers={api_base.Version.string: '1.56'}) + headers={api_base.Version.string: '1.59'}) self.assertEqual(http_client.ACCEPTED, ret.status_code) self.assertEqual(b'', ret.body) self.mock_dnd.assert_called_once_with(context=mock.ANY, diff --git a/ironic/tests/unit/api/controllers/v1/test_utils.py b/ironic/tests/unit/api/controllers/v1/test_utils.py index d18fded4a6..c1d06d1d78 100644 --- a/ironic/tests/unit/api/controllers/v1/test_utils.py +++ b/ironic/tests/unit/api/controllers/v1/test_utils.py @@ -479,6 +479,12 @@ class TestCheckAllowFields(base.TestCase): mock_request.version.minor = 34 self.assertFalse(utils.allow_node_rebuild_with_configdrive()) + def test_allow_configdrive_vendor_data(self, mock_request): + mock_request.version.minor = 59 + self.assertTrue(utils.allow_configdrive_vendor_data()) + mock_request.version.minor = 58 + self.assertFalse(utils.allow_configdrive_vendor_data()) + def test_check_allow_configdrive_fails(self, mock_request): mock_request.version.minor = 35 self.assertRaises(wsme.exc.ClientSideError, @@ -500,16 +506,27 @@ class TestCheckAllowFields(base.TestCase): utils.check_allow_configdrive(states.ACTIVE, "abcd") def test_check_allow_configdrive_as_dict(self, mock_request): - mock_request.version.minor = 56 + mock_request.version.minor = 59 utils.check_allow_configdrive(states.ACTIVE, {'meta_data': {}}) utils.check_allow_configdrive(states.ACTIVE, {'meta_data': {}, 'network_data': {}, - 'user_data': {}}) + 'user_data': {}, + 'vendor_data': {}}) utils.check_allow_configdrive(states.ACTIVE, {'user_data': 'foo'}) utils.check_allow_configdrive(states.ACTIVE, {'user_data': ['foo']}) + def test_check_allow_configdrive_vendor_data_failed(self, mock_request): + mock_request.version.minor = 58 + self.assertRaises(wsme.exc.ClientSideError, + utils.check_allow_configdrive, + states.ACTIVE, + {'meta_data': {}, + 'network_data': {}, + 'user_data': {}, + 'vendor_data': {}}) + def test_check_allow_configdrive_as_dict_invalid(self, mock_request): - mock_request.version.minor = 56 + mock_request.version.minor = 59 self.assertRaises(wsme.exc.ClientSideError, utils.check_allow_configdrive, states.REBUILD, {'foo': 'bar'}) diff --git a/ironic/tests/unit/conductor/test_manager.py b/ironic/tests/unit/conductor/test_manager.py index ac0b60716e..de2bc5f720 100644 --- a/ironic/tests/unit/conductor/test_manager.py +++ b/ironic/tests/unit/conductor/test_manager.py @@ -2205,7 +2205,7 @@ class DoNodeDeployTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): configdrive = 'foo' self._test__do_node_deploy_ok(configdrive=configdrive) - @mock.patch('openstack.baremetal.configdrive.build', autospec=True) + @mock.patch('openstack.baremetal.configdrive.build') def test__do_node_deploy_configdrive_as_dict(self, mock_cd): mock_cd.return_value = 'foo' configdrive = {'user_data': 'abcd'} @@ -2213,9 +2213,10 @@ class DoNodeDeployTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): expected_configdrive='foo') mock_cd.assert_called_once_with({'uuid': self.node.uuid}, network_data=None, - user_data=b'abcd') + user_data=b'abcd', + vendor_data=None) - @mock.patch('openstack.baremetal.configdrive.build', autospec=True) + @mock.patch('openstack.baremetal.configdrive.build') def test__do_node_deploy_configdrive_as_dict_with_meta_data(self, mock_cd): mock_cd.return_value = 'foo' configdrive = {'meta_data': {'uuid': uuidutils.generate_uuid(), @@ -2225,9 +2226,10 @@ class DoNodeDeployTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): expected_configdrive='foo') mock_cd.assert_called_once_with(configdrive['meta_data'], network_data=None, - user_data=None) + user_data=None, + vendor_data=None) - @mock.patch('openstack.baremetal.configdrive.build', autospec=True) + @mock.patch('openstack.baremetal.configdrive.build') def test__do_node_deploy_configdrive_with_network_data(self, mock_cd): mock_cd.return_value = 'foo' configdrive = {'network_data': {'links': []}} @@ -2235,9 +2237,10 @@ class DoNodeDeployTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): expected_configdrive='foo') mock_cd.assert_called_once_with({'uuid': self.node.uuid}, network_data={'links': []}, - user_data=None) + user_data=None, + vendor_data=None) - @mock.patch('openstack.baremetal.configdrive.build', autospec=True) + @mock.patch('openstack.baremetal.configdrive.build') def test__do_node_deploy_configdrive_and_user_data_as_dict(self, mock_cd): mock_cd.return_value = 'foo' configdrive = {'user_data': {'user': 'data'}} @@ -2245,7 +2248,19 @@ class DoNodeDeployTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): expected_configdrive='foo') mock_cd.assert_called_once_with({'uuid': self.node.uuid}, network_data=None, - user_data=b'{"user": "data"}') + user_data=b'{"user": "data"}', + vendor_data=None) + + @mock.patch('openstack.baremetal.configdrive.build') + def test__do_node_deploy_configdrive_with_vendor_data(self, mock_cd): + mock_cd.return_value = 'foo' + configdrive = {'vendor_data': {'foo': 'bar'}} + self._test__do_node_deploy_ok(configdrive=configdrive, + expected_configdrive='foo') + mock_cd.assert_called_once_with({'uuid': self.node.uuid}, + network_data=None, + user_data=None, + vendor_data={'foo': 'bar'}) @mock.patch.object(swift, 'SwiftAPI') @mock.patch('ironic.drivers.modules.fake.FakeDeploy.prepare') diff --git a/lower-constraints.txt b/lower-constraints.txt index de22850c30..8d1909027d 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -20,7 +20,7 @@ keystoneauth1==3.15.0 keystonemiddleware==4.17.0 mock==3.0.0 openstackdocstheme==1.20.0 -openstacksdk==0.31.2 +openstacksdk==0.37.0 os-api-ref==1.4.0 os-traits==0.4.0 oslo.concurrency==3.26.0 diff --git a/releasenotes/notes/configdrive-vendordata-122049bd7c6e1b67.yaml b/releasenotes/notes/configdrive-vendordata-122049bd7c6e1b67.yaml new file mode 100644 index 0000000000..4334e3a8fd --- /dev/null +++ b/releasenotes/notes/configdrive-vendordata-122049bd7c6e1b67.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Adds support for specifying vendor_data when building config drives. + Starting with API version 1.59, a JSON based ``configdrive`` parameter to + ``/v1/nodes//states/provision`` can include the key vendor_data. + This data will be built into the configdrive contents as + vendor_data2.json. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index d57e6ea7c4..76ba55b92c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -47,4 +47,4 @@ jsonschema>=2.6.0 # MIT psutil>=3.2.2 # BSD futurist>=1.2.0 # Apache-2.0 tooz>=1.58.0 # Apache-2.0 -openstacksdk>=0.31.2 # Apache-2.0 +openstacksdk>=0.37.0 # Apache-2.0