Merge trunk.
This commit is contained in:
		| @@ -50,7 +50,6 @@ | |||||||
|  |  | ||||||
| """ | """ | ||||||
|   CLI interface for nova management. |   CLI interface for nova management. | ||||||
|   Connects to the running ADMIN api in the api daemon. |  | ||||||
| """ | """ | ||||||
|  |  | ||||||
| import os | import os | ||||||
| @@ -68,7 +67,9 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): | |||||||
|     sys.path.insert(0, possible_topdir) |     sys.path.insert(0, possible_topdir) | ||||||
|  |  | ||||||
| from nova import db | from nova import db | ||||||
|  | from nova import exception | ||||||
| from nova import flags | from nova import flags | ||||||
|  | from nova import quota | ||||||
| from nova import utils | from nova import utils | ||||||
| from nova.auth import manager | from nova.auth import manager | ||||||
| from nova.cloudpipe import pipelib | from nova.cloudpipe import pipelib | ||||||
| @@ -186,6 +187,13 @@ class RoleCommands(object): | |||||||
| class UserCommands(object): | class UserCommands(object): | ||||||
|     """Class for managing users.""" |     """Class for managing users.""" | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _print_export(user): | ||||||
|  |         """Print export variables to use with API.""" | ||||||
|  |         print 'export EC2_ACCESS_KEY=%s' % user.access | ||||||
|  |         print 'export EC2_SECRET_KEY=%s' % user.secret | ||||||
|  |  | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         self.manager = manager.AuthManager() |         self.manager = manager.AuthManager() | ||||||
|  |  | ||||||
| @@ -193,13 +201,13 @@ class UserCommands(object): | |||||||
|         """creates a new admin and prints exports |         """creates a new admin and prints exports | ||||||
|         arguments: name [access] [secret]""" |         arguments: name [access] [secret]""" | ||||||
|         user = self.manager.create_user(name, access, secret, True) |         user = self.manager.create_user(name, access, secret, True) | ||||||
|         print_export(user) |         self._print_export(user) | ||||||
|  |  | ||||||
|     def create(self, name, access=None, secret=None): |     def create(self, name, access=None, secret=None): | ||||||
|         """creates a new user and prints exports |         """creates a new user and prints exports | ||||||
|         arguments: name [access] [secret]""" |         arguments: name [access] [secret]""" | ||||||
|         user = self.manager.create_user(name, access, secret, False) |         user = self.manager.create_user(name, access, secret, False) | ||||||
|         print_export(user) |         self._print_export(user) | ||||||
|  |  | ||||||
|     def delete(self, name): |     def delete(self, name): | ||||||
|         """deletes an existing user |         """deletes an existing user | ||||||
| @@ -211,7 +219,7 @@ class UserCommands(object): | |||||||
|         arguments: name""" |         arguments: name""" | ||||||
|         user = self.manager.get_user(name) |         user = self.manager.get_user(name) | ||||||
|         if user: |         if user: | ||||||
|             print_export(user) |             self._print_export(user) | ||||||
|         else: |         else: | ||||||
|             print "User %s doesn't exist" % name |             print "User %s doesn't exist" % name | ||||||
|  |  | ||||||
| @@ -222,12 +230,6 @@ class UserCommands(object): | |||||||
|             print user.name |             print user.name | ||||||
|  |  | ||||||
|  |  | ||||||
| def print_export(user): |  | ||||||
|     """Print export variables to use with API.""" |  | ||||||
|     print 'export EC2_ACCESS_KEY=%s' % user.access |  | ||||||
|     print 'export EC2_SECRET_KEY=%s' % user.secret |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class ProjectCommands(object): | class ProjectCommands(object): | ||||||
|     """Class for managing projects.""" |     """Class for managing projects.""" | ||||||
|  |  | ||||||
| @@ -262,6 +264,19 @@ class ProjectCommands(object): | |||||||
|         for project in self.manager.get_projects(): |         for project in self.manager.get_projects(): | ||||||
|             print project.name |             print project.name | ||||||
|  |  | ||||||
|  |     def quota(self, project_id, key=None, value=None): | ||||||
|  |         """Set or display quotas for project | ||||||
|  |         arguments: project_id [key] [value]""" | ||||||
|  |         if key: | ||||||
|  |             quo = {'project_id': project_id, key: value} | ||||||
|  |             try: | ||||||
|  |                 db.quota_update(None, project_id, quo) | ||||||
|  |             except exception.NotFound: | ||||||
|  |                 db.quota_create(None, quo) | ||||||
|  |         project_quota = quota.get_quota(None, project_id) | ||||||
|  |         for key, value in project_quota.iteritems(): | ||||||
|  |             print '%s: %s' % (key, value) | ||||||
|  |  | ||||||
|     def remove(self, project, user): |     def remove(self, project, user): | ||||||
|         """Removes user from project |         """Removes user from project | ||||||
|         arguments: project user""" |         arguments: project user""" | ||||||
| @@ -274,6 +289,7 @@ class ProjectCommands(object): | |||||||
|         with open(filename, 'w') as f: |         with open(filename, 'w') as f: | ||||||
|             f.write(zip_file) |             f.write(zip_file) | ||||||
|  |  | ||||||
|  |  | ||||||
| class FloatingIpCommands(object): | class FloatingIpCommands(object): | ||||||
|     """Class for managing floating ip.""" |     """Class for managing floating ip.""" | ||||||
|  |  | ||||||
| @@ -306,6 +322,7 @@ class FloatingIpCommands(object): | |||||||
|                                   floating_ip['address'], |                                   floating_ip['address'], | ||||||
|                                   instance) |                                   instance) | ||||||
|  |  | ||||||
|  |  | ||||||
| CATEGORIES = [ | CATEGORIES = [ | ||||||
|     ('user', UserCommands), |     ('user', UserCommands), | ||||||
|     ('project', ProjectCommands), |     ('project', ProjectCommands), | ||||||
|   | |||||||
| @@ -1,69 +0,0 @@ | |||||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 |  | ||||||
|  |  | ||||||
| # Copyright 2010 United States Government as represented by the |  | ||||||
| # Administrator of the National Aeronautics and Space Administration. |  | ||||||
| # All Rights Reserved. |  | ||||||
| # |  | ||||||
| #    Licensed under the Apache License, Version 2.0 (the "License"); you may |  | ||||||
| #    not use this file except in compliance with the License. You may obtain |  | ||||||
| #    a copy of the License at |  | ||||||
| # |  | ||||||
| #         http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
| # |  | ||||||
| #    Unless required by applicable law or agreed to in writing, software |  | ||||||
| #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |  | ||||||
| #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |  | ||||||
| #    License for the specific language governing permissions and limitations |  | ||||||
| #    under the License. |  | ||||||
|  |  | ||||||
| """Role-based access control decorators to use fpr wrapping other |  | ||||||
| methods with.""" |  | ||||||
|  |  | ||||||
| from nova import exception |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def allow(*roles): |  | ||||||
|     """Allow the given roles access the wrapped function.""" |  | ||||||
|  |  | ||||||
|     def wrap(func): # pylint: disable-msg=C0111 |  | ||||||
|  |  | ||||||
|         def wrapped_func(self, context, *args, |  | ||||||
|                          **kwargs): # pylint: disable-msg=C0111 |  | ||||||
|             if context.user.is_superuser(): |  | ||||||
|                 return func(self, context, *args, **kwargs) |  | ||||||
|             for role in roles: |  | ||||||
|                 if __matches_role(context, role): |  | ||||||
|                     return func(self, context, *args, **kwargs) |  | ||||||
|             raise exception.NotAuthorized() |  | ||||||
|  |  | ||||||
|         return wrapped_func |  | ||||||
|  |  | ||||||
|     return wrap |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def deny(*roles): |  | ||||||
|     """Deny the given roles access the wrapped function.""" |  | ||||||
|  |  | ||||||
|     def wrap(func): # pylint: disable-msg=C0111 |  | ||||||
|  |  | ||||||
|         def wrapped_func(self, context, *args, |  | ||||||
|                          **kwargs): # pylint: disable-msg=C0111 |  | ||||||
|             if context.user.is_superuser(): |  | ||||||
|                 return func(self, context, *args, **kwargs) |  | ||||||
|             for role in roles: |  | ||||||
|                 if __matches_role(context, role): |  | ||||||
|                     raise exception.NotAuthorized() |  | ||||||
|             return func(self, context, *args, **kwargs) |  | ||||||
|  |  | ||||||
|         return wrapped_func |  | ||||||
|  |  | ||||||
|     return wrap |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def __matches_role(context, role): |  | ||||||
|     """Check if a role is allowed.""" |  | ||||||
|     if role == 'all': |  | ||||||
|         return True |  | ||||||
|     if role == 'none': |  | ||||||
|         return False |  | ||||||
|     return context.project.has_role(context.user.id, role) |  | ||||||
| @@ -1,214 +0,0 @@ | |||||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 |  | ||||||
|  |  | ||||||
| # Copyright 2010 United States Government as represented by the |  | ||||||
| # Administrator of the National Aeronautics and Space Administration. |  | ||||||
| # All Rights Reserved. |  | ||||||
| # |  | ||||||
| #    Licensed under the Apache License, Version 2.0 (the "License"); you may |  | ||||||
| #    not use this file except in compliance with the License. You may obtain |  | ||||||
| #    a copy of the License at |  | ||||||
| # |  | ||||||
| #         http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
| # |  | ||||||
| #    Unless required by applicable law or agreed to in writing, software |  | ||||||
| #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |  | ||||||
| #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |  | ||||||
| #    License for the specific language governing permissions and limitations |  | ||||||
| #    under the License. |  | ||||||
|  |  | ||||||
| """ |  | ||||||
| Admin API controller, exposed through http via the api worker. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import base64 |  | ||||||
|  |  | ||||||
| from nova import db |  | ||||||
| from nova import exception |  | ||||||
| from nova.auth import manager |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def user_dict(user, base64_file=None): |  | ||||||
|     """Convert the user object to a result dict""" |  | ||||||
|     if user: |  | ||||||
|         return { |  | ||||||
|             'username': user.id, |  | ||||||
|             'accesskey': user.access, |  | ||||||
|             'secretkey': user.secret, |  | ||||||
|             'file': base64_file} |  | ||||||
|     else: |  | ||||||
|         return {} |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def project_dict(project): |  | ||||||
|     """Convert the project object to a result dict""" |  | ||||||
|     if project: |  | ||||||
|         return { |  | ||||||
|             'projectname': project.id, |  | ||||||
|             'project_manager_id': project.project_manager_id, |  | ||||||
|             'description': project.description} |  | ||||||
|     else: |  | ||||||
|         return {} |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def host_dict(host): |  | ||||||
|     """Convert a host model object to a result dict""" |  | ||||||
|     if host: |  | ||||||
|         return host.state |  | ||||||
|     else: |  | ||||||
|         return {} |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def admin_only(target): |  | ||||||
|     """Decorator for admin-only API calls""" |  | ||||||
|     def wrapper(*args, **kwargs): |  | ||||||
|         """Internal wrapper method for admin-only API calls""" |  | ||||||
|         context = args[1] |  | ||||||
|         if context.user.is_admin(): |  | ||||||
|             return target(*args, **kwargs) |  | ||||||
|         else: |  | ||||||
|             return {} |  | ||||||
|  |  | ||||||
|     return wrapper |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class AdminController(object): |  | ||||||
|     """ |  | ||||||
|     API Controller for users, hosts, nodes, and workers. |  | ||||||
|     Trivial admin_only wrapper will be replaced with RBAC, |  | ||||||
|     allowing project managers to administer project users. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return 'AdminController' |  | ||||||
|  |  | ||||||
|     @admin_only |  | ||||||
|     def describe_user(self, _context, name, **_kwargs): |  | ||||||
|         """Returns user data, including access and secret keys.""" |  | ||||||
|         return user_dict(manager.AuthManager().get_user(name)) |  | ||||||
|  |  | ||||||
|     @admin_only |  | ||||||
|     def describe_users(self, _context, **_kwargs): |  | ||||||
|         """Returns all users - should be changed to deal with a list.""" |  | ||||||
|         return {'userSet': |  | ||||||
|             [user_dict(u) for u in manager.AuthManager().get_users()] } |  | ||||||
|  |  | ||||||
|     @admin_only |  | ||||||
|     def register_user(self, _context, name, **_kwargs): |  | ||||||
|         """Creates a new user, and returns generated credentials.""" |  | ||||||
|         return user_dict(manager.AuthManager().create_user(name)) |  | ||||||
|  |  | ||||||
|     @admin_only |  | ||||||
|     def deregister_user(self, _context, name, **_kwargs): |  | ||||||
|         """Deletes a single user (NOT undoable.) |  | ||||||
|            Should throw an exception if the user has instances, |  | ||||||
|            volumes, or buckets remaining. |  | ||||||
|         """ |  | ||||||
|         manager.AuthManager().delete_user(name) |  | ||||||
|  |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     @admin_only |  | ||||||
|     def describe_roles(self, context, project_roles=True, **kwargs): |  | ||||||
|         """Returns a list of allowed roles.""" |  | ||||||
|         roles = manager.AuthManager().get_roles(project_roles) |  | ||||||
|         return { 'roles': [{'role': r} for r in roles]} |  | ||||||
|  |  | ||||||
|     @admin_only |  | ||||||
|     def describe_user_roles(self, context, user, project=None, **kwargs): |  | ||||||
|         """Returns a list of roles for the given user. |  | ||||||
|            Omitting project will return any global roles that the user has. |  | ||||||
|            Specifying project will return only project specific roles. |  | ||||||
|         """ |  | ||||||
|         roles = manager.AuthManager().get_user_roles(user, project=project) |  | ||||||
|         return { 'roles': [{'role': r} for r in roles]} |  | ||||||
|  |  | ||||||
|     @admin_only |  | ||||||
|     def modify_user_role(self, context, user, role, project=None, |  | ||||||
|                          operation='add', **kwargs): |  | ||||||
|         """Add or remove a role for a user and project.""" |  | ||||||
|         if operation == 'add': |  | ||||||
|             manager.AuthManager().add_role(user, role, project) |  | ||||||
|         elif operation == 'remove': |  | ||||||
|             manager.AuthManager().remove_role(user, role, project) |  | ||||||
|         else: |  | ||||||
|             raise exception.ApiError('operation must be add or remove') |  | ||||||
|  |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     @admin_only |  | ||||||
|     def generate_x509_for_user(self, _context, name, project=None, **kwargs): |  | ||||||
|         """Generates and returns an x509 certificate for a single user. |  | ||||||
|            Is usually called from a client that will wrap this with |  | ||||||
|            access and secret key info, and return a zip file. |  | ||||||
|         """ |  | ||||||
|         if project is None: |  | ||||||
|             project = name |  | ||||||
|         project = manager.AuthManager().get_project(project) |  | ||||||
|         user = manager.AuthManager().get_user(name) |  | ||||||
|         return user_dict(user, base64.b64encode(project.get_credentials(user))) |  | ||||||
|  |  | ||||||
|     @admin_only |  | ||||||
|     def describe_project(self, context, name, **kwargs): |  | ||||||
|         """Returns project data, including member ids.""" |  | ||||||
|         return project_dict(manager.AuthManager().get_project(name)) |  | ||||||
|  |  | ||||||
|     @admin_only |  | ||||||
|     def describe_projects(self, context, user=None, **kwargs): |  | ||||||
|         """Returns all projects - should be changed to deal with a list.""" |  | ||||||
|         return {'projectSet': |  | ||||||
|             [project_dict(u) for u in |  | ||||||
|             manager.AuthManager().get_projects(user=user)]} |  | ||||||
|  |  | ||||||
|     @admin_only |  | ||||||
|     def register_project(self, context, name, manager_user, description=None, |  | ||||||
|                          member_users=None, **kwargs): |  | ||||||
|         """Creates a new project""" |  | ||||||
|         return project_dict( |  | ||||||
|             manager.AuthManager().create_project( |  | ||||||
|                 name, |  | ||||||
|                 manager_user, |  | ||||||
|                 description=None, |  | ||||||
|                 member_users=None)) |  | ||||||
|  |  | ||||||
|     @admin_only |  | ||||||
|     def deregister_project(self, context, name): |  | ||||||
|         """Permanently deletes a project.""" |  | ||||||
|         manager.AuthManager().delete_project(name) |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     @admin_only |  | ||||||
|     def describe_project_members(self, context, name, **kwargs): |  | ||||||
|         project = manager.AuthManager().get_project(name) |  | ||||||
|         result = { |  | ||||||
|             'members': [{'member': m} for m in project.member_ids]} |  | ||||||
|         return result |  | ||||||
|  |  | ||||||
|     @admin_only |  | ||||||
|     def modify_project_member(self, context, user, project, operation, **kwargs): |  | ||||||
|         """Add or remove a user from a project.""" |  | ||||||
|         if operation =='add': |  | ||||||
|             manager.AuthManager().add_to_project(user, project) |  | ||||||
|         elif operation == 'remove': |  | ||||||
|             manager.AuthManager().remove_from_project(user, project) |  | ||||||
|         else: |  | ||||||
|             raise exception.ApiError('operation must be add or remove') |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     # FIXME(vish): these host commands don't work yet, perhaps some of the |  | ||||||
|     #              required data can be retrieved from service objects? |  | ||||||
|     @admin_only |  | ||||||
|     def describe_hosts(self, _context, **_kwargs): |  | ||||||
|         """Returns status info for all nodes. Includes: |  | ||||||
|             * Disk Space |  | ||||||
|             * Instance List |  | ||||||
|             * RAM used |  | ||||||
|             * CPU used |  | ||||||
|             * DHCP servers running |  | ||||||
|             * Iptables / bridges |  | ||||||
|         """ |  | ||||||
|         return {'hostSet': [host_dict(h) for h in db.host_get_all()]} |  | ||||||
|  |  | ||||||
|     @admin_only |  | ||||||
|     def describe_host(self, _context, name, **_kwargs): |  | ||||||
|         """Returns status info for single node.""" |  | ||||||
|         return host_dict(db.host_get(name)) |  | ||||||
| @@ -1,347 +0,0 @@ | |||||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 |  | ||||||
|  |  | ||||||
| # Copyright 2010 United States Government as represented by the |  | ||||||
| # Administrator of the National Aeronautics and Space Administration. |  | ||||||
| # All Rights Reserved. |  | ||||||
| # |  | ||||||
| #    Licensed under the Apache License, Version 2.0 (the "License"); you may |  | ||||||
| #    not use this file except in compliance with the License. You may obtain |  | ||||||
| #    a copy of the License at |  | ||||||
| # |  | ||||||
| #         http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
| # |  | ||||||
| #    Unless required by applicable law or agreed to in writing, software |  | ||||||
| #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |  | ||||||
| #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |  | ||||||
| #    License for the specific language governing permissions and limitations |  | ||||||
| #    under the License. |  | ||||||
|  |  | ||||||
| """ |  | ||||||
| Tornado REST API Request Handlers for Nova functions |  | ||||||
| Most calls are proxied into the responsible controller. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import logging |  | ||||||
| import multiprocessing |  | ||||||
| import random |  | ||||||
| import re |  | ||||||
| import urllib |  | ||||||
| # TODO(termie): replace minidom with etree |  | ||||||
| from xml.dom import minidom |  | ||||||
|  |  | ||||||
| import tornado.web |  | ||||||
| from twisted.internet import defer |  | ||||||
|  |  | ||||||
| from nova import crypto |  | ||||||
| from nova import exception |  | ||||||
| from nova import flags |  | ||||||
| from nova import utils |  | ||||||
| from nova.auth import manager |  | ||||||
| import nova.cloudpipe.api |  | ||||||
| from nova.endpoint import cloud |  | ||||||
|  |  | ||||||
|  |  | ||||||
| FLAGS = flags.FLAGS |  | ||||||
| flags.DEFINE_integer('cc_port', 8773, 'cloud controller port') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| _log = logging.getLogger("api") |  | ||||||
| _log.setLevel(logging.DEBUG) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| _c2u = re.compile('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def _camelcase_to_underscore(str): |  | ||||||
|     return _c2u.sub(r'_\1', str).lower().strip('_') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def _underscore_to_camelcase(str): |  | ||||||
|     return ''.join([x[:1].upper() + x[1:] for x in str.split('_')]) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def _underscore_to_xmlcase(str): |  | ||||||
|     res = _underscore_to_camelcase(str) |  | ||||||
|     return res[:1].lower() + res[1:] |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class APIRequestContext(object): |  | ||||||
|     def __init__(self, handler, user, project): |  | ||||||
|         self.handler = handler |  | ||||||
|         self.user = user |  | ||||||
|         self.project = project |  | ||||||
|         self.request_id = ''.join( |  | ||||||
|                 [random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-') |  | ||||||
|                  for x in xrange(20)] |  | ||||||
|                 ) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class APIRequest(object): |  | ||||||
|     def __init__(self, controller, action): |  | ||||||
|         self.controller = controller |  | ||||||
|         self.action = action |  | ||||||
|  |  | ||||||
|     def send(self, context, **kwargs): |  | ||||||
|  |  | ||||||
|         try: |  | ||||||
|             method = getattr(self.controller, |  | ||||||
|                              _camelcase_to_underscore(self.action)) |  | ||||||
|         except AttributeError: |  | ||||||
|             _error = ('Unsupported API request: controller = %s,' |  | ||||||
|                       'action = %s') % (self.controller, self.action) |  | ||||||
|             _log.warning(_error) |  | ||||||
|             # TODO: Raise custom exception, trap in apiserver, |  | ||||||
|             #       and reraise as 400 error. |  | ||||||
|             raise Exception(_error) |  | ||||||
|  |  | ||||||
|         args = {} |  | ||||||
|         for key, value in kwargs.items(): |  | ||||||
|             parts = key.split(".") |  | ||||||
|             key = _camelcase_to_underscore(parts[0]) |  | ||||||
|             if len(parts) > 1: |  | ||||||
|                 d = args.get(key, {}) |  | ||||||
|                 d[parts[1]] = value[0] |  | ||||||
|                 value = d |  | ||||||
|             else: |  | ||||||
|                 value = value[0] |  | ||||||
|             args[key] = value |  | ||||||
|  |  | ||||||
|         for key in args.keys(): |  | ||||||
|             if isinstance(args[key], dict): |  | ||||||
|                 if args[key] != {} and args[key].keys()[0].isdigit(): |  | ||||||
|                     s = args[key].items() |  | ||||||
|                     s.sort() |  | ||||||
|                     args[key] = [v for k, v in s] |  | ||||||
|  |  | ||||||
|         d = defer.maybeDeferred(method, context, **args) |  | ||||||
|         d.addCallback(self._render_response, context.request_id) |  | ||||||
|         return d |  | ||||||
|  |  | ||||||
|     def _render_response(self, response_data, request_id): |  | ||||||
|         xml = minidom.Document() |  | ||||||
|  |  | ||||||
|         response_el = xml.createElement(self.action + 'Response') |  | ||||||
|         response_el.setAttribute('xmlns', |  | ||||||
|                                  'http://ec2.amazonaws.com/doc/2009-11-30/') |  | ||||||
|         request_id_el = xml.createElement('requestId') |  | ||||||
|         request_id_el.appendChild(xml.createTextNode(request_id)) |  | ||||||
|         response_el.appendChild(request_id_el) |  | ||||||
|         if(response_data == True): |  | ||||||
|             self._render_dict(xml, response_el, {'return': 'true'}) |  | ||||||
|         else: |  | ||||||
|             self._render_dict(xml, response_el, response_data) |  | ||||||
|  |  | ||||||
|         xml.appendChild(response_el) |  | ||||||
|  |  | ||||||
|         response = xml.toxml() |  | ||||||
|         xml.unlink() |  | ||||||
|         _log.debug(response) |  | ||||||
|         return response |  | ||||||
|  |  | ||||||
|     def _render_dict(self, xml, el, data): |  | ||||||
|         try: |  | ||||||
|             for key in data.keys(): |  | ||||||
|                 val = data[key] |  | ||||||
|                 el.appendChild(self._render_data(xml, key, val)) |  | ||||||
|         except: |  | ||||||
|             _log.debug(data) |  | ||||||
|             raise |  | ||||||
|  |  | ||||||
|     def _render_data(self, xml, el_name, data): |  | ||||||
|         el_name = _underscore_to_xmlcase(el_name) |  | ||||||
|         data_el = xml.createElement(el_name) |  | ||||||
|  |  | ||||||
|         if isinstance(data, list): |  | ||||||
|             for item in data: |  | ||||||
|                 data_el.appendChild(self._render_data(xml, 'item', item)) |  | ||||||
|         elif isinstance(data, dict): |  | ||||||
|             self._render_dict(xml, data_el, data) |  | ||||||
|         elif hasattr(data, '__dict__'): |  | ||||||
|             self._render_dict(xml, data_el, data.__dict__) |  | ||||||
|         elif isinstance(data, bool): |  | ||||||
|             data_el.appendChild(xml.createTextNode(str(data).lower())) |  | ||||||
|         elif data != None: |  | ||||||
|             data_el.appendChild(xml.createTextNode(str(data))) |  | ||||||
|  |  | ||||||
|         return data_el |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class RootRequestHandler(tornado.web.RequestHandler): |  | ||||||
|     def get(self): |  | ||||||
|         # available api versions |  | ||||||
|         versions = [ |  | ||||||
|             '1.0', |  | ||||||
|             '2007-01-19', |  | ||||||
|             '2007-03-01', |  | ||||||
|             '2007-08-29', |  | ||||||
|             '2007-10-10', |  | ||||||
|             '2007-12-15', |  | ||||||
|             '2008-02-01', |  | ||||||
|             '2008-09-01', |  | ||||||
|             '2009-04-04', |  | ||||||
|         ] |  | ||||||
|         for version in versions: |  | ||||||
|             self.write('%s\n' % version) |  | ||||||
|         self.finish() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class MetadataRequestHandler(tornado.web.RequestHandler): |  | ||||||
|     def print_data(self, data): |  | ||||||
|         if isinstance(data, dict): |  | ||||||
|             output = '' |  | ||||||
|             for key in data: |  | ||||||
|                 if key == '_name': |  | ||||||
|                     continue |  | ||||||
|                 output += key |  | ||||||
|                 if isinstance(data[key], dict): |  | ||||||
|                     if '_name' in data[key]: |  | ||||||
|                         output += '=' + str(data[key]['_name']) |  | ||||||
|                     else: |  | ||||||
|                         output += '/' |  | ||||||
|                 output += '\n' |  | ||||||
|             self.write(output[:-1]) # cut off last \n |  | ||||||
|         elif isinstance(data, list): |  | ||||||
|             self.write('\n'.join(data)) |  | ||||||
|         else: |  | ||||||
|             self.write(str(data)) |  | ||||||
|  |  | ||||||
|     def lookup(self, path, data): |  | ||||||
|         items = path.split('/') |  | ||||||
|         for item in items: |  | ||||||
|             if item: |  | ||||||
|                 if not isinstance(data, dict): |  | ||||||
|                     return data |  | ||||||
|                 if not item in data: |  | ||||||
|                     return None |  | ||||||
|                 data = data[item] |  | ||||||
|         return data |  | ||||||
|  |  | ||||||
|     def get(self, path): |  | ||||||
|         cc = self.application.controllers['Cloud'] |  | ||||||
|         meta_data = cc.get_metadata(self.request.remote_ip) |  | ||||||
|         if meta_data is None: |  | ||||||
|             _log.error('Failed to get metadata for ip: %s' % |  | ||||||
|                         self.request.remote_ip) |  | ||||||
|             raise tornado.web.HTTPError(404) |  | ||||||
|         data = self.lookup(path, meta_data) |  | ||||||
|         if data is None: |  | ||||||
|             raise tornado.web.HTTPError(404) |  | ||||||
|         self.print_data(data) |  | ||||||
|         self.finish() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class APIRequestHandler(tornado.web.RequestHandler): |  | ||||||
|     def get(self, controller_name): |  | ||||||
|         self.execute(controller_name) |  | ||||||
|  |  | ||||||
|     @tornado.web.asynchronous |  | ||||||
|     def execute(self, controller_name): |  | ||||||
|         # Obtain the appropriate controller for this request. |  | ||||||
|         try: |  | ||||||
|             controller = self.application.controllers[controller_name] |  | ||||||
|         except KeyError: |  | ||||||
|             self._error('unhandled', 'no controller named %s' % controller_name) |  | ||||||
|             return |  | ||||||
|  |  | ||||||
|         args = self.request.arguments |  | ||||||
|  |  | ||||||
|         # Read request signature. |  | ||||||
|         try: |  | ||||||
|             signature = args.pop('Signature')[0] |  | ||||||
|         except: |  | ||||||
|             raise tornado.web.HTTPError(400) |  | ||||||
|  |  | ||||||
|         # Make a copy of args for authentication and signature verification. |  | ||||||
|         auth_params = {} |  | ||||||
|         for key, value in args.items(): |  | ||||||
|             auth_params[key] = value[0] |  | ||||||
|  |  | ||||||
|         # Get requested action and remove authentication args for final request. |  | ||||||
|         try: |  | ||||||
|             action = args.pop('Action')[0] |  | ||||||
|             access = args.pop('AWSAccessKeyId')[0] |  | ||||||
|             args.pop('SignatureMethod') |  | ||||||
|             args.pop('SignatureVersion') |  | ||||||
|             args.pop('Version') |  | ||||||
|             args.pop('Timestamp') |  | ||||||
|         except: |  | ||||||
|             raise tornado.web.HTTPError(400) |  | ||||||
|  |  | ||||||
|         # Authenticate the request. |  | ||||||
|         try: |  | ||||||
|             (user, project) = manager.AuthManager().authenticate( |  | ||||||
|                 access, |  | ||||||
|                 signature, |  | ||||||
|                 auth_params, |  | ||||||
|                 self.request.method, |  | ||||||
|                 self.request.host, |  | ||||||
|                 self.request.path |  | ||||||
|             ) |  | ||||||
|  |  | ||||||
|         except exception.Error, ex: |  | ||||||
|             logging.debug("Authentication Failure: %s" % ex) |  | ||||||
|             raise tornado.web.HTTPError(403) |  | ||||||
|  |  | ||||||
|         _log.debug('action: %s' % action) |  | ||||||
|  |  | ||||||
|         for key, value in args.items(): |  | ||||||
|             _log.debug('arg: %s\t\tval: %s' % (key, value)) |  | ||||||
|  |  | ||||||
|         request = APIRequest(controller, action) |  | ||||||
|         context = APIRequestContext(self, user, project) |  | ||||||
|         d = request.send(context, **args) |  | ||||||
|         # d.addCallback(utils.debug) |  | ||||||
|  |  | ||||||
|         # TODO: Wrap response in AWS XML format |  | ||||||
|         d.addCallbacks(self._write_callback, self._error_callback) |  | ||||||
|  |  | ||||||
|     def _write_callback(self, data): |  | ||||||
|         self.set_header('Content-Type', 'text/xml') |  | ||||||
|         self.write(data) |  | ||||||
|         self.finish() |  | ||||||
|  |  | ||||||
|     def _error_callback(self, failure): |  | ||||||
|         try: |  | ||||||
|             failure.raiseException() |  | ||||||
|         except exception.ApiError as ex: |  | ||||||
|             if ex.code: |  | ||||||
|                 self._error(ex.code, ex.message) |  | ||||||
|             else: |  | ||||||
|                 self._error(type(ex).__name__, ex.message) |  | ||||||
|         # TODO(vish): do something more useful with unknown exceptions |  | ||||||
|         except Exception as ex: |  | ||||||
|             self._error(type(ex).__name__, str(ex)) |  | ||||||
|             raise |  | ||||||
|  |  | ||||||
|     def post(self, controller_name): |  | ||||||
|         self.execute(controller_name) |  | ||||||
|  |  | ||||||
|     def _error(self, code, message): |  | ||||||
|         self._status_code = 400 |  | ||||||
|         self.set_header('Content-Type', 'text/xml') |  | ||||||
|         self.write('<?xml version="1.0"?>\n') |  | ||||||
|         self.write('<Response><Errors><Error><Code>%s</Code>' |  | ||||||
|                    '<Message>%s</Message></Error></Errors>' |  | ||||||
|                    '<RequestID>?</RequestID></Response>' % (code, message)) |  | ||||||
|         self.finish() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class APIServerApplication(tornado.web.Application): |  | ||||||
|     def __init__(self, controllers): |  | ||||||
|         tornado.web.Application.__init__(self, [ |  | ||||||
|             (r'/', RootRequestHandler), |  | ||||||
|             (r'/cloudpipe/(.*)', nova.cloudpipe.api.CloudPipeRequestHandler), |  | ||||||
|             (r'/cloudpipe', nova.cloudpipe.api.CloudPipeRequestHandler), |  | ||||||
|             (r'/services/([A-Za-z0-9]+)/', APIRequestHandler), |  | ||||||
|             (r'/latest/([-A-Za-z0-9/]*)', MetadataRequestHandler), |  | ||||||
|             (r'/2009-04-04/([-A-Za-z0-9/]*)', MetadataRequestHandler), |  | ||||||
|             (r'/2008-09-01/([-A-Za-z0-9/]*)', MetadataRequestHandler), |  | ||||||
|             (r'/2008-02-01/([-A-Za-z0-9/]*)', MetadataRequestHandler), |  | ||||||
|             (r'/2007-12-15/([-A-Za-z0-9/]*)', MetadataRequestHandler), |  | ||||||
|             (r'/2007-10-10/([-A-Za-z0-9/]*)', MetadataRequestHandler), |  | ||||||
|             (r'/2007-08-29/([-A-Za-z0-9/]*)', MetadataRequestHandler), |  | ||||||
|             (r'/2007-03-01/([-A-Za-z0-9/]*)', MetadataRequestHandler), |  | ||||||
|             (r'/2007-01-19/([-A-Za-z0-9/]*)', MetadataRequestHandler), |  | ||||||
|             (r'/1.0/([-A-Za-z0-9/]*)', MetadataRequestHandler), |  | ||||||
|         ], pool=multiprocessing.Pool(4)) |  | ||||||
|         self.controllers = controllers |  | ||||||
| @@ -1,952 +0,0 @@ | |||||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 |  | ||||||
|  |  | ||||||
| # Copyright 2010 United States Government as represented by the |  | ||||||
| # Administrator of the National Aeronautics and Space Administration. |  | ||||||
| # All Rights Reserved. |  | ||||||
| # |  | ||||||
| #    Licensed under the Apache License, Version 2.0 (the "License"); you may |  | ||||||
| #    not use this file except in compliance with the License. You may obtain |  | ||||||
| #    a copy of the License at |  | ||||||
| # |  | ||||||
| #         http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
| # |  | ||||||
| #    Unless required by applicable law or agreed to in writing, software |  | ||||||
| #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |  | ||||||
| #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |  | ||||||
| #    License for the specific language governing permissions and limitations |  | ||||||
| #    under the License. |  | ||||||
|  |  | ||||||
| """ |  | ||||||
| Cloud Controller: Implementation of EC2 REST API calls, which are |  | ||||||
| dispatched to other nodes via AMQP RPC. State is via distributed |  | ||||||
| datastore. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import base64 |  | ||||||
| import datetime |  | ||||||
| import logging |  | ||||||
| import os |  | ||||||
| import time |  | ||||||
|  |  | ||||||
| import IPy |  | ||||||
|  |  | ||||||
| from twisted.internet import defer |  | ||||||
|  |  | ||||||
| from nova import crypto |  | ||||||
| from nova import db |  | ||||||
| from nova import exception |  | ||||||
| from nova import flags |  | ||||||
| from nova import quota |  | ||||||
| from nova import rpc |  | ||||||
| from nova import utils |  | ||||||
| from nova.auth import rbac |  | ||||||
| from nova.compute.instance_types import INSTANCE_TYPES |  | ||||||
| from nova.endpoint import images |  | ||||||
|  |  | ||||||
|  |  | ||||||
| FLAGS = flags.FLAGS |  | ||||||
| flags.DECLARE('storage_availability_zone', 'nova.volume.manager') |  | ||||||
|  |  | ||||||
| InvalidInputException = exception.InvalidInputException |  | ||||||
|  |  | ||||||
| class QuotaError(exception.ApiError): |  | ||||||
|     """Quota Exceeeded""" |  | ||||||
|     pass |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def _gen_key(context, user_id, key_name): |  | ||||||
|     """Generate a key |  | ||||||
|  |  | ||||||
|     This is a module level method because it is slow and we need to defer |  | ||||||
|     it into a process pool.""" |  | ||||||
|     try: |  | ||||||
|         # NOTE(vish): generating key pair is slow so check for legal |  | ||||||
|         #             creation before creating key_pair |  | ||||||
|         try: |  | ||||||
|             db.key_pair_get(context, user_id, key_name) |  | ||||||
|             raise exception.Duplicate("The key_pair %s already exists" |  | ||||||
|                                       % key_name) |  | ||||||
|         except exception.NotFound: |  | ||||||
|             pass |  | ||||||
|         private_key, public_key, fingerprint = crypto.generate_key_pair() |  | ||||||
|         key = {} |  | ||||||
|         key['user_id'] = user_id |  | ||||||
|         key['name'] = key_name |  | ||||||
|         key['public_key'] = public_key |  | ||||||
|         key['fingerprint'] = fingerprint |  | ||||||
|         db.key_pair_create(context, key) |  | ||||||
|         return {'private_key': private_key, 'fingerprint': fingerprint} |  | ||||||
|     except Exception as ex: |  | ||||||
|         return {'exception': ex} |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class CloudController(object): |  | ||||||
|     """ CloudController provides the critical dispatch between |  | ||||||
|  inbound API calls through the endpoint and messages |  | ||||||
|  sent to the other nodes. |  | ||||||
| """ |  | ||||||
|     def __init__(self): |  | ||||||
|         self.network_manager = utils.import_object(FLAGS.network_manager) |  | ||||||
|         self.setup() |  | ||||||
|  |  | ||||||
|     def __str__(self): |  | ||||||
|         return 'CloudController' |  | ||||||
|  |  | ||||||
|     def setup(self): |  | ||||||
|         """ Ensure the keychains and folders exist. """ |  | ||||||
|         # FIXME(ja): this should be moved to a nova-manage command, |  | ||||||
|         # if not setup throw exceptions instead of running |  | ||||||
|         # Create keys folder, if it doesn't exist |  | ||||||
|         if not os.path.exists(FLAGS.keys_path): |  | ||||||
|             os.makedirs(FLAGS.keys_path) |  | ||||||
|         # Gen root CA, if we don't have one |  | ||||||
|         root_ca_path = os.path.join(FLAGS.ca_path, FLAGS.ca_file) |  | ||||||
|         if not os.path.exists(root_ca_path): |  | ||||||
|             start = os.getcwd() |  | ||||||
|             os.chdir(FLAGS.ca_path) |  | ||||||
|             # TODO(vish): Do this with M2Crypto instead |  | ||||||
|             utils.runthis("Generating root CA: %s", "sh genrootca.sh") |  | ||||||
|             os.chdir(start) |  | ||||||
|  |  | ||||||
|     def _get_mpi_data(self, project_id): |  | ||||||
|         result = {} |  | ||||||
|         for instance in db.instance_get_by_project(None, project_id): |  | ||||||
|             if instance['fixed_ip']: |  | ||||||
|                 line = '%s slots=%d' % (instance['fixed_ip']['str_id'], |  | ||||||
|                     INSTANCE_TYPES[instance['instance_type']]['vcpus']) |  | ||||||
|                 key = str(instance['key_name']) |  | ||||||
|                 if key in result: |  | ||||||
|                     result[key].append(line) |  | ||||||
|                 else: |  | ||||||
|                     result[key] = [line] |  | ||||||
|         return result |  | ||||||
|  |  | ||||||
|     def _trigger_refresh_security_group(self, security_group): |  | ||||||
|         nodes = set([instance.host for instance in security_group.instances]) |  | ||||||
|         for node in nodes: |  | ||||||
|             rpc.call('%s.%s' % (FLAGS.compute_topic, node), |  | ||||||
|                      { "method": "refresh_security_group", |  | ||||||
|                        "args": { "context": None, |  | ||||||
|                                  "security_group_id": security_group.id}}) |  | ||||||
|  |  | ||||||
|     def get_metadata(self, address): |  | ||||||
|         instance_ref = db.fixed_ip_get_instance(None, address) |  | ||||||
|         if instance_ref is None: |  | ||||||
|             return None |  | ||||||
|         mpi = self._get_mpi_data(instance_ref['project_id']) |  | ||||||
|         if instance_ref['key_name']: |  | ||||||
|             keys = { |  | ||||||
|                 '0': { |  | ||||||
|                     '_name': instance_ref['key_name'], |  | ||||||
|                     'openssh-key': instance_ref['key_data'] |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         else: |  | ||||||
|             keys = '' |  | ||||||
|         hostname = instance_ref['hostname'] |  | ||||||
|         floating_ip = db.instance_get_floating_address(None, |  | ||||||
|                                                        instance_ref['id']) |  | ||||||
|         data = { |  | ||||||
|             'user-data': base64.b64decode(instance_ref['user_data']), |  | ||||||
|             'meta-data': { |  | ||||||
|                 'ami-id': instance_ref['image_id'], |  | ||||||
|                 'ami-launch-index': instance_ref['launch_index'], |  | ||||||
|                 'ami-manifest-path': 'FIXME', |  | ||||||
|                 'block-device-mapping': {  # TODO(vish): replace with real data |  | ||||||
|                     'ami': 'sda1', |  | ||||||
|                     'ephemeral0': 'sda2', |  | ||||||
|                     'root': '/dev/sda1', |  | ||||||
|                     'swap': 'sda3' |  | ||||||
|                 }, |  | ||||||
|                 'hostname': hostname, |  | ||||||
|                 'instance-action': 'none', |  | ||||||
|                 'instance-id': instance_ref['str_id'], |  | ||||||
|                 'instance-type': instance_ref['instance_type'], |  | ||||||
|                 'local-hostname': hostname, |  | ||||||
|                 'local-ipv4': address, |  | ||||||
|                 'kernel-id': instance_ref['kernel_id'], |  | ||||||
|                 'placement': { |  | ||||||
|                     'availability-zone': 'nova' # TODO(vish): real zone |  | ||||||
|                 }, |  | ||||||
|                 'public-hostname': hostname, |  | ||||||
|                 'public-ipv4': floating_ip or '', |  | ||||||
|                 'public-keys': keys, |  | ||||||
|                 'ramdisk-id': instance_ref['ramdisk_id'], |  | ||||||
|                 'reservation-id': instance_ref['reservation_id'], |  | ||||||
|                 'security-groups': '', |  | ||||||
|                 'mpi': mpi |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if False:  # TODO(vish): store ancestor ids |  | ||||||
|             data['ancestor-ami-ids'] = [] |  | ||||||
|         if False:  # TODO(vish): store product codes |  | ||||||
|             data['product-codes'] = [] |  | ||||||
|         return data |  | ||||||
|  |  | ||||||
|     @rbac.allow('all') |  | ||||||
|     def describe_availability_zones(self, context, **kwargs): |  | ||||||
|         return {'availabilityZoneInfo': [{'zoneName': 'nova', |  | ||||||
|                                           'zoneState': 'available'}]} |  | ||||||
|  |  | ||||||
|     @rbac.allow('all') |  | ||||||
|     def describe_regions(self, context, region_name=None, **kwargs): |  | ||||||
|         if FLAGS.region_list: |  | ||||||
|             regions = [] |  | ||||||
|             for region in FLAGS.region_list: |  | ||||||
|                 name, _sep, url = region.partition('=') |  | ||||||
|                 regions.append({'regionName': name, |  | ||||||
|                                 'regionEndpoint': url}) |  | ||||||
|         else: |  | ||||||
|             regions = [{'regionName': 'nova', |  | ||||||
|                         'regionEndpoint': FLAGS.ec2_url}] |  | ||||||
|         if region_name: |  | ||||||
|             regions = [r for r in regions if r['regionName'] in region_name] |  | ||||||
|         return {'regionInfo': regions } |  | ||||||
|  |  | ||||||
|     @rbac.allow('all') |  | ||||||
|     def describe_snapshots(self, |  | ||||||
|                            context, |  | ||||||
|                            snapshot_id=None, |  | ||||||
|                            owner=None, |  | ||||||
|                            restorable_by=None, |  | ||||||
|                            **kwargs): |  | ||||||
|         return {'snapshotSet': [{'snapshotId': 'fixme', |  | ||||||
|                                  'volumeId': 'fixme', |  | ||||||
|                                  'status': 'fixme', |  | ||||||
|                                  'startTime': 'fixme', |  | ||||||
|                                  'progress': 'fixme', |  | ||||||
|                                  'ownerId': 'fixme', |  | ||||||
|                                  'volumeSize': 0, |  | ||||||
|                                  'description': 'fixme'}]} |  | ||||||
|  |  | ||||||
|     @rbac.allow('all') |  | ||||||
|     def describe_key_pairs(self, context, key_name=None, **kwargs): |  | ||||||
|         key_pairs = db.key_pair_get_all_by_user(context, context.user.id) |  | ||||||
|         if not key_name is None: |  | ||||||
|             key_pairs = [x for x in key_pairs if x['name'] in key_name] |  | ||||||
|  |  | ||||||
|         result = [] |  | ||||||
|         for key_pair in key_pairs: |  | ||||||
|             # filter out the vpn keys |  | ||||||
|             suffix = FLAGS.vpn_key_suffix |  | ||||||
|             if context.user.is_admin() or not key_pair['name'].endswith(suffix): |  | ||||||
|                 result.append({ |  | ||||||
|                     'keyName': key_pair['name'], |  | ||||||
|                     'keyFingerprint': key_pair['fingerprint'], |  | ||||||
|                 }) |  | ||||||
|  |  | ||||||
|         return {'keypairsSet': result} |  | ||||||
|  |  | ||||||
|     @rbac.allow('all') |  | ||||||
|     def create_key_pair(self, context, key_name, **kwargs): |  | ||||||
|         dcall = defer.Deferred() |  | ||||||
|         pool = context.handler.application.settings.get('pool') |  | ||||||
|         def _complete(kwargs): |  | ||||||
|             if 'exception' in kwargs: |  | ||||||
|                 dcall.errback(kwargs['exception']) |  | ||||||
|                 return |  | ||||||
|             dcall.callback({'keyName': key_name, |  | ||||||
|                 'keyFingerprint': kwargs['fingerprint'], |  | ||||||
|                 'keyMaterial': kwargs['private_key']}) |  | ||||||
|         # TODO(vish): when context is no longer an object, pass it here |  | ||||||
|         pool.apply_async(_gen_key, [None, context.user.id, key_name], |  | ||||||
|             callback=_complete) |  | ||||||
|         return dcall |  | ||||||
|  |  | ||||||
|     @rbac.allow('all') |  | ||||||
|     def delete_key_pair(self, context, key_name, **kwargs): |  | ||||||
|         try: |  | ||||||
|             db.key_pair_destroy(context, context.user.id, key_name) |  | ||||||
|         except exception.NotFound: |  | ||||||
|             # aws returns true even if the key doesn't exist |  | ||||||
|             pass |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     @rbac.allow('all') |  | ||||||
|     def describe_security_groups(self, context, group_name=None, **kwargs): |  | ||||||
|         if context.user.is_admin(): |  | ||||||
|             groups = db.security_group_get_all(context) |  | ||||||
|         else: |  | ||||||
|             groups = db.security_group_get_by_project(context, |  | ||||||
|                                                       context.project.id) |  | ||||||
|         groups = [self._format_security_group(context, g) for g in groups] |  | ||||||
|         if not group_name is None: |  | ||||||
|             groups = [g for g in groups if g.name in group_name] |  | ||||||
|  |  | ||||||
|         return {'securityGroupInfo': groups } |  | ||||||
|  |  | ||||||
|     def _format_security_group(self, context, group): |  | ||||||
|         g = {} |  | ||||||
|         g['groupDescription'] = group.description |  | ||||||
|         g['groupName'] = group.name |  | ||||||
|         g['ownerId'] = context.user.id |  | ||||||
|         g['ipPermissions'] = [] |  | ||||||
|         for rule in group.rules: |  | ||||||
|             r = {} |  | ||||||
|             r['ipProtocol'] = rule.protocol |  | ||||||
|             r['fromPort'] = rule.from_port |  | ||||||
|             r['toPort'] = rule.to_port |  | ||||||
|             r['groups'] = [] |  | ||||||
|             r['ipRanges'] = [] |  | ||||||
|             if rule.group_id: |  | ||||||
|                 source_group = db.security_group_get(context, rule.group_id) |  | ||||||
|                 r['groups'] += [{'groupName': source_group.name, |  | ||||||
|                                  'userId': source_group.user_id}] |  | ||||||
|             else: |  | ||||||
|                 r['ipRanges'] += [{'cidrIp': rule.cidr}] |  | ||||||
|             g['ipPermissions'] += [r] |  | ||||||
|         return g |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     def _authorize_revoke_rule_args_to_dict(self, context, |  | ||||||
|                                             to_port=None, from_port=None, |  | ||||||
|                                             ip_protocol=None, cidr_ip=None, |  | ||||||
|                                             user_id=None, |  | ||||||
|                                             source_security_group_name=None, |  | ||||||
|                                             source_security_group_owner_id=None): |  | ||||||
|  |  | ||||||
|         values = {} |  | ||||||
|  |  | ||||||
|         if source_security_group_name: |  | ||||||
|             source_project_id = self._get_source_project_id(context, |  | ||||||
|                 source_security_group_owner_id) |  | ||||||
|  |  | ||||||
|             source_security_group = \ |  | ||||||
|                     db.security_group_get_by_name(context, |  | ||||||
|                                                   source_project_id, |  | ||||||
|                                                   source_security_group_name) |  | ||||||
|             values['group_id'] = source_security_group.id |  | ||||||
|         elif cidr_ip: |  | ||||||
|             # If this fails, it throws an exception. This is what we want. |  | ||||||
|             IPy.IP(cidr_ip) |  | ||||||
|             values['cidr'] = cidr_ip |  | ||||||
|         else: |  | ||||||
|             return { 'return': False } |  | ||||||
|  |  | ||||||
|         if ip_protocol and from_port and to_port: |  | ||||||
|             from_port   = int(from_port) |  | ||||||
|             to_port     = int(to_port) |  | ||||||
|             ip_protocol = str(ip_protocol) |  | ||||||
|  |  | ||||||
|             if ip_protocol.upper() not in ['TCP','UDP','ICMP']: |  | ||||||
|                  raise InvalidInputException('%s is not a valid ipProtocol' % |  | ||||||
|                                                  (ip_protocol,)) |  | ||||||
|             if ((min(from_port, to_port) < -1) or |  | ||||||
|                 (max(from_port, to_port) > 65535)): |  | ||||||
|                  raise InvalidInputException('Invalid port range') |  | ||||||
|  |  | ||||||
|             values['protocol'] = ip_protocol |  | ||||||
|             values['from_port'] = from_port |  | ||||||
|             values['to_port'] = to_port |  | ||||||
|         else: |  | ||||||
|             # If cidr based filtering, protocol and ports are mandatory |  | ||||||
|             if 'cidr' in values: |  | ||||||
|                 return None |  | ||||||
|  |  | ||||||
|         return values |  | ||||||
|  |  | ||||||
|     @rbac.allow('netadmin') |  | ||||||
|     def revoke_security_group_ingress(self, context, group_name, **kwargs): |  | ||||||
|         security_group = db.security_group_get_by_name(context, |  | ||||||
|                                                        context.project.id, |  | ||||||
|                                                        group_name) |  | ||||||
|  |  | ||||||
|         criteria = self._authorize_revoke_rule_args_to_dict(context, **kwargs) |  | ||||||
|  |  | ||||||
|         for rule in security_group.rules: |  | ||||||
|             for (k,v) in criteria.iteritems(): |  | ||||||
|                 if getattr(rule, k, False) != v: |  | ||||||
|                     break |  | ||||||
|             # If we make it here, we have a match |  | ||||||
|             db.security_group_rule_destroy(context, rule.id) |  | ||||||
|  |  | ||||||
|         self._trigger_refresh_security_group(security_group) |  | ||||||
|  |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     # TODO(soren): Dupe detection. Adding the same rule twice actually |  | ||||||
|     #              adds the same rule twice to the rule set, which is |  | ||||||
|     #              pointless. |  | ||||||
|     # TODO(soren): This has only been tested with Boto as the client. |  | ||||||
|     #              Unfortunately, it seems Boto is using an old API |  | ||||||
|     #              for these operations, so support for newer API versions |  | ||||||
|     #              is sketchy. |  | ||||||
|     @rbac.allow('netadmin') |  | ||||||
|     def authorize_security_group_ingress(self, context, group_name, **kwargs): |  | ||||||
|         security_group = db.security_group_get_by_name(context, |  | ||||||
|                                                        context.project.id, |  | ||||||
|                                                        group_name) |  | ||||||
|  |  | ||||||
|         values = self._authorize_revoke_rule_args_to_dict(context, **kwargs) |  | ||||||
|         values['parent_group_id'] = security_group.id |  | ||||||
|  |  | ||||||
|         security_group_rule = db.security_group_rule_create(context, values) |  | ||||||
|  |  | ||||||
|         self._trigger_refresh_security_group(security_group) |  | ||||||
|  |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     def _get_source_project_id(self, context, source_security_group_owner_id): |  | ||||||
|         if source_security_group_owner_id: |  | ||||||
|         # Parse user:project for source group. |  | ||||||
|             source_parts = source_security_group_owner_id.split(':') |  | ||||||
|  |  | ||||||
|             # If no project name specified, assume it's same as user name. |  | ||||||
|             # Since we're looking up by project name, the user name is not |  | ||||||
|             # used here.  It's only read for EC2 API compatibility. |  | ||||||
|             if len(source_parts) == 2: |  | ||||||
|                 source_project_id = source_parts[1] |  | ||||||
|             else: |  | ||||||
|                 source_project_id = source_parts[0] |  | ||||||
|         else: |  | ||||||
|             source_project_id = context.project.id |  | ||||||
|  |  | ||||||
|         return source_project_id |  | ||||||
|  |  | ||||||
|     @rbac.allow('netadmin') |  | ||||||
|     def create_security_group(self, context, group_name, group_description): |  | ||||||
|         if db.securitygroup_exists(context, context.project.id, group_name): |  | ||||||
|             raise exception.ApiError('group %s already exists' % group_name) |  | ||||||
|  |  | ||||||
|         group = {'user_id' : context.user.id, |  | ||||||
|                  'project_id': context.project.id, |  | ||||||
|                  'name': group_name, |  | ||||||
|                  'description': group_description} |  | ||||||
|         group_ref = db.security_group_create(context, group) |  | ||||||
|  |  | ||||||
|         return {'securityGroupSet': [self._format_security_group(context, |  | ||||||
|                                                                  group_ref)]} |  | ||||||
|  |  | ||||||
|     @rbac.allow('netadmin') |  | ||||||
|     def delete_security_group(self, context, group_name, **kwargs): |  | ||||||
|         security_group = db.security_group_get_by_name(context, |  | ||||||
|                                                        context.project.id, |  | ||||||
|                                                        group_name) |  | ||||||
|         db.security_group_destroy(context, security_group.id) |  | ||||||
|         return True |  | ||||||
|  |  | ||||||
|     @rbac.allow('projectmanager', 'sysadmin') |  | ||||||
|     def get_console_output(self, context, instance_id, **kwargs): |  | ||||||
|         # instance_id is passed in as a list of instances |  | ||||||
|         instance_ref = db.instance_get_by_str(context, instance_id[0]) |  | ||||||
|         return rpc.call('%s.%s' % (FLAGS.compute_topic, |  | ||||||
|                                    instance_ref['host']), |  | ||||||
|                         {"method": "get_console_output", |  | ||||||
|                          "args": {"context": None, |  | ||||||
|                                   "instance_id": instance_ref['id']}}) |  | ||||||
|  |  | ||||||
|     @rbac.allow('projectmanager', 'sysadmin') |  | ||||||
|     def describe_volumes(self, context, **kwargs): |  | ||||||
|         if context.user.is_admin(): |  | ||||||
|             volumes = db.volume_get_all(context) |  | ||||||
|         else: |  | ||||||
|             volumes = db.volume_get_by_project(context, context.project.id) |  | ||||||
|  |  | ||||||
|         volumes = [self._format_volume(context, v) for v in volumes] |  | ||||||
|  |  | ||||||
|         return {'volumeSet': volumes} |  | ||||||
|  |  | ||||||
|     def _format_volume(self, context, volume): |  | ||||||
|         v = {} |  | ||||||
|         v['volumeId'] = volume['str_id'] |  | ||||||
|         v['status'] = volume['status'] |  | ||||||
|         v['size'] = volume['size'] |  | ||||||
|         v['availabilityZone'] = volume['availability_zone'] |  | ||||||
|         v['createTime'] = volume['created_at'] |  | ||||||
|         if context.user.is_admin(): |  | ||||||
|             v['status'] = '%s (%s, %s, %s, %s)' % ( |  | ||||||
|                 volume['status'], |  | ||||||
|                 volume['user_id'], |  | ||||||
|                 volume['host'], |  | ||||||
|                 volume['instance_id'], |  | ||||||
|                 volume['mountpoint']) |  | ||||||
|         if volume['attach_status'] == 'attached': |  | ||||||
|             v['attachmentSet'] = [{'attachTime': volume['attach_time'], |  | ||||||
|                                    'deleteOnTermination': False, |  | ||||||
|                                    'device': volume['mountpoint'], |  | ||||||
|                                    'instanceId': volume['instance_id'], |  | ||||||
|                                    'status': 'attached', |  | ||||||
|                                    'volume_id': volume['str_id']}] |  | ||||||
|         else: |  | ||||||
|             v['attachmentSet'] = [{}] |  | ||||||
|         return v |  | ||||||
|  |  | ||||||
|     @rbac.allow('projectmanager', 'sysadmin') |  | ||||||
|     def create_volume(self, context, size, **kwargs): |  | ||||||
|         # check quota |  | ||||||
|         size = int(size) |  | ||||||
|         if quota.allowed_volumes(context, 1, size) < 1: |  | ||||||
|             logging.warn("Quota exceeeded for %s, tried to create %sG volume", |  | ||||||
|                          context.project.id, size) |  | ||||||
|             raise QuotaError("Volume quota exceeded. You cannot " |  | ||||||
|                              "create a volume of size %s" % |  | ||||||
|                              size) |  | ||||||
|         vol = {} |  | ||||||
|         vol['size'] = size |  | ||||||
|         vol['user_id'] = context.user.id |  | ||||||
|         vol['project_id'] = context.project.id |  | ||||||
|         vol['availability_zone'] = FLAGS.storage_availability_zone |  | ||||||
|         vol['status'] = "creating" |  | ||||||
|         vol['attach_status'] = "detached" |  | ||||||
|         volume_ref = db.volume_create(context, vol) |  | ||||||
|  |  | ||||||
|         rpc.cast(FLAGS.scheduler_topic, |  | ||||||
|                  {"method": "create_volume", |  | ||||||
|                   "args": {"context": None, |  | ||||||
|                            "topic": FLAGS.volume_topic, |  | ||||||
|                            "volume_id": volume_ref['id']}}) |  | ||||||
|  |  | ||||||
|         return {'volumeSet': [self._format_volume(context, volume_ref)]} |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @rbac.allow('projectmanager', 'sysadmin') |  | ||||||
|     def attach_volume(self, context, volume_id, instance_id, device, **kwargs): |  | ||||||
|         volume_ref = db.volume_get_by_str(context, volume_id) |  | ||||||
|         # TODO(vish): abstract status checking? |  | ||||||
|         if volume_ref['status'] != "available": |  | ||||||
|             raise exception.ApiError("Volume status must be available") |  | ||||||
|         if volume_ref['attach_status'] == "attached": |  | ||||||
|             raise exception.ApiError("Volume is already attached") |  | ||||||
|         instance_ref = db.instance_get_by_str(context, instance_id) |  | ||||||
|         host = instance_ref['host'] |  | ||||||
|         rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host), |  | ||||||
|                                 {"method": "attach_volume", |  | ||||||
|                                  "args": {"context": None, |  | ||||||
|                                           "volume_id": volume_ref['id'], |  | ||||||
|                                           "instance_id": instance_ref['id'], |  | ||||||
|                                           "mountpoint": device}}) |  | ||||||
|         return defer.succeed({'attachTime': volume_ref['attach_time'], |  | ||||||
|                               'device': volume_ref['mountpoint'], |  | ||||||
|                               'instanceId': instance_ref['id'], |  | ||||||
|                               'requestId': context.request_id, |  | ||||||
|                               'status': volume_ref['attach_status'], |  | ||||||
|                               'volumeId': volume_ref['id']}) |  | ||||||
|  |  | ||||||
|     @rbac.allow('projectmanager', 'sysadmin') |  | ||||||
|     def detach_volume(self, context, volume_id, **kwargs): |  | ||||||
|         volume_ref = db.volume_get_by_str(context, volume_id) |  | ||||||
|         instance_ref = db.volume_get_instance(context, volume_ref['id']) |  | ||||||
|         if not instance_ref: |  | ||||||
|             raise exception.ApiError("Volume isn't attached to anything!") |  | ||||||
|         # TODO(vish): abstract status checking? |  | ||||||
|         if volume_ref['status'] == "available": |  | ||||||
|             raise exception.ApiError("Volume is already detached") |  | ||||||
|         try: |  | ||||||
|             host = instance_ref['host'] |  | ||||||
|             rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host), |  | ||||||
|                                 {"method": "detach_volume", |  | ||||||
|                                  "args": {"context": None, |  | ||||||
|                                           "instance_id": instance_ref['id'], |  | ||||||
|                                           "volume_id": volume_ref['id']}}) |  | ||||||
|         except exception.NotFound: |  | ||||||
|             # If the instance doesn't exist anymore, |  | ||||||
|             # then we need to call detach blind |  | ||||||
|             db.volume_detached(context) |  | ||||||
|         return defer.succeed({'attachTime': volume_ref['attach_time'], |  | ||||||
|                               'device': volume_ref['mountpoint'], |  | ||||||
|                               'instanceId': instance_ref['str_id'], |  | ||||||
|                               'requestId': context.request_id, |  | ||||||
|                               'status': volume_ref['attach_status'], |  | ||||||
|                               'volumeId': volume_ref['id']}) |  | ||||||
|  |  | ||||||
|     def _convert_to_set(self, lst, label): |  | ||||||
|         if lst == None or lst == []: |  | ||||||
|             return None |  | ||||||
|         if not isinstance(lst, list): |  | ||||||
|             lst = [lst] |  | ||||||
|         return [{label: x} for x in lst] |  | ||||||
|  |  | ||||||
|     @rbac.allow('all') |  | ||||||
|     def describe_instances(self, context, **kwargs): |  | ||||||
|         return defer.succeed(self._format_describe_instances(context)) |  | ||||||
|  |  | ||||||
|     def _format_describe_instances(self, context): |  | ||||||
|         return { 'reservationSet': self._format_instances(context) } |  | ||||||
|  |  | ||||||
|     def _format_run_instances(self, context, reservation_id): |  | ||||||
|         i = self._format_instances(context, reservation_id) |  | ||||||
|         assert len(i) == 1 |  | ||||||
|         return i[0] |  | ||||||
|  |  | ||||||
|     def _format_instances(self, context, reservation_id=None): |  | ||||||
|         reservations = {} |  | ||||||
|         if reservation_id: |  | ||||||
|             instances = db.instance_get_by_reservation(context, |  | ||||||
|                                                        reservation_id) |  | ||||||
|         else: |  | ||||||
|             if context.user.is_admin(): |  | ||||||
|                 instances = db.instance_get_all(context) |  | ||||||
|             else: |  | ||||||
|                 instances = db.instance_get_by_project(context, |  | ||||||
|                                                        context.project.id) |  | ||||||
|         for instance in instances: |  | ||||||
|             if not context.user.is_admin(): |  | ||||||
|                 if instance['image_id'] == FLAGS.vpn_image_id: |  | ||||||
|                     continue |  | ||||||
|             i = {} |  | ||||||
|             i['instanceId'] = instance['str_id'] |  | ||||||
|             i['imageId'] = instance['image_id'] |  | ||||||
|             i['instanceState'] = { |  | ||||||
|                 'code': instance['state'], |  | ||||||
|                 'name': instance['state_description'] |  | ||||||
|             } |  | ||||||
|             fixed_addr = None |  | ||||||
|             floating_addr = None |  | ||||||
|             if instance['fixed_ip']: |  | ||||||
|                 fixed_addr = instance['fixed_ip']['str_id'] |  | ||||||
|                 if instance['fixed_ip']['floating_ips']: |  | ||||||
|                     fixed = instance['fixed_ip'] |  | ||||||
|                     floating_addr = fixed['floating_ips'][0]['str_id'] |  | ||||||
|             i['privateDnsName'] = fixed_addr |  | ||||||
|             i['publicDnsName'] = floating_addr |  | ||||||
|             i['dnsName'] = i['publicDnsName'] or i['privateDnsName'] |  | ||||||
|             i['keyName'] = instance['key_name'] |  | ||||||
|             if context.user.is_admin(): |  | ||||||
|                 i['keyName'] = '%s (%s, %s)' % (i['keyName'], |  | ||||||
|                     instance['project_id'], |  | ||||||
|                     instance['host']) |  | ||||||
|             i['productCodesSet'] = self._convert_to_set([], 'product_codes') |  | ||||||
|             i['instanceType'] = instance['instance_type'] |  | ||||||
|             i['launchTime'] = instance['created_at'] |  | ||||||
|             i['amiLaunchIndex'] = instance['launch_index'] |  | ||||||
|             if not reservations.has_key(instance['reservation_id']): |  | ||||||
|                 r = {} |  | ||||||
|                 r['reservationId'] = instance['reservation_id'] |  | ||||||
|                 r['ownerId'] = instance['project_id'] |  | ||||||
|                 r['groupSet'] = self._convert_to_set([], 'groups') |  | ||||||
|                 r['instancesSet'] = [] |  | ||||||
|                 reservations[instance['reservation_id']] = r |  | ||||||
|             reservations[instance['reservation_id']]['instancesSet'].append(i) |  | ||||||
|  |  | ||||||
|         return list(reservations.values()) |  | ||||||
|  |  | ||||||
|     @rbac.allow('all') |  | ||||||
|     def describe_addresses(self, context, **kwargs): |  | ||||||
|         return self.format_addresses(context) |  | ||||||
|  |  | ||||||
|     def format_addresses(self, context): |  | ||||||
|         addresses = [] |  | ||||||
|         if context.user.is_admin(): |  | ||||||
|             iterator = db.floating_ip_get_all(context) |  | ||||||
|         else: |  | ||||||
|             iterator = db.floating_ip_get_by_project(context, |  | ||||||
|                                                      context.project.id) |  | ||||||
|         for floating_ip_ref in iterator: |  | ||||||
|             address = floating_ip_ref['str_id'] |  | ||||||
|             instance_id = None |  | ||||||
|             if (floating_ip_ref['fixed_ip'] |  | ||||||
|                 and floating_ip_ref['fixed_ip']['instance']): |  | ||||||
|                 instance_id = floating_ip_ref['fixed_ip']['instance']['str_id'] |  | ||||||
|             address_rv = {'public_ip': address, |  | ||||||
|                           'instance_id': instance_id} |  | ||||||
|             if context.user.is_admin(): |  | ||||||
|                 details = "%s (%s)" % (address_rv['instance_id'], |  | ||||||
|                                        floating_ip_ref['project_id']) |  | ||||||
|                 address_rv['instance_id'] = details |  | ||||||
|             addresses.append(address_rv) |  | ||||||
|         return {'addressesSet': addresses} |  | ||||||
|  |  | ||||||
|     @rbac.allow('netadmin') |  | ||||||
|     @defer.inlineCallbacks |  | ||||||
|     def allocate_address(self, context, **kwargs): |  | ||||||
|         # check quota |  | ||||||
|         if quota.allowed_floating_ips(context, 1) < 1: |  | ||||||
|             logging.warn("Quota exceeeded for %s, tried to allocate address", |  | ||||||
|                          context.project.id) |  | ||||||
|             raise QuotaError("Address quota exceeded. You cannot " |  | ||||||
|                              "allocate any more addresses") |  | ||||||
|         network_topic = yield self._get_network_topic(context) |  | ||||||
|         public_ip = yield rpc.call(network_topic, |  | ||||||
|                          {"method": "allocate_floating_ip", |  | ||||||
|                           "args": {"context": None, |  | ||||||
|                                    "project_id": context.project.id}}) |  | ||||||
|         defer.returnValue({'addressSet': [{'publicIp': public_ip}]}) |  | ||||||
|  |  | ||||||
|     @rbac.allow('netadmin') |  | ||||||
|     @defer.inlineCallbacks |  | ||||||
|     def release_address(self, context, public_ip, **kwargs): |  | ||||||
|         # NOTE(vish): Should we make sure this works? |  | ||||||
|         floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) |  | ||||||
|         network_topic = yield self._get_network_topic(context) |  | ||||||
|         rpc.cast(network_topic, |  | ||||||
|                  {"method": "deallocate_floating_ip", |  | ||||||
|                   "args": {"context": None, |  | ||||||
|                            "floating_address": floating_ip_ref['str_id']}}) |  | ||||||
|         defer.returnValue({'releaseResponse': ["Address released."]}) |  | ||||||
|  |  | ||||||
|     @rbac.allow('netadmin') |  | ||||||
|     @defer.inlineCallbacks |  | ||||||
|     def associate_address(self, context, instance_id, public_ip, **kwargs): |  | ||||||
|         instance_ref = db.instance_get_by_str(context, instance_id) |  | ||||||
|         fixed_ip_ref = db.fixed_ip_get_by_instance(context, instance_ref['id']) |  | ||||||
|         floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) |  | ||||||
|         network_topic = yield self._get_network_topic(context) |  | ||||||
|         rpc.cast(network_topic, |  | ||||||
|                  {"method": "associate_floating_ip", |  | ||||||
|                   "args": {"context": None, |  | ||||||
|                            "floating_address": floating_ip_ref['str_id'], |  | ||||||
|                            "fixed_address": fixed_ip_ref['str_id']}}) |  | ||||||
|         defer.returnValue({'associateResponse': ["Address associated."]}) |  | ||||||
|  |  | ||||||
|     @rbac.allow('netadmin') |  | ||||||
|     @defer.inlineCallbacks |  | ||||||
|     def disassociate_address(self, context, public_ip, **kwargs): |  | ||||||
|         floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) |  | ||||||
|         network_topic = yield self._get_network_topic(context) |  | ||||||
|         rpc.cast(network_topic, |  | ||||||
|                  {"method": "disassociate_floating_ip", |  | ||||||
|                   "args": {"context": None, |  | ||||||
|                            "floating_address": floating_ip_ref['str_id']}}) |  | ||||||
|         defer.returnValue({'disassociateResponse': ["Address disassociated."]}) |  | ||||||
|  |  | ||||||
|     @defer.inlineCallbacks |  | ||||||
|     def _get_network_topic(self, context): |  | ||||||
|         """Retrieves the network host for a project""" |  | ||||||
|         network_ref = db.project_get_network(context, context.project.id) |  | ||||||
|         host = network_ref['host'] |  | ||||||
|         if not host: |  | ||||||
|             host = yield rpc.call(FLAGS.network_topic, |  | ||||||
|                                   {"method": "set_network_host", |  | ||||||
|                                    "args": {"context": None, |  | ||||||
|                                             "project_id": context.project.id}}) |  | ||||||
|         defer.returnValue(db.queue_get_for(context, FLAGS.network_topic, host)) |  | ||||||
|  |  | ||||||
|     @rbac.allow('projectmanager', 'sysadmin') |  | ||||||
|     @defer.inlineCallbacks |  | ||||||
|     def run_instances(self, context, **kwargs): |  | ||||||
|         instance_type = kwargs.get('instance_type', 'm1.small') |  | ||||||
|         if instance_type not in INSTANCE_TYPES: |  | ||||||
|             raise exception.ApiError("Unknown instance type: %s", |  | ||||||
|                                      instance_type) |  | ||||||
|         # check quota |  | ||||||
|         max_instances = int(kwargs.get('max_count', 1)) |  | ||||||
|         min_instances = int(kwargs.get('min_count', max_instances)) |  | ||||||
|         num_instances = quota.allowed_instances(context, |  | ||||||
|                                                 max_instances, |  | ||||||
|                                                 instance_type) |  | ||||||
|         if num_instances < min_instances: |  | ||||||
|             logging.warn("Quota exceeeded for %s, tried to run %s instances", |  | ||||||
|                          context.project.id, min_instances) |  | ||||||
|             raise QuotaError("Instance quota exceeded. You can only " |  | ||||||
|                              "run %s more instances of this type." % |  | ||||||
|                              num_instances, "InstanceLimitExceeded") |  | ||||||
|         # make sure user can access the image |  | ||||||
|         # vpn image is private so it doesn't show up on lists |  | ||||||
|         vpn = kwargs['image_id'] == FLAGS.vpn_image_id |  | ||||||
|  |  | ||||||
|         if not vpn: |  | ||||||
|             image = images.get(context, kwargs['image_id']) |  | ||||||
|  |  | ||||||
|         # FIXME(ja): if image is vpn, this breaks |  | ||||||
|         # get defaults from imagestore |  | ||||||
|         image_id = image['imageId'] |  | ||||||
|         kernel_id = image.get('kernelId', FLAGS.default_kernel) |  | ||||||
|         ramdisk_id = image.get('ramdiskId', FLAGS.default_ramdisk) |  | ||||||
|  |  | ||||||
|         # API parameters overrides of defaults |  | ||||||
|         kernel_id = kwargs.get('kernel_id', kernel_id) |  | ||||||
|         ramdisk_id = kwargs.get('ramdisk_id', ramdisk_id) |  | ||||||
|  |  | ||||||
|         # make sure we have access to kernel and ramdisk |  | ||||||
|         images.get(context, kernel_id) |  | ||||||
|         images.get(context, ramdisk_id) |  | ||||||
|  |  | ||||||
|         logging.debug("Going to run %s instances...", num_instances) |  | ||||||
|         launch_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) |  | ||||||
|         key_data = None |  | ||||||
|         if kwargs.has_key('key_name'): |  | ||||||
|             key_pair_ref = db.key_pair_get(context, |  | ||||||
|                                       context.user.id, |  | ||||||
|                                       kwargs['key_name']) |  | ||||||
|             key_data = key_pair_ref['public_key'] |  | ||||||
|  |  | ||||||
|         security_group_arg = kwargs.get('security_group', ["default"]) |  | ||||||
|         if not type(security_group_arg) is list: |  | ||||||
|             security_group_arg = [security_group_arg] |  | ||||||
|  |  | ||||||
|         security_groups = [] |  | ||||||
|         for security_group_name in security_group_arg: |  | ||||||
|             group = db.security_group_get_by_project(context, |  | ||||||
|                                                      context.project.id, |  | ||||||
|                                                      security_group_name) |  | ||||||
|             security_groups.append(group['id']) |  | ||||||
|  |  | ||||||
|         reservation_id = utils.generate_uid('r') |  | ||||||
|         base_options = {} |  | ||||||
|         base_options['state_description'] = 'scheduling' |  | ||||||
|         base_options['image_id'] = image_id |  | ||||||
|         base_options['kernel_id'] = kernel_id |  | ||||||
|         base_options['ramdisk_id'] = ramdisk_id |  | ||||||
|         base_options['reservation_id'] = reservation_id |  | ||||||
|         base_options['key_data'] = key_data |  | ||||||
|         base_options['key_name'] = kwargs.get('key_name', None) |  | ||||||
|         base_options['user_id'] = context.user.id |  | ||||||
|         base_options['project_id'] = context.project.id |  | ||||||
|         base_options['user_data'] = kwargs.get('user_data', '') |  | ||||||
|  |  | ||||||
|         type_data = INSTANCE_TYPES[instance_type] |  | ||||||
|         base_options['memory_mb'] = type_data['memory_mb'] |  | ||||||
|         base_options['vcpus'] = type_data['vcpus'] |  | ||||||
|         base_options['local_gb'] = type_data['local_gb'] |  | ||||||
|  |  | ||||||
|         for num in range(num_instances): |  | ||||||
|             instance_ref = db.instance_create(context, base_options) |  | ||||||
|             inst_id = instance_ref['id'] |  | ||||||
|  |  | ||||||
|             for security_group_id in security_groups: |  | ||||||
|                 db.instance_add_security_group(context, inst_id, |  | ||||||
|                                                security_group_id) |  | ||||||
|  |  | ||||||
|             inst = {} |  | ||||||
|             inst['mac_address'] = utils.generate_mac() |  | ||||||
|             inst['launch_index'] = num |  | ||||||
|             inst['hostname'] = instance_ref['str_id'] |  | ||||||
|             db.instance_update(context, inst_id, inst) |  | ||||||
|             address = self.network_manager.allocate_fixed_ip(context, |  | ||||||
|                                                              inst_id, |  | ||||||
|                                                              vpn) |  | ||||||
|  |  | ||||||
|             # TODO(vish): This probably should be done in the scheduler |  | ||||||
|             #             network is setup when host is assigned |  | ||||||
|             network_topic = yield self._get_network_topic(context) |  | ||||||
|             rpc.call(network_topic, |  | ||||||
|                      {"method": "setup_fixed_ip", |  | ||||||
|                       "args": {"context": None, |  | ||||||
|                                "address": address}}) |  | ||||||
|  |  | ||||||
|             rpc.cast(FLAGS.scheduler_topic, |  | ||||||
|                      {"method": "run_instance", |  | ||||||
|                       "args": {"context": None, |  | ||||||
|                                "topic": FLAGS.compute_topic, |  | ||||||
|                                "instance_id": inst_id}}) |  | ||||||
|             logging.debug("Casting to scheduler for %s/%s's instance %s" % |  | ||||||
|                       (context.project.name, context.user.name, inst_id)) |  | ||||||
|         defer.returnValue(self._format_run_instances(context, |  | ||||||
|                                                      reservation_id)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @rbac.allow('projectmanager', 'sysadmin') |  | ||||||
|     @defer.inlineCallbacks |  | ||||||
|     def terminate_instances(self, context, instance_id, **kwargs): |  | ||||||
|         logging.debug("Going to start terminating instances") |  | ||||||
|         for id_str in instance_id: |  | ||||||
|             logging.debug("Going to try and terminate %s" % id_str) |  | ||||||
|             try: |  | ||||||
|                 instance_ref = db.instance_get_by_str(context, id_str) |  | ||||||
|             except exception.NotFound: |  | ||||||
|                 logging.warning("Instance %s was not found during terminate" |  | ||||||
|                                 % id_str) |  | ||||||
|                 continue |  | ||||||
|  |  | ||||||
|             now = datetime.datetime.utcnow() |  | ||||||
|             db.instance_update(context, |  | ||||||
|                                instance_ref['id'], |  | ||||||
|                                {'terminated_at': now}) |  | ||||||
|             # FIXME(ja): where should network deallocate occur? |  | ||||||
|             address = db.instance_get_floating_address(context, |  | ||||||
|                                                        instance_ref['id']) |  | ||||||
|             if address: |  | ||||||
|                 logging.debug("Disassociating address %s" % address) |  | ||||||
|                 # NOTE(vish): Right now we don't really care if the ip is |  | ||||||
|                 #             disassociated.  We may need to worry about |  | ||||||
|                 #             checking this later.  Perhaps in the scheduler? |  | ||||||
|                 network_topic = yield self._get_network_topic(context) |  | ||||||
|                 rpc.cast(network_topic, |  | ||||||
|                          {"method": "disassociate_floating_ip", |  | ||||||
|                           "args": {"context": None, |  | ||||||
|                                    "address": address}}) |  | ||||||
|  |  | ||||||
|             address = db.instance_get_fixed_address(context, |  | ||||||
|                                                     instance_ref['id']) |  | ||||||
|             if address: |  | ||||||
|                 logging.debug("Deallocating address %s" % address) |  | ||||||
|                 # NOTE(vish): Currently, nothing needs to be done on the |  | ||||||
|                 #             network node until release. If this changes, |  | ||||||
|                 #             we will need to cast here. |  | ||||||
|                 self.network_manager.deallocate_fixed_ip(context, address) |  | ||||||
|  |  | ||||||
|             host = instance_ref['host'] |  | ||||||
|             if host: |  | ||||||
|                 rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host), |  | ||||||
|                          {"method": "terminate_instance", |  | ||||||
|                           "args": {"context": None, |  | ||||||
|                                    "instance_id": instance_ref['id']}}) |  | ||||||
|             else: |  | ||||||
|                 db.instance_destroy(context, instance_ref['id']) |  | ||||||
|         defer.returnValue(True) |  | ||||||
|  |  | ||||||
|     @rbac.allow('projectmanager', 'sysadmin') |  | ||||||
|     def reboot_instances(self, context, instance_id, **kwargs): |  | ||||||
|         """instance_id is a list of instance ids""" |  | ||||||
|         for id_str in instance_id: |  | ||||||
|             instance_ref = db.instance_get_by_str(context, id_str) |  | ||||||
|             host = instance_ref['host'] |  | ||||||
|             rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host), |  | ||||||
|                      {"method": "reboot_instance", |  | ||||||
|                       "args": {"context": None, |  | ||||||
|                                "instance_id": instance_ref['id']}}) |  | ||||||
|         return defer.succeed(True) |  | ||||||
|  |  | ||||||
|     @rbac.allow('projectmanager', 'sysadmin') |  | ||||||
|     def delete_volume(self, context, volume_id, **kwargs): |  | ||||||
|         # TODO: return error if not authorized |  | ||||||
|         volume_ref = db.volume_get_by_str(context, volume_id) |  | ||||||
|         if volume_ref['status'] != "available": |  | ||||||
|             raise exception.ApiError("Volume status must be available") |  | ||||||
|         now = datetime.datetime.utcnow() |  | ||||||
|         db.volume_update(context, volume_ref['id'], {'terminated_at': now}) |  | ||||||
|         host = volume_ref['host'] |  | ||||||
|         rpc.cast(db.queue_get_for(context, FLAGS.volume_topic, host), |  | ||||||
|                             {"method": "delete_volume", |  | ||||||
|                              "args": {"context": None, |  | ||||||
|                                       "volume_id": volume_ref['id']}}) |  | ||||||
|         return defer.succeed(True) |  | ||||||
|  |  | ||||||
|     @rbac.allow('all') |  | ||||||
|     def describe_images(self, context, image_id=None, **kwargs): |  | ||||||
|         # The objectstore does its own authorization for describe |  | ||||||
|         imageSet = images.list(context, image_id) |  | ||||||
|         return defer.succeed({'imagesSet': imageSet}) |  | ||||||
|  |  | ||||||
|     @rbac.allow('projectmanager', 'sysadmin') |  | ||||||
|     def deregister_image(self, context, image_id, **kwargs): |  | ||||||
|         # FIXME: should the objectstore be doing these authorization checks? |  | ||||||
|         images.deregister(context, image_id) |  | ||||||
|         return defer.succeed({'imageId': image_id}) |  | ||||||
|  |  | ||||||
|     @rbac.allow('projectmanager', 'sysadmin') |  | ||||||
|     def register_image(self, context, image_location=None, **kwargs): |  | ||||||
|         # FIXME: should the objectstore be doing these authorization checks? |  | ||||||
|         if image_location is None and kwargs.has_key('name'): |  | ||||||
|             image_location = kwargs['name'] |  | ||||||
|         image_id = images.register(context, image_location) |  | ||||||
|         logging.debug("Registered %s as %s" % (image_location, image_id)) |  | ||||||
|  |  | ||||||
|         return defer.succeed({'imageId': image_id}) |  | ||||||
|  |  | ||||||
|     @rbac.allow('all') |  | ||||||
|     def describe_image_attribute(self, context, image_id, attribute, **kwargs): |  | ||||||
|         if attribute != 'launchPermission': |  | ||||||
|             raise exception.ApiError('attribute not supported: %s' % attribute) |  | ||||||
|         try: |  | ||||||
|             image = images.list(context, image_id)[0] |  | ||||||
|         except IndexError: |  | ||||||
|             raise exception.ApiError('invalid id: %s' % image_id) |  | ||||||
|         result = {'image_id': image_id, 'launchPermission': []} |  | ||||||
|         if image['isPublic']: |  | ||||||
|             result['launchPermission'].append({'group': 'all'}) |  | ||||||
|         return defer.succeed(result) |  | ||||||
|  |  | ||||||
|     @rbac.allow('projectmanager', 'sysadmin') |  | ||||||
|     def modify_image_attribute(self, context, image_id, attribute, operation_type, **kwargs): |  | ||||||
|         # TODO(devcamcar): Support users and groups other than 'all'. |  | ||||||
|         if attribute != 'launchPermission': |  | ||||||
|             raise exception.ApiError('attribute not supported: %s' % attribute) |  | ||||||
|         if not 'user_group' in kwargs: |  | ||||||
|             raise exception.ApiError('user or group not specified') |  | ||||||
|         if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all': |  | ||||||
|             raise exception.ApiError('only group "all" is supported') |  | ||||||
|         if not operation_type in ['add', 'remove']: |  | ||||||
|             raise exception.ApiError('operation_type must be add or remove') |  | ||||||
|         result = images.modify(context, image_id, operation_type) |  | ||||||
|         return defer.succeed(result) |  | ||||||
| @@ -1,108 +0,0 @@ | |||||||
| # vim: tabstop=4 shiftwidth=4 softtabstop=4 |  | ||||||
|  |  | ||||||
| # Copyright 2010 United States Government as represented by the |  | ||||||
| # Administrator of the National Aeronautics and Space Administration. |  | ||||||
| # All Rights Reserved. |  | ||||||
| # |  | ||||||
| #    Licensed under the Apache License, Version 2.0 (the "License"); you may |  | ||||||
| #    not use this file except in compliance with the License. You may obtain |  | ||||||
| #    a copy of the License at |  | ||||||
| # |  | ||||||
| #         http://www.apache.org/licenses/LICENSE-2.0 |  | ||||||
| # |  | ||||||
| #    Unless required by applicable law or agreed to in writing, software |  | ||||||
| #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |  | ||||||
| #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |  | ||||||
| #    License for the specific language governing permissions and limitations |  | ||||||
| #    under the License. |  | ||||||
|  |  | ||||||
| """ |  | ||||||
| Proxy AMI-related calls from the cloud controller, to the running |  | ||||||
| objectstore service. |  | ||||||
| """ |  | ||||||
|  |  | ||||||
| import json |  | ||||||
| import urllib |  | ||||||
|  |  | ||||||
| import boto.s3.connection |  | ||||||
|  |  | ||||||
| from nova import exception |  | ||||||
| from nova import flags |  | ||||||
| from nova import utils |  | ||||||
| from nova.auth import manager |  | ||||||
|  |  | ||||||
|  |  | ||||||
| FLAGS = flags.FLAGS |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def modify(context, image_id, operation): |  | ||||||
|     conn(context).make_request( |  | ||||||
|         method='POST', |  | ||||||
|         bucket='_images', |  | ||||||
|         query_args=qs({'image_id': image_id, 'operation': operation})) |  | ||||||
|  |  | ||||||
|     return True |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def register(context, image_location): |  | ||||||
|     """ rpc call to register a new image based from a manifest """ |  | ||||||
|  |  | ||||||
|     image_id = utils.generate_uid('ami') |  | ||||||
|     conn(context).make_request( |  | ||||||
|             method='PUT', |  | ||||||
|             bucket='_images', |  | ||||||
|             query_args=qs({'image_location': image_location, |  | ||||||
|                            'image_id': image_id})) |  | ||||||
|  |  | ||||||
|     return image_id |  | ||||||
|  |  | ||||||
| def list(context, filter_list=[]): |  | ||||||
|     """ return a list of all images that a user can see |  | ||||||
|  |  | ||||||
|     optionally filtered by a list of image_id """ |  | ||||||
|  |  | ||||||
|     # FIXME: send along the list of only_images to check for |  | ||||||
|     response = conn(context).make_request( |  | ||||||
|             method='GET', |  | ||||||
|             bucket='_images') |  | ||||||
|  |  | ||||||
|     result = json.loads(response.read()) |  | ||||||
|     if not filter_list is None: |  | ||||||
|         return [i for i in result if i['imageId'] in filter_list] |  | ||||||
|     return result |  | ||||||
|  |  | ||||||
| def get(context, image_id): |  | ||||||
|     """return a image object if the context has permissions""" |  | ||||||
|     result = list(context, [image_id]) |  | ||||||
|     if not result: |  | ||||||
|         raise exception.NotFound('Image %s could not be found' % image_id) |  | ||||||
|     image = result[0] |  | ||||||
|     return image |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def deregister(context, image_id): |  | ||||||
|     """ unregister an image """ |  | ||||||
|     conn(context).make_request( |  | ||||||
|             method='DELETE', |  | ||||||
|             bucket='_images', |  | ||||||
|             query_args=qs({'image_id': image_id})) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def conn(context): |  | ||||||
|     access = manager.AuthManager().get_access_key(context.user, |  | ||||||
|                                                   context.project) |  | ||||||
|     secret = str(context.user.secret) |  | ||||||
|     calling = boto.s3.connection.OrdinaryCallingFormat() |  | ||||||
|     return boto.s3.connection.S3Connection(aws_access_key_id=access, |  | ||||||
|                                            aws_secret_access_key=secret, |  | ||||||
|                                            is_secure=False, |  | ||||||
|                                            calling_format=calling, |  | ||||||
|                                            port=FLAGS.s3_port, |  | ||||||
|                                            host=FLAGS.s3_host) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def qs(params): |  | ||||||
|     pairs = [] |  | ||||||
|     for key in params.keys(): |  | ||||||
|         pairs.append(key + '=' + urllib.quote(params[key])) |  | ||||||
|     return '&'.join(pairs) |  | ||||||
| @@ -23,60 +23,12 @@ from boto.ec2 import regioninfo | |||||||
| import httplib | import httplib | ||||||
| import random | import random | ||||||
| import StringIO | import StringIO | ||||||
| from tornado import httpserver | import webob | ||||||
| from twisted.internet import defer |  | ||||||
|  |  | ||||||
| from nova import flags |  | ||||||
| from nova import test | from nova import test | ||||||
|  | from nova import api | ||||||
|  | from nova.api.ec2 import cloud | ||||||
| from nova.auth import manager | from nova.auth import manager | ||||||
| from nova.endpoint import api |  | ||||||
| from nova.endpoint import cloud |  | ||||||
|  |  | ||||||
|  |  | ||||||
| FLAGS = flags.FLAGS |  | ||||||
|  |  | ||||||
|  |  | ||||||
| # NOTE(termie): These are a bunch of helper methods and classes to short |  | ||||||
| #               circuit boto calls and feed them into our tornado handlers, |  | ||||||
| #               it's pretty damn circuitous so apologies if you have to fix |  | ||||||
| #               a bug in it |  | ||||||
| # NOTE(jaypipes) The pylint disables here are for R0913 (too many args) which |  | ||||||
| #                isn't controllable since boto's HTTPRequest needs that many |  | ||||||
| #                args, and for the version-differentiated import of tornado's |  | ||||||
| #                httputil. |  | ||||||
| # NOTE(jaypipes): The disable-msg=E1101 and E1103 below is because pylint is |  | ||||||
| #                 unable to introspect the deferred's return value properly |  | ||||||
|  |  | ||||||
| def boto_to_tornado(method, path, headers, data, # pylint: disable-msg=R0913 |  | ||||||
|                     host, connection=None): |  | ||||||
|     """ translate boto requests into tornado requests |  | ||||||
|  |  | ||||||
|     connection should be a FakeTornadoHttpConnection instance |  | ||||||
|     """ |  | ||||||
|     try: |  | ||||||
|         headers = httpserver.HTTPHeaders() |  | ||||||
|     except AttributeError: |  | ||||||
|         from tornado import httputil # pylint: disable-msg=E0611 |  | ||||||
|         headers = httputil.HTTPHeaders() |  | ||||||
|     for k, v in headers.iteritems(): |  | ||||||
|         headers[k] = v |  | ||||||
|  |  | ||||||
|     req = httpserver.HTTPRequest(method=method, |  | ||||||
|                                  uri=path, |  | ||||||
|                                  headers=headers, |  | ||||||
|                                  body=data, |  | ||||||
|                                  host=host, |  | ||||||
|                                  remote_ip='127.0.0.1', |  | ||||||
|                                  connection=connection) |  | ||||||
|     return req |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def raw_to_httpresponse(response_string): |  | ||||||
|     """translate a raw tornado http response into an httplib.HTTPResponse""" |  | ||||||
|     sock = FakeHttplibSocket(response_string) |  | ||||||
|     resp = httplib.HTTPResponse(sock) |  | ||||||
|     resp.begin() |  | ||||||
|     return resp |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FakeHttplibSocket(object): | class FakeHttplibSocket(object): | ||||||
| @@ -89,85 +41,35 @@ class FakeHttplibSocket(object): | |||||||
|         return self._buffer |         return self._buffer | ||||||
|  |  | ||||||
|  |  | ||||||
| class FakeTornadoStream(object): |  | ||||||
|     """a fake stream to satisfy tornado's assumptions, trivial""" |  | ||||||
|     def set_close_callback(self, _func): |  | ||||||
|         """Dummy callback for stream""" |  | ||||||
|         pass |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FakeTornadoConnection(object): |  | ||||||
|     """A fake connection object for tornado to pass to its handlers |  | ||||||
|  |  | ||||||
|     web requests are expected to write to this as they get data and call |  | ||||||
|     finish when they are done with the request, we buffer the writes and |  | ||||||
|     kick off a callback when it is done so that we can feed the result back |  | ||||||
|     into boto. |  | ||||||
|     """ |  | ||||||
|     def __init__(self, deferred): |  | ||||||
|         self._deferred = deferred |  | ||||||
|         self._buffer = StringIO.StringIO() |  | ||||||
|  |  | ||||||
|     def write(self, chunk): |  | ||||||
|         """Writes a chunk of data to the internal buffer""" |  | ||||||
|         self._buffer.write(chunk) |  | ||||||
|  |  | ||||||
|     def finish(self): |  | ||||||
|         """Finalizes the connection and returns the buffered data via the |  | ||||||
|         deferred callback. |  | ||||||
|         """ |  | ||||||
|         data = self._buffer.getvalue() |  | ||||||
|         self._deferred.callback(data) |  | ||||||
|  |  | ||||||
|     xheaders = None |  | ||||||
|  |  | ||||||
|     @property |  | ||||||
|     def stream(self): # pylint: disable-msg=R0201 |  | ||||||
|         """Required property for interfacing with tornado""" |  | ||||||
|         return FakeTornadoStream() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class FakeHttplibConnection(object): | class FakeHttplibConnection(object): | ||||||
|     """A fake httplib.HTTPConnection for boto to use |     """A fake httplib.HTTPConnection for boto to use | ||||||
|  |  | ||||||
|     requests made via this connection actually get translated and routed into |     requests made via this connection actually get translated and routed into | ||||||
|     our tornado app, we then wait for the response and turn it back into |     our WSGI app, we then wait for the response and turn it back into | ||||||
|     the httplib.HTTPResponse that boto expects. |     the httplib.HTTPResponse that boto expects. | ||||||
|     """ |     """ | ||||||
|     def __init__(self, app, host, is_secure=False): |     def __init__(self, app, host, is_secure=False): | ||||||
|         self.app = app |         self.app = app | ||||||
|         self.host = host |         self.host = host | ||||||
|         self.deferred = defer.Deferred() |  | ||||||
|  |  | ||||||
|     def request(self, method, path, data, headers): |     def request(self, method, path, data, headers): | ||||||
|         """Creates a connection to a fake tornado and sets |         req = webob.Request.blank(path) | ||||||
|         up a deferred request with the supplied data and |         req.method = method | ||||||
|         headers""" |         req.body = data | ||||||
|         conn = FakeTornadoConnection(self.deferred) |         req.headers = headers | ||||||
|         request = boto_to_tornado(connection=conn, |         req.headers['Accept'] = 'text/html' | ||||||
|                                   method=method, |         req.host = self.host | ||||||
|                                   path=path, |         # Call the WSGI app, get the HTTP response | ||||||
|                                   headers=headers, |         resp = str(req.get_response(self.app)) | ||||||
|                                   data=data, |         # For some reason, the response doesn't have "HTTP/1.0 " prepended; I | ||||||
|                                   host=self.host) |         # guess that's a function the web server usually provides. | ||||||
|         self.app(request) |         resp = "HTTP/1.0 %s" % resp | ||||||
|         self.deferred.addCallback(raw_to_httpresponse) |         sock = FakeHttplibSocket(resp) | ||||||
|  |         self.http_response = httplib.HTTPResponse(sock) | ||||||
|  |         self.http_response.begin() | ||||||
|  |  | ||||||
|     def getresponse(self): |     def getresponse(self): | ||||||
|         """A bit of deferred magic for catching the response |         return self.http_response | ||||||
|         from the previously deferred request""" |  | ||||||
|         @defer.inlineCallbacks |  | ||||||
|         def _waiter(): |  | ||||||
|             """Callback that simply yields the deferred's |  | ||||||
|             return value.""" |  | ||||||
|             result = yield self.deferred |  | ||||||
|             defer.returnValue(result) |  | ||||||
|         d = _waiter() |  | ||||||
|         # NOTE(termie): defer.returnValue above should ensure that |  | ||||||
|         #               this deferred has already been called by the time |  | ||||||
|         #               we get here, we are going to cheat and return |  | ||||||
|         #               the result of the callback |  | ||||||
|         return d.result # pylint: disable-msg=E1101 |  | ||||||
|  |  | ||||||
|     def close(self): |     def close(self): | ||||||
|         """Required for compatibility with boto/tornado""" |         """Required for compatibility with boto/tornado""" | ||||||
| @@ -180,11 +82,10 @@ class ApiEc2TestCase(test.BaseTestCase): | |||||||
|         super(ApiEc2TestCase, self).setUp() |         super(ApiEc2TestCase, self).setUp() | ||||||
|  |  | ||||||
|         self.manager = manager.AuthManager() |         self.manager = manager.AuthManager() | ||||||
|         self.cloud = cloud.CloudController() |  | ||||||
|  |  | ||||||
|         self.host = '127.0.0.1' |         self.host = '127.0.0.1' | ||||||
|  |  | ||||||
|         self.app = api.APIServerApplication({'Cloud': self.cloud}) |         self.app = api.API() | ||||||
|  |  | ||||||
|     def expect_http(self, host=None, is_secure=False): |     def expect_http(self, host=None, is_secure=False): | ||||||
|         """Returns a new EC2 connection""" |         """Returns a new EC2 connection""" | ||||||
| @@ -193,12 +94,12 @@ class ApiEc2TestCase(test.BaseTestCase): | |||||||
|                 aws_secret_access_key='fake', |                 aws_secret_access_key='fake', | ||||||
|                 is_secure=False, |                 is_secure=False, | ||||||
|                 region=regioninfo.RegionInfo(None, 'test', self.host), |                 region=regioninfo.RegionInfo(None, 'test', self.host), | ||||||
|                 port=FLAGS.cc_port, |                 port=8773, | ||||||
|                 path='/services/Cloud') |                 path='/services/Cloud') | ||||||
|  |  | ||||||
|         self.mox.StubOutWithMock(self.ec2, 'new_http_connection') |         self.mox.StubOutWithMock(self.ec2, 'new_http_connection') | ||||||
|         http = FakeHttplibConnection( |         http = FakeHttplibConnection( | ||||||
|                 self.app, '%s:%d' % (self.host, FLAGS.cc_port), False) |                 self.app, '%s:8773' % (self.host), False) | ||||||
|         # pylint: disable-msg=E1103 |         # pylint: disable-msg=E1103 | ||||||
|         self.ec2.new_http_connection(host, is_secure).AndReturn(http) |         self.ec2.new_http_connection(host, is_secure).AndReturn(http) | ||||||
|         return http |         return http | ||||||
| @@ -255,6 +156,10 @@ class ApiEc2TestCase(test.BaseTestCase): | |||||||
|         user = self.manager.create_user('fake', 'fake', 'fake', admin=True) |         user = self.manager.create_user('fake', 'fake', 'fake', admin=True) | ||||||
|         project = self.manager.create_project('fake', 'fake', 'fake') |         project = self.manager.create_project('fake', 'fake', 'fake') | ||||||
|  |  | ||||||
|  |         # At the moment, you need both of these to actually be netadmin | ||||||
|  |         self.manager.add_role('fake', 'netadmin') | ||||||
|  |         project.add_role('fake', 'netadmin') | ||||||
|  |  | ||||||
|         security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \ |         security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \ | ||||||
|                                       for x in range(random.randint(4, 8))) |                                       for x in range(random.randint(4, 8))) | ||||||
|  |  | ||||||
| @@ -282,9 +187,13 @@ class ApiEc2TestCase(test.BaseTestCase): | |||||||
|         """ |         """ | ||||||
|         self.expect_http() |         self.expect_http() | ||||||
|         self.mox.ReplayAll() |         self.mox.ReplayAll() | ||||||
|         user = self.manager.create_user('fake', 'fake', 'fake', admin=True) |         user = self.manager.create_user('fake', 'fake', 'fake') | ||||||
|         project = self.manager.create_project('fake', 'fake', 'fake') |         project = self.manager.create_project('fake', 'fake', 'fake') | ||||||
|  |  | ||||||
|  |         # At the moment, you need both of these to actually be netadmin | ||||||
|  |         self.manager.add_role('fake', 'netadmin') | ||||||
|  |         project.add_role('fake', 'netadmin') | ||||||
|  |  | ||||||
|         security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \ |         security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \ | ||||||
|                                       for x in range(random.randint(4, 8))) |                                       for x in range(random.randint(4, 8))) | ||||||
|  |  | ||||||
| @@ -346,6 +255,10 @@ class ApiEc2TestCase(test.BaseTestCase): | |||||||
|         user = self.manager.create_user('fake', 'fake', 'fake', admin=True) |         user = self.manager.create_user('fake', 'fake', 'fake', admin=True) | ||||||
|         project = self.manager.create_project('fake', 'fake', 'fake') |         project = self.manager.create_project('fake', 'fake', 'fake') | ||||||
|          |          | ||||||
|  |         # At the moment, you need both of these to actually be netadmin | ||||||
|  |         self.manager.add_role('fake', 'netadmin') | ||||||
|  |         project.add_role('fake', 'netadmin') | ||||||
|  |  | ||||||
|         security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \ |         security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \ | ||||||
|                                       for x in range(random.randint(4, 8))) |                                       for x in range(random.randint(4, 8))) | ||||||
|         other_security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \ |         other_security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \ | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ from nova import crypto | |||||||
| from nova import flags | from nova import flags | ||||||
| from nova import test | from nova import test | ||||||
| from nova.auth import manager | from nova.auth import manager | ||||||
| from nova.endpoint import cloud | from nova.api.ec2 import cloud | ||||||
|  |  | ||||||
| FLAGS = flags.FLAGS | FLAGS = flags.FLAGS | ||||||
|  |  | ||||||
|   | |||||||
| @@ -35,8 +35,8 @@ from nova import test | |||||||
| from nova import utils | from nova import utils | ||||||
| from nova.auth import manager | from nova.auth import manager | ||||||
| from nova.compute import power_state | from nova.compute import power_state | ||||||
| from nova.endpoint import api | from nova.api.ec2 import context | ||||||
| from nova.endpoint import cloud | from nova.api.ec2 import cloud | ||||||
|  |  | ||||||
|  |  | ||||||
| FLAGS = flags.FLAGS | FLAGS = flags.FLAGS | ||||||
| @@ -63,9 +63,8 @@ class CloudTestCase(test.BaseTestCase): | |||||||
|         self.manager = manager.AuthManager() |         self.manager = manager.AuthManager() | ||||||
|         self.user = self.manager.create_user('admin', 'admin', 'admin', True) |         self.user = self.manager.create_user('admin', 'admin', 'admin', True) | ||||||
|         self.project = self.manager.create_project('proj', 'admin', 'proj') |         self.project = self.manager.create_project('proj', 'admin', 'proj') | ||||||
|         self.context = api.APIRequestContext(handler=None, |         self.context = context.APIRequestContext(user=self.user, | ||||||
|                                              user=self.user, |                                                  project=self.project) | ||||||
|                                              project=self.project) |  | ||||||
|  |  | ||||||
|     def tearDown(self): |     def tearDown(self): | ||||||
|         self.manager.delete_project(self.project) |         self.manager.delete_project(self.project) | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ from nova import flags | |||||||
| from nova import test | from nova import test | ||||||
| from nova import utils | from nova import utils | ||||||
| from nova.auth import manager | from nova.auth import manager | ||||||
| from nova.endpoint import api | from nova.api.ec2 import context | ||||||
|  |  | ||||||
| FLAGS = flags.FLAGS | FLAGS = flags.FLAGS | ||||||
|  |  | ||||||
| @@ -49,7 +49,7 @@ class NetworkTestCase(test.TrialTestCase): | |||||||
|         self.user = self.manager.create_user('netuser', 'netuser', 'netuser') |         self.user = self.manager.create_user('netuser', 'netuser', 'netuser') | ||||||
|         self.projects = [] |         self.projects = [] | ||||||
|         self.network = utils.import_object(FLAGS.network_manager) |         self.network = utils.import_object(FLAGS.network_manager) | ||||||
|         self.context = api.APIRequestContext(None, project=None, user=self.user) |         self.context = context.APIRequestContext(project=None, user=self.user) | ||||||
|         for i in range(5): |         for i in range(5): | ||||||
|             name = 'project%s' % i |             name = 'project%s' % i | ||||||
|             self.projects.append(self.manager.create_project(name, |             self.projects.append(self.manager.create_project(name, | ||||||
|   | |||||||
| @@ -25,8 +25,8 @@ from nova import quota | |||||||
| from nova import test | from nova import test | ||||||
| from nova import utils | from nova import utils | ||||||
| from nova.auth import manager | from nova.auth import manager | ||||||
| from nova.endpoint import cloud | from nova.api.ec2 import cloud | ||||||
| from nova.endpoint import api | from nova.api.ec2 import context | ||||||
|  |  | ||||||
|  |  | ||||||
| FLAGS = flags.FLAGS | FLAGS = flags.FLAGS | ||||||
| @@ -48,9 +48,8 @@ class QuotaTestCase(test.TrialTestCase): | |||||||
|         self.user = self.manager.create_user('admin', 'admin', 'admin', True) |         self.user = self.manager.create_user('admin', 'admin', 'admin', True) | ||||||
|         self.project = self.manager.create_project('admin', 'admin', 'admin') |         self.project = self.manager.create_project('admin', 'admin', 'admin') | ||||||
|         self.network = utils.import_object(FLAGS.network_manager) |         self.network = utils.import_object(FLAGS.network_manager) | ||||||
|         self.context = api.APIRequestContext(handler=None, |         self.context = context.APIRequestContext(project=self.project, | ||||||
|                                              project=self.project, |                                                  user=self.user) | ||||||
|                                              user=self.user) |  | ||||||
|  |  | ||||||
|     def tearDown(self):  # pylint: disable-msg=C0103 |     def tearDown(self):  # pylint: disable-msg=C0103 | ||||||
|         manager.AuthManager().delete_project(self.project) |         manager.AuthManager().delete_project(self.project) | ||||||
| @@ -95,11 +94,11 @@ class QuotaTestCase(test.TrialTestCase): | |||||||
|         for i in range(FLAGS.quota_instances): |         for i in range(FLAGS.quota_instances): | ||||||
|             instance_id = self._create_instance() |             instance_id = self._create_instance() | ||||||
|             instance_ids.append(instance_id) |             instance_ids.append(instance_id) | ||||||
|         self.assertFailure(self.cloud.run_instances(self.context, |         self.assertRaises(cloud.QuotaError, self.cloud.run_instances,  | ||||||
|                                                     min_count=1, |                                             self.context, | ||||||
|                                                     max_count=1, |                                             min_count=1, | ||||||
|                                                     instance_type='m1.small'), |                                             max_count=1, | ||||||
|                            cloud.QuotaError) |                                             instance_type='m1.small') | ||||||
|         for instance_id in instance_ids: |         for instance_id in instance_ids: | ||||||
|             db.instance_destroy(self.context, instance_id) |             db.instance_destroy(self.context, instance_id) | ||||||
|  |  | ||||||
| @@ -107,11 +106,11 @@ class QuotaTestCase(test.TrialTestCase): | |||||||
|         instance_ids = [] |         instance_ids = [] | ||||||
|         instance_id = self._create_instance(cores=4) |         instance_id = self._create_instance(cores=4) | ||||||
|         instance_ids.append(instance_id) |         instance_ids.append(instance_id) | ||||||
|         self.assertFailure(self.cloud.run_instances(self.context, |         self.assertRaises(cloud.QuotaError, self.cloud.run_instances,  | ||||||
|                                                     min_count=1, |                                             self.context, | ||||||
|                                                     max_count=1, |                                             min_count=1, | ||||||
|                                                     instance_type='m1.small'), |                                             max_count=1, | ||||||
|                            cloud.QuotaError) |                                             instance_type='m1.small') | ||||||
|         for instance_id in instance_ids: |         for instance_id in instance_ids: | ||||||
|             db.instance_destroy(self.context, instance_id) |             db.instance_destroy(self.context, instance_id) | ||||||
|  |  | ||||||
| @@ -120,10 +119,9 @@ class QuotaTestCase(test.TrialTestCase): | |||||||
|         for i in range(FLAGS.quota_volumes): |         for i in range(FLAGS.quota_volumes): | ||||||
|             volume_id = self._create_volume() |             volume_id = self._create_volume() | ||||||
|             volume_ids.append(volume_id) |             volume_ids.append(volume_id) | ||||||
|         self.assertRaises(cloud.QuotaError, |         self.assertRaises(cloud.QuotaError, self.cloud.create_volume, | ||||||
|                           self.cloud.create_volume, |                                             self.context, | ||||||
|                           self.context, |                                             size=10) | ||||||
|                           size=10) |  | ||||||
|         for volume_id in volume_ids: |         for volume_id in volume_ids: | ||||||
|             db.volume_destroy(self.context, volume_id) |             db.volume_destroy(self.context, volume_id) | ||||||
|  |  | ||||||
| @@ -151,5 +149,4 @@ class QuotaTestCase(test.TrialTestCase): | |||||||
|         #             make an rpc.call, the test just finishes with OK. It |         #             make an rpc.call, the test just finishes with OK. It | ||||||
|         #             appears to be something in the magic inline callbacks |         #             appears to be something in the magic inline callbacks | ||||||
|         #             that is breaking. |         #             that is breaking. | ||||||
|         self.assertFailure(self.cloud.allocate_address(self.context), |         self.assertRaises(cloud.QuotaError, self.cloud.allocate_address, self.context) | ||||||
|                            cloud.QuotaError) |  | ||||||
|   | |||||||
| @@ -19,7 +19,6 @@ from xml.dom.minidom import parseString | |||||||
| from nova import db | from nova import db | ||||||
| from nova import flags | from nova import flags | ||||||
| from nova import test | from nova import test | ||||||
| from nova.endpoint import cloud |  | ||||||
| from nova.virt import libvirt_conn | from nova.virt import libvirt_conn | ||||||
|  |  | ||||||
| FLAGS = flags.FLAGS | FLAGS = flags.FLAGS | ||||||
|   | |||||||
| @@ -49,7 +49,8 @@ from nova import datastore | |||||||
| from nova import flags | from nova import flags | ||||||
| from nova import twistd | from nova import twistd | ||||||
|  |  | ||||||
| from nova.tests.access_unittest import * | #TODO(gundlach): rewrite and readd this after merge | ||||||
|  | #from nova.tests.access_unittest import * | ||||||
| from nova.tests.auth_unittest import * | from nova.tests.auth_unittest import * | ||||||
| from nova.tests.api_unittest import * | from nova.tests.api_unittest import * | ||||||
| from nova.tests.cloud_unittest import * | from nova.tests.cloud_unittest import * | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Soren Hansen
					Soren Hansen