Change RPC to use new BDM format for instance boot

This patch makes rpc calls that are the result of booting an instance
propagate the new block device mapping format.

The way this is done is by adding a 'legacy_bdm' flag to the conductor
(task) API. Since both the scheduler and the compute service propagate
the block device mapping as part of the request_spec field, a flag named
legacy_bdm_in_spec to indicate that was introduced in their respective
APIs.

Currently block_device_mapping is not used by any of the in-tree filters
in the scheduler, so it is assumed that scheduler's boot functionality
has now transitioned to the new format.

This patch also bumps RPC versions of the task, scheduler and compute
manager classes.

Finally this patch propagates the legacy_bdm_in_spec through the
run_instance callback of the compute manager so that, in case of error -
the instance is re-scheduled with the proper flag set.

blueprint: improve-block-device-handling

Change-Id: I5f25ddd4d586dda91061f065c1796be726b0ede3
This commit is contained in:
Nikola Dipanov 2013-07-15 10:40:50 +02:00
parent 0ef7e15e22
commit 552693e4ad
18 changed files with 89 additions and 65 deletions

View File

@ -851,10 +851,6 @@ class API(base.Base):
self._record_action_start(context, instance,
instance_actions.CREATE)
# NOTE (ndipanov): Switch back to the old format until the
# compute side is ready for it
block_device_mapping = block_device.legacy_mapping(
block_device_mapping)
self.compute_task_api.build_instances(context,
instances=instances, image=boot_meta,
filter_properties=filter_properties,
@ -862,7 +858,8 @@ class API(base.Base):
injected_files=injected_files,
requested_networks=requested_networks,
security_groups=security_groups,
block_device_mapping=block_device_mapping)
block_device_mapping=block_device_mapping,
legacy_bdm=False)
return (instances, reservation_id)

View File

