pulling all qmanager changes into a branch based on trunk, as they were previously stacked on top of melange

This commit is contained in:
Dan Wendlandt
2011-08-22 22:18:43 -07:00
parent c2fb9485f9
commit 632526f0cf
15 changed files with 1660 additions and 28 deletions

View File

@@ -58,11 +58,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...
@@ -681,10 +681,15 @@ 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('--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):
"""Creates fixed ips for host by range"""
# check for certain required inputs
@@ -761,11 +766,14 @@ class NetworkCommands(object):
bridge=bridge,
bridge_interface=bridge_interface,
dns1=dns1,
dns2=dns2)
dns2=dns2,
project_id=project_id,
priority=priority)
def list(self):
"""List all created networks"""
_fmt = "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s"
_fmt = "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s"\
"\t%-15s\t%-15s\t-15s\t-15s"
print _fmt % (_('id'),
_('IPv4'),
_('IPv6'),
@@ -774,7 +782,9 @@ class NetworkCommands(object):
_('DNS2'),
_('VlanID'),
_('project'),
_("uuid"))
_("uuid"),
_('priority'),
_('bridge'))
for network in db.network_get_all(context.get_admin_context()):
print _fmt % (network.id,
network.cidr,
@@ -784,18 +794,19 @@ class NetworkCommands(object):
network.dns2,
network.vlan,
network.project_id,
network.uuid)
network.uuid,
network.priority,
network.bridge)
@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)
class VmCommands(object):

View File

@@ -419,6 +419,11 @@ def virtual_interface_get_by_address(context, address):
return IMPL.virtual_interface_get_by_address(context, address)
def virtual_interface_get_by_uuid(context, vif_uuid):
"""Gets a virtual interface from the table filtering on vif uuid."""
return IMPL.virtual_interface_get_by_uuid(context, vif_uuid)
def virtual_interface_get_by_fixed_ip(context, fixed_ip_id):
"""Gets the virtual interface fixed_ip is associated with."""
return IMPL.virtual_interface_get_by_fixed_ip(context, fixed_ip_id)

View File

