Add in multi-tenant support in openstack api.
This commit is contained in:
@@ -433,6 +433,8 @@ class ProjectCommands(object):
|
||||
"been created.\nPlease create a database by running a "
|
||||
"nova-api server on this host.")
|
||||
|
||||
AccountCommands = ProjectCommands
|
||||
|
||||
|
||||
class FixedIpCommands(object):
|
||||
"""Class for managing fixed ip."""
|
||||
@@ -663,6 +665,7 @@ class VolumeCommands(object):
|
||||
|
||||
CATEGORIES = [
|
||||
('user', UserCommands),
|
||||
('account', AccountCommands),
|
||||
('project', ProjectCommands),
|
||||
('role', RoleCommands),
|
||||
('shell', ShellCommands),
|
||||
|
||||
@@ -27,6 +27,7 @@ import webob.exc
|
||||
from nova import flags
|
||||
from nova import log as logging
|
||||
from nova import wsgi
|
||||
from nova.api.openstack import accounts
|
||||
from nova.api.openstack import faults
|
||||
from nova.api.openstack import backup_schedules
|
||||
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 servers
|
||||
from nova.api.openstack import shared_ip_groups
|
||||
from nova.api.openstack import users
|
||||
from nova.api.openstack import zones
|
||||
|
||||
|
||||
@@ -71,6 +73,18 @@ class APIRouter(wsgi.Router):
|
||||
def __init__(self):
|
||||
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'}
|
||||
if FLAGS.allow_admin_api:
|
||||
LOG.debug(_("Including admin operations in API."))
|
||||
@@ -84,27 +98,38 @@ class APIRouter(wsgi.Router):
|
||||
server_members['inject_network_info'] = 'POST'
|
||||
|
||||
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'})
|
||||
|
||||
mapper.resource("server", "servers", controller=servers.Controller(),
|
||||
collection={'detail': 'GET'},
|
||||
path_prefix="{account_id}/",
|
||||
member=server_members)
|
||||
|
||||
mapper.resource("backup_schedule", "backup_schedule",
|
||||
controller=backup_schedules.Controller(),
|
||||
path_prefix="{account_id}/servers/{server_id}/",
|
||||
parent_resource=dict(member_name='server',
|
||||
collection_name='servers'))
|
||||
|
||||
mapper.resource("console", "consoles",
|
||||
controller=consoles.Controller(),
|
||||
path_prefix="{account_id}/servers/{server_id}/",
|
||||
parent_resource=dict(member_name='server',
|
||||
collection_name='servers'))
|
||||
|
||||
mapper.resource("image", "images", controller=images.Controller(),
|
||||
path_prefix="{account_id}/",
|
||||
collection={'detail': 'GET'})
|
||||
mapper.resource("flavor", "flavors", controller=flavors.Controller(),
|
||||
path_prefix="{account_id}/",
|
||||
collection={'detail': 'GET'})
|
||||
mapper.resource("shared_ip_group", "shared_ip_groups",
|
||||
path_prefix="{account_id}/",
|
||||
collection={'detail': 'GET'},
|
||||
controller=shared_ip_groups.Controller())
|
||||
|
||||
|
||||
73
nova/api/openstack/accounts.py
Normal file
73
nova/api/openstack/accounts.py
Normal 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))
|
||||
@@ -28,11 +28,13 @@ from nova import context
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova import log as logging
|
||||
from nova import manager
|
||||
from nova import utils
|
||||
from nova import wsgi
|
||||
from nova.api.openstack import faults
|
||||
|
||||
LOG = logging.getLogger('nova.api.openstack')
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
@@ -50,14 +52,27 @@ class AuthMiddleware(wsgi.Middleware):
|
||||
def __call__(self, req):
|
||||
if not self.has_authentication(req):
|
||||
return self.authenticate(req)
|
||||
|
||||
user = self.get_user_by_authentication(req)
|
||||
account_name = req.path_info_peek()
|
||||
|
||||
if not user:
|
||||
return faults.Fault(webob.exc.HTTPUnauthorized())
|
||||
|
||||
project = self.auth.get_project(FLAGS.default_project)
|
||||
req.environ['nova.context'] = context.RequestContext(user, project)
|
||||
if not account_name:
|
||||
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
|
||||
|
||||
def has_authentication(self, req):
|
||||
@@ -70,6 +85,7 @@ class AuthMiddleware(wsgi.Middleware):
|
||||
# Unless the request is explicitly made against /<version>/ don't
|
||||
# honor it
|
||||
path_info = req.path_info
|
||||
account_name = None
|
||||
if len(path_info) > 1:
|
||||
return faults.Fault(webob.exc.HTTPUnauthorized())
|
||||
|
||||
@@ -79,7 +95,10 @@ class AuthMiddleware(wsgi.Middleware):
|
||||
except KeyError:
|
||||
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:
|
||||
res = webob.Response()
|
||||
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 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.
|
||||
|
||||
username - string
|
||||
account_name - string
|
||||
key - string API key
|
||||
req - webob.Request object
|
||||
"""
|
||||
ctxt = context.get_admin_context()
|
||||
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:
|
||||
token_hash = hashlib.sha1('%s%s%f' % (username, key,
|
||||
time.time())).hexdigest()
|
||||
token_dict = {}
|
||||
token_dict['token_hash'] = token_hash
|
||||
token_dict['cdn_management_url'] = ''
|
||||
# Same as auth url, e.g. http://foo.org:8774/baz/v1.0
|
||||
token_dict['server_management_url'] = req.url
|
||||
# auth url + project (account) id, e.g.
|
||||
# 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['user_id'] = user.id
|
||||
token = self.db.auth_token_create(ctxt, token_dict)
|
||||
|
||||
@@ -40,15 +40,15 @@ class Controller(wsgi.Controller):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def index(self, req, server_id):
|
||||
def index(self, req, server_id, **kw):
|
||||
""" Returns the list of backup schedules for a given instance """
|
||||
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
|
||||
both create and update through a POST """
|
||||
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 """
|
||||
return faults.Fault(exc.HTTPNotImplemented())
|
||||
|
||||
@@ -55,7 +55,7 @@ class Controller(wsgi.Controller):
|
||||
self.console_api = console.API()
|
||||
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"""
|
||||
consoles = self.console_api.get_consoles(
|
||||
req.environ['nova.context'],
|
||||
@@ -63,14 +63,14 @@ class Controller(wsgi.Controller):
|
||||
return dict(consoles=[_translate_keys(console)
|
||||
for console in consoles])
|
||||
|
||||
def create(self, req, server_id):
|
||||
def create(self, req, server_id, **kw):
|
||||
"""Creates a new console"""
|
||||
#info = self._deserialize(req.body, req)
|
||||
self.console_api.create_console(
|
||||
req.environ['nova.context'],
|
||||
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"""
|
||||
try:
|
||||
console = self.console_api.get_console(
|
||||
@@ -81,11 +81,11 @@ class Controller(wsgi.Controller):
|
||||
return faults.Fault(exc.HTTPNotFound())
|
||||
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"""
|
||||
raise faults.Fault(exc.HTTPNotImplemented())
|
||||
|
||||
def delete(self, req, server_id, id):
|
||||
def delete(self, req, server_id, id, **kw):
|
||||
"""Deletes a console"""
|
||||
try:
|
||||
self.console_api.delete_console(req.environ['nova.context'],
|
||||
|
||||
@@ -32,18 +32,18 @@ class Controller(wsgi.Controller):
|
||||
"attributes": {
|
||||
"flavor": ["id", "name", "ram", "disk"]}}}
|
||||
|
||||
def index(self, req):
|
||||
def index(self, req, **kw):
|
||||
"""Return all flavors in brief."""
|
||||
return dict(flavors=[dict(id=flavor['id'], name=flavor['name'])
|
||||
for flavor in self.detail(req)['flavors']])
|
||||
|
||||
def detail(self, req):
|
||||
def detail(self, req, **kw):
|
||||
"""Return all flavors in detail."""
|
||||
items = [self.show(req, id)['flavor'] for id in self._all_ids()]
|
||||
items = common.limited(items, req)
|
||||
return dict(flavors=items)
|
||||
|
||||
def show(self, req, id):
|
||||
def show(self, req, id, **kw):
|
||||
"""Return data about the given flavor id."""
|
||||
for name, val in instance_types.INSTANCE_TYPES.iteritems():
|
||||
if val['flavorid'] == int(id):
|
||||
|
||||
@@ -115,14 +115,14 @@ class Controller(wsgi.Controller):
|
||||
def __init__(self):
|
||||
self._service = utils.import_object(FLAGS.image_service)
|
||||
|
||||
def index(self, req):
|
||||
def index(self, req, **kw):
|
||||
"""Return all public images in brief"""
|
||||
items = self._service.index(req.environ['nova.context'])
|
||||
items = common.limited(items, req)
|
||||
items = [_filter_keys(item, ('id', 'name')) for item in items]
|
||||
return dict(images=items)
|
||||
|
||||
def detail(self, req):
|
||||
def detail(self, req, **kw):
|
||||
"""Return all public images in detail"""
|
||||
try:
|
||||
items = self._service.detail(req.environ['nova.context'])
|
||||
@@ -136,7 +136,7 @@ class Controller(wsgi.Controller):
|
||||
items = [_translate_status(item) for item in items]
|
||||
return dict(images=items)
|
||||
|
||||
def show(self, req, id):
|
||||
def show(self, req, id, **kw):
|
||||
"""Return data about the given image id"""
|
||||
image_id = common.get_image_id_from_image_hash(self._service,
|
||||
req.environ['nova.context'], id)
|
||||
@@ -145,11 +145,11 @@ class Controller(wsgi.Controller):
|
||||
_convert_image_id_to_hash(image)
|
||||
return dict(image=image)
|
||||
|
||||
def delete(self, req, id):
|
||||
def delete(self, req, id, **kw):
|
||||
# Only public images are supported for now.
|
||||
raise faults.Fault(exc.HTTPNotFound())
|
||||
|
||||
def create(self, req):
|
||||
def create(self, req, **kw):
|
||||
context = req.environ['nova.context']
|
||||
env = self._deserialize(req.body, req)
|
||||
instance_id = env["image"]["serverId"]
|
||||
@@ -160,7 +160,7 @@ class Controller(wsgi.Controller):
|
||||
|
||||
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
|
||||
# we support for now.
|
||||
raise faults.Fault(exc.HTTPNotFound())
|
||||
|
||||
@@ -105,11 +105,11 @@ class Controller(wsgi.Controller):
|
||||
self._image_service = utils.import_object(FLAGS.image_service)
|
||||
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 """
|
||||
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 """
|
||||
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]
|
||||
return dict(servers=res)
|
||||
|
||||
def show(self, req, id):
|
||||
def show(self, req, id, **kw):
|
||||
""" Returns server details by server id """
|
||||
try:
|
||||
instance = self.compute_api.get(req.environ['nova.context'], id)
|
||||
@@ -131,7 +131,7 @@ class Controller(wsgi.Controller):
|
||||
except exception.NotFound:
|
||||
return faults.Fault(exc.HTTPNotFound())
|
||||
|
||||
def delete(self, req, id):
|
||||
def delete(self, req, id, **kw):
|
||||
""" Destroys a server """
|
||||
try:
|
||||
self.compute_api.delete(req.environ['nova.context'], id)
|
||||
@@ -139,7 +139,7 @@ class Controller(wsgi.Controller):
|
||||
return faults.Fault(exc.HTTPNotFound())
|
||||
return exc.HTTPAccepted()
|
||||
|
||||
def create(self, req):
|
||||
def create(self, req, **kw):
|
||||
""" Creates a new server for a given user """
|
||||
env = self._deserialize(req.body, req)
|
||||
if not env:
|
||||
@@ -180,7 +180,7 @@ class Controller(wsgi.Controller):
|
||||
onset_files=env.get('onset_files', []))
|
||||
return _translate_keys(instances[0])
|
||||
|
||||
def update(self, req, id):
|
||||
def update(self, req, id, **kw):
|
||||
""" Updates the server name or password """
|
||||
inst_dict = self._deserialize(req.body, req)
|
||||
if not inst_dict:
|
||||
@@ -202,7 +202,7 @@ class Controller(wsgi.Controller):
|
||||
return faults.Fault(exc.HTTPNotFound())
|
||||
return exc.HTTPNoContent()
|
||||
|
||||
def action(self, req, id):
|
||||
def action(self, req, id, **kw):
|
||||
""" Multi-purpose method used to reboot, rebuild, and
|
||||
resize a server """
|
||||
input_dict = self._deserialize(req.body, req)
|
||||
@@ -219,7 +219,7 @@ class Controller(wsgi.Controller):
|
||||
return faults.Fault(exc.HTTPUnprocessableEntity())
|
||||
return exc.HTTPAccepted()
|
||||
|
||||
def lock(self, req, id):
|
||||
def lock(self, req, id, **kw):
|
||||
"""
|
||||
lock the instance with id
|
||||
admin only operation
|
||||
@@ -234,7 +234,7 @@ class Controller(wsgi.Controller):
|
||||
return faults.Fault(exc.HTTPUnprocessableEntity())
|
||||
return exc.HTTPAccepted()
|
||||
|
||||
def unlock(self, req, id):
|
||||
def unlock(self, req, id, **kw):
|
||||
"""
|
||||
unlock the instance with id
|
||||
admin only operation
|
||||
@@ -249,7 +249,7 @@ class Controller(wsgi.Controller):
|
||||
return faults.Fault(exc.HTTPUnprocessableEntity())
|
||||
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
|
||||
|
||||
@@ -263,7 +263,7 @@ class Controller(wsgi.Controller):
|
||||
return faults.Fault(exc.HTTPUnprocessableEntity())
|
||||
return exc.HTTPAccepted()
|
||||
|
||||
def reset_network(self, req, id):
|
||||
def reset_network(self, req, id, **kw):
|
||||
"""
|
||||
Reset networking on an instance (admin only).
|
||||
|
||||
@@ -277,7 +277,7 @@ class Controller(wsgi.Controller):
|
||||
return faults.Fault(exc.HTTPUnprocessableEntity())
|
||||
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).
|
||||
|
||||
@@ -291,7 +291,7 @@ class Controller(wsgi.Controller):
|
||||
return faults.Fault(exc.HTTPUnprocessableEntity())
|
||||
return exc.HTTPAccepted()
|
||||
|
||||
def pause(self, req, id):
|
||||
def pause(self, req, id, **kw):
|
||||
""" Permit Admins to Pause the server. """
|
||||
ctxt = req.environ['nova.context']
|
||||
try:
|
||||
@@ -302,7 +302,7 @@ class Controller(wsgi.Controller):
|
||||
return faults.Fault(exc.HTTPUnprocessableEntity())
|
||||
return exc.HTTPAccepted()
|
||||
|
||||
def unpause(self, req, id):
|
||||
def unpause(self, req, id, **kw):
|
||||
""" Permit Admins to Unpause the server. """
|
||||
ctxt = req.environ['nova.context']
|
||||
try:
|
||||
@@ -313,7 +313,7 @@ class Controller(wsgi.Controller):
|
||||
return faults.Fault(exc.HTTPUnprocessableEntity())
|
||||
return exc.HTTPAccepted()
|
||||
|
||||
def suspend(self, req, id):
|
||||
def suspend(self, req, id, **kw):
|
||||
"""permit admins to suspend the server"""
|
||||
context = req.environ['nova.context']
|
||||
try:
|
||||
@@ -324,7 +324,7 @@ class Controller(wsgi.Controller):
|
||||
return faults.Fault(exc.HTTPUnprocessableEntity())
|
||||
return exc.HTTPAccepted()
|
||||
|
||||
def resume(self, req, id):
|
||||
def resume(self, req, id, **kw):
|
||||
"""permit admins to resume the server from suspend"""
|
||||
context = req.environ['nova.context']
|
||||
try:
|
||||
@@ -335,7 +335,7 @@ class Controller(wsgi.Controller):
|
||||
return faults.Fault(exc.HTTPUnprocessableEntity())
|
||||
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. """
|
||||
try:
|
||||
self.compute_api.get_ajax_console(req.environ['nova.context'],
|
||||
@@ -344,12 +344,12 @@ class Controller(wsgi.Controller):
|
||||
return faults.Fault(exc.HTTPNotFound())
|
||||
return exc.HTTPAccepted()
|
||||
|
||||
def diagnostics(self, req, id):
|
||||
def diagnostics(self, req, id, **kw):
|
||||
"""Permit Admins to retrieve server diagnostics."""
|
||||
ctxt = req.environ["nova.context"]
|
||||
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."""
|
||||
ctxt = req.environ["nova.context"]
|
||||
items = self.compute_api.get_actions(ctxt, id)
|
||||
|
||||
@@ -40,26 +40,26 @@ class Controller(wsgi.Controller):
|
||||
'attributes': {
|
||||
'sharedIpGroup': []}}}
|
||||
|
||||
def index(self, req):
|
||||
def index(self, req, **kw):
|
||||
""" Returns a list of Shared IP Groups for the user """
|
||||
return dict(sharedIpGroups=[])
|
||||
|
||||
def show(self, req, id):
|
||||
def show(self, req, id, **kw):
|
||||
""" Shows in-depth information on a specific Shared IP Group """
|
||||
return _translate_keys({})
|
||||
|
||||
def update(self, req, id):
|
||||
def update(self, req, id, **kw):
|
||||
""" You can't update a Shared IP Group """
|
||||
raise faults.Fault(exc.HTTPNotImplemented())
|
||||
|
||||
def delete(self, req, id):
|
||||
def delete(self, req, id, **kw):
|
||||
""" Deletes a Shared IP Group """
|
||||
raise faults.Fault(exc.HTTPNotImplemented())
|
||||
|
||||
def detail(self, req):
|
||||
def detail(self, req, **kw):
|
||||
""" Returns a complete list of Shared IP Groups """
|
||||
return _translate_detail_keys({})
|
||||
|
||||
def create(self, req):
|
||||
def create(self, req, **kw):
|
||||
""" Creates a new Shared IP group """
|
||||
raise faults.Fault(exc.HTTPNotImplemented())
|
||||
|
||||
93
nova/api/openstack/users.py
Normal file
93
nova/api/openstack/users.py
Normal 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)))
|
||||
@@ -43,35 +43,35 @@ class Controller(wsgi.Controller):
|
||||
"attributes": {
|
||||
"zone": ["id", "api_url"]}}}
|
||||
|
||||
def index(self, req):
|
||||
def index(self, req, **kw):
|
||||
"""Return all zones in brief"""
|
||||
items = db.zone_get_all(req.environ['nova.context'])
|
||||
items = common.limited(items, req)
|
||||
items = [_scrub_zone(item) for item in items]
|
||||
return dict(zones=items)
|
||||
|
||||
def detail(self, req):
|
||||
def detail(self, req, **kw):
|
||||
"""Return all zones in detail"""
|
||||
return self.index(req)
|
||||
|
||||
def show(self, req, id):
|
||||
def show(self, req, id, **kw):
|
||||
"""Return data about the given zone id"""
|
||||
zone_id = int(id)
|
||||
zone = db.zone_get(req.environ['nova.context'], zone_id)
|
||||
return dict(zone=_scrub_zone(zone))
|
||||
|
||||
def delete(self, req, id):
|
||||
def delete(self, req, id, **kw):
|
||||
zone_id = int(id)
|
||||
db.zone_delete(req.environ['nova.context'], zone_id)
|
||||
return {}
|
||||
|
||||
def create(self, req):
|
||||
def create(self, req, **kw):
|
||||
context = req.environ['nova.context']
|
||||
env = self._deserialize(req.body, req)
|
||||
zone = db.zone_create(context, env["zone"])
|
||||
return dict(zone=_scrub_zone(zone))
|
||||
|
||||
def update(self, req, id):
|
||||
def update(self, req, id, **kw):
|
||||
context = req.environ['nova.context']
|
||||
env = self._deserialize(req.body, req)
|
||||
zone_id = int(id)
|
||||
|
||||
@@ -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-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_USERNAME="%(user)s"
|
||||
export NOVA_USERNAME="%(project)s:%(user)s"
|
||||
export NOVA_URL="%(os)s"
|
||||
|
||||
@@ -1861,8 +1861,11 @@ def project_get_by_user(context, user_id):
|
||||
session = get_session()
|
||||
user = session.query(models.User).\
|
||||
filter_by(deleted=can_read_deleted(context)).\
|
||||
filter_by(id=user_id).\
|
||||
options(joinedload_all('projects')).\
|
||||
first()
|
||||
if not user:
|
||||
raise exception.NotFound(_('Invalid user_id %s') % user_id)
|
||||
return user.projects
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ from paste import urlmap
|
||||
|
||||
from glance import client as glance_client
|
||||
|
||||
from nova import auth
|
||||
from nova import context
|
||||
from nova import exception as exc
|
||||
from nova import flags
|
||||
@@ -35,6 +34,7 @@ import nova.api.openstack.auth
|
||||
from nova.api import openstack
|
||||
from nova.api.openstack import auth
|
||||
from nova.api.openstack import ratelimiting
|
||||
from nova.auth.manager import User, Project
|
||||
from nova.image import glance
|
||||
from nova.image import local
|
||||
from nova.image import service
|
||||
@@ -227,19 +227,97 @@ class FakeAuthDatabase(object):
|
||||
|
||||
class FakeAuthManager(object):
|
||||
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):
|
||||
FakeAuthManager.auth_data[key] = user
|
||||
|
||||
def get_users(self):
|
||||
return FakeAuthManager.auth_data.values()
|
||||
|
||||
def get_user(self, uid):
|
||||
for k, v in FakeAuthManager.auth_data.iteritems():
|
||||
if v.id == uid:
|
||||
return v
|
||||
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
|
||||
|
||||
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):
|
||||
return FakeAuthManager.auth_data.get(key, None)
|
||||
|
||||
|
||||
123
nova/tests/api/openstack/test_accounts.py
Normal file
123
nova/tests/api/openstack/test_accounts.py
Normal 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)
|
||||
@@ -35,7 +35,7 @@ class AdminAPITest(test.TestCase):
|
||||
def setUp(self):
|
||||
super(AdminAPITest, self).setUp()
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
fakes.FakeAuthManager.auth_data = {}
|
||||
fakes.FakeAuthManager.reset_fake_data()
|
||||
fakes.FakeAuthDatabase.data = {}
|
||||
fakes.stub_out_networking(self.stubs)
|
||||
fakes.stub_out_rate_limiting(self.stubs)
|
||||
@@ -50,7 +50,7 @@ class AdminAPITest(test.TestCase):
|
||||
def test_admin_enabled(self):
|
||||
FLAGS.allow_admin_api = True
|
||||
# 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())
|
||||
self.assertEqual(res.status_int, 200)
|
||||
# TODO: Confirm admin operations are available.
|
||||
@@ -58,7 +58,7 @@ class AdminAPITest(test.TestCase):
|
||||
def test_admin_disabled(self):
|
||||
FLAGS.allow_admin_api = False
|
||||
# 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())
|
||||
# TODO: Confirm admin operations are unavailable.
|
||||
self.assertEqual(res.status_int, 200)
|
||||
|
||||
@@ -51,7 +51,9 @@ class Test(test.TestCase):
|
||||
|
||||
def test_authorize_user(self):
|
||||
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.headers['X-Auth-User'] = 'herp'
|
||||
@@ -65,7 +67,9 @@ class Test(test.TestCase):
|
||||
|
||||
def test_authorize_token(self):
|
||||
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.headers['X-Auth-User'] = 'herp'
|
||||
@@ -74,7 +78,7 @@ class Test(test.TestCase):
|
||||
self.assertEqual(result.status, '204 No Content')
|
||||
self.assertEqual(len(result.headers['X-Auth-Token']), 40)
|
||||
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-Storage-Url'], "")
|
||||
@@ -82,7 +86,7 @@ class Test(test.TestCase):
|
||||
token = result.headers['X-Auth-Token']
|
||||
self.stubs.Set(nova.api.openstack, 'APIRouter',
|
||||
fakes.FakeRouter)
|
||||
req = webob.Request.blank('/v1.0/fake')
|
||||
req = webob.Request.blank('/v1.0/test/fake')
|
||||
req.headers['X-Auth-Token'] = token
|
||||
result = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(result.status, '200 OK')
|
||||
@@ -176,6 +180,9 @@ class TestLimiter(test.TestCase):
|
||||
|
||||
def test_authorize_token(self):
|
||||
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))
|
||||
|
||||
req = webob.Request.blank('/v1.0/')
|
||||
@@ -187,7 +194,7 @@ class TestLimiter(test.TestCase):
|
||||
token = result.headers['X-Auth-Token']
|
||||
self.stubs.Set(nova.api.openstack, 'APIRouter',
|
||||
fakes.FakeRouter)
|
||||
req = webob.Request.blank('/v1.0/fake')
|
||||
req = webob.Request.blank('/v1.0/test/fake')
|
||||
req.method = 'POST'
|
||||
req.headers['X-Auth-Token'] = token
|
||||
result = req.get_response(fakes.wsgi_app())
|
||||
|
||||
@@ -28,7 +28,7 @@ class FlavorsTest(test.TestCase):
|
||||
def setUp(self):
|
||||
super(FlavorsTest, self).setUp()
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
fakes.FakeAuthManager.auth_data = {}
|
||||
fakes.FakeAuthManager.reset_fake_data()
|
||||
fakes.FakeAuthDatabase.data = {}
|
||||
fakes.stub_out_networking(self.stubs)
|
||||
fakes.stub_out_rate_limiting(self.stubs)
|
||||
@@ -39,7 +39,7 @@ class FlavorsTest(test.TestCase):
|
||||
super(FlavorsTest, self).tearDown()
|
||||
|
||||
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())
|
||||
|
||||
def test_get_flavor_by_id(self):
|
||||
|
||||
@@ -202,7 +202,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
|
||||
self.orig_image_service = FLAGS.image_service
|
||||
FLAGS.image_service = 'nova.image.glance.GlanceImageService'
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
fakes.FakeAuthManager.auth_data = {}
|
||||
fakes.FakeAuthManager.reset_fake_data()
|
||||
fakes.FakeAuthDatabase.data = {}
|
||||
fakes.stub_out_networking(self.stubs)
|
||||
fakes.stub_out_rate_limiting(self.stubs)
|
||||
@@ -216,7 +216,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
|
||||
super(ImageControllerWithGlanceServiceTest, self).tearDown()
|
||||
|
||||
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_dict = json.loads(res.body)
|
||||
|
||||
@@ -228,7 +228,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
|
||||
"image %s not in fixture index!" % str(image))
|
||||
|
||||
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_dict = json.loads(res.body)
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ class ServersTest(test.TestCase):
|
||||
def setUp(self):
|
||||
super(ServersTest, self).setUp()
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
fakes.FakeAuthManager.auth_data = {}
|
||||
fakes.FakeAuthManager.reset_fake_data()
|
||||
fakes.FakeAuthDatabase.data = {}
|
||||
fakes.stub_out_networking(self.stubs)
|
||||
fakes.stub_out_rate_limiting(self.stubs)
|
||||
@@ -150,7 +150,7 @@ class ServersTest(test.TestCase):
|
||||
super(ServersTest, self).tearDown()
|
||||
|
||||
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_dict = json.loads(res.body)
|
||||
self.assertEqual(res_dict['server']['id'], '1')
|
||||
@@ -161,7 +161,7 @@ class ServersTest(test.TestCase):
|
||||
public = ["1.2.3.4"]
|
||||
new_return_server = return_server_with_addresses(private, public)
|
||||
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_dict = json.loads(res.body)
|
||||
self.assertEqual(res_dict['server']['id'], '1')
|
||||
@@ -173,7 +173,7 @@ class ServersTest(test.TestCase):
|
||||
self.assertEqual(addresses["private"][0], private)
|
||||
|
||||
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_dict = json.loads(res.body)
|
||||
|
||||
@@ -224,7 +224,7 @@ class ServersTest(test.TestCase):
|
||||
name='server_test', imageId=2, flavorId=2,
|
||||
metadata={'hello': 'world', 'open': 'stack'},
|
||||
personality={}))
|
||||
req = webob.Request.blank('/v1.0/servers')
|
||||
req = webob.Request.blank('/v1.0/testacct/servers')
|
||||
req.method = 'POST'
|
||||
req.body = json.dumps(body)
|
||||
|
||||
@@ -233,7 +233,7 @@ class ServersTest(test.TestCase):
|
||||
self.assertEqual(res.status_int, 200)
|
||||
|
||||
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'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(res.status_int, 422)
|
||||
@@ -251,7 +251,7 @@ class ServersTest(test.TestCase):
|
||||
self.stubs.Set(nova.db.api, 'instance_update',
|
||||
server_update)
|
||||
|
||||
req = webob.Request.blank('/v1.0/servers/1')
|
||||
req = webob.Request.blank('/v1.0/testacct/servers/1')
|
||||
req.method = 'PUT'
|
||||
req.body = self.body
|
||||
req.get_response(fakes.wsgi_app())
|
||||
@@ -267,30 +267,30 @@ class ServersTest(test.TestCase):
|
||||
self.stubs.Set(nova.db.api, 'instance_update',
|
||||
server_update)
|
||||
|
||||
req = webob.Request.blank('/v1.0/servers/1')
|
||||
req = webob.Request.blank('/v1.0/testacct/servers/1')
|
||||
req.method = 'PUT'
|
||||
req.body = self.body
|
||||
req.get_response(fakes.wsgi_app())
|
||||
|
||||
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'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(res.status, '404 Not Found')
|
||||
|
||||
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'
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(res.status, '404 Not Found')
|
||||
|
||||
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())
|
||||
self.assertEqual(res.status, '404 Not Found')
|
||||
|
||||
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_dict = json.loads(res.body)
|
||||
|
||||
@@ -321,7 +321,7 @@ class ServersTest(test.TestCase):
|
||||
self.stubs.Set(nova.db.api, 'instance_get_all_by_user',
|
||||
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_dict = json.loads(res.body)
|
||||
|
||||
@@ -341,7 +341,7 @@ class ServersTest(test.TestCase):
|
||||
body = dict(server=dict(
|
||||
name='server_test', imageId=2, flavorId=2, metadata={},
|
||||
personality={}))
|
||||
req = webob.Request.blank('/v1.0/servers/1/pause')
|
||||
req = webob.Request.blank('/v1.0/testacct/servers/1/pause')
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
req.body = json.dumps(body)
|
||||
@@ -353,7 +353,7 @@ class ServersTest(test.TestCase):
|
||||
body = dict(server=dict(
|
||||
name='server_test', imageId=2, flavorId=2, metadata={},
|
||||
personality={}))
|
||||
req = webob.Request.blank('/v1.0/servers/1/unpause')
|
||||
req = webob.Request.blank('/v1.0/testacct/servers/1/unpause')
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
req.body = json.dumps(body)
|
||||
@@ -365,7 +365,7 @@ class ServersTest(test.TestCase):
|
||||
body = dict(server=dict(
|
||||
name='server_test', imageId=2, flavorId=2, metadata={},
|
||||
personality={}))
|
||||
req = webob.Request.blank('/v1.0/servers/1/suspend')
|
||||
req = webob.Request.blank('/v1.0/testacct/servers/1/suspend')
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
req.body = json.dumps(body)
|
||||
@@ -377,7 +377,7 @@ class ServersTest(test.TestCase):
|
||||
body = dict(server=dict(
|
||||
name='server_test', imageId=2, flavorId=2, metadata={},
|
||||
personality={}))
|
||||
req = webob.Request.blank('/v1.0/servers/1/resume')
|
||||
req = webob.Request.blank('/v1.0/testacct/servers/1/resume')
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
req.body = json.dumps(body)
|
||||
@@ -389,7 +389,7 @@ class ServersTest(test.TestCase):
|
||||
body = dict(server=dict(
|
||||
name='server_test', imageId=2, flavorId=2, metadata={},
|
||||
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.content_type = 'application/json'
|
||||
req.body = json.dumps(body)
|
||||
@@ -401,7 +401,8 @@ class ServersTest(test.TestCase):
|
||||
body = dict(server=dict(
|
||||
name='server_test', imageId=2, flavorId=2, metadata={},
|
||||
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.content_type = 'application/json'
|
||||
req.body = json.dumps(body)
|
||||
@@ -409,13 +410,13 @@ class ServersTest(test.TestCase):
|
||||
self.assertEqual(res.status_int, 202)
|
||||
|
||||
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"
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(res.status_int, 404)
|
||||
|
||||
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"
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
self.assertEqual(res.status_int, 404)
|
||||
@@ -424,7 +425,7 @@ class ServersTest(test.TestCase):
|
||||
body = dict(server=dict(
|
||||
name='server_test', imageId=2, flavorId=2, metadata={},
|
||||
personality={}))
|
||||
req = webob.Request.blank('/v1.0/servers/1/action')
|
||||
req = webob.Request.blank('/v1.0/testacct/servers/1/action')
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
req.body = json.dumps(body)
|
||||
@@ -434,7 +435,7 @@ class ServersTest(test.TestCase):
|
||||
body = dict(server=dict(
|
||||
name='server_test', imageId=2, flavorId=2, metadata={},
|
||||
personality={}))
|
||||
req = webob.Request.blank('/v1.0/servers/1/action')
|
||||
req = webob.Request.blank('/v1.0/testacct/servers/1/action')
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
req.body = json.dumps(body)
|
||||
@@ -444,14 +445,14 @@ class ServersTest(test.TestCase):
|
||||
body = dict(server=dict(
|
||||
name='server_test', imageId=2, flavorId=2, metadata={},
|
||||
personality={}))
|
||||
req = webob.Request.blank('/v1.0/servers/1/action')
|
||||
req = webob.Request.blank('/v1.0/testacct/servers/1/action')
|
||||
req.method = 'POST'
|
||||
req.content_type = 'application/json'
|
||||
req.body = json.dumps(body)
|
||||
res = req.get_response(fakes.wsgi_app())
|
||||
|
||||
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'
|
||||
|
||||
self.server_delete_called = False
|
||||
|
||||
139
nova/tests/api/openstack/test_users.py
Normal file
139
nova/tests/api/openstack/test_users.py
Normal 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)
|
||||
@@ -64,7 +64,7 @@ class ZonesTest(test.TestCase):
|
||||
def setUp(self):
|
||||
super(ZonesTest, self).setUp()
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
fakes.FakeAuthManager.auth_data = {}
|
||||
fakes.FakeAuthManager.reset_fake_data()
|
||||
fakes.FakeAuthDatabase.data = {}
|
||||
fakes.stub_out_networking(self.stubs)
|
||||
fakes.stub_out_rate_limiting(self.stubs)
|
||||
@@ -85,7 +85,7 @@ class ZonesTest(test.TestCase):
|
||||
super(ZonesTest, self).tearDown()
|
||||
|
||||
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_dict = json.loads(res.body)
|
||||
|
||||
@@ -93,7 +93,7 @@ class ZonesTest(test.TestCase):
|
||||
self.assertEqual(len(res_dict['zones']), 2)
|
||||
|
||||
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_dict = json.loads(res.body)
|
||||
|
||||
@@ -103,7 +103,7 @@ class ZonesTest(test.TestCase):
|
||||
self.assertEqual(res.status_int, 200)
|
||||
|
||||
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())
|
||||
|
||||
self.assertEqual(res.status_int, 200)
|
||||
@@ -111,7 +111,7 @@ class ZonesTest(test.TestCase):
|
||||
def test_zone_create(self):
|
||||
body = dict(zone=dict(api_url='http://blah.zoo', username='fred',
|
||||
password='fubar'))
|
||||
req = webob.Request.blank('/v1.0/zones')
|
||||
req = webob.Request.blank('/v1.0/testacct/zones')
|
||||
req.method = 'POST'
|
||||
req.body = json.dumps(body)
|
||||
|
||||
@@ -125,7 +125,7 @@ class ZonesTest(test.TestCase):
|
||||
|
||||
def test_zone_update(self):
|
||||
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.body = json.dumps(body)
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@ def _upload_tarball(staging_path, image_id, glance_host, glance_port):
|
||||
'transfer-encoding': 'chunked',
|
||||
'x-image-meta-is_public': 'True',
|
||||
'x-image-meta-status': 'queued',
|
||||
'x-image-meta-type': 'vhd'
|
||||
'x-image-meta-type': 'vhd',
|
||||
}
|
||||
for header, value in headers.iteritems():
|
||||
conn.putheader(header, value)
|
||||
|
||||
Reference in New Issue
Block a user