Add in multi-tenant support in openstack api.

This commit is contained in:
Monsyne Dragon
2011-03-03 15:41:45 +00:00
parent bb7c1b8c63
commit 6797c5acc4
24 changed files with 688 additions and 103 deletions

View File

@@ -433,6 +433,8 @@ class ProjectCommands(object):
"been created.\nPlease create a database by running a " "been created.\nPlease create a database by running a "
"nova-api server on this host.") "nova-api server on this host.")
AccountCommands = ProjectCommands
class FixedIpCommands(object): class FixedIpCommands(object):
"""Class for managing fixed ip.""" """Class for managing fixed ip."""
@@ -663,6 +665,7 @@ class VolumeCommands(object):
CATEGORIES = [ CATEGORIES = [
('user', UserCommands), ('user', UserCommands),
('account', AccountCommands),
('project', ProjectCommands), ('project', ProjectCommands),
('role', RoleCommands), ('role', RoleCommands),
('shell', ShellCommands), ('shell', ShellCommands),

View File

@@ -27,6 +27,7 @@ import webob.exc
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
from nova import wsgi from nova import wsgi
from nova.api.openstack import accounts
from nova.api.openstack import faults from nova.api.openstack import faults
from nova.api.openstack import backup_schedules from nova.api.openstack import backup_schedules
from nova.api.openstack import consoles from nova.api.openstack import consoles
@@ -34,6 +35,7 @@ from nova.api.openstack import flavors
from nova.api.openstack import images from nova.api.openstack import images
from nova.api.openstack import servers from nova.api.openstack import servers
from nova.api.openstack import shared_ip_groups from nova.api.openstack import shared_ip_groups
from nova.api.openstack import users
from nova.api.openstack import zones from nova.api.openstack import zones
@@ -71,6 +73,18 @@ class APIRouter(wsgi.Router):
def __init__(self): def __init__(self):
mapper = routes.Mapper() mapper = routes.Mapper()
accounts_controller = accounts.Controller()
mapper.connect("account", "/{id}",
controller=accounts_controller, action="show",
conditions=dict(method=["GET"]))
if FLAGS.allow_admin_api:
mapper.connect("/{id}",
controller=accounts_controller, action="update",
conditions=dict(method=["PUT"]))
mapper.connect("/{id}",
controller=accounts_controller, action="delete",
conditions=dict(method=["DELETE"]))
server_members = {'action': 'POST'} server_members = {'action': 'POST'}
if FLAGS.allow_admin_api: if FLAGS.allow_admin_api:
LOG.debug(_("Including admin operations in API.")) LOG.debug(_("Including admin operations in API."))
@@ -84,27 +98,38 @@ class APIRouter(wsgi.Router):
server_members['inject_network_info'] = 'POST' server_members['inject_network_info'] = 'POST'
mapper.resource("zone", "zones", controller=zones.Controller(), mapper.resource("zone", "zones", controller=zones.Controller(),
path_prefix="{account_id}/",
collection={'detail': 'GET'})
mapper.resource("user", "users", controller=users.Controller(),
path_prefix="{account_id}/",
collection={'detail': 'GET'}) collection={'detail': 'GET'})
mapper.resource("server", "servers", controller=servers.Controller(), mapper.resource("server", "servers", controller=servers.Controller(),
collection={'detail': 'GET'}, collection={'detail': 'GET'},
path_prefix="{account_id}/",
member=server_members) member=server_members)
mapper.resource("backup_schedule", "backup_schedule", mapper.resource("backup_schedule", "backup_schedule",
controller=backup_schedules.Controller(), controller=backup_schedules.Controller(),
path_prefix="{account_id}/servers/{server_id}/",
parent_resource=dict(member_name='server', parent_resource=dict(member_name='server',
collection_name='servers')) collection_name='servers'))
mapper.resource("console", "consoles", mapper.resource("console", "consoles",
controller=consoles.Controller(), controller=consoles.Controller(),
path_prefix="{account_id}/servers/{server_id}/",
parent_resource=dict(member_name='server', parent_resource=dict(member_name='server',
collection_name='servers')) collection_name='servers'))
mapper.resource("image", "images", controller=images.Controller(), mapper.resource("image", "images", controller=images.Controller(),
path_prefix="{account_id}/",
collection={'detail': 'GET'}) collection={'detail': 'GET'})
mapper.resource("flavor", "flavors", controller=flavors.Controller(), mapper.resource("flavor", "flavors", controller=flavors.Controller(),
path_prefix="{account_id}/",
collection={'detail': 'GET'}) collection={'detail': 'GET'})
mapper.resource("shared_ip_group", "shared_ip_groups", mapper.resource("shared_ip_group", "shared_ip_groups",
path_prefix="{account_id}/",
collection={'detail': 'GET'}, collection={'detail': 'GET'},
controller=shared_ip_groups.Controller()) controller=shared_ip_groups.Controller())

View File

@@ -0,0 +1,73 @@
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import common
from nova import exception
from nova import flags
from nova import log as logging
from nova import wsgi
from nova.auth import manager
FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.api.openstack')
def _translate_keys(account):
return dict(id=account.id,
name=account.name,
description=account.description,
manager=account.project_manager_id)
class Controller(wsgi.Controller):
_serialization_metadata = {
'application/xml': {
"attributes": {
"account": ["id", "name", "description", "manager"]}}}
def __init__(self):
self.manager = manager.AuthManager()
def _check_admin(self, context):
""" We cannot depend on the db layer to check for admin access
for the auth manager, so we do it here """
if not context.is_admin:
raise exception.NotAuthorized("Not admin user.")
def show(self, req, id):
"""Return data about the given account id"""
account = self.manager.get_project(id)
return dict(account=_translate_keys(account))
def delete(self, req, id):
self._check_admin(req.environ['nova.context'])
self.manager.delete_project(id)
return {}
def update(self, req, id):
""" This is really create or update. """
self._check_admin(req.environ['nova.context'])
env = self._deserialize(req.body, req)
description = env['account'].get('description')
manager = env['account'].get('manager')
try:
account = self.manager.get_project(id)
self.manager.modify_project(id, manager, description)
except exception.NotFound:
account = self.manager.create_project(id, manager, description)
return dict(account=_translate_keys(account))

View File

@@ -28,11 +28,13 @@ from nova import context
from nova import db from nova import db
from nova import exception from nova import exception
from nova import flags from nova import flags
from nova import log as logging
from nova import manager from nova import manager
from nova import utils from nova import utils
from nova import wsgi from nova import wsgi
from nova.api.openstack import faults from nova.api.openstack import faults
LOG = logging.getLogger('nova.api.openstack')
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@@ -50,14 +52,27 @@ class AuthMiddleware(wsgi.Middleware):
def __call__(self, req): def __call__(self, req):
if not self.has_authentication(req): if not self.has_authentication(req):
return self.authenticate(req) return self.authenticate(req)
user = self.get_user_by_authentication(req) user = self.get_user_by_authentication(req)
account_name = req.path_info_peek()
if not user: if not user:
return faults.Fault(webob.exc.HTTPUnauthorized()) return faults.Fault(webob.exc.HTTPUnauthorized())
project = self.auth.get_project(FLAGS.default_project) if not account_name:
req.environ['nova.context'] = context.RequestContext(user, project) if self.auth.is_admin(user):
account_name = FLAGS.default_project
else:
return faults.Fault(webob.exc.HTTPUnauthorized())
try:
account = self.auth.get_project(account_name)
except exception.NotFound:
return faults.Fault(webob.exc.HTTPUnauthorized())
if not self.auth.is_admin(user) and \
not self.auth.is_project_member(user, account):
return faults.Fault(webob.exc.HTTPUnauthorized())
req.environ['nova.context'] = context.RequestContext(user, account)
return self.application return self.application
def has_authentication(self, req): def has_authentication(self, req):
@@ -70,6 +85,7 @@ class AuthMiddleware(wsgi.Middleware):
# Unless the request is explicitly made against /<version>/ don't # Unless the request is explicitly made against /<version>/ don't
# honor it # honor it
path_info = req.path_info path_info = req.path_info
account_name = None
if len(path_info) > 1: if len(path_info) > 1:
return faults.Fault(webob.exc.HTTPUnauthorized()) return faults.Fault(webob.exc.HTTPUnauthorized())
@@ -79,7 +95,10 @@ class AuthMiddleware(wsgi.Middleware):
except KeyError: except KeyError:
return faults.Fault(webob.exc.HTTPUnauthorized()) return faults.Fault(webob.exc.HTTPUnauthorized())
token, user = self._authorize_user(username, key, req) if ':' in username:
account_name, username = username.rsplit(':', 1)
token, user = self._authorize_user(username, account_name, key, req)
if user and token: if user and token:
res = webob.Response() res = webob.Response()
res.headers['X-Auth-Token'] = token.token_hash res.headers['X-Auth-Token'] = token.token_hash
@@ -116,23 +135,44 @@ class AuthMiddleware(wsgi.Middleware):
return self.auth.get_user(token.user_id) return self.auth.get_user(token.user_id)
return None return None
def _authorize_user(self, username, key, req): def _authorize_user(self, username, account_name, key, req):
"""Generates a new token and assigns it to a user. """Generates a new token and assigns it to a user.
username - string username - string
account_name - string
key - string API key key - string API key
req - webob.Request object req - webob.Request object
""" """
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
user = self.auth.get_user_from_access_key(key) user = self.auth.get_user_from_access_key(key)
if account_name:
try:
account = self.auth.get_project(account_name)
except exception.NotFound:
return None, None
else:
# (dragondm) punt and try to determine account.
# this is something of a hack, but a user on 1 account is a
# common case, and is the way the current RS code works.
accounts = self.auth.get_projects(user=user)
if len(accounts) == 1:
account = accounts[0]
else:
#we can't tell what account they are logging in for.
return None, None
if user and user.name == username: if user and user.name == username:
token_hash = hashlib.sha1('%s%s%f' % (username, key, token_hash = hashlib.sha1('%s%s%f' % (username, key,
time.time())).hexdigest() time.time())).hexdigest()
token_dict = {} token_dict = {}
token_dict['token_hash'] = token_hash token_dict['token_hash'] = token_hash
token_dict['cdn_management_url'] = '' token_dict['cdn_management_url'] = ''
# Same as auth url, e.g. http://foo.org:8774/baz/v1.0 # auth url + project (account) id, e.g.
token_dict['server_management_url'] = req.url # http://foo.org:8774/baz/v1.0/myacct/
os_url = '%s%s%s/' % (req.url,
'' if req.url.endswith('/') else '/',
account.id)
token_dict['server_management_url'] = os_url
token_dict['storage_url'] = '' token_dict['storage_url'] = ''
token_dict['user_id'] = user.id token_dict['user_id'] = user.id
token = self.db.auth_token_create(ctxt, token_dict) token = self.db.auth_token_create(ctxt, token_dict)

View File

@@ -40,15 +40,15 @@ class Controller(wsgi.Controller):
def __init__(self): def __init__(self):
pass pass
def index(self, req, server_id): def index(self, req, server_id, **kw):
""" Returns the list of backup schedules for a given instance """ """ Returns the list of backup schedules for a given instance """
return _translate_keys({}) return _translate_keys({})
def create(self, req, server_id): def create(self, req, server_id, **kw):
""" No actual update method required, since the existing API allows """ No actual update method required, since the existing API allows
both create and update through a POST """ both create and update through a POST """
return faults.Fault(exc.HTTPNotImplemented()) return faults.Fault(exc.HTTPNotImplemented())
def delete(self, req, server_id, id): def delete(self, req, server_id, id, **kw):
""" Deletes an existing backup schedule """ """ Deletes an existing backup schedule """
return faults.Fault(exc.HTTPNotImplemented()) return faults.Fault(exc.HTTPNotImplemented())

View File

@@ -55,7 +55,7 @@ class Controller(wsgi.Controller):
self.console_api = console.API() self.console_api = console.API()
super(Controller, self).__init__() super(Controller, self).__init__()
def index(self, req, server_id): def index(self, req, server_id, **kw):
"""Returns a list of consoles for this instance""" """Returns a list of consoles for this instance"""
consoles = self.console_api.get_consoles( consoles = self.console_api.get_consoles(
req.environ['nova.context'], req.environ['nova.context'],
@@ -63,14 +63,14 @@ class Controller(wsgi.Controller):
return dict(consoles=[_translate_keys(console) return dict(consoles=[_translate_keys(console)
for console in consoles]) for console in consoles])
def create(self, req, server_id): def create(self, req, server_id, **kw):
"""Creates a new console""" """Creates a new console"""
#info = self._deserialize(req.body, req) #info = self._deserialize(req.body, req)
self.console_api.create_console( self.console_api.create_console(
req.environ['nova.context'], req.environ['nova.context'],
int(server_id)) int(server_id))
def show(self, req, server_id, id): def show(self, req, server_id, id, **kw):
"""Shows in-depth information on a specific console""" """Shows in-depth information on a specific console"""
try: try:
console = self.console_api.get_console( console = self.console_api.get_console(
@@ -81,11 +81,11 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPNotFound()) return faults.Fault(exc.HTTPNotFound())
return _translate_detail_keys(console) return _translate_detail_keys(console)
def update(self, req, server_id, id): def update(self, req, server_id, id, **kw):
"""You can't update a console""" """You can't update a console"""
raise faults.Fault(exc.HTTPNotImplemented()) raise faults.Fault(exc.HTTPNotImplemented())
def delete(self, req, server_id, id): def delete(self, req, server_id, id, **kw):
"""Deletes a console""" """Deletes a console"""
try: try:
self.console_api.delete_console(req.environ['nova.context'], self.console_api.delete_console(req.environ['nova.context'],

View File

@@ -32,18 +32,18 @@ class Controller(wsgi.Controller):
"attributes": { "attributes": {
"flavor": ["id", "name", "ram", "disk"]}}} "flavor": ["id", "name", "ram", "disk"]}}}
def index(self, req): def index(self, req, **kw):
"""Return all flavors in brief.""" """Return all flavors in brief."""
return dict(flavors=[dict(id=flavor['id'], name=flavor['name']) return dict(flavors=[dict(id=flavor['id'], name=flavor['name'])
for flavor in self.detail(req)['flavors']]) for flavor in self.detail(req)['flavors']])
def detail(self, req): def detail(self, req, **kw):
"""Return all flavors in detail.""" """Return all flavors in detail."""
items = [self.show(req, id)['flavor'] for id in self._all_ids()] items = [self.show(req, id)['flavor'] for id in self._all_ids()]
items = common.limited(items, req) items = common.limited(items, req)
return dict(flavors=items) return dict(flavors=items)
def show(self, req, id): def show(self, req, id, **kw):
"""Return data about the given flavor id.""" """Return data about the given flavor id."""
for name, val in instance_types.INSTANCE_TYPES.iteritems(): for name, val in instance_types.INSTANCE_TYPES.iteritems():
if val['flavorid'] == int(id): if val['flavorid'] == int(id):

View File

@@ -115,14 +115,14 @@ class Controller(wsgi.Controller):
def __init__(self): def __init__(self):
self._service = utils.import_object(FLAGS.image_service) self._service = utils.import_object(FLAGS.image_service)
def index(self, req): def index(self, req, **kw):
"""Return all public images in brief""" """Return all public images in brief"""
items = self._service.index(req.environ['nova.context']) items = self._service.index(req.environ['nova.context'])
items = common.limited(items, req) items = common.limited(items, req)
items = [_filter_keys(item, ('id', 'name')) for item in items] items = [_filter_keys(item, ('id', 'name')) for item in items]
return dict(images=items) return dict(images=items)
def detail(self, req): def detail(self, req, **kw):
"""Return all public images in detail""" """Return all public images in detail"""
try: try:
items = self._service.detail(req.environ['nova.context']) items = self._service.detail(req.environ['nova.context'])
@@ -136,7 +136,7 @@ class Controller(wsgi.Controller):
items = [_translate_status(item) for item in items] items = [_translate_status(item) for item in items]
return dict(images=items) return dict(images=items)
def show(self, req, id): def show(self, req, id, **kw):
"""Return data about the given image id""" """Return data about the given image id"""
image_id = common.get_image_id_from_image_hash(self._service, image_id = common.get_image_id_from_image_hash(self._service,
req.environ['nova.context'], id) req.environ['nova.context'], id)
@@ -145,11 +145,11 @@ class Controller(wsgi.Controller):
_convert_image_id_to_hash(image) _convert_image_id_to_hash(image)
return dict(image=image) return dict(image=image)
def delete(self, req, id): def delete(self, req, id, **kw):
# Only public images are supported for now. # Only public images are supported for now.
raise faults.Fault(exc.HTTPNotFound()) raise faults.Fault(exc.HTTPNotFound())
def create(self, req): def create(self, req, **kw):
context = req.environ['nova.context'] context = req.environ['nova.context']
env = self._deserialize(req.body, req) env = self._deserialize(req.body, req)
instance_id = env["image"]["serverId"] instance_id = env["image"]["serverId"]
@@ -160,7 +160,7 @@ class Controller(wsgi.Controller):
return dict(image=image_meta) return dict(image=image_meta)
def update(self, req, id): def update(self, req, id, **kw):
# Users may not modify public images, and that's all that # Users may not modify public images, and that's all that
# we support for now. # we support for now.
raise faults.Fault(exc.HTTPNotFound()) raise faults.Fault(exc.HTTPNotFound())

View File

@@ -105,11 +105,11 @@ class Controller(wsgi.Controller):
self._image_service = utils.import_object(FLAGS.image_service) self._image_service = utils.import_object(FLAGS.image_service)
super(Controller, self).__init__() super(Controller, self).__init__()
def index(self, req): def index(self, req, **kw):
""" Returns a list of server names and ids for a given user """ """ Returns a list of server names and ids for a given user """
return self._items(req, entity_maker=_translate_keys) return self._items(req, entity_maker=_translate_keys)
def detail(self, req): def detail(self, req, **kw):
""" Returns a list of server details for a given user """ """ Returns a list of server details for a given user """
return self._items(req, entity_maker=_translate_detail_keys) return self._items(req, entity_maker=_translate_detail_keys)
@@ -123,7 +123,7 @@ class Controller(wsgi.Controller):
res = [entity_maker(inst)['server'] for inst in limited_list] res = [entity_maker(inst)['server'] for inst in limited_list]
return dict(servers=res) return dict(servers=res)
def show(self, req, id): def show(self, req, id, **kw):
""" Returns server details by server id """ """ Returns server details by server id """
try: try:
instance = self.compute_api.get(req.environ['nova.context'], id) instance = self.compute_api.get(req.environ['nova.context'], id)
@@ -131,7 +131,7 @@ class Controller(wsgi.Controller):
except exception.NotFound: except exception.NotFound:
return faults.Fault(exc.HTTPNotFound()) return faults.Fault(exc.HTTPNotFound())
def delete(self, req, id): def delete(self, req, id, **kw):
""" Destroys a server """ """ Destroys a server """
try: try:
self.compute_api.delete(req.environ['nova.context'], id) self.compute_api.delete(req.environ['nova.context'], id)
@@ -139,7 +139,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPNotFound()) return faults.Fault(exc.HTTPNotFound())
return exc.HTTPAccepted() return exc.HTTPAccepted()
def create(self, req): def create(self, req, **kw):
""" Creates a new server for a given user """ """ Creates a new server for a given user """
env = self._deserialize(req.body, req) env = self._deserialize(req.body, req)
if not env: if not env:
@@ -180,7 +180,7 @@ class Controller(wsgi.Controller):
onset_files=env.get('onset_files', [])) onset_files=env.get('onset_files', []))
return _translate_keys(instances[0]) return _translate_keys(instances[0])
def update(self, req, id): def update(self, req, id, **kw):
""" Updates the server name or password """ """ Updates the server name or password """
inst_dict = self._deserialize(req.body, req) inst_dict = self._deserialize(req.body, req)
if not inst_dict: if not inst_dict:
@@ -202,7 +202,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPNotFound()) return faults.Fault(exc.HTTPNotFound())
return exc.HTTPNoContent() return exc.HTTPNoContent()
def action(self, req, id): def action(self, req, id, **kw):
""" Multi-purpose method used to reboot, rebuild, and """ Multi-purpose method used to reboot, rebuild, and
resize a server """ resize a server """
input_dict = self._deserialize(req.body, req) input_dict = self._deserialize(req.body, req)
@@ -219,7 +219,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity()) return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted() return exc.HTTPAccepted()
def lock(self, req, id): def lock(self, req, id, **kw):
""" """
lock the instance with id lock the instance with id
admin only operation admin only operation
@@ -234,7 +234,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity()) return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted() return exc.HTTPAccepted()
def unlock(self, req, id): def unlock(self, req, id, **kw):
""" """
unlock the instance with id unlock the instance with id
admin only operation admin only operation
@@ -249,7 +249,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity()) return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted() return exc.HTTPAccepted()
def get_lock(self, req, id): def get_lock(self, req, id, **kw):
""" """
return the boolean state of (instance with id)'s lock return the boolean state of (instance with id)'s lock
@@ -263,7 +263,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity()) return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted() return exc.HTTPAccepted()
def reset_network(self, req, id): def reset_network(self, req, id, **kw):
""" """
Reset networking on an instance (admin only). Reset networking on an instance (admin only).
@@ -277,7 +277,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity()) return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted() return exc.HTTPAccepted()
def inject_network_info(self, req, id): def inject_network_info(self, req, id, **kw):
""" """
Inject network info for an instance (admin only). Inject network info for an instance (admin only).
@@ -291,7 +291,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity()) return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted() return exc.HTTPAccepted()
def pause(self, req, id): def pause(self, req, id, **kw):
""" Permit Admins to Pause the server. """ """ Permit Admins to Pause the server. """
ctxt = req.environ['nova.context'] ctxt = req.environ['nova.context']
try: try:
@@ -302,7 +302,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity()) return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted() return exc.HTTPAccepted()
def unpause(self, req, id): def unpause(self, req, id, **kw):
""" Permit Admins to Unpause the server. """ """ Permit Admins to Unpause the server. """
ctxt = req.environ['nova.context'] ctxt = req.environ['nova.context']
try: try:
@@ -313,7 +313,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity()) return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted() return exc.HTTPAccepted()
def suspend(self, req, id): def suspend(self, req, id, **kw):
"""permit admins to suspend the server""" """permit admins to suspend the server"""
context = req.environ['nova.context'] context = req.environ['nova.context']
try: try:
@@ -324,7 +324,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity()) return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted() return exc.HTTPAccepted()
def resume(self, req, id): def resume(self, req, id, **kw):
"""permit admins to resume the server from suspend""" """permit admins to resume the server from suspend"""
context = req.environ['nova.context'] context = req.environ['nova.context']
try: try:
@@ -335,7 +335,7 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPUnprocessableEntity()) return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted() return exc.HTTPAccepted()
def get_ajax_console(self, req, id): def get_ajax_console(self, req, id, **kw):
""" Returns a url to an instance's ajaxterm console. """ """ Returns a url to an instance's ajaxterm console. """
try: try:
self.compute_api.get_ajax_console(req.environ['nova.context'], self.compute_api.get_ajax_console(req.environ['nova.context'],
@@ -344,12 +344,12 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPNotFound()) return faults.Fault(exc.HTTPNotFound())
return exc.HTTPAccepted() return exc.HTTPAccepted()
def diagnostics(self, req, id): def diagnostics(self, req, id, **kw):
"""Permit Admins to retrieve server diagnostics.""" """Permit Admins to retrieve server diagnostics."""
ctxt = req.environ["nova.context"] ctxt = req.environ["nova.context"]
return self.compute_api.get_diagnostics(ctxt, id) return self.compute_api.get_diagnostics(ctxt, id)
def actions(self, req, id): def actions(self, req, id, **kw):
"""Permit Admins to retrieve server actions.""" """Permit Admins to retrieve server actions."""
ctxt = req.environ["nova.context"] ctxt = req.environ["nova.context"]
items = self.compute_api.get_actions(ctxt, id) items = self.compute_api.get_actions(ctxt, id)

View File

@@ -40,26 +40,26 @@ class Controller(wsgi.Controller):
'attributes': { 'attributes': {
'sharedIpGroup': []}}} 'sharedIpGroup': []}}}
def index(self, req): def index(self, req, **kw):
""" Returns a list of Shared IP Groups for the user """ """ Returns a list of Shared IP Groups for the user """
return dict(sharedIpGroups=[]) return dict(sharedIpGroups=[])
def show(self, req, id): def show(self, req, id, **kw):
""" Shows in-depth information on a specific Shared IP Group """ """ Shows in-depth information on a specific Shared IP Group """
return _translate_keys({}) return _translate_keys({})
def update(self, req, id): def update(self, req, id, **kw):
""" You can't update a Shared IP Group """ """ You can't update a Shared IP Group """
raise faults.Fault(exc.HTTPNotImplemented()) raise faults.Fault(exc.HTTPNotImplemented())
def delete(self, req, id): def delete(self, req, id, **kw):
""" Deletes a Shared IP Group """ """ Deletes a Shared IP Group """
raise faults.Fault(exc.HTTPNotImplemented()) raise faults.Fault(exc.HTTPNotImplemented())
def detail(self, req): def detail(self, req, **kw):
""" Returns a complete list of Shared IP Groups """ """ Returns a complete list of Shared IP Groups """
return _translate_detail_keys({}) return _translate_detail_keys({})
def create(self, req): def create(self, req, **kw):
""" Creates a new Shared IP group """ """ Creates a new Shared IP group """
raise faults.Fault(exc.HTTPNotImplemented()) raise faults.Fault(exc.HTTPNotImplemented())

