summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArmando Migliaccio <armando.migliaccio@eu.citrix.com>2012-02-10 19:16:29 +0000
committerArmando Migliaccio <armando.migliaccio@eu.citrix.com>2012-02-21 22:50:26 +0000
commitb88e67c445e618ee7e515d9dd50238afc4f5229b (patch)
treec7cc9c06ddfa6f9e79412fa102a4a3ef9aa11446
parentadaf9049c8fb3652c0962909a3c835e1724d8a17 (diff)
downloadnova-b88e67c445e618ee7e515d9dd50238afc4f5229b.tar.gz
nova-b88e67c445e618ee7e515d9dd50238afc4f5229b.tar.xz
nova-b88e67c445e618ee7e515d9dd50238afc4f5229b.zip
blueprint host-aggregates: host maintenance
First cut at implementing host maintenance (aka host evacuation). This allows zero-downtime upgrades of the hosts by moving VMs off of to another host to carry out hypervisor upgrades. A number of issues have been addressed in this changeset: - improved the semantic of update operation on hosts (as per dabo comment) - refactored host-related operations into a separate class in to improve readability/maintainability - refactored test_hosts to reduce duplicated code - added first stub of host-maintenance operation Change-Id: I933f7cb8736e56c9ecea5255936d8826ef6decec
-rw-r--r--nova/api/openstack/compute/contrib/hosts.py38
-rw-r--r--nova/compute/__init__.py1
-rw-r--r--nova/compute/api.py271
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_hosts.py67
-rw-r--r--nova/tests/test_compute.py6
5 files changed, 204 insertions, 179 deletions
diff --git a/nova/api/openstack/compute/contrib/hosts.py b/nova/api/openstack/compute/contrib/hosts.py
index cbb5e6902..df19872d7 100644
--- a/nova/api/openstack/compute/contrib/hosts.py
+++ b/nova/api/openstack/compute/contrib/hosts.py
@@ -120,7 +120,7 @@ def check_host(fn):
class HostController(object):
"""The Hosts API controller for the OpenStack API."""
def __init__(self):
- self.compute_api = compute.API()
+ self.api = compute.HostAPI()
super(HostController, self).__init__()
@wsgi.serializers(xml=HostIndexTemplate)
@@ -133,31 +133,53 @@ class HostController(object):
@check_host
def update(self, req, id, body):
authorize(req.environ['nova.context'])
+ update_values = {}
for raw_key, raw_val in body.iteritems():
key = raw_key.lower().strip()
val = raw_val.lower().strip()
- # NOTE: (dabo) Right now only 'status' can be set, but other
- # settings may follow.
if key == "status":
if val[:6] in ("enable", "disabl"):
- enabled = val.startswith("enable")
+ update_values['status'] = val.startswith("enable")
else:
explanation = _("Invalid status: '%s'") % raw_val
raise webob.exc.HTTPBadRequest(explanation=explanation)
elif key == "maintenance_mode":
- raise webob.exc.HTTPNotImplemented
+ if val not in ['enable', 'disable']:
+ explanation = _("Invalid mode: '%s'") % raw_val
+ raise webob.exc.HTTPBadRequest(explanation=explanation)
+ update_values['maintenance_mode'] = val == 'enable'
else:
explanation = _("Invalid update setting: '%s'") % raw_key
raise webob.exc.HTTPBadRequest(explanation=explanation)
- return self._set_enabled_status(req, id, enabled=enabled)
+ # this is for handling multiple settings at the same time:
+ # the result dictionaries are merged in the first one.
+ # Note: the 'host' key will always be the same so it's
+ # okay that it gets overwritten.
+ update_setters = {'status': self._set_enabled_status,
+ 'maintenance_mode': self._set_host_maintenance}
+ result = {}
+ for key, value in update_values.iteritems():
+ result.update(update_setters[key](req, id, value))
+ return result
+
+ def _set_host_maintenance(self, req, host, mode=True):
+ """Start/Stop host maintenance window. On start, it triggers
+ guest VMs evacuation."""
+ context = req.environ['nova.context']
+ LOG.audit(_("Putting host %(host)s in maintenance "
+ "mode %(mode)s.") % locals())
+ result = self.api.set_host_maintenance(context, host, mode)
+ if result not in ("on_maintenance", "off_maintenance"):
+ raise webob.exc.HTTPBadRequest(explanation=result)
+ return {"host": host, "maintenance_mode": result}
def _set_enabled_status(self, req, host, enabled):
"""Sets the specified host's ability to accept new instances."""
context = req.environ['nova.context']
state = "enabled" if enabled else "disabled"
LOG.audit(_("Setting host %(host)s to %(state)s.") % locals())
- result = self.compute_api.set_host_enabled(context, host=host,
+ result = self.api.set_host_enabled(context, host=host,
enabled=enabled)
if result not in ("enabled", "disabled"):
# An error message was returned
@@ -169,7 +191,7 @@ class HostController(object):
context = req.environ['nova.context']
authorize(context)
try:
- result = self.compute_api.host_power_action(context, host=host,
+ result = self.api.host_power_action(context, host=host,
action=action)
except NotImplementedError as e:
raise webob.exc.HTTPBadRequest(explanation=e.msg)
diff --git a/nova/compute/__init__.py b/nova/compute/__init__.py
index 72df5ddd5..55d1e2439 100644
--- a/nova/compute/__init__.py
+++ b/nova/compute/__init__.py
@@ -17,6 +17,7 @@
# under the License.
from nova.compute.api import AggregateAPI
+from nova.compute.api import HostAPI
# Importing full names to not pollute the namespace and cause possible
# collisions with use of 'from nova.compute import <foo>' elsewhere.
import nova.flags
diff --git a/nova/compute/api.py b/nova/compute/api.py
index edadf1978..19aeffcde 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -773,10 +773,8 @@ class API(base.Base):
params = {"security_group_id": security_group['id']}
# NOTE(comstud): No instance_uuid argument to this compute manager
# call
- self._cast_compute_message('refresh_security_group_rules',
- context,
- host=instance['host'],
- params=params)
+ _cast_compute_message(self.db, 'refresh_security_group_rules', context,
+ host=instance['host'], params=params)
@wrap_check_policy
def remove_security_group(self, context, instance, security_group_name):
@@ -804,10 +802,8 @@ class API(base.Base):
params = {"security_group_id": security_group['id']}
# NOTE(comstud): No instance_uuid argument to this compute manager
# call
- self._cast_compute_message('refresh_security_group_rules',
- context,
- host=instance['host'],
- params=params)
+ _cast_compute_message(self.db, 'refresh_security_group_rules',
+ context, host=instance['host'], params=params)
@wrap_check_policy
def update(self, context, instance, **kwargs):
@@ -846,8 +842,8 @@ class API(base.Base):
task_state=task_states.POWERING_OFF,
deleted_at=utils.utcnow())
- self._cast_compute_message('power_off_instance', context,
- instance)
+ _cast_compute_message(self.db, 'power_off_instance', context,
+ instance)
else:
LOG.warning(_('No host for instance, deleting immediately'),
instance=instance)
@@ -866,12 +862,12 @@ class API(base.Base):
task_state=task_states.DELETING,
progress=0)
- self._cast_compute_message('terminate_instance', context,
- instance)
+ _cast_compute_message(self.db, 'terminate_instance', context,
+ instance)
else:
self.db.instance_destroy(context, instance['id'])
except exception.InstanceNotFound:
- # NOTE(comstud): Race condition. Instance already gone.
+ # NOTE(comstud): Race condition. Instance already gone.
pass
# NOTE(jerdfelt): The API implies that only ACTIVE and ERROR are
@@ -905,8 +901,8 @@ class API(base.Base):
self.update(context,
instance,
task_state=task_states.POWERING_ON)
- self._cast_compute_message('power_on_instance', context,
- instance)
+ _cast_compute_message(self.db, 'power_on_instance',
+ context, instance)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.SOFT_DELETE])
@@ -931,8 +927,8 @@ class API(base.Base):
progress=0)
rpc_method = rpc.cast if do_cast else rpc.call
- self._cast_or_call_compute_message(rpc_method, 'stop_instance',
- context, instance)
+ _cast_or_call_compute_message(self.db, rpc_method, 'stop_instance',
+ context, instance)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.STOPPED, vm_states.SHUTOFF])
@@ -960,7 +956,7 @@ class API(base.Base):
# TODO(yamahata): injected_files isn't supported right now.
# It is used only for osapi. not for ec2 api.
# availability_zone isn't used by run_instance.
- self._cast_compute_message('start_instance', context, instance)
+ _cast_compute_message(self.db, 'start_instance', context, instance)
#NOTE(bcwaldon): no policy check here since it should be rolled in to
# search_opts in get_all
@@ -1085,49 +1081,6 @@ class API(base.Base):
return self.db.instance_get_all_by_filters(context, filters)
- def _cast_or_call_compute_message(self, rpc_method, compute_method,
- context, instance=None, host=None, params=None):
- """Generic handler for RPC casts and calls to compute.
-
- :param rpc_method: RPC method to use (rpc.call or rpc.cast)
- :param compute_method: Compute manager method to call
- :param context: RequestContext of caller
- :param instance: The instance object to use to find host to send to
- Can be None to not include instance_uuid in args
- :param host: Optional host to send to instead of instance['host']
- Must be specified if 'instance' is None
- :param params: Optional dictionary of arguments to be passed to the
- compute worker
-
- :returns: None
- """
- if not params:
- params = {}
- if not host:
- if not instance:
- raise exception.Error(_("No compute host specified"))
- host = instance['host']
- if not host:
- raise exception.Error(_("Unable to find host for "
- "Instance %s") % instance['uuid'])
- queue = self.db.queue_get_for(context, FLAGS.compute_topic, host)
- if instance:
- params['instance_uuid'] = instance['uuid']
- kwargs = {'method': compute_method, 'args': params}
- return rpc_method(context, queue, kwargs)
-
- def _cast_compute_message(self, *args, **kwargs):
- """Generic handler for RPC casts to compute."""
- self._cast_or_call_compute_message(rpc.cast, *args, **kwargs)
-
- def _call_compute_message(self, *args, **kwargs):
- """Generic handler for RPC calls to compute."""
- return self._cast_or_call_compute_message(rpc.call, *args, **kwargs)
-
- def _cast_scheduler_message(self, context, args):
- """Generic handler for RPC calls to the scheduler."""
- rpc.cast(context, FLAGS.scheduler_topic, args)
-
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF],
task_state=[None, task_states.RESIZE_VERIFY])
@@ -1204,8 +1157,8 @@ class API(base.Base):
recv_meta = self.image_service.create(context, sent_meta)
params = {'image_id': recv_meta['id'], 'image_type': image_type,
'backup_type': backup_type, 'rotation': rotation}
- self._cast_compute_message('snapshot_instance', context,
- instance, params=params)
+ _cast_compute_message(self.db, 'snapshot_instance', context,
+ instance, params=params)
return recv_meta
def _get_minram_mindisk_params(self, context, instance):
@@ -1239,10 +1192,8 @@ class API(base.Base):
instance,
vm_state=vm_states.ACTIVE,
task_state=state)
- self._cast_compute_message('reboot_instance',
- context,
- instance,
- params={'reboot_type': reboot_type})
+ _cast_compute_message(self.db, 'reboot_instance', context,
+ instance, params={'reboot_type': reboot_type})
def _validate_image_href(self, context, image_href):
"""Throws an ImageNotFound exception if image_href does not exist."""
@@ -1277,10 +1228,8 @@ class API(base.Base):
"injected_files": files_to_inject,
}
- self._cast_compute_message('rebuild_instance',
- context,
- instance,
- params=rebuild_params)
+ _cast_compute_message(self.db, 'rebuild_instance', context,
+ instance, params=rebuild_params)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF],
@@ -1300,10 +1249,9 @@ class API(base.Base):
task_state=task_states.RESIZE_REVERTING)
params = {'migration_id': migration_ref['id']}
- self._cast_compute_message('revert_resize', context,
- instance,
- host=migration_ref['dest_compute'],
- params=params)
+ _cast_compute_message(self.db, 'revert_resize', context, instance,
+ host=migration_ref['dest_compute'],
+ params=params)
self.db.migration_update(context, migration_ref['id'],
{'status': 'reverted'})
@@ -1326,10 +1274,9 @@ class API(base.Base):
task_state=None)
params = {'migration_id': migration_ref['id']}
- self._cast_compute_message('confirm_resize', context,
- instance,
- host=migration_ref['source_compute'],
- params=params)
+ _cast_compute_message(self.db, 'confirm_resize', context, instance,
+ host=migration_ref['source_compute'],
+ params=params)
self.db.migration_update(context, migration_ref['id'],
{'status': 'confirmed'})
@@ -1393,24 +1340,21 @@ class API(base.Base):
"request_spec": utils.to_primitive(request_spec),
"filter_properties": filter_properties,
}
- self._cast_scheduler_message(context, {"method": "prep_resize",
- "args": args})
+ _cast_scheduler_message(context,
+ {"method": "prep_resize",
+ "args": args})
@wrap_check_policy
def add_fixed_ip(self, context, instance, network_id):
"""Add fixed_ip from specified network to given instance."""
- self._cast_compute_message('add_fixed_ip_to_instance',
- context,
- instance,
- params=dict(network_id=network_id))
+ _cast_compute_message(self.db, 'add_fixed_ip_to_instance', context,
+ instance, params=dict(network_id=network_id))
@wrap_check_policy
def remove_fixed_ip(self, context, instance, address):
"""Remove fixed_ip from specified network to given instance."""
- self._cast_compute_message('remove_fixed_ip_from_instance',
- context,
- instance,
- params=dict(address=address))
+ _cast_compute_message(self.db, 'remove_fixed_ip_from_instance',
+ context, instance, params=dict(address=address))
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF,
@@ -1422,7 +1366,7 @@ class API(base.Base):
instance,
vm_state=vm_states.ACTIVE,
task_state=task_states.PAUSING)
- self._cast_compute_message('pause_instance', context, instance)
+ _cast_compute_message(self.db, 'pause_instance', context, instance)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.PAUSED])
@@ -1432,28 +1376,13 @@ class API(base.Base):
instance,
vm_state=vm_states.PAUSED,
task_state=task_states.UNPAUSING)
- self._cast_compute_message('unpause_instance', context, instance)
-
- def set_host_enabled(self, context, host, enabled):
- """Sets the specified host's ability to accept new instances."""
- # NOTE(comstud): No instance_uuid argument to this compute manager
- # call
- return self._call_compute_message("set_host_enabled", context,
- host=host, params={"enabled": enabled})
-
- def host_power_action(self, context, host, action):
- """Reboots, shuts down or powers up the host."""
- # NOTE(comstud): No instance_uuid argument to this compute manager
- # call
- return self._call_compute_message("host_power_action", context,
- host=host, params={"action": action})
+ _cast_compute_message(self.db, 'unpause_instance', context, instance)
@wrap_check_policy
def get_diagnostics(self, context, instance):
"""Retrieve diagnostics for the given instance."""
- return self._call_compute_message("get_diagnostics",
- context,
- instance)
+ return _call_compute_message(self.db, "get_diagnostics", context,
+ instance)
@wrap_check_policy
def get_actions(self, context, instance):
@@ -1470,7 +1399,7 @@ class API(base.Base):
instance,
vm_state=vm_states.ACTIVE,
task_state=task_states.SUSPENDING)
- self._cast_compute_message('suspend_instance', context, instance)
+ _cast_compute_message(self.db, 'suspend_instance', context, instance)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.SUSPENDED])
@@ -1480,7 +1409,7 @@ class API(base.Base):
instance,
vm_state=vm_states.SUSPENDED,
task_state=task_states.RESUMING)
- self._cast_compute_message('resume_instance', context, instance)
+ _cast_compute_message(self.db, 'resume_instance', context, instance)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.SHUTOFF,
@@ -1496,9 +1425,8 @@ class API(base.Base):
rescue_params = {
"rescue_password": rescue_password
}
- self._cast_compute_message('rescue_instance', context,
- instance,
- params=rescue_params)
+ _cast_compute_message(self.db, 'rescue_instance', context,
+ instance, params=rescue_params)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.RESCUED])
@@ -1508,8 +1436,7 @@ class API(base.Base):
instance,
vm_state=vm_states.RESCUED,
task_state=task_states.UNRESCUING)
- self._cast_compute_message('unrescue_instance', context,
- instance)
+ _cast_compute_message(self.db, 'unrescue_instance', context, instance)
@wrap_check_policy
@check_instance_state(vm_state=[vm_states.ACTIVE])
@@ -1520,24 +1447,22 @@ class API(base.Base):
task_state=task_states.UPDATING_PASSWORD)
params = {"new_pass": password}
- self._cast_compute_message('set_admin_password', context,
- instance,
- params=params)
+ _cast_compute_message(self.db, 'set_admin_password', context,
+ instance, params=params)
@wrap_check_policy
def inject_file(self, context, instance, path, file_contents):
"""Write a file to the given instance."""
params = {'path': path, 'file_contents': file_contents}
- self._cast_compute_message('inject_file', context,
+ _cast_compute_message(self.db, 'inject_file', context,
instance, params=params)
@wrap_check_policy
def get_vnc_console(self, context, instance, console_type):
"""Get a url to an instance Console."""
- connect_info = self._call_compute_message('get_vnc_console',
- context,
- instance,
- params={"console_type": console_type})
+ connect_info = _call_compute_message(self.db, 'get_vnc_console',
+ context, instance,
+ params={"console_type": console_type})
rpc.call(context, '%s' % FLAGS.consoleauth_topic,
{'method': 'authorize_console',
@@ -1554,22 +1479,18 @@ class API(base.Base):
def get_console_output(self, context, instance, tail_length=None):
"""Get console output for an an instance."""
params = {'tail_length': tail_length}
- return self._call_compute_message('get_console_output',
- context,
- instance,
- params=params)
+ return _call_compute_message(self.db, 'get_console_output', context,
+ instance, params=params)
@wrap_check_policy
def lock(self, context, instance):
"""Lock the given instance."""
- self._cast_compute_message('lock_instance', context, instance)
+ _cast_compute_message(self.db, 'lock_instance', context, instance)
@wrap_check_policy
def unlock(self, context, instance):
"""Unlock the given instance."""
- self._cast_compute_message('unlock_instance',
- context,
- instance)
+ _cast_compute_message(self.db, 'unlock_instance', context, instance)
@wrap_check_policy
def get_lock(self, context, instance):
@@ -1579,13 +1500,13 @@ class API(base.Base):
@wrap_check_policy
def reset_network(self, context, instance):
"""Reset networking on the instance."""
- self._cast_compute_message('reset_network', context, instance)
+ _cast_compute_message(self.db, 'reset_network', context, instance)
@wrap_check_policy
def inject_network_info(self, context, instance):
"""Inject network info for the instance."""
- self._cast_compute_message('inject_network_info', context,
- instance)
+ _cast_compute_message(self.db, 'inject_network_info',
+ context, instance)
@wrap_check_policy
def attach_volume(self, context, instance, volume_id, device):
@@ -1596,9 +1517,8 @@ class API(base.Base):
self.volume_api.check_attach(context, volume)
params = {"volume_id": volume_id,
"mountpoint": device}
- self._cast_compute_message('attach_volume', context,
- instance,
- params=params)
+ _cast_compute_message(self.db, 'attach_volume', context, instance,
+ params=params)
# FIXME(comstud): I wonder if API should pull in the instance from
# the volume ID via volume API and pass it and the volume object here
@@ -1614,9 +1534,8 @@ class API(base.Base):
self.volume_api.check_detach(context, volume)
params = {'volume_id': volume_id}
- self._cast_compute_message('detach_volume', context,
- instance,
- params=params)
+ _cast_compute_message(self.db, 'detach_volume', context, instance,
+ params=params)
return instance
@wrap_check_policy
@@ -1699,6 +1618,31 @@ class API(base.Base):
return self.db.instance_fault_get_by_instance_uuids(context, uuids)
+class HostAPI(base.Base):
+ """Sub-set of the Compute Manager API for managing host operations."""
+ def __init__(self, **kwargs):
+ super(HostAPI, self).__init__(**kwargs)
+
+ def set_host_enabled(self, context, host, enabled):
+ """Sets the specified host's ability to accept new instances."""
+ # NOTE(comstud): No instance_uuid argument to this compute manager
+ # call
+ return _call_compute_message(self.db, "set_host_enabled", context,
+ host=host, params={"enabled": enabled})
+
+ def host_power_action(self, context, host, action):
+ """Reboots, shuts down or powers up the host."""
+ # NOTE(comstud): No instance_uuid argument to this compute manager
+ # call
+ return _call_compute_message(self.db, "host_power_action", context,
+ host=host, params={"action": action})
+
+ def set_host_maintenance(self, context, host, mode):
+ """Start/Stop host maintenance window. On start, it triggers
+ guest VMs evacuation."""
+ raise NotImplementedError()
+
+
class AggregateAPI(base.Base):
"""Sub-set of the Compute Manager API for managing host aggregates."""
def __init__(self, **kwargs):
@@ -1823,3 +1767,50 @@ class AggregateAPI(base.Base):
result["metadata"] = metadata
result["hosts"] = hosts
return result
+
+
+def _cast_or_call_compute_message(db, rpc_method, compute_method,
+ context, instance=None, host=None, params=None):
+ """Generic handler for RPC casts and calls to compute.
+
+ :param rpc_method: RPC method to use (rpc.call or rpc.cast)
+ :param compute_method: Compute manager method to call
+ :param context: RequestContext of caller
+ :param instance: The instance object to use to find host to send to
+ Can be None to not include instance_uuid in args
+ :param host: Optional host to send to instead of instance['host']
+ Must be specified if 'instance' is None
+ :param params: Optional dictionary of arguments to be passed to the
+ compute worker
+
+ :returns: None
+ """
+ if not params:
+ params = {}
+ if not host:
+ if not instance:
+ raise exception.Error(_("No compute host specified"))
+ host = instance['host']
+ if not host:
+ raise exception.Error(_("Unable to find host for "
+ "Instance %s") % instance['uuid'])
+ queue = db.queue_get_for(context, FLAGS.compute_topic, host)
+ if instance:
+ params['instance_uuid'] = instance['uuid']
+ kwargs = {'method': compute_method, 'args': params}
+ return rpc_method(context, queue, kwargs)
+
+
+def _cast_compute_message(db, *args, **kwargs):
+ """Generic handler for RPC casts to compute."""
+ _cast_or_call_compute_message(db, rpc.cast, *args, **kwargs)
+
+
+def _call_compute_message(db, *args, **kwargs):
+ """Generic handler for RPC calls to compute."""
+ return _cast_or_call_compute_message(db, rpc.call, *args, **kwargs)
+
+
+def _cast_scheduler_message(context, args):
+ """Generic handler for RPC calls to the scheduler."""
+ rpc.cast(context, FLAGS.scheduler_topic, args)
diff --git a/nova/tests/api/openstack/compute/contrib/test_hosts.py b/nova/tests/api/openstack/compute/contrib/test_hosts.py
index 213239846..5c882293a 100644
--- a/nova/tests/api/openstack/compute/contrib/test_hosts.py
+++ b/nova/tests/api/openstack/compute/contrib/test_hosts.py
@@ -47,10 +47,19 @@ def stub_set_host_enabled(context, host, enabled):
# that 'host_c1' always succeeds, and 'host_c2'
# always fails
fail = (host == "host_c2")
- status = "enabled" if (enabled ^ fail) else "disabled"
+ status = "enabled" if (enabled != fail) else "disabled"
return status
+def stub_set_host_maintenance(context, host, mode):
+ # We'll simulate success and failure by assuming
+ # that 'host_c1' always succeeds, and 'host_c2'
+ # always fails
+ fail = (host == "host_c2")
+ maintenance = "on_maintenance" if (mode != fail) else "off_maintenance"
+ return maintenance
+
+
def stub_host_power_action(context, host, action):
return action
@@ -96,10 +105,17 @@ class HostTestCase(test.TestCase):
self.controller = os_hosts.HostController()
self.req = FakeRequest()
self.stubs.Set(scheduler_api, 'get_host_list', stub_get_host_list)
- self.stubs.Set(self.controller.compute_api, 'set_host_enabled',
- stub_set_host_enabled)
- self.stubs.Set(self.controller.compute_api, 'host_power_action',
- stub_host_power_action)
+ self.stubs.Set(self.controller.api, 'set_host_enabled',
+ stub_set_host_enabled)
+ self.stubs.Set(self.controller.api, 'set_host_maintenance',
+ stub_set_host_maintenance)
+ self.stubs.Set(self.controller.api, 'host_power_action',
+ stub_host_power_action)
+
+ def _test_host_update(self, host, key, val, expected_value):
+ body = {key: val}
+ result = self.controller.update(self.req, host, body=body)
+ self.assertEqual(result[key], expected_value)
def test_list_hosts(self):
"""Verify that the compute hosts are returned."""
@@ -112,30 +128,20 @@ class HostTestCase(test.TestCase):
self.assertEqual(compute_hosts, expected)
def test_disable_host(self):
- dis_body = {"status": "disable"}
- result_c1 = self.controller.update(self.req, "host_c1", body=dis_body)
- self.assertEqual(result_c1["status"], "disabled")
- result_c2 = self.controller.update(self.req, "host_c2", body=dis_body)
- self.assertEqual(result_c2["status"], "enabled")
+ self._test_host_update('host_c1', 'status', 'disable', 'disabled')
+ self._test_host_update('host_c2', 'status', 'disable', 'enabled')
def test_enable_host(self):
- en_body = {"status": "enable"}
- result_c1 = self.controller.update(self.req, "host_c1", body=en_body)
- self.assertEqual(result_c1["status"], "enabled")
- result_c2 = self.controller.update(self.req, "host_c2", body=en_body)
- self.assertEqual(result_c2["status"], "disabled")
-
- def test_enable_maintainance_mode(self):
- body = {"maintenance_mode": "enable"}
- self.assertRaises(webob.exc.HTTPNotImplemented,
- self.controller.update,
- self.req, "host_c1", body=body)
-
- def test_disable_maintainance_mode_and_enable(self):
- body = {"status": "enable", "maintenance_mode": "disable"}
- self.assertRaises(webob.exc.HTTPNotImplemented,
- self.controller.update,
- self.req, "host_c1", body=body)
+ self._test_host_update('host_c1', 'status', 'enable', 'enabled')
+ self._test_host_update('host_c2', 'status', 'enable', 'disabled')
+
+ def test_enable_maintenance(self):
+ self._test_host_update('host_c1', 'maintenance_mode',
+ 'enable', 'on_maintenance')
+
+ def test_disable_maintenance(self):
+ self._test_host_update('host_c1', 'maintenance_mode',
+ 'disable', 'off_maintenance')
def test_host_startup(self):
result = self.controller.startup(self.req, "host_c1")
@@ -164,6 +170,13 @@ class HostTestCase(test.TestCase):
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
self.req, "host_c1", body=bad_body)
+ def test_good_udpate_keys(self):
+ body = {"status": "disable", "maintenance_mode": "enable"}
+ result = self.controller.update(self.req, 'host_c1', body=body)
+ self.assertEqual(result["host"], "host_c1")
+ self.assertEqual(result["status"], "disabled")
+ self.assertEqual(result["maintenance_mode"], "on_maintenance")
+
def test_bad_host(self):
self.assertRaises(exception.HostNotFound, self.controller.update,
self.req, "bogus_host_name", body={"status": "disable"})
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index d6d54227f..34162bb23 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -2369,8 +2369,7 @@ class ComputeAPITestCase(BaseTestCase):
self.assertEqual(instance_properties['host'], 'host2')
self.assertIn('host2', filter_properties['ignore_hosts'])
- self.stubs.Set(self.compute_api, '_cast_scheduler_message',
- _fake_cast)
+ self.stubs.Set(compute.api, '_cast_scheduler_message', _fake_cast)
context = self.context.elevated()
instance = self._create_fake_instance(dict(host='host2'))
@@ -2389,8 +2388,7 @@ class ComputeAPITestCase(BaseTestCase):
self.assertEqual(instance_properties['host'], 'host2')
self.assertNotIn('host2', filter_properties['ignore_hosts'])
- self.stubs.Set(self.compute_api, '_cast_scheduler_message',
- _fake_cast)
+ self.stubs.Set(compute.api, '_cast_scheduler_message', _fake_cast)
self.flags(allow_resize_to_same_host=True)
context = self.context.elevated()