summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrad Hall <brad@nicira.com>2011-11-04 20:11:53 -0700
committerBrad Hall <brad@nicira.com>2011-11-04 20:11:53 -0700
commit38172d55876e78ff3c4326368de9ea9ddb99e76b (patch)
tree69ddf240eb08fc2847fa194c4b72db29da5ca078
parentf89f27b184eb950f4487002a821b2e9c0f8315c7 (diff)
downloadnova-38172d55876e78ff3c4326368de9ea9ddb99e76b.tar.gz
nova-38172d55876e78ff3c4326368de9ea9ddb99e76b.tar.xz
nova-38172d55876e78ff3c4326368de9ea9ddb99e76b.zip
Add DHCP support to the QuantumManager and break apart dhcp/gateway
This introduces a new flag "quantum_use_dhcp=<boolean>" which indicates whether or not to enable dhcp for all of the networks. If it is set then we start dnsmasq (and provide it with the IP/MACs from Melange) similar to how this was done in linux_net before. Prior to this if you enabled dhcp then you would also get a gateway device.. some people may not want that so we now require that you specify the gateway when creating the network in order to end up with a device that will act as a gateway. If you're using Melange IPAM and you don't specify the gateway you still end up with one because it doesn't allow you to not have one. This lays the groundwork for the option of not having one in the future, at least :) Also, fix quantum/melange ipam interaction We now query for the subnets by net_id/vif_id instead of searching through all the blocks to find the right one. Both of the allocate and deallocate for instance calls are now using the vif_id -> network_id mapping instead of searching the quantum networks. get_port_by_attachment was also changed to take a net_id so that we don't have to search through all of the quantum networks to find the corresponding port. Change-Id: I6a84da35237b6c5f5cdee91ada92642103439a97
-rwxr-xr-xbin/nova-dhcpbridge3
-rwxr-xr-xbin/nova-manage9
-rwxr-xr-xnova/network/linux_net.py63
-rw-r--r--nova/network/manager.py9
-rw-r--r--nova/network/quantum/client.py2
-rw-r--r--nova/network/quantum/manager.py255
-rw-r--r--nova/network/quantum/melange_connection.py18
-rw-r--r--nova/network/quantum/melange_ipam_lib.py94
-rw-r--r--nova/network/quantum/nova_ipam_lib.py34
-rw-r--r--nova/network/quantum/quantum_connection.py31
-rw-r--r--nova/tests/__init__.py1
-rw-r--r--nova/tests/test_network.py35
-rw-r--r--nova/tests/test_nova_manage.py17
-rw-r--r--nova/tests/test_quantum.py49
14 files changed, 482 insertions, 138 deletions
diff --git a/bin/nova-dhcpbridge b/bin/nova-dhcpbridge
index 1c9ae951e..25d3e181d 100755
--- a/bin/nova-dhcpbridge
+++ b/bin/nova-dhcpbridge
@@ -90,7 +90,8 @@ def init_leases(network_id):
"""Get the list of hosts for a network."""
ctxt = context.get_admin_context()
network_ref = db.network_get(ctxt, network_id)
- return linux_net.get_dhcp_leases(ctxt, network_ref)
+ network_manager = utils.import_object(FLAGS.network_manager)
+ return network_manager.get_dhcp_leases(ctxt, network_ref)
def main():
diff --git a/bin/nova-manage b/bin/nova-manage
index c6ec155e4..d56b292f7 100755
--- a/bin/nova-manage
+++ b/bin/nova-manage
@@ -727,6 +727,7 @@ class NetworkCommands(object):
@args('--vpn', dest="vpn_start", help='vpn start')
@args('--fixed_range_v6', dest="fixed_range_v6",
help='IPv6 subnet (ex: fe80::/64')
+ @args('--gateway', dest="gateway", help='gateway')
@args('--gateway_v6', dest="gateway_v6", help='ipv6 gateway')
@args('--bridge', dest="bridge",
metavar='<bridge>',
@@ -746,9 +747,10 @@ class NetworkCommands(object):
help='Network interface priority')
def create(self, label=None, fixed_range_v4=None, num_networks=None,
network_size=None, multi_host=None, vlan_start=None,
- vpn_start=None, fixed_range_v6=None, gateway_v6=None,
- bridge=None, bridge_interface=None, dns1=None, dns2=None,
- project_id=None, priority=None, uuid=None):
+ vpn_start=None, fixed_range_v6=None, gateway=None,
+ gateway_v6=None, bridge=None, bridge_interface=None,
+ dns1=None, dns2=None, project_id=None, priority=None,
+ uuid=None):
"""Creates fixed ips for host by range"""
# check for certain required inputs
@@ -811,6 +813,7 @@ class NetworkCommands(object):
vlan_start=int(vlan_start),
vpn_start=int(vpn_start),
cidr_v6=fixed_range_v6,
+ gateway=gateway,
gateway_v6=gateway_v6,
bridge=bridge,
bridge_interface=bridge_interface,
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index 61096bc72..46d520ade 100755
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -595,11 +595,29 @@ def release_dhcp(dev, address, mac_address):
utils.execute('dhcp_release', dev, address, mac_address, run_as_root=True)
+def update_dhcp(context, dev, network_ref):
+ conffile = _dhcp_file(dev, 'conf')
+ with open(conffile, 'w') as f:
+ f.write(get_dhcp_hosts(context, network_ref))
+ restart_dhcp(dev, network_ref)
+
+
+def update_dhcp_hostfile_with_text(dev, hosts_text):
+ conffile = _dhcp_file(dev, 'conf')
+ with open(conffile, 'w') as f:
+ f.write(hosts_text)
+
+
+def kill_dhcp(dev):
+ pid = _dnsmasq_pid_for(dev)
+ _execute('kill', '-9', pid, run_as_root=True)
+
+
# NOTE(ja): Sending a HUP only reloads the hostfile, so any
# configuration options (like dchp-range, vlan, ...)
# aren't reloaded.
@utils.synchronized('dnsmasq_start')
-def update_dhcp(context, dev, network_ref):
+def restart_dhcp(dev, network_ref):
"""(Re)starts a dnsmasq server for a given network.
If a dnsmasq instance is already running then send a HUP
@@ -607,8 +625,6 @@ def update_dhcp(context, dev, network_ref):
"""
conffile = _dhcp_file(dev, 'conf')
- with open(conffile, 'w') as f:
- f.write(get_dhcp_hosts(context, network_ref))
if FLAGS.use_single_default_gateway:
optsfile = _dhcp_file(dev, 'opts')
@@ -625,7 +641,9 @@ def update_dhcp(context, dev, network_ref):
if pid:
out, _err = _execute('cat', '/proc/%d/cmdline' % pid,
check_exit_code=False)
- if conffile in out:
+ # Using symlinks can cause problems here so just compare the name
+ # of the file itself
+ if conffile.split("/")[-1] in out:
try:
_execute('kill', '-HUP', pid, run_as_root=True)
return
@@ -830,8 +848,8 @@ def _ip_bridge_cmd(action, params, device):
# act as gateway/dhcp/vpn/etc. endpoints not VM interfaces.
-def plug(network, mac_address):
- return interface_driver.plug(network, mac_address)
+def plug(network, mac_address, gateway=True):
+ return interface_driver.plug(network, mac_address, gateway)
def unplug(network):
@@ -862,7 +880,7 @@ class LinuxNetInterfaceDriver(object):
# plugs interfaces using Linux Bridge
class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
- def plug(self, network, mac_address):
+ def plug(self, network, mac_address, gateway=True):
if network.get('vlan', None) is not None:
LinuxBridgeInterfaceDriver.ensure_vlan_bridge(
network['vlan'],
@@ -874,7 +892,7 @@ class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
LinuxBridgeInterfaceDriver.ensure_bridge(
network['bridge'],
network['bridge_interface'],
- network)
+ network, gateway)
return network['bridge']
@@ -914,7 +932,7 @@ class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
@classmethod
@utils.synchronized('ensure_bridge', external=True)
- def ensure_bridge(_self, bridge, interface, net_attrs=None):
+ def ensure_bridge(_self, bridge, interface, net_attrs=None, gateway=True):
"""Create a bridge unless it already exists.
:param interface: the interface to create the bridge on.
@@ -974,19 +992,28 @@ class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
"can't enslave it to bridge %s.\n" % (interface, bridge)):
raise exception.Error('Failed to add interface: %s' % err)
- iptables_manager.ipv4['filter'].add_rule('FORWARD',
+ # Don't forward traffic unless we were told to be a gateway
+ if gateway:
+ iptables_manager.ipv4['filter'].add_rule('FORWARD',
'--in-interface %s -j ACCEPT' % \
bridge)
- iptables_manager.ipv4['filter'].add_rule('FORWARD',
+ iptables_manager.ipv4['filter'].add_rule('FORWARD',
'--out-interface %s -j ACCEPT' % \
bridge)
+ else:
+ iptables_manager.ipv4['filter'].add_rule('FORWARD',
+ '--in-interface %s -j DROP' % \
+ bridge)
+ iptables_manager.ipv4['filter'].add_rule('FORWARD',
+ '--out-interface %s -j DROP' % \
+ bridge)
# plugs interfaces using Open vSwitch
class LinuxOVSInterfaceDriver(LinuxNetInterfaceDriver):
- def plug(self, network, mac_address):
- dev = "gw-" + str(network['id'])
+ def plug(self, network, mac_address, gateway=True):
+ dev = "gw-" + str(network['uuid'][0:11])
if not _device_exists(dev):
bridge = FLAGS.linuxnet_ovs_integration_bridge
_execute('ovs-vsctl',
@@ -1002,6 +1029,14 @@ class LinuxOVSInterfaceDriver(LinuxNetInterfaceDriver):
_execute('ip', 'link', 'set', dev, "address", mac_address,
run_as_root=True)
_execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
+ if not gateway:
+ # If we weren't instructed to act as a gateway then add the
+ # appropriate flows to block all non-dhcp traffic.
+ _execute('ovs-ofctl',
+ 'add-flow', bridge, "priority=1,actions=drop")
+ _execute('ovs-ofctl', 'add-flow', bridge,
+ "udp,tp_dst=67,dl_dst=%s,priority=2,actions=normal" %
+ mac_address)
return dev
@@ -1009,7 +1044,7 @@ class LinuxOVSInterfaceDriver(LinuxNetInterfaceDriver):
return self.get_dev(network)
def get_dev(self, network):
- dev = "gw-" + str(network['id'])
+ dev = "gw-" + str(network['uuid'][0:11])
return dev
iptables_manager = IptablesManager()
diff --git a/nova/network/manager.py b/nova/network/manager.py
index 8bd0dbdd0..7e917e6bf 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -95,6 +95,7 @@ flags.DEFINE_string('floating_range', '4.4.4.0/24',
'Floating IP address block')
flags.DEFINE_string('fixed_range', '10.0.0.0/8', 'Fixed IP address block')
flags.DEFINE_string('fixed_range_v6', 'fd00::/48', 'Fixed IPv6 address block')
+flags.DEFINE_string('gateway', None, 'Default IPv4 gateway')
flags.DEFINE_string('gateway_v6', None, 'Default IPv6 gateway')
flags.DEFINE_integer('cnt_vpn_clients', 0,
'Number of addresses reserved for vpn clients')
@@ -491,6 +492,10 @@ class NetworkManager(manager.SchedulerDependentManager):
network_id,
host=host)
+ def get_dhcp_leases(self, ctxt, network_ref):
+ """Broker the request to the driver to fetch the dhcp leases"""
+ return self.driver.get_dhcp_leases(ctxt, network_ref)
+
def init_host(self):
"""Do any initialization that needs to be run if this is a
standalone service.
@@ -863,7 +868,7 @@ class NetworkManager(manager.SchedulerDependentManager):
self._setup_network(context, network_ref)
def create_networks(self, context, label, cidr, multi_host, num_networks,
- network_size, cidr_v6, gateway_v6, bridge,
+ network_size, cidr_v6, gateway, gateway_v6, bridge,
bridge_interface, dns1=None, dns2=None, **kwargs):
"""Create networks based on parameters."""
# NOTE(jkoelker): these are dummy values to make sure iter works
@@ -947,7 +952,7 @@ class NetworkManager(manager.SchedulerDependentManager):
if cidr and subnet_v4:
net['cidr'] = str(subnet_v4)
net['netmask'] = str(subnet_v4.netmask)
- net['gateway'] = str(subnet_v4[1])
+ net['gateway'] = gateway or str(subnet_v4[1])
net['broadcast'] = str(subnet_v4.broadcast)
net['dhcp_start'] = str(subnet_v4[2])
diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py
index 40c68dfdc..d3833257b 100644
--- a/nova/network/quantum/client.py
+++ b/nova/network/quantum/client.py
@@ -224,8 +224,6 @@ class Client(object):
type(data)))
def deserialize(self, data, status_code):
- if status_code == 202:
- return data
return JSONSerializer().deserialize(data, self.content_type())
def content_type(self, format=None):
diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py
index 16428b92b..407a1a807 100644
--- a/nova/network/quantum/manager.py
+++ b/nova/network/quantum/manager.py
@@ -15,6 +15,10 @@
# License for the specific language governing permissions and limitations
# under the License.
+import time
+
+from netaddr import IPNetwork, IPAddress
+
from nova import db
from nova import exception
from nova import flags
@@ -33,6 +37,10 @@ flags.DEFINE_string('quantum_ipam_lib',
"Indicates underlying IP address management library")
+flags.DEFINE_string('quantum_use_dhcp', 'False',
+ 'Whether or not to enable DHCP for networks')
+
+
class QuantumManager(manager.FlatManager):
"""NetworkManager class that communicates with a Quantum service
via a web services API to provision VM network connectivity.
@@ -43,7 +51,6 @@ class QuantumManager(manager.FlatManager):
Currently, the QuantumManager does NOT support any of the 'gateway'
functionality implemented by the Nova VlanManager, including:
* floating IPs
- * DHCP
* NAT gateway
Support for these capabilities are targted for future releases.
@@ -65,9 +72,14 @@ class QuantumManager(manager.FlatManager):
self.ipam = utils.import_object(ipam_lib).get_ipam_lib(self)
super(QuantumManager, self).__init__(*args, **kwargs)
+ self.driver.init_host()
+ # TODO(bgh): We'll need to enable these when we implement the full L3
+ # functionalities
+ # self.driver.ensure_metadata_ip()
+ # self.driver.metadata_forward()
def create_networks(self, context, label, cidr, multi_host, num_networks,
- network_size, cidr_v6, gateway_v6, bridge,
+ network_size, cidr_v6, gateway, gateway_v6, bridge,
bridge_interface, dns1=None, dns2=None, uuid=None,
**kwargs):
"""Unlike other NetworkManagers, with QuantumManager, each
@@ -98,7 +110,10 @@ class QuantumManager(manager.FlatManager):
ipam_tenant_id = kwargs.get("project_id", None)
priority = kwargs.get("priority", 0)
self.ipam.create_subnet(context, label, ipam_tenant_id, quantum_net_id,
- priority, cidr, gateway_v6, cidr_v6, dns1, dns2)
+ priority, cidr, gateway, gateway_v6,
+ cidr_v6, dns1, dns2)
+
+ return [{'uuid': quantum_net_id}]
def delete_network(self, context, fixed_range, uuid):
"""Lookup network by uuid, delete both the IPAM
@@ -119,6 +134,9 @@ class QuantumManager(manager.FlatManager):
if self.q_conn.network_exists(p['id'], uuid):
project_id = p['id']
break
+ if project_id is None:
+ # If nothing was found we default to this
+ project_id = FLAGS.quantum_default_tenant_id
LOG.debug("Deleting network for tenant: %s" % project_id)
self.ipam.delete_subnets_by_net_id(context, quantum_net_id,
project_id)
@@ -152,7 +170,7 @@ class QuantumManager(manager.FlatManager):
instance_type_id = kwargs['instance_type_id']
host = kwargs.pop('host')
project_id = kwargs.pop('project_id')
- LOG.debug(_("network allocations for instance %s"), instance_id)
+ LOG.debug(_("network allocations for instance %s"), project_id)
requested_networks = kwargs.get('requested_networks')
@@ -163,9 +181,17 @@ class QuantumManager(manager.FlatManager):
net_proj_pairs = self.ipam.get_project_and_global_net_ids(context,
project_id)
+ # Quantum may also know about networks that aren't in the networks
+ # table so we need to query Quanutm for any tenant networks and add
+ # them to net_proj_pairs.
+ qnets = self.q_conn.get_networks(project_id)
+ for qn in qnets['networks']:
+ pair = (qn['id'], project_id)
+ if pair not in net_proj_pairs:
+ net_proj_pairs.append(pair)
+
# Create a port via quantum and attach the vif
for (quantum_net_id, project_id) in net_proj_pairs:
-
# FIXME(danwent): We'd like to have the manager be
# completely decoupled from the nova networks table.
# However, other parts of nova sometimes go behind our
@@ -176,8 +202,28 @@ class QuantumManager(manager.FlatManager):
# solution, but this would require significant work
# elsewhere.
admin_context = context.elevated()
+
+ # We may not be able to get a network_ref here if this network
+ # isn't in the database (i.e. it came from Quantum).
network_ref = db.network_get_by_uuid(admin_context,
quantum_net_id)
+ if network_ref is None:
+ network_ref = {}
+ network_ref = {"uuid": quantum_net_id,
+ "project_id": project_id,
+ # NOTE(bgh): We need to document this somewhere but since
+ # we don't know the priority of any networks we get from
+ # quantum we just give them a priority of 0. If its
+ # necessary to specify the order of the vifs and what
+ # network they map to then the user will have to use the
+ # OSCreateServer extension and specify them explicitly.
+ #
+ # In the future users will be able to tag quantum networks
+ # with a priority .. and at that point we can update the
+ # code here to reflect that.
+ "priority": 0,
+ "id": 'NULL',
+ "label": "quantum-net-%s" % quantum_net_id}
vif_rec = manager.FlatManager.add_virtual_interface(self,
context, instance_id, network_ref['id'])
@@ -186,12 +232,72 @@ class QuantumManager(manager.FlatManager):
q_tenant_id = project_id or FLAGS.quantum_default_tenant_id
self.q_conn.create_and_attach_port(q_tenant_id, quantum_net_id,
vif_rec['uuid'])
- self.ipam.allocate_fixed_ip(context, project_id, quantum_net_id,
- vif_rec)
-
+ # Tell melange to allocate an IP
+ ip = self.ipam.allocate_fixed_ip(context, project_id,
+ quantum_net_id, vif_rec)
+ # Set up/start the dhcp server for this network if necessary
+ if FLAGS.quantum_use_dhcp:
+ self.enable_dhcp(context, quantum_net_id, network_ref,
+ vif_rec, project_id)
return self.get_instance_nw_info(context, instance_id,
instance_type_id, host)
+ def enable_dhcp(self, context, quantum_net_id, network_ref, vif_rec,
+ project_id):
+ LOG.info("Using DHCP for network: %s" % network_ref['label'])
+ # Figure out the ipam tenant id for this subnet: We need to
+ # query for the tenant_id since the network could be created
+ # with the project_id as the tenant or the default tenant.
+ ipam_tenant_id = self.ipam.get_tenant_id_by_net_id(context,
+ quantum_net_id, vif_rec['uuid'], project_id)
+ # Figure out what subnets correspond to this network
+ v4_subnet, v6_subnet = self.ipam.get_subnets_by_net_id(context,
+ ipam_tenant_id, quantum_net_id, vif_rec['uuid'])
+ # Set up (or find) the dhcp server for each of the subnets
+ # returned above (both v4 and v6).
+ for subnet in [v4_subnet, v6_subnet]:
+ if subnet is None or subnet['cidr'] is None:
+ continue
+ # Fill in some of the network fields that we would have
+ # previously gotten from the network table (they'll be
+ # passed to the linux_net functions).
+ network_ref['cidr'] = subnet['cidr']
+ n = IPNetwork(subnet['cidr'])
+ network_ref['dhcp_server'] = IPAddress(n.first + 1)
+ # TODO(bgh): Melange should probably track dhcp_start
+ if not 'dhcp_start' in network_ref or \
+ network_ref['dhcp_start'] is None:
+ network_ref['dhcp_start'] = IPAddress(n.first + 2)
+ network_ref['broadcast'] = IPAddress(n.broadcast)
+ network_ref['gateway'] = subnet['gateway']
+ # Construct the interface id that we'll use for the bridge
+ interface_id = "gw-" + str(network_ref['uuid'][0:11])
+ network_ref['bridge'] = interface_id
+ # Query quantum to see if we've already created a port for
+ # the gateway device and attached the device to the port.
+ # If we haven't then we need to intiialize it and create
+ # it. This device will be the one serving dhcp via
+ # dnsmasq.
+ q_tenant_id = project_id or FLAGS.quantum_default_tenant_id
+ port = self.q_conn.get_port_by_attachment(q_tenant_id,
+ quantum_net_id, interface_id)
+ if not port: # No dhcp server has been started
+ mac_address = self.generate_mac_address()
+ dev = self.driver.plug(network_ref, mac_address,
+ gateway=(network_ref['gateway'] != None))
+ self.driver.initialize_gateway_device(dev, network_ref)
+ LOG.debug("Intializing DHCP for network: %s" %
+ network_ref)
+ self.q_conn.create_and_attach_port(q_tenant_id,
+ quantum_net_id, interface_id)
+ else: # We've already got one and its plugged in
+ dev = interface_id
+
+ hosts = self.get_dhcp_hosts_text(context,
+ subnet['network_id'], project_id)
+ self.driver.update_dhcp_hostfile_with_text(dev, hosts)
+ self.driver.restart_dhcp(dev, network_ref)
+
def get_instance_nw_info(self, context, instance_id,
instance_type_id, host):
"""This method is used by compute to fetch all network data
@@ -214,15 +320,9 @@ class QuantumManager(manager.FlatManager):
vifs = db.virtual_interface_get_by_instance(admin_context,
instance_id)
for vif in vifs:
- q_tenant_id = project_id
- ipam_tenant_id = project_id
- net_id, port_id = self.q_conn.get_port_by_attachment(q_tenant_id,
- vif['uuid'])
- if not net_id:
- q_tenant_id = FLAGS.quantum_default_tenant_id
- ipam_tenant_id = None
- net_id, port_id = self.q_conn.get_port_by_attachment(
- q_tenant_id, vif['uuid'])
+ net = db.network_get(admin_context, vif['network_id'])
+ net_id = net['uuid']
+
if not net_id:
# TODO(bgh): We need to figure out a way to tell if we
# should actually be raising this exception or not.
@@ -232,8 +332,13 @@ class QuantumManager(manager.FlatManager):
# probably just log, continue, and move on.
raise Exception(_("No network for for virtual interface %s") %
vif['uuid'])
- (v4_subnet, v6_subnet) = self.ipam.get_subnets_by_net_id(context,
- ipam_tenant_id, net_id)
+
+ ipam_tenant_id = self.ipam.get_tenant_id_by_net_id(context,
+ net_id, vif['uuid'], project_id)
+ v4_subnet, v6_subnet = \
+ self.ipam.get_subnets_by_net_id(context,
+ ipam_tenant_id, net_id, vif['uuid'])
+
v4_ips = self.ipam.get_v4_ips_by_interface(context,
net_id, vif['uuid'],
project_id=ipam_tenant_id)
@@ -241,8 +346,6 @@ class QuantumManager(manager.FlatManager):
net_id, vif['uuid'],
project_id=ipam_tenant_id)
- quantum_net_id = v4_subnet['network_id'] or v6_subnet['network_id']
-
def ip_dict(ip, subnet):
return {
"ip": ip,
@@ -298,24 +401,35 @@ class QuantumManager(manager.FlatManager):
for vif_ref in vifs:
interface_id = vif_ref['uuid']
q_tenant_id = project_id
- ipam_tenant_id = project_id
- (net_id, port_id) = self.q_conn.get_port_by_attachment(q_tenant_id,
- interface_id)
- if not net_id:
+
+ network_ref = db.network_get(admin_context, vif_ref['network_id'])
+ net_id = network_ref['uuid']
+
+ port_id = self.q_conn.get_port_by_attachment(q_tenant_id,
+ net_id, interface_id)
+ if not port_id:
q_tenant_id = FLAGS.quantum_default_tenant_id
- ipam_tenant_id = None
- (net_id, port_id) = self.q_conn.get_port_by_attachment(
- q_tenant_id, interface_id)
- if not net_id:
+ port_id = self.q_conn.get_port_by_attachment(
+ q_tenant_id, net_id, interface_id)
+
+ if not port_id:
LOG.error("Unable to find port with attachment: %s" %
(interface_id))
- continue
- self.q_conn.detach_and_delete_port(q_tenant_id,
- net_id, port_id)
+ else:
+ self.q_conn.detach_and_delete_port(q_tenant_id,
+ net_id, port_id)
+
+ ipam_tenant_id = self.ipam.get_tenant_id_by_net_id(context,
+ net_id, vif_ref['uuid'], project_id)
self.ipam.deallocate_ips_by_vif(context, ipam_tenant_id,
net_id, vif_ref)
+ # If DHCP is enabled on this network then we need to update the
+ # leases and restart the server.
+ if FLAGS.quantum_use_dhcp:
+ self.update_dhcp(context, ipam_tenant_id, network_ref, vif_ref,
+ project_id)
try:
db.virtual_interface_delete_by_instance(admin_context,
instance_id)
@@ -323,6 +437,37 @@ class QuantumManager(manager.FlatManager):
LOG.error(_("Attempted to deallocate non-existent instance: %s" %
(instance_id)))
+ # TODO(bgh): At some point we should consider merging enable_dhcp() and
+ # update_dhcp()
+ def update_dhcp(self, context, ipam_tenant_id, network_ref, vif_ref,
+ project_id):
+ # Figure out what subnet corresponds to this network/vif
+ v4_subnet, v6_subnet = self.ipam.get_subnets_by_net_id(context,
+ ipam_tenant_id, network_ref['uuid'], vif_ref['uuid'])
+ for subnet in [v4_subnet, v6_subnet]:
+ if subnet is None:
+ continue
+ # Fill in some of the network fields that we would have
+ # previously gotten from the network table (they'll be
+ # passed to the linux_net functions).
+ network_ref['cidr'] = subnet['cidr']
+ n = IPNetwork(subnet['cidr'])
+ network_ref['dhcp_server'] = IPAddress(n.first + 1)
+ network_ref['dhcp_start'] = IPAddress(n.first + 2)
+ network_ref['broadcast'] = IPAddress(n.broadcast)
+ network_ref['gateway'] = IPAddress(n.first + 1)
+ dev = "gw-" + str(network_ref['uuid'][0:11])
+ # And remove the dhcp mappings for the subnet
+ hosts = self.get_dhcp_hosts_text(context,
+ subnet['network_id'], project_id)
+ self.driver.update_dhcp_hostfile_with_text(dev, hosts)
+ # Restart dnsmasq
+ self.driver.kill_dhcp(dev)
+ self.driver.restart_dhcp(dev, network_ref)
+
+ # TODO(bgh): if this is the last instance for the network
+ # then we should actually just kill the dhcp server.
+
def validate_networks(self, context, networks):
"""Validates that this tenant has quantum networks with the associated
UUIDs. This is called by the 'os-create-server-ext' API extension
@@ -334,6 +479,50 @@ class QuantumManager(manager.FlatManager):
project_id = context.project_id
for (net_id, _i) in networks:
- self.ipam.verify_subnet_exists(context, project_id, net_id)
+ # TODO(bgh): At some point we should figure out whether or
+ # not we want the verify_subnet_exists call to be optional.
+ if not self.ipam.verify_subnet_exists(context, project_id,
+ net_id):
+ raise exception.NetworkNotFound(network_id=net_id)
if not self.q_conn.network_exists(project_id, net_id):
raise exception.NetworkNotFound(network_id=net_id)
+
+ # NOTE(bgh): deallocate_for_instance will take care of this.. The reason
+ # we're providing this is so that NetworkManager::release_fixed_ip() isn't
+ # called. It does some database operations that we don't want to happen
+ # and since the majority of the stuff that it does is already taken care
+ # of in our deallocate_for_instance call we don't need to do anything.
+ def release_fixed_ip(self, context, address):
+ pass
+
+ def get_dhcp_hosts_text(self, context, subnet_id, project_id=None):
+ ips = self.ipam.get_allocated_ips(context, subnet_id, project_id)
+ hosts_text = ""
+ admin_context = context.elevated()
+ for ip in ips:
+ address, vif_id = ip
+ vif = db.virtual_interface_get_by_uuid(admin_context, vif_id)
+ mac_address = vif['address']
+ text = "%s,%s.%s,%s\n" % (mac_address, "host-" + address,
+ FLAGS.dhcp_domain, address)
+ hosts_text += text
+ LOG.debug("DHCP hosts: %s" % hosts_text)
+ return hosts_text
+
+ def get_dhcp_leases(self, context, network_ref):
+ """Return a network's hosts config in dnsmasq leasefile format."""
+ subnet_id = network_ref['uuid']
+ project_id = network_ref['project_id']
+ ips = self.ipam.get_allocated_ips(context, subnet_id, project_id)
+ leases_text = ""
+ admin_context = context.elevated()
+ for ip in ips:
+ address, vif_id = ip
+ vif = db.virtual_interface_get_by_uuid(admin_context, vif_id)
+ mac_address = vif['address']
+ text = "%s %s %s %s *\n" % \
+ (int(time.time()) - FLAGS.dhcp_lease_time,
+ mac_address, address, '*')
+ leases_text += text
+ LOG.debug("DHCP leases: %s" % leases_text)
+ return leases_text
diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py
index 71ac9b5f1..d7483d756 100644
--- a/nova/network/quantum/melange_connection.py
+++ b/nova/network/quantum/melange_connection.py
@@ -66,11 +66,12 @@ class MelangeConnection(object):
else:
return httplib.HTTPConnection(self.host, self.port)
- def do_request(self, method, path, body=None, headers=None, params=None):
+ def do_request(self, method, path, body=None, headers=None, params=None,
+ content_type=".json"):
headers = headers or {}
params = params or {}
- url = "/%s/%s.json" % (self.version, path)
+ url = "/%s/%s%s" % (self.version, path, content_type)
if params:
url += "?%s" % urllib.urlencode(params)
try:
@@ -98,13 +99,14 @@ class MelangeConnection(object):
return json.loads(response)['ip_addresses']
def create_block(self, network_id, cidr,
- project_id=None, dns1=None, dns2=None):
+ project_id=None, gateway=None, dns1=None, dns2=None):
tenant_scope = "/tenants/%s" % project_id if project_id else ""
url = "ipam%(tenant_scope)s/ip_blocks" % locals()
req_params = dict(ip_block=dict(cidr=cidr, network_id=network_id,
- type='private', dns1=dns1, dns2=dns2))
+ type='private', gateway=gateway,
+ dns1=dns1, dns2=dns2))
self.post(url, body=json.dumps(req_params),
headers=json_content_type)
@@ -132,6 +134,14 @@ class MelangeConnection(object):
response = self.get(url, headers=json_content_type)
return json.loads(response)['ip_addresses']
+ def get_allocated_ips_for_network(self, network_id, project_id=None):
+ tenant_scope = "/tenants/%s" % project_id if project_id else ""
+ url = ("ipam%(tenant_scope)s/allocated_ip_addresses" % locals())
+ # TODO(bgh): This request fails if you add the ".json" to the end so
+ # it has to call do_request itself. Melange bug?
+ response = self.do_request("GET", url, content_type="")
+ return json.loads(response)['ip_addresses']
+
def deallocate_ips(self, network_id, vif_id, project_id=None):
tenant_scope = "/tenants/%s" % project_id if project_id else ""
diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py
index a0ac10fd3..6f2364a14 100644
--- a/nova/network/quantum/melange_ipam_lib.py
+++ b/nova/network/quantum/melange_ipam_lib.py
@@ -15,8 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from netaddr import IPNetwork
-
+from netaddr import IPNetwork, IPAddress
from nova import db
from nova import exception
from nova import flags
@@ -45,7 +44,7 @@ class QuantumMelangeIPAMLib(object):
def create_subnet(self, context, label, project_id,
quantum_net_id, priority, cidr=None,
- gateway_v6=None, cidr_v6=None,
+ gateway=None, gateway_v6=None, cidr_v6=None,
dns1=None, dns2=None):
"""Contact Melange and create a subnet for any non-NULL
IPv4 or IPv6 subnets.
@@ -59,25 +58,34 @@ class QuantumMelangeIPAMLib(object):
if cidr:
self.m_conn.create_block(quantum_net_id, cidr,
project_id=tenant_id,
+ gateway=gateway,
dns1=dns1, dns2=dns2)
if cidr_v6:
self.m_conn.create_block(quantum_net_id, cidr_v6,
project_id=tenant_id,
+ gateway=gateway_v6,
dns1=dns1, dns2=dns2)
net = {"uuid": quantum_net_id,
"project_id": project_id,
"priority": priority,
"label": label}
+ if FLAGS.quantum_use_dhcp:
+ if cidr:
+ n = IPNetwork(cidr)
+ net['dhcp_start'] = IPAddress(n.first + 2)
+ else:
+ net['dhcp_start'] = None
admin_context = context.elevated()
network = db.network_create_safe(admin_context, net)
def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref):
"""Pass call to allocate fixed IP on to Melange"""
tenant_id = project_id or FLAGS.quantum_default_tenant_id
- self.m_conn.allocate_ip(quantum_net_id,
- vif_ref['uuid'], project_id=tenant_id,
- mac_address=vif_ref['address'])
+ ip = self.m_conn.allocate_ip(quantum_net_id,
+ vif_ref['uuid'], project_id=tenant_id,
+ mac_address=vif_ref['address'])
+ return ip[0]['address']
def get_network_id_by_cidr(self, context, cidr, project_id):
"""Find the Quantum UUID associated with a IPv4 CIDR
@@ -86,6 +94,7 @@ class QuantumMelangeIPAMLib(object):
tenant_id = project_id or FLAGS.quantum_default_tenant_id
all_blocks = self.m_conn.get_blocks(tenant_id)
for b in all_blocks['ip_blocks']:
+ LOG.debug("block: %s" % b)
if b['cidr'] == cidr:
return b['network_id']
raise exception.NotFound(_("No network found for cidr %s" % cidr))
@@ -134,34 +143,43 @@ class QuantumMelangeIPAMLib(object):
return [(network_id, tenant_id)
for priority, network_id, tenant_id in priority_nets]
- def get_subnets_by_net_id(self, context, project_id, net_id):
+ def get_tenant_id_by_net_id(self, context, net_id, vif_id, project_id):
+ ipam_tenant_id = None
+ tenant_ids = [FLAGS.quantum_default_tenant_id, project_id, None]
+ for tid in tenant_ids:
+ try:
+ ips = self.m_conn.get_allocated_ips(net_id, vif_id, tid)
+ except Exception, e:
+ continue
+ ipam_tenant_id = tid
+ break
+ return ipam_tenant_id
+
+ # TODO(bgh): Rename this method .. it's now more of a
+ # "get_subnets_by_net_id_and_vif_id" method, but we could probably just
+ # call it "get_subnets".
+ def get_subnets_by_net_id(self, context, tenant_id, net_id, vif_id):
"""Returns information about the IPv4 and IPv6 subnets
associated with a Quantum Network UUID.
"""
-
- # FIXME(danwent): Melange actually returns the subnet info
- # when we query for a particular interface. We may want to
- # rework the ipam_manager python API to let us take advantage of
- # this, as right now we have to get all blocks and cycle through
- # them.
subnet_v4 = None
subnet_v6 = None
- tenant_id = project_id or FLAGS.quantum_default_tenant_id
- all_blocks = self.m_conn.get_blocks(tenant_id)
- for b in all_blocks['ip_blocks']:
- if b['network_id'] == net_id:
- subnet = {'network_id': b['network_id'],
- 'cidr': b['cidr'],
- 'gateway': b['gateway'],
- 'broadcast': b['broadcast'],
- 'netmask': b['netmask'],
- 'dns1': b['dns1'],
- 'dns2': b['dns2']}
-
- if IPNetwork(b['cidr']).version == 6:
- subnet_v6 = subnet
- else:
- subnet_v4 = subnet
+ ips = self.m_conn.get_allocated_ips(net_id, vif_id, tenant_id)
+
+ for ip_address in ips:
+ block = ip_address['ip_block']
+ print block
+ subnet = {'network_id': block['id'],
+ 'cidr': block['cidr'],
+ 'gateway': block['gateway'],
+ 'broadcast': block['broadcast'],
+ 'netmask': block['netmask'],
+ 'dns1': block['dns1'],
+ 'dns2': block['dns2']}
+ if ip_address['version'] == 4:
+ subnet_v4 = subnet
+ else:
+ subnet_v6 = subnet
return (subnet_v4, subnet_v6)
def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id):
@@ -179,7 +197,7 @@ class QuantumMelangeIPAMLib(object):
project_id, 6)
def _get_ips_by_interface(self, context, net_id, vif_id, project_id,
- ip_version):
+ ip_version):
"""Helper method to fetch v4 or v6 addresses for a particular
virtual interface.
"""
@@ -192,10 +210,16 @@ class QuantumMelangeIPAMLib(object):
"""Confirms that a subnet exists that is associated with the
specified Quantum Network UUID.
"""
+ # TODO(bgh): Would be nice if we could just do something like:
+ # GET /ipam/tenants/{tenant_id}/networks/{network_id}/ instead
+ # of searching through all the blocks. Checking for a 404
+ # will then determine whether it exists.
tenant_id = project_id or FLAGS.quantum_default_tenant_id
- v4_subnet, v6_subnet = self.get_subnets_by_net_id(context, tenant_id,
- quantum_net_id)
- return v4_subnet is not None
+ all_blocks = self.m_conn.get_blocks(tenant_id)
+ for b in all_blocks['ip_blocks']:
+ if b['network_id'] == quantum_net_id:
+ return True
+ return False
def deallocate_ips_by_vif(self, context, project_id, net_id, vif_ref):
"""Deallocate all fixed IPs associated with the specified
@@ -203,3 +227,7 @@ class QuantumMelangeIPAMLib(object):
"""
tenant_id = project_id or FLAGS.quantum_default_tenant_id
self.m_conn.deallocate_ips(net_id, vif_ref['uuid'], tenant_id)
+
+ def get_allocated_ips(self, context, subnet_id, project_id):
+ ips = self.m_conn.get_allocated_ips_for_network(subnet_id, project_id)
+ return [(ip['address'], ip['interface_id']) for ip in ips]
diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py
index 867777af3..ded5bef58 100644
--- a/nova/network/quantum/nova_ipam_lib.py
+++ b/nova/network/quantum/nova_ipam_lib.py
@@ -51,7 +51,7 @@ class QuantumNovaIPAMLib(object):
def create_subnet(self, context, label, tenant_id,
quantum_net_id, priority, cidr=None,
- gateway_v6=None, cidr_v6=None,
+ gateway=None, gateway_v6=None, cidr_v6=None,
dns1=None, dns2=None):
"""Re-use the basic FlatManager create_networks method to
initialize the networks and fixed_ips tables in Nova DB.
@@ -63,7 +63,7 @@ class QuantumNovaIPAMLib(object):
subnet_size = len(netaddr.IPNetwork(cidr))
networks = manager.FlatManager.create_networks(self.net_manager,
admin_context, label, cidr,
- False, 1, subnet_size, cidr_v6,
+ False, 1, subnet_size, cidr_v6, gateway,
gateway_v6, quantum_net_id, None, dns1, dns2)
if len(networks) != 1:
@@ -117,6 +117,7 @@ class QuantumNovaIPAMLib(object):
"""Allocates a single fixed IPv4 address for a virtual interface."""
admin_context = context.elevated()
network = db.network_get_by_uuid(admin_context, quantum_net_id)
+ address = None
if network['cidr']:
address = db.fixed_ip_associate_pool(admin_context,
network['id'],
@@ -124,8 +125,15 @@ class QuantumNovaIPAMLib(object):
values = {'allocated': True,
'virtual_interface_id': vif_rec['id']}
db.fixed_ip_update(admin_context, address, values)
+ return address
- def get_subnets_by_net_id(self, context, tenant_id, net_id):
+ def get_tenant_id_by_net_id(self, context, net_id, vif_id, project_id):
+ """Returns tenant_id for this network. This is only necessary
+ in the melange IPAM case.
+ """
+ return project_id
+
+ def get_subnets_by_net_id(self, context, tenant_id, net_id, _vif_id=None):
"""Returns information about the IPv4 and IPv6 subnets
associated with a Quantum Network UUID.
"""
@@ -177,7 +185,8 @@ class QuantumNovaIPAMLib(object):
such subnet exists.
"""
admin_context = context.elevated()
- db.network_get_by_uuid(admin_context, quantum_net_id)
+ net = db.network_get_by_uuid(admin_context, quantum_net_id)
+ return net is not None
def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref):
"""Deallocate all fixed IPs associated with the specified
@@ -194,3 +203,20 @@ class QuantumNovaIPAMLib(object):
except exception.FixedIpNotFoundForInstance:
LOG.error(_('No fixed IPs to deallocate for vif %s' %
vif_ref['id']))
+
+ def get_allocated_ips(self, context, subnet_id, project_id):
+ """Returns a list of (ip, vif_id) pairs"""
+ admin_context = context.elevated()
+ ips = db.fixed_ip_get_all(admin_context)
+ allocated_ips = []
+ # Get all allocated IPs that are part of this subnet
+ network = db.network_get_by_uuid(admin_context, subnet_id)
+ for ip in ips:
+ # Skip unallocated IPs
+ if not ip['allocated'] == 1:
+ continue
+ if ip['network_id'] == network['id']:
+ vif = db.virtual_interface_get(admin_context,
+ ip['virtual_interface_id'])
+ allocated_ips.append((ip['address'], vif['uuid']))
+ return allocated_ips
diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py
index ce07bc1ab..91c98797c 100644
--- a/nova/network/quantum/quantum_connection.py
+++ b/nova/network/quantum/quantum_connection.py
@@ -79,6 +79,10 @@ class QuantumClientConnection(object):
# Not really an error. Real errors will be propogated to caller
return False
+ def get_networks(self, tenant_id):
+ """Retrieve all networks for this tenant"""
+ return self.client.list_networks(tenant=tenant_id)
+
def create_and_attach_port(self, tenant_id, net_id, interface_id):
"""Creates a Quantum port on the specified network, sets
status to ACTIVE to enable traffic, and attaches the
@@ -102,21 +106,20 @@ class QuantumClientConnection(object):
self.client.detach_resource(net_id, port_id, tenant=tenant_id)
self.client.delete_port(net_id, port_id, tenant=tenant_id)
- def get_port_by_attachment(self, tenant_id, attachment_id):
- """Given a tenant, search for the Quantum network and port
- UUID that has the specified interface-id attachment.
+ def get_port_by_attachment(self, tenant_id, net_id, attachment_id):
+ """Given a tenant and network, search for the port UUID that
+ has the specified interface-id attachment.
"""
# FIXME(danwent): this will be inefficient until the Quantum
# API implements querying a port by the interface-id
- net_list_resdict = self.client.list_networks(tenant=tenant_id)
- for n in net_list_resdict["networks"]:
- net_id = n['id']
- port_list_resdict = self.client.list_ports(net_id,
- tenant=tenant_id)
- for p in port_list_resdict["ports"]:
- port_id = p["id"]
- port_get_resdict = self.client.show_port_attachment(net_id,
+ port_list_resdict = self.client.list_ports(net_id, tenant=tenant_id)
+ for p in port_list_resdict["ports"]:
+ port_id = p["id"]
+ port_get_resdict = self.client.show_port_attachment(net_id,
port_id, tenant=tenant_id)
- if attachment_id == port_get_resdict["attachment"]["id"]:
- return (net_id, port_id)
- return (None, None)
+ # Skip ports without an attachment
+ if "id" not in port_get_resdict["attachment"]:
+ continue
+ if attachment_id == port_get_resdict["attachment"]["id"]:
+ return port_id
+ return None
diff --git a/nova/tests/__init__.py b/nova/tests/__init__.py
index 720d5b0e6..1109dfb70 100644
--- a/nova/tests/__init__.py
+++ b/nova/tests/__init__.py
@@ -63,6 +63,7 @@ def setup():
num_networks=FLAGS.num_networks,
network_size=FLAGS.network_size,
cidr_v6=FLAGS.fixed_range_v6,
+ gateway=FLAGS.gateway,
gateway_v6=FLAGS.gateway_v6,
bridge=FLAGS.flat_network_bridge,
bridge_interface=bridge_interface,
diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py
index c66c38c85..69ac4f2a6 100644
--- a/nova/tests/test_network.py
+++ b/nova/tests/test_network.py
@@ -206,7 +206,7 @@ class FlatNetworkTestCase(test.TestCase):
is_admin=True)
nets = self.network.create_networks(context_admin, 'fake',
'192.168.0.0/24', False, 1,
- 256, None, None, None, None)
+ 256, None, None, None, None, None)
self.assertEqual(1, len(nets))
network = nets[0]
self.assertEqual(3, db.network_count_reserved_ips(context_admin,
@@ -697,7 +697,7 @@ class CommonNetworkTestCase(test.TestCase):
manager = fake_network.FakeNetworkManager()
nets = manager.create_networks(None, 'fake', '192.168.0.0/24',
False, 1, 256, None, None, None,
- None)
+ None, None)
self.assertEqual(1, len(nets))
cidrs = [str(net['cidr']) for net in nets]
self.assertTrue('192.168.0.0/24' in cidrs)
@@ -706,7 +706,7 @@ class CommonNetworkTestCase(test.TestCase):
manager = fake_network.FakeNetworkManager()
nets = manager.create_networks(None, 'fake', '192.168.0.0/24',
False, 2, 128, None, None, None,
- None)
+ None, None)
self.assertEqual(2, len(nets))
cidrs = [str(net['cidr']) for net in nets]
self.assertTrue('192.168.0.0/25' in cidrs)
@@ -721,7 +721,7 @@ class CommonNetworkTestCase(test.TestCase):
self.mox.ReplayAll()
nets = manager.create_networks(None, 'fake', '192.168.0.0/16',
False, 4, 256, None, None, None,
- None)
+ None, None)
self.assertEqual(4, len(nets))
cidrs = [str(net['cidr']) for net in nets]
exp_cidrs = ['192.168.0.0/24', '192.168.1.0/24', '192.168.3.0/24',
@@ -740,7 +740,7 @@ class CommonNetworkTestCase(test.TestCase):
# ValueError: requested cidr (192.168.2.0/24) conflicts with
# existing smaller cidr
args = (None, 'fake', '192.168.2.0/24', False, 1, 256, None, None,
- None, None)
+ None, None, None)
self.assertRaises(ValueError, manager.create_networks, *args)
def test_validate_cidrs_split_smaller_cidr_in_use(self):
@@ -751,7 +751,8 @@ class CommonNetworkTestCase(test.TestCase):
'cidr': '192.168.2.0/25'}])
self.mox.ReplayAll()
nets = manager.create_networks(None, 'fake', '192.168.0.0/16',
- False, 4, 256, None, None, None, None)
+ False, 4, 256, None, None, None, None,
+ None)
self.assertEqual(4, len(nets))
cidrs = [str(net['cidr']) for net in nets]
exp_cidrs = ['192.168.0.0/24', '192.168.1.0/24', '192.168.3.0/24',
@@ -768,7 +769,8 @@ class CommonNetworkTestCase(test.TestCase):
'cidr': '192.168.2.9/29'}])
self.mox.ReplayAll()
nets = manager.create_networks(None, 'fake', '192.168.2.0/24',
- False, 3, 32, None, None, None, None)
+ False, 3, 32, None, None, None, None,
+ None)
self.assertEqual(3, len(nets))
cidrs = [str(net['cidr']) for net in nets]
exp_cidrs = ['192.168.2.32/27', '192.168.2.64/27', '192.168.2.96/27']
@@ -786,7 +788,7 @@ class CommonNetworkTestCase(test.TestCase):
manager.db.network_get_all(ctxt).AndReturn(in_use)
self.mox.ReplayAll()
args = (None, 'fake', '192.168.2.0/24', False, 3, 64, None, None,
- None, None)
+ None, None, None)
# ValueError: Not enough subnets avail to satisfy requested num_
# networks - some subnets in requested range already
# in use
@@ -795,7 +797,7 @@ class CommonNetworkTestCase(test.TestCase):
def test_validate_cidrs_one_in_use(self):
manager = fake_network.FakeNetworkManager()
args = (None, 'fake', '192.168.0.0/24', False, 2, 256, None, None,
- None, None)
+ None, None, None)
# ValueError: network_size * num_networks exceeds cidr size
self.assertRaises(ValueError, manager.create_networks, *args)
@@ -808,13 +810,13 @@ class CommonNetworkTestCase(test.TestCase):
self.mox.ReplayAll()
# ValueError: cidr already in use
args = (None, 'fake', '192.168.0.0/24', False, 1, 256, None, None,
- None, None)
+ None, None, None)
self.assertRaises(ValueError, manager.create_networks, *args)
def test_validate_cidrs_too_many(self):
manager = fake_network.FakeNetworkManager()
args = (None, 'fake', '192.168.0.0/24', False, 200, 256, None, None,
- None, None)
+ None, None, None)
# ValueError: Not enough subnets avail to satisfy requested
# num_networks
self.assertRaises(ValueError, manager.create_networks, *args)
@@ -822,7 +824,8 @@ class CommonNetworkTestCase(test.TestCase):
def test_validate_cidrs_split_partial(self):
manager = fake_network.FakeNetworkManager()
nets = manager.create_networks(None, 'fake', '192.168.0.0/16',
- False, 2, 256, None, None, None, None)
+ False, 2, 256, None, None, None, None,
+ None)
returned_cidrs = [str(net['cidr']) for net in nets]
self.assertTrue('192.168.0.0/24' in returned_cidrs)
self.assertTrue('192.168.1.0/24' in returned_cidrs)
@@ -835,7 +838,7 @@ class CommonNetworkTestCase(test.TestCase):
manager.db.network_get_all(ctxt).AndReturn(fakecidr)
self.mox.ReplayAll()
args = (None, 'fake', '192.168.0.0/24', False, 1, 256, None, None,
- None, None)
+ None, None, None)
# ValueError: requested cidr (192.168.0.0/24) conflicts
# with existing supernet
self.assertRaises(ValueError, manager.create_networks, *args)
@@ -846,7 +849,7 @@ class CommonNetworkTestCase(test.TestCase):
self.stubs.Set(manager, '_create_fixed_ips',
self.fake_create_fixed_ips)
args = [None, 'foo', cidr, None, 1, 256, 'fd00::/48', None, None,
- None]
+ None, None, None]
self.assertTrue(manager.create_networks(*args))
def test_create_networks_cidr_already_used(self):
@@ -857,7 +860,7 @@ class CommonNetworkTestCase(test.TestCase):
manager.db.network_get_all(ctxt).AndReturn(fakecidr)
self.mox.ReplayAll()
args = [None, 'foo', '192.168.0.0/24', None, 1, 256,
- 'fd00::/48', None, None, None]
+ 'fd00::/48', None, None, None, None, None]
self.assertRaises(ValueError, manager.create_networks, *args)
def test_create_networks_many(self):
@@ -866,7 +869,7 @@ class CommonNetworkTestCase(test.TestCase):
self.stubs.Set(manager, '_create_fixed_ips',
self.fake_create_fixed_ips)
args = [None, 'foo', cidr, None, 10, 256, 'fd00::/48', None, None,
- None]
+ None, None, None]
self.assertTrue(manager.create_networks(*args))
def test_get_instance_uuids_by_ip_regex(self):
diff --git a/nova/tests/test_nova_manage.py b/nova/tests/test_nova_manage.py
index b0a76f392..5e124413f 100644
--- a/nova/tests/test_nova_manage.py
+++ b/nova/tests/test_nova_manage.py
@@ -108,11 +108,17 @@ class NetworkCommandsTestCase(test.TestCase):
self.assertEqual(cidr, self.fake_net['cidr'])
return db_fakes.FakeModel(self.fake_net)
+ def fake_network_get_by_uuid(context, uuid):
+ self.assertTrue(context.to_dict()['is_admin'])
+ self.assertEqual(uuid, self.fake_net['uuid'])
+ return db_fakes.FakeModel(self.fake_net)
+
def fake_network_update(context, network_id, values):
self.assertTrue(context.to_dict()['is_admin'])
self.assertEqual(network_id, self.fake_net['id'])
self.assertEqual(values, self.fake_update_value)
self.fake_network_get_by_cidr = fake_network_get_by_cidr
+ self.fake_network_get_by_uuid = fake_network_get_by_uuid
self.fake_network_update = fake_network_update
def tearDown(self):
@@ -131,6 +137,7 @@ class NetworkCommandsTestCase(test.TestCase):
self.assertEqual(kwargs['vlan_start'], 200)
self.assertEqual(kwargs['vpn_start'], 2000)
self.assertEqual(kwargs['cidr_v6'], 'fd00:2::/120')
+ self.assertEqual(kwargs['gateway'], '10.2.0.1')
self.assertEqual(kwargs['gateway_v6'], 'fd00:2::22')
self.assertEqual(kwargs['bridge'], 'br200')
self.assertEqual(kwargs['bridge_interface'], 'eth0')
@@ -149,11 +156,13 @@ class NetworkCommandsTestCase(test.TestCase):
vlan_start=200,
vpn_start=2000,
fixed_range_v6='fd00:2::/120',
+ gateway='10.2.0.1',
gateway_v6='fd00:2::22',
bridge='br200',
bridge_interface='eth0',
dns1='8.8.8.8',
- dns2='8.8.4.4')
+ dns2='8.8.4.4',
+ uuid='aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa')
def test_list(self):
@@ -193,14 +202,14 @@ class NetworkCommandsTestCase(test.TestCase):
self.fake_net = self.net
self.fake_net['project_id'] = None
self.fake_net['host'] = None
- self.stubs.Set(db, 'network_get_by_cidr',
- self.fake_network_get_by_cidr)
+ self.stubs.Set(db, 'network_get_by_uuid',
+ self.fake_network_get_by_uuid)
def fake_network_delete_safe(context, network_id):
self.assertTrue(context.to_dict()['is_admin'])
self.assertEqual(network_id, self.fake_net['id'])
self.stubs.Set(db, 'network_delete_safe', fake_network_delete_safe)
- self.commands.delete(fixed_range=self.fake_net['cidr'])
+ self.commands.delete(uuid=self.fake_net['uuid'])
def test_delete_by_cidr(self):
self.fake_net = self.net
diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py
index fdfc99d49..7d387faaf 100644
--- a/nova/tests/test_quantum.py
+++ b/nova/tests/test_quantum.py
@@ -26,6 +26,8 @@ from nova.network.quantum import manager as quantum_manager
from nova import test
from nova import utils
+import mox
+
LOG = logging.getLogger('nova.tests.quantum_network')
@@ -41,7 +43,7 @@ class FakeQuantumClientConnection(object):
for net_id, n in self.nets.items():
if n['tenant-id'] == tenant_id:
net_ids.append(net_id)
- return net_ids
+ return {'networks': net_ids}
def create_network(self, tenant_id, network_name):
@@ -90,14 +92,22 @@ class FakeQuantumClientConnection(object):
"for tenant %(tenant_id)s" % locals()))
del self.nets[net_id]['ports'][port_id]
- def get_port_by_attachment(self, tenant_id, attachment_id):
- for net_id, n in self.nets.items():
- if n['tenant-id'] == tenant_id:
+ def get_port_by_attachment(self, tenant_id, net_id, attachment_id):
+ for nid, n in self.nets.items():
+ if nid == net_id and n['tenant-id'] == tenant_id:
for port_id, p in n['ports'].items():
if p['attachment-id'] == attachment_id:
- return (net_id, port_id)
+ return port_id
+ return None
+
+ def get_networks(self, tenant_id):
+ nets = []
+ for nid, n in self.nets.items():
+ if n['tenant-id'] == tenant_id:
+ x = {'id': nid}
+ nets.append(x)
+ return {'networks': nets}
- return (None, None)
networks = [{'label': 'project1-net1',
'injected': False,
@@ -184,14 +194,16 @@ class QuantumTestCaseBase(object):
def _create_nets(self):
for n in networks:
ctx = context.RequestContext('user1', n['project_id'])
- self.net_man.create_networks(ctx,
+ nwks = self.net_man.create_networks(ctx,
label=n['label'], cidr=n['cidr'],
multi_host=n['multi_host'],
num_networks=1, network_size=256, cidr_v6=n['cidr_v6'],
+ gateway=n['gateway'],
gateway_v6=n['gateway_v6'], bridge=None,
bridge_interface=None, dns1=n['dns1'],
dns2=n['dns2'], project_id=n['project_id'],
priority=n['priority'])
+ n['uuid'] = nwks[0]['uuid']
def _delete_nets(self):
for n in networks:
@@ -210,6 +222,16 @@ class QuantumTestCaseBase(object):
instance_ref = db.instance_create(ctx,
{"project_id": project_id})
+
+ def func(arg1, arg2):
+ pass
+
+ def func1(arg1):
+ pass
+
+ self.net_man.driver.update_dhcp_hostfile_with_text = func
+ self.net_man.driver.restart_dhcp = func
+ self.net_man.driver.kill_dhcp = func1
nw_info = self.net_man.allocate_for_instance(ctx,
instance_id=instance_ref['id'], host="",
instance_type_id=instance_ref['instance_type_id'],
@@ -249,12 +271,23 @@ class QuantumTestCaseBase(object):
ctx = context.RequestContext('user1', project_id)
net_ids = self.net_man.q_conn.get_networks_for_tenant(project_id)
- requested_networks = [(net_id, None) for net_id in net_ids]
+ requested_networks = [(net_id, None) for net_id in
+ net_ids['networks']]
self.net_man.validate_networks(ctx, requested_networks)
instance_ref = db.instance_create(ctx,
{"project_id": project_id})
+
+ def func(arg1, arg2):
+ pass
+
+ def func1(arg1):
+ pass
+
+ self.net_man.driver.update_dhcp_hostfile_with_text = func
+ self.net_man.driver.restart_dhcp = func
+ self.net_man.driver.kill_dhcp = func1
nw_info = self.net_man.allocate_for_instance(ctx,
instance_id=instance_ref['id'], host="",
instance_type_id=instance_ref['instance_type_id'],