summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@gmail.com>2010-06-29 14:01:13 -0700
committerVishvananda Ishaya <vishvananda@gmail.com>2010-06-29 14:01:13 -0700
commit1dac03ffc58cf519a4a1c9b3c286dd13d9bbbe31 (patch)
tree4dafe57ae96e5e2c5780f03096c5f380e13832ab
parenta013a801246bed9302303c304b90c748e2d7aec0 (diff)
Vpn ips and ports use redis
-rw-r--r--nova/auth/users.py132
-rw-r--r--nova/tests/users_unittest.py11
2 files changed, 109 insertions, 34 deletions
diff --git a/nova/auth/users.py b/nova/auth/users.py
index 7b703aa82..2f537ba15 100644
--- a/nova/auth/users.py
+++ b/nova/auth/users.py
@@ -47,6 +47,7 @@ from nova import exception
from nova import flags
from nova import crypto
from nova import utils
+from nova.compute import model
from nova import objectstore # for flags
@@ -100,6 +101,10 @@ flags.DEFINE_string('credential_cert_file', 'cert.pem',
'Filename of certificate in credentials zip')
flags.DEFINE_string('credential_rc_file', 'novarc',
'Filename of rc in credentials zip')
+flags.DEFINE_integer('vpn_start_port', 8000,
+ 'Start port for the cloudpipe VPN servers')
+flags.DEFINE_integer('vpn_end_port', 9999,
+ 'End port for the cloudpipe VPN servers')
flags.DEFINE_string('vpn_ip', '127.0.0.1',
'Public IP for the cloudpipe VPN servers')
@@ -119,6 +124,7 @@ class AuthBase(object):
else:
return obj
+
class User(AuthBase):
"""id and name are currently the same"""
def __init__(self, id, name, access, secret, admin):
@@ -128,23 +134,6 @@ class User(AuthBase):
self.secret = secret
self.admin = admin
- @property
- def vpn_port(self):
- port_map = self.keeper['vpn_ports']
- if not port_map: port_map = {}
- if not port_map.has_key(self.id):
- ports = port_map.values()
- if len(ports) > 0:
- port_map[self.id] = max(ports) + 1
- else:
- port_map[self.id] = 8000
- self.keeper['vpn_ports'] = port_map
- return self.keeper['vpn_ports'][self.id]
-
- @property
- def vpn_ip(self):
- return FLAGS.vpn_ip
-
def is_superuser(self):
"""allows user to bypass rbac completely"""
if self.admin:
@@ -213,6 +202,7 @@ class User(AuthBase):
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
@@ -228,6 +218,7 @@ class KeyPair(AuthBase):
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):
@@ -243,6 +234,7 @@ class Group(AuthBase):
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
@@ -265,23 +257,13 @@ class Project(Group):
def has_role(self, user, role):
return UserManager.instance().has_role(user, role, self)
-
@property
- def vpn_port(self):
- port_map = self.keeper['vpn_ports']
- if not port_map: port_map = {}
- if not port_map.has_key(self.id):
- ports = port_map.values()
- if len(ports) > 0:
- port_map[self.id] = max(ports) + 1
- else:
- port_map[self.id] = 8000
- self.keeper['vpn_ports'] = port_map
- return self.keeper['vpn_ports'][self.id]
+ def vpn_ip(self):
+ return Vpn(self.id).ip
@property
- def vpn_ip(self):
- return FLAGS.vpn_ip
+ def vpn_port(self):
+ return Vpn(self.id).port
def get_credentials(self, user):
if not isinstance(user, User):
@@ -320,6 +302,77 @@ class Project(Group):
self.id, self.project_manager_id,
self.description, self.member_ids)
+
+class NoMorePorts(exception.Error):
+ pass
+
+
+class Vpn(model.BasicModel):
+ def __init__(self, project_id):
+ self.project_id = project_id
+ super(Vpn, self).__init__()
+
+ @property
+ def identifier(self):
+ return self.project_id
+
+ @classmethod
+ def create(cls, project_id):
+ # TODO (vish): get list of vpn ips from redis
+ for ip in [FLAGS.vpn_ip]:
+ try:
+ port = cls.find_free_port_for_ip(ip)
+ vpn = cls(project_id)
+ # save ip for project
+ vpn['project'] = project_id
+ vpn['ip'] = ip
+ vpn['port'] = port
+ vpn.save()
+ return vpn
+ except NoMorePorts:
+ pass
+ raise NoMorePorts()
+
+ @classmethod
+ def find_free_port_for_ip(cls, ip):
+ # TODO(vish): the redis access should be refactored into a
+ # base class
+ redis = datastore.Redis.instance()
+ key = 'ip:%s:ports'
+ # TODO(vish): these ports should be allocated through an admin
+ # command instead of a flag
+ if (not redis.exists(key) and
+ not redis.exists(cls._redis_association_name('ip', ip))):
+ for i in range(FLAGS.vpn_start_port, FLAGS.vpn_end_port + 1):
+ redis.sadd(key, i)
+
+ port = datastore.Redis.instance().spop(key)
+ if not port:
+ raise NoMorePorts()
+ return port
+
+ @classmethod
+ def num_ports_for_ip(cls, ip):
+ return datastore.Redis.instance().scard('ip:%s:ports')
+
+ @property
+ def ip(self):
+ return self['ip']
+
+ @property
+ def port(self):
+ return int(self['port'])
+
+ def save(self):
+ self.associate_with('ip', self.ip)
+ super(Vpn, self).save()
+
+ def destroy(self):
+ self.unassociate_with('ip', self.ip)
+ datastore.Redis.instance().sadd('ip:%s:ports' % self.ip, self.port)
+ super(Vpn, self).destroy()
+
+
class UserManager(object):
def __init__(self):
if hasattr(self.__class__, '_instance'):
@@ -409,8 +462,13 @@ class UserManager(object):
if member_users:
member_users = [User.safe_id(u) for u in member_users]
with LDAPWrapper() as conn:
- return conn.create_project(name, User.safe_id(manager_user),
- description, member_users)
+ # NOTE(vish): try to associate a vpn ip and port first because
+ # if it throws an exception, we save having to
+ # create and destroy a project
+ Vpn.create(name)
+ return conn.create_project(name,
+ User.safe_id(manager_user), description, member_users)
+
def get_projects(self):
with LDAPWrapper() as conn:
@@ -467,7 +525,13 @@ class UserManager(object):
user = User.safe_id(user)
result = conn.create_user(user, access, secret, admin)
if create_project:
- conn.create_project(user, user, user)
+ # NOTE(vish): if the project creation fails, we delete
+ # the user and return an exception
+ try:
+ conn.create_project(user, user, user)
+ except Exception:
+ conn.delete_user(user)
+ raise
return result
def delete_user(self, user, delete_project=True):
diff --git a/nova/tests/users_unittest.py b/nova/tests/users_unittest.py
index 6e4b9d9df..a31ad4d7a 100644
--- a/nova/tests/users_unittest.py
+++ b/nova/tests/users_unittest.py
@@ -183,6 +183,17 @@ class UserTestCase(test.BaseTestCase):
self.users.remove_role('test1', 'sysadmin')
self.assertFalse(project.has_role('test1', 'sysadmin'))
+ def test_212_vpn_ip_and_port_looks_valid(self):
+ project = self.users.get_project('testproj')
+ self.assert_(project.vpn_ip)
+ self.assert_(project.vpn_port >= FLAGS.vpn_start_port)
+ self.assert_(project.vpn_port <= FLAGS.vpn_end_port)
+
+ def test_213_too_many_vpns(self):
+ for i in xrange(users.Vpn.num_ports_for_ip(FLAGS.vpn_ip)):
+ users.Vpn.create("vpnuser%s" % i)
+ self.assertRaises(users.NoMorePorts, users.Vpn.create, "boom")
+
def test_299_can_delete_project(self):
self.users.delete_project('testproj')
self.assertFalse(filter(lambda p: p.name == 'testproj', self.users.get_projects()))