Merge "Refactor out claim_resources_on_destination into a utility" into stable/pike

This commit is contained in:
Jenkins 2017-09-15 18:03:45 +00:00 committed by Gerrit Code Review
commit ca5400ebcf
4 changed files with 204 additions and 164 deletions

View File

@ -71,7 +71,11 @@ class LiveMigrationTask(base.TaskBase):
# skip_filters=True flag so the scheduler does the work of claiming
# resources on the destination in Placement but still bypass the
# scheduler filters, which honors the 'force' flag in the API.
self._claim_resources_on_destination(source_node, dest_node)
# This raises NoValidHost which will be handled in
# ComputeTaskManager.
scheduler_utils.claim_resources_on_destination(
self.scheduler_client.reportclient, self.instance,
source_node, dest_node)
# TODO(johngarbutt) need to move complexity out of compute manager
# TODO(johngarbutt) disk_over_commit?
@ -129,72 +133,6 @@ class LiveMigrationTask(base.TaskBase):
'across cells.') % self.instance.uuid))
return source_node, dest_node
def _claim_resources_on_destination(self, source_node, dest_node):
"""Copies allocations from source node to dest node in Placement
:param source_node: source ComputeNode where the instance currently
lives
:param dest_node: destination ComputeNode where the instance is being
forced to live migrate.
"""
reportclient = self.scheduler_client.reportclient
# Get the current allocations for the source node and the instance.
source_node_allocations = reportclient.get_allocations_for_instance(
source_node.uuid, self.instance)
if source_node_allocations:
# Generate an allocation request for the destination node.
alloc_request = {
'allocations': [
{
'resource_provider': {
'uuid': dest_node.uuid
},
'resources': source_node_allocations
}
]
}
# The claim_resources method will check for existing allocations
# for the instance and effectively "double up" the allocations for
# both the source and destination node. That's why when requesting
# allocations for resources on the destination node before we live
# migrate, we use the existing resource allocations from the
# source node.
if reportclient.claim_resources(
self.instance.uuid, alloc_request,
self.instance.project_id, self.instance.user_id):
LOG.debug('Instance allocations successfully created on '
'destination node %(dest)s: %(alloc_request)s',
{'dest': dest_node.uuid,
'alloc_request': alloc_request},
instance=self.instance)
else:
# We have to fail even though the user requested that we force
# the host. This is because we need Placement to have an
# accurate reflection of what's allocated on all nodes so the
# scheduler can make accurate decisions about which nodes have
# capacity for building an instance. We also cannot rely on the
# resource tracker in the compute service automatically healing
# the allocations since that code is going away in Queens.
reason = (_('Unable to migrate instance %(instance_uuid)s to '
'host %(host)s. There is not enough capacity on '
'the host for the instance.') %
{'instance_uuid': self.instance.uuid,
'host': self.destination})
raise exception.MigrationPreCheckError(reason=reason)
else:
# This shouldn't happen, but it could be a case where there are
# older (Ocata) computes still so the existing allocations are
# getting overwritten by the update_available_resource periodic
# task in the compute service.
# TODO(mriedem): Make this an error when the auto-heal
# compatibility code in the resource tracker is removed.
LOG.warning('No instance allocations found for source node '
'%(source)s in Placement. Not creating allocations '
'for destination node %(dest)s and assuming the '
'compute service will heal the allocations.',
{'source': source_node.uuid, 'dest': dest_node.uuid},
instance=self.instance)
def _check_destination_is_not_source(self):
if self.destination == self.source:
raise exception.UnableToMigrateToSelf(

View File

@ -218,6 +218,87 @@ def resources_from_request_spec(spec_obj):
return resources
# TODO(mriedem): Remove this when select_destinations() in the scheduler takes
# some sort of skip_filters flag.
def claim_resources_on_destination(
reportclient, instance, source_node, dest_node):
"""Copies allocations from source node to dest node in Placement
Normally the scheduler will allocate resources on a chosen destination
node during a move operation like evacuate and live migration. However,
because of the ability to force a host and bypass the scheduler, this
method can be used to manually copy allocations from the source node to
the forced destination node.
This is only appropriate when the instance flavor on the source node
is the same on the destination node, i.e. don't use this for resize.
:param reportclient: An instance of the SchedulerReportClient.
:param instance: The instance being moved.
:param source_node: source ComputeNode where the instance currently
lives
:param dest_node: destination ComputeNode where the instance is being
moved
:raises NoValidHost: If the allocation claim on the destination
node fails.
"""
# Get the current allocations for the source node and the instance.
source_node_allocations = reportclient.get_allocations_for_instance(
source_node.uuid, instance)
if source_node_allocations:
# Generate an allocation request for the destination node.
alloc_request = {
'allocations': [
{
'resource_provider': {
'uuid': dest_node.uuid
},
'resources': source_node_allocations
}
]
}
# The claim_resources method will check for existing allocations
# for the instance and effectively "double up" the allocations for
# both the source and destination node. That's why when requesting
# allocations for resources on the destination node before we move,
# we use the existing resource allocations from the source node.
if reportclient.claim_resources(
instance.uuid, alloc_request,
instance.project_id, instance.user_id):
LOG.debug('Instance allocations successfully created on '
'destination node %(dest)s: %(alloc_request)s',
{'dest': dest_node.uuid,
'alloc_request': alloc_request},
instance=instance)
else:
# We have to fail even though the user requested that we force
# the host. This is because we need Placement to have an
# accurate reflection of what's allocated on all nodes so the
# scheduler can make accurate decisions about which nodes have
# capacity for building an instance. We also cannot rely on the
# resource tracker in the compute service automatically healing
# the allocations since that code is going away in Queens.
reason = (_('Unable to move instance %(instance_uuid)s to '
'host %(host)s. There is not enough capacity on '
'the host for the instance.') %
{'instance_uuid': instance.uuid,
'host': dest_node.host})
raise exception.NoValidHost(reason=reason)
else:
# This shouldn't happen, but it could be a case where there are
# older (Ocata) computes still so the existing allocations are
# getting overwritten by the update_available_resource periodic
# task in the compute service.
# TODO(mriedem): Make this an error when the auto-heal
# compatibility code in the resource tracker is removed.
LOG.warning('No instance allocations found for source node '
'%(source)s in Placement. Not creating allocations '
'for destination node %(dest)s and assuming the '
'compute service will heal the allocations.',
{'source': source_node.uuid, 'dest': dest_node.uuid},
instance=instance)
def set_vm_state_and_notify(context, instance_uuid, service, method, updates,
ex, request_spec):
"""changes VM state and notifies."""

View File

@ -66,7 +66,8 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
mock.patch.object(self.task, '_check_requested_destination',
return_value=(mock.sentinel.source_node,
mock.sentinel.dest_node)),
mock.patch.object(self.task, '_claim_resources_on_destination'),
mock.patch.object(scheduler_utils,
'claim_resources_on_destination'),
mock.patch.object(self.task.compute_rpcapi, 'live_migration'),
) as (mock_check_up, mock_check_dest, mock_claim, mock_mig):
mock_mig.return_value = "bob"
@ -75,6 +76,7 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
mock_check_up.assert_called_once_with(self.instance_host)
mock_check_dest.assert_called_once_with()
mock_claim.assert_called_once_with(
self.task.scheduler_client.reportclient, self.instance,
mock.sentinel.source_node, mock.sentinel.dest_node)
mock_mig.assert_called_once_with(
self.context,
@ -517,102 +519,6 @@ class LiveMigrationTaskTestCase(test.NoDBTestCase):
self.assertRaises(exception.MigrationPreCheckError,
self.task._call_livem_checks_on_host, {})
def test_claim_resources_on_destination_no_source_allocations(self):
"""Tests the negative scenario where the instance does not have
allocations in Placement on the source compute node so no claim is
attempted on the destination compute node.
"""
source_node = objects.ComputeNode(uuid=uuids.source_node)
dest_node = objects.ComputeNode(uuid=uuids.dest_node)
@mock.patch.object(self.task.scheduler_client.reportclient,
'get_allocations_for_instance', return_value={})
@mock.patch.object(self.task.scheduler_client.reportclient,
'claim_resources',
new_callable=mock.NonCallableMock)
def test(mock_claim, mock_get_allocs):
self.task._claim_resources_on_destination(source_node, dest_node)
mock_get_allocs.assert_called_once_with(
uuids.source_node, self.instance)
test()
def test_claim_resources_on_destination_claim_fails(self):
"""Tests the negative scenario where the resource allocation claim
on the destination compute node fails, resulting in an error.
"""
source_node = objects.ComputeNode(uuid=uuids.source_node)
dest_node = objects.ComputeNode(uuid=uuids.dest_node)
source_res_allocs = {
'VCPU': self.instance.vcpus,
'MEMORY_MB': self.instance.memory_mb,
# This would really include ephemeral and swap too but we're lazy.
'DISK_GB': self.instance.root_gb
}
dest_alloc_request = {
'allocations': [
{
'resource_provider': {
'uuid': uuids.dest_node
},
'resources': source_res_allocs
}
]
}
@mock.patch.object(self.task.scheduler_client.reportclient,
'get_allocations_for_instance',
return_value=source_res_allocs)
@mock.patch.object(self.task.scheduler_client.reportclient,
'claim_resources', return_value=False)
def test(mock_claim, mock_get_allocs):
self.assertRaises(exception.MigrationPreCheckError,
self.task._claim_resources_on_destination,
source_node, dest_node)
mock_get_allocs.assert_called_once_with(
uuids.source_node, self.instance)
mock_claim.assert_called_once_with(
self.instance.uuid, dest_alloc_request,
self.instance.project_id, self.instance.user_id)
test()
def test_claim_resources_on_destination(self):
"""Happy path test where everything is successful."""
source_node = objects.ComputeNode(uuid=uuids.source_node)
dest_node = objects.ComputeNode(uuid=uuids.dest_node)
source_res_allocs = {
'VCPU': self.instance.vcpus,
'MEMORY_MB': self.instance.memory_mb,
# This would really include ephemeral and swap too but we're lazy.
'DISK_GB': self.instance.root_gb
}
dest_alloc_request = {
'allocations': [
{
'resource_provider': {
'uuid': uuids.dest_node
},
'resources': source_res_allocs
}
]
}
@mock.patch.object(self.task.scheduler_client.reportclient,
'get_allocations_for_instance',
return_value=source_res_allocs)
@mock.patch.object(self.task.scheduler_client.reportclient,
'claim_resources', return_value=True)
def test(mock_claim, mock_get_allocs):
self.task._claim_resources_on_destination(source_node, dest_node)
mock_get_allocs.assert_called_once_with(
uuids.source_node, self.instance)
mock_claim.assert_called_once_with(
self.instance.uuid, dest_alloc_request,
self.instance.project_id, self.instance.user_id)
test()
@mock.patch.object(objects.InstanceMapping, 'get_by_instance_uuid',
side_effect=exception.InstanceMappingNotFound(
uuid=uuids.instance))

View File

@ -12,9 +12,14 @@
import mock
from nova import context as nova_context
from nova import exception
from nova import objects
from nova.scheduler.client import report
from nova.scheduler import utils
from nova import test
from nova.tests.unit import fake_instance
from nova.tests import uuidsentinel as uuids
class TestUtils(test.NoDBTestCase):
@ -255,3 +260,113 @@ class TestUtils(test.NoDBTestCase):
}
utils.merge_resources(resources, new_resources, -1)
self.assertEqual(merged, resources)
def test_claim_resources_on_destination_no_source_allocations(self):
"""Tests the negative scenario where the instance does not have
allocations in Placement on the source compute node so no claim is
attempted on the destination compute node.
"""
reportclient = report.SchedulerReportClient()
instance = fake_instance.fake_instance_obj(
nova_context.get_admin_context())
source_node = objects.ComputeNode(
uuid=uuids.source_node, host=instance.host)
dest_node = objects.ComputeNode(uuid=uuids.dest_node, host='dest-host')
@mock.patch.object(reportclient,
'get_allocations_for_instance', return_value={})
@mock.patch.object(reportclient,
'claim_resources',
new_callable=mock.NonCallableMock)
def test(mock_claim, mock_get_allocs):
utils.claim_resources_on_destination(
reportclient, instance, source_node, dest_node)
mock_get_allocs.assert_called_once_with(
uuids.source_node, instance)
test()
def test_claim_resources_on_destination_claim_fails(self):
"""Tests the negative scenario where the resource allocation claim
on the destination compute node fails, resulting in an error.
"""
reportclient = report.SchedulerReportClient()
instance = fake_instance.fake_instance_obj(
nova_context.get_admin_context())
source_node = objects.ComputeNode(
uuid=uuids.source_node, host=instance.host)
dest_node = objects.ComputeNode(uuid=uuids.dest_node, host='dest-host')
source_res_allocs = {
'VCPU': instance.vcpus,
'MEMORY_MB': instance.memory_mb,
# This would really include ephemeral and swap too but we're lazy.
'DISK_GB': instance.root_gb
}
dest_alloc_request = {
'allocations': [
{
'resource_provider': {
'uuid': uuids.dest_node
},
'resources': source_res_allocs
}
]
}
@mock.patch.object(reportclient,
'get_allocations_for_instance',
return_value=source_res_allocs)
@mock.patch.object(reportclient,
'claim_resources', return_value=False)
def test(mock_claim, mock_get_allocs):
self.assertRaises(exception.NoValidHost,
utils.claim_resources_on_destination,
reportclient, instance, source_node, dest_node)
mock_get_allocs.assert_called_once_with(
uuids.source_node, instance)
mock_claim.assert_called_once_with(
instance.uuid, dest_alloc_request,
instance.project_id, instance.user_id)
test()
def test_claim_resources_on_destination(self):
"""Happy path test where everything is successful."""
reportclient = report.SchedulerReportClient()
instance = fake_instance.fake_instance_obj(
nova_context.get_admin_context())
source_node = objects.ComputeNode(
uuid=uuids.source_node, host=instance.host)
dest_node = objects.ComputeNode(uuid=uuids.dest_node, host='dest-host')
source_res_allocs = {
'VCPU': instance.vcpus,
'MEMORY_MB': instance.memory_mb,
# This would really include ephemeral and swap too but we're lazy.
'DISK_GB': instance.root_gb
}
dest_alloc_request = {
'allocations': [
{
'resource_provider': {
'uuid': uuids.dest_node
},
'resources': source_res_allocs
}
]
}
@mock.patch.object(reportclient,
'get_allocations_for_instance',
return_value=source_res_allocs)
@mock.patch.object(reportclient,
'claim_resources', return_value=True)
def test(mock_claim, mock_get_allocs):
utils.claim_resources_on_destination(
reportclient, instance, source_node, dest_node)
mock_get_allocs.assert_called_once_with(
uuids.source_node, instance)
mock_claim.assert_called_once_with(
instance.uuid, dest_alloc_request,
instance.project_id, instance.user_id)
test()