From 83c4a429d29b7d69128d90504f6febc2efe1d3a3 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 3 Aug 2010 01:29:31 -0700 Subject: Fixed instance model associations to host (node) and added association to ip --- nova/compute/model.py | 44 +++++++------- nova/datastore.py | 12 ++-- nova/tests/model_unittest.py | 135 ++++++++++++++++++++----------------------- 3 files changed, 95 insertions(+), 96 deletions(-) diff --git a/nova/compute/model.py b/nova/compute/model.py index 212830d3c..7dd130ca3 100644 --- a/nova/compute/model.py +++ b/nova/compute/model.py @@ -41,9 +41,6 @@ True """ import datetime -import logging -import time -import redis import uuid from nova import datastore @@ -72,19 +69,22 @@ class InstanceDirectory(object): for instance_id in datastore.Redis.instance().smembers('project:%s:instances' % project): yield Instance(instance_id) - def by_node(self, node_id): + @datastore.absorb_connection_error + def by_node(self, node): """returns a list of instances for a node""" + for instance_id in datastore.Redis.instance().smembers('node:%s:instances' % node): + yield Instance(instance_id) - for instance in self.all: - if instance['node_name'] == node_id: - yield instance - - def by_ip(self, ip_address): + def by_ip(self, ip): """returns an instance object that is using the IP""" - for instance in self.all: - if instance['private_dns_name'] == ip_address: - return instance - return None + # NOTE(vish): The ip association should be just a single value, but + # to maintain consistency it is using the standard + # association and the ugly method for retrieving + # the first item in the set below. + result = datastore.Redis.instance().smembers('ip:%s:instances' % ip) + if not result: + return None + return Instance(list(result)[0]) def by_volume(self, volume_id): """returns the instance a volume is attached to""" @@ -122,7 +122,8 @@ class Instance(datastore.BasicModel): 'instance_id': self.instance_id, 'node_name': 'unassigned', 'project_id': 'unassigned', - 'user_id': 'unassigned'} + 'user_id': 'unassigned', + 'private_dns_name': 'unassigned'} @property def identifier(self): @@ -148,19 +149,22 @@ class Instance(datastore.BasicModel): """Call into superclass to save object, then save associations""" # NOTE(todd): doesn't track migration between projects/nodes, # it just adds the first one - should_update_project = self.is_new_record() - should_update_node = self.is_new_record() + is_new = self.is_new_record() + node_set = (self.state['node_name'] != 'unassigned' and + self.initial_state['node_name'] == 'unassigned') success = super(Instance, self).save() - if success and should_update_project: + if success and is_new: self.associate_with("project", self.project) - if success and should_update_node: - self.associate_with("node", self['node_name']) + self.associate_with("ip", self.state['private_dns_name']) + if success and node_set: + self.associate_with("node", self.state['node_name']) return True def destroy(self): """Destroy associations, then destroy the object""" self.unassociate_with("project", self.project) - self.unassociate_with("node", self['node_name']) + self.unassociate_with("node", self.state['node_name']) + self.unassociate_with("ip", self.state['private_dns_name']) return super(Instance, self).destroy() class Host(datastore.BasicModel): diff --git a/nova/datastore.py b/nova/datastore.py index 9c2592334..51ef7a758 100644 --- a/nova/datastore.py +++ b/nova/datastore.py @@ -90,13 +90,15 @@ class BasicModel(object): @absorb_connection_error def __init__(self): - self.initial_state = {} - self.state = Redis.instance().hgetall(self.__redis_key) - if self.state: - self.initial_state = self.state + state = Redis.instance().hgetall(self.__redis_key) + if state: + self.initial_state = state + self.state = dict(self.initial_state) else: + self.initial_state = {} self.state = self.default_state() + def default_state(self): """You probably want to define this in your subclass""" return {} @@ -239,7 +241,7 @@ class BasicModel(object): for key, val in self.state.iteritems(): Redis.instance().hset(self.__redis_key, key, val) self.add_to_index() - self.initial_state = self.state + self.initial_state = dict(self.state) return True @absorb_connection_error diff --git a/nova/tests/model_unittest.py b/nova/tests/model_unittest.py index 6825cfe2a..dc2441c24 100644 --- a/nova/tests/model_unittest.py +++ b/nova/tests/model_unittest.py @@ -19,9 +19,7 @@ from datetime import datetime, timedelta import logging import time -from twisted.internet import defer -from nova import exception from nova import flags from nova import test from nova import utils @@ -49,9 +47,9 @@ class ModelTestCase(test.TrialTestCase): 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['private_dns_name'] = '10.0.0.1' inst.save() return inst @@ -71,118 +69,126 @@ class ModelTestCase(test.TrialTestCase): session_token.save() return session_token - @defer.inlineCallbacks def test_create_instance(self): """store with create_instace, then test that a load finds it""" - instance = yield self.create_instance() - old = yield model.Instance(instance.identifier) + instance = self.create_instance() + old = model.Instance(instance.identifier) self.assertFalse(old.is_new_record()) - @defer.inlineCallbacks def test_delete_instance(self): """create, then destroy, then make sure loads a new record""" - instance = yield self.create_instance() - yield instance.destroy() - newinst = yield model.Instance('i-test') + instance = self.create_instance() + instance.destroy() + newinst = model.Instance('i-test') self.assertTrue(newinst.is_new_record()) - @defer.inlineCallbacks def test_instance_added_to_set(self): - """create, then check that it is listed for the project""" - instance = yield self.create_instance() + """create, then check that it is listed in global set""" + instance = self.create_instance() found = False for x in model.InstanceDirectory().all: if x.identifier == 'i-test': found = True self.assert_(found) - @defer.inlineCallbacks def test_instance_associates_project(self): """create, then check that it is listed for the project""" - instance = yield self.create_instance() + instance = self.create_instance() found = False for x in model.InstanceDirectory().by_project(instance.project): if x.identifier == 'i-test': found = True self.assert_(found) - @defer.inlineCallbacks + def test_instance_associates_ip(self): + """create, then check that it is listed for the ip""" + instance = self.create_instance() + found = False + x = model.InstanceDirectory().by_ip(instance['private_dns_name']) + self.assertEqual(x.identifier, 'i-test') + + def test_instance_associates_node(self): + """create, then check that it is listed for the node_name""" + instance = self.create_instance() + found = False + for x in model.InstanceDirectory().by_node(FLAGS.node_name): + if x.identifier == 'i-test': + found = True + self.assertFalse(found) + instance['node_name'] = 'test_node' + instance.save() + for x in model.InstanceDirectory().by_node('test_node'): + if x.identifier == 'i-test': + found = True + self.assert_(found) + + def test_host_class_finds_hosts(self): - host = yield self.create_host() + host = self.create_host() self.assertEqual('testhost', model.Host.lookup('testhost').identifier) - @defer.inlineCallbacks def test_host_class_doesnt_find_missing_hosts(self): - rv = yield model.Host.lookup('woahnelly') + rv = model.Host.lookup('woahnelly') self.assertEqual(None, rv) - @defer.inlineCallbacks def test_create_host(self): """store with create_host, then test that a load finds it""" - host = yield self.create_host() - old = yield model.Host(host.identifier) + host = self.create_host() + old = model.Host(host.identifier) self.assertFalse(old.is_new_record()) - @defer.inlineCallbacks def test_delete_host(self): """create, then destroy, then make sure loads a new record""" - instance = yield self.create_host() - yield instance.destroy() - newinst = yield model.Host('testhost') + instance = self.create_host() + instance.destroy() + newinst = model.Host('testhost') self.assertTrue(newinst.is_new_record()) - @defer.inlineCallbacks def test_host_added_to_set(self): """create, then check that it is included in list""" - instance = yield self.create_host() + instance = self.create_host() found = False for x in model.Host.all(): if x.identifier == 'testhost': found = True self.assert_(found) - @defer.inlineCallbacks def test_create_daemon_two_args(self): """create a daemon with two arguments""" - d = yield self.create_daemon() + d = self.create_daemon() d = model.Daemon('testhost', 'nova-testdaemon') self.assertFalse(d.is_new_record()) - @defer.inlineCallbacks def test_create_daemon_single_arg(self): """Create a daemon using the combined host:bin format""" - d = yield model.Daemon("testhost:nova-testdaemon") + d = model.Daemon("testhost:nova-testdaemon") d.save() d = model.Daemon('testhost:nova-testdaemon') self.assertFalse(d.is_new_record()) - @defer.inlineCallbacks def test_equality_of_daemon_single_and_double_args(self): """Create a daemon using the combined host:bin arg, find with 2""" - d = yield model.Daemon("testhost:nova-testdaemon") + d = model.Daemon("testhost:nova-testdaemon") d.save() d = model.Daemon('testhost', 'nova-testdaemon') self.assertFalse(d.is_new_record()) - @defer.inlineCallbacks def test_equality_daemon_of_double_and_single_args(self): """Create a daemon using the combined host:bin arg, find with 2""" - d = yield self.create_daemon() + d = self.create_daemon() d = model.Daemon('testhost:nova-testdaemon') self.assertFalse(d.is_new_record()) - @defer.inlineCallbacks def test_delete_daemon(self): """create, then destroy, then make sure loads a new record""" - instance = yield self.create_daemon() - yield instance.destroy() - newinst = yield model.Daemon('testhost', 'nova-testdaemon') + instance = self.create_daemon() + instance.destroy() + newinst = model.Daemon('testhost', 'nova-testdaemon') self.assertTrue(newinst.is_new_record()) - @defer.inlineCallbacks def test_daemon_heartbeat(self): """Create a daemon, sleep, heartbeat, check for update""" - d = yield self.create_daemon() + d = self.create_daemon() ts = d['updated_at'] time.sleep(2) d.heartbeat() @@ -190,70 +196,62 @@ class ModelTestCase(test.TrialTestCase): ts2 = d2['updated_at'] self.assert_(ts2 > ts) - @defer.inlineCallbacks def test_daemon_added_to_set(self): """create, then check that it is included in list""" - instance = yield self.create_daemon() + instance = self.create_daemon() found = False for x in model.Daemon.all(): if x.identifier == 'testhost:nova-testdaemon': found = True self.assert_(found) - @defer.inlineCallbacks def test_daemon_associates_host(self): """create, then check that it is listed for the host""" - instance = yield self.create_daemon() + instance = self.create_daemon() found = False for x in model.Daemon.by_host('testhost'): if x.identifier == 'testhost:nova-testdaemon': found = True self.assertTrue(found) - @defer.inlineCallbacks def test_create_session_token(self): """create""" - d = yield self.create_session_token() + d = self.create_session_token() d = model.SessionToken(d.token) self.assertFalse(d.is_new_record()) - @defer.inlineCallbacks def test_delete_session_token(self): """create, then destroy, then make sure loads a new record""" - instance = yield self.create_session_token() - yield instance.destroy() - newinst = yield model.SessionToken(instance.token) + instance = self.create_session_token() + instance.destroy() + newinst = model.SessionToken(instance.token) self.assertTrue(newinst.is_new_record()) - @defer.inlineCallbacks def test_session_token_added_to_set(self): """create, then check that it is included in list""" - instance = yield self.create_session_token() + instance = self.create_session_token() found = False for x in model.SessionToken.all(): if x.identifier == instance.token: found = True self.assert_(found) - @defer.inlineCallbacks def test_session_token_associates_user(self): """create, then check that it is listed for the user""" - instance = yield self.create_session_token() + instance = self.create_session_token() found = False for x in model.SessionToken.associated_to('user', 'testuser'): if x.identifier == instance.identifier: found = True self.assertTrue(found) - @defer.inlineCallbacks def test_session_token_generation(self): - instance = yield model.SessionToken.generate('username', 'TokenType') + instance = model.SessionToken.generate('username', 'TokenType') self.assertFalse(instance.is_new_record()) - @defer.inlineCallbacks def test_find_generated_session_token(self): - instance = yield model.SessionToken.generate('username', 'TokenType') - found = yield model.SessionToken.lookup(instance.identifier) + instance = model.SessionToken.generate('username', 'TokenType') + found = model.SessionToken.lookup(instance.identifier) self.assert_(found) def test_update_session_token_expiry(self): @@ -264,34 +262,29 @@ class ModelTestCase(test.TrialTestCase): expiry = utils.parse_isotime(instance['expiry']) self.assert_(expiry > datetime.utcnow()) - @defer.inlineCallbacks def test_session_token_lookup_when_expired(self): - instance = yield model.SessionToken.generate("testuser") + instance = model.SessionToken.generate("testuser") instance['expiry'] = datetime.utcnow().strftime(utils.TIME_FORMAT) instance.save() inst = model.SessionToken.lookup(instance.identifier) self.assertFalse(inst) - @defer.inlineCallbacks def test_session_token_lookup_when_not_expired(self): - instance = yield model.SessionToken.generate("testuser") + instance = model.SessionToken.generate("testuser") inst = model.SessionToken.lookup(instance.identifier) self.assert_(inst) - @defer.inlineCallbacks def test_session_token_is_expired_when_expired(self): - instance = yield model.SessionToken.generate("testuser") + instance = model.SessionToken.generate("testuser") instance['expiry'] = datetime.utcnow().strftime(utils.TIME_FORMAT) self.assert_(instance.is_expired()) - @defer.inlineCallbacks def test_session_token_is_expired_when_not_expired(self): - instance = yield model.SessionToken.generate("testuser") + instance = model.SessionToken.generate("testuser") self.assertFalse(instance.is_expired()) - @defer.inlineCallbacks def test_session_token_ttl(self): - instance = yield model.SessionToken.generate("testuser") + instance = model.SessionToken.generate("testuser") now = datetime.utcnow() delta = timedelta(hours=1) instance['expiry'] = (now + delta).strftime(utils.TIME_FORMAT) -- cgit From ecf8608a84960496c6c8e350f99d53537e4581c8 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 3 Aug 2010 14:31:47 -0700 Subject: Huge network refactor, Round I Made network into its own binary Made simple network a plugabble class Fixed unittests Moved various classes around Moved mac generation into network class --- bin/nova-dhcpbridge | 22 +- bin/nova-network | 11 +- nova/compute/exception.py | 40 --- nova/compute/linux_net.py | 181 ------------- nova/compute/network.py | 597 ----------------------------------------- nova/compute/service.py | 6 +- nova/endpoint/cloud.py | 129 +++++---- nova/network/exception.py | 40 +++ nova/network/linux_net.py | 181 +++++++++++++ nova/network/model.py | 549 +++++++++++++++++++++++++++++++++++++ nova/network/service.py | 135 +++++++++- nova/tests/network_unittest.py | 99 ++++--- 12 files changed, 1050 insertions(+), 940 deletions(-) delete mode 100644 nova/compute/exception.py delete mode 100644 nova/compute/linux_net.py delete mode 100644 nova/compute/network.py create mode 100644 nova/network/exception.py create mode 100644 nova/network/linux_net.py create mode 100644 nova/network/model.py diff --git a/bin/nova-dhcpbridge b/bin/nova-dhcpbridge index 0db241b5e..b3e7d456a 100755 --- a/bin/nova-dhcpbridge +++ b/bin/nova-dhcpbridge @@ -35,32 +35,34 @@ sys.path.append(os.path.abspath(os.path.join(__file__, "../../"))) from nova import flags from nova import rpc from nova import utils -from nova.compute import linux_net -from nova.compute import network - +from nova.network import linux_net +from nova.network import model +from nova.network import service FLAGS = flags.FLAGS def add_lease(mac, ip, hostname, interface): if FLAGS.fake_rabbit: - network.lease_ip(ip) + service.VlanNetworkService().lease_ip(ip) else: - rpc.cast(FLAGS.cloud_topic, {"method": "lease_ip", - "args" : {"address": ip}}) + rpc.cast("%s.%s" (FLAGS.network_topic, FLAGS.node_name), + {"method": "lease_ip", + "args" : {"fixed_ip": ip}}) def old_lease(mac, ip, hostname, interface): logging.debug("Adopted old lease or got a change of mac/hostname") def del_lease(mac, ip, hostname, interface): if FLAGS.fake_rabbit: - network.release_ip(ip) + service.VlanNetworkService().release_ip(ip) else: - rpc.cast(FLAGS.cloud_topic, {"method": "release_ip", - "args" : {"address": ip}}) + rpc.cast("%s.%s" (FLAGS.network_topic, FLAGS.node_name), + {"method": "release_ip", + "args" : {"fixed_ip": ip}}) def init_leases(interface): - net = network.get_network_by_interface(interface) + net = model.get_network_by_interface(interface) res = "" for host_name in net.hosts: res += "%s\n" % linux_net.hostDHCP(net, host_name, net.hosts[host_name]) diff --git a/bin/nova-network b/bin/nova-network index 52d6cb70a..b2e2cf173 100755 --- a/bin/nova-network +++ b/bin/nova-network @@ -21,12 +21,19 @@ Twistd daemon for the nova network nodes. """ +from nova import flags from nova import twistd -from nova.network import service +from nova import utils +FLAGS = flags.FLAGS + +flags.DEFINE_string('network_service', + 'nova.network.service.VlanNetworkService', + 'Service Class for Networking') + if __name__ == '__main__': twistd.serve(__file__) if __name__ == '__builtin__': - application = service.NetworkService.create() + application = utils.import_class(FLAGS.network_service).create() diff --git a/nova/compute/exception.py b/nova/compute/exception.py deleted file mode 100644 index 13e4f0a51..000000000 --- a/nova/compute/exception.py +++ /dev/null @@ -1,40 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# 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. - -""" -Exceptions for Compute Node errors, mostly network addressing. -""" - -from nova.exception import Error - - -class NoMoreAddresses(Error): - pass - -class AddressNotAllocated(Error): - pass - -class AddressAlreadyAssociated(Error): - pass - -class AddressNotAssociated(Error): - pass - -class NotValidNetworkSize(Error): - pass - diff --git a/nova/compute/linux_net.py b/nova/compute/linux_net.py deleted file mode 100644 index 4a4b4c8a8..000000000 --- a/nova/compute/linux_net.py +++ /dev/null @@ -1,181 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# 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 signal -import os -import subprocess - -# todo(ja): does the definition of network_path belong here? - -from nova import utils - -from nova import flags -FLAGS=flags.FLAGS - -flags.DEFINE_string('dhcpbridge_flagfile', - '/etc/nova/nova-dhcpbridge.conf', - 'location of flagfile for dhcpbridge') - -def execute(cmd, addl_env=None): - if FLAGS.fake_network: - logging.debug("FAKE NET: %s" % cmd) - return "fake", 0 - else: - return utils.execute(cmd, addl_env=addl_env) - -def runthis(desc, cmd): - if FLAGS.fake_network: - return execute(cmd) - else: - return utils.runthis(desc,cmd) - -def Popen(cmd): - if FLAGS.fake_network: - execute(' '.join(cmd)) - else: - subprocess.Popen(cmd) - - -def device_exists(device): - (out, err) = execute("ifconfig %s" % device) - return not err - -def confirm_rule(cmd): - execute("sudo iptables --delete %s" % (cmd)) - execute("sudo iptables -I %s" % (cmd)) - -def remove_rule(cmd): - execute("sudo iptables --delete %s" % (cmd)) - -def bind_public_ip(ip, interface): - runthis("Binding IP to interface: %s", "sudo ip addr add %s dev %s" % (ip, interface)) - -def unbind_public_ip(ip, interface): - runthis("Binding IP to interface: %s", "sudo ip addr del %s dev %s" % (ip, interface)) - -def vlan_create(net): - """ create a vlan on on a bridge device unless vlan already exists """ - if not device_exists("vlan%s" % net['vlan']): - logging.debug("Starting VLAN inteface for %s network", (net['vlan'])) - execute("sudo vconfig set_name_type VLAN_PLUS_VID_NO_PAD") - execute("sudo vconfig add %s %s" % (FLAGS.bridge_dev, net['vlan'])) - execute("sudo ifconfig vlan%s up" % (net['vlan'])) - -def bridge_create(net): - """ create a bridge on a vlan unless it already exists """ - if not device_exists(net['bridge_name']): - logging.debug("Starting Bridge inteface for %s network", (net['vlan'])) - execute("sudo brctl addbr %s" % (net['bridge_name'])) - execute("sudo brctl setfd %s 0" % (net.bridge_name)) - # execute("sudo brctl setageing %s 10" % (net.bridge_name)) - execute("sudo brctl stp %s off" % (net['bridge_name'])) - execute("sudo brctl addif %s vlan%s" % (net['bridge_name'], net['vlan'])) - if net.bridge_gets_ip: - execute("sudo ifconfig %s %s broadcast %s netmask %s up" % \ - (net['bridge_name'], net.gateway, net.broadcast, net.netmask)) - confirm_rule("FORWARD --in-interface %s -j ACCEPT" % (net['bridge_name'])) - else: - execute("sudo ifconfig %s up" % net['bridge_name']) - -def dnsmasq_cmd(net): - cmd = ['sudo -E dnsmasq', - ' --strict-order', - ' --bind-interfaces', - ' --conf-file=', - ' --pid-file=%s' % dhcp_file(net['vlan'], 'pid'), - ' --listen-address=%s' % net.dhcp_listen_address, - ' --except-interface=lo', - ' --dhcp-range=%s,static,600s' % (net.dhcp_range_start), - ' --dhcp-hostsfile=%s' % dhcp_file(net['vlan'], 'conf'), - ' --dhcp-script=%s' % bin_file('nova-dhcpbridge'), - ' --leasefile-ro'] - return ''.join(cmd) - -def hostDHCP(network, host, mac): - idx = host.split(".")[-1] # Logically, the idx of instances they've launched in this net - return "%s,%s-%s-%s.novalocal,%s" % \ - (mac, network['user_id'], network['vlan'], idx, host) - -# todo(ja): if the system has restarted or pid numbers have wrapped -# then you cannot be certain that the pid refers to the -# dnsmasq. As well, sending a HUP only reloads the hostfile, -# so any configuration options (like dchp-range, vlan, ...) -# aren't reloaded -def start_dnsmasq(network): - """ (re)starts a dnsmasq server for a given network - - if a dnsmasq instance is already running then send a HUP - signal causing it to reload, otherwise spawn a new instance - """ - with open(dhcp_file(network['vlan'], 'conf'), 'w') as f: - for host_name in network.hosts: - f.write("%s\n" % hostDHCP(network, host_name, network.hosts[host_name])) - - pid = dnsmasq_pid_for(network) - - # if dnsmasq is already running, then tell it to reload - if pid: - # todo(ja): use "/proc/%d/cmdline" % (pid) to determine if pid refers - # correct dnsmasq process - try: - os.kill(pid, signal.SIGHUP) - except Exception, e: - logging.debug("Hupping dnsmasq threw %s", e) - - # otherwise delete the existing leases file and start dnsmasq - lease_file = dhcp_file(network['vlan'], 'leases') - if os.path.exists(lease_file): - os.unlink(lease_file) - - # FLAGFILE and DNSMASQ_INTERFACE in env - env = {'FLAGFILE': FLAGS.dhcpbridge_flagfile, - 'DNSMASQ_INTERFACE': network['bridge_name']} - execute(dnsmasq_cmd(network), addl_env=env) - -def stop_dnsmasq(network): - """ stops the dnsmasq instance for a given network """ - pid = dnsmasq_pid_for(network) - - if pid: - 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 """ - - return os.path.abspath("%s/nova-%s.%s" % (FLAGS.networks_path, vlan, kind)) - -def bin_file(script): - return os.path.abspath(os.path.join(__file__, "../../../bin", script)) - -def dnsmasq_pid_for(network): - """ the pid for prior dnsmasq instance for a vlan, - returns None if no pid file exists - - if machine has rebooted pid might be incorrect (caller should check) - """ - - pid_file = dhcp_file(network['vlan'], 'pid') - - if os.path.exists(pid_file): - with open(pid_file, 'r') as f: - return int(f.read()) - diff --git a/nova/compute/network.py b/nova/compute/network.py deleted file mode 100644 index 62d892e58..000000000 --- a/nova/compute/network.py +++ /dev/null @@ -1,597 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# 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. - -""" -Classes for network control, including VLANs, DHCP, and IP allocation. -""" - -import IPy -import logging -import os -import time - -from nova import datastore -from nova import exception -from nova import flags -from nova import utils -from nova.auth import manager -from nova.compute import exception as compute_exception -from nova.compute import linux_net - - -FLAGS = flags.FLAGS -flags.DEFINE_string('networks_path', utils.abspath('../networks'), - 'Location to keep network config files') -flags.DEFINE_integer('public_vlan', 1, 'VLAN for public IP addresses') -flags.DEFINE_string('public_interface', 'vlan1', - 'Interface for public IP addresses') -flags.DEFINE_string('bridge_dev', 'eth1', - 'network device for bridges') -flags.DEFINE_integer('vlan_start', 100, 'First VLAN for private networks') -flags.DEFINE_integer('vlan_end', 4093, 'Last VLAN for private networks') -flags.DEFINE_integer('network_size', 256, - 'Number of addresses in each private subnet') -flags.DEFINE_string('public_range', '4.4.4.0/24', 'Public IP address block') -flags.DEFINE_string('private_range', '10.0.0.0/8', 'Private IP address block') -flags.DEFINE_integer('cnt_vpn_clients', 5, - 'Number of addresses reserved for vpn clients') -flags.DEFINE_integer('cloudpipe_start_port', 12000, - 'Starting port for mapped CloudPipe external ports') - -flags.DEFINE_boolean('simple_network', False, - 'Use simple networking instead of vlans') -flags.DEFINE_string('simple_network_bridge', 'br100', - 'Bridge for simple network instances') -flags.DEFINE_list('simple_network_ips', ['192.168.0.2'], - 'Available ips for simple network') -flags.DEFINE_string('simple_network_template', - utils.abspath('compute/interfaces.template'), - 'Template file for simple network') -flags.DEFINE_string('simple_network_netmask', '255.255.255.0', - 'Netmask for simple network') -flags.DEFINE_string('simple_network_network', '192.168.0.0', - 'Network for simple network') -flags.DEFINE_string('simple_network_gateway', '192.168.0.1', - 'Broadcast for simple network') -flags.DEFINE_string('simple_network_broadcast', '192.168.0.255', - 'Broadcast for simple network') -flags.DEFINE_string('simple_network_dns', '8.8.4.4', - 'Dns for simple network') - -logging.getLogger().setLevel(logging.DEBUG) - - -class Vlan(datastore.BasicModel): - def __init__(self, project, vlan): - """ - Since we don't want to try and find a vlan by its identifier, - but by a project id, we don't call super-init. - """ - self.project_id = project - self.vlan_id = vlan - - @property - def identifier(self): - return "%s:%s" % (self.project_id, self.vlan_id) - - @classmethod - def create(cls, project, vlan): - instance = cls(project, vlan) - instance.save() - return instance - - @classmethod - @datastore.absorb_connection_error - def lookup(cls, project): - set_name = cls._redis_set_name(cls.__name__) - vlan = datastore.Redis.instance().hget(set_name, project) - if vlan: - return cls(project, vlan) - else: - return None - - @classmethod - @datastore.absorb_connection_error - def dict_by_project(cls): - """a hash of project:vlan""" - set_name = cls._redis_set_name(cls.__name__) - return datastore.Redis.instance().hgetall(set_name) - - @classmethod - @datastore.absorb_connection_error - def dict_by_vlan(cls): - """a hash of vlan:project""" - set_name = cls._redis_set_name(cls.__name__) - rv = {} - h = datastore.Redis.instance().hgetall(set_name) - for v in h.keys(): - rv[h[v]] = v - return rv - - @classmethod - @datastore.absorb_connection_error - def all(cls): - set_name = cls._redis_set_name(cls.__name__) - elements = datastore.Redis.instance().hgetall(set_name) - for project in elements: - yield cls(project, elements[project]) - - @datastore.absorb_connection_error - def save(self): - """ - Vlan saves state into a giant hash named "vlans", with keys of - project_id and value of vlan number. Therefore, we skip the - default way of saving into "vlan:ID" and adding to a set of "vlans". - """ - set_name = self._redis_set_name(self.__class__.__name__) - datastore.Redis.instance().hset(set_name, self.project_id, self.vlan_id) - - @datastore.absorb_connection_error - def destroy(self): - set_name = self._redis_set_name(self.__class__.__name__) - datastore.Redis.instance().hdel(set_name, self.project_id) - - def subnet(self): - vlan = int(self.vlan_id) - network = IPy.IP(FLAGS.private_range) - start = (vlan-FLAGS.vlan_start) * FLAGS.network_size - # minus one for the gateway. - return "%s-%s" % (network[start], - network[start + FLAGS.network_size - 1]) - -# CLEANUP: -# TODO(ja): Save the IPs at the top of each subnet for cloudpipe vpn clients -# TODO(ja): use singleton for usermanager instead of self.manager in vlanpool et al -# TODO(ja): does vlanpool "keeper" need to know the min/max - shouldn't FLAGS always win? -# TODO(joshua): Save the IPs at the top of each subnet for cloudpipe vpn clients - -class BaseNetwork(datastore.BasicModel): - override_type = 'network' - NUM_STATIC_IPS = 3 # Network, Gateway, and CloudPipe - - @property - def identifier(self): - return self.network_id - - def default_state(self): - return {'network_id': self.network_id, 'network_str': self.network_str} - - @classmethod - def create(cls, user_id, project_id, security_group, vlan, network_str): - network_id = "%s:%s" % (project_id, security_group) - net = cls(network_id, network_str) - net['user_id'] = user_id - net['project_id'] = project_id - net["vlan"] = vlan - net["bridge_name"] = "br%s" % vlan - net.save() - return net - - def __init__(self, network_id, network_str=None): - self.network_id = network_id - self.network_str = network_str - super(BaseNetwork, self).__init__() - self.save() - - @property - def network(self): - return IPy.IP(self['network_str']) - - @property - def netmask(self): - return self.network.netmask() - - @property - def gateway(self): - return self.network[1] - - @property - def broadcast(self): - return self.network.broadcast() - - @property - def bridge_name(self): - return "br%s" % (self["vlan"]) - - @property - def user(self): - return manager.AuthManager().get_user(self['user_id']) - - @property - def project(self): - return manager.AuthManager().get_project(self['project_id']) - - @property - def _hosts_key(self): - return "network:%s:hosts" % (self['network_str']) - - @property - def hosts(self): - return datastore.Redis.instance().hgetall(self._hosts_key) or {} - - def _add_host(self, _user_id, _project_id, host, target): - datastore.Redis.instance().hset(self._hosts_key, host, target) - - def _rem_host(self, host): - datastore.Redis.instance().hdel(self._hosts_key, host) - - @property - def assigned(self): - return datastore.Redis.instance().hkeys(self._hosts_key) - - @property - def available(self): - # the .2 address is always CloudPipe - # and the top are for vpn clients - for idx in range(self.num_static_ips, len(self.network)-(1 + FLAGS.cnt_vpn_clients)): - address = str(self.network[idx]) - if not address in self.hosts.keys(): - yield str(address) - - @property - def num_static_ips(self): - return BaseNetwork.NUM_STATIC_IPS - - def allocate_ip(self, user_id, project_id, mac): - for address in self.available: - logging.debug("Allocating IP %s to %s" % (address, project_id)) - self._add_host(user_id, project_id, address, mac) - self.express(address=address) - return address - raise compute_exception.NoMoreAddresses("Project %s with network %s" % - (project_id, str(self.network))) - - def lease_ip(self, ip_str): - logging.debug("Leasing allocated IP %s" % (ip_str)) - - def release_ip(self, ip_str): - if not ip_str in self.assigned: - raise compute_exception.AddressNotAllocated() - self.deexpress(address=ip_str) - self._rem_host(ip_str) - - def deallocate_ip(self, ip_str): - # Do nothing for now, cleanup on ip release - pass - - def list_addresses(self): - for address in self.hosts: - yield address - - def express(self, address=None): pass - def deexpress(self, address=None): pass - - -class BridgedNetwork(BaseNetwork): - """ - Virtual Network that can express itself to create a vlan and - a bridge (with or without an IP address/netmask/gateway) - - properties: - bridge_name - string (example value: br42) - vlan - integer (example value: 42) - bridge_dev - string (example: eth0) - bridge_gets_ip - boolean used during bridge creation - - if bridge_gets_ip then network address for bridge uses the properties: - gateway - broadcast - netmask - """ - - bridge_gets_ip = False - override_type = 'network' - - @classmethod - def get_network_for_project(cls, user_id, project_id, security_group): - vlan = get_vlan_for_project(project_id) - network_str = vlan.subnet() - return cls.create(user_id, project_id, security_group, vlan.vlan_id, - network_str) - - def __init__(self, *args, **kwargs): - super(BridgedNetwork, self).__init__(*args, **kwargs) - self['bridge_dev'] = FLAGS.bridge_dev - self.save() - - def express(self, address=None): - super(BridgedNetwork, self).express(address=address) - linux_net.vlan_create(self) - linux_net.bridge_create(self) - -class DHCPNetwork(BridgedNetwork): - """ - properties: - dhcp_listen_address: the ip of the gateway / dhcp host - dhcp_range_start: the first ip to give out - dhcp_range_end: the last ip to give out - """ - bridge_gets_ip = True - override_type = 'network' - - def __init__(self, *args, **kwargs): - super(DHCPNetwork, self).__init__(*args, **kwargs) - # logging.debug("Initing DHCPNetwork object...") - self.dhcp_listen_address = self.network[1] - self.dhcp_range_start = self.network[3] - self.dhcp_range_end = self.network[-(1 + FLAGS.cnt_vpn_clients)] - try: - os.makedirs(FLAGS.networks_path) - # NOTE(todd): I guess this is a lazy way to not have to check if the - # directory exists, but shouldn't we be smarter about - # telling the difference between existing directory and - # permission denied? (Errno 17 vs 13, OSError) - except Exception, err: - pass - - def express(self, address=None): - super(DHCPNetwork, self).express(address=address) - if len(self.assigned) > 0: - logging.debug("Starting dnsmasq server for network with vlan %s", - self['vlan']) - linux_net.start_dnsmasq(self) - else: - logging.debug("Not launching dnsmasq: no hosts.") - self.express_cloudpipe() - - def allocate_vpn_ip(self, mac): - address = str(self.network[2]) - self._add_host(self['user_id'], self['project_id'], address, mac) - self.express(address=address) - return address - - def express_cloudpipe(self): - private_ip = self.network[2] - linux_net.confirm_rule("FORWARD -d %s -p udp --dport 1194 -j ACCEPT" - % (private_ip, )) - linux_net.confirm_rule("PREROUTING -t nat -d %s -p udp --dport %s -j DNAT --to %s:1194" - % (self.project.vpn_ip, self.project.vpn_port, private_ip)) - - def deexpress(self, address=None): - # if this is the last address, stop dns - super(DHCPNetwork, self).deexpress(address=address) - if len(self.assigned) == 0: - linux_net.stop_dnsmasq(self) - else: - linux_net.start_dnsmasq(self) - -class PublicAddress(datastore.BasicModel): - override_type = "address" - - def __init__(self, address): - self.address = address - super(PublicAddress, self).__init__() - - @property - def identifier(self): - return self.address - - def default_state(self): - return {'address': self.address} - - @classmethod - def create(cls, user_id, project_id, address): - addr = cls(address) - addr['user_id'] = user_id - addr['project_id'] = project_id - addr['instance_id'] = 'available' - addr['private_ip'] = 'available' - addr.save() - return addr - -DEFAULT_PORTS = [("tcp",80), ("tcp",22), ("udp",1194), ("tcp",443)] -class PublicNetworkController(BaseNetwork): - override_type = 'network' - - def __init__(self, *args, **kwargs): - network_id = "public:default" - super(PublicNetworkController, self).__init__(network_id, FLAGS.public_range) - self['user_id'] = "public" - self['project_id'] = "public" - self["create_time"] = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) - self["vlan"] = FLAGS.public_vlan - self.save() - self.express() - - @property - def available(self): - for idx in range(2, len(self.network)-1): - address = str(self.network[idx]) - if not address in self.hosts.keys(): - yield address - - @property - def host_objs(self): - for address in self.assigned: - yield PublicAddress(address) - - def get_public_ip_for_instance(self, instance_id): - # FIXME: this should be a lookup - iteration won't scale - for address_record in self.host_objs: - if address_record.get('instance_id', 'available') == instance_id: - return address_record['address'] - - def get_host(self, host): - if host in self.assigned: - return PublicAddress(host) - return None - - def _add_host(self, user_id, project_id, host, _target): - datastore.Redis.instance().hset(self._hosts_key, host, project_id) - PublicAddress.create(user_id, project_id, host) - - def _rem_host(self, host): - PublicAddress(host).destroy() - datastore.Redis.instance().hdel(self._hosts_key, host) - - def associate_address(self, public_ip, private_ip, instance_id): - if not public_ip in self.assigned: - raise compute_exception.AddressNotAllocated() - # TODO(joshua): Keep an index going both ways - for addr in self.host_objs: - if addr.get('private_ip', None) == private_ip: - raise compute_exception.AddressAlreadyAssociated() - addr = self.get_host(public_ip) - if addr.get('private_ip', 'available') != 'available': - raise compute_exception.AddressAlreadyAssociated() - addr['private_ip'] = private_ip - addr['instance_id'] = instance_id - addr.save() - self.express(address=public_ip) - - def disassociate_address(self, public_ip): - if not public_ip in self.assigned: - raise compute_exception.AddressNotAllocated() - addr = self.get_host(public_ip) - if addr.get('private_ip', 'available') == 'available': - raise compute_exception.AddressNotAssociated() - self.deexpress(address=public_ip) - addr['private_ip'] = 'available' - addr['instance_id'] = 'available' - addr.save() - - def express(self, address=None): - addresses = self.host_objs - if address: - addresses = [self.get_host(address)] - for addr in addresses: - if addr.get('private_ip','available') == 'available': - continue - public_ip = addr['address'] - private_ip = addr['private_ip'] - linux_net.bind_public_ip(public_ip, FLAGS.public_interface) - linux_net.confirm_rule("PREROUTING -t nat -d %s -j DNAT --to %s" - % (public_ip, private_ip)) - linux_net.confirm_rule("POSTROUTING -t nat -s %s -j SNAT --to %s" - % (private_ip, public_ip)) - # TODO: Get these from the secgroup datastore entries - linux_net.confirm_rule("FORWARD -d %s -p icmp -j ACCEPT" - % (private_ip)) - for (protocol, port) in DEFAULT_PORTS: - linux_net.confirm_rule("FORWARD -d %s -p %s --dport %s -j ACCEPT" - % (private_ip, protocol, port)) - - def deexpress(self, address=None): - addr = self.get_host(address) - private_ip = addr['private_ip'] - linux_net.unbind_public_ip(address, FLAGS.public_interface) - linux_net.remove_rule("PREROUTING -t nat -d %s -j DNAT --to %s" - % (address, private_ip)) - linux_net.remove_rule("POSTROUTING -t nat -s %s -j SNAT --to %s" - % (private_ip, address)) - linux_net.remove_rule("FORWARD -d %s -p icmp -j ACCEPT" - % (private_ip)) - for (protocol, port) in DEFAULT_PORTS: - linux_net.remove_rule("FORWARD -d %s -p %s --dport %s -j ACCEPT" - % (private_ip, protocol, port)) - - -# FIXME(todd): does this present a race condition, or is there some piece of -# architecture that mitigates it (only one queue listener per net)? -def get_vlan_for_project(project_id): - """ - Allocate vlan IDs to individual users. - """ - vlan = Vlan.lookup(project_id) - if vlan: - return vlan - known_vlans = Vlan.dict_by_vlan() - for vnum in range(FLAGS.vlan_start, FLAGS.vlan_end): - vstr = str(vnum) - if not known_vlans.has_key(vstr): - return Vlan.create(project_id, vnum) - old_project_id = known_vlans[vstr] - if not manager.AuthManager().get_project(old_project_id): - vlan = Vlan.lookup(old_project_id) - if vlan: - # NOTE(todd): This doesn't check for vlan id match, because - # it seems to be assumed that vlan<=>project is - # always a 1:1 mapping. It could be made way - # sexier if it didn't fight against the way - # BasicModel worked and used associate_with - # to build connections to projects. - # NOTE(josh): This is here because we want to make sure we - # don't orphan any VLANs. It is basically - # garbage collection for after projects abandoned - # their reference. - vlan.destroy() - vlan.project_id = project_id - vlan.save() - return vlan - else: - return Vlan.create(project_id, vnum) - raise compute_exception.AddressNotAllocated("Out of VLANs") - -def get_network_by_interface(iface, security_group='default'): - vlan = iface.rpartition("br")[2] - return get_project_network(Vlan.dict_by_vlan().get(vlan), security_group) - -def get_network_by_address(address): - logging.debug("Get Network By Address: %s" % address) - for project in manager.AuthManager().get_projects(): - net = get_project_network(project.id) - if address in net.assigned: - logging.debug("Found %s in %s" % (address, project.id)) - return net - raise compute_exception.AddressNotAllocated() - -def allocate_simple_ip(): - redis = datastore.Redis.instance() - if not redis.exists('ips') and not len(redis.keys('instances:*')): - for address in FLAGS.simple_network_ips: - redis.sadd('ips', address) - address = redis.spop('ips') - if not address: - raise exception.NoMoreAddresses() - return address - -def deallocate_simple_ip(address): - datastore.Redis.instance().sadd('ips', address) - - -def allocate_vpn_ip(user_id, project_id, mac): - return get_project_network(project_id).allocate_vpn_ip(mac) - -def allocate_ip(user_id, project_id, mac): - return get_project_network(project_id).allocate_ip(user_id, project_id, mac) - -def deallocate_ip(address): - return get_network_by_address(address).deallocate_ip(address) - -def release_ip(address): - return get_network_by_address(address).release_ip(address) - -def lease_ip(address): - return get_network_by_address(address).lease_ip(address) - -def get_project_network(project_id, security_group='default'): - """ get a project's private network, allocating one if needed """ - # TODO(todd): It looks goofy to get a project from a UserManager. - # Refactor to still use the LDAP backend, but not User specific. - project = manager.AuthManager().get_project(project_id) - if not project: - raise exception.Error("Project %s doesn't exist, uhoh." % - project_id) - return DHCPNetwork.get_network_for_project(project.project_manager_id, - project.id, security_group) - - -def restart_nets(): - """ Ensure the network for each user is enabled""" - for project in manager.AuthManager().get_projects(): - get_project_network(project.id).express() diff --git a/nova/compute/service.py b/nova/compute/service.py index 9b162edc7..a22240b05 100644 --- a/nova/compute/service.py +++ b/nova/compute/service.py @@ -39,9 +39,9 @@ from nova import service from nova import utils from nova.compute import disk from nova.compute import model -from nova.compute import network from nova.compute import power_state from nova.compute.instance_types import INSTANCE_TYPES +from nova.network import model as net_model from nova.objectstore import image # for image_path flag from nova.virt import connection as virt_connection from nova.volume import service as volume_service @@ -117,10 +117,10 @@ class ComputeService(service.Service): """ launch a new instance with specified options """ logging.debug("Starting instance %s..." % (instance_id)) inst = self.instdir.get(instance_id) - if not FLAGS.simple_network: + if inst.get('network_type', 'dhcp') == 'dhcp': # TODO: Get the real security group of launch in here security_group = "default" - net = network.BridgedNetwork.get_network_for_project(inst['user_id'], + net = net_model.BridgedNetwork.get_network_for_project(inst['user_id'], inst['project_id'], security_group).express() inst['node_name'] = FLAGS.node_name diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 67fc04502..f605eec2c 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -36,11 +36,9 @@ from nova import utils from nova.auth import rbac from nova.auth import manager from nova.compute import model -from nova.compute import network from nova.compute.instance_types import INSTANCE_TYPES -from nova.compute import service as compute_service from nova.endpoint import images -from nova.volume import service as volume_service +from nova.volume import service FLAGS = flags.FLAGS @@ -64,7 +62,6 @@ class CloudController(object): """ def __init__(self): self.instdir = model.InstanceDirectory() - self.network = network.PublicNetworkController() self.setup() @property @@ -76,7 +73,7 @@ class CloudController(object): def volumes(self): """ returns a list of all volumes """ for volume_id in datastore.Redis.instance().smembers("volumes"): - volume = volume_service.get_volume(volume_id) + volume = service.get_volume(volume_id) yield volume def __str__(self): @@ -222,7 +219,7 @@ class CloudController(object): callback=_complete) return d - except users.UserError, e: + except manager.UserError as e: raise @rbac.allow('all') @@ -331,7 +328,7 @@ class CloudController(object): raise exception.NotFound('Instance %s could not be found' % instance_id) def _get_volume(self, context, volume_id): - volume = volume_service.get_volume(volume_id) + volume = service.get_volume(volume_id) 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) @@ -472,29 +469,34 @@ class CloudController(object): @rbac.allow('netadmin') def allocate_address(self, context, **kwargs): - address = self.network.allocate_ip( - context.user.id, context.project.id, 'public') - return defer.succeed({'addressSet': [{'publicIp' : address}]}) + alloc_result = rpc.call(self._get_network_host(context), + {"method": "allocate_elastic_ip"}) + public_ip = alloc_result['result'] + return defer.succeed({'addressSet': [{'publicIp' : public_ip}]}) @rbac.allow('netadmin') def release_address(self, context, public_ip, **kwargs): - self.network.deallocate_ip(public_ip) + # NOTE(vish): Should we make sure this works? + rpc.cast(self._get_network_host(context), + {"method": "deallocate_elastic_ip", + "args": {"elastic_ip": public_ip}}) return defer.succeed({'releaseResponse': ["Address released."]}) @rbac.allow('netadmin') - def associate_address(self, context, instance_id, **kwargs): + def associate_address(self, context, instance_id, public_ip, **kwargs): instance = self._get_instance(context, instance_id) - self.network.associate_address( - kwargs['public_ip'], - instance['private_dns_name'], - instance_id) + address = self._get_address(context, public_ip) + rpc.cast(self._get_network_host(context), + {"method": "associate_elastic_ip", + "args": {"elastic_ip": address['public_ip'], + "fixed_ip": instance['private_dns_name'], + "instance_id": instance['instance_id']}}) return defer.succeed({'associateResponse': ["Address associated."]}) @rbac.allow('netadmin') def disassociate_address(self, context, public_ip, **kwargs): address = self._get_address(context, public_ip) self.network.disassociate_address(public_ip) - # TODO - Strip the IP from the instance return defer.succeed({'disassociateResponse': ["Address disassociated."]}) def release_ip(self, context, private_ip, **kwargs): @@ -505,7 +507,13 @@ class CloudController(object): self.network.lease_ip(private_ip) return defer.succeed({'leaseResponse': ["Address leased."]}) + def get_network_host(self, context): + # FIXME(vish): this is temporary until we store net hosts for project + import socket + return socket.gethostname() + @rbac.allow('projectmanager', 'sysadmin') + @defer.inlineCallbacks def run_instances(self, context, **kwargs): # make sure user can access the image # vpn image is private so it doesn't show up on lists @@ -539,14 +547,25 @@ class CloudController(object): key_data = key_pair.public_key # TODO: Get the real security group of launch in here security_group = "default" - if FLAGS.simple_network: - bridge_name = FLAGS.simple_network_bridge - else: - net = network.BridgedNetwork.get_network_for_project( - context.user.id, context.project.id, security_group) - bridge_name = net['bridge_name'] + create_result = yield rpc.call(FLAGS.network_topic, + {"method": "create_network", + "args": {"user_id": context.user.id, + "project_id": context.project.id, + "security_group": security_group}}) + bridge_name = create_result['result'] + net_host = self._get_network_host(context) for num in range(int(kwargs['max_count'])): + vpn = False + if image_id == FLAGS.vpn_image_id: + vpn = True + allocate_result = yield rpc.call(net_host, + {"method": "allocate_fixed_ip", + "args": {"user_id": context.user.id, + "project_id": context.project.id, + "vpn": vpn}}) inst = self.instdir.new() + inst['mac_address'] = allocate_result['result']['mac_address'] + inst['private_dns_name'] = allocate_result['result']['ip_address'] inst['image_id'] = image_id inst['kernel_id'] = kernel_id inst['ramdisk_id'] = ramdisk_id @@ -558,24 +577,9 @@ class CloudController(object): inst['key_name'] = kwargs.get('key_name', '') inst['user_id'] = context.user.id inst['project_id'] = context.project.id - inst['mac_address'] = utils.generate_mac() inst['ami_launch_index'] = num inst['bridge_name'] = bridge_name - if FLAGS.simple_network: - address = network.allocate_simple_ip() - else: - if inst['image_id'] == FLAGS.vpn_image_id: - address = network.allocate_vpn_ip( - inst['user_id'], - inst['project_id'], - mac=inst['mac_address']) - else: - address = network.allocate_ip( - inst['user_id'], - inst['project_id'], - mac=inst['mac_address']) - inst['private_dns_name'] = str(address) - # TODO: allocate expresses on the router node + inst.save() rpc.cast(FLAGS.compute_topic, {"method": "run_instance", @@ -583,8 +587,7 @@ 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 Network figure out the network name from ip. - return defer.succeed(self._format_instances( - context, reservation_id)) + defer.returnValue(self._format_instances(context, reservation_id)) @rbac.allow('projectmanager', 'sysadmin') def terminate_instances(self, context, instance_id, **kwargs): @@ -594,26 +597,34 @@ class CloudController(object): try: instance = self._get_instance(context, i) except exception.NotFound: - logging.warning("Instance %s was not found during terminate" % i) + 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)) - if FLAGS.simple_network: - network.deallocate_simple_ip(instance.get('private_dns_name', None)) - else: - try: - 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 + elastic_ip = instance.get('public_dns_name', None) + if elastic_ip: + logging.debug("Deallocating address %s" % elastic_ip) + # NOTE(vish): Right now we don't really care if the ip is + # disassociated. We may need to worry about + # checking this later. Perhaps in the scheduler? + rpc.cast(self._get_network_host(context), + {"method": "disassociate_elastic_ip", + "args": {"elastic_ip": elastic_ip}}) + + fixed_ip = instance.get('private_dns_name', None) + if fixed_ip: + logging.debug("Deallocating address %s" % fixed_ip) + # NOTE(vish): Right now we don't really care if the ip is + # actually removed. We may need to worry about + # checking this later. Perhaps in the scheduler? + rpc.cast(self._get_network_host(context), + {"method": "deallocate_fixed_ip", + "args": {"elastic_ip": elastic_ip}}) + + if instance.get('node_name', 'unassigned') != 'unassigned': + # NOTE(joshua?): It's also internal default rpc.cast('%s.%s' % (FLAGS.compute_topic, instance['node_name']), - {"method": "terminate_instance", - "args" : {"instance_id": i}}) + {"method": "terminate_instance", + "args": {"instance_id": i}}) else: instance.destroy() return defer.succeed(True) diff --git a/nova/network/exception.py b/nova/network/exception.py new file mode 100644 index 000000000..5722e9672 --- /dev/null +++ b/nova/network/exception.py @@ -0,0 +1,40 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# 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. + +""" +Exceptions for network errors. +""" + +from nova.exception import Error + + +class NoMoreAddresses(Error): + pass + +class AddressNotAllocated(Error): + pass + +class AddressAlreadyAssociated(Error): + pass + +class AddressNotAssociated(Error): + pass + +class NotValidNetworkSize(Error): + pass + diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py new file mode 100644 index 000000000..4a4b4c8a8 --- /dev/null +++ b/nova/network/linux_net.py @@ -0,0 +1,181 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# 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 signal +import os +import subprocess + +# todo(ja): does the definition of network_path belong here? + +from nova import utils + +from nova import flags +FLAGS=flags.FLAGS + +flags.DEFINE_string('dhcpbridge_flagfile', + '/etc/nova/nova-dhcpbridge.conf', + 'location of flagfile for dhcpbridge') + +def execute(cmd, addl_env=None): + if FLAGS.fake_network: + logging.debug("FAKE NET: %s" % cmd) + return "fake", 0 + else: + return utils.execute(cmd, addl_env=addl_env) + +def runthis(desc, cmd): + if FLAGS.fake_network: + return execute(cmd) + else: + return utils.runthis(desc,cmd) + +def Popen(cmd): + if FLAGS.fake_network: + execute(' '.join(cmd)) + else: + subprocess.Popen(cmd) + + +def device_exists(device): + (out, err) = execute("ifconfig %s" % device) + return not err + +def confirm_rule(cmd): + execute("sudo iptables --delete %s" % (cmd)) + execute("sudo iptables -I %s" % (cmd)) + +def remove_rule(cmd): + execute("sudo iptables --delete %s" % (cmd)) + +def bind_public_ip(ip, interface): + runthis("Binding IP to interface: %s", "sudo ip addr add %s dev %s" % (ip, interface)) + +def unbind_public_ip(ip, interface): + runthis("Binding IP to interface: %s", "sudo ip addr del %s dev %s" % (ip, interface)) + +def vlan_create(net): + """ create a vlan on on a bridge device unless vlan already exists """ + if not device_exists("vlan%s" % net['vlan']): + logging.debug("Starting VLAN inteface for %s network", (net['vlan'])) + execute("sudo vconfig set_name_type VLAN_PLUS_VID_NO_PAD") + execute("sudo vconfig add %s %s" % (FLAGS.bridge_dev, net['vlan'])) + execute("sudo ifconfig vlan%s up" % (net['vlan'])) + +def bridge_create(net): + """ create a bridge on a vlan unless it already exists """ + if not device_exists(net['bridge_name']): + logging.debug("Starting Bridge inteface for %s network", (net['vlan'])) + execute("sudo brctl addbr %s" % (net['bridge_name'])) + execute("sudo brctl setfd %s 0" % (net.bridge_name)) + # execute("sudo brctl setageing %s 10" % (net.bridge_name)) + execute("sudo brctl stp %s off" % (net['bridge_name'])) + execute("sudo brctl addif %s vlan%s" % (net['bridge_name'], net['vlan'])) + if net.bridge_gets_ip: + execute("sudo ifconfig %s %s broadcast %s netmask %s up" % \ + (net['bridge_name'], net.gateway, net.broadcast, net.netmask)) + confirm_rule("FORWARD --in-interface %s -j ACCEPT" % (net['bridge_name'])) + else: + execute("sudo ifconfig %s up" % net['bridge_name']) + +def dnsmasq_cmd(net): + cmd = ['sudo -E dnsmasq', + ' --strict-order', + ' --bind-interfaces', + ' --conf-file=', + ' --pid-file=%s' % dhcp_file(net['vlan'], 'pid'), + ' --listen-address=%s' % net.dhcp_listen_address, + ' --except-interface=lo', + ' --dhcp-range=%s,static,600s' % (net.dhcp_range_start), + ' --dhcp-hostsfile=%s' % dhcp_file(net['vlan'], 'conf'), + ' --dhcp-script=%s' % bin_file('nova-dhcpbridge'), + ' --leasefile-ro'] + return ''.join(cmd) + +def hostDHCP(network, host, mac): + idx = host.split(".")[-1] # Logically, the idx of instances they've launched in this net + return "%s,%s-%s-%s.novalocal,%s" % \ + (mac, network['user_id'], network['vlan'], idx, host) + +# todo(ja): if the system has restarted or pid numbers have wrapped +# then you cannot be certain that the pid refers to the +# dnsmasq. As well, sending a HUP only reloads the hostfile, +# so any configuration options (like dchp-range, vlan, ...) +# aren't reloaded +def start_dnsmasq(network): + """ (re)starts a dnsmasq server for a given network + + if a dnsmasq instance is already running then send a HUP + signal causing it to reload, otherwise spawn a new instance + """ + with open(dhcp_file(network['vlan'], 'conf'), 'w') as f: + for host_name in network.hosts: + f.write("%s\n" % hostDHCP(network, host_name, network.hosts[host_name])) + + pid = dnsmasq_pid_for(network) + + # if dnsmasq is already running, then tell it to reload + if pid: + # todo(ja): use "/proc/%d/cmdline" % (pid) to determine if pid refers + # correct dnsmasq process + try: + os.kill(pid, signal.SIGHUP) + except Exception, e: + logging.debug("Hupping dnsmasq threw %s", e) + + # otherwise delete the existing leases file and start dnsmasq + lease_file = dhcp_file(network['vlan'], 'leases') + if os.path.exists(lease_file): + os.unlink(lease_file) + + # FLAGFILE and DNSMASQ_INTERFACE in env + env = {'FLAGFILE': FLAGS.dhcpbridge_flagfile, + 'DNSMASQ_INTERFACE': network['bridge_name']} + execute(dnsmasq_cmd(network), addl_env=env) + +def stop_dnsmasq(network): + """ stops the dnsmasq instance for a given network """ + pid = dnsmasq_pid_for(network) + + if pid: + 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 """ + + return os.path.abspath("%s/nova-%s.%s" % (FLAGS.networks_path, vlan, kind)) + +def bin_file(script): + return os.path.abspath(os.path.join(__file__, "../../../bin", script)) + +def dnsmasq_pid_for(network): + """ the pid for prior dnsmasq instance for a vlan, + returns None if no pid file exists + + if machine has rebooted pid might be incorrect (caller should check) + """ + + pid_file = dhcp_file(network['vlan'], 'pid') + + if os.path.exists(pid_file): + with open(pid_file, 'r') as f: + return int(f.read()) + diff --git a/nova/network/model.py b/nova/network/model.py new file mode 100644 index 000000000..5346549b8 --- /dev/null +++ b/nova/network/model.py @@ -0,0 +1,549 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# 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. + +""" +Model Classes for network control, including VLANs, DHCP, and IP allocation. +""" + +import IPy +import logging +import os +import time + +from nova import datastore +from nova import exception as nova_exception +from nova import flags +from nova import utils +from nova.auth import manager +from nova.network import exception +from nova.network import linux_net + + +FLAGS = flags.FLAGS +flags.DEFINE_string('networks_path', utils.abspath('../networks'), + 'Location to keep network config files') +flags.DEFINE_integer('public_vlan', 1, 'VLAN for public IP addresses') +flags.DEFINE_string('public_interface', 'vlan1', + 'Interface for public IP addresses') +flags.DEFINE_string('bridge_dev', 'eth1', + 'network device for bridges') +flags.DEFINE_integer('vlan_start', 100, 'First VLAN for private networks') +flags.DEFINE_integer('vlan_end', 4093, 'Last VLAN for private networks') +flags.DEFINE_integer('network_size', 256, + 'Number of addresses in each private subnet') +flags.DEFINE_string('public_range', '4.4.4.0/24', 'Public IP address block') +flags.DEFINE_string('private_range', '10.0.0.0/8', 'Private IP address block') +flags.DEFINE_integer('cnt_vpn_clients', 5, + 'Number of addresses reserved for vpn clients') +flags.DEFINE_integer('cloudpipe_start_port', 12000, + 'Starting port for mapped CloudPipe external ports') + +logging.getLogger().setLevel(logging.DEBUG) + + +class Vlan(datastore.BasicModel): + def __init__(self, project, vlan): + """ + Since we don't want to try and find a vlan by its identifier, + but by a project id, we don't call super-init. + """ + self.project_id = project + self.vlan_id = vlan + + @property + def identifier(self): + return "%s:%s" % (self.project_id, self.vlan_id) + + @classmethod + def create(cls, project, vlan): + instance = cls(project, vlan) + instance.save() + return instance + + @classmethod + @datastore.absorb_connection_error + def lookup(cls, project): + set_name = cls._redis_set_name(cls.__name__) + vlan = datastore.Redis.instance().hget(set_name, project) + if vlan: + return cls(project, vlan) + else: + return None + + @classmethod + @datastore.absorb_connection_error + def dict_by_project(cls): + """a hash of project:vlan""" + set_name = cls._redis_set_name(cls.__name__) + return datastore.Redis.instance().hgetall(set_name) + + @classmethod + @datastore.absorb_connection_error + def dict_by_vlan(cls): + """a hash of vlan:project""" + set_name = cls._redis_set_name(cls.__name__) + rv = {} + h = datastore.Redis.instance().hgetall(set_name) + for v in h.keys(): + rv[h[v]] = v + return rv + + @classmethod + @datastore.absorb_connection_error + def all(cls): + set_name = cls._redis_set_name(cls.__name__) + elements = datastore.Redis.instance().hgetall(set_name) + for project in elements: + yield cls(project, elements[project]) + + @datastore.absorb_connection_error + def save(self): + """ + Vlan saves state into a giant hash named "vlans", with keys of + project_id and value of vlan number. Therefore, we skip the + default way of saving into "vlan:ID" and adding to a set of "vlans". + """ + set_name = self._redis_set_name(self.__class__.__name__) + datastore.Redis.instance().hset(set_name, self.project_id, self.vlan_id) + + @datastore.absorb_connection_error + def destroy(self): + set_name = self._redis_set_name(self.__class__.__name__) + datastore.Redis.instance().hdel(set_name, self.project_id) + + def subnet(self): + vlan = int(self.vlan_id) + network = IPy.IP(FLAGS.private_range) + start = (vlan-FLAGS.vlan_start) * FLAGS.network_size + # minus one for the gateway. + return "%s-%s" % (network[start], + network[start + FLAGS.network_size - 1]) + +# CLEANUP: +# TODO(ja): Save the IPs at the top of each subnet for cloudpipe vpn clients +# TODO(ja): does vlanpool "keeper" need to know the min/max - shouldn't FLAGS always win? +# TODO(joshua): Save the IPs at the top of each subnet for cloudpipe vpn clients + +class BaseNetwork(datastore.BasicModel): + override_type = 'network' + NUM_STATIC_IPS = 3 # Network, Gateway, and CloudPipe + + @property + def identifier(self): + return self.network_id + + def default_state(self): + return {'network_id': self.network_id, 'network_str': self.network_str} + + @classmethod + def create(cls, user_id, project_id, security_group, vlan, network_str): + network_id = "%s:%s" % (project_id, security_group) + net = cls(network_id, network_str) + net['user_id'] = user_id + net['project_id'] = project_id + net["vlan"] = vlan + net["bridge_name"] = "br%s" % vlan + net.save() + return net + + def __init__(self, network_id, network_str=None): + self.network_id = network_id + self.network_str = network_str + super(BaseNetwork, self).__init__() + self.save() + + @property + def network(self): + return IPy.IP(self['network_str']) + + @property + def netmask(self): + return self.network.netmask() + + @property + def gateway(self): + return self.network[1] + + @property + def broadcast(self): + return self.network.broadcast() + + @property + def bridge_name(self): + return "br%s" % (self["vlan"]) + + @property + def user(self): + return manager.AuthManager().get_user(self['user_id']) + + @property + def project(self): + return manager.AuthManager().get_project(self['project_id']) + + @property + def _hosts_key(self): + return "network:%s:hosts" % (self['network_str']) + + @property + def hosts(self): + return datastore.Redis.instance().hgetall(self._hosts_key) or {} + + def _add_host(self, _user_id, _project_id, host, target): + datastore.Redis.instance().hset(self._hosts_key, host, target) + + def _rem_host(self, host): + datastore.Redis.instance().hdel(self._hosts_key, host) + + @property + def assigned(self): + return datastore.Redis.instance().hkeys(self._hosts_key) + + @property + def available(self): + # the .2 address is always CloudPipe + # and the top are for vpn clients + for idx in range(self.num_static_ips, len(self.network)-(1 + FLAGS.cnt_vpn_clients)): + address = str(self.network[idx]) + if not address in self.hosts.keys(): + yield address + + @property + def num_static_ips(self): + return BaseNetwork.NUM_STATIC_IPS + + def allocate_ip(self, user_id, project_id, mac): + for address in self.available: + logging.debug("Allocating IP %s to %s" % (address, project_id)) + self._add_host(user_id, project_id, address, mac) + self.express(address=address) + return address + raise exception.NoMoreAddresses("Project %s with network %s" % + (project_id, str(self.network))) + + def lease_ip(self, ip_str): + logging.debug("Leasing allocated IP %s" % (ip_str)) + + def release_ip(self, ip_str): + if not ip_str in self.assigned: + raise exception.AddressNotAllocated() + self.deexpress(address=ip_str) + self._rem_host(ip_str) + + def deallocate_ip(self, ip_str): + # Do nothing for now, cleanup on ip release + pass + + def list_addresses(self): + for address in self.hosts: + yield address + + def express(self, address=None): pass + def deexpress(self, address=None): pass + + +class BridgedNetwork(BaseNetwork): + """ + Virtual Network that can express itself to create a vlan and + a bridge (with or without an IP address/netmask/gateway) + + properties: + bridge_name - string (example value: br42) + vlan - integer (example value: 42) + bridge_dev - string (example: eth0) + bridge_gets_ip - boolean used during bridge creation + + if bridge_gets_ip then network address for bridge uses the properties: + gateway + broadcast + netmask + """ + + bridge_gets_ip = False + override_type = 'network' + + @classmethod + def get_network_for_project(cls, user_id, project_id, security_group): + vlan = get_vlan_for_project(project_id) + network_str = vlan.subnet() + return cls.create(user_id, project_id, security_group, vlan.vlan_id, + network_str) + + def __init__(self, *args, **kwargs): + super(BridgedNetwork, self).__init__(*args, **kwargs) + self['bridge_dev'] = FLAGS.bridge_dev + self.save() + + def express(self, address=None): + super(BridgedNetwork, self).express(address=address) + linux_net.vlan_create(self) + linux_net.bridge_create(self) + +class DHCPNetwork(BridgedNetwork): + """ + properties: + dhcp_listen_address: the ip of the gateway / dhcp host + dhcp_range_start: the first ip to give out + dhcp_range_end: the last ip to give out + """ + bridge_gets_ip = True + override_type = 'network' + + def __init__(self, *args, **kwargs): + super(DHCPNetwork, self).__init__(*args, **kwargs) + # logging.debug("Initing DHCPNetwork object...") + self.dhcp_listen_address = self.network[1] + self.dhcp_range_start = self.network[3] + self.dhcp_range_end = self.network[-(1 + FLAGS.cnt_vpn_clients)] + try: + os.makedirs(FLAGS.networks_path) + # NOTE(todd): I guess this is a lazy way to not have to check if the + # directory exists, but shouldn't we be smarter about + # telling the difference between existing directory and + # permission denied? (Errno 17 vs 13, OSError) + except Exception, err: + pass + + def express(self, address=None): + super(DHCPNetwork, self).express(address=address) + if len(self.assigned) > 0: + logging.debug("Starting dnsmasq server for network with vlan %s", + self['vlan']) + linux_net.start_dnsmasq(self) + else: + logging.debug("Not launching dnsmasq: no hosts.") + self.express_cloudpipe() + + def allocate_vpn_ip(self, user_id, project_id, mac): + address = str(self.network[2]) + self._add_host(user_id, project_id, address, mac) + self.express(address=address) + return address + + def express_cloudpipe(self): + private_ip = str(self.network[2]) + linux_net.confirm_rule("FORWARD -d %s -p udp --dport 1194 -j ACCEPT" + % (private_ip, )) + linux_net.confirm_rule("PREROUTING -t nat -d %s -p udp --dport %s -j DNAT --to %s:1194" + % (self.project.vpn_ip, self.project.vpn_port, private_ip)) + + def deexpress(self, address=None): + # if this is the last address, stop dns + super(DHCPNetwork, self).deexpress(address=address) + if len(self.assigned) == 0: + linux_net.stop_dnsmasq(self) + else: + linux_net.start_dnsmasq(self) + +class PublicAddress(datastore.BasicModel): + override_type = "address" + + def __init__(self, address): + self.address = address + super(PublicAddress, self).__init__() + + @property + def identifier(self): + return self.address + + def default_state(self): + return {'address': self.address} + + @classmethod + def create(cls, user_id, project_id, address): + addr = cls(address) + addr['user_id'] = user_id + addr['project_id'] = project_id + addr['instance_id'] = 'available' + addr['private_ip'] = 'available' + addr.save() + return addr + +DEFAULT_PORTS = [("tcp",80), ("tcp",22), ("udp",1194), ("tcp",443)] +class PublicNetworkController(BaseNetwork): + override_type = 'network' + + def __init__(self, *args, **kwargs): + network_id = "public:default" + super(PublicNetworkController, self).__init__(network_id, FLAGS.public_range) + self['user_id'] = "public" + self['project_id'] = "public" + self["create_time"] = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) + self["vlan"] = FLAGS.public_vlan + self.save() + self.express() + + @property + def available(self): + for idx in range(2, len(self.network)-1): + address = str(self.network[idx]) + if not address in self.hosts.keys(): + yield address + + @property + def host_objs(self): + for address in self.assigned: + yield PublicAddress(address) + + def get_public_ip_for_instance(self, instance_id): + # FIXME: this should be a lookup - iteration won't scale + for address_record in self.host_objs: + if address_record.get('instance_id', 'available') == instance_id: + return address_record['address'] + + def get_host(self, host): + if host in self.assigned: + return PublicAddress(host) + return None + + def _add_host(self, user_id, project_id, host, _target): + datastore.Redis.instance().hset(self._hosts_key, host, project_id) + PublicAddress.create(user_id, project_id, host) + + def _rem_host(self, host): + PublicAddress(host).destroy() + datastore.Redis.instance().hdel(self._hosts_key, host) + + def associate_address(self, public_ip, private_ip, instance_id): + if not public_ip in self.assigned: + raise exception.AddressNotAllocated() + # TODO(joshua): Keep an index going both ways + for addr in self.host_objs: + if addr.get('private_ip', None) == private_ip: + raise exception.AddressAlreadyAssociated() + addr = self.get_host(public_ip) + if addr.get('private_ip', 'available') != 'available': + raise exception.AddressAlreadyAssociated() + addr['private_ip'] = private_ip + addr['instance_id'] = instance_id + addr.save() + self.express(address=public_ip) + + def disassociate_address(self, public_ip): + if not public_ip in self.assigned: + raise exception.AddressNotAllocated() + addr = self.get_host(public_ip) + if addr.get('private_ip', 'available') == 'available': + raise exception.AddressNotAssociated() + self.deexpress(address=public_ip) + addr['private_ip'] = 'available' + addr['instance_id'] = 'available' + addr.save() + + def express(self, address=None): + addresses = self.host_objs + if address: + addresses = [self.get_host(address)] + for addr in addresses: + if addr.get('private_ip','available') == 'available': + continue + public_ip = addr['address'] + private_ip = addr['private_ip'] + linux_net.bind_public_ip(public_ip, FLAGS.public_interface) + linux_net.confirm_rule("PREROUTING -t nat -d %s -j DNAT --to %s" + % (public_ip, private_ip)) + linux_net.confirm_rule("POSTROUTING -t nat -s %s -j SNAT --to %s" + % (private_ip, public_ip)) + # TODO: Get these from the secgroup datastore entries + linux_net.confirm_rule("FORWARD -d %s -p icmp -j ACCEPT" + % (private_ip)) + for (protocol, port) in DEFAULT_PORTS: + linux_net.confirm_rule("FORWARD -d %s -p %s --dport %s -j ACCEPT" + % (private_ip, protocol, port)) + + def deexpress(self, address=None): + addr = self.get_host(address) + private_ip = addr['private_ip'] + linux_net.unbind_public_ip(address, FLAGS.public_interface) + linux_net.remove_rule("PREROUTING -t nat -d %s -j DNAT --to %s" + % (address, private_ip)) + linux_net.remove_rule("POSTROUTING -t nat -s %s -j SNAT --to %s" + % (private_ip, address)) + linux_net.remove_rule("FORWARD -d %s -p icmp -j ACCEPT" + % (private_ip)) + for (protocol, port) in DEFAULT_PORTS: + linux_net.remove_rule("FORWARD -d %s -p %s --dport %s -j ACCEPT" + % (private_ip, protocol, port)) + + +# FIXME(todd): does this present a race condition, or is there some piece of +# architecture that mitigates it (only one queue listener per net)? +def get_vlan_for_project(project_id): + """ + Allocate vlan IDs to individual users. + """ + vlan = Vlan.lookup(project_id) + if vlan: + return vlan + known_vlans = Vlan.dict_by_vlan() + for vnum in range(FLAGS.vlan_start, FLAGS.vlan_end): + vstr = str(vnum) + if not known_vlans.has_key(vstr): + return Vlan.create(project_id, vnum) + old_project_id = known_vlans[vstr] + if not manager.AuthManager().get_project(old_project_id): + vlan = Vlan.lookup(old_project_id) + if vlan: + # NOTE(todd): This doesn't check for vlan id match, because + # it seems to be assumed that vlan<=>project is + # always a 1:1 mapping. It could be made way + # sexier if it didn't fight against the way + # BasicModel worked and used associate_with + # to build connections to projects. + # NOTE(josh): This is here because we want to make sure we + # don't orphan any VLANs. It is basically + # garbage collection for after projects abandoned + # their reference. + vlan.destroy() + vlan.project_id = project_id + vlan.save() + return vlan + else: + return Vlan.create(project_id, vnum) + raise exception.AddressNotAllocated("Out of VLANs") + +def get_project_network(project_id, security_group='default'): + """ get a project's private network, allocating one if needed """ + project = manager.AuthManager().get_project(project_id) + if not project: + raise nova_exception.NotFound("Project %s doesn't exist." % project_id) + manager_id = project.project_manager_id + return DHCPNetwork.get_network_for_project(manager_id, + project.id, + security_group) + + +def get_network_by_address(address): + # TODO(vish): This is completely the wrong way to do this, but + # I'm getting the network binary working before I + # tackle doing this the right way. + logging.debug("Get Network By Address: %s" % address) + for project in manager.AuthManager().get_projects(): + net = get_project_network(project.id) + if address in net.assigned: + logging.debug("Found %s in %s" % (address, project.id)) + return net + raise exception.AddressNotAllocated() + + +def get_network_by_interface(iface, security_group='default'): + vlan = iface.rpartition("br")[2] + project_id = Vlan.dict_by_vlan().get(vlan) + return get_project_network(project_id, security_group) + + + diff --git a/nova/network/service.py b/nova/network/service.py index 9d87e05e6..97976a752 100644 --- a/nova/network/service.py +++ b/nova/network/service.py @@ -22,14 +22,141 @@ Network Nodes are responsible for allocating ips and setting up network import logging +from nova import datastore +from nova import exception as nova_exception from nova import flags from nova import service - +from nova import utils +from nova.auth import manager +from nova.network import exception +from nova.network import model FLAGS = flags.FLAGS -class NetworkService(service.Service): +flags.DEFINE_string('flat_network_bridge', 'br100', + 'Bridge for simple network instances') +flags.DEFINE_list('flat_network_ips', + ['192.168.0.2','192.168.0.3','192.168.0.4'], + 'Available ips for simple network') +flags.DEFINE_string('flat_network_network', '192.168.0.0', + 'Network for simple network') +flags.DEFINE_string('flat_network_netmask', '255.255.255.0', + 'Netmask for simple network') +flags.DEFINE_string('flat_network_gateway', '192.168.0.1', + 'Broadcast for simple network') +flags.DEFINE_string('flat_network_broadcast', '192.168.0.255', + 'Broadcast for simple network') +flags.DEFINE_string('flat_network_dns', '8.8.4.4', + 'Dns for simple network') + + +class BaseNetworkService(service.Service): + """Implements common network service functionality + + This class must be subclassed. + """ + def __init__(self, *args, **kwargs): + self.network = model.PublicNetworkController() + + def create_network(self, user_id, project_id, security_group='default', + *args, **kwargs): + """Subclass implements creating network and returns network data""" + raise NotImplementedError() + + def allocate_fixed_ip(self, user_id, project_id, *args, **kwargs): + """Subclass implements getting fixed ip from the pool""" + raise NotImplementedError() + + def deallocate_fixed_ip(self, fixed_ip, *args, **kwargs): + """Subclass implements return of ip to the pool""" + raise NotImplementedError() + + def allocate_elastic_ip(self): + """Gets a elastic ip from the pool""" + return self.network.allocate_ip() + + def associate_elastic_ip(self, elastic_ip, fixed_ip, instance_id): + """Associates an elastic ip to a fixed ip""" + self.network.associate_address(elastic_ip, fixed_ip, instance_id) + + def disassociate_elastic_ip(self, elastic_ip): + """Disassociates a elastic ip""" + self.network.disassociate_address(elastic_ip) + + def deallocate_elastic_ip(self, elastic_ip): + """Returns a elastic ip to the pool""" + self.network.deallocate_ip(elastic_ip) + + +class FlatNetworkService(BaseNetworkService): + def create_network(self, user_id, project_id, security_group='default', + *args, **kwargs): + """Creates network and returns bridge + + Flat network service simply returns a common bridge regardless of + project. + """ + return {'network_type': 'injected', + 'bridge_name': FLAGS.flat_network_bridge, + 'network_network': FLAGS.flat_network_network, + 'network_netmask': FLAGS.flat_network_netmask, + 'network_gateway': FLAGS.flat_network_gateway, + 'network_broadcast': FLAGS.flat_network_broadcast, + 'network_dns': FLAGS.flat_network_dns} + + def allocate_fixed_ip(self, user_id, project_id, *args, **kwargs): + """Gets a fixed ip from the pool + + Flat network just grabs the next available ip from the pool + """ + redis = datastore.Redis.instance() + if not redis.exists('ips') and not len(redis.keys('instances:*')): + for fixed_ip in FLAGS.flat_network_ips: + redis.sadd('ips', fixed_ip) + fixed_ip = redis.spop('ips') + if not fixed_ip: + raise exception.NoMoreAddresses() + return {'mac': utils.generate_mac(), + 'ip' : str(fixed_ip)} + + def deallocate_fixed_ip(self, fixed_ip, *args, **kwargs): + """Returns an ip to the pool""" + datastore.Redis.instance().sadd('ips', fixed_ip) + +class VlanNetworkService(BaseNetworkService): """Allocates ips and sets up networks""" + def create_network(self, user_id, project_id, security_group='default', + *args, **kwargs): + """Creates network and returns bridge""" + net = model.get_project_network(project_id, security_group) + return {'network_type': 'dhcp', + 'bridge_name': net['bridge_name']} + + def allocate_fixed_ip(self, user_id, project_id, vpn=False, *args, **kwargs): + """Gets a fixed ip from the pool """ + mac = utils.generate_mac() + net = model.get_project_network(project_id) + if vpn: + fixed_ip = net.allocate_vpn_ip(user_id, project_id, mac) + else: + fixed_ip = net.allocate_ip(user_id, project_id, mac) + return {'mac': mac, + 'ip' : fixed_ip} + + def deallocate_fixed_ip(self, fixed_ip, + *args, **kwargs): + """Returns an ip to the pool""" + model.get_network_by_address(fixed_ip).deallocate_ip(fixed_ip) + + def lease_ip(self, address): + return self. __get_network_by_address(address).lease_ip(address) + + def release_ip(self, address): + return model.get_network_by_address(address).release_ip(address) + + def restart_nets(self): + """ Ensure the network for each user is enabled""" + for project in manager.AuthManager().get_projects(): + model.get_project_network(project.id).express() + - def __init__(self): - logging.debug("Network node working") diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index f24eefb0d..4b2f6c649 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -24,8 +24,9 @@ from nova import flags from nova import test from nova import utils from nova.auth import manager -from nova.compute import network -from nova.compute.exception import NoMoreAddresses +from nova.network import model +from nova.network import service +from nova.network.exception import NoMoreAddresses FLAGS = flags.FLAGS @@ -52,7 +53,8 @@ class NetworkTestCase(test.TrialTestCase): self.projects.append(self.manager.create_project(name, 'netuser', name)) - self.network = network.PublicNetworkController() + self.network = model.PublicNetworkController() + self.service = service.VlanNetworkService() def tearDown(self): super(NetworkTestCase, self).tearDown() @@ -66,16 +68,17 @@ class NetworkTestCase(test.TrialTestCase): self.assertTrue(IPy.IP(address) in pubnet) self.assertTrue(IPy.IP(address) in self.network.network) - def test_allocate_deallocate_ip(self): - address = network.allocate_ip( - self.user.id, self.projects[0].id, utils.generate_mac()) + def test_allocate_deallocate_fixed_ip(self): + result = self.service.allocate_fixed_ip( + self.user.id, self.projects[0].id) + address = result['ip'] + mac = result['mac'] logging.debug("Was allocated %s" % (address)) - net = network.get_project_network(self.projects[0].id, "default") + net = model.get_project_network(self.projects[0].id, "default") self.assertEqual(True, is_in_project(address, self.projects[0].id)) - mac = utils.generate_mac() hostname = "test-host" self.dnsmasq.issue_ip(mac, address, hostname, net.bridge_name) - rv = network.deallocate_ip(address) + rv = self.service.deallocate_fixed_ip(address) # Doesn't go away until it's dhcp released self.assertEqual(True, is_in_project(address, self.projects[0].id)) @@ -84,15 +87,18 @@ class NetworkTestCase(test.TrialTestCase): self.assertEqual(False, is_in_project(address, self.projects[0].id)) def test_range_allocation(self): - mac = utils.generate_mac() - secondmac = utils.generate_mac() hostname = "test-host" - address = network.allocate_ip( - self.user.id, self.projects[0].id, mac) - secondaddress = network.allocate_ip( - self.user, self.projects[1].id, secondmac) - net = network.get_project_network(self.projects[0].id, "default") - secondnet = network.get_project_network(self.projects[1].id, "default") + result = self.service.allocate_fixed_ip( + self.user.id, self.projects[0].id) + mac = result['mac'] + address = result['ip'] + result = self.service.allocate_fixed_ip( + self.user, self.projects[1].id) + secondmac = result['mac'] + secondaddress = result['ip'] + + net = model.get_project_network(self.projects[0].id, "default") + secondnet = model.get_project_network(self.projects[1].id, "default") self.assertEqual(True, is_in_project(address, self.projects[0].id)) self.assertEqual(True, is_in_project(secondaddress, self.projects[1].id)) @@ -103,46 +109,50 @@ class NetworkTestCase(test.TrialTestCase): self.dnsmasq.issue_ip(secondmac, secondaddress, hostname, secondnet.bridge_name) - rv = network.deallocate_ip(address) + rv = self.service.deallocate_fixed_ip(address) self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name) self.assertEqual(False, is_in_project(address, self.projects[0].id)) # First address release shouldn't affect the second self.assertEqual(True, is_in_project(secondaddress, self.projects[1].id)) - rv = network.deallocate_ip(secondaddress) + rv = self.service.deallocate_fixed_ip(secondaddress) self.dnsmasq.release_ip(secondmac, secondaddress, hostname, secondnet.bridge_name) self.assertEqual(False, is_in_project(secondaddress, self.projects[1].id)) def test_subnet_edge(self): - secondaddress = network.allocate_ip(self.user.id, self.projects[0].id, - utils.generate_mac()) + result = self.service.allocate_fixed_ip(self.user.id, + self.projects[0].id) + firstaddress = result['ip'] hostname = "toomany-hosts" for i in range(1,5): project_id = self.projects[i].id - mac = utils.generate_mac() - mac2 = utils.generate_mac() - mac3 = utils.generate_mac() - address = network.allocate_ip( - self.user, project_id, mac) - address2 = network.allocate_ip( - self.user, project_id, mac2) - address3 = network.allocate_ip( - self.user, project_id, mac3) + result = self.service.allocate_fixed_ip( + self.user, project_id) + mac = result['mac'] + address = result['ip'] + result = self.service.allocate_fixed_ip( + self.user, project_id) + mac2 = result['mac'] + address2 = result['ip'] + result = self.service.allocate_fixed_ip( + self.user, project_id) + mac3 = result['mac'] + address3 = result['ip'] self.assertEqual(False, is_in_project(address, self.projects[0].id)) self.assertEqual(False, is_in_project(address2, self.projects[0].id)) self.assertEqual(False, is_in_project(address3, self.projects[0].id)) - rv = network.deallocate_ip(address) - rv = network.deallocate_ip(address2) - rv = network.deallocate_ip(address3) - net = network.get_project_network(project_id, "default") + rv = self.service.deallocate_fixed_ip(address) + rv = self.service.deallocate_fixed_ip(address2) + rv = self.service.deallocate_fixed_ip(address3) + net = model.get_project_network(project_id, "default") self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name) self.dnsmasq.release_ip(mac2, address2, hostname, net.bridge_name) self.dnsmasq.release_ip(mac3, address3, hostname, net.bridge_name) - net = network.get_project_network(self.projects[0].id, "default") - rv = network.deallocate_ip(secondaddress) - self.dnsmasq.release_ip(mac, secondaddress, hostname, net.bridge_name) + net = model.get_project_network(self.projects[0].id, "default") + rv = self.service.deallocate_fixed_ip(firstaddress) + self.dnsmasq.release_ip(mac, firstaddress, hostname, net.bridge_name) def test_release_before_deallocate(self): pass @@ -169,7 +179,7 @@ class NetworkTestCase(test.TrialTestCase): NUM_RESERVED_VPN_IPS) usable addresses """ - net = network.get_project_network(self.projects[0].id, "default") + net = model.get_project_network(self.projects[0].id, "default") # Determine expected number of available IP addresses num_static_ips = net.num_static_ips @@ -183,22 +193,23 @@ class NetworkTestCase(test.TrialTestCase): macs = {} addresses = {} for i in range(0, (num_available_ips - 1)): - macs[i] = utils.generate_mac() - addresses[i] = network.allocate_ip(self.user.id, self.projects[0].id, macs[i]) + result = self.service.allocate_fixed_ip(self.user.id, self.projects[0].id) + macs[i] = result['mac'] + addresses[i] = result['ip'] self.dnsmasq.issue_ip(macs[i], addresses[i], hostname, net.bridge_name) - self.assertRaises(NoMoreAddresses, network.allocate_ip, self.user.id, self.projects[0].id, utils.generate_mac()) + self.assertRaises(NoMoreAddresses, self.service.allocate_fixed_ip, self.user.id, self.projects[0].id) for i in range(0, (num_available_ips - 1)): - rv = network.deallocate_ip(addresses[i]) + rv = self.service.deallocate_fixed_ip(addresses[i]) self.dnsmasq.release_ip(macs[i], addresses[i], hostname, net.bridge_name) def is_in_project(address, project_id): - return address in network.get_project_network(project_id).list_addresses() + return address in model.get_project_network(project_id).list_addresses() def _get_project_addresses(project_id): project_addresses = [] - for addr in network.get_project_network(project_id).list_addresses(): + for addr in model.get_project_network(project_id).list_addresses(): project_addresses.append(addr) return project_addresses -- cgit From 576dade1d53814416977522637bea9e3c32e5483 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 3 Aug 2010 15:13:07 -0700 Subject: change network_service flag to network_type and don't take full class name --- bin/nova-network | 10 ++++++---- nova/network/service.py | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/bin/nova-network b/bin/nova-network index b2e2cf173..1d620b525 100755 --- a/bin/nova-network +++ b/bin/nova-network @@ -25,15 +25,17 @@ from nova import flags from nova import twistd from nova import utils +from nova.network import service FLAGS = flags.FLAGS -flags.DEFINE_string('network_service', - 'nova.network.service.VlanNetworkService', - 'Service Class for Networking') if __name__ == '__main__': twistd.serve(__file__) if __name__ == '__builtin__': - application = utils.import_class(FLAGS.network_service).create() + t = FLAGS.network_type + if t == 'flat': + application = service.FlatNetworkService.create() + elif t == 'vlan': + application = service.VlanNetworkService.create() diff --git a/nova/network/service.py b/nova/network/service.py index 97976a752..72f7db126 100644 --- a/nova/network/service.py +++ b/nova/network/service.py @@ -33,6 +33,9 @@ from nova.network import model FLAGS = flags.FLAGS +flags.DEFINE_string('network_type', + 'flat', + 'Service Class for Networking') flags.DEFINE_string('flat_network_bridge', 'br100', 'Bridge for simple network instances') flags.DEFINE_list('flat_network_ips', -- cgit From 8261e26f78f061de5f5e98f8066da33f9b4e3a23 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 3 Aug 2010 15:48:49 -0700 Subject: use get to retrieve node_name from initial_state --- nova/compute/model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/compute/model.py b/nova/compute/model.py index 7dd130ca3..266a93b9a 100644 --- a/nova/compute/model.py +++ b/nova/compute/model.py @@ -151,7 +151,8 @@ class Instance(datastore.BasicModel): # it just adds the first one is_new = self.is_new_record() node_set = (self.state['node_name'] != 'unassigned' and - self.initial_state['node_name'] == 'unassigned') + self.initial_state.get('node_name', 'unassigned') + == 'unassigned') success = super(Instance, self).save() if success and is_new: self.associate_with("project", self.project) -- cgit From 13ec179c99012ed3d579e19094c0039ccb630796 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 3 Aug 2010 17:27:29 -0700 Subject: created assocaition between project and host, modified commands to get host async, simplified calls to network --- nova/network/service.py | 72 +++++++++++++++++++++++------------------- nova/tests/network_unittest.py | 30 +++++++++--------- 2 files changed, 54 insertions(+), 48 deletions(-) diff --git a/nova/network/service.py b/nova/network/service.py index 72f7db126..45c2867ca 100644 --- a/nova/network/service.py +++ b/nova/network/service.py @@ -23,7 +23,6 @@ Network Nodes are responsible for allocating ips and setting up network import logging from nova import datastore -from nova import exception as nova_exception from nova import flags from nova import service from nova import utils @@ -52,6 +51,13 @@ flags.DEFINE_string('flat_network_broadcast', '192.168.0.255', flags.DEFINE_string('flat_network_dns', '8.8.4.4', 'Dns for simple network') +def get_host_for_project(project_id): + redis = datastore.Redis.instance() + host = redis.get(__host_key(project_id)) + +def __host_key(project_id): + return "network_host:%s" % project_id + class BaseNetworkService(service.Service): """Implements common network service functionality @@ -61,12 +67,18 @@ class BaseNetworkService(service.Service): def __init__(self, *args, **kwargs): self.network = model.PublicNetworkController() - def create_network(self, user_id, project_id, security_group='default', - *args, **kwargs): - """Subclass implements creating network and returns network data""" - raise NotImplementedError() + def get_network_host(self, user_id, project_id, *args, **kwargs): + """Safely becomes the host of the projects network""" + redis = datastore.Redis.instance() + key = __host_key(project_id) + if redis.setnx(key, FLAGS.node_name): + return FLAGS.node_name + else: + return redis.get(key) - def allocate_fixed_ip(self, user_id, project_id, *args, **kwargs): + def allocate_fixed_ip(self, user_id, project_id, + security_group='default', + *args, **kwargs): """Subclass implements getting fixed ip from the pool""" raise NotImplementedError() @@ -92,22 +104,11 @@ class BaseNetworkService(service.Service): class FlatNetworkService(BaseNetworkService): - def create_network(self, user_id, project_id, security_group='default', - *args, **kwargs): - """Creates network and returns bridge + """Basic network where no vlans are used""" - Flat network service simply returns a common bridge regardless of - project. - """ - return {'network_type': 'injected', - 'bridge_name': FLAGS.flat_network_bridge, - 'network_network': FLAGS.flat_network_network, - 'network_netmask': FLAGS.flat_network_netmask, - 'network_gateway': FLAGS.flat_network_gateway, - 'network_broadcast': FLAGS.flat_network_broadcast, - 'network_dns': FLAGS.flat_network_dns} - - def allocate_fixed_ip(self, user_id, project_id, *args, **kwargs): + def allocate_fixed_ip(self, user_id, project_id, + security_group='default', + *args, **kwargs): """Gets a fixed ip from the pool Flat network just grabs the next available ip from the pool @@ -119,23 +120,26 @@ class FlatNetworkService(BaseNetworkService): fixed_ip = redis.spop('ips') if not fixed_ip: raise exception.NoMoreAddresses() - return {'mac': utils.generate_mac(), - 'ip' : str(fixed_ip)} + return {'network_type': 'injected', + 'mac_address': utils.generate_mac(), + 'private_dns_name': str(fixed_ip), + 'bridge_name': FLAGS.flat_network_bridge, + 'network_network': FLAGS.flat_network_network, + 'network_netmask': FLAGS.flat_network_netmask, + 'network_gateway': FLAGS.flat_network_gateway, + 'network_broadcast': FLAGS.flat_network_broadcast, + 'network_dns': FLAGS.flat_network_dns} def deallocate_fixed_ip(self, fixed_ip, *args, **kwargs): """Returns an ip to the pool""" datastore.Redis.instance().sadd('ips', fixed_ip) class VlanNetworkService(BaseNetworkService): - """Allocates ips and sets up networks""" - def create_network(self, user_id, project_id, security_group='default', - *args, **kwargs): - """Creates network and returns bridge""" - net = model.get_project_network(project_id, security_group) - return {'network_type': 'dhcp', - 'bridge_name': net['bridge_name']} + """Vlan network with dhcp""" - def allocate_fixed_ip(self, user_id, project_id, vpn=False, *args, **kwargs): + def allocate_fixed_ip(self, user_id, project_id, + security_group='default', + vpn=False, *args, **kwargs): """Gets a fixed ip from the pool """ mac = utils.generate_mac() net = model.get_project_network(project_id) @@ -143,8 +147,10 @@ class VlanNetworkService(BaseNetworkService): fixed_ip = net.allocate_vpn_ip(user_id, project_id, mac) else: fixed_ip = net.allocate_ip(user_id, project_id, mac) - return {'mac': mac, - 'ip' : fixed_ip} + return {'network_type': 'dhcp', + 'bridge_name': net['bridge_name'], + 'mac_address': mac, + 'private_dns_name' : fixed_ip} def deallocate_fixed_ip(self, fixed_ip, *args, **kwargs): diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index 4b2f6c649..a9695f818 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -71,8 +71,8 @@ class NetworkTestCase(test.TrialTestCase): def test_allocate_deallocate_fixed_ip(self): result = self.service.allocate_fixed_ip( self.user.id, self.projects[0].id) - address = result['ip'] - mac = result['mac'] + address = result['private_dns_name'] + mac = result['mac_address'] logging.debug("Was allocated %s" % (address)) net = model.get_project_network(self.projects[0].id, "default") self.assertEqual(True, is_in_project(address, self.projects[0].id)) @@ -90,12 +90,12 @@ class NetworkTestCase(test.TrialTestCase): hostname = "test-host" result = self.service.allocate_fixed_ip( self.user.id, self.projects[0].id) - mac = result['mac'] - address = result['ip'] + mac = result['mac_address'] + address = result['private_dns_name'] result = self.service.allocate_fixed_ip( self.user, self.projects[1].id) - secondmac = result['mac'] - secondaddress = result['ip'] + secondmac = result['mac_address'] + secondaddress = result['private_dns_name'] net = model.get_project_network(self.projects[0].id, "default") secondnet = model.get_project_network(self.projects[1].id, "default") @@ -124,22 +124,22 @@ class NetworkTestCase(test.TrialTestCase): def test_subnet_edge(self): result = self.service.allocate_fixed_ip(self.user.id, self.projects[0].id) - firstaddress = result['ip'] + firstaddress = result['private_dns_name'] hostname = "toomany-hosts" for i in range(1,5): project_id = self.projects[i].id result = self.service.allocate_fixed_ip( self.user, project_id) - mac = result['mac'] - address = result['ip'] + mac = result['mac_address'] + address = result['private_dns_name'] result = self.service.allocate_fixed_ip( self.user, project_id) - mac2 = result['mac'] - address2 = result['ip'] + mac2 = result['mac_address'] + address2 = result['private_dns_name'] result = self.service.allocate_fixed_ip( self.user, project_id) - mac3 = result['mac'] - address3 = result['ip'] + mac3 = result['mac_address'] + address3 = result['private_dns_name'] self.assertEqual(False, is_in_project(address, self.projects[0].id)) self.assertEqual(False, is_in_project(address2, self.projects[0].id)) self.assertEqual(False, is_in_project(address3, self.projects[0].id)) @@ -194,8 +194,8 @@ class NetworkTestCase(test.TrialTestCase): addresses = {} for i in range(0, (num_available_ips - 1)): result = self.service.allocate_fixed_ip(self.user.id, self.projects[0].id) - macs[i] = result['mac'] - addresses[i] = result['ip'] + macs[i] = result['mac_address'] + addresses[i] = result['private_dns_name'] self.dnsmasq.issue_ip(macs[i], addresses[i], hostname, net.bridge_name) self.assertRaises(NoMoreAddresses, self.service.allocate_fixed_ip, self.user.id, self.projects[0].id) -- cgit From d0ca78ea900d71492212ac531ec75616b02300b0 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 4 Aug 2010 11:12:58 -0700 Subject: it helps to save files BEFORE committing --- nova/endpoint/cloud.py | 60 ++++++++++++++++++++++++++--------------------- nova/virt/libvirt_conn.py | 25 +++++++++++--------- 2 files changed, 47 insertions(+), 38 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index f605eec2c..32cbfdc10 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -38,6 +38,7 @@ from nova.auth import manager from nova.compute import model from nova.compute.instance_types import INSTANCE_TYPES from nova.endpoint import images +from nova.network import service as network_service from nova.volume import service @@ -468,25 +469,31 @@ class CloudController(object): return {'addressesSet': addresses} @rbac.allow('netadmin') + @defer.inlineCallbacks def allocate_address(self, context, **kwargs): - alloc_result = rpc.call(self._get_network_host(context), + network_host = yield self._get_network_host(context) + alloc_result = rpc.call(network_host, {"method": "allocate_elastic_ip"}) public_ip = alloc_result['result'] return defer.succeed({'addressSet': [{'publicIp' : public_ip}]}) @rbac.allow('netadmin') + @defer.inlineCallbacks def release_address(self, context, public_ip, **kwargs): # NOTE(vish): Should we make sure this works? - rpc.cast(self._get_network_host(context), + network_host = yield self._get_network_host(context) + rpc.cast(network_host, {"method": "deallocate_elastic_ip", "args": {"elastic_ip": public_ip}}) return defer.succeed({'releaseResponse': ["Address released."]}) @rbac.allow('netadmin') + @defer.inlineCallbacks def associate_address(self, context, instance_id, public_ip, **kwargs): instance = self._get_instance(context, instance_id) address = self._get_address(context, public_ip) - rpc.cast(self._get_network_host(context), + network_host = yield self._get_network_host(context) + rpc.cast(network_host, {"method": "associate_elastic_ip", "args": {"elastic_ip": address['public_ip'], "fixed_ip": instance['private_dns_name'], @@ -494,23 +501,26 @@ class CloudController(object): return defer.succeed({'associateResponse': ["Address associated."]}) @rbac.allow('netadmin') + @defer.inlineCallbacks def disassociate_address(self, context, public_ip, **kwargs): address = self._get_address(context, public_ip) - self.network.disassociate_address(public_ip) + network_host = yield self._get_network_host(context) + rpc.cast(network_host, + {"method": "associate_elastic_ip", + "args": {"elastic_ip": address['public_ip']}}) return defer.succeed({'disassociateResponse': ["Address disassociated."]}) - def release_ip(self, context, private_ip, **kwargs): - self.network.release_ip(private_ip) - return defer.succeed({'releaseResponse': ["Address released."]}) - - def lease_ip(self, context, private_ip, **kwargs): - self.network.lease_ip(private_ip) - return defer.succeed({'leaseResponse': ["Address leased."]}) - - def get_network_host(self, context): - # FIXME(vish): this is temporary until we store net hosts for project - import socket - return socket.gethostname() + @defer.inlineCallbacks + def _get_network_host(self, context): + """Retrieves the network host for a project""" + host = network_service.get_host_for_project(context.project.id) + if not host: + result = yield rpc.call(FLAGS.network_topic, + {"method": "get_network_host", + "args": {"user_id": context.user.id, + "project_id": context.project.id}}) + host = result['result'] + defer.returnValue(host) @rbac.allow('projectmanager', 'sysadmin') @defer.inlineCallbacks @@ -545,27 +555,21 @@ class CloudController(object): raise exception.ApiError('Key Pair %s not found' % kwargs['key_name']) key_data = key_pair.public_key + network_host = yield self._get_network_host(context) # TODO: Get the real security group of launch in here security_group = "default" - create_result = yield rpc.call(FLAGS.network_topic, - {"method": "create_network", - "args": {"user_id": context.user.id, - "project_id": context.project.id, - "security_group": security_group}}) - bridge_name = create_result['result'] - net_host = self._get_network_host(context) for num in range(int(kwargs['max_count'])): vpn = False if image_id == FLAGS.vpn_image_id: vpn = True - allocate_result = yield rpc.call(net_host, + allocate_result = yield rpc.call(network_host, {"method": "allocate_fixed_ip", "args": {"user_id": context.user.id, "project_id": context.project.id, + "security_group": security_group, "vpn": vpn}}) + allocate_data = allocate_result['result'] inst = self.instdir.new() - inst['mac_address'] = allocate_result['result']['mac_address'] - inst['private_dns_name'] = allocate_result['result']['ip_address'] inst['image_id'] = image_id inst['kernel_id'] = kernel_id inst['ramdisk_id'] = ramdisk_id @@ -578,7 +582,9 @@ class CloudController(object): inst['user_id'] = context.user.id inst['project_id'] = context.project.id inst['ami_launch_index'] = num - inst['bridge_name'] = bridge_name + inst['security_group'] = security_group + for (key, value) in allocate_data: + inst[key] = value inst.save() rpc.cast(FLAGS.compute_topic, diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index c545e4190..8133daf0b 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -47,6 +47,9 @@ FLAGS = flags.FLAGS flags.DEFINE_string('libvirt_xml_template', utils.abspath('compute/libvirt.xml.template'), 'Libvirt XML Template') +flags.DEFINE_string('injected_network_template', + utils.abspath('compute/interfaces.template'), + 'Template file for injected network') def get_connection(read_only): # These are loaded late so that there's no need to install these @@ -201,14 +204,14 @@ class LibvirtConnection(object): key = data['key_data'] net = None - if FLAGS.simple_network: - with open(FLAGS.simple_network_template) as f: + if data.get('network_type', None) == 'injected': + with open(FLAGS.injected_network_template) as f: net = f.read() % {'address': data['private_dns_name'], - 'network': FLAGS.simple_network_network, - 'netmask': FLAGS.simple_network_netmask, - 'gateway': FLAGS.simple_network_gateway, - 'broadcast': FLAGS.simple_network_broadcast, - 'dns': FLAGS.simple_network_dns} + 'network': data['network_network'], + 'netmask': data['network_netmask'], + 'gateway': data['network_gateway'], + 'broadcast': data['network_broadcast'], + 'dns': data['network_dns']} if key or net: logging.info('Injecting data into image %s', data['image_id']) yield disk.inject_data(basepath('disk-raw'), key, net, execute=execute) @@ -255,7 +258,7 @@ class LibvirtConnection(object): """ Note that this function takes an instance ID, not an Instance, so that it can be called by monitor. - + Returns a list of all block devices for this domain. """ domain = self._conn.lookupByName(instance_id) @@ -298,7 +301,7 @@ class LibvirtConnection(object): """ Note that this function takes an instance ID, not an Instance, so that it can be called by monitor. - + Returns a list of all network interfaces for this instance. """ domain = self._conn.lookupByName(instance_id) @@ -341,7 +344,7 @@ class LibvirtConnection(object): """ Note that this function takes an instance ID, not an Instance, so that it can be called by monitor. - """ + """ domain = self._conn.lookupByName(instance_id) return domain.blockStats(disk) @@ -350,6 +353,6 @@ class LibvirtConnection(object): """ Note that this function takes an instance ID, not an Instance, so that it can be called by monitor. - """ + """ domain = self._conn.lookupByName(instance_id) return domain.interfaceStats(interface) -- cgit From b2e220c976b7689a2c5d924395c57012c6b99212 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 4 Aug 2010 11:18:46 -0700 Subject: inline commands use returnValue --- nova/endpoint/cloud.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 32cbfdc10..3db999fba 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -475,7 +475,7 @@ class CloudController(object): alloc_result = rpc.call(network_host, {"method": "allocate_elastic_ip"}) public_ip = alloc_result['result'] - return defer.succeed({'addressSet': [{'publicIp' : public_ip}]}) + defer.returnValue({'addressSet': [{'publicIp' : public_ip}]}) @rbac.allow('netadmin') @defer.inlineCallbacks @@ -485,7 +485,7 @@ class CloudController(object): rpc.cast(network_host, {"method": "deallocate_elastic_ip", "args": {"elastic_ip": public_ip}}) - return defer.succeed({'releaseResponse': ["Address released."]}) + defer.returnValue({'releaseResponse': ["Address released."]}) @rbac.allow('netadmin') @defer.inlineCallbacks @@ -498,7 +498,7 @@ class CloudController(object): "args": {"elastic_ip": address['public_ip'], "fixed_ip": instance['private_dns_name'], "instance_id": instance['instance_id']}}) - return defer.succeed({'associateResponse': ["Address associated."]}) + defer.returnValue({'associateResponse': ["Address associated."]}) @rbac.allow('netadmin') @defer.inlineCallbacks @@ -508,7 +508,7 @@ class CloudController(object): rpc.cast(network_host, {"method": "associate_elastic_ip", "args": {"elastic_ip": address['public_ip']}}) - return defer.succeed({'disassociateResponse': ["Address disassociated."]}) + defer.returnValue({'disassociateResponse': ["Address disassociated."]}) @defer.inlineCallbacks def _get_network_host(self, context): @@ -596,8 +596,10 @@ class CloudController(object): defer.returnValue(self._format_instances(context, reservation_id)) @rbac.allow('projectmanager', 'sysadmin') + @defer.inlineCallbacks def terminate_instances(self, context, instance_id, **kwargs): logging.debug("Going to start terminating instances") + network_host = yield self._get_network_host(context) for i in instance_id: logging.debug("Going to try and terminate %s" % i) try: @@ -612,7 +614,7 @@ class CloudController(object): # NOTE(vish): Right now we don't really care if the ip is # disassociated. We may need to worry about # checking this later. Perhaps in the scheduler? - rpc.cast(self._get_network_host(context), + rpc.cast(network_host, {"method": "disassociate_elastic_ip", "args": {"elastic_ip": elastic_ip}}) @@ -622,7 +624,7 @@ class CloudController(object): # NOTE(vish): Right now we don't really care if the ip is # actually removed. We may need to worry about # checking this later. Perhaps in the scheduler? - rpc.cast(self._get_network_host(context), + rpc.cast(network_host, {"method": "deallocate_fixed_ip", "args": {"elastic_ip": elastic_ip}}) @@ -633,7 +635,7 @@ class CloudController(object): "args": {"instance_id": i}}) else: instance.destroy() - return defer.succeed(True) + defer.returnValue(True) @rbac.allow('projectmanager', 'sysadmin') def reboot_instances(self, context, instance_id, **kwargs): -- cgit From 311daf14758d8a04c5f73fa4e2911e469a716c1f Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 4 Aug 2010 11:44:25 -0700 Subject: don't __ module methods --- nova/network/service.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/network/service.py b/nova/network/service.py index 45c2867ca..b3645d2a3 100644 --- a/nova/network/service.py +++ b/nova/network/service.py @@ -53,9 +53,9 @@ flags.DEFINE_string('flat_network_dns', '8.8.4.4', def get_host_for_project(project_id): redis = datastore.Redis.instance() - host = redis.get(__host_key(project_id)) + host = redis.get(_host_key(project_id)) -def __host_key(project_id): +def _host_key(project_id): return "network_host:%s" % project_id @@ -70,7 +70,7 @@ class BaseNetworkService(service.Service): def get_network_host(self, user_id, project_id, *args, **kwargs): """Safely becomes the host of the projects network""" redis = datastore.Redis.instance() - key = __host_key(project_id) + key = _host_key(project_id) if redis.setnx(key, FLAGS.node_name): return FLAGS.node_name else: -- cgit From e816e7923582d7ac11b7f7a554eec815ea61496e Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 4 Aug 2010 12:02:23 -0700 Subject: use deferreds in network --- nova/network/service.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/nova/network/service.py b/nova/network/service.py index b3645d2a3..e998c5575 100644 --- a/nova/network/service.py +++ b/nova/network/service.py @@ -22,6 +22,8 @@ Network Nodes are responsible for allocating ips and setting up network import logging +from twisted.internet import defer + from nova import datastore from nova import flags from nova import service @@ -72,9 +74,9 @@ class BaseNetworkService(service.Service): redis = datastore.Redis.instance() key = _host_key(project_id) if redis.setnx(key, FLAGS.node_name): - return FLAGS.node_name + return defer.succeed(FLAGS.node_name) else: - return redis.get(key) + return defer.succeed(redis.get(key)) def allocate_fixed_ip(self, user_id, project_id, security_group='default', @@ -120,7 +122,7 @@ class FlatNetworkService(BaseNetworkService): fixed_ip = redis.spop('ips') if not fixed_ip: raise exception.NoMoreAddresses() - return {'network_type': 'injected', + return defer.succeed({'network_type': 'injected', 'mac_address': utils.generate_mac(), 'private_dns_name': str(fixed_ip), 'bridge_name': FLAGS.flat_network_bridge, @@ -128,7 +130,7 @@ class FlatNetworkService(BaseNetworkService): 'network_netmask': FLAGS.flat_network_netmask, 'network_gateway': FLAGS.flat_network_gateway, 'network_broadcast': FLAGS.flat_network_broadcast, - 'network_dns': FLAGS.flat_network_dns} + 'network_dns': FLAGS.flat_network_dns}) def deallocate_fixed_ip(self, fixed_ip, *args, **kwargs): """Returns an ip to the pool""" @@ -147,18 +149,18 @@ class VlanNetworkService(BaseNetworkService): fixed_ip = net.allocate_vpn_ip(user_id, project_id, mac) else: fixed_ip = net.allocate_ip(user_id, project_id, mac) - return {'network_type': 'dhcp', + return defer.succeed({'network_type': 'dhcp', 'bridge_name': net['bridge_name'], 'mac_address': mac, - 'private_dns_name' : fixed_ip} + 'private_dns_name' : fixed_ip}) def deallocate_fixed_ip(self, fixed_ip, *args, **kwargs): """Returns an ip to the pool""" - model.get_network_by_address(fixed_ip).deallocate_ip(fixed_ip) + return model.get_network_by_address(fixed_ip).deallocate_ip(fixed_ip) def lease_ip(self, address): - return self. __get_network_by_address(address).lease_ip(address) + return model.get_network_by_address(address).lease_ip(address) def release_ip(self, address): return model.get_network_by_address(address).release_ip(address) -- cgit From c821709a48eb22db4db182f25f1e405039294d2c Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 4 Aug 2010 12:18:52 -0700 Subject: method should return network topic instead of network host --- nova/endpoint/cloud.py | 32 ++++++++++++++++---------------- nova/network/service.py | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 3db999fba..33be0a612 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -471,8 +471,8 @@ class CloudController(object): @rbac.allow('netadmin') @defer.inlineCallbacks def allocate_address(self, context, **kwargs): - network_host = yield self._get_network_host(context) - alloc_result = rpc.call(network_host, + network_topic = yield self._get_network_topic(context) + alloc_result = rpc.call(network_topic, {"method": "allocate_elastic_ip"}) public_ip = alloc_result['result'] defer.returnValue({'addressSet': [{'publicIp' : public_ip}]}) @@ -481,8 +481,8 @@ class CloudController(object): @defer.inlineCallbacks def release_address(self, context, public_ip, **kwargs): # NOTE(vish): Should we make sure this works? - network_host = yield self._get_network_host(context) - rpc.cast(network_host, + network_topic = yield self._get_network_topic(context) + rpc.cast(network_topic, {"method": "deallocate_elastic_ip", "args": {"elastic_ip": public_ip}}) defer.returnValue({'releaseResponse': ["Address released."]}) @@ -492,8 +492,8 @@ class CloudController(object): def associate_address(self, context, instance_id, public_ip, **kwargs): instance = self._get_instance(context, instance_id) address = self._get_address(context, public_ip) - network_host = yield self._get_network_host(context) - rpc.cast(network_host, + network_topic = yield self._get_network_topic(context) + rpc.cast(network_topic, {"method": "associate_elastic_ip", "args": {"elastic_ip": address['public_ip'], "fixed_ip": instance['private_dns_name'], @@ -504,23 +504,23 @@ class CloudController(object): @defer.inlineCallbacks def disassociate_address(self, context, public_ip, **kwargs): address = self._get_address(context, public_ip) - network_host = yield self._get_network_host(context) - rpc.cast(network_host, + network_topic = yield self._get_network_topic(context) + rpc.cast(network_topic, {"method": "associate_elastic_ip", "args": {"elastic_ip": address['public_ip']}}) defer.returnValue({'disassociateResponse': ["Address disassociated."]}) @defer.inlineCallbacks - def _get_network_host(self, context): + def _get_network_topic(self, context): """Retrieves the network host for a project""" host = network_service.get_host_for_project(context.project.id) if not host: result = yield rpc.call(FLAGS.network_topic, - {"method": "get_network_host", + {"method": "get_network_topic", "args": {"user_id": context.user.id, "project_id": context.project.id}}) host = result['result'] - defer.returnValue(host) + defer.returnValue('%s.%s' %(FLAGS.network_topic, host)) @rbac.allow('projectmanager', 'sysadmin') @defer.inlineCallbacks @@ -555,14 +555,14 @@ class CloudController(object): raise exception.ApiError('Key Pair %s not found' % kwargs['key_name']) key_data = key_pair.public_key - network_host = yield self._get_network_host(context) + network_topic = yield self._get_network_topic(context) # TODO: Get the real security group of launch in here security_group = "default" for num in range(int(kwargs['max_count'])): vpn = False if image_id == FLAGS.vpn_image_id: vpn = True - allocate_result = yield rpc.call(network_host, + allocate_result = yield rpc.call(network_topic, {"method": "allocate_fixed_ip", "args": {"user_id": context.user.id, "project_id": context.project.id, @@ -599,7 +599,7 @@ class CloudController(object): @defer.inlineCallbacks def terminate_instances(self, context, instance_id, **kwargs): logging.debug("Going to start terminating instances") - network_host = yield self._get_network_host(context) + network_topic = yield self._get_network_topic(context) for i in instance_id: logging.debug("Going to try and terminate %s" % i) try: @@ -614,7 +614,7 @@ class CloudController(object): # NOTE(vish): Right now we don't really care if the ip is # disassociated. We may need to worry about # checking this later. Perhaps in the scheduler? - rpc.cast(network_host, + rpc.cast(network_topic, {"method": "disassociate_elastic_ip", "args": {"elastic_ip": elastic_ip}}) @@ -624,7 +624,7 @@ class CloudController(object): # NOTE(vish): Right now we don't really care if the ip is # actually removed. We may need to worry about # checking this later. Perhaps in the scheduler? - rpc.cast(network_host, + rpc.cast(network_topic, {"method": "deallocate_fixed_ip", "args": {"elastic_ip": elastic_ip}}) diff --git a/nova/network/service.py b/nova/network/service.py index e998c5575..1f36abc2b 100644 --- a/nova/network/service.py +++ b/nova/network/service.py @@ -55,7 +55,7 @@ flags.DEFINE_string('flat_network_dns', '8.8.4.4', def get_host_for_project(project_id): redis = datastore.Redis.instance() - host = redis.get(_host_key(project_id)) + return redis.get(_host_key(project_id)) def _host_key(project_id): return "network_host:%s" % project_id -- cgit From 148f319759fc9f566e0e9020ceb8ea00081ff8c8 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 4 Aug 2010 12:48:16 -0700 Subject: fixes in get public address and extra references to self.network --- nova/endpoint/cloud.py | 18 ++++++++++-------- nova/network/model.py | 12 ++++++------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 33be0a612..957b25b9b 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -39,6 +39,7 @@ from nova.compute import model from nova.compute.instance_types import INSTANCE_TYPES from nova.endpoint import images from nova.network import service as network_service +from nova.network import model as network_model from nova.volume import service @@ -307,7 +308,7 @@ class CloudController(object): def _get_address(self, context, public_ip): # FIXME(vish) this should move into network.py - address = self.network.get_host(public_ip) + address = self.network_model.PublicAddress.lookup(public_ip) if address and (context.user.is_admin() or address['project_id'] == context.project.id): return address raise exception.NotFound("Address at ip %s not found" % public_ip) @@ -416,7 +417,7 @@ class CloudController(object): 'code': instance.get('state', 0), 'name': instance.get('state_description', 'pending') } - i['public_dns_name'] = self.network.get_public_ip_for_instance( + i['public_dns_name'] = self.network_model.get_public_ip_for_instance( i['instance_id']) i['private_dns_name'] = instance.get('private_dns_name', None) if not i['public_dns_name']: @@ -451,7 +452,7 @@ class CloudController(object): def format_addresses(self, context): addresses = [] - for address in self.network.host_objs: + for address in self.network_model.PublicAddress.all(): # TODO(vish): implement a by_project iterator for addresses if (context.user.is_admin() or address['project_id'] == self.project.id): @@ -520,7 +521,7 @@ class CloudController(object): "args": {"user_id": context.user.id, "project_id": context.project.id}}) host = result['result'] - defer.returnValue('%s.%s' %(FLAGS.network_topic, host)) + defer.returnValue('%s.%s' %(FLAGS.network_topic, host)) @rbac.allow('projectmanager', 'sysadmin') @defer.inlineCallbacks @@ -608,9 +609,10 @@ class CloudController(object): logging.warning("Instance %s was not found during terminate" % i) continue - elastic_ip = instance.get('public_dns_name', None) - if elastic_ip: - logging.debug("Deallocating address %s" % elastic_ip) + address = self.network_model.get_public_ip_for_instance(i) + if address: + elastic_ip = address['public_ip'] + logging.debug("Disassociating address %s" % elastic_ip) # NOTE(vish): Right now we don't really care if the ip is # disassociated. We may need to worry about # checking this later. Perhaps in the scheduler? @@ -626,7 +628,7 @@ class CloudController(object): # checking this later. Perhaps in the scheduler? rpc.cast(network_topic, {"method": "deallocate_fixed_ip", - "args": {"elastic_ip": elastic_ip}}) + "args": {"fixed_ip": fixed_ip}}) if instance.get('node_name', 'unassigned') != 'unassigned': # NOTE(joshua?): It's also internal default diff --git a/nova/network/model.py b/nova/network/model.py index 5346549b8..7f0fded4c 100644 --- a/nova/network/model.py +++ b/nova/network/model.py @@ -399,12 +399,6 @@ class PublicNetworkController(BaseNetwork): for address in self.assigned: yield PublicAddress(address) - def get_public_ip_for_instance(self, instance_id): - # FIXME: this should be a lookup - iteration won't scale - for address_record in self.host_objs: - if address_record.get('instance_id', 'available') == instance_id: - return address_record['address'] - def get_host(self, host): if host in self.assigned: return PublicAddress(host) @@ -547,3 +541,9 @@ def get_network_by_interface(iface, security_group='default'): +def get_public_ip_for_instance(self, instance_id): + # FIXME: this should be a lookup - iteration won't scale + for address_record in PublicAddress.all(): + if address_record.get('instance_id', 'available') == instance_id: + return address_record['address'] + -- cgit From bfe90c9c26a0c477386f3143c1e9f0563b6a1a97 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 4 Aug 2010 12:51:05 -0700 Subject: reference to self.project instead of context.project + self.network_model instead of network_model --- nova/endpoint/cloud.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 957b25b9b..338a52214 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -308,7 +308,7 @@ class CloudController(object): def _get_address(self, context, public_ip): # FIXME(vish) this should move into network.py - address = self.network_model.PublicAddress.lookup(public_ip) + address = network_model.PublicAddress.lookup(public_ip) if address and (context.user.is_admin() or address['project_id'] == context.project.id): return address raise exception.NotFound("Address at ip %s not found" % public_ip) @@ -417,7 +417,7 @@ class CloudController(object): 'code': instance.get('state', 0), 'name': instance.get('state_description', 'pending') } - i['public_dns_name'] = self.network_model.get_public_ip_for_instance( + i['public_dns_name'] = network_model.get_public_ip_for_instance( i['instance_id']) i['private_dns_name'] = instance.get('private_dns_name', None) if not i['public_dns_name']: @@ -452,10 +452,10 @@ class CloudController(object): def format_addresses(self, context): addresses = [] - for address in self.network_model.PublicAddress.all(): + for address in network_model.PublicAddress.all(): # TODO(vish): implement a by_project iterator for addresses if (context.user.is_admin() or - address['project_id'] == self.project.id): + address['project_id'] == context.project.id): address_rv = { 'public_ip': address['address'], 'instance_id' : address.get('instance_id', 'free') @@ -609,7 +609,7 @@ class CloudController(object): logging.warning("Instance %s was not found during terminate" % i) continue - address = self.network_model.get_public_ip_for_instance(i) + address = network_model.get_public_ip_for_instance(i) if address: elastic_ip = address['public_ip'] logging.debug("Disassociating address %s" % elastic_ip) -- cgit From 4f6d71411ca23a4f92654f000e24fe008f0a00da Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 4 Aug 2010 12:54:21 -0700 Subject: use iteritems --- nova/endpoint/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 338a52214..789663899 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -584,7 +584,7 @@ class CloudController(object): inst['project_id'] = context.project.id inst['ami_launch_index'] = num inst['security_group'] = security_group - for (key, value) in allocate_data: + for (key, value) in allocate_data.iteritems(): inst[key] = value inst.save() -- cgit From a0eb1b9cc2e33c1a90501daeb3776738689e328f Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 4 Aug 2010 14:08:23 -0700 Subject: fix extra reference, method passing to network, various errors in elastic_ips --- nova/endpoint/cloud.py | 12 +++++++----- nova/network/model.py | 6 +++++- nova/network/service.py | 7 +++++-- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 789663899..1695e4b05 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -473,8 +473,10 @@ class CloudController(object): @defer.inlineCallbacks def allocate_address(self, context, **kwargs): network_topic = yield self._get_network_topic(context) - alloc_result = rpc.call(network_topic, - {"method": "allocate_elastic_ip"}) + alloc_result = yield rpc.call(network_topic, + {"method": "allocate_elastic_ip", + "args": {"user_id": context.user.id, + "project_id": context.project.id}}) public_ip = alloc_result['result'] defer.returnValue({'addressSet': [{'publicIp' : public_ip}]}) @@ -496,7 +498,7 @@ class CloudController(object): network_topic = yield self._get_network_topic(context) rpc.cast(network_topic, {"method": "associate_elastic_ip", - "args": {"elastic_ip": address['public_ip'], + "args": {"elastic_ip": address['address'], "fixed_ip": instance['private_dns_name'], "instance_id": instance['instance_id']}}) defer.returnValue({'associateResponse': ["Address associated."]}) @@ -507,8 +509,8 @@ class CloudController(object): address = self._get_address(context, public_ip) network_topic = yield self._get_network_topic(context) rpc.cast(network_topic, - {"method": "associate_elastic_ip", - "args": {"elastic_ip": address['public_ip']}}) + {"method": "disassociate_elastic_ip", + "args": {"elastic_ip": address['address']}}) defer.returnValue({'disassociateResponse': ["Address disassociated."]}) @defer.inlineCallbacks diff --git a/nova/network/model.py b/nova/network/model.py index 7f0fded4c..a1eaf5753 100644 --- a/nova/network/model.py +++ b/nova/network/model.py @@ -412,6 +412,10 @@ class PublicNetworkController(BaseNetwork): PublicAddress(host).destroy() datastore.Redis.instance().hdel(self._hosts_key, host) + def deallocate_ip(self, ip_str): + # NOTE(vish): cleanup is now done on release by the parent class + self.release_ip(ip_str) + def associate_address(self, public_ip, private_ip, instance_id): if not public_ip in self.assigned: raise exception.AddressNotAllocated() @@ -541,7 +545,7 @@ def get_network_by_interface(iface, security_group='default'): -def get_public_ip_for_instance(self, instance_id): +def get_public_ip_for_instance(instance_id): # FIXME: this should be a lookup - iteration won't scale for address_record in PublicAddress.all(): if address_record.get('instance_id', 'available') == instance_id: diff --git a/nova/network/service.py b/nova/network/service.py index 1f36abc2b..bf4aa0073 100644 --- a/nova/network/service.py +++ b/nova/network/service.py @@ -88,9 +88,12 @@ class BaseNetworkService(service.Service): """Subclass implements return of ip to the pool""" raise NotImplementedError() - def allocate_elastic_ip(self): + def allocate_elastic_ip(self, user_id, project_id): """Gets a elastic ip from the pool""" - return self.network.allocate_ip() + # NOTE(vish): Replicating earlier decision to use 'public' as + # mac address name, although this should probably + # be done inside of the PublicNetworkController + return self.network.allocate_ip(user_id, project_id, 'public') def associate_elastic_ip(self, elastic_ip, fixed_ip, instance_id): """Associates an elastic ip to a fixed ip""" -- cgit From 9a038d2b81163d3e658e4fb3be4f8c14aa3b5fab Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 4 Aug 2010 15:44:23 -0700 Subject: fixed tests, moved compute network config call, added notes, made inject option into a boolean --- bin/nova-network | 7 +--- nova/compute/service.py | 19 +++++++---- nova/network/service.py | 75 ++++++++++++++++++++++++++++++++---------- nova/tests/network_unittest.py | 18 +++++----- nova/virt/libvirt_conn.py | 2 +- 5 files changed, 81 insertions(+), 40 deletions(-) diff --git a/bin/nova-network b/bin/nova-network index 1d620b525..ba9063f56 100755 --- a/bin/nova-network +++ b/bin/nova-network @@ -23,7 +23,6 @@ from nova import flags from nova import twistd -from nova import utils from nova.network import service @@ -34,8 +33,4 @@ if __name__ == '__main__': twistd.serve(__file__) if __name__ == '__builtin__': - t = FLAGS.network_type - if t == 'flat': - application = service.FlatNetworkService.create() - elif t == 'vlan': - application = service.VlanNetworkService.create() + application = service.type_to_class(FLAGS.network_type).create() diff --git a/nova/compute/service.py b/nova/compute/service.py index a22240b05..820116453 100644 --- a/nova/compute/service.py +++ b/nova/compute/service.py @@ -41,7 +41,7 @@ from nova.compute import disk from nova.compute import model from nova.compute import power_state from nova.compute.instance_types import INSTANCE_TYPES -from nova.network import model as net_model +from nova.network import service as network_service from nova.objectstore import image # for image_path flag from nova.virt import connection as virt_connection from nova.volume import service as volume_service @@ -117,12 +117,17 @@ class ComputeService(service.Service): """ launch a new instance with specified options """ logging.debug("Starting instance %s..." % (instance_id)) inst = self.instdir.get(instance_id) - if inst.get('network_type', 'dhcp') == 'dhcp': - # TODO: Get the real security group of launch in here - security_group = "default" - net = net_model.BridgedNetwork.get_network_for_project(inst['user_id'], - inst['project_id'], - security_group).express() + # TODO: Get the real security group of launch in here + security_group = "default" + # NOTE(vish): passing network type allows us to express the + # network without making a call to network to find + # out which type of network to setup + network_service.setup_compute_network( + inst.get('network_type', 'vlan'), + inst['user_id'], + inst['project_id'], + security_group) + inst['node_name'] = FLAGS.node_name inst.save() # TODO(vish) check to make sure the availability zone matches diff --git a/nova/network/service.py b/nova/network/service.py index bf4aa0073..57b8bbb78 100644 --- a/nova/network/service.py +++ b/nova/network/service.py @@ -20,8 +20,6 @@ Network Nodes are responsible for allocating ips and setting up network """ -import logging - from twisted.internet import defer from nova import datastore @@ -29,6 +27,7 @@ from nova import flags from nova import service from nova import utils from nova.auth import manager +from nova.exception import NotFound from nova.network import exception from nova.network import model @@ -53,10 +52,24 @@ flags.DEFINE_string('flat_network_broadcast', '192.168.0.255', flags.DEFINE_string('flat_network_dns', '8.8.4.4', 'Dns for simple network') +def type_to_class(network_type): + if network_type == 'flat': + return FlatNetworkService + elif network_type == 'vlan': + return VlanNetworkService + raise NotFound("Couldn't find %s network type" % network_type) + + +def setup_compute_network(network_type, user_id, project_id, security_group): + srv = type_to_class(network_type) + srv.setup_compute_network(network_type, user_id, project_id, security_group) + + def get_host_for_project(project_id): redis = datastore.Redis.instance() return redis.get(_host_key(project_id)) + def _host_key(project_id): return "network_host:%s" % project_id @@ -88,6 +101,12 @@ class BaseNetworkService(service.Service): """Subclass implements return of ip to the pool""" raise NotImplementedError() + @classmethod + def setup_compute_network(self, user_id, project_id, security_group, + *args, **kwargs): + """Sets up matching network for compute hosts""" + raise NotImplementedError() + def allocate_elastic_ip(self, user_id, project_id): """Gets a elastic ip from the pool""" # NOTE(vish): Replicating earlier decision to use 'public' as @@ -111,6 +130,11 @@ class BaseNetworkService(service.Service): class FlatNetworkService(BaseNetworkService): """Basic network where no vlans are used""" + @classmethod + def setup_compute_network(self, fixed_ip, *args, **kwargs): + """Network is created manually""" + pass + def allocate_fixed_ip(self, user_id, project_id, security_group='default', *args, **kwargs): @@ -118,6 +142,9 @@ class FlatNetworkService(BaseNetworkService): Flat network just grabs the next available ip from the pool """ + # NOTE(vish): Some automation could be done here. For example, + # creating the flat_network_bridge and setting up + # a gateway. This is all done manually atm redis = datastore.Redis.instance() if not redis.exists('ips') and not len(redis.keys('instances:*')): for fixed_ip in FLAGS.flat_network_ips: @@ -125,15 +152,16 @@ class FlatNetworkService(BaseNetworkService): fixed_ip = redis.spop('ips') if not fixed_ip: raise exception.NoMoreAddresses() - return defer.succeed({'network_type': 'injected', - 'mac_address': utils.generate_mac(), - 'private_dns_name': str(fixed_ip), - 'bridge_name': FLAGS.flat_network_bridge, - 'network_network': FLAGS.flat_network_network, - 'network_netmask': FLAGS.flat_network_netmask, - 'network_gateway': FLAGS.flat_network_gateway, - 'network_broadcast': FLAGS.flat_network_broadcast, - 'network_dns': FLAGS.flat_network_dns}) + return defer.succeed({'inject_network': True, + 'network_type': FLAGS.network_type, + 'mac_address': utils.generate_mac(), + 'private_dns_name': str(fixed_ip), + 'bridge_name': FLAGS.flat_network_bridge, + 'network_network': FLAGS.flat_network_network, + 'network_netmask': FLAGS.flat_network_netmask, + 'network_gateway': FLAGS.flat_network_gateway, + 'network_broadcast': FLAGS.flat_network_broadcast, + 'network_dns': FLAGS.flat_network_dns}) def deallocate_fixed_ip(self, fixed_ip, *args, **kwargs): """Returns an ip to the pool""" @@ -141,7 +169,10 @@ class FlatNetworkService(BaseNetworkService): class VlanNetworkService(BaseNetworkService): """Vlan network with dhcp""" - + # NOTE(vish): A lot of the interactions with network/model.py can be + # simplified and improved. Also there it may be useful + # to support vlans separately from dhcp, instead of having + # both of them together in this class. def allocate_fixed_ip(self, user_id, project_id, security_group='default', vpn=False, *args, **kwargs): @@ -152,10 +183,10 @@ class VlanNetworkService(BaseNetworkService): fixed_ip = net.allocate_vpn_ip(user_id, project_id, mac) else: fixed_ip = net.allocate_ip(user_id, project_id, mac) - return defer.succeed({'network_type': 'dhcp', - 'bridge_name': net['bridge_name'], - 'mac_address': mac, - 'private_dns_name' : fixed_ip}) + return defer.succeed({'network_type': FLAGS.network_type, + 'bridge_name': net['bridge_name'], + 'mac_address': mac, + 'private_dns_name' : fixed_ip}) def deallocate_fixed_ip(self, fixed_ip, *args, **kwargs): @@ -173,4 +204,14 @@ class VlanNetworkService(BaseNetworkService): for project in manager.AuthManager().get_projects(): model.get_project_network(project.id).express() - + @classmethod + def setup_compute_network(self, user_id, project_id, security_group, + *args, **kwargs): + """Sets up matching network for compute hosts""" + # NOTE(vish): Use BridgedNetwork instead of DHCPNetwork because + # we don't want to run dnsmasq on the client machines + net = model.BridgedNetwork.get_network_for_project( + user_id, + project_id, + security_group) + net.express() diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index a9695f818..42cae327f 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -69,7 +69,7 @@ class NetworkTestCase(test.TrialTestCase): self.assertTrue(IPy.IP(address) in self.network.network) def test_allocate_deallocate_fixed_ip(self): - result = self.service.allocate_fixed_ip( + result = yield self.service.allocate_fixed_ip( self.user.id, self.projects[0].id) address = result['private_dns_name'] mac = result['mac_address'] @@ -88,11 +88,11 @@ class NetworkTestCase(test.TrialTestCase): def test_range_allocation(self): hostname = "test-host" - result = self.service.allocate_fixed_ip( + result = yield self.service.allocate_fixed_ip( self.user.id, self.projects[0].id) mac = result['mac_address'] address = result['private_dns_name'] - result = self.service.allocate_fixed_ip( + result = yield self.service.allocate_fixed_ip( self.user, self.projects[1].id) secondmac = result['mac_address'] secondaddress = result['private_dns_name'] @@ -122,21 +122,21 @@ class NetworkTestCase(test.TrialTestCase): self.assertEqual(False, is_in_project(secondaddress, self.projects[1].id)) def test_subnet_edge(self): - result = self.service.allocate_fixed_ip(self.user.id, + result = yield self.service.allocate_fixed_ip(self.user.id, self.projects[0].id) firstaddress = result['private_dns_name'] hostname = "toomany-hosts" for i in range(1,5): project_id = self.projects[i].id - result = self.service.allocate_fixed_ip( + result = yield self.service.allocate_fixed_ip( self.user, project_id) mac = result['mac_address'] address = result['private_dns_name'] - result = self.service.allocate_fixed_ip( + result = yield self.service.allocate_fixed_ip( self.user, project_id) mac2 = result['mac_address'] address2 = result['private_dns_name'] - result = self.service.allocate_fixed_ip( + result = yield self.service.allocate_fixed_ip( self.user, project_id) mac3 = result['mac_address'] address3 = result['private_dns_name'] @@ -193,12 +193,12 @@ class NetworkTestCase(test.TrialTestCase): macs = {} addresses = {} for i in range(0, (num_available_ips - 1)): - result = self.service.allocate_fixed_ip(self.user.id, self.projects[0].id) + result = yield self.service.allocate_fixed_ip(self.user.id, self.projects[0].id) macs[i] = result['mac_address'] addresses[i] = result['private_dns_name'] self.dnsmasq.issue_ip(macs[i], addresses[i], hostname, net.bridge_name) - self.assertRaises(NoMoreAddresses, self.service.allocate_fixed_ip, self.user.id, self.projects[0].id) + self.assertFailure(self.service.allocate_fixed_ip(self.user.id, self.projects[0].id), NoMoreAddresses) for i in range(0, (num_available_ips - 1)): rv = self.service.deallocate_fixed_ip(addresses[i]) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 8133daf0b..7c3f7a6e1 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -204,7 +204,7 @@ class LibvirtConnection(object): key = data['key_data'] net = None - if data.get('network_type', None) == 'injected': + if data.get('inject_network', False): with open(FLAGS.injected_network_template) as f: net = f.read() % {'address': data['private_dns_name'], 'network': data['network_network'], -- cgit From de456585b67f3eb46bcae5869af4ac83c6d95908 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 4 Aug 2010 15:53:24 -0700 Subject: fix error on terminate instance relating to elastic ip --- nova/endpoint/cloud.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 1695e4b05..349007efa 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -611,9 +611,8 @@ class CloudController(object): logging.warning("Instance %s was not found during terminate" % i) continue - address = network_model.get_public_ip_for_instance(i) - if address: - elastic_ip = address['public_ip'] + elastic_ip = network_model.get_public_ip_for_instance(i) + if elastic_ip: logging.debug("Disassociating address %s" % elastic_ip) # NOTE(vish): Right now we don't really care if the ip is # disassociated. We may need to worry about -- cgit From aa84936cb63cd1913a1640944a9353d018ace13f Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 4 Aug 2010 16:51:48 -0700 Subject: fix rpc command line call, remove useless deferreds --- nova/network/service.py | 34 ++++++++++++++++------------------ nova/rpc.py | 4 ++-- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/nova/network/service.py b/nova/network/service.py index 57b8bbb78..873e31630 100644 --- a/nova/network/service.py +++ b/nova/network/service.py @@ -20,8 +20,6 @@ Network Nodes are responsible for allocating ips and setting up network """ -from twisted.internet import defer - from nova import datastore from nova import flags from nova import service @@ -87,9 +85,9 @@ class BaseNetworkService(service.Service): redis = datastore.Redis.instance() key = _host_key(project_id) if redis.setnx(key, FLAGS.node_name): - return defer.succeed(FLAGS.node_name) + return FLAGS.node_name else: - return defer.succeed(redis.get(key)) + return redis.get(key) def allocate_fixed_ip(self, user_id, project_id, security_group='default', @@ -152,16 +150,16 @@ class FlatNetworkService(BaseNetworkService): fixed_ip = redis.spop('ips') if not fixed_ip: raise exception.NoMoreAddresses() - return defer.succeed({'inject_network': True, - 'network_type': FLAGS.network_type, - 'mac_address': utils.generate_mac(), - 'private_dns_name': str(fixed_ip), - 'bridge_name': FLAGS.flat_network_bridge, - 'network_network': FLAGS.flat_network_network, - 'network_netmask': FLAGS.flat_network_netmask, - 'network_gateway': FLAGS.flat_network_gateway, - 'network_broadcast': FLAGS.flat_network_broadcast, - 'network_dns': FLAGS.flat_network_dns}) + return {'inject_network': True, + 'network_type': FLAGS.network_type, + 'mac_address': utils.generate_mac(), + 'private_dns_name': str(fixed_ip), + 'bridge_name': FLAGS.flat_network_bridge, + 'network_network': FLAGS.flat_network_network, + 'network_netmask': FLAGS.flat_network_netmask, + 'network_gateway': FLAGS.flat_network_gateway, + 'network_broadcast': FLAGS.flat_network_broadcast, + 'network_dns': FLAGS.flat_network_dns} def deallocate_fixed_ip(self, fixed_ip, *args, **kwargs): """Returns an ip to the pool""" @@ -183,10 +181,10 @@ class VlanNetworkService(BaseNetworkService): fixed_ip = net.allocate_vpn_ip(user_id, project_id, mac) else: fixed_ip = net.allocate_ip(user_id, project_id, mac) - return defer.succeed({'network_type': FLAGS.network_type, - 'bridge_name': net['bridge_name'], - 'mac_address': mac, - 'private_dns_name' : fixed_ip}) + return {'network_type': FLAGS.network_type, + 'bridge_name': net['bridge_name'], + 'mac_address': mac, + 'private_dns_name' : fixed_ip} def deallocate_fixed_ip(self, fixed_ip, *args, **kwargs): diff --git a/nova/rpc.py b/nova/rpc.py index ebf140d92..2a550c3ae 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -238,12 +238,12 @@ def send_message(topic, message, wait=True): exchange=msg_id, auto_delete=True, exchange_type="direct", - routing_key=msg_id, - durable=False) + routing_key=msg_id) consumer.register_callback(generic_response) publisher = messaging.Publisher(connection=Connection.instance(), exchange=FLAGS.control_exchange, + durable=False, exchange_type="topic", routing_key=topic) publisher.send(message) -- cgit From 6b70951e5b7cb8cabe5d6eb50fce7ae0a6e55d52 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 4 Aug 2010 17:40:16 -0700 Subject: renamed Vpn to NetworkData, moved the creation of data to inside network --- nova/auth/manager.py | 100 +++--------------------------------------------- nova/network/model.py | 90 +++++++++++++++++++++++++++++++++++++++++++ nova/network/service.py | 20 ++++++++-- 3 files changed, 112 insertions(+), 98 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 2da53a736..dacdeb383 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -37,6 +37,7 @@ from nova import objectstore # for flags from nova import utils from nova.auth import ldapdriver # for flags from nova.auth import signer +from nova.network import model FLAGS = flags.FLAGS @@ -51,7 +52,6 @@ flags.DEFINE_list('global_roles', ['cloudadmin', 'itsec'], 'Roles that apply to all projects') -flags.DEFINE_bool('use_vpn', True, 'Support per-project vpns') flags.DEFINE_string('credentials_template', utils.abspath('auth/novarc.template'), 'Template for creating users rc file') @@ -65,19 +65,11 @@ flags.DEFINE_string('credential_cert_file', 'cert.pem', flags.DEFINE_string('credential_rc_file', 'novarc', 'Filename of rc in credentials zip') -flags.DEFINE_integer('vpn_start_port', 1000, - 'Start port for the cloudpipe VPN servers') -flags.DEFINE_integer('vpn_end_port', 2000, - 'End port for the cloudpipe VPN servers') - flags.DEFINE_string('credential_cert_subject', '/C=US/ST=California/L=MountainView/O=AnsoLabs/' 'OU=NovaDev/CN=%s-%s', 'Subject for certificate for users') -flags.DEFINE_string('vpn_ip', '127.0.0.1', - 'Public IP for the cloudpipe VPN servers') - flags.DEFINE_string('auth_driver', 'nova.auth.ldapdriver.FakeLdapDriver', 'Driver that auth manager uses') @@ -229,86 +221,6 @@ class Project(AuthBase): self.member_ids) -class NoMorePorts(exception.Error): - pass - - -class Vpn(datastore.BasicModel): - """Manages vpn ips and ports for projects""" - def __init__(self, project_id): - self.project_id = project_id - super(Vpn, self).__init__() - - @property - def identifier(self): - """Identifier used for key in redis""" - return self.project_id - - @classmethod - def create(cls, project_id): - """Creates a vpn for project - - This method finds a free ip and port and stores the associated - values in the datastore. - """ - # TODO(vish): get list of vpn ips from redis - port = cls.find_free_port_for_ip(FLAGS.vpn_ip) - vpn = cls(project_id) - # save ip for project - vpn['project'] = project_id - vpn['ip'] = FLAGS.vpn_ip - vpn['port'] = port - vpn.save() - return vpn - - @classmethod - def find_free_port_for_ip(cls, ip): - """Finds a free port for a given ip from the redis set""" - # TODO(vish): these redis commands should be generalized and - # placed into a base class. Conceptually, it is - # similar to an association, but we are just - # storing a set of values instead of keys that - # should be turned into objects. - redis = datastore.Redis.instance() - key = 'ip:%s:ports' % ip - # 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 = redis.spop(key) - if not port: - raise NoMorePorts() - return port - - @classmethod - def num_ports_for_ip(cls, ip): - """Calculates the number of free ports for a given ip""" - return datastore.Redis.instance().scard('ip:%s:ports' % ip) - - @property - def ip(self): - """The ip assigned to the project""" - return self['ip'] - - @property - def port(self): - """The port assigned to the project""" - return int(self['port']) - - def save(self): - """Saves the association to the given ip""" - self.associate_with('ip', self.ip) - super(Vpn, self).save() - - def destroy(self): - """Cleans up datastore and adds port back to pool""" - self.unassociate_with('ip', self.ip) - datastore.Redis.instance().sadd('ip:%s:ports' % self.ip, self.port) - super(Vpn, self).destroy() - class AuthManager(object): """Manager Singleton for dealing with Users, Projects, and Keypairs @@ -581,8 +493,6 @@ class AuthManager(object): description, member_users) if project_dict: - if FLAGS.use_vpn: - Vpn.create(project_dict['id']) return Project(**project_dict) def add_to_project(self, user, project): @@ -619,10 +529,10 @@ class AuthManager(object): @return: A tuple containing (ip, port) or None, None if vpn has not been allocated for user. """ - vpn = Vpn.lookup(Project.safe_id(project)) - if not vpn: - return None, None - return (vpn.ip, vpn.port) + network_data = model.NetworkData.lookup(Project.safe_id(project)) + if not network_data: + raise exception.NotFound('project network data has not been set') + return (network_data.ip, network_data.port) def delete_project(self, project): """Deletes a project""" diff --git a/nova/network/model.py b/nova/network/model.py index a1eaf5753..b065d174e 100644 --- a/nova/network/model.py +++ b/nova/network/model.py @@ -53,6 +53,13 @@ flags.DEFINE_integer('cnt_vpn_clients', 5, flags.DEFINE_integer('cloudpipe_start_port', 12000, 'Starting port for mapped CloudPipe external ports') +flags.DEFINE_string('vpn_ip', utils.get_my_ip(), + 'Public IP for the cloudpipe VPN servers') +flags.DEFINE_integer('vpn_start_port', 1000, + 'Start port for the cloudpipe VPN servers') +flags.DEFINE_integer('vpn_end_port', 2000, + 'End port for the cloudpipe VPN servers') + logging.getLogger().setLevel(logging.DEBUG) @@ -373,6 +380,89 @@ class PublicAddress(datastore.BasicModel): addr.save() return addr + +class NoMorePorts(exception.Error): + pass + + +class NetworkData(datastore.BasicModel): + """Manages network host, and vpn ip and port for projects""" + def __init__(self, project_id): + self.project_id = project_id + super(NetworkData, self).__init__() + + @property + def identifier(self): + """Identifier used for key in redis""" + return self.project_id + + @classmethod + def create(cls, project_id): + """Creates a vpn for project + + This method finds a free ip and port and stores the associated + values in the datastore. + """ + # TODO(vish): will we ever need multiiple ips per host? + port = cls.find_free_port_for_ip(FLAGS.vpn_ip) + network_data = cls(project_id) + # save ip for project + network_data['host'] = FLAGS.node_name + network_data['project'] = project_id + network_data['ip'] = FLAGS.vpn_ip + network_data['port'] = port + network_data.save() + return network_data + + @classmethod + def find_free_port_for_ip(cls, ip): + """Finds a free port for a given ip from the redis set""" + # TODO(vish): these redis commands should be generalized and + # placed into a base class. Conceptually, it is + # similar to an association, but we are just + # storing a set of values instead of keys that + # should be turned into objects. + redis = datastore.Redis.instance() + key = 'ip:%s:ports' % ip + # 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 = redis.spop(key) + if not port: + raise NoMorePorts() + return port + + @classmethod + def num_ports_for_ip(cls, ip): + """Calculates the number of free ports for a given ip""" + return datastore.Redis.instance().scard('ip:%s:ports' % ip) + + @property + def ip(self): + """The ip assigned to the project""" + return self['ip'] + + @property + def port(self): + """The port assigned to the project""" + return int(self['port']) + + def save(self): + """Saves the association to the given ip""" + self.associate_with('ip', self.ip) + super(NetworkData, self).save() + + def destroy(self): + """Cleans up datastore and adds port back to pool""" + self.unassociate_with('ip', self.ip) + datastore.Redis.instance().sadd('ip:%s:ports' % self.ip, self.port) + super(NetworkData, self).destroy() + + DEFAULT_PORTS = [("tcp",80), ("tcp",22), ("udp",1194), ("tcp",443)] class PublicNetworkController(BaseNetwork): override_type = 'network' diff --git a/nova/network/service.py b/nova/network/service.py index 873e31630..243f9a720 100644 --- a/nova/network/service.py +++ b/nova/network/service.py @@ -80,11 +80,14 @@ class BaseNetworkService(service.Service): def __init__(self, *args, **kwargs): self.network = model.PublicNetworkController() - def get_network_host(self, user_id, project_id, *args, **kwargs): + def set_network_host(self, user_id, project_id, *args, **kwargs): """Safely becomes the host of the projects network""" redis = datastore.Redis.instance() key = _host_key(project_id) if redis.setnx(key, FLAGS.node_name): + self._on_set_network_host(user_id, project_id, + security_group='default', + *args, **kwargs) return FLAGS.node_name else: return redis.get(key) @@ -99,6 +102,11 @@ class BaseNetworkService(service.Service): """Subclass implements return of ip to the pool""" raise NotImplementedError() + def _on_set_network_host(self, user_id, project_id, + *args, **kwargs): + """Called when this host becomes the host for a project""" + pass + @classmethod def setup_compute_network(self, user_id, project_id, security_group, *args, **kwargs): @@ -129,7 +137,8 @@ class FlatNetworkService(BaseNetworkService): """Basic network where no vlans are used""" @classmethod - def setup_compute_network(self, fixed_ip, *args, **kwargs): + def setup_compute_network(self, user_id, project_id, security_group, + *args, **kwargs): """Network is created manually""" pass @@ -198,10 +207,15 @@ class VlanNetworkService(BaseNetworkService): return model.get_network_by_address(address).release_ip(address) def restart_nets(self): - """ Ensure the network for each user is enabled""" + """Ensure the network for each user is enabled""" for project in manager.AuthManager().get_projects(): model.get_project_network(project.id).express() + def _on_set_network_host(self, user_id, project_id, + *args, **kwargs): + """Called when this host becomes the host for a project""" + model.NetworkData.create(project_id) + @classmethod def setup_compute_network(self, user_id, project_id, security_group, *args, **kwargs): -- cgit From c1e0abd5be3ac8473aaf255f77fb2357b5771ea9 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 4 Aug 2010 18:00:13 -0700 Subject: fixed circular reference and tests --- nova/auth/manager.py | 4 +- nova/network/model.py | 89 ------------------------------- nova/network/networkdata.py | 116 +++++++++++++++++++++++++++++++++++++++++ nova/network/service.py | 5 +- nova/tests/auth_unittest.py | 14 ----- nova/tests/network_unittest.py | 15 ++++++ 6 files changed, 136 insertions(+), 107 deletions(-) create mode 100644 nova/network/networkdata.py diff --git a/nova/auth/manager.py b/nova/auth/manager.py index dacdeb383..463cfdf4a 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -37,7 +37,7 @@ from nova import objectstore # for flags from nova import utils from nova.auth import ldapdriver # for flags from nova.auth import signer -from nova.network import model +from nova.network import networkdata FLAGS = flags.FLAGS @@ -529,7 +529,7 @@ class AuthManager(object): @return: A tuple containing (ip, port) or None, None if vpn has not been allocated for user. """ - network_data = model.NetworkData.lookup(Project.safe_id(project)) + network_data = networkdata.NetworkData.lookup(Project.safe_id(project)) if not network_data: raise exception.NotFound('project network data has not been set') return (network_data.ip, network_data.port) diff --git a/nova/network/model.py b/nova/network/model.py index b065d174e..daac035e4 100644 --- a/nova/network/model.py +++ b/nova/network/model.py @@ -53,13 +53,6 @@ flags.DEFINE_integer('cnt_vpn_clients', 5, flags.DEFINE_integer('cloudpipe_start_port', 12000, 'Starting port for mapped CloudPipe external ports') -flags.DEFINE_string('vpn_ip', utils.get_my_ip(), - 'Public IP for the cloudpipe VPN servers') -flags.DEFINE_integer('vpn_start_port', 1000, - 'Start port for the cloudpipe VPN servers') -flags.DEFINE_integer('vpn_end_port', 2000, - 'End port for the cloudpipe VPN servers') - logging.getLogger().setLevel(logging.DEBUG) @@ -381,88 +374,6 @@ class PublicAddress(datastore.BasicModel): return addr -class NoMorePorts(exception.Error): - pass - - -class NetworkData(datastore.BasicModel): - """Manages network host, and vpn ip and port for projects""" - def __init__(self, project_id): - self.project_id = project_id - super(NetworkData, self).__init__() - - @property - def identifier(self): - """Identifier used for key in redis""" - return self.project_id - - @classmethod - def create(cls, project_id): - """Creates a vpn for project - - This method finds a free ip and port and stores the associated - values in the datastore. - """ - # TODO(vish): will we ever need multiiple ips per host? - port = cls.find_free_port_for_ip(FLAGS.vpn_ip) - network_data = cls(project_id) - # save ip for project - network_data['host'] = FLAGS.node_name - network_data['project'] = project_id - network_data['ip'] = FLAGS.vpn_ip - network_data['port'] = port - network_data.save() - return network_data - - @classmethod - def find_free_port_for_ip(cls, ip): - """Finds a free port for a given ip from the redis set""" - # TODO(vish): these redis commands should be generalized and - # placed into a base class. Conceptually, it is - # similar to an association, but we are just - # storing a set of values instead of keys that - # should be turned into objects. - redis = datastore.Redis.instance() - key = 'ip:%s:ports' % ip - # 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 = redis.spop(key) - if not port: - raise NoMorePorts() - return port - - @classmethod - def num_ports_for_ip(cls, ip): - """Calculates the number of free ports for a given ip""" - return datastore.Redis.instance().scard('ip:%s:ports' % ip) - - @property - def ip(self): - """The ip assigned to the project""" - return self['ip'] - - @property - def port(self): - """The port assigned to the project""" - return int(self['port']) - - def save(self): - """Saves the association to the given ip""" - self.associate_with('ip', self.ip) - super(NetworkData, self).save() - - def destroy(self): - """Cleans up datastore and adds port back to pool""" - self.unassociate_with('ip', self.ip) - datastore.Redis.instance().sadd('ip:%s:ports' % self.ip, self.port) - super(NetworkData, self).destroy() - - DEFAULT_PORTS = [("tcp",80), ("tcp",22), ("udp",1194), ("tcp",443)] class PublicNetworkController(BaseNetwork): override_type = 'network' diff --git a/nova/network/networkdata.py b/nova/network/networkdata.py new file mode 100644 index 000000000..cec84287c --- /dev/null +++ b/nova/network/networkdata.py @@ -0,0 +1,116 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# 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. + +"""Network Data for projects""" + +from nova import datastore +from nova import exception +from nova import flags +from nova import utils + +FLAGS = flags.FLAGS + + +flags.DEFINE_string('vpn_ip', utils.get_my_ip(), + 'Public IP for the cloudpipe VPN servers') +flags.DEFINE_integer('vpn_start_port', 1000, + 'Start port for the cloudpipe VPN servers') +flags.DEFINE_integer('vpn_end_port', 2000, + 'End port for the cloudpipe VPN servers') + +class NoMorePorts(exception.Error): + pass + + +class NetworkData(datastore.BasicModel): + """Manages network host, and vpn ip and port for projects""" + def __init__(self, project_id): + self.project_id = project_id + super(NetworkData, self).__init__() + + @property + def identifier(self): + """Identifier used for key in redis""" + return self.project_id + + @classmethod + def create(cls, project_id): + """Creates a vpn for project + + This method finds a free ip and port and stores the associated + values in the datastore. + """ + # TODO(vish): will we ever need multiiple ips per host? + port = cls.find_free_port_for_ip(FLAGS.vpn_ip) + network_data = cls(project_id) + # save ip for project + network_data['host'] = FLAGS.node_name + network_data['project'] = project_id + network_data['ip'] = FLAGS.vpn_ip + network_data['port'] = port + network_data.save() + return network_data + + @classmethod + def find_free_port_for_ip(cls, ip): + """Finds a free port for a given ip from the redis set""" + # TODO(vish): these redis commands should be generalized and + # placed into a base class. Conceptually, it is + # similar to an association, but we are just + # storing a set of values instead of keys that + # should be turned into objects. + redis = datastore.Redis.instance() + key = 'ip:%s:ports' % ip + # 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 = redis.spop(key) + if not port: + raise NoMorePorts() + return port + + @classmethod + def num_ports_for_ip(cls, ip): + """Calculates the number of free ports for a given ip""" + return datastore.Redis.instance().scard('ip:%s:ports' % ip) + + @property + def ip(self): + """The ip assigned to the project""" + return self['ip'] + + @property + def port(self): + """The port assigned to the project""" + return int(self['port']) + + def save(self): + """Saves the association to the given ip""" + self.associate_with('ip', self.ip) + super(NetworkData, self).save() + + def destroy(self): + """Cleans up datastore and adds port back to pool""" + self.unassociate_with('ip', self.ip) + datastore.Redis.instance().sadd('ip:%s:ports' % self.ip, self.port) + super(NetworkData, self).destroy() + diff --git a/nova/network/service.py b/nova/network/service.py index 243f9a720..afc20c0d5 100644 --- a/nova/network/service.py +++ b/nova/network/service.py @@ -28,6 +28,7 @@ from nova.auth import manager from nova.exception import NotFound from nova.network import exception from nova.network import model +from nova.network import networkdata FLAGS = flags.FLAGS @@ -81,7 +82,7 @@ class BaseNetworkService(service.Service): self.network = model.PublicNetworkController() def set_network_host(self, user_id, project_id, *args, **kwargs): - """Safely becomes the host of the projects network""" + """Safely sets the host of the projects network""" redis = datastore.Redis.instance() key = _host_key(project_id) if redis.setnx(key, FLAGS.node_name): @@ -214,7 +215,7 @@ class VlanNetworkService(BaseNetworkService): def _on_set_network_host(self, user_id, project_id, *args, **kwargs): """Called when this host becomes the host for a project""" - model.NetworkData.create(project_id) + networkdata.NetworkData.create(project_id) @classmethod def setup_compute_network(self, user_id, project_id, security_group, diff --git a/nova/tests/auth_unittest.py b/nova/tests/auth_unittest.py index 2167c2385..3bd0a432c 100644 --- a/nova/tests/auth_unittest.py +++ b/nova/tests/auth_unittest.py @@ -179,20 +179,6 @@ class AuthTestCase(test.BaseTestCase): self.manager.remove_role('test1', 'sysadmin') self.assertFalse(project.has_role('test1', 'sysadmin')) - def test_212_vpn_ip_and_port_looks_valid(self): - project = self.manager.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): - vpns = [] - for i in xrange(manager.Vpn.num_ports_for_ip(FLAGS.vpn_ip)): - vpns.append(manager.Vpn.create("vpnuser%s" % i)) - self.assertRaises(manager.NoMorePorts, manager.Vpn.create, "boom") - for vpn in vpns: - vpn.destroy() - def test_214_can_retrieve_project_by_user(self): project = self.manager.create_project('testproj2', 'test2', 'Another test project', ['test2']) self.assert_(len(self.manager.get_projects()) > 1) diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index 42cae327f..49147d4ec 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -25,6 +25,7 @@ from nova import test from nova import utils from nova.auth import manager from nova.network import model +from nova.network import networkdata from nova.network import service from nova.network.exception import NoMoreAddresses @@ -154,6 +155,20 @@ class NetworkTestCase(test.TrialTestCase): rv = self.service.deallocate_fixed_ip(firstaddress) self.dnsmasq.release_ip(mac, firstaddress, hostname, net.bridge_name) + def test_212_vpn_ip_and_port_looks_valid(self): + networkdata.NetworkData.create(self.projects[0].id) + self.assert_(self.projects[0].vpn_ip) + self.assert_(self.projects[0].vpn_port >= FLAGS.vpn_start_port) + self.assert_(self.projects[0].vpn_port <= FLAGS.vpn_end_port) + + def test_too_many_vpns(self): + vpns = [] + for i in xrange(networkdata.NetworkData.num_ports_for_ip(FLAGS.vpn_ip)): + vpns.append(networkdata.NetworkData.create("vpnuser%s" % i)) + self.assertRaises(networkdata.NoMorePorts, networkdata.NetworkData.create, "boom") + for vpn in vpns: + vpn.destroy() + def test_release_before_deallocate(self): pass -- cgit From cc64a872c685b931bf76e2323986b427cad777c3 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 4 Aug 2010 18:12:19 -0700 Subject: method is called set_network_host --- nova/endpoint/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 349007efa..00dabba28 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -519,7 +519,7 @@ class CloudController(object): host = network_service.get_host_for_project(context.project.id) if not host: result = yield rpc.call(FLAGS.network_topic, - {"method": "get_network_topic", + {"method": "set_network_host", "args": {"user_id": context.user.id, "project_id": context.project.id}}) host = result['result'] -- cgit From d1709793045de2f77f4a1fb06f63d27cbcf640d1 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 4 Aug 2010 18:37:00 -0700 Subject: clean up nova-manage. If vpn data isn't set for user it skips it --- bin/nova-manage | 23 +++++++++++------------ nova/auth/manager.py | 39 ++++++++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index b0f0029ed..7835c7a77 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -29,16 +29,12 @@ from nova import flags from nova import utils from nova.auth import manager from nova.compute import model -from nova.compute import network from nova.cloudpipe import pipelib from nova.endpoint import cloud FLAGS = flags.FLAGS -class NetworkCommands(object): - def restart(self): - network.restart_nets() class VpnCommands(object): def __init__(self): @@ -170,6 +166,13 @@ class ProjectCommands(object): arguments: name""" self.manager.delete_project(name) + def environment(self, project_id, user_id, filename='novarc'): + """exports environment variables to an sourcable file + arguments: project_id user_id [filename='novarc]""" + rc = self.manager.get_environment_rc(project_id, user_id) + with open(filename, 'w') as f: + f.write(rc) + def list(self): """lists all projects arguments: """ @@ -182,14 +185,11 @@ class ProjectCommands(object): self.manager.remove_from_project(user, project) def zip(self, project_id, user_id, filename='nova.zip'): - """exports credentials for user to a zip file + """exports credentials for project to a zip file 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(project.get_credentials(user_id)) - else: - print "Project %s doesn't exist" % project + zip = self.manager.get_credentials(project_id, user_id) + with open(filename, 'w') as f: + f.write(zip) def usage(script_name): @@ -197,7 +197,6 @@ def usage(script_name): categories = [ - ('network', NetworkCommands), ('user', UserCommands), ('project', ProjectCommands), ('role', RoleCommands), diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 463cfdf4a..312b569aa 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -58,6 +58,8 @@ flags.DEFINE_string('credentials_template', flags.DEFINE_string('vpn_client_template', utils.abspath('cloudpipe/client.ovpn.template'), 'Template for creating users vpn file') +flags.DEFINE_string('credential_vpn_file', 'nova-vpn.conf', + 'Filename of certificate in credentials zip') flags.DEFINE_string('credential_key_file', 'pk.pem', 'Filename of private key in credentials zip') flags.DEFINE_string('credential_cert_file', 'cert.pem', @@ -663,25 +665,27 @@ class AuthManager(object): rc = self.__generate_rc(user.access, user.secret, pid) private_key, signed_cert = self._generate_x509_cert(user.id, pid) - vpn = Vpn.lookup(pid) - if not vpn: - raise exception.Error("No vpn data allocated for project %s" % - project.name) - configfile = open(FLAGS.vpn_client_template,"r") - s = string.Template(configfile.read()) - configfile.close() - config = s.substitute(keyfile=FLAGS.credential_key_file, - certfile=FLAGS.credential_cert_file, - ip=vpn.ip, - port=vpn.port) - tmpdir = tempfile.mkdtemp() zf = os.path.join(tmpdir, "temp.zip") zippy = zipfile.ZipFile(zf, 'w') zippy.writestr(FLAGS.credential_rc_file, rc) zippy.writestr(FLAGS.credential_key_file, private_key) zippy.writestr(FLAGS.credential_cert_file, signed_cert) - zippy.writestr("nebula-client.conf", config) + + network_data = networkdata.NetworkData.lookup(pid) + if network_data: + configfile = open(FLAGS.vpn_client_template,"r") + s = string.Template(configfile.read()) + configfile.close() + config = s.substitute(keyfile=FLAGS.credential_key_file, + certfile=FLAGS.credential_cert_file, + ip=network_data.ip, + port=network_data.port) + zippy.writestr(FLAGS.credential_vpn_file, config) + else: + logging.warn("No vpn data for project %s" % + pid) + zippy.writestr(FLAGS.ca_file, crypto.fetch_ca(user.id)) zippy.close() with open(zf, 'rb') as f: @@ -690,6 +694,15 @@ class AuthManager(object): shutil.rmtree(tmpdir) return buffer + def get_environment_rc(self, user, project=None): + """Get credential zip for user in project""" + if not isinstance(user, User): + user = self.get_user(user) + if project is None: + project = user.id + pid = Project.safe_id(project) + return self.__generate_rc(user.access, user.secret, pid) + def __generate_rc(self, access, secret, pid): """Generate rc file for user""" rc = open(FLAGS.credentials_template).read() -- cgit From 024ad9951dcf33f5a3468e9a790f1636770b2837 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 5 Aug 2010 12:29:50 -0700 Subject: rename networkdata to vpn --- nova/auth/manager.py | 6 +-- nova/network/networkdata.py | 116 ----------------------------------------- nova/network/service.py | 4 +- nova/network/vpn.py | 116 +++++++++++++++++++++++++++++++++++++++++ nova/tests/network_unittest.py | 10 ++-- 5 files changed, 126 insertions(+), 126 deletions(-) delete mode 100644 nova/network/networkdata.py create mode 100644 nova/network/vpn.py diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 312b569aa..cf920d607 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -37,7 +37,7 @@ from nova import objectstore # for flags from nova import utils from nova.auth import ldapdriver # for flags from nova.auth import signer -from nova.network import networkdata +from nova.network import vpn FLAGS = flags.FLAGS @@ -531,7 +531,7 @@ class AuthManager(object): @return: A tuple containing (ip, port) or None, None if vpn has not been allocated for user. """ - network_data = networkdata.NetworkData.lookup(Project.safe_id(project)) + network_data = vpn.NetworkData.lookup(Project.safe_id(project)) if not network_data: raise exception.NotFound('project network data has not been set') return (network_data.ip, network_data.port) @@ -672,7 +672,7 @@ class AuthManager(object): zippy.writestr(FLAGS.credential_key_file, private_key) zippy.writestr(FLAGS.credential_cert_file, signed_cert) - network_data = networkdata.NetworkData.lookup(pid) + network_data = vpn.NetworkData.lookup(pid) if network_data: configfile = open(FLAGS.vpn_client_template,"r") s = string.Template(configfile.read()) diff --git a/nova/network/networkdata.py b/nova/network/networkdata.py deleted file mode 100644 index cec84287c..000000000 --- a/nova/network/networkdata.py +++ /dev/null @@ -1,116 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# 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. - -"""Network Data for projects""" - -from nova import datastore -from nova import exception -from nova import flags -from nova import utils - -FLAGS = flags.FLAGS - - -flags.DEFINE_string('vpn_ip', utils.get_my_ip(), - 'Public IP for the cloudpipe VPN servers') -flags.DEFINE_integer('vpn_start_port', 1000, - 'Start port for the cloudpipe VPN servers') -flags.DEFINE_integer('vpn_end_port', 2000, - 'End port for the cloudpipe VPN servers') - -class NoMorePorts(exception.Error): - pass - - -class NetworkData(datastore.BasicModel): - """Manages network host, and vpn ip and port for projects""" - def __init__(self, project_id): - self.project_id = project_id - super(NetworkData, self).__init__() - - @property - def identifier(self): - """Identifier used for key in redis""" - return self.project_id - - @classmethod - def create(cls, project_id): - """Creates a vpn for project - - This method finds a free ip and port and stores the associated - values in the datastore. - """ - # TODO(vish): will we ever need multiiple ips per host? - port = cls.find_free_port_for_ip(FLAGS.vpn_ip) - network_data = cls(project_id) - # save ip for project - network_data['host'] = FLAGS.node_name - network_data['project'] = project_id - network_data['ip'] = FLAGS.vpn_ip - network_data['port'] = port - network_data.save() - return network_data - - @classmethod - def find_free_port_for_ip(cls, ip): - """Finds a free port for a given ip from the redis set""" - # TODO(vish): these redis commands should be generalized and - # placed into a base class. Conceptually, it is - # similar to an association, but we are just - # storing a set of values instead of keys that - # should be turned into objects. - redis = datastore.Redis.instance() - key = 'ip:%s:ports' % ip - # 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 = redis.spop(key) - if not port: - raise NoMorePorts() - return port - - @classmethod - def num_ports_for_ip(cls, ip): - """Calculates the number of free ports for a given ip""" - return datastore.Redis.instance().scard('ip:%s:ports' % ip) - - @property - def ip(self): - """The ip assigned to the project""" - return self['ip'] - - @property - def port(self): - """The port assigned to the project""" - return int(self['port']) - - def save(self): - """Saves the association to the given ip""" - self.associate_with('ip', self.ip) - super(NetworkData, self).save() - - def destroy(self): - """Cleans up datastore and adds port back to pool""" - self.unassociate_with('ip', self.ip) - datastore.Redis.instance().sadd('ip:%s:ports' % self.ip, self.port) - super(NetworkData, self).destroy() - diff --git a/nova/network/service.py b/nova/network/service.py index afc20c0d5..1a61f49d4 100644 --- a/nova/network/service.py +++ b/nova/network/service.py @@ -28,7 +28,7 @@ from nova.auth import manager from nova.exception import NotFound from nova.network import exception from nova.network import model -from nova.network import networkdata +from nova.network import vpn FLAGS = flags.FLAGS @@ -215,7 +215,7 @@ class VlanNetworkService(BaseNetworkService): def _on_set_network_host(self, user_id, project_id, *args, **kwargs): """Called when this host becomes the host for a project""" - networkdata.NetworkData.create(project_id) + vpn.NetworkData.create(project_id) @classmethod def setup_compute_network(self, user_id, project_id, security_group, diff --git a/nova/network/vpn.py b/nova/network/vpn.py new file mode 100644 index 000000000..cec84287c --- /dev/null +++ b/nova/network/vpn.py @@ -0,0 +1,116 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# 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. + +"""Network Data for projects""" + +from nova import datastore +from nova import exception +from nova import flags +from nova import utils + +FLAGS = flags.FLAGS + + +flags.DEFINE_string('vpn_ip', utils.get_my_ip(), + 'Public IP for the cloudpipe VPN servers') +flags.DEFINE_integer('vpn_start_port', 1000, + 'Start port for the cloudpipe VPN servers') +flags.DEFINE_integer('vpn_end_port', 2000, + 'End port for the cloudpipe VPN servers') + +class NoMorePorts(exception.Error): + pass + + +class NetworkData(datastore.BasicModel): + """Manages network host, and vpn ip and port for projects""" + def __init__(self, project_id): + self.project_id = project_id + super(NetworkData, self).__init__() + + @property + def identifier(self): + """Identifier used for key in redis""" + return self.project_id + + @classmethod + def create(cls, project_id): + """Creates a vpn for project + + This method finds a free ip and port and stores the associated + values in the datastore. + """ + # TODO(vish): will we ever need multiiple ips per host? + port = cls.find_free_port_for_ip(FLAGS.vpn_ip) + network_data = cls(project_id) + # save ip for project + network_data['host'] = FLAGS.node_name + network_data['project'] = project_id + network_data['ip'] = FLAGS.vpn_ip + network_data['port'] = port + network_data.save() + return network_data + + @classmethod + def find_free_port_for_ip(cls, ip): + """Finds a free port for a given ip from the redis set""" + # TODO(vish): these redis commands should be generalized and + # placed into a base class. Conceptually, it is + # similar to an association, but we are just + # storing a set of values instead of keys that + # should be turned into objects. + redis = datastore.Redis.instance() + key = 'ip:%s:ports' % ip + # 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 = redis.spop(key) + if not port: + raise NoMorePorts() + return port + + @classmethod + def num_ports_for_ip(cls, ip): + """Calculates the number of free ports for a given ip""" + return datastore.Redis.instance().scard('ip:%s:ports' % ip) + + @property + def ip(self): + """The ip assigned to the project""" + return self['ip'] + + @property + def port(self): + """The port assigned to the project""" + return int(self['port']) + + def save(self): + """Saves the association to the given ip""" + self.associate_with('ip', self.ip) + super(NetworkData, self).save() + + def destroy(self): + """Cleans up datastore and adds port back to pool""" + self.unassociate_with('ip', self.ip) + datastore.Redis.instance().sadd('ip:%s:ports' % self.ip, self.port) + super(NetworkData, self).destroy() + diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index 49147d4ec..c7d3e86f0 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -25,8 +25,8 @@ from nova import test from nova import utils from nova.auth import manager from nova.network import model -from nova.network import networkdata from nova.network import service +from nova.network import vpn from nova.network.exception import NoMoreAddresses FLAGS = flags.FLAGS @@ -156,16 +156,16 @@ class NetworkTestCase(test.TrialTestCase): self.dnsmasq.release_ip(mac, firstaddress, hostname, net.bridge_name) def test_212_vpn_ip_and_port_looks_valid(self): - networkdata.NetworkData.create(self.projects[0].id) + vpn.NetworkData.create(self.projects[0].id) self.assert_(self.projects[0].vpn_ip) self.assert_(self.projects[0].vpn_port >= FLAGS.vpn_start_port) self.assert_(self.projects[0].vpn_port <= FLAGS.vpn_end_port) def test_too_many_vpns(self): vpns = [] - for i in xrange(networkdata.NetworkData.num_ports_for_ip(FLAGS.vpn_ip)): - vpns.append(networkdata.NetworkData.create("vpnuser%s" % i)) - self.assertRaises(networkdata.NoMorePorts, networkdata.NetworkData.create, "boom") + for i in xrange(vpn.NetworkData.num_ports_for_ip(FLAGS.vpn_ip)): + vpns.append(vpn.NetworkData.create("vpnuser%s" % i)) + self.assertRaises(vpn.NoMorePorts, vpn.NetworkData.create, "boom") for vpn in vpns: vpn.destroy() -- cgit From 778e8152751ebdbb2adad544cc705691395d335d Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Thu, 5 Aug 2010 16:20:26 -0700 Subject: Fixed image modification authorization, API cleanup --- nova/endpoint/cloud.py | 2 ++ nova/objectstore/handler.py | 3 ++- nova/objectstore/image.py | 8 ++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 0ee278f84..cc6216fec 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -677,6 +677,8 @@ class CloudController(object): # TODO(devcamcar): Support users and groups other than 'all'. if attribute != 'launchPermission': raise exception.ApiError('attribute not supported: %s' % attribute) + if not 'user_group' in kwargs: + raise exception.ApiError('user or group not specified') if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all': raise exception.ApiError('only group "all" is supported') if not operation_type in ['add', 'remove']: diff --git a/nova/objectstore/handler.py b/nova/objectstore/handler.py index b4d7e6179..f625a2aa1 100644 --- a/nova/objectstore/handler.py +++ b/nova/objectstore/handler.py @@ -266,7 +266,8 @@ class ImagesResource(Resource): """ 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(request.context)] + images = [i for i in image.Image.all() \ + if i.is_authorized(request.context, readonly=True)] request.write(json.dumps([i.metadata for i in images])) request.finish() diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py index bea2e9637..860298ba6 100644 --- a/nova/objectstore/image.py +++ b/nova/objectstore/image.py @@ -65,9 +65,13 @@ class Image(object): except: pass - def is_authorized(self, context): + def is_authorized(self, context, readonly=False): + # NOTE(devcamcar): Public images can be read by anyone, + # but only modified by admin or owner. try: - return self.metadata['isPublic'] or context.user.is_admin() or self.metadata['imageOwnerId'] == context.project.id + return (self.metadata['isPublic'] and readonly) or \ + context.user.is_admin() or \ + self.metadata['imageOwnerId'] == context.project.id except: return False -- cgit From 63708752366300b4267a9dfbf926f89f9df3f4df Mon Sep 17 00:00:00 2001 From: Chris Behrens Date: Thu, 5 Aug 2010 18:26:39 -0500 Subject: Fixes bug#614090 -- nova.virt.images._fetch_local_image being called with 4 args but only has 3 --- nova/virt/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/images.py b/nova/virt/images.py index 872eb6d6a..48a87b514 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -65,7 +65,7 @@ def _fetch_s3_image(image, path, user, project): cmd += ['-o', path] return process.SharedPool().execute(executable=cmd[0], args=cmd[1:]) -def _fetch_local_image(image, path, _): +def _fetch_local_image(image, path, user, project): source = _image_path('%s/image' % image) return process.simple_execute('cp %s %s' % (source, path)) -- cgit From 5cda99300a437feefac39131bb714e9f85d765ce Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 5 Aug 2010 16:56:23 -0700 Subject: Made group membership check only search group instead of subtree. Roles in a group are removed when a user is removed from that group. Added test --- nova/auth/fakeldap.py | 11 ++++++++--- nova/auth/ldapdriver.py | 25 +++++++++++++++++-------- nova/tests/auth_unittest.py | 10 +++++++++- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/nova/auth/fakeldap.py b/nova/auth/fakeldap.py index 4c32ed9d9..b420924af 100644 --- a/nova/auth/fakeldap.py +++ b/nova/auth/fakeldap.py @@ -28,6 +28,8 @@ import json from nova import datastore +SCOPE_BASE = 0 +SCOPE_ONELEVEL = 1 # not implemented SCOPE_SUBTREE = 2 MOD_ADD = 0 MOD_DELETE = 1 @@ -188,15 +190,18 @@ class FakeLDAP(object): Args: dn -- dn to search under - scope -- only SCOPE_SUBTREE is supported + scope -- only SCOPE_BASE and SCOPE_SUBTREE are supported query -- query to filter objects by fields -- fields to return. Returns all fields if not specified """ - if scope != SCOPE_SUBTREE: + if scope != SCOPE_BASE and scope != SCOPE_SUBTREE: raise NotImplementedError(str(scope)) redis = datastore.Redis.instance() - keys = redis.keys("%s*%s" % (self.__redis_prefix, dn)) + if scope == SCOPE_BASE: + keys = ["%s%s" % (self.__redis_prefix, dn)] + else: + keys = redis.keys("%s*%s" % (self.__redis_prefix, dn)) objects = [] for key in keys: # get the attributes from redis diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 055e8332b..ec739e134 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -272,26 +272,30 @@ class LdapDriver(object): """Check if project exists""" return self.get_project(name) != None - def __find_object(self, dn, query = None): + def __find_object(self, dn, query=None, scope=None): """Find an object by dn and query""" - objects = self.__find_objects(dn, query) + objects = self.__find_objects(dn, query, scope) if len(objects) == 0: return None return objects[0] - def __find_dns(self, dn, query=None): + def __find_dns(self, dn, query=None, scope=None): """Find dns by query""" + if scope is None: # one of the flags is 0!! + scope = self.ldap.SCOPE_SUBTREE try: - res = self.conn.search_s(dn, self.ldap.SCOPE_SUBTREE, query) + res = self.conn.search_s(dn, scope, query) except self.ldap.NO_SUCH_OBJECT: return [] # just return the DNs return [dn for dn, attributes in res] - def __find_objects(self, dn, query = None): + def __find_objects(self, dn, query=None, scope=None): """Find objects by query""" + if scope is None: # one of the flags is 0!! + scope = self.ldap.SCOPE_SUBTREE try: - res = self.conn.search_s(dn, self.ldap.SCOPE_SUBTREE, query) + res = self.conn.search_s(dn, scope, query) except self.ldap.NO_SUCH_OBJECT: return [] # just return the attributes @@ -361,7 +365,8 @@ class LdapDriver(object): if not self.__group_exists(group_dn): return False res = self.__find_object(group_dn, - '(member=%s)' % self.__uid_to_dn(uid)) + '(member=%s)' % self.__uid_to_dn(uid), + self.ldap.SCOPE_BASE) return res != None def __add_to_group(self, uid, group_dn): @@ -391,7 +396,11 @@ class LdapDriver(object): if not self.__is_in_group(uid, group_dn): raise exception.NotFound("User %s is not a member of the group" % (uid,)) - self.__safe_remove_from_group(uid, group_dn) + # NOTE(vish): remove user from group and any sub_groups + sub_dns = self.__find_group_dns_with_member( + group_dn, uid) + for sub_dn in sub_dns: + self.__safe_remove_from_group(uid, sub_dn) def __safe_remove_from_group(self, uid, group_dn): """Remove user from group, deleting group if user is last member""" diff --git a/nova/tests/auth_unittest.py b/nova/tests/auth_unittest.py index 2167c2385..e00297cb1 100644 --- a/nova/tests/auth_unittest.py +++ b/nova/tests/auth_unittest.py @@ -135,10 +135,18 @@ class AuthTestCase(test.BaseTestCase): self.manager.add_to_project('test2', 'testproj') self.assertTrue(self.manager.get_project('testproj').has_member('test2')) - def test_208_can_remove_user_from_project(self): + def test_207_can_remove_user_from_project(self): self.manager.remove_from_project('test2', 'testproj') self.assertFalse(self.manager.get_project('testproj').has_member('test2')) + def test_208_can_remove_add_user_with_role(self): + self.manager.add_to_project('test2', 'testproj') + self.manager.add_role('test2', 'developer', 'testproj') + self.manager.remove_from_project('test2', 'testproj') + self.assertFalse(self.manager.has_role('test2', 'developer', 'testproj')) + self.manager.add_to_project('test2', 'testproj') + self.manager.remove_from_project('test2', 'testproj') + def test_209_can_generate_x509(self): # MUST HAVE RUN CLOUD SETUP BY NOW self.cloud = cloud.CloudController() -- cgit From 66c8abfb9f00ea06517e102f02ef8bdc9469aae8 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 6 Aug 2010 14:31:03 -0700 Subject: fix search/replace error --- nova/tests/network_unittest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index c7d3e86f0..879ee02a4 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -166,8 +166,8 @@ class NetworkTestCase(test.TrialTestCase): for i in xrange(vpn.NetworkData.num_ports_for_ip(FLAGS.vpn_ip)): vpns.append(vpn.NetworkData.create("vpnuser%s" % i)) self.assertRaises(vpn.NoMorePorts, vpn.NetworkData.create, "boom") - for vpn in vpns: - vpn.destroy() + for network_datum in vpns: + network_datum.destroy() def test_release_before_deallocate(self): pass -- cgit