merged trunk and fixed errors
This commit is contained in:
		| @@ -44,18 +44,20 @@ flags.DECLARE('auth_driver', 'nova.auth.manager') | |||||||
| flags.DECLARE('redis_db', 'nova.datastore') | flags.DECLARE('redis_db', 'nova.datastore') | ||||||
| flags.DECLARE('network_size', 'nova.network.manager') | flags.DECLARE('network_size', 'nova.network.manager') | ||||||
| flags.DECLARE('num_networks', 'nova.network.manager') | flags.DECLARE('num_networks', 'nova.network.manager') | ||||||
|  | flags.DECLARE('update_dhcp_on_disassociate', 'nova.network.manager') | ||||||
|  |  | ||||||
|  |  | ||||||
| def add_lease(_mac, ip_address, _hostname, _interface): | def add_lease(mac, ip_address, _hostname, _interface): | ||||||
|     """Set the IP that was assigned by the DHCP server.""" |     """Set the IP that was assigned by the DHCP server.""" | ||||||
|     if FLAGS.fake_rabbit: |     if FLAGS.fake_rabbit: | ||||||
|         logging.debug("leasing ip") |         logging.debug("leasing ip") | ||||||
|         network_manager = utils.import_object(FLAGS.network_manager) |         network_manager = utils.import_object(FLAGS.network_manager) | ||||||
|         network_manager.lease_fixed_ip(None, ip_address) |         network_manager.lease_fixed_ip(None, mac, ip_address) | ||||||
|     else: |     else: | ||||||
|         rpc.cast("%s.%s" % (FLAGS.network_topic, FLAGS.node_name), |         rpc.cast("%s.%s" % (FLAGS.network_topic, FLAGS.host), | ||||||
|                  {"method": "lease_fixed_ip", |                  {"method": "lease_fixed_ip", | ||||||
|                   "args": {"context": None, |                   "args": {"context": None, | ||||||
|  |                            "mac": mac, | ||||||
|                            "address": ip_address}}) |                            "address": ip_address}}) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -64,16 +66,17 @@ def old_lease(_mac, _ip_address, _hostname, _interface): | |||||||
|     logging.debug("Adopted old lease or got a change of mac/hostname") |     logging.debug("Adopted old lease or got a change of mac/hostname") | ||||||
|  |  | ||||||
|  |  | ||||||
| def del_lease(_mac, ip_address, _hostname, _interface): | def del_lease(mac, ip_address, _hostname, _interface): | ||||||
|     """Called when a lease expires.""" |     """Called when a lease expires.""" | ||||||
|     if FLAGS.fake_rabbit: |     if FLAGS.fake_rabbit: | ||||||
|         logging.debug("releasing ip") |         logging.debug("releasing ip") | ||||||
|         network_manager = utils.import_object(FLAGS.network_manager) |         network_manager = utils.import_object(FLAGS.network_manager) | ||||||
|         network_manager.release_fixed_ip(None, ip_address) |         network_manager.release_fixed_ip(None, mac, ip_address) | ||||||
|     else: |     else: | ||||||
|         rpc.cast("%s.%s" % (FLAGS.network_topic, FLAGS.node_name), |         rpc.cast("%s.%s" % (FLAGS.network_topic, FLAGS.host), | ||||||
|                  {"method": "release_fixed_ip", |                  {"method": "release_fixed_ip", | ||||||
|                   "args": {"context": None, |                   "args": {"context": None, | ||||||
|  |                            "mac": mac, | ||||||
|                            "address": ip_address}}) |                            "address": ip_address}}) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										126
									
								
								bin/nova-manage
									
									
									
									
									
								
							
							
						
						
									
										126
									
								
								bin/nova-manage
									
									
									
									
									
								
							| @@ -17,15 +17,47 @@ | |||||||
