Merged Vish's work on adding projects to nova

This commit is contained in:
Jesse Andrews
2010-05-30 15:21:34 -07:00
parent fd278ade0b
commit 94518726fb
30 changed files with 1025 additions and 871 deletions
-69
View File
@@ -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)
+65 -5
View File
@@ -22,6 +22,12 @@ import logging
from nova import datastore from nova import datastore
SCOPE_SUBTREE = 1 SCOPE_SUBTREE = 1
MOD_ADD = 0
MOD_DELETE = 1
SUBS = {
'groupOfNames': ['novaProject']
}
class NO_SUCH_OBJECT(Exception): class NO_SUCH_OBJECT(Exception):
@@ -44,6 +50,46 @@ class FakeLDAP(object):
def unbind_s(self): def unbind_s(self):
pass 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): def search_s(self, dn, scope, query=None, fields=None):
logging.debug("searching for %s" % dn) logging.debug("searching for %s" % dn)
filtered = {} filtered = {}
@@ -51,12 +97,11 @@ class FakeLDAP(object):
for cn, attrs in d.iteritems(): for cn, attrs in d.iteritems():
if cn[-len(dn):] == dn: if cn[-len(dn):] == dn:
filtered[cn] = attrs filtered[cn] = attrs
objects = filtered
if query: if query:
k,v = query[1:-1].split('=')
objects = {} objects = {}
for cn, attrs in filtered.iteritems(): for cn, attrs in filtered.iteritems():
if attrs.has_key(k) and (v in attrs[k] or if self._match_query(query, attrs):
v == attrs[k]):
objects[cn] = attrs objects[cn] = attrs
if objects == {}: if objects == {}:
raise NO_SUCH_OBJECT() raise NO_SUCH_OBJECT()
@@ -75,7 +120,22 @@ class FakeLDAP(object):
self.keeper['objects'] = d self.keeper['objects'] = d
def delete_s(self, cn): def delete_s(self, cn):
logging.debug("creating for %s" % cn) logging.debug("deleting %s" % cn)
d = self.keeper['objects'] or {} d = self.keeper['objects']
del d[cn] del d[cn]
self.keeper['objects'] = d 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
-60
View File
@@ -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
+14 -18
View File
@@ -46,12 +46,9 @@ import urllib
import base64 import base64
from nova.exception import Error from nova.exception import Error
_log = logging.getLogger('signer')
logging.getLogger('signer').setLevel(logging.WARN)
class Signer(object): class Signer(object):
""" hacked up code from boto/connection.py """ """ hacked up code from boto/connection.py """
def __init__(self, secret_key): def __init__(self, secret_key):
self.hmac = hmac.new(secret_key, digestmod=hashlib.sha1) self.hmac = hmac.new(secret_key, digestmod=hashlib.sha1)
if hashlib.sha256: if hashlib.sha256:
@@ -59,15 +56,14 @@ class Signer(object):
def generate(self, params, verb, server_string, path): def generate(self, params, verb, server_string, path):
if params['SignatureVersion'] == '0': if params['SignatureVersion'] == '0':
t = self._calc_signature_0(params) return self._calc_signature_0(params)
elif params['SignatureVersion'] == '1': if params['SignatureVersion'] == '1':
t = self._calc_signature_1(params) return self._calc_signature_1(params)
elif params['SignatureVersion'] == '2': if params['SignatureVersion'] == '2':
t = self._calc_signature_2(params, verb, server_string, path) return self._calc_signature_2(params, verb, server_string, path)
else: raise Error('Unknown Signature Version: %s' % self.SignatureVersion)
raise Error('Unknown Signature Version: %s' % self.SignatureVersion)
return t
def _get_utf8_value(self, value): def _get_utf8_value(self, value):
if not isinstance(value, str) and not isinstance(value, unicode): if not isinstance(value, str) and not isinstance(value, unicode):
value = str(value) value = str(value)
@@ -99,7 +95,7 @@ class Signer(object):
return base64.b64encode(self.hmac.digest()) return base64.b64encode(self.hmac.digest())
def _calc_signature_2(self, params, verb, server_string, path): 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) string_to_sign = '%s\n%s\n%s\n' % (verb, server_string, path)
if self.hmac_256: if self.hmac_256:
hmac = self.hmac_256 hmac = self.hmac_256
@@ -114,13 +110,13 @@ class Signer(object):
val = self._get_utf8_value(params[key]) val = self._get_utf8_value(params[key])
pairs.append(urllib.quote(key, safe='') + '=' + urllib.quote(val, safe='-_~')) pairs.append(urllib.quote(key, safe='') + '=' + urllib.quote(val, safe='-_~'))
qs = '&'.join(pairs) qs = '&'.join(pairs)
_log.debug('query string: %s' % qs) logging.debug('query string: %s' % qs)
string_to_sign += 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) hmac.update(string_to_sign)
b64 = base64.b64encode(hmac.digest()) b64 = base64.b64encode(hmac.digest())
_log.debug('len(b64)=%d' % len(b64)) logging.debug('len(b64)=%d' % len(b64))
_log.debug('base64 encoded digest: %s' % b64) logging.debug('base64 encoded digest: %s' % b64)
return b64 return b64
if __name__ == '__main__': if __name__ == '__main__':
+28 -28
View File
@@ -1,12 +1,12 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright [2010] [Anso Labs, LLC] # Copyright [2010] [Anso Labs, LLC]
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 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 # LDAP Public Key Patch schema for use with openssh-ldappubkey
# Author: Eric AUGE <eau@phear.org> # Author: Eric AUGE <eau@phear.org>
# #
# Based on the proposal of : Mark Ruijter # Based on the proposal of : Mark Ruijter
# #
# octetString SYNTAX # octetString SYNTAX
attributetype ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey' attributetype ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey'
DESC 'MANDATORY: OpenSSH Public key' DESC 'MANDATORY: OpenSSH Public key'
EQUALITY octetStringMatch EQUALITY octetStringMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
# printableString SYNTAX yes|no # printableString SYNTAX yes|no
objectclass ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY objectclass ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY
DESC 'MANDATORY: OpenSSH LPK objectclass' DESC 'MANDATORY: OpenSSH LPK objectclass'
MAY ( sshPublicKey $ uid ) MAY ( sshPublicKey $ uid )
) )
LPK_SCHEMA_EOF LPK_SCHEMA_EOF
@@ -44,7 +44,7 @@ cat >/etc/ldap/schema/nova.schema <<NOVA_SCHEMA_EOF
# Person object for Nova # Person object for Nova
# inetorgperson with extra attributes # inetorgperson with extra attributes
# Author: Vishvananda Ishaya <vishvananda@yahoo.com> # Author: Vishvananda Ishaya <vishvananda@yahoo.com>
# #
# #
# using internet experimental oid arc as per BP64 3.1 # using internet experimental oid arc as per BP64 3.1
@@ -54,32 +54,32 @@ objectidentifier novaOCs novaSchema:4
attributetype ( attributetype (
novaAttrs:1 novaAttrs:1
NAME 'accessKey' NAME 'accessKey'
DESC 'Key for accessing data' DESC 'Key for accessing data'
EQUALITY caseIgnoreMatch EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE SINGLE-VALUE
) )
attributetype ( attributetype (
novaAttrs:2 novaAttrs:2
NAME 'secretKey' NAME 'secretKey'
DESC 'Secret key' DESC 'Secret key'
EQUALITY caseIgnoreMatch EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE SINGLE-VALUE
) )
attributetype ( attributetype (
novaAttrs:3 novaAttrs:3
NAME 'keyFingerprint' NAME 'keyFingerprint'
DESC 'Fingerprint of private key' DESC 'Fingerprint of private key'
EQUALITY caseIgnoreMatch EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE SINGLE-VALUE
) )
attributetype ( attributetype (
@@ -96,7 +96,7 @@ attributetype (
NAME 'projectManager' NAME 'projectManager'
DESC 'Project Managers of a project' DESC 'Project Managers of a project'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
) )
objectClass ( objectClass (
novaOCs:1 novaOCs:1
@@ -120,7 +120,7 @@ objectClass (
novaOCs:3 novaOCs:3
NAME 'novaProject' NAME 'novaProject'
DESC 'Container for project' DESC 'Container for project'
SUP groupofnames SUP groupOfNames
STRUCTURAL STRUCTURAL
MUST ( cn $ projectManager ) MUST ( cn $ projectManager )
) )
Executable → Regular
+386 -178
View File
@@ -22,6 +22,7 @@ import datetime
import logging import logging
import os import os
import shutil import shutil
import string
import tempfile import tempfile
import uuid import uuid
import zipfile import zipfile
@@ -40,7 +41,6 @@ from nova import exception
from nova import flags from nova import flags
from nova import crypto from nova import crypto
from nova import utils from nova import utils
import access as simplerbac
from nova import objectstore # for flags 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('ldap_password', 'changeme', 'LDAP password')
flags.DEFINE_string('user_dn', 'cn=Manager,dc=example,dc=com', 'DN of admin user') 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('user_unit', 'Users', 'OID for Users')
flags.DEFINE_string('ldap_subtree', 'ou=Users,dc=example,dc=com', 'OU for Users') 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('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('credentials_template', flags.DEFINE_string('credentials_template',
utils.abspath('auth/novarc.template'), utils.abspath('auth/novarc.template'),
@@ -71,46 +63,113 @@ flags.DEFINE_string('credential_cert_file', 'cert.pem',
flags.DEFINE_string('credential_rc_file', 'novarc', flags.DEFINE_string('credential_rc_file', 'novarc',
'Filename of rc in credentials zip') 'Filename of rc in credentials zip')
_log = logging.getLogger('auth') class AuthBase(object):
_log.setLevel(logging.WARN) @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(AuthBase):
"""id and name are currently the same"""
class UserError(exception.ApiError):
pass
class InvalidKeyPair(exception.ApiError):
pass
class User(object):
def __init__(self, id, name, access, secret, admin): def __init__(self, id, name, access, secret, admin):
self.manager = UserManager.instance()
self.id = id self.id = id
self.name = name self.name = name
self.access = access self.access = access
self.secret = secret self.secret = secret
self.admin = admin self.admin = admin
self.keeper = datastore.Keeper(prefix="user")
def is_admin(self): def is_admin(self):
"""allows user to see objects from all projects"""
return self.admin return self.admin
def has_role(self, role_type): def is_project_member(self, project):
return self.manager.has_role(self.id, role_type) return UserManager.instance().is_project_member(self, project)
def is_authorized(self, owner_id, action=None): def is_project_manager(self, project):
if self.is_admin() or owner_id == self.id: return UserManager.instance().is_project_manager(self, project)
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): def generate_rc(self):
rc = self.generate_rc() rc = open(FLAGS.credentials_template).read()
private_key, signed_cert = self.generate_x509_cert() rc = rc % { 'access': self.access,
'secret': self.secret,
'ec2': FLAGS.ec2_url,
's3': 'http://%s:%s' % (FLAGS.s3_host, FLAGS.s3_port),
'nova': FLAGS.ca_file,
'cert': FLAGS.credential_cert_file,
'key': FLAGS.credential_key_file,
}
return rc
def generate_key_pair(self, name):
return UserManager.instance().generate_key_pair(self.id, name)
def create_key_pair(self, name, public_key, fingerprint):
return UserManager.instance().create_key_pair(self.id,
name,
public_key,
fingerprint)
def get_key_pair(self, name):
return UserManager.instance().get_key_pair(self.id, name)
def delete_key_pair(self, name):
return UserManager.instance().delete_key_pair(self.id, name)
def get_key_pairs(self):
return UserManager.instance().get_key_pairs(self.id)
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 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() tmpdir = tempfile.mkdtemp()
zf = os.path.join(tmpdir, "temp.zip") zf = os.path.join(tmpdir, "temp.zip")
@@ -126,50 +185,11 @@ class User(object):
shutil.rmtree(tmpdir) shutil.rmtree(tmpdir)
return buffer return buffer
def generate_x509_cert(self, user):
return UserManager.instance().generate_x509_cert(user, self)
def generate_rc(self): def __repr__(self):
rc = open(FLAGS.credentials_template).read() return "Project('%s', '%s', '%s', %s)" % (self.id, self.project_manager_id, self.description, self.member_ids)
rc = rc % { 'access': self.access,
'secret': self.secret,
'ec2': FLAGS.ec2_url,
's3': 'http://%s:%s' % (FLAGS.s3_host, FLAGS.s3_port),
'nova': FLAGS.ca_file,
'cert': FLAGS.credential_cert_file,
'key': FLAGS.credential_key_file,
}
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)
def create_key_pair(self, name, public_key, fingerprint):
return self.manager.create_key_pair(self.id,
name,
public_key,
fingerprint)
def get_key_pair(self, name):
return self.manager.get_key_pair(self.id, name)
def delete_key_pair(self, name):
return self.manager.delete_key_pair(self.id, name)
def get_key_pairs(self):
return self.manager.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
self.public_key = public_key
self.fingerprint = fingerprint
def delete(self):
return self.manager.delete_key_pair(self.owner, self.name)
class UserManager(object): class UserManager(object):
def __init__(self): def __init__(self):
@@ -193,31 +213,69 @@ class UserManager(object):
except: pass except: pass
return cls._instance 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 # 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) user = self.get_user_from_access_key(access_key)
if user == None: if user == None:
return None raise exception.NotFound('No user found for access key')
# hmac can't handle unicode, so encode ensures that secret isn't unicode if project_name is '':
expected_signature = signer.Signer(user.secret.encode()).generate(params, verb, server_string, path) project_name = user.name
_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): project = self.get_project(project_name)
# Map role to ldap group if project == None:
group = FLAGS.__getitem__("ldap_%s" % role) raise exception.NotFound('No project called %s could be found' % project_name)
with LDAPWrapper() as conn: if not user.is_admin() and not project.has_member(user):
return conn.is_member_of(user, group) 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 add_role(self, user, role, project=None): def create_project(self, name, manager_user, description, member_users=None):
# TODO: Project-specific roles if member_users:
group = FLAGS.__getitem__("ldap_%s" % role) member_users = [User.safe_id(u) for u in member_users]
with LDAPWrapper() as conn: with LDAPWrapper() as conn:
return conn.add_to_group(user, group) 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.find_project(Project.safe_id(project))
def add_to_project(self, user, project):
with LDAPWrapper() as conn:
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): def get_user(self, uid):
with LDAPWrapper() as conn: with LDAPWrapper() as conn:
@@ -231,56 +289,59 @@ class UserManager(object):
with LDAPWrapper() as conn: with LDAPWrapper() as conn:
return conn.find_users() 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 access == None: access = str(uuid.uuid4())
if secret == None: secret = str(uuid.uuid4()) if secret == None: secret = str(uuid.uuid4())
with LDAPWrapper() as conn: with LDAPWrapper() as conn:
u = conn.create_user(uid, access, secret, admin) user = User.safe_id(user)
return u 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: 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 # generating key pair is slow so delay generation
# until after check # until after check
user = User.safe_id(user)
with LDAPWrapper() as conn: with LDAPWrapper() as conn:
if not conn.user_exists(uid): if not conn.user_exists(user):
raise UserError("User " + uid + " doesn't exist") raise exception.NotFound("User %s doesn't exist" % user)
if conn.key_pair_exists(uid, key_name): if conn.key_pair_exists(user, key_name):
raise InvalidKeyPair("The keypair '" + raise exception.Duplicate("The keypair %s already exists" % key_name)
key_name +
"' already exists.",
"Duplicate")
private_key, public_key, fingerprint = crypto.generate_key_pair() 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 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: 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: 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: 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: with LDAPWrapper() as conn:
conn.delete_key_pair(uid, key_name) conn.delete_key_pair(User.safe_id(user), key_name)
def get_signed_zip(self, uid): def generate_x509_cert(self, user, project):
user = self.get_user(uid) (private_key, csr) = crypto.generate_x509_cert(self.__cert_subject(User.safe_id(user)))
return user.get_credentials()
def generate_x509_cert(self, uid):
(private_key, csr) = crypto.generate_x509_cert(self.__cert_subject(uid))
# TODO - This should be async call back to the cloud controller # 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) return (private_key, signed_cert)
def sign_cert(self, csr, uid): def sign_cert(self, csr, uid):
@@ -328,28 +389,53 @@ class LDAPWrapper(object):
return [x[1] for x in res] return [x[1] for x in res]
def find_users(self): 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] return [self.__to_user(attr) for attr in attrs]
def find_key_pairs(self, uid): def find_key_pairs(self, uid):
dn = 'uid=%s,%s' % (uid, FLAGS.ldap_subtree) attrs = self.find_objects(self.__uid_to_dn(uid), '(objectclass=novaKeyPair)')
attrs = self.find_objects(dn, '(objectclass=novaKeyPair)')
return [self.__to_key_pair(uid, attr) for attr in attrs] return [self.__to_key_pair(uid, attr) for attr in attrs]
def find_user(self, name): def find_projects(self):
dn = 'uid=%s,%s' % (name, FLAGS.ldap_subtree) attrs = self.find_objects(FLAGS.project_ldap_subtree, '(objectclass=novaProject)')
attr = self.find_object(dn, '(objectclass=novaUser)') return [self.__to_project(attr) for attr in attrs]
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) return self.__to_user(attr)
def find_key_pair(self, uid, key_name):
dn = 'cn=%s,%s' % (key_name,
self.__uid_to_dn(uid))
attr = self.find_object(dn, '(objectclass=novaKeyPair)')
return self.__to_key_pair(uid, attr)
def 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): def user_exists(self, name):
return self.find_user(name) != None return self.find_user(name) != None
def find_key_pair(self, uid, key_name): def key_pair_exists(self, uid, key_name):
dn = 'cn=%s,uid=%s,%s' % (key_name, return self.find_key_pair(uid, key_name) != None
uid,
FLAGS.ldap_subtree) def project_exists(self, name):
attr = self.find_object(dn, '(objectclass=novaKeyPair)') return self.find_project(name) != None
return self.__to_key_pair(uid, attr)
def group_exists(self, dn):
return self.find_group(dn) != None
def delete_key_pairs(self, uid): def delete_key_pairs(self, uid):
keys = self.find_key_pairs(uid) keys = self.find_key_pairs(uid)
@@ -357,12 +443,9 @@ class LDAPWrapper(object):
for key in keys: for key in keys:
self.delete_key_pair(uid, key.name) 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): def create_user(self, name, access_key, secret_key, is_admin):
if self.user_exists(name): if self.user_exists(name):
raise UserError("LDAP user " + name + " already exists") raise exception.Duplicate("LDAP user %s already exists" % name)
attr = [ attr = [
('objectclass', ['person', ('objectclass', ['person',
'organizationalPerson', 'organizationalPerson',
@@ -376,22 +459,115 @@ class LDAPWrapper(object):
('accessKey', [access_key]), ('accessKey', [access_key]),
('isAdmin', [str(is_admin).upper()]), ('isAdmin', [str(is_admin).upper()]),
] ]
self.conn.add_s('uid=%s,%s' % (name, FLAGS.ldap_subtree), self.conn.add_s(self.__uid_to_dn(name), attr)
attr)
return self.__to_user(dict(attr)) return self.__to_user(dict(attr))
def create_project(self, name, project_manager): def create_project(self, name, manager_uid, description, member_uids = None):
# PM can be user object or string containing DN if self.project_exists(name):
pass 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 is_member_of(self, name, group): def add_to_project(self, uid, project_id):
return True dn = 'cn=%s,%s' % (project_id, FLAGS.project_ldap_subtree)
return self.add_to_group(uid, dn)
def add_to_group(self, name, group): def remove_from_project(self, uid, project_id):
pass dn = 'cn=%s,%s' % (project_id, FLAGS.project_ldap_subtree)
return self.remove_from_group(uid, dn)
def remove_from_group(self, name, group): def is_in_project(self, uid, project_id):
pass 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 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): def create_key_pair(self, uid, key_name, public_key, fingerprint):
"""create's a public key in the directory underneath the user""" """create's a public key in the directory underneath the user"""
@@ -403,41 +579,46 @@ class LDAPWrapper(object):
('sshPublicKey', [public_key]), ('sshPublicKey', [public_key]),
('keyFingerprint', [fingerprint]), ('keyFingerprint', [fingerprint]),
] ]
self.conn.add_s('cn=%s,uid=%s,%s' % (key_name, self.conn.add_s('cn=%s,%s' % (key_name,
uid, self.__uid_to_dn(uid)),
FLAGS.ldap_subtree), attr)
attr)
return self.__to_key_pair(uid, dict(attr)) return self.__to_key_pair(uid, dict(attr))
def find_user_by_access_key(self, access): def find_user_by_access_key(self, access):
query = '(' + 'accessKey' + '=' + access + ')' query = '(accessKey=%s)' % access
dn = FLAGS.ldap_subtree dn = FLAGS.user_ldap_subtree
return self.__to_user(self.find_object(dn, query)) 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): def delete_key_pair(self, uid, key_name):
if not self.key_pair_exists(uid, key_name): if not self.key_pair_exists(uid, key_name):
raise UserError("Key Pair " + raise exception.NotFound("Key Pair %s doesn't exist for user %s" %
key_name + (key_name, uid))
" doesn't exist for user " +
uid)
self.conn.delete_s('cn=%s,uid=%s,%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): def delete_group(self, group_dn):
if not self.user_exists(name): if not self.group_exists(group_dn):
raise UserError("User " + raise exception.NotFound("Group at dn %s doesn't exist" % group_dn)
name + self.conn.delete_s(group_dn)
" doesn't exist")
self.delete_key_pairs(name) def delete_project(self, name):
self.conn.delete_s('uid=%s,%s' % (name, project_dn = 'cn=%s,%s' % (name, FLAGS.project_ldap_subtree)
FLAGS.ldap_subtree)) self.delete_group(project_dn)
def __to_user(self, attr): def __to_user(self, attr):
if attr == None: if attr == None:
return None return None
return User( return User(
id = attr['uid'][0], id = attr['uid'][0],
name = attr['uid'][0], name = attr['cn'][0],
access = attr['accessKey'][0], access = attr['accessKey'][0],
secret = attr['secretKey'][0], secret = attr['secretKey'][0],
admin = (attr['isAdmin'][0] == 'TRUE') admin = (attr['isAdmin'][0] == 'TRUE')
@@ -447,8 +628,35 @@ class LDAPWrapper(object):
if attr == None: if attr == None:
return None return None
return KeyPair( return KeyPair(
owner = owner, id = attr['cn'][0],
name = attr['cn'][0], owner_id = owner,
public_key = attr['sshPublicKey'][0], public_key = attr['sshPublicKey'][0],
fingerprint = attr['keyFingerprint'][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)
+25 -7
View File
@@ -1,10 +1,25 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # 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 signal import logging
import os import os
import nova.utils import signal
import subprocess import subprocess
from nova import utils
# todo(ja): does the definition of network_path belong here? # todo(ja): does the definition of network_path belong here?
from nova import flags from nova import flags
@@ -12,16 +27,16 @@ FLAGS=flags.FLAGS
def execute(cmd): def execute(cmd):
if FLAGS.fake_network: if FLAGS.fake_network:
print "FAKE NET: %s" % cmd logging.debug("FAKE NET: %s" % cmd)
return "fake", 0 return "fake", 0
else: else:
nova.utils.execute(cmd) utils.execute(cmd)
def runthis(desc, cmd): def runthis(desc, cmd):
if FLAGS.fake_network: if FLAGS.fake_network:
execute(cmd) execute(cmd)
else: else:
nova.utils.runthis(desc,cmd) utils.runthis(desc,cmd)
def Popen(cmd): def Popen(cmd):
if FLAGS.fake_network: if FLAGS.fake_network:
@@ -110,7 +125,7 @@ def start_dnsmasq(network):
os.kill(pid, signal.SIGHUP) os.kill(pid, signal.SIGHUP)
return return
except Exception, e: 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 # otherwise delete the existing leases file and start dnsmasq
lease_file = dhcp_file(network.vlan, 'leases') lease_file = dhcp_file(network.vlan, 'leases')
@@ -124,7 +139,10 @@ def stop_dnsmasq(network):
pid = dnsmasq_pid_for(network) pid = dnsmasq_pid_for(network)
if pid: 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): def dhcp_file(vlan, kind):
""" return path to a pid, leases or conf file for a vlan """ """ return path to a pid, leases or conf file for a vlan """
+34 -27
View File
@@ -1,4 +1,4 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4 expandtab # vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC] # Copyright [2010] [Anso Labs, LLC]
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,7 +24,7 @@ InstanceDirectory manager.
True True
>>> inst = InstDir['i-123'] >>> inst = InstDir['i-123']
>>> inst['ip'] = "192.168.0.3" >>> inst['ip'] = "192.168.0.3"
>>> inst['owner_id'] = "projectA" >>> inst['project_id'] = "projectA"
>>> inst.save() >>> inst.save()
True True
@@ -46,6 +46,8 @@ from nova import utils
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_string('instances_prefix', 'compute-',
'prefix for keepers for instances')
# TODO(ja): singleton instance of the directory # TODO(ja): singleton instance of the directory
class InstanceDirectory(object): class InstanceDirectory(object):
@@ -62,11 +64,12 @@ class InstanceDirectory(object):
def by_project(self, project): def by_project(self, project):
""" returns a list of instance objects for a 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) yield Instance(instance_id)
def by_node(self, node_id): def by_node(self, node_id):
""" returns a list of instances for a node """ """ returns a list of instances for a node """
for instance in self.all: for instance in self.all:
if instance['node_name'] == node_id: if instance['node_name'] == node_id:
yield instance yield instance
@@ -83,17 +86,13 @@ class InstanceDirectory(object):
pass pass
def exists(self, instance_id): def exists(self, instance_id):
if instance_id in self.keeper['instances']: return self.keeper.set_is_member('instances', instance_id)
return True
return False
@property @property
def all(self): def all(self):
""" returns a list of all instances """ """ returns a list of all instances """
instances = self.keeper['instances'] for instance_id in self.keeper.set_members('instances'):
if instances != None: yield Instance(instance_id)
for instance_id in self.keeper['instances']:
yield Instance(instance_id)
def new(self): def new(self):
""" returns an empty Instance object, with ID """ """ returns an empty Instance object, with ID """
@@ -114,10 +113,12 @@ class Instance(object):
if self.state: if self.state:
self.initial_state = self.state self.initial_state = self.state
else: else:
self.state = {'state' : 'pending', self.state = {'state': 'pending',
'instance_id' : instance_id, 'instance_id': instance_id,
'node_name' : 'unassigned', 'node_name': 'unassigned',
'owner_id' : 'unassigned' } 'project_id': 'unassigned',
'user_id': 'unassigned'
}
@property @property
def __redis_key(self): def __redis_key(self):
@@ -143,8 +144,8 @@ class Instance(object):
def save(self): def save(self):
""" update the directory with the state from this instance """ update the directory with the state from this instance
make sure you've set the owner_id before you call save make sure you've set the project_id and user_id before you call save
for the first time. for the first time.
""" """
# TODO(ja): implement hmset in redis-py and use it # TODO(ja): implement hmset in redis-py and use it
# instead of multiple calls to hset # instead of multiple calls to hset
@@ -157,17 +158,23 @@ class Instance(object):
state[key] = val state[key] = val
self.keeper[self.__redis_key] = state self.keeper[self.__redis_key] = state
if self.initial_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.instance_id)
self.keeper.set_add('instances', self.instance_id) self.keeper.set_add('instances', self.instance_id)
self.initial_state = self.state self.initial_state = self.state
return True 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): def destroy(self):
""" deletes all related records from datastore. """ 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) self.instance_id)
del self.keeper[self.__redis_key] del self.keeper[self.__redis_key]
self.keeper.set_remove('instances', self.instance_id) self.keeper.set_remove('instances', self.instance_id)
@@ -184,18 +191,18 @@ class Instance(object):
pass pass
# class Reservation(object): # class Reservation(object):
# """ ORM wrapper for a batch of launched instances """ # """ ORM wrapper for a batch of launched instances """
# def __init__(self): # def __init__(self):
# pass # pass
# #
# def userdata(self): # def userdata(self):
# """ """ # """ """
# pass # pass
# #
# #
# class NodeDirectory(object): # class NodeDirectory(object):
# def __init__(self): # def __init__(self):
# pass # pass
# #
if __name__ == "__main__": if __name__ == "__main__":
+46 -53
View File
@@ -26,7 +26,6 @@ from nova import vendor
import IPy import IPy
from nova import datastore from nova import datastore
import nova.exception
from nova.compute import exception from nova.compute import exception
from nova import flags from nova import flags
from nova import utils from nova import utils
@@ -113,13 +112,13 @@ class Network(object):
for idx in range(3, len(self.network)-2): for idx in range(3, len(self.network)-2):
yield self.network[idx] 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(): for ip in self.range():
address = str(ip) address = str(ip)
if not address in self.hosts.keys(): 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] = { 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) self.express(address=address)
return address return address
@@ -238,7 +237,6 @@ class DHCPNetwork(VirtNetwork):
else: else:
linux_net.start_dnsmasq(self) linux_net.start_dnsmasq(self)
class PrivateNetwork(DHCPNetwork): class PrivateNetwork(DHCPNetwork):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(PrivateNetwork, self).__init__(**kwargs) super(PrivateNetwork, self).__init__(**kwargs)
@@ -249,23 +247,18 @@ class PrivateNetwork(DHCPNetwork):
'network': self.network_str, 'network': self.network_str,
'hosts': self.hosts} 'hosts': self.hosts}
def express(self, *args, **kwargs):
super(PrivateNetwork, self).express(*args, **kwargs)
class PublicNetwork(Network): class PublicNetwork(Network):
def __init__(self, network="192.168.216.0/24", **kwargs): def __init__(self, network="192.168.216.0/24", **kwargs):
super(PublicNetwork, self).__init__(network=network, **kwargs) super(PublicNetwork, self).__init__(network=network, **kwargs)
self.express() self.express()
def allocate_ip(self, user_id, mac): def allocate_ip(self, user_id, project_id, mac):
for ip in self.range(): for ip in self.range():
address = str(ip) address = str(ip)
if not address in self.hosts.keys(): 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] = { 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) self.express(address=address)
return address return address
@@ -354,8 +347,8 @@ class VlanPool(object):
self.vlans = kwargs.get('vlans', {}) self.vlans = kwargs.get('vlans', {})
self.vlanpool = {} self.vlanpool = {}
self.manager = users.UserManager.instance() self.manager = users.UserManager.instance()
for user_id, vlan in self.vlans.iteritems(): for project_id, vlan in self.vlans.iteritems():
self.vlanpool[vlan] = user_id self.vlanpool[vlan] = project_id
def to_dict(self): def to_dict(self):
return {'vlans': self.vlans} return {'vlans': self.vlans}
@@ -380,25 +373,25 @@ class VlanPool(object):
parsed = json.loads(json_string) parsed = json.loads(json_string)
return cls.from_dict(parsed) return cls.from_dict(parsed)
def assign_vlan(self, user_id, vlan): def assign_vlan(self, project_id, vlan):
logging.debug("Assigning vlan %s to user %s" % (vlan, user_id)) logging.debug("Assigning vlan %s to project %s" % (vlan, project_id))
self.vlans[user_id] = vlan self.vlans[project_id] = vlan
self.vlanpool[vlan] = user_id self.vlanpool[vlan] = project_id
return self.vlans[user_id] return self.vlans[project_id]
def next(self, user_id): def next(self, project_id):
for old_user_id, vlan in self.vlans.iteritems(): for old_project_id, vlan in self.vlans.iteritems():
if not self.manager.get_user(old_user_id): if not self.manager.get_project(old_project_id):
_get_keeper()["%s-default" % old_user_id] = {} _get_keeper()["%s-default" % old_project_id] = {}
del _get_keeper()["%s-default" % old_user_id] del _get_keeper()["%s-default" % old_project_id]
del self.vlans[old_user_id] del self.vlans[old_project_id]
return self.assign_vlan(user_id, vlan) return self.assign_vlan(project_id, vlan)
vlans = self.vlanpool.keys() vlans = self.vlanpool.keys()
vlans.append(self.start) vlans.append(self.start)
nextvlan = max(vlans) + 1 nextvlan = max(vlans) + 1
if nextvlan == self.end: if nextvlan == self.end:
raise exception.AddressNotAllocated("Out of VLANs") raise exception.AddressNotAllocated("Out of VLANs")
return self.assign_vlan(user_id, nextvlan) return self.assign_vlan(project_id, nextvlan)
class NetworkController(object): class NetworkController(object):
@@ -442,37 +435,37 @@ class NetworkController(object):
if address_record.get(u'instance_id', 'free') == instance_id: if address_record.get(u'instance_id', 'free') == instance_id:
return address_record[u'address'] return address_record[u'address']
def get_users_network(self, user_id): def get_project_network(self, project_id):
""" get a user's private network, allocating one if needed """ """ get a project's private network, allocating one if needed """
user = self.manager.get_user(user_id) project = self.manager.get_project(project_id)
if not user: if not project:
raise Exception("User %s doesn't exist, uhoh." % user_id) raise Exception("Project %s doesn't exist, uhoh." % project_id)
usernet = self.get_network_from_name("%s-default" % user_id) project_net = self.get_network_from_name("%s-default" % project_id)
if not usernet: if not project_net:
pool = self.vlan_pool pool = self.vlan_pool
vlan = pool.next(user_id) vlan = pool.next(project_id)
private_pool = NetworkPool() private_pool = NetworkPool()
network_str = private_pool.get_from_vlan(vlan) network_str = private_pool.get_from_vlan(vlan)
logging.debug("Constructing network %s and %s for %s" % (network_str, vlan, user_id)) logging.debug("Constructing network %s and %s for %s" % (network_str, vlan, project_id))
usernet = PrivateNetwork( project_net = PrivateNetwork(
network=network_str, network=network_str,
vlan=vlan) 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() _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 ip = None
net_name = None net_name = None
if type == PrivateNetwork: if type == PrivateNetwork:
net = self.get_users_network(user_id) net = self.get_project_network(project_id)
ip = net.allocate_ip(user_id, mac) ip = net.allocate_ip(user_id, project_id, mac)
net_name = net.name net_name = net.name
_get_keeper()["%s-default" % user_id] = net.to_dict() _get_keeper()["%s-default" % project_id] = net.to_dict()
else: else:
net = self.public_net net = self.public_net
ip = net.allocate_ip(user_id, mac) ip = net.allocate_ip(user_id, project_id, mac)
net_name = net.name net_name = net.name
_get_keeper()['public'] = net.to_dict() _get_keeper()['public'] = net.to_dict()
return (ip, net_name) return (ip, net_name)
@@ -483,19 +476,19 @@ class NetworkController(object):
rv = net.deallocate_ip(str(address)) rv = net.deallocate_ip(str(address))
_get_keeper()['public'] = net.to_dict() _get_keeper()['public'] = net.to_dict()
return rv return rv
for user in self.manager.get_users(): for project in self.manager.get_projects():
if address in self.get_users_network(user.id).network: if address in self.get_project_network(project.id).network:
net = self.get_users_network(user.id) net = self.get_project_network(project.id)
rv = net.deallocate_ip(str(address)) 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 return rv
raise exception.AddressNotAllocated() raise exception.AddressNotAllocated()
def describe_addresses(self, type=PrivateNetwork): def describe_addresses(self, type=PrivateNetwork):
if type == PrivateNetwork: if type == PrivateNetwork:
addresses = [] addresses = []
for user in self.manager.get_users(): for project in self.manager.get_projects():
addresses.extend(self.get_users_network(user.id).list_addresses()) addresses.extend(self.get_project_network(project.id).list_addresses())
return addresses return addresses
return self.public_net.list_addresses() return self.public_net.list_addresses()
@@ -512,8 +505,8 @@ class NetworkController(object):
return rv return rv
def express(self,address=None): def express(self,address=None):
for user in self.manager.get_users(): for project in self.manager.get_projects():
self.get_users_network(user.id).express() self.get_project_network(project.id).express()
def report_state(self): def report_state(self):
pass pass
+7 -11
View File
@@ -57,8 +57,6 @@ flags.DEFINE_bool('use_s3', True,
'whether to get images from s3 or use local copy') 'whether to get images from s3 or use local copy')
flags.DEFINE_string('instances_path', utils.abspath('../instances'), flags.DEFINE_string('instances_path', utils.abspath('../instances'),
'where instances are stored on disk') 'where instances are stored on disk')
flags.DEFINE_string('instances_prefix', 'compute-',
'prefix for keepers for instances')
INSTANCE_TYPES = {} INSTANCE_TYPES = {}
INSTANCE_TYPES['m1.tiny'] = {'memory_mb': 512, 'vcpus': 1, 'local_gb': 0} 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 # be a singleton
PROCESS_POOL_SIZE = 4 PROCESS_POOL_SIZE = 4
class Node(object, service.Service): class Node(object, service.Service):
""" """
Manages the running instances. Manages the running instances.
@@ -240,7 +237,6 @@ def _create_image(data, libvirt_xml):
def image_url(path): def image_url(path):
return "%s:%s/_images/%s" % (FLAGS.s3_host, FLAGS.s3_port, path) return "%s:%s/_images/%s" % (FLAGS.s3_host, FLAGS.s3_port, path)
logging.info(basepath('disk')) logging.info(basepath('disk'))
try: try:
os.makedirs(data['basepath']) os.makedirs(data['basepath'])
@@ -309,15 +305,17 @@ class Instance(object):
return (self.state == Instance.RUNNING or self.state == 'running') return (self.state == Instance.RUNNING or self.state == 'running')
def __init__(self, conn, pool, name, data): 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 """ """ spawn an instance with a given name """
# TODO(termie): pool should probably be a singleton instead of being passed # TODO(termie): pool should probably be a singleton instead of being passed
# here and in the classmethods # here and in the classmethods
self._pool = pool self._pool = pool
self._conn = conn 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 self.datamodel = data
print data
# NOTE(termie): to be passed to multiprocess self._s must be # NOTE(termie): to be passed to multiprocess self._s must be
# pickle-able by cPickle # pickle-able by cPickle
@@ -344,7 +342,8 @@ class Instance(object):
self._s['image_id'] = data.get('image_id', FLAGS.default_image) self._s['image_id'] = data.get('image_id', FLAGS.default_image)
self._s['kernel_id'] = data.get('kernel_id', FLAGS.default_kernel) self._s['kernel_id'] = data.get('kernel_id', FLAGS.default_kernel)
self._s['ramdisk_id'] = data.get('ramdisk_id', FLAGS.default_ramdisk) 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['node_name'] = data.get('node_name', '')
self._s['user_data'] = data.get('user_data', '') self._s['user_data'] = data.get('user_data', '')
self._s['ami_launch_index'] = data.get('ami_launch_index', None) 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['reservation_id'] = data.get('reservation_id', None)
# self._s['state'] = Instance.NOSTATE # self._s['state'] = Instance.NOSTATE
self._s['state'] = data.get('state', Instance.NOSTATE) self._s['state'] = data.get('state', Instance.NOSTATE)
self._s['key_data'] = data.get('key_data', None) self._s['key_data'] = data.get('key_data', None)
# TODO: we may not need to save the next few # TODO: we may not need to save the next few
@@ -415,7 +413,6 @@ class Instance(object):
def update_state(self): def update_state(self):
info = self.info() info = self.info()
self._s['state'] = info['state']
self.datamodel['state'] = info['state'] self.datamodel['state'] = info['state']
self.datamodel['node_name'] = FLAGS.node_name self.datamodel['node_name'] = FLAGS.node_name
self.datamodel.save() self.datamodel.save()
@@ -427,7 +424,6 @@ class Instance(object):
raise exception.Error('trying to destroy already destroyed' raise exception.Error('trying to destroy already destroyed'
' instance: %s' % self.name) ' instance: %s' % self.name)
self._s['state'] = Instance.SHUTDOWN
self.datamodel['state'] = 'shutting_down' self.datamodel['state'] = 'shutting_down'
self.datamodel.save() self.datamodel.save()
try: try:
+10 -13
View File
@@ -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_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?') flags.DEFINE_boolean('use_intermediate_ca', False, 'Should we use intermediate CAs for each project?')
def ca_path(project_id):
def ca_path(username): if project_id:
if username: return "%s/INTER/%s/cacert.pem" % (FLAGS.ca_path, project_id)
return "%s/INTER/%s/cacert.pem" % (FLAGS.ca_path, username)
return "%s/cacert.pem" % (FLAGS.ca_path) 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: if not FLAGS.use_intermediate_ca:
username = None project_id = None
buffer = "" buffer = ""
if username: if project_id:
with open(ca_path(username),"r") as cafile: with open(ca_path(project_id),"r") as cafile:
buffer += cafile.read() buffer += cafile.read()
if username and not chain: if not chain:
return buffer return buffer
with open(ca_path(None),"r") as cafile: with open(ca_path(None),"r") as cafile:
buffer += cafile.read() buffer += cafile.read()
return buffer return buffer
@@ -104,7 +103,6 @@ def generate_x509_cert(subject="/C=US/ST=California/L=The Mission/O=CloudFed/OU=
shutil.rmtree(tmpdir) shutil.rmtree(tmpdir)
return (private_key, csr) return (private_key, csr)
def sign_csr(csr_text, intermediate=None): def sign_csr(csr_text, intermediate=None):
if not FLAGS.use_intermediate_ca: if not FLAGS.use_intermediate_ca:
intermediate = None intermediate = None
@@ -118,7 +116,6 @@ def sign_csr(csr_text, intermediate=None):
os.chdir(start) os.chdir(start)
return _sign_csr(csr_text, user_ca) return _sign_csr(csr_text, user_ca)
def _sign_csr(csr_text, ca_folder): def _sign_csr(csr_text, ca_folder):
tmpfolder = tempfile.mkdtemp() tmpfolder = tempfile.mkdtemp()
csrfile = open("%s/inbound.csr" % (tmpfolder), "w") 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 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT # 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, # 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 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE. # IN THE SOFTWARE.
+10
View File
@@ -264,6 +264,12 @@ class SqliteKeeper(object):
group.remove(value) group.remove(value)
self[item] = group self[item] = group
def set_members(self, item):
group = self[item]
if not group:
group = []
return group
def set_fetch(self, item): def set_fetch(self, item):
# TODO(termie): I don't really know what set_fetch is supposed to do # TODO(termie): I don't really know what set_fetch is supposed to do
group = self[item] group = self[item]
@@ -354,6 +360,10 @@ class RedisKeeper(object):
item = slugify(item, self.prefix) item = slugify(item, self.prefix)
return Redis.instance().srem(item, json.dumps(value)) 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): def set_fetch(self, item):
item = slugify(item, self.prefix) item = slugify(item, self.prefix)
for obj in Redis.instance().sinter([item]): for obj in Redis.instance().sinter([item]):
+14 -15
View File
@@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC] # Copyright [2010] [Anso Labs, LLC]
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 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. API Controller for users, node status, and worker mgmt.
Trivial admin_only wrapper will be replaced with RBAC, Trivial admin_only wrapper will be replaced with RBAC,
allowing project managers to administer project users. allowing project managers to administer project users.
""" """
def __init__(self, user_manager, node_manager=None): def __init__(self, user_manager, node_manager=None):
self.user_manager = user_manager self.user_manager = user_manager
@@ -80,16 +79,14 @@ class AdminController(object):
def describe_users(self, _context, **_kwargs): def describe_users(self, _context, **_kwargs):
"""Returns all users - should be changed to deal with a list. """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()] } [user_dict(u) for u in self.user_manager.get_users()] }
@admin_only @admin_only
def register_user(self, _context, name, **_kwargs): def register_user(self, _context, name, **_kwargs):
""" Creates a new user, and returns generated credentials. """ Creates a new user, and returns generated credentials.
""" """
self.user_manager.create_user(name) return user_dict(self.user_manager.create_user(name))
return user_dict(self.user_manager.get_user(name))
@admin_only @admin_only
def deregister_user(self, _context, name, **_kwargs): def deregister_user(self, _context, name, **_kwargs):
@@ -102,14 +99,17 @@ class AdminController(object):
return True return True
@admin_only @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. """Generates and returns an x509 certificate for a single user.
Is usually called from a client that will wrap this with Is usually called from a client that will wrap this with
access and secret key info, and return a zip file. 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) 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 @admin_only
def describe_nodes(self, _context, **_kwargs): def describe_nodes(self, _context, **_kwargs):
"""Returns status info for all nodes. Includes: """Returns status info for all nodes. Includes:
@@ -120,12 +120,11 @@ class AdminController(object):
* DHCP servers running * DHCP servers running
* Iptables / bridges * Iptables / bridges
""" """
return {'nodeSet': return {'nodeSet':
[node_dict(n) for n in self.node_manager.get_nodes()] } [node_dict(n) for n in self.node_manager.get_nodes()] }
@admin_only @admin_only
def describe_node(self, _context, name, **_kwargs): def describe_node(self, _context, name, **_kwargs):
"""Returns status info for single node. """Returns status info for single node.
""" """
return node_dict(self.node_manager.get_node(name)) return node_dict(self.node_manager.get_node(name))
+22 -21
View File
@@ -1,13 +1,13 @@
#!/usr/bin/python #!/usr/bin/python
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC] # Copyright [2010] [Anso Labs, LLC]
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -41,7 +41,6 @@ from nova.auth import users
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_integer('cc_port', 8773, 'cloud controller port') flags.DEFINE_integer('cc_port', 8773, 'cloud controller port')
_log = logging.getLogger("api") _log = logging.getLogger("api")
_log.setLevel(logging.DEBUG) _log.setLevel(logging.DEBUG)
@@ -63,9 +62,10 @@ def _underscore_to_xmlcase(str):
class APIRequestContext(object): class APIRequestContext(object):
def __init__(self, handler, user): def __init__(self, handler, user, project):
self.handler = handler self.handler = handler
self.user = user self.user = user
self.project = project
self.request_id = ''.join( self.request_id = ''.join(
[random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-') [random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-')
for x in xrange(20)] for x in xrange(20)]
@@ -73,13 +73,11 @@ class APIRequestContext(object):
class APIRequest(object): class APIRequest(object):
def __init__(self, handler, controller, action): def __init__(self, controller, action):
self.handler = handler
self.controller = controller self.controller = controller
self.action = action self.action = action
def send(self, user, **kwargs): def send(self, context, **kwargs):
context = APIRequestContext(self.handler, user)
try: try:
method = getattr(self.controller, method = getattr(self.controller,
@@ -227,7 +225,6 @@ class MetadataRequestHandler(tornado.web.RequestHandler):
self.print_data(data) self.print_data(data)
self.finish() self.finish()
class APIRequestHandler(tornado.web.RequestHandler): class APIRequestHandler(tornado.web.RequestHandler):
def get(self, controller_name): def get(self, controller_name):
self.execute(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. # Get requested action and remove authentication args for final request.
try: try:
action = args.pop('Action')[0] action = args.pop('Action')[0]
args.pop('AWSAccessKeyId') access = args.pop('AWSAccessKeyId')[0]
args.pop('SignatureMethod') args.pop('SignatureMethod')
args.pop('SignatureVersion') args.pop('SignatureVersion')
args.pop('Version') args.pop('Version')
@@ -266,15 +263,18 @@ class APIRequestHandler(tornado.web.RequestHandler):
raise tornado.web.HTTPError(400) raise tornado.web.HTTPError(400)
# Authenticate the request. # Authenticate the request.
user = self.application.user_manager.authenticate( try:
auth_params, (user, project) = users.UserManager.instance().authenticate(
signature, access,
self.request.method, signature,
self.request.host, auth_params,
self.request.path self.request.method,
) self.request.host,
self.request.path
)
if not user: except exception.Error, ex:
logging.debug("Authentication Failure: %s" % ex)
raise tornado.web.HTTPError(403) raise tornado.web.HTTPError(403)
_log.debug('action: %s' % action) _log.debug('action: %s' % action)
@@ -282,8 +282,9 @@ class APIRequestHandler(tornado.web.RequestHandler):
for key, value in args.items(): for key, value in args.items():
_log.debug('arg: %s\t\tval: %s' % (key, value)) _log.debug('arg: %s\t\tval: %s' % (key, value))
request = APIRequest(self, controller, action) request = APIRequest(controller, action)
d = request.send(user, **args) context = APIRequestContext(self, user, project)
d = request.send(context, **args)
# d.addCallback(utils.debug) # d.addCallback(utils.debug)
# TODO: Wrap response in AWS XML format # TODO: Wrap response in AWS XML format
+115 -104
View File
@@ -19,6 +19,7 @@ dispatched to other nodes via AMQP RPC. State is via distributed
datastore. datastore.
""" """
import base64
import json import json
import logging import logging
import os import os
@@ -58,7 +59,6 @@ class CloudController(object):
sent to the other nodes. sent to the other nodes.
""" """
def __init__(self): def __init__(self):
self._instances = datastore.Keeper(FLAGS.instances_prefix)
self.instdir = model.InstanceDirectory() self.instdir = model.InstanceDirectory()
self.network = network.NetworkController() self.network = network.NetworkController()
self.setup() self.setup()
@@ -97,7 +97,7 @@ class CloudController(object):
return self.instdir.by_ip(ip) return self.instdir.by_ip(ip)
def get_metadata(self, ip): def get_metadata(self, ip):
i = self.instdir.by_ip(ip) i = self.get_instance_by_ip(ip)
if i is None: if i is None:
return None return None
if i['key_name']: if i['key_name']:
@@ -145,7 +145,6 @@ class CloudController(object):
data['product-codes'] = i['product_codes'] data['product-codes'] = i['product_codes']
return data return data
def describe_availability_zones(self, context, **kwargs): def describe_availability_zones(self, context, **kwargs):
return {'availabilityZoneInfo': [{'zoneName': 'nova', return {'availabilityZoneInfo': [{'zoneName': 'nova',
'zoneState': 'available'}]} 'zoneState': 'available'}]}
@@ -207,11 +206,9 @@ class CloudController(object):
def get_console_output(self, context, instance_id, **kwargs): def get_console_output(self, context, instance_id, **kwargs):
# instance_id is passed in as a list of instances # 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': if instance['state'] == 'pending':
raise exception.ApiError('Cannot get output for pending instance') 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']), return rpc.call('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
{"method": "get_console_output", {"method": "get_console_output",
"args" : {"instance_id": instance_id[0]}}) "args" : {"instance_id": instance_id[0]}})
@@ -225,7 +222,7 @@ class CloudController(object):
def describe_volumes(self, context, **kwargs): def describe_volumes(self, context, **kwargs):
volumes = [] volumes = []
for volume in self.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) v = self.format_volume(context, volume)
volumes.append(v) volumes.append(v)
return defer.succeed({'volumeSet': volumes}) return defer.succeed({'volumeSet': volumes})
@@ -252,36 +249,59 @@ class CloudController(object):
"args" : {"size": size, "args" : {"size": size,
"user_id": context.user.id}}) "user_id": context.user.id}})
def _format_result(result): def _format_result(result):
volume = self._get_volume(result['result']) volume = self._get_volume(context, result['result'])
return {'volumeSet': [self.format_volume(context, volume)]} return {'volumeSet': [self.format_volume(context, volume)]}
res.addCallback(_format_result) res.addCallback(_format_result)
return res return res
def _get_by_id(self, nodes, id): def _convert_address(self, network_address):
if nodes == {}: # FIXME(vish): this should go away when network.py stores info properly
raise exception.NotFound("%s not found" % id) address = {}
for node_name, node in nodes.iteritems(): address['public_ip'] == network_address[u'address']
if node.has_key(id): address['user_id'] == network_address[u'user_id']
return node_name, node[id] address['project_id'] == network_address.get(u'project_id', address['user_id'])
raise exception.NotFound("%s not found" % id) address['instance_id'] == network_address.get(u'instance_id', None)
return address
def _get_volume(self, volume_id): 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: for volume in self.volumes:
if volume['volume_id'] == volume_id: 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): 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'] storage_node = volume['node_name']
# TODO: (joshua) Fix volumes to store creator id # TODO: (joshua) Fix volumes to store creator id
if not context.user.is_authorized(volume.get('user_id', None)): instance = self._get_instance(context, instance_id)
raise exception.ApiError("%s not authorized for %s" %
(context.user.id, volume_id))
instance = self.instdir.get(instance_id)
compute_node = instance['node_name'] 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'] aoe_device = volume['aoe_device']
# Needs to get right node controller for attaching to # Needs to get right node controller for attaching to
# TODO: Maybe have another exchange that goes to everyone? # TODO: Maybe have another exchange that goes to everyone?
@@ -297,24 +317,17 @@ class CloudController(object):
"mountpoint" : device}}) "mountpoint" : device}})
return defer.succeed(True) return defer.succeed(True)
def detach_volume(self, context, volume_id, **kwargs): def detach_volume(self, context, volume_id, **kwargs):
# TODO(joshua): Make sure the updated state has been received first # 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'] 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(): if 'instance_id' in volume.keys():
instance_id = volume['instance_id'] instance_id = volume['instance_id']
try: try:
instance = self.instdir.get(instance_id) instance = self._get_instance(context, instance_id)
compute_node = instance['node_name'] compute_node = instance['node_name']
mountpoint = volume['mountpoint'] 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), rpc.cast('%s.%s' % (FLAGS.compute_topic, compute_node),
{"method": "detach_volume", {"method": "detach_volume",
"args" : {"instance_id": instance_id, "args" : {"instance_id": instance_id,
@@ -332,16 +345,16 @@ class CloudController(object):
return [{str: x} for x in lst] return [{str: x} for x in lst]
def describe_instances(self, context, **kwargs): 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 == {}: if self.instances == {}:
return {'reservationSet': []} return {'reservationSet': []}
reservations = {} reservations = {}
for inst in self.instances: for inst in self.instances:
instance = inst.values()[0] instance = inst.values()[0]
res_id = instance.get('reservation_id', 'Unknown') 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)): and (reservation_id == None or reservation_id == res_id)):
i = {} i = {}
i['instance_id'] = instance.get('instance_id', None) i['instance_id'] = instance.get('instance_id', None)
@@ -357,7 +370,7 @@ class CloudController(object):
i['public_dns_name'] = i['private_dns_name'] i['public_dns_name'] = i['private_dns_name']
i['dns_name'] = instance.get('dns_name', None) i['dns_name'] = instance.get('dns_name', None)
i['key_name'] = instance.get('key_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'], i['key_name'] = '%s (%s, %s)' % (i['key_name'],
instance.get('owner_id', None), instance.get('node_name','')) instance.get('owner_id', None), instance.get('node_name',''))
i['product_codes_set'] = self._convert_to_set( i['product_codes_set'] = self._convert_to_set(
@@ -369,7 +382,7 @@ class CloudController(object):
if not reservations.has_key(res_id): if not reservations.has_key(res_id):
r = {} r = {}
r['reservation_id'] = res_id 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( r['group_set'] = self._convert_to_set(
instance.get('groups', None), 'group_id') instance.get('groups', None), 'group_id')
r['instances_set'] = [] r['instances_set'] = []
@@ -382,52 +395,52 @@ class CloudController(object):
def describe_addresses(self, context, **kwargs): def describe_addresses(self, context, **kwargs):
return self.format_addresses(context.user) return self.format_addresses(context.user)
def format_addresses(self, user): def format_addresses(self, context):
addresses = [] addresses = []
# TODO(vish): move authorization checking into network.py # TODO(vish): move authorization checking into network.py
for address_record in self.network.describe_addresses( for network_address in self.network.describe_addresses(type=network.PublicNetwork):
type=network.PublicNetwork):
#logging.debug(address_record) #logging.debug(address_record)
if user.is_authorized(address_record[u'user_id']): address = self._convert_address(network_address)
address = { address_rv = {
'public_ip': address_record[u'address'], 'public_ip': address['public_ip'],
'instance_id' : address_record.get(u'instance_id', 'free') 'instance_id' : address.get('instance_id', 'free')
} }
# FIXME: add another field for user id # FIXME: add another field for user id
if user.is_admin(): if context.user.is_admin():
address['instance_id'] = "%s (%s)" % ( address_rv['instance_id'] = "%s (%s, %s)" % (
address['instance_id'], address['instance_id'],
address_record[u'user_id'], address['user_id'],
) address['project_id'],
addresses.append(address) )
addresses.append(address_rv)
# logging.debug(addresses) # logging.debug(addresses)
return {'addressesSet': addresses} return {'addressesSet': addresses}
def allocate_address(self, context, **kwargs): def allocate_address(self, context, **kwargs):
# TODO: Verify user is valid?
kwargs['owner_id'] = context.user.id
(address,network_name) = self.network.allocate_address( (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}]}) return defer.succeed({'addressSet': [{'publicIp' : address}]})
def release_address(self, context, **kwargs): def release_address(self, context, public_ip, **kwargs):
self.network.deallocate_address(kwargs.get('public_ip', None)) address = self._get_address(public_ip)
return defer.succeed({'releaseResponse': ["Address released."]}) return defer.succeed({'releaseResponse': ["Address released."]})
def associate_address(self, context, instance_id, **kwargs): 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( rv = self.network.associate_address(
kwargs['public_ip'], kwargs['public_ip'],
instance['private_dns_name'], instance['private_dns_name'],
instance_id) instance_id)
return defer.succeed({'associateResponse': ["Address associated."]}) return defer.succeed({'associateResponse': ["Address associated."]})
def disassociate_address(self, context, **kwargs): def disassociate_address(self, context, public_ip, **kwargs):
rv = self.network.disassociate_address(kwargs['public_ip']) address = self._get_address(public_ip)
rv = self.network.disassociate_address(public_ip)
# TODO - Strip the IP from the instance # TODO - Strip the IP from the instance
return rv return defer.succeed({'disassociateResponse': ["Address disassociated."]})
def run_instances(self, context, **kwargs): def run_instances(self, context, **kwargs):
image = self._get_image(context, kwargs['image_id'])
logging.debug("Going to run instances...") logging.debug("Going to run instances...")
reservation_id = utils.generate_uid('r') reservation_id = utils.generate_uid('r')
launch_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) 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['launch_time'] = launch_time
inst['key_data'] = key_data or '' inst['key_data'] = key_data or ''
inst['key_name'] = kwargs.get('key_name', '') 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['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = num inst['ami_launch_index'] = num
address, _netname = self.network.allocate_address( 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)) network = self.network.get_users_network(str(context.user.id))
inst['network_str'] = json.dumps(network.to_dict()) inst['network_str'] = json.dumps(network.to_dict())
inst['bridge_name'] = network.bridge_name 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" % logging.debug("Casting to node for %s's instance with IP of %s" %
(context.user.name, inst['private_dns_name'])) (context.user.name, inst['private_dns_name']))
# TODO: Make the NetworkComputeNode figure out the network name from ip. # 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)) context.user, reservation_id))
def terminate_instances(self, context, instance_id, **kwargs): def terminate_instances(self, context, instance_id, **kwargs):
logging.debug("Going to start terminating instances") logging.debug("Going to start terminating instances")
# TODO: return error if not authorized
for i in instance_id: for i in instance_id:
logging.debug("Going to try and terminate %s" % i) logging.debug("Going to try and terminate %s" % i)
instance = self.instdir.get(i) try:
#if instance['state'] == 'pending': instance = self._get_instance(context, i)
# raise exception.ApiError('Cannot terminate pending instance') except exception.NotFound:
if context.user.is_authorized(instance.get('owner_id', None)): 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: try:
self.network.disassociate_address( self.network.deallocate_address(instance.get('private_dns_name', None))
instance.get('public_dns_name', 'bork')) except Exception, _err:
except:
pass pass
if instance.get('private_dns_name', None): if instance.get('node_name', 'unassigned') != 'unassigned': #It's also internal default
logging.debug("Deallocating address %s" % instance.get('private_dns_name', None)) rpc.cast('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
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']),
{"method": "terminate_instance", {"method": "terminate_instance",
"args" : {"instance_id": i}}) "args" : {"instance_id": i}})
else: else:
instance.destroy() instance.destroy()
return defer.succeed(True) return defer.succeed(True)
def reboot_instances(self, context, instance_id, **kwargs): 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: for i in instance_id:
instance = self.instdir.get(i) instance = self._get_instance(context, i)
if instance['state'] == 'pending': if instance['state'] == 'pending':
raise exception.ApiError('Cannot reboot pending instance') 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", {"method": "reboot_instance",
"args" : {"instance_id": i}}) "args" : {"instance_id": i}})
return defer.succeed(True) return defer.succeed(True)
def delete_volume(self, context, volume_id, **kwargs): def delete_volume(self, context, volume_id, **kwargs):
# TODO: return error if not authorized # TODO: return error if not authorized
volume = self._get_volume(volume_id) volume = self._get_volume(context, volume_id)
storage_node = volume['node_name'] storage_node = volume['node_name']
if context.user.is_authorized(volume.get('user_id', None)): rpc.cast('%s.%s' % (FLAGS.storage_topic, storage_node),
rpc.cast('%s.%s' % (FLAGS.storage_topic, storage_node), {"method": "delete_volume",
{"method": "delete_volume", "args" : {"volume_id": volume_id}})
"args" : {"volume_id": volume_id}})
return defer.succeed(True) return defer.succeed(True)
def describe_images(self, context, image_id=None, **kwargs): def describe_images(self, context, image_id=None, **kwargs):
imageSet = images.list(context.user) # The objectstore does its own authorization for describe
if not image_id is None: imageSet = images.list(context, image_id)
imageSet = [i for i in imageSet if i['imageId'] in image_id]
return defer.succeed({'imagesSet': imageSet}) return defer.succeed({'imagesSet': imageSet})
def deregister_image(self, context, image_id, **kwargs): 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}) return defer.succeed({'imageId': image_id})
def register_image(self, context, image_location=None, **kwargs): 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'): if image_location is None and kwargs.has_key('name'):
image_location = kwargs['name'] image_location = kwargs['name']
image_id = images.register(context, image_location)
image_id = images.register(context.user, image_location)
logging.debug("Registered %s as %s" % (image_location, image_id)) logging.debug("Registered %s as %s" % (image_location, image_id))
return defer.succeed({'imageId': image_id}) return defer.succeed({'imageId': image_id})
def modify_image_attribute(self, context, image_id, def modify_image_attribute(self, context, image_id, attribute, operation_type, **kwargs):
attribute, operation_type, **kwargs):
if attribute != 'launchPermission': if attribute != 'launchPermission':
raise exception.ApiError('only launchPermission is supported') raise exception.ApiError('only launchPermission is supported')
if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all': if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all':
+19 -20
View File
@@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC] # Copyright [2010] [Anso Labs, LLC]
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,7 +14,7 @@
# limitations under the License. # 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. objectstore daemon.
""" """
@@ -31,9 +31,8 @@ from nova import utils
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
def modify(context, image_id, operation):
def modify(user, image_id, operation): conn(context).make_request(
conn(user).make_request(
method='POST', method='POST',
bucket='_images', bucket='_images',
query_args=qs({'image_id': image_id, 'operation': operation})) query_args=qs({'image_id': image_id, 'operation': operation}))
@@ -41,11 +40,11 @@ def modify(user, image_id, operation):
return True return True
def register(user, image_location): def register(context, image_location):
""" rpc call to register a new image based from a manifest """ """ rpc call to register a new image based from a manifest """
image_id = utils.generate_uid('ami') image_id = utils.generate_uid('ami')
conn(user).make_request( conn(context).make_request(
method='PUT', method='PUT',
bucket='_images', bucket='_images',
query_args=qs({'image_location': image_location, query_args=qs({'image_location': image_location,
@@ -53,32 +52,32 @@ def register(user, image_location):
return image_id return image_id
def list(context, filter_list=[]):
def list(user, filter_list=[]):
""" return a list of all images that a user can see """ return a list of all images that a user can see
optionally filtered by a list of image_id """ optionally filtered by a list of image_id """
# FIXME: send along the list of only_images to check for # FIXME: send along the list of only_images to check for
response = conn(user).make_request( response = conn(context).make_request(
method='GET', method='GET',
bucket='_images') 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(context, image_id):
def deregister(user, image_id):
""" unregister an image """ """ unregister an image """
conn(user).make_request( conn(context).make_request(
method='DELETE', method='DELETE',
bucket='_images', bucket='_images',
query_args=qs({'image_id': image_id})) query_args=qs({'image_id': image_id}))
def conn(context):
def conn(user):
return boto.s3.connection.S3Connection ( return boto.s3.connection.S3Connection (
aws_access_key_id=user.access, aws_access_key_id='%s:%s' % (context.user.access, context.project.name),
aws_secret_access_key=user.secret, aws_secret_access_key=context.user.secret,
is_secure=False, is_secure=False,
calling_format=boto.s3.connection.OrdinaryCallingFormat(), calling_format=boto.s3.connection.OrdinaryCallingFormat(),
port=FLAGS.s3_port, port=FLAGS.s3_port,
+14 -9
View File
@@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC] # Copyright [2010] [Anso Labs, LLC]
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,7 +14,7 @@
# limitations under the License. # 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. Nova-type exceptions. SHOULD include dedicated exception logging.
""" """
@@ -23,16 +23,21 @@ import traceback
import sys import sys
class Error(Exception): 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'): def __init__(self, message='Unknown', code='Unknown'):
self.message = message self.message = message
self.code = code self.code = code
super(ApiError, self).__init__('%s: %s'% (code, message))
class NotFound(Error): class NotFound(Error):
pass pass
class Duplicate(Error):
pass
class NotAuthorized(Error): class NotAuthorized(Error):
pass pass
@@ -42,12 +47,12 @@ def wrap_exception(f):
return f(*args, **kw) return f(*args, **kw)
except Exception, e: except Exception, e:
if not isinstance(e, Error): 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.exception('Uncaught exception')
# logging.debug(traceback.extract_stack(exc_traceback)) # logging.debug(traceback.extract_stack(exc_traceback))
raise Error(str(e)) raise Error(str(e))
raise raise
_wrap.func_name = f.func_name _wrap.func_name = f.func_name
return _wrap return _wrap
+9 -9
View File
@@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC] # Copyright [2010] [Anso Labs, LLC]
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 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]: for f in self._routes[routing_key]:
logging.debug('Publishing to route %s', f) logging.debug('Publishing to route %s', f)
f(message, routing_key=routing_key) f(message, routing_key=routing_key)
def bind(self, callback, routing_key): def bind(self, callback, routing_key):
self._routes.setdefault(routing_key, []) self._routes.setdefault(routing_key, [])
self._routes[routing_key].append(callback) self._routes[routing_key].append(callback)
@@ -52,7 +52,7 @@ class Queue(object):
def __repr__(self): def __repr__(self):
return '<Queue: %s>' % self.name return '<Queue: %s>' % self.name
def push(self, message, routing_key=None): def push(self, message, routing_key=None):
self._queue.put(message) self._queue.put(message)
@@ -70,7 +70,7 @@ class Backend(object):
#super(__impl, self).__init__(*args, **kwargs) #super(__impl, self).__init__(*args, **kwargs)
self._exchanges = {} self._exchanges = {}
self._queues = {} self._queues = {}
def _reset_all(self): def _reset_all(self):
self._exchanges = {} self._exchanges = {}
self._queues = {} self._queues = {}
@@ -78,7 +78,7 @@ class Backend(object):
def queue_declare(self, queue, **kwargs): def queue_declare(self, queue, **kwargs):
if queue not in self._queues: if queue not in self._queues:
logging.debug('Declaring queue %s', queue) logging.debug('Declaring queue %s', queue)
self._queues[queue] = Queue(queue) self._queues[queue] = Queue(queue)
def exchange_declare(self, exchange, type, *args, **kwargs): def exchange_declare(self, exchange, type, *args, **kwargs):
if exchange not in self._exchanges: if exchange not in self._exchanges:
@@ -92,7 +92,7 @@ class Backend(object):
routing_key) routing_key)
def get(self, queue, no_ack=False): 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 return None
(message_data, content_type, content_encoding) = \ (message_data, content_type, content_encoding) = \
self._queues[queue].pop() self._queues[queue].pop()
@@ -122,7 +122,7 @@ class Backend(object):
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.__instance, attr) return getattr(self.__instance, attr)
def __setattr__(self, attr, value): def __setattr__(self, attr, value):
return setattr(self.__instance, attr, value) return setattr(self.__instance, attr, value)
+9 -11
View File
@@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC] # Copyright [2010] [Anso Labs, LLC]
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 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 import utils
from nova.objectstore import stored from nova.objectstore import stored
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_string('buckets_path', utils.abspath('../buckets'), flags.DEFINE_string('buckets_path', utils.abspath('../buckets'),
'path to s3 buckets') 'path to s3 buckets')
class Bucket(object): class Bucket(object):
def __init__(self, name): def __init__(self, name):
self.name = name self.name = name
@@ -62,11 +60,11 @@ class Bucket(object):
return buckets return buckets
@staticmethod @staticmethod
def create(bucket_name, user): def create(bucket_name, context):
"""Create a new bucket owned by a user. """Create a new bucket owned by a project.
@bucket_name: a string representing the name of the bucket to create @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: Raises:
NotAuthorized: if the bucket is already exists or has invalid name NotAuthorized: if the bucket is already exists or has invalid name
@@ -80,7 +78,7 @@ class Bucket(object):
os.makedirs(path) os.makedirs(path)
with open(path+'.json', 'w') as f: with open(path+'.json', 'w') as f:
json.dump({'ownerId': user.id}, f) json.dump({'ownerId': context.project.id}, f)
@property @property
def metadata(self): def metadata(self):
@@ -101,9 +99,9 @@ class Bucket(object):
except: except:
return None return None
def is_authorized(self, user): def is_authorized(self, context):
try: 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: except Exception, e:
pass pass
+24 -20
View File
@@ -31,6 +31,7 @@ S3 client with this module::
print c.get("mybucket", "mykey").body print c.get("mybucket", "mykey").body
""" """
import datetime import datetime
import os import os
import urllib import urllib
@@ -44,6 +45,7 @@ from tornado import escape, web
from nova import exception from nova import exception
from nova import flags from nova import flags
from nova.endpoint import api
from nova.objectstore import bucket from nova.objectstore import bucket
from nova.objectstore import image from nova.objectstore import image
@@ -87,16 +89,18 @@ class BaseRequestHandler(web.RequestHandler):
SUPPORTED_METHODS = ("PUT", "GET", "DELETE", "HEAD") SUPPORTED_METHODS = ("PUT", "GET", "DELETE", "HEAD")
@property @property
def user(self): def context(self):
if not hasattr(self, '_user'): if not hasattr(self, '_context'):
try: try:
access = self.request.headers['Authorization'].split(' ')[1].split(':')[0] # Authorization Header format: 'AWS <access>:<secret>'
user = self.application.user_manager.get_user_from_access_key(access) access, sep, secret = self.request.headers['Authorization'].split(' ')[1].rpartition(':')
user.secret # FIXME: check signature here! (user, project) = self.application.user_manager.authenticate(access, secret, {}, self.request.method, self.request.host, self.request.path, False)
self._user = user # FIXME: check signature here!
except: self._context = api.APIRequestContext(self, user, project)
except exception.Error, ex:
logging.debug("Authentication Failure: %s" % ex)
raise web.HTTPError(403) raise web.HTTPError(403)
return self._user return self._context
def render_xml(self, value): def render_xml(self, value):
assert isinstance(value, dict) and len(value) == 1 assert isinstance(value, dict) and len(value) == 1
@@ -134,7 +138,7 @@ class BaseRequestHandler(web.RequestHandler):
class RootHandler(BaseRequestHandler): class RootHandler(BaseRequestHandler):
def get(self): 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": { self.render_xml({"ListAllMyBucketsResult": {
"Buckets": {"Bucket": [b.metadata for b in buckets]}, "Buckets": {"Bucket": [b.metadata for b in buckets]},
@@ -148,7 +152,7 @@ class BucketHandler(BaseRequestHandler):
bucket_object = bucket.Bucket(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) raise web.HTTPError(403)
prefix = self.get_argument("prefix", u"") prefix = self.get_argument("prefix", u"")
@@ -162,7 +166,7 @@ class BucketHandler(BaseRequestHandler):
@catch_nova_exceptions @catch_nova_exceptions
def put(self, bucket_name): def put(self, bucket_name):
logging.debug("Creating bucket %s" % (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() self.finish()
@catch_nova_exceptions @catch_nova_exceptions
@@ -170,7 +174,7 @@ class BucketHandler(BaseRequestHandler):
logging.debug("Deleting bucket %s" % (bucket_name)) logging.debug("Deleting bucket %s" % (bucket_name))
bucket_object = bucket.Bucket(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) raise web.HTTPError(403)
bucket_object.delete() bucket_object.delete()
@@ -185,7 +189,7 @@ class ObjectHandler(BaseRequestHandler):
bucket_object = bucket.Bucket(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) raise web.HTTPError(403)
obj = bucket_object[urllib.unquote(object_name)] obj = bucket_object[urllib.unquote(object_name)]
@@ -199,7 +203,7 @@ class ObjectHandler(BaseRequestHandler):
logging.debug("Putting object: %s / %s" % (bucket_name, object_name)) logging.debug("Putting object: %s / %s" % (bucket_name, object_name))
bucket_object = bucket.Bucket(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) raise web.HTTPError(403)
key = urllib.unquote(object_name) key = urllib.unquote(object_name)
@@ -212,7 +216,7 @@ class ObjectHandler(BaseRequestHandler):
logging.debug("Deleting object: %s / %s" % (bucket_name, object_name)) logging.debug("Deleting object: %s / %s" % (bucket_name, object_name))
bucket_object = bucket.Bucket(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) raise web.HTTPError(403)
del bucket_object[urllib.unquote(object_name)] del bucket_object[urllib.unquote(object_name)]
@@ -228,7 +232,7 @@ class ImageHandler(BaseRequestHandler):
""" returns a json listing of all images """ returns a json listing of all images
that a user has permissions to see """ 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])) 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]) bucket_object = bucket.Bucket(image_location.split("/")[0])
manifest = image_location[len(image_location.split('/')[0])+1:] 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) raise web.HTTPError(403)
p = multiprocessing.Process(target=image.Image.create,args= p = multiprocessing.Process(target=image.Image.create,args=
(image_id, image_location, self.user)) (image_id, image_location, self.context))
p.start() p.start()
self.finish() self.finish()
@@ -264,7 +268,7 @@ class ImageHandler(BaseRequestHandler):
image_object = image.Image(image_id) 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) raise web.HTTPError(403)
image_object.set_public(operation=='add') image_object.set_public(operation=='add')
@@ -277,7 +281,7 @@ class ImageHandler(BaseRequestHandler):
image_id = self.get_argument("image_id", u"") image_id = self.get_argument("image_id", u"")
image_object = image.Image(image_id) 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) raise web.HTTPError(403)
image_object.delete() image_object.delete()
+4 -4
View File
@@ -58,9 +58,9 @@ class Image(object):
except: except:
pass pass
def is_authorized(self, user): def is_authorized(self, context):
try: 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: except:
return False return False
@@ -91,7 +91,7 @@ class Image(object):
return json.load(f) return json.load(f)
@staticmethod @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) image_path = os.path.join(FLAGS.images_path, image_id)
os.makedirs(image_path) os.makedirs(image_path)
@@ -119,7 +119,7 @@ class Image(object):
info = { info = {
'imageId': image_id, 'imageId': image_id,
'imageLocation': image_location, 'imageLocation': image_location,
'imageOwnerId': user.id, 'imageOwnerId': context.project.id,
'isPublic': False, # FIXME: grab public from manifest 'isPublic': False, # FIXME: grab public from manifest
'architecture': 'x86_64', # FIXME: grab architecture from manifest 'architecture': 'x86_64', # FIXME: grab architecture from manifest
'type' : image_type 'type' : image_type
-60
View File
@@ -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()
+6 -7
View File
@@ -1,11 +1,11 @@
# Copyright [2010] [Anso Labs, LLC] # Copyright [2010] [Anso Labs, LLC]
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -33,14 +33,13 @@ def get_connection():
path='/services/Cloud', path='/services/Cloud',
debug=99 debug=99
) )
class APIIntegrationTests(unittest.TestCase): class APIIntegrationTests(unittest.TestCase):
def test_001_get_all_images(self): def test_001_get_all_images(self):
conn = get_connection() conn = get_connection()
res = conn.get_all_images() res = conn.get_all_images()
print res
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
+20 -18
View File
@@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC] # Copyright [2010] [Anso Labs, LLC]
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -20,7 +20,6 @@ import unittest
from xml.etree import ElementTree from xml.etree import ElementTree
from nova import vendor from nova import vendor
import mox
from tornado import ioloop from tornado import ioloop
from twisted.internet import defer from twisted.internet import defer
@@ -53,18 +52,24 @@ class CloudTestCase(test.BaseTestCase):
topic=FLAGS.cloud_topic, topic=FLAGS.cloud_topic,
proxy=self.cloud) proxy=self.cloud)
self.injected.append(self.cloud_consumer.attach_to_tornado(self.ioloop)) self.injected.append(self.cloud_consumer.attach_to_tornado(self.ioloop))
# set up a node # set up a node
self.node = node.Node() self.node = node.Node()
self.node_consumer = rpc.AdapterConsumer(connection=self.conn, self.node_consumer = rpc.AdapterConsumer(connection=self.conn,
topic=FLAGS.compute_topic, topic=FLAGS.compute_topic,
proxy=self.node) proxy=self.node)
self.injected.append(self.node_consumer.attach_to_tornado(self.ioloop)) self.injected.append(self.node_consumer.attach_to_tornado(self.ioloop))
user_mocker = mox.Mox() try:
self.admin = user_mocker.CreateMock(users.User) users.UserManager.instance().create_user('admin', 'admin', 'admin')
self.admin.is_authorized(mox.IgnoreArg()).AndReturn(True) except: pass
self.context = api.APIRequestContext(handler=None,user=self.admin) 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): def test_console_output(self):
if FLAGS.fake_libvirt: if FLAGS.fake_libvirt:
@@ -76,7 +81,7 @@ class CloudTestCase(test.BaseTestCase):
logging.debug(output) logging.debug(output)
self.assert_(output) self.assert_(output)
rv = yield self.node.terminate_instance(instance_id) rv = yield self.node.terminate_instance(instance_id)
def test_run_instances(self): def test_run_instances(self):
if FLAGS.fake_libvirt: if FLAGS.fake_libvirt:
logging.debug("Can't test instances without a real virtual env.") logging.debug("Can't test instances without a real virtual env.")
@@ -128,9 +133,7 @@ class CloudTestCase(test.BaseTestCase):
'state': 0x01, 'state': 0x01,
'user_data': '' 'user_data': ''
} }
rv = self.cloud._format_instances(self.context)
rv = self.cloud.format_instances(self.admin)
print rv
self.assert_(len(rv['reservationSet']) == 0) self.assert_(len(rv['reservationSet']) == 0)
# simulate launch of 5 instances # simulate launch of 5 instances
@@ -139,19 +142,18 @@ class CloudTestCase(test.BaseTestCase):
# inst = instance(i) # inst = instance(i)
# self.cloud.instances['pending'][inst['instance_id']] = inst # 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']) == 1)
#self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5) #self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5)
# report 4 nodes each having 1 of the instances # report 4 nodes each having 1 of the instances
#for i in xrange(4): #for i in xrange(4):
# self.cloud.update_state('instances', {('node-%s' % i): {('i-%s' % i): instance(i)}}) # self.cloud.update_state('instances', {('node-%s' % i): {('i-%s' % i): instance(i)}})
# one instance should be pending still # one instance should be pending still
#self.assert_(len(self.cloud.instances['pending'].keys()) == 1) #self.assert_(len(self.cloud.instances['pending'].keys()) == 1)
# check that the reservations collapse # 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']) == 1)
#self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5) #self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5)
+45 -43
View File
@@ -30,84 +30,86 @@ class NetworkTestCase(test.TrialTestCase):
super(NetworkTestCase, self).setUp() super(NetworkTestCase, self).setUp()
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
self.manager = users.UserManager.instance() self.manager = users.UserManager.instance()
try:
self.manager.create_user('netuser', 'netuser', 'netuser')
except: pass
for i in range(0, 6): for i in range(0, 6):
name = 'user%s' % i name = 'project%s' % i
if not self.manager.get_user(name): if not self.manager.get_project(name):
self.manager.create_user(name, name, name) self.manager.create_project(name, 'netuser', name)
self.network = network.NetworkController(netsize=16) self.network = network.NetworkController(netsize=16)
def tearDown(self): def tearDown(self):
super(NetworkTestCase, self).tearDown() super(NetworkTestCase, self).tearDown()
for i in range(0, 6): for i in range(0, 6):
name = 'user%s' % i name = 'project%s' % i
self.manager.delete_user(name) self.manager.delete_project(name)
self.manager.delete_user('netuser')
def test_network_serialization(self): def test_network_serialization(self):
net1 = network.Network(vlan=100, network="192.168.100.0/24", conn=None) 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) net_json = str(net1)
net2 = network.Network.from_json(net_json) net2 = network.Network.from_json(net_json)
self.assertEqual(net_json, str(net2)) self.assertEqual(net_json, str(net2))
self.assertTrue(IPy.IP(address) in net2.network) self.assertTrue(IPy.IP(address) in net2.network)
def test_allocate_deallocate_address(self): def test_allocate_deallocate_address(self):
for flag in flags.FLAGS: (address, net_name) = self.network.allocate_address("netuser",
print "%s=%s" % (flag, flags.FLAGS.get(flag, None)) "project0", "01:24:55:36:f2:a0")
(address, net_name) = self.network.allocate_address(
"user0", "01:24:55:36:f2:a0")
logging.debug("Was allocated %s" % (address)) 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) 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): def test_range_allocation(self):
(address, net_name) = self.network.allocate_address( (address, net_name) = self.network.allocate_address("netuser",
"user0", "01:24:55:36:f2:a0") "project0", "01:24:55:36:f2:a0")
(secondaddress, net_name) = self.network.allocate_address( (secondaddress, net_name) = self.network.allocate_address("netuser",
"user1", "01:24:55:36:f2:a0") "project1", "01:24:55:36:f2:a0")
self.assertEqual(True, address in self._get_user_addresses("user0")) self.assertEqual(True, address in self._get_project_addresses("project0"))
self.assertEqual(True, self.assertEqual(True,
secondaddress in self._get_user_addresses("user1")) secondaddress in self._get_project_addresses("project1"))
self.assertEqual(False, address in self._get_user_addresses("user1")) self.assertEqual(False, address in self._get_project_addresses("project1"))
rv = self.network.deallocate_address(address) 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) rv = self.network.deallocate_address(secondaddress)
self.assertEqual(False, self.assertEqual(False,
secondaddress in self._get_user_addresses("user1")) secondaddress in self._get_project_addresses("project1"))
def test_subnet_edge(self): def test_subnet_edge(self):
(secondaddress, net_name) = self.network.allocate_address("user0") (secondaddress, net_name) = self.network.allocate_address("netuser", "project0")
for user in range(1,5): for project in range(1,5):
user_id = "user%s" % (user) project_id = "project%s" % (project)
(address, net_name) = self.network.allocate_address( (address, net_name) = self.network.allocate_address("netuser",
user_id, "01:24:55:36:f2:a0") project_id, "01:24:55:36:f2:a0")
(address2, net_name) = self.network.allocate_address( (address2, net_name) = self.network.allocate_address("netuser",
user_id, "01:24:55:36:f2:a0") project_id, "01:24:55:36:f2:a0")
(address3, net_name) = self.network.allocate_address( (address3, net_name) = self.network.allocate_address("netuser",
user_id, "01:24:55:36:f2:a0") project_id, "01:24:55:36:f2:a0")
self.assertEqual(False, self.assertEqual(False,
address in self._get_user_addresses("user0")) address in self._get_project_addresses("project0"))
self.assertEqual(False, self.assertEqual(False,
address2 in self._get_user_addresses("user0")) address2 in self._get_project_addresses("project0"))
self.assertEqual(False, 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(address)
rv = self.network.deallocate_address(address2) rv = self.network.deallocate_address(address2)
rv = self.network.deallocate_address(address3) rv = self.network.deallocate_address(address3)
rv = self.network.deallocate_address(secondaddress) rv = self.network.deallocate_address(secondaddress)
def test_too_many_users(self): def test_too_many_projects(self):
for i in range(0, 30): for i in range(0, 30):
name = 'toomany-user%s' % i name = 'toomany-project%s' % i
self.manager.create_user(name, name, name) self.manager.create_project(name, 'netuser', name)
(address, net_name) = self.network.allocate_address( (address, net_name) = self.network.allocate_address("netuser",
name, "01:24:55:36:f2:a0") 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() rv = self.network.describe_addresses()
user_addresses = [] project_addresses = []
for item in rv: for item in rv:
if item['user_id'] == user_id: if item['project_id'] == project_id:
user_addresses.append(item['address']) project_addresses.append(item['address'])
return user_addresses return project_addresses
+16 -20
View File
@@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC] # Copyright [2010] [Anso Labs, LLC]
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,14 +14,9 @@
# limitations under the License. # limitations under the License.
import logging import logging
import StringIO
import time
import unittest
from xml.etree import ElementTree from xml.etree import ElementTree
from nova import vendor from nova import vendor
import mox
from tornado import ioloop
from twisted.internet import defer from twisted.internet import defer
from nova import exception from nova import exception
@@ -39,21 +34,21 @@ class InstanceXmlTestCase(test.TrialTestCase):
def test_serialization(self): def test_serialization(self):
# TODO: Reimplement this, it doesn't make sense in redis-land # TODO: Reimplement this, it doesn't make sense in redis-land
return return
# instance_id = 'foo' # instance_id = 'foo'
# first_node = node.Node() # first_node = node.Node()
# inst = yield first_node.run_instance(instance_id) # inst = yield first_node.run_instance(instance_id)
# #
# # force the state so that we can verify that it changes # # force the state so that we can verify that it changes
# inst._s['state'] = node.Instance.NOSTATE # inst._s['state'] = node.Instance.NOSTATE
# xml = inst.toXml() # xml = inst.toXml()
# self.assert_(ElementTree.parse(StringIO.StringIO(xml))) # self.assert_(ElementTree.parse(StringIO.StringIO(xml)))
# #
# second_node = node.Node() # second_node = node.Node()
# new_inst = node.Instance.fromXml(second_node._conn, pool=second_node._pool, xml=xml) # new_inst = node.Instance.fromXml(second_node._conn, pool=second_node._pool, xml=xml)
# self.assertEqual(new_inst.state, node.Instance.RUNNING) # self.assertEqual(new_inst.state, node.Instance.RUNNING)
# rv = yield first_node.terminate_instance(instance_id) # rv = yield first_node.terminate_instance(instance_id)
class NodeConnectionTestCase(test.TrialTestCase): class NodeConnectionTestCase(test.TrialTestCase):
def setUp(self): def setUp(self):
@@ -64,20 +59,22 @@ class NodeConnectionTestCase(test.TrialTestCase):
fake_users=True, fake_users=True,
redis_db=8) redis_db=8)
self.node = node.Node() self.node = node.Node()
def create_instance(self): def create_instance(self):
instdir = model.InstanceDirectory() instdir = model.InstanceDirectory()
inst = instdir.new() inst = instdir.new()
# TODO(ja): add ami, ari, aki, user_data # TODO(ja): add ami, ari, aki, user_data
inst['reservation_id'] = 'r-fakeres' inst['reservation_id'] = 'r-fakeres'
inst['launch_time'] = '10' 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['node_name'] = FLAGS.node_name
inst['mac_address'] = utils.generate_mac() inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = 0 inst['ami_launch_index'] = 0
inst.save() inst.save()
return inst['instance_id'] return inst['instance_id']
@defer.inlineCallbacks @defer.inlineCallbacks
def test_run_describe_terminate(self): def test_run_describe_terminate(self):
instance_id = self.create_instance() instance_id = self.create_instance()
@@ -96,11 +93,10 @@ class NodeConnectionTestCase(test.TrialTestCase):
def test_reboot(self): def test_reboot(self):
instance_id = self.create_instance() instance_id = self.create_instance()
rv = yield self.node.run_instance(instance_id) rv = yield self.node.run_instance(instance_id)
rv = yield self.node.describe_instances() rv = yield self.node.describe_instances()
logging.debug("describe_instances returns %s" % (rv))
self.assertEqual(rv[instance_id].name, instance_id) self.assertEqual(rv[instance_id].name, instance_id)
yield self.node.reboot_instance(instance_id) yield self.node.reboot_instance(instance_id)
rv = yield self.node.describe_instances() rv = yield self.node.describe_instances()
@@ -111,7 +107,7 @@ class NodeConnectionTestCase(test.TrialTestCase):
def test_console_output(self): def test_console_output(self):
instance_id = self.create_instance() instance_id = self.create_instance()
rv = yield self.node.run_instance(instance_id) rv = yield self.node.run_instance(instance_id)
console = yield self.node.get_console_output(instance_id) console = yield self.node.get_console_output(instance_id)
self.assert_(console) self.assert_(console)
rv = yield self.node.terminate_instance(instance_id) rv = yield self.node.terminate_instance(instance_id)
@@ -123,6 +119,6 @@ class NodeConnectionTestCase(test.TrialTestCase):
rv = yield self.node.describe_instances() rv = yield self.node.describe_instances()
self.assertEqual(rv[instance_id].name, instance_id) self.assertEqual(rv[instance_id].name, instance_id)
self.assertRaises(exception.Error, self.node.run_instance, instance_id) self.assertRaises(exception.Error, self.node.run_instance, instance_id)
rv = yield self.node.terminate_instance(instance_id) rv = yield self.node.terminate_instance(instance_id)
+34 -23
View File
@@ -56,8 +56,6 @@ class ObjectStoreTestCase(test.BaseTestCase):
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
self.um = users.UserManager.instance() self.um = users.UserManager.instance()
def test_buckets(self):
try: try:
self.um.create_user('user1') self.um.create_user('user1')
except: pass except: pass
@@ -67,18 +65,41 @@ class ObjectStoreTestCase(test.BaseTestCase):
try: try:
self.um.create_user('admin_user', admin=True) self.um.create_user('admin_user', admin=True)
except: pass 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()
objectstore.bucket.Bucket.create('new_bucket', self.um.get_user('user1')) 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()
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') bucket = objectstore.bucket.Bucket('new_bucket')
# creator is authorized to use 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 # 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 # 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 # new buckets are empty
self.assert_(bucket.list_keys()['Contents'] == []) self.assert_(bucket.list_keys()['Contents'] == [])
@@ -116,18 +137,13 @@ class ObjectStoreTestCase(test.BaseTestCase):
exception = True exception = True
self.assert_(exception) self.assert_(exception)
self.um.delete_user('user1')
self.um.delete_user('user2')
self.um.delete_user('admin_user')
def test_images(self): def test_images(self):
try: self.context.user = self.um.get_user('user1')
self.um.create_user('image_creator') self.context.project = self.um.get_project('proj1')
except: pass
image_user = self.um.get_user('image_creator')
# create a bucket for our bundle # 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') bucket = objectstore.bucket.Bucket('image_bucket')
# upload an image manifest/parts # upload an image manifest/parts
@@ -136,7 +152,7 @@ class ObjectStoreTestCase(test.BaseTestCase):
bucket[os.path.basename(path)] = open(path, 'rb').read() bucket[os.path.basename(path)] = open(path, 'rb').read()
# register an image # 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 # verify image
my_img = objectstore.image.Image('i-testing') my_img = objectstore.image.Image('i-testing')
@@ -147,14 +163,9 @@ class ObjectStoreTestCase(test.BaseTestCase):
self.assertEqual(sha, '3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3') self.assertEqual(sha, '3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3')
# verify image permissions # verify image permissions
try: self.context.user = self.um.get_user('user2')
self.um.create_user('new_user') self.context.project = self.um.get_project('proj2')
except: pass self.assert_(my_img.is_authorized(self.context) == False)
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')
# class ApiObjectStoreTestCase(test.BaseTestCase): # class ApiObjectStoreTestCase(test.BaseTestCase):
# def setUp(self): # def setUp(self):
+42 -11
View File
@@ -24,11 +24,9 @@ from M2Crypto import X509
from nova import crypto from nova import crypto
from nova import flags from nova import flags
from nova import test from nova import test
from nova import utils
from nova.auth import users from nova.auth import users
from nova.endpoint import cloud from nova.endpoint import cloud
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@@ -40,8 +38,9 @@ class UserTestCase(test.BaseTestCase):
redis_db=8) redis_db=8)
self.users = users.UserManager.instance() 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('test1', 'access', 'secret')
self.users.create_user('test2')
def test_002_can_get_user(self): def test_002_can_get_user(self):
user = self.users.get_user('test1') user = self.users.get_user('test1')
@@ -83,7 +82,6 @@ class UserTestCase(test.BaseTestCase):
key.save_pub_key_bio(bio) key.save_pub_key_bio(bio)
converted = crypto.ssl_pub_to_ssh_pub(bio.read()) converted = crypto.ssl_pub_to_ssh_pub(bio.read())
# assert key fields are equal # assert key fields are equal
print converted
self.assertEqual(public_key.split(" ")[1].strip(), self.assertEqual(public_key.split(" ")[1].strip(),
converted.split(" ")[1].strip()) converted.split(" ")[1].strip())
@@ -101,16 +99,44 @@ class UserTestCase(test.BaseTestCase):
users = self.users.get_users() users = self.users.get_users()
self.assertTrue(filter(lambda u: u.id == 'test1', 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 # MUST HAVE RUN CLOUD SETUP BY NOW
self.cloud = cloud.CloudController() self.cloud = cloud.CloudController()
self.cloud.setup() 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) logging.debug(signed_cert_string)
# Need to verify that it's signed by the right intermediate CA # Need to verify that it's signed by the right intermediate CA
full_chain = crypto.fetch_ca(username='test1', chain=True) full_chain = crypto.fetch_ca(project_id='testproj', chain=True)
int_cert = crypto.fetch_ca(username='test1', chain=False) int_cert = crypto.fetch_ca(project_id='testproj', chain=False)
cloud_cert = crypto.fetch_ca() cloud_cert = crypto.fetch_ca()
logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain) logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain)
signed_cert = X509.load_cert_string(signed_cert_string) signed_cert = X509.load_cert_string(signed_cert_string)
@@ -125,11 +151,16 @@ class UserTestCase(test.BaseTestCase):
else: else:
self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey())) 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') self.users.delete_user('test1')
users = self.users.get_users() 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__": if __name__ == "__main__":
+7 -6
View File
@@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC] # Copyright [2010] [Anso Labs, LLC]
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 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. Currently uses Ata-over-Ethernet.
""" """
import glob
import logging import logging
import random import random
import socket import socket
@@ -52,7 +53,7 @@ flags.DEFINE_integer('shelf_id',
flags.DEFINE_string('storage_availability_zone', flags.DEFINE_string('storage_availability_zone',
'nova', 'nova',
'availability zone of this node') '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?') 'Should we make real storage volumes to attach?')
class BlockStore(object): class BlockStore(object):
@@ -62,7 +63,7 @@ class BlockStore(object):
if FLAGS.fake_storage: if FLAGS.fake_storage:
self.volume_class = FakeVolume self.volume_class = FakeVolume
self._init_volume_group() self._init_volume_group()
self.keeper = datastore.Keeper('instances') self.keeper = datastore.Keeper('storage-')
def report_state(self): def report_state(self):
#TODO: aggregate the state of the system #TODO: aggregate the state of the system
@@ -82,7 +83,7 @@ class BlockStore(object):
def get_volume(self, volume_id): def get_volume(self, volume_id):
""" Returns a redis-backed volume object """ """ 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) return self.volume_class(volume_id=volume_id)
raise exception.Error("Volume does not exist") raise exception.Error("Volume does not exist")
-1
View File
@@ -44,7 +44,6 @@ from twisted.scripts import trial as trial_script
from nova import flags from nova import flags
from nova import twistd from nova import twistd
from nova.tests.access_unittest import *
from nova.tests.api_unittest import * from nova.tests.api_unittest import *
from nova.tests.cloud_unittest import * from nova.tests.cloud_unittest import *
from nova.tests.keeper_unittest import * from nova.tests.keeper_unittest import *