Move logic from os-api-host into compute

Renames host to host_name in various places to make it explicit what it
is the APIs are accepting and what we are passing around.

Moves logic out of the openstack api's host extension to the compute
library. This is in preparation for cells; cells provides its own
compute library that proxies certain calls to child cells.

In an effort to separate out the layers of code, we're moving all DB
related logic out of the openstack api layer and into compute library
layer.

For example @check_host which calls the DB should not be in the openstack
api layer. In the case of cells, we can't check for the existence of the
host until we arrive at the cell that contains the host.

Implements: blueprint host-api-prep-for-cells
Change-Id: Icb861fd323ba707ab3b661f0a2c6d8bb7bfdc85e
This commit is contained in:
Matthew Sherborne 2013-01-08 13:05:16 +10:00
parent b35f866511
commit 745335bc3c
9 changed files with 506 additions and 237 deletions

View File

@ -22,9 +22,7 @@ from xml.parsers import expat
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova.api.openstack import xmlutil
from nova import availability_zones
from nova.compute import api as compute_api
from nova import db
from nova import compute
from nova import exception
from nova.openstack.common import log as logging
@ -94,140 +92,162 @@ class HostUpdateDeserializer(wsgi.XMLDeserializer):
return dict(body=updates)
def _list_hosts(req):
"""Returns a summary list of hosts, optionally filtering
by service type.
"""
context = req.environ['nova.context']
services = db.service_get_all(context, False)
services = availability_zones.set_availability_zones(context, services)
zone = ''
if 'zone' in req.GET:
zone = req.GET['zone']
if zone:
services = [s for s in services if s['availability_zone'] == zone]
hosts = []
for host in services:
hosts.append({"host_name": host['host'], 'service': host['topic'],
'zone': host['availability_zone']})
return hosts
def check_host(fn):
"""Makes sure that the host exists."""
def wrapped(self, req, id, *args, **kwargs):
listed_hosts = _list_hosts(req)
hosts = [h["host_name"] for h in listed_hosts]
if id in hosts:
return fn(self, req, id, *args, **kwargs)
else:
message = _("Host '%s' could not be found.") % id
raise webob.exc.HTTPNotFound(explanation=message)
return wrapped
class HostController(object):
"""The Hosts API controller for the OpenStack API."""
def __init__(self):
self.api = compute_api.HostAPI()
self.api = compute.HostAPI()
super(HostController, self).__init__()
@wsgi.serializers(xml=HostIndexTemplate)
def index(self, req):
authorize(req.environ['nova.context'])
return {'hosts': _list_hosts(req)}
"""
:returns: A dict in the format:
{'hosts': [{'host_name': 'some.host.name',
'service': 'cells'},
{'host_name': 'some.other.host.name',
'service': 'cells'},
{'host_name': 'some.celly.host.name',
'service': 'cells'},
{'host_name': 'console1.host.com',
'service': 'consoleauth'},
{'host_name': 'network1.host.com',
'service': 'network'},
{'host_name': 'netwwork2.host.com',
'service': 'network'},
{'host_name': 'sched1.host.com',
'service': 'scheduler'},
{'host_name': 'sched2.host.com',
'service': 'scheduler'},
{'host_name': 'vol1.host.com',
'service': 'volume'}]}
"""
context = req.environ['nova.context']
authorize(context)
zone = req.GET.get('zone', None)
data = self.api.list_hosts(context, zone)
return {'hosts': data}
@wsgi.serializers(xml=HostUpdateTemplate)
@wsgi.deserializers(xml=HostUpdateDeserializer)
@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()
if key == "status":
if val in ("enable", "disable"):
update_values['status'] = val.startswith("enable")
else:
explanation = _("Invalid status: '%s'") % raw_val
raise webob.exc.HTTPBadRequest(explanation=explanation)
elif key == "maintenance_mode":
if val not in ['enable', 'disable']:
explanation = _("Invalid mode: '%s'") % raw_val
raise webob.exc.HTTPBadRequest(explanation=explanation)
update_values['maintenance_mode'] = val == 'enable'
"""
:param body: example format {'status': 'enable',
'maintenance_mode': 'enable'}
:returns:
"""
def read_enabled(orig_val, msg):
"""
:param orig_val: A string with either 'enable' or 'disable'. May
be surrounded by whitespace, and case doesn't
matter
:param msg: The message to be passed to HTTPBadRequest. A single
%s will be replaced with orig_val.
:returns: True for 'enabled' and False for 'disabled'
"""
val = orig_val.strip().lower()
if val == "enable":
return True
elif val == "disable":
return False
else:
explanation = _("Invalid update setting: '%s'") % raw_key
raise webob.exc.HTTPBadRequest(explanation=explanation)
# 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))
raise webob.exc.HTTPBadRequest(explanation=msg % orig_val)
context = req.environ['nova.context']
authorize(context)
# See what the user wants to 'update'
params = dict([(k.strip().lower(), v) for k, v in body.iteritems()])
orig_status = status = params.pop('status', None)
orig_maint_mode = maint_mode = params.pop('maintenance_mode', None)
# Validate the request
if len(params) > 0:
# Some extra param was passed. Fail.
explanation = _("Invalid update setting: '%s'") % params.keys()[0]
raise webob.exc.HTTPBadRequest(explanation=explanation)
if orig_status is not None:
status = read_enabled(orig_status, _("Invalid status: '%s'"))
if orig_maint_mode is not None:
maint_mode = read_enabled(orig_maint_mode, _("Invalid mode: '%s'"))
if status is None and maint_mode is None:
explanation = _("'status' or 'maintenance_mode' needed for "
"host update")
raise webob.exc.HTTPBadRequest(explanation=explanation)
# Make the calls and merge the results
result = {'host': id}
if status is not None:
result['status'] = self._set_enabled_status(context, id, status)
if maint_mode is not None:
result['maintenance_mode'] = self._set_host_maintenance(context,
id, maint_mode)
return result
def _set_host_maintenance(self, req, host, mode=True):
def _set_host_maintenance(self, context, host_name, 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 "
LOG.audit(_("Putting host %(host_name)s in maintenance "
"mode %(mode)s.") % locals())
try:
result = self.api.set_host_maintenance(context, host, mode)
result = self.api.set_host_maintenance(context, host_name, mode)
except NotImplementedError:
msg = _("Virt driver does not implement host maintenance mode.")
raise webob.exc.HTTPNotImplemented(explanation=msg)
return {"host": host, "maintenance_mode": result}
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.message)
if result not in ("on_maintenance", "off_maintenance"):
raise webob.exc.HTTPBadRequest(explanation=result)
return 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())
def _set_enabled_status(self, context, host_name, enabled):
"""Sets the specified host's ability to accept new instances.
:param enabled: a boolean - if False no new VMs will be able to start
on the host"""
if enabled:
LOG.audit(_("Enabling host %s.") % host_name)
else:
LOG.audit(_("Disabling host %s.") % host_name)
try:
result = self.api.set_host_enabled(context, host=host,
enabled=enabled)
result = self.api.set_host_enabled(context, host_name=host_name,
enabled=enabled)
except NotImplementedError:
msg = _("Virt driver does not implement host disabled status.")
raise webob.exc.HTTPNotImplemented(explanation=msg)
return {"host": host, "status": result}
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.message)
if result not in ("enabled", "disabled"):
raise webob.exc.HTTPBadRequest(explanation=result)
return result
def _host_power_action(self, req, host, action):
def _host_power_action(self, req, host_name, action):
"""Reboots, shuts down or powers up the host."""
context = req.environ['nova.context']
authorize(context)
try:
result = self.api.host_power_action(context, host=host,
result = self.api.host_power_action(context, host_name=host_name,
action=action)
except NotImplementedError:
msg = _("Virt driver does not implement host power management.")
raise webob.exc.HTTPNotImplemented(explanation=msg)
return {"host": host, "power_action": result}
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.message)
return {"host": host_name, "power_action": result}
@wsgi.serializers(xml=HostActionTemplate)
def startup(self, req, id):
return self._host_power_action(req, host=id, action="startup")
return self._host_power_action(req, host_name=id, action="startup")
@wsgi.serializers(xml=HostActionTemplate)
def shutdown(self, req, id):
return self._host_power_action(req, host=id, action="shutdown")
return self._host_power_action(req, host_name=id, action="shutdown")
@wsgi.serializers(xml=HostActionTemplate)
def reboot(self, req, id):
return self._host_power_action(req, host=id, action="reboot")
return self._host_power_action(req, host_name=id, action="reboot")
@wsgi.serializers(xml=HostShowTemplate)
def show(self, req, id):
"""Shows the physical/usage resource given by hosts.
:param context: security context
:param host: hostname
:param id: hostname
:returns: expected to use HostShowTemplate.
ex.::
@ -235,66 +255,15 @@ class HostController(object):
D: {'host': 'hostname','project': 'admin',
'cpu': 1, 'memory_mb': 2048, 'disk_gb': 30}
"""
host = id
context = req.environ['nova.context']
if not context.is_admin:
try:
data = self.api.describe_host(context, id)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.message)
except exception.AdminRequired:
msg = _("Describe-resource is admin only functionality")
raise webob.exc.HTTPForbidden(explanation=msg)
# Getting compute node info and related instances info
try:
compute_ref = db.service_get_all_compute_by_host(context, host)
compute_ref = compute_ref[0]
except exception.ComputeHostNotFound:
raise webob.exc.HTTPNotFound(explanation=_("Host not found"))
instance_refs = db.instance_get_all_by_host(context,
compute_ref['host'])
# Getting total available/used resource
compute_ref = compute_ref['compute_node'][0]
resources = [{'resource': {'host': host, 'project': '(total)',
'cpu': compute_ref['vcpus'],
'memory_mb': compute_ref['memory_mb'],
'disk_gb': compute_ref['local_gb']}},
{'resource': {'host': host, 'project': '(used_now)',
'cpu': compute_ref['vcpus_used'],
'memory_mb': compute_ref['memory_mb_used'],
'disk_gb': compute_ref['local_gb_used']}}]
cpu_sum = 0
mem_sum = 0
hdd_sum = 0
for i in instance_refs:
cpu_sum += i['vcpus']
mem_sum += i['memory_mb']
hdd_sum += i['root_gb'] + i['ephemeral_gb']
resources.append({'resource': {'host': host,
'project': '(used_max)',
'cpu': cpu_sum,
'memory_mb': mem_sum,
'disk_gb': hdd_sum}})
# Getting usage resource per project
project_ids = [i['project_id'] for i in instance_refs]
project_ids = list(set(project_ids))
for project_id in project_ids:
vcpus = [i['vcpus'] for i in instance_refs
if i['project_id'] == project_id]
mem = [i['memory_mb'] for i in instance_refs
if i['project_id'] == project_id]
disk = [i['root_gb'] + i['ephemeral_gb'] for i in instance_refs
if i['project_id'] == project_id]
resources.append({'resource': {'host': host,
'project': project_id,
'cpu': reduce(lambda x, y: x + y, vcpus),
'memory_mb': reduce(lambda x, y: x + y, mem),
'disk_gb': reduce(lambda x, y: x + y, disk)}})
return {'host': resources}
return {'host': data}
class Hosts(extensions.ExtensionDescriptor):