@ -376,7 +376,7 @@ class ComputeVirtAPI(virtapi.VirtAPI):
class ComputeManager(manager.SchedulerDependentManager):
"""Manages the running instances from creation to destruction."""
RPC_API_VERSION = '2.36'
RPC_API_VERSION = '2.37'
def __init__(self, compute_driver=None, *args, **kwargs):
"""Load configuration options and connect to the hypervisor."""
@ -987,7 +987,8 @@ class ComputeManager(manager.SchedulerDependentManager):
def _run_instance(self, context, request_spec,
filter_properties, requested_networks, injected_files,
admin_password, is_first_time, node, instance):
admin_password, is_first_time, node, instance,
legacy_bdm_in_spec):
"""Launch a new instance with specified options."""
extra_usage_info = {}
@ -1012,7 +1013,7 @@ class ComputeManager(manager.SchedulerDependentManager):
instance, network_info = self._build_instance(context,
request_spec, filter_properties, requested_networks,
injected_files, admin_password, is_first_time, node,
instance, image_meta)
instance, image_meta, legacy_bdm_in_spec)
notify("end", msg=_("Success"), network_info=network_info)
except exception.RescheduledException as e:
@ -1051,7 +1052,7 @@ class ComputeManager(manager.SchedulerDependentManager):
def _build_instance(self, context, request_spec, filter_properties,
requested_networks, injected_files, admin_password, is_first_time,
node, instance, image_meta):
node, instance, image_meta, legacy_bdm_in_spec):
context = context.elevated()
# If neutron security groups pass requested security
@ -1135,7 +1136,7 @@ class ComputeManager(manager.SchedulerDependentManager):
rescheduled = self._reschedule_or_error(context, instance,
exc_info, requested_networks, admin_password,
injected_files_orig, is_first_time, request_spec,
filter_properties, bdms)
filter_properties, bdms, legacy_bdm_in_spec)
if rescheduled:
# log the original build error
self._log_original_error(exc_info, instance['uuid'])
@ -1157,7 +1158,8 @@ class ComputeManager(manager.SchedulerDependentManager):
def _reschedule_or_error(self, context, instance, exc_info,
requested_networks, admin_password, injected_files, is_first_time,
request_spec, filter_properties, bdms=None):
request_spec, filter_properties, bdms=None,
legacy_bdm_in_spec=True):
"""Try to re-schedule the build or re-raise the original build error to
error out the instance.
"""
@ -1184,7 +1186,8 @@ class ComputeManager(manager.SchedulerDependentManager):
try:
method_args = (request_spec, admin_password, injected_files,
requested_networks, is_first_time, filter_properties)
requested_networks, is_first_time, filter_properties,
legacy_bdm_in_spec)
task_state = task_states.SCHEDULING
rescheduled = self._reschedule(context, request_spec,
@ -1461,7 +1464,7 @@ class ComputeManager(manager.SchedulerDependentManager):
def run_instance(self, context, instance, request_spec=None,
filter_properties=None, requested_networks=None,
injected_files=None, admin_password=None,
is_first_time=False, node=None):
is_first_time=False, node=None, legacy_bdm_in_spec=True):
if filter_properties is None:
filter_properties = {}
@ -1470,7 +1473,8 @@ class ComputeManager(manager.SchedulerDependentManager):
def do_run_instance():
self._run_instance(context, request_spec,
filter_properties, requested_networks, injected_files,
admin_password, is_first_time, node, instance)
admin_password, is_first_time, node, instance,
legacy_bdm_in_spec)
do_run_instance()
def _try_deallocate_network(self, context, instance,

View File

@ -191,6 +191,7 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
new-world instance objects
2.36 - Made pause_instance() and unpause_instance() take new-world
instance objects
2.37 - Added the leagacy_bdm_in_spec parameter to run_instance
'''
#
@ -590,15 +591,16 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
def run_instance(self, ctxt, instance, host, request_spec,
filter_properties, requested_networks,
injected_files, admin_password,
is_first_time, node=None):
is_first_time, node=None, legacy_bdm_in_spec=True):
instance_p = jsonutils.to_primitive(instance)
self.cast(ctxt, self.make_msg('run_instance', instance=instance_p,
request_spec=request_spec, filter_properties=filter_properties,
requested_networks=requested_networks,
injected_files=injected_files, admin_password=admin_password,
is_first_time=is_first_time, node=node),
is_first_time=is_first_time, node=node,
legacy_bdm_in_spec=legacy_bdm_in_spec),
topic=_compute_topic(self.topic, ctxt, host, None),
version='2.19')
version='2.37')
def set_admin_password(self, ctxt, instance, new_pass):
instance_p = jsonutils.to_primitive(instance)

View File

@ -358,14 +358,16 @@ class LocalComputeTaskAPI(object):
def build_instances(self, context, instances, image,
filter_properties, admin_password, injected_files,
requested_networks, security_groups, block_device_mapping):
requested_networks, security_groups, block_device_mapping,
legacy_bdm=True):
utils.spawn_n(self._manager.build_instances, context,
instances=instances, image=image,
filter_properties=filter_properties,
admin_password=admin_password, injected_files=injected_files,
requested_networks=requested_networks,
security_groups=security_groups,
block_device_mapping=block_device_mapping)
block_device_mapping=block_device_mapping,
legacy_bdm=legacy_bdm)
def unshelve_instance(self, context, instance):
utils.spawn_n(self._manager.unshelve_instance, context,
@ -430,14 +432,15 @@ class ComputeTaskAPI(object):
def build_instances(self, context, instances, image, filter_properties,
admin_password, injected_files, requested_networks,
security_groups, block_device_mapping):
security_groups, block_device_mapping, legacy_bdm=True):
self.conductor_compute_rpcapi.build_instances(context,
instances=instances, image=image,
filter_properties=filter_properties,
admin_password=admin_password, injected_files=injected_files,
requested_networks=requested_networks,
security_groups=security_groups,
block_device_mapping=block_device_mapping)
block_device_mapping=block_device_mapping,
legacy_bdm=legacy_bdm)
def unshelve_instance(self, context, instance):
self.conductor_compute_rpcapi.unshelve_instance(context,

View File

@ -581,7 +581,7 @@ class ComputeTaskManager(base.Base):
"""
RPC_API_NAMESPACE = 'compute_task'
RPC_API_VERSION = '1.4'
RPC_API_VERSION = '1.5'
def __init__(self):
super(ComputeTaskManager, self).__init__()
@ -672,7 +672,7 @@ class ComputeTaskManager(base.Base):
def build_instances(self, context, instances, image, filter_properties,
admin_password, injected_files, requested_networks,
security_groups, block_device_mapping):
security_groups, block_device_mapping, legacy_bdm=True):
request_spec = scheduler_utils.build_request_spec(context, image,
instances)
# NOTE(alaski): For compatibility until a new scheduler method is used.
@ -681,7 +681,8 @@ class ComputeTaskManager(base.Base):
self.scheduler_rpcapi.run_instance(context, request_spec=request_spec,
admin_password=admin_password, injected_files=injected_files,
requested_networks=requested_networks, is_first_time=True,
filter_properties=filter_properties)
filter_properties=filter_properties,
legacy_bdm_in_spec=legacy_bdm)
def _instance_update(self, context, instance_uuid, **kwargs):
(old_ref, instance_ref) = self.db.instance_update_and_get_original(

View File

@ -518,6 +518,7 @@ class ComputeTaskAPI(nova.openstack.common.rpc.proxy.RpcProxy):
1.2 - Added build_instances
1.3 - Added unshelve_instance
1.4 - Added reservations to migrate_server.
1.5 - Added the leagacy_bdm parameter to build_instances
"""
BASE_RPC_API_VERSION = '1.0'
@ -542,7 +543,7 @@ class ComputeTaskAPI(nova.openstack.common.rpc.proxy.RpcProxy):
def build_instances(self, context, instances, image, filter_properties,
admin_password, injected_files, requested_networks,
security_groups, block_device_mapping):
security_groups, block_device_mapping, legacy_bdm=True):
instances_p = [jsonutils.to_primitive(inst) for inst in instances]
image_p = jsonutils.to_primitive(image)
msg = self.make_msg('build_instances', instances=instances_p,
@ -550,8 +551,9 @@ class ComputeTaskAPI(nova.openstack.common.rpc.proxy.RpcProxy):
admin_password=admin_password, injected_files=injected_files,
requested_networks=requested_networks,
security_groups=security_groups,
block_device_mapping=block_device_mapping)
self.cast(context, msg, version='1.2')
block_device_mapping=block_device_mapping,
legacy_bdm=legacy_bdm)
self.cast(context, msg, version='1.5')
def unshelve_instance(self, context, instance):
msg = self.make_msg('unshelve_instance', instance=instance)

View File

@ -92,7 +92,7 @@ class ChanceScheduler(driver.Scheduler):
def schedule_run_instance(self, context, request_spec,
admin_password, injected_files,
requested_networks, is_first_time,
filter_properties):
filter_properties, legacy_bdm_in_spec):
"""Create and run an instance or instances."""
instance_uuids = request_spec.get('instance_uuids')
for num, instance_uuid in enumerate(instance_uuids):
@ -109,7 +109,8 @@ class ChanceScheduler(driver.Scheduler):
admin_password=admin_password,
is_first_time=is_first_time,
request_spec=request_spec,
filter_properties=filter_properties)
filter_properties=filter_properties,
legacy_bdm_in_spec=legacy_bdm_in_spec)
except Exception as ex:
# NOTE(vish): we don't reraise the exception here to make sure
# that all instances in the request get set to

View File

@ -147,7 +147,7 @@ class Scheduler(object):
def schedule_run_instance(self, context, request_spec,
admin_password, injected_files,
requested_networks, is_first_time,
filter_properties):
filter_properties, legacy_bdm_in_spec):
"""Must override schedule_run_instance method for scheduler to work."""
msg = _("Driver must implement schedule_run_instance")
raise NotImplementedError(msg)

View File

@ -64,7 +64,7 @@ class FilterScheduler(driver.Scheduler):
def schedule_run_instance(self, context, request_spec,
admin_password, injected_files,
requested_networks, is_first_time,
filter_properties):
filter_properties, legacy_bdm_in_spec):
"""This method is called from nova.compute.api to provision
an instance. We first create a build plan (a list of WeightedHosts)
and then provision.
@ -113,7 +113,8 @@ class FilterScheduler(driver.Scheduler):
requested_networks,
injected_files, admin_password,
is_first_time,
instance_uuid=instance_uuid)
instance_uuid=instance_uuid,
legacy_bdm_in_spec=legacy_bdm_in_spec)
except Exception as ex:
# NOTE(vish): we don't reraise the exception here to make sure
# that all instances in the request get set to
@ -154,7 +155,8 @@ class FilterScheduler(driver.Scheduler):
def _provision_resource(self, context, weighed_host, request_spec,
filter_properties, requested_networks, injected_files,
admin_password, is_first_time, instance_uuid=None):
admin_password, is_first_time, instance_uuid=None,
legacy_bdm_in_spec=True):
"""Create the requested resource in this Zone."""
# NOTE(vish): add our current instance back into the request spec
request_spec['instance_uuids'] = [instance_uuid]
@ -194,7 +196,8 @@ class FilterScheduler(driver.Scheduler):
requested_networks=requested_networks,
injected_files=injected_files,
admin_password=admin_password, is_first_time=is_first_time,
node=weighed_host.obj.nodename)
node=weighed_host.obj.nodename,
legacy_bdm_in_spec=legacy_bdm_in_spec)
def _get_configuration_options(self):
"""Fetch options dictionary. Broken out for testing."""

View File

@ -57,7 +57,7 @@ QUOTAS = quota.QUOTAS
class SchedulerManager(manager.Manager):
"""Chooses a host to run instances on."""
RPC_API_VERSION = '2.8'
RPC_API_VERSION = '2.9'
def __init__(self, scheduler_driver=None, *args, **kwargs):
if not scheduler_driver:
@ -138,7 +138,7 @@ class SchedulerManager(manager.Manager):
def run_instance(self, context, request_spec, admin_password,
injected_files, requested_networks, is_first_time,
filter_properties):
filter_properties, legacy_bdm_in_spec=True):
"""Tries to call schedule_run_instance on the driver.
Sets instance vm_state to ERROR on exceptions
"""
@ -148,7 +148,8 @@ class SchedulerManager(manager.Manager):
try:
return self.driver.schedule_run_instance(context,
request_spec, admin_password, injected_files,
requested_networks, is_first_time, filter_properties)
requested_networks, is_first_time, filter_properties,
legacy_bdm_in_spec)
except exception.NoValidHost as ex:
# don't re-raise

View File

@ -69,6 +69,7 @@ class SchedulerAPI(nova.openstack.common.rpc.proxy.RpcProxy):
2.7 - Add select_destinations()
2.8 - Deprecate prep_resize()
2.9 - Added the leagacy_bdm_in_spec parameter to run_instances
'''
#
@ -99,13 +100,14 @@ class SchedulerAPI(nova.openstack.common.rpc.proxy.RpcProxy):
def run_instance(self, ctxt, request_spec, admin_password,
injected_files, requested_networks, is_first_time,
filter_properties):
filter_properties, legacy_bdm_in_spec=True):
return self.cast(ctxt, self.make_msg('run_instance',
request_spec=request_spec, admin_password=admin_password,
injected_files=injected_files,
requested_networks=requested_networks,
is_first_time=is_first_time,
filter_properties=filter_properties))
filter_properties=filter_properties,
legacy_bdm_in_spec=legacy_bdm_in_spec), version='2.9')
# NOTE(timello): This method is deprecated and it's functionality has
# been moved to conductor. This should be removed in RPC_API_VERSION 3.0.

