summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/auth/access.py69
-rw-r--r--nova/auth/fakeldap.py70
-rw-r--r--nova/auth/rbac.ldif60
-rw-r--r--nova/auth/signer.py32
-rwxr-xr-xnova/auth/slap.sh56
-rw-r--r--[-rwxr-xr-x]nova/auth/users.py542
-rw-r--r--nova/compute/linux_net.py34
-rw-r--r--nova/compute/model.py61
-rw-r--r--nova/compute/network.py101
-rw-r--r--nova/compute/node.py18
-rw-r--r--nova/crypto.py23
-rw-r--r--nova/datastore.py10
-rw-r--r--nova/endpoint/admin.py29
-rwxr-xr-xnova/endpoint/api.py45
-rw-r--r--nova/endpoint/cloud.py221
-rw-r--r--nova/endpoint/images.py39
-rw-r--r--nova/exception.py23
-rw-r--r--nova/fakerabbit.py18
-rw-r--r--nova/objectstore/bucket.py20
-rw-r--r--nova/objectstore/handler.py44
-rw-r--r--nova/objectstore/image.py8
-rw-r--r--nova/tests/access_unittest.py60
-rw-r--r--nova/tests/api_integration.py13
-rw-r--r--nova/tests/cloud_unittest.py38
-rw-r--r--nova/tests/network_unittest.py88
-rw-r--r--nova/tests/node_unittest.py36
-rw-r--r--nova/tests/objectstore_unittest.py57
-rw-r--r--nova/tests/users_unittest.py53
-rw-r--r--nova/volume/storage.py13
-rw-r--r--run_tests.py1
30 files changed, 1018 insertions, 864 deletions
diff --git a/nova/auth/access.py b/nova/auth/access.py
deleted file mode 100644
index 2c780626d..000000000
--- a/nova/auth/access.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# Copyright [2010] [Anso Labs, 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.
-
-"""
-Simple base set of RBAC rules which map API endpoints to LDAP groups.
-For testing accounts, users will always have PM privileges.
-"""
-
-
-# This is logically a RuleSet or some such.
-
-def allow_describe_images(user, project, target_object):
- return True
-
-def allow_describe_instances(user, project, target_object):
- return True
-
-def allow_describe_addresses(user, project, target_object):
- return True
-
-def allow_run_instances(user, project, target_object):
- # target_object is a reservation, not an instance
- # it needs to include count, type, image, etc.
-
- # First, is the project allowed to use this image
-
- # Second, is this user allowed to launch within this project
-
- # Third, is the count or type within project quota
-
- return True
-
-def allow_terminate_instances(user, project, target_object):
- # In a project, the PMs and Sysadmins can terminate
- return True
-
-def allow_get_console_output(user, project, target_object):
- # If the user launched the instance,
- # Or is a sysadmin in the project,
- return True
-
-def allow_allocate_address(user, project, target_object):
- # There's no security concern in allocation,
- # but it can get expensive. Limit to PM and NE.
- return True
-
-def allow_associate_address(user, project, target_object):
- # project NE only
- # In future, will perform a CloudAudit scan first
- # (Pass / Fail gate)
- return True
-
-def allow_register(user, project, target_object):
- return False
-
-def is_allowed(action, user, project, target_object):
- return globals()['allow_%s' % action](user, project, target_object)
-
diff --git a/nova/auth/fakeldap.py b/nova/auth/fakeldap.py
index c223b250c..8a5bbdf44 100644
--- a/nova/auth/fakeldap.py
+++ b/nova/auth/fakeldap.py
@@ -22,6 +22,12 @@ import logging
from nova import datastore
SCOPE_SUBTREE = 1
+MOD_ADD = 0
+MOD_DELETE = 1
+
+SUBS = {
+ 'groupOfNames': ['novaProject']
+}
class NO_SUCH_OBJECT(Exception):
@@ -44,6 +50,46 @@ class FakeLDAP(object):
def unbind_s(self):
pass
+ def _paren_groups(self, source):
+ count = 0
+ start = 0
+ result = []
+ for pos in xrange(len(source)):
+ if source[pos] == '(':
+ if count == 0:
+ start = pos
+ count += 1
+ if source[pos] == ')':
+ count -= 1
+ if count == 0:
+ result.append(source[start:pos+1])
+
+ def _match_query(self, query, attrs):
+ inner = query[1:-1]
+ if inner.startswith('&'):
+ l, r = self._paren_groups(inner[1:])
+ return self._match_query(l, attrs) and self._match_query(r, attrs)
+ if inner.startswith('|'):
+ l, r = self._paren_groups(inner[1:])
+ return self._match_query(l, attrs) or self._match_query(r, attrs)
+ if inner.startswith('!'):
+ return not self._match_query(query[2:-1], attrs)
+
+ (k, sep, v) = inner.partition('=')
+ return self._match(k, v, attrs)
+
+ def _subs(self, v):
+ if v in SUBS:
+ return [v] + SUBS[v]
+ return [v]
+
+ def _match(self, k, v, attrs):
+ if attrs.has_key(k):
+ for v in self._subs(v):
+ if (v in attrs[k]):
+ return True
+ return False
+
def search_s(self, dn, scope, query=None, fields=None):
logging.debug("searching for %s" % dn)
filtered = {}
@@ -51,12 +97,11 @@ class FakeLDAP(object):
for cn, attrs in d.iteritems():
if cn[-len(dn):] == dn:
filtered[cn] = attrs
+ objects = filtered
if query:
- k,v = query[1:-1].split('=')
objects = {}
for cn, attrs in filtered.iteritems():
- if attrs.has_key(k) and (v in attrs[k] or
- v == attrs[k]):
+ if self._match_query(query, attrs):
objects[cn] = attrs
if objects == {}:
raise NO_SUCH_OBJECT()
@@ -75,7 +120,22 @@ class FakeLDAP(object):
self.keeper['objects'] = d
def delete_s(self, cn):
- logging.debug("creating for %s" % cn)
- d = self.keeper['objects'] or {}
+ logging.debug("deleting %s" % cn)
+ d = self.keeper['objects']
del d[cn]
self.keeper['objects'] = d
+
+ def modify_s(self, cn, attr):
+ logging.debug("modifying %s" % cn)
+ d = self.keeper['objects']
+ for cmd, k, v in attr:
+ logging.debug("command %s" % cmd)
+ if cmd == MOD_ADD:
+ d[cn][k].append(v)
+ else:
+ d[cn][k].remove(v)
+ self.keeper['objects'] = d
+
+
+
+
diff --git a/nova/auth/rbac.ldif b/nova/auth/rbac.ldif
deleted file mode 100644
index 3878d2c1b..000000000
--- a/nova/auth/rbac.ldif
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright [2010] [Anso Labs, 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.
-
-# LDIF fragment to create group branch under root
-
-#dn: ou=Groups,dc=example,dc=com
-#objectclass:organizationalunit
-#ou: groups
-#description: generic groups branch
-
-# create the itpeople entry
-
-dn: cn=sysadmins,ou=Groups,dc=example,dc=com
-objectclass: groupofnames
-cn: itpeople
-description: IT admin group
-# add the group members all of which are
-# assumed to exist under Users
-#member: cn=micky mouse,ou=people,dc=example,dc=com
-member: cn=admin,ou=Users,dc=example,dc=com
-
-dn: cn=netadmins,ou=Groups,dc=example,dc=com
-objectclass: groupofnames
-cn: netadmins
-description: Network admin group
-member: cn=admin,ou=Users,dc=example,dc=com
-
-dn: cn=cloudadmins,ou=Groups,dc=example,dc=com
-objectclass: groupofnames
-cn: cloudadmins
-description: Cloud admin group
-member: cn=admin,ou=Users,dc=example,dc=com
-
-dn: cn=itsec,ou=Groups,dc=example,dc=com
-objectclass: groupofnames
-cn: itsec
-description: IT security users group
-member: cn=admin,ou=Users,dc=example,dc=com
-
-# Example Project Group to demonstrate members
-# and project members
-
-dn: cn=myproject,ou=Groups,dc=example,dc=com
-objectclass: groupofnames
-objectclass: novaProject
-cn: myproject
-description: My Project Group
-member: cn=admin,ou=Users,dc=example,dc=com
-projectManager: cn=admin,ou=Users,dc=example,dc=com
diff --git a/nova/auth/signer.py b/nova/auth/signer.py
index 00aa066fb..4b0169652 100644
--- a/nova/auth/signer.py
+++ b/nova/auth/signer.py
@@ -46,12 +46,9 @@ import urllib
import base64
from nova.exception import Error
-_log = logging.getLogger('signer')
-logging.getLogger('signer').setLevel(logging.WARN)
-
class Signer(object):
""" hacked up code from boto/connection.py """
-
+
def __init__(self, secret_key):
self.hmac = hmac.new(secret_key, digestmod=hashlib.sha1)
if hashlib.sha256:
@@ -59,15 +56,14 @@ class Signer(object):
def generate(self, params, verb, server_string, path):
if params['SignatureVersion'] == '0':
- t = self._calc_signature_0(params)
- elif params['SignatureVersion'] == '1':
- t = self._calc_signature_1(params)
- elif params['SignatureVersion'] == '2':
- t = self._calc_signature_2(params, verb, server_string, path)
- else:
- raise Error('Unknown Signature Version: %s' % self.SignatureVersion)
- return t
-
+ return self._calc_signature_0(params)
+ if params['SignatureVersion'] == '1':
+ return self._calc_signature_1(params)
+ if params['SignatureVersion'] == '2':
+ return self._calc_signature_2(params, verb, server_string, path)
+ raise Error('Unknown Signature Version: %s' % self.SignatureVersion)
+
+
def _get_utf8_value(self, value):
if not isinstance(value, str) and not isinstance(value, unicode):
value = str(value)
@@ -99,7 +95,7 @@ class Signer(object):
return base64.b64encode(self.hmac.digest())
def _calc_signature_2(self, params, verb, server_string, path):
- _log.debug('using _calc_signature_2')
+ logging.debug('using _calc_signature_2')
string_to_sign = '%s\n%s\n%s\n' % (verb, server_string, path)
if self.hmac_256:
hmac = self.hmac_256
@@ -114,13 +110,13 @@ class Signer(object):
val = self._get_utf8_value(params[key])
pairs.append(urllib.quote(key, safe='') + '=' + urllib.quote(val, safe='-_~'))
qs = '&'.join(pairs)
- _log.debug('query string: %s' % qs)
+ logging.debug('query string: %s' % qs)
string_to_sign += qs
- _log.debug('string_to_sign: %s' % string_to_sign)
+ logging.debug('string_to_sign: %s' % string_to_sign)
hmac.update(string_to_sign)
b64 = base64.b64encode(hmac.digest())
- _log.debug('len(b64)=%d' % len(b64))
- _log.debug('base64 encoded digest: %s' % b64)
+ logging.debug('len(b64)=%d' % len(b64))
+ logging.debug('base64 encoded digest: %s' % b64)
return b64
if __name__ == '__main__':
diff --git a/nova/auth/slap.sh b/nova/auth/slap.sh
index a0df4e0ae..c3369e396 100755
--- a/nova/auth/slap.sh
+++ b/nova/auth/slap.sh
@@ -1,12 +1,12 @@
#!/usr/bin/env bash
# Copyright [2010] [Anso Labs, 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.
@@ -21,21 +21,21 @@ cat >/etc/ldap/schema/openssh-lpk_openldap.schema <<LPK_SCHEMA_EOF
#
# LDAP Public Key Patch schema for use with openssh-ldappubkey
# Author: Eric AUGE <eau@phear.org>
-#
+#
# Based on the proposal of : Mark Ruijter
#
# octetString SYNTAX
-attributetype ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey'
- DESC 'MANDATORY: OpenSSH Public key'
+attributetype ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey'
+ DESC 'MANDATORY: OpenSSH Public key'
EQUALITY octetStringMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
# printableString SYNTAX yes|no
objectclass ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY
DESC 'MANDATORY: OpenSSH LPK objectclass'
- MAY ( sshPublicKey $ uid )
+ MAY ( sshPublicKey $ uid )
)
LPK_SCHEMA_EOF
@@ -44,7 +44,7 @@ cat >/etc/ldap/schema/nova.schema <<NOVA_SCHEMA_EOF
# Person object for Nova
# inetorgperson with extra attributes
# Author: Vishvananda Ishaya <vishvananda@yahoo.com>
-#
+#
#
# using internet experimental oid arc as per BP64 3.1
@@ -54,32 +54,32 @@ objectidentifier novaOCs novaSchema:4
attributetype (
novaAttrs:1
- NAME 'accessKey'
- DESC 'Key for accessing data'
- EQUALITY caseIgnoreMatch
- SUBSTR caseIgnoreSubstringsMatch
- SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
- SINGLE-VALUE
+ NAME 'accessKey'
+ DESC 'Key for accessing data'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
)
attributetype (
novaAttrs:2
- NAME 'secretKey'
- DESC 'Secret key'
- EQUALITY caseIgnoreMatch
- SUBSTR caseIgnoreSubstringsMatch
- SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
- SINGLE-VALUE
+ NAME 'secretKey'
+ DESC 'Secret key'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
)
attributetype (
novaAttrs:3
- NAME 'keyFingerprint'
- DESC 'Fingerprint of private key'
- EQUALITY caseIgnoreMatch
- SUBSTR caseIgnoreSubstringsMatch
- SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
- SINGLE-VALUE
+ NAME 'keyFingerprint'
+ DESC 'Fingerprint of private key'
+ EQUALITY caseIgnoreMatch
+ SUBSTR caseIgnoreSubstringsMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
+ SINGLE-VALUE
)
attributetype (
@@ -96,7 +96,7 @@ attributetype (
NAME 'projectManager'
DESC 'Project Managers of a project'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
- )
+ )
objectClass (
novaOCs:1
@@ -120,7 +120,7 @@ objectClass (
novaOCs:3
NAME 'novaProject'
DESC 'Container for project'
- SUP groupofnames
+ SUP groupOfNames
STRUCTURAL
MUST ( cn $ projectManager )
)
diff --git a/nova/auth/users.py b/nova/auth/users.py
index d8ea8ac68..b09bcfcf2 100755..100644
--- a/nova/auth/users.py
+++ b/nova/auth/users.py
@@ -22,6 +22,7 @@ import datetime
import logging
import os
import shutil
+import string
import tempfile
import uuid
import zipfile
@@ -40,7 +41,6 @@ from nova import exception
from nova import flags
from nova import crypto
from nova import utils
-import access as simplerbac
from nova import objectstore # for flags
@@ -50,16 +50,8 @@ flags.DEFINE_string('ldap_url', 'ldap://localhost', 'Point this at your ldap ser
flags.DEFINE_string('ldap_password', 'changeme', 'LDAP password')
flags.DEFINE_string('user_dn', 'cn=Manager,dc=example,dc=com', 'DN of admin user')
flags.DEFINE_string('user_unit', 'Users', 'OID for Users')
-flags.DEFINE_string('ldap_subtree', 'ou=Users,dc=example,dc=com', 'OU for Users')
-
-flags.DEFINE_string('ldap_sysadmin',
- 'cn=sysadmins,ou=Groups,dc=example,dc=com', 'OU for Sysadmins')
-flags.DEFINE_string('ldap_netadmin',
- 'cn=netadmins,ou=Groups,dc=example,dc=com', 'OU for NetAdmins')
-flags.DEFINE_string('ldap_cloudadmin',
- 'cn=cloudadmins,ou=Groups,dc=example,dc=com', 'OU for Cloud Admins')
-flags.DEFINE_string('ldap_itsec',
- 'cn=itsec,ou=Groups,dc=example,dc=com', 'OU for ItSec')
+flags.DEFINE_string('user_ldap_subtree', 'ou=Users,dc=example,dc=com', 'OU for Users')
+flags.DEFINE_string('project_ldap_subtree', 'ou=Groups,dc=example,dc=com', 'OU for Projects')
flags.DEFINE_string('credentials_template',
utils.abspath('auth/novarc.template'),
@@ -71,61 +63,35 @@ flags.DEFINE_string('credential_cert_file', 'cert.pem',
flags.DEFINE_string('credential_rc_file', 'novarc',
'Filename of rc in credentials zip')
-_log = logging.getLogger('auth')
-_log.setLevel(logging.WARN)
-
-
-
-class UserError(exception.ApiError):
- pass
-
-class InvalidKeyPair(exception.ApiError):
- pass
+class AuthBase(object):
+ @classmethod
+ def safe_id(cls, obj):
+ """this method will return the id of the object if the object is of this class, otherwise
+ it will return the original object. This allows methods to accept objects or
+ ids as paramaters"""
+ if isinstance(obj, cls):
+ return obj.id
+ else:
+ return obj
-class User(object):
+class User(AuthBase):
+ """id and name are currently the same"""
def __init__(self, id, name, access, secret, admin):
- self.manager = UserManager.instance()
self.id = id
self.name = name
self.access = access
self.secret = secret
self.admin = admin
- self.keeper = datastore.Keeper(prefix="user")
-
def is_admin(self):
+ """allows user to see objects from all projects"""
return self.admin
- def has_role(self, role_type):
- return self.manager.has_role(self.id, role_type)
-
- def is_authorized(self, owner_id, action=None):
- if self.is_admin() or owner_id == self.id:
- return True
- if action == None:
- return False
- project = None #(Fixme)
- target_object = None # (Fixme, should be passed in)
- return simplerbac.is_allowed(action, self, project, target_object)
-
- def get_credentials(self):
- rc = self.generate_rc()
- private_key, signed_cert = self.generate_x509_cert()
-
- tmpdir = tempfile.mkdtemp()
- zf = os.path.join(tmpdir, "temp.zip")
- zippy = zipfile.ZipFile(zf, 'w')
- zippy.writestr(FLAGS.credential_rc_file, rc)
- zippy.writestr(FLAGS.credential_key_file, private_key)
- zippy.writestr(FLAGS.credential_cert_file, signed_cert)
- zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(self.id))
- zippy.close()
- with open(zf, 'rb') as f:
- buffer = f.read()
-
- shutil.rmtree(tmpdir)
- return buffer
+ def is_project_member(self, project):
+ return UserManager.instance().is_project_member(self, project)
+ def is_project_manager(self, project):
+ return UserManager.instance().is_project_manager(self, project)
def generate_rc(self):
rc = open(FLAGS.credentials_template).read()
@@ -140,36 +106,90 @@ class User(object):
return rc
def generate_key_pair(self, name):
- return self.manager.generate_key_pair(self.id, name)
-
- def generate_x509_cert(self):
- return self.manager.generate_x509_cert(self.id)
+ return UserManager.instance().generate_key_pair(self.id, name)
def create_key_pair(self, name, public_key, fingerprint):
- return self.manager.create_key_pair(self.id,
+ return UserManager.instance().create_key_pair(self.id,
name,
public_key,
fingerprint)
def get_key_pair(self, name):
- return self.manager.get_key_pair(self.id, name)
+ return UserManager.instance().get_key_pair(self.id, name)
def delete_key_pair(self, name):
- return self.manager.delete_key_pair(self.id, name)
+ return UserManager.instance().delete_key_pair(self.id, name)
def get_key_pairs(self):
- return self.manager.get_key_pairs(self.id)
+ return UserManager.instance().get_key_pairs(self.id)
-class KeyPair(object):
- def __init__(self, name, owner, public_key, fingerprint):
- self.manager = UserManager.instance()
- self.owner = owner
- self.name = name
+ def __repr__(self):
+ return "User('%s', '%s', '%s', '%s', %s)" % (self.id, self.name, self.access, self.secret, self.admin)
+
+class KeyPair(AuthBase):
+ def __init__(self, id, owner_id, public_key, fingerprint):
+ self.id = id
+ self.name = id
+ self.owner_id = owner_id
self.public_key = public_key
self.fingerprint = fingerprint
def delete(self):
- return self.manager.delete_key_pair(self.owner, self.name)
+ return UserManager.instance().delete_key_pair(self.owner, self.name)
+
+ def __repr__(self):
+ return "KeyPair('%s', '%s', '%s', '%s')" % (self.id, self.owner_id, self.public_key, self.fingerprint)
+
+class Group(AuthBase):
+ """id and name are currently the same"""
+ def __init__(self, id, description = None, member_ids = None):
+ self.id = id
+ self.name = id
+ self.description = description
+ self.member_ids = member_ids
+
+ def has_member(self, user):
+ return User.safe_id(user) in self.member_ids
+
+ def __repr__(self):
+ return "Group('%s', '%s', %s)" % (self.id, self.description, self.member_ids)
+
+class Project(Group):
+ def __init__(self, id, project_manager_id, description, member_ids):
+ self.project_manager_id = project_manager_id
+ super(Project, self).__init__(id, description, member_ids)
+ self.keeper = datastore.Keeper(prefix="project-")
+
+ @property
+ def project_manager(self):
+ return UserManager.instance().get_user(self.project_manager_id)
+
+ def has_manager(self, user):
+ return User.safe_id(user) == self.project_manager_id
+
+ def get_credentials(self, user):
+ rc = user.generate_rc()
+ private_key, signed_cert = self.generate_x509_cert(user)
+
+ tmpdir = tempfile.mkdtemp()
+ zf = os.path.join(tmpdir, "temp.zip")
+ zippy = zipfile.ZipFile(zf, 'w')
+ zippy.writestr(FLAGS.credential_rc_file, rc)
+ zippy.writestr(FLAGS.credential_key_file, private_key)
+ zippy.writestr(FLAGS.credential_cert_file, signed_cert)
+ zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(self.id))
+ zippy.close()
+ with open(zf, 'rb') as f:
+ buffer = f.read()
+
+ shutil.rmtree(tmpdir)
+ return buffer
+
+ def generate_x509_cert(self, user):
+ return UserManager.instance().generate_x509_cert(user, self)
+
+ def __repr__(self):
+ return "Project('%s', '%s', '%s', %s)" % (self.id, self.project_manager_id, self.description, self.member_ids)
class UserManager(object):
def __init__(self):
@@ -193,31 +213,69 @@ class UserManager(object):
except: pass
return cls._instance
- def authenticate(self, params, signature, verb='GET', server_string='127.0.0.1:8773', path='/'):
+ def authenticate(self, access, signature, params, verb='GET', server_string='127.0.0.1:8773', path='/', verify_signature=True):
# TODO: Check for valid timestamp
- access_key = params['AWSAccessKeyId']
+ (access_key, sep, project_name) = access.partition(':')
+
user = self.get_user_from_access_key(access_key)
if user == None:
- return None
- # hmac can't handle unicode, so encode ensures that secret isn't unicode
- expected_signature = signer.Signer(user.secret.encode()).generate(params, verb, server_string, path)
- _log.debug('user.secret: %s', user.secret)
- _log.debug('expected_signature: %s', expected_signature)
- _log.debug('signature: %s', signature)
- if signature == expected_signature:
- return user
-
- def has_role(self, user, role, project=None):
- # Map role to ldap group
- group = FLAGS.__getitem__("ldap_%s" % role)
+ raise exception.NotFound('No user found for access key')
+ if project_name is '':
+ project_name = user.name
+
+ project = self.get_project(project_name)
+ if project == None:
+ raise exception.NotFound('No project called %s could be found' % project_name)
+ if not user.is_admin() and not project.has_member(user):
+ raise exception.NotFound('User %s is not a member of project %s' % (user.id, project.id))
+ if verify_signature:
+ # hmac can't handle unicode, so encode ensures that secret isn't unicode
+ expected_signature = signer.Signer(user.secret.encode()).generate(params, verb, server_string, path)
+ logging.debug('user.secret: %s', user.secret)
+ logging.debug('expected_signature: %s', expected_signature)
+ logging.debug('signature: %s', signature)
+ if signature != expected_signature:
+ raise exception.NotAuthorized('Signature does not match')
+ return (user, project)
+
+ def create_project(self, name, manager_user, description, member_users=None):
+ if member_users:
+ member_users = [User.safe_id(u) for u in member_users]
+ with LDAPWrapper() as conn:
+ return conn.create_project(name, User.safe_id(manager_user), description, member_users)
+
+ def get_projects(self):
+ with LDAPWrapper() as conn:
+ return conn.find_projects()
+
+
+ def get_project(self, project):
with LDAPWrapper() as conn:
- return conn.is_member_of(user, group)
+ return conn.find_project(Project.safe_id(project))
- def add_role(self, user, role, project=None):
- # TODO: Project-specific roles
- group = FLAGS.__getitem__("ldap_%s" % role)
+ def add_to_project(self, user, project):
with LDAPWrapper() as conn:
- return conn.add_to_group(user, group)
+ return conn.add_to_project(User.safe_id(user), Project.safe_id(project))
+
+ def is_project_manager(self, user, project):
+ if not isinstance(project, Project):
+ project = self.get_project(project)
+ return project.has_manager(user)
+
+ def is_project_member(self, user, project):
+ if isinstance(project, Project):
+ return project.has_member(user)
+ else:
+ with LDAPWrapper() as conn:
+ return conn.is_in_project(User.safe_id(user), project)
+
+ def remove_from_project(self, user, project):
+ with LDAPWrapper() as conn:
+ return conn.remove_from_project(User.safe_id(user), Project.safe_id(project))
+
+ def delete_project(self, project):
+ with LDAPWrapper() as conn:
+ return conn.delete_project(Project.safe_id(project))
def get_user(self, uid):
with LDAPWrapper() as conn:
@@ -231,56 +289,59 @@ class UserManager(object):
with LDAPWrapper() as conn:
return conn.find_users()
- def create_user(self, uid, access=None, secret=None, admin=False):
+ def create_user(self, user, access=None, secret=None, admin=False, create_project=True):
if access == None: access = str(uuid.uuid4())
if secret == None: secret = str(uuid.uuid4())
with LDAPWrapper() as conn:
- u = conn.create_user(uid, access, secret, admin)
- return u
+ user = User.safe_id(user)
+ result = conn.create_user(user, access, secret, admin)
+ if create_project:
+ conn.create_project(user, user, user)
+ return result
- def delete_user(self, uid):
+ def delete_user(self, user, delete_project=True):
with LDAPWrapper() as conn:
- conn.delete_user(uid)
+ user = User.safe_id(user)
+ if delete_project:
+ try:
+ conn.delete_project(user)
+ except exception.NotFound:
+ pass
+ conn.delete_user(user)
- def generate_key_pair(self, uid, key_name):
+ def generate_key_pair(self, user, key_name):
# generating key pair is slow so delay generation
# until after check
+ user = User.safe_id(user)
with LDAPWrapper() as conn:
- if not conn.user_exists(uid):
- raise UserError("User " + uid + " doesn't exist")
- if conn.key_pair_exists(uid, key_name):
- raise InvalidKeyPair("The keypair '" +
- key_name +
- "' already exists.",
- "Duplicate")
+ if not conn.user_exists(user):
+ raise exception.NotFound("User %s doesn't exist" % user)
+ if conn.key_pair_exists(user, 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)
+ self.create_key_pair(User.safe_id(user), key_name, public_key, fingerprint)
return private_key, fingerprint
- def create_key_pair(self, uid, key_name, public_key, fingerprint):
+ def create_key_pair(self, user, key_name, public_key, fingerprint):
with LDAPWrapper() as conn:
- return conn.create_key_pair(uid, key_name, public_key, fingerprint)
+ return conn.create_key_pair(User.safe_id(user), key_name, public_key, fingerprint)
- def get_key_pair(self, uid, key_name):
+ def get_key_pair(self, user, key_name):
with LDAPWrapper() as conn:
- return conn.find_key_pair(uid, key_name)
+ return conn.find_key_pair(User.safe_id(user), key_name)
- def get_key_pairs(self, uid):
+ def get_key_pairs(self, user):
with LDAPWrapper() as conn:
- return conn.find_key_pairs(uid)
+ return conn.find_key_pairs(User.safe_id(user))
- def delete_key_pair(self, uid, key_name):
+ def delete_key_pair(self, user, key_name):
with LDAPWrapper() as conn:
- conn.delete_key_pair(uid, key_name)
-
- def get_signed_zip(self, uid):
- user = self.get_user(uid)
- return user.get_credentials()
+ conn.delete_key_pair(User.safe_id(user), key_name)
- def generate_x509_cert(self, uid):
- (private_key, csr) = crypto.generate_x509_cert(self.__cert_subject(uid))
+ def generate_x509_cert(self, user, project):
+ (private_key, csr) = crypto.generate_x509_cert(self.__cert_subject(User.safe_id(user)))
# TODO - This should be async call back to the cloud controller
- signed_cert = crypto.sign_csr(csr, uid)
+ signed_cert = crypto.sign_csr(csr, Project.safe_id(project))
return (private_key, signed_cert)
def sign_cert(self, csr, uid):
@@ -328,41 +389,63 @@ class LDAPWrapper(object):
return [x[1] for x in res]
def find_users(self):
- attrs = self.find_objects(FLAGS.ldap_subtree, '(objectclass=novaUser)')
+ attrs = self.find_objects(FLAGS.user_ldap_subtree, '(objectclass=novaUser)')
return [self.__to_user(attr) for attr in attrs]
def find_key_pairs(self, uid):
- dn = 'uid=%s,%s' % (uid, FLAGS.ldap_subtree)
- attrs = self.find_objects(dn, '(objectclass=novaKeyPair)')
+ attrs = self.find_objects(self.__uid_to_dn(uid), '(objectclass=novaKeyPair)')
return [self.__to_key_pair(uid, attr) for attr in attrs]
- def find_user(self, name):
- dn = 'uid=%s,%s' % (name, FLAGS.ldap_subtree)
- attr = self.find_object(dn, '(objectclass=novaUser)')
- return self.__to_user(attr)
+ def find_projects(self):
+ attrs = self.find_objects(FLAGS.project_ldap_subtree, '(objectclass=novaProject)')
+ return [self.__to_project(attr) for attr in attrs]
- def user_exists(self, name):
- return self.find_user(name) != None
+ def find_groups_with_member(self, tree, dn):
+ attrs = self.find_objects(tree, '(&(objectclass=groupOfNames)(member=%s))' % dn )
+ return [self.__to_group(attr) for attr in attrs]
+
+ def find_user(self, uid):
+ attr = self.find_object(self.__uid_to_dn(uid), '(objectclass=novaUser)')
+ return self.__to_user(attr)
def find_key_pair(self, uid, key_name):
- dn = 'cn=%s,uid=%s,%s' % (key_name,
- uid,
- FLAGS.ldap_subtree)
+ 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 find_group(self, dn):
+ """uses dn directly instead of custructing it from name"""
+ attr = self.find_object(dn, '(objectclass=groupOfNames)')
+ return self.__to_group(attr)
+
+ def find_project(self, name):
+ dn = 'cn=%s,%s' % (name,
+ FLAGS.project_ldap_subtree)
+ attr = self.find_object(dn, '(objectclass=novaProject)')
+ return self.__to_project(attr)
+
+ def user_exists(self, name):
+ return self.find_user(name) != None
+
+ def key_pair_exists(self, uid, key_name):
+ return self.find_key_pair(uid, key_name) != None
+
+ def project_exists(self, name):
+ return self.find_project(name) != None
+
+ def group_exists(self, dn):
+ return self.find_group(dn) != None
+
def delete_key_pairs(self, uid):
keys = self.find_key_pairs(uid)
if keys != None:
for key in keys:
self.delete_key_pair(uid, key.name)
- def key_pair_exists(self, uid, key_name):
- return self.find_key_pair(uid, key_name) != None
-
def create_user(self, name, access_key, secret_key, is_admin):
if self.user_exists(name):
- raise UserError("LDAP user " + name + " already exists")
+ raise exception.Duplicate("LDAP user %s already exists" % name)
attr = [
('objectclass', ['person',
'organizationalPerson',
@@ -376,22 +459,115 @@ class LDAPWrapper(object):
('accessKey', [access_key]),
('isAdmin', [str(is_admin).upper()]),
]
- self.conn.add_s('uid=%s,%s' % (name, FLAGS.ldap_subtree),
- attr)
+ self.conn.add_s(self.__uid_to_dn(name), attr)
return self.__to_user(dict(attr))
- def create_project(self, name, project_manager):
- # PM can be user object or string containing DN
- pass
-
- def is_member_of(self, name, group):
- return True
-
- def add_to_group(self, name, group):
- pass
+ def create_project(self, name, manager_uid, description, member_uids = None):
+ if self.project_exists(name):
+ raise exception.Duplicate("Project can't be created because project %s already exists" % name)
+ if not self.user_exists(manager_uid):
+ raise exception.NotFound("Project can't be created because manager %s doesn't exist" % manager_uid)
+ manager_dn = self.__uid_to_dn(manager_uid)
+ members = []
+ if member_uids != None:
+ for member_uid in member_uids:
+ if not self.user_exists(member_uid):
+ raise exception.NotFound("Project can't be created because user %s doesn't exist" % member_uid)
+ members.append(self.__uid_to_dn(member_uid))
+ # always add the manager as a member because members is required
+ if not manager_dn in members:
+ members.append(manager_dn)
+ attr = [
+ ('objectclass', ['novaProject']),
+ ('cn', [name]),
+ ('description', [description]),
+ ('projectManager', [manager_dn]),
+ ('member', members)
+ ]
+ self.conn.add_s('cn=%s,%s' % (name, FLAGS.project_ldap_subtree), attr)
+ return self.__to_project(dict(attr))
+
+ def add_to_project(self, uid, project_id):
+ dn = 'cn=%s,%s' % (project_id, FLAGS.project_ldap_subtree)
+ return self.add_to_group(uid, dn)
+
+ def remove_from_project(self, uid, project_id):
+ dn = 'cn=%s,%s' % (project_id, FLAGS.project_ldap_subtree)
+ return self.remove_from_group(uid, dn)
+
+ def is_in_project(self, uid, project_id):
+ dn = 'cn=%s,%s' % (project_id, FLAGS.project_ldap_subtree)
+ return self.is_in_group(uid, dn)
+
+ def __create_group(self, group_dn, name, uid, description, member_uids = None):
+ if self.group_exists(name):
+ raise exception.Duplicate("Group can't be created because group %s already exists" % name)
+ members = []
+ if member_uids != None:
+ for member_uid in member_uids:
+ if not self.user_exists(member_uid):
+ raise exception.NotFound("Group can't be created because user %s doesn't exist" % member_uid)
+ members.append(self.__uid_to_dn(member_uid))
+ dn = self.__uid_to_dn(uid)
+ if not dn in members:
+ members.append(dn)
+ attr = [
+ ('objectclass', ['groupOfNames']),
+ ('cn', [name]),
+ ('description', [description]),
+ ('member', members)
+ ]
+ self.conn.add_s(group_dn, attr)
+ return self.__to_group(dict(attr))
- def remove_from_group(self, name, group):
- pass
+ def is_in_group(self, uid, group_dn):
+ if not self.user_exists(uid):
+ raise exception.NotFound("User %s can't be searched in group becuase the user doesn't exist" % (uid,))
+ if not self.group_exists(group_dn):
+ return False
+ res = self.find_object(group_dn,
+ '(member=%s)' % self.__uid_to_dn(uid))
+ return res != None
+
+ def add_to_group(self, uid, group_dn):
+ if not self.user_exists(uid):
+ raise exception.NotFound("User %s can't be added to the group becuase the user doesn't exist" % (uid,))
+ if not self.group_exists(group_dn):
+ raise exception.NotFound("The group at dn %s doesn't exist" % (group_dn,))
+ if self.is_in_group(uid, group_dn):
+ raise exception.Duplicate("User %s is already a member of the group %s" % (uid, group_dn))
+ attr = [
+ (ldap.MOD_ADD, 'member', self.__uid_to_dn(uid))
+ ]
+ self.conn.modify_s(group_dn, attr)
+
+ def remove_from_group(self, uid, group_dn):
+ if not self.group_exists(group_dn):
+ raise exception.NotFound("The group at dn %s doesn't exist" % (group_dn,))
+ if not self.user_exists(uid):
+ raise exception.NotFound("User %s can't be removed from the group because the user doesn't exist" % (uid,))
+ if not self.is_in_group(uid, group_dn):
+ raise exception.NotFound("User %s is not a member of the group" % (uid,))
+ attr = [
+ (ldap.MOD_DELETE, 'member', self.__uid_to_dn(uid))
+ ]
+ try:
+ self.conn.modify_s(group_dn, attr)
+ except ldap.OBJECT_CLASS_VIOLATION:
+ logging.debug("Attempted to remove the last member of a group. Deleting the group instead.")
+ self.delete_group(group_dn)
+
+ def remove_from_all(self, uid):
+ # FIXME(vish): what if deleted user is a project manager?
+ if not self.user_exists(uid):
+ raise exception.NotFound("User %s can't be removed from all because the user doesn't exist" % (uid,))
+ dn = self.__uid_to_dn(uid)
+ attr = [
+ (ldap.MOD_DELETE, 'member', dn)
+ ]
+ projects = self.find_groups_with_member(FLAGS.project_ldap_subtree, dn)
+ for project in projects:
+ self.conn.modify_s('cn=%s,%s' % (project.id, FLAGS.project_ldap_subtree), attr)
def create_key_pair(self, uid, key_name, public_key, fingerprint):
"""create's a public key in the directory underneath the user"""
@@ -403,41 +579,46 @@ class LDAPWrapper(object):
('sshPublicKey', [public_key]),
('keyFingerprint', [fingerprint]),
]
- self.conn.add_s('cn=%s,uid=%s,%s' % (key_name,
- uid,
- FLAGS.ldap_subtree),
- attr)
+ self.conn.add_s('cn=%s,%s' % (key_name,
+ self.__uid_to_dn(uid)),
+ attr)
return self.__to_key_pair(uid, dict(attr))
def find_user_by_access_key(self, access):
- query = '(' + 'accessKey' + '=' + access + ')'
- dn = FLAGS.ldap_subtree
+ query = '(accessKey=%s)' % access
+ dn = FLAGS.user_ldap_subtree
return self.__to_user(self.find_object(dn, query))
+ def delete_user(self, uid):
+ if not self.user_exists(uid):
+ raise exception.NotFound("User %s doesn't exist" % uid)
+ self.delete_key_pairs(uid)
+ self.remove_from_all(uid)
+ self.conn.delete_s('uid=%s,%s' % (uid,
+ FLAGS.user_ldap_subtree))
+
def delete_key_pair(self, uid, key_name):
if not self.key_pair_exists(uid, key_name):
- raise UserError("Key Pair " +
- key_name +
- " doesn't exist for user " +
- uid)
+ 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_subtree))
+ FLAGS.user_ldap_subtree))
- def delete_user(self, name):
- if not self.user_exists(name):
- raise UserError("User " +
- name +
- " doesn't exist")
- self.delete_key_pairs(name)
- self.conn.delete_s('uid=%s,%s' % (name,
- FLAGS.ldap_subtree))
+ def delete_group(self, group_dn):
+ if not self.group_exists(group_dn):
+ raise exception.NotFound("Group at dn %s doesn't exist" % group_dn)
+ self.conn.delete_s(group_dn)
+
+ def delete_project(self, name):
+ project_dn = 'cn=%s,%s' % (name, FLAGS.project_ldap_subtree)
+ self.delete_group(project_dn)
def __to_user(self, attr):
if attr == None:
return None
return User(
id = attr['uid'][0],
- name = attr['uid'][0],
+ name = attr['cn'][0],
access = attr['accessKey'][0],
secret = attr['secretKey'][0],
admin = (attr['isAdmin'][0] == 'TRUE')
@@ -447,8 +628,35 @@ class LDAPWrapper(object):
if attr == None:
return None
return KeyPair(
- owner = owner,
- name = attr['cn'][0],
+ id = attr['cn'][0],
+ owner_id = owner,
public_key = attr['sshPublicKey'][0],
fingerprint = attr['keyFingerprint'][0],
)
+
+ def __to_group(self, attr):
+ if attr == None:
+ return None
+ member_dns = attr.get('member', [])
+ return Group(
+ id = attr['cn'][0],
+ description = attr.get('description', [None])[0],
+ member_ids = [self.__dn_to_uid(x) for x in member_dns]
+ )
+
+ def __to_project(self, attr):
+ if attr == None:
+ return None
+ member_dns = attr.get('member', [])
+ return Project(
+ id = attr['cn'][0],
+ project_manager_id = self.__dn_to_uid(attr['projectManager'][0]),
+ description = attr.get('description', [None])[0],
+ member_ids = [self.__dn_to_uid(x) for x in member_dns]
+ )
+
+ def __dn_to_uid(self, dn):
+ return dn.split(',')[0].split('=')[1]
+
+ def __uid_to_dn(self, dn):
+ return 'uid=%s,%s' % (dn, FLAGS.user_ldap_subtree)
diff --git a/nova/compute/linux_net.py b/nova/compute/linux_net.py
index 0983241f9..319ccbdd6 100644
--- a/nova/compute/linux_net.py
+++ b/nova/compute/linux_net.py
@@ -1,10 +1,25 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-import signal
+# Copyright [2010] [Anso Labs, 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.
+
+import logging
import os
-import nova.utils
+import signal
import subprocess
+from nova import utils
+
# todo(ja): does the definition of network_path belong here?
from nova import flags
@@ -12,16 +27,16 @@ FLAGS=flags.FLAGS
def execute(cmd):
if FLAGS.fake_network:
- print "FAKE NET: %s" % cmd
+ logging.debug("FAKE NET: %s" % cmd)
return "fake", 0
else:
- nova.utils.execute(cmd)
+ utils.execute(cmd)
def runthis(desc, cmd):
if FLAGS.fake_network:
execute(cmd)
else:
- nova.utils.runthis(desc,cmd)
+ utils.runthis(desc,cmd)
def Popen(cmd):
if FLAGS.fake_network:
@@ -110,7 +125,7 @@ def start_dnsmasq(network):
os.kill(pid, signal.SIGHUP)
return
except Exception, e:
- logging.debug("Killing dnsmasq threw %s", e)
+ logging.debug("Hupping dnsmasq threw %s", e)
# otherwise delete the existing leases file and start dnsmasq
lease_file = dhcp_file(network.vlan, 'leases')
@@ -124,7 +139,10 @@ def stop_dnsmasq(network):
pid = dnsmasq_pid_for(network)
if pid:
- os.kill(pid, signal.SIGTERM)
+ try:
+ os.kill(pid, signal.SIGTERM)
+ except Exception, e:
+ logging.debug("Killing dnsmasq threw %s", e)
def dhcp_file(vlan, kind):
""" return path to a pid, leases or conf file for a vlan """
diff --git a/nova/compute/model.py b/nova/compute/model.py
index 78ed3a101..2754e9e6d 100644
--- a/nova/compute/model.py
+++ b/nova/compute/model.py
@@ -1,4 +1,4 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4 expandtab
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC]
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,7 +24,7 @@ InstanceDirectory manager.
True
>>> inst = InstDir['i-123']
>>> inst['ip'] = "192.168.0.3"
->>> inst['owner_id'] = "projectA"
+>>> inst['project_id'] = "projectA"
>>> inst.save()
True
@@ -46,6 +46,8 @@ from nova import utils
FLAGS = flags.FLAGS
+flags.DEFINE_string('instances_prefix', 'compute-',
+ 'prefix for keepers for instances')
# TODO(ja): singleton instance of the directory
class InstanceDirectory(object):
@@ -62,11 +64,12 @@ class InstanceDirectory(object):
def by_project(self, project):
""" returns a list of instance objects for a project """
- for instance_id in self.keeper['project:%s:instances' % project]:
+ for instance_id in self.keeper.smembers('project:%s:instances' % project):
yield Instance(instance_id)
def by_node(self, node_id):
""" returns a list of instances for a node """
+
for instance in self.all:
if instance['node_name'] == node_id:
yield instance
@@ -83,17 +86,13 @@ class InstanceDirectory(object):
pass
def exists(self, instance_id):
- if instance_id in self.keeper['instances']:
- return True
- return False
+ return self.keeper.set_is_member('instances', instance_id)
@property
def all(self):
""" returns a list of all instances """
- instances = self.keeper['instances']
- if instances != None:
- for instance_id in self.keeper['instances']:
- yield Instance(instance_id)
+ for instance_id in self.keeper.set_members('instances'):
+ yield Instance(instance_id)
def new(self):
""" returns an empty Instance object, with ID """
@@ -114,10 +113,12 @@ class Instance(object):
if self.state:
self.initial_state = self.state
else:
- self.state = {'state' : 'pending',
- 'instance_id' : instance_id,
- 'node_name' : 'unassigned',
- 'owner_id' : 'unassigned' }
+ self.state = {'state': 'pending',
+ 'instance_id': instance_id,
+ 'node_name': 'unassigned',
+ 'project_id': 'unassigned',
+ 'user_id': 'unassigned'
+ }
@property
def __redis_key(self):
@@ -143,8 +144,8 @@ class Instance(object):
def save(self):
""" update the directory with the state from this instance
- make sure you've set the owner_id before you call save
- for the first time.
+ make sure you've set the project_id and user_id before you call save
+ for the first time.
"""
# TODO(ja): implement hmset in redis-py and use it
# instead of multiple calls to hset
@@ -157,17 +158,23 @@ class Instance(object):
state[key] = val
self.keeper[self.__redis_key] = state
if self.initial_state == {}:
- self.keeper.set_add('project:%s:instances' % self.state['owner_id'],
+ self.keeper.set_add('project:%s:instances' % self.project,
self.instance_id)
self.keeper.set_add('instances', self.instance_id)
self.initial_state = self.state
return True
+ @property
+ def project(self):
+ if self.state.get('project_id', None):
+ return self.state['project_id']
+ return self.state.get('owner_id', 'unassigned')
+
def destroy(self):
""" deletes all related records from datastore.
- does NOT do anything to running libvirt state.
+ does NOT do anything to running libvirt state.
"""
- self.keeper.set_remove('project:%s:instances' % self.state['owner_id'],
+ self.keeper.set_remove('project:%s:instances' % self.project,
self.instance_id)
del self.keeper[self.__redis_key]
self.keeper.set_remove('instances', self.instance_id)
@@ -184,18 +191,18 @@ class Instance(object):
pass
# class Reservation(object):
-# """ ORM wrapper for a batch of launched instances """
-# def __init__(self):
-# pass
+# """ ORM wrapper for a batch of launched instances """
+# def __init__(self):
+# pass
#
-# def userdata(self):
-# """ """
-# pass
+# def userdata(self):
+# """ """
+# pass
#
#
# class NodeDirectory(object):
-# def __init__(self):
-# pass
+# def __init__(self):
+# pass
#
if __name__ == "__main__":
diff --git a/nova/compute/network.py b/nova/compute/network.py
index 612295f27..9a9bb9527 100644
--- a/nova/compute/network.py
+++ b/nova/compute/network.py
@@ -26,7 +26,6 @@ from nova import vendor
import IPy
from nova import datastore
-import nova.exception
from nova.compute import exception
from nova import flags
from nova import utils
@@ -113,13 +112,13 @@ class Network(object):
for idx in range(3, len(self.network)-2):
yield self.network[idx]
- def allocate_ip(self, user_id, mac):
+ def allocate_ip(self, user_id, project_id, mac):
for ip in self.range():
address = str(ip)
if not address in self.hosts.keys():
- logging.debug("Allocating IP %s to %s" % (address, user_id))
+ logging.debug("Allocating IP %s to %s" % (address, project_id))
self.hosts[address] = {
- "address" : address, "user_id" : user_id, 'mac' : mac
+ "address" : address, "user_id": user_id, "project_id" : project_id, 'mac' : mac
}
self.express(address=address)
return address
@@ -238,7 +237,6 @@ class DHCPNetwork(VirtNetwork):
else:
linux_net.start_dnsmasq(self)
-
class PrivateNetwork(DHCPNetwork):
def __init__(self, **kwargs):
super(PrivateNetwork, self).__init__(**kwargs)
@@ -249,23 +247,18 @@ class PrivateNetwork(DHCPNetwork):
'network': self.network_str,
'hosts': self.hosts}
- def express(self, *args, **kwargs):
- super(PrivateNetwork, self).express(*args, **kwargs)
-
-
-
class PublicNetwork(Network):
def __init__(self, network="192.168.216.0/24", **kwargs):
super(PublicNetwork, self).__init__(network=network, **kwargs)
self.express()
- def allocate_ip(self, user_id, mac):
+ def allocate_ip(self, user_id, project_id, mac):
for ip in self.range():
address = str(ip)
if not address in self.hosts.keys():
- logging.debug("Allocating IP %s to %s" % (address, user_id))
+ logging.debug("Allocating IP %s to %s" % (address, project_id))
self.hosts[address] = {
- "address" : address, "user_id" : user_id, 'mac' : mac
+ "address" : address, "user_id": user_id, "project_id" : project_id, 'mac' : mac
}
self.express(address=address)
return address
@@ -354,8 +347,8 @@ class VlanPool(object):
self.vlans = kwargs.get('vlans', {})
self.vlanpool = {}
self.manager = users.UserManager.instance()
- for user_id, vlan in self.vlans.iteritems():
- self.vlanpool[vlan] = user_id
+ for project_id, vlan in self.vlans.iteritems():
+ self.vlanpool[vlan] = project_id
def to_dict(self):
return {'vlans': self.vlans}
@@ -380,25 +373,25 @@ class VlanPool(object):
parsed = json.loads(json_string)
return cls.from_dict(parsed)
- def assign_vlan(self, user_id, vlan):
- logging.debug("Assigning vlan %s to user %s" % (vlan, user_id))
- self.vlans[user_id] = vlan
- self.vlanpool[vlan] = user_id
- return self.vlans[user_id]
-
- def next(self, user_id):
- for old_user_id, vlan in self.vlans.iteritems():
- if not self.manager.get_user(old_user_id):
- _get_keeper()["%s-default" % old_user_id] = {}
- del _get_keeper()["%s-default" % old_user_id]
- del self.vlans[old_user_id]
- return self.assign_vlan(user_id, vlan)
+ def assign_vlan(self, project_id, vlan):
+ logging.debug("Assigning vlan %s to project %s" % (vlan, project_id))
+ self.vlans[project_id] = vlan
+ self.vlanpool[vlan] = project_id
+ return self.vlans[project_id]
+
+ def next(self, project_id):
+ for old_project_id, vlan in self.vlans.iteritems():
+ if not self.manager.get_project(old_project_id):
+ _get_keeper()["%s-default" % old_project_id] = {}
+ del _get_keeper()["%s-default" % old_project_id]
+ del self.vlans[old_project_id]
+ return self.assign_vlan(project_id, vlan)
vlans = self.vlanpool.keys()
vlans.append(self.start)
nextvlan = max(vlans) + 1
if nextvlan == self.end:
raise exception.AddressNotAllocated("Out of VLANs")
- return self.assign_vlan(user_id, nextvlan)
+ return self.assign_vlan(project_id, nextvlan)
class NetworkController(object):
@@ -442,37 +435,37 @@ class NetworkController(object):
if address_record.get(u'instance_id', 'free') == instance_id:
return address_record[u'address']
- def get_users_network(self, user_id):
- """ get a user's private network, allocating one if needed """
+ def get_project_network(self, project_id):
+ """ get a project's private network, allocating one if needed """
- user = self.manager.get_user(user_id)
- if not user:
- raise Exception("User %s doesn't exist, uhoh." % user_id)
- usernet = self.get_network_from_name("%s-default" % user_id)
- if not usernet:
+ project = self.manager.get_project(project_id)
+ if not project:
+ raise Exception("Project %s doesn't exist, uhoh." % project_id)
+ project_net = self.get_network_from_name("%s-default" % project_id)
+ if not project_net:
pool = self.vlan_pool
- vlan = pool.next(user_id)
+ vlan = pool.next(project_id)
private_pool = NetworkPool()
network_str = private_pool.get_from_vlan(vlan)
- logging.debug("Constructing network %s and %s for %s" % (network_str, vlan, user_id))
- usernet = PrivateNetwork(
+ logging.debug("Constructing network %s and %s for %s" % (network_str, vlan, project_id))
+ project_net = PrivateNetwork(
network=network_str,
vlan=vlan)
- _get_keeper()["%s-default" % user_id] = usernet.to_dict()
+ _get_keeper()["%s-default" % project_id] = project_net.to_dict()
_get_keeper()['vlans'] = pool.to_dict()
- return usernet
+ return project_net
- def allocate_address(self, user_id, mac=None, type=PrivateNetwork):
+ def allocate_address(self, user_id, project_id, mac=None, type=PrivateNetwork):
ip = None
net_name = None
if type == PrivateNetwork:
- net = self.get_users_network(user_id)
- ip = net.allocate_ip(user_id, mac)
+ net = self.get_project_network(project_id)
+ ip = net.allocate_ip(user_id, project_id, mac)
net_name = net.name
- _get_keeper()["%s-default" % user_id] = net.to_dict()
+ _get_keeper()["%s-default" % project_id] = net.to_dict()
else:
net = self.public_net
- ip = net.allocate_ip(user_id, mac)
+ ip = net.allocate_ip(user_id, project_id, mac)
net_name = net.name
_get_keeper()['public'] = net.to_dict()
return (ip, net_name)
@@ -483,19 +476,19 @@ class NetworkController(object):
rv = net.deallocate_ip(str(address))
_get_keeper()['public'] = net.to_dict()
return rv
- for user in self.manager.get_users():
- if address in self.get_users_network(user.id).network:
- net = self.get_users_network(user.id)
+ for project in self.manager.get_projects():
+ if address in self.get_project_network(project.id).network:
+ net = self.get_project_network(project.id)
rv = net.deallocate_ip(str(address))
- _get_keeper()["%s-default" % user.id] = net.to_dict()
+ _get_keeper()["%s-default" % project.id] = net.to_dict()
return rv
raise exception.AddressNotAllocated()
def describe_addresses(self, type=PrivateNetwork):
if type == PrivateNetwork:
addresses = []
- for user in self.manager.get_users():
- addresses.extend(self.get_users_network(user.id).list_addresses())
+ for project in self.manager.get_projects():
+ addresses.extend(self.get_project_network(project.id).list_addresses())
return addresses
return self.public_net.list_addresses()
@@ -512,8 +505,8 @@ class NetworkController(object):
return rv
def express(self,address=None):
- for user in self.manager.get_users():
- self.get_users_network(user.id).express()
+ for project in self.manager.get_projects():
+ self.get_project_network(project.id).express()
def report_state(self):
pass
diff --git a/nova/compute/node.py b/nova/compute/node.py
index a4de0f98a..0f9a15672 100644
--- a/nova/compute/node.py
+++ b/nova/compute/node.py
@@ -57,8 +57,6 @@ flags.DEFINE_bool('use_s3', True,
'whether to get images from s3 or use local copy')
flags.DEFINE_string('instances_path', utils.abspath('../instances'),
'where instances are stored on disk')
-flags.DEFINE_string('instances_prefix', 'compute-',
- 'prefix for keepers for instances')
INSTANCE_TYPES = {}
INSTANCE_TYPES['m1.tiny'] = {'memory_mb': 512, 'vcpus': 1, 'local_gb': 0}
@@ -73,7 +71,6 @@ INSTANCE_TYPES['c1.medium'] = {'memory_mb': 2048, 'vcpus': 4, 'local_gb': 10}
# be a singleton
PROCESS_POOL_SIZE = 4
-
class Node(object, service.Service):
"""
Manages the running instances.
@@ -240,7 +237,6 @@ def _create_image(data, libvirt_xml):
def image_url(path):
return "%s:%s/_images/%s" % (FLAGS.s3_host, FLAGS.s3_port, path)
-
logging.info(basepath('disk'))
try:
os.makedirs(data['basepath'])
@@ -309,15 +305,17 @@ class Instance(object):
return (self.state == Instance.RUNNING or self.state == 'running')
def __init__(self, conn, pool, name, data):
- # TODO(termie): pool should probably be a singleton instead of being passed
- # here and in the classmethods
""" spawn an instance with a given name """
# TODO(termie): pool should probably be a singleton instead of being passed
# here and in the classmethods
self._pool = pool
self._conn = conn
+ # TODO(vish): this can be removed after data has been updated
+ # data doesn't seem to have a working iterator so in doesn't work
+ if not data.get('owner_id', None) is None:
+ data['user_id'] = data['owner_id']
+ data['project_id'] = data['owner_id']
self.datamodel = data
- print data
# NOTE(termie): to be passed to multiprocess self._s must be
# pickle-able by cPickle
@@ -344,7 +342,8 @@ class Instance(object):
self._s['image_id'] = data.get('image_id', FLAGS.default_image)
self._s['kernel_id'] = data.get('kernel_id', FLAGS.default_kernel)
self._s['ramdisk_id'] = data.get('ramdisk_id', FLAGS.default_ramdisk)
- self._s['owner_id'] = data.get('owner_id', '')
+ self._s['user_id'] = data.get('user_id', None)
+ self._s['project_id'] = data.get('project_id', self._s['user_id'])
self._s['node_name'] = data.get('node_name', '')
self._s['user_data'] = data.get('user_data', '')
self._s['ami_launch_index'] = data.get('ami_launch_index', None)
@@ -352,7 +351,6 @@ class Instance(object):
self._s['reservation_id'] = data.get('reservation_id', None)
# self._s['state'] = Instance.NOSTATE
self._s['state'] = data.get('state', Instance.NOSTATE)
-
self._s['key_data'] = data.get('key_data', None)
# TODO: we may not need to save the next few
@@ -415,7 +413,6 @@ class Instance(object):
def update_state(self):
info = self.info()
- self._s['state'] = info['state']
self.datamodel['state'] = info['state']
self.datamodel['node_name'] = FLAGS.node_name
self.datamodel.save()
@@ -427,7 +424,6 @@ class Instance(object):
raise exception.Error('trying to destroy already destroyed'
' instance: %s' % self.name)
- self._s['state'] = Instance.SHUTDOWN
self.datamodel['state'] = 'shutting_down'
self.datamodel.save()
try:
diff --git a/nova/crypto.py b/nova/crypto.py
index 6add55ee5..1f35ffa39 100644
--- a/nova/crypto.py
+++ b/nova/crypto.py
@@ -39,21 +39,20 @@ flags.DEFINE_string('keys_path', utils.abspath('../keys'), 'Where we keep our ke
flags.DEFINE_string('ca_path', utils.abspath('../CA'), 'Where we keep our root CA')
flags.DEFINE_boolean('use_intermediate_ca', False, 'Should we use intermediate CAs for each project?')
-
-def ca_path(username):
- if username:
- return "%s/INTER/%s/cacert.pem" % (FLAGS.ca_path, username)
+def ca_path(project_id):
+ if project_id:
+ return "%s/INTER/%s/cacert.pem" % (FLAGS.ca_path, project_id)
return "%s/cacert.pem" % (FLAGS.ca_path)
-def fetch_ca(username=None, chain=True):
+def fetch_ca(project_id=None, chain=True):
if not FLAGS.use_intermediate_ca:
- username = None
+ project_id = None
buffer = ""
- if username:
- with open(ca_path(username),"r") as cafile:
+ if project_id:
+ with open(ca_path(project_id),"r") as cafile:
buffer += cafile.read()
- if username and not chain:
- return buffer
+ if not chain:
+ return buffer
with open(ca_path(None),"r") as cafile:
buffer += cafile.read()
return buffer
@@ -104,7 +103,6 @@ def generate_x509_cert(subject="/C=US/ST=California/L=The Mission/O=CloudFed/OU=
shutil.rmtree(tmpdir)
return (private_key, csr)
-
def sign_csr(csr_text, intermediate=None):
if not FLAGS.use_intermediate_ca:
intermediate = None
@@ -118,7 +116,6 @@ def sign_csr(csr_text, intermediate=None):
os.chdir(start)
return _sign_csr(csr_text, user_ca)
-
def _sign_csr(csr_text, ca_folder):
tmpfolder = tempfile.mkdtemp()
csrfile = open("%s/inbound.csr" % (tmpfolder), "w")
@@ -197,7 +194,7 @@ def mkcacert(subject='nova', years=1):
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
-# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
diff --git a/nova/datastore.py b/nova/datastore.py
index 57940d98b..97c00264d 100644
--- a/nova/datastore.py
+++ b/nova/datastore.py
@@ -264,6 +264,12 @@ class SqliteKeeper(object):
group.remove(value)
self[item] = group
+ def set_members(self, item):
+ group = self[item]
+ if not group:
+ group = []
+ return group
+
def set_fetch(self, item):
# TODO(termie): I don't really know what set_fetch is supposed to do
group = self[item]
@@ -354,6 +360,10 @@ class RedisKeeper(object):
item = slugify(item, self.prefix)
return Redis.instance().srem(item, json.dumps(value))
+ def set_members(self, item):
+ item = slugify(item, self.prefix)
+ return [json.loads(v) for v in Redis.instance().smembers(item)]
+
def set_fetch(self, item):
item = slugify(item, self.prefix)
for obj in Redis.instance().sinter([item]):
diff --git a/nova/endpoint/admin.py b/nova/endpoint/admin.py
index e9880acc5..b51929a83 100644
--- a/nova/endpoint/admin.py
+++ b/nova/endpoint/admin.py
@@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, 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.
@@ -61,7 +61,6 @@ class AdminController(object):
API Controller for users, node status, and worker mgmt.
Trivial admin_only wrapper will be replaced with RBAC,
allowing project managers to administer project users.
-
"""
def __init__(self, user_manager, node_manager=None):
self.user_manager = user_manager
@@ -80,16 +79,14 @@ class AdminController(object):
def describe_users(self, _context, **_kwargs):
"""Returns all users - should be changed to deal with a list.
"""
- return {'userSet':
+ return {'userSet':
[user_dict(u) for u in self.user_manager.get_users()] }
@admin_only
def register_user(self, _context, name, **_kwargs):
""" Creates a new user, and returns generated credentials.
"""
- self.user_manager.create_user(name)
-
- return user_dict(self.user_manager.get_user(name))
+ return user_dict(self.user_manager.create_user(name))
@admin_only
def deregister_user(self, _context, name, **_kwargs):
@@ -102,14 +99,17 @@ class AdminController(object):
return True
@admin_only
- def generate_x509_for_user(self, _context, name, **_kwargs):
+ 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 = self.user_manager.get_project(project)
user = self.user_manager.get_user(name)
- return user_dict(user, base64.b64encode(user.get_credentials()))
-
+ return user_dict(user, base64.b64encode(project.get_credentials(user)))
+
@admin_only
def describe_nodes(self, _context, **_kwargs):
"""Returns status info for all nodes. Includes:
@@ -120,12 +120,11 @@ class AdminController(object):
* DHCP servers running
* Iptables / bridges
"""
- return {'nodeSet':
- [node_dict(n) for n in self.node_manager.get_nodes()] }
-
+ return {'nodeSet':
+ [node_dict(n) for n in self.node_manager.get_nodes()] }
+
@admin_only
def describe_node(self, _context, name, **_kwargs):
"""Returns status info for single node.
"""
return node_dict(self.node_manager.get_node(name))
-
diff --git a/nova/endpoint/api.py b/nova/endpoint/api.py
index 5bbda3f56..e70694210 100755
--- a/nova/endpoint/api.py
+++ b/nova/endpoint/api.py
@@ -1,13 +1,13 @@
#!/usr/bin/python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, 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.
@@ -41,7 +41,6 @@ from nova.auth import users
FLAGS = flags.FLAGS
flags.DEFINE_integer('cc_port', 8773, 'cloud controller port')
-
_log = logging.getLogger("api")
_log.setLevel(logging.DEBUG)
@@ -63,9 +62,10 @@ def _underscore_to_xmlcase(str):
class APIRequestContext(object):
- def __init__(self, handler, user):
+ 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)]
@@ -73,13 +73,11 @@ class APIRequestContext(object):
class APIRequest(object):
- def __init__(self, handler, controller, action):
- self.handler = handler
+ def __init__(self, controller, action):
self.controller = controller
self.action = action
- def send(self, user, **kwargs):
- context = APIRequestContext(self.handler, user)
+ def send(self, context, **kwargs):
try:
method = getattr(self.controller,
@@ -227,7 +225,6 @@ class MetadataRequestHandler(tornado.web.RequestHandler):
self.print_data(data)
self.finish()
-
class APIRequestHandler(tornado.web.RequestHandler):
def get(self, controller_name):
self.execute(controller_name)
@@ -257,7 +254,7 @@ class APIRequestHandler(tornado.web.RequestHandler):
# Get requested action and remove authentication args for final request.
try:
action = args.pop('Action')[0]
- args.pop('AWSAccessKeyId')
+ access = args.pop('AWSAccessKeyId')[0]
args.pop('SignatureMethod')
args.pop('SignatureVersion')
args.pop('Version')
@@ -266,15 +263,18 @@ class APIRequestHandler(tornado.web.RequestHandler):
raise tornado.web.HTTPError(400)
# Authenticate the request.
- user = self.application.user_manager.authenticate(
- auth_params,
- signature,
- self.request.method,
- self.request.host,
- self.request.path
- )
-
- if not user:
+ try:
+ (user, project) = users.UserManager.instance().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)
@@ -282,8 +282,9 @@ class APIRequestHandler(tornado.web.RequestHandler):
for key, value in args.items():
_log.debug('arg: %s\t\tval: %s' % (key, value))
- request = APIRequest(self, controller, action)
- d = request.send(user, **args)
+ 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
diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py
index 27dd81aa2..53290e386 100644
--- a/nova/endpoint/cloud.py
+++ b/nova/endpoint/cloud.py
@@ -19,6 +19,7 @@ dispatched to other nodes via AMQP RPC. State is via distributed
datastore.
"""
+import base64
import json
import logging
import os
@@ -58,7 +59,6 @@ class CloudController(object):
sent to the other nodes.
"""
def __init__(self):
- self._instances = datastore.Keeper(FLAGS.instances_prefix)
self.instdir = model.InstanceDirectory()
self.network = network.NetworkController()
self.setup()
@@ -97,7 +97,7 @@ class CloudController(object):
return self.instdir.by_ip(ip)
def get_metadata(self, ip):
- i = self.instdir.by_ip(ip)
+ i = self.get_instance_by_ip(ip)
if i is None:
return None
if i['key_name']:
@@ -145,7 +145,6 @@ class CloudController(object):
data['product-codes'] = i['product_codes']
return data
-
def describe_availability_zones(self, context, **kwargs):
return {'availabilityZoneInfo': [{'zoneName': 'nova',
'zoneState': 'available'}]}
@@ -207,11 +206,9 @@ class CloudController(object):
def get_console_output(self, context, instance_id, **kwargs):
# instance_id is passed in as a list of instances
- instance = self.instdir.get(instance_id[0])
+ instance = self._get_instance(context, instance_id[0])
if instance['state'] == 'pending':
raise exception.ApiError('Cannot get output for pending instance')
- if not context.user.is_authorized(instance.get('owner_id', None)):
- raise exception.ApiError('Not authorized to view output')
return rpc.call('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
{"method": "get_console_output",
"args" : {"instance_id": instance_id[0]}})
@@ -225,7 +222,7 @@ class CloudController(object):
def describe_volumes(self, context, **kwargs):
volumes = []
for volume in self.volumes:
- if context.user.is_authorized(volume.get('user_id', None)):
+ if context.user.is_admin() or volume['project_id'] == context.project.id:
v = self.format_volume(context, volume)
volumes.append(v)
return defer.succeed({'volumeSet': volumes})
@@ -252,36 +249,59 @@ class CloudController(object):
"args" : {"size": size,
"user_id": context.user.id}})
def _format_result(result):
- volume = self._get_volume(result['result'])
+ volume = self._get_volume(context, result['result'])
return {'volumeSet': [self.format_volume(context, volume)]}
res.addCallback(_format_result)
return res
- def _get_by_id(self, nodes, id):
- if nodes == {}:
- raise exception.NotFound("%s not found" % id)
- for node_name, node in nodes.iteritems():
- if node.has_key(id):
- return node_name, node[id]
- raise exception.NotFound("%s not found" % id)
-
- def _get_volume(self, volume_id):
+ def _convert_address(self, network_address):
+ # FIXME(vish): this should go away when network.py stores info properly
+ address = {}
+ address['public_ip'] == network_address[u'address']
+ address['user_id'] == network_address[u'user_id']
+ address['project_id'] == network_address.get(u'project_id', address['user_id'])
+ address['instance_id'] == network_address.get(u'instance_id', None)
+ return address
+
+ def _get_address(self, context, public_ip):
+ # right now all addresses are allocated locally
+ # FIXME(vish) this should move into network.py
+ for network_address in self.network.describe_addresses():
+ if network_address[u'address'] == public_ip:
+ address = self._convert_address(network_address)
+ if context.user.is_admin() or address['project_id'] == context.project.id:
+ return address
+ raise exception.NotFound("Address at ip %s not found" % public_ip)
+
+ def _get_image(self, context, image_id):
+ """passes in context because
+ objectstore does its own authorization"""
+ result = images.list(context, [image_id])
+ if not result:
+ raise exception.NotFound('Image %s could not be found' % image_id)
+ image = result[0]
+ return image
+
+ def _get_instance(self, context, instance_id):
+ for instance in self.instances:
+ if instance['instance_id'] == instance_id:
+ if context.user.is_admin() or instance['project_id'] == context.project.id:
+ return instance
+ raise exception.NotFound('Instance %s could not be found' % instance_id)
+
+ def _get_volume(self, context, volume_id):
for volume in self.volumes:
if volume['volume_id'] == volume_id:
- return volume
+ if context.user.is_admin() or volume['project_id'] == context.project.id:
+ return volume
+ raise exception.NotFound('Volume %s could not be found' % volume_id)
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
- volume = self._get_volume(volume_id)
+ volume = self._get_volume(context, volume_id)
storage_node = volume['node_name']
# TODO: (joshua) Fix volumes to store creator id
- if not context.user.is_authorized(volume.get('user_id', None)):
- raise exception.ApiError("%s not authorized for %s" %
- (context.user.id, volume_id))
- instance = self.instdir.get(instance_id)
+ instance = self._get_instance(context, instance_id)
compute_node = instance['node_name']
- if not context.user.is_authorized(instance.get('owner_id', None)):
- raise exception.ApiError(message="%s not authorized for %s" %
- (context.user.id, instance_id))
aoe_device = volume['aoe_device']
# Needs to get right node controller for attaching to
# TODO: Maybe have another exchange that goes to everyone?
@@ -297,24 +317,17 @@ class CloudController(object):
"mountpoint" : device}})
return defer.succeed(True)
+
def detach_volume(self, context, volume_id, **kwargs):
# TODO(joshua): Make sure the updated state has been received first
- volume = self._get_volume(volume_id)
+ volume = self._get_volume(context, volume_id)
storage_node = volume['node_name']
- if not context.user.is_authorized(volume.get('user_id', None)):
- raise exception.ApiError("%s not authorized for %s" %
- (context.user.id, volume_id))
if 'instance_id' in volume.keys():
instance_id = volume['instance_id']
try:
- instance = self.instdir.get(instance_id)
+ instance = self._get_instance(context, instance_id)
compute_node = instance['node_name']
mountpoint = volume['mountpoint']
- if not context.user.is_authorized(
- instance.get('owner_id', None)):
- raise exception.ApiError(
- "%s not authorized for %s" %
- (context.user.id, instance_id))
rpc.cast('%s.%s' % (FLAGS.compute_topic, compute_node),
{"method": "detach_volume",
"args" : {"instance_id": instance_id,
@@ -332,16 +345,16 @@ class CloudController(object):
return [{str: x} for x in lst]
def describe_instances(self, context, **kwargs):
- return defer.succeed(self.format_instances(context.user))
+ return defer.succeed(self._format_instances(context))
- def format_instances(self, user, reservation_id = None):
+ def _format_instances(self, context, reservation_id = None):
if self.instances == {}:
return {'reservationSet': []}
reservations = {}
for inst in self.instances:
instance = inst.values()[0]
res_id = instance.get('reservation_id', 'Unknown')
- if (user.is_authorized(instance.get('owner_id', None))
+ if ((context.user.is_admin() or context.project.id == instance['project_id'])
and (reservation_id == None or reservation_id == res_id)):
i = {}
i['instance_id'] = instance.get('instance_id', None)
@@ -357,7 +370,7 @@ class CloudController(object):
i['public_dns_name'] = i['private_dns_name']
i['dns_name'] = instance.get('dns_name', None)
i['key_name'] = instance.get('key_name', None)
- if user.is_admin():
+ if context.user.is_admin():
i['key_name'] = '%s (%s, %s)' % (i['key_name'],
instance.get('owner_id', None), instance.get('node_name',''))
i['product_codes_set'] = self._convert_to_set(
@@ -369,7 +382,7 @@ class CloudController(object):
if not reservations.has_key(res_id):
r = {}
r['reservation_id'] = res_id
- r['owner_id'] = instance.get('owner_id', None)
+ r['owner_id'] = instance.get('project_id', None)
r['group_set'] = self._convert_to_set(
instance.get('groups', None), 'group_id')
r['instances_set'] = []
@@ -382,52 +395,52 @@ class CloudController(object):
def describe_addresses(self, context, **kwargs):
return self.format_addresses(context.user)
- def format_addresses(self, user):
+ def format_addresses(self, context):
addresses = []
# TODO(vish): move authorization checking into network.py
- for address_record in self.network.describe_addresses(
- type=network.PublicNetwork):
+ for network_address in self.network.describe_addresses(type=network.PublicNetwork):
#logging.debug(address_record)
- if user.is_authorized(address_record[u'user_id']):
- address = {
- 'public_ip': address_record[u'address'],
- 'instance_id' : address_record.get(u'instance_id', 'free')
- }
- # FIXME: add another field for user id
- if user.is_admin():
- address['instance_id'] = "%s (%s)" % (
- address['instance_id'],
- address_record[u'user_id'],
- )
- addresses.append(address)
+ address = self._convert_address(network_address)
+ address_rv = {
+ 'public_ip': address['public_ip'],
+ 'instance_id' : address.get('instance_id', 'free')
+ }
+ # FIXME: add another field for user id
+ if context.user.is_admin():
+ address_rv['instance_id'] = "%s (%s, %s)" % (
+ address['instance_id'],
+ address['user_id'],
+ address['project_id'],
+ )
+ addresses.append(address_rv)
# logging.debug(addresses)
return {'addressesSet': addresses}
def allocate_address(self, context, **kwargs):
- # TODO: Verify user is valid?
- kwargs['owner_id'] = context.user.id
(address,network_name) = self.network.allocate_address(
- context.user.id, type=network.PublicNetwork)
+ context.user.id, context.project_id, type=network.PublicNetwork)
return defer.succeed({'addressSet': [{'publicIp' : address}]})
- def release_address(self, context, **kwargs):
- self.network.deallocate_address(kwargs.get('public_ip', None))
+ def release_address(self, context, public_ip, **kwargs):
+ address = self._get_address(public_ip)
return defer.succeed({'releaseResponse': ["Address released."]})
def associate_address(self, context, instance_id, **kwargs):
- instance = self.instdir.get(instance_id)
+ instance = self._get_instance(context, instance_id)
rv = self.network.associate_address(
kwargs['public_ip'],
instance['private_dns_name'],
instance_id)
return defer.succeed({'associateResponse': ["Address associated."]})
- def disassociate_address(self, context, **kwargs):
- rv = self.network.disassociate_address(kwargs['public_ip'])
+ def disassociate_address(self, context, public_ip, **kwargs):
+ address = self._get_address(public_ip)
+ rv = self.network.disassociate_address(public_ip)
# TODO - Strip the IP from the instance
- return rv
+ return defer.succeed({'disassociateResponse': ["Address disassociated."]})
def run_instances(self, context, **kwargs):
+ image = self._get_image(context, kwargs['image_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())
@@ -449,11 +462,14 @@ class CloudController(object):
inst['launch_time'] = launch_time
inst['key_data'] = key_data or ''
inst['key_name'] = kwargs.get('key_name', '')
- inst['owner_id'] = context.user.id
+ inst['user_id'] = context.user.id
+ inst['project_id'] = context.project.id
inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = num
address, _netname = self.network.allocate_address(
- inst['owner_id'], mac=inst['mac_address'])
+ user_id=inst['user_id'],
+ project_id=inst['project_id'],
+ mac=inst['mac_address'])
network = self.network.get_users_network(str(context.user.id))
inst['network_str'] = json.dumps(network.to_dict())
inst['bridge_name'] = network.bridge_name
@@ -466,82 +482,77 @@ class CloudController(object):
logging.debug("Casting to node for %s's instance with IP of %s" %
(context.user.name, inst['private_dns_name']))
# TODO: Make the NetworkComputeNode figure out the network name from ip.
- return defer.succeed(self.format_instances(
+ return defer.succeed(self._format_instances(
context.user, reservation_id))
def terminate_instances(self, context, instance_id, **kwargs):
logging.debug("Going to start terminating instances")
- # TODO: return error if not authorized
for i in instance_id:
logging.debug("Going to try and terminate %s" % i)
- instance = self.instdir.get(i)
- #if instance['state'] == 'pending':
- # raise exception.ApiError('Cannot terminate pending instance')
- if context.user.is_authorized(instance.get('owner_id', None)):
+ try:
+ instance = self._get_instance(context, i)
+ except exception.NotFound:
+ logging.warning("Instance %s was not found during terminate" % i)
+ continue
+ try:
+ self.network.disassociate_address(
+ instance.get('public_dns_name', 'bork'))
+ except:
+ pass
+ if instance.get('private_dns_name', None):
+ logging.debug("Deallocating address %s" % instance.get('private_dns_name', None))
try:
- self.network.disassociate_address(
- instance.get('public_dns_name', 'bork'))
- except:
+ self.network.deallocate_address(instance.get('private_dns_name', None))
+ except Exception, _err:
pass
- if instance.get('private_dns_name', None):
- logging.debug("Deallocating address %s" % instance.get('private_dns_name', None))
- try:
- self.network.deallocate_address(instance.get('private_dns_name', None))
- except Exception, _err:
- pass
- if instance.get('node_name', 'unassigned') != 'unassigned': #It's also internal default
- rpc.cast('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
+ if instance.get('node_name', 'unassigned') != 'unassigned': #It's also internal default
+ rpc.cast('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
{"method": "terminate_instance",
"args" : {"instance_id": i}})
- else:
- instance.destroy()
+ else:
+ instance.destroy()
return defer.succeed(True)
def reboot_instances(self, context, instance_id, **kwargs):
- # TODO: return error if not authorized
+ """instance_id is a list of instance ids"""
for i in instance_id:
- instance = self.instdir.get(i)
+ instance = self._get_instance(context, i)
if instance['state'] == 'pending':
raise exception.ApiError('Cannot reboot pending instance')
- if context.user.is_authorized(instance.get('owner_id', None)):
- rpc.cast('%s.%s' % (FLAGS.node_topic, instance['node_name']),
+ rpc.cast('%s.%s' % (FLAGS.node_topic, instance['node_name']),
{"method": "reboot_instance",
"args" : {"instance_id": i}})
return defer.succeed(True)
def delete_volume(self, context, volume_id, **kwargs):
# TODO: return error if not authorized
- volume = self._get_volume(volume_id)
+ volume = self._get_volume(context, volume_id)
storage_node = volume['node_name']
- if context.user.is_authorized(volume.get('user_id', None)):
- rpc.cast('%s.%s' % (FLAGS.storage_topic, storage_node),
- {"method": "delete_volume",
- "args" : {"volume_id": volume_id}})
+ rpc.cast('%s.%s' % (FLAGS.storage_topic, storage_node),
+ {"method": "delete_volume",
+ "args" : {"volume_id": volume_id}})
return defer.succeed(True)
def describe_images(self, context, image_id=None, **kwargs):
- imageSet = images.list(context.user)
- if not image_id is None:
- imageSet = [i for i in imageSet if i['imageId'] in image_id]
-
+ # The objectstore does its own authorization for describe
+ imageSet = images.list(context, image_id)
return defer.succeed({'imagesSet': imageSet})
def deregister_image(self, context, image_id, **kwargs):
- images.deregister(context.user, image_id)
-
+ # FIXME: should the objectstore be doing these authorization checks?
+ images.deregister(context, image_id)
return defer.succeed({'imageId': image_id})
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.user, image_location)
+ image_id = images.register(context, image_location)
logging.debug("Registered %s as %s" % (image_location, image_id))
return defer.succeed({'imageId': image_id})
- def modify_image_attribute(self, context, image_id,
- attribute, operation_type, **kwargs):
+ def modify_image_attribute(self, context, image_id, attribute, operation_type, **kwargs):
if attribute != 'launchPermission':
raise exception.ApiError('only launchPermission is supported')
if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all':
diff --git a/nova/endpoint/images.py b/nova/endpoint/images.py
index f494ce892..673a108e9 100644
--- a/nova/endpoint/images.py
+++ b/nova/endpoint/images.py
@@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, 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.
@@ -14,7 +14,7 @@
# limitations under the License.
"""
-Proxy AMI-related calls from the cloud controller, to the running
+Proxy AMI-related calls from the cloud controller, to the running
objectstore daemon.
"""
@@ -31,9 +31,8 @@ from nova import utils
FLAGS = flags.FLAGS
-
-def modify(user, image_id, operation):
- conn(user).make_request(
+def modify(context, image_id, operation):
+ conn(context).make_request(
method='POST',
bucket='_images',
query_args=qs({'image_id': image_id, 'operation': operation}))
@@ -41,11 +40,11 @@ def modify(user, image_id, operation):
return True
-def register(user, image_location):
+def register(context, image_location):
""" rpc call to register a new image based from a manifest """
image_id = utils.generate_uid('ami')
- conn(user).make_request(
+ conn(context).make_request(
method='PUT',
bucket='_images',
query_args=qs({'image_location': image_location,
@@ -53,32 +52,32 @@ def register(user, image_location):
return image_id
-
-def list(user, filter_list=[]):
+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(user).make_request(
+ response = conn(context).make_request(
method='GET',
bucket='_images')
- return json.loads(response.read())
+ 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 deregister(user, image_id):
+def deregister(context, image_id):
""" unregister an image """
- conn(user).make_request(
+ conn(context).make_request(
method='DELETE',
bucket='_images',
query_args=qs({'image_id': image_id}))
-
-def conn(user):
+def conn(context):
return boto.s3.connection.S3Connection (
- aws_access_key_id=user.access,
- aws_secret_access_key=user.secret,
+ aws_access_key_id='%s:%s' % (context.user.access, context.project.name),
+ aws_secret_access_key=context.user.secret,
is_secure=False,
calling_format=boto.s3.connection.OrdinaryCallingFormat(),
port=FLAGS.s3_port,
diff --git a/nova/exception.py b/nova/exception.py
index dc7b16cdb..82d08e840 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, 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.
@@ -14,7 +14,7 @@
# limitations under the License.
"""
-Nova base exception handling, including decorator for re-raising
+Nova base exception handling, including decorator for re-raising
Nova-type exceptions. SHOULD include dedicated exception logging.
"""
@@ -23,16 +23,21 @@ import traceback
import sys
class Error(Exception):
- pass
+ def __init__(self, message=None):
+ super(Error, self).__init__(message)
-class ApiError(Error):
+class ApiError(Error):
def __init__(self, message='Unknown', code='Unknown'):
self.message = message
self.code = code
+ super(ApiError, self).__init__('%s: %s'% (code, message))
class NotFound(Error):
pass
+class Duplicate(Error):
+ pass
+
class NotAuthorized(Error):
pass
@@ -42,12 +47,12 @@ def wrap_exception(f):
return f(*args, **kw)
except Exception, e:
if not isinstance(e, Error):
- # exc_type, exc_value, exc_traceback = sys.exc_info()
+ # exc_type, exc_value, exc_traceback = sys.exc_info()
logging.exception('Uncaught exception')
- # logging.debug(traceback.extract_stack(exc_traceback))
+ # logging.debug(traceback.extract_stack(exc_traceback))
raise Error(str(e))
raise
_wrap.func_name = f.func_name
return _wrap
-
+
diff --git a/nova/fakerabbit.py b/nova/fakerabbit.py
index ec2e50791..13d432b45 100644
--- a/nova/fakerabbit.py
+++ b/nova/fakerabbit.py
@@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, 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.
@@ -39,7 +39,7 @@ class Exchange(object):
for f in self._routes[routing_key]:
logging.debug('Publishing to route %s', f)
f(message, routing_key=routing_key)
-
+
def bind(self, callback, routing_key):
self._routes.setdefault(routing_key, [])
self._routes[routing_key].append(callback)
@@ -52,7 +52,7 @@ class Queue(object):
def __repr__(self):
return '<Queue: %s>' % self.name
-
+
def push(self, message, routing_key=None):
self._queue.put(message)
@@ -70,7 +70,7 @@ class Backend(object):
#super(__impl, self).__init__(*args, **kwargs)
self._exchanges = {}
self._queues = {}
-
+
def _reset_all(self):
self._exchanges = {}
self._queues = {}
@@ -78,7 +78,7 @@ class Backend(object):
def queue_declare(self, queue, **kwargs):
if queue not in self._queues:
logging.debug('Declaring queue %s', queue)
- self._queues[queue] = Queue(queue)
+ self._queues[queue] = Queue(queue)
def exchange_declare(self, exchange, type, *args, **kwargs):
if exchange not in self._exchanges:
@@ -92,7 +92,7 @@ class Backend(object):
routing_key)
def get(self, queue, no_ack=False):
- if not self._queues[queue].size():
+ if not queue in self._queues or not self._queues[queue].size():
return None
(message_data, content_type, content_encoding) = \
self._queues[queue].pop()
@@ -122,7 +122,7 @@ class Backend(object):
def __getattr__(self, attr):
return getattr(self.__instance, attr)
-
+
def __setattr__(self, attr, value):
return setattr(self.__instance, attr, value)
diff --git a/nova/objectstore/bucket.py b/nova/objectstore/bucket.py
index 0777c2f11..0bf102867 100644
--- a/nova/objectstore/bucket.py
+++ b/nova/objectstore/bucket.py
@@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, 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.
@@ -28,12 +28,10 @@ from nova import flags
from nova import utils
from nova.objectstore import stored
-
FLAGS = flags.FLAGS
flags.DEFINE_string('buckets_path', utils.abspath('../buckets'),
'path to s3 buckets')
-
class Bucket(object):
def __init__(self, name):
self.name = name
@@ -62,11 +60,11 @@ class Bucket(object):
return buckets
@staticmethod
- def create(bucket_name, user):
- """Create a new bucket owned by a user.
+ def create(bucket_name, context):
+ """Create a new bucket owned by a project.
@bucket_name: a string representing the name of the bucket to create
- @user: a nova.auth.user who should own the bucket.
+ @context: a nova.auth.api.ApiContext object representing who owns the bucket.
Raises:
NotAuthorized: if the bucket is already exists or has invalid name
@@ -80,7 +78,7 @@ class Bucket(object):
os.makedirs(path)
with open(path+'.json', 'w') as f:
- json.dump({'ownerId': user.id}, f)
+ json.dump({'ownerId': context.project.id}, f)
@property
def metadata(self):
@@ -101,9 +99,9 @@ class Bucket(object):
except:
return None
- def is_authorized(self, user):
+ def is_authorized(self, context):
try:
- return user.is_admin() or self.owner_id == user.id
+ return context.user.is_admin() or self.owner_id == context.project.id
except Exception, e:
pass
diff --git a/nova/objectstore/handler.py b/nova/objectstore/handler.py
index c3e036a40..a7fff12fc 100644
--- a/nova/objectstore/handler.py
+++ b/nova/objectstore/handler.py
@@ -31,6 +31,7 @@ S3 client with this module::
print c.get("mybucket", "mykey").body
"""
+
import datetime
import os
import urllib
@@ -44,6 +45,7 @@ from tornado import escape, web
from nova import exception
from nova import flags
+from nova.endpoint import api
from nova.objectstore import bucket
from nova.objectstore import image
@@ -87,16 +89,18 @@ class BaseRequestHandler(web.RequestHandler):
SUPPORTED_METHODS = ("PUT", "GET", "DELETE", "HEAD")
@property
- def user(self):
- if not hasattr(self, '_user'):
+ def context(self):
+ if not hasattr(self, '_context'):
try:
- access = self.request.headers['Authorization'].split(' ')[1].split(':')[0]
- user = self.application.user_manager.get_user_from_access_key(access)
- user.secret # FIXME: check signature here!
- self._user = user
- except:
+ # Authorization Header format: 'AWS <access>:<secret>'
+ access, sep, secret = self.request.headers['Authorization'].split(' ')[1].rpartition(':')
+ (user, project) = self.application.user_manager.authenticate(access, secret, {}, self.request.method, self.request.host, self.request.path, False)
+ # FIXME: check signature here!
+ self._context = api.APIRequestContext(self, user, project)
+ except exception.Error, ex:
+ logging.debug("Authentication Failure: %s" % ex)
raise web.HTTPError(403)
- return self._user
+ return self._context
def render_xml(self, value):
assert isinstance(value, dict) and len(value) == 1
@@ -134,7 +138,7 @@ class BaseRequestHandler(web.RequestHandler):
class RootHandler(BaseRequestHandler):
def get(self):
- buckets = [b for b in bucket.Bucket.all() if b.is_authorized(self.user)]
+ buckets = [b for b in bucket.Bucket.all() if b.is_authorized(self.context)]
self.render_xml({"ListAllMyBucketsResult": {
"Buckets": {"Bucket": [b.metadata for b in buckets]},
@@ -148,7 +152,7 @@ class BucketHandler(BaseRequestHandler):
bucket_object = bucket.Bucket(bucket_name)
- if not bucket_object.is_authorized(self.user):
+ if not bucket_object.is_authorized(self.context):
raise web.HTTPError(403)
prefix = self.get_argument("prefix", u"")
@@ -162,7 +166,7 @@ class BucketHandler(BaseRequestHandler):
@catch_nova_exceptions
def put(self, bucket_name):
logging.debug("Creating bucket %s" % (bucket_name))
- bucket.Bucket.create(bucket_name, self.user)
+ bucket.Bucket.create(bucket_name, self.context)
self.finish()
@catch_nova_exceptions
@@ -170,7 +174,7 @@ class BucketHandler(BaseRequestHandler):
logging.debug("Deleting bucket %s" % (bucket_name))
bucket_object = bucket.Bucket(bucket_name)
- if not bucket_object.is_authorized(self.user):
+ if not bucket_object.is_authorized(self.context):
raise web.HTTPError(403)
bucket_object.delete()
@@ -185,7 +189,7 @@ class ObjectHandler(BaseRequestHandler):
bucket_object = bucket.Bucket(bucket_name)
- if not bucket_object.is_authorized(self.user):
+ if not bucket_object.is_authorized(self.context):
raise web.HTTPError(403)
obj = bucket_object[urllib.unquote(object_name)]
@@ -199,7 +203,7 @@ class ObjectHandler(BaseRequestHandler):
logging.debug("Putting object: %s / %s" % (bucket_name, object_name))
bucket_object = bucket.Bucket(bucket_name)
- if not bucket_object.is_authorized(self.user):
+ if not bucket_object.is_authorized(self.context):
raise web.HTTPError(403)
key = urllib.unquote(object_name)
@@ -212,7 +216,7 @@ class ObjectHandler(BaseRequestHandler):
logging.debug("Deleting object: %s / %s" % (bucket_name, object_name))
bucket_object = bucket.Bucket(bucket_name)
- if not bucket_object.is_authorized(self.user):
+ if not bucket_object.is_authorized(self.context):
raise web.HTTPError(403)
del bucket_object[urllib.unquote(object_name)]
@@ -228,7 +232,7 @@ class ImageHandler(BaseRequestHandler):
""" returns a json listing of all images
that a user has permissions to see """
- images = [i for i in image.Image.all() if i.is_authorized(self.user)]
+ images = [i for i in image.Image.all() if i.is_authorized(self.context)]
self.finish(json.dumps([i.metadata for i in images]))
@@ -247,11 +251,11 @@ class ImageHandler(BaseRequestHandler):
bucket_object = bucket.Bucket(image_location.split("/")[0])
manifest = image_location[len(image_location.split('/')[0])+1:]
- if not bucket_object.is_authorized(self.user):
+ if not bucket_object.is_authorized(self.context):
raise web.HTTPError(403)
p = multiprocessing.Process(target=image.Image.create,args=
- (image_id, image_location, self.user))
+ (image_id, image_location, self.context))
p.start()
self.finish()
@@ -264,7 +268,7 @@ class ImageHandler(BaseRequestHandler):
image_object = image.Image(image_id)
- if image_object.owner_id != self.user.id:
+ if not image.is_authorized(self.context):
raise web.HTTPError(403)
image_object.set_public(operation=='add')
@@ -277,7 +281,7 @@ class ImageHandler(BaseRequestHandler):
image_id = self.get_argument("image_id", u"")
image_object = image.Image(image_id)
- if image_object.owner_id != self.user.id:
+ if not image.is_authorized(self.context):
raise web.HTTPError(403)
image_object.delete()
diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py
index 1878487f7..892ada00c 100644
--- a/nova/objectstore/image.py
+++ b/nova/objectstore/image.py
@@ -58,9 +58,9 @@ class Image(object):
except:
pass
- def is_authorized(self, user):
+ def is_authorized(self, context):
try:
- return self.metadata['isPublic'] or self.metadata['imageOwnerId'] == user.id
+ return self.metadata['isPublic'] or context.user.is_admin() or self.metadata['imageOwnerId'] == context.project.id
except:
return False
@@ -91,7 +91,7 @@ class Image(object):
return json.load(f)
@staticmethod
- def create(image_id, image_location, user):
+ def create(image_id, image_location, context):
image_path = os.path.join(FLAGS.images_path, image_id)
os.makedirs(image_path)
@@ -119,7 +119,7 @@ class Image(object):
info = {
'imageId': image_id,
'imageLocation': image_location,
- 'imageOwnerId': user.id,
+ 'imageOwnerId': context.project.id,
'isPublic': False, # FIXME: grab public from manifest
'architecture': 'x86_64', # FIXME: grab architecture from manifest
'type' : image_type
diff --git a/nova/tests/access_unittest.py b/nova/tests/access_unittest.py
deleted file mode 100644
index ab0759c2d..000000000
--- a/nova/tests/access_unittest.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-# Copyright [2010] [Anso Labs, 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.
-
-import logging
-import os
-import unittest
-
-from nova import flags
-from nova import test
-from nova.auth import users
-from nova.endpoint import cloud
-
-FLAGS = flags.FLAGS
-
-class AccessTestCase(test.BaseTestCase):
- def setUp(self):
- FLAGS.fake_libvirt = True
- FLAGS.fake_storage = True
- self.users = users.UserManager.instance()
- super(AccessTestCase, self).setUp()
- # Make a test project
- # Make a test user
- self.users.create_user('test1', 'access', 'secret')
-
- # Make the test user a member of the project
-
- def tearDown(self):
- # Delete the test user
- # Delete the test project
- self.users.delete_user('test1')
- pass
-
- def test_001_basic_user_access(self):
- user = self.users.get_user('test1')
- # instance-foo, should be using object and not owner_id
- instance_id = "i-12345678"
- self.assertTrue(user.is_authorized(instance_id, action="describe_instances"))
-
- def test_002_sysadmin_access(self):
- user = self.users.get_user('test1')
- bucket = "foo/bar/image"
- self.assertFalse(user.is_authorized(bucket, action="register"))
- self.users.add_role(user, "sysadmin")
-
-
-if __name__ == "__main__":
- # TODO: Implement use_fake as an option
- unittest.main()
diff --git a/nova/tests/api_integration.py b/nova/tests/api_integration.py
index d2e1026b8..cf84b9907 100644
--- a/nova/tests/api_integration.py
+++ b/nova/tests/api_integration.py
@@ -1,11 +1,11 @@
# Copyright [2010] [Anso Labs, 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.
@@ -33,14 +33,13 @@ def get_connection():
path='/services/Cloud',
debug=99
)
-
+
class APIIntegrationTests(unittest.TestCase):
def test_001_get_all_images(self):
conn = get_connection()
res = conn.get_all_images()
- print res
-
-
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py
index 568a8dcd3..40eeb8a23 100644
--- a/nova/tests/cloud_unittest.py
+++ b/nova/tests/cloud_unittest.py
@@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, 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.
@@ -20,7 +20,6 @@ import unittest
from xml.etree import ElementTree
from nova import vendor
-import mox
from tornado import ioloop
from twisted.internet import defer
@@ -53,18 +52,24 @@ class CloudTestCase(test.BaseTestCase):
topic=FLAGS.cloud_topic,
proxy=self.cloud)
self.injected.append(self.cloud_consumer.attach_to_tornado(self.ioloop))
-
+
# set up a node
self.node = node.Node()
self.node_consumer = rpc.AdapterConsumer(connection=self.conn,
topic=FLAGS.compute_topic,
proxy=self.node)
self.injected.append(self.node_consumer.attach_to_tornado(self.ioloop))
-
- user_mocker = mox.Mox()
- self.admin = user_mocker.CreateMock(users.User)
- self.admin.is_authorized(mox.IgnoreArg()).AndReturn(True)
- self.context = api.APIRequestContext(handler=None,user=self.admin)
+
+ try:
+ users.UserManager.instance().create_user('admin', 'admin', 'admin')
+ except: pass
+ admin = users.UserManager.instance().get_user('admin')
+ project = users.UserManager.instance().create_project('proj', 'admin', 'proj')
+ self.context = api.APIRequestContext(handler=None,project=project,user=admin)
+
+ def tearDown(self):
+ users.UserManager.instance().delete_project('proj')
+ users.UserManager.instance().delete_user('admin')
def test_console_output(self):
if FLAGS.fake_libvirt:
@@ -76,7 +81,7 @@ class CloudTestCase(test.BaseTestCase):
logging.debug(output)
self.assert_(output)
rv = yield self.node.terminate_instance(instance_id)
-
+
def test_run_instances(self):
if FLAGS.fake_libvirt:
logging.debug("Can't test instances without a real virtual env.")
@@ -128,9 +133,7 @@ class CloudTestCase(test.BaseTestCase):
'state': 0x01,
'user_data': ''
}
-
- rv = self.cloud.format_instances(self.admin)
- print rv
+ rv = self.cloud._format_instances(self.context)
self.assert_(len(rv['reservationSet']) == 0)
# simulate launch of 5 instances
@@ -139,19 +142,18 @@ class CloudTestCase(test.BaseTestCase):
# inst = instance(i)
# self.cloud.instances['pending'][inst['instance_id']] = inst
- #rv = self.cloud.format_instances(self.admin)
+ #rv = self.cloud._format_instances(self.admin)
#self.assert_(len(rv['reservationSet']) == 1)
#self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5)
-
# report 4 nodes each having 1 of the instances
#for i in xrange(4):
# self.cloud.update_state('instances', {('node-%s' % i): {('i-%s' % i): instance(i)}})
-
+
# one instance should be pending still
#self.assert_(len(self.cloud.instances['pending'].keys()) == 1)
# check that the reservations collapse
- #rv = self.cloud.format_instances(self.admin)
+ #rv = self.cloud._format_instances(self.admin)
#self.assert_(len(rv['reservationSet']) == 1)
#self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5)
diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py
index 43c7831a7..a78d9075d 100644
--- a/nova/tests/network_unittest.py
+++ b/nova/tests/network_unittest.py
@@ -30,84 +30,86 @@ class NetworkTestCase(test.TrialTestCase):
super(NetworkTestCase, self).setUp()
logging.getLogger().setLevel(logging.DEBUG)
self.manager = users.UserManager.instance()
+ try:
+ self.manager.create_user('netuser', 'netuser', 'netuser')
+ except: pass
for i in range(0, 6):
- name = 'user%s' % i
- if not self.manager.get_user(name):
- self.manager.create_user(name, name, name)
+ name = 'project%s' % i
+ if not self.manager.get_project(name):
+ self.manager.create_project(name, 'netuser', name)
self.network = network.NetworkController(netsize=16)
def tearDown(self):
super(NetworkTestCase, self).tearDown()
for i in range(0, 6):
- name = 'user%s' % i
- self.manager.delete_user(name)
+ name = 'project%s' % i
+ self.manager.delete_project(name)
+ self.manager.delete_user('netuser')
def test_network_serialization(self):
net1 = network.Network(vlan=100, network="192.168.100.0/24", conn=None)
- address = net1.allocate_ip("user0", "01:24:55:36:f2:a0")
+ address = net1.allocate_ip("netuser", "project0", "01:24:55:36:f2:a0")
net_json = str(net1)
net2 = network.Network.from_json(net_json)
self.assertEqual(net_json, str(net2))
self.assertTrue(IPy.IP(address) in net2.network)
def test_allocate_deallocate_address(self):
- for flag in flags.FLAGS:
- print "%s=%s" % (flag, flags.FLAGS.get(flag, None))
- (address, net_name) = self.network.allocate_address(
- "user0", "01:24:55:36:f2:a0")
+ (address, net_name) = self.network.allocate_address("netuser",
+ "project0", "01:24:55:36:f2:a0")
logging.debug("Was allocated %s" % (address))
- self.assertEqual(True, address in self._get_user_addresses("user0"))
+ self.assertEqual(True, address in self._get_project_addresses("project0"))
rv = self.network.deallocate_address(address)
- self.assertEqual(False, address in self._get_user_addresses("user0"))
+ self.assertEqual(False, address in self._get_project_addresses("project0"))
def test_range_allocation(self):
- (address, net_name) = self.network.allocate_address(
- "user0", "01:24:55:36:f2:a0")
- (secondaddress, net_name) = self.network.allocate_address(
- "user1", "01:24:55:36:f2:a0")
- self.assertEqual(True, address in self._get_user_addresses("user0"))
+ (address, net_name) = self.network.allocate_address("netuser",
+ "project0", "01:24:55:36:f2:a0")
+ (secondaddress, net_name) = self.network.allocate_address("netuser",
+ "project1", "01:24:55:36:f2:a0")
+ self.assertEqual(True, address in self._get_project_addresses("project0"))
self.assertEqual(True,
- secondaddress in self._get_user_addresses("user1"))
- self.assertEqual(False, address in self._get_user_addresses("user1"))
+ secondaddress in self._get_project_addresses("project1"))
+ self.assertEqual(False, address in self._get_project_addresses("project1"))
rv = self.network.deallocate_address(address)
- self.assertEqual(False, address in self._get_user_addresses("user0"))
+ self.assertEqual(False, address in self._get_project_addresses("project0"))
rv = self.network.deallocate_address(secondaddress)
self.assertEqual(False,
- secondaddress in self._get_user_addresses("user1"))
+ secondaddress in self._get_project_addresses("project1"))
def test_subnet_edge(self):
- (secondaddress, net_name) = self.network.allocate_address("user0")
- for user in range(1,5):
- user_id = "user%s" % (user)
- (address, net_name) = self.network.allocate_address(
- user_id, "01:24:55:36:f2:a0")
- (address2, net_name) = self.network.allocate_address(
- user_id, "01:24:55:36:f2:a0")
- (address3, net_name) = self.network.allocate_address(
- user_id, "01:24:55:36:f2:a0")
+ (secondaddress, net_name) = self.network.allocate_address("netuser", "project0")
+ for project in range(1,5):
+ project_id = "project%s" % (project)
+ (address, net_name) = self.network.allocate_address("netuser",
+ project_id, "01:24:55:36:f2:a0")
+ (address2, net_name) = self.network.allocate_address("netuser",
+ project_id, "01:24:55:36:f2:a0")
+ (address3, net_name) = self.network.allocate_address("netuser",
+ project_id, "01:24:55:36:f2:a0")
self.assertEqual(False,
- address in self._get_user_addresses("user0"))
+ address in self._get_project_addresses("project0"))
self.assertEqual(False,
- address2 in self._get_user_addresses("user0"))
+ address2 in self._get_project_addresses("project0"))
self.assertEqual(False,
- address3 in self._get_user_addresses("user0"))
+ address3 in self._get_project_addresses("project0"))
rv = self.network.deallocate_address(address)
rv = self.network.deallocate_address(address2)
rv = self.network.deallocate_address(address3)
rv = self.network.deallocate_address(secondaddress)
- def test_too_many_users(self):
+ def test_too_many_projects(self):
for i in range(0, 30):
- name = 'toomany-user%s' % i
- self.manager.create_user(name, name, name)
- (address, net_name) = self.network.allocate_address(
+ name = 'toomany-project%s' % i
+ self.manager.create_project(name, 'netuser', name)
+ (address, net_name) = self.network.allocate_address("netuser",
name, "01:24:55:36:f2:a0")
- self.manager.delete_user(name)
+ self.manager.delete_project(name)
- def _get_user_addresses(self, user_id):
+ def _get_project_addresses(self, project_id):
rv = self.network.describe_addresses()
- user_addresses = []
+ project_addresses = []
for item in rv:
- if item['user_id'] == user_id:
- user_addresses.append(item['address'])
- return user_addresses
+ if item['project_id'] == project_id:
+ project_addresses.append(item['address'])
+ return project_addresses
diff --git a/nova/tests/node_unittest.py b/nova/tests/node_unittest.py
index 7a6115fcc..5ecd56d52 100644
--- a/nova/tests/node_unittest.py
+++ b/nova/tests/node_unittest.py
@@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, 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.
@@ -14,14 +14,9 @@
# limitations under the License.
import logging
-import StringIO
-import time
-import unittest
from xml.etree import ElementTree
from nova import vendor
-import mox
-from tornado import ioloop
from twisted.internet import defer
from nova import exception
@@ -39,21 +34,21 @@ class InstanceXmlTestCase(test.TrialTestCase):
def test_serialization(self):
# TODO: Reimplement this, it doesn't make sense in redis-land
return
-
+
# instance_id = 'foo'
# first_node = node.Node()
# inst = yield first_node.run_instance(instance_id)
- #
+ #
# # force the state so that we can verify that it changes
# inst._s['state'] = node.Instance.NOSTATE
# xml = inst.toXml()
# self.assert_(ElementTree.parse(StringIO.StringIO(xml)))
- #
+ #
# second_node = node.Node()
# new_inst = node.Instance.fromXml(second_node._conn, pool=second_node._pool, xml=xml)
# self.assertEqual(new_inst.state, node.Instance.RUNNING)
# rv = yield first_node.terminate_instance(instance_id)
-
+
class NodeConnectionTestCase(test.TrialTestCase):
def setUp(self):
@@ -64,20 +59,22 @@ class NodeConnectionTestCase(test.TrialTestCase):
fake_users=True,
redis_db=8)
self.node = node.Node()
-
+
def create_instance(self):
instdir = model.InstanceDirectory()
inst = instdir.new()
# TODO(ja): add ami, ari, aki, user_data
inst['reservation_id'] = 'r-fakeres'
inst['launch_time'] = '10'
- inst['owner_id'] = 'fake'
+ inst['user_id'] = 'fake'
+ inst['project_id'] = 'fake'
+ inst['instance_type'] = 'm1.tiny'
inst['node_name'] = FLAGS.node_name
inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = 0
inst.save()
return inst['instance_id']
-
+
@defer.inlineCallbacks
def test_run_describe_terminate(self):
instance_id = self.create_instance()
@@ -96,11 +93,10 @@ class NodeConnectionTestCase(test.TrialTestCase):
def test_reboot(self):
instance_id = self.create_instance()
rv = yield self.node.run_instance(instance_id)
-
+
rv = yield self.node.describe_instances()
- logging.debug("describe_instances returns %s" % (rv))
self.assertEqual(rv[instance_id].name, instance_id)
-
+
yield self.node.reboot_instance(instance_id)
rv = yield self.node.describe_instances()
@@ -111,7 +107,7 @@ class NodeConnectionTestCase(test.TrialTestCase):
def test_console_output(self):
instance_id = self.create_instance()
rv = yield self.node.run_instance(instance_id)
-
+
console = yield self.node.get_console_output(instance_id)
self.assert_(console)
rv = yield self.node.terminate_instance(instance_id)
@@ -123,6 +119,6 @@ class NodeConnectionTestCase(test.TrialTestCase):
rv = yield self.node.describe_instances()
self.assertEqual(rv[instance_id].name, instance_id)
-
+
self.assertRaises(exception.Error, self.node.run_instance, instance_id)
rv = yield self.node.terminate_instance(instance_id)
diff --git a/nova/tests/objectstore_unittest.py b/nova/tests/objectstore_unittest.py
index 5f41d47a0..812f5418b 100644
--- a/nova/tests/objectstore_unittest.py
+++ b/nova/tests/objectstore_unittest.py
@@ -56,8 +56,6 @@ class ObjectStoreTestCase(test.BaseTestCase):
logging.getLogger().setLevel(logging.DEBUG)
self.um = users.UserManager.instance()
-
- def test_buckets(self):
try:
self.um.create_user('user1')
except: pass
@@ -67,18 +65,41 @@ class ObjectStoreTestCase(test.BaseTestCase):
try:
self.um.create_user('admin_user', admin=True)
except: pass
+ try:
+ self.um.create_project('proj1', 'user1', 'a proj', ['user1'])
+ except: pass
+ try:
+ self.um.create_project('proj2', 'user2', 'a proj', ['user2'])
+ except: pass
+ class Context(object): pass
+ self.context = Context()
+
+ def tearDown(self):
+ self.um.delete_project('proj1')
+ self.um.delete_project('proj2')
+ self.um.delete_user('user1')
+ self.um.delete_user('user2')
+ self.um.delete_user('admin_user')
+ super(ObjectStoreTestCase, self).tearDown()
- objectstore.bucket.Bucket.create('new_bucket', self.um.get_user('user1'))
+ def test_buckets(self):
+ self.context.user = self.um.get_user('user1')
+ self.context.project = self.um.get_project('proj1')
+ objectstore.bucket.Bucket.create('new_bucket', self.context)
bucket = objectstore.bucket.Bucket('new_bucket')
# creator is authorized to use bucket
- self.assert_(bucket.is_authorized(self.um.get_user('user1')))
+ self.assert_(bucket.is_authorized(self.context))
# another user is not authorized
- self.assert_(bucket.is_authorized(self.um.get_user('user2')) == False)
+ self.context.user = self.um.get_user('user2')
+ self.context.project = self.um.get_project('proj2')
+ self.assert_(bucket.is_authorized(self.context) == False)
# admin is authorized to use bucket
- self.assert_(bucket.is_authorized(self.um.get_user('admin_user')))
+ self.context.user = self.um.get_user('admin_user')
+ self.context.project = None
+ self.assert_(bucket.is_authorized(self.context))
# new buckets are empty
self.assert_(bucket.list_keys()['Contents'] == [])
@@ -116,18 +137,13 @@ class ObjectStoreTestCase(test.BaseTestCase):
exception = True
self.assert_(exception)
- self.um.delete_user('user1')
- self.um.delete_user('user2')
- self.um.delete_user('admin_user')
def test_images(self):
- try:
- self.um.create_user('image_creator')
- except: pass
- image_user = self.um.get_user('image_creator')
+ self.context.user = self.um.get_user('user1')
+ self.context.project = self.um.get_project('proj1')
# create a bucket for our bundle
- objectstore.bucket.Bucket.create('image_bucket', image_user)
+ objectstore.bucket.Bucket.create('image_bucket', self.context)
bucket = objectstore.bucket.Bucket('image_bucket')
# upload an image manifest/parts
@@ -136,7 +152,7 @@ class ObjectStoreTestCase(test.BaseTestCase):
bucket[os.path.basename(path)] = open(path, 'rb').read()
# register an image
- objectstore.image.Image.create('i-testing', 'image_bucket/1mb.manifest.xml', image_user)
+ objectstore.image.Image.create('i-testing', 'image_bucket/1mb.manifest.xml', self.context)
# verify image
my_img = objectstore.image.Image('i-testing')
@@ -147,14 +163,9 @@ class ObjectStoreTestCase(test.BaseTestCase):
self.assertEqual(sha, '3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3')
# verify image permissions
- try:
- self.um.create_user('new_user')
- except: pass
- new_user = self.um.get_user('new_user')
- self.assert_(my_img.is_authorized(new_user) == False)
-
- self.um.delete_user('new_user')
- self.um.delete_user('image_creator')
+ self.context.user = self.um.get_user('user2')
+ self.context.project = self.um.get_project('proj2')
+ self.assert_(my_img.is_authorized(self.context) == False)
# class ApiObjectStoreTestCase(test.BaseTestCase):
# def setUp(self):
diff --git a/nova/tests/users_unittest.py b/nova/tests/users_unittest.py
index 70f508b35..ff34b8957 100644
--- a/nova/tests/users_unittest.py
+++ b/nova/tests/users_unittest.py
@@ -24,11 +24,9 @@ from M2Crypto import X509
from nova import crypto
from nova import flags
from nova import test
-from nova import utils
from nova.auth import users
from nova.endpoint import cloud
-
FLAGS = flags.FLAGS
@@ -40,8 +38,9 @@ class UserTestCase(test.BaseTestCase):
redis_db=8)
self.users = users.UserManager.instance()
- def test_001_can_create_user(self):
+ def test_001_can_create_users(self):
self.users.create_user('test1', 'access', 'secret')
+ self.users.create_user('test2')
def test_002_can_get_user(self):
user = self.users.get_user('test1')
@@ -83,7 +82,6 @@ class UserTestCase(test.BaseTestCase):
key.save_pub_key_bio(bio)
converted = crypto.ssl_pub_to_ssh_pub(bio.read())
# assert key fields are equal
- print converted
self.assertEqual(public_key.split(" ")[1].strip(),
converted.split(" ")[1].strip())
@@ -101,16 +99,44 @@ class UserTestCase(test.BaseTestCase):
users = self.users.get_users()
self.assertTrue(filter(lambda u: u.id == 'test1', users))
- def test_011_can_generate_x509(self):
+ def test_201_can_create_project(self):
+ project = self.users.create_project('testproj', 'test1', 'A test project', ['test1'])
+ self.assertTrue(filter(lambda p: p.name == 'testproj', self.users.get_projects()))
+ self.assertEqual(project.name, 'testproj')
+ self.assertEqual(project.description, 'A test project')
+ self.assertEqual(project.project_manager_id, 'test1')
+ self.assertTrue(project.has_member('test1'))
+
+ def test_202_user1_is_project_member(self):
+ self.assertTrue(self.users.get_user('test1').is_project_member('testproj'))
+
+ def test_203_user2_is_not_project_member(self):
+ self.assertFalse(self.users.get_user('test2').is_project_member('testproj'))
+
+ def test_204_user1_is_project_manager(self):
+ self.assertTrue(self.users.get_user('test1').is_project_manager('testproj'))
+
+ def test_205_user2_is_not_project_manager(self):
+ self.assertFalse(self.users.get_user('test2').is_project_manager('testproj'))
+
+ def test_206_can_add_user_to_project(self):
+ self.users.add_to_project('test2', 'testproj')
+ self.assertTrue(self.users.get_project('testproj').has_member('test2'))
+
+ def test_208_can_remove_user_from_project(self):
+ self.users.remove_from_project('test2', 'testproj')
+ self.assertFalse(self.users.get_project('testproj').has_member('test2'))
+
+ def test_209_can_generate_x509(self):
# MUST HAVE RUN CLOUD SETUP BY NOW
self.cloud = cloud.CloudController()
self.cloud.setup()
- private_key, signed_cert_string = self.users.get_user('test1').generate_x509_cert()
+ private_key, signed_cert_string = self.users.get_project('testproj').generate_x509_cert('test1')
logging.debug(signed_cert_string)
# Need to verify that it's signed by the right intermediate CA
- full_chain = crypto.fetch_ca(username='test1', chain=True)
- int_cert = crypto.fetch_ca(username='test1', chain=False)
+ full_chain = crypto.fetch_ca(project_id='testproj', chain=True)
+ int_cert = crypto.fetch_ca(project_id='testproj', chain=False)
cloud_cert = crypto.fetch_ca()
logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain)
signed_cert = X509.load_cert_string(signed_cert_string)
@@ -125,11 +151,16 @@ class UserTestCase(test.BaseTestCase):
else:
self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey()))
- def test_012_can_delete_user(self):
+ def test_299_can_delete_project(self):
+ self.users.delete_project('testproj')
+ self.assertFalse(filter(lambda p: p.name == 'testproj', self.users.get_projects()))
+
+ def test_999_can_delete_users(self):
self.users.delete_user('test1')
users = self.users.get_users()
- if users != None:
- self.assertFalse(filter(lambda u: u.id == 'test1', users))
+ self.assertFalse(filter(lambda u: u.id == 'test1', users))
+ self.users.delete_user('test2')
+ self.assertEqual(self.users.get_user('test2'), None)
if __name__ == "__main__":
diff --git a/nova/volume/storage.py b/nova/volume/storage.py
index 823e1390a..cf64b995f 100644
--- a/nova/volume/storage.py
+++ b/nova/volume/storage.py
@@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, 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.
@@ -19,6 +19,7 @@ destroying persistent storage volumes, ala EBS.
Currently uses Ata-over-Ethernet.
"""
+import glob
import logging
import random
import socket
@@ -52,7 +53,7 @@ flags.DEFINE_integer('shelf_id',
flags.DEFINE_string('storage_availability_zone',
'nova',
'availability zone of this node')
-flags.DEFINE_boolean('fake_storage', False,
+flags.DEFINE_boolean('fake_storage', False,
'Should we make real storage volumes to attach?')
class BlockStore(object):
@@ -62,7 +63,7 @@ class BlockStore(object):
if FLAGS.fake_storage:
self.volume_class = FakeVolume
self._init_volume_group()
- self.keeper = datastore.Keeper('instances')
+ self.keeper = datastore.Keeper('storage-')
def report_state(self):
#TODO: aggregate the state of the system
@@ -82,7 +83,7 @@ class BlockStore(object):
def get_volume(self, volume_id):
""" Returns a redis-backed volume object """
- if volume_id in self.keeper['volumes']:
+ if self.keeper.set_is_member('volumes', volume_id):
return self.volume_class(volume_id=volume_id)
raise exception.Error("Volume does not exist")
diff --git a/run_tests.py b/run_tests.py
index 535a0464a..886ab4bd0 100644
--- a/run_tests.py
+++ b/run_tests.py
@@ -44,7 +44,6 @@ from twisted.scripts import trial as trial_script
from nova import flags
from nova import twistd
-from nova.tests.access_unittest import *
from nova.tests.api_unittest import *
from nova.tests.cloud_unittest import *
from nova.tests.keeper_unittest import *