Skip to content

Commit 6ede6df

Browse files
Error anti-affinity violation on migrations
Error-out the migrations (cold and live) whenever the anti-affinity policy is violated. This addresses violations when multiple concurrent migrations are requested. Added detection on: - prep_resize - check_can_live_migration_destination - pre_live_migration The improved method of detection now locks based on group_id and considers other migrations in-progress as well. Closes-bug: #1821755 Change-Id: I32e6214568bb57f7613ddeba2c2c46da0320fabc (cherry picked from commit 33c8af1) (cherry picked from commit 8b62a4e)
1 parent 2af08fb commit 6ede6df

File tree

3 files changed

+211
-24
lines changed

3 files changed

+211
-24
lines changed

nova/compute/manager.py

Lines changed: 94 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1646,7 +1646,11 @@ def _decode(f):
16461646
return [_decode(f) for f in injected_files]
16471647

16481648
def _validate_instance_group_policy(self, context, instance,
1649-
scheduler_hints):
1649+
scheduler_hints=None):
1650+
1651+
if CONF.workarounds.disable_group_policy_check_upcall:
1652+
return
1653+
16501654
# NOTE(russellb) Instance group policy is enforced by the scheduler.
16511655
# However, there is a race condition with the enforcement of
16521656
# the policy. Since more than one instance may be scheduled at the
@@ -1655,29 +1659,63 @@ def _validate_instance_group_policy(self, context, instance,
16551659
# multiple instances with an affinity policy could end up on different
16561660
# hosts. This is a validation step to make sure that starting the
16571661
# instance here doesn't violate the policy.
1658-
group_hint = scheduler_hints.get('group')
1659-
if not group_hint:
1660-
return
1661-
1662-
# The RequestSpec stores scheduler_hints as key=list pairs so we need
1663-
# to check the type on the value and pull the single entry out. The
1664-
# API request schema validates that the 'group' hint is a single value.
1665-
if isinstance(group_hint, list):
1666-
group_hint = group_hint[0]
1662+
if scheduler_hints is not None:
1663+
# only go through here if scheduler_hints is provided, even if it
1664+
# is empty.
1665+
group_hint = scheduler_hints.get('group')
1666+
if not group_hint:
1667+
return
1668+
else:
1669+
# The RequestSpec stores scheduler_hints as key=list pairs so
1670+
# we need to check the type on the value and pull the single
1671+
# entry out. The API request schema validates that
1672+
# the 'group' hint is a single value.
1673+
if isinstance(group_hint, list):
1674+
group_hint = group_hint[0]
1675+
1676+
group = objects.InstanceGroup.get_by_hint(context, group_hint)
1677+
else:
1678+
# TODO(ganso): a call to DB can be saved by adding request_spec
1679+
# to rpcapi payload of live_migration, pre_live_migration and
1680+
# check_can_live_migrate_destination
1681+
try:
1682+
group = objects.InstanceGroup.get_by_instance_uuid(
1683+
context, instance.uuid)
1684+
except exception.InstanceGroupNotFound:
1685+
return
16671686

1668-
@utils.synchronized(group_hint)
1669-
def _do_validation(context, instance, group_hint):
1670-
group = objects.InstanceGroup.get_by_hint(context, group_hint)
1687+
@utils.synchronized(group['uuid'])
1688+
def _do_validation(context, instance, group):
16711689
if group.policy and 'anti-affinity' == group.policy:
1690+
1691+
# instances on host
16721692
instances_uuids = objects.InstanceList.get_uuids_by_host(
16731693
context, self.host)
16741694
ins_on_host = set(instances_uuids)
1695+
1696+
# instance param is just for logging, the nodename obtained is
1697+
# not actually related to the instance at all
1698+
nodename = self._get_nodename(instance)
1699+
1700+
# instances being migrated to host
1701+
migrations = (
1702+
objects.MigrationList.get_in_progress_by_host_and_node(
1703+
context, self.host, nodename))
1704+
migration_vm_uuids = set([mig['instance_uuid']
1705+
for mig in migrations])
1706+
1707+
total_instances = migration_vm_uuids | ins_on_host
1708+
1709+
# refresh group to get updated members within locked block
1710+
group = objects.InstanceGroup.get_by_uuid(context,
1711+
group['uuid'])
16751712
members = set(group.members)
16761713
# Determine the set of instance group members on this host
16771714
# which are not the instance in question. This is used to
16781715
# determine how many other members from the same anti-affinity
16791716
# group can be on this host.
1680-
members_on_host = ins_on_host & members - set([instance.uuid])
1717+
members_on_host = (total_instances & members -
1718+
set([instance.uuid]))
16811719
rules = group.rules
16821720
if rules and 'max_server_per_host' in rules:
16831721
max_server = rules['max_server_per_host']
@@ -1689,6 +1727,12 @@ def _do_validation(context, instance, group_hint):
16891727
raise exception.RescheduledException(
16901728
instance_uuid=instance.uuid,
16911729
reason=msg)
1730+
1731+
# NOTE(ganso): The check for affinity below does not work and it
1732+
# can easily be violated because the lock happens in different
1733+
# compute hosts.
1734+
# The only fix seems to be a DB lock to perform the check whenever
1735+
# setting the host field to an instance.
16921736
elif group.policy and 'affinity' == group.policy:
16931737
group_hosts = group.get_hosts(exclude=[instance.uuid])
16941738
if group_hosts and self.host not in group_hosts:
@@ -1697,8 +1741,7 @@ def _do_validation(context, instance, group_hint):
16971741
instance_uuid=instance.uuid,
16981742
reason=msg)
16991743

