summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@gmail.com>2010-07-14 16:27:18 -0500
committerVishvananda Ishaya <vishvananda@gmail.com>2010-07-14 16:27:18 -0500
commit56e601cb758347262fb2dcc3e980fe50dfeeb9f2 (patch)
tree95ea80459d8ad358fba5d0058816b6ee81e3f720
parent3326f48e9871dc9e19b516e7541a70e6aa329e74 (diff)
parentfaada0612d8e8580a2a932626c8972b7c2a4ef59 (diff)
downloadnova-56e601cb758347262fb2dcc3e980fe50dfeeb9f2.tar.gz
nova-56e601cb758347262fb2dcc3e980fe50dfeeb9f2.tar.xz
nova-56e601cb758347262fb2dcc3e980fe50dfeeb9f2.zip
Merge of DHCP changes including dnsmasq callbacks
Conflicts: nova/utils.py
-rwxr-xr-xbin/dhcpleasor.py92
-rw-r--r--docs/conf.py2
-rw-r--r--nova/compute/linux_net.py12
-rw-r--r--nova/compute/network.py33
-rw-r--r--nova/endpoint/cloud.py8
-rw-r--r--nova/tests/fake_flags.py2
-rw-r--r--nova/tests/network_unittest.py141
-rw-r--r--nova/utils.py13
8 files changed, 254 insertions, 49 deletions
diff --git a/bin/dhcpleasor.py b/bin/dhcpleasor.py
new file mode 100755
index 000000000..30f8fbdc3
--- /dev/null
+++ b/bin/dhcpleasor.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+# 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.
+#
+# Copyright 2010 Anso Labs, LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+dhcpleasor.py
+
+Handle lease database updates from DHCP servers.
+"""
+
+import sys
+import os
+import logging
+sys.path.append(os.path.abspath(os.path.join(__file__, "../../")))
+
+logging.debug(sys.path)
+import getopt
+from os import environ
+from nova.compute import linux_net
+from nova.compute import network
+from nova import rpc
+
+from nova import flags
+FLAGS = flags.FLAGS
+
+
+def add_lease(mac, ip, hostname, interface):
+ if FLAGS.fake_rabbit:
+ network.lease_ip(ip)
+ else:
+ rpc.cast(FLAGS.cloud_topic, {"method": "lease_ip",
+ "args" : {"address": 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)
+ else:
+ rpc.cast(FLAGS.cloud_topic, {"method": "release_ip",
+ "args" : {"address": ip}})
+
+def init_leases(interface):
+ net = network.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])
+ return res
+
+
+def main(argv=None):
+ if argv is None:
+ argv = sys.argv
+ interface = environ.get('DNSMASQ_INTERFACE', 'br0')
+ if int(environ.get('TESTING', '0')):
+ FLAGS.fake_rabbit = True
+ FLAGS.redis_db = 8
+ FLAGS.network_size = 32
+ FLAGS.fake_libvirt=True
+ FLAGS.fake_network=True
+ FLAGS.fake_users = True
+ action = argv[1]
+ if action in ['add','del','old']:
+ mac = argv[2]
+ ip = argv[3]
+ hostname = argv[4]
+ logging.debug("Called %s for mac %s with ip %s and hostname %s on interface %s" % (action, mac, ip, hostname, interface))
+ globals()[action+'_lease'](mac, ip, hostname, interface)
+ else:
+ print init_leases(interface)
+ exit(0)
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/docs/conf.py b/docs/conf.py
index 9dfdfc8be..bc61f438c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -16,7 +16,7 @@ import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.append(os.path.abspath('.'))
+sys.path.append(os.path.abspath('/Users/jmckenty/Projects/cc'))
sys.path.append([os.path.abspath('../nova'),os.path.abspath('../'),os.path.abspath('../vendor')])
from nova import vendor
diff --git a/nova/compute/linux_net.py b/nova/compute/linux_net.py
index 0bd5ce007..c9e5bb1a7 100644
--- a/nova/compute/linux_net.py
+++ b/nova/compute/linux_net.py
@@ -62,6 +62,9 @@ def remove_rule(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 """
@@ -95,10 +98,10 @@ def dnsmasq_cmd(net):
' --pid-file=%s' % dhcp_file(net['vlan'], 'pid'),
' --listen-address=%s' % net.dhcp_listen_address,
' --except-interface=lo',
- ' --dhcp-range=%s,static,120s' % (net.dhcp_range_start),
- ' --dhcp-lease-max=61',
+ ' --dhcp-range=%s,static,600s' % (net.dhcp_range_start),
' --dhcp-hostsfile=%s' % dhcp_file(net['vlan'], 'conf'),
- ' --dhcp-leasefile=%s' % dhcp_file(net['vlan'], 'leases')]
+ ' --dhcp-script=%s' % bin_file('dhcpleasor.py'),
+ ' --leasefile-ro']
return ''.join(cmd)
def hostDHCP(network, host, mac):
@@ -154,6 +157,9 @@ def dhcp_file(vlan, kind):
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
diff --git a/nova/compute/network.py b/nova/compute/network.py
index ca7abc1af..631f226d1 100644
--- a/nova/compute/network.py
+++ b/nova/compute/network.py
@@ -176,14 +176,21 @@ class BaseNetwork(datastore.RedisModel):
self._add_host(user_id, project_id, address, mac)
self.express(address=address)
return address
- raise exception.NoMoreAddresses()
+ raise exception.NoMoreAddresses("Project %s with network %s" % (project_id, str(self.network)))
- def deallocate_ip(self, ip_str):
+ 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
@@ -213,7 +220,7 @@ class BridgedNetwork(BaseNetwork):
def get_network_for_project(cls, user_id, project_id, security_group):
vlan = get_vlan_for_project(project_id)
network_str = get_subnet_from_vlan(vlan)
- logging.debug("creating network on vlan %s with network string %s" % (vlan, network_str))
+ # logging.debug("creating network on vlan %s with network string %s" % (vlan, network_str))
return cls.create(user_id, project_id, security_group, vlan, network_str)
def __init__(self, *args, **kwargs):
@@ -237,7 +244,7 @@ class DHCPNetwork(BridgedNetwork):
def __init__(self, *args, **kwargs):
super(DHCPNetwork, self).__init__(*args, **kwargs)
- logging.debug("Initing DHCPNetwork object...")
+ # 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)]
@@ -388,6 +395,7 @@ class PublicNetworkController(BaseNetwork):
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"
@@ -430,11 +438,22 @@ def get_vlan_for_project(project_id):
return vlan
raise exception.AddressNotAllocated("Out of VLANs")
+def get_project_id_for_vlan(vlan):
+ assigned_vlans = get_assigned_vlans()
+ for project_id, project_vlan in assigned_vlans.iteritems():
+ if vlan == project_vlan:
+ return project_id
+
+def get_network_by_interface(iface, security_group='default'):
+ vlan = iface.rpartition("br")[2]
+ return get_project_network(get_project_id_for_vlan(vlan), security_group)
def get_network_by_address(address):
+ logging.debug("Get Network By Address: %s" % address)
for project in users.UserManager.instance().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()
@@ -460,6 +479,12 @@ def 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 """
diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py
index f17f4fcdb..9dccc24dc 100644
--- a/nova/endpoint/cloud.py
+++ b/nova/endpoint/cloud.py
@@ -500,6 +500,14 @@ class CloudController(object):
# TODO - Strip the IP from the instance
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."]})
+
@rbac.allow('projectmanager', 'sysadmin')
def run_instances(self, context, **kwargs):
# make sure user can access the image
diff --git a/nova/tests/fake_flags.py b/nova/tests/fake_flags.py
index d40172cfc..3f5d9ff54 100644
--- a/nova/tests/fake_flags.py
+++ b/nova/tests/fake_flags.py
@@ -28,5 +28,5 @@ FLAGS.fake_rabbit = True
FLAGS.fake_network = True
FLAGS.fake_users = True
#FLAGS.keeper_backend = 'sqlite'
-FLAGS.datastore_path = ':memory:'
+# FLAGS.datastore_path = ':memory:'
FLAGS.verbose = True
diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py
index f215c0b3f..bccaacfa7 100644
--- a/nova/tests/network_unittest.py
+++ b/nova/tests/network_unittest.py
@@ -18,6 +18,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import os
import logging
import unittest
@@ -26,6 +27,8 @@ import IPy
from nova import flags
from nova import test
+from nova import exception
+from nova.compute.exception import NoMoreAddresses
from nova.compute import network
from nova.auth import users
from nova import utils
@@ -40,6 +43,7 @@ class NetworkTestCase(test.TrialTestCase):
network_size=32)
logging.getLogger().setLevel(logging.DEBUG)
self.manager = users.UserManager.instance()
+ self.dnsmasq = FakeDNSMasq()
try:
self.manager.create_user('netuser', 'netuser', 'netuser')
except: pass
@@ -66,59 +70,128 @@ class NetworkTestCase(test.TrialTestCase):
address = network.allocate_ip(
"netuser", "project0", utils.generate_mac())
logging.debug("Was allocated %s" % (address))
- self.assertEqual(True, address in self._get_project_addresses("project0"))
+ net = network.get_project_network("project0", "default")
+ self.assertEqual(True, is_in_project(address, "project0"))
+ mac = utils.generate_mac()
+ hostname = "test-host"
+ self.dnsmasq.issue_ip(mac, address, hostname, net.bridge_name)
rv = network.deallocate_ip(address)
- self.assertEqual(False, address in self._get_project_addresses("project0"))
+
+ # Doesn't go away until it's dhcp released
+ self.assertEqual(True, is_in_project(address, "project0"))
+
+ self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name)
+ self.assertEqual(False, is_in_project(address, "project0"))
def test_range_allocation(self):
+ mac = utils.generate_mac()
+ secondmac = utils.generate_mac()
+ hostname = "test-host"
address = network.allocate_ip(
- "netuser", "project0", utils.generate_mac())
+ "netuser", "project0", mac)
secondaddress = network.allocate_ip(
- "netuser", "project1", utils.generate_mac())
- self.assertEqual(True,
- address in self._get_project_addresses("project0"))
- self.assertEqual(True,
- secondaddress in self._get_project_addresses("project1"))
- self.assertEqual(False, address in self._get_project_addresses("project1"))
+ "netuser", "project1", secondmac)
+ net = network.get_project_network("project0", "default")
+ secondnet = network.get_project_network("project1", "default")
+
+ self.assertEqual(True, is_in_project(address, "project0"))
+ self.assertEqual(True, is_in_project(secondaddress, "project1"))
+ self.assertEqual(False, is_in_project(address, "project1"))
+
+ # Addresses are allocated before they're issued
+ self.dnsmasq.issue_ip(mac, address, hostname, net.bridge_name)
+ self.dnsmasq.issue_ip(secondmac, secondaddress,
+ hostname, secondnet.bridge_name)
+
rv = network.deallocate_ip(address)
- self.assertEqual(False, address in self._get_project_addresses("project0"))
+ self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name)
+ self.assertEqual(False, is_in_project(address, "project0"))
+
+ # First address release shouldn't affect the second
+ self.assertEqual(True, is_in_project(secondaddress, "project1"))
+
rv = network.deallocate_ip(secondaddress)
- self.assertEqual(False,
- secondaddress in self._get_project_addresses("project1"))
+ self.dnsmasq.release_ip(secondmac, secondaddress,
+ hostname, secondnet.bridge_name)
+ self.assertEqual(False, is_in_project(secondaddress, "project1"))
def test_subnet_edge(self):
secondaddress = network.allocate_ip("netuser", "project0",
utils.generate_mac())
+ hostname = "toomany-hosts"
for project in range(1,5):
project_id = "project%s" % (project)
+ mac = utils.generate_mac()
+ mac2 = utils.generate_mac()
+ mac3 = utils.generate_mac()
address = network.allocate_ip(
- "netuser", project_id, utils.generate_mac())
+ "netuser", project_id, mac)
address2 = network.allocate_ip(
- "netuser", project_id, utils.generate_mac())
+ "netuser", project_id, mac2)
address3 = network.allocate_ip(
- "netuser", project_id, utils.generate_mac())
- self.assertEqual(False,
- address in self._get_project_addresses("project0"))
- self.assertEqual(False,
- address2 in self._get_project_addresses("project0"))
- self.assertEqual(False,
- address3 in self._get_project_addresses("project0"))
+ "netuser", project_id, mac3)
+ self.assertEqual(False, is_in_project(address, "project0"))
+ self.assertEqual(False, is_in_project(address2, "project0"))
+ self.assertEqual(False, is_in_project(address3, "project0"))
rv = network.deallocate_ip(address)
rv = network.deallocate_ip(address2)
rv = network.deallocate_ip(address3)
+ net = network.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("project0", "default")
rv = network.deallocate_ip(secondaddress)
+ self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name)
- def test_too_many_projects(self):
- for i in range(0, 30):
- name = 'toomany-project%s' % i
- self.manager.create_project(name, 'netuser', name)
- address = network.allocate_ip(
- "netuser", name, utils.generate_mac())
- rv = network.deallocate_ip(address)
- self.manager.delete_project(name)
+ def test_release_before_deallocate(self):
+ pass
+
+ def test_deallocate_before_issued(self):
+ pass
+
+ def test_too_many_addresses(self):
+ """
+ Network size is 32, there are 5 addresses reserved for VPN.
+ So we should get 23 usable addresses
+ """
+ net = network.get_project_network("project0", "default")
+ hostname = "toomany-hosts"
+ macs = {}
+ addresses = {}
+ for i in range(0, 22):
+ macs[i] = utils.generate_mac()
+ addresses[i] = network.allocate_ip("netuser", "project0", macs[i])
+ self.dnsmasq.issue_ip(macs[i], addresses[i], hostname, net.bridge_name)
+
+ self.assertRaises(NoMoreAddresses, network.allocate_ip, "netuser", "project0", utils.generate_mac())
+
+ for i in range(0, 22):
+ rv = network.deallocate_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()
+
+def _get_project_addresses(project_id):
+ project_addresses = []
+ for addr in network.get_project_network(project_id).list_addresses():
+ project_addresses.append(addr)
+ return project_addresses
+
+def binpath(script):
+ return os.path.abspath(os.path.join(__file__, "../../../bin", script))
+
+class FakeDNSMasq(object):
+ def issue_ip(self, mac, ip, hostname, interface):
+ cmd = "%s add %s %s %s" % (binpath('dhcpleasor.py'), mac, ip, hostname)
+ env = {'DNSMASQ_INTERFACE': interface, 'TESTING' : '1'}
+ (out, err) = utils.execute(cmd, addl_env=env)
+ logging.debug("ISSUE_IP: %s, %s " % (out, err))
+
+ def release_ip(self, mac, ip, hostname, interface):
+ cmd = "%s del %s %s %s" % (binpath('dhcpleasor.py'), mac, ip, hostname)
+ env = {'DNSMASQ_INTERFACE': interface, 'TESTING' : '1'}
+ (out, err) = utils.execute(cmd, addl_env=env)
+ logging.debug("RELEASE_IP: %s, %s " % (out, err))
- def _get_project_addresses(self, project_id):
- project_addresses = []
- for addr in network.get_project_network(project_id).list_addresses():
- project_addresses.append(addr)
- return project_addresses
diff --git a/nova/utils.py b/nova/utils.py
index d6d958b3c..2982b5480 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -24,10 +24,10 @@ System-level utilities and helper functions.
import inspect
import logging
-import os.path
+import os
import random
-import socket
import subprocess
+import socket
import sys
from datetime import datetime
@@ -47,11 +47,12 @@ def fetchfile(url, target):
# fp.close()
execute("curl %s -o %s" % (url, target))
-
-def execute(cmd, input=None):
- #logging.debug("Running %s" % (cmd))
+def execute(cmd, input=None, addl_env=None):
+ env = os.environ.copy()
+ if addl_env:
+ env.update(addl_env)
obj = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
result = None
if input != None:
result = obj.communicate(input)