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:
parent
8e66b33b20
commit
ce1638c283
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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},
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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),
|
||||
]
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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.")
|
@ -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)
|
196
quantum/plugins/linuxbridge/db/l2network_db_v2.py
Normal file
196
quantum/plugins/linuxbridge/db/l2network_db_v2.py
Normal 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)
|
@ -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)
|
||||
|
@ -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
|
||||
|
42
quantum/plugins/linuxbridge/tests/unit/test_defaults.py
Normal file
42
quantum/plugins/linuxbridge/tests/unit/test_defaults.py
Normal 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])
|
125
quantum/plugins/linuxbridge/tests/unit/test_lb_db.py
Normal file
125
quantum/plugins/linuxbridge/tests/unit/test_lb_db.py
Normal 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)
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -16,6 +16,7 @@
|
||||
import unittest
|
||||
|
||||
from quantum.openstack.common import cfg
|
||||
from quantum.plugins.openvswitch.common import config
|
||||
|
||||
|
||||
class ConfigurationTest(unittest.TestCase):
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user