diff options
| author | Jenkins <jenkins@review.openstack.org> | 2012-02-15 21:26:26 +0000 |
|---|---|---|
| committer | Gerrit Code Review <review@openstack.org> | 2012-02-15 21:26:26 +0000 |
| commit | 963d24d87d489aaf4270bb7bdc1a4b2cbb02a83b (patch) | |
| tree | a133267a2e89a0464c423b9d1777c203ef53996a /nova | |
| parent | 5e267199ea64d4d45b668ace4a497c49eb942d9b (diff) | |
| parent | 1406327ceeb190ef4584116e49424f2c36dc4a91 (diff) | |
Merge "Expand Quantum Manager Unit Tests + Associated Fixes"
Diffstat (limited to 'nova')
| -rw-r--r-- | nova/network/quantum/client.py | 8 | ||||
| -rw-r--r-- | nova/network/quantum/fake_client.py | 173 | ||||
| -rw-r--r-- | nova/network/quantum/manager.py | 119 | ||||
| -rw-r--r-- | nova/network/quantum/quantum_connection.py | 15 | ||||
| -rw-r--r-- | nova/tests/test_quantum.py | 471 |
5 files changed, 478 insertions, 308 deletions
diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py index e5a50fc57..f67b6feb8 100644 --- a/nova/network/quantum/client.py +++ b/nova/network/quantum/client.py @@ -114,6 +114,7 @@ class Client(object): :param testing_stub: A class that stubs basic server methods for tests :param key_file: The SSL key file to use if use_ssl is true :param cert_file: The SSL cert file to use if use_ssl is true + :param logger: logging object to be used by client library """ self.host = host self.port = port @@ -238,8 +239,7 @@ class Client(object): @api_call def list_networks(self, filter_ops=None): """Fetches a list of all networks for a tenant""" - url = self.networks_path - return self.do_request("GET", url, params=filter_ops) + return self.do_request("GET", self.networks_path, params=filter_ops) @api_call def show_network_details(self, network): @@ -266,8 +266,8 @@ class Client(object): @api_call def list_ports(self, network, filter_ops=None): """Fetches a list of ports on a given network""" - url = self.ports_path % (network) - return self.do_request("GET", url, params=filter_ops) + return self.do_request("GET", self.ports_path % (network), + params=filter_ops) @api_call def show_port_details(self, network, port): diff --git a/nova/network/quantum/fake_client.py b/nova/network/quantum/fake_client.py new file mode 100644 index 000000000..5d9757548 --- /dev/null +++ b/nova/network/quantum/fake_client.py @@ -0,0 +1,173 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nicira Networks +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# @author: Dan Wendlandt Nicira Networks + +import copy + +from nova import utils +from nova.network.quantum import client + +#TODO(danwent): would be nice to have these functions raise QuantumIOErrors +# to make sure such errors are caught and reported properly + + +# this is a fake quantum client that just stores all data in a nested dict +# +# example: +# +#{'<tenant>': +# { '<net-uuid>' : { 'name' : "t1-net1", +# 'ports' : [ { '<port-uuid'> : +# {'state': 'Active', +# 'attachment': 'iface-id'}, +# { 'state': 'Down', +# 'attachment' : None}}] +# } +# } +# } + + +class FakeClient(object): + """A fake Quantum Client for testing""" + + def __init__(self, logger=None, **kwargs): + """Creates a new client to some service. + :param logger: logging object to be used by client library + """ + self.logger = logger + self.tenant_map = {} + + def _get_tenant_nets(self, tenant): + if tenant is None: + raise Exception("'tenant' cannot be None") + return self.tenant_map.setdefault(tenant, {}) + + def _verify_net(self, network, nets): + if network not in nets: + raise client.QuantumNotFoundException("no network with uuid %s" + % network) + + def _verify_net_and_port(self, network, port, nets): + if network not in nets: + raise client.QuantumNotFoundException("no network with uuid %s" + % network) + if port not in nets[network]['ports']: + raise client.QuantumNotFoundException("no port with uuid %s" + % port) + + def list_networks(self, tenant=None, filter_ops=None): + """Fetches a list of all networks for a tenant""" + nets = self._get_tenant_nets(tenant) + if filter_ops: + raise Exception("Need to implement filters %s in " + "quantum fake client" % filter_ops) + return {"networks": [{"id": uuid} for uuid in nets.keys()]} + + def show_network_details(self, network, tenant=None): + """Fetches the details of a certain network""" + nets = self._get_tenant_nets(tenant) + self._verify_net(network, nets) + return {"network": {"id": network, "name": nets[network]["name"]}} + + def create_network(self, body=None, tenant=None): + """Creates a new network""" + nets = self._get_tenant_nets(tenant) + uuid = str(utils.gen_uuid()) + name = body["network"]["name"] + nets[uuid] = {"ports": {}, "name": name} + return {"network": {"id": uuid}} + + def update_network(self, network, body=None, tenant=None): + """Updates a network""" + nets = self._get_tenant_nets(tenant) + self._verify_net(network, nets) + nets[network]['name'] = body["network"]["name"] + return {"network": {"id": network, "name": nets[networks]["name"]}} + + def delete_network(self, network, tenant=None): + """Deletes the specified network""" + nets = self._get_tenant_nets(tenant) + self._verify_net(network, nets) + del nets[network] + + def list_ports(self, network, filter_ops=None, tenant=None): + """Fetches a list of ports on a given network""" + nets = self._get_tenant_nets(tenant) + self._verify_net(network, nets) + + ports = nets[network]['ports'].items() + if filter_ops and 'attachment' in filter_ops: + a = filter_ops.pop('attachment') + ports = [p for p in ports if p[1]['attachment'] == a] + + if filter_ops: + raise Exception("Need to implement files %s in " + "quantum fake client" % filter_ops) + + return {"ports": [{'id': p[0]} for p in ports]} + + def show_port_details(self, network, port, tenant=None): + """Fetches the details of a certain port""" + nets = self._get_tenant_nets(tenant) + self._verify_net_and_port(network, port, nets) + p = nets[network]['ports'][port] + return {"port": {"state": p["state"], "id": port}} + + def create_port(self, network, body=None, tenant=None): + """Creates a new port on a given network""" + nets = self._get_tenant_nets(tenant) + self._verify_net(network, nets) + uuid = str(utils.gen_uuid()) + nets[network]['ports'][uuid] = {'attachment': None, + 'state': body['port']['state']} + return {"port": {"id": uuid}} + + def delete_port(self, network, port, tenant=None): + """Deletes the specified port from a network""" + nets = self._get_tenant_nets(tenant) + self._verify_net_and_port(network, port, nets) + del nets[network]['ports'][port] + + def set_port_state(self, network, port, body=None, tenant=None): + """Sets the state of the specified port""" + nets = self._get_tenant_nets(tenant) + self._verify_net_and_port(network, port, nets) + + new_state = body['port']['state'] + nets[network]['ports'][port]['state'] = new_state + return {"port": {"state": new_state}} + + def show_port_attachment(self, network, port, tenant=None): + """Fetches the attachment-id associated with the specified port""" + nets = self._get_tenant_nets(tenant) + self._verify_net_and_port(network, port, nets) + p = nets[network]['ports'][port] + if p['attachment'] is not None: + return {"attachment": {"id": p['attachment']}} + else: + return {"attachment": {}} + + def attach_resource(self, network, port, body=None, tenant=None): + nets = self._get_tenant_nets(tenant) + self._verify_net_and_port(network, port, nets) + nets[network]['ports'][port]['attachment'] = body['attachment']['id'] + + def detach_resource(self, network, port, tenant=None): + """Removes the attachment-id of the specified port""" + nets = self._get_tenant_nets(tenant) + self._verify_net_and_port(network, port, nets) + nets[network]['ports'][port]['attachment'] = None diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index 76c263ff6..267c70b8c 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -64,11 +64,6 @@ class QuantumManager(manager.FloatingIP, manager.FlatManager): For IP Address management, QuantumManager can be configured to use either Nova's local DB or the Melange IPAM service. - - Currently, the QuantumManager does NOT support: - * floating IPs - - Support for these capabilities are targted for future releases. """ DHCP = FLAGS.quantum_use_dhcp @@ -91,9 +86,6 @@ class QuantumManager(manager.FloatingIP, manager.FlatManager): super(QuantumManager, self).__init__(*args, **kwargs) def init_host(self): - # Don't call into self.driver (linux_net) unless dhcp is enabled - if not FLAGS.quantum_use_dhcp: - return # Initialize general L3 networking self.l3driver.initialize() # Initialize floating ip support (only works for nova ipam currently) @@ -220,9 +212,6 @@ class QuantumManager(manager.FloatingIP, manager.FlatManager): return [{'uuid': quantum_net_id}] - def _generate_gw_dev(self, network_id): - return "gw-" + str(network_id[0:11]) - def delete_network(self, context, fixed_range, uuid): """Lookup network by uuid, delete both the IPAM subnet and the corresponding Quantum network. @@ -230,34 +219,45 @@ class QuantumManager(manager.FloatingIP, manager.FlatManager): The fixed_range parameter is kept here for interface compatibility but is not used. """ - quantum_net_id = uuid - project_id = context.project_id - if project_id is None: - # If nothing was found we default to this - project_id = FLAGS.quantum_default_tenant_id + net_ref = db.network_get_by_uuid(context.elevated(), uuid) + project_id = net_ref['project_id'] q_tenant_id = project_id or FLAGS.quantum_default_tenant_id + net_uuid = net_ref['uuid'] + # Check for any attached ports on the network and fail the deletion if # there is anything but the gateway port attached. If it is only the # gateway port, unattach and delete it. - ports = self.q_conn.get_attached_ports(q_tenant_id, quantum_net_id) - if len(ports) > 1: - raise Exception(_("Network %s in use, cannot delete" % - (quantum_net_id))) - LOG.debug("Ports currently on network: %s" % ports) - for p in ports: - if p["attachment"].startswith("gw-"): - self.q_conn.detach_and_delete_port(q_tenant_id, - quantum_net_id, - p['port-id']) + ports = self.q_conn.get_attached_ports(q_tenant_id, net_uuid) + num_ports = len(ports) + gw_interface_id = self.driver.get_dev(net_ref) + gw_port_uuid = None + if gw_interface_id is not None: + gw_port_uuid = self.q_conn.get_port_by_attachment(q_tenant_id, + net_uuid, gw_interface_id) + + if gw_port_uuid: + num_ports -= 1 + + if num_ports > 0: + raise Exception(_("Network %s has active ports, cannot delete" + % (net_uuid))) + + # only delete gw ports if we are going to finish deleting network + if gw_port_uuid: + self.q_conn.detach_and_delete_port(q_tenant_id, + net_uuid, + gw_port_uuid) + # Now we can delete the network - self.q_conn.delete_network(q_tenant_id, quantum_net_id) - LOG.debug("Deleting network for tenant: %s" % project_id) - self.ipam.delete_subnets_by_net_id(context, quantum_net_id, - project_id) + self.q_conn.delete_network(q_tenant_id, net_uuid) + LOG.debug("Deleting network %s for tenant: %s" % \ + (net_uuid, q_tenant_id)) + self.ipam.delete_subnets_by_net_id(context, net_uuid, project_id) # Get rid of dnsmasq if FLAGS.quantum_use_dhcp: - dev = self._generate_gw_dev(quantum_net_id) - self.driver.kill_dhcp(dev) + dev = self.driver.get_dev(net_ref) + if self.driver._device_exists(dev): + self.driver.kill_dhcp(dev) def allocate_for_instance(self, context, **kwargs): """Called by compute when it is creating a new VM. @@ -289,12 +289,20 @@ class QuantumManager(manager.FloatingIP, manager.FlatManager): LOG.debug(_("network allocations for instance %s"), project_id) requested_networks = kwargs.get('requested_networks') - if requested_networks: - net_proj_pairs = [(net_id, project_id) - for (net_id, _i) in requested_networks] - else: - net_proj_pairs = self.ipam.get_project_and_global_net_ids(context, + net_proj_pairs = self.ipam.get_project_and_global_net_ids(context, project_id) + if requested_networks: + # need to figure out if a requested network is owned + # by the tenant, or by the provider + # Note: these are the only possible options, as the compute + # API already validated networks using validate_network() + proj_net_ids = set([p[0] for p in net_proj_pairs if p[1]]) + net_proj_pairs = [] + for net_id, _i in requested_networks: + if net_id in proj_net_ids: + net_proj_pairs.append((net_id, project_id)) + else: + net_proj_pairs.append((net_id, None)) # Create a port via quantum and attach the vif for (quantum_net_id, net_tenant_id) in net_proj_pairs: @@ -314,23 +322,6 @@ class QuantumManager(manager.FloatingIP, manager.FlatManager): # 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": net_tenant_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} # TODO(tr3buchet): broken. Virtual interfaces require an integer # network ID and it is not nullable @@ -401,7 +392,7 @@ class QuantumManager(manager.FloatingIP, manager.FlatManager): network_ref['broadcast'] = netaddr.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]) + interface_id = self.driver.get_dev(network_ref) 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. @@ -411,20 +402,18 @@ class QuantumManager(manager.FloatingIP, manager.FlatManager): 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 self.l3driver.initialize_gateway(network_ref) - dev = self.driver.get_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(context, dev, network_ref) + self.driver.update_dhcp_hostfile_with_text(interface_id, hosts) + self.driver.restart_dhcp(context, interface_id, network_ref) def add_virtual_interface(self, context, instance_id, network_id): # If we're not using melange, use the default means... @@ -544,8 +533,8 @@ class QuantumManager(manager.FloatingIP, manager.FlatManager): self.ipam.deallocate_ips_by_vif(context, ipam_tenant_id, net_id, vif_ref) - db.virtual_interface_delete(admin_context, vif_ref['id']) + db.virtual_interface_delete(admin_context, vif_ref['id']) # If DHCP is enabled on this network then we need to update the # leases and restart the server. if FLAGS.quantum_use_dhcp: @@ -579,7 +568,7 @@ class QuantumManager(manager.FloatingIP, manager.FlatManager): network_ref['dhcp_start'] = netaddr.IPAddress(n.first + 2) network_ref['broadcast'] = netaddr.IPAddress(n.broadcast) network_ref['gateway'] = netaddr.IPAddress(n.first + 1) - dev = self._generate_gw_dev(network_ref['uuid']) + dev = self.driver.get_dev(network_ref) # And remove the dhcp mappings for the subnet hosts = self.get_dhcp_hosts_text(context, subnet['network_id'], project_id) @@ -604,7 +593,11 @@ class QuantumManager(manager.FloatingIP, manager.FlatManager): 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): + is_tenant_net = self.q_conn.network_exists(project_id, net_id) + is_provider_net = self.q_conn.network_exists( + FLAGS.quantum_default_tenant_id, + net_id) + if not (is_tenant_net or is_provider_net): raise exception.NetworkNotFound(network_id=net_id) # NOTE(bgh): deallocate_for_instance will take care of this.. The reason diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py index b94119ed1..ad14d932c 100644 --- a/nova/network/quantum/quantum_connection.py +++ b/nova/network/quantum/quantum_connection.py @@ -47,9 +47,12 @@ class QuantumClientConnection(object): version of this class for unit tests. """ - def __init__(self): + def __init__(self, client=None): """Initialize Quantum client class based on flags.""" - self.client = quantum_client.Client(FLAGS.quantum_connection_host, + if client: + self.client = client + else: + self.client = quantum_client.Client(FLAGS.quantum_connection_host, FLAGS.quantum_connection_port, format="json", logger=LOG) @@ -127,11 +130,11 @@ class QuantumClientConnection(object): return None port_list_len = len(port_list) - if port_list_len != 1: - LOG.error("Expected single port with attachment " - "%(attachment_id)s, found %(port_list_len)s" % locals()) - if port_list_len >= 1: + if port_list_len == 1: return port_list[0]['id'] + elif port_list_len > 1: + raise Exception("Expected single port with attachment " + "%(attachment_id)s, found %(port_list_len)s" % locals()) return None def get_attached_ports(self, tenant_id, network_id): diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 96c259bd5..cf15cc02f 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2011 Nicira, Inc. +# Copyright 2011,2012 Nicira, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -20,118 +20,34 @@ from nova import db from nova.db.sqlalchemy import models from nova.db.sqlalchemy.session import get_session from nova import exception +from nova import flags from nova import log as logging +from nova.network.quantum import client as quantum_client +from nova.network.quantum import fake_client from nova.network.quantum import manager as quantum_manager from nova.network.quantum import melange_connection +from nova.network.quantum import quantum_connection from nova import test from nova import utils from nova.network import manager LOG = logging.getLogger(__name__) - - -# this class can be used for unit functional/testing on nova, -# as it does not actually make remote calls to the Quantum service -class FakeQuantumClientConnection(object): - - def __init__(self): - self.nets = {} - - def get_networks_for_tenant(self, tenant_id): - net_ids = [] - for net_id, n in self.nets.items(): - if n['tenant-id'] == tenant_id: - net_ids.append(net_id) - return {'networks': net_ids} - - def create_network(self, tenant_id, network_name, **kwargs): - - uuid = str(utils.gen_uuid()) - self.nets[uuid] = {'net-name': network_name, - 'tenant-id': tenant_id, - 'ports': {}} - return uuid - - def delete_network(self, tenant_id, net_id): - if self.nets[net_id]['tenant-id'] == tenant_id: - del self.nets[net_id] - - def network_exists(self, tenant_id, net_id): - try: - return self.nets[net_id]['tenant-id'] == tenant_id - except KeyError: - return False - - def get_network_name(self, tenant_id, net_id): - return self.nets[net_id]['net-name'] - - def _confirm_not_attached(self, interface_id): - for n in self.nets.values(): - for p in n['ports'].values(): - if p['attachment-id'] == interface_id: - raise Exception(_("interface '%s' is already attached" % - interface_id)) - - def create_and_attach_port(self, tenant_id, net_id, interface_id, - **kwargs): - if not self.network_exists(tenant_id, net_id): - raise Exception( - _("network %(net_id)s does not exist for tenant %(tenant_id)" - % locals())) - - self._confirm_not_attached(interface_id) - uuid = str(utils.gen_uuid()) - self.nets[net_id]['ports'][uuid] = \ - {"port-state": "ACTIVE", - "attachment-id": interface_id} - - def detach_and_delete_port(self, tenant_id, net_id, port_id): - if not self.network_exists(tenant_id, net_id): - raise exception.NotFound( - _("network %(net_id)s does not exist " - "for tenant %(tenant_id)s" % locals())) - del self.nets[net_id]['ports'][port_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 port_id - return None - - def get_attached_ports(self, tenant_id, net_id): - ports = [] - 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(): - ports.append({'port-id': port_id, - 'attachment': p['attachment-id']}) - return ports - - 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} +FLAGS = flags.FLAGS networks = [{'label': 'project1-net1', 'injected': False, 'multi_host': False, - 'cidr': '192.168.0.0/24', - 'cidr_v6': '2001:1db8::/64', - 'gateway_v6': '2001:1db8::1', + 'cidr': '100.168.0.0/24', + 'cidr_v6': '100:1db8::/64', + 'gateway_v6': '100:1db8::1', 'netmask_v6': '64', 'netmask': '255.255.255.0', 'bridge': None, 'bridge_interface': None, - 'gateway': '192.168.0.1', - 'broadcast': '192.168.0.255', - 'dns1': '192.168.0.1', - 'dns2': '192.168.0.2', + 'gateway': '100.168.0.1', + 'broadcast': '100.168.0.255', + 'dns1': '8.8.8.8', 'vlan': None, 'host': None, 'vpn_public_address': None, @@ -140,17 +56,16 @@ networks = [{'label': 'project1-net1', {'label': 'project2-net1', 'injected': False, 'multi_host': False, - 'cidr': '192.168.1.0/24', - 'cidr_v6': '2001:1db9::/64', - 'gateway_v6': '2001:1db9::1', + 'cidr': '101.168.1.0/24', + 'cidr_v6': '101:1db9::/64', + 'gateway_v6': '101:1db9::1', 'netmask_v6': '64', 'netmask': '255.255.255.0', 'bridge': None, 'bridge_interface': None, - 'gateway': '192.168.1.1', - 'broadcast': '192.168.1.255', - 'dns1': '192.168.0.1', - 'dns2': '192.168.0.2', + 'gateway': '101.168.1.1', + 'broadcast': '101.168.1.255', + 'dns1': '8.8.8.8', 'vlan': None, 'host': None, 'project_id': 'fake_project2', @@ -158,17 +73,16 @@ networks = [{'label': 'project1-net1', {'label': "public", 'injected': False, 'multi_host': False, - 'cidr': '10.0.0.0/24', - 'cidr_v6': '2001:1dba::/64', - 'gateway_v6': '2001:1dba::1', + 'cidr': '102.0.0.0/24', + 'cidr_v6': '102:1dba::/64', + 'gateway_v6': '102:1dba::1', 'netmask_v6': '64', 'netmask': '255.255.255.0', 'bridge': None, 'bridge_interface': None, - 'gateway': '10.0.0.1', - 'broadcast': '10.0.0.255', - 'dns1': '10.0.0.1', - 'dns2': '10.0.0.2', + 'gateway': '102.0.0.1', + 'broadcast': '102.0.0.255', + 'dns1': '8.8.8.8', 'vlan': None, 'host': None, 'project_id': None, @@ -176,31 +90,99 @@ networks = [{'label': 'project1-net1', {'label': "project2-net2", 'injected': False, 'multi_host': False, - 'cidr': '9.0.0.0/24', - 'cidr_v6': '2001:1dbb::/64', - 'gateway_v6': '2001:1dbb::1', + 'cidr': '103.0.0.0/24', + 'cidr_v6': '103:1dbb::/64', + 'gateway_v6': '103:1dbb::1', 'netmask_v6': '64', 'netmask': '255.255.255.0', 'bridge': None, 'bridge_interface': None, - 'gateway': '9.0.0.1', - 'broadcast': '9.0.0.255', - 'dns1': '9.0.0.1', - 'dns2': '9.0.0.2', + 'gateway': '103.0.0.1', + 'broadcast': '103.0.0.255', + 'dns1': '8.8.8.8', 'vlan': None, 'host': None, 'project_id': "fake_project2", 'priority': 2}] -# this is a base class to be used by all other Quantum Test classes +class QuantumConnectionTestCase(test.TestCase): + + def test_connection(self): + fc = fake_client.FakeClient(LOG) + qc = quantum_connection.QuantumClientConnection(client=fc) + t = "tenant1" + net1_name = "net1" + net1_uuid = qc.create_network(t, net1_name) + self.assertEquals(net1_name, qc.get_network_name(t, net1_uuid)) + self.assertTrue(qc.network_exists(t, net1_uuid)) + self.assertFalse(qc.network_exists(t, "fake-uuid")) + self.assertFalse(qc.network_exists("fake-tenant", net1_uuid)) + + nets = qc.get_networks(t)['networks'] + self.assertEquals(len(nets), 1) + self.assertEquals(nets[0]['id'], net1_uuid) + + num_ports = 10 + for i in range(0, num_ports): + qc.create_and_attach_port(t, net1_uuid, + 'iface' + str(i), state='ACTIVE') + + self.assertEquals(len(qc.get_attached_ports(t, net1_uuid)), num_ports) + + for i in range(0, num_ports): + port_uuid = qc.get_port_by_attachment(t, net1_uuid, + 'iface' + str(i)) + self.assertTrue(port_uuid) + qc.detach_and_delete_port(t, net1_uuid, port_uuid) + + self.assertEquals(len(qc.get_attached_ports(t, net1_uuid)), 0) + + # test port not found + qc.create_and_attach_port(t, net1_uuid, 'foo', state='ACTIVE') + port_uuid = qc.get_port_by_attachment(t, net1_uuid, 'foo') + qc.detach_and_delete_port(t, net1_uuid, port_uuid) + self.assertRaises(quantum_client.QuantumNotFoundException, + qc.detach_and_delete_port, t, + net1_uuid, port_uuid) + + qc.delete_network(t, net1_uuid) + self.assertFalse(qc.network_exists(t, net1_uuid)) + self.assertEquals(len(qc.get_networks(t)['networks']), 0) + + self.assertRaises(quantum_client.QuantumNotFoundException, + qc.get_network_name, t, net1_uuid) + + +# this is a base class to be used by other QuantumManager Test classes class QuantumNovaTestCase(test.TestCase): + def setUp(self): super(QuantumNovaTestCase, self).setUp() + self.flags(quantum_use_dhcp=True) + self.flags(l3_lib="nova.network.l3.LinuxNetL3") + linuxdrv = "nova.network.linux_net.LinuxOVSInterfaceDriver" + self.flags(linuxnet_interface_driver=linuxdrv) + fc = fake_client.FakeClient(LOG) + qc = quantum_connection.QuantumClientConnection(client=fc) + self.net_man = quantum_manager.QuantumManager( ipam_lib="nova.network.quantum.nova_ipam_lib", - q_conn=FakeQuantumClientConnection()) + q_conn=qc) + + def func(arg1, arg2): + pass + + def func2(arg1, arg2, arg3): + pass + + def func1(arg1): + pass + + self.net_man.driver.update_dhcp_hostfile_with_text = func + self.net_man.driver.restart_dhcp = func2 + self.net_man.driver.kill_dhcp = func1 # Tests seem to create some networks by default, which # we don't want. So we delete them. @@ -219,6 +201,8 @@ class QuantumNovaTestCase(test.TestCase): for fip_ref in result: session.delete(fip_ref) + self.net_man.init_host() + def _create_network(self, n): ctx = context.RequestContext('user1', n['project_id']) nwks = self.net_man.create_networks( @@ -230,13 +214,12 @@ class QuantumNovaTestCase(test.TestCase): 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'] -class QuantumNovaIPAMTestCase(QuantumNovaTestCase): +class QuantumManagerTestCase(QuantumNovaTestCase): def test_create_and_delete_nets(self): self._create_nets() self._delete_nets() @@ -248,143 +231,131 @@ class QuantumNovaIPAMTestCase(QuantumNovaTestCase): def _delete_nets(self): for n in networks: ctx = context.RequestContext('user1', n['project_id']) - db_nets = db.network_get_all(ctx.elevated()) - for x in db_nets: - if x['label'] == n['label']: - n['uuid'] = x['uuid'] self.net_man.delete_network(ctx, None, n['uuid']) + self.assertRaises(exception.NoNetworksFound, + db.network_get_all, ctx.elevated()) - def test_allocate_and_deallocate_instance_static(self): - self._create_nets() + def _validate_nw_info(self, nw_info, expected_net_labels): - project_id = "fake_project1" - ctx = context.RequestContext('user1', project_id) + self.assertEquals(len(nw_info), len(expected_net_labels)) - instance_ref = db.instance_create(ctx, - {"project_id": project_id}) + ctx = context.RequestContext('user1', 'foo').elevated() + all_net_map = {} + for n in db.network_get_all(ctx): + all_net_map[n['label']] = n - def func(arg1, arg2): - pass + for i in range(0, len(nw_info)): + vif = nw_info[i] + net = all_net_map[expected_net_labels[i]] - def func2(arg1, arg2, arg3): - pass + # simple test assumes that each starting prefix is unique + expected_v4_cidr_start = net['cidr'].split(".")[0].lower() + expected_v6_cidr_start = net['cidr_v6'].split(":")[0].lower() - def func1(arg1): - pass + for subnet in vif['network']['subnets']: + addr = subnet['ips'][0]['address'] + if subnet['version'] == 4: + address_start = addr.split(".")[0].lower() + self.assertTrue(expected_v4_cidr_start, address_start) + else: + address_start = addr.split(":")[0].lower() + self.assertTrue(expected_v6_cidr_start, address_start) + + # confirm that there is a DHCP device on corresponding net + for l in expected_net_labels: + n = all_net_map[l] + tenant_id = (n['project_id'] or + FLAGS.quantum_default_tenant_id) + ports = self.net_man.q_conn.get_attached_ports( + tenant_id, n['uuid']) + self.assertEquals(len(ports), 2) # gw + instance VIF + + # make sure we aren't allowed to delete network with + # active port + self.assertRaises(Exception, self.net_man.delete_network, + ctx, None, n['uuid']) + + def _check_vifs(self, expect_num_vifs): + ctx = context.RequestContext('user1', "").elevated() + self.assertEqual(len(db.virtual_interface_get_all(ctx)), + expect_num_vifs) + + def _allocate_and_deallocate_instance(self, project_id, requested_networks, + expected_labels): + + ctx = context.RequestContext('user1', project_id) + self._check_vifs(0) + + instance_ref = db.instance_create(ctx, + {"project_id": project_id}) - self.net_man.driver.update_dhcp_hostfile_with_text = func - self.net_man.driver.restart_dhcp = func2 - self.net_man.driver.kill_dhcp = func1 nw_info = self.net_man.allocate_for_instance(ctx.elevated(), instance_id=instance_ref['id'], host="", rxtx_factor=3, - project_id=project_id) + project_id=project_id, + requested_networks=requested_networks) + + self._check_vifs(len(nw_info)) - self.assertEquals(len(nw_info), 2) + self._validate_nw_info(nw_info, expected_labels) - cidrs = ['10.', '192.'] - addrs = ['10.', '192.'] - cidrs_v6 = ['2001:1dba:', '2001:1db8:'] - addrs_v6 = ['2001:1dba:', '2001:1db8:'] + nw_info = self.net_man.get_instance_nw_info(ctx, instance_ref['id'], + instance_ref['uuid'], + instance_ref['instance_type_id'], "") - def check_for_startswith(choices, choice): - for v in choices: - if choice.startswith(v): - choices.remove(v) - return True - return False + self._check_vifs(len(nw_info)) + self._validate_nw_info(nw_info, expected_labels) - # we don't know which order the NICs will be in until we - # introduce the notion of priority + port_net_pairs = [] for vif in nw_info: - for subnet in vif['network']['subnets']: - cidr = subnet['cidr'].lower() - if subnet['version'] == 4: - # v4 cidr - self.assertTrue(check_for_startswith(cidrs, cidr)) - # v4 address - address = subnet['ips'][0]['address'] - self.assertTrue(check_for_startswith(addrs, address)) - else: - # v6 cidr - self.assertTrue(check_for_startswith(cidrs_v6, cidr)) - # v6 address - address = subnet['ips'][0]['address'] - self.assertTrue(check_for_startswith(addrs_v6, address)) + nid = vif['network']['id'] + pid = self.net_man.q_conn.get_port_by_attachment( + project_id, nid, vif['id']) + if pid is None: + pid = self.net_man.q_conn.get_port_by_attachment( + FLAGS.quantum_default_tenant_id, + nid, vif['id']) + self.assertTrue(pid is not None) + port_net_pairs.append((pid, nid)) self.net_man.deallocate_for_instance(ctx, instance_id=instance_ref['id'], project_id=project_id) + for pid, nid in port_net_pairs: + self.assertRaises(quantum_client.QuantumNotFoundException, + self.net_man.q_conn.detach_and_delete_port, + project_id, nid, pid) + self.assertRaises(quantum_client.QuantumNotFoundException, + self.net_man.q_conn.detach_and_delete_port, + FLAGS.quantum_default_tenant_id, nid, pid) + + self._check_vifs(0) + + def test_allocate_and_deallocate_instance_static(self): + self._create_nets() + self._allocate_and_deallocate_instance("fake_project1", None, + ['public', 'project1-net1']) self._delete_nets() def test_allocate_and_deallocate_instance_dynamic(self): + self._create_nets() project_id = "fake_project2" 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['networks']] + all_valid_networks = self.net_man.ipam.get_project_and_global_net_ids( + ctx, project_id) + requested_networks = [(n[0], None) for n in all_valid_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 - - def func2(arg1, arg2, arg3): - pass - - self.net_man.driver.update_dhcp_hostfile_with_text = func - self.net_man.driver.restart_dhcp = func2 - self.net_man.driver.kill_dhcp = func1 - nw_info = self.net_man.allocate_for_instance(ctx, - instance_id=instance_ref['id'], host="", - rxtx_factor=3, - project_id=project_id, - requested_networks=requested_networks) - - self.assertEquals(len(nw_info), 2) - - cidrs = ['9.', '192.'] - addrs = ['9.', '192.'] - cidrs_v6 = ['2001:1dbb:', '2001:1db9:'] - addrs_v6 = ['2001:1dbb:', '2001:1db9:'] - - def check_for_startswith(choices, choice): - for v in choices: - if choice.startswith(v): - choices.remove(v) - return True - - # we don't know which order the NICs will be in until we - # introduce the notion of priority - for vif in nw_info: - for subnet in vif['network']['subnets']: - cidr = subnet['cidr'].lower() - if subnet['version'] == 4: - # v4 cidr - self.assertTrue(check_for_startswith(cidrs, cidr)) - # v4 address - address = subnet['ips'][0]['address'] - self.assertTrue(check_for_startswith(addrs, address)) - else: - # v6 cidr - self.assertTrue(check_for_startswith(cidrs_v6, cidr)) - # v6 address - address = subnet['ips'][0]['address'] - self.assertTrue(check_for_startswith(addrs_v6, address)) - - self.net_man.deallocate_for_instance(ctx, - instance_id=instance_ref['id'], - project_id=project_id) + label_map = {} + for n in db.network_get_all(ctx.elevated()): + label_map[n['uuid']] = n['label'] + expected_labels = [label_map[uid] for uid, _i in requested_networks] + self._allocate_and_deallocate_instance(project_id, requested_networks, + expected_labels) self._delete_nets() def test_validate_bad_network(self): @@ -392,6 +363,32 @@ class QuantumNovaIPAMTestCase(QuantumNovaTestCase): self.assertRaises(exception.NetworkNotFound, self.net_man.validate_networks, ctx, [("", None)]) + def test_create_net_external_uuid(self): + """Tests use case where network can be created directly via + Quantum API, then the UUID is passed in via nova-manage""" + project_id = "foo_project" + ctx = context.RequestContext('user1', project_id) + net_id = self.net_man.q_conn.create_network(project_id, 'net1') + self.net_man.create_networks( + ctx, + label='achtungbaby', + cidr="9.9.9.0/24", + multi_host=False, + num_networks=1, + network_size=256, + cidr_v6=None, + gateway="9.9.9.1", + gateway_v6=None, + bridge=None, + bridge_interface=None, + dns1="8.8.8.8", + project_id=project_id, + priority=9, + uuid=net_id) + net = db.network_get_by_uuid(ctx.elevated(), net_id) + self.assertTrue(net is not None) + self.assertEquals(net['uuid'], net_id) + class QuantumNovaMACGenerationTestCase(QuantumNovaTestCase): def test_local_mac_address_creation(self): @@ -403,8 +400,9 @@ class QuantumNovaMACGenerationTestCase(QuantumNovaTestCase): ctx = context.RequestContext('user1', project_id) self._create_network(networks[0]) - net_ids = self.net_man.q_conn.get_networks_for_tenant(project_id) - requested_networks = [(net_id, None) for net_id in net_ids['networks']] + all_valid_networks = self.net_man.ipam.get_project_and_global_net_ids( + ctx, project_id) + requested_networks = [(n[0], None) for n in all_valid_networks] instance_ref = db.api.instance_create(ctx, {"project_id": project_id}) @@ -424,8 +422,9 @@ class QuantumNovaMACGenerationTestCase(QuantumNovaTestCase): ctx = context.RequestContext('user1', project_id) self._create_network(networks[0]) - net_ids = self.net_man.q_conn.get_networks_for_tenant(project_id) - requested_networks = [(net_id, None) for net_id in net_ids['networks']] + all_valid_networks = self.net_man.ipam.get_project_and_global_net_ids( + ctx, project_id) + requested_networks = [(n[0], None) for n in all_valid_networks] instance_ref = db.api.instance_create(ctx, {"project_id": project_id}) @@ -448,8 +447,9 @@ class QuantumNovaPortSecurityTestCase(QuantumNovaTestCase): ctx = context.RequestContext('user1', project_id) self._create_network(networks[0]) - net_ids = self.net_man.q_conn.get_networks_for_tenant(project_id) - requested_networks = [(net_id, None) for net_id in net_ids['networks']] + all_valid_networks = self.net_man.ipam.get_project_and_global_net_ids( + ctx, project_id) + requested_networks = [(n[0], None) for n in all_valid_networks] instance_ref = db.api.instance_create(ctx, {"project_id": project_id}) @@ -483,8 +483,9 @@ class QuantumNovaPortSecurityTestCase(QuantumNovaTestCase): ctx = context.RequestContext('user1', project_id) self._create_network(networks[0]) - net_ids = self.net_man.q_conn.get_networks_for_tenant(project_id) - requested_networks = [(net_id, None) for net_id in net_ids['networks']] + all_valid_networks = self.net_man.ipam.get_project_and_global_net_ids( + ctx, project_id) + requested_networks = [(n[0], None) for n in all_valid_networks] instance_ref = db.api.instance_create(ctx, {"project_id": project_id}) |
