Allow vendor_data to be included in a configdrive dict

configdrive can contain a vendor_data2.json file containing key/value
pairs injected by nova's vendordata mechanism[1].

This change lets Ironic accept a vendor_data key when configdrive is
provided as json, allowing parity with nova.

This change requires an openstacksdk release 0.37.0

[1] https://www.madebymikal.com/nova-vendordata-deployment-an-excessively-detailed-guide/

Change-Id: Id990b970619a113c5d5ead47fb550870d91b5e04
Task: 36756
Story: 2006597
Blueprint: nova-less-deploy
This commit is contained in:
Steve Baker 2019-09-23 05:16:05 +00:00
parent b462ca420b
commit 7ebad2e344
12 changed files with 97 additions and 27 deletions

View File

@ -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".

View File

@ -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)
--------------------

View File

@ -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.

View File

@ -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)

View File

@ -197,7 +197,7 @@ RELEASE_MAPPING = {
}
},
'master': {
'api': '1.58',
'api': '1.59',
'rpc': '1.48',
'objects': {
'Allocation': ['1.0'],

View File

@ -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):

View File

@ -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,

View File

@ -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'})

View File

@ -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')

View File

@ -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

View File

@ -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/<node>/states/provision`` can include the key vendor_data.
This data will be built into the configdrive contents as
vendor_data2.json.

View File

@ -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