merged trunk

This commit is contained in:
Chris Behrens
2011-09-08 12:40:45 -07:00
21 changed files with 1832 additions and 27 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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')

View File

@@ -420,6 +420,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)
@@ -715,6 +720,11 @@ def network_get_by_bridge(context, bridge):
return IMPL.network_get_by_bridge(context, bridge)
def network_get_by_uuid(context, uuid):
"""Get a network by uuid or raise if it does not exist."""
return IMPL.network_get_by_uuid(context, uuid)
def network_get_by_cidr(context, cidr):
"""Get a network by cidr or raise if it does not exist"""
return IMPL.network_get_by_cidr(context, cidr)

View File

@@ -944,6 +944,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.
@@ -1857,6 +1873,19 @@ def network_get_by_bridge(context, bridge):
return result
@require_admin_context
def network_get_by_uuid(context, uuid):
session = get_session()
result = session.query(models.Network).\
filter_by(uuid=uuid).\
filter_by(deleted=False).\
first()
if not result:
raise exception.NetworkNotFoundForUUID(uuid=uuid)
return result
@require_admin_context
def network_get_by_cidr(context, cidr):
session = get_session()

View File

@@ -0,0 +1,44 @@
# 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 sqlalchemy import *
from migrate import *
from nova import log as logging
from nova import utils
meta = MetaData()
networks = Table('networks', meta,
Column("id", Integer(), primary_key=True, nullable=False))
# Add priority column to networks table
priority = Column('priority', Integer())
def upgrade(migrate_engine):
meta.bind = migrate_engine
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

@@ -628,6 +628,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

@@ -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.")

View File

