diff options
| author | Josh Kearney <josh.kearney@rackspace.com> | 2010-12-27 10:53:32 -0600 |
|---|---|---|
| committer | Josh Kearney <josh.kearney@rackspace.com> | 2010-12-27 10:53:32 -0600 |
| commit | 951da0d458755d339a226cc54250921fecc6b735 (patch) | |
| tree | 5080a43dc827479a3346be5cffd1aad2d897c471 | |
| parent | bd46ab4a721da856da5743c9f55ab5e50ec9b60f (diff) | |
| parent | 0ef58bac84f5a5824cf91cc3d583ded2ccb1fd9a (diff) | |
Merged trunk
29 files changed, 407 insertions, 200 deletions
@@ -4,6 +4,7 @@ Anthony Young <sleepsonthefloor@gmail.com> Armando Migliaccio <Armando.Migliaccio@eu.citrix.com> Chris Behrens <cbehrens@codestud.com> Chmouel Boudjnah <chmouel@chmouel.com> +David Pravec <David.Pravec@danix.org> Dean Troyer <dtroyer@gmail.com> Devin Carlen <devin.carlen@gmail.com> Ed Leafe <ed@leafe.com> @@ -26,6 +27,7 @@ Paul Voccio <paul@openstack.org> Rick Clark <rick@openstack.org> Ryan Lane <rlane@wikimedia.org> Ryan Lucio <rlucio@internap.com> +Salvatore Orlando <salvatore.orlando@eu.citrix.com> Sandy Walsh <sandy.walsh@rackspace.com> Soren Hansen <soren.hansen@rackspace.com> Thierry Carrez <thierry@openstack.org> @@ -34,3 +36,4 @@ Trey Morris <trey.morris@rackspace.com> Vishvananda Ishaya <vishvananda@gmail.com> Youcef Laribi <Youcef.Laribi@eu.citrix.com> Zhixue Wu <Zhixue.Wu@citrix.com> + diff --git a/contrib/nova.sh b/contrib/nova.sh index 30df4edb6..da1ba030c 100755 --- a/contrib/nova.sh +++ b/contrib/nova.sh @@ -15,7 +15,7 @@ if [ ! -n "$HOST_IP" ]; then # NOTE(vish): This will just get the first ip in the list, so if you # have more than one eth device set up, this will fail, and # you should explicitly set HOST_IP in your environment - HOST_IP=`ifconfig | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'` + HOST_IP=`LC_ALL=C ifconfig | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'` fi USE_MYSQL=${USE_MYSQL:-0} diff --git a/contrib/puppet/files/production/nova-iptables b/contrib/puppet/files/production/nova-iptables index b7b52df87..61e2ca2b9 100755 --- a/contrib/puppet/files/production/nova-iptables +++ b/contrib/puppet/files/production/nova-iptables @@ -30,6 +30,8 @@ if [ -f /etc/default/nova-iptables ] ; then . /etc/default/nova-iptables fi +export LC_ALL=C + API_PORT=${API_PORT:-"8773"} if [ ! -n "$IP" ]; then diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 803470570..26fed847b 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -24,6 +24,7 @@ Root WSGI middleware for all API controllers. :ec2api_subdomain: subdomain running the EC2 API (default: ec2) """ +import logging import routes import webob.dec diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index d8cd86116..ff123b22e 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -45,10 +45,14 @@ from nova.auth import manager FLAGS = flags.FLAGS -flags.DEFINE_string('nova_api_auth', - 'nova.api.openstack.auth.BasicApiAuthManager', +flags.DEFINE_string('os_api_auth', + 'nova.api.openstack.auth.AuthMiddleware', 'The auth mechanism to use for the OpenStack API implemenation') +flags.DEFINE_string('os_api_ratelimiting', + 'nova.api.openstack.ratelimiting.RateLimitingMiddleware', + 'Default ratelimiting implementation for the Openstack API') + flags.DEFINE_bool('allow_admin_api', False, 'When True, this API service will accept admin operations.') @@ -58,7 +62,10 @@ class API(wsgi.Middleware): """WSGI entry point for all OpenStack API requests.""" def __init__(self): - app = AuthMiddleware(RateLimitingMiddleware(APIRouter())) + auth_middleware = utils.import_class(FLAGS.os_api_auth) + ratelimiting_middleware = \ + utils.import_class(FLAGS.os_api_ratelimiting) + app = auth_middleware(ratelimiting_middleware(APIRouter())) super(API, self).__init__(app) @webob.dec.wsgify @@ -67,101 +74,11 @@ class API(wsgi.Middleware): return req.get_response(self.application) except Exception as ex: logging.warn(_("Caught error: %s") % str(ex)) - logging.debug(traceback.format_exc()) + logging.error(traceback.format_exc()) exc = webob.exc.HTTPInternalServerError(explanation=str(ex)) return faults.Fault(exc) -class AuthMiddleware(wsgi.Middleware): - """Authorize the openstack API request or return an HTTP Forbidden.""" - - def __init__(self, application): - self.auth_driver = utils.import_class(FLAGS.nova_api_auth)() - super(AuthMiddleware, self).__init__(application) - - @webob.dec.wsgify - def __call__(self, req): - if 'X-Auth-Token' not in req.headers: - return self.auth_driver.authenticate(req) - - user = self.auth_driver.authorize_token(req.headers["X-Auth-Token"]) - - if not user: - return faults.Fault(webob.exc.HTTPUnauthorized()) - - req.environ['nova.context'] = context.RequestContext(user, user) - return self.application - - -class RateLimitingMiddleware(wsgi.Middleware): - """Rate limit incoming requests according to the OpenStack rate limits.""" - - def __init__(self, application, service_host=None): - """Create a rate limiting middleware that wraps the given application. - - By default, rate counters are stored in memory. If service_host is - specified, the middleware instead relies on the ratelimiting.WSGIApp - at the given host+port to keep rate counters. - """ - super(RateLimitingMiddleware, self).__init__(application) - if not service_host: - #TODO(gundlach): These limits were based on limitations of Cloud - #Servers. We should revisit them in Nova. - self.limiter = ratelimiting.Limiter(limits={ - 'DELETE': (100, ratelimiting.PER_MINUTE), - 'PUT': (10, ratelimiting.PER_MINUTE), - 'POST': (10, ratelimiting.PER_MINUTE), - 'POST servers': (50, ratelimiting.PER_DAY), - 'GET changes-since': (3, ratelimiting.PER_MINUTE), - }) - else: - self.limiter = ratelimiting.WSGIAppProxy(service_host) - - @webob.dec.wsgify - def __call__(self, req): - """Rate limit the request. - - If the request should be rate limited, return a 413 status with a - Retry-After header giving the time when the request would succeed. - """ - action_name = self.get_action_name(req) - if not action_name: - # Not rate limited - return self.application - delay = self.get_delay(action_name, - req.environ['nova.context'].user_id) - if delay: - # TODO(gundlach): Get the retry-after format correct. - exc = webob.exc.HTTPRequestEntityTooLarge( - explanation=_('Too many requests.'), - headers={'Retry-After': time.time() + delay}) - raise faults.Fault(exc) - return self.application - - def get_delay(self, action_name, username): - """Return the delay for the given action and username, or None if - the action would not be rate limited. - """ - if action_name == 'POST servers': - # "POST servers" is a POST, so it counts against "POST" too. - # Attempt the "POST" first, lest we are rate limited by "POST" but - # use up a precious "POST servers" call. - delay = self.limiter.perform("POST", username=username) - if delay: - return delay - return self.limiter.perform(action_name, username=username) - - def get_action_name(self, req): - """Return the action name for this request.""" - if req.method == 'GET' and 'changes-since' in req.GET: - return 'GET changes-since' - if req.method == 'POST' and req.path_info.startswith('/servers'): - return 'POST servers' - if req.method in ['PUT', 'POST', 'DELETE']: - return req.method - return None - - class APIRouter(wsgi.Router): """ Routes requests on the OpenStack API to the appropriate controller @@ -195,22 +112,3 @@ class APIRouter(wsgi.Router): controller=sharedipgroups.Controller()) super(APIRouter, self).__init__(mapper) - - -def limited(items, req): - """Return a slice of items according to requested offset and limit. - - items - a sliceable - req - wobob.Request possibly containing offset and limit GET variables. - offset is where to start in the list, and limit is the maximum number - of items to return. - - If limit is not specified, 0, or > 1000, defaults to 1000. - """ - offset = int(req.GET.get('offset', 0)) - limit = int(req.GET.get('limit', 0)) - if not limit: - limit = 1000 - limit = min(1000, limit) - range_end = offset + limit - return items[offset:range_end] diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index fcda97ab1..e24e58fd3 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -1,3 +1,20 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 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.import datetime + import datetime import hashlib import json @@ -7,29 +24,45 @@ import webob.exc import webob.dec from nova import auth +from nova import context from nova import db from nova import flags from nova import manager from nova import utils +from nova import wsgi from nova.api.openstack import faults FLAGS = flags.FLAGS -class Context(object): - pass - - -class BasicApiAuthManager(object): - """ Implements a somewhat rudimentary version of OpenStack Auth""" +class AuthMiddleware(wsgi.Middleware): + """Authorize the openstack API request or return an HTTP Forbidden.""" - def __init__(self, db_driver=None): + def __init__(self, application, db_driver=None): if not db_driver: db_driver = FLAGS.db_driver self.db = utils.import_object(db_driver) self.auth = auth.manager.AuthManager() - self.context = Context() - super(BasicApiAuthManager, self).__init__() + super(AuthMiddleware, self).__init__(application) + + @webob.dec.wsgify + def __call__(self, req): + if not self.has_authentication(req): + return self.authenticate(req) + + user = self.get_user_by_authentication(req) + + if not user: + return faults.Fault(webob.exc.HTTPUnauthorized()) + + req.environ['nova.context'] = context.RequestContext(user, user) + return self.application + + def has_authentication(self, req): + return 'X-Auth-Token' in req.headers + + def get_user_by_authentication(self, req): + return self.authorize_token(req.headers["X-Auth-Token"]) def authenticate(self, req): # Unless the request is explicitly made against /<version>/ don't @@ -68,11 +101,12 @@ class BasicApiAuthManager(object): This method will also remove the token if the timestamp is older than 2 days ago. """ - token = self.db.auth_get_token(self.context, token_hash) + ctxt = context.get_admin_context() + token = self.db.auth_get_token(ctxt, token_hash) if token: delta = datetime.datetime.now() - token.created_at if delta.days >= 2: - self.db.auth_destroy_token(self.context, token) + self.db.auth_destroy_token(ctxt, token) else: return self.auth.get_user(token.user_id) return None @@ -84,6 +118,7 @@ class BasicApiAuthManager(object): key - string API key req - webob.Request object """ + ctxt = context.get_admin_context() user = self.auth.get_user_from_access_key(key) if user and user.name == username: token_hash = hashlib.sha1('%s%s%f' % (username, key, @@ -95,6 +130,6 @@ class BasicApiAuthManager(object): token_dict['server_management_url'] = req.url token_dict['storage_url'] = '' token_dict['user_id'] = user.id - token = self.db.auth_create_token(self.context, token_dict) + token = self.db.auth_create_token(ctxt, token_dict) return token, user return None, None diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py new file mode 100644 index 000000000..ac0572c96 --- /dev/null +++ b/nova/api/openstack/common.py @@ -0,0 +1,36 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 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. + + +def limited(items, req): + """Return a slice of items according to requested offset and limit. + + items - a sliceable + req - wobob.Request possibly containing offset and limit GET variables. + offset is where to start in the list, and limit is the maximum number + of items to return. + + If limit is not specified, 0, or > 1000, defaults to 1000. + """ + + offset = int(req.GET.get('offset', 0)) + limit = int(req.GET.get('limit', 0)) + if not limit: + limit = 1000 + limit = min(1000, limit) + range_end = offset + limit + return items[offset:range_end] diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index f23f74fd1..f620d4107 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -18,6 +18,7 @@ from webob import exc from nova.api.openstack import faults +from nova.api.openstack import common from nova.compute import instance_types from nova import wsgi import nova.api.openstack @@ -39,7 +40,7 @@ class Controller(wsgi.Controller): def detail(self, req): """Return all flavors in detail.""" items = [self.show(req, id)['flavor'] for id in self._all_ids()] - items = nova.api.openstack.limited(items, req) + items = common.limited(items, req) return dict(flavors=items) def show(self, req, id): diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 4a0a8e6f1..d3312aba8 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -22,6 +22,8 @@ from nova import utils from nova import wsgi import nova.api.openstack import nova.image.service + +from nova.api.openstack import common from nova.api.openstack import faults @@ -48,11 +50,12 @@ class Controller(wsgi.Controller): """Return all public images in detail.""" try: images = self._service.detail(req.environ['nova.context']) - images = nova.api.openstack.limited(images, req) + images = common.limited(images, req) except NotImplementedError: # Emulate detail() using repeated calls to show() + ctxt = req.environ['nova.context'] images = self._service.index(ctxt) - images = nova.api.openstack.limited(images, req) + images = common.limited(images, req) images = [self._service.show(ctxt, i['id']) for i in images] return dict(images=images) diff --git a/nova/api/openstack/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py index 918caf055..91a8b2e55 100644 --- a/nova/api/openstack/ratelimiting/__init__.py +++ b/nova/api/openstack/ratelimiting/__init__.py @@ -1,3 +1,20 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 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.import datetime + """Rate limiting of arbitrary actions.""" import httplib @@ -6,6 +23,8 @@ import urllib import webob.dec import webob.exc +from nova import wsgi +from nova.api.openstack import faults # Convenience constants for the limits dictionary passed to Limiter(). PER_SECOND = 1 @@ -14,6 +33,83 @@ PER_HOUR = 60 * 60 PER_DAY = 60 * 60 * 24 +class RateLimitingMiddleware(wsgi.Middleware): + """Rate limit incoming requests according to the OpenStack rate limits.""" + + def __init__(self, application, service_host=None): + """Create a rate limiting middleware that wraps the given application. + + By default, rate counters are stored in memory. If service_host is + specified, the middleware instead relies on the ratelimiting.WSGIApp + at the given host+port to keep rate counters. + """ + if not service_host: + #TODO(gundlach): These limits were based on limitations of Cloud + #Servers. We should revisit them in Nova. + self.limiter = Limiter(limits={ + 'DELETE': (100, PER_MINUTE), + 'PUT': (10, PER_MINUTE), + 'POST': (10, PER_MINUTE), + 'POST servers': (50, PER_DAY), + 'GET changes-since': (3, PER_MINUTE), + }) + else: + self.limiter = WSGIAppProxy(service_host) + super(RateLimitingMiddleware, self).__init__(application) + + @webob.dec.wsgify + def __call__(self, req): + """Rate limit the request. + + If the request should be rate limited, return a 413 status with a + Retry-After header giving the time when the request would succeed. + """ + return self.limited_request(req, self.application) + + def limited_request(self, req, application): + """Rate limit the request. + + If the request should be rate limited, return a 413 status with a + Retry-After header giving the time when the request would succeed. + """ + action_name = self.get_action_name(req) + if not action_name: + # Not rate limited + return application + delay = self.get_delay(action_name, + req.environ['nova.context'].user_id) + if delay: + # TODO(gundlach): Get the retry-after format correct. + exc = webob.exc.HTTPRequestEntityTooLarge( + explanation=('Too many requests.'), + headers={'Retry-After': time.time() + delay}) + raise faults.Fault(exc) + return application + + def get_delay(self, action_name, username): + """Return the delay for the given action and username, or None if + the action would not be rate limited. + """ + if action_name == 'POST servers': + # "POST servers" is a POST, so it counts against "POST" too. + # Attempt the "POST" first, lest we are rate limited by "POST" but + # use up a precious "POST servers" call. + delay = self.limiter.perform("POST", username=username) + if delay: + return delay + return self.limiter.perform(action_name, username=username) + + def get_action_name(self, req): + """Return the action name for this request.""" + if req.method == 'GET' and 'changes-since' in req.GET: + return 'GET changes-since' + if req.method == 'POST' and req.path_info.startswith('/servers'): + return 'POST servers' + if req.method in ['PUT', 'POST', 'DELETE']: + return req.method + return None + + class Limiter(object): """Class providing rate limiting of arbitrary actions.""" diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f758b252a..b2d9432e6 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -22,6 +22,7 @@ from webob import exc from nova import exception from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults from nova.auth import manager as auth_manager from nova.compute import api as compute_api @@ -98,7 +99,7 @@ class Controller(wsgi.Controller): """ instance_list = self.compute_api.get_instances( req.environ['nova.context']) - limited_list = nova.api.openstack.limited(instance_list, req) + limited_list = common.limited(instance_list, req) res = [entity_maker(inst)['server'] for inst in limited_list] return _entity_list(res) diff --git a/nova/api/openstack/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py index e805ca9f7..75d02905c 100644 --- a/nova/api/openstack/sharedipgroups.py +++ b/nova/api/openstack/sharedipgroups.py @@ -19,4 +19,22 @@ from nova import wsgi class Controller(wsgi.Controller): - pass + """ The Shared IP Groups Controller for the Openstack API """ + + def index(self, req): + raise NotImplementedError + + def show(self, req, id): + raise NotImplementedError + + def update(self, req, id): + raise NotImplementedError + + def delete(self, req, id): + raise NotImplementedError + + def detail(self, req): + raise NotImplementedError + + def create(self, req): + raise NotImplementedError diff --git a/nova/cloudpipe/bootscript.template b/nova/cloudpipe/bootscript.template index 11578c134..94dea3f87 100755 --- a/nova/cloudpipe/bootscript.template +++ b/nova/cloudpipe/bootscript.template @@ -19,6 +19,7 @@ # This gets zipped and run on the cloudpipe-managed OpenVPN server +export LC_ALL=C export VPN_IP=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{print $$1}'` export BROADCAST=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f3 | awk '{print $$1}'` export DHCP_MASK=`ifconfig | grep 'inet addr:'| grep -v '127.0.0.1' | cut -d: -f4 | awk '{print $$1}'` diff --git a/nova/compute/api.py b/nova/compute/api.py index 1c24c6634..91d6b92a9 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -91,15 +91,16 @@ class ComputeAPI(base.Base): is_vpn = image_id == FLAGS.vpn_image_id if not is_vpn: image = self.image_service.show(context, image_id) - - # If kernel_id/ramdisk_id isn't explicitly set in API call - # we take the defaults from the image's metadata if kernel_id is None: kernel_id = image.get('kernelId', None) if ramdisk_id is None: ramdisk_id = image.get('ramdiskId', None) - - # Make sure we have access to kernel and ramdisk + #No kernel and ramdisk for raw images + if kernel_id == str(FLAGS.null_kernel): + kernel_id = None + ramdisk_id = None + logging.debug("Creating a raw instance") + # Make sure we have access to kernel and ramdisk (if not raw) if kernel_id: self.image_service.show(context, kernel_id) if ramdisk_id: diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 52d0c389d..7e945e4cb 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1175,11 +1175,13 @@ def iscsi_target_create_safe(context, values): ################### +@require_admin_context def auth_destroy_token(_context, token): session = get_session() session.delete(token) +@require_admin_context def auth_get_token(_context, token_hash): session = get_session() tk = session.query(models.AuthToken).\ @@ -1190,6 +1192,7 @@ def auth_get_token(_context, token_hash): return tk +@require_admin_context def auth_create_token(_context, token): tk = models.AuthToken() tk.update(token) diff --git a/nova/tests/api/openstack/__init__.py b/nova/tests/api/openstack/__init__.py index 2e357febe..9e183bd0d 100644 --- a/nova/tests/api/openstack/__init__.py +++ b/nova/tests/api/openstack/__init__.py @@ -17,11 +17,16 @@ import unittest -from nova.api.openstack import limited -from nova.api.openstack import RateLimitingMiddleware +from nova import context +from nova import flags +from nova.api.openstack.ratelimiting import RateLimitingMiddleware +from nova.api.openstack.common import limited from nova.tests.api.fakes import APIStub +from nova import utils from webob import Request +FLAGS = flags.FLAGS + class RateLimitingMiddlewareTest(unittest.TestCase): @@ -46,6 +51,8 @@ class RateLimitingMiddlewareTest(unittest.TestCase): def exhaust(self, middleware, method, url, username, times): req = Request.blank(url, dict(REQUEST_METHOD=method), headers={'X-Auth-User': username}) + req.environ['nova.context'] = context.RequestContext(username, + username) for i in range(times): resp = req.get_response(middleware) self.assertEqual(resp.status_int, 200) @@ -62,7 +69,7 @@ class RateLimitingMiddlewareTest(unittest.TestCase): middleware = RateLimitingMiddleware(APIStub()) self.exhaust(middleware, 'POST', '/servers/4', 'usr1', 10) self.exhaust(middleware, 'POST', '/images/4', 'usr2', 10) - self.assertTrue(set(middleware.limiter._levels) == + self.assertTrue(set(middleware.limiter._levels) == \ set(['usr1:POST', 'usr1:POST servers', 'usr2:POST'])) def test_POST_servers_action_correctly_ratelimited(self): diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 21b8aac1c..79663e43a 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -29,8 +29,11 @@ from nova import exception as exc from nova import flags from nova import utils import nova.api.openstack.auth -from nova.image import service +from nova.api.openstack import auth +from nova.api.openstack import ratelimiting from nova.image import glance +from nova.image import local +from nova.image import service from nova.tests import fake_flags from nova.wsgi import Router @@ -51,10 +54,11 @@ class FakeRouter(Router): return res -def fake_auth_init(self): +def fake_auth_init(self, application): self.db = FakeAuthDatabase() self.context = Context() self.auth = FakeAuthManager() + self.application = application @webob.dec.wsgify @@ -75,28 +79,28 @@ def stub_out_image_service(stubs): def fake_image_show(meh, context, id): return dict(kernelId=1, ramdiskId=1) - stubs.Set(nova.image.local.LocalImageService, 'show', fake_image_show) + stubs.Set(local.LocalImageService, 'show', fake_image_show) def stub_out_auth(stubs): def fake_auth_init(self, app): self.application = app - stubs.Set(nova.api.openstack.AuthMiddleware, + stubs.Set(nova.api.openstack.auth.AuthMiddleware, '__init__', fake_auth_init) - stubs.Set(nova.api.openstack.AuthMiddleware, + stubs.Set(nova.api.openstack.auth.AuthMiddleware, '__call__', fake_wsgi) def stub_out_rate_limiting(stubs): def fake_rate_init(self, app): - super(nova.api.openstack.RateLimitingMiddleware, self).__init__(app) + super(ratelimiting.RateLimitingMiddleware, self).__init__(app) self.application = app - stubs.Set(nova.api.openstack.RateLimitingMiddleware, + stubs.Set(nova.api.openstack.ratelimiting.RateLimitingMiddleware, '__init__', fake_rate_init) - stubs.Set(nova.api.openstack.RateLimitingMiddleware, + stubs.Set(nova.api.openstack.ratelimiting.RateLimitingMiddleware, '__call__', fake_wsgi) @@ -173,7 +177,7 @@ class FakeToken(object): class FakeRequestContext(object): - def __init__(self, user, project): + def __init__(self, user, project, *args, **kwargs): self.user_id = 1 self.project_id = 1 diff --git a/nova/tests/api/openstack/test_auth.py b/nova/tests/api/openstack/test_auth.py index 7b427c2db..489a1dfbf 100644 --- a/nova/tests/api/openstack/test_auth.py +++ b/nova/tests/api/openstack/test_auth.py @@ -34,7 +34,7 @@ class Test(unittest.TestCase): def setUp(self): self.stubs = stubout.StubOutForTesting() - self.stubs.Set(nova.api.openstack.auth.BasicApiAuthManager, + self.stubs.Set(nova.api.openstack.auth.AuthMiddleware, '__init__', fakes.fake_auth_init) self.stubs.Set(context, 'RequestContext', fakes.FakeRequestContext) fakes.FakeAuthManager.auth_data = {} @@ -131,7 +131,7 @@ class Test(unittest.TestCase): class TestLimiter(unittest.TestCase): def setUp(self): self.stubs = stubout.StubOutForTesting() - self.stubs.Set(nova.api.openstack.auth.BasicApiAuthManager, + self.stubs.Set(nova.api.openstack.auth.AuthMiddleware, '__init__', fakes.fake_auth_init) self.stubs.Set(context, 'RequestContext', fakes.FakeRequestContext) fakes.FakeAuthManager.auth_data = {} diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 8dab8de2f..1c155abe4 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -157,7 +157,6 @@ class LibvirtConnTestCase(test.TestCase): (lambda t: t.find('./devices/serial/source').get( 'path').split('/')[1], 'console.log'), (lambda t: t.find('./memory').text, '2097152')] - if rescue: common_checks += [ (lambda t: t.findall('./devices/disk/source')[0].get( diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index b5d3ea395..ed2e4ffde 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -48,6 +48,7 @@ class XenAPIVolumeTestCase(test.TestCase): FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' fakes.stub_out_db_instance_api(self.stubs) + stubs.stub_out_get_target(self.stubs) fake.reset() self.values = {'name': 1, 'id': 1, 'project_id': 'fake', diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 1dacad6a3..a7e592fee 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -18,12 +18,13 @@ from nova.virt import xenapi_conn from nova.virt.xenapi import fake +from nova.virt.xenapi import volume_utils def stubout_session(stubs, cls): - """ Stubs out two methods from XenAPISession """ + """Stubs out two methods from XenAPISession""" def fake_import(self): - """ Stubs out get_imported_xenapi of XenAPISession """ + """Stubs out get_imported_xenapi of XenAPISession""" fake_module = 'nova.virt.xenapi.fake' from_list = ['fake'] return __import__(fake_module, globals(), locals(), from_list, -1) @@ -34,6 +35,14 @@ def stubout_session(stubs, cls): fake_import) +def stub_out_get_target(stubs): + """Stubs out _get_target in volume_utils""" + def fake_get_target(volume_id): + return (None, None) + + stubs.Set(volume_utils, '_get_target', fake_get_target) + + class FakeSessionForVMTests(fake.SessionBase): """ Stubs out a XenAPISession for VM tests """ def __init__(self, uri): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 809a484dc..67b9fc47e 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -512,11 +512,10 @@ class LibvirtConnection(object): if FLAGS.allow_project_net_traffic: net, mask = _get_net_and_mask(network['cidr']) - extra_params = ( - "<parameter name=\"PROJNET\" value=\"%s\" />\n" - "<parameter name=\"PROJMASK\" value=\"%s\" />\n") % ( - net, - mask) + extra_params = ("<parameter name=\"PROJNET\" " + "value=\"%s\" />\n" + "<parameter name=\"PROJMASK\" " + "value=\"%s\" />\n") % (net, mask) else: extra_params = "\n" @@ -802,8 +801,8 @@ class NWFilterFirewall(object): the base filter are all in place. """ - nwfilter_xml = ("<filter name='nova-instance-%s' chain='root'>\n") % ( - instance['name']) + nwfilter_xml = ("<filter name='nova-instance-%s' " + "chain='root'>\n") % instance['name'] if instance['image_id'] == FLAGS.vpn_image_id: nwfilter_xml += " <filterref filter='nova-vpn' />\n" @@ -816,8 +815,8 @@ class NWFilterFirewall(object): for security_group in instance.security_groups: self.ensure_security_group_filter(security_group['id']) - nwfilter_xml += (" <filterref filter='nova-secgroup-%d' />\n") % ( - security_group['id']) + nwfilter_xml += (" <filterref filter='nova-secgroup-%d' " + "/>\n") % security_group['id'] nwfilter_xml += "</filter>" self._define_filter(nwfilter_xml) diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 7a6c9ee71..1eaf31c25 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -235,6 +235,7 @@ class SessionBase(object): elif '.' in name: impl = getattr(self, name.replace('.', '_')) if impl is not None: + def callit(*params): logging.warn('Calling %s %s', name, impl) self._check_session(params) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 89e02c917..2f02f4677 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -43,15 +43,31 @@ XENAPI_POWER_STATE = { 'Crashed': power_state.CRASHED} +class ImageType: + """ + Enumeration class for distinguishing different image types + 0 - kernel/ramdisk image (goes on dom0's filesystem) + 1 - disk image (local SR, partitioned by objectstore plugin) + 2 - raw disk image (local SR, NOT partitioned by plugin) + """ + + KERNEL_RAMDISK = 0 + DISK = 1 + DISK_RAW = 2 + + class VMHelper(HelperBase): """ The class that wraps the helper methods together. """ @classmethod - def create_vm(cls, session, instance, kernel, ramdisk): + def create_vm(cls, session, instance, kernel, ramdisk, pv_kernel=False): """Create a VM record. Returns a Deferred that gives the new - VM reference.""" + VM reference. + the pv_kernel flag indicates whether the guest is HVM or PV + """ + instance_type = instance_types.INSTANCE_TYPES[instance.instance_type] mem = str(long(instance_type['memory_mb']) * 1024 * 1024) vcpus = str(instance_type['vcpus']) @@ -70,9 +86,9 @@ class VMHelper(HelperBase): 'actions_after_reboot': 'restart', 'actions_after_crash': 'destroy', 'PV_bootloader': '', - 'PV_kernel': kernel, - 'PV_ramdisk': ramdisk, - 'PV_args': 'root=/dev/xvda1', + 'PV_kernel': '', + 'PV_ramdisk': '', + 'PV_args': '', 'PV_bootloader_args': '', 'PV_legacy_args': '', 'HVM_boot_policy': '', @@ -84,7 +100,25 @@ class VMHelper(HelperBase): 'user_version': '0', 'other_config': {}, } - logging.debug(_('Created VM %s...'), instance.name) + #Complete VM configuration record according to the image type + #non-raw/raw with PV kernel/raw in HVM mode + if instance.kernel_id: + rec['PV_bootloader'] = '' + rec['PV_kernel'] = kernel + rec['PV_ramdisk'] = ramdisk + rec['PV_args'] = 'root=/dev/xvda1' + rec['PV_bootloader_args'] = '' + rec['PV_legacy_args'] = '' + else: + if pv_kernel: + rec['PV_args'] = 'noninteractive' + rec['PV_bootloader'] = 'pygrub' + else: + rec['HVM_boot_policy'] = 'BIOS order' + rec['HVM_boot_params'] = {'order': 'dc'} + rec['platform'] = {'acpi': 'true', 'apic': 'true', + 'pae': 'true', 'viridian': 'true'} + logging.debug('Created VM %s...', instance.name) vm_ref = session.call_xenapi('VM.create', rec) logging.debug(_('Created VM %s as %s.'), instance.name, vm_ref) return vm_ref @@ -170,22 +204,24 @@ class VMHelper(HelperBase): return vif_ref @classmethod - def fetch_image(cls, session, image, user, project, use_sr): - """use_sr: True to put the image as a VDI in an SR, False to place - it on dom0's filesystem. The former is for VM disks, the latter for - its kernel and ramdisk (if external kernels are being used). - Returns a Deferred that gives the new VDI UUID.""" - + def fetch_image(cls, session, image, user, project, type): + """ + type is interpreted as an ImageType instance + """ url = images.image_url(image) access = AuthManager().get_access_key(user, project) - logging.debug(_("Asking xapi to fetch %s as %s"), url, access) - fn = use_sr and 'get_vdi' or 'get_kernel' + logging.debug("Asking xapi to fetch %s as %s", url, access) + fn = (type != ImageType.KERNEL_RAMDISK) and 'get_vdi' or 'get_kernel' args = {} args['src_url'] = url args['username'] = access args['password'] = user.secret - if use_sr: + args['add_partition'] = 'false' + args['raw'] = 'false' + if type != ImageType.KERNEL_RAMDISK: args['add_partition'] = 'true' + if type == ImageType.DISK_RAW: + args['raw'] = 'true' task = session.async_call_plugin('objectstore', fn, args) #FIXME(armando): find a solution to missing instance_id #with Josh Kearney @@ -193,6 +229,22 @@ class VMHelper(HelperBase): return uuid @classmethod + def lookup_image(cls, session, vdi_ref): + logging.debug("Looking up vdi %s for PV kernel", vdi_ref) + fn = "is_vdi_pv" + args = {} + args['vdi-ref'] = vdi_ref + #TODO: Call proper function in plugin + task = session.async_call_plugin('objectstore', fn, args) + pv_str = session.wait_for_task(task) + if pv_str.lower() == 'true': + pv = True + elif pv_str.lower() == 'false': + pv = False + logging.debug("PV Kernel in VDI:%d", pv) + return pv + + @classmethod def lookup(cls, session, i): """Look the instance i up, and returns it if available""" vms = session.get_xenapi().VM.get_by_name_label(i) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 7571fe2e4..96c0d3e64 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -29,6 +29,7 @@ from nova.auth.manager import AuthManager from nova.compute import power_state from nova.virt.xenapi.network_utils import NetworkHelper from nova.virt.xenapi.vm_utils import VMHelper +from nova.virt.xenapi.vm_utils import ImageType class VMOps(object): @@ -64,16 +65,30 @@ class VMOps(object): user = AuthManager().get_user(instance.user_id) project = AuthManager().get_project(instance.project_id) - vdi_uuid = VMHelper.fetch_image( - self._session, instance.image_id, user, project, True) - kernel = VMHelper.fetch_image( - self._session, instance.kernel_id, user, project, False) - ramdisk = VMHelper.fetch_image( - self._session, instance.ramdisk_id, user, project, False) + #if kernel is not present we must download a raw disk + if instance.kernel_id: + disk_image_type = ImageType.DISK + else: + disk_image_type = ImageType.DISK_RAW + vdi_uuid = VMHelper.fetch_image(self._session, + instance.image_id, user, project, disk_image_type) vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid) - vm_ref = VMHelper.create_vm( - self._session, instance, kernel, ramdisk) + #Have a look at the VDI and see if it has a PV kernel + pv_kernel = False + if not instance.kernel_id: + pv_kernel = VMHelper.lookup_image(self._session, vdi_ref) + kernel = None + if instance.kernel_id: + kernel = VMHelper.fetch_image(self._session, + instance.kernel_id, user, project, ImageType.KERNEL_RAMDISK) + ramdisk = None + if instance.ramdisk_id: + ramdisk = VMHelper.fetch_image(self._session, + instance.ramdisk_id, user, project, ImageType.KERNEL_RAMDISK) + vm_ref = VMHelper.create_vm(self._session, + instance, kernel, ramdisk, pv_kernel) VMHelper.create_vbd(self._session, vm_ref, vdi_ref, 0, True) + if network_ref: VMHelper.create_vif(self._session, vm_ref, network_ref, instance.mac_address) diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py index a0c0a67d4..1ca813bcf 100644 --- a/nova/virt/xenapi/volume_utils.py +++ b/nova/virt/xenapi/volume_utils.py @@ -60,13 +60,11 @@ class VolumeHelper(HelperBase): 'port': info['targetPort'], 'targetIQN': info['targetIQN'], 'chapuser': info['chapuser'], - 'chappassword': info['chappassword'] - } + 'chappassword': info['chappassword']} else: record = {'target': info['targetHost'], 'port': info['targetPort'], - 'targetIQN': info['targetIQN'] - } + 'targetIQN': info['targetIQN']} try: sr_ref = session.get_xenapi().SR.create( session.get_xenapi_host(), diff --git a/plugins/xenapi/etc/xapi.d/plugins/objectstore b/plugins/xenapi/etc/xapi.d/plugins/objectstore index 271e7337f..8ee2f748d 100644 --- a/plugins/xenapi/etc/xapi.d/plugins/objectstore +++ b/plugins/xenapi/etc/xapi.d/plugins/objectstore @@ -43,24 +43,43 @@ SECTOR_SIZE = 512 MBR_SIZE_SECTORS = 63 MBR_SIZE_BYTES = MBR_SIZE_SECTORS * SECTOR_SIZE - +def is_vdi_pv(session,args): + logging.debug("Checking wheter VDI has PV kernel") + vdi = exists(args, 'vdi-ref') + pv=with_vdi_in_dom0(session, vdi, False, + lambda dev: _is_vdi_pv('/dev/%s' % dev)) + if pv: + return 'true' + else: + return 'false' + +def _is_vdi_pv(dest): + logging.debug("Running pygrub against %s",dest) + output=os.popen('pygrub -qn %s' % dest) + pv=False + for line in output.readlines(): + #try to find kernel string + m=re.search('(?<=kernel:)/.*(?:>)',line) + if m: + if m.group(0).find('xen')!=-1: + pv=True + logging.debug("PV:%d",pv) + return pv + def get_vdi(session, args): src_url = exists(args, 'src_url') username = exists(args, 'username') password = exists(args, 'password') + raw_image=validate_bool(args, 'raw', 'false') add_partition = validate_bool(args, 'add_partition', 'false') - (proto, netloc, url_path, _, _, _) = urlparse.urlparse(src_url) - sr = find_sr(session) if sr is None: raise Exception('Cannot find SR to write VDI to') - virtual_size = \ get_content_length(proto, netloc, url_path, username, password) if virtual_size < 0: raise Exception('Cannot get VDI size') - vdi_size = virtual_size if add_partition: # Make room for MBR. @@ -69,18 +88,19 @@ def get_vdi(session, args): vdi = create_vdi(session, sr, src_url, vdi_size, False) with_vdi_in_dom0(session, vdi, False, lambda dev: get_vdi_(proto, netloc, url_path, - username, password, add_partition, + username, password, add_partition,raw_image, virtual_size, '/dev/%s' % dev)) return session.xenapi.VDI.get_uuid(vdi) -def get_vdi_(proto, netloc, url_path, username, password, add_partition, +def get_vdi_(proto, netloc, url_path, username, password, add_partition,raw_image, virtual_size, dest): - if add_partition: + #Salvatore: vdi should not be partitioned for raw images + if (add_partition and not raw_image): write_partition(virtual_size, dest) - offset = add_partition and MBR_SIZE_BYTES or 0 + offset = (add_partition and not raw_image and MBR_SIZE_BYTES) or 0 get(proto, netloc, url_path, username, password, dest, offset) @@ -228,4 +248,5 @@ def download_all(response, length, dest_file, offset): if __name__ == '__main__': XenAPIPlugin.dispatch({'get_vdi': get_vdi, - 'get_kernel': get_kernel}) + 'get_kernel': get_kernel, + 'is_vdi_pv': is_vdi_pv}) diff --git a/tools/clean-vlans b/tools/clean-vlans index f5b0295ad..820a9dbe5 100755 --- a/tools/clean-vlans +++ b/tools/clean-vlans @@ -17,6 +17,8 @@ # License for the specific language governing permissions and limitations # under the License. +export LC_ALL=C + sudo ifconfig -a | grep br | grep -v bridge | cut -f1 -d" " | xargs -n1 -ifoo ifconfig foo down sudo ifconfig -a | grep br | grep -v bridge | cut -f1 -d" " | xargs -n1 -ifoo brctl delbr foo sudo ifconfig -a | grep vlan | grep -v vlan124 | grep -v vlan5 | cut -f1 -d" " | xargs -n1 -ifoo ifconfig foo down diff --git a/tools/setup_iptables.sh b/tools/setup_iptables.sh index 673353eb4..8be8cd812 100755 --- a/tools/setup_iptables.sh +++ b/tools/setup_iptables.sh @@ -36,7 +36,7 @@ else # NOTE(vish): This will just get the first ip in the list, so if you # have more than one eth device set up, this will fail, and # you should explicitly pass in the ip of the instance - IP=`ifconfig | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'` + IP=`LC_ALL=C ifconfig | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'` fi if [ -n "$3" ]; then |