View File

@@ -0,0 +1,93 @@
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import common
from nova import exception
from nova import flags
from nova import log as logging
from nova import wsgi
from nova.auth import manager
FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.api.openstack')
def _translate_keys(user):
return dict(id=user.id,
name=user.name,
access=user.access,
secret=user.secret,
admin=user.admin)
class Controller(wsgi.Controller):
_serialization_metadata = {
'application/xml': {
"attributes": {
"user": ["id", "name", "access", "secret", "admin"]}}}
def __init__(self):
self.manager = manager.AuthManager()
def _check_admin(self, context):
""" We cannot depend on the db layer to check for admin access
for the auth manager, so we do it here """
if not context.is_admin:
raise exception.NotAuthorized("Not admin user")
def index(self, req, **kw):
"""Return all users in brief"""
users = self.manager.get_users()
users = common.limited(users, req)
users = [_translate_keys(user) for user in users]
return dict(users=users)
def detail(self, req, **kw):
"""Return all users in detail"""
return self.index(req)
def show(self, req, id, **kw):
"""Return data about the given user id"""
user = self.manager.get_user(id)
return dict(user=_translate_keys(user))
def delete(self, req, id, **kw):
self._check_admin(req.environ['nova.context'])
self.manager.delete_user(id)
return {}
def create(self, req, **kw):
self._check_admin(req.environ['nova.context'])
env = self._deserialize(req.body, req)
is_admin = env['user'].get('admin') in ('T', 'True', True)
name = env['user'].get('name')
access = env['user'].get('access')
secret = env['user'].get('secret')
user = self.manager.create_user(name, access, secret, is_admin)
return dict(user=_translate_keys(user))
def update(self, req, id, **kw):
self._check_admin(req.environ['nova.context'])
env = self._deserialize(req.body, req)
is_admin = env['user'].get('admin')
if is_admin is not None:
is_admin = is_admin in ('T', 'True', True)
access = env['user'].get('access')
secret = env['user'].get('secret')
self.manager.modify_user(id, access, secret, is_admin)
return dict(user=_translate_keys(self.manager.get_user(id)))