@@ -549,21 +549,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 _ in xrange(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."""
@@ -792,6 +794,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))
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,307 @@
# 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
# FIXME(danwent): All content in this file should be removed once the
# packaging work for the quantum client libraries is complete.
# At that point, we will be able to just install the libraries as a
# dependency and import from quantum.client.* and quantum.common.*
# Until then, we have simplified versions of these classes in this file.
class JSONSerializer(object):
"""This is a simple json-only serializer to use until we can just grab
the standard serializer from the quantum library.
"""
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)
# The full client lib will expose more
# granular exceptions, for now, just try to distinguish
# between the cases we care about.
class QuantumNotFoundException(Exception):
"""Indicates that Quantum Server returned 404"""
pass
class QuantumServerException(Exception):
"""Indicates any non-404 error from Quantum Server"""
pass
class QuantumIOException(Exception):
"""Indicates network IO trouble reaching Quantum Server"""
pass
class api_call(object):
"""A Decorator to add support for format and tenant overriding"""
def __init__(self, func):
self.func = func
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 = None
try:
ret = self.func(instance, *args)
finally:
(instance.format, instance.tenant) = (format, tenant)
return ret
return with_params
class Client(object):
"""A base client class - derived from Glance.BaseClient"""
action_prefix = '/v1.0/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", testing_stub=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 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
"""
self.host = host
self.port = port
self.use_ssl = use_ssl
self.tenant = tenant
self.format = format
self.connection = None
self.testing_stub = testing_stub
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.testing_stub:
return self.testing_stub
elif 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)s %(action)s\n" %
locals()))
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 == httplib.NOT_FOUND:
raise QuantumNotFoundException(
_("Quantum entity not found: %s" % data))
if status_code in (httplib.OK,
httplib.CREATED,
httplib.ACCEPTED,
httplib.NO_CONTENT):
if data is not None and len(data):
return self.deserialize(data, status_code)
else:
raise QuantumServerException(
_("Server %(status_code)s error: %(data)s"
% locals()))
except (socket.error, IOError), e:
raise QuantumIOException(_("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 not data:
return None
elif type(data) is dict:
return JSONSerializer().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 JSONSerializer().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,324 @@
# 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.network import manager
from nova.network.quantum import quantum_connection
from nova import utils
LOG = logging.getLogger("nova.network.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):
"""NetworkManager class that communicates with a Quantum service
via a web services API to provision VM network connectivity.
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 any of the 'gateway'
functionality implemented by the Nova VlanManager, including:
* floating IPs
* DHCP
* NAT gateway
Support for these capabilities are targted for future releases.
"""
def __init__(self, q_conn=None, ipam_lib=None, *args, **kwargs):
"""Initialize two key libraries, the connection to a
Quantum service, and the library for implementing IPAM.
Calls inherited FlatManager constructor.
"""
if not q_conn:
q_conn = quantum_connection.QuantumClientConnection()
self.q_conn = q_conn
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, uuid=None,
**kwargs):
"""Unlike other NetworkManagers, with QuantumManager, each
create_networks calls should create only a single network.
Two scenarios exist:
- no 'uuid' is specified, in which case we contact
Quantum and create a new network.
- an existing 'uuid' is specified, corresponding to
a Quantum network created out of band.
In both cases, we initialize a subnet using the IPAM lib.
"""
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 = uuid
if quantum_net_id:
if not self.q_conn.network_exists(q_tenant_id, quantum_net_id):
raise Exception(_("Unable to find existing quantum " \
" network for tenant '%(q_tenant_id)s' with "
"net-id '%(quantum_net_id)s'" % locals()))
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)
priority = kwargs.get("priority", 0)
self.ipam.create_subnet(context, label, ipam_tenant_id, quantum_net_id,
priority, cidr, gateway_v6, cidr_v6, dns1, dns2)
def delete_network(self, context, fixed_range):
"""Lookup network by IPv4 cidr, delete both the IPAM
subnet and the corresponding Quantum network.
"""
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)
q_tenant_id = project_id or FLAGS.quantum_default_tenant_id
self.q_conn.delete_network(q_tenant_id, quantum_net_id)
def allocate_for_instance(self, context, **kwargs):
"""Called by compute when it is creating a new VM.
There are three key tasks:
- Determine the number and order of vNICs to create
- Allocate IP addresses
- Create ports on a Quantum network and attach vNICs.
We support two approaches to determining vNICs:
- By default, a VM gets a vNIC for any network belonging
to the VM's project, and a vNIC for any "global" network
that has a NULL project_id. vNIC order is determined
by the network's 'priority' field.
- If the 'os-create-server-ext' was used to create the VM,
only the networks in 'requested_networks' are used to
create vNICs, and the vNIC order is determiend by the
order in the requested_networks array.
For each vNIC, use the FlatManager to create the entries
in the virtual_interfaces table, contact Quantum to
create a port and attachment the vNIC, and use the IPAM
lib to allocate IP addresses.
"""
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)
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 (quantum_net_id, project_id) in net_proj_pairs:
# FIXME(danwent): We'd like to have the manager be
# completely decoupled from the nova networks table.
# However, other parts of nova sometimes go behind our
# back and access network data directly from the DB. So
# for now, the quantum manager knows that there is a nova
# networks DB table and accesses it here. updating the
# virtual_interfaces table to use UUIDs would be one
# solution, but this would require significant work
# elsewhere.
admin_context = context.elevated()
network_ref = db.network_get_by_uuid(admin_context,
quantum_net_id)
vif_rec = manager.FlatManager.add_virtual_interface(self,
context, instance_id, network_ref['id'])
# talk to Quantum API to create and attach port.
q_tenant_id = project_id or FLAGS.quantum_default_tenant_id
self.q_conn.create_and_attach_port(q_tenant_id, quantum_net_id,
vif_rec['uuid'])
self.ipam.allocate_fixed_ip(context, project_id, quantum_net_id,
vif_rec)
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):
"""This method is used by compute to fetch all network data
that should be used when creating the VM.
The method simply loops through all virtual interfaces
stored in the nova DB and queries the IPAM lib to get
the associated IP data.
The format of returned data is 'defined' by the initial
set of NetworkManagers found in nova/network/manager.py .
Ideally this 'interface' will be more formally defined
in the future.
"""
network_info = []
instance = db.instance_get(context, instance_id)
project_id = instance.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:
# TODO(bgh): We need to figure out a way to tell if we
# should actually be raising this exception or not.
# In the case that a VM spawn failed it may not have
# attached the vif and raising the exception here
# prevents deletion of the VM. In that case we should
# probably just log, continue, and move on.
raise Exception(_("No network for for virtual interface %s") %
vif['uuid'])
(v4_subnet, v6_subnet) = self.ipam.get_subnets_by_net_id(context,
ipam_tenant_id, net_id)
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:
if v6_subnet['cidr']:
network_dict['cidr_v6'] = v6_subnet['cidr']
info['ip6s'] = [ip_dict(ip, v6_subnet) for ip in v6_ips]
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 and 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):
"""Called when a VM is terminated. Loop through each virtual
interface in the Nova DB and remove the Quantum port and
clear the IP allocation using the IPAM. Finally, remove
the virtual interfaces from the Nova DB.
"""
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)
try:
db.virtual_interface_delete_by_instance(admin_context,
instance_id)
except exception.InstanceNotFound:
LOG.error(_("Attempted to deallocate non-existent instance: %s" %
(instance_id)))
def validate_networks(self, context, networks):
"""Validates that this tenant has quantum networks with the associated
UUIDs. This is called by the 'os-create-server-ext' API extension
code so that we can return an API error code to the caller if they
request an invalid network.
"""
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,141 @@
# 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"}
# FIXME(danwent): talk to the Melange folks about creating a
# client lib that we can import as a library, instead of
# have to have all of the client code in here.
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
self.version = "v0.1"
def get(self, path, params=None, headers=None):
return self.do_request("GET", path, params=params, headers=headers)
def post(self, path, body=None, headers=None):
return self.do_request("POST", path, body=body, headers=headers)
def delete(self, path, headers=None):
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=None, params=None):
headers = headers or {}
params = params or {}
url = "/%s/%s.json" % (self.version, path)
if params:
url += "?%s" % 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,205 @@
# 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 db
from nova import exception
from nova import flags
from nova import log as logging
from nova.network.quantum import melange_connection
LOG = logging.getLogger("nova.network.quantum.melange_ipam_lib")
FLAGS = flags.FLAGS
def get_ipam_lib(net_man):
return QuantumMelangeIPAMLib()
class QuantumMelangeIPAMLib(object):
"""Implements Quantum IP Address Management (IPAM) interface
using the Melange service, which is access using the Melange
web services API.
"""
def __init__(self):
"""Initialize class used to connect to Melange server"""
self.m_conn = melange_connection.MelangeConnection()
def create_subnet(self, context, label, project_id,
quantum_net_id, priority, cidr=None,
gateway_v6=None, cidr_v6=None,
dns1=None, dns2=None):
"""Contact Melange and create a subnet for any non-NULL
IPv4 or IPv6 subnets.
Also create a entry in the Nova networks DB, but only
to store values not represented in Melange or to
temporarily provide compatibility with Nova code that
accesses IPAM data directly via the DB (e.g., nova-api)
"""
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)
net = {"uuid": quantum_net_id,
"project_id": project_id,
"priority": priority,
"label": label}
admin_context = context.elevated()
network = db.network_create_safe(admin_context, net)
def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref):
"""Pass call to allocate fixed IP on to Melange"""
tenant_id = project_id or FLAGS.quantum_default_tenant_id
self.m_conn.allocate_ip(quantum_net_id,
vif_ref['uuid'], project_id=tenant_id,
mac_address=vif_ref['address'])
def get_network_id_by_cidr(self, context, cidr, project_id):
"""Find the Quantum UUID associated with a IPv4 CIDR
address for the specified tenant.
"""
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']
raise exception.NotFound(_("No network found for cidr %s" % cidr))
def delete_subnets_by_net_id(self, context, net_id, project_id):
"""Find Melange block associated with the Quantum UUID,
then tell Melange to delete that block.
"""
admin_context = context.elevated()
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)
network = db.network_get_by_uuid(admin_context, net_id)
db.network_delete_safe(context, network['id'])
def get_project_and_global_net_ids(self, context, project_id):
"""Fetches all networks associated with this project, or
that are "global" (i.e., have no project set).
Returns list sorted by 'priority' (lowest integer value
is highest priority).
"""
if project_id is None:
raise Exception(_("get_project_and_global_net_ids must be called"
" with a non-null project_id"))
admin_context = context.elevated()
# Decorate with priority
priority_nets = []
for tenant_id in (project_id, FLAGS.quantum_default_tenant_id):
blocks = self.m_conn.get_blocks(tenant_id)
for ip_block in blocks['ip_blocks']:
network_id = ip_block['network_id']
network = db.network_get_by_uuid(admin_context, network_id)
if network:
priority = network['priority']
priority_nets.append((priority, network_id, tenant_id))
# Sort by priority
priority_nets.sort()
# Undecorate
return [(network_id, tenant_id)
for priority, network_id, tenant_id in priority_nets]
def get_subnets_by_net_id(self, context, project_id, net_id):
"""Returns information about the IPv4 and IPv6 subnets
associated with a Quantum Network UUID.
"""
# FIXME(danwent): Melange actually returns the subnet info
# when we query for a particular interface. We may want to
# rework the ipam_manager python API to let us take advantage of
# this, as right now we have to get all blocks and cycle through
# them.
subnet_v4 = None
subnet_v6 = None
tenant_id = project_id or FLAGS.quantum_default_tenant_id
all_blocks = self.m_conn.get_blocks(tenant_id)
for b in all_blocks['ip_blocks']:
if b['network_id'] == net_id:
subnet = {'network_id': b['network_id'],
'cidr': b['cidr'],
'gateway': b['gateway'],
'broadcast': b['broadcast'],
'netmask': b['netmask'],
'dns1': b['dns1'],
'dns2': b['dns2']}
if IPNetwork(b['cidr']).version == 6:
subnet_v6 = subnet
else:
subnet_v4 = subnet
return (subnet_v4, subnet_v6)
def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id):
"""Returns a list of IPv4 address strings associated with
the specified virtual interface.
"""
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):
"""Returns a list of IPv6 address strings associated with
the specified virtual interface.
"""
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):
"""Helper method to fetch v4 or v6 addresses for a particular
virtual interface.
"""
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):
"""Confirms that a subnet exists that is associated with the
specified Quantum Network UUID.
"""
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):
"""Deallocate all fixed IPs associated with the specified
virtual interface.
"""
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,195 @@
# 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 netaddr
from nova import db
from nova import exception
from nova import flags
from nova import ipv6
from nova import log as logging
from nova.network import manager
from nova.network.quantum import melange_connection as melange
from nova import utils
LOG = logging.getLogger("nova.network.quantum.nova_ipam_lib")
FLAGS = flags.FLAGS
def get_ipam_lib(net_man):
return QuantumNovaIPAMLib(net_man)
class QuantumNovaIPAMLib(object):
"""Implements Quantum IP Address Management (IPAM) interface
using the local Nova database. This implementation is inline
with how IPAM is used by other NetworkManagers.
"""
def __init__(self, net_manager):
"""Holds a reference to the "parent" network manager, used
to take advantage of various FlatManager methods to avoid
code duplication.
"""
self.net_manager = net_manager
def create_subnet(self, context, label, tenant_id,
quantum_net_id, priority, cidr=None,
gateway_v6=None, cidr_v6=None,
dns1=None, dns2=None):
"""Re-use the basic FlatManager create_networks method to
initialize the networks and fixed_ips tables in Nova DB.
Also stores a few more fields in the networks table that
are needed by Quantum but not the FlatManager.
"""
admin_context = context.elevated()
subnet_size = len(netaddr.IPNetwork(cidr))
networks = 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)
if len(networks) != 1:
raise Exception(_("Error creating network entry"))
network = networks[0]
net = {"project_id": tenant_id,
"priority": priority,
"uuid": quantum_net_id}
db.network_update(admin_context, network['id'], net)
def get_network_id_by_cidr(self, context, cidr, project_id):
""" Grabs Quantum network UUID based on IPv4 CIDR. """
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['uuid']
def delete_subnets_by_net_id(self, context, net_id, project_id):
"""Deletes a network based on Quantum UUID. Uses FlatManager
delete_network to avoid duplication.
"""
admin_context = context.elevated()
network = db.network_get_by_uuid(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):
"""Fetches all networks associated with this project, or
that are "global" (i.e., have no project set).
Returns list sorted by 'priority'.
"""
admin_context = context.elevated()
networks = db.project_get_networks(admin_context, project_id, False)
networks.extend(db.project_get_networks(admin_context, None, False))
id_priority_map = {}
net_list = []
for n in networks:
net_id = n['uuid']
net_list.append((net_id, n["project_id"]))
id_priority_map[net_id] = n['priority']
return sorted(net_list, key=lambda x: id_priority_map[x[0]])
def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec):
"""Allocates a single fixed IPv4 address for a virtual interface."""
admin_context = context.elevated()
network = db.network_get_by_uuid(admin_context, quantum_net_id)
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):
"""Returns information about the IPv4 and IPv6 subnets
associated with a Quantum Network UUID.
"""
n = db.network_get_by_uuid(context.elevated(), net_id)
subnet_data_v4 = {
'network_id': n['uuid'],
'cidr': n['cidr'],
'gateway': n['gateway'],
'broadcast': n['broadcast'],
'netmask': n['netmask'],
'dns1': n['dns1'],
'dns2': n['dns2']}
subnet_data_v6 = {
'network_id': n['uuid'],
'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):
"""Returns a list of IPv4 address strings associated with
the specified virtual interface, based on the fixed_ips table.
"""
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 [fixed_ip['address'] for fixed_ip in fixed_ips]
def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id):
"""Returns a list containing a single IPv6 address strings
associated with the specified virtual interface.
"""
admin_context = context.elevated()
network = db.network_get_by_uuid(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):
"""Confirms that a subnet exists that is associated with the
specified Quantum Network UUID. Raises an exception if no
such subnet exists.
"""
admin_context = context.elevated()
db.network_get_by_uuid(admin_context, quantum_net_id)
def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref):
"""Deallocate all fixed IPs associated with the specified
virtual interface.
"""
try:
admin_context = context.elevated()
fixed_ips = db.fixed_ip_get_by_virtual_interface(admin_context,
vif_ref['id'])
for fixed_ip in fixed_ips:
db.fixed_ip_update(admin_context, fixed_ip['address'],
{'allocated': False,
'virtual_interface_id': None})
except exception.FixedIpNotFoundForInstance:
LOG.error(_('No fixed IPs to deallocate for vif %s' %
vif_ref['id']))

