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

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)

View File

@ -22,6 +22,12 @@ import logging
from nova import datastore
SCOPE_SUBTREE = 1
MOD_ADD = 0
MOD_DELETE = 1
SUBS = {
'groupOfNames': ['novaProject']
}
class NO_SUCH_OBJECT(Exception):
@ -44,6 +50,46 @@ class FakeLDAP(object):
def unbind_s(self):
pass
def _paren_groups(self, source):
count = 0
start = 0
result = []
for pos in xrange(len(source)):
if source[pos] == '(':
if count == 0:
start = pos
count += 1
if source[pos] == ')':
count -= 1
if count == 0:
result.append(source[start:pos+1])
def _match_query(self, query, attrs):
inner = query[1:-1]
if inner.startswith('&'):
l, r = self._paren_groups(inner[1:])
return self._match_query(l, attrs) and self._match_query(r, attrs)
if inner.startswith('|'):
l, r = self._paren_groups(inner[1:])
return self._match_query(l, attrs) or self._match_query(r, attrs)
if inner.startswith('!'):
return not self._match_query(query[2:-1], attrs)
(k, sep, v) = inner.partition('=')
return self._match(k, v, attrs)
def _subs(self, v):
if v in SUBS:
return [v] + SUBS[v]
return [v]
def _match(self, k, v, attrs):
if attrs.has_key(k):
for v in self._subs(v):
if (v in attrs[k]):
return True
return False
def search_s(self, dn, scope, query=None, fields=None):
logging.debug("searching for %s" % dn)
filtered = {}
@ -51,12 +97,11 @@ class FakeLDAP(object):
for cn, attrs in d.iteritems():
if cn[-len(dn):] == dn:
filtered[cn] = attrs
objects = filtered
if query:
k,v = query[1:-1].split('=')
objects = {}
for cn, attrs in filtered.iteritems():
if attrs.has_key(k) and (v in attrs[k] or
v == attrs[k]):
if self._match_query(query, attrs):
objects[cn] = attrs
if objects == {}:
raise NO_SUCH_OBJECT()
@ -75,7 +120,22 @@ class FakeLDAP(object):
self.keeper['objects'] = d
def delete_s(self, cn):
logging.debug("creating for %s" % cn)
d = self.keeper['objects'] or {}
logging.debug("deleting %s" % cn)
d = self.keeper['objects']
del d[cn]
self.keeper['objects'] = d
def modify_s(self, cn, attr):
logging.debug("modifying %s" % cn)
d = self.keeper['objects']
for cmd, k, v in attr:
logging.debug("command %s" % cmd)
if cmd == MOD_ADD:
d[cn][k].append(v)
else:
d[cn][k].remove(v)
self.keeper['objects'] = d

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

View File

@ -46,12 +46,9 @@ import urllib
import base64
from nova.exception import Error
_log = logging.getLogger('signer')
logging.getLogger('signer').setLevel(logging.WARN)
class Signer(object):
""" hacked up code from boto/connection.py """
def __init__(self, secret_key):
self.hmac = hmac.new(secret_key, digestmod=hashlib.sha1)
if hashlib.sha256:
@ -59,15 +56,14 @@ class Signer(object):
def generate(self, params, verb, server_string, path):
if params['SignatureVersion'] == '0':
t = self._calc_signature_0(params)
elif params['SignatureVersion'] == '1':
t = self._calc_signature_1(params)
elif params['SignatureVersion'] == '2':
t = self._calc_signature_2(params, verb, server_string, path)
else:
raise Error('Unknown Signature Version: %s' % self.SignatureVersion)
return t
return self._calc_signature_0(params)
if params['SignatureVersion'] == '1':
return self._calc_signature_1(params)
if params['SignatureVersion'] == '2':
return self._calc_signature_2(params, verb, server_string, path)
raise Error('Unknown Signature Version: %s' % self.SignatureVersion)
def _get_utf8_value(self, value):
if not isinstance(value, str) and not isinstance(value, unicode):
value = str(value)
@ -99,7 +95,7 @@ class Signer(object):
return base64.b64encode(self.hmac.digest())
def _calc_signature_2(self, params, verb, server_string, path):
_log.debug('using _calc_signature_2')
logging.debug('using _calc_signature_2')
string_to_sign = '%s\n%s\n%s\n' % (verb, server_string, path)
if self.hmac_256:
hmac = self.hmac_256
@ -114,13 +110,13 @@ class Signer(object):
val = self._get_utf8_value(params[key])
pairs.append(urllib.quote(key, safe='') + '=' + urllib.quote(val, safe='-_~'))
qs = '&'.join(pairs)
_log.debug('query string: %s' % qs)
logging.debug('query string: %s' % qs)
string_to_sign += qs
_log.debug('string_to_sign: %s' % string_to_sign)
logging.debug('string_to_sign: %s' % string_to_sign)
hmac.update(string_to_sign)
b64 = base64.b64encode(hmac.digest())
_log.debug('len(b64)=%d' % len(b64))
_log.debug('base64 encoded digest: %s' % b64)
logging.debug('len(b64)=%d' % len(b64))
logging.debug('base64 encoded digest: %s' % b64)
return b64
if __name__ == '__main__':

View File

