Merge "Add option to protect available nodes from accidental deletion"

This commit is contained in:
Zuul 2019-03-05 14:21:08 +00:00 committed by Gerrit Code Review
commit 5d25189b13
5 changed files with 38 additions and 3 deletions

View File

@ -223,7 +223,7 @@ UPDATE_ALLOWED_STATES = (DEPLOYFAIL, INSPECTING, INSPECTFAIL, INSPECTWAIT,
UNRESCUEFAIL) UNRESCUEFAIL)
"""Transitional states in which we allow updating a node.""" """Transitional states in which we allow updating a node."""
DELETE_ALLOWED_STATES = (AVAILABLE, MANAGEABLE, ENROLL, ADOPTFAIL) DELETE_ALLOWED_STATES = (MANAGEABLE, ENROLL, ADOPTFAIL)
"""States in which node deletion is allowed.""" """States in which node deletion is allowed."""
STABLE_STATES = (ENROLL, MANAGEABLE, AVAILABLE, ACTIVE, ERROR, RESCUE) STABLE_STATES = (ENROLL, MANAGEABLE, AVAILABLE, ACTIVE, ERROR, RESCUE)

View File

@ -2269,15 +2269,18 @@ class ConductorManager(base_manager.BaseConductorManager):
# CLEANFAIL -> MANAGEABLE # CLEANFAIL -> MANAGEABLE
# INSPECTIONFAIL -> MANAGEABLE # INSPECTIONFAIL -> MANAGEABLE
# DEPLOYFAIL -> DELETING # DEPLOYFAIL -> DELETING
delete_allowed_states = states.DELETE_ALLOWED_STATES
if CONF.conductor.allow_deleting_available_nodes:
delete_allowed_states += (states.AVAILABLE,)
if (not node.maintenance if (not node.maintenance
and node.provision_state and node.provision_state
not in states.DELETE_ALLOWED_STATES): not in delete_allowed_states):
msg = (_('Can not delete node "%(node)s" while it is in ' msg = (_('Can not delete node "%(node)s" while it is in '
'provision state "%(state)s". Valid provision states ' 'provision state "%(state)s". Valid provision states '
'to perform deletion are: "%(valid_states)s", ' 'to perform deletion are: "%(valid_states)s", '
'or set the node into maintenance mode') % 'or set the node into maintenance mode') %
{'node': node.uuid, 'state': node.provision_state, {'node': node.uuid, 'state': node.provision_state,
'valid_states': states.DELETE_ALLOWED_STATES}) 'valid_states': delete_allowed_states})
raise exception.InvalidState(msg) raise exception.InvalidState(msg)
if node.console_enabled: if node.console_enabled:
notify_utils.emit_console_notification( notify_utils.emit_console_notification(

View File

@ -199,6 +199,10 @@ opts = [
'255 characters and is case insensitive. This ' '255 characters and is case insensitive. This '
'conductor will only manage nodes with a matching ' 'conductor will only manage nodes with a matching '
'"conductor_group" field set on the node.')), '"conductor_group" field set on the node.')),
cfg.BoolOpt('allow_deleting_available_nodes',
default=True,
help=_('Allow deleting nodes which are in state '
'\'available\'. Defaults to True.')),
] ]

View File

@ -0,0 +1,12 @@
---
features:
- Adds option 'allow_deleting_available_nodes' to control whether nodes in
state 'available' should be deletable (which is and stays the default).
Setting this option to False will remove 'available' from the list of
states in which nodes can be deleted from ironic. It hence provides
protection against accidental removal of nodes which are ready for
allocation (and is meant as a safeguard for the operational effort to
bring nodes into this state). For backwards compatibility reasons, the
default value for this option is True. The other states in which nodes
can be deleted from ironic ('manageable', 'enroll', and 'adoptfail')
remain unchanged.

View File

@ -5090,6 +5090,22 @@ class DestroyNodeTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
node.refresh() node.refresh()
self.assertIsNone(node.reservation) self.assertIsNone(node.reservation)
def test_destroy_node_protected_provision_state_available(self):
CONF.set_override('allow_deleting_available_nodes',
False, group='conductor')
self._start_service()
node = obj_utils.create_test_node(self.context,
provision_state=states.AVAILABLE)
exc = self.assertRaises(messaging.rpc.ExpectedException,
self.service.destroy_node,
self.context, node.uuid)
# Compare true exception hidden by @messaging.expected_exceptions
self.assertEqual(exception.InvalidState, exc.exc_info[0])
# Verify reservation was released.
node.refresh()
self.assertIsNone(node.reservation)
def test_destroy_node_protected(self): def test_destroy_node_protected(self):
self._start_service() self._start_service()
node = obj_utils.create_test_node(self.context, node = obj_utils.create_test_node(self.context,