Implementation of second phase of provider extension.

Enhances provider extension to support flat networks and VLANs on
multiple physical networks. Implements blueprint provider-networks.

To create a flat network using the CLI with admin rights:

net-create --tenant_id <tenant-id> <net-name> --provider:network_type flat --provider:physical_network <physical-network>

To create a VLAN network using the CLI with admin rights:

net-create --tenant_id <tenant-id> <net-name> --provider:network_type vlan --provider:physical_network <physical-network> --provider:vlan_id <vlan-id>

The provider extension is supported by the linuxbridge and openvswitch
plugins and their agents [openvswitch phase 2 implementation is
in-progress, and does not yet support flat networks or multiple
interfaces].

Ranges of VLANs available on named physical networks for normal
allocation are specified in the plugin's config file via the
ListOpt syntax:

network_vlan_ranges = <physical_network>:<vlan_min>:<vlan_max>

The mapping of each named physical network to its physical network
interface is specified (per-agent-host) in the agent's config file via
the ListOpt syntax:

physical_interface_mappings = <physical_network>:<physical_interface>

See quantum/plugins/linuxbridge/README for details and examples of
network_vlan_ranges and physical_interface_mappings usage.

Also, bulk operations are enabled for the linuxbridge plugin.

Change-Id: I93402bd5cc6316e9408ea71c3b3989d06898ee30
This commit is contained in:
Bob Kukura 2012-08-06 09:42:20 -04:00
parent 8e66b33b20
commit ce1638c283
22 changed files with 839 additions and 581 deletions

View File

@ -43,7 +43,7 @@ api_paste_config = api-paste.ini
# allow_bulk = True
# RPC configuration options. Defined in rpc __init__
# The messaging module to use, defaults to kombu.
# rpc_backend = quantum.openstack.common.notifier.rpc.impl_kombu
# rpc_backend = quantum.openstack.common.rpc.impl_kombu
# Size of RPC thread pool
# rpc_thread_pool_size = 64,
# Size of RPC connection pool

View File

@ -1,6 +1,9 @@
[VLANS]
vlan_start = 1000
vlan_end = 3000
# (ListOpt) Comma-separated list of
# <physical_network>:<vlan_min>:<vlan_max> tuples enumerating ranges
# of VLAN IDs on named physical networks that are available for
# allocation.
# network_vlan_ranges = default:1000:2999
[DATABASE]
# This line MUST be changed to actually run the plugin.
@ -16,8 +19,12 @@ sql_connection = sqlite://
reconnect_interval = 2
[LINUX_BRIDGE]
# This is the interface connected to the switch on your Quantum network
physical_interface = eth1
# (ListOpt) Comma-separated list of
# <physical_network>:<physical_interface> tuples mapping physical
# network names to agent's node-specific physical network
# interfaces. Server uses physical network names for validation but
# ignores interfaces.
# physical_interface_mappings = default:eth1
[AGENT]
# Agent's polling interval in seconds
@ -25,7 +32,5 @@ polling_interval = 2
# Change to "sudo quantum-rootwrap" to limit commands that can be run
# as root.
root_helper = sudo
# Use Quantumv2 API
# target_v2_api = False
# Use RPC messaging to interface between agent and plugin
# rpc = True

View File

@ -27,6 +27,10 @@ from quantum.common import exceptions as q_exc
LOG = logging.getLogger(__name__)
def is_attr_set(attribute):
return attribute not in (None, ATTR_NOT_SPECIFIED)
def _validate_boolean(data, valid_values=None):
if data in [True, False]:
return
@ -46,6 +50,19 @@ def _validate_values(data, valid_values=None):
return msg
def _validate_range(data, valid_values=None):
min_value = valid_values[0]
max_value = valid_values[1]
if data >= min_value and data <= max_value:
return
else:
msg_dict = dict(data=data, min_value=min_value, max_value=max_value)
msg = _("%(data)s is not in range %(min_value)s through "
"%(max_value)s") % msg_dict
LOG.debug("validate_range: %s", msg)
return msg
def _validate_mac_address(data, valid_values=None):
try:
netaddr.EUI(data)
@ -120,6 +137,7 @@ MAC_PATTERN = "^%s[aceACE02468](:%s{2}){5}$" % (HEX_ELEM, HEX_ELEM)
# Dictionary that maintains a list of validation functions
validators = {'type:boolean': _validate_boolean,
'type:values': _validate_values,
'type:range': _validate_range,
'type:mac_address': _validate_mac_address,
'type:ip_address': _validate_ip_address,
'type:ip_address_or_none': _validate_ip_address_or_none,

View File

@ -119,7 +119,8 @@ class IpAddressInUse(InUse):
class VlanIdInUse(InUse):
message = _("Unable to create the network. "
"The VLAN %(vlan_id)s is in use.")
"The VLAN %(vlan_id)s on physical network "
"%(physical_network)s is in use.")
class ResourceExhausted(QuantumException):

View File

@ -755,7 +755,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
def update_network(self, context, id, network):
n = network['network']
with context.session.begin():
with context.session.begin(subtransactions=True):
network = self._get_network(context, id)
# validate 'shared' parameter
if 'shared' in n:
@ -764,7 +764,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
return self._make_network_dict(network)
def delete_network(self, context, id):
with context.session.begin():
with context.session.begin(subtransactions=True):
network = self._get_network(context, id)
filter = {'network_id': [id]}
@ -874,7 +874,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
s = subnet['subnet']
self._validate_subnet(s)
with context.session.begin():
with context.session.begin(subtransactions=True):
if "dns_nameservers" in s:
old_dns_list = self._get_dns_by_subnet(context, id)
@ -922,7 +922,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
return self._make_subnet_dict(subnet)
def delete_subnet(self, context, id):
with context.session.begin():
with context.session.begin(subtransactions=True):
subnet = self._get_subnet(context, id)
# Check if ports are using this subnet
allocated_qry = context.session.query(models_v2.IPAllocation)
@ -999,7 +999,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
def update_port(self, context, id, port):
p = port['port']
with context.session.begin():
with context.session.begin(subtransactions=True):
port = self._get_port(context, id)
# Check if the IPs need to be updated
if 'fixed_ips' in p:
@ -1024,7 +1024,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
return self._make_port_dict(port)
def delete_port(self, context, id):
with context.session.begin():
with context.session.begin(subtransactions=True):
port = self._get_port(context, id)
allocated_qry = context.session.query(models_v2.IPAllocation)

View File