@ -1,12 +1,12 @@
#!/usr/bin/env bash
# Copyright [2010] [Anso Labs, LLC]
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -21,21 +21,21 @@ cat >/etc/ldap/schema/openssh-lpk_openldap.schema <<LPK_SCHEMA_EOF
#
# LDAP Public Key Patch schema for use with openssh-ldappubkey
# Author: Eric AUGE <eau@phear.org>
#
#
# Based on the proposal of : Mark Ruijter
#
# octetString SYNTAX
attributetype ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey'
DESC 'MANDATORY: OpenSSH Public key'
attributetype ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey'
DESC 'MANDATORY: OpenSSH Public key'
EQUALITY octetStringMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
# printableString SYNTAX yes|no
objectclass ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY
DESC 'MANDATORY: OpenSSH LPK objectclass'
MAY ( sshPublicKey $ uid )
MAY ( sshPublicKey $ uid )
)
LPK_SCHEMA_EOF
@ -44,7 +44,7 @@ cat >/etc/ldap/schema/nova.schema <<NOVA_SCHEMA_EOF
# Person object for Nova
# inetorgperson with extra attributes
# Author: Vishvananda Ishaya <vishvananda@yahoo.com>
#
#
#
# using internet experimental oid arc as per BP64 3.1
@ -54,32 +54,32 @@ objectidentifier novaOCs novaSchema:4
attributetype (
novaAttrs:1
NAME 'accessKey'
DESC 'Key for accessing data'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
NAME 'accessKey'
DESC 'Key for accessing data'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
)
attributetype (
novaAttrs:2
NAME 'secretKey'
DESC 'Secret key'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
NAME 'secretKey'
DESC 'Secret key'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
)
attributetype (
novaAttrs:3
NAME 'keyFingerprint'
DESC 'Fingerprint of private key'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
NAME 'keyFingerprint'
DESC 'Fingerprint of private key'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE
)
attributetype (
@ -96,7 +96,7 @@ attributetype (
NAME 'projectManager'
DESC 'Project Managers of a project'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
)
)
objectClass (
novaOCs:1
@ -120,7 +120,7 @@ objectClass (
novaOCs:3
NAME 'novaProject'
DESC 'Container for project'
SUP groupofnames
SUP groupOfNames
STRUCTURAL
MUST ( cn $ projectManager )
)

564
nova/auth/users.py Executable file → Normal file
View File

@ -22,6 +22,7 @@ import datetime
import logging
import os
import shutil
import string
import tempfile
import uuid
import zipfile
@ -40,7 +41,6 @@ from nova import exception
from nova import flags
from nova import crypto
from nova import utils
import access as simplerbac
from nova import objectstore # for flags
@ -50,16 +50,8 @@ flags.DEFINE_string('ldap_url', 'ldap://localhost', 'Point this at your ldap ser
flags.DEFINE_string('ldap_password', 'changeme', 'LDAP password')
flags.DEFINE_string('user_dn', 'cn=Manager,dc=example,dc=com', 'DN of admin user')
flags.DEFINE_string('user_unit', 'Users', 'OID for Users')
flags.DEFINE_string('ldap_subtree', 'ou=Users,dc=example,dc=com', 'OU for Users')
flags.DEFINE_string('ldap_sysadmin',
'cn=sysadmins,ou=Groups,dc=example,dc=com', 'OU for Sysadmins')
flags.DEFINE_string('ldap_netadmin',
'cn=netadmins,ou=Groups,dc=example,dc=com', 'OU for NetAdmins')
flags.DEFINE_string('ldap_cloudadmin',
'cn=cloudadmins,ou=Groups,dc=example,dc=com', 'OU for Cloud Admins')
flags.DEFINE_string('ldap_itsec',
'cn=itsec,ou=Groups,dc=example,dc=com', 'OU for ItSec')
flags.DEFINE_string('user_ldap_subtree', 'ou=Users,dc=example,dc=com', 'OU for Users')
flags.DEFINE_string('project_ldap_subtree', 'ou=Groups,dc=example,dc=com', 'OU for Projects')
flags.DEFINE_string('credentials_template',
utils.abspath('auth/novarc.template'),
@ -71,46 +63,113 @@ flags.DEFINE_string('credential_cert_file', 'cert.pem',
flags.DEFINE_string('credential_rc_file', 'novarc',
'Filename of rc in credentials zip')
_log = logging.getLogger('auth')
_log.setLevel(logging.WARN)
class AuthBase(object):
@classmethod
def safe_id(cls, obj):
"""this method will return the id of the object if the object is of this class, otherwise
it will return the original object. This allows methods to accept objects or
ids as paramaters"""
if isinstance(obj, cls):
return obj.id
else:
return obj
class UserError(exception.ApiError):
pass
class InvalidKeyPair(exception.ApiError):
pass
class User(object):
class User(AuthBase):
"""id and name are currently the same"""
def __init__(self, id, name, access, secret, admin):
self.manager = UserManager.instance()
self.id = id
self.name = name
self.access = access
self.secret = secret
self.admin = admin
self.keeper = datastore.Keeper(prefix="user")
def is_admin(self):
"""allows user to see objects from all projects"""
return self.admin
def has_role(self, role_type):
return self.manager.has_role(self.id, role_type)
def is_project_member(self, project):
return UserManager.instance().is_project_member(self, project)
def is_authorized(self, owner_id, action=None):
if self.is_admin() or owner_id == self.id:
return True
if action == None:
return False
project = None #(Fixme)
target_object = None # (Fixme, should be passed in)
return simplerbac.is_allowed(action, self, project, target_object)
def is_project_manager(self, project):
return UserManager.instance().is_project_manager(self, project)
def get_credentials(self):
rc = self.generate_rc()
private_key, signed_cert = self.generate_x509_cert()
def generate_rc(self):
rc = open(FLAGS.credentials_template).read()
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()
zf = os.path.join(tmpdir, "temp.zip")
@ -126,50 +185,11 @@ class User(object):
shutil.rmtree(tmpdir)
return buffer
def generate_x509_cert(self, user):
return UserManager.instance().generate_x509_cert(user, self)
def generate_rc(self):
rc = open(FLAGS.credentials_template).read()
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)
def __repr__(self):
return "Project('%s', '%s', '%s', %s)" % (self.id, self.project_manager_id, self.description, self.member_ids)
class UserManager(object):
def __init__(self):
@ -193,31 +213,69 @@ class UserManager(object):
except: pass
return cls._instance
def authenticate(self, params, signature, verb='GET', server_string='127.0.0.1:8773', path='/'):
def authenticate(self, access, signature, params, verb='GET', server_string='127.0.0.1:8773', path='/', verify_signature=True):
# TODO: Check for valid timestamp
access_key = params['AWSAccessKeyId']
(access_key, sep, project_name) = access.partition(':')
user = self.get_user_from_access_key(access_key)
if user == None:
return None
# hmac can't handle unicode, so encode ensures that secret isn't unicode
expected_signature = signer.Signer(user.secret.encode()).generate(params, verb, server_string, path)
_log.debug('user.secret: %s', user.secret)
_log.debug('expected_signature: %s', expected_signature)
_log.debug('signature: %s', signature)
if signature == expected_signature:
return user
raise exception.NotFound('No user found for access key')
if project_name is '':
project_name = user.name
def has_role(self, user, role, project=None):
# Map role to ldap group
group = FLAGS.__getitem__("ldap_%s" % role)
with LDAPWrapper() as conn:
return conn.is_member_of(user, group)
project = self.get_project(project_name)
if project == None:
raise exception.NotFound('No project called %s could be found' % project_name)
if not user.is_admin() and not project.has_member(user):
raise exception.NotFound('User %s is not a member of project %s' % (user.id, project.id))
if verify_signature:
# hmac can't handle unicode, so encode ensures that secret isn't unicode
expected_signature = signer.Signer(user.secret.encode()).generate(params, verb, server_string, path)
logging.debug('user.secret: %s', user.secret)
logging.debug('expected_signature: %s', expected_signature)
logging.debug('signature: %s', signature)
if signature != expected_signature:
raise exception.NotAuthorized('Signature does not match')
return (user, project)
def add_role(self, user, role, project=None):
# TODO: Project-specific roles
group = FLAGS.__getitem__("ldap_%s" % role)
def create_project(self, name, manager_user, description, member_users=None):
if member_users:
member_users = [User.safe_id(u) for u in member_users]
with LDAPWrapper() as conn:
return conn.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):
with LDAPWrapper() as conn:
@ -231,56 +289,59 @@ class UserManager(object):
with LDAPWrapper() as conn:
return conn.find_users()
def create_user(self, uid, access=None, secret=None, admin=False):
def create_user(self, user, access=None, secret=None, admin=False, create_project=True):
if access == None: access = str(uuid.uuid4())
if secret == None: secret = str(uuid.uuid4())
with LDAPWrapper() as conn:
u = conn.create_user(uid, access, secret, admin)
return u
user = User.safe_id(user)
result = conn.create_user(user, access, secret, admin)
if create_project:
conn.create_project(user, user, user)
return result
def delete_user(self, uid):
def delete_user(self, user, delete_project=True):
with LDAPWrapper() as conn:
conn.delete_user(uid)
user = User.safe_id(user)
if delete_project:
try:
conn.delete_project(user)
except exception.NotFound:
pass
conn.delete_user(user)
def generate_key_pair(self, uid, key_name):
def generate_key_pair(self, user, key_name):
# generating key pair is slow so delay generation
# until after check
user = User.safe_id(user)
with LDAPWrapper() as conn:
if not conn.user_exists(uid):
raise UserError("User " + uid + " doesn't exist")
if conn.key_pair_exists(uid, key_name):
raise InvalidKeyPair("The keypair '" +
key_name +
"' already exists.",
"Duplicate")
if not conn.user_exists(user):
raise exception.NotFound("User %s doesn't exist" % user)
if conn.key_pair_exists(user, key_name):
raise exception.Duplicate("The keypair %s already exists" % key_name)
private_key, public_key, fingerprint = crypto.generate_key_pair()
self.create_key_pair(uid, key_name, public_key, fingerprint)
self.create_key_pair(User.safe_id(user), key_name, public_key, fingerprint)
return private_key, fingerprint
def create_key_pair(self, uid, key_name, public_key, fingerprint):
def create_key_pair(self, user, key_name, public_key, fingerprint):
with LDAPWrapper() as conn:
return conn.create_key_pair(uid, key_name, public_key, fingerprint)
return conn.create_key_pair(User.safe_id(user), key_name, public_key, fingerprint)
def get_key_pair(self, uid, key_name):
def get_key_pair(self, user, key_name):
with LDAPWrapper() as conn:
return conn.find_key_pair(uid, key_name)
return conn.find_key_pair(User.safe_id(user), key_name)
def get_key_pairs(self, uid):
def get_key_pairs(self, user):
with LDAPWrapper() as conn:
return conn.find_key_pairs(uid)
return conn.find_key_pairs(User.safe_id(user))
def delete_key_pair(self, uid, key_name):
def delete_key_pair(self, user, key_name):
with LDAPWrapper() as conn:
conn.delete_key_pair(uid, key_name)
conn.delete_key_pair(User.safe_id(user), key_name)
def get_signed_zip(self, uid):
user = self.get_user(uid)
return user.get_credentials()
def generate_x509_cert(self, uid):
(private_key, csr) = crypto.generate_x509_cert(self.__cert_subject(uid))
def generate_x509_cert(self, user, project):
(private_key, csr) = crypto.generate_x509_cert(self.__cert_subject(User.safe_id(user)))
# TODO - This should be async call back to the cloud controller
signed_cert = crypto.sign_csr(csr, uid)
signed_cert = crypto.sign_csr(csr, Project.safe_id(project))
return (private_key, signed_cert)
def sign_cert(self, csr, uid):
@ -328,28 +389,53 @@ class LDAPWrapper(object):
return [x[1] for x in res]
def find_users(self):
attrs = self.find_objects(FLAGS.ldap_subtree, '(objectclass=novaUser)')
attrs = self.find_objects(FLAGS.user_ldap_subtree, '(objectclass=novaUser)')
return [self.__to_user(attr) for attr in attrs]
def find_key_pairs(self, uid):
dn = 'uid=%s,%s' % (uid, FLAGS.ldap_subtree)
attrs = self.find_objects(dn, '(objectclass=novaKeyPair)')
attrs = self.find_objects(self.__uid_to_dn(uid), '(objectclass=novaKeyPair)')
return [self.__to_key_pair(uid, attr) for attr in attrs]
def find_user(self, name):
dn = 'uid=%s,%s' % (name, FLAGS.ldap_subtree)
attr = self.find_object(dn, '(objectclass=novaUser)')
def find_projects(self):
attrs = self.find_objects(FLAGS.project_ldap_subtree, '(objectclass=novaProject)')
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)
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):
return self.find_user(name) != None
def find_key_pair(self, uid, key_name):
dn = 'cn=%s,uid=%s,%s' % (key_name,
uid,
FLAGS.ldap_subtree)
attr = self.find_object(dn, '(objectclass=novaKeyPair)')
return self.__to_key_pair(uid, attr)
def key_pair_exists(self, uid, key_name):
return self.find_key_pair(uid, key_name) != None
def project_exists(self, name):
return self.find_project(name) != None
def group_exists(self, dn):
return self.find_group(dn) != None
def delete_key_pairs(self, uid):
keys = self.find_key_pairs(uid)
@ -357,12 +443,9 @@ class LDAPWrapper(object):
for key in keys:
self.delete_key_pair(uid, key.name)
def key_pair_exists(self, uid, key_name):
return self.find_key_pair(uid, key_name) != None
def create_user(self, name, access_key, secret_key, is_admin):
if self.user_exists(name):
raise UserError("LDAP user " + name + " already exists")
raise exception.Duplicate("LDAP user %s already exists" % name)
attr = [
('objectclass', ['person',
'organizationalPerson',
@ -376,22 +459,115 @@ class LDAPWrapper(object):
('accessKey', [access_key]),
('isAdmin', [str(is_admin).upper()]),
]
self.conn.add_s('uid=%s,%s' % (name, FLAGS.ldap_subtree),
attr)
self.conn.add_s(self.__uid_to_dn(name), attr)
return self.__to_user(dict(attr))
def create_project(self, name, project_manager):
# PM can be user object or string containing DN
pass
def create_project(self, name, manager_uid, description, member_uids = None):
if self.project_exists(name):
raise exception.Duplicate("Project can't be created because project %s already exists" % name)
if not self.user_exists(manager_uid):
raise exception.NotFound("Project can't be created because manager %s doesn't exist" % manager_uid)
manager_dn = self.__uid_to_dn(manager_uid)
members = []
if member_uids != None:
for member_uid in member_uids:
if not self.user_exists(member_uid):
raise exception.NotFound("Project can't be created because user %s doesn't exist" % member_uid)
members.append(self.__uid_to_dn(member_uid))
# always add the manager as a member because members is required
if not manager_dn in members:
members.append(manager_dn)
attr = [
('objectclass', ['novaProject']),
('cn', [name]),
('description', [description]),
('projectManager', [manager_dn]),
('member', members)
]
self.conn.add_s('cn=%s,%s' % (name, FLAGS.project_ldap_subtree), attr)
return self.__to_project(dict(attr))
def is_member_of(self, name, group):
return True
def add_to_project(self, uid, project_id):
dn = 'cn=%s,%s' % (project_id, FLAGS.project_ldap_subtree)
return self.add_to_group(uid, dn)
def add_to_group(self, name, group):
pass
def remove_from_project(self, uid, project_id):
dn = 'cn=%s,%s' % (project_id, FLAGS.project_ldap_subtree)
return self.remove_from_group(uid, dn)
def remove_from_group(self, name, group):
pass
def is_in_project(self, uid, project_id):
dn = 'cn=%s,%s' % (project_id, FLAGS.project_ldap_subtree)
return self.is_in_group(uid, dn)
def __create_group(self, group_dn, name, uid, description, member_uids = None):
if self.group_exists(name):
raise exception.Duplicate("Group can't be created because group %s already exists" % name)
members = []
if member_uids != None:
for member_uid in member_uids:
if not self.user_exists(member_uid):
raise exception.NotFound("Group can't be created because user %s doesn't exist" % member_uid)
members.append(self.__uid_to_dn(member_uid))
dn = self.__uid_to_dn(uid)
if not dn in members:
members.append(dn)
attr = [
('objectclass', ['groupOfNames']),
('cn', [name]),
('description', [description]),
('member', members)
]
self.conn.add_s(group_dn, attr)
return self.__to_group(dict(attr))
def is_in_group(self, uid, group_dn):
if not self.user_exists(uid):
raise exception.NotFound("User %s can't be searched in group becuase the user doesn't exist" % (uid,))
if not self.group_exists(group_dn):
return False
res = self.find_object(group_dn,
'(member=%s)' % self.__uid_to_dn(uid))
return res != None
def add_to_group(self, uid, group_dn):
if not self.user_exists(uid):
raise exception.NotFound("User %s can't be added to the group becuase the user doesn't exist" % (uid,))
if not self.group_exists(group_dn):
raise exception.NotFound("The group at dn %s doesn't exist" % (group_dn,))
if self.is_in_group(uid, group_dn):
raise exception.Duplicate("User %s is already a member of the group %s" % (uid, group_dn))
attr = [
(ldap.MOD_ADD, 'member', self.__uid_to_dn(uid))
]
self.conn.modify_s(group_dn, attr)
def remove_from_group(self, uid, group_dn):
if not self.group_exists(group_dn):
raise exception.NotFound("The group at dn %s doesn't exist" % (group_dn,))
if not self.user_exists(uid):
raise exception.NotFound("User %s can't be removed from the group because the user doesn't exist" % (uid,))
if not self.is_in_group(uid, group_dn):
raise exception.NotFound("User %s is not a member of the group" % (uid,))
attr = [
(ldap.MOD_DELETE, 'member', self.__uid_to_dn(uid))
]
try:
self.conn.modify_s(group_dn, attr)
except ldap.OBJECT_CLASS_VIOLATION:
logging.debug("Attempted to remove the last member of a group. Deleting the group instead.")
self.delete_group(group_dn)
def remove_from_all(self, uid):
# FIXME(vish): what if deleted user is a project manager?
if not self.user_exists(uid):
raise exception.NotFound("User %s can't be removed from all because the user doesn't exist" % (uid,))
dn = self.__uid_to_dn(uid)
attr = [
(ldap.MOD_DELETE, 'member', dn)
]
projects = self.find_groups_with_member(FLAGS.project_ldap_subtree, dn)
for project in projects:
self.conn.modify_s('cn=%s,%s' % (project.id, FLAGS.project_ldap_subtree), attr)
def create_key_pair(self, uid, key_name, public_key, fingerprint):
"""create's a public key in the directory underneath the user"""
@ -403,41 +579,46 @@ class LDAPWrapper(object):
('sshPublicKey', [public_key]),
('keyFingerprint', [fingerprint]),
]
self.conn.add_s('cn=%s,uid=%s,%s' % (key_name,
uid,
FLAGS.ldap_subtree),
attr)
self.conn.add_s('cn=%s,%s' % (key_name,
self.__uid_to_dn(uid)),
attr)
return self.__to_key_pair(uid, dict(attr))
def find_user_by_access_key(self, access):
query = '(' + 'accessKey' + '=' + access + ')'
dn = FLAGS.ldap_subtree
query = '(accessKey=%s)' % access
dn = FLAGS.user_ldap_subtree
return self.__to_user(self.find_object(dn, query))
def delete_user(self, uid):
if not self.user_exists(uid):
raise exception.NotFound("User %s doesn't exist" % uid)
self.delete_key_pairs(uid)
self.remove_from_all(uid)
self.conn.delete_s('uid=%s,%s' % (uid,
FLAGS.user_ldap_subtree))
def delete_key_pair(self, uid, key_name):
if not self.key_pair_exists(uid, key_name):
raise UserError("Key Pair " +
key_name +
" doesn't exist for user " +
uid)
raise exception.NotFound("Key Pair %s doesn't exist for user %s" %
(key_name, uid))
self.conn.delete_s('cn=%s,uid=%s,%s' % (key_name, uid,
FLAGS.ldap_subtree))
FLAGS.user_ldap_subtree))
def delete_user(self, name):
if not self.user_exists(name):
raise UserError("User " +
name +
" doesn't exist")
self.delete_key_pairs(name)
self.conn.delete_s('uid=%s,%s' % (name,
FLAGS.ldap_subtree))
def delete_group(self, group_dn):
if not self.group_exists(group_dn):
raise exception.NotFound("Group at dn %s doesn't exist" % group_dn)
self.conn.delete_s(group_dn)
def delete_project(self, name):
project_dn = 'cn=%s,%s' % (name, FLAGS.project_ldap_subtree)
self.delete_group(project_dn)
def __to_user(self, attr):
if attr == None:
return None
return User(
id = attr['uid'][0],
name = attr['uid'][0],
name = attr['cn'][0],
access = attr['accessKey'][0],
secret = attr['secretKey'][0],
admin = (attr['isAdmin'][0] == 'TRUE')
@ -447,8 +628,35 @@ class LDAPWrapper(object):
if attr == None:
return None
return KeyPair(
owner = owner,
name = attr['cn'][0],
id = attr['cn'][0],
owner_id = owner,
public_key = attr['sshPublicKey'][0],
fingerprint = attr['keyFingerprint'][0],
)
def __to_group(self, attr):
if attr == None:
return None
member_dns = attr.get('member', [])
return Group(
id = attr['cn'][0],
description = attr.get('description', [None])[0],
member_ids = [self.__dn_to_uid(x) for x in member_dns]
)
def __to_project(self, attr):
if attr == None:
return None
member_dns = attr.get('member', [])
return Project(
id = attr['cn'][0],
project_manager_id = self.__dn_to_uid(attr['projectManager'][0]),
description = attr.get('description', [None])[0],
member_ids = [self.__dn_to_uid(x) for x in member_dns]
)
def __dn_to_uid(self, dn):
return dn.split(',')[0].split('=')[1]
def __uid_to_dn(self, dn):
return 'uid=%s,%s' % (dn, FLAGS.user_ldap_subtree)

View File

@ -1,10 +1,25 @@
# 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 nova.utils
import signal
import subprocess
from nova import utils
# todo(ja): does the definition of network_path belong here?
from nova import flags
@ -12,16 +27,16 @@ FLAGS=flags.FLAGS
def execute(cmd):
if FLAGS.fake_network:
print "FAKE NET: %s" % cmd
logging.debug("FAKE NET: %s" % cmd)
return "fake", 0
else:
nova.utils.execute(cmd)
utils.execute(cmd)
def runthis(desc, cmd):
if FLAGS.fake_network:
execute(cmd)
else:
nova.utils.runthis(desc,cmd)
utils.runthis(desc,cmd)
def Popen(cmd):
if FLAGS.fake_network:
@ -110,7 +125,7 @@ def start_dnsmasq(network):
os.kill(pid, signal.SIGHUP)
return
except Exception, e:
logging.debug("Killing dnsmasq threw %s", e)
logging.debug("Hupping dnsmasq threw %s", e)
# otherwise delete the existing leases file and start dnsmasq
lease_file = dhcp_file(network.vlan, 'leases')
@ -124,7 +139,10 @@ def stop_dnsmasq(network):
pid = dnsmasq_pid_for(network)
if pid:
os.kill(pid, signal.SIGTERM)
try:
os.kill(pid, signal.SIGTERM)
except Exception, e:
logging.debug("Killing dnsmasq threw %s", e)
def dhcp_file(vlan, kind):
""" return path to a pid, leases or conf file for a vlan """

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]
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -24,7 +24,7 @@ InstanceDirectory manager.
True
>>> inst = InstDir['i-123']
>>> inst['ip'] = "192.168.0.3"
>>> inst['owner_id'] = "projectA"
>>> inst['project_id'] = "projectA"
>>> inst.save()
True
@ -46,6 +46,8 @@ from nova import utils
FLAGS = flags.FLAGS
flags.DEFINE_string('instances_prefix', 'compute-',
'prefix for keepers for instances')
# TODO(ja): singleton instance of the directory
class InstanceDirectory(object):
@ -62,11 +64,12 @@ class InstanceDirectory(object):
def by_project(self, project):
""" returns a list of instance objects for a project """
for instance_id in self.keeper['project:%s:instances' % project]:
for instance_id in self.keeper.smembers('project:%s:instances' % project):
yield Instance(instance_id)
def by_node(self, node_id):
""" returns a list of instances for a node """
for instance in self.all:
if instance['node_name'] == node_id:
yield instance
@ -83,17 +86,13 @@ class InstanceDirectory(object):
pass
def exists(self, instance_id):
if instance_id in self.keeper['instances']:
return True
return False
return self.keeper.set_is_member('instances', instance_id)
@property
def all(self):
""" returns a list of all instances """
instances = self.keeper['instances']
if instances != None:
for instance_id in self.keeper['instances']:
yield Instance(instance_id)
for instance_id in self.keeper.set_members('instances'):
yield Instance(instance_id)
def new(self):
""" returns an empty Instance object, with ID """
@ -114,10 +113,12 @@ class Instance(object):
if self.state:
self.initial_state = self.state
else:
self.state = {'state' : 'pending',
'instance_id' : instance_id,
'node_name' : 'unassigned',
'owner_id' : 'unassigned' }
self.state = {'state': 'pending',
'instance_id': instance_id,
'node_name': 'unassigned',
'project_id': 'unassigned',
'user_id': 'unassigned'
}
@property
def __redis_key(self):
@ -143,8 +144,8 @@ class Instance(object):
def save(self):
""" update the directory with the state from this instance
make sure you've set the owner_id before you call save
for the first time.
make sure you've set the project_id and user_id before you call save
for the first time.
"""
# TODO(ja): implement hmset in redis-py and use it
# instead of multiple calls to hset
@ -157,17 +158,23 @@ class Instance(object):
state[key] = val
self.keeper[self.__redis_key] = state
if self.initial_state == {}:
self.keeper.set_add('project:%s:instances' % self.state['owner_id'],
self.keeper.set_add('project:%s:instances' % self.project,
self.instance_id)
self.keeper.set_add('instances', self.instance_id)
self.initial_state = self.state
return True
@property
def project(self):
if self.state.get('project_id', None):
return self.state['project_id']
return self.state.get('owner_id', 'unassigned')
def destroy(self):
""" deletes all related records from datastore.
does NOT do anything to running libvirt state.
does NOT do anything to running libvirt state.
"""
self.keeper.set_remove('project:%s:instances' % self.state['owner_id'],
self.keeper.set_remove('project:%s:instances' % self.project,
self.instance_id)
del self.keeper[self.__redis_key]
self.keeper.set_remove('instances', self.instance_id)
@ -184,18 +191,18 @@ class Instance(object):
pass
# class Reservation(object):
# """ ORM wrapper for a batch of launched instances """
# def __init__(self):
# pass
# """ ORM wrapper for a batch of launched instances """
# def __init__(self):
# pass
#
# def userdata(self):
# """ """
# pass
# def userdata(self):
# """ """
# pass
#
#
# class NodeDirectory(object):
# def __init__(self):
# pass
# def __init__(self):
# pass
#
if __name__ == "__main__":

View File

@ -26,7 +26,6 @@ from nova import vendor
import IPy
from nova import datastore
import nova.exception
from nova.compute import exception
from nova import flags
from nova import utils
@ -113,13 +112,13 @@ class Network(object):
for idx in range(3, len(self.network)-2):
yield self.network[idx]
def allocate_ip(self, user_id, mac):
def allocate_ip(self, user_id, project_id, mac):
for ip in self.range():
address = str(ip)
if not address in self.hosts.keys():
logging.debug("Allocating IP %s to %s" % (address, user_id))
logging.debug("Allocating IP %s to %s" % (address, project_id))
self.hosts[address] = {
"address" : address, "user_id" : user_id, 'mac' : mac
"address" : address, "user_id": user_id, "project_id" : project_id, 'mac' : mac
}
self.express(address=address)
return address
@ -238,7 +237,6 @@ class DHCPNetwork(VirtNetwork):
else:
linux_net.start_dnsmasq(self)
class PrivateNetwork(DHCPNetwork):
def __init__(self, **kwargs):
super(PrivateNetwork, self).__init__(**kwargs)
@ -249,23 +247,18 @@ class PrivateNetwork(DHCPNetwork):
'network': self.network_str,
'hosts': self.hosts}
def express(self, *args, **kwargs):
super(PrivateNetwork, self).express(*args, **kwargs)
class PublicNetwork(Network):
def __init__(self, network="192.168.216.0/24", **kwargs):
super(PublicNetwork, self).__init__(network=network, **kwargs)
self.express()
def allocate_ip(self, user_id, mac):
def allocate_ip(self, user_id, project_id, mac):
for ip in self.range():
address = str(ip)
if not address in self.hosts.keys():
logging.debug("Allocating IP %s to %s" % (address, user_id))
logging.debug("Allocating IP %s to %s" % (address, project_id))
self.hosts[address] = {
"address" : address, "user_id" : user_id, 'mac' : mac
"address" : address, "user_id": user_id, "project_id" : project_id, 'mac' : mac
}
self.express(address=address)
return address
@ -354,8 +347,8 @@ class VlanPool(object):
self.vlans = kwargs.get('vlans', {})
self.vlanpool = {}
self.manager = users.UserManager.instance()
for user_id, vlan in self.vlans.iteritems():
self.vlanpool[vlan] = user_id
for project_id, vlan in self.vlans.iteritems():
self.vlanpool[vlan] = project_id
def to_dict(self):
return {'vlans': self.vlans}
@ -380,25 +373,25 @@ class VlanPool(object):
parsed = json.loads(json_string)
return cls.from_dict(parsed)
def assign_vlan(self, user_id, vlan):
logging.debug("Assigning vlan %s to user %s" % (vlan, user_id))
self.vlans[user_id] = vlan
self.vlanpool[vlan] = user_id
return self.vlans[user_id]
def assign_vlan(self, project_id, vlan):
logging.debug("Assigning vlan %s to project %s" % (vlan, project_id))
self.vlans[project_id] = vlan
self.vlanpool[vlan] = project_id
return self.vlans[project_id]
def next(self, user_id):
for old_user_id, vlan in self.vlans.iteritems():
if not self.manager.get_user(old_user_id):
_get_keeper()["%s-default" % old_user_id] = {}
del _get_keeper()["%s-default" % old_user_id]
del self.vlans[old_user_id]
return self.assign_vlan(user_id, vlan)
def next(self, project_id):
for old_project_id, vlan in self.vlans.iteritems():
if not self.manager.get_project(old_project_id):
_get_keeper()["%s-default" % old_project_id] = {}
del _get_keeper()["%s-default" % old_project_id]
del self.vlans[old_project_id]
return self.assign_vlan(project_id, vlan)
vlans = self.vlanpool.keys()
vlans.append(self.start)
nextvlan = max(vlans) + 1
if nextvlan == self.end:
raise exception.AddressNotAllocated("Out of VLANs")
return self.assign_vlan(user_id, nextvlan)
return self.assign_vlan(project_id, nextvlan)
class NetworkController(object):
@ -442,37 +435,37 @@ class NetworkController(object):
if address_record.get(u'instance_id', 'free') == instance_id:
return address_record[u'address']
def get_users_network(self, user_id):
""" get a user's private network, allocating one if needed """
def get_project_network(self, project_id):
""" get a project's private network, allocating one if needed """
user = self.manager.get_user(user_id)
if not user:
raise Exception("User %s doesn't exist, uhoh." % user_id)
usernet = self.get_network_from_name("%s-default" % user_id)
if not usernet:
project = self.manager.get_project(project_id)
if not project:
raise Exception("Project %s doesn't exist, uhoh." % project_id)
project_net = self.get_network_from_name("%s-default" % project_id)
if not project_net:
pool = self.vlan_pool
vlan = pool.next(user_id)
vlan = pool.next(project_id)
private_pool = NetworkPool()
network_str = private_pool.get_from_vlan(vlan)
logging.debug("Constructing network %s and %s for %s" % (network_str, vlan, user_id))
usernet = PrivateNetwork(
logging.debug("Constructing network %s and %s for %s" % (network_str, vlan, project_id))
project_net = PrivateNetwork(
network=network_str,
vlan=vlan)
_get_keeper()["%s-default" % user_id] = usernet.to_dict()
_get_keeper()["%s-default" % project_id] = project_net.to_dict()
_get_keeper()['vlans'] = pool.to_dict()
return usernet
return project_net
def allocate_address(self, user_id, mac=None, type=PrivateNetwork):
def allocate_address(self, user_id, project_id, mac=None, type=PrivateNetwork):
ip = None
net_name = None
if type == PrivateNetwork:
net = self.get_users_network(user_id)
ip = net.allocate_ip(user_id, mac)
net = self.get_project_network(project_id)
ip = net.allocate_ip(user_id, project_id, mac)
net_name = net.name
_get_keeper()["%s-default" % user_id] = net.to_dict()
_get_keeper()["%s-default" % project_id] = net.to_dict()
else:
net = self.public_net
ip = net.allocate_ip(user_id, mac)
ip = net.allocate_ip(user_id, project_id, mac)
net_name = net.name
_get_keeper()['public'] = net.to_dict()
return (ip, net_name)
@ -483,19 +476,19 @@ class NetworkController(object):
rv = net.deallocate_ip(str(address))
_get_keeper()['public'] = net.to_dict()
return rv
for user in self.manager.get_users():
if address in self.get_users_network(user.id).network:
net = self.get_users_network(user.id)
for project in self.manager.get_projects():
if address in self.get_project_network(project.id).network:
net = self.get_project_network(project.id)
rv = net.deallocate_ip(str(address))
_get_keeper()["%s-default" % user.id] = net.to_dict()
_get_keeper()["%s-default" % project.id] = net.to_dict()
return rv
raise exception.AddressNotAllocated()
def describe_addresses(self, type=PrivateNetwork):
if type == PrivateNetwork:
addresses = []
for user in self.manager.get_users():
addresses.extend(self.get_users_network(user.id).list_addresses())
for project in self.manager.get_projects():
addresses.extend(self.get_project_network(project.id).list_addresses())
return addresses
return self.public_net.list_addresses()
@ -512,8 +505,8 @@ class NetworkController(object):
return rv
def express(self,address=None):
for user in self.manager.get_users():
self.get_users_network(user.id).express()
for project in self.manager.get_projects():
self.get_project_network(project.id).express()
def report_state(self):
pass

View File

@ -57,8 +57,6 @@ flags.DEFINE_bool('use_s3', True,
'whether to get images from s3 or use local copy')
flags.DEFINE_string('instances_path', utils.abspath('../instances'),
'where instances are stored on disk')
flags.DEFINE_string('instances_prefix', 'compute-',
'prefix for keepers for instances')
INSTANCE_TYPES = {}
INSTANCE_TYPES['m1.tiny'] = {'memory_mb': 512, 'vcpus': 1, 'local_gb': 0}
@ -73,7 +71,6 @@ INSTANCE_TYPES['c1.medium'] = {'memory_mb': 2048, 'vcpus': 4, 'local_gb': 10}
# be a singleton
PROCESS_POOL_SIZE = 4
class Node(object, service.Service):
"""
Manages the running instances.
@ -240,7 +237,6 @@ def _create_image(data, libvirt_xml):
def image_url(path):
return "%s:%s/_images/%s" % (FLAGS.s3_host, FLAGS.s3_port, path)
logging.info(basepath('disk'))
try:
os.makedirs(data['basepath'])
@ -309,15 +305,17 @@ class Instance(object):
return (self.state == Instance.RUNNING or self.state == 'running')
def __init__(self, conn, pool, name, data):
# TODO(termie): pool should probably be a singleton instead of being passed
# here and in the classmethods
""" spawn an instance with a given name """
# TODO(termie): pool should probably be a singleton instead of being passed
# here and in the classmethods
self._pool = pool
self._conn = conn
# TODO(vish): this can be removed after data has been updated
# data doesn't seem to have a working iterator so in doesn't work
if not data.get('owner_id', None) is None:
data['user_id'] = data['owner_id']
data['project_id'] = data['owner_id']
self.datamodel = data
print data
# NOTE(termie): to be passed to multiprocess self._s must be
# pickle-able by cPickle
@ -344,7 +342,8 @@ class Instance(object):
self._s['image_id'] = data.get('image_id', FLAGS.default_image)
self._s['kernel_id'] = data.get('kernel_id', FLAGS.default_kernel)
self._s['ramdisk_id'] = data.get('ramdisk_id', FLAGS.default_ramdisk)
self._s['owner_id'] = data.get('owner_id', '')
self._s['user_id'] = data.get('user_id', None)
self._s['project_id'] = data.get('project_id', self._s['user_id'])
self._s['node_name'] = data.get('node_name', '')
self._s['user_data'] = data.get('user_data', '')
self._s['ami_launch_index'] = data.get('ami_launch_index', None)
@ -352,7 +351,6 @@ class Instance(object):
self._s['reservation_id'] = data.get('reservation_id', None)
# self._s['state'] = Instance.NOSTATE
self._s['state'] = data.get('state', Instance.NOSTATE)
self._s['key_data'] = data.get('key_data', None)
# TODO: we may not need to save the next few
@ -415,7 +413,6 @@ class Instance(object):
def update_state(self):
info = self.info()
self._s['state'] = info['state']
self.datamodel['state'] = info['state']
self.datamodel['node_name'] = FLAGS.node_name
self.datamodel.save()
@ -427,7 +424,6 @@ class Instance(object):
raise exception.Error('trying to destroy already destroyed'
' instance: %s' % self.name)
self._s['state'] = Instance.SHUTDOWN
self.datamodel['state'] = 'shutting_down'
self.datamodel.save()
try:

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_boolean('use_intermediate_ca', False, 'Should we use intermediate CAs for each project?')
def ca_path(username):
if username:
return "%s/INTER/%s/cacert.pem" % (FLAGS.ca_path, username)
def ca_path(project_id):
if project_id:
return "%s/INTER/%s/cacert.pem" % (FLAGS.ca_path, project_id)
return "%s/cacert.pem" % (FLAGS.ca_path)
def fetch_ca(username=None, chain=True):
def fetch_ca(project_id=None, chain=True):
if not FLAGS.use_intermediate_ca:
username = None
project_id = None
buffer = ""
if username:
with open(ca_path(username),"r") as cafile:
if project_id:
with open(ca_path(project_id),"r") as cafile:
buffer += cafile.read()
if username and not chain:
return buffer
if not chain:
return buffer
with open(ca_path(None),"r") as cafile:
buffer += cafile.read()
return buffer
@ -104,7 +103,6 @@ def generate_x509_cert(subject="/C=US/ST=California/L=The Mission/O=CloudFed/OU=
shutil.rmtree(tmpdir)
return (private_key, csr)
def sign_csr(csr_text, intermediate=None):
if not FLAGS.use_intermediate_ca:
intermediate = None
@ -118,7 +116,6 @@ def sign_csr(csr_text, intermediate=None):
os.chdir(start)
return _sign_csr(csr_text, user_ca)
def _sign_csr(csr_text, ca_folder):
tmpfolder = tempfile.mkdtemp()
csrfile = open("%s/inbound.csr" % (tmpfolder), "w")
@ -197,7 +194,7 @@ def mkcacert(subject='nova', years=1):
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.

View File

@ -264,6 +264,12 @@ class SqliteKeeper(object):
group.remove(value)
self[item] = group
def set_members(self, item):
group = self[item]
if not group:
group = []
return group
def set_fetch(self, item):
# TODO(termie): I don't really know what set_fetch is supposed to do
group = self[item]
@ -354,6 +360,10 @@ class RedisKeeper(object):
item = slugify(item, self.prefix)
return Redis.instance().srem(item, json.dumps(value))
def set_members(self, item):
item = slugify(item, self.prefix)
return [json.loads(v) for v in Redis.instance().smembers(item)]
def set_fetch(self, item):
item = slugify(item, self.prefix)
for obj in Redis.instance().sinter([item]):

View File

@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC]
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -61,7 +61,6 @@ class AdminController(object):
API Controller for users, node status, and worker mgmt.
Trivial admin_only wrapper will be replaced with RBAC,
allowing project managers to administer project users.
"""
def __init__(self, user_manager, node_manager=None):
self.user_manager = user_manager
@ -80,16 +79,14 @@ class AdminController(object):
def describe_users(self, _context, **_kwargs):
"""Returns all users - should be changed to deal with a list.
"""
return {'userSet':
return {'userSet':
[user_dict(u) for u in self.user_manager.get_users()] }
@admin_only
def register_user(self, _context, name, **_kwargs):
""" Creates a new user, and returns generated credentials.
"""
self.user_manager.create_user(name)
return user_dict(self.user_manager.get_user(name))
return user_dict(self.user_manager.create_user(name))
@admin_only
def deregister_user(self, _context, name, **_kwargs):
@ -102,14 +99,17 @@ class AdminController(object):
return True
@admin_only
def generate_x509_for_user(self, _context, name, **_kwargs):
def generate_x509_for_user(self, _context, name, project=None, **kwargs):
"""Generates and returns an x509 certificate for a single user.
Is usually called from a client that will wrap this with
access and secret key info, and return a zip file.
"""
if project is None:
project = name
project = self.user_manager.get_project(project)
user = self.user_manager.get_user(name)
return user_dict(user, base64.b64encode(user.get_credentials()))
return user_dict(user, base64.b64encode(project.get_credentials(user)))
@admin_only
def describe_nodes(self, _context, **_kwargs):
"""Returns status info for all nodes. Includes:
@ -120,12 +120,11 @@ class AdminController(object):
* DHCP servers running
* Iptables / bridges
"""
return {'nodeSet':
[node_dict(n) for n in self.node_manager.get_nodes()] }
return {'nodeSet':
[node_dict(n) for n in self.node_manager.get_nodes()] }
@admin_only
def describe_node(self, _context, name, **_kwargs):
"""Returns status info for single node.
"""
return node_dict(self.node_manager.get_node(name))

View File

@ -1,13 +1,13 @@
#!/usr/bin/python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC]
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -41,7 +41,6 @@ from nova.auth import users
FLAGS = flags.FLAGS
flags.DEFINE_integer('cc_port', 8773, 'cloud controller port')
_log = logging.getLogger("api")
_log.setLevel(logging.DEBUG)
@ -63,9 +62,10 @@ def _underscore_to_xmlcase(str):
class APIRequestContext(object):
def __init__(self, handler, user):
def __init__(self, handler, user, project):
self.handler = handler
self.user = user
self.project = project
self.request_id = ''.join(
[random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-')
for x in xrange(20)]
@ -73,13 +73,11 @@ class APIRequestContext(object):
class APIRequest(object):
def __init__(self, handler, controller, action):
self.handler = handler
def __init__(self, controller, action):
self.controller = controller
self.action = action
def send(self, user, **kwargs):
context = APIRequestContext(self.handler, user)
def send(self, context, **kwargs):
try:
method = getattr(self.controller,
@ -227,7 +225,6 @@ class MetadataRequestHandler(tornado.web.RequestHandler):
self.print_data(data)
self.finish()
class APIRequestHandler(tornado.web.RequestHandler):
def get(self, controller_name):
self.execute(controller_name)
@ -257,7 +254,7 @@ class APIRequestHandler(tornado.web.RequestHandler):
# Get requested action and remove authentication args for final request.
try:
action = args.pop('Action')[0]
args.pop('AWSAccessKeyId')
access = args.pop('AWSAccessKeyId')[0]
args.pop('SignatureMethod')
args.pop('SignatureVersion')
args.pop('Version')
@ -266,15 +263,18 @@ class APIRequestHandler(tornado.web.RequestHandler):
raise tornado.web.HTTPError(400)
# Authenticate the request.
user = self.application.user_manager.authenticate(
auth_params,
signature,
self.request.method,
self.request.host,
self.request.path
)
try:
(user, project) = users.UserManager.instance().authenticate(
access,
signature,
auth_params,
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)
_log.debug('action: %s' % action)
@ -282,8 +282,9 @@ class APIRequestHandler(tornado.web.RequestHandler):
for key, value in args.items():
_log.debug('arg: %s\t\tval: %s' % (key, value))
request = APIRequest(self, controller, action)
d = request.send(user, **args)
request = APIRequest(controller, action)
context = APIRequestContext(self, user, project)
d = request.send(context, **args)
# d.addCallback(utils.debug)
# TODO: Wrap response in AWS XML format

View File

@ -19,6 +19,7 @@ dispatched to other nodes via AMQP RPC. State is via distributed
datastore.
"""
import base64
import json
import logging
import os
@ -58,7 +59,6 @@ class CloudController(object):
sent to the other nodes.
"""
def __init__(self):
self._instances = datastore.Keeper(FLAGS.instances_prefix)
self.instdir = model.InstanceDirectory()
self.network = network.NetworkController()
self.setup()
@ -97,7 +97,7 @@ class CloudController(object):
return self.instdir.by_ip(ip)
def get_metadata(self, ip):
i = self.instdir.by_ip(ip)
i = self.get_instance_by_ip(ip)
if i is None:
return None
if i['key_name']:
@ -145,7 +145,6 @@ class CloudController(object):
data['product-codes'] = i['product_codes']
return data
def describe_availability_zones(self, context, **kwargs):
return {'availabilityZoneInfo': [{'zoneName': 'nova',
'zoneState': 'available'}]}
@ -207,11 +206,9 @@ class CloudController(object):
def get_console_output(self, context, instance_id, **kwargs):
# instance_id is passed in as a list of instances
instance = self.instdir.get(instance_id[0])
instance = self._get_instance(context, instance_id[0])
if instance['state'] == 'pending':
raise exception.ApiError('Cannot get output for pending instance')
if not context.user.is_authorized(instance.get('owner_id', None)):
raise exception.ApiError('Not authorized to view output')
return rpc.call('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
{"method": "get_console_output",
"args" : {"instance_id": instance_id[0]}})
@ -225,7 +222,7 @@ class CloudController(object):
def describe_volumes(self, context, **kwargs):
volumes = []
for volume in self.volumes:
if context.user.is_authorized(volume.get('user_id', None)):
if context.user.is_admin() or volume['project_id'] == context.project.id:
v = self.format_volume(context, volume)
volumes.append(v)
return defer.succeed({'volumeSet': volumes})
@ -252,36 +249,59 @@ class CloudController(object):
"args" : {"size": size,
"user_id": context.user.id}})
def _format_result(result):
volume = self._get_volume(result['result'])
volume = self._get_volume(context, result['result'])
return {'volumeSet': [self.format_volume(context, volume)]}
res.addCallback(_format_result)
return res
def _get_by_id(self, nodes, id):
if nodes == {}:
raise exception.NotFound("%s not found" % id)
for node_name, node in nodes.iteritems():
if node.has_key(id):
return node_name, node[id]
raise exception.NotFound("%s not found" % id)
def _convert_address(self, network_address):
# FIXME(vish): this should go away when network.py stores info properly
address = {}
address['public_ip'] == network_address[u'address']
address['user_id'] == network_address[u'user_id']
address['project_id'] == network_address.get(u'project_id', address['user_id'])
address['instance_id'] == network_address.get(u'instance_id', None)
return address
def _get_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:
if volume['volume_id'] == volume_id:
return volume
if context.user.is_admin() or volume['project_id'] == context.project.id:
return volume
raise exception.NotFound('Volume %s could not be found' % volume_id)
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
volume = self._get_volume(volume_id)
volume = self._get_volume(context, volume_id)
storage_node = volume['node_name']
# TODO: (joshua) Fix volumes to store creator id
if not context.user.is_authorized(volume.get('user_id', None)):
raise exception.ApiError("%s not authorized for %s" %
(context.user.id, volume_id))
instance = self.instdir.get(instance_id)
instance = self._get_instance(context, instance_id)
compute_node = instance['node_name']
if not context.user.is_authorized(instance.get('owner_id', None)):
raise exception.ApiError(message="%s not authorized for %s" %
(context.user.id, instance_id))
aoe_device = volume['aoe_device']
# Needs to get right node controller for attaching to
# TODO: Maybe have another exchange that goes to everyone?
@ -297,24 +317,17 @@ class CloudController(object):
"mountpoint" : device}})
return defer.succeed(True)
def detach_volume(self, context, volume_id, **kwargs):
# TODO(joshua): Make sure the updated state has been received first
volume = self._get_volume(volume_id)
volume = self._get_volume(context, volume_id)
storage_node = volume['node_name']
if not context.user.is_authorized(volume.get('user_id', None)):
raise exception.ApiError("%s not authorized for %s" %
(context.user.id, volume_id))
if 'instance_id' in volume.keys():
instance_id = volume['instance_id']
try:
instance = self.instdir.get(instance_id)
instance = self._get_instance(context, instance_id)
compute_node = instance['node_name']
mountpoint = volume['mountpoint']
if not context.user.is_authorized(
instance.get('owner_id', None)):
raise exception.ApiError(
"%s not authorized for %s" %
(context.user.id, instance_id))
rpc.cast('%s.%s' % (FLAGS.compute_topic, compute_node),
{"method": "detach_volume",
"args" : {"instance_id": instance_id,
@ -332,16 +345,16 @@ class CloudController(object):
return [{str: x} for x in lst]
def describe_instances(self, context, **kwargs):
return defer.succeed(self.format_instances(context.user))
return defer.succeed(self._format_instances(context))
def format_instances(self, user, reservation_id = None):
def _format_instances(self, context, reservation_id = None):
if self.instances == {}:
return {'reservationSet': []}
reservations = {}
for inst in self.instances:
instance = inst.values()[0]
res_id = instance.get('reservation_id', 'Unknown')
if (user.is_authorized(instance.get('owner_id', None))
if ((context.user.is_admin() or context.project.id == instance['project_id'])
and (reservation_id == None or reservation_id == res_id)):
i = {}
i['instance_id'] = instance.get('instance_id', None)
@ -357,7 +370,7 @@ class CloudController(object):
i['public_dns_name'] = i['private_dns_name']
i['dns_name'] = instance.get('dns_name', None)
i['key_name'] = instance.get('key_name', None)
if user.is_admin():
if context.user.is_admin():
i['key_name'] = '%s (%s, %s)' % (i['key_name'],
instance.get('owner_id', None), instance.get('node_name',''))
i['product_codes_set'] = self._convert_to_set(
@ -369,7 +382,7 @@ class CloudController(object):
if not reservations.has_key(res_id):
r = {}
r['reservation_id'] = res_id
r['owner_id'] = instance.get('owner_id', None)
r['owner_id'] = instance.get('project_id', None)
r['group_set'] = self._convert_to_set(
instance.get('groups', None), 'group_id')
r['instances_set'] = []
@ -382,52 +395,52 @@ class CloudController(object):
def describe_addresses(self, context, **kwargs):
return self.format_addresses(context.user)
def format_addresses(self, user):
def format_addresses(self, context):
addresses = []
# TODO(vish): move authorization checking into network.py
for address_record in self.network.describe_addresses(
type=network.PublicNetwork):
for network_address in self.network.describe_addresses(type=network.PublicNetwork):
#logging.debug(address_record)
if user.is_authorized(address_record[u'user_id']):
address = {
'public_ip': address_record[u'address'],
'instance_id' : address_record.get(u'instance_id', 'free')
}
# FIXME: add another field for user id
if user.is_admin():
address['instance_id'] = "%s (%s)" % (
address['instance_id'],
address_record[u'user_id'],
)
addresses.append(address)
address = self._convert_address(network_address)
address_rv = {
'public_ip': address['public_ip'],
'instance_id' : address.get('instance_id', 'free')
}
# FIXME: add another field for user id
if context.user.is_admin():
address_rv['instance_id'] = "%s (%s, %s)" % (
address['instance_id'],
address['user_id'],
address['project_id'],
)
addresses.append(address_rv)
# logging.debug(addresses)
return {'addressesSet': addresses}
def allocate_address(self, context, **kwargs):
# TODO: Verify user is valid?
kwargs['owner_id'] = context.user.id
(address,network_name) = self.network.allocate_address(
context.user.id, type=network.PublicNetwork)
context.user.id, context.project_id, type=network.PublicNetwork)
return defer.succeed({'addressSet': [{'publicIp' : address}]})
def release_address(self, context, **kwargs):
self.network.deallocate_address(kwargs.get('public_ip', None))
def release_address(self, context, public_ip, **kwargs):
address = self._get_address(public_ip)
return defer.succeed({'releaseResponse': ["Address released."]})
def associate_address(self, context, instance_id, **kwargs):
instance = self.instdir.get(instance_id)
instance = self._get_instance(context, instance_id)
rv = self.network.associate_address(
kwargs['public_ip'],
instance['private_dns_name'],
instance_id)
return defer.succeed({'associateResponse': ["Address associated."]})
def disassociate_address(self, context, **kwargs):
rv = self.network.disassociate_address(kwargs['public_ip'])
def disassociate_address(self, context, public_ip, **kwargs):
address = self._get_address(public_ip)
rv = self.network.disassociate_address(public_ip)
# TODO - Strip the IP from the instance
return rv
return defer.succeed({'disassociateResponse': ["Address disassociated."]})
def run_instances(self, context, **kwargs):
image = self._get_image(context, kwargs['image_id'])
logging.debug("Going to run instances...")
reservation_id = utils.generate_uid('r')
launch_time = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
@ -449,11 +462,14 @@ class CloudController(object):
inst['launch_time'] = launch_time
inst['key_data'] = key_data or ''
inst['key_name'] = kwargs.get('key_name', '')
inst['owner_id'] = context.user.id
inst['user_id'] = context.user.id
inst['project_id'] = context.project.id
inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = num
address, _netname = self.network.allocate_address(
inst['owner_id'], mac=inst['mac_address'])
user_id=inst['user_id'],
project_id=inst['project_id'],
mac=inst['mac_address'])
network = self.network.get_users_network(str(context.user.id))
inst['network_str'] = json.dumps(network.to_dict())
inst['bridge_name'] = network.bridge_name
@ -466,82 +482,77 @@ class CloudController(object):
logging.debug("Casting to node for %s's instance with IP of %s" %
(context.user.name, inst['private_dns_name']))
# TODO: Make the NetworkComputeNode figure out the network name from ip.
return defer.succeed(self.format_instances(
return defer.succeed(self._format_instances(
context.user, reservation_id))
def terminate_instances(self, context, instance_id, **kwargs):
logging.debug("Going to start terminating instances")
# TODO: return error if not authorized
for i in instance_id:
logging.debug("Going to try and terminate %s" % i)
instance = self.instdir.get(i)
#if instance['state'] == 'pending':
# raise exception.ApiError('Cannot terminate pending instance')
if context.user.is_authorized(instance.get('owner_id', None)):
try:
instance = self._get_instance(context, i)
except exception.NotFound:
logging.warning("Instance %s was not found during terminate" % i)
continue
try:
self.network.disassociate_address(
instance.get('public_dns_name', 'bork'))
except:
pass
if instance.get('private_dns_name', None):
logging.debug("Deallocating address %s" % instance.get('private_dns_name', None))
try:
self.network.disassociate_address(
instance.get('public_dns_name', 'bork'))
except:
self.network.deallocate_address(instance.get('private_dns_name', None))
except Exception, _err:
pass
if instance.get('private_dns_name', None):
logging.debug("Deallocating address %s" % instance.get('private_dns_name', None))
try:
self.network.deallocate_address(instance.get('private_dns_name', None))
except Exception, _err:
pass
if instance.get('node_name', 'unassigned') != 'unassigned': #It's also internal default
rpc.cast('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
if instance.get('node_name', 'unassigned') != 'unassigned': #It's also internal default
rpc.cast('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
{"method": "terminate_instance",
"args" : {"instance_id": i}})
else:
instance.destroy()
else:
instance.destroy()
return defer.succeed(True)
def reboot_instances(self, context, instance_id, **kwargs):
# TODO: return error if not authorized
"""instance_id is a list of instance ids"""
for i in instance_id:
instance = self.instdir.get(i)
instance = self._get_instance(context, i)
if instance['state'] == 'pending':
raise exception.ApiError('Cannot reboot pending instance')
if context.user.is_authorized(instance.get('owner_id', None)):
rpc.cast('%s.%s' % (FLAGS.node_topic, instance['node_name']),
rpc.cast('%s.%s' % (FLAGS.node_topic, instance['node_name']),
{"method": "reboot_instance",
"args" : {"instance_id": i}})
return defer.succeed(True)
def delete_volume(self, context, volume_id, **kwargs):
# TODO: return error if not authorized
volume = self._get_volume(volume_id)
volume = self._get_volume(context, volume_id)
storage_node = volume['node_name']
if context.user.is_authorized(volume.get('user_id', None)):
rpc.cast('%s.%s' % (FLAGS.storage_topic, storage_node),
{"method": "delete_volume",
"args" : {"volume_id": volume_id}})
rpc.cast('%s.%s' % (FLAGS.storage_topic, storage_node),
{"method": "delete_volume",
"args" : {"volume_id": volume_id}})
return defer.succeed(True)
def describe_images(self, context, image_id=None, **kwargs):
imageSet = images.list(context.user)
if not image_id is None:
imageSet = [i for i in imageSet if i['imageId'] in image_id]
# The objectstore does its own authorization for describe
imageSet = images.list(context, image_id)
return defer.succeed({'imagesSet': imageSet})
def deregister_image(self, context, image_id, **kwargs):
images.deregister(context.user, image_id)
# FIXME: should the objectstore be doing these authorization checks?
images.deregister(context, image_id)
return defer.succeed({'imageId': image_id})
def register_image(self, context, image_location=None, **kwargs):
# FIXME: should the objectstore be doing these authorization checks?
if image_location is None and kwargs.has_key('name'):
image_location = kwargs['name']
image_id = images.register(context.user, image_location)
image_id = images.register(context, image_location)
logging.debug("Registered %s as %s" % (image_location, image_id))
return defer.succeed({'imageId': image_id})
def modify_image_attribute(self, context, image_id,
attribute, operation_type, **kwargs):
def modify_image_attribute(self, context, image_id, attribute, operation_type, **kwargs):
if attribute != 'launchPermission':
raise exception.ApiError('only launchPermission is supported')
if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all':

View File

@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC]
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -14,7 +14,7 @@
# limitations under the License.
"""
Proxy AMI-related calls from the cloud controller, to the running
Proxy AMI-related calls from the cloud controller, to the running
objectstore daemon.
"""
@ -31,9 +31,8 @@ from nova import utils
FLAGS = flags.FLAGS
def modify(user, image_id, operation):
conn(user).make_request(
def modify(context, image_id, operation):
conn(context).make_request(
method='POST',
bucket='_images',
query_args=qs({'image_id': image_id, 'operation': operation}))
@ -41,11 +40,11 @@ def modify(user, image_id, operation):
return True
def register(user, image_location):
def register(context, image_location):
""" rpc call to register a new image based from a manifest """
image_id = utils.generate_uid('ami')
conn(user).make_request(
conn(context).make_request(
method='PUT',
bucket='_images',
query_args=qs({'image_location': image_location,
@ -53,32 +52,32 @@ def register(user, image_location):
return image_id
def list(user, filter_list=[]):
def list(context, filter_list=[]):
""" return a list of all images that a user can see
optionally filtered by a list of image_id """
# FIXME: send along the list of only_images to check for
response = conn(user).make_request(
response = conn(context).make_request(
method='GET',
bucket='_images')
return json.loads(response.read())
result = json.loads(response.read())
if not filter_list is None:
return [i for i in result if i['imageId'] in filter_list]
return result
def deregister(user, image_id):
def deregister(context, image_id):
""" unregister an image """
conn(user).make_request(
conn(context).make_request(
method='DELETE',
bucket='_images',
query_args=qs({'image_id': image_id}))
def conn(user):
def conn(context):
return boto.s3.connection.S3Connection (
aws_access_key_id=user.access,
aws_secret_access_key=user.secret,
aws_access_key_id='%s:%s' % (context.user.access, context.project.name),
aws_secret_access_key=context.user.secret,
is_secure=False,
calling_format=boto.s3.connection.OrdinaryCallingFormat(),
port=FLAGS.s3_port,

View File

@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC]
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -14,7 +14,7 @@
# limitations under the License.
"""
Nova base exception handling, including decorator for re-raising
Nova base exception handling, including decorator for re-raising
Nova-type exceptions. SHOULD include dedicated exception logging.
"""
@ -23,16 +23,21 @@ import traceback
import sys
class Error(Exception):
pass
def __init__(self, message=None):
super(Error, self).__init__(message)
class ApiError(Error):
class ApiError(Error):
def __init__(self, message='Unknown', code='Unknown'):
self.message = message
self.code = code
super(ApiError, self).__init__('%s: %s'% (code, message))
class NotFound(Error):
pass
class Duplicate(Error):
pass
class NotAuthorized(Error):
pass
@ -42,12 +47,12 @@ def wrap_exception(f):
return f(*args, **kw)
except Exception, e:
if not isinstance(e, Error):
# exc_type, exc_value, exc_traceback = sys.exc_info()
# exc_type, exc_value, exc_traceback = sys.exc_info()
logging.exception('Uncaught exception')
# logging.debug(traceback.extract_stack(exc_traceback))
# logging.debug(traceback.extract_stack(exc_traceback))
raise Error(str(e))
raise
_wrap.func_name = f.func_name
return _wrap

View File

@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC]
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -39,7 +39,7 @@ class Exchange(object):
for f in self._routes[routing_key]:
logging.debug('Publishing to route %s', f)
f(message, routing_key=routing_key)
def bind(self, callback, routing_key):
self._routes.setdefault(routing_key, [])
self._routes[routing_key].append(callback)
@ -52,7 +52,7 @@ class Queue(object):
def __repr__(self):
return '<Queue: %s>' % self.name
def push(self, message, routing_key=None):
self._queue.put(message)
@ -70,7 +70,7 @@ class Backend(object):
#super(__impl, self).__init__(*args, **kwargs)
self._exchanges = {}
self._queues = {}
def _reset_all(self):
self._exchanges = {}
self._queues = {}
@ -78,7 +78,7 @@ class Backend(object):
def queue_declare(self, queue, **kwargs):
if queue not in self._queues:
logging.debug('Declaring queue %s', queue)
self._queues[queue] = Queue(queue)
self._queues[queue] = Queue(queue)
def exchange_declare(self, exchange, type, *args, **kwargs):
if exchange not in self._exchanges:
@ -92,7 +92,7 @@ class Backend(object):
routing_key)
def get(self, queue, no_ack=False):
if not self._queues[queue].size():
if not queue in self._queues or not self._queues[queue].size():
return None
(message_data, content_type, content_encoding) = \
self._queues[queue].pop()
@ -122,7 +122,7 @@ class Backend(object):
def __getattr__(self, attr):
return getattr(self.__instance, attr)
def __setattr__(self, attr, value):
return setattr(self.__instance, attr, value)

View File

@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC]
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -28,12 +28,10 @@ from nova import flags
from nova import utils
from nova.objectstore import stored
FLAGS = flags.FLAGS
flags.DEFINE_string('buckets_path', utils.abspath('../buckets'),
'path to s3 buckets')
class Bucket(object):
def __init__(self, name):
self.name = name
@ -62,11 +60,11 @@ class Bucket(object):
return buckets
@staticmethod
def create(bucket_name, user):
"""Create a new bucket owned by a user.
def create(bucket_name, context):
"""Create a new bucket owned by a project.
@bucket_name: a string representing the name of the bucket to create
@user: a nova.auth.user who should own the bucket.
@context: a nova.auth.api.ApiContext object representing who owns the bucket.
Raises:
NotAuthorized: if the bucket is already exists or has invalid name
@ -80,7 +78,7 @@ class Bucket(object):
os.makedirs(path)
with open(path+'.json', 'w') as f:
json.dump({'ownerId': user.id}, f)
json.dump({'ownerId': context.project.id}, f)
@property
def metadata(self):
@ -101,9 +99,9 @@ class Bucket(object):
except:
return None
def is_authorized(self, user):
def is_authorized(self, context):
try:
return user.is_admin() or self.owner_id == user.id
return context.user.is_admin() or self.owner_id == context.project.id
except Exception, e:
pass

View File

@ -31,6 +31,7 @@ S3 client with this module::
print c.get("mybucket", "mykey").body
"""
import datetime
import os
import urllib
@ -44,6 +45,7 @@ from tornado import escape, web
from nova import exception
from nova import flags
from nova.endpoint import api
from nova.objectstore import bucket
from nova.objectstore import image
@ -87,16 +89,18 @@ class BaseRequestHandler(web.RequestHandler):
SUPPORTED_METHODS = ("PUT", "GET", "DELETE", "HEAD")
@property
def user(self):
if not hasattr(self, '_user'):
def context(self):
if not hasattr(self, '_context'):
try:
access = self.request.headers['Authorization'].split(' ')[1].split(':')[0]
user = self.application.user_manager.get_user_from_access_key(access)
user.secret # FIXME: check signature here!
self._user = user
except:
# Authorization Header format: 'AWS <access>:<secret>'
access, sep, secret = self.request.headers['Authorization'].split(' ')[1].rpartition(':')
(user, project) = self.application.user_manager.authenticate(access, secret, {}, self.request.method, self.request.host, self.request.path, False)
# FIXME: check signature here!
self._context = api.APIRequestContext(self, user, project)
except exception.Error, ex:
logging.debug("Authentication Failure: %s" % ex)
raise web.HTTPError(403)
return self._user
return self._context
def render_xml(self, value):
assert isinstance(value, dict) and len(value) == 1
@ -134,7 +138,7 @@ class BaseRequestHandler(web.RequestHandler):
class RootHandler(BaseRequestHandler):
def get(self):
buckets = [b for b in bucket.Bucket.all() if b.is_authorized(self.user)]
buckets = [b for b in bucket.Bucket.all() if b.is_authorized(self.context)]
self.render_xml({"ListAllMyBucketsResult": {
"Buckets": {"Bucket": [b.metadata for b in buckets]},
@ -148,7 +152,7 @@ class BucketHandler(BaseRequestHandler):
bucket_object = bucket.Bucket(bucket_name)
if not bucket_object.is_authorized(self.user):
if not bucket_object.is_authorized(self.context):
raise web.HTTPError(403)
prefix = self.get_argument("prefix", u"")
@ -162,7 +166,7 @@ class BucketHandler(BaseRequestHandler):
@catch_nova_exceptions
def put(self, bucket_name):
logging.debug("Creating bucket %s" % (bucket_name))
bucket.Bucket.create(bucket_name, self.user)
bucket.Bucket.create(bucket_name, self.context)
self.finish()
@catch_nova_exceptions
@ -170,7 +174,7 @@ class BucketHandler(BaseRequestHandler):
logging.debug("Deleting bucket %s" % (bucket_name))
bucket_object = bucket.Bucket(bucket_name)
if not bucket_object.is_authorized(self.user):
if not bucket_object.is_authorized(self.context):
raise web.HTTPError(403)
bucket_object.delete()
@ -185,7 +189,7 @@ class ObjectHandler(BaseRequestHandler):
bucket_object = bucket.Bucket(bucket_name)
if not bucket_object.is_authorized(self.user):
if not bucket_object.is_authorized(self.context):
raise web.HTTPError(403)
obj = bucket_object[urllib.unquote(object_name)]
@ -199,7 +203,7 @@ class ObjectHandler(BaseRequestHandler):
logging.debug("Putting object: %s / %s" % (bucket_name, object_name))
bucket_object = bucket.Bucket(bucket_name)
if not bucket_object.is_authorized(self.user):
if not bucket_object.is_authorized(self.context):
raise web.HTTPError(403)
key = urllib.unquote(object_name)
@ -212,7 +216,7 @@ class ObjectHandler(BaseRequestHandler):
logging.debug("Deleting object: %s / %s" % (bucket_name, object_name))
bucket_object = bucket.Bucket(bucket_name)
if not bucket_object.is_authorized(self.user):
if not bucket_object.is_authorized(self.context):
raise web.HTTPError(403)
del bucket_object[urllib.unquote(object_name)]
@ -228,7 +232,7 @@ class ImageHandler(BaseRequestHandler):
""" returns a json listing of all images
that a user has permissions to see """
images = [i for i in image.Image.all() if i.is_authorized(self.user)]
images = [i for i in image.Image.all() if i.is_authorized(self.context)]
self.finish(json.dumps([i.metadata for i in images]))
@ -247,11 +251,11 @@ class ImageHandler(BaseRequestHandler):
bucket_object = bucket.Bucket(image_location.split("/")[0])
manifest = image_location[len(image_location.split('/')[0])+1:]
if not bucket_object.is_authorized(self.user):
if not bucket_object.is_authorized(self.context):
raise web.HTTPError(403)
p = multiprocessing.Process(target=image.Image.create,args=
(image_id, image_location, self.user))
(image_id, image_location, self.context))
p.start()
self.finish()
@ -264,7 +268,7 @@ class ImageHandler(BaseRequestHandler):
image_object = image.Image(image_id)
if image_object.owner_id != self.user.id:
if not image.is_authorized(self.context):
raise web.HTTPError(403)
image_object.set_public(operation=='add')
@ -277,7 +281,7 @@ class ImageHandler(BaseRequestHandler):
image_id = self.get_argument("image_id", u"")
image_object = image.Image(image_id)
if image_object.owner_id != self.user.id:
if not image.is_authorized(self.context):
raise web.HTTPError(403)
image_object.delete()

View File

@ -58,9 +58,9 @@ class Image(object):
except:
pass
def is_authorized(self, user):
def is_authorized(self, context):
try:
return self.metadata['isPublic'] or self.metadata['imageOwnerId'] == user.id
return self.metadata['isPublic'] or context.user.is_admin() or self.metadata['imageOwnerId'] == context.project.id
except:
return False
@ -91,7 +91,7 @@ class Image(object):
return json.load(f)
@staticmethod
def create(image_id, image_location, user):
def create(image_id, image_location, context):
image_path = os.path.join(FLAGS.images_path, image_id)
os.makedirs(image_path)
@ -119,7 +119,7 @@ class Image(object):
info = {
'imageId': image_id,
'imageLocation': image_location,
'imageOwnerId': user.id,
'imageOwnerId': context.project.id,
'isPublic': False, # FIXME: grab public from manifest
'architecture': 'x86_64', # FIXME: grab architecture from manifest
'type' : image_type

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()

View File

@ -1,11 +1,11 @@
# Copyright [2010] [Anso Labs, LLC]
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -33,14 +33,13 @@ def get_connection():
path='/services/Cloud',
debug=99
)
class APIIntegrationTests(unittest.TestCase):
def test_001_get_all_images(self):
conn = get_connection()
res = conn.get_all_images()
print res
if __name__ == '__main__':
unittest.main()

View File

@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC]
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -20,7 +20,6 @@ import unittest
from xml.etree import ElementTree
from nova import vendor
import mox
from tornado import ioloop
from twisted.internet import defer
@ -53,18 +52,24 @@ class CloudTestCase(test.BaseTestCase):
topic=FLAGS.cloud_topic,
proxy=self.cloud)
self.injected.append(self.cloud_consumer.attach_to_tornado(self.ioloop))
# set up a node
self.node = node.Node()
self.node_consumer = rpc.AdapterConsumer(connection=self.conn,
topic=FLAGS.compute_topic,
proxy=self.node)
self.injected.append(self.node_consumer.attach_to_tornado(self.ioloop))
user_mocker = mox.Mox()
self.admin = user_mocker.CreateMock(users.User)
self.admin.is_authorized(mox.IgnoreArg()).AndReturn(True)
self.context = api.APIRequestContext(handler=None,user=self.admin)
try:
users.UserManager.instance().create_user('admin', 'admin', 'admin')
except: pass
admin = users.UserManager.instance().get_user('admin')
project = users.UserManager.instance().create_project('proj', 'admin', 'proj')
self.context = api.APIRequestContext(handler=None,project=project,user=admin)
def tearDown(self):
users.UserManager.instance().delete_project('proj')
users.UserManager.instance().delete_user('admin')
def test_console_output(self):
if FLAGS.fake_libvirt:
@ -76,7 +81,7 @@ class CloudTestCase(test.BaseTestCase):
logging.debug(output)
self.assert_(output)
rv = yield self.node.terminate_instance(instance_id)
def test_run_instances(self):
if FLAGS.fake_libvirt:
logging.debug("Can't test instances without a real virtual env.")
@ -128,9 +133,7 @@ class CloudTestCase(test.BaseTestCase):
'state': 0x01,
'user_data': ''
}
rv = self.cloud.format_instances(self.admin)
print rv
rv = self.cloud._format_instances(self.context)
self.assert_(len(rv['reservationSet']) == 0)
# simulate launch of 5 instances
@ -139,19 +142,18 @@ class CloudTestCase(test.BaseTestCase):
# inst = instance(i)
# self.cloud.instances['pending'][inst['instance_id']] = inst
#rv = self.cloud.format_instances(self.admin)
#rv = self.cloud._format_instances(self.admin)
#self.assert_(len(rv['reservationSet']) == 1)
#self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5)
# report 4 nodes each having 1 of the instances
#for i in xrange(4):
# self.cloud.update_state('instances', {('node-%s' % i): {('i-%s' % i): instance(i)}})
# one instance should be pending still
#self.assert_(len(self.cloud.instances['pending'].keys()) == 1)
# check that the reservations collapse
#rv = self.cloud.format_instances(self.admin)
#rv = self.cloud._format_instances(self.admin)
#self.assert_(len(rv['reservationSet']) == 1)
#self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5)

View File

@ -30,84 +30,86 @@ class NetworkTestCase(test.TrialTestCase):
super(NetworkTestCase, self).setUp()
logging.getLogger().setLevel(logging.DEBUG)
self.manager = users.UserManager.instance()
try:
self.manager.create_user('netuser', 'netuser', 'netuser')
except: pass
for i in range(0, 6):
name = 'user%s' % i
if not self.manager.get_user(name):
self.manager.create_user(name, name, name)
name = 'project%s' % i
if not self.manager.get_project(name):
self.manager.create_project(name, 'netuser', name)
self.network = network.NetworkController(netsize=16)
def tearDown(self):
super(NetworkTestCase, self).tearDown()
for i in range(0, 6):
name = 'user%s' % i
self.manager.delete_user(name)
name = 'project%s' % i
self.manager.delete_project(name)
self.manager.delete_user('netuser')
def test_network_serialization(self):
net1 = network.Network(vlan=100, network="192.168.100.0/24", conn=None)
address = net1.allocate_ip("user0", "01:24:55:36:f2:a0")
address = net1.allocate_ip("netuser", "project0", "01:24:55:36:f2:a0")
net_json = str(net1)
net2 = network.Network.from_json(net_json)
self.assertEqual(net_json, str(net2))
self.assertTrue(IPy.IP(address) in net2.network)
def test_allocate_deallocate_address(self):
for flag in flags.FLAGS:
print "%s=%s" % (flag, flags.FLAGS.get(flag, None))
(address, net_name) = self.network.allocate_address(
"user0", "01:24:55:36:f2:a0")
(address, net_name) = self.network.allocate_address("netuser",
"project0", "01:24:55:36:f2:a0")
logging.debug("Was allocated %s" % (address))
self.assertEqual(True, address in self._get_user_addresses("user0"))
self.assertEqual(True, address in self._get_project_addresses("project0"))
rv = self.network.deallocate_address(address)
self.assertEqual(False, address in self._get_user_addresses("user0"))
self.assertEqual(False, address in self._get_project_addresses("project0"))
def test_range_allocation(self):
(address, net_name) = self.network.allocate_address(
"user0", "01:24:55:36:f2:a0")
(secondaddress, net_name) = self.network.allocate_address(
"user1", "01:24:55:36:f2:a0")
self.assertEqual(True, address in self._get_user_addresses("user0"))
(address, net_name) = self.network.allocate_address("netuser",
"project0", "01:24:55:36:f2:a0")
(secondaddress, net_name) = self.network.allocate_address("netuser",
"project1", "01:24:55:36:f2:a0")
self.assertEqual(True, address in self._get_project_addresses("project0"))
self.assertEqual(True,
secondaddress in self._get_user_addresses("user1"))
self.assertEqual(False, address in self._get_user_addresses("user1"))
secondaddress in self._get_project_addresses("project1"))
self.assertEqual(False, address in self._get_project_addresses("project1"))
rv = self.network.deallocate_address(address)
self.assertEqual(False, address in self._get_user_addresses("user0"))
self.assertEqual(False, address in self._get_project_addresses("project0"))
rv = self.network.deallocate_address(secondaddress)
self.assertEqual(False,
secondaddress in self._get_user_addresses("user1"))
secondaddress in self._get_project_addresses("project1"))
def test_subnet_edge(self):
(secondaddress, net_name) = self.network.allocate_address("user0")
for user in range(1,5):
user_id = "user%s" % (user)
(address, net_name) = self.network.allocate_address(
user_id, "01:24:55:36:f2:a0")
(address2, net_name) = self.network.allocate_address(
user_id, "01:24:55:36:f2:a0")
(address3, net_name) = self.network.allocate_address(
user_id, "01:24:55:36:f2:a0")
(secondaddress, net_name) = self.network.allocate_address("netuser", "project0")
for project in range(1,5):
project_id = "project%s" % (project)
(address, net_name) = self.network.allocate_address("netuser",
project_id, "01:24:55:36:f2:a0")
(address2, net_name) = self.network.allocate_address("netuser",
project_id, "01:24:55:36:f2:a0")
(address3, net_name) = self.network.allocate_address("netuser",
project_id, "01:24:55:36:f2:a0")
self.assertEqual(False,
address in self._get_user_addresses("user0"))
address in self._get_project_addresses("project0"))
self.assertEqual(False,
address2 in self._get_user_addresses("user0"))
address2 in self._get_project_addresses("project0"))
self.assertEqual(False,
address3 in self._get_user_addresses("user0"))
address3 in self._get_project_addresses("project0"))
rv = self.network.deallocate_address(address)
rv = self.network.deallocate_address(address2)
rv = self.network.deallocate_address(address3)
rv = self.network.deallocate_address(secondaddress)
def test_too_many_users(self):
def test_too_many_projects(self):
for i in range(0, 30):
name = 'toomany-user%s' % i
self.manager.create_user(name, name, name)
(address, net_name) = self.network.allocate_address(
name = 'toomany-project%s' % i
self.manager.create_project(name, 'netuser', name)
(address, net_name) = self.network.allocate_address("netuser",
name, "01:24:55:36:f2:a0")
self.manager.delete_user(name)
self.manager.delete_project(name)
def _get_user_addresses(self, user_id):
def _get_project_addresses(self, project_id):
rv = self.network.describe_addresses()
user_addresses = []
project_addresses = []
for item in rv:
if item['user_id'] == user_id:
user_addresses.append(item['address'])
return user_addresses
if item['project_id'] == project_id:
project_addresses.append(item['address'])
return project_addresses

View File

@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC]
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -14,14 +14,9 @@
# limitations under the License.
import logging
import StringIO
import time
import unittest
from xml.etree import ElementTree
from nova import vendor
import mox
from tornado import ioloop
from twisted.internet import defer
from nova import exception
@ -39,21 +34,21 @@ class InstanceXmlTestCase(test.TrialTestCase):
def test_serialization(self):
# TODO: Reimplement this, it doesn't make sense in redis-land
return
# instance_id = 'foo'
# first_node = node.Node()
# inst = yield first_node.run_instance(instance_id)
#
#
# # force the state so that we can verify that it changes
# inst._s['state'] = node.Instance.NOSTATE
# xml = inst.toXml()
# self.assert_(ElementTree.parse(StringIO.StringIO(xml)))
#
#
# second_node = node.Node()
# new_inst = node.Instance.fromXml(second_node._conn, pool=second_node._pool, xml=xml)
# self.assertEqual(new_inst.state, node.Instance.RUNNING)
# rv = yield first_node.terminate_instance(instance_id)
class NodeConnectionTestCase(test.TrialTestCase):
def setUp(self):
@ -64,20 +59,22 @@ class NodeConnectionTestCase(test.TrialTestCase):
fake_users=True,
redis_db=8)
self.node = node.Node()
def create_instance(self):
instdir = model.InstanceDirectory()
inst = instdir.new()
# TODO(ja): add ami, ari, aki, user_data
inst['reservation_id'] = 'r-fakeres'
inst['launch_time'] = '10'
inst['owner_id'] = 'fake'
inst['user_id'] = 'fake'
inst['project_id'] = 'fake'
inst['instance_type'] = 'm1.tiny'
inst['node_name'] = FLAGS.node_name
inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = 0
inst.save()
return inst['instance_id']
@defer.inlineCallbacks
def test_run_describe_terminate(self):
instance_id = self.create_instance()
@ -96,11 +93,10 @@ class NodeConnectionTestCase(test.TrialTestCase):
def test_reboot(self):
instance_id = self.create_instance()
rv = yield self.node.run_instance(instance_id)
rv = yield self.node.describe_instances()
logging.debug("describe_instances returns %s" % (rv))
self.assertEqual(rv[instance_id].name, instance_id)
yield self.node.reboot_instance(instance_id)
rv = yield self.node.describe_instances()
@ -111,7 +107,7 @@ class NodeConnectionTestCase(test.TrialTestCase):
def test_console_output(self):
instance_id = self.create_instance()
rv = yield self.node.run_instance(instance_id)
console = yield self.node.get_console_output(instance_id)
self.assert_(console)
rv = yield self.node.terminate_instance(instance_id)
@ -123,6 +119,6 @@ class NodeConnectionTestCase(test.TrialTestCase):
rv = yield self.node.describe_instances()
self.assertEqual(rv[instance_id].name, instance_id)
self.assertRaises(exception.Error, self.node.run_instance, instance_id)
rv = yield self.node.terminate_instance(instance_id)

View File

@ -56,8 +56,6 @@ class ObjectStoreTestCase(test.BaseTestCase):
logging.getLogger().setLevel(logging.DEBUG)
self.um = users.UserManager.instance()
def test_buckets(self):
try:
self.um.create_user('user1')
except: pass
@ -67,18 +65,41 @@ class ObjectStoreTestCase(test.BaseTestCase):
try:
self.um.create_user('admin_user', admin=True)
except: pass
try:
self.um.create_project('proj1', 'user1', 'a proj', ['user1'])
except: pass
try:
self.um.create_project('proj2', 'user2', 'a proj', ['user2'])
except: pass
class Context(object): pass
self.context = Context()
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')
# creator is authorized to use bucket
self.assert_(bucket.is_authorized(self.um.get_user('user1')))
self.assert_(bucket.is_authorized(self.context))
# another user is not authorized
self.assert_(bucket.is_authorized(self.um.get_user('user2')) == False)
self.context.user = self.um.get_user('user2')
self.context.project = self.um.get_project('proj2')
self.assert_(bucket.is_authorized(self.context) == False)
# admin is authorized to use bucket
self.assert_(bucket.is_authorized(self.um.get_user('admin_user')))
self.context.user = self.um.get_user('admin_user')
self.context.project = None
self.assert_(bucket.is_authorized(self.context))
# new buckets are empty
self.assert_(bucket.list_keys()['Contents'] == [])
@ -116,18 +137,13 @@ class ObjectStoreTestCase(test.BaseTestCase):
exception = True
self.assert_(exception)
self.um.delete_user('user1')
self.um.delete_user('user2')
self.um.delete_user('admin_user')
def test_images(self):
try:
self.um.create_user('image_creator')
except: pass
image_user = self.um.get_user('image_creator')
self.context.user = self.um.get_user('user1')
self.context.project = self.um.get_project('proj1')
# create a bucket for our bundle
objectstore.bucket.Bucket.create('image_bucket', image_user)
objectstore.bucket.Bucket.create('image_bucket', self.context)
bucket = objectstore.bucket.Bucket('image_bucket')
# upload an image manifest/parts
@ -136,7 +152,7 @@ class ObjectStoreTestCase(test.BaseTestCase):
bucket[os.path.basename(path)] = open(path, 'rb').read()
# register an image
objectstore.image.Image.create('i-testing', 'image_bucket/1mb.manifest.xml', image_user)
objectstore.image.Image.create('i-testing', 'image_bucket/1mb.manifest.xml', self.context)
# verify image
my_img = objectstore.image.Image('i-testing')
@ -147,14 +163,9 @@ class ObjectStoreTestCase(test.BaseTestCase):
self.assertEqual(sha, '3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3')
# verify image permissions
try:
self.um.create_user('new_user')
except: pass
new_user = self.um.get_user('new_user')
self.assert_(my_img.is_authorized(new_user) == False)
self.um.delete_user('new_user')
self.um.delete_user('image_creator')
self.context.user = self.um.get_user('user2')
self.context.project = self.um.get_project('proj2')
self.assert_(my_img.is_authorized(self.context) == False)
# class ApiObjectStoreTestCase(test.BaseTestCase):
# def setUp(self):

View File

@ -24,11 +24,9 @@ from M2Crypto import X509
from nova import crypto
from nova import flags
from nova import test
from nova import utils
from nova.auth import users
from nova.endpoint import cloud
FLAGS = flags.FLAGS
@ -40,8 +38,9 @@ class UserTestCase(test.BaseTestCase):
redis_db=8)
self.users = users.UserManager.instance()
def test_001_can_create_user(self):
def test_001_can_create_users(self):
self.users.create_user('test1', 'access', 'secret')
self.users.create_user('test2')
def test_002_can_get_user(self):
user = self.users.get_user('test1')
@ -83,7 +82,6 @@ class UserTestCase(test.BaseTestCase):
key.save_pub_key_bio(bio)
converted = crypto.ssl_pub_to_ssh_pub(bio.read())
# assert key fields are equal
print converted
self.assertEqual(public_key.split(" ")[1].strip(),
converted.split(" ")[1].strip())
@ -101,16 +99,44 @@ class UserTestCase(test.BaseTestCase):
users = self.users.get_users()
self.assertTrue(filter(lambda u: u.id == 'test1', users))
def test_011_can_generate_x509(self):
def test_201_can_create_project(self):
project = self.users.create_project('testproj', 'test1', 'A test project', ['test1'])
self.assertTrue(filter(lambda p: p.name == 'testproj', self.users.get_projects()))
self.assertEqual(project.name, 'testproj')
self.assertEqual(project.description, 'A test project')
self.assertEqual(project.project_manager_id, 'test1')
self.assertTrue(project.has_member('test1'))
def test_202_user1_is_project_member(self):
self.assertTrue(self.users.get_user('test1').is_project_member('testproj'))
def test_203_user2_is_not_project_member(self):
self.assertFalse(self.users.get_user('test2').is_project_member('testproj'))
def test_204_user1_is_project_manager(self):
self.assertTrue(self.users.get_user('test1').is_project_manager('testproj'))
def test_205_user2_is_not_project_manager(self):
self.assertFalse(self.users.get_user('test2').is_project_manager('testproj'))
def test_206_can_add_user_to_project(self):
self.users.add_to_project('test2', 'testproj')
self.assertTrue(self.users.get_project('testproj').has_member('test2'))
def test_208_can_remove_user_from_project(self):
self.users.remove_from_project('test2', 'testproj')
self.assertFalse(self.users.get_project('testproj').has_member('test2'))
def test_209_can_generate_x509(self):
# MUST HAVE RUN CLOUD SETUP BY NOW
self.cloud = cloud.CloudController()
self.cloud.setup()
private_key, signed_cert_string = self.users.get_user('test1').generate_x509_cert()
private_key, signed_cert_string = self.users.get_project('testproj').generate_x509_cert('test1')
logging.debug(signed_cert_string)
# Need to verify that it's signed by the right intermediate CA
full_chain = crypto.fetch_ca(username='test1', chain=True)
int_cert = crypto.fetch_ca(username='test1', chain=False)
full_chain = crypto.fetch_ca(project_id='testproj', chain=True)
int_cert = crypto.fetch_ca(project_id='testproj', chain=False)
cloud_cert = crypto.fetch_ca()
logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain)
signed_cert = X509.load_cert_string(signed_cert_string)
@ -125,11 +151,16 @@ class UserTestCase(test.BaseTestCase):
else:
self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey()))
def test_012_can_delete_user(self):
def test_299_can_delete_project(self):
self.users.delete_project('testproj')
self.assertFalse(filter(lambda p: p.name == 'testproj', self.users.get_projects()))
def test_999_can_delete_users(self):
self.users.delete_user('test1')
users = self.users.get_users()
if users != None:
self.assertFalse(filter(lambda u: u.id == 'test1', users))
self.assertFalse(filter(lambda u: u.id == 'test1', users))
self.users.delete_user('test2')
self.assertEqual(self.users.get_user('test2'), None)
if __name__ == "__main__":

View File

@ -1,12 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright [2010] [Anso Labs, LLC]
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#
# http://www.apache.org/licenses/LICENSE-2.0
#
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -19,6 +19,7 @@ destroying persistent storage volumes, ala EBS.
Currently uses Ata-over-Ethernet.
"""
import glob
import logging
import random
import socket
@ -52,7 +53,7 @@ flags.DEFINE_integer('shelf_id',
flags.DEFINE_string('storage_availability_zone',
'nova',
'availability zone of this node')
flags.DEFINE_boolean('fake_storage', False,
flags.DEFINE_boolean('fake_storage', False,
'Should we make real storage volumes to attach?')
class BlockStore(object):
@ -62,7 +63,7 @@ class BlockStore(object):
if FLAGS.fake_storage:
self.volume_class = FakeVolume
self._init_volume_group()
self.keeper = datastore.Keeper('instances')
self.keeper = datastore.Keeper('storage-')
def report_state(self):
#TODO: aggregate the state of the system
@ -82,7 +83,7 @@ class BlockStore(object):
def get_volume(self, volume_id):
""" Returns a redis-backed volume object """
if volume_id in self.keeper['volumes']:
if self.keeper.set_is_member('volumes', volume_id):
return self.volume_class(volume_id=volume_id)
raise exception.Error("Volume does not exist")

View File

@ -44,7 +44,6 @@ from twisted.scripts import trial as trial_script
from nova import flags
from nova import twistd
from nova.tests.access_unittest import *
from nova.tests.api_unittest import *
from nova.tests.cloud_unittest import *
from nova.tests.keeper_unittest import *