View File

@@ -43,35 +43,35 @@ class Controller(wsgi.Controller):
"attributes": { "attributes": {
"zone": ["id", "api_url"]}}} "zone": ["id", "api_url"]}}}
def index(self, req): def index(self, req, **kw):
"""Return all zones in brief""" """Return all zones in brief"""
items = db.zone_get_all(req.environ['nova.context']) items = db.zone_get_all(req.environ['nova.context'])
items = common.limited(items, req) items = common.limited(items, req)
items = [_scrub_zone(item) for item in items] items = [_scrub_zone(item) for item in items]
return dict(zones=items) return dict(zones=items)
def detail(self, req): def detail(self, req, **kw):
"""Return all zones in detail""" """Return all zones in detail"""
return self.index(req) return self.index(req)
def show(self, req, id): def show(self, req, id, **kw):
"""Return data about the given zone id""" """Return data about the given zone id"""
zone_id = int(id) zone_id = int(id)
zone = db.zone_get(req.environ['nova.context'], zone_id) zone = db.zone_get(req.environ['nova.context'], zone_id)
return dict(zone=_scrub_zone(zone)) return dict(zone=_scrub_zone(zone))
def delete(self, req, id): def delete(self, req, id, **kw):
zone_id = int(id) zone_id = int(id)
db.zone_delete(req.environ['nova.context'], zone_id) db.zone_delete(req.environ['nova.context'], zone_id)
return {} return {}
def create(self, req): def create(self, req, **kw):
context = req.environ['nova.context'] context = req.environ['nova.context']
env = self._deserialize(req.body, req) env = self._deserialize(req.body, req)
zone = db.zone_create(context, env["zone"]) zone = db.zone_create(context, env["zone"])
return dict(zone=_scrub_zone(zone)) return dict(zone=_scrub_zone(zone))
def update(self, req, id): def update(self, req, id, **kw):
context = req.environ['nova.context'] context = req.environ['nova.context']
env = self._deserialize(req.body, req) env = self._deserialize(req.body, req)
zone_id = int(id) zone_id = int(id)

