Regenerate and pass configdrive when rebuild Ironic nodes

Previously, the configdrive could only be set when setting
the node's provisioning state to "active". When rebuilding,
the old configdrive was used and therefore was never updated
with latest content.

Since Ironic API microversion 1.35, it is now allowed to provide
a configdrive when setting the node's provisioning state to "rebuild".

Blueprint: rebuild-ironic-config-drive
Related-bug: #1575935
Depends-On: I9a5529f9fa796c75621e9f4354886bf3032cc248
Change-Id: I1f9056f66519b9ca2f4e23143559735f2bff8943
This commit is contained in:
Mathieu Gagné 2017-09-07 13:10:00 -04:00
parent 8628c75738
commit 878c44f0cf
5 changed files with 101 additions and 13 deletions

View File

@ -81,12 +81,12 @@ class IronicClientWrapperTestCase(test.NoDBTestCase):
# nova.utils.get_ksa_adapter().get_endpoint()
self.get_ksa_adapter.assert_called_once_with(
'baremetal', ksa_auth=self.get_auth_plugin.return_value,
ksa_session='session', min_version=(1, 32),
ksa_session='session', min_version=(1, 35),
max_version=(1, ksa_disc.LATEST))
expected = {'session': 'session',
'max_retries': CONF.ironic.api_max_retries,
'retry_interval': CONF.ironic.api_retry_interval,
'os_ironic_api_version': '1.32',
'os_ironic_api_version': '1.35',
'ironic_url':
self.get_ksa_adapter.return_value.get_endpoint.return_value}
mock_ir_cli.assert_called_once_with(1, **expected)
@ -106,13 +106,13 @@ class IronicClientWrapperTestCase(test.NoDBTestCase):
# nova.utils.get_endpoint_data
self.get_ksa_adapter.assert_called_once_with(
'baremetal', ksa_auth=self.get_auth_plugin.return_value,
ksa_session='session', min_version=(1, 32),
ksa_session='session', min_version=(1, 35),
max_version=(1, ksa_disc.LATEST))
# When get_endpoint_data raises any ServiceNotFound, None is returned.
expected = {'session': 'session',
'max_retries': CONF.ironic.api_max_retries,
'retry_interval': CONF.ironic.api_retry_interval,
'os_ironic_api_version': '1.32',
'os_ironic_api_version': '1.35',
'ironic_url': None}
mock_ir_cli.assert_called_once_with(1, **expected)
@ -130,7 +130,7 @@ class IronicClientWrapperTestCase(test.NoDBTestCase):
expected = {'session': 'session',
'max_retries': CONF.ironic.api_max_retries,
'retry_interval': CONF.ironic.api_retry_interval,
'os_ironic_api_version': '1.32',
'os_ironic_api_version': '1.35',
'ironic_url': endpoint}
mock_ir_cli.assert_called_once_with(1, **expected)

View File

