Merge of DHCP changes including dnsmasq callbacks

Conflicts:
	nova/utils.py
This commit is contained in:
Vishvananda Ishaya
2010-07-14 16:27:18 -05:00
8 changed files with 254 additions and 49 deletions
+92
View File
@@ -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())
+1 -1
View File
@@ -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
+9 -3
View File
@@ -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
+29 -4
View File
@@ -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 """
+8
View File
@@ -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
+1 -1
View File
@@ -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
+107 -34
View File
@@ -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
+7 -6
View File
@@ -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)