summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEwan Mellor <ewan.mellor@citrix.com>2010-07-29 22:06:17 +0100
committerEwan Mellor <ewan.mellor@citrix.com>2010-07-29 22:06:17 +0100
commite588b82a991107720137d21d89f0fb24f55fdf50 (patch)
tree057ef6bedf567ff072af12358e1e3b1cbd2c97c5
parent04a6a0267e7dc0f4e587e43f23b4acf0dcef52fc (diff)
parent73a47dfecf4b1ba66a45421bbd925f3e0db054c5 (diff)
downloadnova-e588b82a991107720137d21d89f0fb24f55fdf50.tar.gz
nova-e588b82a991107720137d21d89f0fb24f55fdf50.tar.xz
nova-e588b82a991107720137d21d89f0fb24f55fdf50.zip
Merged with trunk.
-rw-r--r--.bzrignore1
-rw-r--r--MANIFEST.in1
-rw-r--r--doc/source/conf.py4
-rw-r--r--nova/adminclient.py163
-rw-r--r--nova/auth/manager.py24
-rw-r--r--nova/endpoint/admin.py74
-rw-r--r--nova/endpoint/cloud.py8
-rw-r--r--nova/process.py12
-rw-r--r--nova/tests/process_unittest.py11
-rw-r--r--nova/utils.py10
-rw-r--r--setup.py25
11 files changed, 295 insertions, 38 deletions
diff --git a/.bzrignore b/.bzrignore
index c3a502a1a..ab099d3e3 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -1,2 +1,3 @@
run_tests.err.log
.nova-venv
+ChangeLog
diff --git a/MANIFEST.in b/MANIFEST.in
index e917077c5..4eb28bde6 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,6 @@
include HACKING LICENSE run_tests.py run_tests.sh
include README builddeb.sh exercise_rsapi.py
+include ChangeLog
graft CA
graft doc
graft smoketests
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 1c1ae7f48..349d23af2 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -47,9 +47,9 @@ copyright = u'2010, United States Government as represented by the Administrator
# built documents.
#
# The short X.Y version.
-version = '0.42'
+version = '0.9'
# The full version, including alpha/beta/rc tags.
-release = '0.42'
+release = '0.9.1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/nova/adminclient.py b/nova/adminclient.py
index db392feb1..fceeac274 100644
--- a/nova/adminclient.py
+++ b/nova/adminclient.py
@@ -23,6 +23,7 @@ import base64
import boto
from boto.ec2.regioninfo import RegionInfo
+
class UserInfo(object):
"""
Information about a Nova user, as parsed through SAX
@@ -56,6 +57,64 @@ class UserInfo(object):
elif name == 'secretkey':
self.secretkey = str(value)
+class ProjectInfo(object):
+ """
+ Information about a Nova project, as parsed through SAX
+ Fields include:
+ projectname
+ description
+ projectManagerId
+ memberIds
+ """
+
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.projectname = None
+ self.description = None
+ self.projectManagerId = None
+ self.memberIds = []
+
+ def __repr__(self):
+ return 'ProjectInfo:%s' % self.projectname
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'projectname':
+ self.projectname = value
+ elif name == 'description':
+ self.description = value
+ elif name == 'projectManagerId':
+ self.projectManagerId = value
+ elif name == 'memberId':
+ self.memberIds.append(value)
+ else:
+ setattr(self, name, str(value))
+
+class ProjectMember(object):
+ """
+ Information about a Nova project member, as parsed through SAX.
+ Fields include:
+ memberId
+ """
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.memberId = None
+
+ def __repr__(self):
+ return 'ProjectMember:%s' % self.memberId
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'member':
+ self.memberId = value
+ else:
+ setattr(self, name, str(value))
+
+
class HostInfo(object):
"""
Information about a Nova Host, as parsed through SAX:
@@ -99,20 +158,20 @@ class NovaAdminClient(object):
**kwargs)
self.apiconn.APIVersion = 'nova'
- def connection_for(self, username, **kwargs):
+ def connection_for(self, username, project, **kwargs):
"""
Returns a boto ec2 connection for the given username.
"""
user = self.get_user(username)
+ access_key = '%s:%s' % (user.accesskey, project)
return boto.connect_ec2(
- aws_access_key_id=user.accesskey,
+ aws_access_key_id=access_key,
aws_secret_access_key=user.secretkey,
is_secure=False,
region=RegionInfo(None, self.region, self.clc_ip),
port=8773,
path='/services/Cloud',
- **kwargs
- )
+ **kwargs)
def get_users(self):
""" grabs the list of all users """
@@ -137,6 +196,102 @@ class NovaAdminClient(object):
""" deletes a user """
return self.apiconn.get_object('DeregisterUser', {'Name': username}, UserInfo)
+ def add_user_role(self, user, role, project=None):
+ """
+ Add a role to a user either globally or for a specific project.
+ """
+ return self.modify_user_role(user, role, project=project,
+ operation='add')
+
+ def remove_user_role(self, user, role, project=None):
+ """
+ Remove a role from a user either globally or for a specific project.
+ """
+ return self.modify_user_role(user, role, project=project,
+ operation='remove')
+
+ def modify_user_role(self, user, role, project=None, operation='add',
+ **kwargs):
+ """
+ Add or remove a role for a user and project.
+ """
+ params = {'User': user,
+ 'Role': role,
+ 'Project': project,
+ 'Operation': operation}
+ return self.apiconn.get_status('ModifyUserRole', params)
+
+ def get_projects(self, user=None):
+ """
+ Returns a list of all projects.
+ """
+ if user:
+ params = {'User': user}
+ else:
+ params = {}
+ return self.apiconn.get_list('DescribeProjects',
+ params,
+ [('item', ProjectInfo)])
+
+ def get_project(self, name):
+ """
+ Returns a single project with the specified name.
+ """
+ project = self.apiconn.get_object('DescribeProject',
+ {'Name': name},
+ ProjectInfo)
+
+ if project.projectname != None:
+ return project
+
+ def create_project(self, projectname, manager_user, description=None,
+ member_users=None):
+ """
+ Creates a new project.
+ """
+ params = {'Name': projectname,
+ 'ManagerUser': manager_user,
+ 'Description': description,
+ 'MemberUsers': member_users}
+ return self.apiconn.get_object('RegisterProject', params, ProjectInfo)
+
+ def delete_project(self, projectname):
+ """
+ Permanently deletes the specified project.
+ """
+ return self.apiconn.get_object('DeregisterProject',
+ {'Name': projectname},
+ ProjectInfo)
+
+ def get_project_members(self, name):
+ """
+ Returns a list of members of a project.
+ """
+ return self.apiconn.get_list('DescribeProjectMembers',
+ {'Name': name},
+ [('item', ProjectMember)])
+
+ def add_project_member(self, user, project):
+ """
+ Adds a user to a project.
+ """
+ return self.modify_project_member(user, project, operation='add')
+
+ def remove_project_member(self, user, project):
+ """
+ Removes a user from a project.
+ """
+ return self.modify_project_member(user, project, operation='remove')
+
+ def modify_project_member(self, user, project, operation='add'):
+ """
+ Adds or removes a user from a project.
+ """
+ params = {'User': user,
+ 'Project': project,
+ 'Operation': operation}
+ return self.apiconn.get_status('ModifyProjectMember', params)
+
def get_zip(self, username):
""" returns the content of a zip file containing novarc and access credentials. """
return self.apiconn.get_object('GenerateX509ForUser', {'Name': username}, UserInfo).file
diff --git a/nova/auth/manager.py b/nova/auth/manager.py
index 7307f673b..2da53a736 100644
--- a/nova/auth/manager.py
+++ b/nova/auth/manager.py
@@ -323,25 +323,21 @@ class AuthManager(object):
"""
_instance=None
def __new__(cls, *args, **kwargs):
- """Returns the AuthManager singleton with driver set
-
- __init__ is run every time AuthManager() is called, so we need to do
- any constructor related stuff here. The driver that is specified
- in the flagfile is loaded here.
- """
+ """Returns the AuthManager singleton"""
if not cls._instance:
cls._instance = super(AuthManager, cls).__new__(
cls, *args, **kwargs)
- mod_str, sep, driver_str = FLAGS.auth_driver.rpartition('.')
- try:
- __import__(mod_str)
- cls._instance.driver = getattr(sys.modules[mod_str],
- driver_str)
- except (ImportError, AttributeError):
- raise exception.Error('Auth driver %s cannot be found'
- % FLAGS.auth_driver)
return cls._instance
+ def __init__(self, driver=None, *args, **kwargs):
+ """Inits the driver from parameter or flag
+
+ __init__ is run every time AuthManager() is called, so we only
+ reset the driver if it is not set or a new driver is specified.
+ """
+ if driver or not getattr(self, 'driver', None):
+ self.driver = utils.import_class(driver or FLAGS.auth_driver)
+
def authenticate(self, access, signature, params, verb='GET',
server_string='127.0.0.1:8773', path='/',
check_type='ec2', headers=None):
diff --git a/nova/endpoint/admin.py b/nova/endpoint/admin.py
index 55a8e4238..c4b8c05ca 100644
--- a/nova/endpoint/admin.py
+++ b/nova/endpoint/admin.py
@@ -25,6 +25,7 @@ import base64
from nova.auth import manager
from nova.compute import model
+
def user_dict(user, base64_file=None):
"""Convert the user object to a result dict"""
if user:
@@ -32,8 +33,17 @@ def user_dict(user, base64_file=None):
'username': user.id,
'accesskey': user.access,
'secretkey': user.secret,
- 'file': base64_file,
- }
+ '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 {}
@@ -93,6 +103,19 @@ class AdminController(object):
return True
@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
@@ -105,6 +128,53 @@ class AdminController(object):
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
+
+ @admin_only
def describe_hosts(self, _context, **_kwargs):
"""Returns status info for all nodes. Includes:
* Disk Space
diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py
index 8a4edbc0b..67fc04502 100644
--- a/nova/endpoint/cloud.py
+++ b/nova/endpoint/cloud.py
@@ -519,14 +519,14 @@ class CloudController(object):
kernel_id = image.get('kernelId', FLAGS.default_kernel)
ramdisk_id = image.get('ramdiskId', FLAGS.default_ramdisk)
- # make sure we have access to kernel and ramdisk
- self._get_image(context, kernel_id)
- self._get_image(context, ramdisk_id)
-
# 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
+ self._get_image(context, kernel_id)
+ self._get_image(context, ramdisk_id)
+
logging.debug("Going to run instances...")
reservation_id = utils.generate_uid('r')
launch_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
diff --git a/nova/process.py b/nova/process.py
index d3558ed2e..2dc56372f 100644
--- a/nova/process.py
+++ b/nova/process.py
@@ -205,13 +205,13 @@ class ProcessPool(object):
self._pool.release()
return rv
-class SharedPool(ProcessPool):
+class SharedPool(object):
_instance = None
- def __new__(cls, *args, **kwargs):
- if not cls._instance:
- cls._instance = super(SharedPool, cls).__new__(
- cls, *args, **kwargs)
- return cls._instance
+ def __init__(self):
+ if SharedPool._instance is None:
+ self.__class__._instance = ProcessPool()
+ def __getattr__(self, key):
+ return getattr(self._instance, key)
def simple_execute(cmd, **kwargs):
return SharedPool().simple_execute(cmd, **kwargs)
diff --git a/nova/tests/process_unittest.py b/nova/tests/process_unittest.py
index 1c15b69a0..75187e1fc 100644
--- a/nova/tests/process_unittest.py
+++ b/nova/tests/process_unittest.py
@@ -118,5 +118,12 @@ class ProcessTestCase(test.TrialTestCase):
def test_shared_pool_is_singleton(self):
pool1 = process.SharedPool()
pool2 = process.SharedPool()
- self.assert_(id(pool1) == id(pool2))
-
+ self.assertEqual(id(pool1._instance), id(pool2._instance))
+
+ def test_shared_pool_works_as_singleton(self):
+ d1 = process.simple_execute('sleep 1')
+ d2 = process.simple_execute('sleep 0.005')
+ # lp609749: would have failed with
+ # exceptions.AssertionError: Someone released me too many times:
+ # too many tokens!
+ return d1
diff --git a/nova/utils.py b/nova/utils.py
index a1eb0a092..0016b656e 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -29,11 +29,21 @@ import subprocess
import socket
import sys
+from nova import exception
from nova import flags
FLAGS = flags.FLAGS
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
+def import_class(import_str):
+ """Returns a class from a string including module and class"""
+ mod_str, _sep, class_str = import_str.rpartition('.')
+ try:
+ __import__(mod_str)
+ return getattr(sys.modules[mod_str], class_str)
+ except (ImportError, AttributeError):
+ raise exception.NotFound('Class %s cannot be found' % class_str)
+
def fetchfile(url, target):
logging.debug("Fetching %s" % url)
# c = pycurl.Curl()
diff --git a/setup.py b/setup.py
index 50d5f2a3d..0fd286f7d 100644
--- a/setup.py
+++ b/setup.py
@@ -17,6 +17,24 @@
# under the License.
from setuptools import setup, find_packages
+from setuptools.command.sdist import sdist
+
+import os
+import subprocess
+
+
+class local_sdist(sdist):
+ """Customized sdist hook - builds the ChangeLog file from VC first"""
+
+ def run(self):
+ if os.path.isdir('.bzr'):
+ # We're in a bzr branch
+ log_cmd = subprocess.Popen(["bzr", "log", "--gnu"],
+ stdout=subprocess.PIPE)
+ changelog = log_cmd.communicate()[0]
+ with open("ChangeLog", "w") as changelog_file:
+ changelog_file.write(changelog)
+ sdist.run(self)
setup(name='nova',
version='0.9.1',
@@ -24,7 +42,8 @@ setup(name='nova',
author='OpenStack',
author_email='nova@lists.launchpad.net',
url='http://www.openstack.org/',
- packages = find_packages(exclude=['bin','smoketests']),
+ cmdclass={'sdist': local_sdist},
+ packages=find_packages(exclude=['bin', 'smoketests']),
scripts=['bin/nova-api',
'bin/nova-compute',
'bin/nova-dhcpbridge',
@@ -34,6 +53,4 @@ setup(name='nova',
'bin/nova-network',
'bin/nova-objectstore',
'bin/nova-rsapi',
- 'bin/nova-volume',
- ]
- )
+ 'bin/nova-volume'])