From d897aac1f3d7873b5245c8a76de241c6c664f60a Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 31 May 2010 18:56:20 -0700 Subject: [PATCH 1/3] Josh's networking refactor, modified to work with projects --- nova/datastore.py | 4 +- nova/endpoint/cloud.py | 45 +++++++------------- nova/tests/network_unittest.py | 77 ++++++++++++++++++---------------- 3 files changed, 58 insertions(+), 68 deletions(-) diff --git a/nova/datastore.py b/nova/datastore.py index 97c00264..0da89d59 100644 --- a/nova/datastore.py +++ b/nova/datastore.py @@ -28,6 +28,7 @@ import json import logging import os import sqlite3 +import time from nova import vendor import redis @@ -77,10 +78,11 @@ class RedisModel(object): def set_default_state(self): self.state = {'state' : 'pending'} self.state[self.object_type+"_id"] = self.object_id + self.state["create_time"] = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) @property def __redis_key(self): - """ Magic string for instance keys """ + """ Magic string for keys """ return '%s:%s' % (self.object_type, self.object_id) def __repr__(self): diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 53290e38..768a6b3e 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -60,7 +60,7 @@ class CloudController(object): """ def __init__(self): self.instdir = model.InstanceDirectory() - self.network = network.NetworkController() + self.network = network.PublicNetworkController() self.setup() @property @@ -254,21 +254,10 @@ class CloudController(object): res.addCallback(_format_result) return res - def _convert_address(self, network_address): - # FIXME(vish): this should go away when network.py stores info properly - address = {} - address['public_ip'] == network_address[u'address'] - address['user_id'] == network_address[u'user_id'] - address['project_id'] == network_address.get(u'project_id', address['user_id']) - address['instance_id'] == network_address.get(u'instance_id', None) - return address - def _get_address(self, context, public_ip): - # right now all addresses are allocated locally # FIXME(vish) this should move into network.py - for network_address in self.network.describe_addresses(): - if network_address[u'address'] == public_ip: - address = self._convert_address(network_address) + for address in self.network.hosts: + if address['address'] == public_ip: 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) @@ -398,14 +387,12 @@ class CloudController(object): def format_addresses(self, context): addresses = [] # TODO(vish): move authorization checking into network.py - for network_address in self.network.describe_addresses(type=network.PublicNetwork): + for address in self.network.hosts: #logging.debug(address_record) - address = self._convert_address(network_address) address_rv = { - 'public_ip': address['public_ip'], + 'public_ip': address['address'], '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'], @@ -417,17 +404,17 @@ class CloudController(object): return {'addressesSet': addresses} def allocate_address(self, context, **kwargs): - (address,network_name) = self.network.allocate_address( - context.user.id, context.project_id, type=network.PublicNetwork) + address = self.network.allocate_ip( + context.user.id, context.project.id, 'public') return defer.succeed({'addressSet': [{'publicIp' : address}]}) def release_address(self, context, public_ip, **kwargs): - address = self._get_address(public_ip) + self.network.deallocate_ip(public_ip) return defer.succeed({'releaseResponse': ["Address released."]}) def associate_address(self, context, instance_id, **kwargs): instance = self._get_instance(context, instance_id) - rv = self.network.associate_address( + self.network.associate_address( kwargs['public_ip'], instance['private_dns_name'], instance_id) @@ -435,7 +422,7 @@ class CloudController(object): def disassociate_address(self, context, public_ip, **kwargs): address = self._get_address(public_ip) - rv = self.network.disassociate_address(public_ip) + self.network.disassociate_address(public_ip) # TODO - Strip the IP from the instance return defer.succeed({'disassociateResponse': ["Address disassociated."]}) @@ -466,14 +453,10 @@ class CloudController(object): inst['project_id'] = context.project.id inst['mac_address'] = utils.generate_mac() inst['ami_launch_index'] = num - address, _netname = self.network.allocate_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 + address = network.allocate_ip( + inst['user_id'], inst['project_id'], mac=inst['mac_address']) inst['private_dns_name'] = str(address) + inst['bridge_name'] = network.BridgedNetwork.get_network_for_project(inst['user_id'], inst['project_id'])['bridge_name'] # TODO: allocate expresses on the router node inst.save() rpc.cast(FLAGS.compute_topic, @@ -502,7 +485,7 @@ class CloudController(object): 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)) + self.network.deallocate_ip(instance.get('private_dns_name', None)) except Exception, _err: pass if instance.get('node_name', 'unassigned') != 'unassigned': #It's also internal default diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index a78d9075..a2ad89a3 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -23,11 +23,17 @@ from nova import flags from nova import test from nova.compute import network from nova.auth import users +from nova import utils class NetworkTestCase(test.TrialTestCase): def setUp(self): super(NetworkTestCase, self).setUp() + self.flags(fake_libvirt=True, + fake_storage=True, + fake_network=True, + network_size=32, + redis_db=8) logging.getLogger().setLevel(logging.DEBUG) self.manager = users.UserManager.instance() try: @@ -37,7 +43,7 @@ class NetworkTestCase(test.TrialTestCase): name = 'project%s' % i if not self.manager.get_project(name): self.manager.create_project(name, 'netuser', name) - self.network = network.NetworkController(netsize=16) + self.network = network.PublicNetworkController() def tearDown(self): super(NetworkTestCase, self).tearDown() @@ -46,70 +52,69 @@ class NetworkTestCase(test.TrialTestCase): 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("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_public_network_allocation(self): + pubnet = IPy.IP(flags.FLAGS.public_range) + address = self.network.allocate_ip("netuser", "project0", "public") + self.assertTrue(IPy.IP(address) in pubnet) + self.assertTrue(IPy.IP(address) in self.network.network) - def test_allocate_deallocate_address(self): - (address, net_name) = self.network.allocate_address("netuser", - "project0", "01:24:55:36:f2:a0") + def test_allocate_deallocate_ip(self): + address = network.allocate_ip( + "netuser", "project0", utils.generate_mac()) logging.debug("Was allocated %s" % (address)) self.assertEqual(True, address in self._get_project_addresses("project0")) - rv = self.network.deallocate_address(address) + rv = network.deallocate_ip(address) self.assertEqual(False, address in self._get_project_addresses("project0")) def test_range_allocation(self): - (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")) + address = network.allocate_ip( + "netuser", "project0", utils.generate_mac()) + secondaddress = network.allocate_ip( + "netuser", "project1", utils.generate_mac()) + self.assertEqual(True, + address in self._get_project_addresses("project0")) self.assertEqual(True, secondaddress in self._get_project_addresses("project1")) self.assertEqual(False, address in self._get_project_addresses("project1")) - rv = self.network.deallocate_address(address) + rv = network.deallocate_ip(address) self.assertEqual(False, address in self._get_project_addresses("project0")) - rv = self.network.deallocate_address(secondaddress) + rv = network.deallocate_ip(secondaddress) self.assertEqual(False, secondaddress in self._get_project_addresses("project1")) def test_subnet_edge(self): - (secondaddress, net_name) = self.network.allocate_address("netuser", "project0") + secondaddress = network.allocate_ip("netuser", "project0", + utils.generate_mac()) 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") + address = network.allocate_ip( + "netuser", project_id, utils.generate_mac()) + address2 = network.allocate_ip( + "netuser", project_id, utils.generate_mac()) + address3 = network.allocate_ip( + "netuser", project_id, utils.generate_mac()) self.assertEqual(False, address in self._get_project_addresses("project0")) self.assertEqual(False, address2 in self._get_project_addresses("project0")) self.assertEqual(False, 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) + rv = network.deallocate_ip(address) + rv = network.deallocate_ip(address2) + rv = network.deallocate_ip(address3) + rv = network.deallocate_ip(secondaddress) def test_too_many_projects(self): for i in range(0, 30): 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") + address = network.allocate_ip( + "netuser", name, utils.generate_mac()) + rv = network.deallocate_ip(address) self.manager.delete_project(name) def _get_project_addresses(self, project_id): - rv = self.network.describe_addresses() project_addresses = [] - for item in rv: - if item['project_id'] == project_id: - project_addresses.append(item['address']) + for addr in network.get_project_network(project_id).list_addresses(): + project_addresses.append(addr) return project_addresses From a7896d3e39c064067586d447039c01feaec732fa Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 1 Jun 2010 13:47:10 -0700 Subject: [PATCH 2/3] Fix novarc to use project when creating access key --- nova/auth/novarc.template | 8 ++++---- nova/auth/users.py | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/nova/auth/novarc.template b/nova/auth/novarc.template index a993d188..ee1bc75f 100644 --- a/nova/auth/novarc.template +++ b/nova/auth/novarc.template @@ -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. @@ -13,7 +13,7 @@ # limitations under the License. NOVA_KEY_DIR=$(pushd $(dirname $BASH_SOURCE)>/dev/null; pwd; popd>/dev/null) -export EC2_ACCESS_KEY="%(access)s" +export EC2_ACCESS_KEY="%(access)s:%(project)s" export EC2_SECRET_KEY="%(secret)s" export EC2_URL="%(ec2)s" export S3_URL="%(s3)s" diff --git a/nova/auth/users.py b/nova/auth/users.py index b09bcfcf..c60922fe 100644 --- a/nova/auth/users.py +++ b/nova/auth/users.py @@ -93,9 +93,12 @@ class User(AuthBase): def is_project_manager(self, project): return UserManager.instance().is_project_manager(self, project) - def generate_rc(self): + def generate_rc(self, project=None): + if project is None: + project = self.id rc = open(FLAGS.credentials_template).read() rc = rc % { 'access': self.access, + 'project': project, 'secret': self.secret, 'ec2': FLAGS.ec2_url, 's3': 'http://%s:%s' % (FLAGS.s3_host, FLAGS.s3_port), @@ -168,7 +171,9 @@ class Project(Group): return User.safe_id(user) == self.project_manager_id def get_credentials(self, user): - rc = user.generate_rc() + if not isinstance(user, User): + user = UserManager.instance().get_user(user) + rc = user.generate_rc(self.id) private_key, signed_cert = self.generate_x509_cert(user) tmpdir = tempfile.mkdtemp() @@ -238,7 +243,7 @@ class UserManager(object): raise exception.NotAuthorized('Signature does not match') return (user, project) - def create_project(self, name, manager_user, description, member_users=None): + def create_project(self, name, manager_user, description=None, member_users=None): if member_users: member_users = [User.safe_id(u) for u in member_users] with LDAPWrapper() as conn: @@ -462,12 +467,15 @@ class LDAPWrapper(object): self.conn.add_s(self.__uid_to_dn(name), attr) return self.__to_user(dict(attr)) - def create_project(self, name, manager_uid, description, member_uids = None): + def create_project(self, name, manager_uid, description=None, 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) + # description is a required attribute + if description is None: + description = name members = [] if member_uids != None: for member_uid in member_uids: From 6afc357aa30e01e5e711fecb8514dfe9ef904586 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 1 Jun 2010 13:48:06 -0700 Subject: [PATCH 3/3] Add project methods to nova-manage --- bin/nova-manage | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index d2108626..765eb1f5 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -71,15 +71,35 @@ class UserCommands(object): for user in self.manager.get_users(): print user.name - def zip(self, name, filename='nova.zip'): +class ProjectCommands(object): + def __init__(self): + self.manager = users.UserManager.instance() + + def create(self, name, project_manager, description=None): + """creates a new project + arguments: name project_manager [description]""" + user = self.manager.create_project(name, project_manager, description) + + def delete(self, name): + """deletes an existing project + arguments: name""" + self.manager.delete_project(name) + + def list(self): + """lists all projects + arguments: """ + for project in self.manager.get_projects(): + print project.name + + def zip(self, project_id, user_id, filename='nova.zip'): """exports credentials for user to a zip file - arguments: name [filename='nova.zip]""" - user = self.manager.get_user(name) - if user: + arguments: project_id user_id [filename='nova.zip]""" + project = self.manager.get_project(project_id) + if project: with open(filename, 'w') as f: - f.write(user.get_credentials()) + f.write(project.get_credentials(user_id)) else: - print "User %s doesn't exist" % name + print "Project %s doesn't exist" % project def usage(script_name): @@ -88,6 +108,7 @@ def usage(script_name): categories = [ ('user', UserCommands), + ('project', ProjectCommands), ]