summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorTodd Willey <todd@rubidine.com>2010-07-14 23:42:55 -0400
committerTodd Willey <todd@rubidine.com>2010-07-14 23:42:55 -0400
commit1624e2aa51d6a77fbcbbf75f756aa88d27d1c474 (patch)
treec50daf203223f4530bc0acd45bf51ff230b944c5 /nova
parentb0b2d607b4f2db8ffbb5d091c4a3cd33ea6ed672 (diff)
parentebb56bcf492dc1ae132757f59f4ad82e1bf53d6e (diff)
Merge branch 'master' into apply_api
Conflicts: nova/compute/network.py nova/utils.py
Diffstat (limited to 'nova')
-rw-r--r--nova/auth/users.py8
-rw-r--r--nova/compute/disk.py26
-rw-r--r--nova/compute/interfaces.template18
-rw-r--r--nova/compute/linux_net.py12
-rw-r--r--nova/compute/network.py70
-rw-r--r--nova/compute/node.py32
-rw-r--r--nova/crypto.py21
-rw-r--r--nova/endpoint/cloud.py57
-rw-r--r--nova/tests/fake_flags.py2
-rw-r--r--nova/tests/network_unittest.py141
-rw-r--r--nova/utils.py17
-rw-r--r--nova/volume/storage.py2
12 files changed, 302 insertions, 104 deletions
diff --git a/nova/auth/users.py b/nova/auth/users.py
index 7b703aa82..9edbe0022 100644
--- a/nova/auth/users.py
+++ b/nova/auth/users.py
@@ -100,6 +100,10 @@ flags.DEFINE_string('credential_cert_file', 'cert.pem',
'Filename of certificate in credentials zip')
flags.DEFINE_string('credential_rc_file', 'novarc',
'Filename of rc in credentials zip')
+flags.DEFINE_string('credential_cert_subject',
+ '/C=US/ST=California/L=MountainView/O=AnsoLabs/'
+ 'OU=NovaDev/CN=%s-%s',
+ 'Subject for certificate for users')
flags.DEFINE_string('vpn_ip', '127.0.0.1',
'Public IP for the cloudpipe VPN servers')
@@ -517,7 +521,7 @@ class UserManager(object):
def __cert_subject(self, uid):
# FIXME(ja) - this should be pulled from a global configuration
- return "/C=US/ST=California/L=MountainView/O=AnsoLabs/OU=NovaDev/CN=%s-%s" % (uid, str(datetime.datetime.utcnow().isoformat()))
+ return FLAGS.credential_cert_subject % (uid, utils.isotime())
class LDAPWrapper(object):
@@ -707,7 +711,7 @@ class LDAPWrapper(object):
def __create_group(self, group_dn, name, uid,
description, member_uids = None):
- if self.group_exists(name):
+ if self.group_exists(group_dn):
raise exception.Duplicate("Group can't be created because "
"group %s already exists" % name)
members = []
diff --git a/nova/compute/disk.py b/nova/compute/disk.py
index bd6a010ee..b6398f41e 100644
--- a/nova/compute/disk.py
+++ b/nova/compute/disk.py
@@ -87,12 +87,14 @@ def partition(infile, outfile, local_bytes=0, local_type='ext2', execute=None):
% (infile, outfile, sector_size, primary_first))
@defer.inlineCallbacks
-def inject_key(key, image, partition=None, execute=None):
- """Injects a ssh key into a disk image.
- It adds the specified key to /root/.ssh/authorized_keys
+def inject_data(image, key=None, net=None, partition=None, execute=None):
+ """Injects a ssh key and optionally net data into a disk image.
+
it will mount the image as a fully partitioned disk and attempt to inject
into the specified partition number.
+
If partition is not specified it mounts the image as a single partition.
+
"""
out, err = yield execute('sudo losetup -f --show %s' % image)
if err:
@@ -119,15 +121,17 @@ def inject_key(key, image, partition=None, execute=None):
raise exception.Error('Failed to mount filesystem: %s' % err)
try:
- # inject key file
- yield _inject_into_fs(key, tmpdir, execute=execute)
+ if key:
+ # inject key file
+ yield _inject_key_into_fs(key, tmpdir, execute=execute)
+ if net:
+ yield _inject_net_into_fs(net, tmpdir, execute=execute)
finally:
# unmount device
yield execute('sudo umount %s' % mapped_device)
finally:
# remove temporary directory
- # TODO(termie): scary, is there any thing we can check here?
- yield execute('rm -rf %s' % tmpdir)
+ yield execute('rmdir %s' % tmpdir)
if not partition is None:
# remove partitions
yield execute('sudo kpartx -d %s' % device)
@@ -136,7 +140,7 @@ def inject_key(key, image, partition=None, execute=None):
yield execute('sudo losetup -d %s' % device)
@defer.inlineCallbacks
-def _inject_into_fs(key, fs, execute=None):
+def _inject_key_into_fs(key, fs, execute=None):
sshdir = os.path.join(os.path.join(fs, 'root'), '.ssh')
yield execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter
yield execute('sudo chown root %s' % sshdir)
@@ -144,3 +148,9 @@ def _inject_into_fs(key, fs, execute=None):
keyfile = os.path.join(sshdir, 'authorized_keys')
yield execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n')
+@defer.inlineCallbacks
+def _inject_net_into_fs(net, fs, execute=None):
+ netfile = os.path.join(os.path.join(os.path.join(
+ fs, 'etc'), 'network'), 'interfaces')
+ yield execute('sudo tee %s' % netfile, net)
+
diff --git a/nova/compute/interfaces.template b/nova/compute/interfaces.template
new file mode 100644
index 000000000..11df301f6
--- /dev/null
+++ b/nova/compute/interfaces.template
@@ -0,0 +1,18 @@
+# This file describes the network interfaces available on your system
+# and how to activate them. For more information, see interfaces(5).
+
+# The loopback network interface
+auto lo
+iface lo inet loopback
+
+# The primary network interface
+auto eth0
+iface eth0 inet static
+ address %(address)s
+ netmask %(netmask)s
+ network %(network)s
+ broadcast %(broadcast)s
+ gateway %(gateway)s
+ dns-nameservers %(dns)s
+
+
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 b2458828e..e5d3d18df 100644
--- a/nova/compute/network.py
+++ b/nova/compute/network.py
@@ -58,6 +58,26 @@ flags.DEFINE_integer('cnt_vpn_clients', 5,
flags.DEFINE_integer('cloudpipe_start_port', 12000,
'Starting port for mapped CloudPipe external ports')
+flags.DEFINE_boolean('simple_network', False,
+ 'Use simple networking instead of vlans')
+flags.DEFINE_string('simple_network_bridge', 'br100',
+ 'Bridge for simple network instances')
+flags.DEFINE_list('simple_network_ips', ['192.168.0.2'],
+ 'Available ips for simple network')
+flags.DEFINE_string('simple_network_template',
+ utils.abspath('compute/interfaces.template'),
+ 'Template file for simple network')
+flags.DEFINE_string('simple_network_netmask', '255.255.255.0',
+ 'Netmask for simple network')
+flags.DEFINE_string('simple_network_network', '192.168.0.0',
+ 'Network for simple network')
+flags.DEFINE_string('simple_network_gateway', '192.168.0.1',
+ 'Broadcast for simple network')
+flags.DEFINE_string('simple_network_broadcast', '192.168.0.255',
+ 'Broadcast for simple network')
+flags.DEFINE_string('simple_network_dns', '8.8.4.4',
+ 'Dns for simple network')
+
logging.getLogger().setLevel(logging.DEBUG)
@@ -188,18 +208,9 @@ class BaseNetwork(datastore.BasicModel):
return self.network.broadcast()
@property
- def gateway(self):
- return self.network[1]
-
- @property
def bridge_name(self):
return "br%s" % (self["vlan"])
- def range(self):
- # the .2 address is always CloudPipe
- for idx in range(3, len(self.network)-2):
- yield self.network[idx]
-
@property
def user(self):
return users.UserManager.instance().get_user(self['user_id'])
@@ -214,7 +225,7 @@ class BaseNetwork(datastore.BasicModel):
@property
def hosts(self):
- return datastore.Redis.instance().hgetall(self._hosts_key)
+ return datastore.Redis.instance().hgetall(self._hosts_key) or {}
def _add_host(self, _user_id, _project_id, host, target):
datastore.Redis.instance().hset(self._hosts_key, host, target)
@@ -241,14 +252,22 @@ class BaseNetwork(datastore.BasicModel):
self._add_host(user_id, project_id, address, mac)
self.express(address=address)
return address
- raise compute_exception.NoMoreAddresses()
+ raise compute_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 compute_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
@@ -280,8 +299,6 @@ class BridgedNetwork(BaseNetwork):
def get_network_for_project(cls, user_id, project_id, security_group):
vlan = get_vlan_for_project(project_id)
network_str = vlan.subnet()
- logging.debug("creating network on vlan %s with network string %s",
- vlan.vlan_id, network_str)
return cls.create(user_id, project_id, security_group, vlan.vlan_id,
network_str)
@@ -307,7 +324,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)]
@@ -470,6 +487,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"
@@ -517,12 +535,28 @@ def get_vlan_for_project(project_id):
raise compute_exception.AddressNotAllocated("Out of VLANs")
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 compute_exception.AddressNotAllocated()
+def allocate_simple_ip():
+ redis = datastore.Redis.instance()
+ if not redis.exists('ips') and not len(redis.keys('instances:*')):
+ for address in FLAGS.simple_network_ips:
+ redis.sadd('ips', address)
+ address = redis.spop('ips')
+ if not address:
+ raise exception.NoMoreAddresses()
+ return address
+
+def deallocate_simple_ip(address):
+ datastore.Redis.instance().sadd('ips', address)
+
+
def allocate_vpn_ip(user_id, project_id, mac):
return get_project_network(project_id).allocate_vpn_ip(mac)
@@ -531,6 +565,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/compute/node.py b/nova/compute/node.py
index f045107f8..f41bc34ea 100644
--- a/nova/compute/node.py
+++ b/nova/compute/node.py
@@ -57,7 +57,7 @@ from nova.objectstore import image # for image_path flag
FLAGS = flags.FLAGS
flags.DEFINE_string('libvirt_xml_template',
utils.abspath('compute/libvirt.xml.template'),
- 'Network XML Template')
+ 'Libvirt XML Template')
flags.DEFINE_bool('use_s3', True,
'whether to get images from s3 or use local copy')
flags.DEFINE_string('instances_path', utils.abspath('../instances'),
@@ -162,9 +162,10 @@ class Node(object, service.Service):
""" launch a new instance with specified options """
logging.debug("Starting instance %s..." % (instance_id))
inst = self.instdir.get(instance_id)
- # TODO: Get the real security group of launch in here
- security_group = "default"
- net = network.BridgedNetwork.get_network_for_project(inst['user_id'],
+ if not FLAGS.simple_network:
+ # TODO: Get the real security group of launch in here
+ security_group = "default"
+ net = network.BridgedNetwork.get_network_for_project(inst['user_id'],
inst['project_id'],
security_group).express()
inst['node_name'] = FLAGS.node_name
@@ -493,12 +494,23 @@ class Instance(object):
if not os.path.exists(basepath('ramdisk')):
yield _fetch_file(data['ramdisk_id'], basepath('ramdisk'))
- execute = lambda cmd, input=None: self._pool.simpleExecute(cmd=cmd, input=input, error_ok=1)
-
- if data['key_data']:
- logging.info('Injecting key data into image %s', data['image_id'])
- yield disk.inject_key(
- data['key_data'], basepath('disk-raw'), execute=execute)
+ execute = lambda cmd, input=None: self._pool.simpleExecute(cmd=cmd,
+ input=input,
+ error_ok=1)
+
+ key = data['key_data']
+ net = None
+ if FLAGS.simple_network:
+ with open(FLAGS.simple_network_template) as f:
+ net = f.read() % {'address': data['private_dns_name'],
+ 'network': FLAGS.simple_network_network,
+ 'netmask': FLAGS.simple_network_netmask,
+ 'gateway': FLAGS.simple_network_gateway,
+ 'broadcast': FLAGS.simple_network_broadcast,
+ 'dns': FLAGS.simple_network_dns}
+ if key or net:
+ logging.info('Injecting data into image %s', data['image_id'])
+ yield disk.inject_data(basepath('disk-raw'), key, net, execute=execute)
if os.path.exists(basepath('disk')):
yield self._pool.simpleExecute('rm -f %s' % basepath('disk'))
diff --git a/nova/crypto.py b/nova/crypto.py
index 413796ccc..fc6ed714f 100644
--- a/nova/crypto.py
+++ b/nova/crypto.py
@@ -23,10 +23,12 @@ Wrappers around standard crypto, including root and intermediate CAs,
SSH keypairs and x509 certificates.
"""
+import base64
import hashlib
import logging
import os
import shutil
+import struct
import tempfile
import time
import utils
@@ -86,14 +88,17 @@ def generate_key_pair(bits=1024):
def ssl_pub_to_ssh_pub(ssl_public_key, name='root', suffix='nova'):
- """requires lsh-utils"""
- convert="sed -e'1d' -e'$d' | pkcs1-conv --public-key-info --base-64 |" \
- + " sexp-conv | sed -e'1s/(rsa-pkcs1/(rsa-pkcs1-sha1/' | sexp-conv -s" \
- + " transport | lsh-export-key --openssh"
- (out, err) = utils.execute(convert, ssl_public_key)
- if err:
- raise exception.Error("Failed to generate key: %s", err)
- return '%s %s@%s\n' %(out.strip(), name, suffix)
+ rsa_key = M2Crypto.RSA.load_pub_key_bio(M2Crypto.BIO.MemoryBuffer(ssl_public_key))
+ e, n = rsa_key.pub()
+
+ key_type = 'ssh-rsa'
+
+ key_data = struct.pack('>I', len(key_type))
+ key_data += key_type
+ key_data += '%s%s' % (e,n)
+
+ b64_blob = base64.b64encode(key_data)
+ return '%s %s %s@%s\n' %(key_type, b64_blob, name, suffix)
def generate_x509_cert(subject, bits=1024):
diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py
index 931c6c6e1..9dccc24dc 100644
--- a/nova/endpoint/cloud.py
+++ b/nova/endpoint/cloud.py
@@ -115,9 +115,9 @@ class CloudController(object):
def get_metadata(self, ip):
i = self.get_instance_by_ip(ip)
- mpi = self._get_mpi_data(i['project_id'])
if i is None:
return None
+ mpi = self._get_mpi_data(i['project_id'])
if i['key_name']:
keys = {
'0': {
@@ -403,15 +403,17 @@ class CloudController(object):
def _format_instances(self, context, reservation_id = None):
reservations = {}
- for instance in self.instdir.all:
+ if context.user.is_admin():
+ instgenerator = self.instdir.all
+ else:
+ instgenerator = self.instdir.by_project(context.project.id)
+ for instance in instgenerator:
res_id = instance.get('reservation_id', 'Unknown')
if reservation_id != None and reservation_id != res_id:
continue
if not context.user.is_admin():
if instance['image_id'] == FLAGS.vpn_image_id:
continue
- if context.project.id != instance['project_id']:
- continue
i = {}
i['instance_id'] = instance.get('instance_id', None)
i['image_id'] = instance.get('image_id', None)
@@ -498,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
@@ -516,11 +526,20 @@ class CloudController(object):
key_data = key_pair.public_key
# TODO: Get the real security group of launch in here
security_group = "default"
- bridge_name = network.BridgedNetwork.get_network_for_project(context.user.id, context.project.id, security_group)['bridge_name']
+ if FLAGS.simple_network:
+ bridge_name = FLAGS.simple_network_bridge
+ else:
+ net = network.BridgedNetwork.get_network_for_project(
+ context.user.id, context.project.id, security_group)
+ bridge_name = net['bridge_name']
for num in range(int(kwargs['max_count'])):
inst = self.instdir.new()
# TODO(ja): add ari, aki
inst['image_id'] = kwargs['image_id']
+ if 'kernel_id' in kwargs:
+ inst['kernel_id'] = kwargs['kernel_id']
+ if 'ramdisk_id' in kwargs:
+ inst['ramdisk_id'] = kwargs['ramdisk_id']
inst['user_data'] = kwargs.get('user_data', '')
inst['instance_type'] = kwargs.get('instance_type', 'm1.small')
inst['reservation_id'] = reservation_id
@@ -532,12 +551,19 @@ class CloudController(object):
inst['mac_address'] = utils.generate_mac()
inst['ami_launch_index'] = num
inst['bridge_name'] = bridge_name
- if inst['image_id'] == FLAGS.vpn_image_id:
- address = network.allocate_vpn_ip(
- inst['user_id'], inst['project_id'], mac=inst['mac_address'])
+ if FLAGS.simple_network:
+ address = network.allocate_simple_ip()
else:
- address = network.allocate_ip(
- inst['user_id'], inst['project_id'], mac=inst['mac_address'])
+ 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()
@@ -567,10 +593,13 @@ class CloudController(object):
pass
if instance.get('private_dns_name', None):
logging.debug("Deallocating address %s" % instance.get('private_dns_name', None))
- try:
- self.network.deallocate_ip(instance.get('private_dns_name', None))
- except Exception, _err:
- pass
+ if FLAGS.simple_network:
+ network.deallocate_simple_ip(instance.get('private_dns_name', None))
+ else:
+ try:
+ self.network.deallocate_ip(instance.get('private_dns_name', None))
+ except Exception, _err:
+ pass
if instance.get('node_name', 'unassigned') != 'unassigned': #It's also internal default
rpc.cast('%s.%s' % (FLAGS.compute_topic, instance['node_name']),
{"method": "terminate_instance",
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 094de5d74..2982b5480 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -22,13 +22,13 @@
System-level utilities and helper functions.
"""
+import inspect
import logging
+import os
+import random
+import subprocess
import socket
import sys
-import os.path
-import inspect
-import subprocess
-import random
from datetime import datetime
from nova import flags
@@ -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)
diff --git a/nova/volume/storage.py b/nova/volume/storage.py
index 491c891fd..288ab76ba 100644
--- a/nova/volume/storage.py
+++ b/nova/volume/storage.py
@@ -247,7 +247,7 @@ class Volume(datastore.BasicModel):
def _exec_export(self):
utils.runthis("Creating AOE export: %s",
- "sudo vblade- persist setup %s %s %s /dev/%s/%s" %
+ "sudo vblade-persist setup %s %s %s /dev/%s/%s" %
(self['shelf_id'],
self['blade_id'],
FLAGS.aoe_eth_dev,