@ -17,9 +17,17 @@ from quantum.api.v2 import attributes
EXTENDED_ATTRIBUTES_2_0 = {
'networks': {
# TODO(rkukura): specify validation
'provider:vlan_id': {'allow_post': True, 'allow_put': False,
'provider:network_type': {'allow_post': True, 'allow_put': True,
'validate': {'type:values': ['flat',
'vlan']},
'default': attributes.ATTR_NOT_SPECIFIED,
'is_visible': True},
'provider:physical_network': {'allow_post': True, 'allow_put': True,
'default': attributes.ATTR_NOT_SPECIFIED,
'is_visible': True},
'provider:vlan_id': {'allow_post': True, 'allow_put': True,
'convert_to': int,
'validate': {'type:range': (1, 4095)},
'default': attributes.ATTR_NOT_SPECIFIED,
'is_visible': True},
}

View File

@ -58,9 +58,9 @@ quantum_use_dhcp=true
Make the Linux Bridge plugin the current quantum plugin
- edit quantum.conf and change the core_plugin according to the API version
V1: "core_plugin = quantum.plugins.linuxbridge.LinuxBridgePlugin.LinuxBridgePlugin"
V2: "core_plugin = quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2"
- edit quantum.conf and change the core_plugin
core_plugin = quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2
# -- Database config.
@ -104,10 +104,26 @@ mysql> FLUSH PRIVILEGES;
actually running the server set it to mysql. At any given time, only one
of these should be active in the conf file (you can comment out the other).
- Remember to change the interface configuration to indicate the correct
ethernet interface on that particular host which is being used to participate
in the Quantum networks. This configuration has to be applied on each host
on which the agent runs.
- On the quantum server, network_vlan_ranges must be configured in
linuxbridge_conf.ini to specify the names of the physical networks
managed by the linuxbridge plugin, along with the ranges of VLAN IDs
available on each physical network for allocation to virtual
networks. An entry of the form
"<physical_network>:<vlan_min>:<vlan_max>" specifies a VLAN range on
the named physical network. An entry of the form
"<physical_network>" specifies a named network without making a
range of VLANs available for allocation. Networks specified using
either form are available for adminstrators to create provider flat
networks and provider VLANs. Multiple VLAN ranges can be specified
for the same physical network.
The following example linuxbridge_conf.ini entry shows three
physical networks that can be used to create provider networks, with
ranges of VLANs available for allocation on two of them:
[VLANS]
network_vlan_ranges = physnet1:1000:2999,physnet1:3000:3999,physnet2,physnet3:1:4094
# -- Agent configuration
@ -122,6 +138,22 @@ mysql> FLUSH PRIVILEGES;
Note: debug and logging information should be updated in etc/quantum.conf
- On each compute node, the network_interface_mappings must be
configured in linuxbridge_conf.ini to map each physical network name
to the physical interface connecting the node to that physical
network. Entries are of the form
"<physical_network>:<physical_interface>". For example, one compute
node may use the following physical_inteface_mappings entries:
[LINUX_BRIDGE]
physical_interface_mappings = physnet1:eth1,physnet2:eth2,physnet3:eth3
while another might use:
[LINUX_BRIDGE]
physical_interface_mappings = physnet1:em3,physnet2:em2,physnet3:em1
$ Run the following:
python linuxbridge_quantum_agent.py --config-file quantum.conf
--config-file linuxbridge_conf.ini

View File

@ -34,6 +34,7 @@ import eventlet
import pyudev
from sqlalchemy.ext.sqlsoup import SqlSoup
from quantum.agent.linux import utils
from quantum.agent import rpc as agent_rpc
from quantum.common import config as logging_config
from quantum.common import topics
@ -42,8 +43,7 @@ from quantum.openstack.common import context
from quantum.openstack.common import rpc
from quantum.openstack.common.rpc import dispatcher
from quantum.plugins.linuxbridge.common import config
from quantum.agent.linux import utils
from quantum.plugins.linuxbridge.common import constants
logging.basicConfig()
LOG = logging.getLogger(__name__)
@ -67,9 +67,8 @@ DEFAULT_RECONNECT_INTERVAL = 2
class LinuxBridge:
def __init__(self, br_name_prefix, physical_interface, root_helper):
self.br_name_prefix = br_name_prefix
self.physical_interface = physical_interface
def __init__(self, interface_mappings, root_helper):
self.interface_mappings = interface_mappings
self.root_helper = root_helper
def device_exists(self, device):
@ -92,14 +91,14 @@ class LinuxBridge:
if not network_id:
LOG.warning("Invalid Network ID, will lead to incorrect bridge"
"name")
bridge_name = self.br_name_prefix + network_id[0:11]
bridge_name = BRIDGE_NAME_PREFIX + network_id[0:11]
return bridge_name
def get_subinterface_name(self, vlan_id):
def get_subinterface_name(self, physical_interface, vlan_id):
if not vlan_id:
LOG.warning("Invalid VLAN ID, will lead to incorrect "
"subinterface name")
subinterface_name = '%s.%s' % (self.physical_interface, vlan_id)
subinterface_name = '%s.%s' % (physical_interface, vlan_id)
return subinterface_name
def get_tap_device_name(self, interface_id):
@ -174,21 +173,27 @@ class LinuxBridge:
DEVICE_NAME_PLACEHOLDER, device_name)
return os.path.exists(bridge_port_path)
def ensure_vlan_bridge(self, network_id, vlan_id):
def ensure_vlan_bridge(self, network_id, physical_interface, vlan_id):
"""Create a vlan and bridge unless they already exist."""
interface = self.ensure_vlan(vlan_id)
interface = self.ensure_vlan(physical_interface, vlan_id)
bridge_name = self.get_bridge_name(network_id)
self.ensure_bridge(bridge_name, interface)
return interface
def ensure_vlan(self, vlan_id):
def ensure_flat_bridge(self, network_id, physical_interface):
"""Create a non-vlan bridge unless it already exists."""
bridge_name = self.get_bridge_name(network_id)
self.ensure_bridge(bridge_name, physical_interface)
return physical_interface
def ensure_vlan(self, physical_interface, vlan_id):
"""Create a vlan unless it already exists."""
interface = self.get_subinterface_name(vlan_id)
interface = self.get_subinterface_name(physical_interface, vlan_id)
if not self.device_exists(interface):
LOG.debug("Creating subinterface %s for VLAN %s on interface %s" %
(interface, vlan_id, self.physical_interface))
(interface, vlan_id, physical_interface))
if utils.execute(['ip', 'link', 'add', 'link',
self.physical_interface,
physical_interface,
'name', interface, 'type', 'vlan', 'id',
vlan_id], root_helper=self.root_helper):
return
@ -225,7 +230,8 @@ class LinuxBridge:
utils.execute(['brctl', 'addif', bridge_name, interface],
root_helper=self.root_helper)
def add_tap_interface(self, network_id, vlan_id, tap_device_name):
def add_tap_interface(self, network_id, physical_interface, vlan_id,
tap_device_name):
"""
If a VIF has been plugged into a network, this function will
add the corresponding tap device to the relevant bridge
@ -249,7 +255,10 @@ class LinuxBridge:
tap_device_name], root_helper=self.root_helper):
return False
self.ensure_vlan_bridge(network_id, vlan_id)
if int(vlan_id) == constants.FLAT_VLAN_ID:
self.ensure_flat_bridge(network_id, physical_interface)
else:
self.ensure_vlan_bridge(network_id, physical_interface, vlan_id)
if utils.execute(['brctl', 'addif', bridge_name, tap_device_name],
root_helper=self.root_helper):
return False
@ -257,26 +266,38 @@ class LinuxBridge:
bridge_name))
return True
def add_interface(self, network_id, vlan_id, interface_id):
def add_interface(self, network_id, physical_network, vlan_id,
interface_id):
if not interface_id:
"""
Since the VIF id is null, no VIF is plugged into this port
no more processing is required
"""
return False
physical_interface = self.interface_mappings.get(physical_network)
if not physical_interface:
LOG.error("No mapping for physical network %s" % physical_network)
return False
if interface_id.startswith(GATEWAY_INTERFACE_PREFIX):
return self.add_tap_interface(network_id, vlan_id, interface_id)
return self.add_tap_interface(network_id,
physical_interface, vlan_id,
interface_id)
else:
tap_device_name = self.get_tap_device_name(interface_id)
return self.add_tap_interface(network_id, vlan_id, tap_device_name)
return self.add_tap_interface(network_id,
physical_interface, vlan_id,
tap_device_name)
def delete_vlan_bridge(self, bridge_name):
if self.device_exists(bridge_name):
interfaces_on_bridge = self.get_interfaces_on_bridge(bridge_name)
for interface in interfaces_on_bridge:
self.remove_interface(bridge_name, interface)
if interface.startswith(self.physical_interface):
self.delete_vlan(interface)
for physical_interface in self.interface_mappings.itervalues():
if interface.startswith(physical_interface):
self.delete_vlan(interface)
LOG.debug("Deleting bridge %s" % bridge_name)
if utils.execute(['ip', 'link', 'set', bridge_name, 'down'],
@ -360,23 +381,23 @@ class LinuxBridgeRpcCallbacks():
class LinuxBridgeQuantumAgentDB:
def __init__(self, br_name_prefix, physical_interface, polling_interval,
reconnect_interval, root_helper, target_v2_api,
db_connection_url):
def __init__(self, interface_mappings, polling_interval,
reconnect_interval, root_helper, db_connection_url):
self.polling_interval = polling_interval
self.root_helper = root_helper
self.setup_linux_bridge(br_name_prefix, physical_interface)
self.target_v2_api = target_v2_api
self.setup_linux_bridge(interface_mappings)
self.reconnect_interval = reconnect_interval
self.db_connected = False
self.db_connection_url = db_connection_url
def setup_linux_bridge(self, br_name_prefix, physical_interface):
self.linux_br = LinuxBridge(br_name_prefix, physical_interface,
self.root_helper)
def setup_linux_bridge(self, interface_mappings):
self.linux_br = LinuxBridge(interface_mappings, self.root_helper)
def process_port_binding(self, network_id, interface_id, vlan_id):
return self.linux_br.add_interface(network_id, vlan_id, interface_id)
def process_port_binding(self, network_id, interface_id,
physical_network, vlan_id):
return self.linux_br.add_interface(network_id,
physical_network, vlan_id,
interface_id)
def remove_port_binding(self, network_id, interface_id):
bridge_name = self.linux_br.get_bridge_name(network_id)
@ -437,16 +458,18 @@ class LinuxBridgeQuantumAgentDB:
old_port_bindings):
vlan_bindings = {}
try:
vlan_binds = db.vlan_bindings.all()
network_binds = db.network_bindings.all()
except Exception as e:
LOG.info("Unable to get vlan bindings! Exception: %s" % e)
LOG.info("Unable to get network bindings! Exception: %s" % e)
self.db_connected = False
return {VLAN_BINDINGS: {},
PORT_BINDINGS: []}
vlans_string = ""
for bind in vlan_binds:
entry = {'network_id': bind.network_id, 'vlan_id': bind.vlan_id}
for bind in network_binds:
entry = {'network_id': bind.network_id,
'physical_network': bind.physical_network,
'vlan_id': bind.vlan_id}
vlan_bindings[bind.network_id] = entry
vlans_string = "%s %s" % (vlans_string, entry)
@ -462,19 +485,12 @@ class LinuxBridgeQuantumAgentDB:
all_bindings = {}
for bind in port_binds:
append_entry = False
if self.target_v2_api:
all_bindings[bind.id] = bind
entry = {'network_id': bind.network_id,
'uuid': bind.id,
'status': bind.status,
'interface_id': bind.id}
append_entry = bind.admin_state_up
else:
all_bindings[bind.uuid] = bind
entry = {'network_id': bind.network_id, 'state': bind.state,
'op_status': bind.op_status, 'uuid': bind.uuid,
'interface_id': bind.interface_id}
append_entry = bind.state == 'ACTIVE'
all_bindings[bind.id] = bind
entry = {'network_id': bind.network_id,
'uuid': bind.id,
'status': bind.status,
'interface_id': bind.id}
append_entry = bind.admin_state_up
if append_entry:
port_bindings.append(entry)
@ -484,15 +500,15 @@ class LinuxBridgeQuantumAgentDB:
ports_string = "%s %s" % (ports_string, pb)
port_id = pb['uuid']
interface_id = pb['interface_id']
network_id = pb['network_id']
vlan_id = str(vlan_bindings[pb['network_id']]['vlan_id'])
if self.process_port_binding(pb['network_id'],
physical_network = vlan_bindings[network_id]['physical_network']
vlan_id = str(vlan_bindings[network_id]['vlan_id'])
if self.process_port_binding(network_id,
interface_id,
physical_network,
vlan_id):
if self.target_v2_api:
all_bindings[port_id].status = OP_STATUS_UP
else:
all_bindings[port_id].op_status = OP_STATUS_UP
all_bindings[port_id].status = OP_STATUS_UP
plugged_interfaces.append(interface_id)
@ -539,15 +555,16 @@ class LinuxBridgeQuantumAgentDB:
class LinuxBridgeQuantumAgentRPC:
def __init__(self, br_name_prefix, physical_interface, polling_interval,
def __init__(self, interface_mappings, polling_interval,
root_helper):
self.polling_interval = polling_interval
self.root_helper = root_helper
self.setup_linux_bridge(br_name_prefix, physical_interface)
self.setup_rpc(physical_interface)
self.setup_linux_bridge(interface_mappings)
self.setup_rpc(interface_mappings.values())
def setup_rpc(self, physical_interface):
mac = utils.get_interface_mac(physical_interface)
def setup_rpc(self, physical_interfaces):
# REVISIT try until one succeeds?
mac = utils.get_interface_mac(physical_interfaces[0])
self.agent_id = '%s%s' % ('lb', (mac.replace(":", "")))
self.topic = topics.AGENT
self.plugin_rpc = agent_rpc.PluginApi(topics.PLUGIN)
@ -569,12 +586,14 @@ class LinuxBridgeQuantumAgentRPC:
monitor = pyudev.Monitor.from_netlink(self.udev)
monitor.filter_by('net')
def setup_linux_bridge(self, br_name_prefix, physical_interface):
self.linux_br = LinuxBridge(br_name_prefix, physical_interface,
self.root_helper)
def setup_linux_bridge(self, interface_mappings):
self.linux_br = LinuxBridge(interface_mappings, self.root_helper)
def process_port_binding(self, network_id, interface_id, vlan_id):
return self.linux_br.add_interface(network_id, vlan_id, interface_id)
def process_port_binding(self, network_id, interface_id,
physical_network, vlan_id):
return self.linux_br.add_interface(network_id,
physical_network, vlan_id,
interface_id)
def remove_port_binding(self, network_id, interface_id):
bridge_name = self.linux_br.get_bridge_name(network_id)
@ -633,6 +652,7 @@ class LinuxBridgeQuantumAgentRPC:
# create the networking for the port
self.process_port_binding(details['network_id'],
details['port_id'],
details['physical_network'],
details['vlan_id'])
else:
self.remove_port_binding(details['network_id'],
@ -696,29 +716,32 @@ def main():
# (TODO) gary - swap with common logging
logging_config.setup_logging(cfg.CONF)
br_name_prefix = BRIDGE_NAME_PREFIX
physical_interface = cfg.CONF.LINUX_BRIDGE.physical_interface
interface_mappings = {}
for mapping in cfg.CONF.LINUX_BRIDGE.physical_interface_mappings:
try:
physical_network, physical_interface = mapping.split(':')
interface_mappings[physical_network] = physical_interface
LOG.debug("physical network %s mapped to physical interface %s" %
(physical_network, physical_interface))
except ValueError as ex:
LOG.error("Invalid physical interface mapping: \'%s\' - %s" %
(mapping, ex))
sys.exit(1)
polling_interval = cfg.CONF.AGENT.polling_interval
reconnect_interval = cfg.CONF.DATABASE.reconnect_interval
root_helper = cfg.CONF.AGENT.root_helper
rpc = cfg.CONF.AGENT.rpc
if not cfg.CONF.AGENT.target_v2_api:
rpc = False
if rpc:
plugin = LinuxBridgeQuantumAgentRPC(br_name_prefix,
physical_interface,
plugin = LinuxBridgeQuantumAgentRPC(interface_mappings,
polling_interval,
root_helper)
else:
db_connection_url = cfg.CONF.DATABASE.sql_connection
target_v2_api = cfg.CONF.AGENT.target_v2_api
plugin = LinuxBridgeQuantumAgentDB(br_name_prefix,
physical_interface,
plugin = LinuxBridgeQuantumAgentDB(interface_mappings,
polling_interval,
reconnect_interval,
root_helper,
target_v2_api,
db_connection_url)
LOG.info("Agent initialized successfully, now running... ")
plugin.daemon_loop()

View File

@ -19,10 +19,15 @@
from quantum.openstack.common import cfg
DEFAULT_VLAN_RANGES = ['default:1000:2999']
DEFAULT_INTERFACE_MAPPINGS = ['default:eth1']
vlan_opts = [
cfg.IntOpt('vlan_start', default=1000),
cfg.IntOpt('vlan_end', default=3000),
cfg.ListOpt('network_vlan_ranges',
default=DEFAULT_VLAN_RANGES,
help="List of <physical_network>:<vlan_min>:<vlan_max> "
"or <physical_network>"),
]
database_opts = [
@ -32,13 +37,14 @@ database_opts = [
]
bridge_opts = [
cfg.StrOpt('physical_interface', default='eth1'),
cfg.ListOpt('physical_interface_mappings',
default=DEFAULT_INTERFACE_MAPPINGS,
help="List of <physical_network>:<physical_interface>"),
]
agent_opts = [
cfg.IntOpt('polling_interval', default=2),
cfg.StrOpt('root_helper', default='sudo'),
cfg.BoolOpt('target_v2_api', default=False),
cfg.BoolOpt('rpc', default=True),
]

View File

@ -17,47 +17,11 @@
# @author: Sumit Naiksatam, Cisco Systems, Inc.
PORT_STATE = 'port-state'
FLAT_VLAN_ID = -1
PORT_UP = "ACTIVE"
PORT_DOWN = "DOWN"
UUID = 'uuid'
TENANTID = 'tenant_id'
NETWORKID = 'network_id'
NETWORKNAME = 'name'
NETWORKPORTS = 'ports'
OPSTATUS = 'op_status'
INTERFACEID = 'interface_id'
PORTSTATE = 'state'
PORTID = 'port_id'
PPNAME = 'name'
PPVLANID = 'vlan_id'
VLANID = 'vlan_id'
VLANNAME = 'vlan_name'
ATTACHMENT = 'attachment'
PORT_ID = 'port-id'
PORT_OP_STATUS = 'port-op-status'
NET_ID = 'net-id'
NET_NAME = 'net-name'
NET_PORTS = 'net-ports'
NET_OP_STATUS = 'net-op-status'
NET_VLAN_NAME = 'net-vlan-name'
NET_VLAN_ID = 'net-vlan-id'
NET_TENANTS = 'net-tenants'
USERNAME = 'username'
PASSWORD = 'password'
DELIMITERS = "[,;:\b\s]"
UUID_LENGTH = 36
UNPLUGGED = '(detached)'
ASSOCIATION_STATUS = 'association_status'
ATTACHED = 'attached'
DETACHED = 'detached'

View File

@ -1,58 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2012 Cisco Systems, 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.
#
# @author: Sumit Naiksatam, Cisco Systems, Inc.
# @author: Rohit Agarwalla, Cisco Systems, Inc.
"""
Exceptions used by the LinuxBridge plugin
"""
from quantum.common import exceptions
class NetworksLimit(exceptions.QuantumException):
"""Total number of network objects limit has been hit"""
message = _("Unable to create new network. Number of networks"
"for the system has exceeded the limit")
class NetworkVlanBindingAlreadyExists(exceptions.QuantumException):
"""Binding cannot be created, since it already exists"""
message = _("NetworkVlanBinding for %(vlan_id)s and network "
"%(network_id)s already exists")
class NetworkVlanBindingNotFound(exceptions.QuantumException):
"""Binding could not be found"""
message = _("NetworkVlanBinding for network "
"%(network_id)s does not exist")
class VlanIDNotFound(exceptions.QuantumException):
"""VLAN ID cannot be found"""
message = _("Vlan ID %(vlan_id)s not found")
class VlanIDNotAvailable(exceptions.QuantumException):
"""No VLAN ID available"""
message = _("No Vlan ID available")
class UnableToChangeVlanRange(exceptions.QuantumException):
"""No VLAN ID available"""
message = _("Current VLAN ID range %(range_start)s to %(range_end)s "
"cannot be changed. Please check plugin conf file.")

View File

@ -1,311 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012, Cisco Systems, Inc.
#
# 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: Rohit Agarwalla, Cisco Systems, Inc.
import logging
from sqlalchemy import func
from sqlalchemy.orm import exc
from quantum.api import api_common
from quantum.common import exceptions as q_exc
import quantum.db.api as db
from quantum.db import models_v2
from quantum.openstack.common import cfg
from quantum.plugins.linuxbridge.common import config
from quantum.plugins.linuxbridge.common import exceptions as c_exc
from quantum.plugins.linuxbridge.db import l2network_models_v2
LOG = logging.getLogger(__name__)
# The global variable for the database version model
L2_MODEL = l2network_models_v2
def initialize(base=None):
global L2_MODEL
options = {"sql_connection": "%s" % cfg.CONF.DATABASE.sql_connection}
options.update({"sql_max_retries": cfg.CONF.DATABASE.sql_max_retries})
options.update({"reconnect_interval":
cfg.CONF.DATABASE.reconnect_interval})
if base:
options.update({"base": base})
db.configure_db(options)
create_vlanids()
def create_vlanids():
"""Prepopulate the vlan_bindings table"""
LOG.debug("create_vlanids() called")
session = db.get_session()
start = cfg.CONF.VLANS.vlan_start
end = cfg.CONF.VLANS.vlan_end
try:
vlanid = session.query(L2_MODEL.VlanID).one()
except exc.MultipleResultsFound:
"""
TODO (Sumit): Salvatore rightly points out that this will not handle
change in VLAN ID range across server reboots. This is currently not
a supported feature. This logic will need to change if this feature
has to be supported.
Per Dan's suggestion we just throw a server exception for now.
"""
current_start = (
int(session.query(func.min(L2_MODEL.VlanID.vlan_id)).
one()[0]))
current_end = (
int(session.query(func.max(L2_MODEL.VlanID.vlan_id)).
one()[0]))
if current_start != start or current_end != end:
LOG.debug("Old VLAN range %s-%s" % (current_start, current_end))
LOG.debug("New VLAN range %s-%s" % (start, end))
raise c_exc.UnableToChangeVlanRange(range_start=current_start,
range_end=current_end)
except exc.NoResultFound:
LOG.debug("Setting VLAN range to %s-%s" % (start, end))
while start <= end:
vlanid = L2_MODEL.VlanID(start)
session.add(vlanid)
start += 1
session.flush()
return
def get_all_vlanids():
"""Get all the vlanids"""
LOG.debug("get_all_vlanids() called")
session = db.get_session()
try:
vlanids = (session.query(L2_MODEL.VlanID).
all())
return vlanids
except exc.NoResultFound:
return []
def is_vlanid_used(vlan_id):
"""Check if a vlanid is in use"""
LOG.debug("is_vlanid_used() called")
session = db.get_session()
try:
vlanid = (session.query(L2_MODEL.VlanID).
filter_by(vlan_id=vlan_id).
one())
return vlanid["vlan_used"]
except exc.NoResultFound:
raise c_exc.VlanIDNotFound(vlan_id=vlan_id)
def release_vlanid(vlan_id):
"""Set the vlanid state to be unused, and delete if not in range"""
LOG.debug("release_vlanid() called")
session = db.get_session()
try:
vlanid = (session.query(L2_MODEL.VlanID).
filter_by(vlan_id=vlan_id).
one())
vlanid["vlan_used"] = False
if (vlan_id >= cfg.CONF.VLANS.vlan_start and
vlan_id <= cfg.CONF.VLANS.vlan_end):
session.merge(vlanid)
else:
session.delete(vlanid)
session.flush()
return vlanid["vlan_used"]
except exc.NoResultFound:
raise c_exc.VlanIDNotFound(vlan_id=vlan_id)
return
def delete_vlanid(vlan_id):
"""Delete a vlanid entry from db"""
LOG.debug("delete_vlanid() called")
session = db.get_session()
try:
vlanid = (session.query(L2_MODEL.VlanID).
filter_by(vlan_id=vlan_id).
one())
session.delete(vlanid)
session.flush()
return vlanid
except exc.NoResultFound:
raise c_exc.VlanIDNotFound(vlan_id=vlan_id)
def reserve_vlanid():
"""Reserve the first unused vlanid"""
LOG.debug("reserve_vlanid() called")
session = db.get_session()
try:
rvlan = (session.query(L2_MODEL.VlanID).
first())
if not rvlan:
create_vlanids()
rvlan = (session.query(L2_MODEL.VlanID).
filter_by(vlan_used=False).
first())
if not rvlan:
raise c_exc.VlanIDNotAvailable()
rvlanid = (session.query(L2_MODEL.VlanID).
filter_by(vlan_id=rvlan["vlan_id"]).
one())
rvlanid["vlan_used"] = True
session.merge(rvlanid)
session.flush()
return rvlan["vlan_id"]
except exc.NoResultFound:
raise c_exc.VlanIDNotAvailable()
def reserve_specific_vlanid(vlan_id):
"""Reserve a specific vlanid"""
LOG.debug("reserve_specific_vlanid() called")
if vlan_id < 1 or vlan_id > 4094:
msg = _("Specified VLAN %s outside legal range (1-4094)") % vlan_id
raise q_exc.InvalidInput(error_message=msg)
session = db.get_session()
try:
rvlanid = (session.query(l2network_models_v2.VlanID).
filter_by(vlan_id=vlan_id).
one())
if rvlanid["vlan_used"]:
raise q_exc.VlanIdInUse(vlan_id=vlan_id)
LOG.debug("reserving dynamic vlanid %s" % vlan_id)
rvlanid["vlan_used"] = True
session.merge(rvlanid)
except exc.NoResultFound:
rvlanid = l2network_models_v2.VlanID(vlan_id)
LOG.debug("reserving non-dynamic vlanid %s" % vlan_id)
rvlanid["vlan_used"] = True
session.add(rvlanid)
session.flush()
def get_all_vlanids_used():
"""Get all the vlanids used"""
LOG.debug("get_all_vlanids() called")
session = db.get_session()
try:
vlanids = (session.query(L2_MODEL.VlanID).
filter_by(vlan_used=True).
all())
return vlanids
except exc.NoResultFound:
return []
def get_all_vlan_bindings():
"""List all the vlan to network associations"""
LOG.debug("get_all_vlan_bindings() called")
session = db.get_session()
try:
bindings = (session.query(L2_MODEL.VlanBinding).
all())
return bindings
except exc.NoResultFound:
return []
def get_vlan_binding(netid):
"""List the vlan given a network_id"""
LOG.debug("get_vlan_binding() called")
session = db.get_session()
try:
binding = (session.query(L2_MODEL.VlanBinding).
filter_by(network_id=netid).
one())
return binding
except exc.NoResultFound:
raise c_exc.NetworkVlanBindingNotFound(network_id=netid)
def add_vlan_binding(vlanid, netid):
"""Add a vlan to network association"""
LOG.debug("add_vlan_binding() called")
session = db.get_session()
try:
binding = (session.query(L2_MODEL.VlanBinding).
filter_by(vlan_id=vlanid).
one())
raise c_exc.NetworkVlanBindingAlreadyExists(vlan_id=vlanid,
network_id=netid)
except exc.NoResultFound:
binding = L2_MODEL.VlanBinding(vlanid, netid)
session.add(binding)
session.flush()
return binding
def remove_vlan_binding(netid):
"""Remove a vlan to network association"""
LOG.debug("remove_vlan_binding() called")
session = db.get_session()
try:
binding = (session.query(L2_MODEL.VlanBinding).
filter_by(network_id=netid).
one())
session.delete(binding)
session.flush()
return binding
except exc.NoResultFound:
pass
def update_vlan_binding(netid, newvlanid=None):
"""Update a vlan to network association"""
LOG.debug("update_vlan_binding() called")
session = db.get_session()
try:
binding = (session.query(L2_MODEL.VlanBinding).
filter_by(network_id=netid).
one())
if newvlanid:
binding["vlan_id"] = newvlanid
session.merge(binding)
session.flush()
return binding
except exc.NoResultFound:
raise q_exc.NetworkNotFound(net_id=netid)
def get_port_from_device(device):
"""Get port from database"""
LOG.debug("get_port_from_device() called")
session = db.get_session()
ports = session.query(models_v2.Port).all()
if not ports:
return
for port in ports:
if port['id'].startswith(device):
return port
return
def set_port_status(port_id, status):
"""Set the port status"""
LOG.debug("set_port_status as %s called", status)
session = db.get_session()
try:
port = session.query(models_v2.Port).filter_by(id=port_id).one()
port['status'] = status
if status == api_common.PORT_STATUS_DOWN:
port['device_id'] = ''
session.merge(port)
session.flush()
except exc.NoResultFound:
raise q_exc.PortNotFound(port_id=port_id)

View File

@ -0,0 +1,196 @@
# Copyright (c) 2012 OpenStack, LLC.
#
# 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 logging
from sqlalchemy.orm import exc
from quantum.api import api_common
from quantum.common import exceptions as q_exc
import quantum.db.api as db
from quantum.db import models_v2
from quantum.openstack.common import cfg
from quantum.plugins.linuxbridge.common import config
from quantum.plugins.linuxbridge.db import l2network_models_v2
LOG = logging.getLogger(__name__)
def initialize():
options = {"sql_connection": "%s" % cfg.CONF.DATABASE.sql_connection}
options.update({"sql_max_retries": cfg.CONF.DATABASE.sql_max_retries})
options.update({"reconnect_interval":
cfg.CONF.DATABASE.reconnect_interval})
options.update({"base": models_v2.model_base.BASEV2})
db.configure_db(options)
def sync_network_states(network_vlan_ranges):
"""Synchronize network_states table with current configured VLAN ranges."""
# process vlan ranges for each physical network separately
for physical_network, vlan_ranges in network_vlan_ranges.iteritems():
# determine current configured allocatable vlans for this
# physical network
vlan_ids = set()
for vlan_range in vlan_ranges:
vlan_ids |= set(xrange(vlan_range[0], vlan_range[1] + 1))
session = db.get_session()
with session.begin():
# remove from table unallocated vlans not currently allocatable
try:
states = (session.query(l2network_models_v2.NetworkState).
filter_by(physical_network=physical_network).
all())
for state in states:
try:
# see if vlan is allocatable
vlan_ids.remove(state.vlan_id)
except KeyError:
# it's not allocatable, so check if its allocated
if not state.allocated:
# it's not, so remove it from table
LOG.debug("removing vlan %s on physical network "
"%s from pool" %
(state.vlan_id, physical_network))
session.delete(state)
except exc.NoResultFound:
pass
# add missing allocatable vlans to table
for vlan_id in sorted(vlan_ids):
state = l2network_models_v2.NetworkState(physical_network,
vlan_id)
session.add(state)
def get_network_state(physical_network, vlan_id):
"""Get state of specified network"""
session = db.get_session()
try:
state = (session.query(l2network_models_v2.NetworkState).
filter_by(physical_network=physical_network,
vlan_id=vlan_id).
one())
return state
except exc.NoResultFound:
return None
def reserve_network(session):
with session.begin(subtransactions=True):
state = (session.query(l2network_models_v2.NetworkState).
filter_by(allocated=False).
first())
if not state:
raise q_exc.NoNetworkAvailable()
LOG.debug("reserving vlan %s on physical network %s from pool" %
(state.vlan_id, state.physical_network))
state.allocated = True
return (state.physical_network, state.vlan_id)
def reserve_specific_network(session, physical_network, vlan_id):
with session.begin(subtransactions=True):
try:
state = (session.query(l2network_models_v2.NetworkState).
filter_by(physical_network=physical_network,
vlan_id=vlan_id).
one())
if state.allocated:
raise q_exc.VlanIdInUse(vlan_id=vlan_id,
physical_network=physical_network)
LOG.debug("reserving specific vlan %s on physical network %s "
"from pool" % (vlan_id, physical_network))
state.allocated = True
except exc.NoResultFound:
LOG.debug("reserving specific vlan %s on physical network %s "
"outside pool" % (vlan_id, physical_network))
state = l2network_models_v2.NetworkState(physical_network, vlan_id)
state.allocated = True
session.add(state)
def release_network(session, physical_network, vlan_id, network_vlan_ranges):
with session.begin(subtransactions=True):
try:
state = (session.query(l2network_models_v2.NetworkState).
filter_by(physical_network=physical_network,
vlan_id=vlan_id).
one())
state.allocated = False
inside = False
for vlan_range in network_vlan_ranges.get(physical_network, []):
if vlan_id >= vlan_range[0] and vlan_id <= vlan_range[1]:
inside = True
break
if inside:
LOG.debug("releasing vlan %s on physical network %s to pool" %
(vlan_id, physical_network))
else:
LOG.debug("releasing vlan %s on physical network %s outside "
"pool" % (vlan_id, physical_network))
session.delete(state)
except exc.NoResultFound:
LOG.warning("vlan_id %s on physical network %s not found" %
(vlan_id, physical_network))
def add_network_binding(session, network_id, physical_network, vlan_id):
with session.begin(subtransactions=True):
binding = l2network_models_v2.NetworkBinding(network_id,
physical_network, vlan_id)
session.add(binding)
def get_network_binding(session, network_id):
try:
binding = (session.query(l2network_models_v2.NetworkBinding).
filter_by(network_id=network_id).
one())
return binding
except exc.NoResultFound:
return
def get_port_from_device(device):
"""Get port from database"""
LOG.debug("get_port_from_device() called")
session = db.get_session()
ports = session.query(models_v2.Port).all()
if not ports:
return
for port in ports:
if port['id'].startswith(device):
return port
return
def set_port_status(port_id, status):
"""Set the port status"""
LOG.debug("set_port_status as %s called", status)
session = db.get_session()
try:
port = session.query(models_v2.Port).filter_by(id=port_id).one()
port['status'] = status
if status == api_common.PORT_STATUS_DOWN:
port['device_id'] = ''
session.merge(port)
session.flush()
except exc.NoResultFound:
raise q_exc.PortNotFound(port_id=port_id)

View File

@ -14,38 +14,46 @@
# limitations under the License.
import sqlalchemy as sa
from sqlalchemy import orm
from quantum.db import model_base
class VlanID(model_base.BASEV2):
"""Represents a vlan_id usage"""
__tablename__ = 'vlan_ids'
class NetworkState(model_base.BASEV2):
"""Represents state of vlan_id on physical network"""
__tablename__ = 'network_states'
vlan_id = sa.Column(sa.Integer, nullable=False, primary_key=True)
vlan_used = sa.Column(sa.Boolean, nullable=False)
physical_network = sa.Column(sa.String(64), nullable=False,
primary_key=True)
vlan_id = sa.Column(sa.Integer, nullable=False, primary_key=True,
autoincrement=False)
allocated = sa.Column(sa.Boolean, nullable=False)
def __init__(self, vlan_id):
def __init__(self, physical_network, vlan_id):
self.physical_network = physical_network
self.vlan_id = vlan_id
self.vlan_used = False
self.allocated = False
def __repr__(self):
return "<VlanID(%d,%s)>" % (self.vlan_id, self.vlan_used)
return "<NetworkState(%s,%d,%s)>" % (self.physical_network,
self.vlan_id, self.allocated)
class VlanBinding(model_base.BASEV2):
"""Represents a binding of vlan_id to network_id"""
__tablename__ = 'vlan_bindings'
class NetworkBinding(model_base.BASEV2):
"""Represents binding of virtual network to physical_network and vlan_id"""
__tablename__ = 'network_bindings'
network_id = sa.Column(sa.String(36), sa.ForeignKey('networks.id',
ondelete="CASCADE"),
network_id = sa.Column(sa.String(36),
sa.ForeignKey('networks.id', ondelete="CASCADE"),
primary_key=True)
physical_network = sa.Column(sa.String(64), nullable=False)
vlan_id = sa.Column(sa.Integer, nullable=False)
def __init__(self, vlan_id, network_id):
self.vlan_id = vlan_id
def __init__(self, network_id, physical_network, vlan_id):
self.network_id = network_id
self.physical_network = physical_network
self.vlan_id = vlan_id
def __repr__(self):
return "<VlanBinding(%d,%s)>" % (self.vlan_id, self.network_id)
return "<NetworkBinding(%s,%s,%d)>" % (self.network_id,
self.physical_network,
self.vlan_id)

View File

@ -14,10 +14,13 @@
# limitations under the License.
import logging
import sys
from quantum.api import api_common
from quantum.api.v2 import attributes
from quantum.common import exceptions as q_exc
from quantum.common import topics
from quantum.db import api as db_api
from quantum.db import db_base_plugin_v2
from quantum.db import models_v2
from quantum.openstack.common import context
@ -25,7 +28,8 @@ from quantum.openstack.common import cfg
from quantum.openstack.common import rpc
from quantum.openstack.common.rpc import dispatcher
from quantum.openstack.common.rpc import proxy
from quantum.plugins.linuxbridge.db import l2network_db as cdb
from quantum.plugins.linuxbridge.common import constants
from quantum.plugins.linuxbridge.db import l2network_db_v2 as db
from quantum import policy
@ -39,8 +43,8 @@ class LinuxBridgeRpcCallbacks():
# Device names start with "tap"
TAP_PREFIX_LEN = 3
def __init__(self, context):
self.context = context
def __init__(self, rpc_context):
self.rpc_context = rpc_context
def create_rpc_dispatcher(self):
'''Get the rpc dispatcher for this manager.
@ -50,38 +54,40 @@ class LinuxBridgeRpcCallbacks():
'''
return dispatcher.RpcDispatcher([self])
def get_device_details(self, context, **kwargs):
def get_device_details(self, rpc_context, **kwargs):
"""Agent requests device details"""
agent_id = kwargs.get('agent_id')
device = kwargs.get('device')
LOG.debug("Device %s details requested from %s", device, agent_id)
port = cdb.get_port_from_device(device[self.TAP_PREFIX_LEN:])
port = db.get_port_from_device(device[self.TAP_PREFIX_LEN:])
if port:
vlan_binding = cdb.get_vlan_binding(port['network_id'])
binding = db.get_network_binding(db_api.get_session(),
port['network_id'])
entry = {'device': device,
'vlan_id': vlan_binding['vlan_id'],
'physical_network': binding.physical_network,
'vlan_id': binding.vlan_id,
'network_id': port['network_id'],
'port_id': port['id'],
'admin_state_up': port['admin_state_up']}
# Set the port status to UP
cdb.set_port_status(port['id'], api_common.PORT_STATUS_UP)
db.set_port_status(port['id'], api_common.PORT_STATUS_UP)
else:
entry = {'device': device}
LOG.debug("%s can not be found in database", device)
return entry
def update_device_down(self, context, **kwargs):
def update_device_down(self, rpc_context, **kwargs):
"""Device no longer exists on agent"""
# (TODO) garyk - live migration and port status
agent_id = kwargs.get('agent_id')
device = kwargs.get('device')
LOG.debug("Device %s no longer exists on %s", device, agent_id)
port = cdb.get_port_from_device(device[self.TAP_PREFIX_LEN:])
port = db.get_port_from_device(device[self.TAP_PREFIX_LEN:])
if port:
entry = {'device': device,
'exists': True}
# Set port status to DOWN
cdb.set_port_status(port['id'], api_common.PORT_STATUS_UP)
db.set_port_status(port['id'], api_common.PORT_STATUS_DOWN)
else:
entry = {'device': device,
'exists': False}
@ -115,10 +121,11 @@ class AgentNotifierApi(proxy.RpcProxy):
network_id=network_id),
topic=self.topic_network_delete)
def port_update(self, context, port, vlan_id):
def port_update(self, context, port, physical_network, vlan_id):
self.fanout_cast(context,
self.make_msg('port_update',
port=port,
physical_network=physical_network,
vlan_id=vlan_id),
topic=self.topic_port_update)
@ -137,24 +144,29 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2):
be updated to take advantage of it.
"""
# This attribute specifies whether the plugin supports or not
# bulk operations. Name mangling is used in order to ensure it
# is qualified by class
__native_bulk_support = True
supported_extension_aliases = ["provider"]
def __init__(self):
cdb.initialize(base=models_v2.model_base.BASEV2)
db.initialize()
self._parse_network_vlan_ranges()
db.sync_network_states(self.network_vlan_ranges)
self.rpc = cfg.CONF.AGENT.rpc
if cfg.CONF.AGENT.rpc and cfg.CONF.AGENT.target_v2_api:
self.setup_rpc()
if not cfg.CONF.AGENT.target_v2_api:
self.rpc = False
if self.rpc:
self._setup_rpc()
LOG.debug("Linux Bridge Plugin initialization complete")
def setup_rpc(self):
def _setup_rpc(self):
# RPC support
self.topic = topics.PLUGIN
self.context = context.RequestContext('quantum', 'quantum',
is_admin=False)
self.rpc_context = context.RequestContext('quantum', 'quantum',
is_admin=False)
self.conn = rpc.create_connection(new=True)
self.callbacks = LinuxBridgeRpcCallbacks(self.context)
self.callbacks = LinuxBridgeRpcCallbacks(self.rpc_context)
self.dispatcher = self.callbacks.create_rpc_dispatcher()
self.conn.create_consumer(self.topic, self.dispatcher,
fanout=False)
@ -162,7 +174,32 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2):
self.conn.consume_in_thread()
self.notifier = AgentNotifierApi(topics.AGENT)
# TODO(rkukura) Use core mechanism for attribute authorization
def _parse_network_vlan_ranges(self):
self.network_vlan_ranges = {}
for entry in cfg.CONF.VLANS.network_vlan_ranges:
if ':' in entry:
try:
physical_network, vlan_min, vlan_max = entry.split(':')
self._add_network_vlan_range(physical_network,
int(vlan_min),
int(vlan_max))
except ValueError as ex:
LOG.error("Invalid network VLAN range: \'%s\' - %s" %
(entry, ex))
sys.exit(1)
else:
self._add_network(entry)
LOG.debug("network VLAN ranges: %s" % self.network_vlan_ranges)
def _add_network_vlan_range(self, physical_network, vlan_min, vlan_max):
self._add_network(physical_network)
self.network_vlan_ranges[physical_network].append((vlan_min, vlan_max))
def _add_network(self, physical_network):
if physical_network not in self.network_vlan_ranges:
self.network_vlan_ranges[physical_network] = []
# REVISIT(rkukura) Use core mechanism for attribute authorization
# when available.
def _check_provider_view_auth(self, context, network):
@ -177,40 +214,119 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2):
def _extend_network_dict(self, context, network):
if self._check_provider_view_auth(context, network):
vlan_binding = cdb.get_vlan_binding(network['id'])
network['provider:vlan_id'] = vlan_binding['vlan_id']
binding = db.get_network_binding(context.session, network['id'])
network['provider:physical_network'] = binding.physical_network
if binding.vlan_id == constants.FLAT_VLAN_ID:
network['provider:network_type'] = 'flat'
network['provider:vlan_id'] = None
else:
network['provider:network_type'] = 'vlan'
network['provider:vlan_id'] = binding.vlan_id
def _process_provider_create(self, context, attrs):
network_type = attrs.get('provider:network_type')
physical_network = attrs.get('provider:physical_network')
vlan_id = attrs.get('provider:vlan_id')
network_type_set = attributes.is_attr_set(network_type)
physical_network_set = attributes.is_attr_set(physical_network)
vlan_id_set = attributes.is_attr_set(vlan_id)
if not (network_type_set or physical_network_set or vlan_id_set):
return (None, None, None)
# Authorize before exposing plugin details to client
self._enforce_provider_set_auth(context, attrs)
if not network_type_set:
msg = _("provider:network_type required")
raise q_exc.InvalidInput(error_message=msg)
elif network_type == 'flat':
if vlan_id_set:
msg = _("provider:vlan_id specified for flat network")
raise q_exc.InvalidInput(error_message=msg)
else:
vlan_id = constants.FLAT_VLAN_ID
elif network_type == 'vlan':
if not vlan_id_set:
msg = _("provider:vlan_id required")
raise q_exc.InvalidInput(error_message=msg)
else:
msg = _("invalid provider:network_type %s" % network_type)
raise q_exc.InvalidInput(error_message=msg)
if physical_network_set:
if physical_network not in self.network_vlan_ranges:
msg = _("unknown provider:physical_network %s" %
physical_network)
raise q_exc.InvalidInput(error_message=msg)
elif 'default' in self.network_vlan_ranges:
physical_network = 'default'
else:
msg = _("provider:physical_network required")
raise q_exc.InvalidInput(error_message=msg)
return (network_type, physical_network, vlan_id)
def _check_provider_update(self, context, attrs):
network_type = attrs.get('provider:network_type')
physical_network = attrs.get('provider:physical_network')
vlan_id = attrs.get('provider:vlan_id')
network_type_set = attributes.is_attr_set(network_type)
physical_network_set = attributes.is_attr_set(physical_network)
vlan_id_set = attributes.is_attr_set(vlan_id)
if not (network_type_set or physical_network_set or vlan_id_set):
return
# Authorize before exposing plugin details to client
self._enforce_provider_set_auth(context, attrs)
msg = _("plugin does not support updating provider attributes")
raise q_exc.InvalidInput(error_message=msg)
def create_network(self, context, network):
net = super(LinuxBridgePluginV2, self).create_network(context,
network)
try:
vlan_id = network['network'].get('provider:vlan_id')
if vlan_id not in (None, attributes.ATTR_NOT_SPECIFIED):
self._enforce_provider_set_auth(context, net)
cdb.reserve_specific_vlanid(int(vlan_id))
(network_type, physical_network,
vlan_id) = self._process_provider_create(context,
network['network'])
session = context.session
with session.begin(subtransactions=True):
if not network_type:
physical_network, vlan_id = db.reserve_network(session)
else:
vlan_id = cdb.reserve_vlanid()
cdb.add_vlan_binding(vlan_id, net['id'])
db.reserve_specific_network(session, physical_network, vlan_id)
net = super(LinuxBridgePluginV2, self).create_network(context,
network)
db.add_network_binding(session, net['id'],
physical_network, vlan_id)
self._extend_network_dict(context, net)
except:
super(LinuxBridgePluginV2, self).delete_network(context,
net['id'])
raise
# note - exception will rollback entire transaction
return net
def update_network(self, context, id, network):
net = super(LinuxBridgePluginV2, self).update_network(context, id,
network)
self._extend_network_dict(context, net)
self._check_provider_update(context, network['network'])
session = context.session
with session.begin(subtransactions=True):
net = super(LinuxBridgePluginV2, self).update_network(context, id,
network)
self._extend_network_dict(context, net)
return net
def delete_network(self, context, id):
vlan_binding = cdb.get_vlan_binding(id)
result = super(LinuxBridgePluginV2, self).delete_network(context, id)
cdb.release_vlanid(vlan_binding['vlan_id'])
session = context.session
with session.begin(subtransactions=True):
binding = db.get_network_binding(session, id)
result = super(LinuxBridgePluginV2, self).delete_network(context,
id)
db.release_network(session, binding.physical_network,
binding.vlan_id, self.network_vlan_ranges)
# the network_binding record is deleted via cascade from
# the network record, so explicit removal is not necessary
if self.rpc:
self.notifier.network_delete(self.context, id)
return result
self.notifier.network_delete(self.rpc_context, id)
def get_network(self, context, id, fields=None, verbose=None):
net = super(LinuxBridgePluginV2, self).get_network(context, id,
@ -233,7 +349,9 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2):
port = super(LinuxBridgePluginV2, self).update_port(context, id, port)
if self.rpc:
if original_port['admin_state_up'] != port['admin_state_up']:
vlan_binding = cdb.get_vlan_binding(port['network_id'])
self.notifier.port_update(self.context, port,
vlan_binding['vlan_id'])
binding = db.get_network_binding(context.session,
port['network_id'])
self.notifier.port_update(self.rpc_context, port,
binding.physical_network,
binding.vlan_id)
return port