View File

@ -33,6 +33,17 @@ nova.openstack.common.cfg.CONF.register_opts(_compute_opts)
def API(*args, **kwargs):
importutils = nova.openstack.common.importutils
compute_api_class = nova.openstack.common.cfg.CONF.compute_api_class
cls = importutils.import_class(compute_api_class)
return cls(*args, **kwargs)
class_name = nova.openstack.common.cfg.CONF.compute_api_class
return importutils.import_object(class_name, *args, **kwargs)
def HostAPI(*args, **kwargs):
"""
Returns the 'HostAPI' class from the same module as the configured compute
api
"""
importutils = nova.openstack.common.importutils
compute_api_class_name = nova.openstack.common.cfg.CONF.compute_api_class
compute_api_class = importutils.import_class(compute_api_class_name)
class_name = compute_api_class.__module__ + ".HostAPI"
return importutils.import_object(class_name, *args, **kwargs)

View File

@ -29,6 +29,7 @@ import time
import urllib
import uuid
from nova import availability_zones
from nova import block_device
from nova.compute import instance_types
from nova.compute import power_state
@ -150,7 +151,7 @@ def policy_decorator(scope):
wrap_check_policy = policy_decorator(scope='compute')
wrap_check_security_groups_policy = policy_decorator(
scope='compute:security_groups')
scope='compute:security_groups')
def check_policy(context, action, target, scope='compute'):
@ -844,10 +845,10 @@ class API(base.Base):
def trigger_provider_fw_rules_refresh(self, context):
"""Called when a rule is added/removed from a provider firewall."""
hosts = [x['host'] for (x, idx)
in self.db.service_get_all_compute_sorted(context)]
for host in hosts:
self.compute_rpcapi.refresh_provider_fw_rules(context, host)
host_names = [x['host'] for (x, idx)
in self.db.service_get_all_compute_sorted(context)]
for host_name in host_names:
self.compute_rpcapi.refresh_provider_fw_rules(context, host_name)
@wrap_check_policy
def update(self, context, instance, **kwargs):
@ -944,13 +945,14 @@ class API(base.Base):
host=src_host, cast=False,
reservations=downsize_reservations)
is_up = False
# NOTE(jogo): db allows for multiple compute services per host
try:
services = self.db.service_get_all_compute_by_host(
context.elevated(), instance['host'])
except exception.ComputeHostNotFound:
services = []
is_up = False
for service in services:
if self.servicegroup_api.service_is_up(service):
is_up = True
@ -1865,9 +1867,9 @@ class API(base.Base):
"""Retrieve diagnostics for the given instance."""
return self.compute_rpcapi.get_diagnostics(context, instance=instance)
def get_backdoor_port(self, context, host):
def get_backdoor_port(self, context, host_name):
"""Retrieve backdoor port."""
return self.compute_rpcapi.get_backdoor_port(context, host)
return self.compute_rpcapi.get_backdoor_port(context, host_name)
@wrap_check_policy
@check_instance_lock
@ -2133,45 +2135,148 @@ class API(base.Base):
@check_instance_state(vm_state=[vm_states.ACTIVE])
def live_migrate(self, context, instance, block_migration,
disk_over_commit, host):
disk_over_commit, host_name):
"""Migrate a server lively to a new host."""
LOG.debug(_("Going to try to live migrate instance to %s"),
host, instance=instance)
host_name, instance=instance)
instance = self.update(context, instance,
task_state=task_states.MIGRATING,
expected_task_state=None)
self.scheduler_rpcapi.live_migration(context, block_migration,
disk_over_commit, instance, host)
disk_over_commit, instance, host_name)
def check_host(fn):
"""Decorator that makes sure that the host exists."""
def wrapped(self, context, host_name, *args, **kwargs):
if self.does_host_exist(context, host_name):
return fn(self, context, host_name, *args, **kwargs)
else:
raise exception.HostNotFound(host=host_name)
return wrapped
class HostAPI(base.Base):
"""Sub-set of the Compute Manager API for managing host operations."""
def __init__(self):
self.compute_rpcapi = compute_rpcapi.ComputeAPI()
super(HostAPI, self).__init__()
"""Sub-set of the Compute Manager API for managing host operations."""
def set_host_enabled(self, context, host, enabled):
@check_host
def set_host_enabled(self, context, host_name, enabled):
"""Sets the specified host's ability to accept new instances."""
# NOTE(comstud): No instance_uuid argument to this compute manager
# call
return self.compute_rpcapi.set_host_enabled(context, enabled=enabled,
host=host)
host=host_name)
def get_host_uptime(self, context, host):
@check_host
def get_host_uptime(self, context, host_name):
"""Returns the result of calling "uptime" on the target host."""
# NOTE(comstud): No instance_uuid argument to this compute manager
# call
return self.compute_rpcapi.get_host_uptime(context, host=host)
return self.compute_rpcapi.get_host_uptime(context, host=host_name)
def host_power_action(self, context, host, action):
@check_host
def host_power_action(self, context, host_name, action):
"""Reboots, shuts down or powers up the host."""
# NOTE(comstud): No instance_uuid argument to this compute manager
# call
return self.compute_rpcapi.host_power_action(context, action=action,
host=host)
host=host_name)
def list_hosts(self, context, zone=None, service=None):
"""Returns a summary list of enabled hosts, optionally filtering
by zone and/or service type.
"""
LOG.debug(_("Listing hosts"))
services = self.db.service_get_all(context, False)
services = availability_zones.set_availability_zones(context, services)
if zone:
services = [s for s in services if s['availability_zone'] == zone]
hosts = []
for host in services:
hosts.append({'host_name': host['host'], 'service': host['topic'],
'zone': host['availability_zone']})
if service:
hosts = [host for host in hosts
if host["service"] == service]
return hosts
def does_host_exist(self, context, host_name):
"""
Returns True if the host with host_name exists, False otherwise
"""
return self.db.service_does_host_exist(context, host_name)
def describe_host(self, context, host_name):
"""
Returns information about a host in this kind of format:
:returns:
ex.::
{'host': 'hostname',
'project': 'admin',
'cpu': 1,
'memory_mb': 2048,
'disk_gb': 30}
"""
# Getting compute node info and related instances info
try:
compute_ref = self.db.service_get_all_compute_by_host(context,
host_name)
compute_ref = compute_ref[0]
except exception.ComputeHostNotFound:
raise exception.HostNotFound(host=host_name)
instance_refs = self.db.instance_get_all_by_host(context,
compute_ref['host'])
# Getting total available/used resource
compute_ref = compute_ref['compute_node'][0]
resources = [{'resource': {'host': host_name, 'project': '(total)',
'cpu': compute_ref['vcpus'],
'memory_mb': compute_ref['memory_mb'],
'disk_gb': compute_ref['local_gb']}},
{'resource': {'host': host_name, 'project': '(used_now)',
'cpu': compute_ref['vcpus_used'],
'memory_mb': compute_ref['memory_mb_used'],
'disk_gb': compute_ref['local_gb_used']}}]
cpu_sum = 0
mem_sum = 0
hdd_sum = 0
for i in instance_refs:
cpu_sum += i['vcpus']
mem_sum += i['memory_mb']
hdd_sum += i['root_gb'] + i['ephemeral_gb']
resources.append({'resource': {'host': host_name,
'project': '(used_max)',
'cpu': cpu_sum,
'memory_mb': mem_sum,
'disk_gb': hdd_sum}})
# Getting usage resource per project
project_ids = [i['project_id'] for i in instance_refs]
project_ids = list(set(project_ids))
for project_id in project_ids:
vcpus = [i['vcpus'] for i in instance_refs
if i['project_id'] == project_id]
mem = [i['memory_mb'] for i in instance_refs
if i['project_id'] == project_id]
disk = [i['root_gb'] + i['ephemeral_gb'] for i in instance_refs
if i['project_id'] == project_id]
resources.append({'resource': {'host': host_name,
'project': project_id,
'cpu': sum(vcpus),
'memory_mb': sum(mem),
'disk_gb': sum(disk)}})
return resources
@check_host
def set_host_maintenance(self, context, host, mode):
"""Start/Stop host maintenance window. On start, it triggers
guest VMs evacuation."""
@ -2237,25 +2342,27 @@ class AggregateAPI(base.Base):
reason='not empty')
self.db.aggregate_delete(context, aggregate_id)
def add_host_to_aggregate(self, context, aggregate_id, host):
def add_host_to_aggregate(self, context, aggregate_id, host_name):
"""Adds the host to an aggregate."""
# validates the host; ComputeHostNotFound is raised if invalid
service = self.db.service_get_all_compute_by_host(context, host)[0]
service = self.db.service_get_all_compute_by_host(
context, host_name)[0]
aggregate = self.db.aggregate_get(context, aggregate_id)
self.db.aggregate_host_add(context, aggregate_id, host)
self.db.aggregate_host_add(context, aggregate_id, host_name)
#NOTE(jogo): Send message to host to support resource pools
self.compute_rpcapi.add_aggregate_host(context,
aggregate=aggregate, host_param=host, host=host)
aggregate=aggregate, host_param=host_name, host=host_name)
return self.get_aggregate(context, aggregate_id)
def remove_host_from_aggregate(self, context, aggregate_id, host):
def remove_host_from_aggregate(self, context, aggregate_id, host_name):
"""Removes host from the aggregate."""
# validates the host; ComputeHostNotFound is raised if invalid
service = self.db.service_get_all_compute_by_host(context, host)[0]
service = self.db.service_get_all_compute_by_host(
context, host_name)[0]
aggregate = self.db.aggregate_get(context, aggregate_id)
self.db.aggregate_host_delete(context, aggregate_id, host)
self.db.aggregate_host_delete(context, aggregate_id, host_name)
self.compute_rpcapi.remove_aggregate_host(context,
aggregate=aggregate, host_param=host, host=host)
aggregate=aggregate, host_param=host_name, host=host_name)
return self.get_aggregate(context, aggregate_id)
def _get_aggregate_info(self, context, aggregate):

