Merged with trunkw

This commit is contained in:
Nachi Ueno
2011-08-19 13:25:49 -07:00
27 changed files with 450 additions and 805 deletions

View File

@@ -101,6 +101,7 @@ Stephanie Reese <reese.sm@gmail.com>
Thierry Carrez <thierry@openstack.org> Thierry Carrez <thierry@openstack.org>
Todd Willey <todd@ansolabs.com> Todd Willey <todd@ansolabs.com>
Trey Morris <trey.morris@rackspace.com> Trey Morris <trey.morris@rackspace.com>
Troy Toman <troy.toman@rackspace.com>
Tushar Patil <tushar.vitthal.patil@gmail.com> Tushar Patil <tushar.vitthal.patil@gmail.com>
Vasiliy Shlykov <vash@vasiliyshlykov.org> Vasiliy Shlykov <vash@vasiliyshlykov.org>
Vishvananda Ishaya <vishvananda@gmail.com> Vishvananda Ishaya <vishvananda@gmail.com>

View File

@@ -24,7 +24,6 @@ from eventlet import greenthread
from eventlet.green import urllib2 from eventlet.green import urllib2
import exceptions import exceptions
import gettext
import os import os
import sys import sys
import time import time
@@ -38,11 +37,11 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
gettext.install('nova', unicode=1)
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
from nova import rpc from nova import rpc
from nova import service
from nova import utils from nova import utils
from nova import wsgi from nova import wsgi
@@ -141,5 +140,5 @@ if __name__ == '__main__':
acp = AjaxConsoleProxy() acp = AjaxConsoleProxy()
acp.register_listeners() acp.register_listeners()
server = wsgi.Server("AJAX Console Proxy", acp, port=acp_port) server = wsgi.Server("AJAX Console Proxy", acp, port=acp_port)
server.start() service.serve(server)
server.wait() service.wait()

View File

@@ -19,12 +19,14 @@
"""Starter script for Nova API. """Starter script for Nova API.
Starts both the EC2 and OpenStack APIs in separate processes. Starts both the EC2 and OpenStack APIs in separate greenthreads.
""" """
import eventlet
eventlet.monkey_patch()
import os import os
import signal
import sys import sys
@@ -33,32 +35,19 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(
if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")): if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
import nova.service
import nova.utils
from nova import flags from nova import flags
from nova import log as logging
from nova import service
FLAGS = flags.FLAGS from nova import utils
def main():
"""Launch EC2 and OSAPI services."""
nova.utils.Bootstrapper.bootstrap_binary(sys.argv)
nova.utils.monkey_patch()
launcher = nova.service.Launcher()
for api in FLAGS.enabled_apis:
service = nova.service.WSGIService(api)
launcher.launch_service(service)
signal.signal(signal.SIGTERM, lambda *_: launcher.stop())
try:
launcher.wait()
except KeyboardInterrupt:
launcher.stop()
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main()) utils.default_flagfile()
flags.FLAGS(sys.argv)
logging.setup()
utils.monkey_patch()
servers = []
for api in flags.FLAGS.enabled_apis:
servers.append(service.WSGIService(api))
service.serve(*servers)
service.wait()

47
bin/nova-api-ec2 Executable file
View File

@@ -0,0 +1,47 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# 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.
"""Starter script for Nova EC2 API."""
import eventlet
eventlet.monkey_patch()
import os
import sys
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(
sys.argv[0]), os.pardir, os.pardir))
if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")):
sys.path.insert(0, possible_topdir)
from nova import flags
from nova import log as logging
from nova import service
from nova import utils
if __name__ == '__main__':
utils.default_flagfile()
flags.FLAGS(sys.argv)
logging.setup()
utils.monkey_patch()
server = service.WSGIService('ec2')
service.serve(server)
service.wait()

47
bin/nova-api-os Executable file
View File

@@ -0,0 +1,47 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# 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.
"""Starter script for Nova OS API."""
import eventlet
eventlet.monkey_patch()
import os
import sys
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(
sys.argv[0]), os.pardir, os.pardir))
if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")):
sys.path.insert(0, possible_topdir)
from nova import flags
from nova import log as logging
from nova import service
from nova import utils
if __name__ == '__main__':
utils.default_flagfile()
flags.FLAGS(sys.argv)
logging.setup()
utils.monkey_patch()
server = service.WSGIService('osapi')
service.serve(server)
service.wait()

View File

@@ -22,7 +22,6 @@
import eventlet import eventlet
eventlet.monkey_patch() eventlet.monkey_patch()
import gettext
import os import os
import sys import sys
@@ -34,7 +33,6 @@ POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')): if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')):
sys.path.insert(0, POSSIBLE_TOPDIR) sys.path.insert(0, POSSIBLE_TOPDIR)
gettext.install('nova', unicode=1)
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
@@ -46,5 +44,6 @@ if __name__ == '__main__':
flags.FLAGS(sys.argv) flags.FLAGS(sys.argv)
logging.setup() logging.setup()
utils.monkey_patch() utils.monkey_patch()
service.serve() server = service.Service.create(binary='nova-compute')
service.serve(server)
service.wait() service.wait()

View File

@@ -21,7 +21,6 @@
import eventlet import eventlet
eventlet.monkey_patch() eventlet.monkey_patch()
import gettext
import os import os
import sys import sys
@@ -33,7 +32,6 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
gettext.install('nova', unicode=1)
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
@@ -44,5 +42,6 @@ if __name__ == '__main__':
utils.default_flagfile() utils.default_flagfile()
flags.FLAGS(sys.argv) flags.FLAGS(sys.argv)
logging.setup() logging.setup()
service.serve() server = service.Service.create(binary='nova-console')
service.serve(server)
service.wait() service.wait()

View File

@@ -52,7 +52,7 @@ flags.DECLARE('update_dhcp_on_disassociate', 'nova.network.manager')
LOG = logging.getLogger('nova.dhcpbridge') LOG = logging.getLogger('nova.dhcpbridge')
def add_lease(mac, ip_address, _interface): def add_lease(mac, ip_address):
"""Set the IP that was assigned by the DHCP server.""" """Set the IP that was assigned by the DHCP server."""
if FLAGS.fake_rabbit: if FLAGS.fake_rabbit:
LOG.debug(_("leasing ip")) LOG.debug(_("leasing ip"))
@@ -66,13 +66,13 @@ def add_lease(mac, ip_address, _interface):
"args": {"address": ip_address}}) "args": {"address": ip_address}})
def old_lease(mac, ip_address, interface): def old_lease(mac, ip_address):
"""Update just as add lease.""" """Update just as add lease."""
LOG.debug(_("Adopted old lease or got a change of mac")) LOG.debug(_("Adopted old lease or got a change of mac"))
add_lease(mac, ip_address, interface) add_lease(mac, ip_address)
def del_lease(mac, ip_address, _interface): def del_lease(mac, ip_address):
"""Called when a lease expires.""" """Called when a lease expires."""
if FLAGS.fake_rabbit: if FLAGS.fake_rabbit:
LOG.debug(_("releasing ip")) LOG.debug(_("releasing ip"))
@@ -99,8 +99,6 @@ def main():
utils.default_flagfile(flagfile) utils.default_flagfile(flagfile)
argv = FLAGS(sys.argv) argv = FLAGS(sys.argv)
logging.setup() logging.setup()
# check ENV first so we don't break any older deploys
network_id = int(os.environ.get('NETWORK_ID'))
if int(os.environ.get('TESTING', '0')): if int(os.environ.get('TESTING', '0')):
from nova.tests import fake_flags from nova.tests import fake_flags
@@ -115,11 +113,19 @@ def main():
if action in ['add', 'del', 'old']: if action in ['add', 'del', 'old']:
mac = argv[2] mac = argv[2]
ip = argv[3] ip = argv[3]
msg = _("Called %(action)s for mac %(mac)s with ip %(ip)s" msg = _("Called '%(action)s' for mac '%(mac)s' with ip '%(ip)s'") % \
" on interface %(interface)s") % locals() {"action": action,
"mac": mac,
"ip": ip}
LOG.debug(msg) LOG.debug(msg)
globals()[action + '_lease'](mac, ip, interface) globals()[action + '_lease'](mac, ip)
else: else:
try:
network_id = int(os.environ.get('NETWORK_ID'))
except TypeError:
LOG.error(_("Environment variable 'NETWORK_ID' must be set."))
sys.exit(1)
print init_leases(network_id) print init_leases(network_id)
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -20,7 +20,9 @@
"""Starter script for Nova Direct API.""" """Starter script for Nova Direct API."""
import gettext import eventlet
eventlet.monkey_patch()
import os import os
import sys import sys
@@ -32,12 +34,12 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
gettext.install('nova', unicode=1)
from nova import compute from nova import compute
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
from nova import network from nova import network
from nova import service
from nova import utils from nova import utils
from nova import volume from nova import volume
from nova import wsgi from nova import wsgi
@@ -97,5 +99,6 @@ if __name__ == '__main__':
with_auth, with_auth,
host=FLAGS.direct_host, host=FLAGS.direct_host,
port=FLAGS.direct_port) port=FLAGS.direct_port)
server.start()
server.wait() service.serve(server)
service.wait()

