Prevent changes of a resource class for an active node
Doing so would confuse nova-scheduler, and may result in attempts to schedule a new instance on such node. The API version is not updated, as this behavior is broken already, we're just moving the breakage to the API level. Change-Id: I758587d36c927c8eed852170728f6267ae18f001
This commit is contained in:
parent
15c31b9023
commit
6525a0da29
@ -165,6 +165,9 @@ class ConductorManager(base_manager.BaseConductorManager):
|
|||||||
# interfaces for active nodes in the future.
|
# interfaces for active nodes in the future.
|
||||||
allowed_update_states = [states.ENROLL, states.INSPECTING,
|
allowed_update_states = [states.ENROLL, states.INSPECTING,
|
||||||
states.MANAGEABLE, states.AVAILABLE]
|
states.MANAGEABLE, states.AVAILABLE]
|
||||||
|
action = _("Node %(node)s can not have %(field)s "
|
||||||
|
"updated unless it is in one of allowed "
|
||||||
|
"(%(allowed)s) states or in maintenance mode.")
|
||||||
for iface in drivers_base.ALL_INTERFACES:
|
for iface in drivers_base.ALL_INTERFACES:
|
||||||
interface_field = '%s_interface' % iface
|
interface_field = '%s_interface' % iface
|
||||||
if interface_field not in delta:
|
if interface_field not in delta:
|
||||||
@ -172,13 +175,10 @@ class ConductorManager(base_manager.BaseConductorManager):
|
|||||||
|
|
||||||
if not (node_obj.provision_state in allowed_update_states or
|
if not (node_obj.provision_state in allowed_update_states or
|
||||||
node_obj.maintenance):
|
node_obj.maintenance):
|
||||||
action = _("Node %(node)s can not have %(iface)s "
|
|
||||||
"updated unless it is in one of allowed "
|
|
||||||
"(%(allowed)s) states or in maintenance mode.")
|
|
||||||
raise exception.InvalidState(
|
raise exception.InvalidState(
|
||||||
action % {'node': node_obj.uuid,
|
action % {'node': node_obj.uuid,
|
||||||
'allowed': ', '.join(allowed_update_states),
|
'allowed': ', '.join(allowed_update_states),
|
||||||
'iface': interface_field})
|
'field': interface_field})
|
||||||
|
|
||||||
driver_factory.check_and_update_node_interfaces(node_obj)
|
driver_factory.check_and_update_node_interfaces(node_obj)
|
||||||
|
|
||||||
@ -192,6 +192,17 @@ class ConductorManager(base_manager.BaseConductorManager):
|
|||||||
raise exception.NodeAssociated(
|
raise exception.NodeAssociated(
|
||||||
node=node_id, instance=task.node.instance_uuid)
|
node=node_id, instance=task.node.instance_uuid)
|
||||||
|
|
||||||
|
# NOTE(dtantsur): if the resource class is changed for an active
|
||||||
|
# instance, nova will not update its internal record. That will
|
||||||
|
# result in the new resource class exposed on the node as available
|
||||||
|
# for consumption, and nova may try to schedule on this node again.
|
||||||
|
if ('resource_class' in delta and task.node.resource_class and
|
||||||
|
task.node.provision_state not in allowed_update_states):
|
||||||
|
raise exception.InvalidState(
|
||||||
|
action % {'node': node_obj.uuid,
|
||||||
|
'allowed': ', '.join(allowed_update_states),
|
||||||
|
'field': 'resource_class'})
|
||||||
|
|
||||||
node_obj.save()
|
node_obj.save()
|
||||||
|
|
||||||
return node_obj
|
return node_obj
|
||||||
|
@ -655,6 +655,54 @@ class UpdateNodeTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
|
|||||||
for iface_name in self.IFACE_UPDATE_DICT:
|
for iface_name in self.IFACE_UPDATE_DICT:
|
||||||
self._test_update_node_interface_invalid(node, iface_name)
|
self._test_update_node_interface_invalid(node, iface_name)
|
||||||
|
|
||||||
|
def _test_update_node_change_resource_class(self, state,
|
||||||
|
resource_class=None,
|
||||||
|
new_resource_class='new',
|
||||||
|
expect_error=False):
|
||||||
|
node = obj_utils.create_test_node(self.context, driver='fake',
|
||||||
|
uuid=uuidutils.generate_uuid(),
|
||||||
|
provision_state=state,
|
||||||
|
resource_class=resource_class)
|
||||||
|
self.addCleanup(node.destroy)
|
||||||
|
|
||||||
|
node.resource_class = new_resource_class
|
||||||
|
if expect_error:
|
||||||
|
exc = self.assertRaises(messaging.rpc.ExpectedException,
|
||||||
|
self.service.update_node,
|
||||||
|
self.context,
|
||||||
|
node)
|
||||||
|
# Compare true exception hidden by @messaging.expected_exceptions
|
||||||
|
self.assertEqual(exception.InvalidState, exc.exc_info[0])
|
||||||
|
|
||||||
|
# verify change did not happen
|
||||||
|
res = objects.Node.get_by_uuid(self.context, node['uuid'])
|
||||||
|
self.assertEqual(resource_class, res['resource_class'])
|
||||||
|
else:
|
||||||
|
self.service.update_node(self.context, node)
|
||||||
|
|
||||||
|
res = objects.Node.get_by_uuid(self.context, node['uuid'])
|
||||||
|
self.assertEqual('new', res['resource_class'])
|
||||||
|
|
||||||
|
def test_update_resource_class_allowed_state(self):
|
||||||
|
for state in [states.ENROLL, states.MANAGEABLE, states.INSPECTING,
|
||||||
|
states.AVAILABLE]:
|
||||||
|
self._test_update_node_change_resource_class(
|
||||||
|
state, resource_class='old', expect_error=False)
|
||||||
|
|
||||||
|
def test_update_resource_class_no_previous_value(self):
|
||||||
|
for state in [states.ENROLL, states.MANAGEABLE, states.INSPECTING,
|
||||||
|
states.AVAILABLE, states.ACTIVE]:
|
||||||
|
self._test_update_node_change_resource_class(
|
||||||
|
state, resource_class=None, expect_error=False)
|
||||||
|
|
||||||
|
def test_update_resource_class_not_allowed(self):
|
||||||
|
self._test_update_node_change_resource_class(
|
||||||
|
states.ACTIVE, resource_class='old', new_resource_class='new',
|
||||||
|
expect_error=True)
|
||||||
|
self._test_update_node_change_resource_class(
|
||||||
|
states.ACTIVE, resource_class='old', new_resource_class=None,
|
||||||
|
expect_error=True)
|
||||||
|
|
||||||
|
|
||||||
@mgr_utils.mock_record_keepalive
|
@mgr_utils.mock_record_keepalive
|
||||||
class VendorPassthruTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
|
class VendorPassthruTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase):
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
Changing the ``resource_class`` field of a node in the ``active`` state
|
||||||
|
or any of the transient states is no longer possible. Please update your
|
||||||
|
scripts to only set a resource class for nodes that are not deployed to.
|
||||||
|
Setting a resource class for nodes that do not have it is still possible.
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
No longer allows changing the ``resource_class`` field for ``active`` nodes
|
||||||
|
if it was already set to a non-empty value. Doing so would break the
|
||||||
|
Compute scheduler.
|
Loading…
Reference in New Issue
Block a user