summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChiradeep Vittal <chiradeep@chiradeep-lt2>2011-01-04 15:42:59 -0800
committerChiradeep Vittal <chiradeep@chiradeep-lt2>2011-01-04 15:42:59 -0800
commitc40e41f7fbfac12ce07bbd1ee6bf57d1897e6009 (patch)
tree9cc9a299117fcee66b7668725325beeb04f8cc90
parent91e44607d1454a9c2e258910f009a034fb9cff1c (diff)
parentdd1e36b9690a2c2de18c565c496b25295a13d0aa (diff)
downloadnova-c40e41f7fbfac12ce07bbd1ee6bf57d1897e6009.tar.gz
nova-c40e41f7fbfac12ce07bbd1ee6bf57d1897e6009.tar.xz
nova-c40e41f7fbfac12ce07bbd1ee6bf57d1897e6009.zip
Merge from trunk again
-rwxr-xr-xbin/nova-api-paste109
-rwxr-xr-xbin/nova-manage50
-rw-r--r--doc/source/adminguide/distros/ubuntu.10.04.rst13
-rw-r--r--doc/source/adminguide/getting.started.rst6
-rw-r--r--doc/source/adminguide/multi.node.install.rst4
-rw-r--r--etc/nova-api.conf63
-rw-r--r--nova/api/ec2/__init__.py55
-rw-r--r--nova/api/ec2/cloud.py39
-rw-r--r--nova/api/ec2/metadatarequesthandler.py4
-rw-r--r--nova/api/openstack/__init__.py23
-rw-r--r--nova/api/openstack/auth.py6
-rw-r--r--nova/api/openstack/ratelimiting/__init__.py6
-rw-r--r--nova/compute/api.py4
-rw-r--r--nova/compute/manager.py1
-rw-r--r--nova/db/api.py5
-rw-r--r--nova/db/sqlalchemy/__init__.py21
-rw-r--r--nova/db/sqlalchemy/api.py2
-rw-r--r--nova/db/sqlalchemy/models.py2
-rw-r--r--nova/db/sqlalchemy/session.py4
-rw-r--r--nova/flags.py5
-rw-r--r--nova/scheduler/driver.py5
-rw-r--r--nova/scheduler/simple.py26
-rw-r--r--nova/tests/test_scheduler.py90
-rw-r--r--nova/tests/test_service.py26
-rw-r--r--nova/tests/test_xenapi.py19
-rw-r--r--tools/pip-requires4
26 files changed, 559 insertions, 33 deletions
diff --git a/bin/nova-api-paste b/bin/nova-api-paste
new file mode 100755
index 000000000..6ee833a18
--- /dev/null
+++ b/bin/nova-api-paste
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+# pylint: disable-msg=C0103
+# 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 API."""
+
+import gettext
+import logging
+import os
+import sys
+
+from paste import deploy
+
+# If ../nova/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+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)
+
+gettext.install('nova', unicode=1)
+
+from nova import flags
+from nova import wsgi
+
+LOG = logging.getLogger('nova.api')
+LOG.setLevel(logging.DEBUG)
+LOG.addHandler(logging.StreamHandler())
+
+FLAGS = flags.FLAGS
+
+API_ENDPOINTS = ['ec2', 'openstack']
+
+
+def load_configuration(paste_config):
+ """Load the paste configuration from the config file and return it."""
+ config = None
+ # Try each known name to get the global DEFAULTS, which will give ports
+ for name in API_ENDPOINTS:
+ try:
+ config = deploy.appconfig("config:%s" % paste_config, name=name)
+ except LookupError:
+ pass
+ if config:
+ verbose = config.get('verbose', None)
+ if verbose:
+ FLAGS.verbose = int(verbose) == 1
+ if FLAGS.verbose:
+ logging.getLogger().setLevel(logging.DEBUG)
+ return config
+ LOG.debug(_("Paste config at %s has no secion for known apis"),
+ paste_config)
+ print _("Paste config at %s has no secion for any known apis") % \
+ paste_config
+ os.exit(1)
+
+
+def launch_api(paste_config_file, section, server, port, host):
+ """Launch an api server from the specified port and IP."""
+ LOG.debug(_("Launching %s api on %s:%s"), section, host, port)
+ app = deploy.loadapp('config:%s' % paste_config_file, name=section)
+ server.start(app, int(port), host)
+
+
+def run_app(paste_config_file):
+ LOG.debug(_("Using paste.deploy config at: %s"), configfile)
+ config = load_configuration(paste_config_file)
+ LOG.debug(_("Configuration: %r"), config)
+ server = wsgi.Server()
+ ip = config.get('host', '0.0.0.0')
+ for api in API_ENDPOINTS:
+ port = config.get("%s_port" % api, None)
+ if not port:
+ continue
+ host = config.get("%s_host" % api, ip)
+ launch_api(configfile, api, server, port, host)
+ LOG.debug(_("All api servers launched, now waiting"))
+ server.wait()
+
+
+if __name__ == '__main__':
+ FLAGS(sys.argv)
+ configfiles = ['/etc/nova/nova-api.conf']
+ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
+ configfiles.insert(0,
+ os.path.join(possible_topdir, 'etc', 'nova-api.conf'))
+ for configfile in configfiles:
+ if os.path.exists(configfile):
+ run_app(configfile)
+ break
+ else:
+ LOG.debug(_("Skipping missing configuration: %s"), configfile)
diff --git a/bin/nova-manage b/bin/nova-manage
index 599e02a7e..3416c1a52 100755
--- a/bin/nova-manage
+++ b/bin/nova-manage
@@ -53,6 +53,7 @@
CLI interface for nova management.
"""
+import datetime
import gettext
import logging
import os
@@ -452,6 +453,52 @@ class NetworkCommands(object):
int(network_size), int(vlan_start),
int(vpn_start))
+
+class ServiceCommands(object):
+ """Enable and disable running services"""
+
+ def list(self, host=None, service=None):
+ """Show a list of all running services. Filter by host & service name.
+ args: [host] [service]"""
+ ctxt = context.get_admin_context()
+ now = datetime.datetime.utcnow()
+ services = db.service_get_all(ctxt)
+ if host:
+ services = [s for s in services if s['host'] == host]
+ if service:
+ services = [s for s in services if s['binary'] == service]
+ for svc in services:
+ delta = now - (svc['updated_at'] or svc['created_at'])
+ alive = (delta.seconds <= 15)
+ art = (alive and ":-)") or "XXX"
+ active = 'enabled'
+ if svc['disabled']:
+ active = 'disabled'
+ print "%-10s %-10s %-8s %s %s" % (svc['host'], svc['binary'],
+ active, art,
+ svc['updated_at'])
+
+ def enable(self, host, service):
+ """Enable scheduling for a service
+ args: host service"""
+ ctxt = context.get_admin_context()
+ svc = db.service_get_by_args(ctxt, host, service)
+ if not svc:
+ print "Unable to find service"
+ return
+ db.service_update(ctxt, svc['id'], {'disabled': False})
+
+ def disable(self, host, service):
+ """Disable scheduling for a service
+ args: host service"""
+ ctxt = context.get_admin_context()
+ svc = db.service_get_by_args(ctxt, host, service)
+ if not svc:
+ print "Unable to find service"
+ return
+ db.service_update(ctxt, svc['id'], {'disabled': True})
+
+
CATEGORIES = [
('user', UserCommands),
('project', ProjectCommands),
@@ -459,7 +506,8 @@ CATEGORIES = [
('shell', ShellCommands),
('vpn', VpnCommands),
('floating', FloatingIpCommands),
- ('network', NetworkCommands)]
+ ('network', NetworkCommands),
+ ('service', ServiceCommands)]
def lazy_match(name, key_value_tuples):
diff --git a/doc/source/adminguide/distros/ubuntu.10.04.rst b/doc/source/adminguide/distros/ubuntu.10.04.rst
index ce368fab8..9d856458a 100644
--- a/doc/source/adminguide/distros/ubuntu.10.04.rst
+++ b/doc/source/adminguide/distros/ubuntu.10.04.rst
@@ -16,13 +16,13 @@ Here's a script you can use to install (and then run) Nova on Ubuntu or Debian (
Step 2: Install dependencies
----------------------------
-Nova requires rabbitmq for messaging and optionally you can use redis for storing state, so install these first.
+Nova requires rabbitmq for messaging, so install that first.
*Note:* You must have sudo installed to run these commands as shown here.
::
- sudo apt-get install rabbitmq-server redis-server
+ sudo apt-get install rabbitmq-server
You'll see messages starting with "Reading package lists... Done" and you must confirm by typing Y that you want to continue.
@@ -31,11 +31,10 @@ If you're running on Ubuntu 10.04, you'll need to install Twisted and python-gfl
::
- sudo apt-get install python-twisted
-
- sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 95C71FE2
- sudo sh -c 'echo "deb http://ppa.launchpad.net/openstack/openstack-ppa/ubuntu lucid main" > /etc/apt/sources.list.d/openstackppa.list'
- sudo apt-get update && sudo apt-get install python-gflags
+ sudo add-get install python-software-properties
+ sudo add-apt-repository ppa:nova-core/trunk
+ sudo apt-get update
+ sudo apt-get install python-twisted python-gflags
Once you've done this, continue at Step 3 here: :doc:`../single.node.install`
diff --git a/doc/source/adminguide/getting.started.rst b/doc/source/adminguide/getting.started.rst
index 3e8073606..0cadeb45e 100644
--- a/doc/source/adminguide/getting.started.rst
+++ b/doc/source/adminguide/getting.started.rst
@@ -76,11 +76,11 @@ External unix tools that are required:
* aoetools and vblade-persist (if you use aoe-volumes)
Nova uses cutting-edge versions of many packages. There are ubuntu packages in
-the nova-core ppa. You can use add this ppa to your sources list on an ubuntu
-machine with the following commands::
+the nova-core trunk ppa. You can use add this ppa to your sources list on an
+ubuntu machine with the following commands::
sudo apt-get install -y python-software-properties
- sudo add-apt-repository ppa:nova-core/ppa
+ sudo add-apt-repository ppa:nova-core/trunk
Recommended
-----------
diff --git a/doc/source/adminguide/multi.node.install.rst b/doc/source/adminguide/multi.node.install.rst
index 40490e71e..a652e44b7 100644
--- a/doc/source/adminguide/multi.node.install.rst
+++ b/doc/source/adminguide/multi.node.install.rst
@@ -46,12 +46,12 @@ Assumptions
Step 1 Use apt-get to get the latest code
-----------------------------------------
-1. Setup Nova PPA with https://launchpad.net/~nova-core/+archive/ppa.
+1. Setup Nova PPA with https://launchpad.net/~nova-core/+archive/trunk.
::
sudo apt-get install python-software-properties
- sudo add-apt-repository ppa:nova-core/ppa
+ sudo add-apt-repository ppa:nova-core/trunk
2. Run update.
diff --git a/etc/nova-api.conf b/etc/nova-api.conf
new file mode 100644
index 000000000..c5dd0aaec
--- /dev/null
+++ b/etc/nova-api.conf
@@ -0,0 +1,63 @@
+[DEFAULT]
+verbose = 1
+ec2_port = 8773
+ec2_address = 0.0.0.0
+openstack_port = 8774
+openstack_address = 0.0.0.0
+
+#######
+# EC2 #
+#######
+
+[composite:ec2]
+use = egg:Paste#urlmap
+/: ec2versions
+/services: ec2api
+/latest: ec2metadata
+/200: ec2metadata
+/1.0: ec2metadata
+
+[pipeline:ec2api]
+pipeline = authenticate router authorizer ec2executor
+
+[filter:authenticate]
+paste.filter_factory = nova.api.ec2:authenticate_factory
+
+[filter:router]
+paste.filter_factory = nova.api.ec2:router_factory
+
+[filter:authorizer]
+paste.filter_factory = nova.api.ec2:authorizer_factory
+
+[app:ec2executor]
+paste.app_factory = nova.api.ec2:executor_factory
+
+[app:ec2versions]
+paste.app_factory = nova.api.ec2:versions_factory
+
+[app:ec2metadata]
+paste.app_factory = nova.api.ec2.metadatarequesthandler:metadata_factory
+
+#############
+# Openstack #
+#############
+
+[composite:openstack]
+use = egg:Paste#urlmap
+/: osversions
+/v1.0: openstackapi
+
+[pipeline:openstackapi]
+pipeline = auth ratelimit osapi
+
+[filter:auth]
+paste.filter_factory = nova.api.openstack.auth:auth_factory
+
+[filter:ratelimit]
+paste.filter_factory = nova.api.openstack.ratelimiting:ratelimit_factory
+
+[app:osapi]
+paste.app_factory = nova.api.openstack:router_factory
+
+[app:osversions]
+paste.app_factory = nova.api.openstack:versions_factory
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index 51d33bcc6..aa3bfaeb4 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -294,10 +294,9 @@ class Executor(wsgi.Application):
args = req.environ['ec2.action_args']
api_request = apirequest.APIRequest(controller, action)
+ result = None
try:
result = api_request.send(context, **args)
- req.headers['Content-Type'] = 'text/xml'
- return result
except exception.ApiError as ex:
if ex.code:
@@ -307,6 +306,12 @@ class Executor(wsgi.Application):
# TODO(vish): do something more useful with unknown exceptions
except Exception as ex:
return self._error(req, type(ex).__name__, str(ex))
+ else:
+ resp = webob.Response()
+ resp.status = 200
+ resp.headers['Content-Type'] = 'text/xml'
+ resp.body = str(result)
+ return resp
def _error(self, req, code, message):
logging.error("%s: %s", code, message)
@@ -318,3 +323,49 @@ class Executor(wsgi.Application):
'<Message>%s</Message></Error></Errors>'
'<RequestID>?</RequestID></Response>' % (code, message))
return resp
+
+
+class Versions(wsgi.Application):
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ """Respond to a request for all EC2 versions."""
+ # available api versions
+ versions = [
+ '1.0',
+ '2007-01-19',
+ '2007-03-01',
+ '2007-08-29',
+ '2007-10-10',
+ '2007-12-15',
+ '2008-02-01',
+ '2008-09-01',
+ '2009-04-04',
+ ]
+ return ''.join('%s\n' % v for v in versions)
+
+
+def authenticate_factory(global_args, **local_args):
+ def authenticator(app):
+ return Authenticate(app)
+ return authenticator
+
+
+def router_factory(global_args, **local_args):
+ def router(app):
+ return Router(app)
+ return router
+
+
+def authorizer_factory(global_args, **local_args):
+ def authorizer(app):
+ return Authorizer(app)
+ return authorizer
+
+
+def executor_factory(global_args, **local_args):
+ return Executor()
+
+
+def versions_factory(global_args, **local_args):
+ return Versions()
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index e09261f00..9fb6307a8 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -188,9 +188,46 @@ class CloudController(object):
return data
def describe_availability_zones(self, context, **kwargs):
+ if ('zone_name' in kwargs and
+ 'verbose' in kwargs['zone_name'] and
+ context.is_admin):
+ return self._describe_availability_zones_verbose(context,
+ **kwargs)
+ else:
+ return self._describe_availability_zones(context, **kwargs)
+
+ def _describe_availability_zones(self, context, **kwargs):
return {'availabilityZoneInfo': [{'zoneName': 'nova',
'zoneState': 'available'}]}
+ def _describe_availability_zones_verbose(self, context, **kwargs):
+ rv = {'availabilityZoneInfo': [{'zoneName': 'nova',
+ 'zoneState': 'available'}]}
+
+ services = db.service_get_all(context)
+ now = db.get_time()
+ hosts = []
+ for host in [service['host'] for service in services]:
+ if not host in hosts:
+ hosts.append(host)
+ for host in hosts:
+ rv['availabilityZoneInfo'].append({'zoneName': '|- %s' % host,
+ 'zoneState': ''})
+ hsvcs = [service for service in services \
+ if service['host'] == host]
+ for svc in hsvcs:
+ delta = now - (svc['updated_at'] or svc['created_at'])
+ alive = (delta.seconds <= FLAGS.service_down_time)
+ art = (alive and ":-)") or "XXX"
+ active = 'enabled'
+ if svc['disabled']:
+ active = 'disabled'
+ rv['availabilityZoneInfo'].append({
+ 'zoneName': '| |- %s' % svc['binary'],
+ 'zoneState': '%s %s %s' % (active, art,
+ svc['updated_at'])})
+ return rv
+
def describe_regions(self, context, region_name=None, **kwargs):
if FLAGS.region_list:
regions = []
@@ -765,6 +802,8 @@ class CloudController(object):
key_name=kwargs.get('key_name'),
user_data=kwargs.get('user_data'),
security_group=kwargs.get('security_group'),
+ availability_zone=kwargs.get('placement', {}).get(
+ 'AvailabilityZone'),
generate_hostname=internal_id_to_ec2_id)
return self._format_run_instances(context,
instances[0]['reservation_id'])
diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py
index f832863a9..a57a6698a 100644
--- a/nova/api/ec2/metadatarequesthandler.py
+++ b/nova/api/ec2/metadatarequesthandler.py
@@ -79,3 +79,7 @@ class MetadataRequestHandler(object):
if data is None:
raise webob.exc.HTTPNotFound()
return self.print_data(data)
+
+
+def metadata_factory(global_args, **local_args):
+ return MetadataRequestHandler()
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index ea6dff004..a1430caed 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -20,7 +20,6 @@
WSGI middleware for OpenStack API controllers.
"""
-import json
import time
import logging
@@ -41,7 +40,6 @@ from nova.api.openstack import images
from nova.api.openstack import ratelimiting
from nova.api.openstack import servers
from nova.api.openstack import sharedipgroups
-from nova.auth import manager
FLAGS = flags.FLAGS
@@ -115,3 +113,24 @@ class APIRouter(wsgi.Router):
controller=sharedipgroups.Controller())
super(APIRouter, self).__init__(mapper)
+
+
+class Versions(wsgi.Application):
+ @webob.dec.wsgify
+ def __call__(self, req):
+ """Respond to a request for all OpenStack API versions."""
+ response = {
+ "versions": [
+ dict(status="CURRENT", id="v1.0")]}
+ metadata = {
+ "application/xml": {
+ "attributes": dict(version=["status", "id"])}}
+ return wsgi.Serializer(req.environ, metadata).to_content_type(response)
+
+
+def router_factory(global_cof, **local_conf):
+ return APIRouter()
+
+
+def versions_factory(global_conf, **local_conf):
+ return Versions()
diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py
index 1dfdd5318..00e817c8d 100644
--- a/nova/api/openstack/auth.py
+++ b/nova/api/openstack/auth.py
@@ -134,3 +134,9 @@ class AuthMiddleware(wsgi.Middleware):
token = self.db.auth_create_token(ctxt, token_dict)
return token, user
return None, None
+
+
+def auth_factory(global_conf, **local_conf):
+ def auth(app):
+ return AuthMiddleware(app)
+ return auth
diff --git a/nova/api/openstack/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py
index cbb4b897e..81b83142f 100644
--- a/nova/api/openstack/ratelimiting/__init__.py
+++ b/nova/api/openstack/ratelimiting/__init__.py
@@ -219,3 +219,9 @@ class WSGIAppProxy(object):
# No delay
return None
return float(resp.getheader('X-Wait-Seconds'))
+
+
+def ratelimit_factory(global_conf, **local_conf):
+ def rl(app):
+ return RateLimitingMiddleware(app)
+ return rl
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 28189e4a9..07c69bd31 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -74,6 +74,7 @@ class ComputeAPI(base.Base):
max_count=1, kernel_id=None, ramdisk_id=None,
display_name='', description='', key_name=None,
key_data=None, security_group='default',
+ availability_zone=None,
user_data=None,
generate_hostname=generate_default_hostname):
"""Create the number of instances requested if quote and
@@ -141,7 +142,8 @@ class ComputeAPI(base.Base):
'display_description': description,
'user_data': user_data or '',
'key_name': key_name,
- 'key_data': key_data}
+ 'key_data': key_data,
+ 'availability_zone': availability_zone}
elevated = context.elevated()
instances = []
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index c9aff75ac..6e8f34347 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -327,6 +327,7 @@ class ComputeManager(manager.Manager):
instance_ref["internal_id"])
return self.driver.get_diagnostics(instance_ref)
+ @exception.wrap_exception
def suspend_instance(self, context, instance_id):
"""suspend the instance with instance_id"""
context = context.elevated()
diff --git a/nova/db/api.py b/nova/db/api.py
index 127f15478..bab698d9d 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -27,6 +27,9 @@ The underlying driver is loaded as a :class:`LazyPluggable`.
:sql_connection: string specifying the sqlalchemy connection to use, like:
`sqlite:///var/lib/nova/nova.sqlite`.
+
+:enable_new_services: when adding a new service to the database, is it in the
+ pool of available hardware (Default: True)
"""
from nova import exception
@@ -37,6 +40,8 @@ from nova import utils
FLAGS = flags.FLAGS
flags.DEFINE_string('db_backend', 'sqlalchemy',
'The backend to use for db')
+flags.DEFINE_boolean('enable_new_services', True,
+ 'Services to be added to the available pool on create')
IMPL = utils.LazyPluggable(FLAGS['db_backend'],
diff --git a/nova/db/sqlalchemy/__init__.py b/nova/db/sqlalchemy/__init__.py
index 3288ebd20..22aa1cfe6 100644
--- a/nova/db/sqlalchemy/__init__.py
+++ b/nova/db/sqlalchemy/__init__.py
@@ -19,6 +19,25 @@
"""
SQLAlchemy database backend
"""
+import logging
+import time
+
+from sqlalchemy.exc import OperationalError
+
+from nova import flags
from nova.db.sqlalchemy import models
-models.register_models()
+
+FLAGS = flags.FLAGS
+
+
+for i in xrange(FLAGS.sql_max_retries):
+ if i > 0:
+ time.sleep(FLAGS.sql_retry_interval)
+
+ try:
+ models.register_models()
+ break
+ except OperationalError:
+ logging.exception(_("Data store is unreachable."
+ " Trying again in %d seconds.") % FLAGS.sql_retry_interval)
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 8e68d12a4..72369d187 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -236,6 +236,8 @@ def service_get_by_args(context, host, binary):
def service_create(context, values):
service_ref = models.Service()
service_ref.update(values)
+ if not FLAGS.enable_new_services:
+ service_ref.disabled = True
service_ref.save()
return service_ref
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index a050aef23..0c9c387fc 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -220,6 +220,8 @@ class Instance(BASE, NovaBase):
launched_at = Column(DateTime)
terminated_at = Column(DateTime)
+ availability_zone = Column(String(255))
+
# User editable field for display in user-facing UIs
display_name = Column(String(255))
display_description = Column(String(255))
diff --git a/nova/db/sqlalchemy/session.py b/nova/db/sqlalchemy/session.py
index e0d84c107..c3876c02a 100644
--- a/nova/db/sqlalchemy/session.py
+++ b/nova/db/sqlalchemy/session.py
@@ -36,7 +36,9 @@ def get_session(autocommit=True, expire_on_commit=False):
global _MAKER
if not _MAKER:
if not _ENGINE:
- _ENGINE = create_engine(FLAGS.sql_connection, echo=False)
+ _ENGINE = create_engine(FLAGS.sql_connection,
+ pool_recycle=FLAGS.sql_idle_timeout,
+ echo=False)
_MAKER = (sessionmaker(bind=_ENGINE,
autocommit=autocommit,
expire_on_commit=expire_on_commit))
diff --git a/nova/flags.py b/nova/flags.py
index d773a7e4c..4b7334927 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -263,6 +263,11 @@ DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'),
DEFINE_string('sql_connection',
'sqlite:///$state_path/nova.sqlite',
'connection string for sql database')
+DEFINE_string('sql_idle_timeout',
+ '3600',
+ 'timeout for idle sql database connections')
+DEFINE_integer('sql_max_retries', 12, 'sql connection attempts')
+DEFINE_integer('sql_retry_interval', 10, 'sql connection retry interval')
DEFINE_string('compute_manager', 'nova.compute.manager.ComputeManager',
'Manager for compute')
diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py
index 08d7033f5..66e46c1b9 100644
--- a/nova/scheduler/driver.py
+++ b/nova/scheduler/driver.py
@@ -37,6 +37,11 @@ class NoValidHost(exception.Error):
pass
+class WillNotSchedule(exception.Error):
+ """The specified host is not up or doesn't exist."""
+ pass
+
+
class Scheduler(object):
"""The base class that all Scheduler clases should inherit from."""
diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py
index f9171ab35..47baf0d73 100644
--- a/nova/scheduler/simple.py
+++ b/nova/scheduler/simple.py
@@ -43,6 +43,19 @@ class SimpleScheduler(chance.ChanceScheduler):
def schedule_run_instance(self, context, instance_id, *_args, **_kwargs):
"""Picks a host that is up and has the fewest running instances."""
instance_ref = db.instance_get(context, instance_id)
+ if instance_ref['availability_zone'] and context.is_admin:
+ zone, _x, host = instance_ref['availability_zone'].partition(':')
+ service = db.service_get_by_args(context.elevated(), host,
+ 'nova-compute')
+ if not self.service_is_up(service):
+ raise driver.WillNotSchedule("Host %s is not alive" % host)
+
+ # TODO(vish): this probably belongs in the manager, if we
+ # can generalize this somehow
+ now = datetime.datetime.utcnow()
+ db.instance_update(context, instance_id, {'host': host,
+ 'scheduled_at': now})
+ return host
results = db.service_get_all_compute_sorted(context)
for result in results:
(service, instance_cores) = result
@@ -62,6 +75,19 @@ class SimpleScheduler(chance.ChanceScheduler):
def schedule_create_volume(self, context, volume_id, *_args, **_kwargs):
"""Picks a host that is up and has the fewest volumes."""
volume_ref = db.volume_get(context, volume_id)
+ if (':' in volume_ref['availability_zone']) and context.is_admin:
+ zone, _x, host = volume_ref['availability_zone'].partition(':')
+ service = db.service_get_by_args(context.elevated(), host,
+ 'nova-volume')
+ if not self.service_is_up(service):
+ raise driver.WillNotSchedule("Host %s not available" % host)
+
+ # TODO(vish): this probably belongs in the manager, if we
+ # can generalize this somehow
+ now = datetime.datetime.utcnow()
+ db.volume_update(context, volume_id, {'host': host,
+ 'scheduled_at': now})
+ return host
results = db.service_get_all_volume_sorted(context)
for result in results:
(service, volume_gigabytes) = result
diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py
index 78e4a1c77..a9937d797 100644
--- a/nova/tests/test_scheduler.py
+++ b/nova/tests/test_scheduler.py
@@ -19,6 +19,8 @@
Tests For Scheduler
"""
+import datetime
+
from nova import context
from nova import db
from nova import flags
@@ -95,7 +97,7 @@ class SimpleDriverTestCase(test.TestCase):
self.manager.delete_user(self.user)
self.manager.delete_project(self.project)
- def _create_instance(self):
+ def _create_instance(self, **kwargs):
"""Create a test instance"""
inst = {}
inst['image_id'] = 'ami-test'
@@ -106,6 +108,7 @@ class SimpleDriverTestCase(test.TestCase):
inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = 0
inst['vcpus'] = 1
+ inst['availability_zone'] = kwargs.get('availability_zone', None)
return db.instance_create(self.context, inst)['id']
def _create_volume(self):
@@ -114,9 +117,33 @@ class SimpleDriverTestCase(test.TestCase):
vol['image_id'] = 'ami-test'
vol['reservation_id'] = 'r-fakeres'
vol['size'] = 1
+ vol['availability_zone'] = 'test'
return db.volume_create(self.context, vol)['id']
- def test_hosts_are_up(self):
+ def test_doesnt_report_disabled_hosts_as_up(self):
+ """Ensures driver doesn't find hosts before they are enabled"""
+ # NOTE(vish): constructing service without create method
+ # because we are going to use it without queue
+ compute1 = service.Service('host1',
+ 'nova-compute',
+ 'compute',
+ FLAGS.compute_manager)
+ compute1.start()
+ compute2 = service.Service('host2',
+ 'nova-compute',
+ 'compute',
+ FLAGS.compute_manager)
+ compute2.start()
+ s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute')
+ s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute')
+ db.service_update(self.context, s1['id'], {'disabled': True})
+ db.service_update(self.context, s2['id'], {'disabled': True})
+ hosts = self.scheduler.driver.hosts_up(self.context, 'compute')
+ self.assertEqual(0, len(hosts))
+ compute1.kill()
+ compute2.kill()
+
+ def test_reports_enabled_hosts_as_up(self):
"""Ensures driver can find the hosts that are up"""
# NOTE(vish): constructing service without create method
# because we are going to use it without queue
@@ -131,7 +158,7 @@ class SimpleDriverTestCase(test.TestCase):
FLAGS.compute_manager)
compute2.start()
hosts = self.scheduler.driver.hosts_up(self.context, 'compute')
- self.assertEqual(len(hosts), 2)
+ self.assertEqual(2, len(hosts))
compute1.kill()
compute2.kill()
@@ -158,6 +185,63 @@ class SimpleDriverTestCase(test.TestCase):
compute1.kill()
compute2.kill()
+ def test_specific_host_gets_instance(self):
+ """Ensures if you set availability_zone it launches on that zone"""
+ compute1 = service.Service('host1',
+ 'nova-compute',
+ 'compute',
+ FLAGS.compute_manager)
+ compute1.start()
+ compute2 = service.Service('host2',
+ 'nova-compute',
+ 'compute',
+ FLAGS.compute_manager)
+ compute2.start()
+ instance_id1 = self._create_instance()
+ compute1.run_instance(self.context, instance_id1)
+ instance_id2 = self._create_instance(availability_zone='nova:host1')
+ host = self.scheduler.driver.schedule_run_instance(self.context,
+ instance_id2)
+ self.assertEqual('host1', host)
+ compute1.terminate_instance(self.context, instance_id1)
+ db.instance_destroy(self.context, instance_id2)
+ compute1.kill()
+ compute2.kill()
+
+ def test_wont_sechedule_if_specified_host_is_down(self):
+ compute1 = service.Service('host1',
+ 'nova-compute',
+ 'compute',
+ FLAGS.compute_manager)
+ compute1.start()
+ s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute')
+ now = datetime.datetime.utcnow()
+ delta = datetime.timedelta(seconds=FLAGS.service_down_time * 2)
+ past = now - delta
+ db.service_update(self.context, s1['id'], {'updated_at': past})
+ instance_id2 = self._create_instance(availability_zone='nova:host1')
+ self.assertRaises(driver.WillNotSchedule,
+ self.scheduler.driver.schedule_run_instance,
+ self.context,
+ instance_id2)
+ db.instance_destroy(self.context, instance_id2)
+ compute1.kill()
+
+ def test_will_schedule_on_disabled_host_if_specified(self):
+ compute1 = service.Service('host1',
+ 'nova-compute',
+ 'compute',
+ FLAGS.compute_manager)
+ compute1.start()
+ s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute')
+ db.service_update(self.context, s1['id'], {'disabled': True})
+ instance_id2 = self._create_instance(availability_zone='nova:host1')
+ host = self.scheduler.driver.schedule_run_instance(self.context,
+ instance_id2)
+ self.assertEqual('host1', host)
+ db.instance_destroy(self.context, instance_id2)
+ compute1.kill()
+
def test_too_many_cores(self):
"""Ensures we don't go over max cores"""
compute1 = service.Service('host1',
diff --git a/nova/tests/test_service.py b/nova/tests/test_service.py
index b30838ad7..9f1a181a0 100644
--- a/nova/tests/test_service.py
+++ b/nova/tests/test_service.py
@@ -22,6 +22,8 @@ Unit Tests for remote procedure calls using queue
import mox
+from nova import context
+from nova import db
from nova import exception
from nova import flags
from nova import rpc
@@ -72,6 +74,30 @@ class ServiceManagerTestCase(test.TestCase):
self.assertEqual(serv.test_method(), 'service')
+class ServiceFlagsTestCase(test.TestCase):
+ def test_service_enabled_on_create_based_on_flag(self):
+ self.flags(enable_new_services=True)
+ host = 'foo'
+ binary = 'nova-fake'
+ app = service.Service.create(host=host, binary=binary)
+ app.start()
+ app.stop()
+ ref = db.service_get(context.get_admin_context(), app.service_id)
+ db.service_destroy(context.get_admin_context(), app.service_id)
+ self.assert_(not ref['disabled'])
+
+ def test_service_disabled_on_create_based_on_flag(self):
+ self.flags(enable_new_services=False)
+ host = 'foo'
+ binary = 'nova-fake'
+ app = service.Service.create(host=host, binary=binary)
+ app.start()
+ app.stop()
+ ref = db.service_get(context.get_admin_context(), app.service_id)
+ db.service_destroy(context.get_admin_context(), app.service_id)
+ self.assert_(ref['disabled'])
+
+
class ServiceTestCase(test.TestCase):
"""Test cases for Services"""
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py
index c95a53af3..33571dad0 100644
--- a/nova/tests/test_xenapi.py
+++ b/nova/tests/test_xenapi.py
@@ -250,15 +250,16 @@ class XenAPIVMTestCase(test.TestCase):
def _create_instance(self):
"""Creates and spawns a test instance"""
- values = {'name': 1, 'id': 1,
- 'project_id': self.project.id,
- 'user_id': self.user.id,
- 'image_id': 1,
- 'kernel_id': 2,
- 'ramdisk_id': 3,
- 'instance_type': 'm1.large',
- 'mac_address': 'aa:bb:cc:dd:ee:ff'
- }
+ values = {
+ 'name': 1,
+ 'id': 1,
+ 'project_id': self.project.id,
+ 'user_id': self.user.id,
+ 'image_id': 1,
+ 'kernel_id': 2,
+ 'ramdisk_id': 3,
+ 'instance_type': 'm1.large',
+ 'mac_address': 'aa:bb:cc:dd:ee:ff'}
instance = db.instance_create(values)
self.conn.spawn(instance)
return instance
diff --git a/tools/pip-requires b/tools/pip-requires
index e9559521b..341043114 100644
--- a/tools/pip-requires
+++ b/tools/pip-requires
@@ -22,4 +22,6 @@ mox==0.5.0
greenlet==0.3.1
nose
bzr
-Twisted>=10.1.0 \ No newline at end of file
+Twisted>=10.1.0
+PasteDeploy
+paste