Skip to content

Commit

Permalink
Extract live-migration scheduler logic from the scheduler driver
Browse files Browse the repository at this point in the history
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
JohnGarbutt authored and John Garbutt committed Jun 24, 2013
1 parent facf42d commit 2d7bedd
Show file tree
Hide file tree
Showing 11 changed files with 559 additions and 838 deletions.
11 changes: 11 additions & 0 deletions nova/conductor/tasks/__init__.py
@@ -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.
173 changes: 173 additions & 0 deletions nova/conductor/tasks/live_migrate.py
@@ -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)
5 changes: 5 additions & 0 deletions nova/scheduler/chance.py
Expand Up @@ -25,6 +25,7 @@

from oslo.config import cfg

from nova.compute import rpcapi as compute_rpcapi
from nova import exception
from nova.scheduler import driver

Expand All @@ -35,6 +36,10 @@
class ChanceScheduler(driver.Scheduler):
"""Implements Scheduler as a random node selector."""

def __init__(self, *args, **kwargs):
super(ChanceScheduler, self).__init__(*args, **kwargs)
self.compute_rpcapi = compute_rpcapi.ComputeAPI()

def _filter_hosts(self, request_spec, hosts, filter_properties):
"""Filter a list of hosts based on request_spec."""

Expand Down

0 comments on commit 2d7bedd

Please sign in to comment.