View File

@@ -11,5 +11,5 @@ export EUCALYPTUS_CERT=${NOVA_CERT} # euca-bundle-image seems to require this se
alias ec2-bundle-image="ec2-bundle-image --cert ${EC2_CERT} --privatekey ${EC2_PRIVATE_KEY} --user 42 --ec2cert ${NOVA_CERT}" alias ec2-bundle-image="ec2-bundle-image --cert ${EC2_CERT} --privatekey ${EC2_PRIVATE_KEY} --user 42 --ec2cert ${NOVA_CERT}"
alias ec2-upload-bundle="ec2-upload-bundle -a ${EC2_ACCESS_KEY} -s ${EC2_SECRET_KEY} --url ${S3_URL} --ec2cert ${NOVA_CERT}" alias ec2-upload-bundle="ec2-upload-bundle -a ${EC2_ACCESS_KEY} -s ${EC2_SECRET_KEY} --url ${S3_URL} --ec2cert ${NOVA_CERT}"
export NOVA_API_KEY="%(access)s" export NOVA_API_KEY="%(access)s"
export NOVA_USERNAME="%(user)s" export NOVA_USERNAME="%(project)s:%(user)s"
export NOVA_URL="%(os)s" export NOVA_URL="%(os)s"

View File

@@ -1861,8 +1861,11 @@ def project_get_by_user(context, user_id):
session = get_session() session = get_session()
user = session.query(models.User).\ user = session.query(models.User).\
filter_by(deleted=can_read_deleted(context)).\ filter_by(deleted=can_read_deleted(context)).\
filter_by(id=user_id).\
options(joinedload_all('projects')).\ options(joinedload_all('projects')).\
first() first()
if not user:
raise exception.NotFound(_('Invalid user_id %s') % user_id)
return user.projects return user.projects

View File

