merge from trunk and update .mailmap file
This commit is contained in:
commit
715a2c599d
3
.mailmap
3
.mailmap
@ -29,6 +29,7 @@
|
||||
<matt.dietz@rackspace.com> <matthewdietz@Matthew-Dietzs-MacBook-Pro.local>
|
||||
<matt.dietz@rackspace.com> <mdietz@openstack>
|
||||
<mordred@inaugust.com> <mordred@hudson>
|
||||
<naveedm9@gmail.com> <naveed.massjouni@rackspace.com>
|
||||
<nirmal.ranganathan@rackspace.com> <nirmal.ranganathan@rackspace.coom>
|
||||
<paul@openstack.org> <paul.voccio@rackspace.com>
|
||||
<paul@openstack.org> <pvoccio@castor.local>
|
||||
@ -36,6 +37,7 @@
|
||||
<rlane@wikimedia.org> <laner@controller>
|
||||
<sleepsonthefloor@gmail.com> <root@tonbuntu>
|
||||
<soren.hansen@rackspace.com> <soren@linux2go.dk>
|
||||
<throughnothing@gmail.com> <will.wolf@rackspace.com>
|
||||
<todd@ansolabs.com> <todd@lapex>
|
||||
<todd@ansolabs.com> <todd@rubidine.com>
|
||||
<tushar.vitthal.patil@gmail.com> <tpatil@vertex.co.in>
|
||||
@ -44,5 +46,4 @@
|
||||
<ueno.nachi@lab.ntt.co.jp> <openstack@lab.ntt.co.jp>
|
||||
<vishvananda@gmail.com> <root@mirror.nasanebula.net>
|
||||
<vishvananda@gmail.com> <root@ubuntu>
|
||||
<naveedm9@gmail.com> <naveed.massjouni@rackspace.com>
|
||||
<vishvananda@gmail.com> <vishvananda@yahoo.com>
|
||||
|
1
Authors
1
Authors
@ -44,6 +44,7 @@ Josh Kearney <josh@jk0.org>
|
||||
Josh Kleinpeter <josh@kleinpeter.org>
|
||||
Joshua McKenty <jmckenty@gmail.com>
|
||||
Justin Santa Barbara <justin@fathomdb.com>
|
||||
Justin Shepherd <jshepher@rackspace.com>
|
||||
Kei Masumoto <masumotok@nttdata.co.jp>
|
||||
Ken Pepple <ken.pepple@gmail.com>
|
||||
Kevin Bringard <kbringard@attinteractive.com>
|
||||
|
@ -594,10 +594,7 @@ class ControllerV10(Controller):
|
||||
def _parse_update(self, context, server_id, inst_dict, update_dict):
|
||||
if 'adminPass' in inst_dict['server']:
|
||||
update_dict['admin_pass'] = inst_dict['server']['adminPass']
|
||||
try:
|
||||
self.compute_api.set_admin_password(context, server_id)
|
||||
except exception.TimeoutException:
|
||||
return exc.HTTPRequestTimeout()
|
||||
self.compute_api.set_admin_password(context, server_id)
|
||||
|
||||
def _action_rebuild(self, info, request, instance_id):
|
||||
context = request.environ['nova.context']
|
||||
|
@ -305,9 +305,9 @@ class AuthManager(object):
|
||||
if check_type == 's3':
|
||||
sign = signer.Signer(user.secret.encode())
|
||||
expected_signature = sign.s3_authorization(headers, verb, path)
|
||||
LOG.debug('user.secret: %s', user.secret)
|
||||
LOG.debug('expected_signature: %s', expected_signature)
|
||||
LOG.debug('signature: %s', signature)
|
||||
LOG.debug(_('user.secret: %s'), user.secret)
|
||||
LOG.debug(_('expected_signature: %s'), expected_signature)
|
||||
LOG.debug(_('signature: %s'), signature)
|
||||
if signature != expected_signature:
|
||||
LOG.audit(_("Invalid signature for user %s"), user.name)
|
||||
raise exception.InvalidSignature(signature=signature,
|
||||
@ -317,10 +317,20 @@ class AuthManager(object):
|
||||
# secret isn't unicode
|
||||
expected_signature = signer.Signer(user.secret.encode()).generate(
|
||||
params, verb, server_string, path)
|
||||
LOG.debug('user.secret: %s', user.secret)
|
||||
LOG.debug('expected_signature: %s', expected_signature)
|
||||
LOG.debug('signature: %s', signature)
|
||||
LOG.debug(_('user.secret: %s'), user.secret)
|
||||
LOG.debug(_('expected_signature: %s'), expected_signature)
|
||||
LOG.debug(_('signature: %s'), signature)
|
||||
if signature != expected_signature:
|
||||
(addr_str, port_str) = utils.parse_server_string(server_string)
|
||||
# If the given server_string contains port num, try without it.
|
||||
if port_str != '':
|
||||
host_only_signature = signer.Signer(
|
||||
user.secret.encode()).generate(params, verb,
|
||||
addr_str, path)
|
||||
LOG.debug(_('host_only_signature: %s'),
|
||||
host_only_signature)
|
||||
if signature == host_only_signature:
|
||||
return (user, project)
|
||||
LOG.audit(_("Invalid signature for user %s"), user.name)
|
||||
raise exception.InvalidSignature(signature=signature,
|
||||
user=user)
|
||||
|
@ -482,6 +482,17 @@ class API(base.Base):
|
||||
"""Generic handler for RPC calls to the scheduler."""
|
||||
rpc.cast(context, FLAGS.scheduler_topic, args)
|
||||
|
||||
def _find_host(self, context, instance_id):
|
||||
"""Find the host associated with an instance."""
|
||||
for attempts in xrange(10):
|
||||
instance = self.get(context, instance_id)
|
||||
host = instance["host"]
|
||||
if host:
|
||||
return host
|
||||
time.sleep(1)
|
||||
raise exception.Error(_("Unable to find host for Instance %s")
|
||||
% instance_id)
|
||||
|
||||
def snapshot(self, context, instance_id, name):
|
||||
"""Snapshot the given instance.
|
||||
|
||||
@ -635,8 +646,12 @@ class API(base.Base):
|
||||
|
||||
def set_admin_password(self, context, instance_id, password=None):
|
||||
"""Set the root/admin password for the given instance."""
|
||||
self._cast_compute_message(
|
||||
'set_admin_password', context, instance_id, password)
|
||||
host = self._find_host(context, instance_id)
|
||||
|
||||
rpc.cast(context,
|
||||
self.db.queue_get_for(context, FLAGS.compute_topic, host),
|
||||
{"method": "set_admin_password",
|
||||
"args": {"instance_id": instance_id, "new_pass": password}})
|
||||
|
||||
def inject_file(self, context, instance_id):
|
||||
"""Write a file to the given instance."""
|
||||
|
@ -40,6 +40,7 @@ import os
|
||||
import socket
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import functools
|
||||
|
||||
from eventlet import greenthread
|
||||
@ -130,6 +131,7 @@ class ComputeManager(manager.SchedulerDependentManager):
|
||||
self.network_manager = utils.import_object(FLAGS.network_manager)
|
||||
self.volume_manager = utils.import_object(FLAGS.volume_manager)
|
||||
self.network_api = network.API()
|
||||
self._last_host_check = 0
|
||||
super(ComputeManager, self).__init__(service_name="compute",
|
||||
*args, **kwargs)
|
||||
|
||||
@ -404,21 +406,30 @@ class ComputeManager(manager.SchedulerDependentManager):
|
||||
def set_admin_password(self, context, instance_id, new_pass=None):
|
||||
"""Set the root/admin password for an instance on this host."""
|
||||
context = context.elevated()
|
||||
instance_ref = self.db.instance_get(context, instance_id)
|
||||
instance_id = instance_ref['id']
|
||||
instance_state = instance_ref['state']
|
||||
expected_state = power_state.RUNNING
|
||||
if instance_state != expected_state:
|
||||
LOG.warn(_('trying to reset the password on a non-running '
|
||||
'instance: %(instance_id)s (state: %(instance_state)s '
|
||||
'expected: %(expected_state)s)') % locals())
|
||||
LOG.audit(_('instance %s: setting admin password'),
|
||||
instance_ref['name'])
|
||||
|
||||
if new_pass is None:
|
||||
# Generate a random password
|
||||
new_pass = utils.generate_password(FLAGS.password_length)
|
||||
self.driver.set_admin_password(instance_ref, new_pass)
|
||||
self._update_state(context, instance_id)
|
||||
|
||||
while True:
|
||||
instance_ref = self.db.instance_get(context, instance_id)
|
||||
instance_id = instance_ref["id"]
|
||||
instance_state = instance_ref["state"]
|
||||
expected_state = power_state.RUNNING
|
||||
|
||||
if instance_state != expected_state:
|
||||
time.sleep(5)
|
||||
continue
|
||||
else:
|
||||
try:
|
||||
self.driver.set_admin_password(instance_ref, new_pass)
|
||||
LOG.audit(_("Instance %s: Root password set"),
|
||||
instance_ref["name"])
|
||||
break
|
||||
except Exception, e:
|
||||
# Catch all here because this could be anything.
|
||||
LOG.exception(e)
|
||||
continue
|
||||
|
||||
@exception.wrap_exception
|
||||
@checks_instance_lock
|
||||
@ -1083,6 +1094,13 @@ class ComputeManager(manager.SchedulerDependentManager):
|
||||
unicode(ex))
|
||||
error_list.append(ex)
|
||||
|
||||
try:
|
||||
self._report_driver_status()
|
||||
except Exception as ex:
|
||||
LOG.warning(_("Error during report_driver_status(): %s"),
|
||||
unicode(ex))
|
||||
error_list.append(ex)
|
||||
|
||||
try:
|
||||
self._poll_instance_states(context)
|
||||
except Exception as ex:
|
||||
@ -1092,6 +1110,16 @@ class ComputeManager(manager.SchedulerDependentManager):
|
||||
|
||||
return error_list
|
||||
|
||||
def _report_driver_status(self):
|
||||
curr_time = time.time()
|
||||
if curr_time - self._last_host_check > FLAGS.host_state_interval:
|
||||
self._last_host_check = curr_time
|
||||
LOG.info(_("Updating host status"))
|
||||
# This will grab info about the host and queue it
|
||||
# to be sent to the Schedulers.
|
||||
self.update_service_capabilities(
|
||||
self.driver.get_host_stats(refresh=True))
|
||||
|
||||
def _poll_instance_states(self, context):
|
||||
vm_instances = self.driver.list_instances_detail()
|
||||
vm_instances = dict((vm.name, vm) for vm in vm_instances)
|
||||
|
@ -457,6 +457,11 @@ class ZoneNotFound(NotFound):
|
||||
message = _("Zone %(zone_id)s could not be found.")
|
||||
|
||||
|
||||
class SchedulerHostFilterDriverNotFound(NotFound):
|
||||
message = _("Scheduler Host Filter Driver %(driver_name)s could"
|
||||
" not be found.")
|
||||
|
||||
|
||||
class InstanceMetadataNotFound(NotFound):
|
||||
message = _("Instance %(instance_id)s has no metadata with "
|
||||
"key %(metadata_key)s.")
|
||||
|
@ -76,11 +76,9 @@ def zone_update(context, zone_id, data):
|
||||
return db.zone_update(context, zone_id, data)
|
||||
|
||||
|
||||
def get_zone_capabilities(context, service=None):
|
||||
"""Returns a dict of key, value capabilities for this zone,
|
||||
or for a particular class of services running in this zone."""
|
||||
return _call_scheduler('get_zone_capabilities', context=context,
|
||||
params=dict(service=service))
|
||||
def get_zone_capabilities(context):
|
||||
"""Returns a dict of key, value capabilities for this zone."""
|
||||
return _call_scheduler('get_zone_capabilities', context=context)
|
||||
|
||||
|
||||
def update_service_capabilities(context, service_name, host, capabilities):
|
||||
|
288
nova/scheduler/host_filter.py
Normal file
288
nova/scheduler/host_filter.py
Normal file
@ -0,0 +1,288 @@
|
||||
# Copyright (c) 2011 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.
|
||||
|
||||
"""
|
||||
Host Filter is a driver mechanism for requesting instance resources.
|
||||
Three drivers are included: AllHosts, Flavor & JSON. AllHosts just
|
||||
returns the full, unfiltered list of hosts. Flavor is a hard coded
|
||||
matching mechanism based on flavor criteria and JSON is an ad-hoc
|
||||
filter grammar.
|
||||
|
||||
Why JSON? The requests for instances may come in through the
|
||||
REST interface from a user or a parent Zone.
|
||||
Currently Flavors and/or InstanceTypes are used for
|
||||
specifing the type of instance desired. Specific Nova users have
|
||||
noted a need for a more expressive way of specifying instances.
|
||||
Since we don't want to get into building full DSL this is a simple
|
||||
form as an example of how this could be done. In reality, most
|
||||
consumers will use the more rigid filters such as FlavorFilter.
|
||||
|
||||
Note: These are "required" capability filters. These capabilities
|
||||
used must be present or the host will be excluded. The hosts
|
||||
returned are then weighed by the Weighted Scheduler. Weights
|
||||
can take the more esoteric factors into consideration (such as
|
||||
server affinity and customer separation).
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova import log as logging
|
||||
from nova import utils
|
||||
|
||||
LOG = logging.getLogger('nova.scheduler.host_filter')
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
flags.DEFINE_string('default_host_filter_driver',
|
||||
'nova.scheduler.host_filter.AllHostsFilter',
|
||||
'Which driver to use for filtering hosts.')
|
||||
|
||||
|
||||
class HostFilter(object):
|
||||
"""Base class for host filter drivers."""
|
||||
|
||||
def instance_type_to_filter(self, instance_type):
|
||||
"""Convert instance_type into a filter for most common use-case."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def filter_hosts(self, zone_manager, query):
|
||||
"""Return a list of hosts that fulfill the filter."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def _full_name(self):
|
||||
"""module.classname of the filter driver"""
|
||||
return "%s.%s" % (self.__module__, self.__class__.__name__)
|
||||
|
||||
|
||||
class AllHostsFilter(HostFilter):
|
||||
"""NOP host filter driver. Returns all hosts in ZoneManager.
|
||||
This essentially does what the old Scheduler+Chance used
|
||||
to give us."""
|
||||
|
||||
def instance_type_to_filter(self, instance_type):
|
||||
"""Return anything to prevent base-class from raising
|
||||
exception."""
|
||||
return (self._full_name(), instance_type)
|
||||
|
||||
def filter_hosts(self, zone_manager, query):
|
||||
"""Return a list of hosts from ZoneManager list."""
|
||||
return [(host, services)
|
||||
for host, services in zone_manager.service_states.iteritems()]
|
||||
|
||||
|
||||
class FlavorFilter(HostFilter):
|
||||
"""HostFilter driver hard-coded to work with flavors."""
|
||||
|
||||
def instance_type_to_filter(self, instance_type):
|
||||
"""Use instance_type to filter hosts."""
|
||||
return (self._full_name(), instance_type)
|
||||
|
||||
def filter_hosts(self, zone_manager, query):
|
||||
"""Return a list of hosts that can create instance_type."""
|
||||
instance_type = query
|
||||
selected_hosts = []
|
||||
for host, services in zone_manager.service_states.iteritems():
|
||||
capabilities = services.get('compute', {})
|
||||
host_ram_mb = capabilities['host_memory_free']
|
||||
disk_bytes = capabilities['disk_available']
|
||||
if host_ram_mb >= instance_type['memory_mb'] and \
|
||||
disk_bytes >= instance_type['local_gb']:
|
||||
selected_hosts.append((host, capabilities))
|
||||
return selected_hosts
|
||||
|
||||
#host entries (currently) are like:
|
||||
# {'host_name-description': 'Default install of XenServer',
|
||||
# 'host_hostname': 'xs-mini',
|
||||
# 'host_memory_total': 8244539392,
|
||||
# 'host_memory_overhead': 184225792,
|
||||
# 'host_memory_free': 3868327936,
|
||||
# 'host_memory_free_computed': 3840843776},
|
||||
# 'host_other-config': {},
|
||||
# 'host_ip_address': '192.168.1.109',
|
||||
# 'host_cpu_info': {},
|
||||
# 'disk_available': 32954957824,
|
||||
# 'disk_total': 50394562560,
|
||||
# 'disk_used': 17439604736},
|
||||
# 'host_uuid': 'cedb9b39-9388-41df-8891-c5c9a0c0fe5f',
|
||||
# 'host_name-label': 'xs-mini'}
|
||||
|
||||
# instance_type table has:
|
||||
#name = Column(String(255), unique=True)
|
||||
#memory_mb = Column(Integer)
|
||||
#vcpus = Column(Integer)
|
||||
#local_gb = Column(Integer)
|
||||
#flavorid = Column(Integer, unique=True)
|
||||
#swap = Column(Integer, nullable=False, default=0)
|
||||
#rxtx_quota = Column(Integer, nullable=False, default=0)
|
||||
#rxtx_cap = Column(Integer, nullable=False, default=0)
|
||||
|
||||
|
||||
class JsonFilter(HostFilter):
|
||||
"""Host Filter driver to allow simple JSON-based grammar for
|
||||
selecting hosts."""
|
||||
|
||||
def _equals(self, args):
|
||||
"""First term is == all the other terms."""
|
||||
if len(args) < 2:
|
||||
return False
|
||||
lhs = args[0]
|
||||
for rhs in args[1:]:
|
||||
if lhs != rhs:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _less_than(self, args):
|
||||
"""First term is < all the other terms."""
|
||||
if len(args) < 2:
|
||||
return False
|
||||
lhs = args[0]
|
||||
for rhs in args[1:]:
|
||||
if lhs >= rhs:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _greater_than(self, args):
|
||||
"""First term is > all the other terms."""
|
||||
if len(args) < 2:
|
||||
return False
|
||||
lhs = args[0]
|
||||
for rhs in args[1:]:
|
||||
if lhs <= rhs:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _in(self, args):
|
||||
"""First term is in set of remaining terms"""
|
||||
if len(args) < 2:
|
||||
return False
|
||||
return args[0] in args[1:]
|
||||
|
||||
def _less_than_equal(self, args):
|
||||
"""First term is <= all the other terms."""
|
||||
if len(args) < 2:
|
||||
return False
|
||||
lhs = args[0]
|
||||
for rhs in args[1:]:
|
||||
if lhs > rhs:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _greater_than_equal(self, args):
|
||||
"""First term is >= all the other terms."""
|
||||
if len(args) < 2:
|
||||
return False
|
||||
lhs = args[0]
|
||||
for rhs in args[1:]:
|
||||
if lhs < rhs:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _not(self, args):
|
||||
"""Flip each of the arguments."""
|
||||
if len(args) == 0:
|
||||
return False
|
||||
return [not arg for arg in args]
|
||||
|
||||
def _or(self, args):
|
||||
"""True if any arg is True."""
|
||||
return True in args
|
||||
|
||||
def _and(self, args):
|
||||
"""True if all args are True."""
|
||||
return False not in args
|
||||
|
||||
commands = {
|
||||
'=': _equals,
|
||||
'<': _less_than,
|
||||
'>': _greater_than,
|
||||
'in': _in,
|
||||
'<=': _less_than_equal,
|
||||
'>=': _greater_than_equal,
|
||||
'not': _not,
|
||||
'or': _or,
|
||||
'and': _and,
|
||||
}
|
||||
|
||||
def instance_type_to_filter(self, instance_type):
|
||||
"""Convert instance_type into JSON filter object."""
|
||||
required_ram = instance_type['memory_mb']
|
||||
required_disk = instance_type['local_gb']
|
||||
query = ['and',
|
||||
['>=', '$compute.host_memory_free', required_ram],
|
||||
['>=', '$compute.disk_available', required_disk]
|
||||
]
|
||||
return (self._full_name(), json.dumps(query))
|
||||
|
||||
def _parse_string(self, string, host, services):
|
||||
"""Strings prefixed with $ are capability lookups in the
|
||||
form '$service.capability[.subcap*]'"""
|
||||
if not string:
|
||||
return None
|
||||
if string[0] != '$':
|
||||
return string
|
||||
|
||||
path = string[1:].split('.')
|
||||
for item in path:
|
||||
services = services.get(item, None)
|
||||
if not services:
|
||||
return None
|
||||
return services
|
||||
|
||||
def _process_filter(self, zone_manager, query, host, services):
|
||||
"""Recursively parse the query structure."""
|
||||
if len(query) == 0:
|
||||
return True
|
||||
cmd = query[0]
|
||||
method = self.commands[cmd] # Let exception fly.
|
||||
cooked_args = []
|
||||
for arg in query[1:]:
|
||||
if isinstance(arg, list):
|
||||
arg = self._process_filter(zone_manager, arg, host, services)
|
||||
elif isinstance(arg, basestring):
|
||||
arg = self._parse_string(arg, host, services)
|
||||
if arg != None:
|
||||
cooked_args.append(arg)
|
||||
result = method(self, cooked_args)
|
||||
return result
|
||||
|
||||
def filter_hosts(self, zone_manager, query):
|
||||
"""Return a list of hosts that can fulfill filter."""
|
||||
expanded = json.loads(query)
|
||||
hosts = []
|
||||
for host, services in zone_manager.service_states.iteritems():
|
||||
r = self._process_filter(zone_manager, expanded, host, services)
|
||||
if isinstance(r, list):
|
||||
r = True in r
|
||||
if r:
|
||||
hosts.append((host, services))
|
||||
return hosts
|
||||
|
||||
|
||||
DRIVERS = [AllHostsFilter, FlavorFilter, JsonFilter]
|
||||
|
||||
|
||||
def choose_driver(driver_name=None):
|
||||
"""Since the caller may specify which driver to use we need
|
||||
to have an authoritative list of what is permissible. This
|
||||
function checks the driver name against a predefined set
|
||||
of acceptable drivers."""
|
||||
|
||||
if not driver_name:
|
||||
driver_name = FLAGS.default_host_filter_driver
|
||||
for driver in DRIVERS:
|
||||
if "%s.%s" % (driver.__module__, driver.__name__) == driver_name:
|
||||
return driver()
|
||||
raise exception.SchedulerHostFilterDriverNotFound(driver_name=driver_name)
|
@ -60,10 +60,9 @@ class SchedulerManager(manager.Manager):
|
||||
"""Get a list of zones from the ZoneManager."""
|
||||
return self.zone_manager.get_zone_list()
|
||||
|
||||
def get_zone_capabilities(self, context=None, service=None):
|
||||
"""Get the normalized set of capabilites for this zone,
|
||||
or for a particular service."""
|
||||
return self.zone_manager.get_zone_capabilities(context, service)
|
||||
def get_zone_capabilities(self, context=None):
|
||||
"""Get the normalized set of capabilites for this zone."""
|
||||
return self.zone_manager.get_zone_capabilities(context)
|
||||
|
||||
def update_service_capabilities(self, context=None, service_name=None,
|
||||
host=None, capabilities={}):
|
||||
|
@ -106,28 +106,26 @@ class ZoneManager(object):
|
||||
def __init__(self):
|
||||
self.last_zone_db_check = datetime.min
|
||||
self.zone_states = {} # { <zone_id> : ZoneState }
|
||||
self.service_states = {} # { <service> : { <host> : { cap k : v }}}
|
||||
self.service_states = {} # { <host> : { <service> : { cap k : v }}}
|
||||
self.green_pool = greenpool.GreenPool()
|
||||
|
||||
def get_zone_list(self):
|
||||
"""Return the list of zones we know about."""
|
||||
return [zone.to_dict() for zone in self.zone_states.values()]
|
||||
|
||||
def get_zone_capabilities(self, context, service=None):
|
||||
def get_zone_capabilities(self, context):
|
||||
"""Roll up all the individual host info to generic 'service'
|
||||
capabilities. Each capability is aggregated into
|
||||
<cap>_min and <cap>_max values."""
|
||||
service_dict = self.service_states
|
||||
if service:
|
||||
service_dict = {service: self.service_states.get(service, {})}
|
||||
hosts_dict = self.service_states
|
||||
|
||||
# TODO(sandy) - be smarter about fabricating this structure.
|
||||
# But it's likely to change once we understand what the Best-Match
|
||||
# code will need better.
|
||||
combined = {} # { <service>_<cap> : (min, max), ... }
|
||||
for service_name, host_dict in service_dict.iteritems():
|
||||
for host, caps_dict in host_dict.iteritems():
|
||||
for cap, value in caps_dict.iteritems():
|
||||
for host, host_dict in hosts_dict.iteritems():
|
||||
for service_name, service_dict in host_dict.iteritems():
|
||||
for cap, value in service_dict.iteritems():
|
||||
key = "%s_%s" % (service_name, cap)
|
||||
min_value, max_value = combined.get(key, (value, value))
|
||||
min_value = min(min_value, value)
|
||||
@ -171,6 +169,6 @@ class ZoneManager(object):
|
||||
"""Update the per-service capabilities based on this notification."""
|
||||
logging.debug(_("Received %(service_name)s service update from "
|
||||
"%(host)s: %(capabilities)s") % locals())
|
||||
service_caps = self.service_states.get(service_name, {})
|
||||
service_caps[host] = capabilities
|
||||
self.service_states[service_name] = service_caps
|
||||
service_caps = self.service_states.get(host, {})
|
||||
service_caps[service_name] = capabilities
|
||||
self.service_states[host] = service_caps
|
||||
|
@ -134,6 +134,10 @@ def fake_compute_api(cls, req, id):
|
||||
return True
|
||||
|
||||
|
||||
def find_host(self, context, instance_id):
|
||||
return "nova"
|
||||
|
||||
|
||||
class ServersTest(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
@ -473,6 +477,7 @@ class ServersTest(test.TestCase):
|
||||
"_get_kernel_ramdisk_from_image", kernel_ramdisk_mapping)
|
||||
self.stubs.Set(nova.api.openstack.common,
|
||||
"get_image_id_from_image_hash", image_id_from_hash)
|
||||
self.stubs.Set(nova.compute.api.API, "_find_host", find_host)
|
||||
|
||||
def _test_create_instance_helper(self):
|
||||
self._setup_for_create_instance()
|
||||
@ -767,6 +772,7 @@ class ServersTest(test.TestCase):
|
||||
|
||||
self.stubs.Set(nova.db.api, 'instance_update',
|
||||
server_update)
|
||||
self.stubs.Set(nova.compute.api.API, "_find_host", find_host)
|
||||
|
||||
req = webob.Request.blank('/v1.0/servers/1')
|
||||
req.method = 'PUT'
|
||||
|
@ -75,7 +75,7 @@ def zone_get_all_db(context):
|
||||
]
|
||||
|
||||
|
||||
def zone_capabilities(method, context, params):
|
||||
def zone_capabilities(method, context):
|
||||
return dict()
|
||||
|
||||
|
||||
|
@ -101,9 +101,43 @@ class _AuthManagerBaseTestCase(test.TestCase):
|
||||
self.assertEqual('private-party', u.access)
|
||||
|
||||
def test_004_signature_is_valid(self):
|
||||
#self.assertTrue(self.manager.authenticate(**boto.generate_url ...? ))
|
||||
pass
|
||||
#raise NotImplementedError
|
||||
with user_generator(self.manager, name='admin', secret='admin',
|
||||
access='admin'):
|
||||
with project_generator(self.manager, name="admin",
|
||||
manager_user='admin'):
|
||||
accesskey = 'admin:admin'
|
||||
expected_result = (self.manager.get_user('admin'),
|
||||
self.manager.get_project('admin'))
|
||||
# captured sig and query string using boto 1.9b/euca2ools 1.2
|
||||
sig = 'd67Wzd9Bwz8xid9QU+lzWXcF2Y3tRicYABPJgrqfrwM='
|
||||
auth_params = {'AWSAccessKeyId': 'admin:admin',
|
||||
'Action': 'DescribeAvailabilityZones',
|
||||
'SignatureMethod': 'HmacSHA256',
|
||||
'SignatureVersion': '2',
|
||||
'Timestamp': '2011-04-22T11:29:29',
|
||||
'Version': '2009-11-30'}
|
||||
self.assertTrue(expected_result, self.manager.authenticate(
|
||||
accesskey,
|
||||
sig,
|
||||
auth_params,
|
||||
'GET',
|
||||
'127.0.0.1:8773',
|
||||
'/services/Cloud/'))
|
||||
# captured sig and query string using RightAWS 1.10.0
|
||||
sig = 'ECYLU6xdFG0ZqRVhQybPJQNJ5W4B9n8fGs6+/fuGD2c='
|
||||
auth_params = {'AWSAccessKeyId': 'admin:admin',
|
||||
'Action': 'DescribeAvailabilityZones',
|
||||
'SignatureMethod': 'HmacSHA256',
|
||||
'SignatureVersion': '2',
|
||||
'Timestamp': '2011-04-22T11:29:49.000Z',
|
||||
'Version': '2008-12-01'}
|
||||
self.assertTrue(expected_result, self.manager.authenticate(
|
||||
accesskey,
|
||||
sig,
|
||||
auth_params,
|
||||
'GET',
|
||||
'127.0.0.1',
|
||||
'/services/Cloud'))
|
||||
|
||||
def test_005_can_get_credentials(self):
|
||||
return
|
||||
|
@ -21,6 +21,7 @@ Tests For Compute
|
||||
|
||||
import datetime
|
||||
import mox
|
||||
import stubout
|
||||
|
||||
from nova import compute
|
||||
from nova import context
|
||||
@ -52,6 +53,10 @@ class FakeTime(object):
|
||||
self.counter += t
|
||||
|
||||
|
||||
def nop_report_driver_status(self):
|
||||
pass
|
||||
|
||||
|
||||
class ComputeTestCase(test.TestCase):
|
||||
"""Test case for compute"""
|
||||
def setUp(self):
|
||||
@ -649,6 +654,10 @@ class ComputeTestCase(test.TestCase):
|
||||
|
||||
def test_run_kill_vm(self):
|
||||
"""Detect when a vm is terminated behind the scenes"""
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
self.stubs.Set(compute_manager.ComputeManager,
|
||||
'_report_driver_status', nop_report_driver_status)
|
||||
|
||||
instance_id = self._create_instance()
|
||||
|
||||
self.compute.run_instance(self.context, instance_id)
|
||||
|
208
nova/tests/test_host_filter.py
Normal file
208
nova/tests/test_host_filter.py
Normal file
@ -0,0 +1,208 @@
|
||||
# Copyright 2011 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.
|
||||
"""
|
||||
Tests For Scheduler Host Filter Drivers.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova import test
|
||||
from nova.scheduler import host_filter
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
class FakeZoneManager:
|
||||
pass
|
||||
|
||||
|
||||
class HostFilterTestCase(test.TestCase):
|
||||
"""Test case for host filter drivers."""
|
||||
|
||||
def _host_caps(self, multiplier):
|
||||
# Returns host capabilities in the following way:
|
||||
# host1 = memory:free 10 (100max)
|
||||
# disk:available 100 (1000max)
|
||||
# hostN = memory:free 10 + 10N
|
||||
# disk:available 100 + 100N
|
||||
# in other words: hostN has more resources than host0
|
||||
# which means ... don't go above 10 hosts.
|
||||
return {'host_name-description': 'XenServer %s' % multiplier,
|
||||
'host_hostname': 'xs-%s' % multiplier,
|
||||
'host_memory_total': 100,
|
||||
'host_memory_overhead': 10,
|
||||
'host_memory_free': 10 + multiplier * 10,
|
||||
'host_memory_free-computed': 10 + multiplier * 10,
|
||||
'host_other-config': {},
|
||||
'host_ip_address': '192.168.1.%d' % (100 + multiplier),
|
||||
'host_cpu_info': {},
|
||||
'disk_available': 100 + multiplier * 100,
|
||||
'disk_total': 1000,
|
||||
'disk_used': 0,
|
||||
'host_uuid': 'xxx-%d' % multiplier,
|
||||
'host_name-label': 'xs-%s' % multiplier}
|
||||
|
||||
def setUp(self):
|
||||
self.old_flag = FLAGS.default_host_filter_driver
|
||||
FLAGS.default_host_filter_driver = \
|
||||
'nova.scheduler.host_filter.AllHostsFilter'
|
||||
self.instance_type = dict(name='tiny',
|
||||
memory_mb=50,
|
||||
vcpus=10,
|
||||
local_gb=500,
|
||||
flavorid=1,
|
||||
swap=500,
|
||||
rxtx_quota=30000,
|
||||
rxtx_cap=200)
|
||||
|
||||
self.zone_manager = FakeZoneManager()
|
||||
states = {}
|
||||
for x in xrange(10):
|
||||
states['host%02d' % (x + 1)] = {'compute': self._host_caps(x)}
|
||||
self.zone_manager.service_states = states
|
||||
|
||||
def tearDown(self):
|
||||
FLAGS.default_host_filter_driver = self.old_flag
|
||||
|
||||
def test_choose_driver(self):
|
||||
# Test default driver ...
|
||||
driver = host_filter.choose_driver()
|
||||
self.assertEquals(driver._full_name(),
|
||||
'nova.scheduler.host_filter.AllHostsFilter')
|
||||
# Test valid driver ...
|
||||
driver = host_filter.choose_driver(
|
||||
'nova.scheduler.host_filter.FlavorFilter')
|
||||
self.assertEquals(driver._full_name(),
|
||||
'nova.scheduler.host_filter.FlavorFilter')
|
||||
# Test invalid driver ...
|
||||
try:
|
||||
host_filter.choose_driver('does not exist')
|
||||
self.fail("Should not find driver")
|
||||
except exception.SchedulerHostFilterDriverNotFound:
|
||||
pass
|
||||
|
||||
def test_all_host_driver(self):
|
||||
driver = host_filter.AllHostsFilter()
|
||||
cooked = driver.instance_type_to_filter(self.instance_type)
|
||||
hosts = driver.filter_hosts(self.zone_manager, cooked)
|
||||
self.assertEquals(10, len(hosts))
|
||||
for host, capabilities in hosts:
|
||||
self.assertTrue(host.startswith('host'))
|
||||
|
||||
def test_flavor_driver(self):
|
||||
driver = host_filter.FlavorFilter()
|
||||
# filter all hosts that can support 50 ram and 500 disk
|
||||
name, cooked = driver.instance_type_to_filter(self.instance_type)
|
||||
self.assertEquals('nova.scheduler.host_filter.FlavorFilter', name)
|
||||
hosts = driver.filter_hosts(self.zone_manager, cooked)
|
||||
self.assertEquals(6, len(hosts))
|
||||
just_hosts = [host for host, caps in hosts]
|
||||
just_hosts.sort()
|
||||
self.assertEquals('host05', just_hosts[0])
|
||||
self.assertEquals('host10', just_hosts[5])
|
||||
|
||||
def test_json_driver(self):
|
||||
driver = host_filter.JsonFilter()
|
||||
# filter all hosts that can support 50 ram and 500 disk
|
||||
name, cooked = driver.instance_type_to_filter(self.instance_type)
|
||||
self.assertEquals('nova.scheduler.host_filter.JsonFilter', name)
|
||||
hosts = driver.filter_hosts(self.zone_manager, cooked)
|
||||
self.assertEquals(6, len(hosts))
|
||||
just_hosts = [host for host, caps in hosts]
|
||||
just_hosts.sort()
|
||||
self.assertEquals('host05', just_hosts[0])
|
||||
self.assertEquals('host10', just_hosts[5])
|
||||
|
||||
# Try some custom queries
|
||||
|
||||
raw = ['or',
|
||||
['and',
|
||||
['<', '$compute.host_memory_free', 30],
|
||||
['<', '$compute.disk_available', 300]
|
||||
],
|
||||
['and',
|
||||
['>', '$compute.host_memory_free', 70],
|
||||
['>', '$compute.disk_available', 700]
|
||||
]
|
||||
]
|
||||
cooked = json.dumps(raw)
|
||||
hosts = driver.filter_hosts(self.zone_manager, cooked)
|
||||
|
||||
self.assertEquals(5, len(hosts))
|
||||
just_hosts = [host for host, caps in hosts]
|
||||
just_hosts.sort()
|
||||
for index, host in zip([1, 2, 8, 9, 10], just_hosts):
|
||||
self.assertEquals('host%02d' % index, host)
|
||||
|
||||
raw = ['not',
|
||||
['=', '$compute.host_memory_free', 30],
|
||||
]
|
||||
cooked = json.dumps(raw)
|
||||
hosts = driver.filter_hosts(self.zone_manager, cooked)
|
||||
|
||||
self.assertEquals(9, len(hosts))
|
||||
just_hosts = [host for host, caps in hosts]
|
||||
just_hosts.sort()
|
||||
for index, host in zip([1, 2, 4, 5, 6, 7, 8, 9, 10], just_hosts):
|
||||
self.assertEquals('host%02d' % index, host)
|
||||
|
||||
raw = ['in', '$compute.host_memory_free', 20, 40, 60, 80, 100]
|
||||
cooked = json.dumps(raw)
|
||||
hosts = driver.filter_hosts(self.zone_manager, cooked)
|
||||
|
||||
self.assertEquals(5, len(hosts))
|
||||
just_hosts = [host for host, caps in hosts]
|
||||
just_hosts.sort()
|
||||
for index, host in zip([2, 4, 6, 8, 10], just_hosts):
|
||||
self.assertEquals('host%02d' % index, host)
|
||||
|
||||
# Try some bogus input ...
|
||||
raw = ['unknown command', ]
|
||||
cooked = json.dumps(raw)
|
||||
try:
|
||||
driver.filter_hosts(self.zone_manager, cooked)
|
||||
self.fail("Should give KeyError")
|
||||
except KeyError, e:
|
||||
pass
|
||||
|
||||
self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps([])))
|
||||
self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps({})))
|
||||
self.assertTrue(driver.filter_hosts(self.zone_manager, json.dumps(
|
||||
['not', True, False, True, False]
|
||||
)))
|
||||
|
||||
try:
|
||||
driver.filter_hosts(self.zone_manager, json.dumps(
|
||||
'not', True, False, True, False
|
||||
))
|
||||
self.fail("Should give KeyError")
|
||||
except KeyError, e:
|
||||
pass
|
||||
|
||||
self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps(
|
||||
['=', '$foo', 100]
|
||||
)))
|
||||
self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps(
|
||||
['=', '$.....', 100]
|
||||
)))
|
||||
self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps(
|
||||
['>', ['and', ['or', ['not', ['<', ['>=', ['<=', ['in', ]]]]]]]]
|
||||
)))
|
||||
|
||||
self.assertFalse(driver.filter_hosts(self.zone_manager, json.dumps(
|
||||
['=', {}, ['>', '$missing....foo']]
|
||||
)))
|
@ -250,3 +250,28 @@ class GetFromPathTestCase(test.TestCase):
|
||||
input = {'a': [1, 2, {'b': 'b_1'}]}
|
||||
self.assertEquals([1, 2, {'b': 'b_1'}], f(input, "a"))
|
||||
self.assertEquals(['b_1'], f(input, "a/b"))
|
||||
|
||||
|
||||
class GenericUtilsTestCase(test.TestCase):
|
||||
def test_parse_server_string(self):
|
||||
result = utils.parse_server_string('::1')
|
||||
self.assertEqual(('::1', ''), result)
|
||||
result = utils.parse_server_string('[::1]:8773')
|
||||
self.assertEqual(('::1', '8773'), result)
|
||||
result = utils.parse_server_string('2001:db8::192.168.1.1')
|
||||
self.assertEqual(('2001:db8::192.168.1.1', ''), result)
|
||||
result = utils.parse_server_string('[2001:db8::192.168.1.1]:8773')
|
||||
self.assertEqual(('2001:db8::192.168.1.1', '8773'), result)
|
||||
result = utils.parse_server_string('192.168.1.1')
|
||||
self.assertEqual(('192.168.1.1', ''), result)
|
||||
result = utils.parse_server_string('192.168.1.2:8773')
|
||||
self.assertEqual(('192.168.1.2', '8773'), result)
|
||||
result = utils.parse_server_string('192.168.1.3')
|
||||
self.assertEqual(('192.168.1.3', ''), result)
|
||||
result = utils.parse_server_string('www.example.com:8443')
|
||||
self.assertEqual(('www.example.com', '8443'), result)
|
||||
result = utils.parse_server_string('www.example.com')
|
||||
self.assertEqual(('www.example.com', ''), result)
|
||||
# error case
|
||||
result = utils.parse_server_string('www.exa:mple.com:8443')
|
||||
self.assertEqual(('', ''), result)
|
||||
|
@ -17,6 +17,7 @@
|
||||
"""Test suite for XenAPI."""
|
||||
|
||||
import functools
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import stubout
|
||||
@ -665,3 +666,52 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase):
|
||||
self.fake_instance.image_id = glance_stubs.FakeGlance.IMAGE_VHD
|
||||
self.fake_instance.kernel_id = None
|
||||
self.assert_disk_type(vm_utils.ImageType.DISK_VHD)
|
||||
|
||||
|
||||
class FakeXenApi(object):
|
||||
"""Fake XenApi for testing HostState."""
|
||||
|
||||
class FakeSR(object):
|
||||
def get_record(self, ref):
|
||||
return {'virtual_allocation': 10000,
|
||||
'physical_utilisation': 20000}
|
||||
|
||||
SR = FakeSR()
|
||||
|
||||
|
||||
class FakeSession(object):
|
||||
"""Fake Session class for HostState testing."""
|
||||
|
||||
def async_call_plugin(self, *args):
|
||||
return None
|
||||
|
||||
def wait_for_task(self, *args):
|
||||
vm = {'total': 10,
|
||||
'overhead': 20,
|
||||
'free': 30,
|
||||
'free-computed': 40}
|
||||
return json.dumps({'host_memory': vm})
|
||||
|
||||
def get_xenapi(self):
|
||||
return FakeXenApi()
|
||||
|
||||
|
||||
class HostStateTestCase(test.TestCase):
|
||||
"""Tests HostState, which holds metrics from XenServer that get
|
||||
reported back to the Schedulers."""
|
||||
|
||||
def _fake_safe_find_sr(self, session):
|
||||
"""None SR ref since we're ignoring it in FakeSR."""
|
||||
return None
|
||||
|
||||
def test_host_state(self):
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
self.stubs.Set(vm_utils, 'safe_find_sr', self._fake_safe_find_sr)
|
||||
host_state = xenapi_conn.HostState(FakeSession())
|
||||
stats = host_state._stats
|
||||
self.assertEquals(stats['disk_total'], 10000)
|
||||
self.assertEquals(stats['disk_used'], 20000)
|
||||
self.assertEquals(stats['host_memory_total'], 10)
|
||||
self.assertEquals(stats['host_memory_overhead'], 20)
|
||||
self.assertEquals(stats['host_memory_free'], 30)
|
||||
self.assertEquals(stats['host_memory_free_computed'], 40)
|
||||
|
@ -78,38 +78,32 @@ class ZoneManagerTestCase(test.TestCase):
|
||||
|
||||
def test_service_capabilities(self):
|
||||
zm = zone_manager.ZoneManager()
|
||||
caps = zm.get_zone_capabilities(self, None)
|
||||
caps = zm.get_zone_capabilities(None)
|
||||
self.assertEquals(caps, {})
|
||||
|
||||
zm.update_service_capabilities("svc1", "host1", dict(a=1, b=2))
|
||||
caps = zm.get_zone_capabilities(self, None)
|
||||
caps = zm.get_zone_capabilities(None)
|
||||
self.assertEquals(caps, dict(svc1_a=(1, 1), svc1_b=(2, 2)))
|
||||
|
||||
zm.update_service_capabilities("svc1", "host1", dict(a=2, b=3))
|
||||
caps = zm.get_zone_capabilities(self, None)
|
||||
caps = zm.get_zone_capabilities(None)
|
||||
self.assertEquals(caps, dict(svc1_a=(2, 2), svc1_b=(3, 3)))
|
||||
|
||||
zm.update_service_capabilities("svc1", "host2", dict(a=20, b=30))
|
||||
caps = zm.get_zone_capabilities(self, None)
|
||||
caps = zm.get_zone_capabilities(None)
|
||||
self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30)))
|
||||
|
||||
zm.update_service_capabilities("svc10", "host1", dict(a=99, b=99))
|
||||
caps = zm.get_zone_capabilities(self, None)
|
||||
caps = zm.get_zone_capabilities(None)
|
||||
self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30),
|
||||
svc10_a=(99, 99), svc10_b=(99, 99)))
|
||||
|
||||
zm.update_service_capabilities("svc1", "host3", dict(c=5))
|
||||
caps = zm.get_zone_capabilities(self, None)
|
||||
caps = zm.get_zone_capabilities(None)
|
||||
self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30),
|
||||
svc1_c=(5, 5), svc10_a=(99, 99),
|
||||
svc10_b=(99, 99)))
|
||||
|
||||
caps = zm.get_zone_capabilities(self, 'svc1')
|
||||
self.assertEquals(caps, dict(svc1_a=(2, 20), svc1_b=(3, 30),
|
||||
svc1_c=(5, 5)))
|
||||
caps = zm.get_zone_capabilities(self, 'svc10')
|
||||
self.assertEquals(caps, dict(svc10_a=(99, 99), svc10_b=(99, 99)))
|
||||
|
||||
def test_refresh_from_db_replace_existing(self):
|
||||
zm = zone_manager.ZoneManager()
|
||||
zone_state = zone_manager.ZoneState()
|
||||
|
@ -709,3 +709,33 @@ def check_isinstance(obj, cls):
|
||||
raise Exception(_('Expected object of type: %s') % (str(cls)))
|
||||
# TODO(justinsb): Can we make this better??
|
||||
return cls() # Ugly PyLint hack
|
||||
|
||||
|
||||
def parse_server_string(server_str):
|
||||
"""
|
||||
Parses the given server_string and returns a list of host and port.
|
||||
If it's not a combination of host part and port, the port element
|
||||
is a null string. If the input is invalid expression, return a null
|
||||
list.
|
||||
"""
|
||||
try:
|
||||
# First of all, exclude pure IPv6 address (w/o port).
|
||||
if netaddr.valid_ipv6(server_str):
|
||||
return (server_str, '')
|
||||
|
||||
# Next, check if this is IPv6 address with a port number combination.
|
||||
if server_str.find("]:") != -1:
|
||||
(address, port) = server_str.replace('[', '', 1).split(']:')
|
||||
return (address, port)
|
||||
|
||||
# Third, check if this is a combination of an address and a port
|
||||
if server_str.find(':') == -1:
|
||||
return (server_str, '')
|
||||
|
||||
# This must be a combination of an address and a port
|
||||
(address, port) = server_str.split(':')
|
||||
return (address, port)
|
||||
|
||||
except:
|
||||
LOG.debug(_('Invalid server_string: %s' % server_str))
|
||||
return ('', '')
|
||||
|
@ -486,3 +486,11 @@ class HyperVConnection(driver.ComputeDriver):
|
||||
def update_available_resource(self, ctxt, host):
|
||||
"""This method is supported only by libvirt."""
|
||||
return
|
||||
|
||||
def update_host_status(self):
|
||||
"""See xenapi_conn.py implementation."""
|
||||
pass
|
||||
|
||||
def get_host_stats(self, refresh=False):
|
||||
"""See xenapi_conn.py implementation."""
|
||||
pass
|
||||
|
@ -1582,6 +1582,14 @@ class LibvirtConnection(driver.ComputeDriver):
|
||||
"""See comments of same method in firewall_driver."""
|
||||
self.firewall_driver.unfilter_instance(instance_ref)
|
||||
|
||||
def update_host_status(self):
|
||||
"""See xenapi_conn.py implementation."""
|
||||
pass
|
||||
|
||||
def get_host_stats(self, refresh=False):
|
||||
"""See xenapi_conn.py implementation."""
|
||||
pass
|
||||
|
||||
|
||||
class FirewallDriver(object):
|
||||
def prepare_instance_filter(self, instance, network_info=None):
|
||||
|
@ -428,11 +428,12 @@ class VMOps(object):
|
||||
|
||||
"""
|
||||
# Need to uniquely identify this request.
|
||||
transaction_id = str(uuid.uuid4())
|
||||
key_init_transaction_id = str(uuid.uuid4())
|
||||
# The simple Diffie-Hellman class is used to manage key exchange.
|
||||
dh = SimpleDH()
|
||||
args = {'id': transaction_id, 'pub': str(dh.get_public())}
|
||||
resp = self._make_agent_call('key_init', instance, '', args)
|
||||
key_init_args = {'id': key_init_transaction_id,
|
||||
'pub': str(dh.get_public())}
|
||||
resp = self._make_agent_call('key_init', instance, '', key_init_args)
|
||||
if resp is None:
|
||||
# No response from the agent
|
||||
return
|
||||
@ -446,8 +447,9 @@ class VMOps(object):
|
||||
dh.compute_shared(agent_pub)
|
||||
enc_pass = dh.encrypt(new_pass)
|
||||
# Send the encrypted password
|
||||
args['enc_pass'] = enc_pass
|
||||
resp = self._make_agent_call('password', instance, '', args)
|
||||
password_transaction_id = str(uuid.uuid4())
|
||||
password_args = {'id': password_transaction_id, 'enc_pass': enc_pass}
|
||||
resp = self._make_agent_call('password', instance, '', password_args)
|
||||
if resp is None:
|
||||
# No response from the agent
|
||||
return
|
||||
|
@ -57,6 +57,8 @@ reactor thread if the VM.get_by_name_label or VM.get_record calls block.
|
||||
- suffix "_rec" for record objects
|
||||
"""
|
||||
|
||||
import json
|
||||
import random
|
||||
import sys
|
||||
import urlparse
|
||||
import xmlrpclib
|
||||
@ -67,10 +69,12 @@ from eventlet import timeout
|
||||
|
||||
from nova import context
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova import utils
|
||||
from nova import flags
|
||||
from nova import log as logging
|
||||
from nova.virt import driver
|
||||
from nova.virt.xenapi import vm_utils
|
||||
from nova.virt.xenapi.vmops import VMOps
|
||||
from nova.virt.xenapi.volumeops import VolumeOps
|
||||
|
||||
@ -168,6 +172,13 @@ class XenAPIConnection(driver.ComputeDriver):
|
||||
session = XenAPISession(url, user, pw)
|
||||
self._vmops = VMOps(session)
|
||||
self._volumeops = VolumeOps(session)
|
||||
self._host_state = None
|
||||
|
||||
@property
|
||||
def HostState(self):
|
||||
if not self._host_state:
|
||||
self._host_state = HostState(self.session)
|
||||
return self._host_state
|
||||
|
||||
def init_host(self, host):
|
||||
#FIXME(armando): implement this
|
||||
@ -315,6 +326,16 @@ class XenAPIConnection(driver.ComputeDriver):
|
||||
"""This method is supported only by libvirt."""
|
||||
raise NotImplementedError('This method is supported only by libvirt.')
|
||||
|
||||
def update_host_status(self):
|
||||
"""Update the status info of the host, and return those values
|
||||
to the calling program."""
|
||||
return self.HostState.update_status()
|
||||
|
||||
def get_host_stats(self, refresh=False):
|
||||
"""Return the current state of the host. If 'refresh' is
|
||||
True, run the update first."""
|
||||
return self.HostState.get_host_stats(refresh=refresh)
|
||||
|
||||
|
||||
class XenAPISession(object):
|
||||
"""The session to invoke XenAPI SDK calls"""
|
||||
@ -436,6 +457,65 @@ class XenAPISession(object):
|
||||
raise
|
||||
|
||||
|
||||
class HostState(object):
|
||||
"""Manages information about the XenServer host this compute
|
||||
node is running on.
|
||||
"""
|
||||
def __init__(self, session):
|
||||
super(HostState, self).__init__()
|
||||
self._session = session
|
||||
self._stats = {}
|
||||
self.update_status()
|
||||
|
||||
def get_host_stats(self, refresh=False):
|
||||
"""Return the current state of the host. If 'refresh' is
|
||||
True, run the update first.
|
||||
"""
|
||||
if refresh:
|
||||
self.update_status()
|
||||
return self._stats
|
||||
|
||||
def update_status(self):
|
||||
"""Since under Xenserver, a compute node runs on a given host,
|
||||
we can get host status information using xenapi.
|
||||
"""
|
||||
LOG.debug(_("Updating host stats"))
|
||||
# Make it something unlikely to match any actual instance ID
|
||||
task_id = random.randint(-80000, -70000)
|
||||
task = self._session.async_call_plugin("xenhost", "host_data", {})
|
||||
task_result = self._session.wait_for_task(task, task_id)
|
||||
if not task_result:
|
||||
task_result = json.dumps("")
|
||||
try:
|
||||
data = json.loads(task_result)
|
||||
except ValueError as e:
|
||||
# Invalid JSON object
|
||||
LOG.error(_("Unable to get updated status: %s") % e)
|
||||
return
|
||||
# Get the SR usage
|
||||
try:
|
||||
sr_ref = vm_utils.safe_find_sr(self._session)
|
||||
except exception.NotFound as e:
|
||||
# No SR configured
|
||||
LOG.error(_("Unable to get SR for this host: %s") % e)
|
||||
return
|
||||
sr_rec = self._session.get_xenapi().SR.get_record(sr_ref)
|
||||
total = int(sr_rec["virtual_allocation"])
|
||||
used = int(sr_rec["physical_utilisation"])
|
||||
data["disk_total"] = total
|
||||
data["disk_used"] = used
|
||||
data["disk_available"] = total - used
|
||||
host_memory = data.get('host_memory', None)
|
||||
if host_memory:
|
||||
data["host_memory_total"] = host_memory.get('total', 0)
|
||||
data["host_memory_overhead"] = host_memory.get('overhead', 0)
|
||||
data["host_memory_free"] = host_memory.get('free', 0)
|
||||
data["host_memory_free_computed"] = \
|
||||
host_memory.get('free-computed', 0)
|
||||
del data['host_memory']
|
||||
self._stats = data
|
||||
|
||||
|
||||
def _parse_xmlrpc_value(val):
|
||||
"""Parse the given value as if it were an XML-RPC value. This is
|
||||
sometimes used as the format for the task.result field."""
|
||||
|
@ -53,7 +53,6 @@ class TimeoutError(StandardError):
|
||||
pass
|
||||
|
||||
|
||||
@jsonify
|
||||
def key_init(self, arg_dict):
|
||||
"""Handles the Diffie-Hellman key exchange with the agent to
|
||||
establish the shared secret key used to encrypt/decrypt sensitive
|
||||
@ -72,7 +71,6 @@ def key_init(self, arg_dict):
|
||||
return resp
|
||||
|
||||
|
||||
@jsonify
|
||||
def password(self, arg_dict):
|
||||
"""Writes a request to xenstore that tells the agent to set
|
||||
the root password for the given VM. The password should be
|
||||
@ -80,7 +78,6 @@ def password(self, arg_dict):
|
||||
previous call to key_init. The encrypted password value should
|
||||
be passed as the value for the 'enc_pass' key in arg_dict.
|
||||
"""
|
||||
pub = int(arg_dict["pub"])
|
||||
enc_pass = arg_dict["enc_pass"]
|
||||
arg_dict["value"] = json.dumps({"name": "password", "value": enc_pass})
|
||||
request_id = arg_dict["id"]
|
||||
|
@ -59,12 +59,12 @@ def read_record(self, arg_dict):
|
||||
cmd = ["xenstore-read", "/local/domain/%(dom_id)s/%(path)s" % arg_dict]
|
||||
try:
|
||||
ret, result = _run_command(cmd)
|
||||
return result.rstrip("\n")
|
||||
return result.strip()
|
||||
except pluginlib.PluginError, e:
|
||||
if arg_dict.get("ignore_missing_path", False):
|
||||
cmd = ["xenstore-exists",
|
||||
"/local/domain/%(dom_id)s/%(path)s" % arg_dict]
|
||||
ret, result = _run_command(cmd).strip()
|
||||
ret, result = _run_command(cmd)
|
||||
# If the path exists, the cmd should return "0"
|
||||
if ret != 0:
|
||||
# No such path, so ignore the error and return the
|
||||
@ -171,7 +171,7 @@ def _paths_from_ls(recs):
|
||||
def _run_command(cmd):
|
||||
"""Abstracts out the basics of issuing system commands. If the command
|
||||
returns anything in stderr, a PluginError is raised with that information.
|
||||
Otherwise, the output from stdout is returned.
|
||||
Otherwise, a tuple of (return code, stdout data) is returned.
|
||||
"""
|
||||
pipe = subprocess.PIPE
|
||||
proc = subprocess.Popen(cmd, stdin=pipe, stdout=pipe, stderr=pipe,
|
||||
@ -180,7 +180,7 @@ def _run_command(cmd):
|
||||
err = proc.stderr.read()
|
||||
if err:
|
||||
raise pluginlib.PluginError(err)
|
||||
return proc.stdout.read()
|
||||
return (ret, proc.stdout.read())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -31,3 +31,6 @@ sphinx
|
||||
glance
|
||||
nova-adminclient
|
||||
suds==0.4
|
||||
coverage
|
||||
nosexcover
|
||||
GitPython
|
||||
|
Loading…
Reference in New Issue
Block a user