View File

@@ -22,7 +22,6 @@
import eventlet import eventlet
eventlet.monkey_patch() eventlet.monkey_patch()
import gettext
import os import os
import sys import sys
@@ -34,7 +33,6 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
gettext.install('nova', unicode=1)
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
@@ -46,5 +44,6 @@ if __name__ == '__main__':
flags.FLAGS(sys.argv) flags.FLAGS(sys.argv)
logging.setup() logging.setup()
utils.monkey_patch() utils.monkey_patch()
service.serve() server = service.Service.create(binary='nova-network')
service.serve(server)
service.wait() service.wait()

View File

@@ -17,11 +17,11 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
""" """Daemon for nova objectstore. Supports S3 API."""
Daemon for nova objectstore. Supports S3 API.
""" import eventlet
eventlet.monkey_patch()
import gettext
import os import os
import sys import sys
@@ -33,10 +33,10 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
gettext.install('nova', unicode=1)
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
from nova import service
from nova import utils from nova import utils
from nova import wsgi from nova import wsgi
from nova.objectstore import s3server from nova.objectstore import s3server
@@ -55,5 +55,5 @@ if __name__ == '__main__':
router, router,
port=FLAGS.s3_port, port=FLAGS.s3_port,
host=FLAGS.s3_host) host=FLAGS.s3_host)
server.start() service.serve(server)
server.wait() service.wait()

View File