@@ -26,7 +26,6 @@ from paste import urlmap
from glance import client as glance_client from glance import client as glance_client
from nova import auth
from nova import context from nova import context
from nova import exception as exc from nova import exception as exc
from nova import flags from nova import flags
@@ -35,6 +34,7 @@ import nova.api.openstack.auth
from nova.api import openstack from nova.api import openstack
from nova.api.openstack import auth from nova.api.openstack import auth
from nova.api.openstack import ratelimiting from nova.api.openstack import ratelimiting
from nova.auth.manager import User, Project
from nova.image import glance from nova.image import glance
from nova.image import local from nova.image import local
from nova.image import service from nova.image import service
@@ -227,19 +227,97 @@ class FakeAuthDatabase(object):
class FakeAuthManager(object): class FakeAuthManager(object):
auth_data = {} auth_data = {}
projects = {}
@classmethod
def clear_fakes(cls):
cls.auth_data = {}
cls.projects = {}
@classmethod
def reset_fake_data(cls):
cls.auth_data = dict(acc1=User('guy1', 'guy1', 'acc1',
'fortytwo!', False))
cls.projects = dict(testacct=Project('testacct',
'testacct',
'guy1',
'test',
[]))
def add_user(self, key, user): def add_user(self, key, user):
FakeAuthManager.auth_data[key] = user FakeAuthManager.auth_data[key] = user
def get_users(self):
return FakeAuthManager.auth_data.values()
def get_user(self, uid): def get_user(self, uid):
for k, v in FakeAuthManager.auth_data.iteritems(): for k, v in FakeAuthManager.auth_data.iteritems():
if v.id == uid: if v.id == uid:
return v return v
return None return None
def get_project(self, pid): def delete_user(self, uid):
for k, v in FakeAuthManager.auth_data.items():
if v.id == uid:
del FakeAuthManager.auth_data[k]
return None return None
def create_user(self, name, access=None, secret=None, admin=False):
u = User(name, name, access, secret, admin)
FakeAuthManager.auth_data[access] = u
return u
def modify_user(self, user_id, access=None, secret=None, admin=None):
user = None
for k, v in FakeAuthManager.auth_data.iteritems():
if v.id == user_id:
user = v
if user:
user.access = access
user.secret = secret
if admin is not None:
user.admin = admin
def is_admin(self, user):
return user.admin
def is_project_member(self, user, project):
return ((user.id in project.member_ids) or
(user.id == project.project_manager_id))
def create_project(self, name, manager_user, description=None,
member_users=None):
member_ids = [User.safe_id(m) for m in member_users] \
if member_users else []
p = Project(name, name, User.safe_id(manager_user),
description, member_ids)
FakeAuthManager.projects[name] = p
return p
def delete_project(self, pid):
if pid in FakeAuthManager.projects:
del FakeAuthManager.projects[pid]
def modify_project(self, project, manager_user=None, description=None):
p = FakeAuthManager.projects.get(project)
p.project_manager_id = User.safe_id(manager_user)
p.description = description
def get_project(self, pid):
p = FakeAuthManager.projects.get(pid)
if p:
return p
else:
raise exc.NotFound
def get_projects(self, user=None):
if not user:
return FakeAuthManager.projects.values()
else:
return [p for p in FakeAuthManager.projects.values()
if (user.id in p.member_ids) or
(user.id == p.project_manager_id)]
def get_user_from_access_key(self, key): def get_user_from_access_key(self, key):
return FakeAuthManager.auth_data.get(key, None) return FakeAuthManager.auth_data.get(key, None)

View File

@@ -0,0 +1,123 @@
# 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 stubout
import webob
import json
import nova.api
import nova.api.openstack.auth
from nova import context
from nova import flags
from nova import test
from nova.auth.manager import User
from nova.tests.api.openstack import fakes
FLAGS = flags.FLAGS
FLAGS.verbose = True
def fake_init(self):
self.manager = fakes.FakeAuthManager()
def fake_admin_check(self, req):
return True
class AccountsTest(test.TestCase):
def setUp(self):
super(AccountsTest, self).setUp()
self.stubs = stubout.StubOutForTesting()
self.stubs.Set(nova.api.openstack.accounts.Controller, '__init__',
fake_init)
self.stubs.Set(nova.api.openstack.accounts.Controller, '_check_admin',
fake_admin_check)
fakes.FakeAuthManager.auth_data = {}
fakes.FakeAuthManager.projects = {}
fakes.FakeAuthDatabase.data = {}
fakes.stub_out_networking(self.stubs)
fakes.stub_out_rate_limiting(self.stubs)
fakes.stub_out_auth(self.stubs)
self.allow_admin = FLAGS.allow_admin_api
FLAGS.allow_admin_api = True
fakemgr = fakes.FakeAuthManager()
joeuser = User('guy1', 'guy1', 'acc1', 'fortytwo!', False)
superuser = User('guy2', 'guy2', 'acc2', 'swordfish', True)
fakemgr.add_user(joeuser.access, joeuser)
fakemgr.add_user(superuser.access, superuser)
fakemgr.create_project('test1', joeuser)
fakemgr.create_project('test2', superuser)
def tearDown(self):
self.stubs.UnsetAll()
FLAGS.allow_admin_api = self.allow_admin
super(AccountsTest, self).tearDown()
def test_get_account(self):
req = webob.Request.blank('/v1.0/test1')
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res_dict['account']['id'], 'test1')
self.assertEqual(res_dict['account']['name'], 'test1')
self.assertEqual(res_dict['account']['manager'], 'guy1')
self.assertEqual(res.status_int, 200)
def test_account_delete(self):
req = webob.Request.blank('/v1.0/test1')
req.method = 'DELETE'
res = req.get_response(fakes.wsgi_app())
self.assertTrue('test1' not in fakes.FakeAuthManager.projects)
self.assertEqual(res.status_int, 200)
def test_account_create(self):
body = dict(account=dict(description='test account',
manager='guy1'))
req = webob.Request.blank('/v1.0/newacct')
req.method = 'PUT'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res.status_int, 200)
self.assertEqual(res_dict['account']['id'], 'newacct')
self.assertEqual(res_dict['account']['name'], 'newacct')
self.assertEqual(res_dict['account']['description'], 'test account')
self.assertEqual(res_dict['account']['manager'], 'guy1')
self.assertTrue('newacct' in
fakes.FakeAuthManager.projects)
self.assertEqual(len(fakes.FakeAuthManager.projects.values()), 3)
def test_account_update(self):
body = dict(account=dict(description='test account',
manager='guy2'))
req = webob.Request.blank('/v1.0/test1')
req.method = 'PUT'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res.status_int, 200)
self.assertEqual(res_dict['account']['id'], 'test1')
self.assertEqual(res_dict['account']['name'], 'test1')
self.assertEqual(res_dict['account']['description'], 'test account')
self.assertEqual(res_dict['account']['manager'], 'guy2')
self.assertEqual(len(fakes.FakeAuthManager.projects.values()), 2)

View File

@@ -35,7 +35,7 @@ class AdminAPITest(test.TestCase):
def setUp(self): def setUp(self):
super(AdminAPITest, self).setUp() super(AdminAPITest, self).setUp()
self.stubs = stubout.StubOutForTesting() self.stubs = stubout.StubOutForTesting()
fakes.FakeAuthManager.auth_data = {} fakes.FakeAuthManager.reset_fake_data()
fakes.FakeAuthDatabase.data = {} fakes.FakeAuthDatabase.data = {}
fakes.stub_out_networking(self.stubs) fakes.stub_out_networking(self.stubs)
fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_rate_limiting(self.stubs)
@@ -50,7 +50,7 @@ class AdminAPITest(test.TestCase):
def test_admin_enabled(self): def test_admin_enabled(self):
FLAGS.allow_admin_api = True FLAGS.allow_admin_api = True
# We should still be able to access public operations. # We should still be able to access public operations.
req = webob.Request.blank('/v1.0/flavors') req = webob.Request.blank('/v1.0/testacct/flavors')
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 200) self.assertEqual(res.status_int, 200)
# TODO: Confirm admin operations are available. # TODO: Confirm admin operations are available.
@@ -58,7 +58,7 @@ class AdminAPITest(test.TestCase):
def test_admin_disabled(self): def test_admin_disabled(self):
FLAGS.allow_admin_api = False FLAGS.allow_admin_api = False
# We should still be able to access public operations. # We should still be able to access public operations.
req = webob.Request.blank('/v1.0/flavors') req = webob.Request.blank('/v1.0/testacct/flavors')
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
# TODO: Confirm admin operations are unavailable. # TODO: Confirm admin operations are unavailable.
self.assertEqual(res.status_int, 200) self.assertEqual(res.status_int, 200)

View File

