Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extract live-migration scheduler logic from the scheduler driver
Before moving the control of live-migration into the conductor, extract the live-migration control logic into a separate class. The callback to select_hosts will be replaced by a new scheduler rpc method in a later changeset. Part of blueprint live-migration-to-conductor Change-Id: I6de33ada6dc377e20f8df07da92244f2c150b9fe
- Loading branch information
1 parent
facf42d
commit 2d7bedd
Showing
11 changed files
with
559 additions
and
838 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
# not use this file except in compliance with the License. You may obtain | ||
# a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
# License for the specific language governing permissions and limitations | ||
# under the License. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
# vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||
|
||
# Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
# not use this file except in compliance with the License. You may obtain | ||
# a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
# License for the specific language governing permissions and limitations | ||
# under the License. | ||
|
||
from oslo.config import cfg | ||
|
||
from nova.compute import flavors | ||
from nova.compute import power_state | ||
from nova.compute import rpcapi as compute_rpcapi | ||
from nova import db | ||
from nova import exception | ||
from nova.image import glance | ||
from nova.openstack.common import log as logging | ||
from nova import servicegroup | ||
|
||
LOG = logging.getLogger(__name__) | ||
|
||
CONF = cfg.CONF | ||
CONF.import_opt('scheduler_max_attempts', 'nova.scheduler.driver') | ||
|
||
|
||
class LiveMigrationTask(object): | ||
def __init__(self, context, instance, destination, | ||
block_migration, disk_over_commit, | ||
select_hosts_callback): | ||
self.context = context | ||
self.instance = instance | ||
self.destination = destination | ||
self.block_migration = block_migration | ||
self.disk_over_commit = disk_over_commit | ||
self.select_hosts_callback = select_hosts_callback | ||
self.source = instance['host'] | ||
self.migrate_data = None | ||
self.compute_rpcapi = compute_rpcapi.ComputeAPI() | ||
self.servicegroup_api = servicegroup.API() | ||
self.image_service = glance.get_default_image_service() | ||
|
||
def execute(self): | ||
self._check_instance_is_running() | ||
self._check_host_is_up(self.source) | ||
|
||
if not self.destination: | ||
self.destination = self._find_destination() | ||
else: | ||
self._check_requested_destination() | ||
|
||
#TODO(johngarbutt) need to move complexity out of compute manager | ||
return self.compute_rpcapi.live_migration(self.context, | ||
host=self.source, | ||
instance=self.instance, | ||
dest=self.destination, | ||
block_migration=self.block_migration, | ||
migrate_data=self.migrate_data) | ||
#TODO(johngarbutt) disk_over_commit? | ||
|
||
def rollback(self): | ||
#TODO(johngarbutt) need to implement the clean up operation | ||
raise NotImplementedError() | ||
|
||
def _check_instance_is_running(self): | ||
if self.instance['power_state'] != power_state.RUNNING: | ||
raise exception.InstanceNotRunning( | ||
instance_id=self.instance['uuid']) | ||
|
||
def _check_host_is_up(self, host): | ||
try: | ||
service = db.service_get_by_compute_host(self.context, host) | ||
except exception.NotFound: | ||
raise exception.ComputeServiceUnavailable(host=host) | ||
|
||
if not self.servicegroup_api.service_is_up(service): | ||
raise exception.ComputeServiceUnavailable(host=host) | ||
|
||
def _check_requested_destination(self): | ||
self._check_destination_is_not_source() | ||
self._check_host_is_up(self.destination) | ||
self._check_destination_has_enough_memory() | ||
self._check_compatible_with_source_hypervisor(self.destination) | ||
self._call_livem_checks_on_host(self.destination) | ||
|
||
def _check_destination_is_not_source(self): | ||
if self.destination == self.source: | ||
raise exception.UnableToMigrateToSelf( | ||
instance_id=self.instance['uuid'], host=self.destination) | ||
|
||
def _check_destination_has_enough_memory(self): | ||
avail = self._get_compute_info(self.destination)['free_ram_mb'] | ||
mem_inst = self.instance['memory_mb'] | ||
|
||
if not mem_inst or avail <= mem_inst: | ||
instance_uuid = self.instance['uuid'] | ||
dest = self.destination | ||
reason = _("Unable to migrate %(instance_uuid)s to %(dest)s: " | ||
"Lack of memory(host:%(avail)s <= " | ||
"instance:%(mem_inst)s)") | ||
raise exception.MigrationPreCheckError(reason=reason % dict( | ||
instance_uuid=instance_uuid, dest=dest, avail=avail, | ||
mem_inst=mem_inst)) | ||
|
||
def _get_compute_info(self, host): | ||
service_ref = db.service_get_by_compute_host(self.context, host) | ||
return service_ref['compute_node'][0] | ||
|
||
def _check_compatible_with_source_hypervisor(self, destination): | ||
source_info = self._get_compute_info(self.source) | ||
destination_info = self._get_compute_info(destination) | ||
|
||
source_type = source_info['hypervisor_type'] | ||
destination_type = destination_info['hypervisor_type'] | ||
if source_type != destination_type: | ||
raise exception.InvalidHypervisorType() | ||
|
||
source_version = source_info['hypervisor_version'] | ||
destination_version = destination_info['hypervisor_version'] | ||
if source_version > destination_version: | ||
raise exception.DestinationHypervisorTooOld() | ||
|
||
def _call_livem_checks_on_host(self, destination): | ||
self.migrate_data = self.compute_rpcapi.\ | ||
check_can_live_migrate_destination(self.context, self.instance, | ||
destination, self.block_migration, self.disk_over_commit) | ||
|
||
def _find_destination(self): | ||
#TODO(johngarbutt) this retry loop should be shared | ||
ignore_hosts = [self.source] | ||
image = self.image_service.show(self.context, | ||
self.instance['image_ref']) | ||
instance_type = flavors.extract_flavor(self.instance) | ||
|
||
host = None | ||
while host is None: | ||
self._check_not_over_max_attempts(ignore_hosts) | ||
|
||
host = self._get_candidate_destination(image, | ||
instance_type, ignore_hosts) | ||
try: | ||
self._check_compatible_with_source_hypervisor(host) | ||
self._call_livem_checks_on_host(host) | ||
except exception.Invalid as e: | ||
LOG.debug(_("Skipping host: %(host)s because: %(e)s") % | ||
{"host": host, "e": e}) | ||
ignore_hosts.append(host) | ||
host = None | ||
return host | ||
|
||
def _get_candidate_destination(self, image, instance_type, ignore_hosts): | ||
request_spec = {'instance_properties': self.instance, | ||
'instance_type': instance_type, | ||
'instance_uuids': [self.instance['uuid']], | ||
'image': image} | ||
filter_properties = {'ignore_hosts': ignore_hosts} | ||
#TODO(johngarbutt) this should be an rpc call to scheduler | ||
return self.select_hosts_callback(self.context, request_spec, | ||
filter_properties)[0] | ||
|
||
def _check_not_over_max_attempts(self, ignore_hosts): | ||
attempts = len(ignore_hosts) | ||
if attempts > CONF.scheduler_max_attempts: | ||
msg = (_('Exceeded max scheduling attempts %(max_attempts)d for ' | ||
'instance %(instance_uuid)s during live migration') | ||
% {'max_attempts': attempts, | ||
'instance_uuid': self.instance['uuid']}) | ||
raise exception.NoValidHost(reason=msg) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.