Clean up instance_uuid as part of the node's tear down

Prior to this patch Ironic would expect Nova to clean up the instance
association, but this assumption does not play well with the standalone
version of Ironic or cleaning.

This patch keeps a backward compatibility layer with old versions of Nova
that will try to clean up the instance_uuid field even if it's already
cleaned.

Depends-On: I0b1b710056d48c8b7bb2b46fdaba192922926420
Closes-Bug: #1436568
Change-Id: I58bc848ab3a60d3e9ab16d00ce96a603df1c244a
This commit is contained in:
Lucas Alvares Gomes 2015-07-23 13:15:47 +01:00
parent 344120bfa3
commit 2894bcd849
4 changed files with 35 additions and 26 deletions

View File

@ -1036,20 +1036,23 @@ class NodesController(rest.RestController):
rpc_node = api_utils.get_rpc_node(node_ident) rpc_node = api_utils.get_rpc_node(node_ident)
# Check if node is transitioning state, although nodes in some states # TODO(lucasagomes): This code is here for backward compatibility
# can be updated. # with old nova Ironic drivers that will attempt to remove the
if ((rpc_node.maintenance or # instance even if it's already deleted in Ironic. This conditional
rpc_node.provision_state == ir_states.CLEANING) and # should be removed in the next cycle (Mitaka).
patch == [{'op': 'remove', 'path': '/instance_uuid'}]): remove_inst_uuid_patch = [{'op': 'remove', 'path': '/instance_uuid'}]
# Allow node.instance_uuid removal during cleaning, but not other if (rpc_node.provision_state in (ir_states.CLEANING,
# operations. Also allow it during maintenance, to break ir_states.CLEANWAIT)
# association with Nova in case of serious problems. and patch == remove_inst_uuid_patch):
# TODO(JoshNang) remove node.instance_uuid when removing # The instance_uuid is already removed as part of the node's
# instance_info and stop removing node.instance_uuid in the Nova # tear down, skip this update.
# Ironic driver. Bug: 1436568 return Node.convert_with_links(rpc_node)
elif rpc_node.maintenance and patch == remove_inst_uuid_patch:
LOG.debug('Removing instance uuid %(instance)s from node %(node)s', LOG.debug('Removing instance uuid %(instance)s from node %(node)s',
{'instance': rpc_node.instance_uuid, {'instance': rpc_node.instance_uuid,
'node': rpc_node.uuid}) 'node': rpc_node.uuid})
# Check if node is transitioning state, although nodes in some states
# can be updated.
elif (rpc_node.target_provision_state and rpc_node.provision_state elif (rpc_node.target_provision_state and rpc_node.provision_state
not in ir_states.UPDATE_ALLOWED_STATES): not in ir_states.UPDATE_ALLOWED_STATES):
msg = _("Node %s can not be updated while a state transition " msg = _("Node %s can not be updated while a state transition "

View File

@ -805,8 +805,9 @@ class ConductorManager(periodic_task.PeriodicTasks):
# NOTE(deva): there is no need to unset conductor_affinity # NOTE(deva): there is no need to unset conductor_affinity
# because it is a reference to the most recent conductor which # because it is a reference to the most recent conductor which
# deployed a node, and does not limit any future actions. # deployed a node, and does not limit any future actions.
# But we do need to clear the instance_info # But we do need to clear the instance-related fields.
node.instance_info = {} node.instance_info = {}
node.instance_uuid = None
driver_internal_info = node.driver_internal_info driver_internal_info = node.driver_internal_info
driver_internal_info.pop('instance', None) driver_internal_info.pop('instance', None)
node.driver_internal_info = driver_internal_info node.driver_internal_info = driver_internal_info

View File

@ -1010,11 +1010,12 @@ class TestPatch(test_api_base.FunctionalTest):
self.assertEqual(400, response.status_code) self.assertEqual(400, response.status_code)
self.assertTrue(response.json['error_message']) self.assertTrue(response.json['error_message'])
def test_remove_instance_uuid_cleaning(self): def test_remove_instance_uuid_clean_backward_compat(self):
for state in (states.CLEANING, states.CLEANWAIT):
node = obj_utils.create_test_node( node = obj_utils.create_test_node(
self.context, self.context,
uuid=uuidutils.generate_uuid(), uuid=uuidutils.generate_uuid(),
provision_state=states.CLEANING, provision_state=state,
target_provision_state=states.AVAILABLE) target_provision_state=states.AVAILABLE)
self.mock_update_node.return_value = node self.mock_update_node.return_value = node
response = self.patch_json('/nodes/%s' % node.uuid, response = self.patch_json('/nodes/%s' % node.uuid,
@ -1022,8 +1023,10 @@ class TestPatch(test_api_base.FunctionalTest):
'path': '/instance_uuid'}]) 'path': '/instance_uuid'}])
self.assertEqual('application/json', response.content_type) self.assertEqual('application/json', response.content_type)
self.assertEqual(200, response.status_code) self.assertEqual(200, response.status_code)
self.mock_update_node.assert_called_once_with( # NOTE(lucasagomes): instance_uuid is already removed as part of
mock.ANY, mock.ANY, 'test-topic') # node's tear down, assert update has not been called. This test
# should be removed in the next cycle (Mitaka).
self.assertFalse(self.mock_update_node.called)
def test_add_state_in_cleaning(self): def test_add_state_in_cleaning(self):
node = obj_utils.create_test_node( node = obj_utils.create_test_node(

View File

@ -1374,6 +1374,7 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
node = obj_utils.create_test_node( node = obj_utils.create_test_node(
self.context, driver='fake', provision_state=states.DELETING, self.context, driver='fake', provision_state=states.DELETING,
target_provision_state=states.AVAILABLE, target_provision_state=states.AVAILABLE,
instance_uuid=uuidutils.generate_uuid(),
instance_info={'foo': 'bar'}, instance_info={'foo': 'bar'},
driver_internal_info={'is_whole_disk_image': False, driver_internal_info={'is_whole_disk_image': False,
'instance': {'ephemeral_gb': 10}}) 'instance': {'ephemeral_gb': 10}})
@ -1386,6 +1387,7 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
self.assertEqual(states.CLEANING, node.provision_state) self.assertEqual(states.CLEANING, node.provision_state)
self.assertEqual(states.AVAILABLE, node.target_provision_state) self.assertEqual(states.AVAILABLE, node.target_provision_state)
self.assertIsNone(node.last_error) self.assertIsNone(node.last_error)
self.assertIsNone(node.instance_uuid)
self.assertEqual({}, node.instance_info) self.assertEqual({}, node.instance_info)
self.assertNotIn('instance', node.driver_internal_info) self.assertNotIn('instance', node.driver_internal_info)
mock_tear_down.assert_called_once_with(mock.ANY) mock_tear_down.assert_called_once_with(mock.ANY)