@@ -51,7 +51,9 @@ class Test(test.TestCase):
def test_authorize_user(self): def test_authorize_user(self):
f = fakes.FakeAuthManager() f = fakes.FakeAuthManager()
f.add_user('derp', nova.auth.manager.User(1, 'herp', None, None, None)) u = nova.auth.manager.User(1, 'herp', None, None, None)
f.add_user('derp', u)
f.create_project('test', u)
req = webob.Request.blank('/v1.0/') req = webob.Request.blank('/v1.0/')
req.headers['X-Auth-User'] = 'herp' req.headers['X-Auth-User'] = 'herp'
@@ -65,7 +67,9 @@ class Test(test.TestCase):
def test_authorize_token(self): def test_authorize_token(self):
f = fakes.FakeAuthManager() f = fakes.FakeAuthManager()
f.add_user('derp', nova.auth.manager.User(1, 'herp', None, None, None)) u = nova.auth.manager.User(1, 'herp', None, None, None)
f.add_user('derp', u)
f.create_project('test', u)
req = webob.Request.blank('/v1.0/', {'HTTP_HOST': 'foo'}) req = webob.Request.blank('/v1.0/', {'HTTP_HOST': 'foo'})
req.headers['X-Auth-User'] = 'herp' req.headers['X-Auth-User'] = 'herp'
@@ -74,7 +78,7 @@ class Test(test.TestCase):
self.assertEqual(result.status, '204 No Content') self.assertEqual(result.status, '204 No Content')
self.assertEqual(len(result.headers['X-Auth-Token']), 40) self.assertEqual(len(result.headers['X-Auth-Token']), 40)
self.assertEqual(result.headers['X-Server-Management-Url'], self.assertEqual(result.headers['X-Server-Management-Url'],
"http://foo/v1.0/") "http://foo/v1.0/test/")
self.assertEqual(result.headers['X-CDN-Management-Url'], self.assertEqual(result.headers['X-CDN-Management-Url'],
"") "")
self.assertEqual(result.headers['X-Storage-Url'], "") self.assertEqual(result.headers['X-Storage-Url'], "")
@@ -82,7 +86,7 @@ class Test(test.TestCase):
token = result.headers['X-Auth-Token'] token = result.headers['X-Auth-Token']
self.stubs.Set(nova.api.openstack, 'APIRouter', self.stubs.Set(nova.api.openstack, 'APIRouter',
fakes.FakeRouter) fakes.FakeRouter)
req = webob.Request.blank('/v1.0/fake') req = webob.Request.blank('/v1.0/test/fake')
req.headers['X-Auth-Token'] = token req.headers['X-Auth-Token'] = token
result = req.get_response(fakes.wsgi_app()) result = req.get_response(fakes.wsgi_app())
self.assertEqual(result.status, '200 OK') self.assertEqual(result.status, '200 OK')
@@ -176,6 +180,9 @@ class TestLimiter(test.TestCase):
def test_authorize_token(self): def test_authorize_token(self):
f = fakes.FakeAuthManager() f = fakes.FakeAuthManager()
u = nova.auth.manager.User(1, 'herp', None, None, None)
f.add_user('derp', u)
f.create_project('test', u)
f.add_user('derp', nova.auth.manager.User(1, 'herp', None, None, None)) f.add_user('derp', nova.auth.manager.User(1, 'herp', None, None, None))
req = webob.Request.blank('/v1.0/') req = webob.Request.blank('/v1.0/')
@@ -187,7 +194,7 @@ class TestLimiter(test.TestCase):
token = result.headers['X-Auth-Token'] token = result.headers['X-Auth-Token']
self.stubs.Set(nova.api.openstack, 'APIRouter', self.stubs.Set(nova.api.openstack, 'APIRouter',
fakes.FakeRouter) fakes.FakeRouter)
req = webob.Request.blank('/v1.0/fake') req = webob.Request.blank('/v1.0/test/fake')
req.method = 'POST' req.method = 'POST'
req.headers['X-Auth-Token'] = token req.headers['X-Auth-Token'] = token
result = req.get_response(fakes.wsgi_app()) result = req.get_response(fakes.wsgi_app())

View File

@@ -28,7 +28,7 @@ class FlavorsTest(test.TestCase):
def setUp(self): def setUp(self):
super(FlavorsTest, self).setUp() super(FlavorsTest, self).setUp()
self.stubs = stubout.StubOutForTesting() self.stubs = stubout.StubOutForTesting()
fakes.FakeAuthManager.auth_data = {} fakes.FakeAuthManager.reset_fake_data()
fakes.FakeAuthDatabase.data = {} fakes.FakeAuthDatabase.data = {}
fakes.stub_out_networking(self.stubs) fakes.stub_out_networking(self.stubs)
fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_rate_limiting(self.stubs)
@@ -39,7 +39,7 @@ class FlavorsTest(test.TestCase):
super(FlavorsTest, self).tearDown() super(FlavorsTest, self).tearDown()
def test_get_flavor_list(self): def test_get_flavor_list(self):
req = webob.Request.blank('/v1.0/flavors') req = webob.Request.blank('/v1.0/testacct/flavors')
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
def test_get_flavor_by_id(self): def test_get_flavor_by_id(self):

View File

@@ -202,7 +202,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
self.orig_image_service = FLAGS.image_service self.orig_image_service = FLAGS.image_service
FLAGS.image_service = 'nova.image.glance.GlanceImageService' FLAGS.image_service = 'nova.image.glance.GlanceImageService'
self.stubs = stubout.StubOutForTesting() self.stubs = stubout.StubOutForTesting()
fakes.FakeAuthManager.auth_data = {} fakes.FakeAuthManager.reset_fake_data()
fakes.FakeAuthDatabase.data = {} fakes.FakeAuthDatabase.data = {}
fakes.stub_out_networking(self.stubs) fakes.stub_out_networking(self.stubs)
fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_rate_limiting(self.stubs)
@@ -216,7 +216,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
super(ImageControllerWithGlanceServiceTest, self).tearDown() super(ImageControllerWithGlanceServiceTest, self).tearDown()
def test_get_image_index(self): def test_get_image_index(self):
req = webob.Request.blank('/v1.0/images') req = webob.Request.blank('/v1.0/testacct/images')
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body) res_dict = json.loads(res.body)
@@ -228,7 +228,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
"image %s not in fixture index!" % str(image)) "image %s not in fixture index!" % str(image))
def test_get_image_details(self): def test_get_image_details(self):
req = webob.Request.blank('/v1.0/images/detail') req = webob.Request.blank('/v1.0/testacct/images/detail')
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body) res_dict = json.loads(res.body)

View File