View File

@ -132,6 +132,15 @@ def service_get_all(context, disabled=None):
return IMPL.service_get_all(context, disabled)
def service_does_host_exist(context, host_name, include_disabled=False):
"""Returns True if 'host_name' is found in the services table, False
otherwise
:param: host_name - the name of the host we want to check if it exists
:param: include_disabled - Set to True to include hosts from disabled
services"""
return IMPL.service_does_host_exist(context, host_name, include_disabled)
def service_get_all_by_topic(context, topic):
"""Get all services for a given topic."""
return IMPL.service_get_all_by_topic(context, topic)

View File

@ -336,6 +336,15 @@ def service_get_all(context, disabled=None):
return query.all()
@require_admin_context
def service_does_host_exist(context, host_name, include_disabled):
query = get_session().query(func.count(models.Service.host)).\
filter_by(host=host_name)
if not include_disabled:
query = query.filter_by(disabled=False)
return query.scalar() > 0
@require_admin_context
def service_get_all_by_topic(context, topic):
return model_query(context, models.Service, read_deleted="no").\

View File

@ -19,59 +19,68 @@ import webob.exc
from nova.api.openstack.compute.contrib import hosts as os_hosts
from nova.compute import power_state
from nova.compute import vm_states
from nova import context
from nova import context as context_maker
from nova import db
from nova.openstack.common import log as logging
from nova import test
from nova.tests import fake_hosts
LOG = logging.getLogger(__name__)
HOST_LIST = {"hosts": [
{"host_name": "host_c1", "service": "compute", "zone": "nova"},
{"host_name": "host_c2", "service": "compute", "zone": "nova"}]
}
HOST_LIST_NOVA_ZONE = [
{"host_name": "host_c1", "service": "compute", "zone": "nova"},
{"host_name": "host_c2", "service": "compute", "zone": "nova"}]
SERVICES_LIST = [
{"host": "host_c1", "topic": "compute"},
{"host": "host_c2", "topic": "compute"}]
def stub_service_get_all(self, req):
return SERVICES_LIST
def stub_service_get_all(context, disabled=None):
return fake_hosts.SERVICES_LIST
def stub_set_host_enabled(context, host, enabled):
if host == "notimplemented":
def stub_service_does_host_exist(context, host_name):
return host_name in [row['host'] for row in stub_service_get_all(context)]
def stub_set_host_enabled(context, host_name, enabled):
"""
Simulates three possible behaviours for VM drivers or compute drivers when
enabling or disabling a host.
'enabled' means new instances can go to this host
'disabled' means they can't
"""
results = {True: "enabled", False: "disabled"}
if host_name == "notimplemented":
# The vm driver for this host doesn't support this feature
raise NotImplementedError()
elif host_name == "host_c2":
# Simulate a failure
return results[not enabled]
else:
# Do the right thing
return results[enabled]
def stub_set_host_maintenance(context, host_name, mode):
# We'll simulate success and failure by assuming
# that 'host_c1' always succeeds, and 'host_c2'
# always fails
fail = (host == "host_c2")
status = "enabled" if (enabled != fail) else "disabled"
return status
def stub_set_host_maintenance(context, host, mode):
if host == "notimplemented":
results = {True: "on_maintenance", False: "off_maintenance"}
if host_name == "notimplemented":
# The vm driver for this host doesn't support this feature
raise NotImplementedError()
# 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
elif host_name == "host_c2":
# Simulate a failure
return results[not mode]
else:
# Do the right thing
return results[mode]
def stub_host_power_action(context, host, action):
if host == "notimplemented":
def stub_host_power_action(context, host_name, action):
if host_name == "notimplemented":
raise NotImplementedError()
return action
def _create_instance(**kwargs):
"""Create a test instance."""
ctxt = context.get_admin_context()
ctxt = context_maker.get_admin_context()
return db.instance_create(ctxt, _create_instance_dict(**kwargs))
@ -99,12 +108,12 @@ def _create_instance_dict(**kwargs):
class FakeRequest(object):
environ = {"nova.context": context.get_admin_context()}
environ = {"nova.context": context_maker.get_admin_context()}
GET = {}
class FakeRequestWithNovaZone(object):
environ = {"nova.context": context.get_admin_context()}
environ = {"nova.context": context_maker.get_admin_context()}
GET = {"zone": "nova"}
@ -114,14 +123,22 @@ class HostTestCase(test.TestCase):
def setUp(self):
super(HostTestCase, self).setUp()
self.controller = os_hosts.HostController()
self.hosts_api = self.controller.api
self.req = FakeRequest()
# Pretend we have fake_hosts.HOST_LIST in the DB
self.stubs.Set(db, 'service_get_all',
stub_service_get_all)
self.stubs.Set(self.controller.api, 'set_host_enabled',
# Only hosts in our fake DB exist
self.stubs.Set(db, 'service_does_host_exist',
stub_service_does_host_exist)
# 'host_c1' always succeeds, and 'host_c2'
self.stubs.Set(self.hosts_api, 'set_host_enabled',
stub_set_host_enabled)
self.stubs.Set(self.controller.api, 'set_host_maintenance',
# 'host_c1' always succeeds, and 'host_c2'
self.stubs.Set(self.hosts_api, 'set_host_maintenance',
stub_set_host_maintenance)
self.stubs.Set(self.controller.api, 'host_power_action',
self.stubs.Set(self.hosts_api, 'host_power_action',
stub_host_power_action)
def _test_host_update(self, host, key, val, expected_value):
@ -130,14 +147,17 @@ class HostTestCase(test.TestCase):
self.assertEqual(result[key], expected_value)
def test_list_hosts(self):
# Verify that the compute hosts are returned.
hosts = os_hosts._list_hosts(self.req)
self.assertEqual(hosts, HOST_LIST['hosts'])
"""Verify that the compute hosts are returned."""
result = self.controller.index(self.req)
self.assert_('hosts' in result)
hosts = result['hosts']
self.assertEqual(fake_hosts.HOST_LIST, hosts)
def test_list_hosts_with_zone(self):
req = FakeRequestWithNovaZone()
hosts = os_hosts._list_hosts(req)
self.assertEqual(hosts, HOST_LIST_NOVA_ZONE)
result = self.controller.index(FakeRequestWithNovaZone())
self.assert_('hosts' in result)
hosts = result['hosts']
self.assertEqual(fake_hosts.HOST_LIST_NOVA_ZONE, hosts)
def test_disable_host(self):
self._test_host_update('host_c1', 'status', 'disable', 'disabled')
@ -222,10 +242,6 @@ class HostTestCase(test.TestCase):
self.assertEqual(result["status"], "disabled")
self.assertEqual(result["maintenance_mode"], "on_maintenance")
def test_bad_host(self):
self.assertRaises(webob.exc.HTTPNotFound, self.controller.update,
self.req, "bogus_host_name", {"status": "disable"})
def test_show_forbidden(self):
self.req.environ["nova.context"].is_admin = False
dest = 'dummydest'
@ -244,7 +260,7 @@ class HostTestCase(test.TestCase):
def _create_compute_service(self):
"""Create compute-manager(ComputeNode and Service record)."""
ctxt = context.get_admin_context()
ctxt = self.req.environ["nova.context"]
dic = {'host': 'dummy', 'binary': 'nova-compute', 'topic': 'compute',
'report_count': 0}
s_ref = db.service_create(ctxt, dic)
@ -259,8 +275,8 @@ class HostTestCase(test.TestCase):
return db.service_get(ctxt, s_ref['id'])
def test_show_no_project(self):
# No instance are running on the given host.
ctxt = context.get_admin_context()
"""No instances are running on the given host."""
ctxt = context_maker.get_admin_context()
s_ref = self._create_compute_service()
result = self.controller.show(self.req, s_ref['host'])
@ -275,8 +291,8 @@ class HostTestCase(test.TestCase):
db.service_destroy(ctxt, s_ref['id'])
def test_show_works_correctly(self):
# show() works correctly as expected.
ctxt = context.get_admin_context()
"""show() works correctly as expected."""
ctxt = context_maker.get_admin_context()
s_ref = self._create_compute_service()
i_ref1 = _create_instance(project_id='p-01', host=s_ref['host'])
i_ref2 = _create_instance(project_id='p-02', vcpus=3,
@ -303,17 +319,17 @@ class HostSerializerTest(test.TestCase):
def test_index_serializer(self):
serializer = os_hosts.HostIndexTemplate()
text = serializer.serialize(HOST_LIST)
text = serializer.serialize(fake_hosts.OS_API_HOST_LIST)
tree = etree.fromstring(text)
self.assertEqual('hosts', tree.tag)
self.assertEqual(len(HOST_LIST['hosts']), len(tree))
for i in range(len(HOST_LIST)):
self.assertEqual(len(fake_hosts.HOST_LIST), len(tree))
for i in range(len(fake_hosts.HOST_LIST)):
self.assertEqual('host', tree[i].tag)
self.assertEqual(HOST_LIST['hosts'][i]['host_name'],
self.assertEqual(fake_hosts.HOST_LIST[i]['host_name'],
tree[i].get('host_name'))
self.assertEqual(HOST_LIST['hosts'][i]['service'],
self.assertEqual(fake_hosts.HOST_LIST[i]['service'],
tree[i].get('service'))
def test_update_serializer_with_status(self):

View File

@ -5770,7 +5770,7 @@ class ComputeAPITestCase(BaseTestCase):
self.compute_api.live_migrate(self.context, instance,
block_migration=True,
disk_over_commit=True,
host='fake_dest_host')
host_name='fake_dest_host')
instance = db.instance_get_by_uuid(self.context, instance_uuid)
self.assertEqual(instance['task_state'], task_states.MIGRATING)
@ -6066,7 +6066,7 @@ class ComputePolicyTestCase(BaseTestCase):
self.assertRaises(exception.PolicyNotAuthorized,
self.compute_api.get_instance_faults,
self.context, instances)
context.get_admin_context(), instances)
def test_force_host_fail(self):
rules = {"compute:create": [],
@ -6098,11 +6098,19 @@ class ComputeHostAPITestCase(BaseTestCase):
call_info['msg'] = msg
self.stubs.Set(rpc, 'call', fake_rpc_call)
def _pretend_fake_host_exists(self, ctxt):
"""Sets it so that the host API always thinks that 'fake_host'
exists"""
self.mox.StubOutWithMock(self.host_api, 'does_host_exist')
self.host_api.does_host_exist(ctxt, 'fake_host').AndReturn(True)
self.mox.ReplayAll()
def test_set_host_enabled(self):
ctxt = context.RequestContext('fake', 'fake')
ctxt = context.get_admin_context()
call_info = {}
self._rpc_call_stub(call_info)
self._pretend_fake_host_exists(ctxt)
self.host_api.set_host_enabled(ctxt, 'fake_host', 'fake_enabled')
self.assertEqual(call_info['context'], ctxt)
self.assertEqual(call_info['topic'], 'compute.fake_host')
@ -6116,6 +6124,7 @@ class ComputeHostAPITestCase(BaseTestCase):
call_info = {}
self._rpc_call_stub(call_info)
self._pretend_fake_host_exists(ctxt)
self.host_api.get_host_uptime(ctxt, 'fake_host')
self.assertEqual(call_info['context'], ctxt)
self.assertEqual(call_info['topic'], 'compute.fake_host')
@ -6125,9 +6134,10 @@ class ComputeHostAPITestCase(BaseTestCase):
'version': compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION})
def test_host_power_action(self):
ctxt = context.RequestContext('fake', 'fake')
ctxt = context.get_admin_context()
call_info = {}
self._rpc_call_stub(call_info)
self._pretend_fake_host_exists(ctxt)
self.host_api.host_power_action(ctxt, 'fake_host', 'fake_action')
self.assertEqual(call_info['context'], ctxt)
self.assertEqual(call_info['topic'], 'compute.fake_host')
@ -6138,9 +6148,10 @@ class ComputeHostAPITestCase(BaseTestCase):
compute_rpcapi.ComputeAPI.BASE_RPC_API_VERSION})
def test_set_host_maintenance(self):
ctxt = context.RequestContext('fake', 'fake')
ctxt = context.get_admin_context()
call_info = {}
self._rpc_call_stub(call_info)
self._pretend_fake_host_exists(ctxt)
self.host_api.set_host_maintenance(ctxt, 'fake_host', 'fake_mode')
self.assertEqual(call_info['context'], ctxt)
self.assertEqual(call_info['topic'], 'compute.fake_host')

View File

@ -0,0 +1,105 @@
# Copyright (c) 2012 OpenStack, LLC.
# All Rights Reserved.
#
# 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 nova.compute import api
from nova import context
from nova import db
from nova import exception
from nova import test
from nova.tests import fake_hosts
class HostApiTestCase(test.TestCase):
"""
Tests 'host' subset of the compute api
"""
def setUp(self):
super(HostApiTestCase, self).setUp()
self.compute_rpcapi = api.compute_rpcapi
self.api = api.HostAPI()
def test_bad_host_set_enabled(self):
"""
Tests that actions on single hosts that don't exist blow up without
having to reach the host via rpc. Should raise HostNotFound if you
try to update a host that is not in the DB
"""
self.assertRaises(exception.HostNotFound, self.api.set_host_enabled,
context.get_admin_context(), "bogus_host_name", False)
def test_list_compute_hosts(self):
ctx = context.get_admin_context()
self.mox.StubOutWithMock(db, 'service_get_all')
db.service_get_all(ctx, False).AndReturn(fake_hosts.SERVICES_LIST)
self.mox.ReplayAll()
compute_hosts = self.api.list_hosts(ctx, service="compute")
self.mox.VerifyAll()
expected = [host for host in fake_hosts.HOST_LIST
if host["service"] == "compute"]
self.assertEqual(expected, compute_hosts)
def test_describe_host(self):
"""
Makes sure that describe_host returns the correct information
given our fake input.
"""
ctx = context.get_admin_context()
self.mox.StubOutWithMock(db, 'service_get_all_compute_by_host')
host_name = 'host_c1'
db.service_get_all_compute_by_host(ctx, host_name).AndReturn(
[{'host': 'fake_host',
'compute_node': [
{'vcpus': 4,
'vcpus_used': 1,
'memory_mb': 8192,
'memory_mb_used': 2048,
'local_gb': 1024,
'local_gb_used': 648}
]
}])
self.mox.StubOutWithMock(db, 'instance_get_all_by_host')
db.instance_get_all_by_host(ctx, 'fake_host').AndReturn(
[{'project_id': 42,
'vcpus': 1,
'memory_mb': 2048,
'root_gb': 648,
'ephemeral_gb': 0,
}])
self.mox.ReplayAll()
result = self.api.describe_host(ctx, host_name)
self.assertEqual(result,
[{'resource': {'cpu': 4,
'disk_gb': 1024,
'host': 'host_c1',
'memory_mb': 8192,
'project': '(total)'}},
{'resource': {'cpu': 1,
'disk_gb': 648,
'host': 'host_c1',
'memory_mb': 2048,
'project': '(used_now)'}},
{'resource': {'cpu': 1,
'disk_gb': 648,
'host': 'host_c1',
'memory_mb': 2048,
'project': '(used_max)'}},
{'resource': {'cpu': 1,
'disk_gb': 648,
'host': 'host_c1',
'memory_mb': 2048,
'project': 42}}]
)
self.mox.VerifyAll()

32
nova/tests/fake_hosts.py Normal file
View File

@ -0,0 +1,32 @@
# Copyright (c) 2012 OpenStack, LLC.
# All Rights Reserved.
#
# 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.
"""
Provides some fake hosts to test host and service related functions
"""
HOST_LIST = [
{"host_name": "host_c1", "service": "compute", "zone": "nova"},
{"host_name": "host_c2", "service": "compute", "zone": "nova"}]
OS_API_HOST_LIST = {"hosts": HOST_LIST}
HOST_LIST_NOVA_ZONE = [
{"host_name": "host_c1", "service": "compute", "zone": "nova"},
{"host_name": "host_c2", "service": "compute", "zone": "nova"}]
SERVICES_LIST = [
{"host": "host_c1", "topic": "compute"},
{"host": "host_c2", "topic": "compute"}]