Merged Vish's work on adding projects to nova
This commit is contained in:
parent
fd278ade0b
commit
94518726fb
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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__':
|
||||
|
|
|
@ -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 )
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 """
|
||||
|
|
|
@ -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__":
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]):
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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__":
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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 *
|
||||
|
|
Loading…
Reference in New Issue