@@ -118,7 +118,7 @@ class ServersTest(test.TestCase):
def setUp(self): def setUp(self):
super(ServersTest, self).setUp() super(ServersTest, self).setUp()
self.stubs = stubout.StubOutForTesting() self.stubs = stubout.StubOutForTesting()
fakes.FakeAuthManager.auth_data = {} fakes.FakeAuthManager.reset_fake_data()
fakes.FakeAuthDatabase.data = {} fakes.FakeAuthDatabase.data = {}
fakes.stub_out_networking(self.stubs) fakes.stub_out_networking(self.stubs)
fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_rate_limiting(self.stubs)
@@ -150,7 +150,7 @@ class ServersTest(test.TestCase):
super(ServersTest, self).tearDown() super(ServersTest, self).tearDown()
def test_get_server_by_id(self): def test_get_server_by_id(self):
req = webob.Request.blank('/v1.0/servers/1') req = webob.Request.blank('/v1.0/testacct/servers/1')
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body) res_dict = json.loads(res.body)
self.assertEqual(res_dict['server']['id'], '1') self.assertEqual(res_dict['server']['id'], '1')
@@ -161,7 +161,7 @@ class ServersTest(test.TestCase):
public = ["1.2.3.4"] public = ["1.2.3.4"]
new_return_server = return_server_with_addresses(private, public) new_return_server = return_server_with_addresses(private, public)
self.stubs.Set(nova.db.api, 'instance_get', new_return_server) self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
req = webob.Request.blank('/v1.0/servers/1') req = webob.Request.blank('/v1.0/testacct/servers/1')
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body) res_dict = json.loads(res.body)
self.assertEqual(res_dict['server']['id'], '1') self.assertEqual(res_dict['server']['id'], '1')
@@ -173,7 +173,7 @@ class ServersTest(test.TestCase):
self.assertEqual(addresses["private"][0], private) self.assertEqual(addresses["private"][0], private)
def test_get_server_list(self): def test_get_server_list(self):
req = webob.Request.blank('/v1.0/servers') req = webob.Request.blank('/v1.0/testacct/servers')
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body) res_dict = json.loads(res.body)
@@ -224,7 +224,7 @@ class ServersTest(test.TestCase):
name='server_test', imageId=2, flavorId=2, name='server_test', imageId=2, flavorId=2,
metadata={'hello': 'world', 'open': 'stack'}, metadata={'hello': 'world', 'open': 'stack'},
personality={})) personality={}))
req = webob.Request.blank('/v1.0/servers') req = webob.Request.blank('/v1.0/testacct/servers')
req.method = 'POST' req.method = 'POST'
req.body = json.dumps(body) req.body = json.dumps(body)
@@ -233,7 +233,7 @@ class ServersTest(test.TestCase):
self.assertEqual(res.status_int, 200) self.assertEqual(res.status_int, 200)
def test_update_no_body(self): def test_update_no_body(self):
req = webob.Request.blank('/v1.0/servers/1') req = webob.Request.blank('/v1.0/testacct/servers/1')
req.method = 'PUT' req.method = 'PUT'
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 422) self.assertEqual(res.status_int, 422)
@@ -251,7 +251,7 @@ class ServersTest(test.TestCase):
self.stubs.Set(nova.db.api, 'instance_update', self.stubs.Set(nova.db.api, 'instance_update',
server_update) server_update)
req = webob.Request.blank('/v1.0/servers/1') req = webob.Request.blank('/v1.0/testacct/servers/1')
req.method = 'PUT' req.method = 'PUT'
req.body = self.body req.body = self.body
req.get_response(fakes.wsgi_app()) req.get_response(fakes.wsgi_app())
@@ -267,30 +267,30 @@ class ServersTest(test.TestCase):
self.stubs.Set(nova.db.api, 'instance_update', self.stubs.Set(nova.db.api, 'instance_update',
server_update) server_update)
req = webob.Request.blank('/v1.0/servers/1') req = webob.Request.blank('/v1.0/testacct/servers/1')
req.method = 'PUT' req.method = 'PUT'
req.body = self.body req.body = self.body
req.get_response(fakes.wsgi_app()) req.get_response(fakes.wsgi_app())
def test_create_backup_schedules(self): def test_create_backup_schedules(self):
req = webob.Request.blank('/v1.0/servers/1/backup_schedules') req = webob.Request.blank('/v1.0/testacct/servers/1/backup_schedules')
req.method = 'POST' req.method = 'POST'
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status, '404 Not Found') self.assertEqual(res.status, '404 Not Found')
def test_delete_backup_schedules(self): def test_delete_backup_schedules(self):
req = webob.Request.blank('/v1.0/servers/1/backup_schedules') req = webob.Request.blank('/v1.0/testacct/servers/1/backup_schedules')
req.method = 'DELETE' req.method = 'DELETE'
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status, '404 Not Found') self.assertEqual(res.status, '404 Not Found')
def test_get_server_backup_schedules(self): def test_get_server_backup_schedules(self):
req = webob.Request.blank('/v1.0/servers/1/backup_schedules') req = webob.Request.blank('/v1.0/testacct/servers/1/backup_schedules')
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status, '404 Not Found') self.assertEqual(res.status, '404 Not Found')
def test_get_all_server_details(self): def test_get_all_server_details(self):
req = webob.Request.blank('/v1.0/servers/detail') req = webob.Request.blank('/v1.0/testacct/servers/detail')
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body) res_dict = json.loads(res.body)
@@ -321,7 +321,7 @@ class ServersTest(test.TestCase):
self.stubs.Set(nova.db.api, 'instance_get_all_by_user', self.stubs.Set(nova.db.api, 'instance_get_all_by_user',
return_servers_with_host) return_servers_with_host)
req = webob.Request.blank('/v1.0/servers/detail') req = webob.Request.blank('/v1.0/testacct/servers/detail')
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body) res_dict = json.loads(res.body)
@@ -341,7 +341,7 @@ class ServersTest(test.TestCase):
body = dict(server=dict( body = dict(server=dict(
name='server_test', imageId=2, flavorId=2, metadata={}, name='server_test', imageId=2, flavorId=2, metadata={},
personality={})) personality={}))
req = webob.Request.blank('/v1.0/servers/1/pause') req = webob.Request.blank('/v1.0/testacct/servers/1/pause')
req.method = 'POST' req.method = 'POST'
req.content_type = 'application/json' req.content_type = 'application/json'
req.body = json.dumps(body) req.body = json.dumps(body)
@@ -353,7 +353,7 @@ class ServersTest(test.TestCase):
body = dict(server=dict( body = dict(server=dict(
name='server_test', imageId=2, flavorId=2, metadata={}, name='server_test', imageId=2, flavorId=2, metadata={},
personality={})) personality={}))
req = webob.Request.blank('/v1.0/servers/1/unpause') req = webob.Request.blank('/v1.0/testacct/servers/1/unpause')
req.method = 'POST' req.method = 'POST'
req.content_type = 'application/json' req.content_type = 'application/json'
req.body = json.dumps(body) req.body = json.dumps(body)
@@ -365,7 +365,7 @@ class ServersTest(test.TestCase):
body = dict(server=dict( body = dict(server=dict(
name='server_test', imageId=2, flavorId=2, metadata={}, name='server_test', imageId=2, flavorId=2, metadata={},
personality={})) personality={}))
req = webob.Request.blank('/v1.0/servers/1/suspend') req = webob.Request.blank('/v1.0/testacct/servers/1/suspend')
req.method = 'POST' req.method = 'POST'
req.content_type = 'application/json' req.content_type = 'application/json'
req.body = json.dumps(body) req.body = json.dumps(body)
@@ -377,7 +377,7 @@ class ServersTest(test.TestCase):
body = dict(server=dict( body = dict(server=dict(
name='server_test', imageId=2, flavorId=2, metadata={}, name='server_test', imageId=2, flavorId=2, metadata={},
personality={})) personality={}))
req = webob.Request.blank('/v1.0/servers/1/resume') req = webob.Request.blank('/v1.0/testacct/servers/1/resume')
req.method = 'POST' req.method = 'POST'
req.content_type = 'application/json' req.content_type = 'application/json'
req.body = json.dumps(body) req.body = json.dumps(body)
@@ -389,7 +389,7 @@ class ServersTest(test.TestCase):
body = dict(server=dict( body = dict(server=dict(
name='server_test', imageId=2, flavorId=2, metadata={}, name='server_test', imageId=2, flavorId=2, metadata={},
personality={})) personality={}))
req = webob.Request.blank('/v1.0/servers/1/reset_network') req = webob.Request.blank('/v1.0/testacct/servers/1/reset_network')
req.method = 'POST' req.method = 'POST'
req.content_type = 'application/json' req.content_type = 'application/json'
req.body = json.dumps(body) req.body = json.dumps(body)
@@ -401,7 +401,8 @@ class ServersTest(test.TestCase):
body = dict(server=dict( body = dict(server=dict(
name='server_test', imageId=2, flavorId=2, metadata={}, name='server_test', imageId=2, flavorId=2, metadata={},
personality={})) personality={}))
req = webob.Request.blank('/v1.0/servers/1/inject_network_info') req = webob.Request.blank(
'/v1.0/testacct/servers/1/inject_network_info')
req.method = 'POST' req.method = 'POST'
req.content_type = 'application/json' req.content_type = 'application/json'
req.body = json.dumps(body) req.body = json.dumps(body)
@@ -409,13 +410,13 @@ class ServersTest(test.TestCase):
self.assertEqual(res.status_int, 202) self.assertEqual(res.status_int, 202)
def test_server_diagnostics(self): def test_server_diagnostics(self):
req = webob.Request.blank("/v1.0/servers/1/diagnostics") req = webob.Request.blank("/v1.0/testacct/servers/1/diagnostics")
req.method = "GET" req.method = "GET"
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 404) self.assertEqual(res.status_int, 404)
def test_server_actions(self): def test_server_actions(self):
req = webob.Request.blank("/v1.0/servers/1/actions") req = webob.Request.blank("/v1.0/testacct/servers/1/actions")
req.method = "GET" req.method = "GET"
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 404) self.assertEqual(res.status_int, 404)
@@ -424,7 +425,7 @@ class ServersTest(test.TestCase):
body = dict(server=dict( body = dict(server=dict(
name='server_test', imageId=2, flavorId=2, metadata={}, name='server_test', imageId=2, flavorId=2, metadata={},
personality={})) personality={}))
req = webob.Request.blank('/v1.0/servers/1/action') req = webob.Request.blank('/v1.0/testacct/servers/1/action')
req.method = 'POST' req.method = 'POST'
req.content_type = 'application/json' req.content_type = 'application/json'
req.body = json.dumps(body) req.body = json.dumps(body)
@@ -434,7 +435,7 @@ class ServersTest(test.TestCase):
body = dict(server=dict( body = dict(server=dict(
name='server_test', imageId=2, flavorId=2, metadata={}, name='server_test', imageId=2, flavorId=2, metadata={},
personality={})) personality={}))
req = webob.Request.blank('/v1.0/servers/1/action') req = webob.Request.blank('/v1.0/testacct/servers/1/action')
req.method = 'POST' req.method = 'POST'
req.content_type = 'application/json' req.content_type = 'application/json'
req.body = json.dumps(body) req.body = json.dumps(body)
@@ -444,14 +445,14 @@ class ServersTest(test.TestCase):
body = dict(server=dict( body = dict(server=dict(
name='server_test', imageId=2, flavorId=2, metadata={}, name='server_test', imageId=2, flavorId=2, metadata={},
personality={})) personality={}))
req = webob.Request.blank('/v1.0/servers/1/action') req = webob.Request.blank('/v1.0/testacct/servers/1/action')
req.method = 'POST' req.method = 'POST'
req.content_type = 'application/json' req.content_type = 'application/json'
req.body = json.dumps(body) req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
def test_delete_server_instance(self): def test_delete_server_instance(self):
req = webob.Request.blank('/v1.0/servers/1') req = webob.Request.blank('/v1.0/testacct/servers/1')
req.method = 'DELETE' req.method = 'DELETE'
self.server_delete_called = False self.server_delete_called = False

