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
5 changed files with 209 additions and 36 deletions

92
bin/dhcpleasor.py Executable file
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())

View File

@@ -16,7 +16,7 @@ import sys, os
# If extensions (or modules to document with autodoc) are in another directory, # 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 # 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. # 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')]) sys.path.append([os.path.abspath('../nova'),os.path.abspath('../'),os.path.abspath('../vendor')])
from nova import vendor from nova import vendor

View File

@@ -500,6 +500,14 @@ class CloudController(object):
# TODO - Strip the IP from the instance # 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):
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') @rbac.allow('projectmanager', 'sysadmin')
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

View File

@@ -28,5 +28,5 @@ FLAGS.fake_rabbit = True
FLAGS.fake_network = True FLAGS.fake_network = True
FLAGS.fake_users = True FLAGS.fake_users = True
#FLAGS.keeper_backend = 'sqlite' #FLAGS.keeper_backend = 'sqlite'
FLAGS.datastore_path = ':memory:' # FLAGS.datastore_path = ':memory:'
FLAGS.verbose = True FLAGS.verbose = True

View File

@@ -18,6 +18,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import os
import logging import logging
import unittest import unittest
@@ -26,6 +27,8 @@ import IPy
from nova import flags from nova import flags
from nova import test from nova import test
from nova import exception
from nova.compute.exception import NoMoreAddresses
from nova.compute import network from nova.compute import network
from nova.auth import users from nova.auth import users
from nova import utils from nova import utils
@@ -40,6 +43,7 @@ class NetworkTestCase(test.TrialTestCase):
network_size=32) network_size=32)
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
self.manager = users.UserManager.instance() self.manager = users.UserManager.instance()
self.dnsmasq = FakeDNSMasq()
try: try:
self.manager.create_user('netuser', 'netuser', 'netuser') self.manager.create_user('netuser', 'netuser', 'netuser')
except: pass except: pass
@@ -66,59 +70,128 @@ class NetworkTestCase(test.TrialTestCase):
address = network.allocate_ip( address = network.allocate_ip(
"netuser", "project0", utils.generate_mac()) "netuser", "project0", utils.generate_mac())
logging.debug("Was allocated %s" % (address)) 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) 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): def test_range_allocation(self):
mac = utils.generate_mac()
secondmac = utils.generate_mac()
hostname = "test-host"
address = network.allocate_ip( address = network.allocate_ip(
"netuser", "project0", utils.generate_mac()) "netuser", "project0", mac)
secondaddress = network.allocate_ip( secondaddress = network.allocate_ip(
"netuser", "project1", utils.generate_mac()) "netuser", "project1", secondmac)
self.assertEqual(True, net = network.get_project_network("project0", "default")
address in self._get_project_addresses("project0")) secondnet = network.get_project_network("project1", "default")
self.assertEqual(True,
secondaddress in self._get_project_addresses("project1")) self.assertEqual(True, is_in_project(address, "project0"))
self.assertEqual(False, address in self._get_project_addresses("project1")) 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) 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) rv = network.deallocate_ip(secondaddress)
self.assertEqual(False, self.dnsmasq.release_ip(secondmac, secondaddress,
secondaddress in self._get_project_addresses("project1")) hostname, secondnet.bridge_name)
self.assertEqual(False, is_in_project(secondaddress, "project1"))
def test_subnet_edge(self): def test_subnet_edge(self):
secondaddress = network.allocate_ip("netuser", "project0", secondaddress = network.allocate_ip("netuser", "project0",
utils.generate_mac()) utils.generate_mac())
hostname = "toomany-hosts"
for project in range(1,5): for project in range(1,5):
project_id = "project%s" % (project) project_id = "project%s" % (project)
mac = utils.generate_mac()
mac2 = utils.generate_mac()
mac3 = utils.generate_mac()
address = network.allocate_ip( address = network.allocate_ip(
"netuser", project_id, utils.generate_mac()) "netuser", project_id, mac)
address2 = network.allocate_ip( address2 = network.allocate_ip(
"netuser", project_id, utils.generate_mac()) "netuser", project_id, mac2)
address3 = network.allocate_ip( address3 = network.allocate_ip(
"netuser", project_id, utils.generate_mac()) "netuser", project_id, mac3)
self.assertEqual(False, self.assertEqual(False, is_in_project(address, "project0"))
address in self._get_project_addresses("project0")) self.assertEqual(False, is_in_project(address2, "project0"))
self.assertEqual(False, self.assertEqual(False, is_in_project(address3, "project0"))
address2 in self._get_project_addresses("project0"))
self.assertEqual(False,
address3 in self._get_project_addresses("project0"))
rv = network.deallocate_ip(address) rv = network.deallocate_ip(address)
rv = network.deallocate_ip(address2) rv = network.deallocate_ip(address2)
rv = network.deallocate_ip(address3) 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) rv = network.deallocate_ip(secondaddress)
self.dnsmasq.release_ip(mac, address, hostname, net.bridge_name)
def test_too_many_projects(self): def test_release_before_deallocate(self):
for i in range(0, 30): pass
name = 'toomany-project%s' % i
self.manager.create_project(name, 'netuser', name) def test_deallocate_before_issued(self):
address = network.allocate_ip( pass
"netuser", name, utils.generate_mac())
rv = network.deallocate_ip(address) def test_too_many_addresses(self):
self.manager.delete_project(name) """
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