@ -2027,30 +2027,90 @@ class IronicDriverTestCase(test.NoDBTestCase):
test.MatchType(objects.ImageMeta),
flavor, preserve)
mock_set_pstate.assert_called_once_with(node_uuid,
ironic_states.REBUILD)
ironic_states.REBUILD,
configdrive=mock.ANY)
mock_looping.assert_called_once_with(mock_wait_active, instance)
fake_looping_call.start.assert_called_once_with(
interval=CONF.ironic.api_retry_interval)
fake_looping_call.wait.assert_called_once_with()
def test_rebuild_preserve_ephemeral(self):
@mock.patch.object(ironic_driver.IronicDriver, '_generate_configdrive')
@mock.patch.object(configdrive, 'required_by')
def test_rebuild_preserve_ephemeral(self, mock_required_by,
mock_configdrive):
mock_required_by.return_value = False
self._test_rebuild(preserve=True)
# assert configdrive was not generated
mock_configdrive.assert_not_called()
def test_rebuild_no_preserve_ephemeral(self):
@mock.patch.object(ironic_driver.IronicDriver, '_generate_configdrive')
@mock.patch.object(configdrive, 'required_by')
def test_rebuild_no_preserve_ephemeral(self, mock_required_by,
mock_configdrive):
mock_required_by.return_value = False
self._test_rebuild(preserve=False)
@mock.patch.object(ironic_driver.IronicDriver, '_generate_configdrive')
@mock.patch.object(configdrive, 'required_by')
def test_rebuild_with_configdrive(self, mock_required_by,
mock_configdrive):
mock_required_by.return_value = True
self._test_rebuild()
# assert configdrive was generated
mock_configdrive.assert_called_once_with(
self.ctx, mock.ANY, mock.ANY, mock.ANY, extra_md={}, files=None)
@mock.patch.object(ironic_driver.IronicDriver, '_generate_configdrive')
@mock.patch.object(configdrive, 'required_by')
@mock.patch.object(ironic_driver.IronicDriver,
'_add_instance_info_to_node')
@mock.patch.object(FAKE_CLIENT.node, 'get')
@mock.patch.object(objects.Instance, 'save')
def test_rebuild_with_configdrive_failure(self, mock_save, mock_get,
mock_add_instance_info,
mock_required_by,
mock_configdrive):
node_uuid = uuidutils.generate_uuid()
node = ironic_utils.get_test_node(uuid=node_uuid,
instance_uuid=self.instance_uuid,
instance_type_id=5)
mock_get.return_value = node
mock_required_by.return_value = True
mock_configdrive.side_effect = exception.NovaException()
image_meta = ironic_utils.get_test_image_meta()
flavor_id = 5
flavor = objects.Flavor(flavor_id=flavor_id, name='baremetal')
instance = fake_instance.fake_instance_obj(self.ctx,
uuid=self.instance_uuid,
node=node_uuid,
instance_type_id=flavor_id)
instance.flavor = flavor
self.assertRaises(exception.InstanceDeployFailure,
self.driver.rebuild,
context=self.ctx, instance=instance, image_meta=image_meta,
injected_files=None, admin_password=None, allocations={},
bdms=None, detach_block_devices=None,
attach_block_devices=None)
@mock.patch.object(ironic_driver.IronicDriver, '_generate_configdrive')
@mock.patch.object(configdrive, 'required_by')
@mock.patch.object(FAKE_CLIENT.node, 'set_provision_state')
@mock.patch.object(ironic_driver.IronicDriver,
'_add_instance_info_to_node')
@mock.patch.object(FAKE_CLIENT.node, 'get')
@mock.patch.object(objects.Instance, 'save')
def test_rebuild_failures(self, mock_save, mock_get,
mock_add_instance_info, mock_set_pstate):
mock_add_instance_info, mock_set_pstate,
mock_required_by, mock_configdrive):
node_uuid = uuidutils.generate_uuid()
node = ironic_utils.get_test_node(uuid=node_uuid,
instance_uuid=self.instance_uuid,
instance_type_id=5)
mock_get.return_value = node
mock_required_by.return_value = False
image_meta = ironic_utils.get_test_image_meta()
flavor_id = 5

View File

@ -32,7 +32,7 @@ ironic = None
IRONIC_GROUP = nova.conf.ironic.ironic_group
# The API version required by the Ironic driver
IRONIC_API_VERSION = (1, 32)
IRONIC_API_VERSION = (1, 35)
class IronicClientWrapper(object):

View File

@ -1516,8 +1516,7 @@ class IronicDriver(virt_driver.ComputeDriver):
:param image_meta: Image object returned by nova.image.glance
that defines the image from which to boot this instance. Ignored
by this driver.
:param injected_files: User files to inject into instance. Ignored
by this driver.
:param injected_files: User files to inject into instance.
:param admin_password: Administrator password to set in
instance. Ignored by this driver.
:param allocations: Information about resources allocated to the
@ -1554,10 +1553,33 @@ class IronicDriver(virt_driver.ComputeDriver):
self._add_instance_info_to_node(node, instance, image_meta,
instance.flavor, preserve_ephemeral)
# Config drive
configdrive_value = None
if configdrive.required_by(instance):
extra_md = {}
if admin_password:
extra_md['admin_pass'] = admin_password
try:
configdrive_value = self._generate_configdrive(
context, instance, node, network_info, extra_md=extra_md,
files=injected_files)
except Exception as e:
with excutils.save_and_reraise_exception():
msg = ("Failed to build configdrive: %s" %
six.text_type(e))
LOG.error(msg, instance=instance)
raise exception.InstanceDeployFailure(msg)
LOG.info("Config drive for instance %(instance)s on "
"baremetal node %(node)s created.",
{'instance': instance['uuid'], 'node': node_uuid})
# Trigger the node rebuild/redeploy.
try:
self.ironicclient.call("node.set_provision_state",
node_uuid, ironic_states.REBUILD)
node_uuid, ironic_states.REBUILD,
configdrive=configdrive_value)
except (exception.NovaException, # Retry failed
ironic.exc.InternalServerError, # Validations
ironic.exc.BadRequest) as e: # Maintenance

View File

@ -0,0 +1,6 @@
---
features:
- |
Now when you rebuild a baremetal instance, a new config drive
will be generated for the node based on the passed in personality files,
metadata, admin password, etc. This fix requires Ironic API 1.35.