Delay rendering configdrive
When the configdrive input is JSON (meta_data, etc), delay the rendering until the ISO image is actually used. It has two benefits: 1) Avoid storing a large ISO image in instance_info, 2) Allow deploy steps to access the original user's input. Fix configdrive masking to correctly mask dicts. Story: #2008875 Task: #42419 Change-Id: I86d30bbb505b8c794bfa6412606f4516f8885aa9
This commit is contained in:
parent
a362bbc9e4
commit
172d1b22df
@ -1423,6 +1423,9 @@ def node_sanitize(node, fields):
|
|||||||
if not show_instance_secrets and node.get('instance_info'):
|
if not show_instance_secrets and node.get('instance_info'):
|
||||||
node['instance_info'] = strutils.mask_dict_password(
|
node['instance_info'] = strutils.mask_dict_password(
|
||||||
node['instance_info'], "******")
|
node['instance_info'], "******")
|
||||||
|
# NOTE(dtantsur): configdrive may be a dict
|
||||||
|
if node['instance_info'].get('configdrive'):
|
||||||
|
node['instance_info']['configdrive'] = "******"
|
||||||
# NOTE(tenbrae): agent driver may store a swift temp_url on the
|
# NOTE(tenbrae): agent driver may store a swift temp_url on the
|
||||||
# instance_info, which shouldn't be exposed to non-admin users.
|
# instance_info, which shouldn't be exposed to non-admin users.
|
||||||
# Now that ironic supports additional policies, we need to hide
|
# Now that ironic supports additional policies, we need to hide
|
||||||
|
@ -130,8 +130,6 @@ def do_node_deploy(task, conductor_id=None, configdrive=None,
|
|||||||
utils.wipe_deploy_internal_info(task)
|
utils.wipe_deploy_internal_info(task)
|
||||||
try:
|
try:
|
||||||
if configdrive:
|
if configdrive:
|
||||||
if isinstance(configdrive, dict):
|
|
||||||
configdrive = utils.build_configdrive(node, configdrive)
|
|
||||||
_store_configdrive(node, configdrive)
|
_store_configdrive(node, configdrive)
|
||||||
except (exception.SwiftOperationError, exception.ConfigInvalid) as e:
|
except (exception.SwiftOperationError, exception.ConfigInvalid) as e:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
@ -417,6 +415,10 @@ def _store_configdrive(node, configdrive):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if CONF.deploy.configdrive_use_object_store:
|
if CONF.deploy.configdrive_use_object_store:
|
||||||
|
# Don't store the JSON source in swift.
|
||||||
|
if isinstance(configdrive, dict):
|
||||||
|
configdrive = utils.build_configdrive(node, configdrive)
|
||||||
|
|
||||||
# NOTE(lucasagomes): No reason to use a different timeout than
|
# NOTE(lucasagomes): No reason to use a different timeout than
|
||||||
# the one used for deploying the node
|
# the one used for deploying the node
|
||||||
timeout = (CONF.conductor.configdrive_swift_temp_url_duration
|
timeout = (CONF.conductor.configdrive_swift_temp_url_duration
|
||||||
|
@ -1005,6 +1005,21 @@ def build_configdrive(node, configdrive):
|
|||||||
vendor_data=configdrive.get('vendor_data'))
|
vendor_data=configdrive.get('vendor_data'))
|
||||||
|
|
||||||
|
|
||||||
|
def get_configdrive_image(node):
|
||||||
|
"""Get configdrive as an ISO image or a URL.
|
||||||
|
|
||||||
|
Converts the JSON representation into an image. URLs and raw contents
|
||||||
|
are returned unchanged.
|
||||||
|
|
||||||
|
:param node: an Ironic node object.
|
||||||
|
:returns: A gzipped and base64 encoded configdrive as a string.
|
||||||
|
"""
|
||||||
|
configdrive = node.instance_info.get('configdrive')
|
||||||
|
if isinstance(configdrive, dict):
|
||||||
|
configdrive = build_configdrive(node, configdrive)
|
||||||
|
return configdrive
|
||||||
|
|
||||||
|
|
||||||
def fast_track_able(task):
|
def fast_track_able(task):
|
||||||
"""Checks if the operation can be a streamlined deployment sequence.
|
"""Checks if the operation can be a streamlined deployment sequence.
|
||||||
|
|
||||||
|
@ -561,7 +561,11 @@ class AgentDeploy(CustomAgentDeploy):
|
|||||||
if disk_label is not None:
|
if disk_label is not None:
|
||||||
image_info['disk_label'] = disk_label
|
image_info['disk_label'] = disk_label
|
||||||
|
|
||||||
configdrive = node.instance_info.get('configdrive')
|
configdrive = manager_utils.get_configdrive_image(node)
|
||||||
|
if configdrive:
|
||||||
|
# FIXME(dtantsur): remove this duplication once IPA is ready:
|
||||||
|
# https://review.opendev.org/c/openstack/ironic-python-agent/+/790471
|
||||||
|
image_info['configdrive'] = configdrive
|
||||||
# Now switch into the corresponding in-band deploy step and let the
|
# Now switch into the corresponding in-band deploy step and let the
|
||||||
# result be polled normally.
|
# result be polled normally.
|
||||||
new_step = {'interface': 'deploy',
|
new_step = {'interface': 'deploy',
|
||||||
|
@ -284,7 +284,7 @@ def _prepare_variables(task):
|
|||||||
image['checksum'] = 'md5:%s' % checksum
|
image['checksum'] = 'md5:%s' % checksum
|
||||||
_add_ssl_image_options(image)
|
_add_ssl_image_options(image)
|
||||||
variables = {'image': image}
|
variables = {'image': image}
|
||||||
configdrive = i_info.get('configdrive')
|
configdrive = manager_utils.get_configdrive_image(task.node)
|
||||||
if configdrive:
|
if configdrive:
|
||||||
if urlparse.urlparse(configdrive).scheme in ('http', 'https'):
|
if urlparse.urlparse(configdrive).scheme in ('http', 'https'):
|
||||||
cfgdrv_type = 'url'
|
cfgdrv_type = 'url'
|
||||||
|
@ -636,21 +636,12 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
|||||||
managers = redfish_utils.get_system(task.node).managers
|
managers = redfish_utils.get_system(task.node).managers
|
||||||
|
|
||||||
deploy_info = _parse_deploy_info(node)
|
deploy_info = _parse_deploy_info(node)
|
||||||
configdrive = node.instance_info.get('configdrive')
|
|
||||||
iso_ref = image_utils.prepare_boot_iso(task, deploy_info, **params)
|
iso_ref = image_utils.prepare_boot_iso(task, deploy_info, **params)
|
||||||
_eject_vmedia(task, managers, sushy.VIRTUAL_MEDIA_CD)
|
_eject_vmedia(task, managers, sushy.VIRTUAL_MEDIA_CD)
|
||||||
_insert_vmedia(task, managers, iso_ref, sushy.VIRTUAL_MEDIA_CD)
|
_insert_vmedia(task, managers, iso_ref, sushy.VIRTUAL_MEDIA_CD)
|
||||||
|
|
||||||
if configdrive and boot_option == 'ramdisk':
|
if boot_option == 'ramdisk':
|
||||||
_eject_vmedia(task, managers, sushy.VIRTUAL_MEDIA_USBSTICK)
|
self._attach_configdrive(task, managers)
|
||||||
cd_ref = image_utils.prepare_configdrive_image(task, configdrive)
|
|
||||||
try:
|
|
||||||
_insert_vmedia(task, managers, cd_ref,
|
|
||||||
sushy.VIRTUAL_MEDIA_USBSTICK)
|
|
||||||
except exception.InvalidParameterValue:
|
|
||||||
raise exception.InstanceDeployFailure(
|
|
||||||
_('Cannot attach configdrive for node %s: no suitable '
|
|
||||||
'virtual USB slot has been found') % node.uuid)
|
|
||||||
|
|
||||||
del managers
|
del managers
|
||||||
|
|
||||||
@ -660,6 +651,21 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
|||||||
"%(device)s", {'node': task.node.uuid,
|
"%(device)s", {'node': task.node.uuid,
|
||||||
'device': boot_devices.CDROM})
|
'device': boot_devices.CDROM})
|
||||||
|
|
||||||
|
def _attach_configdrive(self, task, managers):
|
||||||
|
configdrive = manager_utils.get_configdrive_image(task.node)
|
||||||
|
if not configdrive:
|
||||||
|
return
|
||||||
|
|
||||||
|
_eject_vmedia(task, managers, sushy.VIRTUAL_MEDIA_USBSTICK)
|
||||||
|
cd_ref = image_utils.prepare_configdrive_image(task, configdrive)
|
||||||
|
try:
|
||||||
|
_insert_vmedia(task, managers, cd_ref,
|
||||||
|
sushy.VIRTUAL_MEDIA_USBSTICK)
|
||||||
|
except exception.InvalidParameterValue:
|
||||||
|
raise exception.InstanceDeployFailure(
|
||||||
|
_('Cannot attach configdrive for node %s: no suitable '
|
||||||
|
'virtual USB slot has been found') % task.node.uuid)
|
||||||
|
|
||||||
def _eject_all(self, task):
|
def _eject_all(self, task):
|
||||||
managers = redfish_utils.get_system(task.node).managers
|
managers = redfish_utils.get_system(task.node).managers
|
||||||
|
|
||||||
@ -676,7 +682,7 @@ class RedfishVirtualMediaBoot(base.BootInterface):
|
|||||||
|
|
||||||
boot_option = deploy_utils.get_boot_option(task.node)
|
boot_option = deploy_utils.get_boot_option(task.node)
|
||||||
if (boot_option == 'ramdisk'
|
if (boot_option == 'ramdisk'
|
||||||
and task.node.instance_info.get('configdrive')):
|
and task.node.instance_info.get('configdrive') is not None):
|
||||||
_eject_vmedia(task, managers, sushy.VIRTUAL_MEDIA_USBSTICK)
|
_eject_vmedia(task, managers, sushy.VIRTUAL_MEDIA_USBSTICK)
|
||||||
image_utils.cleanup_disk_image(task, prefix='configdrive')
|
image_utils.cleanup_disk_image(task, prefix='configdrive')
|
||||||
|
|
||||||
|
@ -174,11 +174,12 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
|||||||
d['driver_info'] = strutils.mask_dict_password(
|
d['driver_info'] = strutils.mask_dict_password(
|
||||||
d.get('driver_info', {}), "******")
|
d.get('driver_info', {}), "******")
|
||||||
iinfo = d.pop('instance_info', {})
|
iinfo = d.pop('instance_info', {})
|
||||||
if not mask_configdrive:
|
configdrive = iinfo.pop('configdrive', None)
|
||||||
configdrive = iinfo.pop('configdrive', None)
|
|
||||||
d['instance_info'] = strutils.mask_dict_password(iinfo, "******")
|
d['instance_info'] = strutils.mask_dict_password(iinfo, "******")
|
||||||
if not mask_configdrive and configdrive:
|
if configdrive is not None:
|
||||||
d['instance_info']['configdrive'] = configdrive
|
d['instance_info']['configdrive'] = (
|
||||||
|
"******" if mask_configdrive else configdrive
|
||||||
|
)
|
||||||
d['driver_internal_info'] = strutils.mask_dict_password(
|
d['driver_internal_info'] = strutils.mask_dict_password(
|
||||||
d.get('driver_internal_info', {}), "******")
|
d.get('driver_internal_info', {}), "******")
|
||||||
return d
|
return d
|
||||||
|
@ -186,6 +186,25 @@ class TestListNodes(test_api_base.BaseApiTest):
|
|||||||
self.assertNotIn('allocation_id', data)
|
self.assertNotIn('allocation_id', data)
|
||||||
self.assertIn('allocation_uuid', data)
|
self.assertIn('allocation_uuid', data)
|
||||||
|
|
||||||
|
def test_get_one_configdrive_dict(self):
|
||||||
|
fake_instance_info = {
|
||||||
|
"configdrive": {'user_data': 'data'},
|
||||||
|
"image_url": "http://example.com/test_image_url",
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
node = obj_utils.create_test_node(self.context,
|
||||||
|
chassis_id=self.chassis.id,
|
||||||
|
instance_info=fake_instance_info)
|
||||||
|
data = self.get_json(
|
||||||
|
'/nodes/%s' % node.uuid,
|
||||||
|
headers={api_base.Version.string: str(api_v1.max_version())})
|
||||||
|
self.assertEqual(node.uuid, data['uuid'])
|
||||||
|
self.assertEqual('******', data['driver_info']['fake_password'])
|
||||||
|
self.assertEqual('bar', data['driver_info']['foo'])
|
||||||
|
self.assertEqual('******', data['instance_info']['configdrive'])
|
||||||
|
self.assertEqual('******', data['instance_info']['image_url'])
|
||||||
|
self.assertEqual('bar', data['instance_info']['foo'])
|
||||||
|
|
||||||
def test_get_one_with_json(self):
|
def test_get_one_with_json(self):
|
||||||
# Test backward compatibility with guess_content_type_from_ext
|
# Test backward compatibility with guess_content_type_from_ext
|
||||||
node = obj_utils.create_test_node(self.context,
|
node = obj_utils.create_test_node(self.context,
|
||||||
|
@ -174,63 +174,6 @@ class DoNodeDeployTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
|
|||||||
def test__do_node_deploy_fast_track(self):
|
def test__do_node_deploy_fast_track(self):
|
||||||
self._test__do_node_deploy_ok(fast_track=True)
|
self._test__do_node_deploy_ok(fast_track=True)
|
||||||
|
|
||||||
@mock.patch('openstack.baremetal.configdrive.build', autospec=True)
|
|
||||||
def test__do_node_deploy_configdrive_as_dict(self, mock_cd):
|
|
||||||
mock_cd.return_value = 'foo'
|
|
||||||
configdrive = {'user_data': 'abcd'}
|
|
||||||
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=b'abcd',
|
|
||||||
vendor_data=None)
|
|
||||||
|
|
||||||
@mock.patch('openstack.baremetal.configdrive.build', autospec=True)
|
|
||||||
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(),
|
|
||||||
'name': 'new-name',
|
|
||||||
'hostname': 'example.com'}}
|
|
||||||
self._test__do_node_deploy_ok(configdrive=configdrive,
|
|
||||||
expected_configdrive='foo')
|
|
||||||
mock_cd.assert_called_once_with(configdrive['meta_data'],
|
|
||||||
network_data=None,
|
|
||||||
user_data=None,
|
|
||||||
vendor_data=None)
|
|
||||||
|
|
||||||
@mock.patch('openstack.baremetal.configdrive.build', autospec=True)
|
|
||||||
def test__do_node_deploy_configdrive_with_network_data(self, mock_cd):
|
|
||||||
mock_cd.return_value = 'foo'
|
|
||||||
configdrive = {'network_data': {'links': []}}
|
|
||||||
self._test__do_node_deploy_ok(configdrive=configdrive,
|
|
||||||
expected_configdrive='foo')
|
|
||||||
mock_cd.assert_called_once_with({'uuid': self.node.uuid},
|
|
||||||
network_data={'links': []},
|
|
||||||
user_data=None,
|
|
||||||
vendor_data=None)
|
|
||||||
|
|
||||||
@mock.patch('openstack.baremetal.configdrive.build', autospec=True)
|
|
||||||
def test__do_node_deploy_configdrive_and_user_data_as_dict(self, mock_cd):
|
|
||||||
mock_cd.return_value = 'foo'
|
|
||||||
configdrive = {'user_data': {'user': 'data'}}
|
|
||||||
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=b'{"user": "data"}',
|
|
||||||
vendor_data=None)
|
|
||||||
|
|
||||||
@mock.patch('openstack.baremetal.configdrive.build', autospec=True)
|
|
||||||
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', autospec=True)
|
@mock.patch.object(swift, 'SwiftAPI', autospec=True)
|
||||||
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.prepare',
|
@mock.patch('ironic.drivers.modules.fake.FakeDeploy.prepare',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@ -1014,6 +957,37 @@ class StoreConfigDriveTestCase(db_base.DbTestCase):
|
|||||||
self.node.refresh()
|
self.node.refresh()
|
||||||
self.assertEqual(expected_instance_info, self.node.instance_info)
|
self.assertEqual(expected_instance_info, self.node.instance_info)
|
||||||
|
|
||||||
|
@mock.patch.object(conductor_utils, 'build_configdrive', autospec=True)
|
||||||
|
def test_store_configdrive_swift_build(self, mock_cd, mock_swift):
|
||||||
|
container_name = 'foo_container'
|
||||||
|
timeout = 123
|
||||||
|
expected_obj_name = 'configdrive-%s' % self.node.uuid
|
||||||
|
expected_obj_header = {'X-Delete-After': str(timeout)}
|
||||||
|
expected_instance_info = {'configdrive': 'http://1.2.3.4'}
|
||||||
|
|
||||||
|
mock_cd.return_value = 'fake'
|
||||||
|
|
||||||
|
# set configs and mocks
|
||||||
|
CONF.set_override('configdrive_use_object_store', True,
|
||||||
|
group='deploy')
|
||||||
|
CONF.set_override('configdrive_swift_container', container_name,
|
||||||
|
group='conductor')
|
||||||
|
CONF.set_override('deploy_callback_timeout', timeout,
|
||||||
|
group='conductor')
|
||||||
|
mock_swift.return_value.get_temp_url.return_value = 'http://1.2.3.4'
|
||||||
|
|
||||||
|
deployments._store_configdrive(self.node, {'meta_data': {}})
|
||||||
|
|
||||||
|
mock_swift.assert_called_once_with()
|
||||||
|
mock_swift.return_value.create_object.assert_called_once_with(
|
||||||
|
container_name, expected_obj_name, mock.ANY,
|
||||||
|
object_headers=expected_obj_header)
|
||||||
|
mock_swift.return_value.get_temp_url.assert_called_once_with(
|
||||||
|
container_name, expected_obj_name, timeout)
|
||||||
|
self.node.refresh()
|
||||||
|
self.assertEqual(expected_instance_info, self.node.instance_info)
|
||||||
|
mock_cd.assert_called_once_with(self.node, {'meta_data': {}})
|
||||||
|
|
||||||
def test_store_configdrive_swift_no_deploy_timeout(self, mock_swift):
|
def test_store_configdrive_swift_no_deploy_timeout(self, mock_swift):
|
||||||
container_name = 'foo_container'
|
container_name = 'foo_container'
|
||||||
expected_obj_name = 'configdrive-%s' % self.node.uuid
|
expected_obj_name = 'configdrive-%s' % self.node.uuid
|
||||||
|
@ -2308,3 +2308,71 @@ class CacheVendorTestCase(db_base.DbTestCase):
|
|||||||
self.node.refresh()
|
self.node.refresh()
|
||||||
self.assertNotIn('vendor', self.node.properties)
|
self.assertNotIn('vendor', self.node.properties)
|
||||||
self.assertTrue(mock_log.called)
|
self.assertTrue(mock_log.called)
|
||||||
|
|
||||||
|
|
||||||
|
class GetConfigDriveImageTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(GetConfigDriveImageTestCase, self).setUp()
|
||||||
|
self.node = obj_utils.create_test_node(
|
||||||
|
self.context,
|
||||||
|
uuid=uuidutils.generate_uuid(),
|
||||||
|
instance_info={})
|
||||||
|
|
||||||
|
def test_no_configdrive(self):
|
||||||
|
self.assertIsNone(conductor_utils.get_configdrive_image(self.node))
|
||||||
|
|
||||||
|
def test_string(self):
|
||||||
|
self.node.instance_info['configdrive'] = 'data'
|
||||||
|
self.assertEqual('data',
|
||||||
|
conductor_utils.get_configdrive_image(self.node))
|
||||||
|
|
||||||
|
@mock.patch('openstack.baremetal.configdrive.build', autospec=True)
|
||||||
|
def test_build_empty(self, mock_cd):
|
||||||
|
self.node.instance_info['configdrive'] = {}
|
||||||
|
self.assertEqual(mock_cd.return_value,
|
||||||
|
conductor_utils.get_configdrive_image(self.node))
|
||||||
|
mock_cd.assert_called_once_with({'uuid': self.node.uuid},
|
||||||
|
network_data=None,
|
||||||
|
user_data=None,
|
||||||
|
vendor_data=None)
|
||||||
|
|
||||||
|
@mock.patch('openstack.baremetal.configdrive.build', autospec=True)
|
||||||
|
def test_build_populated(self, mock_cd):
|
||||||
|
configdrive = {
|
||||||
|
'meta_data': {'uuid': uuidutils.generate_uuid(),
|
||||||
|
'name': 'new-name',
|
||||||
|
'hostname': 'example.com'},
|
||||||
|
'network_data': {'links': []},
|
||||||
|
'vendor_data': {'foo': 'bar'},
|
||||||
|
}
|
||||||
|
self.node.instance_info['configdrive'] = configdrive
|
||||||
|
self.assertEqual(mock_cd.return_value,
|
||||||
|
conductor_utils.get_configdrive_image(self.node))
|
||||||
|
mock_cd.assert_called_once_with(
|
||||||
|
configdrive['meta_data'],
|
||||||
|
network_data=configdrive['network_data'],
|
||||||
|
user_data=None,
|
||||||
|
vendor_data=configdrive['vendor_data'])
|
||||||
|
|
||||||
|
@mock.patch('openstack.baremetal.configdrive.build', autospec=True)
|
||||||
|
def test_build_user_data_as_string(self, mock_cd):
|
||||||
|
self.node.instance_info['configdrive'] = {'user_data': 'abcd'}
|
||||||
|
self.assertEqual(mock_cd.return_value,
|
||||||
|
conductor_utils.get_configdrive_image(self.node))
|
||||||
|
mock_cd.assert_called_once_with({'uuid': self.node.uuid},
|
||||||
|
network_data=None,
|
||||||
|
user_data=b'abcd',
|
||||||
|
vendor_data=None)
|
||||||
|
|
||||||
|
@mock.patch('openstack.baremetal.configdrive.build', autospec=True)
|
||||||
|
def test_build_user_data_as_dict(self, mock_cd):
|
||||||
|
self.node.instance_info['configdrive'] = {
|
||||||
|
'user_data': {'user': 'data'}
|
||||||
|
}
|
||||||
|
self.assertEqual(mock_cd.return_value,
|
||||||
|
conductor_utils.get_configdrive_image(self.node))
|
||||||
|
mock_cd.assert_called_once_with({'uuid': self.node.uuid},
|
||||||
|
network_data=None,
|
||||||
|
user_data=b'{"user": "data"}',
|
||||||
|
vendor_data=None)
|
||||||
|
@ -495,6 +495,36 @@ class TestAnsibleMethods(AnsibleDeployTestCaseBase):
|
|||||||
mock.call().write('fake-content'),
|
mock.call().write('fake-content'),
|
||||||
mock.call().__exit__(None, None, None)))
|
mock.call().__exit__(None, None, None)))
|
||||||
|
|
||||||
|
@mock.patch.object(utils, 'build_configdrive', autospec=True)
|
||||||
|
def test__prepare_variables_configdrive_json(self, mock_build_configdrive):
|
||||||
|
i_info = self.node.instance_info
|
||||||
|
i_info['configdrive'] = {'meta_data': {}}
|
||||||
|
self.node.instance_info = i_info
|
||||||
|
self.node.save()
|
||||||
|
mock_build_configdrive.return_value = 'fake-content'
|
||||||
|
configdrive_path = ('%(tempdir)s/%(node)s.cndrive' %
|
||||||
|
{'tempdir': ansible_deploy.CONF.tempdir,
|
||||||
|
'node': self.node.uuid})
|
||||||
|
expected = {"image": {"url": "http://image",
|
||||||
|
"validate_certs": "yes",
|
||||||
|
"source": "fake-image",
|
||||||
|
"disk_format": "qcow2",
|
||||||
|
"checksum": "md5:checksum"},
|
||||||
|
'configdrive': {'type': 'file',
|
||||||
|
'location': configdrive_path}}
|
||||||
|
with mock.patch.object(ansible_deploy, 'open', mock.mock_open(),
|
||||||
|
create=True) as open_mock:
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
self.assertEqual(expected,
|
||||||
|
ansible_deploy._prepare_variables(task))
|
||||||
|
mock_build_configdrive.assert_called_once_with(
|
||||||
|
task.node, {'meta_data': {}})
|
||||||
|
open_mock.assert_has_calls((
|
||||||
|
mock.call(configdrive_path, 'w'),
|
||||||
|
mock.call().__enter__(),
|
||||||
|
mock.call().write('fake-content'),
|
||||||
|
mock.call().__exit__(None, None, None)))
|
||||||
|
|
||||||
def test__validate_clean_steps(self):
|
def test__validate_clean_steps(self):
|
||||||
steps = [{"interface": "deploy",
|
steps = [{"interface": "deploy",
|
||||||
"name": "foo",
|
"name": "foo",
|
||||||
|
@ -849,15 +849,16 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
|
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True)
|
@mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, '_parse_deploy_info', autospec=True)
|
@mock.patch.object(redfish_boot, '_parse_deploy_info', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'manager_utils', autospec=True)
|
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
|
||||||
|
autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
def test_prepare_instance_ramdisk_boot(
|
def test_prepare_instance_ramdisk_boot(
|
||||||
self, mock_system, mock_boot_mode_utils, mock_deploy_utils,
|
self, mock_system, mock_boot_mode_utils, mock_deploy_utils,
|
||||||
mock_manager_utils, mock__parse_deploy_info, mock__insert_vmedia,
|
mock_node_set_boot_device, mock__parse_deploy_info,
|
||||||
mock__eject_vmedia, mock_prepare_boot_iso, mock_prepare_disk,
|
mock__insert_vmedia, mock__eject_vmedia, mock_prepare_boot_iso,
|
||||||
mock_clean_up_instance):
|
mock_prepare_disk, mock_clean_up_instance):
|
||||||
|
|
||||||
configdrive = 'Y29udGVudA=='
|
configdrive = 'Y29udGVudA=='
|
||||||
managers = mock_system.return_value.managers
|
managers = mock_system.return_value.managers
|
||||||
@ -899,7 +900,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
'cd-url', sushy.VIRTUAL_MEDIA_USBSTICK),
|
'cd-url', sushy.VIRTUAL_MEDIA_USBSTICK),
|
||||||
])
|
])
|
||||||
|
|
||||||
mock_manager_utils.node_set_boot_device.assert_called_once_with(
|
mock_node_set_boot_device.assert_called_once_with(
|
||||||
task, boot_devices.CDROM, persistent=True)
|
task, boot_devices.CDROM, persistent=True)
|
||||||
|
|
||||||
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
||||||
@ -910,14 +911,16 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
|
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True)
|
@mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, '_parse_deploy_info', autospec=True)
|
@mock.patch.object(redfish_boot, '_parse_deploy_info', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'manager_utils', autospec=True)
|
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
|
||||||
|
autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
def test_prepare_instance_ramdisk_boot_iso(
|
def test_prepare_instance_ramdisk_boot_iso(
|
||||||
self, mock_system, mock_boot_mode_utils, mock_deploy_utils,
|
self, mock_system, mock_boot_mode_utils, mock_deploy_utils,
|
||||||
mock_manager_utils, mock__parse_deploy_info, mock__insert_vmedia,
|
mock_node_set_boot_device, mock__parse_deploy_info,
|
||||||
mock__eject_vmedia, mock_prepare_boot_iso, mock_clean_up_instance):
|
mock__insert_vmedia, mock__eject_vmedia, mock_prepare_boot_iso,
|
||||||
|
mock_clean_up_instance):
|
||||||
|
|
||||||
managers = mock_system.return_value.managers
|
managers = mock_system.return_value.managers
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
@ -948,7 +951,7 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
mock__insert_vmedia.assert_called_once_with(
|
mock__insert_vmedia.assert_called_once_with(
|
||||||
task, managers, 'image-url', sushy.VIRTUAL_MEDIA_CD)
|
task, managers, 'image-url', sushy.VIRTUAL_MEDIA_CD)
|
||||||
|
|
||||||
mock_manager_utils.node_set_boot_device.assert_called_once_with(
|
mock_node_set_boot_device.assert_called_once_with(
|
||||||
task, boot_devices.CDROM, persistent=True)
|
task, boot_devices.CDROM, persistent=True)
|
||||||
|
|
||||||
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
||||||
@ -959,14 +962,16 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
|
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True)
|
@mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, '_parse_deploy_info', autospec=True)
|
@mock.patch.object(redfish_boot, '_parse_deploy_info', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'manager_utils', autospec=True)
|
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
|
||||||
|
autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
||||||
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||||
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
def test_prepare_instance_ramdisk_boot_iso_boot(
|
def test_prepare_instance_ramdisk_boot_iso_boot(
|
||||||
self, mock_system, mock_boot_mode_utils, mock_deploy_utils,
|
self, mock_system, mock_boot_mode_utils, mock_deploy_utils,
|
||||||
mock_manager_utils, mock__parse_deploy_info, mock__insert_vmedia,
|
mock_node_set_boot_device, mock__parse_deploy_info,
|
||||||
mock__eject_vmedia, mock_prepare_boot_iso, mock_clean_up_instance):
|
mock__insert_vmedia, mock__eject_vmedia, mock_prepare_boot_iso,
|
||||||
|
mock_clean_up_instance):
|
||||||
|
|
||||||
managers = mock_system.return_value.managers
|
managers = mock_system.return_value.managers
|
||||||
with task_manager.acquire(self.context, self.node.uuid,
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
@ -991,7 +996,76 @@ class RedfishVirtualMediaBootTestCase(db_base.DbTestCase):
|
|||||||
mock__insert_vmedia.assert_called_once_with(
|
mock__insert_vmedia.assert_called_once_with(
|
||||||
task, managers, 'image-url', sushy.VIRTUAL_MEDIA_CD)
|
task, managers, 'image-url', sushy.VIRTUAL_MEDIA_CD)
|
||||||
|
|
||||||
mock_manager_utils.node_set_boot_device.assert_called_once_with(
|
mock_node_set_boot_device.assert_called_once_with(
|
||||||
|
task, boot_devices.CDROM, persistent=True)
|
||||||
|
|
||||||
|
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
||||||
|
|
||||||
|
@mock.patch.object(redfish_boot.manager_utils, 'build_configdrive',
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch.object(redfish_boot.RedfishVirtualMediaBoot,
|
||||||
|
'_eject_all', autospec=True)
|
||||||
|
@mock.patch.object(image_utils, 'prepare_configdrive_image', autospec=True)
|
||||||
|
@mock.patch.object(image_utils, 'prepare_boot_iso', autospec=True)
|
||||||
|
@mock.patch.object(redfish_boot, '_eject_vmedia', autospec=True)
|
||||||
|
@mock.patch.object(redfish_boot, '_insert_vmedia', autospec=True)
|
||||||
|
@mock.patch.object(redfish_boot, '_parse_deploy_info', autospec=True)
|
||||||
|
@mock.patch.object(redfish_boot.manager_utils, 'node_set_boot_device',
|
||||||
|
autospec=True)
|
||||||
|
@mock.patch.object(redfish_boot, 'deploy_utils', autospec=True)
|
||||||
|
@mock.patch.object(redfish_boot, 'boot_mode_utils', autospec=True)
|
||||||
|
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
|
||||||
|
def test_prepare_instance_ramdisk_boot_render_configdrive(
|
||||||
|
self, mock_system, mock_boot_mode_utils, mock_deploy_utils,
|
||||||
|
mock_node_set_boot_device, mock__parse_deploy_info,
|
||||||
|
mock__insert_vmedia, mock__eject_vmedia, mock_prepare_boot_iso,
|
||||||
|
mock_prepare_disk, mock_clean_up_instance, mock_build_configdrive):
|
||||||
|
|
||||||
|
configdrive = 'Y29udGVudA=='
|
||||||
|
managers = mock_system.return_value.managers
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=True) as task:
|
||||||
|
task.node.provision_state = states.DEPLOYING
|
||||||
|
task.node.driver_internal_info[
|
||||||
|
'root_uuid_or_disk_id'] = self.node.uuid
|
||||||
|
task.node.instance_info['configdrive'] = {'meta_data': {}}
|
||||||
|
|
||||||
|
mock_build_configdrive.return_value = configdrive
|
||||||
|
|
||||||
|
mock_deploy_utils.get_boot_option.return_value = 'ramdisk'
|
||||||
|
|
||||||
|
d_info = {
|
||||||
|
'deploy_kernel': 'kernel',
|
||||||
|
'deploy_ramdisk': 'ramdisk',
|
||||||
|
'bootloader': 'bootloader'
|
||||||
|
}
|
||||||
|
mock__parse_deploy_info.return_value = d_info
|
||||||
|
|
||||||
|
mock_prepare_boot_iso.return_value = 'image-url'
|
||||||
|
mock_prepare_disk.return_value = 'cd-url'
|
||||||
|
|
||||||
|
task.driver.boot.prepare_instance(task)
|
||||||
|
|
||||||
|
mock_clean_up_instance.assert_called_once_with(mock.ANY, task)
|
||||||
|
|
||||||
|
mock_build_configdrive.assert_called_once_with(
|
||||||
|
task.node, {'meta_data': {}})
|
||||||
|
mock_prepare_boot_iso.assert_called_once_with(task, d_info)
|
||||||
|
mock_prepare_disk.assert_called_once_with(task, configdrive)
|
||||||
|
|
||||||
|
mock__eject_vmedia.assert_has_calls([
|
||||||
|
mock.call(task, managers, sushy.VIRTUAL_MEDIA_CD),
|
||||||
|
mock.call(task, managers, sushy.VIRTUAL_MEDIA_USBSTICK),
|
||||||
|
])
|
||||||
|
|
||||||
|
mock__insert_vmedia.assert_has_calls([
|
||||||
|
mock.call(task, managers,
|
||||||
|
'image-url', sushy.VIRTUAL_MEDIA_CD),
|
||||||
|
mock.call(task, managers,
|
||||||
|
'cd-url', sushy.VIRTUAL_MEDIA_USBSTICK),
|
||||||
|
])
|
||||||
|
|
||||||
|
mock_node_set_boot_device.assert_called_once_with(
|
||||||
task, boot_devices.CDROM, persistent=True)
|
task, boot_devices.CDROM, persistent=True)
|
||||||
|
|
||||||
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
mock_boot_mode_utils.sync_boot_mode.assert_called_once_with(task)
|
||||||
|
@ -1482,6 +1482,73 @@ class TestAgentDeploy(CommonTestsMixin, db_base.DbTestCase):
|
|||||||
self.assertEqual(states.ACTIVE,
|
self.assertEqual(states.ACTIVE,
|
||||||
task.node.target_provision_state)
|
task.node.target_provision_state)
|
||||||
|
|
||||||
|
@mock.patch.object(manager_utils, 'build_configdrive', autospec=True)
|
||||||
|
def test_write_image_render_configdrive(self, mock_build_configdrive):
|
||||||
|
self.node.provision_state = states.DEPLOYWAIT
|
||||||
|
self.node.target_provision_state = states.ACTIVE
|
||||||
|
i_info = self.node.instance_info
|
||||||
|
i_info['kernel'] = 'kernel'
|
||||||
|
i_info['ramdisk'] = 'ramdisk'
|
||||||
|
i_info['root_gb'] = 10
|
||||||
|
i_info['swap_mb'] = 10
|
||||||
|
i_info['ephemeral_mb'] = 0
|
||||||
|
i_info['ephemeral_format'] = 'abc'
|
||||||
|
i_info['configdrive'] = {'meta_data': {}}
|
||||||
|
i_info['preserve_ephemeral'] = False
|
||||||
|
i_info['image_type'] = 'partition'
|
||||||
|
i_info['root_mb'] = 10240
|
||||||
|
i_info['deploy_boot_mode'] = 'bios'
|
||||||
|
i_info['capabilities'] = {"boot_option": "local",
|
||||||
|
"disk_label": "msdos"}
|
||||||
|
self.node.instance_info = i_info
|
||||||
|
driver_internal_info = self.node.driver_internal_info
|
||||||
|
driver_internal_info['is_whole_disk_image'] = False
|
||||||
|
self.node.driver_internal_info = driver_internal_info
|
||||||
|
self.node.save()
|
||||||
|
test_temp_url = 'http://image'
|
||||||
|
expected_image_info = {
|
||||||
|
'urls': [test_temp_url],
|
||||||
|
'id': 'fake-image',
|
||||||
|
'node_uuid': self.node.uuid,
|
||||||
|
'checksum': 'checksum',
|
||||||
|
'disk_format': 'qcow2',
|
||||||
|
'container_format': 'bare',
|
||||||
|
'stream_raw_images': True,
|
||||||
|
'kernel': 'kernel',
|
||||||
|
'ramdisk': 'ramdisk',
|
||||||
|
'root_gb': 10,
|
||||||
|
'swap_mb': 10,
|
||||||
|
'ephemeral_mb': 0,
|
||||||
|
'ephemeral_format': 'abc',
|
||||||
|
'configdrive': 'configdrive',
|
||||||
|
'preserve_ephemeral': False,
|
||||||
|
'image_type': 'partition',
|
||||||
|
'root_mb': 10240,
|
||||||
|
'boot_option': 'local',
|
||||||
|
'deploy_boot_mode': 'bios',
|
||||||
|
'disk_label': 'msdos'
|
||||||
|
}
|
||||||
|
|
||||||
|
mock_build_configdrive.return_value = 'configdrive'
|
||||||
|
|
||||||
|
client_mock = mock.MagicMock(spec_set=['execute_deploy_step'])
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid,
|
||||||
|
shared=False) as task:
|
||||||
|
task.driver.deploy._client = client_mock
|
||||||
|
task.driver.deploy.write_image(task)
|
||||||
|
|
||||||
|
step = {'step': 'write_image', 'interface': 'deploy',
|
||||||
|
'args': {'image_info': expected_image_info,
|
||||||
|
'configdrive': 'configdrive'}}
|
||||||
|
client_mock.execute_deploy_step.assert_called_once_with(
|
||||||
|
step, task.node, mock.ANY)
|
||||||
|
self.assertEqual(states.DEPLOYWAIT, task.node.provision_state)
|
||||||
|
self.assertEqual(states.ACTIVE,
|
||||||
|
task.node.target_provision_state)
|
||||||
|
mock_build_configdrive.assert_called_once_with(
|
||||||
|
task.node, {'meta_data': {}})
|
||||||
|
|
||||||
@mock.patch.object(deploy_utils, 'remove_http_instance_symlink',
|
@mock.patch.object(deploy_utils, 'remove_http_instance_symlink',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
@mock.patch.object(agent.LOG, 'warning', spec_set=True, autospec=True)
|
@mock.patch.object(agent.LOG, 'warning', spec_set=True, autospec=True)
|
||||||
|
@ -61,6 +61,18 @@ class TestNodeObject(db_base.DbTestCase, obj_utils.SchemasTestMixIn):
|
|||||||
# Ensure the node can be serialised.
|
# Ensure the node can be serialised.
|
||||||
jsonutils.dumps(d)
|
jsonutils.dumps(d)
|
||||||
|
|
||||||
|
def test_as_dict_secure_configdrive_as_dict(self):
|
||||||
|
self.node.driver_info['ipmi_password'] = 'fake'
|
||||||
|
self.node.instance_info['configdrive'] = {'user_data': 'data'}
|
||||||
|
self.node.driver_internal_info['agent_secret_token'] = 'abc'
|
||||||
|
d = self.node.as_dict(secure=True)
|
||||||
|
self.assertEqual('******', d['driver_info']['ipmi_password'])
|
||||||
|
self.assertEqual('******', d['instance_info']['configdrive'])
|
||||||
|
self.assertEqual('******',
|
||||||
|
d['driver_internal_info']['agent_secret_token'])
|
||||||
|
# Ensure the node can be serialised.
|
||||||
|
jsonutils.dumps(d)
|
||||||
|
|
||||||
def test_as_dict_secure_with_configdrive(self):
|
def test_as_dict_secure_with_configdrive(self):
|
||||||
self.node.driver_info['ipmi_password'] = 'fake'
|
self.node.driver_info['ipmi_password'] = 'fake'
|
||||||
self.node.instance_info['configdrive'] = 'data'
|
self.node.instance_info['configdrive'] = 'data'
|
||||||
@ -73,6 +85,19 @@ class TestNodeObject(db_base.DbTestCase, obj_utils.SchemasTestMixIn):
|
|||||||
# Ensure the node can be serialised.
|
# Ensure the node can be serialised.
|
||||||
jsonutils.dumps(d)
|
jsonutils.dumps(d)
|
||||||
|
|
||||||
|
def test_as_dict_secure_with_configdrive_as_dict(self):
|
||||||
|
self.node.driver_info['ipmi_password'] = 'fake'
|
||||||
|
self.node.instance_info['configdrive'] = {'user_data': 'data'}
|
||||||
|
self.node.driver_internal_info['agent_secret_token'] = 'abc'
|
||||||
|
d = self.node.as_dict(secure=True, mask_configdrive=False)
|
||||||
|
self.assertEqual('******', d['driver_info']['ipmi_password'])
|
||||||
|
self.assertEqual({'user_data': 'data'},
|
||||||
|
d['instance_info']['configdrive'])
|
||||||
|
self.assertEqual('******',
|
||||||
|
d['driver_internal_info']['agent_secret_token'])
|
||||||
|
# Ensure the node can be serialised.
|
||||||
|
jsonutils.dumps(d)
|
||||||
|
|
||||||
def test_as_dict_with_traits(self):
|
def test_as_dict_with_traits(self):
|
||||||
self.fake_node['traits'] = ['CUSTOM_1']
|
self.fake_node['traits'] = ['CUSTOM_1']
|
||||||
self.node = obj_utils.get_test_node(self.ctxt, **self.fake_node)
|
self.node = obj_utils.get_test_node(self.ctxt, **self.fake_node)
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
other:
|
||||||
|
- |
|
||||||
|
Configuration drives are now stored in their JSON representation and only
|
||||||
|
rendered when needed. This allows deploy steps to access the original
|
||||||
|
JSON representation rather than only the rendered ISO image.
|
Loading…
Reference in New Issue
Block a user