@@ -688,10 +688,8 @@ def fixed_ip_associate(context, address, instance_id, network_id=None):
def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None):
session = get_session()
with session.begin():
network_or_none = or_(models.FixedIp.network_id == network_id,
models.FixedIp.network_id == None)
fixed_ip_ref = session.query(models.FixedIp).\
filter(network_or_none).\
filter_by(network_id=network_id).\
filter_by(reserved=False).\
filter_by(deleted=False).\
filter_by(instance=None).\
@@ -928,6 +926,22 @@ def virtual_interface_get_by_address(context, address):
return vif_ref
@require_context
def virtual_interface_get_by_uuid(context, vif_uuid):
"""Gets a virtual interface from the table.
:param vif_uuid: = the uuid of the interface you're looking to get
"""
session = get_session()
vif_ref = session.query(models.VirtualInterface).\
filter_by(uuid=vif_uuid).\
options(joinedload('network')).\
options(joinedload('instance')).\
options(joinedload('fixed_ips')).\
first()
return vif_ref
@require_context
def virtual_interface_get_by_fixed_ip(context, fixed_ip_id):
"""Gets the virtual interface fixed_ip is associated with.

View File

@@ -0,0 +1,45 @@
# Copyright 2011 OpenStack LLC.
# 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.
import datetime
from sqlalchemy import *
from migrate import *
from nova import log as logging
from nova import utils
meta = MetaData()
# Add priority column to networks table
priority = Column('priority', Integer())
def upgrade(migrate_engine):
meta.bind = migrate_engine
# grab tables and (column for dropping later)
networks = Table('networks', meta, autoload=True)
try:
networks.create_column(priority)
except Exception:
logging.error(_("priority column not added to networks table"))
raise
def downgrade(migrate_engine):
meta.bind = migrate_engine
networks.drop_column(priority)

View File

@@ -560,6 +560,7 @@ class Network(BASE, NovaBase):
dhcp_start = Column(String(255))
project_id = Column(String(255))
priority = Column(Integer)
host = Column(String(255)) # , ForeignKey('hosts.id'))
uuid = Column(String(36))

View File

@@ -443,7 +443,7 @@ class NetworkManager(manager.SchedulerDependentManager):
try:
fixed_ips = kwargs.get('fixed_ips') or \
self.db.fixed_ip_get_by_instance(context, instance_id)
except exceptions.FixedIpNotFoundForInstance:
except exception.FixedIpNotFoundForInstance:
fixed_ips = []
LOG.debug(_("network deallocation for instance |%s|"), instance_id,
context=context)
@@ -541,21 +541,23 @@ class NetworkManager(manager.SchedulerDependentManager):
def _allocate_mac_addresses(self, context, instance_id, networks):
"""Generates mac addresses and creates vif rows in db for them."""
for network in networks:
vif = {'address': self.generate_mac_address(),
self.add_virtual_interface(context, instance_id, network['id'])
def add_virtual_interface(self, context, instance_id, network_id):
vif = {'address': self.generate_mac_address(),
'instance_id': instance_id,
'network_id': network['id'],
'network_id': network_id,
'uuid': str(utils.gen_uuid())}
# try FLAG times to create a vif record with a unique mac_address
for i in range(FLAGS.create_unique_mac_address_attempts):
try:
self.db.virtual_interface_create(context, vif)
break
except exception.VirtualInterfaceCreateException:
vif['address'] = self.generate_mac_address()
else:
self.db.virtual_interface_delete_by_instance(context,
# try FLAG times to create a vif record with a unique mac_address
for i in range(FLAGS.create_unique_mac_address_attempts):
try:
return self.db.virtual_interface_create(context, vif)
except exception.VirtualInterfaceCreateException:
vif['address'] = self.generate_mac_address()
else:
self.db.virtual_interface_delete_by_instance(context,
instance_id)
raise exception.VirtualInterfaceMacAddressException()
raise exception.VirtualInterfaceMacAddressException()
def generate_mac_address(self):
"""Generate an Ethernet MAC address."""
@@ -784,6 +786,15 @@ class NetworkManager(manager.SchedulerDependentManager):
self._create_fixed_ips(context, network['id'])
return networks
def delete_network(self, context, fixed_range, require_disassociated=True):
network = db.network_get_by_cidr(context, fixed_range)
if require_disassociated and network.project_id is not None:
raise ValueError(_('Network must be disassociated from project %s'
' before delete' % network.project_id))
self.db.network_delete_safe(context, network.id)
@property
def _bottom_reserved_ips(self): # pylint: disable=R0201
"""Number of reserved ips at the bottom of the range."""

View File

@@ -0,0 +1,16 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 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.

View File

@@ -0,0 +1,306 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Citrix Systems
# 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: Tyler Smith, Cisco Systems
import httplib
import json
import socket
import urllib
# this is a simple json-only serializer to use until
# we can just grab the standard serializer
# from the quantum library
class Serializer:
def serialize(self, data, content_type):
try:
return json.dumps(data)
except TypeError:
pass
return json.dumps(to_primitive(data))
def deserialize(self, data, content_type):
return json.loads(data)
class api_call(object):
"""A Decorator to add support for format and tenant overriding"""
def __init__(self, f):
self.f = f
def __get__(self, instance, owner):
def with_params(*args, **kwargs):
# Temporarily set format and tenant for this request
(format, tenant) = (instance.format, instance.tenant)
if 'format' in kwargs:
instance.format = kwargs['format']
if 'tenant' in kwargs:
instance.tenant = kwargs['tenant']
ret = self.f(instance, *args)
(instance.format, instance.tenant) = (format, tenant)
return ret
return with_params
class Client(object):
"""A base client class - derived from Glance.BaseClient"""
action_prefix = '/v0.1/tenants/{tenant_id}'
"""Action query strings"""
networks_path = "/networks"
network_path = "/networks/%s"
ports_path = "/networks/%s/ports"
port_path = "/networks/%s/ports/%s"
attachment_path = "/networks/%s/ports/%s/attachment"
def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None,
format="xml", testingStub=None, key_file=None, cert_file=None,
logger=None):
"""
Creates a new client to some service.
:param host: The host where service resides
:param port: The port where service resides
:param use_ssl: True to use SSL, False to use HTTP
:param tenant: The tenant ID to make requests with
:param format: The format to query the server with
:param testingStub: 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
"""
self.host = host
self.port = port
self.use_ssl = use_ssl
self.tenant = tenant
self.format = format
self.connection = None
self.testingStub = testingStub
self.key_file = key_file
self.cert_file = cert_file
self.logger = logger
def get_connection_type(self):
"""
Returns the proper connection type
"""
if self.testingStub:
return self.testingStub
if self.use_ssl:
return httplib.HTTPSConnection
else:
return httplib.HTTPConnection
def do_request(self, method, action, body=None,
headers=None, params=None):
"""
Connects to the server and issues a request.
Returns the result data, or raises an appropriate exception if
HTTP status code is not 2xx
:param method: HTTP method ("GET", "POST", "PUT", etc...)
:param body: string of data to send, or None (default)
:param headers: mapping of key/value pairs to add as headers
:param params: dictionary of key/value pairs to add to append
to action
"""
# Ensure we have a tenant id
if not self.tenant:
raise Exception("Tenant ID not set")
# Add format and tenant_id
action += ".%s" % self.format
action = Client.action_prefix + action
action = action.replace('{tenant_id}', self.tenant)
if type(params) is dict:
action += '?' + urllib.urlencode(params)
try:
connection_type = self.get_connection_type()
headers = headers or {"Content-Type":
"application/%s" % self.format}
# Open connection and send request, handling SSL certs
certs = {'key_file': self.key_file, 'cert_file': self.cert_file}
certs = dict((x, certs[x]) for x in certs if certs[x] != None)
if self.use_ssl and len(certs):
c = connection_type(self.host, self.port, **certs)
else:
c = connection_type(self.host, self.port)
if self.logger:
self.logger.debug("Quantum Client Request:\n" \
+ method + " " + action + "\n")
if body:
self.logger.debug(body)
c.request(method, action, body, headers)
res = c.getresponse()
status_code = self.get_status_code(res)
data = res.read()
if self.logger:
self.logger.debug("Quantum Client Reply (code = %s) :\n %s" \
% (str(status_code), data))
if status_code in (httplib.OK,
httplib.CREATED,
httplib.ACCEPTED,
httplib.NO_CONTENT):
return self.deserialize(data, status_code)
else:
raise Exception("Server returned error: %s" % res.read())
except (socket.error, IOError), e:
raise Exception("Unable to connect to "
"server. Got error: %s" % e)
def get_status_code(self, response):
"""
Returns the integer status code from the response, which
can be either a Webob.Response (used in testing) or httplib.Response
"""
if hasattr(response, 'status_int'):
return response.status_int
else:
return response.status
def serialize(self, data):
if data is None:
return None
elif type(data) is dict:
return Serializer().serialize(data, self.content_type())
else:
raise Exception("unable to deserialize object of type = '%s'" \
% type(data))
def deserialize(self, data, status_code):
if status_code == 202:
return data
return Serializer().deserialize(data, self.content_type())
def content_type(self, format=None):
if not format:
format = self.format
return "application/%s" % (format)
@api_call
def list_networks(self):
"""
Fetches a list of all networks for a tenant
"""
return self.do_request("GET", self.networks_path)
@api_call
def show_network_details(self, network):
"""
Fetches the details of a certain network
"""
return self.do_request("GET", self.network_path % (network))
@api_call
def create_network(self, body=None):
"""
Creates a new network
"""
body = self.serialize(body)
return self.do_request("POST", self.networks_path, body=body)
@api_call
def update_network(self, network, body=None):
"""
Updates a network
"""
body = self.serialize(body)
return self.do_request("PUT", self.network_path % (network), body=body)
@api_call
def delete_network(self, network):
"""
Deletes the specified network
"""
return self.do_request("DELETE", self.network_path % (network))
@api_call
def list_ports(self, network):
"""
Fetches a list of ports on a given network
"""
return self.do_request("GET", self.ports_path % (network))
@api_call
def show_port_details(self, network, port):
"""
Fetches the details of a certain port
"""
return self.do_request("GET", self.port_path % (network, port))
@api_call
def create_port(self, network, body=None):
"""
Creates a new port on a given network
"""
body = self.serialize(body)
return self.do_request("POST", self.ports_path % (network), body=body)
@api_call
def delete_port(self, network, port):
"""
Deletes the specified port from a network
"""
return self.do_request("DELETE", self.port_path % (network, port))
@api_call
def set_port_state(self, network, port, body=None):
"""
Sets the state of the specified port
"""
body = self.serialize(body)
return self.do_request("PUT",
self.port_path % (network, port), body=body)
@api_call
def show_port_attachment(self, network, port):
"""
Fetches the attachment-id associated with the specified port
"""
return self.do_request("GET", self.attachment_path % (network, port))
@api_call
def attach_resource(self, network, port, body=None):
"""
Sets the attachment-id of the specified port
"""
body = self.serialize(body)
return self.do_request("PUT",
self.attachment_path % (network, port), body=body)
@api_call
def detach_resource(self, network, port):
"""
Removes the attachment-id of the specified port
"""
return self.do_request("DELETE",
self.attachment_path % (network, port))

View File

@@ -0,0 +1,213 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, 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 exception
from nova import ipv6
from nova import log as logging
from nova import utils
import math
from netaddr import IPNetwork
LOG = logging.getLogger("network.quantum.fake")
# 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:
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:
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 %s does not exist for tenant %s" %\
(net_id, tenant_id))
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("network %s does not exist for tenant %s" %\
(net_id, tenant_id))
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)
def get_ipam_lib(net_man):
return FakeQuantumIPAMLib()
class FakeQuantumIPAMLib():
def __init__(self):
self.subnets = {}
def create_subnet(self, context, label, tenant_id, quantum_net_id,
cidr=None, gateway_v6=None, cidr_v6=None,
dns1=None, dns2=None):
if int(cidr.split("/")[1]) != 24:
raise Exception("fake ipam_lib only supports /24s")
v4_ips = []
net_start = cidr[0:cidr.rfind(".") + 1]
subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1]))))
for i in xrange(2, subnet_size - 1):
v4_ips.append({"ip": net_start + str(i),
"allocated": False,
"virtual_interface_id": None,
"instance_id": None})
self.subnets[quantum_net_id] = {\
"label": label,
"gateway": net_start + "1",
"netmask": "255.255.255.0",
"broadcast": net_start + "255",
"cidr": cidr,
"gateway_v6": gateway_v6,
"cidr_v6": cidr_v6,
"dns1": dns1,
"dns2": dns2,
"project_id": tenant_id,
"v4_ips": v4_ips}
def get_network_id_by_cidr(self, context, cidr, project_id):
for net_id, s in self.subnets.items():
if s['cidr'] == cidr or s['cidr_v6'] == cidr:
return net_id
return None
def delete_subnets_by_net_id(self, context, net_id, project_id):
self.verify_subnet_exists(context, project_id, net_id)
del self.subnets[net_id]
def get_project_and_global_net_ids(self, context, project_id):
net_ids = []
for nid, s in self.subnets.items():
if s['project_id'] == project_id or \
s['project_id'] == None:
net_ids.append((nid, s['project_id']))
return net_ids
def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec):
subnet = self.subnets[quantum_net_id]
for i in xrange(0, len(subnet['v4_ips'])):
ip = subnet['v4_ips'][i]
if not ip['allocated']:
subnet['v4_ips'][i] = {'ip': ip['ip'],
'allocated': True,
'virtual_interface_id': vif_rec['uuid'],
'instance_id': vif_rec['instance_id']}
return
raise Exception("Unable to find available IP for net '%s'" %\
quantum_net_id)
def get_subnets_by_net_id(self, context, tenant_id, net_id):
self.verify_subnet_exists(context, tenant_id, net_id)
subnet_data = self.subnets[net_id]
subnet_data_v4 = {
'network_id': net_id,
'cidr': subnet_data['cidr'],
'gateway': subnet_data['gateway'],
'broadcast': subnet_data['broadcast'],
'netmask': subnet_data['netmask'],
'dns1': subnet_data['dns1'],
'dns2': subnet_data['dns2']}
subnet_data_v6 = {
'network_id': net_id,
'cidr': subnet_data['cidr_v6'],
'gateway': subnet_data['gateway_v6'],
'broadcast': None,
'netmask': None,
'dns1': None,
'dns2': None}
return (subnet_data_v4, subnet_data_v6)
def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id):
self.verify_subnet_exists(context, project_id, net_id)
subnet_data = self.subnets[net_id]
if subnet_data['cidr_v6']:
ip = ipv6.to_global(subnet_data['cidr_v6'],
"ca:fe:de:ad:be:ef",
project_id)
return [ip]
return []
def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id):
self.verify_subnet_exists(context, project_id, net_id)
subnet_data = self.subnets[net_id]
for ip in subnet_data['v4_ips']:
if ip['virtual_interface_id'] == vif_id:
return [ip['ip']]
return []
def verify_subnet_exists(self, context, tenant_id, quantum_net_id):
if quantum_net_id not in self.subnets:
raise exception.NetworkNotFound(network_id=quantum_net_id)
def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref):
s = self.subnets[net_id]
for ip in s['v4_ips']:
if ip['virtual_interface_id'] == vif_ref['id']:
ip['allocated'] = False
ip['instance_id'] = None
ip['virtual_interface_id'] = None

View File

@@ -0,0 +1,232 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, 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 db
from nova import exception
from nova import flags
from nova import log as logging
from nova import manager
from nova import utils
from nova.network import manager
from nova.network.quantum import quantum_connection
from nova.network.quantum import fake
LOG = logging.getLogger("quantum_manager")
FLAGS = flags.FLAGS
flags.DEFINE_string('quantum_ipam_lib',
'nova.network.quantum.nova_ipam_lib',
"Indicates underlying IP address management library")
class QuantumManager(manager.FlatManager):
def __init__(self, ipam_lib=None, *args, **kwargs):
if FLAGS.fake_network:
self.q_conn = fake.FakeQuantumClientConnection()
else:
self.q_conn = quantum_connection.QuantumClientConnection()
if not ipam_lib:
ipam_lib = FLAGS.quantum_ipam_lib
self.ipam = utils.import_object(ipam_lib).get_ipam_lib(self)
super(QuantumManager, self).__init__(*args, **kwargs)
def create_networks(self, context, label, cidr, multi_host, num_networks,
network_size, cidr_v6, gateway_v6, bridge,
bridge_interface, dns1=None, dns2=None, **kwargs):
if num_networks != 1:
raise Exception("QuantumManager requires that only one"
" network is created per call")
q_tenant_id = kwargs["project_id"] or \
FLAGS.quantum_default_tenant_id
quantum_net_id = bridge
if quantum_net_id:
if not q_conn.network_exists(q_tenant_id, quantum_net_id):
raise Exception("Unable to find existing quantum " \
" network for tenant '%s' with net-id '%s'" % \
(q_tenant_id, quantum_net_id))
else:
# otherwise, create network from default quantum pool
quantum_net_id = self.q_conn.create_network(q_tenant_id, label)
ipam_tenant_id = kwargs.get("project_id", None)
self.ipam.create_subnet(context, label, ipam_tenant_id, quantum_net_id,
cidr, gateway_v6, cidr_v6, dns1, dns2)
def delete_network(self, context, fixed_range):
project_id = context.project_id
quantum_net_id = self.ipam.get_network_id_by_cidr(
context, fixed_range, project_id)
self.ipam.delete_subnets_by_net_id(context, quantum_net_id,
project_id)
try:
q_tenant_id = project_id or FLAGS.quantum_default_tenant_id
self.q_conn.delete_network(q_tenant_id, quantum_net_id)
except Exception, e:
raise Exception("Unable to delete Quantum Network with "
"fixed_range = %s (ports still in use?)." % fixed_range)
def allocate_for_instance(self, context, **kwargs):
instance_id = kwargs.pop('instance_id')
instance_type_id = kwargs['instance_type_id']
host = kwargs.pop('host')
project_id = kwargs.pop('project_id')
LOG.debug(_("network allocations for instance %s"), instance_id)
# if using the create-server-networks extension, 'requested_networks'
# will be defined, otherwise, just grab relevant nets from IPAM
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,
project_id)
# Create a port via quantum and attach the vif
for (net_id, project_id) in net_proj_pairs:
vif_rec = manager.FlatManager.add_virtual_interface(self,
context, instance, None)
q_tenant_id = project_id or FLAGS.quantum_default_tenant_id
self.q_conn.create_and_attach_port(q_tenant_id, net_id,
vif_rec['uuid'])
self.ipam.allocate_fixed_ip(context, project_id, net_id, vif_rec)
return self.get_instance_nw_info(context, instance_id,
instance_type_id, host)
def get_instance_nw_info(self, context, instance_id,
instance_type_id, host):
network_info = []
project_id = context.project_id
admin_context = context.elevated()
vifs = db.virtual_interface_get_by_instance(admin_context,
instance_id)
for vif in vifs:
q_tenant_id = project_id
ipam_tenant_id = project_id
net_id, port_id = self.q_conn.get_port_by_attachment(q_tenant_id,
vif['uuid'])
if not net_id:
q_tenant_id = FLAGS.quantum_default_tenant_id
ipam_tenant_id = None
net_id, port_id = self.q_conn.get_port_by_attachment(
q_tenant_id, vif['uuid'])
if not net_id:
raise Exception(_("No network for for virtual interface %s") %\
vif['uuid'])
(v4_subnet, v6_subnet) = self.ipam.get_subnets_by_net_id(context,
ipam_tenant_id, net_id)
v4_ips = self.ipam.get_v4_ips_by_interface(context,
net_id, vif['uuid'],
project_id=ipam_tenant_id)
v6_ips = self.ipam.get_v6_ips_by_interface(context,
net_id, vif['uuid'],
project_id=ipam_tenant_id)
quantum_net_id = v4_subnet['network_id'] or v6_subnet['network_id']
def ip_dict(ip, subnet):
return {
"ip": ip,
"netmask": subnet["netmask"],
"enabled": "1"}
network_dict = {
'cidr': v4_subnet['cidr'],
'injected': True,
'multi_host': False}
info = {
'gateway': v4_subnet['gateway'],
'dhcp_server': v4_subnet['gateway'],
'broadcast': v4_subnet['broadcast'],
'mac': vif['address'],
'vif_uuid': vif['uuid'],
'dns': [],
'ips': [ip_dict(ip, v4_subnet) for ip in v4_ips]}
if v6_subnet['cidr']:
network_dict['cidr_v6'] = v6_subnet['cidr']
info['ip6s'] = [ip_dict(ip, v6_subnet) for ip in v6_ips]
# TODO(tr3buchet): handle ip6 routes here as well
if v6_subnet['gateway']:
info['gateway6'] = v6_subnet['gateway']
dns_dict = {}
for s in [v4_subnet, v6_subnet]:
for k in ['dns1', 'dns2']:
if s[k]:
dns_dict[s[k]] = None
info['dns'] = [d for d in dns_dict.keys()]
network_info.append((network_dict, info))
return network_info
def deallocate_for_instance(self, context, **kwargs):
instance_id = kwargs.get('instance_id')
project_id = kwargs.pop('project_id', None)
admin_context = context.elevated()
vifs = db.virtual_interface_get_by_instance(admin_context,
instance_id)
for vif_ref in vifs:
interface_id = vif_ref['uuid']
q_tenant_id = project_id
ipam_tenant_id = project_id
(net_id, port_id) = self.q_conn.get_port_by_attachment(q_tenant_id,
interface_id)
if not net_id:
q_tenant_id = FLAGS.quantum_default_tenant_id
ipam_tenant_id = None
(net_id, port_id) = self.q_conn.get_port_by_attachment(
q_tenant_id, interface_id)
if not net_id:
LOG.error("Unable to find port with attachment: %s" % \
(interface_id))
continue
self.q_conn.detach_and_delete_port(q_tenant_id,
net_id, port_id)
self.ipam.deallocate_ips_by_vif(context, ipam_tenant_id,
net_id, vif_ref)
self.net_manager.db.virtual_interface_delete_by_instance(admin_context,
instance_id)
self._do_trigger_security_group_members_refresh_for_instance(
instance_id)
# validates that this tenant has quantum networks with the associated
# UUIDs. This is called by the 'os-create-server-ext' API extension
# code so that we can return an API error code to the caller if they
# request an invalid network.
def validate_networks(self, context, networks):
if networks is None:
return
project_id = context.project_id
for (net_id, _i) in networks:
self.ipam.verify_subnet_exists(context, project_id, net_id)
if not self.q_conn.network_exists(project_id, net_id):
raise exception.NetworkNotFound(network_id=net_id)

View File

@@ -0,0 +1,133 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 OpenStack LLC.
# 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.
import httplib
import socket
import urllib
import json
from nova import flags
FLAGS = flags.FLAGS
flags.DEFINE_string('melange_host',
'127.0.0.1',
'HOST for connecting to melange')
flags.DEFINE_string('melange_port',
'9898',
'PORT for connecting to melange')
json_content_type = {'Content-type': "application/json"}
class MelangeConnection(object):
def __init__(self, host=None, port=None, use_ssl=False):
if host is None:
host = FLAGS.melange_host
if port is None:
port = int(FLAGS.melange_port)
self.host = host
self.port = port
self.use_ssl = use_ssl
def get(self, path, params={}, headers={}):
return self.do_request("GET", path, params=params, headers=headers)
def post(self, path, body=None, headers={}):
return self.do_request("POST", path, body=body, headers=headers)
def delete(self, path, headers={}):
return self.do_request("DELETE", path, headers=headers)
def _get_connection(self):
if self.use_ssl:
return httplib.HTTPSConnection(self.host, self.port)
else:
return httplib.HTTPConnection(self.host, self.port)
def do_request(self, method, path, body=None, headers={}, params={}):
url = path + '.json?' + urllib.urlencode(params)
try:
connection = self._get_connection()
connection.request(method, url, body, headers)
response = connection.getresponse()
response_str = response.read()
if response.status < 400:
return response_str
raise Exception("Server returned error: %s", response_str)
except (socket.error, IOError), e:
raise Exception("Unable to connect to "
"server. Got error: %s" % e)
def allocate_ip(self, network_id, vif_id,
project_id=None, mac_address=None):
tenant_scope = "/tenants/%s" % project_id if project_id else ""
request_body = (json.dumps(dict(network=dict(mac_address=mac_address,
tenant_id=project_id)))
if mac_address else None)
url = ("/ipam%(tenant_scope)s/networks/%(network_id)s/"
"interfaces/%(vif_id)s/ip_allocations" % locals())
response = self.post(url, body=request_body,
headers=json_content_type)
return json.loads(response)['ip_addresses']
def create_block(self, network_id, cidr,
project_id=None, dns1=None, dns2=None):
tenant_scope = "/tenants/%s" % project_id if project_id else ""
url = "/ipam%(tenant_scope)s/ip_blocks" % locals()
req_params = dict(ip_block=dict(cidr=cidr, network_id=network_id,
type='private', dns1=dns1, dns2=dns2))
self.post(url, body=json.dumps(req_params),
headers=json_content_type)
def delete_block(self, block_id, project_id=None):
tenant_scope = "/tenants/%s" % project_id if project_id else ""
url = "/ipam%(tenant_scope)s/ip_blocks/%(block_id)s" % locals()
self.delete(url, headers=json_content_type)
def get_blocks(self, project_id=None):
tenant_scope = "/tenants/%s" % project_id if project_id else ""
url = "/ipam%(tenant_scope)s/ip_blocks" % locals()
response = self.get(url, headers=json_content_type)
return json.loads(response)
def get_allocated_ips(self, network_id, vif_id, project_id=None):
tenant_scope = "/tenants/%s" % project_id if project_id else ""
url = ("/ipam%(tenant_scope)s/networks/%(network_id)s/"
"interfaces/%(vif_id)s/ip_allocations" % locals())
response = self.get(url, headers=json_content_type)
return json.loads(response)['ip_addresses']
def deallocate_ips(self, network_id, vif_id, project_id=None):
tenant_scope = "/tenants/%s" % project_id if project_id else ""
url = ("/ipam%(tenant_scope)s/networks/%(network_id)s/"
"interfaces/%(vif_id)s/ip_allocations" % locals())
self.delete(url, headers=json_content_type)

View File

@@ -0,0 +1,135 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, 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 netaddr import IPNetwork
from nova import flags
from nova import log as logging
from nova.network.quantum import melange_connection
LOG = logging.getLogger("quantum_melange_ipam")
FLAGS = flags.FLAGS
def get_ipam_lib(net_man):
return QuantumMelangeIPAMLib()
class QuantumMelangeIPAMLib:
def __init__(self):
self.m_conn = melange_connection.MelangeConnection()
def create_subnet(self, context, label, project_id,
quantum_net_id, cidr=None,
gateway_v6=None, cidr_v6=None,
dns1=None, dns2=None):
tenant_id = project_id or FLAGS.quantum_default_tenant_id
if cidr:
self.m_conn.create_block(quantum_net_id, cidr,
project_id=tenant_id,
dns1=dns1, dns2=dns2)
if cidr_v6:
self.m_conn.create_block(quantum_net_id, cidr_v6,
project_id=tenant_id,
dns1=dns1, dns2=dns2)
def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref):
tenant_id = project_id or FLAGS.quantum_default_tenant_id
self.m_conn.allocate_ip(quantum_net_id,
vif_ref['uuid'], project_id=tenant_id,
mac_address=vif_ref['address'])
def get_network_id_by_cidr(self, context, cidr, project_id):
tenant_id = project_id or FLAGS.quantum_default_tenant_id
all_blocks = self.m_conn.get_blocks(tenant_id)
for b in all_blocks['ip_blocks']:
if b['cidr'] == cidr:
return b['network_id']
def delete_subnets_by_net_id(self, context, net_id, project_id):
tenant_id = project_id or FLAGS.quantum_default_tenant_id
all_blocks = self.m_conn.get_blocks(tenant_id)
for b in all_blocks['ip_blocks']:
if b['network_id'] == net_id:
self.m_conn.delete_block(b['id'], tenant_id)
# get all networks with this project_id, as well as all networks
# where the project-id is not set (these are shared networks)
def get_project_and_global_net_ids(self, context, project_id):
id_proj_map = {}
if not project_id:
raise Exception("get_project_and_global_net_ids must be called" \
" with a non-null project_id")
tenant_id = project_id
all_tenant_blocks = self.m_conn.get_blocks(tenant_id)
for b in all_tenant_blocks['ip_blocks']:
id_proj_map[b['network_id']] = tenant_id
tenant_id = FLAGS.quantum_default_tenant_id
all_provider_blocks = self.m_conn.get_blocks(tenant_id)
for b in all_provider_blocks['ip_blocks']:
id_proj_map[b['network_id']] = tenant_id
return id_proj_map.items()
# FIXME: there must be a more efficient way to do this,
# talk to the melange folks
def get_subnets_by_net_id(self, context, project_id, net_id):
subnet_v4 = None
subnet_v6 = None
tenant_id = project_id or FLAGS.quantum_default_tenant_id
all_blocks = self.m_conn.get_blocks(tenant_id)
for b in all_blocks['ip_blocks']:
if b['network_id'] == net_id:
subnet = {'network_id': b['network_id'],
'cidr': b['cidr'],
'gateway': b['gateway'],
'broadcast': b['broadcast'],
'netmask': b['netmask'],
'dns1': b['dns1'],
'dns2': b['dns2']}
if IPNetwork(b['cidr']).version == 6:
subnet_v6 = subnet
else:
subnet_v4 = subnet
return (subnet_v4, subnet_v6)
def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id):
return self.get_ips_by_interface(context, net_id, vif_id,
project_id, 4)
def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id):
return self.get_ips_by_interface(context, net_id, vif_id,
project_id, 6)
def get_ips_by_interface(self, context, net_id, vif_id, project_id,
ip_version):
tenant_id = project_id or FLAGS.quantum_default_tenant_id
ip_list = self.m_conn.get_allocated_ips(net_id, vif_id, tenant_id)
return [ip['address'] for ip in ip_list \
if IPNetwork(ip['address']).version == ip_version]
def verify_subnet_exists(self, context, project_id, quantum_net_id):
tenant_id = project_id or FLAGS.quantum_default_tenant_id
v4_subnet, v6_subnet = self.get_subnets_by_net_id(context, tenant_id,
quantum_net_id)
return v4_subnet is not None
def deallocate_ips_by_vif(self, context, project_id, net_id, vif_ref):
tenant_id = project_id or FLAGS.quantum_default_tenant_id
self.m_conn.deallocate_ips(net_id, vif_ref['uuid'], tenant_id)

View File

@@ -0,0 +1,152 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, 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.
import math
#from nova import context
from nova import db
from nova import exception
from nova import flags
from nova import ipv6
from nova import log as logging
from nova import utils
from nova.network import manager
from nova.network.quantum import melange_connection as melange
LOG = logging.getLogger("quantum_nova_ipam_lib")
FLAGS = flags.FLAGS
def get_ipam_lib(net_man):
return QuantumNovaIPAMLib(net_man)
class QuantumNovaIPAMLib:
def __init__(self, net_manager):
self.net_manager = net_manager
def create_subnet(self, context, label, tenant_id,
quantum_net_id, cidr=None,
gateway_v6=None, cidr_v6=None,
dns1=None, dns2=None):
print "creating subnet %s" % cidr
admin_context = context.elevated()
subnet_size = int(math.pow(2, (32 - int(cidr.split("/")[1]))))
manager.FlatManager.create_networks(self.net_manager,
admin_context, label, cidr,
False, 1, subnet_size, cidr_v6,
gateway_v6, quantum_net_id, None, dns1, dns2)
# now grab the network and update project_id
network = db.network_get_by_bridge(admin_context, quantum_net_id)
net = {"project_id": tenant_id}
db.network_update(admin_context, network['id'], net)
def get_network_id_by_cidr(self, context, cidr, project_id):
admin_context = context.elevated()
network = db.network_get_by_cidr(admin_context, cidr)
if not network:
raise Exception("No network with fixed_range = %s" \
% fixed_range)
return network['bridge']
def delete_subnets_by_net_id(self, context, net_id, project_id):
admin_context = context.elevated()
network = db.network_get_by_bridge(admin_context, net_id)
if not network:
raise Exception("No network with net_id = %s" % net_id)
manager.FlatManager.delete_network(self.net_manager,
admin_context, network['cidr'],
require_disassociated=False)
def get_project_and_global_net_ids(self, context, project_id):
# get all networks with this project_id, as well as all networks
# where the project-id is not set (these are shared networks)
admin_context = context.elevated()
networks = db.project_get_networks(admin_context, project_id, False)
networks.extend(db.project_get_networks(admin_context, None, False))
return [(n['bridge'], n['project_id']) for n in networks]
def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec):
admin_context = context.elevated()
network = db.network_get_by_bridge(admin_context, quantum_net_id)
if network['cidr']:
address = db.fixed_ip_associate_pool(admin_context,
network['id'],
vif_rec['instance_id'])
values = {'allocated': True,
'virtual_interface_id': vif_rec['id']}
db.fixed_ip_update(admin_context, address, values)
def get_subnets_by_net_id(self, context, tenant_id, net_id):
n = db.network_get_by_bridge(context.elevated(), net_id)
subnet_data_v4 = {
'network_id': n['bridge'],
'cidr': n['cidr'],
'gateway': n['gateway'],
'broadcast': n['broadcast'],
'netmask': n['netmask'],
'dns1': n['dns1'],
'dns2': n['dns2']
}
subnet_data_v6 = {
'network_id': n['bridge'],
'cidr': n['cidr_v6'],
'gateway': n['gateway_v6'],
'broadcast': None,
'netmask': None,
'dns1': None,
'dns2': None
}
return (subnet_data_v4, subnet_data_v6)
def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id):
vif_rec = db.virtual_interface_get_by_uuid(context, vif_id)
fixed_ips = db.fixed_ip_get_by_virtual_interface(context,
vif_rec['id'])
return [f['address'] for f in fixed_ips]
def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id):
admin_context = context.elevated()
network = db.network_get_by_bridge(admin_context, net_id)
vif_rec = db.virtual_interface_get_by_uuid(context, vif_id)
if network['cidr_v6']:
ip = ipv6.to_global(network['cidr_v6'],
vif_rec['address'],
project_id)
return [ip]
return []
def verify_subnet_exists(self, context, tenant_id, quantum_net_id):
admin_context = context.elevated()
network = db.network_get_by_bridge(admin_context, quantum_net_id)
def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref):
try:
admin_context = context.elevated()
fixed_ips = db.fixed_ip_get_by_virtual_interface(admin_context,
vif_ref['id'])
for f in fixed_ips:
db.fixed_ip_update(admin_context, f['address'],
{'allocated': False,
'virtual_interface_id': None})
except exception.FixedIpNotFoundForInstance:
LOG.error(_('Failed to deallocate fixed IP for vif %s' % \
vif_ref['id']))

View File

@@ -0,0 +1,97 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 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.
from nova import flags
from nova import log as logging
from nova import utils
from nova.network.quantum.client import Client
LOG = logging.getLogger("nova.network.quantum")
FLAGS = flags.FLAGS
flags.DEFINE_string('quantum_connection_host',
'127.0.0.1',
'HOST for connecting to quantum')
flags.DEFINE_string('quantum_connection_port',
'9696',
'PORT for connecting to quantum')
flags.DEFINE_string('quantum_default_tenant_id',
"default",
'Default tenant id when creating quantum networks')
class QuantumClientConnection:
def __init__(self):
self.client = Client(FLAGS.quantum_connection_host,
FLAGS.quantum_connection_port,
format="json",
logger=LOG)
def create_network(self, tenant_id, network_name):
data = {'network': {'net-name': network_name}}
resdict = self.client.create_network(data, tenant=tenant_id)
return resdict["networks"]["network"]["id"]
def delete_network(self, tenant_id, net_id):
self.client.delete_network(net_id, tenant=tenant_id)
def network_exists(self, tenant_id, net_id):
try:
self.client.show_network_details(net_id, tenant=tenant_id)
except:
# FIXME: client lib should expose more granular exceptions
# so we can confirm we're getting a 404 and not some other error
return False
return True
def create_and_attach_port(self, tenant_id, net_id, interface_id):
LOG.debug("Connecting interface %s to net %s for %s" % \
(interface_id, net_id, tenant_id))
port_data = {'port': {'port-state': 'ACTIVE'}}
resdict = self.client.create_port(net_id, port_data, tenant=tenant_id)
port_id = resdict["ports"]["port"]["id"]
attach_data = {'port': {'attachment-id': interface_id}}
self.client.attach_resource(net_id, port_id, attach_data,
tenant=tenant_id)
def detach_and_delete_port(self, tenant_id, net_id, port_id):
LOG.debug("Deleteing port %s on net %s for %s" % \
(port_id, net_id, tenant_id))
self.client.detach_resource(net_id, port_id, tenant=tenant_id)
self.client.delete_port(net_id, port_id, tenant=tenant_id)
# FIXME: this will be inefficient until API implements querying
def get_port_by_attachment(self, tenant_id, attachment_id):
net_list_resdict = self.client.list_networks(tenant=tenant_id)
for n in net_list_resdict["networks"]:
net_id = n['id']
port_list_resdict = self.client.list_ports(net_id,
tenant=tenant_id)
for p in port_list_resdict["ports"]:
port_id = p["id"]
port_get_resdict = self.client.show_port_attachment(net_id,
port_id, tenant=tenant_id)
if attachment_id == port_get_resdict["attachment"]:
return (net_id, port_id)
return (None, None)

261
nova/tests/test_quantum.py Normal file
View File

@@ -0,0 +1,261 @@
# 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 import exception
from nova import log as logging
from nova import test
from nova.network.quantum import manager as quantum_manager
LOG = logging.getLogger('nova.tests.quantum_network')
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'},
{'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',
'vpn_public_address': '192.168.1.2'},
{'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,
'vpn_public_address': None,
'project_id': None},
{'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,
'vpn_public_address': None,
'project_id': "fake_project2"}]
# 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'])
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, {})
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.") or \
nw_info[1][0]['cidr'].startswith("10."))
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("10.") or \
nw_info[1][1]['ips'][0]['ip'].startswith("10."))
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:1dba:") or \
nw_info[1][0]['cidr_v6'].startswith("2001:1dba:"))
self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1db8:") or \
nw_info[1][0]['cidr_v6'].startswith("2001:1db8:"))
# v6 address
self.assertTrue(\
nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dba:") or \
nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1dba:"))
self.assertTrue(\
nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1db8:") or \
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, {})
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 QuantumFakeIPAMTestCase(QuantumTestCaseBase, test.TestCase):
def setUp(self):
super(QuantumFakeIPAMTestCase, self).setUp()
self.net_man = quantum_manager.QuantumManager( \
ipam_lib="nova.network.quantum.fake")
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")
# tests seem to create some networks by default, which
# 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'])
# Cannot run this unit tests auotmatically for now, as it requires
# melange to be running locally.
#
#class QuantumMelangeIPAMTestCase(QuantumTestCaseBase, test.TestCase):
#
# def setUp(self):
# super(QuantumMelangeIPAMTestCase, self).setUp()
# self.net_man = quantum_manager.QuantumManager( \
# ipam_lib="nova.network.quantum.melange_ipam_lib")