summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorDan Wendlandt <dan@nicira.com>2012-02-13 09:58:14 -0800
committerDan Wendlandt <dan@nicira.com>2012-02-15 10:47:11 -0800
commit1406327ceeb190ef4584116e49424f2c36dc4a91 (patch)
tree2c1c0ed6331e3929f09786a52ac351d4715748e8 /nova
parent4c756df0bd48cc5ccbb672be7f2948ec33b6e17f (diff)
Expand Quantum Manager Unit Tests + Associated Fixes
- Add fake quantum client to support more complete testing of Quantum Mgr - Fix issue related to filters not being appended to Quantum queries - Expand unit tests to check for # vifs, ports created, nic order. - Add direct unit tests for quantum_connection class. - improve error reporting for get_port_by_attachment - remove invalid comment about not supporting floating IPs - Remove E-3 hack that only allowed L3 gateway if DHCP was enabled. Proper way to disable L3 gateway is to use the nova.network.l3.NullL3 driver. - fix delete_network to properly find UUIDs for project specific networks - fix issue with 'requested_networks' not working properly for a provider network. - remove dead chunk of code that is unused because all networks in quantum currently correspond to an entry in the nova db. - make sure validate_networks allows both provider + tenant networks. Update: incorporated feedback from first round of reviews. Change-Id: Id63d27d457c63efe7ea27a7fbd7470a0a5709125
Diffstat (limited to 'nova')
-rw-r--r--nova/network/quantum/client.py8
-rw-r--r--nova/network/quantum/fake_client.py173
-rw-r--r--nova/network/quantum/manager.py119
-rw-r--r--nova/network/quantum/quantum_connection.py15
-rw-r--r--nova/tests/test_quantum.py471
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})