diff options
author | Brad Hall <brad@nicira.com> | 2011-11-04 20:11:53 -0700 |
---|---|---|
committer | Brad Hall <brad@nicira.com> | 2011-11-04 20:11:53 -0700 |
commit | 38172d55876e78ff3c4326368de9ea9ddb99e76b (patch) | |
tree | 69ddf240eb08fc2847fa194c4b72db29da5ca078 | |
parent | f89f27b184eb950f4487002a821b2e9c0f8315c7 (diff) | |
download | nova-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-x | bin/nova-dhcpbridge | 3 | ||||
-rwxr-xr-x | bin/nova-manage | 9 | ||||
-rwxr-xr-x | nova/network/linux_net.py | 63 | ||||
-rw-r--r-- | nova/network/manager.py | 9 | ||||
-rw-r--r-- | nova/network/quantum/client.py | 2 | ||||
-rw-r--r-- | nova/network/quantum/manager.py | 255 | ||||
-rw-r--r-- | nova/network/quantum/melange_connection.py | 18 | ||||
-rw-r--r-- | nova/network/quantum/melange_ipam_lib.py | 94 | ||||
-rw-r--r-- | nova/network/quantum/nova_ipam_lib.py | 34 | ||||
-rw-r--r-- | nova/network/quantum/quantum_connection.py | 31 | ||||
-rw-r--r-- | nova/tests/__init__.py | 1 | ||||
-rw-r--r-- | nova/tests/test_network.py | 35 | ||||
-rw-r--r-- | nova/tests/test_nova_manage.py | 17 | ||||
-rw-r--r-- | nova/tests/test_quantum.py | 49 |
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'], |