| #    License for the specific language governing permissions and limitations | #    License for the specific language governing permissions and limitations | ||||||
| #    under the License. | #    under the License. | ||||||
|  |  | ||||||
|  | # Interactive shell based on Django: | ||||||
|  | # | ||||||
|  | # Copyright (c) 2005, the Lawrence Journal-World | ||||||
|  | # All rights reserved. | ||||||
|  | # | ||||||
|  | # Redistribution and use in source and binary forms, with or without modification, | ||||||
|  | # are permitted provided that the following conditions are met: | ||||||
|  | # | ||||||
|  | #     1. Redistributions of source code must retain the above copyright notice, | ||||||
|  | #        this list of conditions and the following disclaimer. | ||||||
|  | # | ||||||
|  | #     2. Redistributions in binary form must reproduce the above copyright | ||||||
|  | #        notice, this list of conditions and the following disclaimer in the | ||||||
|  | #        documentation and/or other materials provided with the distribution. | ||||||
|  | # | ||||||
|  | #     3. Neither the name of Django nor the names of its contributors may be used | ||||||
|  | #        to endorse or promote products derived from this software without | ||||||
|  | #        specific prior written permission. | ||||||
|  | # | ||||||
|  | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||||||
|  | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||||||
|  | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||||
|  | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||||||
|  | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||||||
|  | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||||||
|  | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||||||
|  | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||||
|  | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||||
|  | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
|  |  | ||||||
|  |  | ||||||
| """ | """ | ||||||
|   CLI interface for nova management. |   CLI interface for nova management. | ||||||
|   Connects to the running ADMIN api in the api daemon. |  | ||||||
| """ | """ | ||||||
|  |  | ||||||
| import os | import os | ||||||
| import sys | import sys | ||||||
| import time | import time | ||||||
|  |  | ||||||
|  | import IPy | ||||||
|  |  | ||||||
| # If ../nova/__init__.py exists, add ../ to Python search path, so that | # If ../nova/__init__.py exists, add ../ to Python search path, so that | ||||||
| # it will override what happens to be installed in /usr/(local/)lib/python... | # it will override what happens to be installed in /usr/(local/)lib/python... | ||||||
| possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), | possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), | ||||||
| @@ -35,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 | ||||||
| @@ -101,6 +135,29 @@ class VpnCommands(object): | |||||||
|         self.pipe.launch_vpn_instance(project_id) |         self.pipe.launch_vpn_instance(project_id) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ShellCommands(object): | ||||||
|  |     def run(self): | ||||||
|  |         "Runs a Python interactive interpreter. Tries to use IPython, if it's available." | ||||||
|  |         try: | ||||||
|  |             import IPython | ||||||
|  |             # Explicitly pass an empty list as arguments, because otherwise IPython | ||||||
|  |             # would use sys.argv from this script. | ||||||
|  |             shell = IPython.Shell.IPShell(argv=[]) | ||||||
|  |             shell.mainloop() | ||||||
|  |         except ImportError: | ||||||
|  |             import code | ||||||
|  |             try: # Try activating rlcompleter, because it's handy. | ||||||
|  |                 import readline | ||||||
|  |             except ImportError: | ||||||
|  |                 pass | ||||||
|  |             else: | ||||||
|  |                 # We don't have to wrap the following import in a 'try', because | ||||||
|  |                 # we already know 'readline' was imported successfully. | ||||||
|  |                 import rlcompleter | ||||||
|  |                 readline.parse_and_bind("tab:complete") | ||||||
|  |             code.interact() | ||||||
|  |  | ||||||
|  |  | ||||||
| class RoleCommands(object): | class RoleCommands(object): | ||||||
|     """Class for managing roles.""" |     """Class for managing roles.""" | ||||||
|  |  | ||||||
| @@ -130,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() | ||||||
|  |  | ||||||
| @@ -137,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 | ||||||
| @@ -155,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 | ||||||
|  |  | ||||||
| @@ -166,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.""" | ||||||
|  |  | ||||||
| @@ -206,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""" | ||||||
| @@ -219,11 +290,46 @@ class ProjectCommands(object): | |||||||
|             f.write(zip_file) |             f.write(zip_file) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FloatingIpCommands(object): | ||||||
|  |     """Class for managing floating ip.""" | ||||||
|  |  | ||||||
|  |     def create(self, host, range): | ||||||
|  |         """Creates floating ips for host by range | ||||||
|  |         arguments: host ip_range""" | ||||||
|  |         for address in IPy.IP(range): | ||||||
|  |             db.floating_ip_create(None, {'address': str(address), | ||||||
|  |                                          'host': host}) | ||||||
|  |  | ||||||
|  |     def delete(self, ip_range): | ||||||
|  |         """Deletes floating ips by range | ||||||
|  |         arguments: range""" | ||||||
|  |         for address in IPy.IP(ip_range): | ||||||
|  |             db.floating_ip_destroy(None, str(address)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def list(self, host=None): | ||||||
|  |         """Lists all floating ips (optionally by host) | ||||||
|  |         arguments: [host]""" | ||||||
|  |         if host == None: | ||||||
|  |             floating_ips = db.floating_ip_get_all(None) | ||||||
|  |         else: | ||||||
|  |             floating_ips = db.floating_ip_get_all_by_host(None, host) | ||||||
|  |         for floating_ip in floating_ips: | ||||||
|  |             instance = None | ||||||
|  |             if floating_ip['fixed_ip']: | ||||||
|  |                 instance = floating_ip['fixed_ip']['instance']['str_id'] | ||||||
|  |             print "%s\t%s\t%s" % (floating_ip['host'], | ||||||
|  |                                   floating_ip['address'], | ||||||
|  |                                   instance) | ||||||
|  |  | ||||||
|  |  | ||||||
| CATEGORIES = [ | CATEGORIES = [ | ||||||
|     ('user', UserCommands), |     ('user', UserCommands), | ||||||
|     ('project', ProjectCommands), |     ('project', ProjectCommands), | ||||||
|     ('role', RoleCommands), |     ('role', RoleCommands), | ||||||
|  |     ('shell', ShellCommands), | ||||||
|     ('vpn', VpnCommands), |     ('vpn', VpnCommands), | ||||||
|  |     ('floating', FloatingIpCommands) | ||||||
| ] | ] | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								bin/nova-scheduler
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										43
									
								
								bin/nova-scheduler
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | #!/usr/bin/env python | ||||||
|  | # 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. | ||||||
|  |  | ||||||
|  | """ | ||||||
|  |   Twistd daemon for the nova scheduler nodes. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import os | ||||||
|  | import sys | ||||||
|  |  | ||||||
|  | # If ../nova/__init__.py exists, add ../ to Python search path, so that | ||||||
|  | # it will override what happens to be installed in /usr/(local/)lib/python... | ||||||
|  | possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), | ||||||
|  |                                    os.pardir, | ||||||
|  |                                    os.pardir)) | ||||||
|  | if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): | ||||||
|  |     sys.path.insert(0, possible_topdir) | ||||||
|  |  | ||||||
|  | from nova import service | ||||||
|  | from nova import twistd | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     twistd.serve(__file__) | ||||||
|  |  | ||||||
|  | if __name__ == '__builtin__': | ||||||
|  |     application = service.Service.create() | ||||||
| @@ -33,6 +33,7 @@ SCOPE_ONELEVEL = 1 # not implemented | |||||||
| SCOPE_SUBTREE = 2 | SCOPE_SUBTREE = 2 | ||||||
| MOD_ADD = 0 | MOD_ADD = 0 | ||||||
| MOD_DELETE = 1 | MOD_DELETE = 1 | ||||||
|  | MOD_REPLACE = 2 | ||||||
|  |  | ||||||
|  |  | ||||||
| class NO_SUCH_OBJECT(Exception): # pylint: disable-msg=C0103 | class NO_SUCH_OBJECT(Exception): # pylint: disable-msg=C0103 | ||||||
| @@ -175,7 +176,7 @@ class FakeLDAP(object): | |||||||
|         Args: |         Args: | ||||||
|         dn -- a dn |         dn -- a dn | ||||||
|         attrs -- a list of tuples in the following form: |         attrs -- a list of tuples in the following form: | ||||||
|             ([MOD_ADD | MOD_DELETE], attribute, value) |             ([MOD_ADD | MOD_DELETE | MOD_REPACE], attribute, value) | ||||||
|  |  | ||||||
|         """ |         """ | ||||||
|         redis = datastore.Redis.instance() |         redis = datastore.Redis.instance() | ||||||
| @@ -185,6 +186,8 @@ class FakeLDAP(object): | |||||||
|             values = _from_json(redis.hget(key, k)) |             values = _from_json(redis.hget(key, k)) | ||||||
|             if cmd == MOD_ADD: |             if cmd == MOD_ADD: | ||||||
|                 values.append(v) |                 values.append(v) | ||||||
|  |             elif cmd == MOD_REPLACE: | ||||||
|  |                 values = [v] | ||||||
|             else: |             else: | ||||||
|                 values.remove(v) |                 values.remove(v) | ||||||
|             values = redis.hset(key, k, _to_json(values)) |             values = redis.hset(key, k, _to_json(values)) | ||||||
|   | |||||||
| @@ -99,13 +99,6 @@ class LdapDriver(object): | |||||||
|         dn = FLAGS.ldap_user_subtree |         dn = FLAGS.ldap_user_subtree | ||||||
|         return self.__to_user(self.__find_object(dn, query)) |         return self.__to_user(self.__find_object(dn, query)) | ||||||
|  |  | ||||||
|     def get_key_pair(self, uid, key_name): |  | ||||||
|         """Retrieve key pair by uid and key name""" |  | ||||||
|         dn = 'cn=%s,%s' % (key_name, |  | ||||||
|                            self.__uid_to_dn(uid)) |  | ||||||
|         attr = self.__find_object(dn, '(objectclass=novaKeyPair)') |  | ||||||
|         return self.__to_key_pair(uid, attr) |  | ||||||
|  |  | ||||||
|     def get_project(self, pid): |     def get_project(self, pid): | ||||||
|         """Retrieve project by id""" |         """Retrieve project by id""" | ||||||
|         dn = 'cn=%s,%s' % (pid, |         dn = 'cn=%s,%s' % (pid, | ||||||
| @@ -119,12 +112,6 @@ class LdapDriver(object): | |||||||
|                                   '(objectclass=novaUser)') |                                   '(objectclass=novaUser)') | ||||||
|         return [self.__to_user(attr) for attr in attrs] |         return [self.__to_user(attr) for attr in attrs] | ||||||
|  |  | ||||||
|     def get_key_pairs(self, uid): |  | ||||||
|         """Retrieve list of key pairs""" |  | ||||||
|         attrs = self.__find_objects(self.__uid_to_dn(uid), |  | ||||||
|                                   '(objectclass=novaKeyPair)') |  | ||||||
|         return [self.__to_key_pair(uid, attr) for attr in attrs] |  | ||||||
|  |  | ||||||
|     def get_projects(self, uid=None): |     def get_projects(self, uid=None): | ||||||
|         """Retrieve list of projects""" |         """Retrieve list of projects""" | ||||||
|         pattern = '(objectclass=novaProject)' |         pattern = '(objectclass=novaProject)' | ||||||
| @@ -154,21 +141,6 @@ class LdapDriver(object): | |||||||
|         self.conn.add_s(self.__uid_to_dn(name), attr) |         self.conn.add_s(self.__uid_to_dn(name), attr) | ||||||
|         return self.__to_user(dict(attr)) |         return self.__to_user(dict(attr)) | ||||||
|  |  | ||||||
|     def create_key_pair(self, uid, key_name, public_key, fingerprint): |  | ||||||
|         """Create a key pair""" |  | ||||||
|         # TODO(vish): possibly refactor this to store keys in their own ou |  | ||||||
|         #   and put dn reference in the user object |  | ||||||
|         attr = [ |  | ||||||
|             ('objectclass', ['novaKeyPair']), |  | ||||||
|             ('cn', [key_name]), |  | ||||||
|             ('sshPublicKey', [public_key]), |  | ||||||
|             ('keyFingerprint', [fingerprint]), |  | ||||||
|         ] |  | ||||||
|         self.conn.add_s('cn=%s,%s' % (key_name, |  | ||||||
|                                       self.__uid_to_dn(uid)), |  | ||||||
|                                       attr) |  | ||||||
|         return self.__to_key_pair(uid, dict(attr)) |  | ||||||
|  |  | ||||||
|     def create_project(self, name, manager_uid, |     def create_project(self, name, manager_uid, | ||||||
|                        description=None, member_uids=None): |                        description=None, member_uids=None): | ||||||
|         """Create a project""" |         """Create a project""" | ||||||
| @@ -202,6 +174,24 @@ class LdapDriver(object): | |||||||
|         self.conn.add_s('cn=%s,%s' % (name, FLAGS.ldap_project_subtree), attr) |         self.conn.add_s('cn=%s,%s' % (name, FLAGS.ldap_project_subtree), attr) | ||||||
|         return self.__to_project(dict(attr)) |         return self.__to_project(dict(attr)) | ||||||
|  |  | ||||||
|  |     def modify_project(self, project_id, manager_uid=None, description=None): | ||||||
|  |         """Modify an existing project""" | ||||||
|  |         if not manager_uid and not description: | ||||||
|  |             return | ||||||
|  |         attr = [] | ||||||
|  |         if manager_uid: | ||||||
|  |             if not self.__user_exists(manager_uid): | ||||||
|  |                 raise exception.NotFound("Project can't be modified because " | ||||||
|  |                                          "manager %s doesn't exist" % | ||||||
|  |                                          manager_uid) | ||||||
|  |             manager_dn = self.__uid_to_dn(manager_uid) | ||||||
|  |             attr.append((self.ldap.MOD_REPLACE, 'projectManager', manager_dn)) | ||||||
|  |         if description: | ||||||
|  |             attr.append((self.ldap.MOD_REPLACE, 'description', description)) | ||||||
|  |         self.conn.modify_s('cn=%s,%s' % (project_id, | ||||||
|  |                                          FLAGS.ldap_project_subtree), | ||||||
|  |                            attr) | ||||||
|  |  | ||||||
|     def add_to_project(self, uid, project_id): |     def add_to_project(self, uid, project_id): | ||||||
|         """Add user to project""" |         """Add user to project""" | ||||||
|         dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) |         dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) | ||||||
| @@ -265,19 +255,10 @@ class LdapDriver(object): | |||||||
|         """Delete a user""" |         """Delete a user""" | ||||||
|         if not self.__user_exists(uid): |         if not self.__user_exists(uid): | ||||||
|             raise exception.NotFound("User %s doesn't exist" % uid) |             raise exception.NotFound("User %s doesn't exist" % uid) | ||||||
|         self.__delete_key_pairs(uid) |  | ||||||
|         self.__remove_from_all(uid) |         self.__remove_from_all(uid) | ||||||
|         self.conn.delete_s('uid=%s,%s' % (uid, |         self.conn.delete_s('uid=%s,%s' % (uid, | ||||||
|                                           FLAGS.ldap_user_subtree)) |                                           FLAGS.ldap_user_subtree)) | ||||||
|  |  | ||||||
|     def delete_key_pair(self, uid, key_name): |  | ||||||
|         """Delete a key pair""" |  | ||||||
|         if not self.__key_pair_exists(uid, key_name): |  | ||||||
|             raise exception.NotFound("Key Pair %s doesn't exist for user %s" % |  | ||||||
|                             (key_name, uid)) |  | ||||||
|         self.conn.delete_s('cn=%s,uid=%s,%s' % (key_name, uid, |  | ||||||
|                                           FLAGS.ldap_user_subtree)) |  | ||||||
|  |  | ||||||
|     def delete_project(self, project_id): |     def delete_project(self, project_id): | ||||||
|         """Delete a project""" |         """Delete a project""" | ||||||
|         project_dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) |         project_dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) | ||||||
| @@ -288,10 +269,6 @@ class LdapDriver(object): | |||||||
|         """Check if user exists""" |         """Check if user exists""" | ||||||
|         return self.get_user(uid) != None |         return self.get_user(uid) != None | ||||||
|  |  | ||||||
|     def __key_pair_exists(self, uid, key_name): |  | ||||||
|         """Check if key pair exists""" |  | ||||||
|         return self.get_key_pair(uid, key_name) != None |  | ||||||
|  |  | ||||||
|     def __project_exists(self, project_id): |     def __project_exists(self, project_id): | ||||||
|         """Check if project exists""" |         """Check if project exists""" | ||||||
|         return self.get_project(project_id) != None |         return self.get_project(project_id) != None | ||||||
| @@ -341,13 +318,6 @@ class LdapDriver(object): | |||||||
|         """Check if group exists""" |         """Check if group exists""" | ||||||
|         return self.__find_object(dn, '(objectclass=groupOfNames)') != None |         return self.__find_object(dn, '(objectclass=groupOfNames)') != None | ||||||
|  |  | ||||||
|     def __delete_key_pairs(self, uid): |  | ||||||
|         """Delete all key pairs for user""" |  | ||||||
|         keys = self.get_key_pairs(uid) |  | ||||||
|         if keys != None: |  | ||||||
|             for key in keys: |  | ||||||
|                 self.delete_key_pair(uid, key['name']) |  | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def __role_to_dn(role, project_id=None): |     def __role_to_dn(role, project_id=None): | ||||||
|         """Convert role to corresponding dn""" |         """Convert role to corresponding dn""" | ||||||
| @@ -472,18 +442,6 @@ class LdapDriver(object): | |||||||
|             'secret': attr['secretKey'][0], |             'secret': attr['secretKey'][0], | ||||||
|             'admin': (attr['isAdmin'][0] == 'TRUE')} |             'admin': (attr['isAdmin'][0] == 'TRUE')} | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def __to_key_pair(owner, attr): |  | ||||||
|         """Convert ldap attributes to KeyPair object""" |  | ||||||
|         if attr == None: |  | ||||||
|             return None |  | ||||||
|         return { |  | ||||||
|             'id': attr['cn'][0], |  | ||||||
|             'name': attr['cn'][0], |  | ||||||
|             'owner_id': owner, |  | ||||||
|             'public_key': attr['sshPublicKey'][0], |  | ||||||
|             'fingerprint': attr['keyFingerprint'][0]} |  | ||||||
|  |  | ||||||
|     def __to_project(self, attr): |     def __to_project(self, attr): | ||||||
|         """Convert ldap attributes to Project object""" |         """Convert ldap attributes to Project object""" | ||||||
|         if attr == None: |         if attr == None: | ||||||
|   | |||||||
| @@ -128,24 +128,6 @@ class User(AuthBase): | |||||||
|     def is_project_manager(self, project): |     def is_project_manager(self, project): | ||||||
|         return AuthManager().is_project_manager(self, project) |         return AuthManager().is_project_manager(self, project) | ||||||
|  |  | ||||||
|     def generate_key_pair(self, name): |  | ||||||
|         return AuthManager().generate_key_pair(self.id, name) |  | ||||||
|  |  | ||||||
|     def create_key_pair(self, name, public_key, fingerprint): |  | ||||||
|         return AuthManager().create_key_pair(self.id, |  | ||||||
|                                              name, |  | ||||||
|                                              public_key, |  | ||||||
|                                              fingerprint) |  | ||||||
|  |  | ||||||
|     def get_key_pair(self, name): |  | ||||||
|         return AuthManager().get_key_pair(self.id, name) |  | ||||||
|  |  | ||||||
|     def delete_key_pair(self, name): |  | ||||||
|         return AuthManager().delete_key_pair(self.id, name) |  | ||||||
|  |  | ||||||
|     def get_key_pairs(self): |  | ||||||
|         return AuthManager().get_key_pairs(self.id) |  | ||||||
|  |  | ||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         return "User('%s', '%s', '%s', '%s', %s)" % (self.id, |         return "User('%s', '%s', '%s', '%s', %s)" % (self.id, | ||||||
|                                                      self.name, |                                                      self.name, | ||||||
| @@ -154,29 +136,6 @@ class User(AuthBase): | |||||||
|                                                      self.admin) |                                                      self.admin) | ||||||
|  |  | ||||||
|  |  | ||||||
| class KeyPair(AuthBase): |  | ||||||
|     """Represents an ssh key returned from the datastore |  | ||||||
|  |  | ||||||
|     Even though this object is named KeyPair, only the public key and |  | ||||||
|     fingerprint is stored. The user's private key is not saved. |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     def __init__(self, id, name, owner_id, public_key, fingerprint): |  | ||||||
|         AuthBase.__init__(self) |  | ||||||
|         self.id = id |  | ||||||
|         self.name = name |  | ||||||
|         self.owner_id = owner_id |  | ||||||
|         self.public_key = public_key |  | ||||||
|         self.fingerprint = fingerprint |  | ||||||
|  |  | ||||||
|     def __repr__(self): |  | ||||||
|         return "KeyPair('%s', '%s', '%s', '%s', '%s')" % (self.id, |  | ||||||
|                                                           self.name, |  | ||||||
|                                                           self.owner_id, |  | ||||||
|                                                           self.public_key, |  | ||||||
|                                                           self.fingerprint) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class Project(AuthBase): | class Project(AuthBase): | ||||||
|     """Represents a Project returned from the datastore""" |     """Represents a Project returned from the datastore""" | ||||||
|  |  | ||||||
| @@ -539,6 +498,26 @@ class AuthManager(object): | |||||||
|                 db.security_group_create({}, values) |                 db.security_group_create({}, values) | ||||||
|                 return project |                 return project | ||||||
|  |  | ||||||
|  |     def modify_project(self, project, manager_user=None, description=None): | ||||||
|  |         """Modify a project | ||||||
|  |  | ||||||
|  |         @type name: Project or project_id | ||||||
|  |         @param project: The project to modify. | ||||||
|  |  | ||||||
|  |         @type manager_user: User or uid | ||||||
|  |         @param manager_user: This user will be the new project manager. | ||||||
|  |  | ||||||
|  |         @type description: str | ||||||
|  |         @param project: This will be the new description of the project. | ||||||
|  |  | ||||||
|  |         """ | ||||||
|  |         if manager_user: | ||||||
|  |             manager_user = User.safe_id(manager_user) | ||||||
|  |         with self.driver() as drv: | ||||||
|  |             drv.modify_project(Project.safe_id(project), | ||||||
|  |                                manager_user, | ||||||
|  |                                description) | ||||||
|  |  | ||||||
|     def add_to_project(self, user, project): |     def add_to_project(self, user, project): | ||||||
|         """Add user to project""" |         """Add user to project""" | ||||||
|         with self.driver() as drv: |         with self.driver() as drv: | ||||||
| @@ -659,67 +638,13 @@ class AuthManager(object): | |||||||
|                 return User(**user_dict) |                 return User(**user_dict) | ||||||
|  |  | ||||||
|     def delete_user(self, user): |     def delete_user(self, user): | ||||||
|         """Deletes a user""" |         """Deletes a user | ||||||
|         with self.driver() as drv: |  | ||||||
|             drv.delete_user(User.safe_id(user)) |  | ||||||
|  |  | ||||||
|     def generate_key_pair(self, user, key_name): |         Additionally deletes all users key_pairs""" | ||||||
|         """Generates a key pair for a user |  | ||||||
|  |  | ||||||
|         Generates a public and private key, stores the public key using the |  | ||||||
|         key_name, and returns the private key and fingerprint. |  | ||||||
|  |  | ||||||
|         @type user: User or uid |  | ||||||
|         @param user: User for which to create key pair. |  | ||||||
|  |  | ||||||
|         @type key_name: str |  | ||||||
|         @param key_name: Name to use for the generated KeyPair. |  | ||||||
|  |  | ||||||
|         @rtype: tuple (private_key, fingerprint) |  | ||||||
|         @return: A tuple containing the private_key and fingerprint. |  | ||||||
|         """ |  | ||||||
|         # NOTE(vish): generating key pair is slow so check for legal |  | ||||||
|         #             creation before creating keypair |  | ||||||
|         uid = User.safe_id(user) |         uid = User.safe_id(user) | ||||||
|  |         db.key_pair_destroy_all_by_user(None, uid) | ||||||
|         with self.driver() as drv: |         with self.driver() as drv: | ||||||
|             if not drv.get_user(uid): |             drv.delete_user(uid) | ||||||
|                 raise exception.NotFound("User %s doesn't exist" % user) |  | ||||||
|             if drv.get_key_pair(uid, key_name): |  | ||||||
|                 raise exception.Duplicate("The keypair %s already exists" |  | ||||||
|                                           % key_name) |  | ||||||
|         private_key, public_key, fingerprint = crypto.generate_key_pair() |  | ||||||
|         self.create_key_pair(uid, key_name, public_key, fingerprint) |  | ||||||
|         return private_key, fingerprint |  | ||||||
|  |  | ||||||
|     def create_key_pair(self, user, key_name, public_key, fingerprint): |  | ||||||
|         """Creates a key pair for user""" |  | ||||||
|         with self.driver() as drv: |  | ||||||
|             kp_dict = drv.create_key_pair(User.safe_id(user), |  | ||||||
|                                           key_name, |  | ||||||
|                                           public_key, |  | ||||||
|                                           fingerprint) |  | ||||||
|             if kp_dict: |  | ||||||
|                 return KeyPair(**kp_dict) |  | ||||||
|  |  | ||||||
|     def get_key_pair(self, user, key_name): |  | ||||||
|         """Retrieves a key pair for user""" |  | ||||||
|         with self.driver() as drv: |  | ||||||
|             kp_dict = drv.get_key_pair(User.safe_id(user), key_name) |  | ||||||
|             if kp_dict: |  | ||||||
|                 return KeyPair(**kp_dict) |  | ||||||
|  |  | ||||||
|     def get_key_pairs(self, user): |  | ||||||
|         """Retrieves all key pairs for user""" |  | ||||||
|         with self.driver() as drv: |  | ||||||
|             kp_list = drv.get_key_pairs(User.safe_id(user)) |  | ||||||
|             if not kp_list: |  | ||||||
|                 return [] |  | ||||||
|             return [KeyPair(**kp_dict) for kp_dict in kp_list] |  | ||||||
|  |  | ||||||
|     def delete_key_pair(self, user, key_name): |  | ||||||
|         """Deletes a key pair for user""" |  | ||||||
|         with self.driver() as drv: |  | ||||||
|             drv.delete_key_pair(User.safe_id(user), key_name) |  | ||||||
|  |  | ||||||
|     def get_credentials(self, user, project=None): |     def get_credentials(self, user, project=None): | ||||||
|         """Get credential zip for user in project""" |         """Get credential zip for user in project""" | ||||||
|   | |||||||
| @@ -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,344 +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: |  | ||||||
|             self._error(type(ex).__name__ + "." + ex.code, 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,870 +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 logging |  | ||||||
| import os |  | ||||||
| import time |  | ||||||
|  |  | ||||||
| from twisted.internet import defer |  | ||||||
|  |  | ||||||
| from nova import db |  | ||||||
| from nova import exception |  | ||||||
| from nova import flags |  | ||||||
| from nova import rpc |  | ||||||
| from nova import utils |  | ||||||
| from nova.auth import rbac |  | ||||||
| from nova.auth import manager |  | ||||||
| from nova.compute.instance_types import INSTANCE_TYPES |  | ||||||
| from nova.endpoint import images |  | ||||||
|  |  | ||||||
|  |  | ||||||
| FLAGS = flags.FLAGS |  | ||||||
| flags.DECLARE('storage_availability_zone', 'nova.volume.manager') |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def _gen_key(user_id, key_name): |  | ||||||
|     """ Tuck this into AuthManager """ |  | ||||||
|     try: |  | ||||||
|         mgr = manager.AuthManager() |  | ||||||
|         private_key, fingerprint = mgr.generate_key_pair(user_id, key_name) |  | ||||||
|     except Exception as ex: |  | ||||||
|         return {'exception': ex} |  | ||||||
|     return {'private_key': private_key, 'fingerprint': fingerprint} |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 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(project_id): |  | ||||||
|             line = '%s slots=%d' % (instance.fixed_ip['str_id'], |  | ||||||
|                 INSTANCE_TYPES[instance['instance_type']]['vcpus']) |  | ||||||
|             if instance['key_name'] in result: |  | ||||||
|                 result[instance['key_name']].append(line) |  | ||||||
|             else: |  | ||||||
|                 result[instance['key_name']] = [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_ip_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['ami_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': { |  | ||||||
|                     'availaibility-zone': instance_ref['availability_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): |  | ||||||
|         # TODO(vish): region_name is an array.  Support filtering |  | ||||||
|         return {'regionInfo': [{'regionName': 'nova', |  | ||||||
|                                 'regionUrl': FLAGS.ec2_url}]} |  | ||||||
|  |  | ||||||
|     @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 = context.user.get_key_pairs() |  | ||||||
|         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']}) |  | ||||||
|         pool.apply_async(_gen_key, [context.user.id, key_name], |  | ||||||
|             callback=_complete) |  | ||||||
|         return dcall |  | ||||||
|  |  | ||||||
|     @rbac.allow('all') |  | ||||||
|     def delete_key_pair(self, context, key_name, **kwargs): |  | ||||||
|         context.user.delete_key_pair(key_name) |  | ||||||
|         # aws returns true even if the key doens't exist |  | ||||||
|         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 |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @rbac.allow('netadmin') |  | ||||||
|     def revoke_security_group_ingress(self, context, group_name, |  | ||||||
|                                       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): |  | ||||||
|         security_group = db.security_group_get_by_name(context, |  | ||||||
|                                                        context.project.id, |  | ||||||
|                                                        group_name) |  | ||||||
|  |  | ||||||
|         criteria = {} |  | ||||||
|  |  | ||||||
|         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) |  | ||||||
|  |  | ||||||
|             criteria['group_id'] = source_security_group |  | ||||||
|         elif cidr_ip: |  | ||||||
|             criteria['cidr'] = cidr_ip |  | ||||||
|         else: |  | ||||||
|             return { 'return': False } |  | ||||||
|  |  | ||||||
|         if ip_protocol and from_port and to_port: |  | ||||||
|             criteria['protocol'] = ip_protocol |  | ||||||
|             criteria['from_port'] = from_port |  | ||||||
|             criteria['to_port'] = to_port |  | ||||||
|         else: |  | ||||||
|             # If cidr based filtering, protocol and ports are mandatory |  | ||||||
|             if 'cidr' in criteria: |  | ||||||
|                 return { 'return': False } |  | ||||||
|  |  | ||||||
|         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): Lots and lots of input validation. We're accepting |  | ||||||
|     #              strings here (such as ipProtocol), which are put into |  | ||||||
|     #              filter rules verbatim. |  | ||||||
|     # 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. |  | ||||||
|     # TODO(soren): De-duplicate the turning method arguments into dict stuff. |  | ||||||
|     #              revoke_security_group_ingress uses the exact same logic. |  | ||||||
|     @rbac.allow('netadmin') |  | ||||||
|     def authorize_security_group_ingress(self, context, group_name, |  | ||||||
|                                          to_port=None, from_port=None, |  | ||||||
|                                          ip_protocol=None, cidr_ip=None, |  | ||||||
|                                          source_security_group_name=None, |  | ||||||
|                                          source_security_group_owner_id=None): |  | ||||||
|         security_group = db.security_group_get_by_name(context, |  | ||||||
|                                                        context.project.id, |  | ||||||
|                                                        group_name) |  | ||||||
|         values = { 'parent_group_id' : security_group.id } |  | ||||||
|  |  | ||||||
|         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: |  | ||||||
|             values['cidr'] = cidr_ip |  | ||||||
|         else: |  | ||||||
|             return { 'return': False } |  | ||||||
|  |  | ||||||
|         if ip_protocol and from_port and to_port: |  | ||||||
|             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 |  | ||||||
|  |  | ||||||
|         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'], |  | ||||||
|                 '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): |  | ||||||
|         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.volume_topic, {"method": "create_volume", |  | ||||||
|                                       "args": {"context": None, |  | ||||||
|                                                "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['attach_status'] == "attached": |  | ||||||
|             raise exception.ApiError("Volume is already attached") |  | ||||||
|         instance_ref = db.instance_get_by_str(context, instance_id) |  | ||||||
|         host = db.instance_get_host(context, instance_ref['id']) |  | ||||||
|         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.Error("Volume isn't attached to anything!") |  | ||||||
|         # TODO(vish): abstract status checking? |  | ||||||
|         if volume_ref['status'] == "available": |  | ||||||
|             raise exception.Error("Volume is already detached") |  | ||||||
|         try: |  | ||||||
|             host = db.instance_get_host(context, instance_ref['id']) |  | ||||||
|             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 not 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'] |  | ||||||
|             } |  | ||||||
|             floating_addr = db.instance_get_floating_address(context, |  | ||||||
|                                                              instance['id']) |  | ||||||
|             i['publicDnsName'] = floating_addr |  | ||||||
|             fixed_addr = db.instance_get_fixed_address(context, |  | ||||||
|                                                        instance['id']) |  | ||||||
|             i['privateDnsName'] = fixed_addr |  | ||||||
|             if not i['publicDnsName']: |  | ||||||
|                 i['publicDnsName'] = i['privateDnsName'] |  | ||||||
|             i['dnsName'] = None |  | ||||||
|             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['id_str'] |  | ||||||
|             instance_ref = db.floating_ip_get_instance(address) |  | ||||||
|             address_rv = {'public_ip': address, |  | ||||||
|                           'instance_id': instance_ref['id_str']} |  | ||||||
|             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): |  | ||||||
|         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_ip": 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_ip": floating_ip_ref['str_id'], |  | ||||||
|                                    "fixed_ip": fixed_ip_ref['str_id'], |  | ||||||
|                                    "instance_id": instance_ref['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_ip": 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 = db.network_get_host(context, network_ref['id']) |  | ||||||
|         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): |  | ||||||
|         # 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 instances...") |  | ||||||
|         launch_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) |  | ||||||
|         key_data = None |  | ||||||
|         if kwargs.has_key('key_name'): |  | ||||||
|             key_pair = context.user.get_key_pair(kwargs['key_name']) |  | ||||||
|             if not key_pair: |  | ||||||
|                 raise exception.ApiError('Key Pair %s not found' % |  | ||||||
|                                          kwargs['key_name']) |  | ||||||
|             key_data = key_pair.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['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', '') |  | ||||||
|         base_options['instance_type'] = kwargs.get('instance_type', 'm1.small') |  | ||||||
|  |  | ||||||
|         for num in range(int(kwargs['max_count'])): |  | ||||||
|             inst_id = db.instance_create(context, base_options) |  | ||||||
|  |  | ||||||
|             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'] = inst_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.compute_topic, |  | ||||||
|                  {"method": "run_instance", |  | ||||||
|                   "args": {"context": None, |  | ||||||
|                            "instance_id": inst_id}}) |  | ||||||
|             logging.debug("Casting to node 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 |  | ||||||
|  |  | ||||||
|             # 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. |  | ||||||
|                 db.fixed_ip_deallocate(context, address) |  | ||||||
|  |  | ||||||
|             host = db.instance_get_host(context, instance_ref['id']) |  | ||||||
|             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 = db.instance_get_host(context, instance_ref['id']) |  | ||||||
|             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) |  | ||||||
|         host = db.volume_get_host(context, volume_ref['id']) |  | ||||||
|         rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host), |  | ||||||
|                             {"method": "delete_volume", |  | ||||||
|                              "args": {"context": None, |  | ||||||
|                                       "volume_id": volume_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) |  | ||||||
| @@ -167,10 +167,14 @@ def DECLARE(name, module_string, flag_values=FLAGS): | |||||||
| # Define any app-specific flags in their own files, docs at: | # Define any app-specific flags in their own files, docs at: | ||||||
| # http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39 | # http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39 | ||||||
|  |  | ||||||
|  | DEFINE_list('region_list', | ||||||
|  |             [], | ||||||
|  |             'list of region=url pairs separated by commas') | ||||||
| DEFINE_string('connection_type', 'libvirt', 'libvirt, xenapi or fake') | DEFINE_string('connection_type', 'libvirt', 'libvirt, xenapi or fake') | ||||||
| DEFINE_integer('s3_port', 3333, 's3 port') | DEFINE_integer('s3_port', 3333, 's3 port') | ||||||
| DEFINE_string('s3_host', '127.0.0.1', 's3 host') | DEFINE_string('s3_host', '127.0.0.1', 's3 host') | ||||||
| DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') | DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') | ||||||
|  | DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') | ||||||
| DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on') | DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on') | ||||||
| DEFINE_string('network_topic', 'network', 'the topic network nodes listen on') | DEFINE_string('network_topic', 'network', 'the topic network nodes listen on') | ||||||
|  |  | ||||||
| @@ -213,6 +217,8 @@ DEFINE_string('network_manager', 'nova.network.manager.VlanManager', | |||||||
|               'Manager for network') |               'Manager for network') | ||||||
| DEFINE_string('volume_manager', 'nova.volume.manager.AOEManager', | DEFINE_string('volume_manager', 'nova.volume.manager.AOEManager', | ||||||
|               'Manager for volume') |               'Manager for volume') | ||||||
|  | DEFINE_string('scheduler_manager', 'nova.scheduler.manager.SchedulerManager', | ||||||
|  |               'Manager for scheduler') | ||||||
|  |  | ||||||
| DEFINE_string('host', socket.gethostname(), | DEFINE_string('host', socket.gethostname(), | ||||||
|               'name of this node') |               'name of this node') | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ from twisted.internet import protocol | |||||||
| from twisted.internet import reactor | from twisted.internet import reactor | ||||||
|  |  | ||||||
| from nova import flags | from nova import flags | ||||||
| from nova.utils import ProcessExecutionError | from nova.exception import ProcessExecutionError | ||||||
|  |  | ||||||
| FLAGS = flags.FLAGS | FLAGS = flags.FLAGS | ||||||
| flags.DEFINE_integer('process_pool_size', 4, | flags.DEFINE_integer('process_pool_size', 4, | ||||||
| @@ -127,7 +127,7 @@ def get_process_output(executable, args=None, env=None, path=None, | |||||||
|     deferred = defer.Deferred() |     deferred = defer.Deferred() | ||||||
|     cmd = executable |     cmd = executable | ||||||
|     if args: |     if args: | ||||||
|         cmd = cmd + " " + ' '.join(args) |         cmd = " ".join([cmd] + args) | ||||||
|     logging.debug("Running cmd: %s", cmd) |     logging.debug("Running cmd: %s", cmd) | ||||||
|     process_handler = BackRelayWithInput( |     process_handler = BackRelayWithInput( | ||||||
|             deferred, |             deferred, | ||||||
| @@ -141,8 +141,8 @@ def get_process_output(executable, args=None, env=None, path=None, | |||||||
|     executable = str(executable) |     executable = str(executable) | ||||||
|     if not args is None: |     if not args is None: | ||||||
|         args = [str(x) for x in args] |         args = [str(x) for x in args] | ||||||
|     process_reactor.spawnProcess(   process_handler, executable, |     process_reactor.spawnProcess(process_handler, executable, | ||||||
|                                     (executable,)+tuple(args), env, path) |                                  (executable,)+tuple(args), env, path) | ||||||
|     return deferred |     return deferred | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								nova/scheduler/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								nova/scheduler/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||||
|  |  | ||||||
|  | # Copyright (c) 2010 Openstack, LLC. | ||||||
|  | # | ||||||
|  | #    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. | ||||||
|  |  | ||||||
|  | """ | ||||||
|  | :mod:`nova.scheduler` -- Scheduler Nodes | ||||||
|  | ===================================================== | ||||||
|  |  | ||||||
|  | .. automodule:: nova.scheduler | ||||||
|  |    :platform: Unix | ||||||
|  |    :synopsis: Module that picks a compute node to run a VM instance. | ||||||
|  | .. moduleauthor:: Chris Behrens <cbehrens@codestud.com> | ||||||
|  | """ | ||||||
							
								
								
									
										90
									
								
								nova/scheduler/simple.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								nova/scheduler/simple.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | |||||||
|  | # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||||
|  |  | ||||||
|  | # Copyright (c) 2010 Openstack, LLC. | ||||||
|  | # 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. | ||||||
|  |  | ||||||
|  | """ | ||||||
|  | Simple Scheduler | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import datetime | ||||||
|  |  | ||||||
|  | from nova import db | ||||||
|  | from nova import flags | ||||||
|  | from nova.scheduler import driver | ||||||
|  | from nova.scheduler import chance | ||||||
|  |  | ||||||
|  | FLAGS = flags.FLAGS | ||||||
|  | flags.DEFINE_integer("max_cores", 16, | ||||||
|  |                      "maximum number of instance cores to allow per host") | ||||||
|  | flags.DEFINE_integer("max_gigabytes", 10000, | ||||||
|  |                      "maximum number of volume gigabytes to allow per host") | ||||||
|  | flags.DEFINE_integer("max_networks", 1000, | ||||||
|  |                      "maximum number of networks to allow per host") | ||||||
|  |  | ||||||
|  | class SimpleScheduler(chance.ChanceScheduler): | ||||||
|  |     """Implements Naive Scheduler that tries to find least loaded host.""" | ||||||
|  |  | ||||||
|  |     def schedule_run_instance(self, context, instance_id, *_args, **_kwargs): | ||||||
|  |         """Picks a host that is up and has the fewest running instances.""" | ||||||
|  |         instance_ref = db.instance_get(context, instance_id) | ||||||
|  |         results = db.service_get_all_compute_sorted(context) | ||||||
|  |         for result in results: | ||||||
|  |             (service, instance_cores) = result | ||||||
|  |             if instance_cores + instance_ref['vcpus'] > FLAGS.max_cores: | ||||||
|  |                 raise driver.NoValidHost("All hosts have too many cores") | ||||||
|  |             if self.service_is_up(service): | ||||||
|  |                 # NOTE(vish): this probably belongs in the manager, if we | ||||||
|  |                 #             can generalize this somehow | ||||||
|  |                 now = datetime.datetime.utcnow() | ||||||
|  |                 db.instance_update(context, | ||||||
|  |                                    instance_id, | ||||||
|  |                                    {'host': service['host'], | ||||||
|  |                                     'scheduled_at': now}) | ||||||
|  |                 return service['host'] | ||||||
|  |         raise driver.NoValidHost("No hosts found") | ||||||
|  |  | ||||||
|  |     def schedule_create_volume(self, context, volume_id, *_args, **_kwargs): | ||||||
|  |         """Picks a host that is up and has the fewest volumes.""" | ||||||
|  |         volume_ref = db.volume_get(context, volume_id) | ||||||
|  |         results = db.service_get_all_volume_sorted(context) | ||||||
|  |         for result in results: | ||||||
|  |             (service, volume_gigabytes) = result | ||||||
|  |             if volume_gigabytes + volume_ref['size'] > FLAGS.max_gigabytes: | ||||||
|  |                 raise driver.NoValidHost("All hosts have too many gigabytes") | ||||||
|  |             if self.service_is_up(service): | ||||||
|  |                 # NOTE(vish): this probably belongs in the manager, if we | ||||||
|  |                 #             can generalize this somehow | ||||||
|  |                 now = datetime.datetime.utcnow() | ||||||
|  |                 db.volume_update(context, | ||||||
|  |                                  volume_id, | ||||||
|  |                                  {'host': service['host'], | ||||||
|  |                                   'scheduled_at': now}) | ||||||
|  |                 return service['host'] | ||||||
|  |         raise driver.NoValidHost("No hosts found") | ||||||
|  |  | ||||||
|  |     def schedule_set_network_host(self, context, *_args, **_kwargs): | ||||||
|  |         """Picks a host that is up and has the fewest networks.""" | ||||||
|  |  | ||||||
|  |         results = db.service_get_all_network_sorted(context) | ||||||
|  |         for result in results: | ||||||
|  |             (service, instance_count) = result | ||||||
|  |             if instance_count >= FLAGS.max_networks: | ||||||
|  |                 raise driver.NoValidHost("All hosts have too many networks") | ||||||
|  |             if self.service_is_up(service): | ||||||
|  |                 return service['host'] | ||||||
|  |         raise driver.NoValidHost("No hosts found") | ||||||
| @@ -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,7 +82,6 @@ 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' | ||||||
|  |  | ||||||
| @@ -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 | ||||||
| @@ -224,7 +125,8 @@ class ApiEc2TestCase(test.BaseTestCase): | |||||||
|                           for x in range(random.randint(4, 8))) |                           for x in range(random.randint(4, 8))) | ||||||
|         user = self.manager.create_user('fake', 'fake', 'fake') |         user = self.manager.create_user('fake', 'fake', 'fake') | ||||||
|         project = self.manager.create_project('fake', 'fake', 'fake') |         project = self.manager.create_project('fake', 'fake', 'fake') | ||||||
|         self.manager.generate_key_pair(user.id, keyname) |         # NOTE(vish): create depends on pool, so call helper directly | ||||||
|  |         cloud._gen_key(None, user.id, keyname) | ||||||
|  |  | ||||||
|         rv = self.ec2.get_all_key_pairs() |         rv = self.ec2.get_all_key_pairs() | ||||||
|         results = [k for k in rv if k.name == keyname] |         results = [k for k in rv if k.name == keyname] | ||||||
|   | |||||||
| @@ -17,8 +17,6 @@ | |||||||
| #    under the License. | #    under the License. | ||||||
|  |  | ||||||
| import logging | import logging | ||||||
| from M2Crypto import BIO |  | ||||||
| from M2Crypto import RSA |  | ||||||
| from M2Crypto import X509 | from M2Crypto import X509 | ||||||
| import unittest | import unittest | ||||||
|  |  | ||||||
| @@ -26,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 | ||||||
|  |  | ||||||
| @@ -65,35 +63,6 @@ class AuthTestCase(test.BaseTestCase): | |||||||
|         'export S3_URL="http://127.0.0.1:3333/"\n' + |         'export S3_URL="http://127.0.0.1:3333/"\n' + | ||||||
|         'export EC2_USER_ID="test1"\n') |         'export EC2_USER_ID="test1"\n') | ||||||
|  |  | ||||||
|     def test_006_test_key_storage(self): |  | ||||||
|         user = self.manager.get_user('test1') |  | ||||||
|         user.create_key_pair('public', 'key', 'fingerprint') |  | ||||||
|         key = user.get_key_pair('public') |  | ||||||
|         self.assertEqual('key', key.public_key) |  | ||||||
|         self.assertEqual('fingerprint', key.fingerprint) |  | ||||||
|  |  | ||||||
|     def test_007_test_key_generation(self): |  | ||||||
|         user = self.manager.get_user('test1') |  | ||||||
|         private_key, fingerprint = user.generate_key_pair('public2') |  | ||||||
|         key = RSA.load_key_string(private_key, callback=lambda: None) |  | ||||||
|         bio = BIO.MemoryBuffer() |  | ||||||
|         public_key = user.get_key_pair('public2').public_key |  | ||||||
|         key.save_pub_key_bio(bio) |  | ||||||
|         converted = crypto.ssl_pub_to_ssh_pub(bio.read()) |  | ||||||
|         # assert key fields are equal |  | ||||||
|         self.assertEqual(public_key.split(" ")[1].strip(), |  | ||||||
|                          converted.split(" ")[1].strip()) |  | ||||||
|  |  | ||||||
|     def test_008_can_list_key_pairs(self): |  | ||||||
|         keys = self.manager.get_user('test1').get_key_pairs() |  | ||||||
|         self.assertTrue(filter(lambda k: k.name == 'public', keys)) |  | ||||||
|         self.assertTrue(filter(lambda k: k.name == 'public2', keys)) |  | ||||||
|  |  | ||||||
|     def test_009_can_delete_key_pair(self): |  | ||||||
|         self.manager.get_user('test1').delete_key_pair('public') |  | ||||||
|         keys = self.manager.get_user('test1').get_key_pairs() |  | ||||||
|         self.assertFalse(filter(lambda k: k.name == 'public', keys)) |  | ||||||
|  |  | ||||||
|     def test_010_can_list_users(self): |     def test_010_can_list_users(self): | ||||||
|         users = self.manager.get_users() |         users = self.manager.get_users() | ||||||
|         logging.warn(users) |         logging.warn(users) | ||||||
| @@ -204,6 +173,12 @@ class AuthTestCase(test.BaseTestCase): | |||||||
|         self.assert_(len(self.manager.get_projects()) > 1) |         self.assert_(len(self.manager.get_projects()) > 1) | ||||||
|         self.assertEqual(len(self.manager.get_projects('test2')), 1) |         self.assertEqual(len(self.manager.get_projects('test2')), 1) | ||||||
|  |  | ||||||
|  |     def test_220_can_modify_project(self): | ||||||
|  |         self.manager.modify_project('testproj', 'test2', 'new description') | ||||||
|  |         project = self.manager.get_project('testproj') | ||||||
|  |         self.assertEqual(project.project_manager_id, 'test2') | ||||||
|  |         self.assertEqual(project.description, 'new description') | ||||||
|  |  | ||||||
|     def test_299_can_delete_project(self): |     def test_299_can_delete_project(self): | ||||||
|         self.manager.delete_project('testproj') |         self.manager.delete_project('testproj') | ||||||
|         self.assertFalse(filter(lambda p: p.name == 'testproj', self.manager.get_projects())) |         self.assertFalse(filter(lambda p: p.name == 'testproj', self.manager.get_projects())) | ||||||
|   | |||||||
| @@ -17,20 +17,26 @@ | |||||||
| #    under the License. | #    under the License. | ||||||
|  |  | ||||||
| import logging | import logging | ||||||
|  | from M2Crypto import BIO | ||||||
|  | from M2Crypto import RSA | ||||||
| import StringIO | import StringIO | ||||||
| import time | import time | ||||||
|  |  | ||||||
| from tornado import ioloop | from tornado import ioloop | ||||||
| from twisted.internet import defer | from twisted.internet import defer | ||||||
| import unittest | import unittest | ||||||
| from xml.etree import ElementTree | from xml.etree import ElementTree | ||||||
|  |  | ||||||
|  | from nova import crypto | ||||||
|  | from nova import db | ||||||
| from nova import flags | from nova import flags | ||||||
| from nova import rpc | from nova import rpc | ||||||
| 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.compute import power_state | ||||||
| from nova.endpoint import cloud | from nova.api.ec2 import context | ||||||
|  | from nova.api.ec2 import cloud | ||||||
|  |  | ||||||
|  |  | ||||||
| FLAGS = flags.FLAGS | FLAGS = flags.FLAGS | ||||||
| @@ -54,16 +60,20 @@ class CloudTestCase(test.BaseTestCase): | |||||||
|                                                      proxy=self.compute) |                                                      proxy=self.compute) | ||||||
|         self.injected.append(self.compute_consumer.attach_to_tornado(self.ioloop)) |         self.injected.append(self.compute_consumer.attach_to_tornado(self.ioloop)) | ||||||
|  |  | ||||||
|         try: |         self.manager = manager.AuthManager() | ||||||
|             manager.AuthManager().create_user('admin', 'admin', 'admin') |         self.user = self.manager.create_user('admin', 'admin', 'admin', True) | ||||||
|         except: pass |         self.project = self.manager.create_project('proj', 'admin', 'proj') | ||||||
|         admin = manager.AuthManager().get_user('admin') |         self.context = context.APIRequestContext(user=self.user, | ||||||
|         project = manager.AuthManager().create_project('proj', 'admin', 'proj') |                                                  project=self.project) | ||||||
|         self.context = api.APIRequestContext(handler=None,project=project,user=admin) |  | ||||||
|  |  | ||||||
|     def tearDown(self): |     def tearDown(self): | ||||||
|         manager.AuthManager().delete_project('proj') |         self.manager.delete_project(self.project) | ||||||
|         manager.AuthManager().delete_user('admin') |         self.manager.delete_user(self.user) | ||||||
|  |         super(CloudTestCase, self).setUp() | ||||||
|  |  | ||||||
|  |     def _create_key(self, name): | ||||||
|  |         # NOTE(vish): create depends on pool, so just call helper directly | ||||||
|  |         return cloud._gen_key(self.context, self.context.user.id, name) | ||||||
|  |  | ||||||
|     def test_console_output(self): |     def test_console_output(self): | ||||||
|         if FLAGS.connection_type == 'fake': |         if FLAGS.connection_type == 'fake': | ||||||
| @@ -76,6 +86,33 @@ class CloudTestCase(test.BaseTestCase): | |||||||
|         self.assert_(output) |         self.assert_(output) | ||||||
|         rv = yield self.compute.terminate_instance(instance_id) |         rv = yield self.compute.terminate_instance(instance_id) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def test_key_generation(self): | ||||||
|  |         result = self._create_key('test') | ||||||
|  |         private_key = result['private_key'] | ||||||
|  |         key = RSA.load_key_string(private_key, callback=lambda: None) | ||||||
|  |         bio = BIO.MemoryBuffer() | ||||||
|  |         public_key = db.key_pair_get(self.context, | ||||||
|  |                                     self.context.user.id, | ||||||
|  |                                     'test')['public_key'] | ||||||
|  |         key.save_pub_key_bio(bio) | ||||||
|  |         converted = crypto.ssl_pub_to_ssh_pub(bio.read()) | ||||||
|  |         # assert key fields are equal | ||||||
|  |         self.assertEqual(public_key.split(" ")[1].strip(), | ||||||
|  |                          converted.split(" ")[1].strip()) | ||||||
|  |  | ||||||
|  |     def test_describe_key_pairs(self): | ||||||
|  |         self._create_key('test1') | ||||||
|  |         self._create_key('test2') | ||||||
|  |         result = self.cloud.describe_key_pairs(self.context) | ||||||
|  |         keys = result["keypairsSet"] | ||||||
|  |         self.assertTrue(filter(lambda k: k['keyName'] == 'test1', keys)) | ||||||
|  |         self.assertTrue(filter(lambda k: k['keyName'] == 'test2', keys)) | ||||||
|  |  | ||||||
|  |     def test_delete_key_pair(self): | ||||||
|  |         self._create_key('test') | ||||||
|  |         self.cloud.delete_key_pair(self.context, 'test') | ||||||
|  |  | ||||||
|     def test_run_instances(self): |     def test_run_instances(self): | ||||||
|         if FLAGS.connection_type == 'fake': |         if FLAGS.connection_type == 'fake': | ||||||
|             logging.debug("Can't test instances without a real virtual env.") |             logging.debug("Can't test instances without a real virtual env.") | ||||||
| @@ -94,7 +131,7 @@ class CloudTestCase(test.BaseTestCase): | |||||||
|             rv = yield defer.succeed(time.sleep(1)) |             rv = yield defer.succeed(time.sleep(1)) | ||||||
|             info = self.cloud._get_instance(instance['instance_id']) |             info = self.cloud._get_instance(instance['instance_id']) | ||||||
|             logging.debug(info['state']) |             logging.debug(info['state']) | ||||||
|             if info['state'] == node.Instance.RUNNING: |             if info['state'] == power_state.RUNNING: | ||||||
|                 break |                 break | ||||||
|         self.assert_(rv) |         self.assert_(rv) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,6 +18,8 @@ | |||||||
| """ | """ | ||||||
| Tests For Compute | Tests For Compute | ||||||
| """ | """ | ||||||
|  |  | ||||||
|  | import datetime | ||||||
| import logging | import logging | ||||||
|  |  | ||||||
| from twisted.internet import defer | from twisted.internet import defer | ||||||
| @@ -48,6 +50,7 @@ class ComputeTestCase(test.TrialTestCase): | |||||||
|     def tearDown(self):  # pylint: disable-msg=C0103 |     def tearDown(self):  # pylint: disable-msg=C0103 | ||||||
|         self.manager.delete_user(self.user) |         self.manager.delete_user(self.user) | ||||||
|         self.manager.delete_project(self.project) |         self.manager.delete_project(self.project) | ||||||
|  |         super(ComputeTestCase, self).tearDown() | ||||||
|  |  | ||||||
|     def _create_instance(self): |     def _create_instance(self): | ||||||
|         """Create a test instance""" |         """Create a test instance""" | ||||||
| @@ -60,7 +63,7 @@ class ComputeTestCase(test.TrialTestCase): | |||||||
|         inst['instance_type'] = 'm1.tiny' |         inst['instance_type'] = 'm1.tiny' | ||||||
|         inst['mac_address'] = utils.generate_mac() |         inst['mac_address'] = utils.generate_mac() | ||||||
|         inst['ami_launch_index'] = 0 |         inst['ami_launch_index'] = 0 | ||||||
|         return db.instance_create(self.context, inst) |         return db.instance_create(self.context, inst)['id'] | ||||||
|  |  | ||||||
|     @defer.inlineCallbacks |     @defer.inlineCallbacks | ||||||
|     def test_run_terminate(self): |     def test_run_terminate(self): | ||||||
| @@ -79,6 +82,24 @@ class ComputeTestCase(test.TrialTestCase): | |||||||
|         logging.info("After terminating instances: %s", instances) |         logging.info("After terminating instances: %s", instances) | ||||||
|         self.assertEqual(len(instances), 0) |         self.assertEqual(len(instances), 0) | ||||||
|  |  | ||||||
|  |     @defer.inlineCallbacks | ||||||
|  |     def test_run_terminate_timestamps(self): | ||||||
|  |         """Make sure timestamps are set for launched and destroyed""" | ||||||
|  |         instance_id = self._create_instance() | ||||||
|  |         instance_ref = db.instance_get(self.context, instance_id) | ||||||
|  |         self.assertEqual(instance_ref['launched_at'], None) | ||||||
|  |         self.assertEqual(instance_ref['deleted_at'], None) | ||||||
|  |         launch = datetime.datetime.utcnow() | ||||||
|  |         yield self.compute.run_instance(self.context, instance_id) | ||||||
|  |         instance_ref = db.instance_get(self.context, instance_id) | ||||||
|  |         self.assert_(instance_ref['launched_at'] > launch) | ||||||
|  |         self.assertEqual(instance_ref['deleted_at'], None) | ||||||
|  |         terminate = datetime.datetime.utcnow() | ||||||
|  |         yield self.compute.terminate_instance(self.context, instance_id) | ||||||
|  |         instance_ref = db.instance_get({'deleted': True}, instance_id) | ||||||
|  |         self.assert_(instance_ref['launched_at'] < terminate) | ||||||
|  |         self.assert_(instance_ref['deleted_at'] > terminate) | ||||||
|  |  | ||||||
|     @defer.inlineCallbacks |     @defer.inlineCallbacks | ||||||
|     def test_reboot(self): |     def test_reboot(self): | ||||||
|         """Ensure instance can be rebooted""" |         """Ensure instance can be rebooted""" | ||||||
|   | |||||||
| @@ -28,6 +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.api.ec2 import context | ||||||
|  |  | ||||||
| FLAGS = flags.FLAGS | FLAGS = flags.FLAGS | ||||||
|  |  | ||||||
| @@ -48,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 = None |         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, | ||||||
| @@ -56,12 +57,12 @@ class NetworkTestCase(test.TrialTestCase): | |||||||
|                                                              name)) |                                                              name)) | ||||||
|             # create the necessary network data for the project |             # create the necessary network data for the project | ||||||
|             self.network.set_network_host(self.context, self.projects[i].id) |             self.network.set_network_host(self.context, self.projects[i].id) | ||||||
|         instance_id = db.instance_create(None, |         instance_ref = db.instance_create(None, | ||||||
|                                          {'mac_address': utils.generate_mac()}) |                                          {'mac_address': utils.generate_mac()}) | ||||||
|         self.instance_id = instance_id |         self.instance_id = instance_ref['id'] | ||||||
|         instance_id = db.instance_create(None, |         instance_ref = db.instance_create(None, | ||||||
|                                          {'mac_address': utils.generate_mac()}) |                                          {'mac_address': utils.generate_mac()}) | ||||||
|         self.instance2_id = instance_id |         self.instance2_id = instance_ref['id'] | ||||||
|  |  | ||||||
|     def tearDown(self):  # pylint: disable-msg=C0103 |     def tearDown(self):  # pylint: disable-msg=C0103 | ||||||
|         super(NetworkTestCase, self).tearDown() |         super(NetworkTestCase, self).tearDown() | ||||||
| @@ -75,12 +76,10 @@ class NetworkTestCase(test.TrialTestCase): | |||||||
|  |  | ||||||
|     def _create_address(self, project_num, instance_id=None): |     def _create_address(self, project_num, instance_id=None): | ||||||
|         """Create an address in given project num""" |         """Create an address in given project num""" | ||||||
|         net = db.project_get_network(None, self.projects[project_num].id) |  | ||||||
|         address = db.fixed_ip_allocate(None, net['id']) |  | ||||||
|         if instance_id is None: |         if instance_id is None: | ||||||
|             instance_id = self.instance_id |             instance_id = self.instance_id | ||||||
|         db.fixed_ip_instance_associate(None, address, instance_id) |         self.context.project = self.projects[project_num] | ||||||
|         return address |         return self.network.allocate_fixed_ip(self.context, instance_id) | ||||||
|  |  | ||||||
|     def test_public_network_association(self): |     def test_public_network_association(self): | ||||||
|         """Makes sure that we can allocaate a public ip""" |         """Makes sure that we can allocaate a public ip""" | ||||||
| @@ -103,14 +102,14 @@ class NetworkTestCase(test.TrialTestCase): | |||||||
|         address = db.instance_get_floating_address(None, self.instance_id) |         address = db.instance_get_floating_address(None, self.instance_id) | ||||||
|         self.assertEqual(address, None) |         self.assertEqual(address, None) | ||||||
|         self.network.deallocate_floating_ip(self.context, float_addr) |         self.network.deallocate_floating_ip(self.context, float_addr) | ||||||
|         db.fixed_ip_deallocate(None, fix_addr) |         self.network.deallocate_fixed_ip(self.context, fix_addr) | ||||||
|  |  | ||||||
|     def test_allocate_deallocate_fixed_ip(self): |     def test_allocate_deallocate_fixed_ip(self): | ||||||
|         """Makes sure that we can allocate and deallocate a fixed ip""" |         """Makes sure that we can allocate and deallocate a fixed ip""" | ||||||
|         address = self._create_address(0) |         address = self._create_address(0) | ||||||
|         self.assertTrue(is_allocated_in_project(address, self.projects[0].id)) |         self.assertTrue(is_allocated_in_project(address, self.projects[0].id)) | ||||||
|         lease_ip(address) |         lease_ip(address) | ||||||
|         db.fixed_ip_deallocate(None, address) |         self.network.deallocate_fixed_ip(self.context, address) | ||||||
|  |  | ||||||
|         # Doesn't go away until it's dhcp released |         # Doesn't go away until it's dhcp released | ||||||
|         self.assertTrue(is_allocated_in_project(address, self.projects[0].id)) |         self.assertTrue(is_allocated_in_project(address, self.projects[0].id)) | ||||||
| @@ -131,14 +130,14 @@ class NetworkTestCase(test.TrialTestCase): | |||||||
|         lease_ip(address) |         lease_ip(address) | ||||||
|         lease_ip(address2) |         lease_ip(address2) | ||||||
|  |  | ||||||
|         db.fixed_ip_deallocate(None, address) |         self.network.deallocate_fixed_ip(self.context, address) | ||||||
|         release_ip(address) |         release_ip(address) | ||||||
|         self.assertFalse(is_allocated_in_project(address, self.projects[0].id)) |         self.assertFalse(is_allocated_in_project(address, self.projects[0].id)) | ||||||
|  |  | ||||||
|         # First address release shouldn't affect the second |         # First address release shouldn't affect the second | ||||||
|         self.assertTrue(is_allocated_in_project(address2, self.projects[1].id)) |         self.assertTrue(is_allocated_in_project(address2, self.projects[1].id)) | ||||||
|  |  | ||||||
|         db.fixed_ip_deallocate(None, address2) |         self.network.deallocate_fixed_ip(self.context, address2) | ||||||
|         release_ip(address2) |         release_ip(address2) | ||||||
|         self.assertFalse(is_allocated_in_project(address2, |         self.assertFalse(is_allocated_in_project(address2, | ||||||
|                                                  self.projects[1].id)) |                                                  self.projects[1].id)) | ||||||
| @@ -147,10 +146,23 @@ class NetworkTestCase(test.TrialTestCase): | |||||||
|         """Makes sure that private ips don't overlap""" |         """Makes sure that private ips don't overlap""" | ||||||
|         first = self._create_address(0) |         first = self._create_address(0) | ||||||
|         lease_ip(first) |         lease_ip(first) | ||||||
|  |         instance_ids = [] | ||||||
|         for i in range(1, 5): |         for i in range(1, 5): | ||||||
|             address = self._create_address(i) |             mac = utils.generate_mac() | ||||||
|             address2 = self._create_address(i) |             instance_ref = db.instance_create(None, | ||||||
|             address3 = self._create_address(i) |                                               {'mac_address': mac}) | ||||||
|  |             instance_ids.append(instance_ref['id']) | ||||||
|  |             address = self._create_address(i, instance_ref['id']) | ||||||
|  |             mac = utils.generate_mac() | ||||||
|  |             instance_ref = db.instance_create(None, | ||||||
|  |                                               {'mac_address': mac}) | ||||||
|  |             instance_ids.append(instance_ref['id']) | ||||||
|  |             address2 = self._create_address(i, instance_ref['id']) | ||||||
|  |             mac = utils.generate_mac() | ||||||
|  |             instance_ref = db.instance_create(None, | ||||||
|  |                                               {'mac_address': mac}) | ||||||
|  |             instance_ids.append(instance_ref['id']) | ||||||
|  |             address3 = self._create_address(i, instance_ref['id']) | ||||||
|             lease_ip(address) |             lease_ip(address) | ||||||
|             lease_ip(address2) |             lease_ip(address2) | ||||||
|             lease_ip(address3) |             lease_ip(address3) | ||||||
| @@ -160,14 +172,16 @@ class NetworkTestCase(test.TrialTestCase): | |||||||
|                                                      self.projects[0].id)) |                                                      self.projects[0].id)) | ||||||
|             self.assertFalse(is_allocated_in_project(address3, |             self.assertFalse(is_allocated_in_project(address3, | ||||||
|                                                      self.projects[0].id)) |                                                      self.projects[0].id)) | ||||||
|             db.fixed_ip_deallocate(None, address) |             self.network.deallocate_fixed_ip(self.context, address) | ||||||
|             db.fixed_ip_deallocate(None, address2) |             self.network.deallocate_fixed_ip(self.context, address2) | ||||||
|             db.fixed_ip_deallocate(None, address3) |             self.network.deallocate_fixed_ip(self.context, address3) | ||||||
|             release_ip(address) |             release_ip(address) | ||||||
|             release_ip(address2) |             release_ip(address2) | ||||||
|             release_ip(address3) |             release_ip(address3) | ||||||
|  |         for instance_id in instance_ids: | ||||||
|  |             db.instance_destroy(None, instance_id) | ||||||
|         release_ip(first) |         release_ip(first) | ||||||
|         db.fixed_ip_deallocate(None, first) |         self.network.deallocate_fixed_ip(self.context, first) | ||||||
|  |  | ||||||
|     def test_vpn_ip_and_port_looks_valid(self): |     def test_vpn_ip_and_port_looks_valid(self): | ||||||
|         """Ensure the vpn ip and port are reasonable""" |         """Ensure the vpn ip and port are reasonable""" | ||||||
| @@ -194,12 +208,12 @@ class NetworkTestCase(test.TrialTestCase): | |||||||
|         """Makes sure that ip addresses that are deallocated get reused""" |         """Makes sure that ip addresses that are deallocated get reused""" | ||||||
|         address = self._create_address(0) |         address = self._create_address(0) | ||||||
|         lease_ip(address) |         lease_ip(address) | ||||||
|         db.fixed_ip_deallocate(None, address) |         self.network.deallocate_fixed_ip(self.context, address) | ||||||
|         release_ip(address) |         release_ip(address) | ||||||
|  |  | ||||||
|         address2 = self._create_address(0) |         address2 = self._create_address(0) | ||||||
|         self.assertEqual(address, address2) |         self.assertEqual(address, address2) | ||||||
|         db.fixed_ip_deallocate(None, address2) |         self.network.deallocate_fixed_ip(self.context, address2) | ||||||
|  |  | ||||||
|     def test_available_ips(self): |     def test_available_ips(self): | ||||||
|         """Make sure the number of available ips for the network is correct |         """Make sure the number of available ips for the network is correct | ||||||
| @@ -226,21 +240,27 @@ class NetworkTestCase(test.TrialTestCase): | |||||||
|         num_available_ips = db.network_count_available_ips(None, |         num_available_ips = db.network_count_available_ips(None, | ||||||
|                                                            network['id']) |                                                            network['id']) | ||||||
|         addresses = [] |         addresses = [] | ||||||
|  |         instance_ids = [] | ||||||
|         for i in range(num_available_ips): |         for i in range(num_available_ips): | ||||||
|             address = self._create_address(0) |             mac = utils.generate_mac() | ||||||
|  |             instance_ref = db.instance_create(None, | ||||||
|  |                                               {'mac_address': mac}) | ||||||
|  |             instance_ids.append(instance_ref['id']) | ||||||
|  |             address = self._create_address(0, instance_ref['id']) | ||||||
|             addresses.append(address) |             addresses.append(address) | ||||||
|             lease_ip(address) |             lease_ip(address) | ||||||
|  |  | ||||||
|         self.assertEqual(db.network_count_available_ips(None, |         self.assertEqual(db.network_count_available_ips(None, | ||||||
|                                                         network['id']), 0) |                                                         network['id']), 0) | ||||||
|         self.assertRaises(db.NoMoreAddresses, |         self.assertRaises(db.NoMoreAddresses, | ||||||
|                           db.fixed_ip_allocate, |                           self.network.allocate_fixed_ip, | ||||||
|                           None, |                           self.context, | ||||||
|                           network['id']) |                           'foo') | ||||||
|  |  | ||||||
|         for i in range(len(addresses)): |         for i in range(num_available_ips): | ||||||
|             db.fixed_ip_deallocate(None, addresses[i]) |             self.network.deallocate_fixed_ip(self.context, addresses[i]) | ||||||
|             release_ip(addresses[i]) |             release_ip(addresses[i]) | ||||||
|  |             db.instance_destroy(None, instance_ids[i]) | ||||||
|         self.assertEqual(db.network_count_available_ips(None, |         self.assertEqual(db.network_count_available_ips(None, | ||||||
|                                                         network['id']), |                                                         network['id']), | ||||||
|                          num_available_ips) |                          num_available_ips) | ||||||
| @@ -263,7 +283,10 @@ def binpath(script): | |||||||
| def lease_ip(private_ip): | def lease_ip(private_ip): | ||||||
|     """Run add command on dhcpbridge""" |     """Run add command on dhcpbridge""" | ||||||
|     network_ref = db.fixed_ip_get_network(None, private_ip) |     network_ref = db.fixed_ip_get_network(None, private_ip) | ||||||
|     cmd = "%s add fake %s fake" % (binpath('nova-dhcpbridge'), private_ip) |     instance_ref = db.fixed_ip_get_instance(None, private_ip) | ||||||
|  |     cmd = "%s add %s %s fake" % (binpath('nova-dhcpbridge'), | ||||||
|  |                                  instance_ref['mac_address'], | ||||||
|  |                                  private_ip) | ||||||
|     env = {'DNSMASQ_INTERFACE': network_ref['bridge'], |     env = {'DNSMASQ_INTERFACE': network_ref['bridge'], | ||||||
|            'TESTING': '1', |            'TESTING': '1', | ||||||
|            'FLAGFILE': FLAGS.dhcpbridge_flagfile} |            'FLAGFILE': FLAGS.dhcpbridge_flagfile} | ||||||
| @@ -274,7 +297,10 @@ def lease_ip(private_ip): | |||||||
| def release_ip(private_ip): | def release_ip(private_ip): | ||||||
|     """Run del command on dhcpbridge""" |     """Run del command on dhcpbridge""" | ||||||
|     network_ref = db.fixed_ip_get_network(None, private_ip) |     network_ref = db.fixed_ip_get_network(None, private_ip) | ||||||
|     cmd = "%s del fake %s fake" % (binpath('nova-dhcpbridge'), private_ip) |     instance_ref = db.fixed_ip_get_instance(None, private_ip) | ||||||
|  |     cmd = "%s del %s %s fake" % (binpath('nova-dhcpbridge'), | ||||||
|  |                                  instance_ref['mac_address'], | ||||||
|  |                                  private_ip) | ||||||
|     env = {'DNSMASQ_INTERFACE': network_ref['bridge'], |     env = {'DNSMASQ_INTERFACE': network_ref['bridge'], | ||||||
|            'TESTING': '1', |            'TESTING': '1', | ||||||
|            'FLAGFILE': FLAGS.dhcpbridge_flagfile} |            'FLAGFILE': FLAGS.dhcpbridge_flagfile} | ||||||
|   | |||||||
| @@ -185,7 +185,7 @@ class S3APITestCase(test.TrialTestCase): | |||||||
|         """Setup users, projects, and start a test server.""" |         """Setup users, projects, and start a test server.""" | ||||||
|         super(S3APITestCase, self).setUp() |         super(S3APITestCase, self).setUp() | ||||||
|  |  | ||||||
|         FLAGS.auth_driver = 'nova.auth.ldapdriver.FakeLdapDriver', |         FLAGS.auth_driver = 'nova.auth.ldapdriver.FakeLdapDriver' | ||||||
|         FLAGS.buckets_path = os.path.join(OSS_TEMPDIR, 'buckets') |         FLAGS.buckets_path = os.path.join(OSS_TEMPDIR, 'buckets') | ||||||
|  |  | ||||||
|         self.auth_manager = manager.AuthManager() |         self.auth_manager = manager.AuthManager() | ||||||
|   | |||||||
							
								
								
									
										152
									
								
								nova/tests/quota_unittest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								nova/tests/quota_unittest.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | |||||||
|  | # 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. | ||||||
|  |  | ||||||
|  | import logging | ||||||
|  |  | ||||||
|  | from nova import db | ||||||
|  | from nova import exception | ||||||
|  | from nova import flags | ||||||
|  | from nova import quota | ||||||
|  | from nova import test | ||||||
|  | from nova import utils | ||||||
|  | from nova.auth import manager | ||||||
|  | from nova.api.ec2 import cloud | ||||||
|  | from nova.api.ec2 import context | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FLAGS = flags.FLAGS | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class QuotaTestCase(test.TrialTestCase): | ||||||
|  |     def setUp(self):  # pylint: disable-msg=C0103 | ||||||
|  |         logging.getLogger().setLevel(logging.DEBUG) | ||||||
|  |         super(QuotaTestCase, self).setUp() | ||||||
|  |         self.flags(connection_type='fake', | ||||||
|  |                    quota_instances=2, | ||||||
|  |                    quota_cores=4, | ||||||
|  |                    quota_volumes=2, | ||||||
|  |                    quota_gigabytes=20, | ||||||
|  |                    quota_floating_ips=1) | ||||||
|  |  | ||||||
|  |         self.cloud = cloud.CloudController() | ||||||
|  |         self.manager = manager.AuthManager() | ||||||
|  |         self.user = self.manager.create_user('admin', 'admin', 'admin', True) | ||||||
|  |         self.project = self.manager.create_project('admin', 'admin', 'admin') | ||||||
|  |         self.network = utils.import_object(FLAGS.network_manager) | ||||||
|  |         self.context = context.APIRequestContext(project=self.project, | ||||||
|  |                                                  user=self.user) | ||||||
|  |  | ||||||
|  |     def tearDown(self):  # pylint: disable-msg=C0103 | ||||||
|  |         manager.AuthManager().delete_project(self.project) | ||||||
|  |         manager.AuthManager().delete_user(self.user) | ||||||
|  |         super(QuotaTestCase, self).tearDown() | ||||||
|  |  | ||||||
|  |     def _create_instance(self, cores=2): | ||||||
|  |         """Create a test instance""" | ||||||
|  |         inst = {} | ||||||
|  |         inst['image_id'] = 'ami-test' | ||||||
|  |         inst['reservation_id'] = 'r-fakeres' | ||||||
|  |         inst['user_id'] = self.user.id | ||||||
|  |         inst['project_id'] = self.project.id | ||||||
|  |         inst['instance_type'] = 'm1.large' | ||||||
|  |         inst['vcpus'] = cores | ||||||
|  |         inst['mac_address'] = utils.generate_mac() | ||||||
|  |         return db.instance_create(self.context, inst)['id'] | ||||||
|  |  | ||||||
|  |     def _create_volume(self, size=10): | ||||||
|  |         """Create a test volume""" | ||||||
|  |         vol = {} | ||||||
|  |         vol['user_id'] = self.user.id | ||||||
|  |         vol['project_id'] = self.project.id | ||||||
|  |         vol['size'] = size | ||||||
|  |         return db.volume_create(self.context, vol)['id'] | ||||||
|  |  | ||||||
|  |     def test_quota_overrides(self): | ||||||
|  |         """Make sure overriding a projects quotas works""" | ||||||
|  |         num_instances = quota.allowed_instances(self.context, 100, 'm1.small') | ||||||
|  |         self.assertEqual(num_instances, 2) | ||||||
|  |         db.quota_create(self.context, {'project_id': self.project.id, | ||||||
|  |                                        'instances': 10}) | ||||||
|  |         num_instances = quota.allowed_instances(self.context, 100, 'm1.small') | ||||||
|  |         self.assertEqual(num_instances, 4) | ||||||
|  |         db.quota_update(self.context, self.project.id, {'cores': 100}) | ||||||
|  |         num_instances = quota.allowed_instances(self.context, 100, 'm1.small') | ||||||
|  |         self.assertEqual(num_instances, 10) | ||||||
|  |         db.quota_destroy(self.context, self.project.id) | ||||||
|  |  | ||||||
|  |     def test_too_many_instances(self): | ||||||
|  |         instance_ids = [] | ||||||
|  |         for i in range(FLAGS.quota_instances): | ||||||
|  |             instance_id = self._create_instance() | ||||||
|  |             instance_ids.append(instance_id) | ||||||
|  |         self.assertRaises(cloud.QuotaError, self.cloud.run_instances,  | ||||||
|  |                                             self.context, | ||||||
|  |                                             min_count=1, | ||||||
|  |                                             max_count=1, | ||||||
|  |                                             instance_type='m1.small') | ||||||
|  |         for instance_id in instance_ids: | ||||||
|  |             db.instance_destroy(self.context, instance_id) | ||||||
|  |  | ||||||
|  |     def test_too_many_cores(self): | ||||||
|  |         instance_ids = [] | ||||||
|  |         instance_id = self._create_instance(cores=4) | ||||||
|  |         instance_ids.append(instance_id) | ||||||
|  |         self.assertRaises(cloud.QuotaError, self.cloud.run_instances,  | ||||||
|  |                                             self.context, | ||||||
|  |                                             min_count=1, | ||||||
|  |                                             max_count=1, | ||||||
|  |                                             instance_type='m1.small') | ||||||
|  |         for instance_id in instance_ids: | ||||||
|  |             db.instance_destroy(self.context, instance_id) | ||||||
|  |  | ||||||
|  |     def test_too_many_volumes(self): | ||||||
|  |         volume_ids = [] | ||||||
|  |         for i in range(FLAGS.quota_volumes): | ||||||
|  |             volume_id = self._create_volume() | ||||||
|  |             volume_ids.append(volume_id) | ||||||
|  |         self.assertRaises(cloud.QuotaError, self.cloud.create_volume, | ||||||
|  |                                             self.context, | ||||||
|  |                                             size=10) | ||||||
|  |         for volume_id in volume_ids: | ||||||
|  |             db.volume_destroy(self.context, volume_id) | ||||||
|  |  | ||||||
|  |     def test_too_many_gigabytes(self): | ||||||
|  |         volume_ids = [] | ||||||
|  |         volume_id = self._create_volume(size=20) | ||||||
|  |         volume_ids.append(volume_id) | ||||||
|  |         self.assertRaises(cloud.QuotaError, | ||||||
|  |                           self.cloud.create_volume, | ||||||
|  |                           self.context, | ||||||
|  |                           size=10) | ||||||
|  |         for volume_id in volume_ids: | ||||||
|  |             db.volume_destroy(self.context, volume_id) | ||||||
|  |  | ||||||
|  |     def test_too_many_addresses(self): | ||||||
|  |         address = '192.168.0.100' | ||||||
|  |         try: | ||||||
|  |             db.floating_ip_get_by_address(None, address) | ||||||
|  |         except exception.NotFound: | ||||||
|  |             db.floating_ip_create(None, {'address': address, | ||||||
|  |                                          'host': FLAGS.host}) | ||||||
|  |         float_addr = self.network.allocate_floating_ip(self.context, | ||||||
|  |                                                        self.project.id) | ||||||
|  |         # NOTE(vish): This assert never fails. When cloud attempts to | ||||||
|  |         #             make an rpc.call, the test just finishes with OK. It | ||||||
|  |         #             appears to be something in the magic inline callbacks | ||||||
|  |         #             that is breaking. | ||||||
|  |         self.assertRaises(cloud.QuotaError, self.cloud.allocate_address, self.context) | ||||||
							
								
								
									
										231
									
								
								nova/tests/scheduler_unittest.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								nova/tests/scheduler_unittest.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,231 @@ | |||||||
|  | # 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. | ||||||
|  | """ | ||||||
|  | Tests For Scheduler | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from nova import db | ||||||
|  | from nova import flags | ||||||
|  | from nova import service | ||||||
|  | from nova import test | ||||||
|  | from nova import rpc | ||||||
|  | from nova import utils | ||||||
|  | from nova.auth import manager as auth_manager | ||||||
|  | from nova.scheduler import manager | ||||||
|  | from nova.scheduler import driver | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FLAGS = flags.FLAGS | ||||||
|  | flags.DECLARE('max_cores', 'nova.scheduler.simple') | ||||||
|  |  | ||||||
|  | class TestDriver(driver.Scheduler): | ||||||
|  |     """Scheduler Driver for Tests""" | ||||||
|  |     def schedule(context, topic, *args, **kwargs): | ||||||
|  |         return 'fallback_host' | ||||||
|  |  | ||||||
|  |     def schedule_named_method(context, topic, num): | ||||||
|  |         return 'named_host' | ||||||
|  |  | ||||||
|  | class SchedulerTestCase(test.TrialTestCase): | ||||||
|  |     """Test case for scheduler""" | ||||||
|  |     def setUp(self):  # pylint: disable=C0103 | ||||||
|  |         super(SchedulerTestCase, self).setUp() | ||||||
|  |         self.flags(scheduler_driver='nova.tests.scheduler_unittest.TestDriver') | ||||||
|  |  | ||||||
|  |     def test_fallback(self): | ||||||
|  |         scheduler = manager.SchedulerManager() | ||||||
|  |         self.mox.StubOutWithMock(rpc, 'cast', use_mock_anything=True) | ||||||
|  |         rpc.cast('topic.fallback_host', | ||||||
|  |                  {'method': 'noexist', | ||||||
|  |                   'args': {'context': None, | ||||||
|  |                            'num': 7}}) | ||||||
|  |         self.mox.ReplayAll() | ||||||
|  |         scheduler.noexist(None, 'topic', num=7) | ||||||
|  |  | ||||||
|  |     def test_named_method(self): | ||||||
|  |         scheduler = manager.SchedulerManager() | ||||||
|  |         self.mox.StubOutWithMock(rpc, 'cast', use_mock_anything=True) | ||||||
|  |         rpc.cast('topic.named_host', | ||||||
|  |                  {'method': 'named_method', | ||||||
|  |                   'args': {'context': None, | ||||||
|  |                            'num': 7}}) | ||||||
|  |         self.mox.ReplayAll() | ||||||
|  |         scheduler.named_method(None, 'topic', num=7) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SimpleDriverTestCase(test.TrialTestCase): | ||||||
|  |     """Test case for simple driver""" | ||||||
|  |     def setUp(self):  # pylint: disable-msg=C0103 | ||||||
|  |         super(SimpleDriverTestCase, self).setUp() | ||||||
|  |         self.flags(connection_type='fake', | ||||||
|  |                    max_cores=4, | ||||||
|  |                    max_gigabytes=4, | ||||||
|  |                    volume_driver='nova.volume.driver.FakeAOEDriver', | ||||||
|  |                    scheduler_driver='nova.scheduler.simple.SimpleScheduler') | ||||||
|  |         self.scheduler = manager.SchedulerManager() | ||||||
|  |         self.context = None | ||||||
|  |         self.manager = auth_manager.AuthManager() | ||||||
|  |         self.user = self.manager.create_user('fake', 'fake', 'fake') | ||||||
|  |         self.project = self.manager.create_project('fake', 'fake', 'fake') | ||||||
|  |         self.context = None | ||||||
|  |  | ||||||
|  |     def tearDown(self):  # pylint: disable-msg=C0103 | ||||||
|  |         self.manager.delete_user(self.user) | ||||||
|  |         self.manager.delete_project(self.project) | ||||||
|  |  | ||||||
|  |     def _create_instance(self): | ||||||
|  |         """Create a test instance""" | ||||||
|  |         inst = {} | ||||||
|  |         inst['image_id'] = 'ami-test' | ||||||
|  |         inst['reservation_id'] = 'r-fakeres' | ||||||
|  |         inst['user_id'] = self.user.id | ||||||
|  |         inst['project_id'] = self.project.id | ||||||
|  |         inst['instance_type'] = 'm1.tiny' | ||||||
|  |         inst['mac_address'] = utils.generate_mac() | ||||||
|  |         inst['ami_launch_index'] = 0 | ||||||
|  |         inst['vcpus'] = 1 | ||||||
|  |         return db.instance_create(self.context, inst)['id'] | ||||||
|  |  | ||||||
|  |     def _create_volume(self): | ||||||
|  |         """Create a test volume""" | ||||||
|  |         vol = {} | ||||||
|  |         vol['image_id'] = 'ami-test' | ||||||
|  |         vol['reservation_id'] = 'r-fakeres' | ||||||
|  |         vol['size'] = 1 | ||||||
|  |         return db.volume_create(self.context, vol)['id'] | ||||||
|  |  | ||||||
|  |     def test_hosts_are_up(self): | ||||||
|  |         """Ensures driver can find the hosts that are up""" | ||||||
|  |         # NOTE(vish): constructing service without create method | ||||||
|  |         #             because we are going to use it without queue | ||||||
|  |         compute1 = service.Service('host1', | ||||||
|  |                                    'nova-compute', | ||||||
|  |                                    'compute', | ||||||
|  |                                    FLAGS.compute_manager) | ||||||
|  |         compute2 = service.Service('host2', | ||||||
|  |                                    'nova-compute', | ||||||
|  |                                    'compute', | ||||||
|  |                                    FLAGS.compute_manager) | ||||||
|  |         hosts = self.scheduler.driver.hosts_up(self.context, 'compute') | ||||||
|  |         self.assertEqual(len(hosts), 2) | ||||||
|  |         compute1.kill() | ||||||
|  |         compute2.kill() | ||||||
|  |  | ||||||
|  |     def test_least_busy_host_gets_instance(self): | ||||||
|  |         """Ensures the host with less cores gets the next one""" | ||||||
|  |         compute1 = service.Service('host1', | ||||||
|  |                                    'nova-compute', | ||||||
|  |                                    'compute', | ||||||
|  |                                    FLAGS.compute_manager) | ||||||
|  |         compute2 = service.Service('host2', | ||||||
|  |                                    'nova-compute', | ||||||
|  |                                    'compute', | ||||||
|  |                                    FLAGS.compute_manager) | ||||||
|  |         instance_id1 = self._create_instance() | ||||||
|  |         compute1.run_instance(self.context, instance_id1) | ||||||
|  |         instance_id2 = self._create_instance() | ||||||
|  |         host = self.scheduler.driver.schedule_run_instance(self.context, | ||||||
|  |                                                            instance_id2) | ||||||
|  |         self.assertEqual(host, 'host2') | ||||||
|  |         compute1.terminate_instance(self.context, instance_id1) | ||||||
|  |         db.instance_destroy(self.context, instance_id2) | ||||||
|  |         compute1.kill() | ||||||
|  |         compute2.kill() | ||||||
|  |  | ||||||
|  |     def test_too_many_cores(self): | ||||||
|  |         """Ensures we don't go over max cores""" | ||||||
|  |         compute1 = service.Service('host1', | ||||||
|  |                                    'nova-compute', | ||||||
|  |                                    'compute', | ||||||
|  |                                    FLAGS.compute_manager) | ||||||
|  |         compute2 = service.Service('host2', | ||||||
|  |                                    'nova-compute', | ||||||
|  |                                    'compute', | ||||||
|  |                                    FLAGS.compute_manager) | ||||||
|  |         instance_ids1 = [] | ||||||
|  |         instance_ids2 = [] | ||||||
|  |         for index in xrange(FLAGS.max_cores): | ||||||
|  |             instance_id = self._create_instance() | ||||||
|  |             compute1.run_instance(self.context, instance_id) | ||||||
|  |             instance_ids1.append(instance_id) | ||||||
|  |             instance_id = self._create_instance() | ||||||
|  |             compute2.run_instance(self.context, instance_id) | ||||||
|  |             instance_ids2.append(instance_id) | ||||||
|  |         instance_id = self._create_instance() | ||||||
|  |         self.assertRaises(driver.NoValidHost, | ||||||
|  |                           self.scheduler.driver.schedule_run_instance, | ||||||
|  |                           self.context, | ||||||
|  |                           instance_id) | ||||||
|  |         for instance_id in instance_ids1: | ||||||
|  |             compute1.terminate_instance(self.context, instance_id) | ||||||
|  |         for instance_id in instance_ids2: | ||||||
|  |             compute2.terminate_instance(self.context, instance_id) | ||||||
|  |         compute1.kill() | ||||||
|  |         compute2.kill() | ||||||
|  |  | ||||||
|  |     def test_least_busy_host_gets_volume(self): | ||||||
|  |         """Ensures the host with less gigabytes gets the next one""" | ||||||
|  |         volume1 = service.Service('host1', | ||||||
|  |                                    'nova-volume', | ||||||
|  |                                    'volume', | ||||||
|  |                                    FLAGS.volume_manager) | ||||||
|  |         volume2 = service.Service('host2', | ||||||
|  |                                    'nova-volume', | ||||||
|  |                                    'volume', | ||||||
|  |                                    FLAGS.volume_manager) | ||||||
|  |         volume_id1 = self._create_volume() | ||||||
|  |         volume1.create_volume(self.context, volume_id1) | ||||||
|  |         volume_id2 = self._create_volume() | ||||||
|  |         host = self.scheduler.driver.schedule_create_volume(self.context, | ||||||
|  |                                                             volume_id2) | ||||||
|  |         self.assertEqual(host, 'host2') | ||||||
|  |         volume1.delete_volume(self.context, volume_id1) | ||||||
|  |         db.volume_destroy(self.context, volume_id2) | ||||||
|  |         volume1.kill() | ||||||
|  |         volume2.kill() | ||||||
|  |  | ||||||
|  |     def test_too_many_gigabytes(self): | ||||||
|  |         """Ensures we don't go over max gigabytes""" | ||||||
|  |         volume1 = service.Service('host1', | ||||||
|  |                                    'nova-volume', | ||||||
|  |                                    'volume', | ||||||
|  |                                    FLAGS.volume_manager) | ||||||
|  |         volume2 = service.Service('host2', | ||||||
|  |                                    'nova-volume', | ||||||
|  |                                    'volume', | ||||||
|  |                                    FLAGS.volume_manager) | ||||||
|  |         volume_ids1 = [] | ||||||
|  |         volume_ids2 = [] | ||||||
|  |         for index in xrange(FLAGS.max_gigabytes): | ||||||
|  |             volume_id = self._create_volume() | ||||||
|  |             volume1.create_volume(self.context, volume_id) | ||||||
|  |             volume_ids1.append(volume_id) | ||||||
|  |             volume_id = self._create_volume() | ||||||
|  |             volume2.create_volume(self.context, volume_id) | ||||||
|  |             volume_ids2.append(volume_id) | ||||||
|  |         volume_id = self._create_volume() | ||||||
|  |         self.assertRaises(driver.NoValidHost, | ||||||
|  |                           self.scheduler.driver.schedule_create_volume, | ||||||
|  |                           self.context, | ||||||
|  |                           volume_id) | ||||||
|  |         for volume_id in volume_ids1: | ||||||
|  |             volume1.delete_volume(self.context, volume_id) | ||||||
|  |         for volume_id in volume_ids2: | ||||||
|  |             volume2.delete_volume(self.context, volume_id) | ||||||
|  |         volume1.kill() | ||||||
|  |         volume2.kill() | ||||||
| @@ -87,7 +87,7 @@ class ServiceTestCase(test.BaseTestCase): | |||||||
|                                        host, |                                        host, | ||||||
|                                        binary).AndRaise(exception.NotFound()) |                                        binary).AndRaise(exception.NotFound()) | ||||||
|         service.db.service_create(None, |         service.db.service_create(None, | ||||||
|                                   service_create).AndReturn(service_ref['id']) |                                   service_create).AndReturn(service_ref) | ||||||
|         self.mox.ReplayAll() |         self.mox.ReplayAll() | ||||||
|  |  | ||||||
|         app = service.Service.create(host=host, binary=binary) |         app = service.Service.create(host=host, binary=binary) | ||||||
| @@ -131,7 +131,7 @@ class ServiceTestCase(test.BaseTestCase): | |||||||
|                                       host, |                                       host, | ||||||
|                                       binary).AndRaise(exception.NotFound()) |                                       binary).AndRaise(exception.NotFound()) | ||||||
|         service.db.service_create(None, |         service.db.service_create(None, | ||||||
|                                   service_create).AndReturn(service_ref['id']) |                                   service_create).AndReturn(service_ref) | ||||||
|         service.db.service_get(None, service_ref['id']).AndReturn(service_ref) |         service.db.service_get(None, service_ref['id']).AndReturn(service_ref) | ||||||
|         service.db.service_update(None, service_ref['id'], |         service.db.service_update(None, service_ref['id'], | ||||||
|                                   mox.ContainsKeyValue('report_count', 1)) |                                   mox.ContainsKeyValue('report_count', 1)) | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ 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.api.ec2 import cloud | ||||||
| from nova.virt import libvirt_conn | from nova.virt import libvirt_conn | ||||||
|  |  | ||||||
| FLAGS = flags.FLAGS | FLAGS = flags.FLAGS | ||||||
| @@ -179,7 +179,8 @@ class NWFilterTestCase(test.TrialTestCase): | |||||||
|  |  | ||||||
|         self.fake_libvirt_connection.nwfilterDefineXML = _filterDefineXMLMock |         self.fake_libvirt_connection.nwfilterDefineXML = _filterDefineXMLMock | ||||||
|  |  | ||||||
|         inst_id = db.instance_create({}, { 'user_id' : 'fake', 'project_id' : 'fake' }) |         inst_id = db.instance_create({}, {'user_id': 'fake', | ||||||
|  |                                           'project_id': 'fake'})['id'] | ||||||
|         security_group = self.setup_and_return_security_group() |         security_group = self.setup_and_return_security_group() | ||||||
|  |  | ||||||
|         db.instance_add_security_group({}, inst_id, security_group.id) |         db.instance_add_security_group({}, inst_id, security_group.id) | ||||||
|   | |||||||
| @@ -108,7 +108,7 @@ class VolumeTestCase(test.TrialTestCase): | |||||||
|         inst['instance_type'] = 'm1.tiny' |         inst['instance_type'] = 'm1.tiny' | ||||||
|         inst['mac_address'] = utils.generate_mac() |         inst['mac_address'] = utils.generate_mac() | ||||||
|         inst['ami_launch_index'] = 0 |         inst['ami_launch_index'] = 0 | ||||||
|         instance_id = db.instance_create(self.context, inst) |         instance_id = db.instance_create(self.context, inst)['id'] | ||||||
|         mountpoint = "/dev/sdf" |         mountpoint = "/dev/sdf" | ||||||
|         volume_id = self._create_volume() |         volume_id = self._create_volume() | ||||||
|         yield self.volume.create_volume(self.context, volume_id) |         yield self.volume.create_volume(self.context, volume_id) | ||||||
|   | |||||||
| @@ -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 * | ||||||
| @@ -58,7 +59,9 @@ from nova.tests.flags_unittest import * | |||||||
| from nova.tests.network_unittest import * | from nova.tests.network_unittest import * | ||||||
| from nova.tests.objectstore_unittest import * | from nova.tests.objectstore_unittest import * | ||||||
| from nova.tests.process_unittest import * | from nova.tests.process_unittest import * | ||||||
|  | from nova.tests.quota_unittest import * | ||||||
| from nova.tests.rpc_unittest import * | from nova.tests.rpc_unittest import * | ||||||
|  | from nova.tests.scheduler_unittest import * | ||||||
| from nova.tests.service_unittest import * | from nova.tests.service_unittest import * | ||||||
| from nova.tests.validator_unittest import * | from nova.tests.validator_unittest import * | ||||||
| from nova.tests.volume_unittest import * | from nova.tests.volume_unittest import * | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Vishvananda Ishaya
					Vishvananda Ishaya