View File

@ -8602,12 +8602,12 @@ class ComputeRescheduleOrErrorTestCase(BaseTestCase):
[], mox.IgnoreArg(), [], None, set_access_ip=False).AndRaise(
test.TestingException("BuildError"))
self.compute._reschedule_or_error(mox.IgnoreArg(), self.instance,
mox.IgnoreArg(), None, None, None, False, None, {}, []).\
AndReturn(True)
mox.IgnoreArg(), None, None, None,
False, None, {}, [], False).AndReturn(True)
self.mox.ReplayAll()
self.compute._run_instance(self.context, None, {}, None, None, None,
False, None, self.instance)
False, None, self.instance, False)
def test_shutdown_instance_fail(self):
"""Test shutdown instance failing before re-scheduling logic can even
@ -8747,7 +8747,7 @@ class ComputeRescheduleOrErrorTestCase(BaseTestCase):
# test succeeds if mocked method '_reschedule_or_error' is not
# called.
self.compute._run_instance(self.context, None, {}, None, None, None,
False, None, self.instance)
False, None, self.instance, False)
def test_no_reschedule_on_unexpected_task_state(self):
# instance shouldn't be rescheduled if unexpected task state arises.
@ -8764,7 +8764,7 @@ class ComputeRescheduleOrErrorTestCase(BaseTestCase):
self.mox.ReplayAll()
self.assertRaises(exception.UnexpectedTaskStateError,
self.compute._run_instance, self.context, None, {}, None, None,
None, False, None, self.instance)
None, False, None, self.instance, False)
class ComputeRescheduleResizeOrReraiseTestCase(BaseTestCase):
@ -9132,7 +9132,7 @@ class ComputeInjectedFilesTestCase(BaseTestCase):
def _roe(context, instance, exc_info, requested_networks,
admin_password, injected_files, is_first_time, request_spec,
filter_properties, bdms=None):
filter_properties, bdms=None, legacy_bdm_in_spec=False):
self.assertEqual(expected, injected_files)
return True

View File

@ -344,7 +344,7 @@ class ComputeRpcAPITestCase(test.TestCase):
request_spec='fake_spec', filter_properties={},
requested_networks='networks', injected_files='files',
admin_password='pw', is_first_time=True, node='node',
version='2.19')
legacy_bdm_in_spec=False, version='2.37')
def test_set_admin_password(self):
self._test_compute_api('set_admin_password', 'call',

View File

@ -1304,7 +1304,7 @@ class _BaseTaskTestCase(object):
admin_password='admin_password',
injected_files='injected_files',
requested_networks='requested_networks', is_first_time=True,
filter_properties={})
filter_properties={}, legacy_bdm_in_spec=False)
self.mox.ReplayAll()
self.conductor.build_instances(self.context,
instances=[{'uuid': 'fakeuuid',
@ -1316,7 +1316,8 @@ class _BaseTaskTestCase(object):
injected_files='injected_files',
requested_networks='requested_networks',
security_groups='security_groups',
block_device_mapping='block_device_mapping')
block_device_mapping='block_device_mapping',
legacy_bdm=False)
def test_unshelve_instance_on_host(self):
db_instance = jsonutils.to_primitive(self._create_fake_instance())

View File

@ -94,7 +94,8 @@ class ChanceSchedulerTestCase(test_scheduler.SchedulerTestCase):
compute_rpcapi.ComputeAPI.run_instance(ctxt, host='host3',
instance=instance1, requested_networks=None,
injected_files=None, admin_password=None, is_first_time=None,
request_spec=request_spec, filter_properties={})
request_spec=request_spec, filter_properties={},
legacy_bdm_in_spec=False)
# instance 2
ctxt.elevated().AndReturn(ctxt_elevated)
@ -105,11 +106,12 @@ class ChanceSchedulerTestCase(test_scheduler.SchedulerTestCase):
compute_rpcapi.ComputeAPI.run_instance(ctxt, host='host1',
instance=instance2, requested_networks=None,
injected_files=None, admin_password=None, is_first_time=None,
request_spec=request_spec, filter_properties={})
request_spec=request_spec, filter_properties={},
legacy_bdm_in_spec=False)
self.mox.ReplayAll()
self.driver.schedule_run_instance(ctxt, request_spec,
None, None, None, None, {})
None, None, None, None, {}, False)
def test_basic_schedule_run_instance_no_hosts(self):
ctxt = context.RequestContext('fake', 'fake', False)
@ -136,7 +138,7 @@ class ChanceSchedulerTestCase(test_scheduler.SchedulerTestCase):
self.mox.ReplayAll()
self.driver.schedule_run_instance(
ctxt, request_spec, None, None, None, None, {})
ctxt, request_spec, None, None, None, None, {}, False)
def test_select_hosts(self):
ctxt = context.RequestContext('fake', 'fake', False)

View File

@ -82,7 +82,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
self.mox.ReplayAll()
sched.schedule_run_instance(
fake_context, request_spec, None, None, None, None, {})
fake_context, request_spec, None, None,
None, None, {}, False)
def test_run_instance_non_admin(self):
self.was_admin = False
@ -113,7 +114,7 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
mox.IsA(exception.NoValidHost), mox.IgnoreArg())
self.mox.ReplayAll()
sched.schedule_run_instance(
fake_context, request_spec, None, None, None, None, {})
fake_context, request_spec, None, None, None, None, {}, False)
self.assertTrue(self.was_admin)
def test_scheduler_includes_launch_index(self):
@ -145,17 +146,19 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
fake_context, 'host1',
mox.Func(_has_launch_index(0)), {},
None, None, None, None,
instance_uuid='fake-uuid1').AndReturn(instance1)
instance_uuid='fake-uuid1',
legacy_bdm_in_spec=False).AndReturn(instance1)
# instance 2
self.driver._provision_resource(
fake_context, 'host2',
mox.Func(_has_launch_index(1)), {},
None, None, None, None,
instance_uuid='fake-uuid2').AndReturn(instance2)
instance_uuid='fake-uuid2',
legacy_bdm_in_spec=False).AndReturn(instance2)
self.mox.ReplayAll()
self.driver.schedule_run_instance(fake_context, request_spec,
None, None, None, None, {})
None, None, None, None, {}, False)
def test_schedule_happy_day(self):
"""Make sure there's nothing glaringly wrong with _schedule()
@ -285,7 +288,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
self.context, request_spec, admin_password=None,
injected_files=None, requested_networks=None,
is_first_time=False,
filter_properties=filter_properties)
filter_properties=filter_properties,
legacy_bdm_in_spec=False)
uuids = request_spec.get('instance_uuids')
self.assertEqual(uuids, instance_uuids)
@ -368,7 +372,7 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
instance=instance1_1, requested_networks=None,
injected_files=None, admin_password=None, is_first_time=None,
request_spec=request_spec1, filter_properties=mox.IgnoreArg(),
node='node3')
node='node3', legacy_bdm_in_spec=False)
driver.instance_update_db(fake_context, instance1_2['uuid'],
extra_values=expected_metadata).WithSideEffects(
@ -377,10 +381,10 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase):
instance=instance1_2, requested_networks=None,
injected_files=None, admin_password=None, is_first_time=None,
request_spec=request_spec1, filter_properties=mox.IgnoreArg(),
node='node4')
node='node4', legacy_bdm_in_spec=False)
self.mox.ReplayAll()
sched.schedule_run_instance(fake_context, request_spec1,
None, None, None, None, filter_properties)
None, None, None, None, filter_properties, False)
def test_schedule_host_pool(self):
"""Make sure the scheduler_host_subset_size property works properly."""