@@ -19,7 +19,8 @@
"""VNC Console Proxy Server.""" """VNC Console Proxy Server."""
import eventlet import eventlet
import gettext eventlet.monkey_patch()
import os import os
import sys import sys
@@ -29,7 +30,6 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
gettext.install('nova', unicode=1)
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
@@ -41,7 +41,7 @@ from nova.vnc import auth
from nova.vnc import proxy from nova.vnc import proxy
LOG = logging.getLogger('nova.vnc-proxy') LOG = logging.getLogger('nova.vncproxy')
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@@ -81,7 +81,7 @@ if __name__ == "__main__":
FLAGS(sys.argv) FLAGS(sys.argv)
logging.setup() logging.setup()
LOG.audit(_("Starting nova-vnc-proxy node (version %s)"), LOG.audit(_("Starting nova-vncproxy node (version %s)"),
version.version_string_with_vcs()) version.version_string_with_vcs())
if not (os.path.exists(FLAGS.vncproxy_wwwroot) and if not (os.path.exists(FLAGS.vncproxy_wwwroot) and
@@ -107,13 +107,10 @@ if __name__ == "__main__":
else: else:
with_auth = auth.VNCNovaAuthMiddleware(with_logging) with_auth = auth.VNCNovaAuthMiddleware(with_logging)
service.serve()
server = wsgi.Server("VNC Proxy", server = wsgi.Server("VNC Proxy",
with_auth, with_auth,
host=FLAGS.vncproxy_host, host=FLAGS.vncproxy_host,
port=FLAGS.vncproxy_port) port=FLAGS.vncproxy_port)
server.start()
server.start_tcp(handle_flash_socket_policy, 843, host=FLAGS.vncproxy_host) server.start_tcp(handle_flash_socket_policy, 843, host=FLAGS.vncproxy_host)
service.serve(server)
server.wait() service.wait()

View File

@@ -22,7 +22,6 @@
import eventlet import eventlet
eventlet.monkey_patch() eventlet.monkey_patch()
import gettext
import os import os
import sys import sys
@@ -34,7 +33,6 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
gettext.install('nova', unicode=1)
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
@@ -45,5 +43,7 @@ if __name__ == '__main__':
utils.default_flagfile() utils.default_flagfile()
flags.FLAGS(sys.argv) flags.FLAGS(sys.argv)
logging.setup() logging.setup()
service.serve() utils.monkey_patch()
server = service.Service.create(binary='nova-volume')
service.serve(server)
service.wait() service.wait()

View File

@@ -21,5 +21,7 @@
.. automodule:: nova.scheduler .. automodule:: nova.scheduler
:platform: Unix :platform: Unix
:synopsis: Module that picks a compute node to run a VM instance. :synopsis: Module that picks a compute node to run a VM instance.
.. moduleauthor:: Sandy Walsh <sandy.walsh@rackspace.com>
.. moduleauthor:: Ed Leafe <ed@leafe.com>
.. moduleauthor:: Chris Behrens <cbehrens@codestud.com> .. moduleauthor:: Chris Behrens <cbehrens@codestud.com>
""" """

View File

@@ -14,10 +14,10 @@
# under the License. # under the License.
""" """
The AbsractScheduler is a base class Scheduler for creating instances The AbsractScheduler is an abstract class Scheduler for creating instances
across zones. There are two expansion points to this class for: locally or across zones. Two methods should be overridden in order to
1. Assigning Weights to hosts for requested instances customize the behavior: filter_hosts() and weigh_hosts(). The default
2. Filtering Hosts based on required instance capabilities behavior is to simply select all hosts and weight them the same.
""" """
import operator import operator
@@ -45,44 +45,44 @@ LOG = logging.getLogger('nova.scheduler.abstract_scheduler')
class InvalidBlob(exception.NovaException): class InvalidBlob(exception.NovaException):
message = _("Ill-formed or incorrectly routed 'blob' data sent " message = _("Ill-formed or incorrectly routed 'blob' data sent "
"to instance create request.") "to instance create request.")
class AbstractScheduler(driver.Scheduler): class AbstractScheduler(driver.Scheduler):
"""Base class for creating Schedulers that can work across any nova """Base class for creating Schedulers that can work across any nova
deployment, from simple designs to multiply-nested zones. deployment, from simple designs to multiply-nested zones.
""" """
def _call_zone_method(self, context, method, specs, zones): def _call_zone_method(self, context, method, specs, zones):
"""Call novaclient zone method. Broken out for testing.""" """Call novaclient zone method. Broken out for testing."""
return api.call_zone_method(context, method, specs=specs, zones=zones) return api.call_zone_method(context, method, specs=specs, zones=zones)
def _provision_resource_locally(self, context, build_plan_item, def _provision_resource_locally(self, context, build_plan_item,
request_spec, kwargs): request_spec, kwargs):
"""Create the requested resource in this Zone.""" """Create the requested resource in this Zone."""
host = build_plan_item['hostname'] host = build_plan_item['hostname']
base_options = request_spec['instance_properties'] base_options = request_spec['instance_properties']
image = request_spec['image'] image = request_spec['image']
instance_type = request_spec.get('instance_type')
# TODO(sandy): I guess someone needs to add block_device_mapping # TODO(sandy): I guess someone needs to add block_device_mapping
# support at some point? Also, OS API has no concept of security # support at some point? Also, OS API has no concept of security
# groups. # groups.
instance = compute_api.API().create_db_entry_for_new_instance(context, instance = compute_api.API().create_db_entry_for_new_instance(context,
image, base_options, None, []) instance_type, image, base_options, None, [])
instance_id = instance['id'] instance_id = instance['id']
kwargs['instance_id'] = instance_id kwargs['instance_id'] = instance_id
rpc.cast(context, queue = db.queue_get_for(context, "compute", host)
db.queue_get_for(context, "compute", host), params = {"method": "run_instance", "args": kwargs}
{"method": "run_instance", rpc.cast(context, queue, params)
"args": kwargs})
LOG.debug(_("Provisioning locally via compute node %(host)s") LOG.debug(_("Provisioning locally via compute node %(host)s")
% locals()) % locals())
def _decrypt_blob(self, blob): def _decrypt_blob(self, blob):
"""Returns the decrypted blob or None if invalid. Broken out """Returns the decrypted blob or None if invalid. Broken out
for testing.""" for testing.
"""
decryptor = crypto.decryptor(FLAGS.build_plan_encryption_key) decryptor = crypto.decryptor(FLAGS.build_plan_encryption_key)
try: try:
json_entry = decryptor(blob) json_entry = decryptor(blob)
@@ -92,15 +92,15 @@ class AbstractScheduler(driver.Scheduler):
return None return None
def _ask_child_zone_to_create_instance(self, context, zone_info, def _ask_child_zone_to_create_instance(self, context, zone_info,
request_spec, kwargs): request_spec, kwargs):
"""Once we have determined that the request should go to one """Once we have determined that the request should go to one
of our children, we need to fabricate a new POST /servers/ of our children, we need to fabricate a new POST /servers/
call with the same parameters that were passed into us. call with the same parameters that were passed into us.
Note that we have to reverse engineer from our args to get back the Note that we have to reverse engineer from our args to get back the
image, flavor, ipgroup, etc. since the original call could have image, flavor, ipgroup, etc. since the original call could have
come in from EC2 (which doesn't use these things).""" come in from EC2 (which doesn't use these things).
"""
instance_type = request_spec['instance_type'] instance_type = request_spec['instance_type']
instance_properties = request_spec['instance_properties'] instance_properties = request_spec['instance_properties']
@@ -109,30 +109,26 @@ class AbstractScheduler(driver.Scheduler):
meta = instance_properties['metadata'] meta = instance_properties['metadata']
flavor_id = instance_type['flavorid'] flavor_id = instance_type['flavorid']
reservation_id = instance_properties['reservation_id'] reservation_id = instance_properties['reservation_id']
files = kwargs['injected_files'] files = kwargs['injected_files']
ipgroup = None # Not supported in OS API ... yet ipgroup = None # Not supported in OS API ... yet
child_zone = zone_info['child_zone'] child_zone = zone_info['child_zone']
child_blob = zone_info['child_blob'] child_blob = zone_info['child_blob']
zone = db.zone_get(context, child_zone) zone = db.zone_get(context, child_zone)
url = zone.api_url url = zone.api_url
LOG.debug(_("Forwarding instance create call to child zone %(url)s" LOG.debug(_("Forwarding instance create call to child zone %(url)s"
". ReservationID=%(reservation_id)s") ". ReservationID=%(reservation_id)s") % locals())
% locals())
nova = None nova = None
try: try:
nova = novaclient.Client(zone.username, zone.password, None, url) nova = novaclient.Client(zone.username, zone.password, None, url)
nova.authenticate() nova.authenticate()
except novaclient_exceptions.BadRequest, e: except novaclient_exceptions.BadRequest, e:
raise exception.NotAuthorized(_("Bad credentials attempting " raise exception.NotAuthorized(_("Bad credentials attempting "
"to talk to zone at %(url)s.") % locals()) "to talk to zone at %(url)s.") % locals())
nova.servers.create(name, image_ref, flavor_id, ipgroup, meta, files, nova.servers.create(name, image_ref, flavor_id, ipgroup, meta, files,
child_blob, reservation_id=reservation_id) child_blob, reservation_id=reservation_id)
def _provision_resource_from_blob(self, context, build_plan_item, def _provision_resource_from_blob(self, context, build_plan_item,
instance_id, request_spec, kwargs): instance_id, request_spec, kwargs):
"""Create the requested resource locally or in a child zone """Create the requested resource locally or in a child zone
based on what is stored in the zone blob info. based on what is stored in the zone blob info.
@@ -145,8 +141,8 @@ class AbstractScheduler(driver.Scheduler):
means we gathered the info from one of our children. means we gathered the info from one of our children.
It's possible that, when we decrypt the 'blob' field, it It's possible that, when we decrypt the 'blob' field, it
contains "child_blob" data. In which case we forward the contains "child_blob" data. In which case we forward the
request.""" request.
"""
host_info = None host_info = None
if "blob" in build_plan_item: if "blob" in build_plan_item:
# Request was passed in from above. Is it for us? # Request was passed in from above. Is it for us?
@@ -161,21 +157,20 @@ class AbstractScheduler(driver.Scheduler):
# Valid data ... is it for us? # Valid data ... is it for us?
if 'child_zone' in host_info and 'child_blob' in host_info: if 'child_zone' in host_info and 'child_blob' in host_info:
self._ask_child_zone_to_create_instance(context, host_info, self._ask_child_zone_to_create_instance(context, host_info,
request_spec, kwargs) request_spec, kwargs)
else: else:
self._provision_resource_locally(context, host_info, request_spec, self._provision_resource_locally(context, host_info, request_spec,
kwargs) kwargs)
def _provision_resource(self, context, build_plan_item, instance_id, def _provision_resource(self, context, build_plan_item, instance_id,
request_spec, kwargs): request_spec, kwargs):
"""Create the requested resource in this Zone or a child zone.""" """Create the requested resource in this Zone or a child zone."""
if "hostname" in build_plan_item: if "hostname" in build_plan_item:
self._provision_resource_locally(context, build_plan_item, self._provision_resource_locally(context, build_plan_item,
request_spec, kwargs) request_spec, kwargs)
return return
self._provision_resource_from_blob(context, build_plan_item, self._provision_resource_from_blob(context, build_plan_item,
instance_id, request_spec, kwargs) instance_id, request_spec, kwargs)
def _adjust_child_weights(self, child_results, zones): def _adjust_child_weights(self, child_results, zones):
"""Apply the Scale and Offset values from the Zone definition """Apply the Scale and Offset values from the Zone definition
@@ -185,13 +180,11 @@ class AbstractScheduler(driver.Scheduler):
for zone_id, result in child_results: for zone_id, result in child_results:
if not result: if not result:
continue continue
assert isinstance(zone_id, int) assert isinstance(zone_id, int)
for zone_rec in zones: for zone_rec in zones:
if zone_rec['id'] != zone_id: if zone_rec['id'] != zone_id:
continue continue
for item in result: for item in result:
try: try:
offset = zone_rec['weight_offset'] offset = zone_rec['weight_offset']
@@ -202,10 +195,10 @@ class AbstractScheduler(driver.Scheduler):
item['raw_weight'] = raw_weight item['raw_weight'] = raw_weight
except KeyError: except KeyError:
LOG.exception(_("Bad child zone scaling values " LOG.exception(_("Bad child zone scaling values "
"for Zone: %(zone_id)s") % locals()) "for Zone: %(zone_id)s") % locals())
def schedule_run_instance(self, context, instance_id, request_spec, def schedule_run_instance(self, context, instance_id, request_spec,
*args, **kwargs): *args, **kwargs):
"""This method is called from nova.compute.api to provision """This method is called from nova.compute.api to provision
an instance. However we need to look at the parameters being an instance. However we need to look at the parameters being
passed in to see if this is a request to: passed in to see if this is a request to:
@@ -214,13 +207,11 @@ class AbstractScheduler(driver.Scheduler):
to simply create the instance (either in this zone or to simply create the instance (either in this zone or
a child zone). a child zone).
""" """
# TODO(sandy): We'll have to look for richer specs at some point. # TODO(sandy): We'll have to look for richer specs at some point.
blob = request_spec.get('blob') blob = request_spec.get('blob')
if blob: if blob:
self._provision_resource(context, request_spec, instance_id, self._provision_resource(context, request_spec, instance_id,
request_spec, kwargs) request_spec, kwargs)
return None return None
num_instances = request_spec.get('num_instances', 1) num_instances = request_spec.get('num_instances', 1)
@@ -235,10 +226,9 @@ class AbstractScheduler(driver.Scheduler):
for num in xrange(num_instances): for num in xrange(num_instances):
if not build_plan: if not build_plan:
break break
build_plan_item = build_plan.pop(0) build_plan_item = build_plan.pop(0)
self._provision_resource(context, build_plan_item, instance_id, self._provision_resource(context, build_plan_item, instance_id,
request_spec, kwargs) request_spec, kwargs)
# Returning None short-circuits the routing to Compute (since # Returning None short-circuits the routing to Compute (since
# we've already done it here) # we've already done it here)
@@ -251,58 +241,44 @@ class AbstractScheduler(driver.Scheduler):
anything about the children. anything about the children.
""" """
return self._schedule(context, "compute", request_spec, return self._schedule(context, "compute", request_spec,
*args, **kwargs) *args, **kwargs)
# TODO(sandy): We're only focused on compute instances right now,
# so we don't implement the default "schedule()" method required
# of Schedulers.
def schedule(self, context, topic, request_spec, *args, **kwargs): def schedule(self, context, topic, request_spec, *args, **kwargs):
"""The schedule() contract requires we return the one """The schedule() contract requires we return the one
best-suited host for this request. best-suited host for this request.
""" """
raise driver.NoValidHost(_('No hosts were available')) # TODO(sandy): We're only focused on compute instances right now,
# so we don't implement the default "schedule()" method required
# of Schedulers.
msg = _("No host selection for %s defined." % topic)
raise driver.NoValidHost(msg)
def _schedule(self, context, topic, request_spec, *args, **kwargs): def _schedule(self, context, topic, request_spec, *args, **kwargs):
"""Returns a list of hosts that meet the required specs, """Returns a list of hosts that meet the required specs,
ordered by their fitness. ordered by their fitness.
""" """
if topic != "compute": if topic != "compute":
raise NotImplementedError(_("Scheduler only understands" msg = _("Scheduler only understands Compute nodes (for now)")
" Compute nodes (for now)")) raise NotImplementedError(msg)
num_instances = request_spec.get('num_instances', 1) # Get all available hosts.
instance_type = request_spec['instance_type'] all_hosts = self.zone_manager.service_states.iteritems()
unfiltered_hosts = [(host, services[topic])
for host, services in all_hosts
if topic in services]
weighted = [] # Filter local hosts based on requirements ...
host_list = None filtered_hosts = self.filter_hosts(topic, request_spec,
unfiltered_hosts)
if not filtered_hosts:
LOG.warn(_("No hosts available"))
return []
for i in xrange(num_instances): # weigh the selected hosts.
# Filter local hosts based on requirements ... # weighted_hosts = [{weight=weight, hostname=hostname,
# # capabilities=capabs}, ...]
# The first pass through here will pass 'None' as the weighted_hosts = self.weigh_hosts(topic, request_spec, filtered_hosts)
# host_list.. which tells the filter to build the full # Next, tack on the host weights from the child zones
# list of hosts.
# On a 2nd pass, the filter can modify the host_list with
# any updates it needs to make based on resources that
# may have been consumed from a previous build..
host_list = self.filter_hosts(topic, request_spec, host_list)
if not host_list:
LOG.warn(_("Filter returned no hosts after processing "
"%(i)d of %(num_instances)d instances") % locals())
break
# then weigh the selected hosts.
# weighted = [{weight=weight, hostname=hostname,
# capabilities=capabs}, ...]
weights = self.weigh_hosts(topic, request_spec, host_list)
weights.sort(key=operator.itemgetter('weight'))
best_weight = weights[0]
weighted.append(best_weight)
self.consume_resources(topic, best_weight['capabilities'],
instance_type)
# Next, tack on the best weights from the child zones ...
json_spec = json.dumps(request_spec) json_spec = json.dumps(request_spec)
all_zones = db.zone_get_all(context) all_zones = db.zone_get_all(context)
child_results = self._call_zone_method(context, "select", child_results = self._call_zone_method(context, "select",
@@ -314,90 +290,32 @@ class AbstractScheduler(driver.Scheduler):
# it later if needed. This implicitly builds a zone # it later if needed. This implicitly builds a zone
# path structure. # path structure.
host_dict = {"weight": weighting["weight"], host_dict = {"weight": weighting["weight"],
"child_zone": child_zone, "child_zone": child_zone,
"child_blob": weighting["blob"]} "child_blob": weighting["blob"]}
weighted.append(host_dict) weighted_hosts.append(host_dict)
weighted_hosts.sort(key=operator.itemgetter('weight'))
return weighted_hosts
weighted.sort(key=operator.itemgetter('weight')) def filter_hosts(self, topic, request_spec, host_list):
return weighted """Filter the full host list returned from the ZoneManager. By default,
this method only applies the basic_ram_filter(), meaning all hosts
with at least enough RAM for the requested instance are returned.
def compute_filter(self, hostname, capabilities, request_spec): Override in subclasses to provide greater selectivity.
"""Return whether or not we can schedule to this compute node.
Derived classes should override this and return True if the host
is acceptable for scheduling.
""" """
instance_type = request_spec['instance_type'] def basic_ram_filter(hostname, capabilities, request_spec):
requested_mem = instance_type['memory_mb'] * 1024 * 1024 """Only return hosts with sufficient available RAM."""
return capabilities['host_memory_free'] >= requested_mem instance_type = request_spec['instance_type']
requested_mem = instance_type['memory_mb'] * 1024 * 1024
return capabilities['host_memory_free'] >= requested_mem
def hold_filter_hosts(self, topic, request_spec, hosts=None): return [(host, services) for host, services in host_list
"""Filter the full host list (from the ZoneManager)""" if basic_ram_filter(host, services, request_spec)]
# NOTE(dabo): The logic used by the current _schedule() method
# is incorrect. Since this task is just to refactor the classes,
# I'm not fixing the logic now - that will be the next task.
# So for now this method is just renamed; afterwards this will
# become the filter_hosts() method, and the one below will
# be removed.
filter_name = request_spec.get('filter', None)
# Make sure that the requested filter is legitimate.
selected_filter = host_filter.choose_host_filter(filter_name)
# TODO(sandy): We're only using InstanceType-based specs
# currently. Later we'll need to snoop for more detailed
# host filter requests.
instance_type = request_spec['instance_type']
name, query = selected_filter.instance_type_to_filter(instance_type)
return selected_filter.filter_hosts(self.zone_manager, query)
def filter_hosts(self, topic, request_spec, host_list=None):
"""Return a list of hosts which are acceptable for scheduling.
Return value should be a list of (hostname, capability_dict)s.
Derived classes may override this, but may find the
'<topic>_filter' function more appropriate.
"""
def _default_filter(self, hostname, capabilities, request_spec):
"""Default filter function if there's no <topic>_filter"""
# NOTE(sirp): The default logic is the equivalent to
# AllHostsFilter
return True
filter_func = getattr(self, '%s_filter' % topic, _default_filter)
if host_list is None:
first_run = True
host_list = self.zone_manager.service_states.iteritems()
else:
first_run = False
filtered_hosts = []
for host, services in host_list:
if first_run:
if topic not in services:
continue
services = services[topic]
if filter_func(host, services, request_spec):
filtered_hosts.append((host, services))
return filtered_hosts
def weigh_hosts(self, topic, request_spec, hosts): def weigh_hosts(self, topic, request_spec, hosts):
"""Derived classes may override this to provide more sophisticated """This version assigns a weight of 1 to all hosts, making selection
scheduling objectives of any host basically a random event. Override this method in your
subclass to add logic to prefer one potential host over another.
""" """
# NOTE(sirp): The default logic is the same as the NoopCostFunction
return [dict(weight=1, hostname=hostname, capabilities=capabilities) return [dict(weight=1, hostname=hostname, capabilities=capabilities)
for hostname, capabilities in hosts] for hostname, capabilities in hosts]
def compute_consume(self, capabilities, instance_type):
"""Consume compute resources for selected host"""
requested_mem = max(instance_type['memory_mb'], 0) * 1024 * 1024
capabilities['host_memory_free'] -= requested_mem
def consume_resources(self, topic, capabilities, instance_type):
"""Consume resources for a specific host. 'host' is a tuple
of the hostname and the services"""
consume_func = getattr(self, '%s_consume' % topic, None)
if not consume_func:
return
consume_func(capabilities, instance_type)

View File

@@ -0,0 +1,59 @@
# 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.
"""
The BaseScheduler is the base class Scheduler for creating instances
across zones. There are two expansion points to this class for:
1. Assigning Weights to hosts for requested instances
2. Filtering Hosts based on required instance capabilities
"""
from nova import flags
from nova import log as logging
from nova.scheduler import abstract_scheduler
from nova.scheduler import host_filter
FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.scheduler.base_scheduler')
class BaseScheduler(abstract_scheduler.AbstractScheduler):
"""Base class for creating Schedulers that can work across any nova
deployment, from simple designs to multiply-nested zones.
"""
def filter_hosts(self, topic, request_spec, hosts=None):
"""Filter the full host list (from the ZoneManager)"""
filter_name = request_spec.get('filter', None)
# Make sure that the requested filter is legitimate.
selected_filter = host_filter.choose_host_filter(filter_name)
# TODO(sandy): We're only using InstanceType-based specs
# currently. Later we'll need to snoop for more detailed
# host filter requests.
instance_type = request_spec.get("instance_type", None)
if instance_type is None:
# No way to select; return the specified hosts
return hosts or []
name, query = selected_filter.instance_type_to_filter(instance_type)
return selected_filter.filter_hosts(self.zone_manager, query)
def weigh_hosts(self, topic, request_spec, hosts):
"""Derived classes may override this to provide more sophisticated
scheduling objectives
"""
# NOTE(sirp): The default logic is the same as the NoopCostFunction
return [dict(weight=1, hostname=hostname, capabilities=capabilities)
for hostname, capabilities in hosts]

View File

@@ -20,283 +20,33 @@ either incompatible or insufficient to accept a newly-requested instance
are removed by Host Filter classes from consideration. Those that pass are removed by Host Filter classes from consideration. Those that pass
the filter are then passed on for weighting or other process for ordering. the filter are then passed on for weighting or other process for ordering.
Three filters are included: AllHosts, Flavor & JSON. AllHosts just Filters are in the 'filters' directory that is off the 'scheduler'
returns the full, unfiltered list of hosts. Flavor is a hard coded directory of nova. Additional filters can be created and added to that
matching mechanism based on flavor criteria and JSON is an ad-hoc directory; be sure to add them to the filters/__init__.py file so that
filter grammar. they are part of the nova.schedulers.filters namespace.
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.
""" """
import json import types
from nova import exception from nova import exception
from nova import flags from nova import flags
from nova import log as logging import nova.scheduler
from nova import utils
LOG = logging.getLogger('nova.scheduler.host_filter')
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_string('default_host_filter',
'nova.scheduler.host_filter.AllHostsFilter',
'Which filter to use for filtering hosts.')
class HostFilter(object): def _get_filters():
"""Base class for host filters.""" # Imported here to avoid circular imports
from nova.scheduler import filters
def instance_type_to_filter(self, instance_type): def get_itm(nm):
"""Convert instance_type into a filter for most common use-case.""" return getattr(filters, nm)
raise NotImplementedError()
def filter_hosts(self, zone_manager, query): return [get_itm(itm) for itm in dir(filters)
"""Return a list of hosts that fulfill the filter.""" if (type(get_itm(itm)) is types.TypeType)
raise NotImplementedError() and issubclass(get_itm(itm), filters.AbstractHostFilter)
and get_itm(itm) is not filters.AbstractHostFilter]
def _full_name(self):
"""module.classname of the filter."""
return "%s.%s" % (self.__module__, self.__class__.__name__)
class AllHostsFilter(HostFilter):
""" NOP host filter. 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 InstanceTypeFilter(HostFilter):
"""HostFilter hard-coded to work with InstanceType records."""
def instance_type_to_filter(self, instance_type):
"""Use instance_type to filter hosts."""
return (self._full_name(), instance_type)
def _satisfies_extra_specs(self, capabilities, instance_type):
"""Check that the capabilities provided by the compute service
satisfy the extra specs associated with the instance type"""
if 'extra_specs' not in instance_type:
return True
# Note(lorinh): For now, we are just checking exact matching on the
# values. Later on, we want to handle numerical
# values so we can represent things like number of GPU cards
try:
for key, value in instance_type['extra_specs'].iteritems():
if capabilities[key] != value:
return False
except KeyError:
return False
return True
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']
spec_ram = instance_type['memory_mb']
spec_disk = instance_type['local_gb']
extra_specs = instance_type['extra_specs']
if ((host_ram_mb >= spec_ram) and (disk_bytes >= spec_disk) and
self._satisfies_extra_specs(capabilities, instance_type)):
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 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
FILTERS = [AllHostsFilter, InstanceTypeFilter, JsonFilter]
def choose_host_filter(filter_name=None): def choose_host_filter(filter_name=None):
@@ -307,8 +57,7 @@ def choose_host_filter(filter_name=None):
""" """
if not filter_name: if not filter_name:
filter_name = FLAGS.default_host_filter filter_name = FLAGS.default_host_filter
for filter_class in FILTERS: for filter_class in _get_filters():
host_match = "%s.%s" % (filter_class.__module__, filter_class.__name__) if filter_class.__name__ == filter_name:
if host_match == filter_name:
return filter_class() return filter_class()
raise exception.SchedulerHostFilterNotFound(filter_name=filter_name) raise exception.SchedulerHostFilterNotFound(filter_name=filter_name)

View File

@@ -22,14 +22,12 @@ The cost-function and weights are tabulated, and the host with the least cost
is then selected for provisioning. is then selected for provisioning.
""" """
# TODO(dabo): This class will be removed in the next merge prop; it remains now
# because much of the code will be refactored into different classes.
import collections import collections
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
from nova.scheduler import abstract_scheduler from nova.scheduler import base_scheduler
from nova import utils from nova import utils
from nova import exception from nova import exception
@@ -37,14 +35,16 @@ LOG = logging.getLogger('nova.scheduler.least_cost')
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_list('least_cost_scheduler_cost_functions', flags.DEFINE_list('least_cost_scheduler_cost_functions',
['nova.scheduler.least_cost.noop_cost_fn'], ['nova.scheduler.least_cost.noop_cost_fn'],
'Which cost functions the LeastCostScheduler should use.') 'Which cost functions the LeastCostScheduler should use.')
# TODO(sirp): Once we have enough of these rules, we can break them out into a # TODO(sirp): Once we have enough of these rules, we can break them out into a
# cost_functions.py file (perhaps in a least_cost_scheduler directory) # cost_functions.py file (perhaps in a least_cost_scheduler directory)
flags.DEFINE_integer('noop_cost_fn_weight', 1, flags.DEFINE_integer('noop_cost_fn_weight', 1,
'How much weight to give the noop cost function') 'How much weight to give the noop cost function')
flags.DEFINE_integer('compute_fill_first_cost_fn_weight', 1,
'How much weight to give the fill-first cost function')
def noop_cost_fn(host): def noop_cost_fn(host):
@@ -52,87 +52,20 @@ def noop_cost_fn(host):
return 1 return 1
flags.DEFINE_integer('compute_fill_first_cost_fn_weight', 1,
'How much weight to give the fill-first cost function')
def compute_fill_first_cost_fn(host): def compute_fill_first_cost_fn(host):
"""Prefer hosts that have less ram available, filter_hosts will exclude """Prefer hosts that have less ram available, filter_hosts will exclude
hosts that don't have enough ram""" hosts that don't have enough ram.
hostname, caps = host """
free_mem = caps['host_memory_free'] hostname, service = host
caps = service.get("compute", {})
free_mem = caps.get("host_memory_free", 0)
return free_mem return free_mem
class LeastCostScheduler(abstract_scheduler.AbstractScheduler):
def __init__(self, *args, **kwargs):
self.cost_fns_cache = {}
super(LeastCostScheduler, self).__init__(*args, **kwargs)
def get_cost_fns(self, topic):
"""Returns a list of tuples containing weights and cost functions to
use for weighing hosts
"""
if topic in self.cost_fns_cache:
return self.cost_fns_cache[topic]
cost_fns = []
for cost_fn_str in FLAGS.least_cost_scheduler_cost_functions:
if '.' in cost_fn_str:
short_name = cost_fn_str.split('.')[-1]
else:
short_name = cost_fn_str
cost_fn_str = "%s.%s.%s" % (
__name__, self.__class__.__name__, short_name)
if not (short_name.startswith('%s_' % topic) or
short_name.startswith('noop')):
continue
try:
# NOTE(sirp): import_class is somewhat misnamed since it can
# any callable from a module
cost_fn = utils.import_class(cost_fn_str)
except exception.ClassNotFound:
raise exception.SchedulerCostFunctionNotFound(
cost_fn_str=cost_fn_str)
try:
flag_name = "%s_weight" % cost_fn.__name__
weight = getattr(FLAGS, flag_name)
except AttributeError:
raise exception.SchedulerWeightFlagNotFound(
flag_name=flag_name)
cost_fns.append((weight, cost_fn))
self.cost_fns_cache[topic] = cost_fns
return cost_fns
def weigh_hosts(self, topic, request_spec, hosts):
"""Returns a list of dictionaries of form:
[ {weight: weight, hostname: hostname, capabilities: capabs} ]
"""
cost_fns = self.get_cost_fns(topic)
costs = weighted_sum(domain=hosts, weighted_fns=cost_fns)
weighted = []
weight_log = []
for cost, (hostname, caps) in zip(costs, hosts):
weight_log.append("%s: %s" % (hostname, "%.2f" % cost))
weight_dict = dict(weight=cost, hostname=hostname,
capabilities=caps)
weighted.append(weight_dict)
LOG.debug(_("Weighted Costs => %s") % weight_log)
return weighted
def normalize_list(L): def normalize_list(L):
"""Normalize an array of numbers such that each element satisfies: """Normalize an array of numbers such that each element satisfies:
0 <= e <= 1""" 0 <= e <= 1
"""
if not L: if not L:
return L return L
max_ = max(L) max_ = max(L)
@@ -160,12 +93,10 @@ def weighted_sum(domain, weighted_fns, normalize=True):
score_table = collections.defaultdict(list) score_table = collections.defaultdict(list)
for weight, fn in weighted_fns: for weight, fn in weighted_fns:
scores = [fn(elem) for elem in domain] scores = [fn(elem) for elem in domain]
if normalize: if normalize:
norm_scores = normalize_list(scores) norm_scores = normalize_list(scores)
else: else:
norm_scores = scores norm_scores = scores
for idx, score in enumerate(norm_scores): for idx, score in enumerate(norm_scores):
weighted_score = score * weight weighted_score = score * weight
score_table[idx].append(weighted_score) score_table[idx].append(weighted_score)
@@ -175,5 +106,66 @@ def weighted_sum(domain, weighted_fns, normalize=True):
for idx in sorted(score_table): for idx in sorted(score_table):
elem_score = sum(score_table[idx]) elem_score = sum(score_table[idx])
domain_scores.append(elem_score) domain_scores.append(elem_score)
return domain_scores return domain_scores
class LeastCostScheduler(base_scheduler.BaseScheduler):
def __init__(self, *args, **kwargs):
self.cost_fns_cache = {}
super(LeastCostScheduler, self).__init__(*args, **kwargs)
def get_cost_fns(self, topic):
"""Returns a list of tuples containing weights and cost functions to
use for weighing hosts
"""
if topic in self.cost_fns_cache:
return self.cost_fns_cache[topic]
cost_fns = []
for cost_fn_str in FLAGS.least_cost_scheduler_cost_functions:
if '.' in cost_fn_str:
short_name = cost_fn_str.split('.')[-1]
else:
short_name = cost_fn_str
cost_fn_str = "%s.%s.%s" % (
__name__, self.__class__.__name__, short_name)
if not (short_name.startswith('%s_' % topic) or
short_name.startswith('noop')):
continue
try:
# NOTE(sirp): import_class is somewhat misnamed since it can
# any callable from a module
cost_fn = utils.import_class(cost_fn_str)
except exception.ClassNotFound:
raise exception.SchedulerCostFunctionNotFound(
cost_fn_str=cost_fn_str)
try:
flag_name = "%s_weight" % cost_fn.__name__
weight = getattr(FLAGS, flag_name)
except AttributeError:
raise exception.SchedulerWeightFlagNotFound(
flag_name=flag_name)
cost_fns.append((weight, cost_fn))
self.cost_fns_cache[topic] = cost_fns
return cost_fns
def weigh_hosts(self, topic, request_spec, hosts):
"""Returns a list of dictionaries of form:
[ {weight: weight, hostname: hostname, capabilities: capabs} ]
"""
cost_fns = self.get_cost_fns(topic)
costs = weighted_sum(domain=hosts, weighted_fns=cost_fns)
weighted = []
weight_log = []
for cost, (hostname, service) in zip(costs, hosts):
caps = service[topic]
weight_log.append("%s: %s" % (hostname, "%.2f" % cost))
weight_dict = dict(weight=cost, hostname=hostname,
capabilities=caps)
weighted.append(weight_dict)
LOG.debug(_("Weighted Costs => %s") % weight_log)
return weighted

View File

@@ -77,6 +77,9 @@ class FakeZoneManager(zone_manager.ZoneManager):
'host3': { 'host3': {
'compute': {'host_memory_free': 3221225472}, 'compute': {'host_memory_free': 3221225472},
}, },
'host4': {
'compute': {'host_memory_free': 999999999},
},
} }

View File

@@ -21,6 +21,7 @@ import json
from nova import exception from nova import exception
from nova import test from nova import test
from nova.scheduler import host_filter from nova.scheduler import host_filter
from nova.scheduler import filters
class FakeZoneManager: class FakeZoneManager:
@@ -55,7 +56,7 @@ class HostFilterTestCase(test.TestCase):
def setUp(self): def setUp(self):
super(HostFilterTestCase, self).setUp() super(HostFilterTestCase, self).setUp()
default_host_filter = 'nova.scheduler.host_filter.AllHostsFilter' default_host_filter = 'AllHostsFilter'
self.flags(default_host_filter=default_host_filter) self.flags(default_host_filter=default_host_filter)
self.instance_type = dict(name='tiny', self.instance_type = dict(name='tiny',
memory_mb=50, memory_mb=50,
@@ -98,13 +99,10 @@ class HostFilterTestCase(test.TestCase):
def test_choose_filter(self): def test_choose_filter(self):
# Test default filter ... # Test default filter ...
hf = host_filter.choose_host_filter() hf = host_filter.choose_host_filter()
self.assertEquals(hf._full_name(), self.assertEquals(hf._full_name().split(".")[-1], 'AllHostsFilter')
'nova.scheduler.host_filter.AllHostsFilter')
# Test valid filter ... # Test valid filter ...
hf = host_filter.choose_host_filter( hf = host_filter.choose_host_filter('InstanceTypeFilter')
'nova.scheduler.host_filter.InstanceTypeFilter') self.assertEquals(hf._full_name().split(".")[-1], 'InstanceTypeFilter')
self.assertEquals(hf._full_name(),
'nova.scheduler.host_filter.InstanceTypeFilter')
# Test invalid filter ... # Test invalid filter ...
try: try:
host_filter.choose_host_filter('does not exist') host_filter.choose_host_filter('does not exist')
@@ -113,7 +111,7 @@ class HostFilterTestCase(test.TestCase):
pass pass
def test_all_host_filter(self): def test_all_host_filter(self):
hf = host_filter.AllHostsFilter() hf = filters.AllHostsFilter()
cooked = hf.instance_type_to_filter(self.instance_type) cooked = hf.instance_type_to_filter(self.instance_type)
hosts = hf.filter_hosts(self.zone_manager, cooked) hosts = hf.filter_hosts(self.zone_manager, cooked)
self.assertEquals(10, len(hosts)) self.assertEquals(10, len(hosts))
@@ -121,11 +119,10 @@ class HostFilterTestCase(test.TestCase):
self.assertTrue(host.startswith('host')) self.assertTrue(host.startswith('host'))
def test_instance_type_filter(self): def test_instance_type_filter(self):
hf = host_filter.InstanceTypeFilter() hf = filters.InstanceTypeFilter()
# filter all hosts that can support 50 ram and 500 disk # filter all hosts that can support 50 ram and 500 disk
name, cooked = hf.instance_type_to_filter(self.instance_type) name, cooked = hf.instance_type_to_filter(self.instance_type)
self.assertEquals('nova.scheduler.host_filter.InstanceTypeFilter', self.assertEquals(name.split(".")[-1], 'InstanceTypeFilter')
name)
hosts = hf.filter_hosts(self.zone_manager, cooked) hosts = hf.filter_hosts(self.zone_manager, cooked)
self.assertEquals(6, len(hosts)) self.assertEquals(6, len(hosts))
just_hosts = [host for host, caps in hosts] just_hosts = [host for host, caps in hosts]
@@ -134,21 +131,20 @@ class HostFilterTestCase(test.TestCase):
self.assertEquals('host10', just_hosts[5]) self.assertEquals('host10', just_hosts[5])
def test_instance_type_filter_extra_specs(self): def test_instance_type_filter_extra_specs(self):
hf = host_filter.InstanceTypeFilter() hf = filters.InstanceTypeFilter()
# filter all hosts that can support 50 ram and 500 disk # filter all hosts that can support 50 ram and 500 disk
name, cooked = hf.instance_type_to_filter(self.gpu_instance_type) name, cooked = hf.instance_type_to_filter(self.gpu_instance_type)
self.assertEquals('nova.scheduler.host_filter.InstanceTypeFilter', self.assertEquals(name.split(".")[-1], 'InstanceTypeFilter')
name)
hosts = hf.filter_hosts(self.zone_manager, cooked) hosts = hf.filter_hosts(self.zone_manager, cooked)
self.assertEquals(1, len(hosts)) self.assertEquals(1, len(hosts))
just_hosts = [host for host, caps in hosts] just_hosts = [host for host, caps in hosts]
self.assertEquals('host07', just_hosts[0]) self.assertEquals('host07', just_hosts[0])
def test_json_filter(self): def test_json_filter(self):
hf = host_filter.JsonFilter() hf = filters.JsonFilter()
# filter all hosts that can support 50 ram and 500 disk # filter all hosts that can support 50 ram and 500 disk
name, cooked = hf.instance_type_to_filter(self.instance_type) name, cooked = hf.instance_type_to_filter(self.instance_type)
self.assertEquals('nova.scheduler.host_filter.JsonFilter', name) self.assertEquals(name.split(".")[-1], 'JsonFilter')
hosts = hf.filter_hosts(self.zone_manager, cooked) hosts = hf.filter_hosts(self.zone_manager, cooked)
self.assertEquals(6, len(hosts)) self.assertEquals(6, len(hosts))
just_hosts = [host for host, caps in hosts] just_hosts = [host for host, caps in hosts]
@@ -192,7 +188,6 @@ class HostFilterTestCase(test.TestCase):
raw = ['in', '$compute.host_memory_free', 20, 40, 60, 80, 100] raw = ['in', '$compute.host_memory_free', 20, 40, 60, 80, 100]
cooked = json.dumps(raw) cooked = json.dumps(raw)
hosts = hf.filter_hosts(self.zone_manager, cooked) hosts = hf.filter_hosts(self.zone_manager, cooked)
self.assertEquals(5, len(hosts)) self.assertEquals(5, len(hosts))
just_hosts = [host for host, caps in hosts] just_hosts = [host for host, caps in hosts]
just_hosts.sort() just_hosts.sort()

View File

@@ -15,6 +15,7 @@
""" """
Tests For Least Cost Scheduler Tests For Least Cost Scheduler
""" """
import copy
from nova import test from nova import test
from nova.scheduler import least_cost from nova.scheduler import least_cost
@@ -81,7 +82,7 @@ class LeastCostSchedulerTestCase(test.TestCase):
super(LeastCostSchedulerTestCase, self).tearDown() super(LeastCostSchedulerTestCase, self).tearDown()
def assertWeights(self, expected, num, request_spec, hosts): def assertWeights(self, expected, num, request_spec, hosts):
weighted = self.sched.weigh_hosts(num, request_spec, hosts) weighted = self.sched.weigh_hosts("compute", request_spec, hosts)
self.assertDictListMatch(weighted, expected, approx_equal=True) self.assertDictListMatch(weighted, expected, approx_equal=True)
def test_no_hosts(self): def test_no_hosts(self):
@@ -122,19 +123,24 @@ class LeastCostSchedulerTestCase(test.TestCase):
self.flags(least_cost_scheduler_cost_functions=[ self.flags(least_cost_scheduler_cost_functions=[
'nova.scheduler.least_cost.compute_fill_first_cost_fn'], 'nova.scheduler.least_cost.compute_fill_first_cost_fn'],
compute_fill_first_cost_fn_weight=1) compute_fill_first_cost_fn_weight=1)
num = 1 num = 1
instance_type = {'memory_mb': 1024} instance_type = {'memory_mb': 1024}
request_spec = {'instance_type': instance_type} request_spec = {'instance_type': instance_type}
hosts = self.sched.filter_hosts('compute', request_spec, None) svc_states = self.sched.zone_manager.service_states.iteritems()
all_hosts = [(host, services["compute"])
for host, services in svc_states
if "compute" in services]
hosts = self.sched.filter_hosts('compute', request_spec, all_hosts)
expected = [] expected = []
for idx, (hostname, caps) in enumerate(hosts): for idx, (hostname, services) in enumerate(hosts):
caps = copy.deepcopy(services["compute"])
# Costs are normalized so over 10 hosts, each host with increasing # Costs are normalized so over 10 hosts, each host with increasing
# free ram will cost 1/N more. Since the lowest cost host has some # free ram will cost 1/N more. Since the lowest cost host has some
# free ram, we add in the 1/N for the base_cost # free ram, we add in the 1/N for the base_cost
weight = 0.1 + (0.1 * idx) weight = 0.1 + (0.1 * idx)
weight_dict = dict(weight=weight, hostname=hostname) wtd_dict = dict(hostname=hostname, weight=weight,
expected.append(weight_dict) capabilities=caps)
expected.append(wtd_dict)
self.assertWeights(expected, num, request_spec, hosts) self.assertWeights(expected, num, request_spec, hosts)

View File

@@ -32,6 +32,7 @@ from nova import context
from nova import exception from nova import exception
from nova import test from nova import test
from nova import wsgi from nova import wsgi
from nova.api import auth
from nova.api import ec2 from nova.api import ec2
from nova.api.ec2 import apirequest from nova.api.ec2 import apirequest
from nova.api.ec2 import cloud from nova.api.ec2 import cloud
@@ -199,7 +200,7 @@ class ApiEc2TestCase(test.TestCase):
# NOTE(vish): skipping the Authorizer # NOTE(vish): skipping the Authorizer
roles = ['sysadmin', 'netadmin'] roles = ['sysadmin', 'netadmin']
ctxt = context.RequestContext('fake', 'fake', roles=roles) ctxt = context.RequestContext('fake', 'fake', roles=roles)
self.app = wsgi.InjectContext(ctxt, self.app = auth.InjectContext(ctxt,
ec2.Requestify(ec2.Authorizer(ec2.Executor()), ec2.Requestify(ec2.Authorizer(ec2.Executor()),
'nova.api.ec2.cloud.CloudController')) 'nova.api.ec2.cloud.CloudController'))

View File

@@ -487,6 +487,17 @@ class CloudTestCase(test.TestCase):
db.service_destroy(self.context, comp1['id']) db.service_destroy(self.context, comp1['id'])
db.service_destroy(self.context, comp2['id']) db.service_destroy(self.context, comp2['id'])
def test_describe_instances_deleted(self):
args1 = {'reservation_id': 'a', 'image_ref': 1, 'host': 'host1'}
inst1 = db.instance_create(self.context, args1)
args2 = {'reservation_id': 'b', 'image_ref': 1, 'host': 'host1'}
inst2 = db.instance_create(self.context, args2)
db.instance_destroy(self.context, inst1.id)
result = self.cloud.describe_instances(self.context)
result = result['reservationSet'][0]['instancesSet']
self.assertEqual(result[0]['instanceId'],
ec2utils.id_to_ec2_id(inst2.id))
def _block_device_mapping_create(self, instance_id, mappings): def _block_device_mapping_create(self, instance_id, mappings):
volumes = [] volumes = []
for bdm in mappings: for bdm in mappings:

View File

@@ -76,3 +76,20 @@ class DbApiTestCase(test.TestCase):
self.assertEqual(instance['id'], result['id']) self.assertEqual(instance['id'], result['id'])
self.assertEqual(result['fixed_ips'][0]['floating_ips'][0].address, self.assertEqual(result['fixed_ips'][0]['floating_ips'][0].address,
'1.2.1.2') '1.2.1.2')
def test_instance_get_all_by_filters(self):
args = {'reservation_id': 'a', 'image_ref': 1, 'host': 'host1'}
inst1 = db.instance_create(self.context, args)
inst2 = db.instance_create(self.context, args)
result = db.instance_get_all_by_filters(self.context, {})
self.assertTrue(2, len(result))
def test_instance_get_all_by_filters_deleted(self):
args1 = {'reservation_id': 'a', 'image_ref': 1, 'host': 'host1'}
inst1 = db.instance_create(self.context, args1)
args2 = {'reservation_id': 'b', 'image_ref': 1, 'host': 'host1'}
inst2 = db.instance_create(self.context, args2)
db.instance_destroy(self.context, inst1.id)
result = db.instance_get_all_by_filters(self.context.elevated(), {})
self.assertEqual(1, len(result))
self.assertEqual(result[0].id, inst2.id)

View File

@@ -1,200 +0,0 @@
# 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 Filters.
"""
import json
from nova import exception
from nova import test
from nova.scheduler import host_filter
class FakeZoneManager:
pass
class HostFilterTestCase(test.TestCase):
"""Test case for host filters."""
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):
super(HostFilterTestCase, self).setUp()
default_host_filter = 'nova.scheduler.host_filter.AllHostsFilter'
self.flags(default_host_filter=default_host_filter)
self.instance_type = dict(name='tiny',
memory_mb=50,
vcpus=10,
local_gb=500,
flavorid=1,
swap=500,
rxtx_quota=30000,
rxtx_cap=200,
extra_specs={})
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 test_choose_filter(self):
# Test default filter ...
hf = host_filter.choose_host_filter()
self.assertEquals(hf._full_name(),
'nova.scheduler.host_filter.AllHostsFilter')
# Test valid filter ...
hf = host_filter.choose_host_filter(
'nova.scheduler.host_filter.InstanceTypeFilter')
self.assertEquals(hf._full_name(),
'nova.scheduler.host_filter.InstanceTypeFilter')
# Test invalid filter ...
try:
host_filter.choose_host_filter('does not exist')
self.fail("Should not find host filter.")
except exception.SchedulerHostFilterNotFound:
pass
def test_all_host_filter(self):
hf = host_filter.AllHostsFilter()
cooked = hf.instance_type_to_filter(self.instance_type)
hosts = hf.filter_hosts(self.zone_manager, cooked)
self.assertEquals(10, len(hosts))
for host, capabilities in hosts:
self.assertTrue(host.startswith('host'))
def test_instance_type_filter(self):
hf = host_filter.InstanceTypeFilter()
# filter all hosts that can support 50 ram and 500 disk
name, cooked = hf.instance_type_to_filter(self.instance_type)
self.assertEquals('nova.scheduler.host_filter.InstanceTypeFilter',
name)
hosts = hf.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_filter(self):
hf = host_filter.JsonFilter()
# filter all hosts that can support 50 ram and 500 disk
name, cooked = hf.instance_type_to_filter(self.instance_type)
self.assertEquals('nova.scheduler.host_filter.JsonFilter', name)
hosts = hf.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 = hf.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 = hf.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 = hf.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:
hf.filter_hosts(self.zone_manager, cooked)
self.fail("Should give KeyError")
except KeyError, e:
pass
self.assertTrue(hf.filter_hosts(self.zone_manager, json.dumps([])))
self.assertTrue(hf.filter_hosts(self.zone_manager, json.dumps({})))
self.assertTrue(hf.filter_hosts(self.zone_manager, json.dumps(
['not', True, False, True, False])))
try:
hf.filter_hosts(self.zone_manager, json.dumps(
'not', True, False, True, False))
self.fail("Should give KeyError")
except KeyError, e:
pass
self.assertFalse(hf.filter_hosts(self.zone_manager,
json.dumps(['=', '$foo', 100])))
self.assertFalse(hf.filter_hosts(self.zone_manager,
json.dumps(['=', '$.....', 100])))
self.assertFalse(hf.filter_hosts(self.zone_manager,
json.dumps(
['>', ['and', ['or', ['not', ['<', ['>=', ['<=', ['in', ]]]]]]]])))
self.assertFalse(hf.filter_hosts(self.zone_manager,
json.dumps(['=', {}, ['>', '$missing....foo']])))

View File

@@ -836,6 +836,7 @@ class LibvirtConnTestCase(test.TestCase):
count = (0 <= str(e.message).find('Unexpected method call')) count = (0 <= str(e.message).find('Unexpected method call'))
shutil.rmtree(os.path.join(FLAGS.instances_path, instance.name)) shutil.rmtree(os.path.join(FLAGS.instances_path, instance.name))
shutil.rmtree(os.path.join(FLAGS.instances_path, '_base'))
self.assertTrue(count) self.assertTrue(count)

View File

@@ -108,11 +108,14 @@ floating_ip_fields = {'id': 0,
vifs = [{'id': 0, vifs = [{'id': 0,
'address': 'DE:AD:BE:EF:00:00', 'address': 'DE:AD:BE:EF:00:00',
'uuid': '00000000-0000-0000-0000-0000000000000000',
'network_id': 0, 'network_id': 0,
'network': FakeModel(**networks[0]), 'network': FakeModel(**networks[0]),
'instance_id': 0}, 'instance_id': 0},
{'id': 1, {'id': 1,
'address': 'DE:AD:BE:EF:00:01', 'address': 'DE:AD:BE:EF:00:01',
'uuid': '00000000-0000-0000-0000-0000000000000001',
'network_id': 0,
'network_id': 1, 'network_id': 1,
'network': FakeModel(**networks[1]), 'network': FakeModel(**networks[1]),
'instance_id': 0}] 'instance_id': 0}]
@@ -163,6 +166,8 @@ class FlatNetworkTestCase(test.TestCase):
'ips': 'DONTCARE', 'ips': 'DONTCARE',
'label': 'test%s' % i, 'label': 'test%s' % i,
'mac': 'DE:AD:BE:EF:00:0%s' % i, 'mac': 'DE:AD:BE:EF:00:0%s' % i,
'vif_uuid': ('00000000-0000-0000-0000-000000000000000%s' %
i),
'rxtx_cap': 'DONTCARE', 'rxtx_cap': 'DONTCARE',
'should_create_vlan': False, 'should_create_vlan': False,
'should_create_bridge': False} 'should_create_bridge': False}