summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoshua McKenty <jmckenty@joshua-mckentys-macbook-pro.local>2010-07-07 12:06:34 -0700
committerVishvananda Ishaya <vishvananda@gmail.com>2010-07-07 12:06:34 -0700
commitb7ea2f70581f6acd927ea7b65adaffeeb4b8d2ba (patch)
tree6f00bf4a6207486478b0142f0297081ebcf85d8d
parent5e8337aec03f5a697c90779eb66a457aae4e7ae0 (diff)
downloadnova-b7ea2f70581f6acd927ea7b65adaffeeb4b8d2ba.tar.gz
nova-b7ea2f70581f6acd927ea7b65adaffeeb4b8d2ba.tar.xz
nova-b7ea2f70581f6acd927ea7b65adaffeeb4b8d2ba.zip
Capture signals from dnsmasq and use them to update network state.
-rwxr-xr-xbin/dhcpleasor.py68
-rw-r--r--nova/compute/linux_net.py9
-rw-r--r--nova/compute/network.py14
-rw-r--r--nova/tests/fake_flags.py2
-rw-r--r--nova/tests/network_unittest.py34
-rw-r--r--nova/utils.py10
6 files changed, 129 insertions, 8 deletions
diff --git a/bin/dhcpleasor.py b/bin/dhcpleasor.py
new file mode 100755
index 000000000..07e63884f
--- /dev/null
+++ b/bin/dhcpleasor.py
@@ -0,0 +1,68 @@
+#!/opt/local/bin/python
+
+# 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 network
+from nova import flags
+FLAGS = flags.FLAGS
+
+
+def add_lease(mac, ip, hostname, interface):
+ pass
+
+def old_lease(mac, ip, hostname, interface):
+ pass
+
+def del_lease(mac, ip, hostname, interface):
+ # TODO - get net from interface instead
+ net = network.get_network_by_address(ip)
+ net.release_ip(ip)
+
+def init_leases(interface):
+ return ""
+
+
+def main(argv=None):
+ if argv is None:
+ argv = sys.argv
+ interface = environ.get('DNSMASQ_INTERFACE', 'br0')
+ old_redis_db = FLAGS.redis_db
+ FLAGS.redis_db = int(environ.get('REDIS_DB', '0'))
+ 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)
+ FLAGS.redis_db = old_redis_db
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/nova/compute/linux_net.py b/nova/compute/linux_net.py
index 0bd5ce007..b44cf9437 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 """
@@ -98,7 +101,8 @@ def dnsmasq_cmd(net):
' --dhcp-range=%s,static,120s' % (net.dhcp_range_start),
' --dhcp-lease-max=61',
' --dhcp-hostsfile=%s' % dhcp_file(net['vlan'], 'conf'),
- ' --dhcp-leasefile=%s' % dhcp_file(net['vlan'], 'leases')]
+ ' --dhcp-leasefile=%s' % dhcp_file(net['vlan'], 'leases'),
+ ' ---dhcp-script=%s' % bin_file('dhcpleasor.py')]
return ''.join(cmd)
def hostDHCP(network, host, mac):
@@ -154,6 +158,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 911d0344a..7a347aa11 100644
--- a/nova/compute/network.py
+++ b/nova/compute/network.py
@@ -162,12 +162,16 @@ class BaseNetwork(datastore.RedisModel):
return address
raise exception.NoMoreAddresses()
- def deallocate_ip(self, 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
@@ -197,7 +201,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):
@@ -221,7 +225,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)]
@@ -372,6 +376,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"
@@ -416,8 +421,11 @@ def get_vlan_for_project(project_id):
def get_network_by_address(address):
+ logging.debug("Get Network By Address:")
for project in users.UserManager.instance().get_projects():
+ logging.debug(" looking at project %s", project.id)
net = get_project_network(project.id)
+ logging.debug(" is %s in %s ?" % (address, str(net.assigned)))
if address in net.assigned:
return net
raise exception.AddressNotAllocated()
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..4c9f340c1 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,7 @@ import IPy
from nova import flags
from nova import test
+from nova import exception
from nova.compute import network
from nova.auth import users
from nova import utils
@@ -40,6 +42,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
@@ -63,11 +66,23 @@ class NetworkTestCase(test.TrialTestCase):
self.assertTrue(IPy.IP(address) in self.network.network)
def test_allocate_deallocate_ip(self):
+ # Address should be allocated
+ # Then, simulate acquisition of the address
+ # Deallocate it, and wait for simulated ip release
+ # then confirm it's gone.
address = network.allocate_ip(
"netuser", "project0", utils.generate_mac())
logging.debug("Was allocated %s" % (address))
+ net = network.get_project_network("project0", "default")
self.assertEqual(True, address in self._get_project_addresses("project0"))
+ mac = utils.generate_mac()
+ hostname = "test-host"
+ self.dnsmasq.issue_ip(mac, address, hostname, net.bridge_name)
rv = network.deallocate_ip(address)
+ # Doesn't go away until it's dhcp released
+ self.assertEqual(True, address in self._get_project_addresses("project0"))
+
+ self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name)
self.assertEqual(False, address in self._get_project_addresses("project0"))
def test_range_allocation(self):
@@ -122,3 +137,22 @@ class NetworkTestCase(test.TrialTestCase):
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, 'REDIS_DB' : '8'}
+ (out, err) = utils.execute(cmd, addl_env=env)
+ logging.debug(out)
+ logging.debug(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, 'REDIS_DB' : '8'}
+ (out, err) = utils.execute(cmd, addl_env=env)
+ logging.debug(out)
+ logging.debug(err)
+ \ No newline at end of file
diff --git a/nova/utils.py b/nova/utils.py
index 325b062ee..cbfdd835d 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -26,6 +26,7 @@ import logging
import socket
import sys
import os.path
+from os import environ
import inspect
import subprocess
import random
@@ -46,11 +47,14 @@ def fetchfile(url, target):
# fp.close()
execute("curl %s -o %s" % (url, target))
-
-def execute(cmd, input=None):
+def execute(cmd, input=None, addl_env=None):
#logging.debug("Running %s" % (cmd))
+ env = os.environ.copy()
+ if addl_env:
+ env.update(addl_env)
+ logging.debug(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)