View File

@ -0,0 +1,42 @@
# Copyright (c) 2012 OpenStack, LLC.
#
# 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 unittest2 as unittest
from quantum.openstack.common import cfg
from quantum.plugins.linuxbridge.common import config
class ConfigurationTest(unittest.TestCase):
def test_defaults(self):
self.assertEqual('sqlite://',
cfg.CONF.DATABASE.sql_connection)
self.assertEqual(-1,
cfg.CONF.DATABASE.sql_max_retries)
self.assertEqual(2,
cfg.CONF.DATABASE.reconnect_interval)
self.assertEqual(2,
cfg.CONF.AGENT.polling_interval)
self.assertEqual('sudo',
cfg.CONF.AGENT.root_helper)
ranges = cfg.CONF.VLANS.network_vlan_ranges
self.assertEqual(1, len(ranges))
self.assertEqual('default:1000:2999', ranges[0])
mappings = cfg.CONF.LINUX_BRIDGE.physical_interface_mappings
self.assertEqual(1, len(mappings))
self.assertEqual('default:eth1', mappings[0])

View File

@ -0,0 +1,125 @@
# Copyright (c) 2012 OpenStack, LLC.
#
# 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 unittest2
from quantum.common import exceptions as q_exc
from quantum.db import api as db
from quantum.plugins.linuxbridge.db import l2network_db_v2 as lb_db
PHYS_NET = 'physnet1'
VLAN_MIN = 10
VLAN_MAX = 19
VLAN_RANGES = {PHYS_NET: [(VLAN_MIN, VLAN_MAX)]}
UPDATED_VLAN_RANGES = {PHYS_NET: [(VLAN_MIN + 5, VLAN_MAX + 5)]}
TEST_NETWORK_ID = 'abcdefghijklmnopqrstuvwxyz'
class NetworkStatesTest(unittest2.TestCase):
def setUp(self):
lb_db.initialize()
lb_db.sync_network_states(VLAN_RANGES)
self.session = db.get_session()
def tearDown(self):
db.clear_db()
def test_sync_network_states(self):
self.assertIsNone(lb_db.get_network_state(PHYS_NET,
VLAN_MIN - 1))
self.assertFalse(lb_db.get_network_state(PHYS_NET,
VLAN_MIN).allocated)
self.assertFalse(lb_db.get_network_state(PHYS_NET,
VLAN_MIN + 1).allocated)
self.assertFalse(lb_db.get_network_state(PHYS_NET,
VLAN_MAX).allocated)
self.assertIsNone(lb_db.get_network_state(PHYS_NET,
VLAN_MAX + 1))
lb_db.sync_network_states(UPDATED_VLAN_RANGES)
self.assertIsNone(lb_db.get_network_state(PHYS_NET,
VLAN_MIN + 5 - 1))
self.assertFalse(lb_db.get_network_state(PHYS_NET,
VLAN_MIN + 5).allocated)
self.assertFalse(lb_db.get_network_state(PHYS_NET,
VLAN_MIN + 5 + 1).allocated)
self.assertFalse(lb_db.get_network_state(PHYS_NET,
VLAN_MAX + 5).allocated)
self.assertIsNone(lb_db.get_network_state(PHYS_NET,
VLAN_MAX + 5 + 1))
def test_network_pool(self):
vlan_ids = set()
for x in xrange(VLAN_MIN, VLAN_MAX + 1):
physical_network, vlan_id = lb_db.reserve_network(self.session)
self.assertEqual(physical_network, PHYS_NET)
self.assertGreaterEqual(vlan_id, VLAN_MIN)
self.assertLessEqual(vlan_id, VLAN_MAX)
vlan_ids.add(vlan_id)
with self.assertRaises(q_exc.NoNetworkAvailable):
physical_network, vlan_id = lb_db.reserve_network(self.session)
for vlan_id in vlan_ids:
lb_db.release_network(self.session, PHYS_NET, vlan_id, VLAN_RANGES)
def test_specific_network_inside_pool(self):
vlan_id = VLAN_MIN + 5
self.assertFalse(lb_db.get_network_state(PHYS_NET,
vlan_id).allocated)
lb_db.reserve_specific_network(self.session, PHYS_NET, vlan_id)
self.assertTrue(lb_db.get_network_state(PHYS_NET,
vlan_id).allocated)
with self.assertRaises(q_exc.VlanIdInUse):
lb_db.reserve_specific_network(self.session, PHYS_NET, vlan_id)
lb_db.release_network(self.session, PHYS_NET, vlan_id, VLAN_RANGES)
self.assertFalse(lb_db.get_network_state(PHYS_NET,
vlan_id).allocated)
def test_specific_network_outside_pool(self):
vlan_id = VLAN_MAX + 5
self.assertIsNone(lb_db.get_network_state(PHYS_NET, vlan_id))
lb_db.reserve_specific_network(self.session, PHYS_NET, vlan_id)
self.assertTrue(lb_db.get_network_state(PHYS_NET,
vlan_id).allocated)
with self.assertRaises(q_exc.VlanIdInUse):
lb_db.reserve_specific_network(self.session, PHYS_NET, vlan_id)
lb_db.release_network(self.session, PHYS_NET, vlan_id, VLAN_RANGES)
self.assertIsNone(lb_db.get_network_state(PHYS_NET, vlan_id))
class NetworkBindingsTest(unittest2.TestCase):
def setUp(self):
lb_db.initialize()
self.session = db.get_session()
def tearDown(self):
db.clear_db()
def test_add_network_binding(self):
self.assertIsNone(lb_db.get_network_binding(self.session,
TEST_NETWORK_ID))
lb_db.add_network_binding(self.session, TEST_NETWORK_ID, PHYS_NET,
1234)
binding = lb_db.get_network_binding(self.session, TEST_NETWORK_ID)
self.assertIsNotNone(binding)
self.assertEqual(binding.network_id, TEST_NETWORK_ID)
self.assertEqual(binding.physical_network, PHYS_NET)
self.assertEqual(binding.vlan_id, 1234)