View File

@@ -0,0 +1,118 @@
# 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.network.quantum import client as quantum_client
from nova import utils
LOG = logging.getLogger("nova.network.quantum.quantum_connection")
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(object):
"""Abstracts connection to Quantum service into higher level
operations performed by the QuantumManager.
Separating this out as a class also let's us create a 'fake'
version of this class for unit tests.
"""
def __init__(self):
"""Initialize Quantum client class based on flags."""
self.client = quantum_client.Client(FLAGS.quantum_connection_host,
FLAGS.quantum_connection_port,
format="json",
logger=LOG)
def create_network(self, tenant_id, network_name):
"""Create network using specified name, return Quantum
network UUID.
"""
data = {'network': {'name': network_name}}
resdict = self.client.create_network(data, tenant=tenant_id)
return resdict["network"]["id"]
def delete_network(self, tenant_id, net_id):
"""Deletes Quantum network with specified UUID."""
self.client.delete_network(net_id, tenant=tenant_id)
def network_exists(self, tenant_id, net_id):
"""Determine if a Quantum network exists for the
specified tenant.
"""
try:
self.client.show_network_details(net_id, tenant=tenant_id)
return True
except client.QuantumNotFoundException:
# Not really an error. Real errors will be propogated to caller
return False
def create_and_attach_port(self, tenant_id, net_id, interface_id):
"""Creates a Quantum port on the specified network, sets
status to ACTIVE to enable traffic, and attaches the
vNIC with the specified interface-id.
"""
LOG.debug(_("Connecting interface %(interface_id)s to "
"net %(net_id)s for %(tenant_id)s" % locals()))
port_data = {'port': {'state': 'ACTIVE'}}
resdict = self.client.create_port(net_id, port_data, tenant=tenant_id)
port_id = resdict["port"]["id"]
attach_data = {'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):
"""Detach and delete the specified Quantum port."""
LOG.debug(_("Deleting port %(port_id)s on net %(net_id)s"
" for %(tenant_id)s" % locals()))
self.client.detach_resource(net_id, port_id, tenant=tenant_id)
self.client.delete_port(net_id, port_id, tenant=tenant_id)
def get_port_by_attachment(self, tenant_id, attachment_id):
"""Given a tenant, search for the Quantum network and port
UUID that has the specified interface-id attachment.
"""
# FIXME(danwent): this will be inefficient until the Quantum
# API implements querying a port by the interface-id
net_list_resdict = self.client.list_networks(tenant=tenant_id)
for n in net_list_resdict["networks"]:
net_id = n['id']
port_list_resdict = self.client.list_ports(net_id,
tenant=tenant_id)
for p in port_list_resdict["ports"]:
port_id = p["id"]
port_get_resdict = self.client.show_port_attachment(net_id,
port_id, tenant=tenant_id)
if attachment_id == port_get_resdict["attachment"]["id"]:
return (net_id, port_id)
return (None, None)

View File

@@ -55,5 +55,17 @@ class BaseScheduler(abstract_scheduler.AbstractScheduler):
scheduling objectives
"""
# NOTE(sirp): The default logic is the same as the NoopCostFunction
return [dict(weight=1, hostname=hostname, capabilities=capabilities)
for hostname, capabilities in hosts]
hosts = [dict(weight=1, hostname=hostname, capabilities=capabilities)
for hostname, capabilities in hosts]
# NOTE(Vek): What we actually need to return is enough hosts
# for all the instances!
num_instances = request_spec.get('num_instances', 1)
instances = []
while num_instances > len(hosts):
instances.extend(hosts)
num_instances -= len(hosts)
if num_instances > 0:
instances.extend(hosts[:num_instances])
return instances

View File

@@ -26,6 +26,7 @@ from nova import test
from nova.compute import api as compute_api
from nova.scheduler import driver
from nova.scheduler import abstract_scheduler
from nova.scheduler import base_scheduler
from nova.scheduler import zone_manager
@@ -65,6 +66,11 @@ class FakeAbstractScheduler(abstract_scheduler.AbstractScheduler):
pass
class FakeBaseScheduler(base_scheduler.BaseScheduler):
# No need to stub anything at the moment
pass
class FakeZoneManager(zone_manager.ZoneManager):
def __init__(self):
self.service_states = {
@@ -387,3 +393,30 @@ class AbstractSchedulerTestCase(test.TestCase):
# 0 from local zones, 12 from remotes
self.assertEqual(12, len(build_plan))
class BaseSchedulerTestCase(test.TestCase):
"""Test case for Base Scheduler."""
def test_weigh_hosts(self):
"""
Try to weigh a short list of hosts and make sure enough
entries for a larger number instances are returned.
"""
sched = FakeBaseScheduler()
# Fake out a list of hosts
zm = FakeZoneManager()
hostlist = [(host, services['compute'])
for host, services in zm.service_states.items()
if 'compute' in services]
# Call weigh_hosts()
num_instances = len(hostlist) * 2 + len(hostlist) / 2
instlist = sched.weigh_hosts('compute',
dict(num_instances=num_instances),
hostlist)
# Should be enough entries to cover all instances
self.assertEqual(len(instlist), num_instances)

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

@@ -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)

View File

@@ -101,7 +101,7 @@ class LibvirtOpenVswitchDriver(VIFDriver):
"""VIF driver for Open vSwitch."""
def get_dev_name(_self, iface_id):
return "tap-" + iface_id[0:15]
return "tap" + iface_id[0:11]
def plug(self, instance, network, mapping):
iface_id = mapping['vif_uuid']

View File

@@ -188,9 +188,16 @@ class VMOps(object):
ramdisk = VMHelper.fetch_image(context, self._session,
instance, instance.ramdisk_id, instance.user_id,
instance.project_id, ImageType.RAMDISK)[0]
# Create the VM ref and attach the first disk
first_vdi_ref = self._session.call_xenapi('VDI.get_by_uuid',
vdis[0]['vdi_uuid'])
# NOTE(jk0): Since vdi_type may contain either 'os' or 'swap', we
# need to ensure that the 'swap' VDI is not chosen as the mount
# point for file injection.
first_vdi_ref = None
for vdi in vdis:
if vdi.get('vdi_type') != 'swap':
# Create the VM ref and attach the first disk
first_vdi_ref = self._session.call_xenapi(
'VDI.get_by_uuid', vdi['vdi_uuid'])
vm_mode = instance.vm_mode and instance.vm_mode.lower()
if vm_mode == 'pv':