summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbin/nova-manage3
-rw-r--r--nova/api/openstack/__init__.py25
-rw-r--r--nova/api/openstack/accounts.py73
-rw-r--r--nova/api/openstack/auth.py54
-rw-r--r--nova/api/openstack/backup_schedules.py6
-rw-r--r--nova/api/openstack/consoles.py10
-rw-r--r--nova/api/openstack/flavors.py6
-rw-r--r--nova/api/openstack/images.py12
-rw-r--r--nova/api/openstack/servers.py38
-rw-r--r--nova/api/openstack/shared_ip_groups.py12
-rw-r--r--nova/api/openstack/users.py93
-rw-r--r--nova/api/openstack/zones.py12
-rw-r--r--nova/auth/novarc.template2
-rw-r--r--nova/db/sqlalchemy/api.py3
-rw-r--r--nova/tests/api/openstack/fakes.py82
-rw-r--r--nova/tests/api/openstack/test_accounts.py123
-rw-r--r--nova/tests/api/openstack/test_adminapi.py6
-rw-r--r--nova/tests/api/openstack/test_auth.py17
-rw-r--r--nova/tests/api/openstack/test_flavors.py4
-rw-r--r--nova/tests/api/openstack/test_images.py6
-rw-r--r--nova/tests/api/openstack/test_servers.py51
-rw-r--r--nova/tests/api/openstack/test_users.py139
-rw-r--r--nova/tests/api/openstack/test_zones.py12
-rw-r--r--plugins/xenserver/xenapi/etc/xapi.d/plugins/glance2
24 files changed, 688 insertions, 103 deletions
diff --git a/bin/nova-manage b/bin/nova-manage
index 89332f2af..8f8f0a6a8 100755
--- a/bin/nova-manage
+++ b/bin/nova-manage
@@ -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),
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index b1b38ed2d..73d52192e 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -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())
diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py
new file mode 100644
index 000000000..264fdab99
--- /dev/null
+++ b/nova/api/openstack/accounts.py
@@ -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))
diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py
index 6011e6115..e77910fed 100644
--- a/nova/api/openstack/auth.py
+++ b/nova/api/openstack/auth.py
@@ -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)
diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py
index 7abb5f884..a4d5939df 100644
--- a/nova/api/openstack/backup_schedules.py
+++ b/nova/api/openstack/backup_schedules.py
@@ -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())
diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py
index 9ebdbe710..85b2a4140 100644
--- a/nova/api/openstack/consoles.py
+++ b/nova/api/openstack/consoles.py
@@ -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'],
diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py
index f620d4107..79c3e1ab3 100644
--- a/nova/api/openstack/flavors.py
+++ b/nova/api/openstack/flavors.py
@@ -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):
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index cf85a496f..5bc5b9978 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -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())
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 69273ad7b..426de92be 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -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)
diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py
index 5d78f9377..e3c917749 100644
--- a/nova/api/openstack/shared_ip_groups.py
+++ b/nova/api/openstack/shared_ip_groups.py
@@ -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())
diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py
new file mode 100644
index 000000000..c0b7544f9
--- /dev/null
+++ b/nova/api/openstack/users.py
@@ -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)))
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
index d5206da20..30bf2b67b 100644
--- a/nova/api/openstack/zones.py
+++ b/nova/api/openstack/zones.py
@@ -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)
diff --git a/nova/auth/novarc.template b/nova/auth/novarc.template
index cda2ecc28..1c917ad44 100644
--- a/nova/auth/novarc.template
+++ b/nova/auth/novarc.template
@@ -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"
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 6df2a8843..e311f310a 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -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
diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py
index 49ce8c1b5..03b26e29a 100644
--- a/nova/tests/api/openstack/fakes.py
+++ b/nova/tests/api/openstack/fakes.py
@@ -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)
diff --git a/nova/tests/api/openstack/test_accounts.py b/nova/tests/api/openstack/test_accounts.py
new file mode 100644
index 000000000..b2e89824a
--- /dev/null
+++ b/nova/tests/api/openstack/test_accounts.py
@@ -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)
diff --git a/nova/tests/api/openstack/test_adminapi.py b/nova/tests/api/openstack/test_adminapi.py
index dfce1b127..7cb9e8450 100644
--- a/nova/tests/api/openstack/test_adminapi.py
+++ b/nova/tests/api/openstack/test_adminapi.py
@@ -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)
diff --git a/nova/tests/api/openstack/test_auth.py b/nova/tests/api/openstack/test_auth.py
index ff8d42a14..8268a6fb9 100644
--- a/nova/tests/api/openstack/test_auth.py
+++ b/nova/tests/api/openstack/test_auth.py
@@ -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())
diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py
index 761265965..370dc007c 100644
--- a/nova/tests/api/openstack/test_flavors.py
+++ b/nova/tests/api/openstack/test_flavors.py
@@ -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):
diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py
index e232bc3d5..819ca001e 100644
--- a/nova/tests/api/openstack/test_images.py
+++ b/nova/tests/api/openstack/test_images.py
@@ -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)
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 78beb7df9..d592e06b0 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -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
diff --git a/nova/tests/api/openstack/test_users.py b/nova/tests/api/openstack/test_users.py
new file mode 100644
index 000000000..bd32254cd
--- /dev/null
+++ b/nova/tests/api/openstack/test_users.py
@@ -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)
diff --git a/nova/tests/api/openstack/test_zones.py b/nova/tests/api/openstack/test_zones.py
index 555b206b9..51f13af48 100644
--- a/nova/tests/api/openstack/test_zones.py
+++ b/nova/tests/api/openstack/test_zones.py
@@ -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)
diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance
index 7531af4ec..a45d32cb2 100644
--- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance
+++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/glance
@@ -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)