Skip to content

Commit cd084ae

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Error anti-affinity violation on migrations"
2 parents 7cabd6d + 33c8af1 commit cd084ae

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
@@ -1610,7 +1610,11 @@ def _decode(f):
16101610
return [_decode(f) for f in injected_files]
16111611

16121612
def _validate_instance_group_policy(self, context, instance,
1613-
scheduler_hints):
1613+
scheduler_hints=None):
1614+
1615+
if CONF.workarounds.disable_group_policy_check_upcall:
1616+
return
1617+
16141618
# NOTE(russellb) Instance group policy is enforced by the scheduler.
16151619
# However, there is a race condition with the enforcement of
16161620
# the policy. Since more than one instance may be scheduled at the
@@ -1619,29 +1623,63 @@ def _validate_instance_group_policy(self, context, instance,
16191623
# multiple instances with an affinity policy could end up on different
16201624
# hosts. This is a validation step to make sure that starting the
16211625
# instance here doesn't violate the policy.
1622-
group_hint = scheduler_hints.get('group')
1623-
if not group_hint:
1624-
return
1625-
1626-
# The RequestSpec stores scheduler_hints as key=list pairs so we need
1627-
# to check the type on the value and pull the single entry out. The
1628-
# API request schema validates that the 'group' hint is a single value.
1629-
if isinstance(group_hint, list):
1630-
group_hint = group_hint[0]
1626+
if scheduler_hints is not None:
1627+
# only go through here if scheduler_hints is provided, even if it
1628+
# is empty.
1629+
group_hint = scheduler_hints.get('group')
1630+
if not group_hint:
1631+
return
1632+
else:
1633+
# The RequestSpec stores scheduler_hints as key=list pairs so
1634+
# we need to check the type on the value and pull the single
1635+
# entry out. The API request schema validates that
1636+
# the 'group' hint is a single value.
1637+
if isinstance(group_hint, list):
1638+
group_hint = group_hint[0]
1639+
1640+
group = objects.InstanceGroup.get_by_hint(context, group_hint)
1641+
else:
1642+
# TODO(ganso): a call to DB can be saved by adding request_spec
1643+
# to rpcapi payload of live_migration, pre_live_migration and
1644+
# check_can_live_migrate_destination
1645+
try:
1646+
group = objects.InstanceGroup.get_by_instance_uuid(
1647+
context, instance.uuid)
1648+
except exception.InstanceGroupNotFound:
1649+
return
16311650

1632-
@utils.synchronized(group_hint)
1633-
def _do_validation(context, instance, group_hint):
1634-
group = objects.InstanceGroup.get_by_hint(context, group_hint)
1651+
@utils.synchronized(group['uuid'])
1652+
def _do_validation(context, instance, group):
16351653
if group.policy and 'anti-affinity' == group.policy:
1654+
1655+
# instances on host
16361656
instances_uuids = objects.InstanceList.get_uuids_by_host(
16371657
context, self.host)
16381658
ins_on_host = set(instances_uuids)
1659+
1660+
# instance param is just for logging, the nodename obtained is
1661+
# not actually related to the instance at all
1662+
nodename = self._get_nodename(instance)
1663+
1664+
# instances being migrated to host
1665+
migrations = (
1666+
objects.MigrationList.get_in_progress_by_host_and_node(
1667+
context, self.host, nodename))
1668+
migration_vm_uuids = set([mig['instance_uuid']
1669+
for mig in migrations])
1670+
1671+
total_instances = migration_vm_uuids | ins_on_host
1672+
1673+
# refresh group to get updated members within locked block
1674+
group = objects.InstanceGroup.get_by_uuid(context,
1675+
group['uuid'])
16391676
members = set(group.members)
16401677
# Determine the set of instance group members on this host
16411678
# which are not the instance in question. This is used to
16421679
# determine how many other members from the same anti-affinity
16431680
# group can be on this host.
1644-
members_on_host = ins_on_host & members - set([instance.uuid])
1681+
members_on_host = (total_instances & members -
1682+
set([instance.uuid]))
16451683
rules = group.rules
16461684
if rules and 'max_server_per_host' in rules:
16471685
max_server = rules['max_server_per_host']
@@ -1653,6 +1691,12 @@ def _do_validation(context, instance, group_hint):
16531691
raise exception.RescheduledException(
16541692
instance_uuid=instance.uuid,
16551693
reason=msg)
1694+
1695+
# NOTE(ganso): The check for affinity below does not work and it
1696+
# can easily be violated because the lock happens in different
1697+
# compute hosts.
1698+
# The only fix seems to be a DB lock to perform the check whenever
1699+
# setting the host field to an instance.
16561700
elif group.policy and 'affinity' == group.policy:
16571701
group_hosts = group.get_hosts(exclude=[instance.uuid])
16581702
if group_hosts and self.host not in group_hosts:
@@ -1661,8 +1705,7 @@ def _do_validation(context, instance, group_hint):
16611705
instance_uuid=instance.uuid,
16621706
reason=msg)
16631707