View File

@ -60,7 +60,8 @@ class SchedulerRpcAPITestCase(test.NoDBTestCase):
request_spec='fake_request_spec',
admin_password='pw', injected_files='fake_injected_files',
requested_networks='fake_requested_networks',
is_first_time=True, filter_properties='fake_filter_properties')
is_first_time=True, filter_properties='fake_filter_properties',
legacy_bdm_in_spec=False, version='2.9')
def test_prep_resize(self):
self._test_scheduler_api('prep_resize', rpc_method='cast',

View File

@ -192,7 +192,7 @@ class SchedulerManagerTestCase(test.NoDBTestCase):
'instance_uuids': [fake_instance_uuid]}
self.manager.driver.schedule_run_instance(self.context,
request_spec, None, None, None, None, {}).AndRaise(
request_spec, None, None, None, None, {}, False).AndRaise(
exception.NoValidHost(reason=""))
old, new_ref = db.instance_update_and_get_original(self.context,
fake_instance_uuid,
@ -204,7 +204,7 @@ class SchedulerManagerTestCase(test.NoDBTestCase):
self.mox.ReplayAll()
self.manager.run_instance(self.context, request_spec,
None, None, None, None, {})
None, None, None, None, {}, False)
def test_live_migration_schedule_novalidhost(self):
inst = {"uuid": "fake-instance-id",
@ -586,7 +586,7 @@ class SchedulerDriverBaseTestCase(SchedulerTestCase):
self.assertRaises(NotImplementedError,
self.driver.schedule_run_instance,
self.context, fake_request_spec, None, None, None,
None, None)
None, None, False)
def test_unimplemented_select_hosts(self):
self.assertRaises(NotImplementedError,