From e76fe31644ab616dbde14e1b2063ab8419410404 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sun, 12 Sep 2010 16:02:22 -0700 Subject: Periodic callback for services and managers. Added code to automatically disassociate stale ip addresses --- nova/db/api.py | 5 +++++ nova/db/sqlalchemy/api.py | 14 ++++++++++++++ nova/manager.py | 6 ++++++ nova/network/manager.py | 37 +++++++++++++++++++++++++------------ nova/service.py | 24 ++++++++++++++++++++---- nova/tests/service_unittest.py | 9 +++++++-- 6 files changed, 77 insertions(+), 18 deletions(-) (limited to 'nova') diff --git a/nova/db/api.py b/nova/db/api.py index 1529888ba..43f50555f 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -170,6 +170,11 @@ def fixed_ip_disassociate(context, address): return IMPL.fixed_ip_disassociate(context, address) +def fixed_ip_disassociate_all_by_timeout(context, host, time): + """Disassociate old fixed ips from host""" + return IMPL.fixed_ip_disassociate_all_by_timeout(context, host, time) + + def fixed_ip_get_by_address(context, address): """Get a fixed ip by address or raise if it does not exist.""" return IMPL.fixed_ip_get_by_address(context, address) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 3d3b766fc..fa2981899 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -244,6 +244,20 @@ def fixed_ip_disassociate(_context, address): fixed_ip_ref.save(session=session) +def fixed_ip_disassociate_all_by_timeout(_context, host, time): + session = get_session() + result = session.execute('update fixed_ips set instance_id = NULL ' + 'WHERE id IN (SELECT fixed_ips.id FROM fixed_ips ' + 'INNER JOIN networks ' + 'ON fixed_ips.network_id = ' + 'networks.id ' + 'WHERE host = :host) ' + 'AND updated_at < :time ' + 'AND instance_id IS NOT NULL', + {'host': host, + 'time': time.isoformat()}) + return result.rowcount + def fixed_ip_get_by_address(_context, address): session = get_session() with session.begin(): diff --git a/nova/manager.py b/nova/manager.py index e9aa50c56..0694907bd 100644 --- a/nova/manager.py +++ b/nova/manager.py @@ -22,6 +22,7 @@ Base class for managers of different parts of the system from nova import utils from nova import flags +from twisted.internet import defer FLAGS = flags.FLAGS flags.DEFINE_string('db_driver', 'nova.db.api', @@ -37,3 +38,8 @@ class Manager(object): if not db_driver: db_driver = FLAGS.db_driver self.db = utils.import_object(db_driver) # pylint: disable-msg=C0103 + + @defer.inlineCallbacks + def periodic_tasks(self, context=None): + """Tasks to be run at a periodic interval""" + yield diff --git a/nova/network/manager.py b/nova/network/manager.py index 9564a3e33..b3541aacc 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -20,10 +20,12 @@ Network Hosts are responsible for allocating ips and setting up network """ +import datetime import logging import math import IPy +from twisted.internet import defer from nova import db from nova import exception @@ -62,7 +64,9 @@ flags.DEFINE_integer('cnt_vpn_clients', 5, flags.DEFINE_string('network_driver', 'nova.network.linux_net', 'Driver to use for network creation') flags.DEFINE_bool('update_dhcp_on_disassociate', False, - 'Whether to update dhcp when fixed_ip is disassocated') + 'Whether to update dhcp when fixed_ip is disassociated') +flags.DEFINE_integer('fixed_ip_disassociate_timeout', 600, + 'Seconds after which a deallocated ip is disassociated') class AddressAlreadyAllocated(exception.Error): @@ -90,7 +94,7 @@ class NetworkManager(manager.Manager): network_id = network_ref['id'] host = self.db.network_set_host(context, network_id, - FLAGS.host) + self.host) self._on_set_network_host(context, network_id) return host @@ -118,7 +122,7 @@ class NetworkManager(manager.Manager): """Gets an floating ip from the pool""" # TODO(vish): add floating ips through manage command return self.db.floating_ip_allocate_address(context, - FLAGS.host, + self.host, project_id) def associate_floating_ip(self, context, floating_address, fixed_address): @@ -218,6 +222,21 @@ class FlatManager(NetworkManager): class VlanManager(NetworkManager): """Vlan network with dhcp""" + + @defer.inlineCallbacks + def periodic_tasks(self, context=None): + """Tasks to be run at a periodic interval""" + yield super(VlanManager, self).periodic_tasks(context) + now = datetime.datetime.utcnow() + timeout = FLAGS.fixed_ip_disassociate_timeout + time = now - datetime.timedelta(seconds=timeout) + num = self.db.fixed_ip_disassociate_all_by_timeout(self, + self.host, + time) + if num: + logging.debug("Dissassociated %s stale fixed ip(s)", num) + + def allocate_fixed_ip(self, context, instance_id, *args, **kwargs): """Gets a fixed ip from the pool""" network_ref = self.db.project_get_network(context, context.project.id) @@ -235,14 +254,6 @@ class VlanManager(NetworkManager): """Returns a fixed ip to the pool""" self.db.fixed_ip_update(context, address, {'allocated': False}) fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) - if not fixed_ip_ref['leased']: - self.db.fixed_ip_disassociate(context, address) - # NOTE(vish): dhcp server isn't updated until next setup, this - # means there will stale entries in the conf file - # the code below will update the file if necessary - if FLAGS.update_dhcp_on_disassociate: - network_ref = self.db.fixed_ip_get_network(context, address) - self.driver.update_dhcp(context, network_ref['id']) def setup_fixed_ip(self, context, address): @@ -287,7 +298,9 @@ class VlanManager(NetworkManager): if instance_ref['mac_address'] != mac: raise exception.Error("IP %s released from bad mac %s vs %s" % (address, instance_ref['mac_address'], mac)) - self.db.fixed_ip_update(context, address, {'leased': False}) + self.db.fixed_ip_update(context, + fixed_ip_ref['str_id'], + {'leased': False}) if not fixed_ip_ref['allocated']: self.db.fixed_ip_disassociate(context, address) # NOTE(vish): dhcp server isn't updated until next setup, this diff --git a/nova/service.py b/nova/service.py index 870dd6ceb..df450025b 100644 --- a/nova/service.py +++ b/nova/service.py @@ -37,7 +37,11 @@ from nova import utils FLAGS = flags.FLAGS flags.DEFINE_integer('report_interval', 10, - 'seconds between nodes reporting state to cloud', + 'seconds between nodes reporting state to datastore', + lower_bound=1) + +flags.DEFINE_integer('periodic_interval', 60, + 'seconds between running periodic tasks', lower_bound=1) @@ -80,7 +84,8 @@ class Service(object, service.Service): binary=None, topic=None, manager=None, - report_interval=None): + report_interval=None, + periodic_interval=None): """Instantiates class and passes back application object. Args: @@ -89,6 +94,7 @@ class Service(object, service.Service): topic, defaults to bin_name - "nova-" part manager, defaults to FLAGS._manager report_interval, defaults to FLAGS.report_interval + periodic_interval, defaults to FLAGS.periodic_interval """ if not host: host = FLAGS.host @@ -100,6 +106,8 @@ class Service(object, service.Service): manager = FLAGS.get('%s_manager' % topic, None) if not report_interval: report_interval = FLAGS.report_interval + if not periodic_interval: + periodic_interval = FLAGS.periodic_interval logging.warn("Starting %s node", topic) service_obj = cls(host, binary, topic, manager) conn = rpc.Connection.instance() @@ -112,11 +120,14 @@ class Service(object, service.Service): topic='%s.%s' % (topic, host), proxy=service_obj) + consumer_all.attach_to_twisted() + consumer_node.attach_to_twisted() + pulse = task.LoopingCall(service_obj.report_state) pulse.start(interval=report_interval, now=False) - consumer_all.attach_to_twisted() - consumer_node.attach_to_twisted() + pulse = task.LoopingCall(service_obj.periodic_tasks) + pulse.start(interval=periodic_interval, now=False) # This is the parent service that twistd will be looking for when it # parses this file, return it so that we can get it into globals. @@ -131,6 +142,11 @@ class Service(object, service.Service): except exception.NotFound: logging.warn("Service killed that has no database entry") + @defer.inlineCallbacks + def periodic_tasks(self, context=None): + """Tasks to be run at a periodic interval""" + yield self.manager.periodic_tasks(context) + @defer.inlineCallbacks def report_state(self, context=None): """Update the state of this service in the datastore.""" diff --git a/nova/tests/service_unittest.py b/nova/tests/service_unittest.py index 01da0eb8a..06f80e82c 100644 --- a/nova/tests/service_unittest.py +++ b/nova/tests/service_unittest.py @@ -65,15 +65,20 @@ class ServiceTestCase(test.BaseTestCase): proxy=mox.IsA(service.Service)).AndReturn( rpc.AdapterConsumer) + rpc.AdapterConsumer.attach_to_twisted() + rpc.AdapterConsumer.attach_to_twisted() + # Stub out looping call a bit needlessly since we don't have an easy # way to cancel it (yet) when the tests finishes service.task.LoopingCall(mox.IgnoreArg()).AndReturn( service.task.LoopingCall) service.task.LoopingCall.start(interval=mox.IgnoreArg(), now=mox.IgnoreArg()) + service.task.LoopingCall(mox.IgnoreArg()).AndReturn( + service.task.LoopingCall) + service.task.LoopingCall.start(interval=mox.IgnoreArg(), + now=mox.IgnoreArg()) - rpc.AdapterConsumer.attach_to_twisted() - rpc.AdapterConsumer.attach_to_twisted() service_create = {'host': host, 'binary': binary, 'topic': topic, -- cgit From efeb5243ffd5e588748d8786ac82a04e302f0612 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sun, 12 Sep 2010 17:24:06 -0700 Subject: workaround for mysql select in update --- nova/db/sqlalchemy/api.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index fa2981899..aa8670253 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -246,12 +246,19 @@ def fixed_ip_disassociate(_context, address): def fixed_ip_disassociate_all_by_timeout(_context, host, time): session = get_session() - result = session.execute('update fixed_ips set instance_id = NULL ' - 'WHERE id IN (SELECT fixed_ips.id FROM fixed_ips ' - 'INNER JOIN networks ' - 'ON fixed_ips.network_id = ' - 'networks.id ' - 'WHERE host = :host) ' + # NOTE(vish): The annoying nested select here is because SQLite doesn't + # support JOINs in UPDATEs and Mysql doesn't support SELECT + # from the same table you are updating without using a temp + # table. It would be great if we can coax sqlalchemy into + # generating this update for us without having to update + # each fixed_ip individually. + result = session.execute('UPDATE fixed_ips SET instance_id = NULL ' + 'WHERE id IN (SELECT x.id FROM ' + '(SELECT fixed_ips.id FROM fixed_ips ' + 'INNER JOIN networks ' + 'ON fixed_ips.network_id = ' + 'networks.id ' + 'WHERE host = :host) as x) ' 'AND updated_at < :time ' 'AND instance_id IS NOT NULL', {'host': host, -- cgit From cb2a28d82210e43a221a8fa8f2a03a7dbd6e8779 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sun, 12 Sep 2010 17:56:28 -0700 Subject: speed up the query and make sure allocated is false --- nova/db/sqlalchemy/api.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index aa8670253..ca5e12f95 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -258,9 +258,10 @@ def fixed_ip_disassociate_all_by_timeout(_context, host, time): 'INNER JOIN networks ' 'ON fixed_ips.network_id = ' 'networks.id ' - 'WHERE host = :host) as x) ' - 'AND updated_at < :time ' - 'AND instance_id IS NOT NULL', + 'WHERE networks.host = :host ' + 'AND fixed_ip.updated_at < :time ' + 'AND fixed_ip.instance_id IS NOT NULL' + 'AND fixed_ip.allocated = 0) as x) ', {'host': host, 'time': time.isoformat()}) return result.rowcount -- cgit From 3f9118b2ae025cd707642b179fe58459dd39dbc3 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sun, 12 Sep 2010 18:06:59 -0700 Subject: set leased = 0 as well on disassociate update --- nova/db/sqlalchemy/api.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ca5e12f95..b4b68fa02 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -252,16 +252,17 @@ def fixed_ip_disassociate_all_by_timeout(_context, host, time): # table. It would be great if we can coax sqlalchemy into # generating this update for us without having to update # each fixed_ip individually. - result = session.execute('UPDATE fixed_ips SET instance_id = NULL ' + result = session.execute('UPDATE fixed_ips SET instance_id = NULL, ' + 'leased = 0 ' 'WHERE id IN (SELECT x.id FROM ' - '(SELECT fixed_ips.id FROM fixed_ips ' - 'INNER JOIN networks ' - 'ON fixed_ips.network_id = ' - 'networks.id ' - 'WHERE networks.host = :host ' - 'AND fixed_ip.updated_at < :time ' - 'AND fixed_ip.instance_id IS NOT NULL' - 'AND fixed_ip.allocated = 0) as x) ', + '(SELECT fixed_ips.id FROM fixed_ips ' + 'INNER JOIN networks ' + 'ON fixed_ips.network_id = ' + 'networks.id ' + 'WHERE networks.host = :host ' + 'AND fixed_ip.updated_at < :time ' + 'AND fixed_ip.instance_id IS NOT NULL' + 'AND fixed_ip.allocated = 0) as x) ', {'host': host, 'time': time.isoformat()}) return result.rowcount -- cgit From 8e5a2ab3614da2676a3627cf62fedfedf7f3585c Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sun, 12 Sep 2010 18:16:52 -0700 Subject: missed a space --- nova/db/sqlalchemy/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index b4b68fa02..5ec477666 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -261,7 +261,7 @@ def fixed_ip_disassociate_all_by_timeout(_context, host, time): 'networks.id ' 'WHERE networks.host = :host ' 'AND fixed_ip.updated_at < :time ' - 'AND fixed_ip.instance_id IS NOT NULL' + 'AND fixed_ip.instance_id IS NOT NULL ' 'AND fixed_ip.allocated = 0) as x) ', {'host': host, 'time': time.isoformat()}) -- cgit From dbb3358f851c245ccb5ea9a9c7ace636b4e73a80 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sun, 12 Sep 2010 18:25:15 -0700 Subject: simplified query --- nova/db/sqlalchemy/api.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 5ec477666..18a43cad4 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -246,23 +246,15 @@ def fixed_ip_disassociate(_context, address): def fixed_ip_disassociate_all_by_timeout(_context, host, time): session = get_session() - # NOTE(vish): The annoying nested select here is because SQLite doesn't - # support JOINs in UPDATEs and Mysql doesn't support SELECT - # from the same table you are updating without using a temp - # table. It would be great if we can coax sqlalchemy into - # generating this update for us without having to update - # each fixed_ip individually. + # NOTE(vish): The nested select is because sqlite doesn't support + # JOINs in UPDATEs. result = session.execute('UPDATE fixed_ips SET instance_id = NULL, ' 'leased = 0 ' - 'WHERE id IN (SELECT x.id FROM ' - '(SELECT fixed_ips.id FROM fixed_ips ' - 'INNER JOIN networks ' - 'ON fixed_ips.network_id = ' - 'networks.id ' - 'WHERE networks.host = :host ' - 'AND fixed_ip.updated_at < :time ' - 'AND fixed_ip.instance_id IS NOT NULL ' - 'AND fixed_ip.allocated = 0) as x) ', + 'WHERE network_id IN (SELECT id FROM networks ' + 'WHERE host = :host) ' + 'AND updated_at < :time ' + 'AND instance_id IS NOT NULL ' + 'AND allocated = 0', {'host': host, 'time': time.isoformat()}) return result.rowcount -- cgit From 892e55724e8265865a1b298e478d98acd42c68d7 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sun, 12 Sep 2010 22:12:24 -0700 Subject: move the warnings about leasing ips --- nova/network/manager.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index b3541aacc..b4c4a53b4 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -270,9 +270,6 @@ class VlanManager(NetworkManager): """Called by dhcp-bridge when ip is leased""" logging.debug("Leasing IP %s", address) fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) - if not fixed_ip_ref['allocated']: - logging.warn("IP %s leased that was already deallocated", address) - return instance_ref = fixed_ip_ref['instance'] if not instance_ref: raise exception.Error("IP %s leased that isn't associated" % @@ -283,14 +280,13 @@ class VlanManager(NetworkManager): self.db.fixed_ip_update(context, fixed_ip_ref['str_id'], {'leased': True}) + if not fixed_ip_ref['allocated']: + logging.warn("IP %s leased that was already deallocated", address) def release_fixed_ip(self, context, mac, address): """Called by dhcp-bridge when ip is released""" logging.debug("Releasing IP %s", address) fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address) - if not fixed_ip_ref['leased']: - logging.warn("IP %s released that was not leased", address) - return instance_ref = fixed_ip_ref['instance'] if not instance_ref: raise exception.Error("IP %s released that isn't associated" % @@ -298,6 +294,8 @@ class VlanManager(NetworkManager): if instance_ref['mac_address'] != mac: raise exception.Error("IP %s released from bad mac %s vs %s" % (address, instance_ref['mac_address'], mac)) + if not fixed_ip_ref['leased']: + logging.warn("IP %s released that was not leased", address) self.db.fixed_ip_update(context, fixed_ip_ref['str_id'], {'leased': False}) -- cgit From f3698b8da4bd63abfade32c9894ac2095672344e Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 21 Sep 2010 15:44:49 -0700 Subject: cleaned up exception handling for fixed_ip_get --- nova/db/sqlalchemy/api.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 026cdf10d..daa9e991e 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -19,15 +19,13 @@ Implementation of SQLAlchemy backend """ -import sys - from nova import db from nova import exception from nova import flags from nova.db.sqlalchemy import models from nova.db.sqlalchemy.session import get_session from sqlalchemy import or_ -from sqlalchemy.orm import exc, joinedload_all +from sqlalchemy.orm import joinedload_all from sqlalchemy.sql import func FLAGS = flags.FLAGS @@ -352,17 +350,14 @@ def fixed_ip_disassociate_all_by_timeout(_context, host, time): def fixed_ip_get_by_address(_context, address): session = get_session() - with session.begin(): - try: - return session.query(models.FixedIp - ).options(joinedload_all('instance') - ).filter_by(address=address - ).filter_by(deleted=False - ).one() - except exc.NoResultFound: - new_exc = exception.NotFound("No model for address %s" % address) - raise new_exc.__class__, new_exc, sys.exc_info()[2] - + result = session.query(models.FixedIp + ).options(joinedload_all('instance') + ).filter_by(address=address + ).filter_by(deleted=False + ).first() + if not result: + raise exception.NotFound("No model for address %s" % address) + return result def fixed_ip_get_instance(_context, address): session = get_session() -- cgit From 05e3e188e03624884ed019fe9cd8f216c9262f98 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Tue, 28 Sep 2010 20:36:50 -0400 Subject: Fault support --- nova/api/rackspace/__init__.py | 9 +++-- nova/api/rackspace/auth.py | 7 ++-- nova/api/rackspace/faults.py | 61 ++++++++++++++++++++++++++++++++++ nova/api/rackspace/flavors.py | 3 +- nova/api/rackspace/images.py | 7 ++-- nova/tests/api/rackspace/testfaults.py | 30 +++++++++++++++++ 6 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 nova/api/rackspace/faults.py create mode 100644 nova/tests/api/rackspace/testfaults.py (limited to 'nova') diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py index c24d08585..447037020 100644 --- a/nova/api/rackspace/__init__.py +++ b/nova/api/rackspace/__init__.py @@ -31,6 +31,7 @@ import webob from nova import flags from nova import utils from nova import wsgi +from nova.api.rackspace import faults from nova.api.rackspace import flavors from nova.api.rackspace import images from nova.api.rackspace import ratelimiting @@ -66,7 +67,7 @@ class AuthMiddleware(wsgi.Middleware): user = self.auth_driver.authorize_token(req.headers["X-Auth-Token"]) if not user: - return webob.exc.HTTPUnauthorized() + return faults.Fault(webob.exc.HTTPUnauthorized()) context = {'user': user} req.environ['nova.context'] = context return self.application @@ -109,8 +110,10 @@ class RateLimitingMiddleware(wsgi.Middleware): delay = self.get_delay(action_name, username) if delay: # TODO(gundlach): Get the retry-after format correct. - raise webob.exc.HTTPRequestEntityTooLarge(headers={ - 'Retry-After': time.time() + delay}) + 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): diff --git a/nova/api/rackspace/auth.py b/nova/api/rackspace/auth.py index ce5a967eb..519263367 100644 --- a/nova/api/rackspace/auth.py +++ b/nova/api/rackspace/auth.py @@ -9,6 +9,7 @@ from nova import auth from nova import manager from nova import db from nova import utils +from nova.api.rackspace import faults FLAGS = flags.FLAGS @@ -34,13 +35,13 @@ class BasicApiAuthManager(object): # honor it path_info = req.path_info if len(path_info) > 1: - return webob.exc.HTTPUnauthorized() + return faults.Fault(webob.exc.HTTPUnauthorized()) try: username, key = req.headers['X-Auth-User'], \ req.headers['X-Auth-Key'] except KeyError: - return webob.exc.HTTPUnauthorized() + return faults.Fault(webob.exc.HTTPUnauthorized()) username, key = req.headers['X-Auth-User'], req.headers['X-Auth-Key'] token, user = self._authorize_user(username, key) @@ -55,7 +56,7 @@ class BasicApiAuthManager(object): res.status = '204' return res else: - return webob.exc.HTTPUnauthorized() + return faults.Fault(webob.exc.HTTPUnauthorized()) def authorize_token(self, token_hash): """ retrieves user information from the datastore given a token diff --git a/nova/api/rackspace/faults.py b/nova/api/rackspace/faults.py new file mode 100644 index 000000000..fd6bc3623 --- /dev/null +++ b/nova/api/rackspace/faults.py @@ -0,0 +1,61 @@ +# 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 webob.dec + +from nova import wsgi + + +class Fault(wsgi.Application): + + """An RS API fault response.""" + + _fault_names = { + 400: "badRequest", + 401: "unauthorized", + 403: "resizeNotAllowed", + 404: "itemNotFound", + 405: "badMethod", + 409: "inProgress", + 413: "overLimit", + 415: "badMediaType", + 501: "notImplemented", + 503: "serviceUnavailable"} + + def __init__(self, exception): + """Create a Fault for the given webob.exc.exception.""" + self.exception = exception + + @webob.dec.wsgify + def __call__(self, req): + """Generate a WSGI response based on self.exception.""" + # Replace the body with fault details. + code = self.exception.status_int + fault_name = self._fault_names.get(code, "cloudServersFault") + fault_data = { + fault_name: { + 'code': code, + 'message': self.exception.explanation}} + if code == 413: + retry = self.exception.headers['Retry-After'] + fault_data[fault_name]['retryAfter'] = retry + # 'code' is an attribute on the fault tag itself + metadata = {'application/xml': {'attributes': {fault_name: 'code'}}} + serializer = wsgi.Serializer(req.environ, metadata) + self.exception.body = serializer.to_content_type(fault_data) + return self.exception diff --git a/nova/api/rackspace/flavors.py b/nova/api/rackspace/flavors.py index 60b35c939..6cc57be33 100644 --- a/nova/api/rackspace/flavors.py +++ b/nova/api/rackspace/flavors.py @@ -16,6 +16,7 @@ # under the License. from nova.api.rackspace import base +from nova.api.rackspace import faults from nova.compute import instance_types from webob import exc @@ -47,7 +48,7 @@ class Controller(base.Controller): item = dict(ram=val['memory_mb'], disk=val['local_gb'], id=val['flavorid'], name=name) return dict(flavor=item) - raise exc.HTTPNotFound() + raise faults.Fault(exc.HTTPNotFound()) def _all_ids(self): """Return the list of all flavorids.""" diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index 2f3e928b9..1c50d0bec 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -18,6 +18,7 @@ import nova.image.service from nova.api.rackspace import base from nova.api.rackspace import _id_translator +from nova.api.rackspace import faults from webob import exc class Controller(base.Controller): @@ -57,14 +58,14 @@ class Controller(base.Controller): def delete(self, req, id): # Only public images are supported for now. - raise exc.HTTPNotFound() + raise faults.Fault(exc.HTTPNotFound()) def create(self, req): # Only public images are supported for now, so a request to # make a backup of a server cannot be supproted. - raise exc.HTTPNotFound() + raise faults.Fault(exc.HTTPNotFound()) def update(self, req, id): # Users may not modify public images, and that's all that # we support for now. - raise exc.HTTPNotFound() + raise faults.Fault(exc.HTTPNotFound()) diff --git a/nova/tests/api/rackspace/testfaults.py b/nova/tests/api/rackspace/testfaults.py new file mode 100644 index 000000000..74caffd30 --- /dev/null +++ b/nova/tests/api/rackspace/testfaults.py @@ -0,0 +1,30 @@ +import unittest +import webob +import webob.exc + +from nova.api.rackspace import faults + +class TestFaults(unittest.TestCase): + + def test_fault_parts(self): + req = webob.Request.blank('/.xml') + f = faults.Fault(webob.exc.HTTPBadRequest(explanation='scram')) + resp = req.get_response(f) + + first_two_words = resp.body.strip().split()[:2] + self.assertEqual(first_two_words, ['']) + body_without_spaces = ''.join(resp.body.split()) + self.assertTrue('scram' in body_without_spaces) + + def test_retry_header(self): + req = webob.Request.blank('/.xml') + exc = webob.exc.HTTPRequestEntityTooLarge(explanation='sorry', + headers={'Retry-After': 4}) + f = faults.Fault(exc) + resp = req.get_response(f) + first_two_words = resp.body.strip().split()[:2] + self.assertEqual(first_two_words, ['']) + body_sans_spaces = ''.join(resp.body.split()) + self.assertTrue('sorry' in body_sans_spaces) + self.assertTrue('4' in body_sans_spaces) + self.assertEqual(resp.headers['Retry-After'], 4) -- cgit From fe139bbdee60aadd720cb7a83d0846f2824c078f Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Wed, 29 Sep 2010 00:49:04 -0700 Subject: Began wiring up context authorization --- nova/db/sqlalchemy/api.py | 50 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 9c3caf9af..b5847d299 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -19,6 +19,7 @@ Implementation of SQLAlchemy backend """ +import logging import sys from nova import db @@ -48,6 +49,24 @@ def _deleted(context): return context.get('deleted', False) +def is_admin_context(context): + if not context: + logging.warning('Use of empty request context is deprecated') + return True + if not context.user: + return True + return context.user.is_admin() + + +def is_user_context(context): + if not context: + logging.warning('Use of empty request context is deprecated') + return False + if not context.user or not context.project: + return False + return True + + ################### @@ -869,14 +888,41 @@ def volume_detached(_context, volume_id): def volume_get(context, volume_id): - return models.Volume.find(volume_id, deleted=_deleted(context)) + session = get_session() + + if is_admin_context(context): + volume_ref = session.query(models.Volume + ).filter_by(id=volume_id + ).filter_by(deleted=_deleted(context) + ).first() + if not volume_ref: + raise exception.NotFound('No volume for id %s' % volume_id) + + if is_user_context(context): + volume_ref = session.query(models.Volume + ).filter_by(project_id=project_id + ).filter_by(id=volume_id + ).filter_by(deleted=False + ).first() + if not volume_ref: + raise exception.NotFound('No volume for id %s' % volume_id) + + raise exception.NotAuthorized() def volume_get_all(context): - return models.Volume.all(deleted=_deleted(context)) + if is_admin_context(context): + return models.Volume.all(deleted=_deleted(context)) + raise exception.NotAuthorized() def volume_get_all_by_project(context, project_id): + if is_user_context(context): + if context.project.id != project_id: + raise exception.NotAuthorized() + elif not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() return session.query(models.Volume ).filter_by(project_id=project_id -- cgit From 4c1aa3d96f0c44d3e01864ca3128e9b052d1d7fd Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Wed, 29 Sep 2010 10:17:10 -0400 Subject: After update from trunk, a few more exceptions that need to be converted to Faults --- nova/api/rackspace/backup_schedules.py | 7 ++++--- nova/api/rackspace/servers.py | 17 +++++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/api/rackspace/backup_schedules.py b/nova/api/rackspace/backup_schedules.py index 46da778ee..cb83023bc 100644 --- a/nova/api/rackspace/backup_schedules.py +++ b/nova/api/rackspace/backup_schedules.py @@ -20,6 +20,7 @@ from webob import exc from nova import wsgi from nova.api.rackspace import _id_translator +from nova.api.rackspace import faults import nova.image.service class Controller(wsgi.Controller): @@ -27,12 +28,12 @@ class Controller(wsgi.Controller): pass def index(self, req, server_id): - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPNotFound()) def create(self, req, server_id): """ No actual update method required, since the existing API allows both create and update through a POST """ - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPNotFound()) def delete(self, req, server_id): - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPNotFound()) diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 4ab04bde7..888d67542 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -24,6 +24,7 @@ from nova import rpc from nova import utils from nova import wsgi from nova.api.rackspace import _id_translator +from nova.api.rackspace import faults from nova.compute import power_state import nova.image.service @@ -120,7 +121,7 @@ class Controller(wsgi.Controller): if inst: if inst.user_id == user_id: return _entity_detail(inst) - raise exc.HTTPNotFound() + raise faults.Fault(exc.HTTPNotFound()) def delete(self, req, id): """ Destroys a server """ @@ -128,13 +129,13 @@ class Controller(wsgi.Controller): instance = self.db_driver.instance_get(None, id) if instance and instance['user_id'] == user_id: self.db_driver.instance_destroy(None, id) - return exc.HTTPAccepted() - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPAccepted()) + return faults.Fault(exc.HTTPNotFound()) def create(self, req): """ Creates a new server for a given user """ if not req.environ.has_key('inst_dict'): - return exc.HTTPUnprocessableEntity() + return faults.Fault(exc.HTTPUnprocessableEntity()) inst = self._build_server_instance(req) @@ -147,22 +148,22 @@ class Controller(wsgi.Controller): def update(self, req, id): """ Updates the server name or password """ if not req.environ.has_key('inst_dict'): - return exc.HTTPUnprocessableEntity() + return faults.Fault(exc.HTTPUnprocessableEntity()) instance = self.db_driver.instance_get(None, id) if not instance: - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPNotFound()) attrs = req.environ['nova.context'].get('model_attributes', None) if attrs: self.db_driver.instance_update(None, id, _filter_params(attrs)) - return exc.HTTPNoContent() + return faults.Fault(exc.HTTPNoContent()) def action(self, req, id): """ multi-purpose method used to reboot, rebuild, and resize a server """ if not req.environ.has_key('inst_dict'): - return exc.HTTPUnprocessableEntity() + return faults.Fault(exc.HTTPUnprocessableEntity()) def _build_server_instance(self, req): """Build instance data structure and save it to the data store.""" -- cgit From 29eca7e7992fc5c073d70f7c8ca5e5bc03f62af7 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Wed, 29 Sep 2010 11:37:26 -0400 Subject: Limit entity lists by &offset and &limit --- nova/api/rackspace/__init__.py | 20 ++++++++++++++++++++ nova/api/rackspace/flavors.py | 5 ++++- nova/api/rackspace/images.py | 2 ++ nova/api/rackspace/servers.py | 18 ++++++++++++------ nova/tests/api/rackspace/__init__.py | 29 +++++++++++++++++++++++++++++ 5 files changed, 67 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py index 98802663f..48104f6df 100644 --- a/nova/api/rackspace/__init__.py +++ b/nova/api/rackspace/__init__.py @@ -165,3 +165,23 @@ 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/rackspace/flavors.py b/nova/api/rackspace/flavors.py index 3bcf170e5..ba7aa937c 100644 --- a/nova/api/rackspace/flavors.py +++ b/nova/api/rackspace/flavors.py @@ -15,9 +15,11 @@ # License for the specific language governing permissions and limitations # under the License. +from webob import exc + from nova.compute import instance_types from nova import wsgi -from webob import exc +import nova.api.rackspace class Controller(wsgi.Controller): """Flavor controller for the Rackspace API.""" @@ -38,6 +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.rackspace.limited(items, req) return dict(flavors=items) def show(self, req, id): diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index 11b058dec..7da17e6a7 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -19,6 +19,7 @@ from webob import exc from nova import wsgi from nova.api.rackspace import _id_translator +import nova.api.rackspace import nova.image.service class Controller(wsgi.Controller): @@ -45,6 +46,7 @@ class Controller(wsgi.Controller): def detail(self, req): """Return all public images in detail.""" data = self._service.index() + data = nova.api.rackspace.limited(data, req) for img in data: img['id'] = self._id_translator.to_rs_id(img['id']) return dict(images=data) diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 4ab04bde7..958fc86a3 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -25,6 +25,7 @@ from nova import utils from nova import wsgi from nova.api.rackspace import _id_translator from nova.compute import power_state +import nova.api.rackspace import nova.image.service FLAGS = flags.FLAGS @@ -101,16 +102,21 @@ class Controller(wsgi.Controller): def index(self, req): """ Returns a list of server names and ids for a given user """ - user_id = req.environ['nova.context']['user']['id'] - instance_list = self.db_driver.instance_get_all_by_user(None, user_id) - res = [_entity_inst(inst)['server'] for inst in instance_list] - return _entity_list(res) + return self._items(req, entity_maker=_entity_inst) def detail(self, req): """ Returns a list of server details for a given user """ + return self._items(req, entity_maker=_entity_detail) + + def _items(self, req, entity_maker): + """Returns a list of servers for a given user. + + entity_maker - either _entity_detail or _entity_inst + """ user_id = req.environ['nova.context']['user']['id'] - res = [_entity_detail(inst)['server'] for inst in - self.db_driver.instance_get_all_by_user(None, user_id)] + instance_list = self.db_driver.instance_get_all_by_user(None, user_id) + limited_list = nova.api.rackspace.limited(instance_list, req) + res = [entity_maker(inst)['server'] for inst in limited_list] return _entity_list(res) def show(self, req, id): diff --git a/nova/tests/api/rackspace/__init__.py b/nova/tests/api/rackspace/__init__.py index 622cb4335..bfd0f87a7 100644 --- a/nova/tests/api/rackspace/__init__.py +++ b/nova/tests/api/rackspace/__init__.py @@ -17,6 +17,7 @@ import unittest +from nova.api.rackspace import limited from nova.api.rackspace import RateLimitingMiddleware from nova.tests.api.test_helper import * from webob import Request @@ -77,3 +78,31 @@ class RateLimitingMiddlewareTest(unittest.TestCase): self.assertEqual(middleware.limiter.__class__.__name__, "Limiter") middleware = RateLimitingMiddleware(APIStub(), service_host='foobar') self.assertEqual(middleware.limiter.__class__.__name__, "WSGIAppProxy") + + +class LimiterTest(unittest.TestCase): + + def testLimiter(self): + items = range(2000) + req = Request.blank('/') + self.assertEqual(limited(items, req), items[ :1000]) + req = Request.blank('/?offset=0') + self.assertEqual(limited(items, req), items[ :1000]) + req = Request.blank('/?offset=3') + self.assertEqual(limited(items, req), items[3:1003]) + req = Request.blank('/?offset=2005') + self.assertEqual(limited(items, req), []) + req = Request.blank('/?limit=10') + self.assertEqual(limited(items, req), items[ :10]) + req = Request.blank('/?limit=0') + self.assertEqual(limited(items, req), items[ :1000]) + req = Request.blank('/?limit=3000') + self.assertEqual(limited(items, req), items[ :1000]) + req = Request.blank('/?offset=1&limit=3') + self.assertEqual(limited(items, req), items[1:4]) + req = Request.blank('/?offset=3&limit=0') + self.assertEqual(limited(items, req), items[3:1003]) + req = Request.blank('/?offset=3&limit=1500') + self.assertEqual(limited(items, req), items[3:1003]) + req = Request.blank('/?offset=3000&limit=10') + self.assertEqual(limited(items, req), []) -- cgit From 0868bcee453665b1ce24d43a90b3addfaab8c49d Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 29 Sep 2010 12:16:53 -0500 Subject: Server update name and password --- nova/api/rackspace/servers.py | 61 +++++++++++++++++++++++-------------- nova/db/sqlalchemy/models.py | 3 ++ nova/tests/api/rackspace/servers.py | 36 +++++++++++++++++++--- nova/wsgi.py | 9 ++++++ 4 files changed, 82 insertions(+), 27 deletions(-) (limited to 'nova') diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 4ab04bde7..c156309bd 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime import time from webob import exc @@ -25,11 +26,13 @@ from nova import utils from nova import wsgi from nova.api.rackspace import _id_translator from nova.compute import power_state +from nova.wsgi import Serializer import nova.image.service FLAGS = flags.FLAGS - +flags.DEFINE_string('rs_network_manager', 'nova.network.manager.FlatManager', + 'Networking for rackspace') def translator_instance(): """ Helper method for initializing the image id translator """ @@ -82,7 +85,6 @@ def _entity_inst(inst): class Controller(wsgi.Controller): """ The Server API controller for the Openstack API """ - _serialization_metadata = { 'application/xml': { @@ -146,16 +148,19 @@ class Controller(wsgi.Controller): def update(self, req, id): """ Updates the server name or password """ - if not req.environ.has_key('inst_dict'): + user_id = req.environ['nova.context']['user']['id'] + + inst_dict = self._deserialize(req.body, req) + + if not inst_dict: return exc.HTTPUnprocessableEntity() instance = self.db_driver.instance_get(None, id) - if not instance: + if not instance or instance.user_id != user_id: return exc.HTTPNotFound() - attrs = req.environ['nova.context'].get('model_attributes', None) - if attrs: - self.db_driver.instance_update(None, id, _filter_params(attrs)) + self.db_driver.instance_update(None, id, + _filter_params(inst_dict['server'])) return exc.HTTPNoContent() def action(self, req, id): @@ -170,34 +175,44 @@ class Controller(wsgi.Controller): inst = {} env = req.environ['inst_dict'] + user_id = req.environ['nova.context']['user']['id'] + inst['rs_id'] = _new_rs_id(user_id) image_id = env['server']['imageId'] + opaque_id = translator_instance().from_rs_id(image_id) - inst['name'] = env['server']['server_name'] + inst['name'] = env['server']['name'] inst['image_id'] = opaque_id inst['instance_type'] = env['server']['flavorId'] - - user_id = req.environ['nova.context']['user']['id'] inst['user_id'] = user_id - inst['launch_time'] = ltime inst['mac_address'] = utils.generate_mac() - inst['project_id'] = env['project']['id'] - inst['reservation_id'] = reservation - reservation = utils.generate_uid('r') + #TODO(dietz) These are the attributes I'm unsure of + inst['state_description'] = 'scheduling' + inst['kernel_id'] = '' + inst['ramdisk_id'] = '' + inst['reservation_id'] = utils.generate_uid('r') + inst['key_data'] = '' + inst['key_name'] = '' + inst['security_group'] = '' + + # Flavor related attributes + inst['instance_type'] = '' + inst['memory_mb'] = '' + inst['vcpus'] = '' + inst['local_gb'] = '' - address = self.network.allocate_ip( - inst['user_id'], - inst['project_id'], - mac=inst['mac_address']) + + + #TODO(dietz): This seems necessary. How do these apply across + #the Rackspace implementation? + inst['project_id'] = '' - inst['private_dns_name'] = str(address) - inst['bridge_name'] = network.BridgedNetwork.get_network_for_project( - inst['user_id'], - inst['project_id'], - 'default')['bridge_name'] + self.network_manager = utils.import_object(FLAGS.rs_network_manager) + + address = self.network_manager.allocate_fixed_ip( None, inst['id']) ref = self.db_driver.instance_create(None, inst) inst['id'] = ref.id diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 3d2460c39..83fc7c852 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -199,6 +199,9 @@ class Instance(BASE, NovaBase): id = Column(Integer, primary_key=True) ec2_id = Column(String(10), unique=True) + rs_id = Column(String(255)) + rs_root_password = Column(String(255)) + user_id = Column(String(255)) project_id = Column(String(255)) diff --git a/nova/tests/api/rackspace/servers.py b/nova/tests/api/rackspace/servers.py index 9fd8e5e88..81682288c 100644 --- a/nova/tests/api/rackspace/servers.py +++ b/nova/tests/api/rackspace/servers.py @@ -95,11 +95,39 @@ class ServersTest(unittest.TestCase): # res = req.get_response(nova.api.API()) # print res - def test_update_server_password(self): - pass - def test_update_server_name(self): - pass + def test_update_bad_params(self): + """ Confirm that update is filtering params """ + inst_dict = dict(cat='leopard', name='server_test', adminPass='bacon') + self.body = json.dumps(dict(server=inst_dict)) + + def server_update(context, id, params): + self.update_called = True + filtered_dict = dict(name='server_test', adminPass='bacon') + self.assertEqual(params, filtered_dict) + + self.stubs.Set(nova.db.api, 'instance_update', + server_update) + + req = webob.Request.blank('/v1.0/servers/1') + req.method = 'PUT' + req.body = self.body + req.get_response(nova.api.API()) + + def test_update_server(self): + inst_dict = dict(name='server_test', adminPass='bacon') + self.body = json.dumps(dict(server=inst_dict)) + + def server_update(context, id, params): + self.assertEqual(params, json.loads(self.body)['server']) + + self.stubs.Set(nova.db.api, 'instance_update', + server_update) + + req = webob.Request.blank('/v1.0/servers/1') + req.method = 'PUT' + req.body = self.body + req.get_response(nova.api.API()) def test_create_backup_schedules(self): req = webob.Request.blank('/v1.0/servers/1/backup_schedules') diff --git a/nova/wsgi.py b/nova/wsgi.py index da9374542..48c4dabc2 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -230,6 +230,15 @@ class Controller(object): serializer = Serializer(request.environ, _metadata) return serializer.to_content_type(data) + def _deserialize(self, data, request): + """ + Deserialize the request body to the response type requested in request. + Uses self._serialization_metadata if it exists, which is a dict mapping + MIME types to information needed to serialize to that type. + """ + _metadata = getattr(type(self), "_serialization_metadata", {}) + serializer = Serializer(request.environ, _metadata) + return serializer.deserialize(data) class Serializer(object): """ -- cgit From 9c4319a83a6e7d61ffa6b78e9f17ea35821c5526 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 29 Sep 2010 12:58:40 -0500 Subject: Make update work correctly --- nova/api/rackspace/servers.py | 20 ++++++++++++++------ nova/db/sqlalchemy/models.py | 2 +- nova/tests/api/rackspace/servers.py | 5 +++-- 3 files changed, 18 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index c156309bd..aa955c222 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -34,7 +34,12 @@ FLAGS = flags.FLAGS flags.DEFINE_string('rs_network_manager', 'nova.network.manager.FlatManager', 'Networking for rackspace') -def translator_instance(): +def _instance_id_translator(): + """ Helper method for initializing an id translator for Rackspace instance + ids """ + return _id_translator.RackspaceAPIIdTranslator( "instance", 'nova') + +def _image_id_translator(): """ Helper method for initializing the image id translator """ service = nova.image.service.ImageService.load() return _id_translator.RackspaceAPIIdTranslator( @@ -42,11 +47,11 @@ def translator_instance(): def _filter_params(inst_dict): """ Extracts all updatable parameters for a server update request """ - keys = ['name', 'adminPass'] + keys = dict(name='name', admin_pass='adminPass') new_attrs = {} - for k in keys: - if inst_dict.has_key(k): - new_attrs[k] = inst_dict[k] + for k, v in keys.items(): + if inst_dict.has_key(v): + new_attrs[k] = inst_dict[v] return new_attrs def _entity_list(entities): @@ -174,10 +179,13 @@ class Controller(wsgi.Controller): ltime = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) inst = {} + inst_id_trans = _instance_id_translator() + image_id_trans = _image_id_translator() + env = req.environ['inst_dict'] user_id = req.environ['nova.context']['user']['id'] - inst['rs_id'] = _new_rs_id(user_id) + inst['rs_id'] = inst_id_trans.to_rs_id() image_id = env['server']['imageId'] opaque_id = translator_instance().from_rs_id(image_id) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 83fc7c852..2a314b037 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -200,7 +200,7 @@ class Instance(BASE, NovaBase): ec2_id = Column(String(10), unique=True) rs_id = Column(String(255)) - rs_root_password = Column(String(255)) + admin_pass = Column(String(255)) user_id = Column(String(255)) project_id = Column(String(255)) diff --git a/nova/tests/api/rackspace/servers.py b/nova/tests/api/rackspace/servers.py index 81682288c..e08c9c6fd 100644 --- a/nova/tests/api/rackspace/servers.py +++ b/nova/tests/api/rackspace/servers.py @@ -103,7 +103,7 @@ class ServersTest(unittest.TestCase): def server_update(context, id, params): self.update_called = True - filtered_dict = dict(name='server_test', adminPass='bacon') + filtered_dict = dict(name='server_test', admin_pass='bacon') self.assertEqual(params, filtered_dict) self.stubs.Set(nova.db.api, 'instance_update', @@ -119,7 +119,8 @@ class ServersTest(unittest.TestCase): self.body = json.dumps(dict(server=inst_dict)) def server_update(context, id, params): - self.assertEqual(params, json.loads(self.body)['server']) + filtered_dict = dict(name='server_test', admin_pass='bacon') + self.assertEqual(params, filtered_dict) self.stubs.Set(nova.db.api, 'instance_update', server_update) -- cgit From 2136f12d29cef9acc7dc6ee0a5901fa3878160f8 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Wed, 29 Sep 2010 15:09:39 -0400 Subject: Make Fault raiseable by inheriting from webob.exc.HTTPException. Change from using self.exception which is reserved by HTTPException to self.wrapped_exc. --- nova/api/rackspace/faults.py | 17 +++++++++-------- nova/tests/api/rackspace/testfaults.py | 10 ++++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/api/rackspace/faults.py b/nova/api/rackspace/faults.py index fd6bc3623..32e5c866f 100644 --- a/nova/api/rackspace/faults.py +++ b/nova/api/rackspace/faults.py @@ -17,11 +17,12 @@ import webob.dec +import webob.exc from nova import wsgi -class Fault(wsgi.Application): +class Fault(webob.exc.HTTPException): """An RS API fault response.""" @@ -39,23 +40,23 @@ class Fault(wsgi.Application): def __init__(self, exception): """Create a Fault for the given webob.exc.exception.""" - self.exception = exception + self.wrapped_exc = exception @webob.dec.wsgify def __call__(self, req): - """Generate a WSGI response based on self.exception.""" + """Generate a WSGI response based on the exception passed to ctor.""" # Replace the body with fault details. - code = self.exception.status_int + code = self.wrapped_exc.status_int fault_name = self._fault_names.get(code, "cloudServersFault") fault_data = { fault_name: { 'code': code, - 'message': self.exception.explanation}} + 'message': self.wrapped_exc.explanation}} if code == 413: - retry = self.exception.headers['Retry-After'] + retry = self.wrapped_exc.headers['Retry-After'] fault_data[fault_name]['retryAfter'] = retry # 'code' is an attribute on the fault tag itself metadata = {'application/xml': {'attributes': {fault_name: 'code'}}} serializer = wsgi.Serializer(req.environ, metadata) - self.exception.body = serializer.to_content_type(fault_data) - return self.exception + self.wrapped_exc.body = serializer.to_content_type(fault_data) + return self.wrapped_exc diff --git a/nova/tests/api/rackspace/testfaults.py b/nova/tests/api/rackspace/testfaults.py index 74caffd30..b2931bc98 100644 --- a/nova/tests/api/rackspace/testfaults.py +++ b/nova/tests/api/rackspace/testfaults.py @@ -1,5 +1,6 @@ import unittest import webob +import webob.dec import webob.exc from nova.api.rackspace import faults @@ -28,3 +29,12 @@ class TestFaults(unittest.TestCase): self.assertTrue('sorry' in body_sans_spaces) self.assertTrue('4' in body_sans_spaces) self.assertEqual(resp.headers['Retry-After'], 4) + + def test_raise(self): + @webob.dec.wsgify + def raiser(req): + raise faults.Fault(webob.exc.HTTPNotFound(explanation='whut?')) + req = webob.Request.blank('/.xml') + resp = req.get_response(raiser) + self.assertEqual(resp.status_int, 404) + self.assertTrue('whut?' in resp.body) -- cgit From 072661db01ed196eac92ceb1e942429a0e380e4a Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Wed, 29 Sep 2010 15:52:02 -0400 Subject: Support reboot in api.rackspace by extracting reboot function from api.ec2 into api.cloud. --- nova/api/cloud.py | 42 ++++++++++++++++++++++++++++++ nova/api/ec2/cloud.py | 8 ++---- nova/api/rackspace/servers.py | 10 +++++-- nova/tests/api/rackspace/images.py | 1 + nova/tests/api/rackspace/sharedipgroups.py | 1 + 5 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 nova/api/cloud.py (limited to 'nova') diff --git a/nova/api/cloud.py b/nova/api/cloud.py new file mode 100644 index 000000000..345677d4f --- /dev/null +++ b/nova/api/cloud.py @@ -0,0 +1,42 @@ +# 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. + +""" +Methods for API calls to control instances via AMQP. +""" + + +from nova import db +from nova import flags +from nova import rpc + +FLAGS = flags.FLAGS + + +def reboot(instance_id, context=None): + """Reboot the given instance. + + #TODO(gundlach) not actually sure what context is used for by ec2 here + -- I think we can just remove it and use None all the time. + """ + instance_ref = db.instance_get_by_ec2_id(None, instance_id) + host = instance_ref['host'] + rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "reboot_instance", + "args": {"context": None, + "instance_id": instance_ref['id']}}) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 05e8065f3..4d962fcdd 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -36,6 +36,7 @@ from nova import quota from nova import rpc from nova import utils from nova.compute.instance_types import INSTANCE_TYPES +from nova.api import cloud from nova.api.ec2 import images @@ -664,12 +665,7 @@ class CloudController(object): def reboot_instances(self, context, instance_id, **kwargs): """instance_id is a list of instance ids""" for id_str in instance_id: - instance_ref = db.instance_get_by_ec2_id(context, id_str) - host = instance_ref['host'] - rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "reboot_instance", - "args": {"context": None, - "instance_id": instance_ref['id']}}) + cloud.reboot(id_str, context=context) return True def delete_volume(self, context, volume_id, **kwargs): diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index aa955c222..4f81e25f9 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -24,6 +24,7 @@ from nova import flags from nova import rpc from nova import utils from nova import wsgi +from nova.api import cloud from nova.api.rackspace import _id_translator from nova.compute import power_state from nova.wsgi import Serializer @@ -171,8 +172,13 @@ class Controller(wsgi.Controller): def action(self, req, id): """ multi-purpose method used to reboot, rebuild, and resize a server """ - if not req.environ.has_key('inst_dict'): - return exc.HTTPUnprocessableEntity() + input_dict = self._deserialize(req.body, req) + try: + reboot_type = input_dict['reboot']['type'] + except Exception: + raise faults.Fault(webob.exc.HTTPNotImplemented()) + opaque_id = _instance_id_translator().from_rsapi_id(id) + cloud.reboot(opaque_id) def _build_server_instance(self, req): """Build instance data structure and save it to the data store.""" diff --git a/nova/tests/api/rackspace/images.py b/nova/tests/api/rackspace/images.py index 560d8c898..4c9987e8b 100644 --- a/nova/tests/api/rackspace/images.py +++ b/nova/tests/api/rackspace/images.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import stubout import unittest from nova.api.rackspace import images diff --git a/nova/tests/api/rackspace/sharedipgroups.py b/nova/tests/api/rackspace/sharedipgroups.py index b4b281db7..1906b54f5 100644 --- a/nova/tests/api/rackspace/sharedipgroups.py +++ b/nova/tests/api/rackspace/sharedipgroups.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import stubout import unittest from nova.api.rackspace import sharedipgroups -- cgit From e258998923b7e8fa92656aa409f875b640df930c Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Wed, 29 Sep 2010 13:26:14 -0700 Subject: Progress on volumes Fixed foreign keys to respect deleted flag --- nova/db/sqlalchemy/api.py | 130 ++++++++++++++++++++++++++++++------------- nova/db/sqlalchemy/models.py | 35 +++++++++--- 2 files changed, 118 insertions(+), 47 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index b5847d299..28b937233 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -30,7 +30,7 @@ from nova.db.sqlalchemy import models from nova.db.sqlalchemy.session import get_session from sqlalchemy import or_ from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import joinedload_all +from sqlalchemy.orm import joinedload, joinedload_all from sqlalchemy.sql import exists, func FLAGS = flags.FLAGS @@ -811,6 +811,7 @@ def quota_destroy(_context, project_id): def volume_allocate_shelf_and_blade(_context, volume_id): + # TODO(devcamcar): Make admin only session = get_session() with session.begin(): export_device = session.query(models.ExportDevice @@ -839,7 +840,7 @@ def volume_attached(_context, volume_id, instance_id, mountpoint): volume_ref.save(session=session) -def volume_create(_context, values): +def volume_create(context, values): volume_ref = models.Volume() for (key, value) in values.iteritems(): volume_ref[key] = value @@ -848,7 +849,7 @@ def volume_create(_context, values): with session.begin(): while volume_ref.ec2_id == None: ec2_id = utils.generate_uid(volume_ref.__prefix__) - if not volume_ec2_id_exists(_context, ec2_id, session=session): + if not volume_ec2_id_exists(context, ec2_id, session=session): volume_ref.ec2_id = ec2_id volume_ref.save(session=session) return volume_ref @@ -876,10 +877,10 @@ def volume_destroy(_context, volume_id): {'id': volume_id}) -def volume_detached(_context, volume_id): +def volume_detached(context, volume_id): session = get_session() with session.begin(): - volume_ref = models.Volume.find(volume_id, session=session) + volume_ref = volume_get(context, volume_id, session=session) volume_ref['status'] = 'available' volume_ref['mountpoint'] = None volume_ref['attach_status'] = 'detached' @@ -887,27 +888,29 @@ def volume_detached(_context, volume_id): volume_ref.save(session=session) -def volume_get(context, volume_id): - session = get_session() +def volume_get(context, volume_id, session=None): + if not session: + session = get_session() + result = None if is_admin_context(context): - volume_ref = session.query(models.Volume - ).filter_by(id=volume_id - ).filter_by(deleted=_deleted(context) - ).first() - if not volume_ref: - raise exception.NotFound('No volume for id %s' % volume_id) + result = session.query(models.Volume + ).filter_by(id=volume_id + ).filter_by(deleted=_deleted(context) + ).first() + elif is_user_context(context): + result = session.query(models.Volume + ).filter_by(project_id=context.project.project_id + ).filter_by(id=volume_id + ).filter_by(deleted=False + ).first() + else: + raise exception.NotAuthorized() - if is_user_context(context): - volume_ref = session.query(models.Volume - ).filter_by(project_id=project_id - ).filter_by(id=volume_id - ).filter_by(deleted=False - ).first() - if not volume_ref: - raise exception.NotFound('No volume for id %s' % volume_id) + if not result: + raise exception.NotFound('No volume for id %s' % volume_id) - raise exception.NotAuthorized() + return result def volume_get_all(context): @@ -916,6 +919,7 @@ def volume_get_all(context): raise exception.NotAuthorized() + def volume_get_all_by_project(context, project_id): if is_user_context(context): if context.project.id != project_id: @@ -932,42 +936,92 @@ def volume_get_all_by_project(context, project_id): def volume_get_by_ec2_id(context, ec2_id): session = get_session() - volume_ref = session.query(models.Volume + result = None + + if is_admin_context(context): + result = session.query(models.Volume ).filter_by(ec2_id=ec2_id ).filter_by(deleted=_deleted(context) ).first() - if not volume_ref: - raise exception.NotFound('Volume %s not found' % (ec2_id)) + elif is_user_context(context): + result = session.query(models.Volume + ).filter_by(project_id=context.project.id + ).filter_by(ec2_id=ec2_id + ).filter_by(deleted=False + ).first() + else: + raise exception.NotAuthorized() - return volume_ref + if not result: + raise exception.NotFound('Volume %s not found' % ec2_id) + + return result def volume_ec2_id_exists(context, ec2_id, session=None): if not session: session = get_session() - return session.query(exists().where(models.Volume.id==ec2_id)).one()[0] + + if is_admin_context(context) or is_user_context(context): + return session.query(exists( + ).where(models.Volume.id==ec2_id) + ).one()[0] + else: + raise exception.NotAuthorized() -def volume_get_instance(_context, volume_id): +def volume_get_instance(context, volume_id): session = get_session() - with session.begin(): - return models.Volume.find(volume_id, session=session).instance + result = None + + if is_admin_context(context): + result = session.query(models.Volume + ).filter_by(id=volume_id + ).filter_by(deleted=_deleted(context) + ).options(joinedload('instance') + ).first() + elif is_user_context(context): + result = session.query(models.Volume + ).filter_by(project_id=context.project.id + ).filter_by(deleted=False + ).options(joinedload('instance') + ).first() + else: + raise exception.NotAuthorized() + + if not result: + raise exception.NotFound('Volume %s not found' % ec2_id) + + return result.instance -def volume_get_shelf_and_blade(_context, volume_id): +def volume_get_shelf_and_blade(context, volume_id): session = get_session() - export_device = session.query(models.ExportDevice - ).filter_by(volume_id=volume_id - ).first() - if not export_device: + result = None + + if is_admin_context(context): + result = session.query(models.ExportDevice + ).filter_by(volume_id=volume_id + ).first() + elif is_user_context(context): + result = session.query(models.ExportDevice + ).join(models.Volume + ).filter(models.Volume.project_id==context.project.id + ).filter_by(volume_id=volume_id + ).first() + else: + raise exception.NotAuthorized() + + if not result: raise exception.NotFound() - return (export_device.shelf_id, export_device.blade_id) + return (result.shelf_id, result.blade_id) -def volume_update(_context, volume_id, values): + +def volume_update(context, volume_id, values): session = get_session() with session.begin(): - volume_ref = models.Volume.find(volume_id, session=session) + volume_ref = volume_get(context, volume_id, session=session) for (key, value) in values.iteritems(): volume_ref[key] = value volume_ref.save(session=session) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 01e58b05e..1b9edf475 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -282,7 +282,11 @@ class Volume(BASE, NovaBase): size = Column(Integer) availability_zone = Column(String(255)) # TODO(vish): foreign key? instance_id = Column(Integer, ForeignKey('instances.id'), nullable=True) - instance = relationship(Instance, backref=backref('volumes')) + instance = relationship(Instance, + backref=backref('volumes'), + foreign_keys=instance_id, + primaryjoin='and_(Volume.instance_id==Instance.id,' + 'Volume.deleted==False)') mountpoint = Column(String(255)) attach_time = Column(String(255)) # TODO(vish): datetime status = Column(String(255)) # TODO(vish): enum? @@ -333,8 +337,11 @@ class ExportDevice(BASE, NovaBase): shelf_id = Column(Integer) blade_id = Column(Integer) volume_id = Column(Integer, ForeignKey('volumes.id'), nullable=True) - volume = relationship(Volume, backref=backref('export_device', - uselist=False)) + volume = relationship(Volume, + backref=backref('export_device', uselist=False), + foreign_keys=volume_id, + primaryjoin='and_(ExportDevice.volume_id==Volume.id,' + 'ExportDevice.deleted==False)') class KeyPair(BASE, NovaBase): @@ -407,8 +414,12 @@ class NetworkIndex(BASE, NovaBase): id = Column(Integer, primary_key=True) index = Column(Integer, unique=True) network_id = Column(Integer, ForeignKey('networks.id'), nullable=True) - network = relationship(Network, backref=backref('network_index', - uselist=False)) + network = relationship(Network, + backref=backref('network_index', uselist=False), + foreign_keys=network_id, + primaryjoin='and_(NetworkIndex.network_id==Network.id,' + 'NetworkIndex.deleted==False)') + class AuthToken(BASE, NovaBase): """Represents an authorization token for all API transactions. Fields @@ -432,8 +443,11 @@ class FixedIp(BASE, NovaBase): network_id = Column(Integer, ForeignKey('networks.id'), nullable=True) network = relationship(Network, backref=backref('fixed_ips')) instance_id = Column(Integer, ForeignKey('instances.id'), nullable=True) - instance = relationship(Instance, backref=backref('fixed_ip', - uselist=False)) + instance = relationship(Instance, + backref=backref('fixed_ip', uselist=False), + foreign_keys=instance_id, + primaryjoin='and_(FixedIp.instance_id==Instance.id,' + 'FixedIp.deleted==False)') allocated = Column(Boolean, default=False) leased = Column(Boolean, default=False) reserved = Column(Boolean, default=False) @@ -462,8 +476,11 @@ class FloatingIp(BASE, NovaBase): id = Column(Integer, primary_key=True) address = Column(String(255)) fixed_ip_id = Column(Integer, ForeignKey('fixed_ips.id'), nullable=True) - fixed_ip = relationship(FixedIp, backref=backref('floating_ips')) - + fixed_ip = relationship(FixedIp, + backref=backref('floating_ips'), + foreign_keys=fixed_ip_id, + primaryjoin='and_(FloatingIp.fixed_ip_id==FixedIp.id,' + 'FloatingIp.deleted==False)') project_id = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) -- cgit From 128ec65cf39e74b53903dd9788a58c8eb513abe8 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 29 Sep 2010 16:21:28 -0500 Subject: Server creation up to, but not including, network configuration --- nova/api/rackspace/servers.py | 97 +++++++++++++++++++++------------ nova/db/sqlalchemy/models.py | 1 - nova/tests/api/rackspace/auth.py | 8 ++- nova/tests/api/rackspace/servers.py | 48 +++++++++++----- nova/tests/api/rackspace/test_helper.py | 17 +++++- nova/wsgi.py | 11 ++-- 6 files changed, 123 insertions(+), 59 deletions(-) (limited to 'nova') diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index aa955c222..cc971adc0 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -25,6 +25,7 @@ from nova import rpc from nova import utils from nova import wsgi from nova.api.rackspace import _id_translator +from nova.compute import instance_types from nova.compute import power_state from nova.wsgi import Serializer import nova.image.service @@ -39,11 +40,11 @@ def _instance_id_translator(): ids """ return _id_translator.RackspaceAPIIdTranslator( "instance", 'nova') -def _image_id_translator(): +def _image_service(): """ Helper method for initializing the image id translator """ service = nova.image.service.ImageService.load() - return _id_translator.RackspaceAPIIdTranslator( - "image", service.__class__.__name__) + return (service, _id_translator.RackspaceAPIIdTranslator( + "image", service.__class__.__name__)) def _filter_params(inst_dict): """ Extracts all updatable parameters for a server update request """ @@ -122,8 +123,11 @@ class Controller(wsgi.Controller): def show(self, req, id): """ Returns server details by server id """ + inst_id_trans = _instance_id_translator() + inst_id = inst_id_trans.from_rs_id(id) + user_id = req.environ['nova.context']['user']['id'] - inst = self.db_driver.instance_get(None, id) + inst = self.db_driver.instance_get_by_ec2_id(None, inst_id) if inst: if inst.user_id == user_id: return _entity_detail(inst) @@ -131,8 +135,11 @@ class Controller(wsgi.Controller): def delete(self, req, id): """ Destroys a server """ + inst_id_trans = _instance_id_translator() + inst_id = inst_id_trans.from_rs_id(id) + user_id = req.environ['nova.context']['user']['id'] - instance = self.db_driver.instance_get(None, id) + instance = self.db_driver.instance_get_by_ec2_id(None, inst_id) if instance and instance['user_id'] == user_id: self.db_driver.instance_destroy(None, id) return exc.HTTPAccepted() @@ -140,10 +147,16 @@ class Controller(wsgi.Controller): def create(self, req): """ Creates a new server for a given user """ - if not req.environ.has_key('inst_dict'): + + env = self._deserialize(req.body, req) + if not env: return exc.HTTPUnprocessableEntity() - inst = self._build_server_instance(req) + #try: + inst = self._build_server_instance(req, env) + #except Exception, e: + # print e + # return exc.HTTPUnprocessableEntity() rpc.cast( FLAGS.compute_topic, { @@ -153,6 +166,8 @@ class Controller(wsgi.Controller): def update(self, req, id): """ Updates the server name or password """ + inst_id_trans = _instance_id_translator() + inst_id = inst_id_trans.from_rs_id(id) user_id = req.environ['nova.context']['user']['id'] inst_dict = self._deserialize(req.body, req) @@ -160,7 +175,7 @@ class Controller(wsgi.Controller): if not inst_dict: return exc.HTTPUnprocessableEntity() - instance = self.db_driver.instance_get(None, id) + instance = self.db_driver.instance_get_by_ec2_id(None, inst_id) if not instance or instance.user_id != user_id: return exc.HTTPNotFound() @@ -174,56 +189,68 @@ class Controller(wsgi.Controller): if not req.environ.has_key('inst_dict'): return exc.HTTPUnprocessableEntity() - def _build_server_instance(self, req): + def _build_server_instance(self, req, env): """Build instance data structure and save it to the data store.""" ltime = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) inst = {} inst_id_trans = _instance_id_translator() - image_id_trans = _image_id_translator() - env = req.environ['inst_dict'] user_id = req.environ['nova.context']['user']['id'] - inst['rs_id'] = inst_id_trans.to_rs_id() + instance_type, flavor = None, None + for k, v in instance_types.INSTANCE_TYPES.iteritems(): + if v['flavorid'] == env['server']['flavorId']: + instance_type, flavor = k, v + break + + if not flavor: + raise Exception, "Flavor not found" + image_id = env['server']['imageId'] - opaque_id = translator_instance().from_rs_id(image_id) + img_service, image_id_trans = _image_service() + + opaque_image_id = image_id_trans.to_rs_id(image_id) + image = img_service.show(opaque_image_id) - inst['name'] = env['server']['name'] - inst['image_id'] = opaque_id - inst['instance_type'] = env['server']['flavorId'] + if not image: + raise Exception, "Image not found" + + inst['server_name'] = env['server']['name'] + inst['image_id'] = opaque_image_id inst['user_id'] = user_id inst['launch_time'] = ltime inst['mac_address'] = utils.generate_mac() + inst['project_id'] = user_id - #TODO(dietz) These are the attributes I'm unsure of inst['state_description'] = 'scheduling' - inst['kernel_id'] = '' - inst['ramdisk_id'] = '' + inst['kernel_id'] = image.get('kernelId', FLAGS.default_kernel) + inst['ramdisk_id'] = image.get('ramdiskId', FLAGS.default_ramdisk) inst['reservation_id'] = utils.generate_uid('r') - inst['key_data'] = '' - inst['key_name'] = '' - inst['security_group'] = '' - # Flavor related attributes - inst['instance_type'] = '' - inst['memory_mb'] = '' - inst['vcpus'] = '' - inst['local_gb'] = '' + #TODO(dietz) this may be ill advised + key_pair_ref = self.db_driver.key_pair_get_all_by_user( + None, user_id)[0] - + inst['key_data'] = key_pair_ref['public_key'] + inst['key_name'] = key_pair_ref['name'] - #TODO(dietz): This seems necessary. How do these apply across - #the Rackspace implementation? - inst['project_id'] = '' + #TODO(dietz) stolen from ec2 api, see TODO there + inst['security_group'] = 'default' - self.network_manager = utils.import_object(FLAGS.rs_network_manager) - - address = self.network_manager.allocate_fixed_ip( None, inst['id']) + # Flavor related attributes + inst['instance_type'] = instance_type + inst['memory_mb'] = flavor['memory_mb'] + inst['vcpus'] = flavor['vcpus'] + inst['local_gb'] = flavor['local_gb'] ref = self.db_driver.instance_create(None, inst) - inst['id'] = ref.id + inst['id'] = inst_id_trans.to_rs_id(ref.ec2_id) + + #self.network_manager = utils.import_object(FLAGS.rs_network_manager) + # + #address = self.network_manager.allocate_fixed_ip( None, inst['id']) return inst diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 2a314b037..22446fc64 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -199,7 +199,6 @@ class Instance(BASE, NovaBase): id = Column(Integer, primary_key=True) ec2_id = Column(String(10), unique=True) - rs_id = Column(String(255)) admin_pass = Column(String(255)) user_id = Column(String(255)) diff --git a/nova/tests/api/rackspace/auth.py b/nova/tests/api/rackspace/auth.py index a6e10970f..56677c2f4 100644 --- a/nova/tests/api/rackspace/auth.py +++ b/nova/tests/api/rackspace/auth.py @@ -1,12 +1,14 @@ -import webob -import webob.dec +import datetime import unittest + import stubout +import webob +import webob.dec + import nova.api import nova.api.rackspace.auth from nova import auth from nova.tests.api.rackspace import test_helper -import datetime class Test(unittest.TestCase): def setUp(self): diff --git a/nova/tests/api/rackspace/servers.py b/nova/tests/api/rackspace/servers.py index e08c9c6fd..d759eba3c 100644 --- a/nova/tests/api/rackspace/servers.py +++ b/nova/tests/api/rackspace/servers.py @@ -26,6 +26,7 @@ import nova.api.rackspace from nova.api.rackspace import servers import nova.db.api from nova.db.sqlalchemy.models import Instance +import nova.rpc from nova.tests.api.test_helper import * from nova.tests.api.rackspace import test_helper @@ -52,8 +53,11 @@ class ServersTest(unittest.TestCase): test_helper.stub_for_testing(self.stubs) test_helper.stub_out_rate_limiting(self.stubs) test_helper.stub_out_auth(self.stubs) + test_helper.stub_out_id_translator(self.stubs) + test_helper.stub_out_key_pair_funcs(self.stubs) + test_helper.stub_out_image_service(self.stubs) self.stubs.Set(nova.db.api, 'instance_get_all', return_servers) - self.stubs.Set(nova.db.api, 'instance_get', return_server) + self.stubs.Set(nova.db.api, 'instance_get_by_ec2_id', return_server) self.stubs.Set(nova.db.api, 'instance_get_all_by_user', return_servers) @@ -67,9 +71,6 @@ class ServersTest(unittest.TestCase): self.assertEqual(res_dict['server']['id'], '1') self.assertEqual(res_dict['server']['name'], 'server1') - def test_get_backup_schedule(self): - pass - def test_get_server_list(self): req = webob.Request.blank('/v1.0/servers') res = req.get_response(nova.api.API()) @@ -82,19 +83,36 @@ class ServersTest(unittest.TestCase): self.assertEqual(s.get('imageId', None), None) i += 1 - #def test_create_instance(self): - # test_helper.stub_out_image_translator(self.stubs) - # body = dict(server=dict( - # name='server_test', imageId=2, flavorId=2, metadata={}, - # personality = {} - # )) - # req = webob.Request.blank('/v1.0/servers') - # req.method = 'POST' - # req.body = json.dumps(body) + def test_create_instance(self): + def instance_create(context, inst): + class Foo(object): + ec2_id = 1 + return Foo() - # res = req.get_response(nova.api.API()) + def fake_cast(*args, **kwargs): + pass - # print res + self.stubs.Set(nova.db.api, 'instance_create', instance_create) + self.stubs.Set(nova.rpc, 'cast', fake_cast) + + test_helper.stub_out_id_translator(self.stubs) + body = dict(server=dict( + name='server_test', imageId=2, flavorId=2, metadata={}, + personality = {} + )) + req = webob.Request.blank('/v1.0/servers') + req.method = 'POST' + req.body = json.dumps(body) + + res = req.get_response(nova.api.API()) + + self.assertEqual(res.status_int, 200) + + def test_update_no_body(self): + req = webob.Request.blank('/v1.0/servers/1') + req.method = 'PUT' + res = req.get_response(nova.api.API()) + self.assertEqual(res.status_int, 422) def test_update_bad_params(self): """ Confirm that update is filtering params """ diff --git a/nova/tests/api/rackspace/test_helper.py b/nova/tests/api/rackspace/test_helper.py index aa7fb382c..962f0ba4c 100644 --- a/nova/tests/api/rackspace/test_helper.py +++ b/nova/tests/api/rackspace/test_helper.py @@ -9,6 +9,7 @@ from nova import utils from nova import flags import nova.api.rackspace.auth import nova.api.rackspace._id_translator +from nova.image import service from nova.wsgi import Router FLAGS = flags.FLAGS @@ -40,7 +41,21 @@ def fake_wsgi(self, req): req.environ['inst_dict'] = json.loads(req.body) return self.application -def stub_out_image_translator(stubs): + + +def stub_out_key_pair_funcs(stubs): + def key_pair(context, user_id): + return [dict(name='key', public_key='public_key')] + stubs.Set(nova.db.api, 'key_pair_get_all_by_user', + key_pair) + +def stub_out_image_service(stubs): + def fake_image_show(meh, id): + return dict(kernelId=1, ramdiskId=1) + + stubs.Set(nova.image.service.LocalImageService, 'show', fake_image_show) + +def stub_out_id_translator(stubs): class FakeTranslator(object): def __init__(self, id_type, service_name): pass diff --git a/nova/wsgi.py b/nova/wsgi.py index 48c4dabc2..b91d91121 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -281,10 +281,13 @@ class Serializer(object): The string must be in the format of a supported MIME type. """ datastring = datastring.strip() - is_xml = (datastring[0] == '<') - if not is_xml: - return json.loads(datastring) - return self._from_xml(datastring) + try: + is_xml = (datastring[0] == '<') + if not is_xml: + return json.loads(datastring) + return self._from_xml(datastring) + except: + return None def _from_xml(self, datastring): xmldata = self.metadata.get('application/xml', {}) -- cgit From f4cf49ec3761bdd38dd1a6cb064875b90e65ad4e Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Wed, 29 Sep 2010 14:27:31 -0700 Subject: Wired up context auth for services --- nova/db/sqlalchemy/api.py | 111 ++++++++++++++++++++++++++++++++++--------- nova/db/sqlalchemy/models.py | 15 ------ 2 files changed, 89 insertions(+), 37 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 28b937233..01a5af38b 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -71,16 +71,37 @@ def is_user_context(context): def service_destroy(context, service_id): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): - service_ref = models.Service.find(service_id, session=session) + service_ref = service_get(context, service_id, session=session) service_ref.delete(session=session) -def service_get(_context, service_id): - return models.Service.find(service_id) + +def service_get(context, service_id, session=None): + if not is_admin_context(context): + raise exception.NotAuthorized() + + if not session: + session = get_session() + + result = session.query(models.Service + ).filter_by(id=service_id + ).filter_by(deleted=_deleted(context) + ).first() + + if not result: + raise exception.NotFound('No service for id %s' % service_id) + + return result def service_get_all_by_topic(context, topic): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() return session.query(models.Service ).filter_by(deleted=False @@ -89,7 +110,10 @@ def service_get_all_by_topic(context, topic): ).all() -def _service_get_all_topic_subquery(_context, session, topic, subq, label): +def _service_get_all_topic_subquery(context, session, topic, subq, label): + if not is_admin_context(context): + raise exception.NotAuthorized() + sort_value = getattr(subq.c, label) return session.query(models.Service, func.coalesce(sort_value, 0) ).filter_by(topic=topic @@ -101,6 +125,9 @@ def _service_get_all_topic_subquery(_context, session, topic, subq, label): def service_get_all_compute_sorted(context): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): # NOTE(vish): The intended query is below @@ -125,6 +152,9 @@ def service_get_all_compute_sorted(context): def service_get_all_network_sorted(context): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): topic = 'network' @@ -142,6 +172,9 @@ def service_get_all_network_sorted(context): def service_get_all_volume_sorted(context): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): topic = 'volume' @@ -158,11 +191,27 @@ def service_get_all_volume_sorted(context): label) -def service_get_by_args(_context, host, binary): - return models.Service.find_by_args(host, binary) +def service_get_by_args(context, host, binary): + if not is_admin_context(context): + raise exception.NotAuthorized() + + session = get_session() + result = session.query(models.Service + ).filter_by(host=host + ).filter_by(binary=binary + ).filter_by(deleted=_deleted(context) + ).first() + + if not result: + raise exception.NotFound('No service for %s, %s' % (host, binary)) + + return result + +def service_create(context, values): + if not is_admin_context(context): + return exception.NotAuthorized() -def service_create(_context, values): service_ref = models.Service() for (key, value) in values.iteritems(): service_ref[key] = value @@ -170,10 +219,13 @@ def service_create(_context, values): return service_ref -def service_update(_context, service_id, values): +def service_update(context, service_id, values): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): - service_ref = models.Service.find(service_id, session=session) + service_ref = session_get(context, service_id, session=session) for (key, value) in values.iteritems(): service_ref[key] = value service_ref.save(session=session) @@ -428,8 +480,8 @@ def instance_destroy(_context, instance_id): instance_ref.delete(session=session) -def instance_get(context, instance_id): - return models.Instance.find(instance_id, deleted=_deleted(context)) +def instance_get(context, instance_id, session=None): + return models.Instance.find(instance_id, session=session, deleted=_deleted(context)) def instance_get_all(context): @@ -810,8 +862,10 @@ def quota_destroy(_context, project_id): ################### -def volume_allocate_shelf_and_blade(_context, volume_id): - # TODO(devcamcar): Make admin only +def volume_allocate_shelf_and_blade(context, volume_id): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): export_device = session.query(models.ExportDevice @@ -828,15 +882,17 @@ def volume_allocate_shelf_and_blade(_context, volume_id): return (export_device.shelf_id, export_device.blade_id) -def volume_attached(_context, volume_id, instance_id, mountpoint): +def volume_attached(context, volume_id, instance_id, mountpoint): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): - volume_ref = models.Volume.find(volume_id, session=session) + volume_ref = volume_get(context, volume_id, session=session) volume_ref['status'] = 'in-use' volume_ref['mountpoint'] = mountpoint volume_ref['attach_status'] = 'attached' - volume_ref.instance = models.Instance.find(instance_id, - session=session) + volume_ref.instance = instance_get(context, instance_id, session=session) volume_ref.save(session=session) @@ -855,7 +911,10 @@ def volume_create(context, values): return volume_ref -def volume_data_get_for_project(_context, project_id): +def volume_data_get_for_project(context, project_id): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() result = session.query(func.count(models.Volume.id), func.sum(models.Volume.size) @@ -866,7 +925,10 @@ def volume_data_get_for_project(_context, project_id): return (result[0] or 0, result[1] or 0) -def volume_destroy(_context, volume_id): +def volume_destroy(context, volume_id): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): # TODO(vish): do we have to use sql here? @@ -878,6 +940,9 @@ def volume_destroy(_context, volume_id): def volume_detached(context, volume_id): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): volume_ref = volume_get(context, volume_id, session=session) @@ -914,10 +979,12 @@ def volume_get(context, volume_id, session=None): def volume_get_all(context): - if is_admin_context(context): - return models.Volume.all(deleted=_deleted(context)) + if not is_admin_context(context): + raise exception.NotAuthorized() - raise exception.NotAuthorized() + return session.query(models.Volume + ).filter_by(deleted=_deleted(context) + ).all() def volume_get_all_by_project(context, project_id): diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 1b9edf475..b9bb8e4f2 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -176,21 +176,6 @@ class Service(BASE, NovaBase): report_count = Column(Integer, nullable=False, default=0) disabled = Column(Boolean, default=False) - @classmethod - def find_by_args(cls, host, binary, session=None, deleted=False): - if not session: - session = get_session() - try: - return session.query(cls - ).filter_by(host=host - ).filter_by(binary=binary - ).filter_by(deleted=deleted - ).one() - except exc.NoResultFound: - new_exc = exception.NotFound("No model for %s, %s" % (host, - binary)) - raise new_exc.__class__, new_exc, sys.exc_info()[2] - class Instance(BASE, NovaBase): """Represents a guest vm""" -- cgit From 35741ff23bec2b4f301b93128fd018e9c8e70945 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 29 Sep 2010 17:06:35 -0500 Subject: Servers stuff --- nova/api/rackspace/servers.py | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 357e0895f..40cd4f691 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -26,6 +26,8 @@ from nova import utils from nova import wsgi from nova.api import cloud from nova.api.rackspace import _id_translator +from nova.api.rackspace import context +from nova.api.rackspace import faults from nova.compute import instance_types from nova.compute import power_state from nova.wsgi import Serializer @@ -254,10 +256,38 @@ class Controller(wsgi.Controller): ref = self.db_driver.instance_create(None, inst) inst['id'] = inst_id_trans.to_rs_id(ref.ec2_id) - #self.network_manager = utils.import_object(FLAGS.rs_network_manager) - # - #address = self.network_manager.allocate_fixed_ip( None, inst['id']) + # TODO(dietz): this isn't explicitly necessary, but the networking + # calls depend on an object with a project_id property + context = context.APIRequestContext(user_id) + + inst['mac_address'] = utils.generate_mac() + + #TODO(dietz) is this necessary? + inst['launch_index'] = 0 + + inst['hostname'] = instance_ref['ec2_id'] + self.db_driver.instance_update(None, inst_id, inst) + self.network_manager = utils.import_object(FLAGS.rs_network_manager) + address = self.network_manager.allocate_fixed_ip(context, inst_id) + + # TODO(vish): This probably should be done in the scheduler + # network is setup when host is assigned + network_topic = self._get_network_topic(user_id) + rpc.call(network_topic, + {"method": "setup_fixed_ip", + "args": {"context": None, + "address": address}}) return inst - + def _get_network_topic(self, user_id): + """Retrieves the network host for a project""" + network_ref = self.db_driver.project_get_network(None, + user_id) + host = network_ref['host'] + if not host: + host = rpc.call(FLAGS.network_topic, + {"method": "set_network_host", + "args": {"context": None, + "project_id": user_id}}) + return self.db_driver.queue_get_for(None, FLAGS.network_topic, host) -- cgit From 33f101c309852f358ab30a8af64ef64a848f16ae Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 29 Sep 2010 17:36:26 -0500 Subject: Some minor cleanup --- nova/api/rackspace/servers.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index c3d56debd..0d285999b 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -213,14 +213,11 @@ class Controller(wsgi.Controller): user_id = req.environ['nova.context']['user']['id'] - instance_type, flavor = None, None - for k, v in instance_types.INSTANCE_TYPES.iteritems(): - if v['flavorid'] == env['server']['flavorId']: - instance_type, flavor = k, v - break + flavor_id = env['server']['flavorId'] - if not flavor: - raise Exception, "Flavor not found" + instance_type, flavor = [(k, v) for k, v in + instance_types.INSTANCE_TYPES.iteritems() + if v['flavorid'] == flavor_id][0] image_id = env['server']['imageId'] @@ -262,10 +259,10 @@ class Controller(wsgi.Controller): ref = self.db_driver.instance_create(None, inst) inst['id'] = inst_id_trans.to_rs_id(ref.ec2_id) - # TODO(dietz): this isn't explicitly necessary, but the networking - # calls depend on an object with a project_id property + # calls depend on an object with a project_id property, and therefore + # should be cleaned up later api_context = context.APIRequestContext(user_id) inst['mac_address'] = utils.generate_mac() -- cgit From 41e940a5cfa62d56e7d4f111827217c64f0ec61d Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 29 Sep 2010 17:40:46 -0500 Subject: Forgot the context module --- nova/api/rackspace/context.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 nova/api/rackspace/context.py (limited to 'nova') diff --git a/nova/api/rackspace/context.py b/nova/api/rackspace/context.py new file mode 100644 index 000000000..3a7941f0f --- /dev/null +++ b/nova/api/rackspace/context.py @@ -0,0 +1,32 @@ +# 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. + +""" +APIRequestContext +""" + +import random + +class APIRequestContext(object): + """ This is an adapter class to get around all of the assumptions made in + the FlatNetworking """ + def __init__(self, user_id): + class Dummy(object): pass + self.user_id = user_id + self.project = Dummy() + self.project.id = self.user_id -- cgit From 6b932780b6bf10b387ad04be6ec88395cae6b564 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 29 Sep 2010 17:52:15 -0500 Subject: pylint and pep8 cleanup --- nova/api/rackspace/context.py | 8 +++++--- nova/api/rackspace/servers.py | 8 +++----- nova/tests/api/rackspace/flavors.py | 1 - 3 files changed, 8 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/api/rackspace/context.py b/nova/api/rackspace/context.py index 3a7941f0f..924ee151e 100644 --- a/nova/api/rackspace/context.py +++ b/nova/api/rackspace/context.py @@ -22,11 +22,13 @@ APIRequestContext import random +class Project(object): + def __init__(self, user_id): + self.id = user_id + class APIRequestContext(object): """ This is an adapter class to get around all of the assumptions made in the FlatNetworking """ def __init__(self, user_id): - class Dummy(object): pass self.user_id = user_id - self.project = Dummy() - self.project.id = self.user_id + self.project = Project(user_id) diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 0d285999b..965deb402 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -15,7 +15,6 @@ # License for the specific language governing permissions and limitations # under the License. -import datetime import time import webob @@ -31,7 +30,6 @@ from nova.api.rackspace import context from nova.api.rackspace import faults from nova.compute import instance_types from nova.compute import power_state -from nova.wsgi import Serializer import nova.api.rackspace import nova.image.service @@ -201,7 +199,7 @@ class Controller(wsgi.Controller): reboot_type = input_dict['reboot']['type'] except Exception: raise faults.Fault(webob.exc.HTTPNotImplemented()) - opaque_id = _instance_id_translator().from_rsapi_id(id) + opaque_id = _instance_id_translator().from_rs_id(id) cloud.reboot(opaque_id) def _build_server_instance(self, req, env): @@ -273,8 +271,8 @@ class Controller(wsgi.Controller): inst['hostname'] = ref.ec2_id self.db_driver.instance_update(None, inst['id'], inst) - self.network_manager = utils.import_object(FLAGS.rs_network_manager) - address = self.network_manager.allocate_fixed_ip(api_context, + network_manager = utils.import_object(FLAGS.rs_network_manager) + address = network_manager.allocate_fixed_ip(api_context, inst['id']) # TODO(vish): This probably should be done in the scheduler diff --git a/nova/tests/api/rackspace/flavors.py b/nova/tests/api/rackspace/flavors.py index 7bd1ea1c4..d25a2e2be 100644 --- a/nova/tests/api/rackspace/flavors.py +++ b/nova/tests/api/rackspace/flavors.py @@ -38,7 +38,6 @@ class FlavorsTest(unittest.TestCase): def test_get_flavor_list(self): req = webob.Request.blank('/v1.0/flavors') res = req.get_response(nova.api.API()) - print res def test_get_flavor_by_id(self): pass -- cgit From 13a73f2606f4b9dee4e51cccbb7e48c8ce322b76 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 29 Sep 2010 18:00:24 -0500 Subject: Missed a few attributes while mirroring the ec2 instance spin up --- nova/api/rackspace/servers.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'nova') diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 965deb402..2395cb358 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -239,6 +239,9 @@ class Controller(wsgi.Controller): inst['ramdisk_id'] = image.get('ramdiskId', FLAGS.default_ramdisk) inst['reservation_id'] = utils.generate_uid('r') + inst['display_name'] = env['server']['name'] + inst['display_description'] = env['server']['name'] + #TODO(dietz) this may be ill advised key_pair_ref = self.db_driver.key_pair_get_all_by_user( None, user_id)[0] -- cgit From b075b504a0a402fc4e8c24379804633139883008 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 29 Sep 2010 19:28:14 -0500 Subject: Whoops, forgot the exception handling bit --- nova/api/rackspace/servers.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 2395cb358..11efd8aef 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -160,11 +160,10 @@ class Controller(wsgi.Controller): if not env: return faults.Fault(exc.HTTPUnprocessableEntity()) - #try: - inst = self._build_server_instance(req, env) - #except Exception, e: - # print e - # return exc.HTTPUnprocessableEntity() + try: + inst = self._build_server_instance(req, env) + except Exception, e: + return faults.Fault(exc.HTTPUnprocessableEntity()) rpc.cast( FLAGS.compute_topic, { -- cgit From 734df1fbad8195e7cd7072d0d0aeb5b94841f121 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Wed, 29 Sep 2010 19:09:00 -0700 Subject: Made network tests pass again --- nova/db/api.py | 1 - nova/db/sqlalchemy/api.py | 233 +++++++++++++++++++++++++++++------------ nova/db/sqlalchemy/models.py | 26 ----- nova/network/manager.py | 3 +- nova/tests/network_unittest.py | 1 + 5 files changed, 170 insertions(+), 94 deletions(-) (limited to 'nova') diff --git a/nova/db/api.py b/nova/db/api.py index b68a0fe8f..4cfdd788c 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -175,7 +175,6 @@ def floating_ip_get_by_address(context, address): return IMPL.floating_ip_get_by_address(context, address) -def floating_ip_get_instance(context, address): """Get an instance for a floating ip by address.""" return IMPL.floating_ip_get_instance(context, address) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 01a5af38b..d129df2be 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -234,7 +234,13 @@ def service_update(context, service_id, values): ################### -def floating_ip_allocate_address(_context, host, project_id): +def floating_ip_allocate_address(context, host, project_id): + if is_user_context(context): + if context.project.id != project_id: + raise exception.NotAuthorized() + elif not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): floating_ip_ref = session.query(models.FloatingIp @@ -253,7 +259,10 @@ def floating_ip_allocate_address(_context, host, project_id): return floating_ip_ref['address'] -def floating_ip_create(_context, values): +def floating_ip_create(context, values): + if not is_user_context(context) and not is_admin_context(context): + raise exception.NotAuthorized() + floating_ip_ref = models.FloatingIp() for (key, value) in values.iteritems(): floating_ip_ref[key] = value @@ -261,7 +270,13 @@ def floating_ip_create(_context, values): return floating_ip_ref['address'] -def floating_ip_count_by_project(_context, project_id): +def floating_ip_count_by_project(context, project_id): + if is_user_context(context): + if context.project.id != project_id: + raise exception.NotAuthorized() + elif not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() return session.query(models.FloatingIp ).filter_by(project_id=project_id @@ -269,39 +284,63 @@ def floating_ip_count_by_project(_context, project_id): ).count() -def floating_ip_fixed_ip_associate(_context, floating_address, fixed_address): +#@require_context +def floating_ip_fixed_ip_associate(context, floating_address, fixed_address): + if not is_user_context(context) and not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): - floating_ip_ref = models.FloatingIp.find_by_str(floating_address, - session=session) - fixed_ip_ref = models.FixedIp.find_by_str(fixed_address, - session=session) + # TODO(devcamcar): How to ensure floating_id belongs to user? + floating_ip_ref = floating_ip_get_by_address(context, + floating_address, + session=session) + fixed_ip_ref = fixed_ip_get_by_address(context, + fixed_address, + session=session) floating_ip_ref.fixed_ip = fixed_ip_ref floating_ip_ref.save(session=session) -def floating_ip_deallocate(_context, address): +#@require_context +def floating_ip_deallocate(context, address): + if not is_user_context(context) and not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): - floating_ip_ref = models.FloatingIp.find_by_str(address, - session=session) + # TODO(devcamcar): How to ensure floating id belongs to user? + floating_ip_ref = floating_ip_get_by_address(context, + address, + session=session) floating_ip_ref['project_id'] = None floating_ip_ref.save(session=session) +#@require_context +def floating_ip_destroy(context, address): + if not is_user_context(context) and not is_admin_context(context): + raise exception.NotAuthorized() -def floating_ip_destroy(_context, address): session = get_session() with session.begin(): - floating_ip_ref = models.FloatingIp.find_by_str(address, - session=session) + # TODO(devcamcar): Ensure address belongs to user. + floating_ip_ref = get_floating_ip_by_address(context, + address, + session=session) floating_ip_ref.delete(session=session) -def floating_ip_disassociate(_context, address): +def floating_ip_disassociate(context, address): + if not is_user_context(context) and is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): - floating_ip_ref = models.FloatingIp.find_by_str(address, - session=session) + # TODO(devcamcar): Ensure address belongs to user. + # Does get_floating_ip_by_address handle this? + floating_ip_ref = floating_ip_get_by_address(context, + address, + session=session) fixed_ip_ref = floating_ip_ref.fixed_ip if fixed_ip_ref: fixed_ip_address = fixed_ip_ref['address'] @@ -311,16 +350,22 @@ def floating_ip_disassociate(_context, address): floating_ip_ref.save(session=session) return fixed_ip_address +#@require_admin_context +def floating_ip_get_all(context): + if not is_admin_context(context): + raise exception.NotAuthorized() -def floating_ip_get_all(_context): session = get_session() return session.query(models.FloatingIp ).options(joinedload_all('fixed_ip.instance') ).filter_by(deleted=False ).all() +#@require_admin_context +def floating_ip_get_all_by_host(context, host): + if not is_admin_context(context): + raise exception.NotAuthorized() -def floating_ip_get_all_by_host(_context, host): session = get_session() return session.query(models.FloatingIp ).options(joinedload_all('fixed_ip.instance') @@ -328,7 +373,15 @@ def floating_ip_get_all_by_host(_context, host): ).filter_by(deleted=False ).all() -def floating_ip_get_all_by_project(_context, project_id): +#@require_context +def floating_ip_get_all_by_project(context, project_id): + # TODO(devcamcar): Change to decorate and check project_id separately. + if is_user_context(context): + if context.project.id != project_id: + raise exception.NotAuthorized() + elif not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() return session.query(models.FloatingIp ).options(joinedload_all('fixed_ip.instance') @@ -336,22 +389,38 @@ def floating_ip_get_all_by_project(_context, project_id): ).filter_by(deleted=False ).all() -def floating_ip_get_by_address(_context, address): - return models.FloatingIp.find_by_str(address) +#@require_context +def floating_ip_get_by_address(context, address, session=None): + # TODO(devcamcar): Ensure the address belongs to user. + if not is_user_context(context) and not is_admin_context(context): + raise exception.NotAuthorized() + + if not session: + session = get_session() + + result = session.query(models.FloatingIp + ).filter_by(address=address + ).filter_by(deleted=_deleted(context) + ).first() + if not result: + raise exception.NotFound('No fixed ip for address %s' % address) + return result -def floating_ip_get_instance(_context, address): - session = get_session() - with session.begin(): - floating_ip_ref = models.FloatingIp.find_by_str(address, - session=session) - return floating_ip_ref.fixed_ip.instance + + # floating_ip_ref = get_floating_ip_by_address(context, + # address, + # session=session) + # return floating_ip_ref.fixed_ip.instance ################### +#@require_context +def fixed_ip_associate(context, address, instance_id): + if not is_user_context(context) and not is_admin_context(context): + raise exception.NotAuthorized() -def fixed_ip_associate(_context, address, instance_id): session = get_session() with session.begin(): fixed_ip_ref = session.query(models.FixedIp @@ -364,12 +433,17 @@ def fixed_ip_associate(_context, address, instance_id): # then this has concurrency issues if not fixed_ip_ref: raise db.NoMoreAddresses() - fixed_ip_ref.instance = models.Instance.find(instance_id, - session=session) + fixed_ip_ref.instance = instance_get(context, + instance_id, + session=session) session.add(fixed_ip_ref) -def fixed_ip_associate_pool(_context, network_id, instance_id): +#@require_admin_context +def fixed_ip_associate_pool(context, network_id, instance_id): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): network_or_none = or_(models.FixedIp.network_id == network_id, @@ -386,14 +460,16 @@ def fixed_ip_associate_pool(_context, network_id, instance_id): if not fixed_ip_ref: raise db.NoMoreAddresses() if not fixed_ip_ref.network: - fixed_ip_ref.network = models.Network.find(network_id, - session=session) - fixed_ip_ref.instance = models.Instance.find(instance_id, - session=session) + fixed_ip_ref.network = network_get(context, + network_id, + session=session) + fixed_ip_ref.instance = instance_get(context, + instance_id, + session=session) session.add(fixed_ip_ref) return fixed_ip_ref['address'] - +#@require_context def fixed_ip_create(_context, values): fixed_ip_ref = models.FixedIp() for (key, value) in values.iteritems(): @@ -401,45 +477,56 @@ def fixed_ip_create(_context, values): fixed_ip_ref.save() return fixed_ip_ref['address'] - -def fixed_ip_disassociate(_context, address): +#@require_context +def fixed_ip_disassociate(context, address): session = get_session() with session.begin(): - fixed_ip_ref = models.FixedIp.find_by_str(address, session=session) + fixed_ip_ref = fixed_ip_get_by_address(context, + address, + session=session) fixed_ip_ref.instance = None fixed_ip_ref.save(session=session) -def fixed_ip_get_by_address(_context, address): - session = get_session() - with session.begin(): - try: - return session.query(models.FixedIp - ).options(joinedload_all('instance') - ).filter_by(address=address - ).filter_by(deleted=False - ).one() - except exc.NoResultFound: - new_exc = exception.NotFound("No model for address %s" % address) - raise new_exc.__class__, new_exc, sys.exc_info()[2] - - -def fixed_ip_get_instance(_context, address): - session = get_session() - with session.begin(): - return models.FixedIp.find_by_str(address, session=session).instance +#@require_context +def fixed_ip_get_by_address(context, address, session=None): + # TODO(devcamcar): Ensure floating ip belongs to user. + # Only possible if it is associated with an instance. + # May have to use system context for this always. + if not session: + session = get_session() + result = session.query(models.FixedIp + ).filter_by(address=address + ).filter_by(deleted=_deleted(context) + ).options(joinedload('network') + ).options(joinedload('instance') + ).first() + if not result: + raise exception.NotFound('No floating ip for address %s' % address) + + return result + + +#@require_context +def fixed_ip_get_instance(context, address): + fixed_ip_ref = fixed_ip_get_by_address(context, address) + return fixed_ip_ref.instance -def fixed_ip_get_network(_context, address): - session = get_session() - with session.begin(): - return models.FixedIp.find_by_str(address, session=session).network +#@require_admin_context +def fixed_ip_get_network(context, address): + fixed_ip_ref = fixed_ip_get_by_address(context, address) + return fixed_ip_ref.network -def fixed_ip_update(_context, address, values): + +#@require_context +def fixed_ip_update(context, address, values): session = get_session() with session.begin(): - fixed_ip_ref = models.FixedIp.find_by_str(address, session=session) + fixed_ip_ref = fixed_ip_get_by_address(context, + address, + session=session) for (key, value) in values.iteritems(): fixed_ip_ref[key] = value fixed_ip_ref.save(session=session) @@ -462,7 +549,9 @@ def instance_create(_context, values): instance_ref.save(session=session) return instance_ref + def instance_data_get_for_project(_context, project_id): + # TODO(devmcar): Admin only session = get_session() result = session.query(func.count(models.Instance.id), func.sum(models.Instance.vcpus) @@ -474,6 +563,7 @@ def instance_data_get_for_project(_context, project_id): def instance_destroy(_context, instance_id): + # TODO(devcamcar): Support user context session = get_session() with session.begin(): instance_ref = models.Instance.find(instance_id, session=session) @@ -481,17 +571,21 @@ def instance_destroy(_context, instance_id): def instance_get(context, instance_id, session=None): + # TODO(devcamcar): Support user context return models.Instance.find(instance_id, session=session, deleted=_deleted(context)) def instance_get_all(context): + # TODO(devcamcar): Admin only session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') ).filter_by(deleted=_deleted(context) ).all() + def instance_get_all_by_user(context, user_id): + # TODO(devcamcar): Admin only session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') @@ -499,7 +593,9 @@ def instance_get_all_by_user(context, user_id): ).filter_by(user_id=user_id ).all() + def instance_get_all_by_project(context, project_id): + # TODO(devcamcar): Support user context session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') @@ -509,6 +605,7 @@ def instance_get_all_by_project(context, project_id): def instance_get_all_by_reservation(_context, reservation_id): + # TODO(devcamcar): Support user context session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') @@ -518,6 +615,7 @@ def instance_get_all_by_reservation(_context, reservation_id): def instance_get_by_ec2_id(context, ec2_id): + # TODO(devcamcar): Support user context session = get_session() instance_ref = session.query(models.Instance ).filter_by(ec2_id=ec2_id @@ -536,6 +634,7 @@ def instance_ec2_id_exists(context, ec2_id, session=None): def instance_get_fixed_address(_context, instance_id): + # TODO(devcamcar): Support user context session = get_session() with session.begin(): instance_ref = models.Instance.find(instance_id, session=session) @@ -545,6 +644,7 @@ def instance_get_fixed_address(_context, instance_id): def instance_get_floating_address(_context, instance_id): + # TODO(devcamcar): Support user context session = get_session() with session.begin(): instance_ref = models.Instance.find(instance_id, session=session) @@ -557,6 +657,7 @@ def instance_get_floating_address(_context, instance_id): def instance_is_vpn(context, instance_id): + # TODO(devcamcar): Admin only # TODO(vish): Move this into image code somewhere instance_ref = instance_get(context, instance_id) return instance_ref['image_id'] == FLAGS.vpn_image_id @@ -683,8 +784,8 @@ def network_destroy(_context, network_id): {'id': network_id}) -def network_get(_context, network_id): - return models.Network.find(network_id) +def network_get(_context, network_id, session=None): + return models.Network.find(network_id, session=session) # NOTE(vish): pylint complains because of the long method name, but diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index b9bb8e4f2..7a085c4df 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -441,19 +441,6 @@ class FixedIp(BASE, NovaBase): def str_id(self): return self.address - @classmethod - def find_by_str(cls, str_id, session=None, deleted=False): - if not session: - session = get_session() - try: - return session.query(cls - ).filter_by(address=str_id - ).filter_by(deleted=deleted - ).one() - except exc.NoResultFound: - new_exc = exception.NotFound("No model for address %s" % str_id) - raise new_exc.__class__, new_exc, sys.exc_info()[2] - class FloatingIp(BASE, NovaBase): """Represents a floating ip that dynamically forwards to a fixed ip""" @@ -469,19 +456,6 @@ class FloatingIp(BASE, NovaBase): project_id = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) - @classmethod - def find_by_str(cls, str_id, session=None, deleted=False): - if not session: - session = get_session() - try: - return session.query(cls - ).filter_by(address=str_id - ).filter_by(deleted=deleted - ).one() - except exc.NoResultFound: - new_exc = exception.NotFound("No model for address %s" % str_id) - raise new_exc.__class__, new_exc, sys.exc_info()[2] - def register_models(): """Register Models and create metadata""" diff --git a/nova/network/manager.py b/nova/network/manager.py index a7126ea4f..d125d28d8 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -232,7 +232,8 @@ class VlanManager(NetworkManager): address = network_ref['vpn_private_address'] self.db.fixed_ip_associate(context, address, instance_id) else: - address = self.db.fixed_ip_associate_pool(context, + # TODO(devcamcar) Pass system context here. + address = self.db.fixed_ip_associate_pool(None, network_ref['id'], instance_id) self.db.fixed_ip_update(context, address, {'allocated': True}) diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index da65b50a2..110e8430c 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -84,6 +84,7 @@ class NetworkTestCase(test.TrialTestCase): def test_public_network_association(self): """Makes sure that we can allocaate a public ip""" # TODO(vish): better way of adding floating ips + self.context.project = self.projects[0] pubnet = IPy.IP(flags.FLAGS.public_range) address = str(pubnet[0]) try: -- cgit From d32d95e08d67084ea04ccd1565ce6faffb1766ce Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Wed, 29 Sep 2010 20:29:55 -0700 Subject: Finished instance context auth --- nova/db/sqlalchemy/api.py | 185 ++++++++++++++++++++++++++++++----------- nova/tests/compute_unittest.py | 2 + nova/tests/network_unittest.py | 4 +- 3 files changed, 141 insertions(+), 50 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index d129df2be..9ab53b89b 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -41,9 +41,10 @@ FLAGS = flags.FLAGS # pylint: disable-msg=C0111 def _deleted(context): """Calculates whether to include deleted objects based on context. - - Currently just looks for a flag called deleted in the context dict. + Currently just looks for a flag called deleted in the context dict. """ + if is_user_context(context): + return False if not hasattr(context, 'get'): return False return context.get('deleted', False) @@ -69,7 +70,7 @@ def is_user_context(context): ################### - +#@require_admin_context def service_destroy(context, service_id): if not is_admin_context(context): raise exception.NotAuthorized() @@ -80,6 +81,7 @@ def service_destroy(context, service_id): service_ref.delete(session=session) +#@require_admin_context def service_get(context, service_id, session=None): if not is_admin_context(context): raise exception.NotAuthorized() @@ -98,6 +100,7 @@ def service_get(context, service_id, session=None): return result +#@require_admin_context def service_get_all_by_topic(context, topic): if not is_admin_context(context): raise exception.NotAuthorized() @@ -110,6 +113,7 @@ def service_get_all_by_topic(context, topic): ).all() +#@require_admin_context def _service_get_all_topic_subquery(context, session, topic, subq, label): if not is_admin_context(context): raise exception.NotAuthorized() @@ -124,6 +128,7 @@ def _service_get_all_topic_subquery(context, session, topic, subq, label): ).all() +#@require_admin_context def service_get_all_compute_sorted(context): if not is_admin_context(context): raise exception.NotAuthorized() @@ -151,6 +156,7 @@ def service_get_all_compute_sorted(context): label) +#@require_admin_context def service_get_all_network_sorted(context): if not is_admin_context(context): raise exception.NotAuthorized() @@ -171,6 +177,7 @@ def service_get_all_network_sorted(context): label) +#@require_admin_context def service_get_all_volume_sorted(context): if not is_admin_context(context): raise exception.NotAuthorized() @@ -191,6 +198,7 @@ def service_get_all_volume_sorted(context): label) +#@require_admin_context def service_get_by_args(context, host, binary): if not is_admin_context(context): raise exception.NotAuthorized() @@ -208,6 +216,7 @@ def service_get_by_args(context, host, binary): return result +#@require_admin_context def service_create(context, values): if not is_admin_context(context): return exception.NotAuthorized() @@ -219,6 +228,7 @@ def service_create(context, values): return service_ref +#@require_admin_context def service_update(context, service_id, values): if not is_admin_context(context): raise exception.NotAuthorized() @@ -234,12 +244,11 @@ def service_update(context, service_id, values): ################### +#@require_context def floating_ip_allocate_address(context, host, project_id): if is_user_context(context): if context.project.id != project_id: raise exception.NotAuthorized() - elif not is_admin_context(context): - raise exception.NotAuthorized() session = get_session() with session.begin(): @@ -259,6 +268,7 @@ def floating_ip_allocate_address(context, host, project_id): return floating_ip_ref['address'] +#@require_context def floating_ip_create(context, values): if not is_user_context(context) and not is_admin_context(context): raise exception.NotAuthorized() @@ -270,12 +280,11 @@ def floating_ip_create(context, values): return floating_ip_ref['address'] +#@require_context def floating_ip_count_by_project(context, project_id): if is_user_context(context): if context.project.id != project_id: raise exception.NotAuthorized() - elif not is_admin_context(context): - raise exception.NotAuthorized() session = get_session() return session.query(models.FloatingIp @@ -316,6 +325,7 @@ def floating_ip_deallocate(context, address): floating_ip_ref['project_id'] = None floating_ip_ref.save(session=session) + #@require_context def floating_ip_destroy(context, address): if not is_user_context(context) and not is_admin_context(context): @@ -330,6 +340,7 @@ def floating_ip_destroy(context, address): floating_ip_ref.delete(session=session) +#@require_context def floating_ip_disassociate(context, address): if not is_user_context(context) and is_admin_context(context): raise exception.NotAuthorized() @@ -350,6 +361,7 @@ def floating_ip_disassociate(context, address): floating_ip_ref.save(session=session) return fixed_ip_address + #@require_admin_context def floating_ip_get_all(context): if not is_admin_context(context): @@ -361,6 +373,7 @@ def floating_ip_get_all(context): ).filter_by(deleted=False ).all() + #@require_admin_context def floating_ip_get_all_by_host(context, host): if not is_admin_context(context): @@ -373,6 +386,7 @@ def floating_ip_get_all_by_host(context, host): ).filter_by(deleted=False ).all() + #@require_context def floating_ip_get_all_by_project(context, project_id): # TODO(devcamcar): Change to decorate and check project_id separately. @@ -389,6 +403,7 @@ def floating_ip_get_all_by_project(context, project_id): ).filter_by(deleted=False ).all() + #@require_context def floating_ip_get_by_address(context, address, session=None): # TODO(devcamcar): Ensure the address belongs to user. @@ -408,14 +423,9 @@ def floating_ip_get_by_address(context, address, session=None): return result - # floating_ip_ref = get_floating_ip_by_address(context, - # address, - # session=session) - # return floating_ip_ref.fixed_ip.instance - - ################### + #@require_context def fixed_ip_associate(context, address, instance_id): if not is_user_context(context) and not is_admin_context(context): @@ -469,6 +479,7 @@ def fixed_ip_associate_pool(context, network_id, instance_id): session.add(fixed_ip_ref) return fixed_ip_ref['address'] + #@require_context def fixed_ip_create(_context, values): fixed_ip_ref = models.FixedIp() @@ -477,6 +488,7 @@ def fixed_ip_create(_context, values): fixed_ip_ref.save() return fixed_ip_ref['address'] + #@require_context def fixed_ip_disassociate(context, address): session = get_session() @@ -535,7 +547,8 @@ def fixed_ip_update(context, address, values): ################### -def instance_create(_context, values): +#@require_context +def instance_create(context, values): instance_ref = models.Instance() for (key, value) in values.iteritems(): instance_ref[key] = value @@ -544,14 +557,14 @@ def instance_create(_context, values): with session.begin(): while instance_ref.ec2_id == None: ec2_id = utils.generate_uid(instance_ref.__prefix__) - if not instance_ec2_id_exists(_context, ec2_id, session=session): + if not instance_ec2_id_exists(context, ec2_id, session=session): instance_ref.ec2_id = ec2_id instance_ref.save(session=session) return instance_ref -def instance_data_get_for_project(_context, project_id): - # TODO(devmcar): Admin only +#@require_admin_context +def instance_data_get_for_project(context, project_id): session = get_session() result = session.query(func.count(models.Instance.id), func.sum(models.Instance.vcpus) @@ -562,21 +575,42 @@ def instance_data_get_for_project(_context, project_id): return (result[0] or 0, result[1] or 0) -def instance_destroy(_context, instance_id): - # TODO(devcamcar): Support user context +#@require_context +def instance_destroy(context, instance_id): session = get_session() with session.begin(): - instance_ref = models.Instance.find(instance_id, session=session) + instance_ref = instance_get(context, instance_id, session=session) instance_ref.delete(session=session) +#@require_context def instance_get(context, instance_id, session=None): - # TODO(devcamcar): Support user context - return models.Instance.find(instance_id, session=session, deleted=_deleted(context)) + if not session: + session = get_session() + result = None + + if is_admin_context(context): + result = session.query(models.Instance + ).filter_by(id=instance_id + ).filter_by(deleted=_deleted(context) + ).first() + elif is_user_context(context): + result = session.query(models.Instance + ).filter_by(project_id=context.project.id + ).filter_by(id=instance_id + ).filter_by(deleted=False + ).first() + if not result: + raise exception.NotFound('No instance for id %s' % instance_id) + + return result +#@require_admin_context def instance_get_all(context): - # TODO(devcamcar): Admin only + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') @@ -584,8 +618,11 @@ def instance_get_all(context): ).all() +#@require_admin_context def instance_get_all_by_user(context, user_id): - # TODO(devcamcar): Admin only + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') @@ -594,8 +631,12 @@ def instance_get_all_by_user(context, user_id): ).all() +#@require_context def instance_get_all_by_project(context, project_id): - # TODO(devcamcar): Support user context + if is_user_context(context): + if context.project.id != project_id: + raise exception.NotAuthorized() + session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') @@ -604,50 +645,68 @@ def instance_get_all_by_project(context, project_id): ).all() -def instance_get_all_by_reservation(_context, reservation_id): - # TODO(devcamcar): Support user context +#@require_context +def instance_get_all_by_reservation(context, reservation_id): session = get_session() - return session.query(models.Instance - ).options(joinedload_all('fixed_ip.floating_ips') - ).filter_by(reservation_id=reservation_id - ).filter_by(deleted=False - ).all() + + if is_admin_context(context): + return session.query(models.Instance + ).options(joinedload_all('fixed_ip.floating_ips') + ).filter_by(reservation_id=reservation_id + ).filter_by(deleted=_deleted(context) + ).all() + elif is_user_context(context): + return session.query(models.Instance + ).options(joinedload_all('fixed_ip.floating_ips') + ).filter_by(project_id=context.project.id + ).filter_by(reservation_id=reservation_id + ).filter_by(deleted=False + ).all() +#@require_context def instance_get_by_ec2_id(context, ec2_id): - # TODO(devcamcar): Support user context session = get_session() - instance_ref = session.query(models.Instance + + if is_admin_context(context): + result = session.query(models.Instance ).filter_by(ec2_id=ec2_id ).filter_by(deleted=_deleted(context) ).first() - if not instance_ref: + elif is_user_context(context): + result = session.query(models.Instance + ).filter_by(project_id=context.project.id + ).filter_by(ec2_id=ec2_id + ).filter_by(deleted=False + ).first() + if not result: raise exception.NotFound('Instance %s not found' % (ec2_id)) - return instance_ref + return result +#@require_context def instance_ec2_id_exists(context, ec2_id, session=None): if not session: session = get_session() return session.query(exists().where(models.Instance.id==ec2_id)).one()[0] -def instance_get_fixed_address(_context, instance_id): - # TODO(devcamcar): Support user context +#@require_context +def instance_get_fixed_address(context, instance_id): session = get_session() with session.begin(): - instance_ref = models.Instance.find(instance_id, session=session) + instance_ref = instance_get(context, instance_id, session=session) if not instance_ref.fixed_ip: return None return instance_ref.fixed_ip['address'] -def instance_get_floating_address(_context, instance_id): - # TODO(devcamcar): Support user context +#@require_context +def instance_get_floating_address(context, instance_id): session = get_session() with session.begin(): - instance_ref = models.Instance.find(instance_id, session=session) + instance_ref = instance_get(context, instance_id, session=session) if not instance_ref.fixed_ip: return None if not instance_ref.fixed_ip.floating_ips: @@ -656,14 +715,20 @@ def instance_get_floating_address(_context, instance_id): return instance_ref.fixed_ip.floating_ips[0]['address'] +#@require_admin_context def instance_is_vpn(context, instance_id): - # TODO(devcamcar): Admin only + if not is_admin_context(context): + raise exception.NotAuthorized() # TODO(vish): Move this into image code somewhere instance_ref = instance_get(context, instance_id) return instance_ref['image_id'] == FLAGS.vpn_image_id +#@require_admin_context def instance_set_state(context, instance_id, state, description=None): + if not is_admin_context(context): + raise exception.NotAuthorized() + # TODO(devcamcar): Move this out of models and into driver from nova.compute import power_state if not description: @@ -674,10 +739,11 @@ def instance_set_state(context, instance_id, state, description=None): 'state_description': description}) -def instance_update(_context, instance_id, values): +#@require_context +def instance_update(context, instance_id, values): session = get_session() with session.begin(): - instance_ref = models.Instance.find(instance_id, session=session) + instance_ref = instance_get(context, instance_id, session=session) for (key, value) in values.iteritems(): instance_ref[key] = value instance_ref.save(session=session) @@ -686,6 +752,7 @@ def instance_update(_context, instance_id, values): ################### +#@require_context def key_pair_create(_context, values): key_pair_ref = models.KeyPair() for (key, value) in values.iteritems(): @@ -694,7 +761,8 @@ def key_pair_create(_context, values): return key_pair_ref -def key_pair_destroy(_context, user_id, name): +#@require_context +def key_pair_destroy(context, user_id, name): session = get_session() with session.begin(): key_pair_ref = models.KeyPair.find_by_args(user_id, @@ -784,8 +852,27 @@ def network_destroy(_context, network_id): {'id': network_id}) -def network_get(_context, network_id, session=None): - return models.Network.find(network_id, session=session) +#@require_context +def network_get(context, network_id, session=None): + if not session: + session = get_session() + result = None + + if is_admin_context(context): + result = session.query(models.Network + ).filter_by(id=network_id + ).filter_by(deleted=_deleted(context) + ).first() + elif is_user_context(context): + result = session.query(models.Network + ).filter_by(project_id=context.project.id + ).filter_by(id=network_id + ).filter_by(deleted=False + ).first() + if not result: + raise exception.NotFound('No network for id %s' % network_id) + + return result # NOTE(vish): pylint complains because of the long method name, but @@ -1066,7 +1153,7 @@ def volume_get(context, volume_id, session=None): ).first() elif is_user_context(context): result = session.query(models.Volume - ).filter_by(project_id=context.project.project_id + ).filter_by(project_id=context.project.id ).filter_by(id=volume_id ).filter_by(deleted=False ).first() diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index f5c0f1c09..e705c2552 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -96,6 +96,8 @@ class ComputeTestCase(test.TrialTestCase): self.assertEqual(instance_ref['deleted_at'], None) terminate = datetime.datetime.utcnow() yield self.compute.terminate_instance(self.context, instance_id) + # TODO(devcamcar): Pass deleted in using system context. + # context.read_deleted ? instance_ref = db.instance_get({'deleted': True}, instance_id) self.assert_(instance_ref['launched_at'] < terminate) self.assert_(instance_ref['deleted_at'] > terminate) diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index 110e8430c..ca6a4bbc2 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -56,7 +56,9 @@ class NetworkTestCase(test.TrialTestCase): 'netuser', name)) # create the necessary network data for the project - self.network.set_network_host(self.context, self.projects[i].id) + user_context = context.APIRequestContext(project=self.projects[i], + user=self.user) + self.network.set_network_host(user_context, self.projects[i].id) instance_ref = db.instance_create(None, {'mac_address': utils.generate_mac()}) self.instance_id = instance_ref['id'] -- cgit From ea5dcda819f2656589df177331f693f945d98f4a Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Wed, 29 Sep 2010 20:35:24 -0700 Subject: Finished instance context auth --- nova/db/sqlalchemy/api.py | 32 +++++++++++++++++++++++++++++--- nova/tests/network_unittest.py | 1 + 2 files changed, 30 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 9ab53b89b..2d553d98d 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -794,11 +794,21 @@ def key_pair_get_all_by_user(_context, user_id): ################### -def network_count(_context): - return models.Network.count() +#@require_admin_context +def network_count(context): + if not is_admin_context(context): + raise exception.NotAuthorized() + return session.query(models.Network + ).filter_by(deleted=deleted + ).count() + +#@require_admin_context def network_count_allocated_ips(_context, network_id): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() return session.query(models.FixedIp ).filter_by(network_id=network_id @@ -807,7 +817,11 @@ def network_count_allocated_ips(_context, network_id): ).count() +#@require_admin_context def network_count_available_ips(_context, network_id): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() return session.query(models.FixedIp ).filter_by(network_id=network_id @@ -817,7 +831,11 @@ def network_count_available_ips(_context, network_id): ).count() +#@require_admin_context def network_count_reserved_ips(_context, network_id): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() return session.query(models.FixedIp ).filter_by(network_id=network_id @@ -826,7 +844,11 @@ def network_count_reserved_ips(_context, network_id): ).count() +#@require_admin_context def network_create(_context, values): + if not is_admin_context(context): + raise exception.NotAuthorized() + network_ref = models.Network() for (key, value) in values.iteritems(): network_ref[key] = value @@ -834,7 +856,11 @@ def network_create(_context, values): return network_ref -def network_destroy(_context, network_id): +#@require_admin_context +def network_destroy(context, network_id): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): # TODO(vish): do we have to use sql here? diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index ca6a4bbc2..e01d7cff9 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -49,6 +49,7 @@ class NetworkTestCase(test.TrialTestCase): self.user = self.manager.create_user('netuser', 'netuser', 'netuser') self.projects = [] self.network = utils.import_object(FLAGS.network_manager) + # TODO(devcamcar): Passing project=None is Bad(tm). self.context = context.APIRequestContext(project=None, user=self.user) for i in range(5): name = 'project%s' % i -- cgit From e716990fd58521f8c0166330ec9bc62c7cd91b7e Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Wed, 29 Sep 2010 20:54:15 -0700 Subject: Finished context auth for network --- nova/db/sqlalchemy/api.py | 103 ++++++++++++++++++++++++++++++++-------------- nova/network/manager.py | 3 +- 2 files changed, 73 insertions(+), 33 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 2d553d98d..23589b7d8 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -799,13 +799,14 @@ def network_count(context): if not is_admin_context(context): raise exception.NotAuthorized() + session = get_session() return session.query(models.Network - ).filter_by(deleted=deleted + ).filter_by(deleted=_deleted(context) ).count() #@require_admin_context -def network_count_allocated_ips(_context, network_id): +def network_count_allocated_ips(context, network_id): if not is_admin_context(context): raise exception.NotAuthorized() @@ -818,7 +819,7 @@ def network_count_allocated_ips(_context, network_id): #@require_admin_context -def network_count_available_ips(_context, network_id): +def network_count_available_ips(context, network_id): if not is_admin_context(context): raise exception.NotAuthorized() @@ -832,7 +833,7 @@ def network_count_available_ips(_context, network_id): #@require_admin_context -def network_count_reserved_ips(_context, network_id): +def network_count_reserved_ips(context, network_id): if not is_admin_context(context): raise exception.NotAuthorized() @@ -845,7 +846,7 @@ def network_count_reserved_ips(_context, network_id): #@require_admin_context -def network_create(_context, values): +def network_create(context, values): if not is_admin_context(context): raise exception.NotAuthorized() @@ -904,7 +905,11 @@ def network_get(context, network_id, session=None): # NOTE(vish): pylint complains because of the long method name, but # it fits with the names of the rest of the methods # pylint: disable-msg=C0103 -def network_get_associated_fixed_ips(_context, network_id): +#@require_admin_context +def network_get_associated_fixed_ips(context, network_id): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() return session.query(models.FixedIp ).options(joinedload_all('instance') @@ -914,18 +919,28 @@ def network_get_associated_fixed_ips(_context, network_id): ).all() -def network_get_by_bridge(_context, bridge): +#@require_admin_context +def network_get_by_bridge(context, bridge): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() - rv = session.query(models.Network + result = session.query(models.Network ).filter_by(bridge=bridge ).filter_by(deleted=False ).first() - if not rv: + + if not result: raise exception.NotFound('No network for bridge %s' % bridge) - return rv + + return result -def network_get_index(_context, network_id): +#@require_admin_context +def network_get_index(context, network_id): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): network_index = session.query(models.NetworkIndex @@ -933,19 +948,34 @@ def network_get_index(_context, network_id): ).filter_by(deleted=False ).with_lockmode('update' ).first() + if not network_index: raise db.NoMoreNetworks() - network_index['network'] = models.Network.find(network_id, - session=session) + + network_index['network'] = network_get(context, + network_id, + session=session) session.add(network_index) + return network_index['index'] -def network_index_count(_context): - return models.NetworkIndex.count() +#@require_admin_context +def network_index_count(context): + if not is_admin_context(context): + raise exception.NotAuthorized() + + session = get_session() + return session.query(models.NetworkIndex + ).filter_by(deleted=_deleted(context) + ).count() + +#@require_admin_context +def network_index_create_safe(context, values): + if not is_admin_context(context): + raise exception.NotAuthorized() -def network_index_create_safe(_context, values): network_index_ref = models.NetworkIndex() for (key, value) in values.iteritems(): network_index_ref[key] = value @@ -955,29 +985,35 @@ def network_index_create_safe(_context, values): pass -def network_set_host(_context, network_id, host_id): +#@require_admin_context +def network_set_host(context, network_id, host_id): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): - network = session.query(models.Network - ).filter_by(id=network_id - ).filter_by(deleted=False - ).with_lockmode('update' - ).first() - if not network: - raise exception.NotFound("Couldn't find network with %s" % - network_id) + network_ref = session.query(models.Network + ).filter_by(id=network_id + ).filter_by(deleted=False + ).with_lockmode('update' + ).first() + if not network_ref: + raise exception.NotFound('No network for id %s' % network_id) + # NOTE(vish): if with_lockmode isn't supported, as in sqlite, # then this has concurrency issues - if not network['host']: - network['host'] = host_id - session.add(network) - return network['host'] + if not network_ref['host']: + network_ref['host'] = host_id + session.add(network_ref) + + return network_ref['host'] -def network_update(_context, network_id, values): +#@require_context +def network_update(context, network_id, values): session = get_session() with session.begin(): - network_ref = models.Network.find(network_id, session=session) + network_ref = network_get(context, network_id, session=session) for (key, value) in values.iteritems(): network_ref[key] = value network_ref.save(session=session) @@ -985,7 +1021,10 @@ def network_update(_context, network_id, values): ################### - +# YOU ARE HERE. +# random idea for system user: +# ctx = context.system_user(on_behalf_of=user, read_deleted=False) +# TODO(devcamcar): Rename to network_get_all_by_project def project_get_network(_context, project_id): session = get_session() rv = session.query(models.Network diff --git a/nova/network/manager.py b/nova/network/manager.py index d125d28d8..ecf2fa2c2 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -88,7 +88,8 @@ class NetworkManager(manager.Manager): # TODO(vish): can we minimize db access by just getting the # id here instead of the ref? network_id = network_ref['id'] - host = self.db.network_set_host(context, + # TODO(devcamcar): Replace with system context + host = self.db.network_set_host(None, network_id, self.host) self._on_set_network_host(context, network_id) -- cgit From 98cac90592658773791eb15b19ed60adf0a57d96 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Thu, 30 Sep 2010 00:36:10 -0700 Subject: Completed quota context auth --- nova/db/sqlalchemy/api.py | 103 +++++++++++++++++++++++++++++++------------ nova/db/sqlalchemy/models.py | 12 ----- 2 files changed, 75 insertions(+), 40 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 23589b7d8..b225a6a88 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1021,19 +1021,22 @@ def network_update(context, network_id, values): ################### -# YOU ARE HERE. -# random idea for system user: -# ctx = context.system_user(on_behalf_of=user, read_deleted=False) -# TODO(devcamcar): Rename to network_get_all_by_project -def project_get_network(_context, project_id): + +#@require_context +def project_get_network(context, project_id): + if not is_admin_context(context) and not is_user_context(context): + raise error.NotAuthorized() + session = get_session() - rv = session.query(models.Network + result= session.query(models.Network ).filter_by(project_id=project_id ).filter_by(deleted=False ).first() - if not rv: + + if not result: raise exception.NotFound('No network for project: %s' % project_id) - return rv + + return result ################### @@ -1043,14 +1046,26 @@ def queue_get_for(_context, topic, physical_node_id): # FIXME(ja): this should be servername? return "%s.%s" % (topic, physical_node_id) + ################### -def export_device_count(_context): - return models.ExportDevice.count() +#@require_admin_context +def export_device_count(context): + if not is_admin_context(context): + raise exception.notauthorized() + + session = get_session() + return session.query(models.ExportDevice + ).filter_by(deleted=_deleted(context) + ).count() + +#@require_admin_context +def export_device_create(context, values): + if not is_admin_context(context): + raise exception.notauthorized() -def export_device_create(_context, values): export_device_ref = models.ExportDevice() for (key, value) in values.iteritems(): export_device_ref[key] = value @@ -1084,7 +1099,29 @@ def auth_create_token(_context, token): ################### +#@require_admin_context +def quota_get(context, project_id, session=None): + if not is_admin_context(context): + raise exception.NotAuthorized() + + if not session: + session = get_session() + + result = session.query(models.Quota + ).filter_by(project_id=project_id + ).filter_by(deleted=_deleted(context) + ).first() + if not result: + raise exception.NotFound('No quota for project_id %s' % project_id) + + return result + + +#@require_admin_context def quota_create(_context, values): + if not is_admin_context(context): + raise exception.NotAuthorized() + quota_ref = models.Quota() for (key, value) in values.iteritems(): quota_ref[key] = value @@ -1092,29 +1129,34 @@ def quota_create(_context, values): return quota_ref -def quota_get(_context, project_id): - return models.Quota.find_by_str(project_id) - +#@require_admin_context +def quota_update(context, project_id, values): + if not is_admin_context(context): + raise exception.NotAuthorized() -def quota_update(_context, project_id, values): session = get_session() with session.begin(): - quota_ref = models.Quota.find_by_str(project_id, session=session) + quota_ref = quota_get(context, project_id, session=session) for (key, value) in values.iteritems(): quota_ref[key] = value quota_ref.save(session=session) -def quota_destroy(_context, project_id): +#@require_admin_context +def quota_destroy(context, project_id): + if not is_admin_context(context): + raise exception.NotAuthorized() + session = get_session() with session.begin(): - quota_ref = models.Quota.find_by_str(project_id, session=session) + quota_ref = quota_get(context, project_id, session=session) quota_ref.delete(session=session) ################### +#@require_admin_context def volume_allocate_shelf_and_blade(context, volume_id): if not is_admin_context(context): raise exception.NotAuthorized() @@ -1135,6 +1177,7 @@ def volume_allocate_shelf_and_blade(context, volume_id): return (export_device.shelf_id, export_device.blade_id) +#@require_admin_context def volume_attached(context, volume_id, instance_id, mountpoint): if not is_admin_context(context): raise exception.NotAuthorized() @@ -1149,6 +1192,7 @@ def volume_attached(context, volume_id, instance_id, mountpoint): volume_ref.save(session=session) +#@require_context def volume_create(context, values): volume_ref = models.Volume() for (key, value) in values.iteritems(): @@ -1164,6 +1208,7 @@ def volume_create(context, values): return volume_ref +#@require_admin_context def volume_data_get_for_project(context, project_id): if not is_admin_context(context): raise exception.NotAuthorized() @@ -1178,6 +1223,7 @@ def volume_data_get_for_project(context, project_id): return (result[0] or 0, result[1] or 0) +#@require_admin_context def volume_destroy(context, volume_id): if not is_admin_context(context): raise exception.NotAuthorized() @@ -1192,6 +1238,7 @@ def volume_destroy(context, volume_id): {'id': volume_id}) +#@require_admin_context def volume_detached(context, volume_id): if not is_admin_context(context): raise exception.NotAuthorized() @@ -1206,6 +1253,7 @@ def volume_detached(context, volume_id): volume_ref.save(session=session) +#@require_context def volume_get(context, volume_id, session=None): if not session: session = get_session() @@ -1222,15 +1270,13 @@ def volume_get(context, volume_id, session=None): ).filter_by(id=volume_id ).filter_by(deleted=False ).first() - else: - raise exception.NotAuthorized() - if not result: raise exception.NotFound('No volume for id %s' % volume_id) return result +#@require_admin_context def volume_get_all(context): if not is_admin_context(context): raise exception.NotAuthorized() @@ -1239,7 +1285,7 @@ def volume_get_all(context): ).filter_by(deleted=_deleted(context) ).all() - +#@require_context def volume_get_all_by_project(context, project_id): if is_user_context(context): if context.project.id != project_id: @@ -1254,6 +1300,7 @@ def volume_get_all_by_project(context, project_id): ).all() +#@require_context def volume_get_by_ec2_id(context, ec2_id): session = get_session() result = None @@ -1278,6 +1325,7 @@ def volume_get_by_ec2_id(context, ec2_id): return result +#@require_context def volume_ec2_id_exists(context, ec2_id, session=None): if not session: session = get_session() @@ -1286,10 +1334,9 @@ def volume_ec2_id_exists(context, ec2_id, session=None): return session.query(exists( ).where(models.Volume.id==ec2_id) ).one()[0] - else: - raise exception.NotAuthorized() +#@require_context def volume_get_instance(context, volume_id): session = get_session() result = None @@ -1315,6 +1362,7 @@ def volume_get_instance(context, volume_id): return result.instance +#@require_context def volume_get_shelf_and_blade(context, volume_id): session = get_session() result = None @@ -1329,15 +1377,14 @@ def volume_get_shelf_and_blade(context, volume_id): ).filter(models.Volume.project_id==context.project.id ).filter_by(volume_id=volume_id ).first() - else: - raise exception.NotAuthorized() - if not result: - raise exception.NotFound() + raise exception.NotFound('No export device found for volume %s' % + volume_id) return (result.shelf_id, result.blade_id) +#@require_context def volume_update(context, volume_id, values): session = get_session() with session.begin(): diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 7a085c4df..76444127f 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -302,18 +302,6 @@ class Quota(BASE, NovaBase): def str_id(self): return self.project_id - @classmethod - def find_by_str(cls, str_id, session=None, deleted=False): - if not session: - session = get_session() - try: - return session.query(cls - ).filter_by(project_id=str_id - ).filter_by(deleted=deleted - ).one() - except exc.NoResultFound: - new_exc = exception.NotFound("No model for project_id %s" % str_id) - raise new_exc.__class__, new_exc, sys.exc_info()[2] class ExportDevice(BASE, NovaBase): """Represates a shelf and blade that a volume can be exported on""" -- cgit From 30541d48b17ab4626791d969388871b3a1b7758f Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Thu, 30 Sep 2010 01:07:05 -0700 Subject: Wired up context auth for keypairs --- nova/db/sqlalchemy/api.py | 46 +++++++++++++++++++++++++++++++++++--------- nova/db/sqlalchemy/models.py | 20 ------------------- 2 files changed, 37 insertions(+), 29 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index b225a6a88..302322979 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -753,7 +753,7 @@ def instance_update(context, instance_id, values): #@require_context -def key_pair_create(_context, values): +def key_pair_create(context, values): key_pair_ref = models.KeyPair() for (key, value) in values.iteritems(): key_pair_ref[key] = value @@ -763,15 +763,22 @@ def key_pair_create(_context, values): #@require_context def key_pair_destroy(context, user_id, name): + if is_user_context(context): + if context.user.id != user_id: + raise exception.NotAuthorized() + session = get_session() with session.begin(): - key_pair_ref = models.KeyPair.find_by_args(user_id, - name, - session=session) + key_pair_ref = key_pair_get(context, user_id, name, session=session) key_pair_ref.delete(session=session) -def key_pair_destroy_all_by_user(_context, user_id): +#@require_context +def key_pair_destroy_all_by_user(context, user_id): + if is_user_context(context): + if context.user.id != user_id: + raise exception.NotAuthorized() + session = get_session() with session.begin(): # TODO(vish): do we have to use sql here? @@ -779,11 +786,32 @@ def key_pair_destroy_all_by_user(_context, user_id): {'id': user_id}) -def key_pair_get(_context, user_id, name): - return models.KeyPair.find_by_args(user_id, name) +#@require_context +def key_pair_get(context, user_id, name, session=None): + if is_user_context(context): + if context.user.id != user_id: + raise exception.NotAuthorized() + + if not session: + session = get_session() + + result = session.query(models.KeyPair + ).filter_by(user_id=user_id + ).filter_by(name=name + ).filter_by(deleted=_deleted(context) + ).first() + if not result: + raise exception.NotFound('no keypair for user %s, name %s' % + (user_id, name)) + return result + +#@require_context +def key_pair_get_all_by_user(context, user_id): + if is_user_context(context): + if context.user.id != user_id: + raise exception.NotAuthorized() -def key_pair_get_all_by_user(_context, user_id): session = get_session() return session.query(models.KeyPair ).filter_by(user_id=user_id @@ -1118,7 +1146,7 @@ def quota_get(context, project_id, session=None): #@require_admin_context -def quota_create(_context, values): +def quota_create(context, values): if not is_admin_context(context): raise exception.NotAuthorized() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 76444127f..1f5bdf9f5 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -332,26 +332,6 @@ class KeyPair(BASE, NovaBase): def str_id(self): return '%s.%s' % (self.user_id, self.name) - @classmethod - def find_by_str(cls, str_id, session=None, deleted=False): - user_id, _sep, name = str_id.partition('.') - return cls.find_by_str(user_id, name, session, deleted) - - @classmethod - def find_by_args(cls, user_id, name, session=None, deleted=False): - if not session: - session = get_session() - try: - return session.query(cls - ).filter_by(user_id=user_id - ).filter_by(name=name - ).filter_by(deleted=deleted - ).one() - except exc.NoResultFound: - new_exc = exception.NotFound("No model for user %s, name %s" % - (user_id, name)) - raise new_exc.__class__, new_exc, sys.exc_info()[2] - class Network(BASE, NovaBase): """Represents a network""" -- cgit From 336523b36ceb8f5302acd443b7f1171b67575f73 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Thu, 30 Sep 2010 01:11:16 -0700 Subject: Removed deprecated bits from NovaBase --- nova/db/sqlalchemy/models.py | 38 -------------------------------------- 1 file changed, 38 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 1f5bdf9f5..a29090c60 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -50,44 +50,6 @@ class NovaBase(object): deleted_at = Column(DateTime) deleted = Column(Boolean, default=False) - @classmethod - def all(cls, session=None, deleted=False): - """Get all objects of this type""" - if not session: - session = get_session() - return session.query(cls - ).filter_by(deleted=deleted - ).all() - - @classmethod - def count(cls, session=None, deleted=False): - """Count objects of this type""" - if not session: - session = get_session() - return session.query(cls - ).filter_by(deleted=deleted - ).count() - - @classmethod - def find(cls, obj_id, session=None, deleted=False): - """Find object by id""" - if not session: - session = get_session() - try: - return session.query(cls - ).filter_by(id=obj_id - ).filter_by(deleted=deleted - ).one() - except exc.NoResultFound: - new_exc = exception.NotFound("No model for id %s" % obj_id) - raise new_exc.__class__, new_exc, sys.exc_info()[2] - - @classmethod - def find_by_str(cls, str_id, session=None, deleted=False): - """Find object by str_id""" - int_id = int(str_id.rpartition('-')[2]) - return cls.find(int_id, session=session, deleted=deleted) - @property def str_id(self): """Get string id of object (generally prefix + '-' + id)""" -- cgit From 8bd81f3ec811e19f6e7faf7a4fe271f85fbc7fc7 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Thu, 30 Sep 2010 02:02:14 -0700 Subject: Simplified authorization with decorators" " --- nova/db/sqlalchemy/api.py | 408 ++++++++++++++++------------------------------ 1 file changed, 142 insertions(+), 266 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 302322979..0e7d2e664 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -51,6 +51,7 @@ def _deleted(context): def is_admin_context(context): + """Indicates if the request context is an administrator.""" if not context: logging.warning('Use of empty request context is deprecated') return True @@ -60,6 +61,7 @@ def is_admin_context(context): def is_user_context(context): + """Indicates if the request context is a normal user.""" if not context: logging.warning('Use of empty request context is deprecated') return False @@ -68,24 +70,62 @@ def is_user_context(context): return True +def authorize_project_context(context, project_id): + """Ensures that the request context has permission to access the + given project. + """ + if is_user_context(context): + if not context.project: + raise exception.NotAuthorized() + elif context.project.id != project_id: + raise exception.NotAuthorized() + + +def authorize_user_context(context, user_id): + """Ensures that the request context has permission to access the + given user. + """ + if is_user_context(context): + if not context.user: + raise exception.NotAuthorized() + elif context.user.id != user_id: + raise exception.NotAuthorized() + + +def require_admin_context(f): + """Decorator used to indicate that the method requires an + administrator context. + """ + def wrapper(*args, **kwargs): + if not is_admin_context(args[0]): + raise exception.NotAuthorized() + return f(*args, **kwargs) + return wrapper + + +def require_context(f): + """Decorator used to indicate that the method requires either + an administrator or normal user context. + """ + def wrapper(*args, **kwargs): + if not is_admin_context(args[0]) and not is_user_context(args[0]): + raise exception.NotAuthorized() + return f(*args, **kwargs) + return wrapper + + ################### -#@require_admin_context +@require_admin_context def service_destroy(context, service_id): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): service_ref = service_get(context, service_id, session=session) service_ref.delete(session=session) -#@require_admin_context +@require_admin_context def service_get(context, service_id, session=None): - if not is_admin_context(context): - raise exception.NotAuthorized() - if not session: session = get_session() @@ -100,11 +140,8 @@ def service_get(context, service_id, session=None): return result -#@require_admin_context +@require_admin_context def service_get_all_by_topic(context, topic): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() return session.query(models.Service ).filter_by(deleted=False @@ -113,11 +150,8 @@ def service_get_all_by_topic(context, topic): ).all() -#@require_admin_context +@require_admin_context def _service_get_all_topic_subquery(context, session, topic, subq, label): - if not is_admin_context(context): - raise exception.NotAuthorized() - sort_value = getattr(subq.c, label) return session.query(models.Service, func.coalesce(sort_value, 0) ).filter_by(topic=topic @@ -128,11 +162,8 @@ def _service_get_all_topic_subquery(context, session, topic, subq, label): ).all() -#@require_admin_context +@require_admin_context def service_get_all_compute_sorted(context): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): # NOTE(vish): The intended query is below @@ -156,11 +187,8 @@ def service_get_all_compute_sorted(context): label) -#@require_admin_context +@require_admin_context def service_get_all_network_sorted(context): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): topic = 'network' @@ -177,11 +205,8 @@ def service_get_all_network_sorted(context): label) -#@require_admin_context +@require_admin_context def service_get_all_volume_sorted(context): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): topic = 'volume' @@ -198,11 +223,8 @@ def service_get_all_volume_sorted(context): label) -#@require_admin_context +@require_admin_context def service_get_by_args(context, host, binary): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() result = session.query(models.Service ).filter_by(host=host @@ -216,11 +238,8 @@ def service_get_by_args(context, host, binary): return result -#@require_admin_context +@require_admin_context def service_create(context, values): - if not is_admin_context(context): - return exception.NotAuthorized() - service_ref = models.Service() for (key, value) in values.iteritems(): service_ref[key] = value @@ -228,11 +247,8 @@ def service_create(context, values): return service_ref -#@require_admin_context +@require_admin_context def service_update(context, service_id, values): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): service_ref = session_get(context, service_id, session=session) @@ -244,11 +260,9 @@ def service_update(context, service_id, values): ################### -#@require_context +@require_context def floating_ip_allocate_address(context, host, project_id): - if is_user_context(context): - if context.project.id != project_id: - raise exception.NotAuthorized() + authorize_project_context(context, project_id) session = get_session() with session.begin(): @@ -268,11 +282,8 @@ def floating_ip_allocate_address(context, host, project_id): return floating_ip_ref['address'] -#@require_context +@require_context def floating_ip_create(context, values): - if not is_user_context(context) and not is_admin_context(context): - raise exception.NotAuthorized() - floating_ip_ref = models.FloatingIp() for (key, value) in values.iteritems(): floating_ip_ref[key] = value @@ -280,11 +291,9 @@ def floating_ip_create(context, values): return floating_ip_ref['address'] -#@require_context +@require_context def floating_ip_count_by_project(context, project_id): - if is_user_context(context): - if context.project.id != project_id: - raise exception.NotAuthorized() + authorize_project_context(context, project_id) session = get_session() return session.query(models.FloatingIp @@ -293,11 +302,8 @@ def floating_ip_count_by_project(context, project_id): ).count() -#@require_context +@require_context def floating_ip_fixed_ip_associate(context, floating_address, fixed_address): - if not is_user_context(context) and not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): # TODO(devcamcar): How to ensure floating_id belongs to user? @@ -311,11 +317,8 @@ def floating_ip_fixed_ip_associate(context, floating_address, fixed_address): floating_ip_ref.save(session=session) -#@require_context +@require_context def floating_ip_deallocate(context, address): - if not is_user_context(context) and not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): # TODO(devcamcar): How to ensure floating id belongs to user? @@ -326,11 +329,8 @@ def floating_ip_deallocate(context, address): floating_ip_ref.save(session=session) -#@require_context +@require_context def floating_ip_destroy(context, address): - if not is_user_context(context) and not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): # TODO(devcamcar): Ensure address belongs to user. @@ -340,11 +340,8 @@ def floating_ip_destroy(context, address): floating_ip_ref.delete(session=session) -#@require_context +@require_context def floating_ip_disassociate(context, address): - if not is_user_context(context) and is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): # TODO(devcamcar): Ensure address belongs to user. @@ -362,11 +359,8 @@ def floating_ip_disassociate(context, address): return fixed_ip_address -#@require_admin_context +@require_admin_context def floating_ip_get_all(context): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() return session.query(models.FloatingIp ).options(joinedload_all('fixed_ip.instance') @@ -374,11 +368,8 @@ def floating_ip_get_all(context): ).all() -#@require_admin_context +@require_admin_context def floating_ip_get_all_by_host(context, host): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() return session.query(models.FloatingIp ).options(joinedload_all('fixed_ip.instance') @@ -387,14 +378,9 @@ def floating_ip_get_all_by_host(context, host): ).all() -#@require_context +@require_context def floating_ip_get_all_by_project(context, project_id): - # TODO(devcamcar): Change to decorate and check project_id separately. - if is_user_context(context): - if context.project.id != project_id: - raise exception.NotAuthorized() - elif not is_admin_context(context): - raise exception.NotAuthorized() + authorize_project_context(context, project_id) session = get_session() return session.query(models.FloatingIp @@ -404,12 +390,9 @@ def floating_ip_get_all_by_project(context, project_id): ).all() -#@require_context +@require_context def floating_ip_get_by_address(context, address, session=None): # TODO(devcamcar): Ensure the address belongs to user. - if not is_user_context(context) and not is_admin_context(context): - raise exception.NotAuthorized() - if not session: session = get_session() @@ -426,11 +409,8 @@ def floating_ip_get_by_address(context, address, session=None): ################### -#@require_context +@require_context def fixed_ip_associate(context, address, instance_id): - if not is_user_context(context) and not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): fixed_ip_ref = session.query(models.FixedIp @@ -449,11 +429,8 @@ def fixed_ip_associate(context, address, instance_id): session.add(fixed_ip_ref) -#@require_admin_context +@require_admin_context def fixed_ip_associate_pool(context, network_id, instance_id): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): network_or_none = or_(models.FixedIp.network_id == network_id, @@ -480,7 +457,7 @@ def fixed_ip_associate_pool(context, network_id, instance_id): return fixed_ip_ref['address'] -#@require_context +@require_context def fixed_ip_create(_context, values): fixed_ip_ref = models.FixedIp() for (key, value) in values.iteritems(): @@ -489,7 +466,7 @@ def fixed_ip_create(_context, values): return fixed_ip_ref['address'] -#@require_context +@require_context def fixed_ip_disassociate(context, address): session = get_session() with session.begin(): @@ -500,7 +477,7 @@ def fixed_ip_disassociate(context, address): fixed_ip_ref.save(session=session) -#@require_context +@require_context def fixed_ip_get_by_address(context, address, session=None): # TODO(devcamcar): Ensure floating ip belongs to user. # Only possible if it is associated with an instance. @@ -520,19 +497,19 @@ def fixed_ip_get_by_address(context, address, session=None): return result -#@require_context +@require_context def fixed_ip_get_instance(context, address): fixed_ip_ref = fixed_ip_get_by_address(context, address) return fixed_ip_ref.instance -#@require_admin_context +@require_admin_context def fixed_ip_get_network(context, address): fixed_ip_ref = fixed_ip_get_by_address(context, address) return fixed_ip_ref.network -#@require_context +@require_context def fixed_ip_update(context, address, values): session = get_session() with session.begin(): @@ -547,7 +524,7 @@ def fixed_ip_update(context, address, values): ################### -#@require_context +@require_context def instance_create(context, values): instance_ref = models.Instance() for (key, value) in values.iteritems(): @@ -563,7 +540,7 @@ def instance_create(context, values): return instance_ref -#@require_admin_context +@require_admin_context def instance_data_get_for_project(context, project_id): session = get_session() result = session.query(func.count(models.Instance.id), @@ -575,7 +552,7 @@ def instance_data_get_for_project(context, project_id): return (result[0] or 0, result[1] or 0) -#@require_context +@require_context def instance_destroy(context, instance_id): session = get_session() with session.begin(): @@ -583,7 +560,7 @@ def instance_destroy(context, instance_id): instance_ref.delete(session=session) -#@require_context +@require_context def instance_get(context, instance_id, session=None): if not session: session = get_session() @@ -606,11 +583,8 @@ def instance_get(context, instance_id, session=None): return result -#@require_admin_context +@require_admin_context def instance_get_all(context): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') @@ -618,11 +592,8 @@ def instance_get_all(context): ).all() -#@require_admin_context +@require_admin_context def instance_get_all_by_user(context, user_id): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') @@ -631,11 +602,9 @@ def instance_get_all_by_user(context, user_id): ).all() -#@require_context +@require_context def instance_get_all_by_project(context, project_id): - if is_user_context(context): - if context.project.id != project_id: - raise exception.NotAuthorized() + authorize_project_context(context, project_id) session = get_session() return session.query(models.Instance @@ -645,7 +614,7 @@ def instance_get_all_by_project(context, project_id): ).all() -#@require_context +@require_context def instance_get_all_by_reservation(context, reservation_id): session = get_session() @@ -664,7 +633,7 @@ def instance_get_all_by_reservation(context, reservation_id): ).all() -#@require_context +@require_context def instance_get_by_ec2_id(context, ec2_id): session = get_session() @@ -685,14 +654,14 @@ def instance_get_by_ec2_id(context, ec2_id): return result -#@require_context +@require_context def instance_ec2_id_exists(context, ec2_id, session=None): if not session: session = get_session() return session.query(exists().where(models.Instance.id==ec2_id)).one()[0] -#@require_context +@require_context def instance_get_fixed_address(context, instance_id): session = get_session() with session.begin(): @@ -702,7 +671,7 @@ def instance_get_fixed_address(context, instance_id): return instance_ref.fixed_ip['address'] -#@require_context +@require_context def instance_get_floating_address(context, instance_id): session = get_session() with session.begin(): @@ -715,20 +684,15 @@ def instance_get_floating_address(context, instance_id): return instance_ref.fixed_ip.floating_ips[0]['address'] -#@require_admin_context +@require_admin_context def instance_is_vpn(context, instance_id): - if not is_admin_context(context): - raise exception.NotAuthorized() # TODO(vish): Move this into image code somewhere instance_ref = instance_get(context, instance_id) return instance_ref['image_id'] == FLAGS.vpn_image_id -#@require_admin_context +@require_admin_context def instance_set_state(context, instance_id, state, description=None): - if not is_admin_context(context): - raise exception.NotAuthorized() - # TODO(devcamcar): Move this out of models and into driver from nova.compute import power_state if not description: @@ -739,7 +703,7 @@ def instance_set_state(context, instance_id, state, description=None): 'state_description': description}) -#@require_context +@require_context def instance_update(context, instance_id, values): session = get_session() with session.begin(): @@ -752,7 +716,7 @@ def instance_update(context, instance_id, values): ################### -#@require_context +@require_context def key_pair_create(context, values): key_pair_ref = models.KeyPair() for (key, value) in values.iteritems(): @@ -761,11 +725,9 @@ def key_pair_create(context, values): return key_pair_ref -#@require_context +@require_context def key_pair_destroy(context, user_id, name): - if is_user_context(context): - if context.user.id != user_id: - raise exception.NotAuthorized() + authorize_user_context(context, user_id) session = get_session() with session.begin(): @@ -773,11 +735,9 @@ def key_pair_destroy(context, user_id, name): key_pair_ref.delete(session=session) -#@require_context +@require_context def key_pair_destroy_all_by_user(context, user_id): - if is_user_context(context): - if context.user.id != user_id: - raise exception.NotAuthorized() + authorize_user_context(context, user_id) session = get_session() with session.begin(): @@ -786,11 +746,9 @@ def key_pair_destroy_all_by_user(context, user_id): {'id': user_id}) -#@require_context +@require_context def key_pair_get(context, user_id, name, session=None): - if is_user_context(context): - if context.user.id != user_id: - raise exception.NotAuthorized() + authorize_user_context(context, user_id) if not session: session = get_session() @@ -806,11 +764,9 @@ def key_pair_get(context, user_id, name, session=None): return result -#@require_context +@require_context def key_pair_get_all_by_user(context, user_id): - if is_user_context(context): - if context.user.id != user_id: - raise exception.NotAuthorized() + authorize_user_context(context, user_id) session = get_session() return session.query(models.KeyPair @@ -822,22 +778,16 @@ def key_pair_get_all_by_user(context, user_id): ################### -#@require_admin_context +@require_admin_context def network_count(context): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() return session.query(models.Network ).filter_by(deleted=_deleted(context) ).count() -#@require_admin_context +@require_admin_context def network_count_allocated_ips(context, network_id): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() return session.query(models.FixedIp ).filter_by(network_id=network_id @@ -846,11 +796,8 @@ def network_count_allocated_ips(context, network_id): ).count() -#@require_admin_context +@require_admin_context def network_count_available_ips(context, network_id): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() return session.query(models.FixedIp ).filter_by(network_id=network_id @@ -860,11 +807,8 @@ def network_count_available_ips(context, network_id): ).count() -#@require_admin_context +@require_admin_context def network_count_reserved_ips(context, network_id): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() return session.query(models.FixedIp ).filter_by(network_id=network_id @@ -873,11 +817,8 @@ def network_count_reserved_ips(context, network_id): ).count() -#@require_admin_context +@require_admin_context def network_create(context, values): - if not is_admin_context(context): - raise exception.NotAuthorized() - network_ref = models.Network() for (key, value) in values.iteritems(): network_ref[key] = value @@ -885,11 +826,8 @@ def network_create(context, values): return network_ref -#@require_admin_context +@require_admin_context def network_destroy(context, network_id): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): # TODO(vish): do we have to use sql here? @@ -907,7 +845,7 @@ def network_destroy(context, network_id): {'id': network_id}) -#@require_context +@require_context def network_get(context, network_id, session=None): if not session: session = get_session() @@ -933,11 +871,8 @@ def network_get(context, network_id, session=None): # NOTE(vish): pylint complains because of the long method name, but # it fits with the names of the rest of the methods # pylint: disable-msg=C0103 -#@require_admin_context +@require_admin_context def network_get_associated_fixed_ips(context, network_id): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() return session.query(models.FixedIp ).options(joinedload_all('instance') @@ -947,11 +882,8 @@ def network_get_associated_fixed_ips(context, network_id): ).all() -#@require_admin_context +@require_admin_context def network_get_by_bridge(context, bridge): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() result = session.query(models.Network ).filter_by(bridge=bridge @@ -964,11 +896,8 @@ def network_get_by_bridge(context, bridge): return result -#@require_admin_context +@require_admin_context def network_get_index(context, network_id): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): network_index = session.query(models.NetworkIndex @@ -988,22 +917,16 @@ def network_get_index(context, network_id): return network_index['index'] -#@require_admin_context +@require_admin_context def network_index_count(context): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() return session.query(models.NetworkIndex ).filter_by(deleted=_deleted(context) ).count() -#@require_admin_context +@require_admin_context def network_index_create_safe(context, values): - if not is_admin_context(context): - raise exception.NotAuthorized() - network_index_ref = models.NetworkIndex() for (key, value) in values.iteritems(): network_index_ref[key] = value @@ -1013,11 +936,8 @@ def network_index_create_safe(context, values): pass -#@require_admin_context +@require_admin_context def network_set_host(context, network_id, host_id): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): network_ref = session.query(models.Network @@ -1037,7 +957,7 @@ def network_set_host(context, network_id, host_id): return network_ref['host'] -#@require_context +@require_context def network_update(context, network_id, values): session = get_session() with session.begin(): @@ -1050,11 +970,8 @@ def network_update(context, network_id, values): ################### -#@require_context +@require_context def project_get_network(context, project_id): - if not is_admin_context(context) and not is_user_context(context): - raise error.NotAuthorized() - session = get_session() result= session.query(models.Network ).filter_by(project_id=project_id @@ -1078,22 +995,16 @@ def queue_get_for(_context, topic, physical_node_id): ################### -#@require_admin_context +@require_admin_context def export_device_count(context): - if not is_admin_context(context): - raise exception.notauthorized() - session = get_session() return session.query(models.ExportDevice ).filter_by(deleted=_deleted(context) ).count() -#@require_admin_context +@require_admin_context def export_device_create(context, values): - if not is_admin_context(context): - raise exception.notauthorized() - export_device_ref = models.ExportDevice() for (key, value) in values.iteritems(): export_device_ref[key] = value @@ -1127,11 +1038,8 @@ def auth_create_token(_context, token): ################### -#@require_admin_context +@require_admin_context def quota_get(context, project_id, session=None): - if not is_admin_context(context): - raise exception.NotAuthorized() - if not session: session = get_session() @@ -1145,11 +1053,8 @@ def quota_get(context, project_id, session=None): return result -#@require_admin_context +@require_admin_context def quota_create(context, values): - if not is_admin_context(context): - raise exception.NotAuthorized() - quota_ref = models.Quota() for (key, value) in values.iteritems(): quota_ref[key] = value @@ -1157,11 +1062,8 @@ def quota_create(context, values): return quota_ref -#@require_admin_context +@require_admin_context def quota_update(context, project_id, values): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): quota_ref = quota_get(context, project_id, session=session) @@ -1170,11 +1072,8 @@ def quota_update(context, project_id, values): quota_ref.save(session=session) -#@require_admin_context +@require_admin_context def quota_destroy(context, project_id): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): quota_ref = quota_get(context, project_id, session=session) @@ -1184,11 +1083,8 @@ def quota_destroy(context, project_id): ################### -#@require_admin_context +@require_admin_context def volume_allocate_shelf_and_blade(context, volume_id): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): export_device = session.query(models.ExportDevice @@ -1205,11 +1101,8 @@ def volume_allocate_shelf_and_blade(context, volume_id): return (export_device.shelf_id, export_device.blade_id) -#@require_admin_context +@require_admin_context def volume_attached(context, volume_id, instance_id, mountpoint): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): volume_ref = volume_get(context, volume_id, session=session) @@ -1220,7 +1113,7 @@ def volume_attached(context, volume_id, instance_id, mountpoint): volume_ref.save(session=session) -#@require_context +@require_context def volume_create(context, values): volume_ref = models.Volume() for (key, value) in values.iteritems(): @@ -1236,11 +1129,8 @@ def volume_create(context, values): return volume_ref -#@require_admin_context +@require_admin_context def volume_data_get_for_project(context, project_id): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() result = session.query(func.count(models.Volume.id), func.sum(models.Volume.size) @@ -1251,11 +1141,8 @@ def volume_data_get_for_project(context, project_id): return (result[0] or 0, result[1] or 0) -#@require_admin_context +@require_admin_context def volume_destroy(context, volume_id): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): # TODO(vish): do we have to use sql here? @@ -1266,11 +1153,8 @@ def volume_destroy(context, volume_id): {'id': volume_id}) -#@require_admin_context +@require_admin_context def volume_detached(context, volume_id): - if not is_admin_context(context): - raise exception.NotAuthorized() - session = get_session() with session.begin(): volume_ref = volume_get(context, volume_id, session=session) @@ -1281,7 +1165,7 @@ def volume_detached(context, volume_id): volume_ref.save(session=session) -#@require_context +@require_context def volume_get(context, volume_id, session=None): if not session: session = get_session() @@ -1304,22 +1188,15 @@ def volume_get(context, volume_id, session=None): return result -#@require_admin_context +@require_admin_context def volume_get_all(context): - if not is_admin_context(context): - raise exception.NotAuthorized() - return session.query(models.Volume ).filter_by(deleted=_deleted(context) ).all() -#@require_context +@require_context def volume_get_all_by_project(context, project_id): - if is_user_context(context): - if context.project.id != project_id: - raise exception.NotAuthorized() - elif not is_admin_context(context): - raise exception.NotAuthorized() + authorize_project_context(context, project_id) session = get_session() return session.query(models.Volume @@ -1328,7 +1205,7 @@ def volume_get_all_by_project(context, project_id): ).all() -#@require_context +@require_context def volume_get_by_ec2_id(context, ec2_id): session = get_session() result = None @@ -1353,18 +1230,17 @@ def volume_get_by_ec2_id(context, ec2_id): return result -#@require_context +@require_context def volume_ec2_id_exists(context, ec2_id, session=None): if not session: session = get_session() - if is_admin_context(context) or is_user_context(context): - return session.query(exists( - ).where(models.Volume.id==ec2_id) - ).one()[0] + return session.query(exists( + ).where(models.Volume.id==ec2_id) + ).one()[0] -#@require_context +@require_context def volume_get_instance(context, volume_id): session = get_session() result = None @@ -1390,7 +1266,7 @@ def volume_get_instance(context, volume_id): return result.instance -#@require_context +@require_context def volume_get_shelf_and_blade(context, volume_id): session = get_session() result = None @@ -1412,7 +1288,7 @@ def volume_get_shelf_and_blade(context, volume_id): return (result.shelf_id, result.blade_id) -#@require_context +@require_context def volume_update(context, volume_id, values): session = get_session() with session.begin(): -- cgit From 8c21cc52b1ba007fc12964ea5973290a3f660662 Mon Sep 17 00:00:00 2001 From: Ewan Mellor Date: Thu, 30 Sep 2010 10:21:44 +0100 Subject: Bug #651887: xenapi list_instances completely broken Don't just compute the result for list_instances -- return it! --- nova/virt/xenapi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py index 1c6de4403..0d06b1fce 100644 --- a/nova/virt/xenapi.py +++ b/nova/virt/xenapi.py @@ -103,8 +103,8 @@ class XenAPIConnection(object): self._conn.login_with_password(user, pw) def list_instances(self): - result = [self._conn.xenapi.VM.get_name_label(vm) \ - for vm in self._conn.xenapi.VM.get_all()] + return [self._conn.xenapi.VM.get_name_label(vm) \ + for vm in self._conn.xenapi.VM.get_all()] @defer.inlineCallbacks def spawn(self, instance): -- cgit From cf456bdb2a767644d95599aa1c8f580279959a4e Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Thu, 30 Sep 2010 02:47:05 -0700 Subject: Refactored APIRequestContext --- nova/api/context.py | 46 +++++++++++++++++++++++++++ nova/api/ec2/__init__.py | 8 ++--- nova/api/ec2/context.py | 33 -------------------- nova/db/sqlalchemy/api.py | 71 +++++++++++++++++++----------------------- nova/network/manager.py | 2 -- nova/tests/compute_unittest.py | 8 ++--- 6 files changed, 86 insertions(+), 82 deletions(-) create mode 100644 nova/api/context.py delete mode 100644 nova/api/ec2/context.py (limited to 'nova') diff --git a/nova/api/context.py b/nova/api/context.py new file mode 100644 index 000000000..b66cfe468 --- /dev/null +++ b/nova/api/context.py @@ -0,0 +1,46 @@ +# 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. + +""" +APIRequestContext +""" + +import random + + +class APIRequestContext(object): + def __init__(self, user, project): + self.user = user + self.project = project + self.request_id = ''.join( + [random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-') + for x in xrange(20)] + ) + if user: + self.is_admin = user.is_admin() + else: + self.is_admin = False + self.read_deleted = False + + +def get_admin_context(user=None, read_deleted=False): + context_ref = APIRequestContext(user=user, project=None) + context_ref.is_admin = True + context_ref.read_deleted = read_deleted + return context_ref + diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 7a958f841..6b538a7f1 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -27,8 +27,8 @@ import webob.exc from nova import exception from nova import flags from nova import wsgi +from nova.api import context from nova.api.ec2 import apirequest -from nova.api.ec2 import context from nova.api.ec2 import admin from nova.api.ec2 import cloud from nova.auth import manager @@ -193,15 +193,15 @@ class Authorizer(wsgi.Middleware): return True if 'none' in roles: return False - return any(context.project.has_role(context.user.id, role) + return any(context.project.has_role(context.user.id, role) for role in roles) - + class Executor(wsgi.Application): """Execute an EC2 API request. - Executes 'ec2.action' upon 'ec2.controller', passing 'ec2.context' and + Executes 'ec2.action' upon 'ec2.controller', passing 'ec2.context' and 'ec2.action_args' (all variables in WSGI environ.) Returns an XML response, or a 400 upon failure. """ diff --git a/nova/api/ec2/context.py b/nova/api/ec2/context.py deleted file mode 100644 index c53ba98d9..000000000 --- a/nova/api/ec2/context.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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. - -""" -APIRequestContext -""" - -import random - - -class APIRequestContext(object): - def __init__(self, user, project): - self.user = user - self.project = project - self.request_id = ''.join( - [random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-') - for x in xrange(20)] - ) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 0e7d2e664..fc5ee2235 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -21,6 +21,7 @@ Implementation of SQLAlchemy backend import logging import sys +import warnings from nova import db from nova import exception @@ -36,28 +37,13 @@ from sqlalchemy.sql import exists, func FLAGS = flags.FLAGS -# NOTE(vish): disabling docstring pylint because the docstrings are -# in the interface definition -# pylint: disable-msg=C0111 -def _deleted(context): - """Calculates whether to include deleted objects based on context. - Currently just looks for a flag called deleted in the context dict. - """ - if is_user_context(context): - return False - if not hasattr(context, 'get'): - return False - return context.get('deleted', False) - - def is_admin_context(context): """Indicates if the request context is an administrator.""" if not context: - logging.warning('Use of empty request context is deprecated') - return True - if not context.user: + warnings.warn('Use of empty request context is deprecated', + DeprecationWarning) return True - return context.user.is_admin() + return context.is_admin def is_user_context(context): @@ -92,6 +78,13 @@ def authorize_user_context(context, user_id): raise exception.NotAuthorized() +def use_deleted(context): + """Indicates if the context has access to deleted objects.""" + if not context: + return False + return context.read_deleted + + def require_admin_context(f): """Decorator used to indicate that the method requires an administrator context. @@ -131,7 +124,7 @@ def service_get(context, service_id, session=None): result = session.query(models.Service ).filter_by(id=service_id - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).first() if not result: @@ -229,7 +222,7 @@ def service_get_by_args(context, host, binary): result = session.query(models.Service ).filter_by(host=host ).filter_by(binary=binary - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).first() if not result: @@ -398,7 +391,7 @@ def floating_ip_get_by_address(context, address, session=None): result = session.query(models.FloatingIp ).filter_by(address=address - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).first() if not result: raise exception.NotFound('No fixed ip for address %s' % address) @@ -487,7 +480,7 @@ def fixed_ip_get_by_address(context, address, session=None): result = session.query(models.FixedIp ).filter_by(address=address - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).options(joinedload('network') ).options(joinedload('instance') ).first() @@ -569,7 +562,7 @@ def instance_get(context, instance_id, session=None): if is_admin_context(context): result = session.query(models.Instance ).filter_by(id=instance_id - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).first() elif is_user_context(context): result = session.query(models.Instance @@ -588,7 +581,7 @@ def instance_get_all(context): session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).all() @@ -597,7 +590,7 @@ def instance_get_all_by_user(context, user_id): session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).filter_by(user_id=user_id ).all() @@ -610,7 +603,7 @@ def instance_get_all_by_project(context, project_id): return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') ).filter_by(project_id=project_id - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).all() @@ -622,7 +615,7 @@ def instance_get_all_by_reservation(context, reservation_id): return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') ).filter_by(reservation_id=reservation_id - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).all() elif is_user_context(context): return session.query(models.Instance @@ -640,7 +633,7 @@ def instance_get_by_ec2_id(context, ec2_id): if is_admin_context(context): result = session.query(models.Instance ).filter_by(ec2_id=ec2_id - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).first() elif is_user_context(context): result = session.query(models.Instance @@ -756,7 +749,7 @@ def key_pair_get(context, user_id, name, session=None): result = session.query(models.KeyPair ).filter_by(user_id=user_id ).filter_by(name=name - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).first() if not result: raise exception.NotFound('no keypair for user %s, name %s' % @@ -782,7 +775,7 @@ def key_pair_get_all_by_user(context, user_id): def network_count(context): session = get_session() return session.query(models.Network - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).count() @@ -854,7 +847,7 @@ def network_get(context, network_id, session=None): if is_admin_context(context): result = session.query(models.Network ).filter_by(id=network_id - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).first() elif is_user_context(context): result = session.query(models.Network @@ -921,7 +914,7 @@ def network_get_index(context, network_id): def network_index_count(context): session = get_session() return session.query(models.NetworkIndex - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).count() @@ -999,7 +992,7 @@ def queue_get_for(_context, topic, physical_node_id): def export_device_count(context): session = get_session() return session.query(models.ExportDevice - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).count() @@ -1045,7 +1038,7 @@ def quota_get(context, project_id, session=None): result = session.query(models.Quota ).filter_by(project_id=project_id - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).first() if not result: raise exception.NotFound('No quota for project_id %s' % project_id) @@ -1174,7 +1167,7 @@ def volume_get(context, volume_id, session=None): if is_admin_context(context): result = session.query(models.Volume ).filter_by(id=volume_id - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).first() elif is_user_context(context): result = session.query(models.Volume @@ -1191,7 +1184,7 @@ def volume_get(context, volume_id, session=None): @require_admin_context def volume_get_all(context): return session.query(models.Volume - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).all() @require_context @@ -1201,7 +1194,7 @@ def volume_get_all_by_project(context, project_id): session = get_session() return session.query(models.Volume ).filter_by(project_id=project_id - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).all() @@ -1213,7 +1206,7 @@ def volume_get_by_ec2_id(context, ec2_id): if is_admin_context(context): result = session.query(models.Volume ).filter_by(ec2_id=ec2_id - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).first() elif is_user_context(context): result = session.query(models.Volume @@ -1248,7 +1241,7 @@ def volume_get_instance(context, volume_id): if is_admin_context(context): result = session.query(models.Volume ).filter_by(id=volume_id - ).filter_by(deleted=_deleted(context) + ).filter_by(deleted=use_deleted(context) ).options(joinedload('instance') ).first() elif is_user_context(context): diff --git a/nova/network/manager.py b/nova/network/manager.py index ecf2fa2c2..265c0d742 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -88,7 +88,6 @@ class NetworkManager(manager.Manager): # TODO(vish): can we minimize db access by just getting the # id here instead of the ref? network_id = network_ref['id'] - # TODO(devcamcar): Replace with system context host = self.db.network_set_host(None, network_id, self.host) @@ -233,7 +232,6 @@ class VlanManager(NetworkManager): address = network_ref['vpn_private_address'] self.db.fixed_ip_associate(context, address, instance_id) else: - # TODO(devcamcar) Pass system context here. address = self.db.fixed_ip_associate_pool(None, network_ref['id'], instance_id) diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index e705c2552..1e2bb113b 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -30,7 +30,7 @@ from nova import flags from nova import test from nova import utils from nova.auth import manager - +from nova.api import context FLAGS = flags.FLAGS @@ -96,9 +96,9 @@ class ComputeTestCase(test.TrialTestCase): self.assertEqual(instance_ref['deleted_at'], None) terminate = datetime.datetime.utcnow() yield self.compute.terminate_instance(self.context, instance_id) - # TODO(devcamcar): Pass deleted in using system context. - # context.read_deleted ? - instance_ref = db.instance_get({'deleted': True}, instance_id) + self.context = context.get_admin_context(user=self.user, + read_deleted=True) + instance_ref = db.instance_get(self.context, instance_id) self.assert_(instance_ref['launched_at'] < terminate) self.assert_(instance_ref['deleted_at'] > terminate) -- cgit From ab948224a5c6ea976def30927ac7668dd765dbca Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Thu, 30 Sep 2010 03:13:47 -0700 Subject: Cleaned up db/api.py --- nova/db/api.py | 4 ---- 1 file changed, 4 deletions(-) (limited to 'nova') diff --git a/nova/db/api.py b/nova/db/api.py index 4cfdd788c..290b460a6 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -175,10 +175,6 @@ def floating_ip_get_by_address(context, address): return IMPL.floating_ip_get_by_address(context, address) - """Get an instance for a floating ip by address.""" - return IMPL.floating_ip_get_instance(context, address) - - #################### -- cgit From 75c5ba6aae6a57a61771ed78b6797c90f7da6940 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Thu, 30 Sep 2010 09:22:46 -0500 Subject: Grabbed the wrong copyright info --- nova/api/rackspace/context.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/rackspace/context.py b/nova/api/rackspace/context.py index 924ee151e..77394615b 100644 --- a/nova/api/rackspace/context.py +++ b/nova/api/rackspace/context.py @@ -1,7 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. +# Copyright 2010 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may -- cgit From 76d9510ac496051697f7cdf0d693d5146e83283a Mon Sep 17 00:00:00 2001 From: Ewan Mellor Date: Thu, 30 Sep 2010 16:00:53 +0100 Subject: Bug #652103: NameError in exception handler in sqlalchemy API layer Fix reference to NoResultFound. --- nova/db/sqlalchemy/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 9c3caf9af..e63d3b18e 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -30,6 +30,7 @@ from nova.db.sqlalchemy.session import get_session from sqlalchemy import or_ from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import joinedload_all +from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.sql import exists, func FLAGS = flags.FLAGS @@ -348,7 +349,7 @@ def fixed_ip_get_by_address(_context, address): ).filter_by(address=address ).filter_by(deleted=False ).one() - except exc.NoResultFound: + except NoResultFound: new_exc = exception.NotFound("No model for address %s" % address) raise new_exc.__class__, new_exc, sys.exc_info()[2] -- cgit From ae55b04a4b25c6756311f001e36b5bbd76675e8c Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 30 Sep 2010 18:48:45 -0400 Subject: Mongo bad, swift good. --- nova/objectstore/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/objectstore/__init__.py b/nova/objectstore/__init__.py index b8890ac03..ecad9be7c 100644 --- a/nova/objectstore/__init__.py +++ b/nova/objectstore/__init__.py @@ -22,7 +22,7 @@ .. automodule:: nova.objectstore :platform: Unix - :synopsis: Currently a trivial file-based system, getting extended w/ mongo. + :synopsis: Currently a trivial file-based system, getting extended w/ swift. .. moduleauthor:: Jesse Andrews .. moduleauthor:: Devin Carlen .. moduleauthor:: Vishvananda Ishaya -- cgit From c9e14d6257f0b488bd892c09d284091c0f612dd7 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Fri, 1 Oct 2010 01:44:17 -0700 Subject: Locked down fixed ips and improved network tests --- nova/db/sqlalchemy/api.py | 98 ++++++++++++++++-------------------------- nova/tests/network_unittest.py | 44 ++++++++++--------- 2 files changed, 60 insertions(+), 82 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index fc5ee2235..860723516 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -78,7 +78,7 @@ def authorize_user_context(context, user_id): raise exception.NotAuthorized() -def use_deleted(context): +def can_read_deleted(context): """Indicates if the context has access to deleted objects.""" if not context: return False @@ -124,7 +124,7 @@ def service_get(context, service_id, session=None): result = session.query(models.Service ).filter_by(id=service_id - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).first() if not result: @@ -222,9 +222,8 @@ def service_get_by_args(context, host, binary): result = session.query(models.Service ).filter_by(host=host ).filter_by(binary=binary - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).first() - if not result: raise exception.NotFound('No service for %s, %s' % (host, binary)) @@ -256,7 +255,6 @@ def service_update(context, service_id, values): @require_context def floating_ip_allocate_address(context, host, project_id): authorize_project_context(context, project_id) - session = get_session() with session.begin(): floating_ip_ref = session.query(models.FloatingIp @@ -287,7 +285,6 @@ def floating_ip_create(context, values): @require_context def floating_ip_count_by_project(context, project_id): authorize_project_context(context, project_id) - session = get_session() return session.query(models.FloatingIp ).filter_by(project_id=project_id @@ -374,7 +371,6 @@ def floating_ip_get_all_by_host(context, host): @require_context def floating_ip_get_all_by_project(context, project_id): authorize_project_context(context, project_id) - session = get_session() return session.query(models.FloatingIp ).options(joinedload_all('fixed_ip.instance') @@ -391,7 +387,7 @@ def floating_ip_get_by_address(context, address, session=None): result = session.query(models.FloatingIp ).filter_by(address=address - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).first() if not result: raise exception.NotFound('No fixed ip for address %s' % address) @@ -406,6 +402,7 @@ def floating_ip_get_by_address(context, address, session=None): def fixed_ip_associate(context, address, instance_id): session = get_session() with session.begin(): + instance = instance_get(context, instance_id, session=session) fixed_ip_ref = session.query(models.FixedIp ).filter_by(address=address ).filter_by(deleted=False @@ -416,9 +413,7 @@ def fixed_ip_associate(context, address, instance_id): # then this has concurrency issues if not fixed_ip_ref: raise db.NoMoreAddresses() - fixed_ip_ref.instance = instance_get(context, - instance_id, - session=session) + fixed_ip_ref.instance = instance session.add(fixed_ip_ref) @@ -472,21 +467,21 @@ def fixed_ip_disassociate(context, address): @require_context def fixed_ip_get_by_address(context, address, session=None): - # TODO(devcamcar): Ensure floating ip belongs to user. - # Only possible if it is associated with an instance. - # May have to use system context for this always. if not session: session = get_session() result = session.query(models.FixedIp ).filter_by(address=address - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).options(joinedload('network') ).options(joinedload('instance') ).first() if not result: raise exception.NotFound('No floating ip for address %s' % address) + if is_user_context(context): + authorize_project_context(context, result.instance.project_id) + return result @@ -562,7 +557,7 @@ def instance_get(context, instance_id, session=None): if is_admin_context(context): result = session.query(models.Instance ).filter_by(id=instance_id - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).first() elif is_user_context(context): result = session.query(models.Instance @@ -581,7 +576,7 @@ def instance_get_all(context): session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).all() @@ -590,7 +585,7 @@ def instance_get_all_by_user(context, user_id): session = get_session() return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).filter_by(user_id=user_id ).all() @@ -603,7 +598,7 @@ def instance_get_all_by_project(context, project_id): return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') ).filter_by(project_id=project_id - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).all() @@ -615,7 +610,7 @@ def instance_get_all_by_reservation(context, reservation_id): return session.query(models.Instance ).options(joinedload_all('fixed_ip.floating_ips') ).filter_by(reservation_id=reservation_id - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).all() elif is_user_context(context): return session.query(models.Instance @@ -633,7 +628,7 @@ def instance_get_by_ec2_id(context, ec2_id): if is_admin_context(context): result = session.query(models.Instance ).filter_by(ec2_id=ec2_id - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).first() elif is_user_context(context): result = session.query(models.Instance @@ -749,7 +744,7 @@ def key_pair_get(context, user_id, name, session=None): result = session.query(models.KeyPair ).filter_by(user_id=user_id ).filter_by(name=name - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).first() if not result: raise exception.NotFound('no keypair for user %s, name %s' % @@ -775,7 +770,7 @@ def key_pair_get_all_by_user(context, user_id): def network_count(context): session = get_session() return session.query(models.Network - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).count() @@ -847,7 +842,7 @@ def network_get(context, network_id, session=None): if is_admin_context(context): result = session.query(models.Network ).filter_by(id=network_id - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).first() elif is_user_context(context): result = session.query(models.Network @@ -914,7 +909,7 @@ def network_get_index(context, network_id): def network_index_count(context): session = get_session() return session.query(models.NetworkIndex - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).count() @@ -992,7 +987,7 @@ def queue_get_for(_context, topic, physical_node_id): def export_device_count(context): session = get_session() return session.query(models.ExportDevice - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).count() @@ -1038,7 +1033,7 @@ def quota_get(context, project_id, session=None): result = session.query(models.Quota ).filter_by(project_id=project_id - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).first() if not result: raise exception.NotFound('No quota for project_id %s' % project_id) @@ -1167,7 +1162,7 @@ def volume_get(context, volume_id, session=None): if is_admin_context(context): result = session.query(models.Volume ).filter_by(id=volume_id - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).first() elif is_user_context(context): result = session.query(models.Volume @@ -1184,7 +1179,7 @@ def volume_get(context, volume_id, session=None): @require_admin_context def volume_get_all(context): return session.query(models.Volume - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).all() @require_context @@ -1194,7 +1189,7 @@ def volume_get_all_by_project(context, project_id): session = get_session() return session.query(models.Volume ).filter_by(project_id=project_id - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).all() @@ -1206,7 +1201,7 @@ def volume_get_by_ec2_id(context, ec2_id): if is_admin_context(context): result = session.query(models.Volume ).filter_by(ec2_id=ec2_id - ).filter_by(deleted=use_deleted(context) + ).filter_by(deleted=can_read_deleted(context) ).first() elif is_user_context(context): result = session.query(models.Volume @@ -1233,47 +1228,26 @@ def volume_ec2_id_exists(context, ec2_id, session=None): ).one()[0] -@require_context +@require_admin_context def volume_get_instance(context, volume_id): session = get_session() - result = None - - if is_admin_context(context): - result = session.query(models.Volume - ).filter_by(id=volume_id - ).filter_by(deleted=use_deleted(context) - ).options(joinedload('instance') - ).first() - elif is_user_context(context): - result = session.query(models.Volume - ).filter_by(project_id=context.project.id - ).filter_by(deleted=False - ).options(joinedload('instance') - ).first() - else: - raise exception.NotAuthorized() - + result = session.query(models.Volume + ).filter_by(id=volume_id + ).filter_by(deleted=can_read_deleted(context) + ).options(joinedload('instance') + ).first() if not result: raise exception.NotFound('Volume %s not found' % ec2_id) return result.instance -@require_context +@require_admin_context def volume_get_shelf_and_blade(context, volume_id): session = get_session() - result = None - - if is_admin_context(context): - result = session.query(models.ExportDevice - ).filter_by(volume_id=volume_id - ).first() - elif is_user_context(context): - result = session.query(models.ExportDevice - ).join(models.Volume - ).filter(models.Volume.project_id==context.project.id - ).filter_by(volume_id=volume_id - ).first() + result = session.query(models.ExportDevice + ).filter_by(volume_id=volume_id + ).first() if not result: raise exception.NotFound('No export device found for volume %s' % volume_id) diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index e01d7cff9..e601c480c 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -49,7 +49,6 @@ class NetworkTestCase(test.TrialTestCase): self.user = self.manager.create_user('netuser', 'netuser', 'netuser') self.projects = [] self.network = utils.import_object(FLAGS.network_manager) - # TODO(devcamcar): Passing project=None is Bad(tm). self.context = context.APIRequestContext(project=None, user=self.user) for i in range(5): name = 'project%s' % i @@ -60,11 +59,9 @@ class NetworkTestCase(test.TrialTestCase): user_context = context.APIRequestContext(project=self.projects[i], user=self.user) self.network.set_network_host(user_context, self.projects[i].id) - instance_ref = db.instance_create(None, - {'mac_address': utils.generate_mac()}) + instance_ref = self._create_instance(0) self.instance_id = instance_ref['id'] - instance_ref = db.instance_create(None, - {'mac_address': utils.generate_mac()}) + instance_ref = self._create_instance(1) self.instance2_id = instance_ref['id'] def tearDown(self): # pylint: disable-msg=C0103 @@ -77,6 +74,15 @@ class NetworkTestCase(test.TrialTestCase): self.manager.delete_project(project) self.manager.delete_user(self.user) + def _create_instance(self, project_num, mac=None): + if not mac: + mac = utils.generate_mac() + project = self.projects[project_num] + self.context.project = project + return db.instance_create(self.context, + {'project_id': project.id, + 'mac_address': mac}) + def _create_address(self, project_num, instance_id=None): """Create an address in given project num""" if instance_id is None: @@ -84,6 +90,11 @@ class NetworkTestCase(test.TrialTestCase): self.context.project = self.projects[project_num] return self.network.allocate_fixed_ip(self.context, instance_id) + def _deallocate_address(self, project_num, address): + self.context.project = self.projects[project_num] + self.network.deallocate_fixed_ip(self.context, address) + + def test_public_network_association(self): """Makes sure that we can allocaate a public ip""" # TODO(vish): better way of adding floating ips @@ -134,14 +145,14 @@ class NetworkTestCase(test.TrialTestCase): lease_ip(address) lease_ip(address2) - self.network.deallocate_fixed_ip(self.context, address) + self._deallocate_address(0, address) release_ip(address) self.assertFalse(is_allocated_in_project(address, self.projects[0].id)) # First address release shouldn't affect the second self.assertTrue(is_allocated_in_project(address2, self.projects[1].id)) - self.network.deallocate_fixed_ip(self.context, address2) + self._deallocate_address(1, address2) release_ip(address2) self.assertFalse(is_allocated_in_project(address2, self.projects[1].id)) @@ -152,24 +163,19 @@ class NetworkTestCase(test.TrialTestCase): lease_ip(first) instance_ids = [] for i in range(1, 5): - mac = utils.generate_mac() - instance_ref = db.instance_create(None, - {'mac_address': mac}) + instance_ref = self._create_instance(i, mac=utils.generate_mac()) instance_ids.append(instance_ref['id']) address = self._create_address(i, instance_ref['id']) - mac = utils.generate_mac() - instance_ref = db.instance_create(None, - {'mac_address': mac}) + instance_ref = self._create_instance(i, mac=utils.generate_mac()) instance_ids.append(instance_ref['id']) address2 = self._create_address(i, instance_ref['id']) - mac = utils.generate_mac() - instance_ref = db.instance_create(None, - {'mac_address': mac}) + instance_ref = self._create_instance(i, mac=utils.generate_mac()) instance_ids.append(instance_ref['id']) address3 = self._create_address(i, instance_ref['id']) lease_ip(address) lease_ip(address2) lease_ip(address3) + self.context.project = self.projects[i] self.assertFalse(is_allocated_in_project(address, self.projects[0].id)) self.assertFalse(is_allocated_in_project(address2, @@ -185,7 +191,7 @@ class NetworkTestCase(test.TrialTestCase): for instance_id in instance_ids: db.instance_destroy(None, instance_id) release_ip(first) - self.network.deallocate_fixed_ip(self.context, first) + self._deallocate_address(0, first) def test_vpn_ip_and_port_looks_valid(self): """Ensure the vpn ip and port are reasonable""" @@ -246,9 +252,7 @@ class NetworkTestCase(test.TrialTestCase): addresses = [] instance_ids = [] for i in range(num_available_ips): - mac = utils.generate_mac() - instance_ref = db.instance_create(None, - {'mac_address': mac}) + instance_ref = self._create_instance(0) instance_ids.append(instance_ref['id']) address = self._create_address(0, instance_ref['id']) addresses.append(address) -- cgit