1700-
if not CONF.workarounds.disable_group_policy_check_upcall:
1701-
_do_validation(context, instance, group_hint)
1744+
_do_validation(context, instance, group)
17021745

17031746
def _log_original_error(self, exc_info, instance_uuid):
17041747
LOG.error('Error: %s', exc_info[1], instance_uuid=instance_uuid,
@@ -5217,10 +5260,24 @@ def prep_resize(self, context, image, instance, instance_type,
52175260
with self._error_out_instance_on_exception(
52185261
context, instance, instance_state=instance_state),\
52195262
errors_out_migration_ctxt(migration):
5263+
52205264
self._send_prep_resize_notifications(
52215265
context, instance, fields.NotificationPhase.START,
52225266
instance_type)
52235267
try:
5268+
scheduler_hints = self._get_scheduler_hints(filter_properties,
5269+
request_spec)
5270+
# Error out if this host cannot accept the new instance due
5271+
# to anti-affinity. At this point the migration is already
5272+
# in-progress, so this is the definitive moment to abort due to
5273+
# the policy violation. Also, exploding here is covered by the
5274+
# cleanup methods in except block.
5275+
try:
5276+
self._validate_instance_group_policy(context, instance,
5277+
scheduler_hints)
5278+
except exception.RescheduledException as e:
5279+
raise exception.InstanceFaultRollback(inner_exception=e)
5280+
52245281
self._prep_resize(context, image, instance,
52255282
instance_type, filter_properties,
52265283
node, migration, request_spec,
@@ -7823,6 +7880,20 @@ def check_can_live_migrate_destination(self, ctxt, instance,
78237880
:param limits: objects.SchedulerLimits object for this live migration.
78247881
:returns: a LiveMigrateData object (hypervisor-dependent)
78257882
"""
7883+
7884+
# Error out if this host cannot accept the new instance due
7885+
# to anti-affinity. This check at this moment is not very accurate, as
7886+
# multiple requests may be happening concurrently and miss the lock,
7887+
# but when it works it provides a better user experience by failing
7888+
# earlier. Also, it should be safe to explode here, error becomes
7889+
# NoValidHost and instance status remains ACTIVE.
7890+
try:
7891+
self._validate_instance_group_policy(ctxt, instance)
7892+
except exception.RescheduledException as e:
7893+
msg = ("Failed to validate instance group policy "
7894+
"due to: {}".format(e))
7895+
raise exception.MigrationPreCheckError(reason=msg)
7896+
78267897
src_compute_info = obj_base.obj_to_primitive(
78277898
self._get_compute_info(ctxt, instance.host))
78287899
dst_compute_info = obj_base.obj_to_primitive(
@@ -7965,6 +8036,13 @@ def pre_live_migration(self, context, instance, block_migration, disk,
79658036
"""
79668037
LOG.debug('pre_live_migration data is %s', migrate_data)
79678038

8039+
# Error out if this host cannot accept the new instance due
8040+
# to anti-affinity. At this point the migration is already in-progress,
8041+
# so this is the definitive moment to abort due to the policy
8042+
# violation. Also, it should be safe to explode here. The instance
8043+
# status remains ACTIVE, migration status failed.
8044+
self._validate_instance_group_policy(context, instance)
8045+
79688046
migrate_data.old_vol_attachment_ids = {}
79698047
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
79708048
context, instance.uuid)

0 commit comments

Comments
 (0)