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
This commit is contained in:
Vishvananda Ishaya
2010-08-03 14:31:47 -07:00
parent c02dc69e72
commit e163a8475d
4 changed files with 146 additions and 115 deletions

View File

@@ -35,32 +35,34 @@ sys.path.append(os.path.abspath(os.path.join(__file__, "../../")))
from nova import flags from nova import flags
from nova import rpc from nova import rpc
from nova import utils from nova import utils
from nova.compute import linux_net from nova.network import linux_net
from nova.compute import network from nova.network import model
from nova.network import service
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
def add_lease(mac, ip, hostname, interface): def add_lease(mac, ip, hostname, interface):
if FLAGS.fake_rabbit: if FLAGS.fake_rabbit:
network.lease_ip(ip) service.VlanNetworkService().lease_ip(ip)
else: else:
rpc.cast(FLAGS.cloud_topic, {"method": "lease_ip", rpc.cast("%s.%s" (FLAGS.network_topic, FLAGS.node_name),
"args" : {"address": ip}}) {"method": "lease_ip",
"args" : {"fixed_ip": ip}})
def old_lease(mac, ip, hostname, interface): def old_lease(mac, ip, hostname, interface):
logging.debug("Adopted old lease or got a change of mac/hostname") logging.debug("Adopted old lease or got a change of mac/hostname")
def del_lease(mac, ip, hostname, interface): def del_lease(mac, ip, hostname, interface):
if FLAGS.fake_rabbit: if FLAGS.fake_rabbit:
network.release_ip(ip) service.VlanNetworkService().release_ip(ip)
else: else:
rpc.cast(FLAGS.cloud_topic, {"method": "release_ip", rpc.cast("%s.%s" (FLAGS.network_topic, FLAGS.node_name),
"args" : {"address": ip}}) {"method": "release_ip",
"args" : {"fixed_ip": ip}})
def init_leases(interface): def init_leases(interface):
net = network.get_network_by_interface(interface) net = model.get_network_by_interface(interface)
res = "" res = ""
for host_name in net.hosts: for host_name in net.hosts:
res += "%s\n" % linux_net.hostDHCP(net, host_name, net.hosts[host_name]) res += "%s\n" % linux_net.hostDHCP(net, host_name, net.hosts[host_name])

View File

@@ -21,12 +21,19 @@
Twistd daemon for the nova network nodes. Twistd daemon for the nova network nodes.
""" """
from nova import flags
from nova import twistd 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__': if __name__ == '__main__':
twistd.serve(__file__) twistd.serve(__file__)
if __name__ == '__builtin__': if __name__ == '__builtin__':
application = service.NetworkService.create() application = utils.import_class(FLAGS.network_service).create()

View File

