This code contains contains a new NetworkManager class that can leverage Quantum + Melange.
Thierry suggested that I merge prop this to get feedback, even though it is too late to get a branch of this size in for D-4. Feedback will help determine whether we can get this code in during the Diablo time frame or not. This branch was developed stacked on top of the melange branch, which is still not merged, but I am merge-propping it without that branch so that the diff is correct. The vast majority of the code is in nova/network/quantum and is only invoked if the network manager is set to nova.network.quantum.QuantumManager In addition to implementing networks with Quantum instead of the linux bridge, the QuantumManager also provides a new, more flexible model of associating vNICs with networks. It supports the creation of networks that are specific to a project and networks that are "global". When a VM is created, it gets a vNIC for each project network, as well as each global network. For example, this could support giving a VM a "public" vNIC on a shared network and a "private" vNIC on a tenant-specific network. The branch also implements Tushar's 'os-create-server-ext' extension, so that the server create API call can indicate the set of networks the VM should be attached to (if this extension is used, it replaces the above mechanism for determining vNICs). QuantumManager can use either the traditional IPAM functionality in nova DB (i.e., the networks, fixed_ips tables) or melange. Similar to FlatManager, QuantumManager does not currently support DHCP, gateway/NAT, or floating IPs. This branch requires additional testing before it is really ready for merge, but we thought it would be best to merge prop as soon as possible to identify any significant concerns people had.
This commit is contained in:
commit
995300ff9b
1
.mailmap
1
.mailmap
|
@ -15,6 +15,7 @@
|
|||
<code@term.ie> <termie@preciousroy.local>
|
||||
<corywright@gmail.com> <cory.wright@rackspace.com>
|
||||
<dan@nicira.com> <danwent@dan-xs3-cs>
|
||||
<dan@nicira.com> danwent@gmail.com
|
||||
<devin.carlen@gmail.com> <devcamcar@illian.local>
|
||||
<ewan.mellor@citrix.com> <emellor@silver>
|
||||
<itoumsn@nttdata.co.jp> <itoumsn@shayol>
|
||||
|
|
1
Authors
1
Authors
|
@ -11,6 +11,7 @@ Antony Messerli <ant@openstack.org>
|
|||
Armando Migliaccio <Armando.Migliaccio@eu.citrix.com>
|
||||
Arvind Somya <asomya@cisco.com>
|
||||
Bilal Akhtar <bilalakhtar@ubuntu.com>
|
||||
Brad Hall <brad@nicira.com>
|
||||
Brian Lamar <brian.lamar@rackspace.com>
|
||||
Brian Schott <bschott@isi.edu>
|
||||
Brian Waldon <brian.waldon@rackspace.com>
|
||||
|
|
|
@ -59,11 +59,11 @@ import glob
|
|||
import json
|
||||
import math
|
||||
import netaddr
|
||||
from optparse import OptionParser
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from optparse import OptionParser
|
||||
|
||||
# If ../nova/__init__.py exists, add ../ to Python search path, so that
|
||||
# it will override what happens to be installed in /usr/(local/)lib/python...
|
||||
|
@ -685,10 +685,17 @@ class NetworkCommands(object):
|
|||
help='Multi host')
|
||||
@args('--dns1', dest="dns1", metavar="<DNS Address>", help='First DNS')
|
||||
@args('--dns2', dest="dns2", metavar="<DNS Address>", help='Second DNS')
|
||||
@args('--uuid', dest="net_uuid", metavar="<network uuid>",
|
||||
help='Network UUID')
|
||||
@args('--project_id', dest="project_id", metavar="<project id>",
|
||||
help='Project id')
|
||||
@args('--priority', dest="priority", metavar="<number>",
|
||||
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):
|
||||
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
|
||||
|
@ -765,7 +772,10 @@ class NetworkCommands(object):
|
|||
bridge=bridge,
|
||||
bridge_interface=bridge_interface,
|
||||
dns1=dns1,
|
||||
dns2=dns2)
|
||||
dns2=dns2,
|
||||
project_id=project_id,
|
||||
priority=priority,
|
||||
uuid=uuid)
|
||||
|
||||
def list(self):
|
||||
"""List all created networks"""
|
||||
|
@ -790,16 +800,29 @@ class NetworkCommands(object):
|
|||
network.project_id,
|
||||
network.uuid)
|
||||
|
||||
def quantum_list(self):
|
||||
"""List all created networks with Quantum-relevant fields"""
|
||||
_fmt = "%-32s\t%-10s\t%-10s\t%s , %s"
|
||||
print _fmt % (_('uuid'),
|
||||
_('project'),
|
||||
_('priority'),
|
||||
_('cidr_v4'),
|
||||
_('cidr_v6'))
|
||||
for network in db.network_get_all(context.get_admin_context()):
|
||||
print _fmt % (network.uuid,
|
||||
network.project_id,
|
||||
network.priority,
|
||||
network.cidr,
|
||||
network.cidr_v6)
|
||||
|
||||
@args('--network', dest="fixed_range", metavar='<x.x.x.x/yy>',
|
||||
help='Network to delete')
|
||||
def delete(self, fixed_range):
|
||||
"""Deletes a network"""
|
||||
network = db.network_get_by_cidr(context.get_admin_context(), \
|
||||
fixed_range)
|
||||
if network.project_id is not None:
|
||||
raise ValueError(_('Network must be disassociated from project %s'
|
||||
' before delete' % network.project_id))
|
||||
db.network_delete_safe(context.get_admin_context(), network.id)
|
||||
|
||||
# delete the network
|
||||
net_manager = utils.import_object(FLAGS.network_manager)
|
||||
net_manager.delete_network(context.get_admin_context(), fixed_range)
|
||||
|
||||
@args('--network', dest="fixed_range", metavar='<x.x.x.x/yy>',
|
||||
help='Network to modify')
|
||||
|
|
|
@ -435,6 +435,10 @@ class NetworkNotFoundForBridge(NetworkNotFound):
|
|||
message = _("Network could not be found for bridge %(bridge)s")
|
||||
|
||||
|
||||
class NetworkNotFoundForUUID(NetworkNotFound):
|
||||
message = _("Network could not be found for uuid %(uuid)s")
|
||||
|
||||
|
||||
class NetworkNotFoundForCidr(NetworkNotFound):
|
||||
message = _("Network could not be found with cidr %(cidr)s.")
|
||||
|
||||
|
|
|
@ -0,0 +1,323 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 Nicira, Inc.
|
||||
# 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.
|
||||
|
||||
from nova import context
|
||||
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 ipv6
|
||||
from nova import log as logging
|
||||
from nova.network.quantum import manager as quantum_manager
|
||||
from nova import test
|
||||
from nova import utils
|
||||
|
||||
LOG = logging.getLogger('nova.tests.quantum_network')
|
||||
|
||||
|
||||
# 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 net_ids
|
||||
|
||||
def create_network(self, tenant_id, network_name):
|
||||
|
||||
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 _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):
|
||||
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, attachment_id):
|
||||
for net_id, n in self.nets.items():
|
||||
if 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 (None, None)
|
||||
|
||||
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',
|
||||
'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',
|
||||
'vlan': None,
|
||||
'host': None,
|
||||
'vpn_public_address': None,
|
||||
'project_id': 'fake_project1',
|
||||
'priority': 1},
|
||||
{'label': 'project2-net1',
|
||||
'injected': False,
|
||||
'multi_host': False,
|
||||
'cidr': '192.168.1.0/24',
|
||||
'cidr_v6': '2001:1db9::/64',
|
||||
'gateway_v6': '2001: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',
|
||||
'vlan': None,
|
||||
'host': None,
|
||||
'project_id': 'fake_project2',
|
||||
'priority': 1},
|
||||
{'label': "public",
|
||||
'injected': False,
|
||||
'multi_host': False,
|
||||
'cidr': '10.0.0.0/24',
|
||||
'cidr_v6': '2001:1dba::/64',
|
||||
'gateway_v6': '2001: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',
|
||||
'vlan': None,
|
||||
'host': None,
|
||||
'project_id': None,
|
||||
'priority': 0},
|
||||
{'label': "project2-net2",
|
||||
'injected': False,
|
||||
'multi_host': False,
|
||||
'cidr': '9.0.0.0/24',
|
||||
'cidr_v6': '2001:1dbb::/64',
|
||||
'gateway_v6': '2001: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',
|
||||
'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 QuantumTestCaseBase(object):
|
||||
|
||||
def test_create_and_delete_nets(self):
|
||||
self._create_nets()
|
||||
self._delete_nets()
|
||||
|
||||
def _create_nets(self):
|
||||
for n in networks:
|
||||
ctx = context.RequestContext('user1', n['project_id'])
|
||||
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_v6=n['gateway_v6'], bridge=None,
|
||||
bridge_interface=None, dns1=n['dns1'],
|
||||
dns2=n['dns2'], project_id=n['project_id'],
|
||||
priority=n['priority'])
|
||||
|
||||
def _delete_nets(self):
|
||||
for n in networks:
|
||||
ctx = context.RequestContext('user1', n['project_id'])
|
||||
self.net_man.delete_network(ctx, n['cidr'])
|
||||
|
||||
def test_allocate_and_deallocate_instance_static(self):
|
||||
self._create_nets()
|
||||
|
||||
project_id = "fake_project1"
|
||||
ctx = context.RequestContext('user1', project_id)
|
||||
|
||||
instance_ref = db.api.instance_create(ctx,
|
||||
{"project_id": project_id})
|
||||
nw_info = self.net_man.allocate_for_instance(ctx,
|
||||
instance_id=instance_ref['id'], host="",
|
||||
instance_type_id=instance_ref['instance_type_id'],
|
||||
project_id=project_id)
|
||||
|
||||
self.assertEquals(len(nw_info), 2)
|
||||
|
||||
# we don't know which order the NICs will be in until we
|
||||
# introduce the notion of priority
|
||||
# v4 cidr
|
||||
self.assertTrue(nw_info[0][0]['cidr'].startswith("10."))
|
||||
self.assertTrue(nw_info[1][0]['cidr'].startswith("192."))
|
||||
|
||||
# v4 address
|
||||
self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("10."))
|
||||
self.assertTrue(nw_info[1][1]['ips'][0]['ip'].startswith("192."))
|
||||
|
||||
# v6 cidr
|
||||
self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1dba:"))
|
||||
self.assertTrue(nw_info[1][0]['cidr_v6'].startswith("2001:1db8:"))
|
||||
|
||||
# v6 address
|
||||
self.assertTrue(
|
||||
nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dba:"))
|
||||
self.assertTrue(
|
||||
nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1db8:"))
|
||||
|
||||
self.net_man.deallocate_for_instance(ctx,
|
||||
instance_id=instance_ref['id'],
|
||||
project_id=project_id)
|
||||
|
||||
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]
|
||||
|
||||
self.net_man.validate_networks(ctx, requested_networks)
|
||||
|
||||
instance_ref = db.api.instance_create(ctx,
|
||||
{"project_id": project_id})
|
||||
nw_info = self.net_man.allocate_for_instance(ctx,
|
||||
instance_id=instance_ref['id'], host="",
|
||||
instance_type_id=instance_ref['instance_type_id'],
|
||||
project_id=project_id,
|
||||
requested_networks=requested_networks)
|
||||
|
||||
self.assertEquals(len(nw_info), 2)
|
||||
|
||||
# we don't know which order the NICs will be in until we
|
||||
# introduce the notion of priority
|
||||
# v4 cidr
|
||||
self.assertTrue(nw_info[0][0]['cidr'].startswith("9.") or
|
||||
nw_info[1][0]['cidr'].startswith("9."))
|
||||
self.assertTrue(nw_info[0][0]['cidr'].startswith("192.") or
|
||||
nw_info[1][0]['cidr'].startswith("192."))
|
||||
|
||||
# v4 address
|
||||
self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("9.") or
|
||||
nw_info[1][1]['ips'][0]['ip'].startswith("9."))
|
||||
self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("192.") or
|
||||
nw_info[1][1]['ips'][0]['ip'].startswith("192."))
|
||||
|
||||
# v6 cidr
|
||||
self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1dbb:") or
|
||||
nw_info[1][0]['cidr_v6'].startswith("2001:1dbb:"))
|
||||
self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1db9:") or
|
||||
nw_info[1][0]['cidr_v6'].startswith("2001:1db9:"))
|
||||
|
||||
# v6 address
|
||||
self.assertTrue(
|
||||
nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dbb:") or
|
||||
nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1dbb:"))
|
||||
self.assertTrue(
|
||||
nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1db9:") or
|
||||
nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1db9:"))
|
||||
|
||||
self.net_man.deallocate_for_instance(ctx,
|
||||
instance_id=instance_ref['id'],
|
||||
project_id=project_id)
|
||||
|
||||
self._delete_nets()
|
||||
|
||||
def test_validate_bad_network(self):
|
||||
ctx = context.RequestContext('user1', 'fake_project1')
|
||||
self.assertRaises(exception.NetworkNotFound,
|
||||
self.net_man.validate_networks, ctx, [("", None)])
|
||||
|
||||
|
||||
class QuantumNovaIPAMTestCase(QuantumTestCaseBase, test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(QuantumNovaIPAMTestCase, self).setUp()
|
||||
|
||||
self.net_man = quantum_manager.QuantumManager(
|
||||
ipam_lib="nova.network.quantum.nova_ipam_lib",
|
||||
q_conn=FakeQuantumClientConnection())
|
||||
|
||||
# Tests seem to create some networks by default, which
|
||||
# we don't want. So we delete them.
|
||||
|
||||
ctx = context.RequestContext('user1', 'fake_project1').elevated()
|
||||
for n in db.network_get_all(ctx):
|
||||
db.network_delete_safe(ctx, n['id'])
|
||||
|
||||
# Other unit tests (e.g., test_compute.py) have a nasty
|
||||
# habit of of creating fixed IPs and not cleaning up, which
|
||||
# can confuse these tests, so we remove all existing fixed
|
||||
# ips before starting.
|
||||
session = get_session()
|
||||
result = session.query(models.FixedIp).all()
|
||||
with session.begin():
|
||||
for fip_ref in result:
|
||||
session.delete(fip_ref)
|
Loading…
Reference in New Issue