View File

@ -74,7 +74,9 @@ class rpcApiTestCase(unittest2.TestCase):
topics.PORT,
topics.UPDATE),
'port_update', rpc_method='fanout_cast',
port='fake_port', vlan_id='fake_vlan_id')
port='fake_port',
physical_network='fake_net',
vlan_id='fake_vlan_id')
def test_device_details(self):
rpcapi = agent_rpc.PluginApi(topics.PLUGIN)

View File

@ -140,7 +140,9 @@ def reserve_specific_vlan_id(vlan_id, session):
filter_by(vlan_id=vlan_id).
one())
if record.vlan_used:
raise q_exc.VlanIdInUse(vlan_id=vlan_id)
# REVISIT(rkukura) pass phyiscal_network
raise q_exc.VlanIdInUse(vlan_id=vlan_id,
physical_network='default')
LOG.debug("reserving specific vlan %s from pool" % vlan_id)
record.vlan_used = True
except exc.NoResultFound:

View File

@ -234,15 +234,86 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
network['provider:vlan_id'] = ovs_db_v2.get_vlan(
network['id'], context.session)
def _process_provider_create(self, context, attrs):
network_type = attrs.get('provider:network_type')
physical_network = attrs.get('provider:physical_network')
vlan_id = attrs.get('provider:vlan_id')
network_type_set = attributes.is_attr_set(network_type)
physical_network_set = attributes.is_attr_set(physical_network)
vlan_id_set = attributes.is_attr_set(vlan_id)
if not (network_type_set or physical_network_set or vlan_id_set):
return (None, None, None)
# Authorize before exposing plugin details to client
self._enforce_provider_set_auth(context, attrs)
if not network_type_set:
msg = _("provider:network_type required")
raise q_exc.InvalidInput(error_message=msg)
elif network_type == 'flat':
msg = _("plugin does not support flat networks")
raise q_exc.InvalidInput(error_message=msg)
# REVISIT(rkukura) to be enabled in phase 3
# if vlan_id_set:
# msg = _("provider:vlan_id specified for flat network")
# raise q_exc.InvalidInput(error_message=msg)
# else:
# vlan_id = db.FLAT_VLAN_ID
elif network_type == 'vlan':
if not vlan_id_set:
msg = _("provider:vlan_id required")
raise q_exc.InvalidInput(error_message=msg)
else:
msg = _("invalid provider:network_type %s" % network_type)
raise q_exc.InvalidInput(error_message=msg)
if physical_network_set:
msg = _("plugin does not support specifying physical_network")
raise q_exc.InvalidInput(error_message=msg)
# REVISIT(rkukura) to be enabled in phase 3
# if physical_network not in self.physical_networks:
# msg = _("unknown provider:physical_network %s" %
# physical_network)
# raise q_exc.InvalidInput(error_message=msg)
#elif 'default' in self.physical_networks:
# physical_network = 'default'
#else:
# msg = _("provider:physical_network required")
# raise q_exc.InvalidInput(error_message=msg)
return (network_type, physical_network, vlan_id)
def _check_provider_update(self, context, attrs):
network_type = attrs.get('provider:network_type')
physical_network = attrs.get('provider:physical_network')
vlan_id = attrs.get('provider:vlan_id')
network_type_set = attributes.is_attr_set(network_type)
physical_network_set = attributes.is_attr_set(physical_network)
vlan_id_set = attributes.is_attr_set(vlan_id)
if not (network_type_set or physical_network_set or vlan_id_set):
return
# Authorize before exposing plugin details to client
self._enforce_provider_set_auth(context, attrs)
msg = _("plugin does not support updating provider attributes")
raise q_exc.InvalidInput(error_message=msg)
def create_network(self, context, network):
(network_type, physical_network,
vlan_id) = self._process_provider_create(context,
network['network'])
net = super(OVSQuantumPluginV2, self).create_network(context, network)
try:
vlan_id = network['network'].get('provider:vlan_id')
if vlan_id not in (None, attributes.ATTR_NOT_SPECIFIED):
self._enforce_provider_set_auth(context, net)
ovs_db_v2.reserve_specific_vlan_id(vlan_id, context.session)
else:
if not network_type:
vlan_id = ovs_db_v2.reserve_vlan_id(context.session)
else:
ovs_db_v2.reserve_specific_vlan_id(vlan_id, context.session)
except Exception:
super(OVSQuantumPluginV2, self).delete_network(context, net['id'])
raise
@ -253,6 +324,8 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
return net
def update_network(self, context, id, network):
self._check_provider_update(context, network['network'])
net = super(OVSQuantumPluginV2, self).update_network(context, id,
network)
self._extend_network_dict(context, net)

View File

@ -16,6 +16,7 @@
import unittest
from quantum.openstack.common import cfg
from quantum.plugins.openvswitch.common import config
class ConfigurationTest(unittest.TestCase):

View File

@ -16,3 +16,6 @@ api_extensions_path = unit/extensions
# Paste configuration file
api_paste_config = api-paste.ini.test
# The messaging module to use, defaults to kombu.
rpc_backend = quantum.openstack.common.rpc.impl_fake