@@ -36,11 +36,9 @@ from nova import utils
from nova.auth import rbac from nova.auth import rbac
from nova.auth import manager from nova.auth import manager
from nova.compute import model from nova.compute import model
from nova.compute import network
from nova.compute.instance_types import INSTANCE_TYPES from nova.compute.instance_types import INSTANCE_TYPES
from nova.compute import service as compute_service
from nova.endpoint import images from nova.endpoint import images
from nova.volume import service as volume_service from nova.volume import service
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@@ -64,7 +62,6 @@ class CloudController(object):
""" """
def __init__(self): def __init__(self):
self.instdir = model.InstanceDirectory() self.instdir = model.InstanceDirectory()
self.network = network.PublicNetworkController()
self.setup() self.setup()
@property @property
@@ -76,7 +73,7 @@ class CloudController(object):
def volumes(self): def volumes(self):
""" returns a list of all volumes """ """ returns a list of all volumes """
for volume_id in datastore.Redis.instance().smembers("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 yield volume
def __str__(self): def __str__(self):
@@ -222,7 +219,7 @@ class CloudController(object):
callback=_complete) callback=_complete)
return d return d
except users.UserError, e: except manager.UserError as e:
raise raise
@rbac.allow('all') @rbac.allow('all')
@@ -331,7 +328,7 @@ class CloudController(object):
raise exception.NotFound('Instance %s could not be found' % instance_id) raise exception.NotFound('Instance %s could not be found' % instance_id)
def _get_volume(self, context, volume_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: if context.user.is_admin() or volume['project_id'] == context.project.id:
return volume return volume
raise exception.NotFound('Volume %s could not be found' % volume_id) raise exception.NotFound('Volume %s could not be found' % volume_id)
@@ -472,29 +469,34 @@ class CloudController(object):
@rbac.allow('netadmin') @rbac.allow('netadmin')
def allocate_address(self, context, **kwargs): def allocate_address(self, context, **kwargs):
address = self.network.allocate_ip( alloc_result = rpc.call(self._get_network_host(context),
context.user.id, context.project.id, 'public') {"method": "allocate_elastic_ip"})
return defer.succeed({'addressSet': [{'publicIp' : address}]}) public_ip = alloc_result['result']
return defer.succeed({'addressSet': [{'publicIp' : public_ip}]})
@rbac.allow('netadmin') @rbac.allow('netadmin')
def release_address(self, context, public_ip, **kwargs): 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."]}) return defer.succeed({'releaseResponse': ["Address released."]})
@rbac.allow('netadmin') @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) instance = self._get_instance(context, instance_id)
self.network.associate_address( address = self._get_address(context, public_ip)
kwargs['public_ip'], rpc.cast(self._get_network_host(context),
instance['private_dns_name'], {"method": "associate_elastic_ip",
instance_id) "args": {"elastic_ip": address['public_ip'],
"fixed_ip": instance['private_dns_name'],
"instance_id": instance['instance_id']}})
return defer.succeed({'associateResponse': ["Address associated."]}) return defer.succeed({'associateResponse': ["Address associated."]})
@rbac.allow('netadmin') @rbac.allow('netadmin')
def disassociate_address(self, context, public_ip, **kwargs): def disassociate_address(self, context, public_ip, **kwargs):
address = self._get_address(context, public_ip) address = self._get_address(context, public_ip)
self.network.disassociate_address(public_ip) self.network.disassociate_address(public_ip)
# TODO - Strip the IP from the instance
return defer.succeed({'disassociateResponse': ["Address disassociated."]}) return defer.succeed({'disassociateResponse': ["Address disassociated."]})
def release_ip(self, context, private_ip, **kwargs): def release_ip(self, context, private_ip, **kwargs):
@@ -505,7 +507,13 @@ class CloudController(object):
self.network.lease_ip(private_ip) self.network.lease_ip(private_ip)
return defer.succeed({'leaseResponse': ["Address leased."]}) 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') @rbac.allow('projectmanager', 'sysadmin')
@defer.inlineCallbacks
def run_instances(self, context, **kwargs): def run_instances(self, context, **kwargs):
# make sure user can access the image # make sure user can access the image
# vpn image is private so it doesn't show up on lists # 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 key_data = key_pair.public_key
# TODO: Get the real security group of launch in here # TODO: Get the real security group of launch in here
security_group = "default" security_group = "default"
if FLAGS.simple_network: create_result = yield rpc.call(FLAGS.network_topic,
bridge_name = FLAGS.simple_network_bridge {"method": "create_network",
else: "args": {"user_id": context.user.id,
net = network.BridgedNetwork.get_network_for_project( "project_id": context.project.id,
context.user.id, context.project.id, security_group) "security_group": security_group}})
bridge_name = net['bridge_name'] bridge_name = create_result['result']
net_host = self._get_network_host(context)
for num in range(int(kwargs['max_count'])): 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 = 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['image_id'] = image_id
inst['kernel_id'] = kernel_id inst['kernel_id'] = kernel_id
inst['ramdisk_id'] = ramdisk_id inst['ramdisk_id'] = ramdisk_id
@@ -558,24 +577,9 @@ class CloudController(object):
inst['key_name'] = kwargs.get('key_name', '') inst['key_name'] = kwargs.get('key_name', '')
inst['user_id'] = context.user.id inst['user_id'] = context.user.id
inst['project_id'] = context.project.id inst['project_id'] = context.project.id
inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = num inst['ami_launch_index'] = num
inst['bridge_name'] = bridge_name 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() inst.save()
rpc.cast(FLAGS.compute_topic, rpc.cast(FLAGS.compute_topic,
{"method": "run_instance", {"method": "run_instance",
@@ -583,8 +587,7 @@ class CloudController(object):
logging.debug("Casting to node for %s's instance with IP of %s" % logging.debug("Casting to node for %s's instance with IP of %s" %
(context.user.name, inst['private_dns_name'])) (context.user.name, inst['private_dns_name']))
# TODO: Make Network figure out the network name from ip. # TODO: Make Network figure out the network name from ip.
return defer.succeed(self._format_instances( defer.returnValue(self._format_instances(context, reservation_id))
context, reservation_id))
@rbac.allow('projectmanager', 'sysadmin') @rbac.allow('projectmanager', 'sysadmin')
def terminate_instances(self, context, instance_id, **kwargs): def terminate_instances(self, context, instance_id, **kwargs):
@@ -594,26 +597,34 @@ class CloudController(object):
try: try:
instance = self._get_instance(context, i) instance = self._get_instance(context, i)
except exception.NotFound: except exception.NotFound:
logging.warning("Instance %s was not found during terminate" % i) logging.warning("Instance %s was not found during terminate"
% i)
continue continue
try: elastic_ip = instance.get('public_dns_name', None)
self.network.disassociate_address( if elastic_ip:
instance.get('public_dns_name', 'bork')) logging.debug("Deallocating address %s" % elastic_ip)
except: # NOTE(vish): Right now we don't really care if the ip is
pass # disassociated. We may need to worry about
if instance.get('private_dns_name', None): # checking this later. Perhaps in the scheduler?
logging.debug("Deallocating address %s" % instance.get('private_dns_name', None)) rpc.cast(self._get_network_host(context),
if FLAGS.simple_network: {"method": "disassociate_elastic_ip",
network.deallocate_simple_ip(instance.get('private_dns_name', None)) "args": {"elastic_ip": elastic_ip}})
else:
try: fixed_ip = instance.get('private_dns_name', None)
self.network.deallocate_ip(instance.get('private_dns_name', None)) if fixed_ip:
except Exception, _err: logging.debug("Deallocating address %s" % fixed_ip)
pass # NOTE(vish): Right now we don't really care if the ip is
if instance.get('node_name', 'unassigned') != 'unassigned': #It's also internal default # 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']), rpc.cast('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
{"method": "terminate_instance", {"method": "terminate_instance",
"args" : {"instance_id": i}}) "args": {"instance_id": i}})
else: else:
instance.destroy() instance.destroy()
return defer.succeed(True) return defer.succeed(True)

View File

@@ -24,8 +24,9 @@ from nova import flags
from nova import test from nova import test
from nova import utils from nova import utils
from nova.auth import manager from nova.auth import manager
from nova.compute import network from nova.network import model
from nova.compute.exception import NoMoreAddresses from nova.network import service
from nova.network.exception import NoMoreAddresses
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@@ -52,7 +53,8 @@ class NetworkTestCase(test.TrialTestCase):
self.projects.append(self.manager.create_project(name, self.projects.append(self.manager.create_project(name,
'netuser', 'netuser',
name)) name))
self.network = network.PublicNetworkController() self.network = model.PublicNetworkController()
self.service = service.VlanNetworkService()
def tearDown(self): def tearDown(self):
super(NetworkTestCase, self).tearDown() 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 pubnet)
self.assertTrue(IPy.IP(address) in self.network.network) self.assertTrue(IPy.IP(address) in self.network.network)
def test_allocate_deallocate_ip(self): def test_allocate_deallocate_fixed_ip(self):
address = network.allocate_ip( result = self.service.allocate_fixed_ip(
self.user.id, self.projects[0].id, utils.generate_mac()) self.user.id, self.projects[0].id)
address = result['ip']
mac = result['mac']
logging.debug("Was allocated %s" % (address)) 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)) self.assertEqual(True, is_in_project(address, self.projects[0].id))
mac = utils.generate_mac()
hostname = "test-host" hostname = "test-host"
self.dnsmasq.issue_ip(mac, address, hostname, net.bridge_name) 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 # Doesn't go away until it's dhcp released
self.assertEqual(True, is_in_project(address, self.projects[0].id)) 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)) self.assertEqual(False, is_in_project(address, self.projects[0].id))
def test_range_allocation(self): def test_range_allocation(self):
mac = utils.generate_mac()
secondmac = utils.generate_mac()
hostname = "test-host" hostname = "test-host"
address = network.allocate_ip( result = self.service.allocate_fixed_ip(
self.user.id, self.projects[0].id, mac) self.user.id, self.projects[0].id)
secondaddress = network.allocate_ip( mac = result['mac']
self.user, self.projects[1].id, secondmac) address = result['ip']
net = network.get_project_network(self.projects[0].id, "default") result = self.service.allocate_fixed_ip(
secondnet = network.get_project_network(self.projects[1].id, "default") 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(address, self.projects[0].id))
self.assertEqual(True, is_in_project(secondaddress, self.projects[1].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, self.dnsmasq.issue_ip(secondmac, secondaddress,
hostname, secondnet.bridge_name) 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.dnsmasq.release_ip(mac, address, hostname, net.bridge_name)
self.assertEqual(False, is_in_project(address, self.projects[0].id)) self.assertEqual(False, is_in_project(address, self.projects[0].id))
# First address release shouldn't affect the second # First address release shouldn't affect the second
self.assertEqual(True, is_in_project(secondaddress, self.projects[1].id)) 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, self.dnsmasq.release_ip(secondmac, secondaddress,
hostname, secondnet.bridge_name) hostname, secondnet.bridge_name)
self.assertEqual(False, is_in_project(secondaddress, self.projects[1].id)) self.assertEqual(False, is_in_project(secondaddress, self.projects[1].id))
def test_subnet_edge(self): def test_subnet_edge(self):
secondaddress = network.allocate_ip(self.user.id, self.projects[0].id, result = self.service.allocate_fixed_ip(self.user.id,
utils.generate_mac()) self.projects[0].id)
firstaddress = result['ip']
hostname = "toomany-hosts" hostname = "toomany-hosts"
for i in range(1,5): for i in range(1,5):
project_id = self.projects[i].id project_id = self.projects[i].id
mac = utils.generate_mac() result = self.service.allocate_fixed_ip(
mac2 = utils.generate_mac() self.user, project_id)
mac3 = utils.generate_mac() mac = result['mac']
address = network.allocate_ip( address = result['ip']
self.user, project_id, mac) result = self.service.allocate_fixed_ip(
address2 = network.allocate_ip( self.user, project_id)
self.user, project_id, mac2) mac2 = result['mac']
address3 = network.allocate_ip( address2 = result['ip']
self.user, project_id, mac3) 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(address, self.projects[0].id))
self.assertEqual(False, is_in_project(address2, 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)) self.assertEqual(False, is_in_project(address3, self.projects[0].id))
rv = network.deallocate_ip(address) rv = self.service.deallocate_fixed_ip(address)
rv = network.deallocate_ip(address2) rv = self.service.deallocate_fixed_ip(address2)
rv = network.deallocate_ip(address3) rv = self.service.deallocate_fixed_ip(address3)
net = network.get_project_network(project_id, "default") net = model.get_project_network(project_id, "default")
self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name) self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name)
self.dnsmasq.release_ip(mac2, address2, hostname, net.bridge_name) self.dnsmasq.release_ip(mac2, address2, hostname, net.bridge_name)
self.dnsmasq.release_ip(mac3, address3, hostname, net.bridge_name) self.dnsmasq.release_ip(mac3, address3, hostname, net.bridge_name)
net = network.get_project_network(self.projects[0].id, "default") net = model.get_project_network(self.projects[0].id, "default")
rv = network.deallocate_ip(secondaddress) rv = self.service.deallocate_fixed_ip(firstaddress)
self.dnsmasq.release_ip(mac, secondaddress, hostname, net.bridge_name) self.dnsmasq.release_ip(mac, firstaddress, hostname, net.bridge_name)
def test_release_before_deallocate(self): def test_release_before_deallocate(self):
pass pass
@@ -169,7 +179,7 @@ class NetworkTestCase(test.TrialTestCase):
NUM_RESERVED_VPN_IPS) NUM_RESERVED_VPN_IPS)
usable addresses 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 # Determine expected number of available IP addresses
num_static_ips = net.num_static_ips num_static_ips = net.num_static_ips
@@ -183,22 +193,23 @@ class NetworkTestCase(test.TrialTestCase):
macs = {} macs = {}
addresses = {} addresses = {}
for i in range(0, (num_available_ips - 1)): for i in range(0, (num_available_ips - 1)):
macs[i] = utils.generate_mac() result = self.service.allocate_fixed_ip(self.user.id, self.projects[0].id)
addresses[i] = network.allocate_ip(self.user.id, self.projects[0].id, macs[i]) macs[i] = result['mac']
addresses[i] = result['ip']
self.dnsmasq.issue_ip(macs[i], addresses[i], hostname, net.bridge_name) 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)): 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) self.dnsmasq.release_ip(macs[i], addresses[i], hostname, net.bridge_name)
def is_in_project(address, project_id): 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): def _get_project_addresses(project_id):
project_addresses = [] 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) project_addresses.append(addr)
return project_addresses return project_addresses