1664-
if not CONF.workarounds.disable_group_policy_check_upcall:
1665-
_do_validation(context, instance, group_hint)
1708+
_do_validation(context, instance, group)
16661709

16671710
def _log_original_error(self, exc_info, instance_uuid):
16681711
LOG.error('Error: %s', exc_info[1], instance_uuid=instance_uuid,
@@ -5174,10 +5217,24 @@ def prep_resize(self, context, image, instance, flavor,
51745217
with self._error_out_instance_on_exception(
51755218
context, instance, instance_state=instance_state),\
51765219
errors_out_migration_ctxt(migration):
5220+
51775221
self._send_prep_resize_notifications(
51785222
context, instance, fields.NotificationPhase.START,
51795223
flavor)
51805224
try:
5225+
scheduler_hints = self._get_scheduler_hints(filter_properties,
5226+
request_spec)
5227+
# Error out if this host cannot accept the new instance due
5228+
# to anti-affinity. At this point the migration is already
5229+
# in-progress, so this is the definitive moment to abort due to
5230+
# the policy violation. Also, exploding here is covered by the
5231+
# cleanup methods in except block.
5232+
try:
5233+
self._validate_instance_group_policy(context, instance,
5234+
scheduler_hints)
5235+
except exception.RescheduledException as e:
5236+
raise exception.InstanceFaultRollback(inner_exception=e)
5237+
51815238
self._prep_resize(context, image, instance,
51825239
flavor, filter_properties,
51835240
node, migration, request_spec,
@@ -7909,6 +7966,20 @@ def check_can_live_migrate_destination(self, ctxt, instance,
79097966
:param limits: objects.SchedulerLimits object for this live migration.
79107967
:returns: a LiveMigrateData object (hypervisor-dependent)
79117968
"""
7969+
7970+
# Error out if this host cannot accept the new instance due
7971+
# to anti-affinity. This check at this moment is not very accurate, as
7972+
# multiple requests may be happening concurrently and miss the lock,
7973+
# but when it works it provides a better user experience by failing
7974+
# earlier. Also, it should be safe to explode here, error becomes
7975+
# NoValidHost and instance status remains ACTIVE.
7976+
try:
7977+
self._validate_instance_group_policy(ctxt, instance)
7978+
except exception.RescheduledException as e:
7979+
msg = ("Failed to validate instance group policy "
7980+
"due to: {}".format(e))
7981+
raise exception.MigrationPreCheckError(reason=msg)
7982+
79127983
src_compute_info = obj_base.obj_to_primitive(
79137984
self._get_compute_info(ctxt, instance.host))
79147985
dst_compute_info = obj_base.obj_to_primitive(
@@ -8048,6 +8119,13 @@ def pre_live_migration(self, context, instance, disk, migrate_data):
80488119
"""
80498120
LOG.debug('pre_live_migration data is %s', migrate_data)
80508121

8122+
# Error out if this host cannot accept the new instance due
8123+
# to anti-affinity. At this point the migration is already in-progress,
8124+
# so this is the definitive moment to abort due to the policy
8125+
# violation. Also, it should be safe to explode here. The instance
8126+
# status remains ACTIVE, migration status failed.
8127+
self._validate_instance_group_policy(context, instance)
8128+
80518129
migrate_data.old_vol_attachment_ids = {}
80528130
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
80538131
context, instance.uuid)

0 commit comments

Comments
 (0)