View File

@@ -0,0 +1,139 @@
# 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 stubout
import webob
import json
import nova.api
import nova.api.openstack.auth
from nova import context
from nova import flags
from nova import test
from nova.auth.manager import User, Project
from nova.tests.api.openstack import fakes
FLAGS = flags.FLAGS
FLAGS.verbose = True
def fake_init(self):
self.manager = fakes.FakeAuthManager()
def fake_admin_check(self, req):
return True
class UsersTest(test.TestCase):
def setUp(self):
super(UsersTest, self).setUp()
self.stubs = stubout.StubOutForTesting()
self.stubs.Set(nova.api.openstack.users.Controller, '__init__',
fake_init)
self.stubs.Set(nova.api.openstack.users.Controller, '_check_admin',
fake_admin_check)
fakes.FakeAuthManager.auth_data = {}
fakes.FakeAuthManager.projects = dict(testacct=Project('testacct',
'testacct',
'guy1',
'test',
[]))
fakes.FakeAuthDatabase.data = {}
fakes.stub_out_networking(self.stubs)
fakes.stub_out_rate_limiting(self.stubs)
fakes.stub_out_auth(self.stubs)
self.allow_admin = FLAGS.allow_admin_api
FLAGS.allow_admin_api = True
fakemgr = fakes.FakeAuthManager()
fakemgr.add_user('acc1', User('guy1', 'guy1', 'acc1',
'fortytwo!', False))
fakemgr.add_user('acc2', User('guy2', 'guy2', 'acc2',
'swordfish', True))
def tearDown(self):
self.stubs.UnsetAll()
FLAGS.allow_admin_api = self.allow_admin
super(UsersTest, self).tearDown()
def test_get_user_list(self):
req = webob.Request.blank('/v1.0/testacct/users')
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res.status_int, 200)
self.assertEqual(len(res_dict['users']), 2)
def test_get_user_by_id(self):
req = webob.Request.blank('/v1.0/testacct/users/guy2')
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res_dict['user']['id'], 'guy2')
self.assertEqual(res_dict['user']['name'], 'guy2')
self.assertEqual(res_dict['user']['secret'], 'swordfish')
self.assertEqual(res_dict['user']['admin'], True)
self.assertEqual(res.status_int, 200)
def test_user_delete(self):
req = webob.Request.blank('/v1.0/testacct/users/guy1')
req.method = 'DELETE'
res = req.get_response(fakes.wsgi_app())
self.assertTrue('guy1' not in [u.id for u in
fakes.FakeAuthManager.auth_data.values()])
self.assertEqual(res.status_int, 200)
def test_user_create(self):
body = dict(user=dict(name='test_guy',
access='acc3',
secret='invasionIsInNormandy',
admin=True))
req = webob.Request.blank('/v1.0/testacct/users')
req.method = 'POST'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res.status_int, 200)
self.assertEqual(res_dict['user']['id'], 'test_guy')
self.assertEqual(res_dict['user']['name'], 'test_guy')
self.assertEqual(res_dict['user']['access'], 'acc3')
self.assertEqual(res_dict['user']['secret'], 'invasionIsInNormandy')
self.assertEqual(res_dict['user']['admin'], True)
self.assertTrue('test_guy' in [u.id for u in
fakes.FakeAuthManager.auth_data.values()])
self.assertEqual(len(fakes.FakeAuthManager.auth_data.values()), 3)
def test_user_update(self):
body = dict(user=dict(name='guy2',
access='acc2',
secret='invasionIsInNormandy'))
req = webob.Request.blank('/v1.0/testacct/users/guy2')
req.method = 'PUT'
req.body = json.dumps(body)
res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body)
self.assertEqual(res.status_int, 200)
self.assertEqual(res_dict['user']['id'], 'guy2')
self.assertEqual(res_dict['user']['name'], 'guy2')
self.assertEqual(res_dict['user']['access'], 'acc2')
self.assertEqual(res_dict['user']['secret'], 'invasionIsInNormandy')
self.assertEqual(res_dict['user']['admin'], True)

View File

@@ -64,7 +64,7 @@ class ZonesTest(test.TestCase):
def setUp(self): def setUp(self):
super(ZonesTest, self).setUp() super(ZonesTest, self).setUp()
self.stubs = stubout.StubOutForTesting() self.stubs = stubout.StubOutForTesting()
fakes.FakeAuthManager.auth_data = {} fakes.FakeAuthManager.reset_fake_data()
fakes.FakeAuthDatabase.data = {} fakes.FakeAuthDatabase.data = {}
fakes.stub_out_networking(self.stubs) fakes.stub_out_networking(self.stubs)
fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_rate_limiting(self.stubs)
@@ -85,7 +85,7 @@ class ZonesTest(test.TestCase):
super(ZonesTest, self).tearDown() super(ZonesTest, self).tearDown()
def test_get_zone_list(self): def test_get_zone_list(self):
req = webob.Request.blank('/v1.0/zones') req = webob.Request.blank('/v1.0/testacct/zones')
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body) res_dict = json.loads(res.body)
@@ -93,7 +93,7 @@ class ZonesTest(test.TestCase):
self.assertEqual(len(res_dict['zones']), 2) self.assertEqual(len(res_dict['zones']), 2)
def test_get_zone_by_id(self): def test_get_zone_by_id(self):
req = webob.Request.blank('/v1.0/zones/1') req = webob.Request.blank('/v1.0/testacct/zones/1')
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
res_dict = json.loads(res.body) res_dict = json.loads(res.body)
@@ -103,7 +103,7 @@ class ZonesTest(test.TestCase):
self.assertEqual(res.status_int, 200) self.assertEqual(res.status_int, 200)
def test_zone_delete(self): def test_zone_delete(self):
req = webob.Request.blank('/v1.0/zones/1') req = webob.Request.blank('/v1.0/testacct/zones/1')
res = req.get_response(fakes.wsgi_app()) res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 200) self.assertEqual(res.status_int, 200)
@@ -111,7 +111,7 @@ class ZonesTest(test.TestCase):
def test_zone_create(self): def test_zone_create(self):
body = dict(zone=dict(api_url='http://blah.zoo', username='fred', body = dict(zone=dict(api_url='http://blah.zoo', username='fred',
password='fubar')) password='fubar'))
req = webob.Request.blank('/v1.0/zones') req = webob.Request.blank('/v1.0/testacct/zones')
req.method = 'POST' req.method = 'POST'
req.body = json.dumps(body) req.body = json.dumps(body)
@@ -125,7 +125,7 @@ class ZonesTest(test.TestCase):
def test_zone_update(self): def test_zone_update(self):
body = dict(zone=dict(username='zeb', password='sneaky')) body = dict(zone=dict(username='zeb', password='sneaky'))
req = webob.Request.blank('/v1.0/zones/1') req = webob.Request.blank('/v1.0/testacct/zones/1')
req.method = 'PUT' req.method = 'PUT'
req.body = json.dumps(body) req.body = json.dumps(body)

View File

@@ -207,7 +207,7 @@ def _upload_tarball(staging_path, image_id, glance_host, glance_port):
'transfer-encoding': 'chunked', 'transfer-encoding': 'chunked',
'x-image-meta-is_public': 'True', 'x-image-meta-is_public': 'True',
'x-image-meta-status': 'queued', 'x-image-meta-status': 'queued',
'x-image-meta-type': 'vhd' 'x-image-meta-type': 'vhd',
} }
for header, value in headers.iteritems(): for header, value in headers.iteritems():
conn.putheader(header, value) conn.putheader(header, value)