blueprint quantum-linux-bridge-plugin

Squashed commit of the following:

commit 6c4995736a56349923d34353031eb301780fc6d2
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sat Jan 21 22:31:09 2012 -0800

    Some more explanation in the README.

    Changing defaults in the conf file.

commit 924b118468d7bd21737e9e2cf468ff83d0a20764
Author: Shweta <shpadubi@cisco.com>
Date:   Sat Jan 21 20:58:39 2012 -0500

    Adding Unit Tests for LinuxBridge Agent

commit 12115650257483172c5e2bc889634dbdf3596d27
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sat Jan 21 05:21:24 2012 -0800

    Adding sqlite requirement

    Changing default mysql port number

    Fixing log statement

commit 0ad1400e5dfc445b94e9024d92321eb3cd0588a5
Merge: 1b7ba8f 9c5c2ca
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sat Jan 21 05:12:44 2012 -0800

    Merge remote branch 'upstream/master' into snaiksat/linux-bridge-plugin

commit 1b7ba8f7e7b6657734b669194ddfdcfcbfc833be
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sat Jan 21 04:04:50 2012 -0800

    Fixes to get the tests to run correctly.

    Also incorporated changes to be able to run both sqlite and mysql DBs.

commit 4cead17576c293319dfdfd363dd18e81ba196b3b
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Fri Jan 20 15:32:35 2012 -0800

    Fixed inccorect calls to the DB

commit c4f325729fbd06ee3f5d3520da4d23b2cd8c353b
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Fri Jan 20 12:18:18 2012 -0800

    Removing the specialized db modules (which used InnoDB engine) and
    instead using the Quantum DB now.
    Incorporated changes to setup so that the Linux Bridge plugin can be
    installed.
    Other changes to README and tests.

commit b9498939d723e353808cface87f4453e33e94b41
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Mon Jan 16 20:00:14 2012 -0800

    Adding unit tests

commit a0ab990fdcbf67a950d08c6b5b6d20ceb850619a
Merge: 60e38cc f268b5e
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Mon Jan 16 18:02:55 2012 -0800

    Merge remote branch 'upstream/master' into snaiksat/linux-bridge-plugin

commit 60e38cc44886b5c8c9e47d89d8912d1dee23fbd1
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Mon Jan 16 13:44:37 2012 -0800

    This contains a fix for the earlier reported issue with the DHCP
    failing.

    The gateway IP address is now applied both to the bridge, and the
    gateway interface.

commit ffea86a3465b8a5ed93b13f818e0afaefa6787ee
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sun Jan 15 20:00:38 2012 -0800

    Fixing an issue in the agent, sometimes the bridges for deleted networks
    were not getting cleaned up

commit 87f76fc34f1c70cd82576b8698d704853c892422
Merge: c8b097a 60d171e
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sun Jan 15 19:40:33 2012 -0800

    Merge remote branch 'upstream/master' into snaiksat/linux-bridge-plugin

commit c8b097abc2060b2eae01d84f9fed2c89851d93fd
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sun Jan 15 19:37:08 2012 -0800

    Simplified the logic for creating the bridge on the nova network host.

commit 499dbacd4c5352c5320f3b6e5e8cd7f3629dbcc8
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Fri Jan 13 16:07:53 2012 -0800

    Fix for the DHCP issue, now applying Gateway IP to the bridge

    Also MAC address from original tap device are applied to bridge
    and vlan subinterface

commit 6b4a2aea59702e2c12eeacc86101df9f6770d5ec
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sat Jan 7 14:29:00 2012 -0800

    Optimizations for processing in the loop

commit 01aa47d3572439b193077432c63bf2b85c629edb
Merge: 184f5dd 05df087
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sun Jan 1 19:04:17 2012 -0800

    Changes to incorporate Operational Status
    Merge remote branch 'origin' into snaiksat/linux-bridge-plugin

    Conflicts:
    	quantum/db/api.py

commit 05df0870191fac0353fe12a33efff68ef7ed0784
Merge: 31d586b 5b23b5e
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Fri Dec 30 12:30:05 2011 -0800

    Merge remote branch 'upstream/master'

commit 184f5dd8b73bc51025509792c15203ee73cd015e
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Mon Dec 12 02:09:12 2011 -0800

    Initial checkin for Linux Bridge L2 plugin.

Change-Id: I5b27538be6a768a6f7eb77409197f7d8b4bbc628
This commit is contained in:
Sumit Naiksatam 2012-01-22 01:35:45 -08:00
parent 10e44e7178
commit d2055d6998
24 changed files with 2613 additions and 0 deletions

View File

@ -3,3 +3,4 @@ include etc/*
include etc/init.d/*
include etc/quantum/plugins/openvswitch/*
include etc/quantum/plugins/cisco/*
include etc/quantum/plugins/linuxbridge/*

View File

@ -0,0 +1,24 @@
[VLANS]
vlan_start=1000
vlan_end=3000
[DATABASE]
# Use the following when running the tests for the in-memory DB
connection = sqlite
# Uncomment the following for using the MySQL DB when actually running the plugin,
# also remove the earlier sqlite connection configuration
#connection = mysql
name = quantum_linux_bridge
user = <mysql_user_name_here>
pass = <mysql_password_here>
host = <hostname_or_IP_address_of_Quantum_server>
# If you use a non-default port for the DB, change the following
port = 3306
[LINUX_BRIDGE]
#this is the interface connected to the switch on your Quantum network
physical_interface = eth1
[AGENT]
#agent's polling interval in seconds
polling_interval = 2

View File

@ -0,0 +1,257 @@
"""
# 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: Sumit Naiksatam, Cisco Systems, Inc.
"""
import logging
from quantum.api.api_common import OperationalStatus
from quantum.common import exceptions as exc
from quantum.db import api as db
from quantum.plugins.linuxbridge import plugin_configuration as conf
from quantum.plugins.linuxbridge.common import constants as const
from quantum.plugins.linuxbridge.common import utils as cutil
from quantum.plugins.linuxbridge.db import l2network_db as cdb
from quantum.quantum_plugin_base import QuantumPluginBase
LOG = logging.getLogger(__name__)
class LinuxBridgePlugin(QuantumPluginBase):
"""
LinuxBridgePlugin provides support for Quantum abstractions
using LinuxBridge. A new VLAN is created for each network.
It relies on an agent to perform the actual bridge configuration
on each host.
"""
def __init__(self, configfile=None):
cdb.initialize()
LOG.debug("Linux Bridge Plugin initialization done successfully")
def _get_vlan_for_tenant(self, tenant_id, **kwargs):
"""Get an available VLAN ID"""
try:
return cdb.reserve_vlanid()
except:
raise Exception("Failed to reserve VLAN ID for network")
def _release_vlan_for_tenant(self, tenant_id, net_id, **kwargs):
"""Release the ID"""
vlan_binding = cdb.get_vlan_binding(net_id)
return cdb.release_vlanid(vlan_binding[const.VLANID])
def _validate_port_state(self, port_state):
if port_state.upper() not in ('ACTIVE', 'DOWN'):
raise exc.StateInvalid(port_state=port_state)
return True
def get_all_networks(self, tenant_id, **kwargs):
"""
Returns a dictionary containing all
<network_uuid, network_name> for
the specified tenant.
"""
LOG.debug("LinuxBridgePlugin.get_all_networks() called")
networks_list = db.network_list(tenant_id)
new_networks_list = []
for network in networks_list:
new_network_dict = cutil.make_net_dict(network[const.UUID],
network[const.NETWORKNAME],
[], network[const.OPSTATUS])
new_networks_list.append(new_network_dict)
# This plugin does not perform filtering at the moment
return new_networks_list
def get_network_details(self, tenant_id, net_id):
"""
retrieved a list of all the remote vifs that
are attached to the network
"""
LOG.debug("LinuxBridgePlugin.get_network_details() called")
network = db.network_get(net_id)
ports_list = db.port_list(net_id)
ports_on_net = []
for port in ports_list:
new_port = cutil.make_port_dict(port)
ports_on_net.append(new_port)
new_network = cutil.make_net_dict(network[const.UUID],
network[const.NETWORKNAME],
ports_on_net,
network[const.OPSTATUS])
return new_network
def create_network(self, tenant_id, net_name, **kwargs):
"""
Creates a new Virtual Network, and assigns it
a symbolic name.
"""
LOG.debug("LinuxBridgePlugin.create_network() called")
new_network = db.network_create(tenant_id, net_name,
op_status=OperationalStatus.UP)
new_net_id = new_network[const.UUID]
vlan_id = self._get_vlan_for_tenant(tenant_id)
cdb.add_vlan_binding(vlan_id, new_net_id)
new_net_dict = {const.NET_ID: new_net_id,
const.NET_NAME: net_name,
const.NET_PORTS: [],
const.NET_OP_STATUS: new_network[const.OPSTATUS]}
return new_net_dict
def delete_network(self, tenant_id, net_id):
"""
Deletes the network with the specified network identifier
belonging to the specified tenant.
"""
LOG.debug("LinuxBridgePlugin.delete_network() called")
net = db.network_get(net_id)
if net:
ports_on_net = db.port_list(net_id)
if len(ports_on_net) > 0:
for port in ports_on_net:
if port[const.INTERFACEID]:
raise exc.NetworkInUse(net_id=net_id)
for port in ports_on_net:
self.delete_port(tenant_id, net_id, port[const.UUID])
net_dict = cutil.make_net_dict(net[const.UUID],
net[const.NETWORKNAME],
[], net[const.OPSTATUS])
try:
self._release_vlan_for_tenant(tenant_id, net_id)
cdb.remove_vlan_binding(net_id)
except Exception as excp:
LOG.warning("Exception: %s" % excp)
db.network_update(net_id, tenant_id, {const.OPSTATUS:
OperationalStatus.DOWN})
db.network_destroy(net_id)
return net_dict
# Network not found
raise exc.NetworkNotFound(net_id=net_id)
def update_network(self, tenant_id, net_id, **kwargs):
"""
Updates the attributes of a particular Virtual Network.
"""
LOG.debug("LinuxBridgePlugin.update_network() called")
network = db.network_update(net_id, tenant_id, **kwargs)
net_dict = cutil.make_net_dict(network[const.UUID],
network[const.NETWORKNAME],
[], network[const.OPSTATUS])
return net_dict
def get_all_ports(self, tenant_id, net_id, **kwargs):
"""
Retrieves all port identifiers belonging to the
specified Virtual Network.
"""
LOG.debug("LinuxBridgePlugin.get_all_ports() called")
network = db.network_get(net_id)
ports_list = db.port_list(net_id)
ports_on_net = []
for port in ports_list:
new_port = cutil.make_port_dict(port)
ports_on_net.append(new_port)
# This plugin does not perform filtering at the moment
return ports_on_net
def get_port_details(self, tenant_id, net_id, port_id):
"""
This method allows the user to retrieve a remote interface
that is attached to this particular port.
"""
LOG.debug("LinuxBridgePlugin.get_port_details() called")
network = db.network_get(net_id)
port = db.port_get(port_id, net_id)
new_port_dict = cutil.make_port_dict(port)
return new_port_dict
def create_port(self, tenant_id, net_id, port_state=None, **kwargs):
"""
Creates a port on the specified Virtual Network.
"""
LOG.debug("LinuxBridgePlugin.create_port() called")
port = db.port_create(net_id, port_state,
op_status=OperationalStatus.DOWN)
unique_port_id_string = port[const.UUID]
new_port_dict = cutil.make_port_dict(port)
return new_port_dict
def update_port(self, tenant_id, net_id, port_id, **kwargs):
"""
Updates the attributes of a port on the specified Virtual Network.
"""
LOG.debug("LinuxBridgePlugin.update_port() called")
network = db.network_get(net_id)
self._validate_port_state(kwargs["state"])
port = db.port_update(port_id, net_id, **kwargs)
new_port_dict = cutil.make_port_dict(port)
return new_port_dict
def delete_port(self, tenant_id, net_id, port_id):
"""
Deletes a port on a specified Virtual Network,
if the port contains a remote interface attachment,
the remote interface is first un-plugged and then the port
is deleted.
"""
LOG.debug("LinuxBridgePlugin.delete_port() called")
network = db.network_get(net_id)
port = db.port_get(port_id, net_id)
attachment_id = port[const.INTERFACEID]
if not attachment_id:
db.port_destroy(port_id, net_id)
new_port_dict = cutil.make_port_dict(port)
return new_port_dict
else:
raise exc.PortInUse(port_id=port_id, net_id=net_id,
att_id=attachment_id)
def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id):
"""
Attaches a remote interface to the specified port on the
specified Virtual Network.
"""
LOG.debug("LinuxBridgePlugin.plug_interface() called")
network = db.network_get(net_id)
port = db.port_get(port_id, net_id)
attachment_id = port[const.INTERFACEID]
if attachment_id:
raise exc.PortInUse(port_id=port_id, net_id=net_id,
att_id=attachment_id)
db.port_set_attachment(port_id, net_id, remote_interface_id)
def unplug_interface(self, tenant_id, net_id, port_id):
"""
Detaches a remote interface from the specified port on the
specified Virtual Network.
"""
LOG.debug("LinuxBridgePlugin.unplug_interface() called")
network = db.network_get(net_id)
port = db.port_get(port_id, net_id)
attachment_id = port[const.INTERFACEID]
if attachment_id == None:
raise exc.InvalidDetach(port_id=port_id, net_id=net_id,
att_id=remote_interface_id)
db.port_unset_attachment(port_id, net_id)
db.port_update(port_id, net_id, op_status=OperationalStatus.DOWN)

View File

@ -0,0 +1,155 @@
# -- Background
The Quantum Linux Bridge plugin is a plugin that allows you to manage
connectivity between VMs on hosts that are capable of running a Linux Bridge.
The Quantum Linux Bridge plugin consists of three components:
1) The plugin itself: The plugin uses a database backend (mysql for
now) to store configuration and mappings that are used by the
agent. The mysql server runs on a central server (often the same
host as nova itself).
2) The quantum service host which will be running quantum. This can
be run on the server running nova.
3) An agent which runs on the host and communicates with the host operating
system. The agent gathers the configuration and mappings from
the mysql database running on the quantum host.
The sections below describe how to configure and run the quantum
service with the Linux Bridge plugin.
# -- Python library dependencies
Make sure you have the following package(s) installedi on quantum server
host as well as any hosts which run the agent:
python-configobj
bridge-utils
python-mysqldb
sqlite3
# -- Nova configuration (controller node)
1) Make sure to set up nova using the quantum network manager in the
nova.conf on the node that will be running nova-network.
--network_manager=nova.network.quantum.manager.QuantumManager
# -- Nova configuration (compute node(s))
1) Configure the vif driver, and libvirt/vif type
--connection_type=libvirt
--libvirt_type=qemu
--libvirt_vif_type=ethernet
--libvirt_vif_driver=nova.virt.libvirt.vif_linuxbridge_quantum.QuantumLibvirtLinuxBridgeDriver
--linuxnet_interface_driver=nova.network.quantum.linux_net_linux_bridge.QuantumLibvirtLinuxBridgeDriver
The above two drivers are packaged with Quantum in the location:
quantum/plugins/linuxbridge/nova
These need to copied to the compute node in the appropriate locations.
a) Copy:
quantum/plugins/linuxbridge/nova/vif_linuxbridge_quantum.py
to:
nova/virt/libvirt/vif_linuxbridge_quantum.py
b) Copy:
quantum/plugins/linuxbridge/nova/linux_net_linux_bridge.py
to:
nova/network/quantum/linux_net_linux_bridge.py
2) If you want a DHCP server to be run for the VMs to acquire IPs,
add the following flag to your nova.conf file:
--quantum_use_dhcp
(Note: For more details on how to work with Quantum using Nova, i.e. how to create networks and such,
please refer to the top level Quantum README which points to the relevant documentation.)
# -- Quantum configuration
Make the Linux Bridge plugin the current quantum plugin
- edit etc/plugins.ini and change the provider line to be:
provider = quantum.plugins.linuxbridge.LinuxBridgePlugin.LinuxBridgePlugin
# -- Database config.
(Note: The plugin ships with a default SQLite in-memory database configuration,
and can be used to run tests without performing the suggested DB config below.)
The Linux Bridge quantum plugin requires access to a mysql database in order
to store configuration and mappings that will be used by the agent. Here is
how to set up the database on the host that you will be running the quantum
service on.
MySQL should be installed on the host, and all plugins and clients
must be configured with access to the database.
To prep mysql, run:
$ mysql -u root -p -e "create database quantum_linux_bridge"
# log in to mysql service
$ mysql -u root -p
# The Linux Bridge Quantum agent running on each compute node must be able to
# make a mysql connection back to the main database server.
mysql> GRANT USAGE ON *.* to root@'yourremotehost' IDENTIFIED BY 'newpassword';
# force update of authorization changes
mysql> FLUSH PRIVILEGES;
(Note: If the remote connection fails to MySQL, you might need to add the IP address,
and/or fully-qualified hostname, and/or unqualified hostname in the above GRANT sql
command. Also, you might need to specify "ALL" instead of "USAGE".)
# -- Plugin configuration
- Edit the configuration file:
etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini
Make sure it matches your mysql configuration. This file must be updated
with the addresses and credentials to access the database.
Note: When running the tests, set the connection type to sqlite, and when
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.
# -- Agent configuration
- Edit the configuration file:
etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini
- Copy quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py
and etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini
to the compute node.
$ Run the following:
sudo python linuxbridge_quantum_agent.py linuxbridge_conf.ini
(Use --verbose option to see the logs)
# -- Running Tests
(Note: The plugin ships with a default SQLite in-memory database configuration,
and can be used to run tests out of the box. Alternatively you can perform the
DB configuration for a persistent database as mentioned in the Database
Configuration section.)
- To run tests related to the Plugin and the VLAN management (run the
following from the top level Quantum directory):
PLUGIN_DIR=quantum/plugins/linuxbridge ./run_tests.sh -N
- The above will not however run the tests for the agent (which deals
with creating the bridge and interfaces). To run the agent tests, run the
following from the top level Quantum directory:
sudo PLUGIN_DIR=quantum/plugins/linuxbridge ./run_tests.sh -N tests.unit._test_linuxbridgeAgent
(Note: To run the agent tests you should have the environment setup as
indicated in the Agent Configuration, and also have the necessary dependencies
insalled.)

View File

View File

@ -0,0 +1,470 @@
#!/usr/bin/env python
# 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.
#
#
# Performs per host Linux Bridge configuration for Quantum.
# Based on the structure of the OpenVSwitch agent in the
# Quantum OpenVSwitch Plugin.
# @author: Sumit Naiksatam, Cisco Systems, Inc.
#
from optparse import OptionParser
from subprocess import *
import ConfigParser
import logging as LOG
import MySQLdb
import os
import signal
import sqlite3
import sys
import time
BRIDGE_NAME_PREFIX = "brq"
GATEWAY_INTERFACE_PREFIX = "gw-"
TAP_INTERFACE_PREFIX = "tap"
BRIDGE_FS = "/sys/devices/virtual/net/"
BRIDGE_NAME_PLACEHOLDER = "bridge_name"
BRIDGE_INTERFACES_FS = BRIDGE_FS + BRIDGE_NAME_PLACEHOLDER + "/brif/"
PORT_OPSTATUS_UPDATESQL = "UPDATE ports SET op_status = '%s' WHERE uuid = '%s'"
DEVICE_NAME_PLACEHOLDER = "device_name"
BRIDGE_PORT_FS_FOR_DEVICE = BRIDGE_FS + DEVICE_NAME_PLACEHOLDER + "/brport"
VLAN_BINDINGS = "vlan_bindings"
PORT_BINDINGS = "port_bindings"
OP_STATUS_UP = "UP"
OP_STATUS_DOWN = "DOWN"
DB_CONNECTION = None
class LinuxBridge:
def __init__(self, br_name_prefix, physical_interface):
self.br_name_prefix = br_name_prefix
self.physical_interface = physical_interface
def run_cmd(self, args):
LOG.debug("Running command: " + " ".join(args))
p = Popen(args, stdout=PIPE)
retval = p.communicate()[0]
if p.returncode == -(signal.SIGALRM):
LOG.debug("Timeout running command: " + " ".join(args))
if retval:
LOG.debug("Command returned: %s" % retval)
return retval
def device_exists(self, device):
"""Check if ethernet device exists."""
retval = self.run_cmd(['ip', 'link', 'show', 'dev', device])
if retval:
return True
else:
return False
def get_bridge_name(self, network_id):
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]
return bridge_name
def get_subinterface_name(self, 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)
return subinterface_name
def get_tap_device_name(self, interface_id):
if not interface_id:
LOG.warning("Invalid Interface ID, will lead to incorrect " \
"tap device name")
tap_device_name = TAP_INTERFACE_PREFIX + interface_id[0:11]
return tap_device_name
def get_all_quantum_bridges(self):
quantum_bridge_list = []
bridge_list = os.listdir(BRIDGE_FS)
for bridge in bridge_list:
if bridge.startswith(BRIDGE_NAME_PREFIX):
quantum_bridge_list.append(bridge)
return quantum_bridge_list
def get_interfaces_on_bridge(self, bridge_name):
if self.device_exists(bridge_name):
bridge_interface_path = \
BRIDGE_INTERFACES_FS.replace(BRIDGE_NAME_PLACEHOLDER,
bridge_name)
return os.listdir(bridge_interface_path)
def get_all_tap_devices(self):
tap_devices = []
retval = self.run_cmd(['ip', 'tuntap'])
rows = retval.split('\n')
for row in rows:
split_row = row.split(':')
if split_row[0].startswith(TAP_INTERFACE_PREFIX):
tap_devices.append(split_row[0])
return tap_devices
def get_all_gateway_devices(self):
gw_devices = []
retval = self.run_cmd(['ip', 'tuntap'])
rows = retval.split('\n')
for row in rows:
split_row = row.split(':')
if split_row[0].startswith(GATEWAY_INTERFACE_PREFIX):
gw_devices.append(split_row[0])
return gw_devices
def get_bridge_for_tap_device(self, tap_device_name):
bridges = self.get_all_quantum_bridges()
for bridge in bridges:
interfaces = self.get_interfaces_on_bridge(bridge)
if tap_device_name in interfaces:
return bridge
return None
def is_device_on_bridge(self, device_name):
if not device_name:
return False
else:
bridge_port_path = \
BRIDGE_PORT_FS_FOR_DEVICE.replace(DEVICE_NAME_PLACEHOLDER,
device_name)
return os.path.exists(bridge_port_path)
def ensure_vlan_bridge(self, network_id, vlan_id):
"""Create a vlan and bridge unless they already exist."""
interface = self.ensure_vlan(vlan_id)
bridge_name = self.get_bridge_name(network_id)
self.ensure_bridge(bridge_name, interface)
return interface
def ensure_vlan(self, vlan_id):
"""Create a vlan unless it already exists."""
interface = self.get_subinterface_name(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))
if self.run_cmd(['ip', 'link', 'add', 'link',
self.physical_interface,
'name', interface, 'type', 'vlan', 'id',
vlan_id]):
return
if self.run_cmd(['ip', 'link', 'set', interface, 'up']):
return
LOG.debug("Done creating subinterface %s" % interface)
return interface
def ensure_bridge(self, bridge_name, interface):
"""
Create a bridge unless it already exists.
"""
if not self.device_exists(bridge_name):
LOG.debug("Starting bridge %s for subinterface %s" % (bridge_name,
interface))
if self.run_cmd(['brctl', 'addbr', bridge_name]):
return
if self.run_cmd(['brctl', 'setfd', bridge_name, str(0)]):
return
if self.run_cmd(['brctl', 'stp', bridge_name, 'off']):
return
if self.run_cmd(['ip', 'link', 'set', bridge_name, 'up']):
return
LOG.debug("Done starting bridge %s for subinterface %s" %
(bridge_name, interface))
self.run_cmd(['brctl', 'addif', bridge_name, interface])
def add_tap_interface(self, network_id, 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
"""
if not tap_device_name:
return False
if not self.device_exists(tap_device_name):
LOG.debug("Tap device: %s does not exist on this host, skipped" %
tap_device_name)
return False
current_bridge_name = \
self.get_bridge_for_tap_device(tap_device_name)
bridge_name = self.get_bridge_name(network_id)
if bridge_name == current_bridge_name:
return False
LOG.debug("Adding device %s to bridge %s" % (tap_device_name,
bridge_name))
if current_bridge_name:
if self.run_cmd(['brctl', 'delif', current_bridge_name,
tap_device_name]):
return False
self.ensure_vlan_bridge(network_id, vlan_id)
if self.run_cmd(['brctl', 'addif', bridge_name, tap_device_name]):
return False
LOG.debug("Done adding device %s to bridge %s" % (tap_device_name,
bridge_name))
return True
def add_interface(self, network_id, 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
if interface_id.startswith(GATEWAY_INTERFACE_PREFIX):
return self.add_tap_interface(network_id, 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)
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)
LOG.debug("Deleting bridge %s" % bridge_name)
if self.run_cmd(['ip', 'link', 'set', bridge_name, 'down']):
return
if self.run_cmd(['brctl', 'delbr', bridge_name]):
return
LOG.debug("Done deleting bridge %s" % bridge_name)
else:
LOG.error("Cannot delete bridge %s, does not exist" % bridge_name)
def remove_interface(self, bridge_name, interface_name):
if self.device_exists(bridge_name):
if not self.is_device_on_bridge(interface_name):
return True
LOG.debug("Removing device %s from bridge %s" % \
(interface_name, bridge_name))
if self.run_cmd(['brctl', 'delif', bridge_name, interface_name]):
return False
LOG.debug("Done removing device %s from bridge %s" % \
(interface_name, bridge_name))
return True
else:
LOG.debug("Cannot remove device %s, bridge %s does not exist" % \
(interface_name, bridge_name))
return False
def delete_vlan(self, interface):
if self.device_exists(interface):
LOG.debug("Deleting subinterface %s for vlan" % interface)
if self.run_cmd(['ip', 'link', 'set', interface, 'down']):
return
if self.run_cmd(['ip', 'link', 'delete', interface]):
return
LOG.debug("Done deleting subinterface %s" % interface)
class LinuxBridgeQuantumAgent:
def __init__(self, br_name_prefix, physical_interface, polling_interval):
self.polling_interval = int(polling_interval)
self.setup_linux_bridge(br_name_prefix, physical_interface)
def setup_linux_bridge(self, br_name_prefix, physical_interface):
self.linux_br = LinuxBridge(br_name_prefix, physical_interface)
def process_port_binding(self, port_id, network_id, interface_id,
vlan_id):
return self.linux_br.add_interface(network_id, vlan_id, interface_id)
def process_unplugged_interfaces(self, plugged_interfaces):
"""
If there are any tap devices that are not corresponding to the
list of attached VIFs, then those are corresponding to recently
unplugged VIFs, so we need to remove those tap devices from their
current bridge association
"""
plugged_tap_device_names = []
plugged_gateway_device_names = []
for interface in plugged_interfaces:
if interface.startswith(GATEWAY_INTERFACE_PREFIX):
"""
The name for the gateway devices is set by the linux net
driver, hence we use the name as is
"""
plugged_gateway_device_names.append(interface)
else:
tap_device_name = self.linux_br.get_tap_device_name(interface)
plugged_tap_device_names.append(tap_device_name)
LOG.debug("plugged tap device names %s" % plugged_tap_device_names)
for tap_device in self.linux_br.get_all_tap_devices():
if tap_device not in plugged_tap_device_names:
current_bridge_name = \
self.linux_br.get_bridge_for_tap_device(tap_device)
if current_bridge_name:
self.linux_br.remove_interface(current_bridge_name,
tap_device)
for gw_device in self.linux_br.get_all_gateway_devices():
if gw_device not in plugged_gateway_device_names:
current_bridge_name = \
self.linux_br.get_bridge_for_tap_device(gw_device)
if current_bridge_name:
self.linux_br.remove_interface(current_bridge_name,
gw_device)
def process_deleted_networks(self, vlan_bindings):
current_quantum_networks = vlan_bindings.keys()
current_quantum_bridge_names = []
for network in current_quantum_networks:
bridge_name = self.linux_br.get_bridge_name(network)
current_quantum_bridge_names.append(bridge_name)
quantum_bridges_on_this_host = self.linux_br.get_all_quantum_bridges()
for bridge in quantum_bridges_on_this_host:
if bridge not in current_quantum_bridge_names:
self.linux_br.delete_vlan_bridge(bridge)
def manage_networks_on_host(self, conn, old_vlan_bindings,
old_port_bindings):
if DB_CONNECTION != 'sqlite':
cursor = MySQLdb.cursors.DictCursor(conn)
else:
cursor = conn.cursor()
cursor.execute("SELECT * FROM vlan_bindings")
rows = cursor.fetchall()
cursor.close()
vlan_bindings = {}
vlans_string = ""
for row in rows:
vlan_bindings[row['network_id']] = row
vlans_string = "%s %s" % (vlans_string, row)
plugged_interfaces = []
cursor = MySQLdb.cursors.DictCursor(conn)
cursor.execute("SELECT * FROM ports where state = 'ACTIVE'")
port_bindings = cursor.fetchall()
cursor.close()
ports_string = ""
for pb in port_bindings:
ports_string = "%s %s" % (ports_string, pb)
if pb['interface_id']:
vlan_id = \
str(vlan_bindings[pb['network_id']]['vlan_id'])
if self.process_port_binding(pb['uuid'],
pb['network_id'],
pb['interface_id'],
vlan_id):
cursor = MySQLdb.cursors.DictCursor(conn)
sql = PORT_OPSTATUS_UPDATESQL % (pb['uuid'],
OP_STATUS_UP)
cursor.execute(sql)
cursor.close()
plugged_interfaces.append(pb['interface_id'])
if old_port_bindings != port_bindings:
LOG.debug("Port-bindings: %s" % ports_string)
self.process_unplugged_interfaces(plugged_interfaces)
if old_vlan_bindings != vlan_bindings:
LOG.debug("VLAN-bindings: %s" % vlans_string)
self.process_deleted_networks(vlan_bindings)
conn.commit()
return {VLAN_BINDINGS: vlan_bindings,
PORT_BINDINGS: port_bindings}
def daemon_loop(self, conn):
old_vlan_bindings = {}
old_port_bindings = {}
while True:
bindings = self.manage_networks_on_host(conn,
old_vlan_bindings,
old_port_bindings)
old_vlan_bindings = bindings[VLAN_BINDINGS]
old_port_bindings = bindings[PORT_BINDINGS]
time.sleep(self.polling_interval)
if __name__ == "__main__":
usagestr = "%prog [OPTIONS] <config file>"
parser = OptionParser(usage=usagestr)
parser.add_option("-v", "--verbose", dest="verbose",
action="store_true", default=False, help="turn on verbose logging")
options, args = parser.parse_args()
if options.verbose:
LOG.basicConfig(level=LOG.DEBUG)
else:
LOG.basicConfig(level=LOG.WARN)
if len(args) != 1:
parser.print_help()
sys.exit(1)
config_file = args[0]
config = ConfigParser.ConfigParser()
conn = None
try:
fh = open(config_file)
fh.close()
config.read(config_file)
br_name_prefix = BRIDGE_NAME_PREFIX
physical_interface = config.get("LINUX_BRIDGE", "physical_interface")
polling_interval = config.get("AGENT", "polling_interval")
'Establish database connection and load models'
DB_CONNECTION = config.get("DATABASE", "connection")
if DB_CONNECTION == 'sqlite':
LOG.info("Connecting to sqlite DB")
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
else:
db_name = config.get("DATABASE", "name")
db_user = config.get("DATABASE", "user")
db_pass = config.get("DATABASE", "pass")
db_host = config.get("DATABASE", "host")
db_port = int(config.get("DATABASE", "port"))
LOG.info("Connecting to database %s on %s" % (db_name, db_host))
conn = MySQLdb.connect(host=db_host, user=db_user, port=db_port,
passwd=db_pass, db=db_name)
except Exception, e:
LOG.error("Unable to parse config file \"%s\": \nException%s"
% (config_file, str(e)))
sys.exit(1)
try:
plugin = LinuxBridgeQuantumAgent(br_name_prefix, physical_interface,
polling_interval)
LOG.info("Agent initialized successfully, now running...")
plugin.daemon_loop(conn)
finally:
if conn:
conn.close()
sys.exit(0)

View File

@ -0,0 +1,20 @@
"""
# 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.
#
"""

View File

@ -0,0 +1,34 @@
"""
# 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.
#
"""
from configobj import ConfigObj
class ConfigParser(ConfigObj):
"""Config Parser based on the ConfigObj module"""
def __init__(self, filename):
super(ConfigParser, self).__init__(filename, raise_errors=True,
file_error=True)
def dummy(self, section, key):
"""Dummy function to return the same key, used in walk"""
return section[key]

View File

@ -0,0 +1,65 @@
"""
# 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.
#
"""
PORT_STATE = 'port-state'
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

@ -0,0 +1,72 @@
# 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.")
try:
_("test")
except NameError:
def _(a_string):
"""
Default implementation of the gettext string
translation function: no translation
"""
return a_string
except TypeError:
# during doctesting, _ might mean something else
pass

View File

@ -0,0 +1,50 @@
"""
# 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.
#
"""
import logging
from quantum.api.api_common import OperationalStatus
from quantum.plugins.linuxbridge.common import constants as const
LOG = logging.getLogger(__name__)
def make_net_dict(net_id, net_name, ports, op_status):
"""Helper funciton"""
res = {const.NET_ID: net_id, const.NET_NAME: net_name, const.NET_OP_STATUS:
op_status}
if ports:
res[const.NET_PORTS] = ports
return res
def make_port_dict(port):
"""Helper funciton"""
if port[const.PORTSTATE] == const.PORT_UP:
op_status = port[const.OPSTATUS]
else:
op_status = OperationalStatus.DOWN
return {const.PORT_ID: str(port[const.UUID]),
const.PORT_STATE: port[const.PORTSTATE],
const.PORT_OP_STATUS: op_status,
const.NET_ID: port[const.NETWORKID],
const.ATTACHMENT: port[const.INTERFACEID]}

View File

@ -0,0 +1,18 @@
# 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.
#

View File

@ -0,0 +1,255 @@
# 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.
from sqlalchemy import func
from sqlalchemy.orm import exc
from quantum.common import exceptions as q_exc
from quantum.plugins.linuxbridge import plugin_configuration as conf
from quantum.plugins.linuxbridge.common import exceptions as c_exc
from quantum.plugins.linuxbridge.db import l2network_models
import logging
import quantum.db.api as db
LOG = logging.getLogger(__name__)
def initialize():
'Establish database connection and load models'
if conf.DB_CONNECTION == 'sqlite':
options = {"sql_connection": "sqlite://"}
else:
options = {"sql_connection": "mysql://%s:%s@%s:%s/%s" % (conf.DB_USER,
conf.DB_PASS,
conf.DB_HOST,
conf.DB_PORT,
conf.DB_NAME)}
db.configure_db(options)
create_vlanids()
def create_vlanids():
"""Prepopulates the vlan_bindings table"""
LOG.debug("create_vlanids() called")
session = db.get_session()
start = int(conf.VLAN_START)
end = int(conf.VLAN_END)
try:
vlanid = session.query(l2network_models.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(l2network_models.VlanID.vlan_id)).
one()[0])
current_end = \
int(session.query(func.max(l2network_models.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 = l2network_models.VlanID(start)
session.add(vlanid)
start += 1
session.flush()
return
def get_all_vlanids():
"""Gets all the vlanids"""
LOG.debug("get_all_vlanids() called")
session = db.get_session()
try:
vlanids = session.query(l2network_models.VlanID).\
all()
return vlanids
except exc.NoResultFound:
return []
def is_vlanid_used(vlan_id):
"""Checks if a vlanid is in use"""
LOG.debug("is_vlanid_used() called")
session = db.get_session()
try:
vlanid = session.query(l2network_models.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):
"""Sets the vlanid state to be unused"""
LOG.debug("release_vlanid() called")
session = db.get_session()
try:
vlanid = session.query(l2network_models.VlanID).\
filter_by(vlan_id=vlan_id).\
one()
vlanid["vlan_used"] = False
session.merge(vlanid)
session.flush()
return vlanid["vlan_used"]
except exc.NoResultFound:
raise c_exc.VlanIDNotFound(vlan_id=vlan_id)
return
def delete_vlanid(vlan_id):
"""Deletes a vlanid entry from db"""
LOG.debug("delete_vlanid() called")
session = db.get_session()
try:
vlanid = session.query(l2network_models.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():
"""Reserves the first unused vlanid"""
LOG.debug("reserve_vlanid() called")
session = db.get_session()
try:
rvlan = session.query(l2network_models.VlanID).\
first()
if not rvlan:
create_vlanids()
rvlan = session.query(l2network_models.VlanID).\
filter_by(vlan_used=False).\
first()
if not rvlan:
raise c_exc.VlanIDNotAvailable()
rvlanid = session.query(l2network_models.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 get_all_vlanids_used():
"""Gets all the vlanids used"""
LOG.debug("get_all_vlanids() called")
session = db.get_session()
try:
vlanids = session.query(l2network_models.VlanID).\
filter_by(vlan_used=True).\
all()
return vlanids
except exc.NoResultFound:
return []
def get_all_vlan_bindings():
"""Lists all the vlan to network associations"""
LOG.debug("get_all_vlan_bindings() called")
session = db.get_session()
try:
bindings = session.query(l2network_models.VlanBinding).\
all()
return bindings
except exc.NoResultFound:
return []
def get_vlan_binding(netid):
"""Lists the vlan given a network_id"""
LOG.debug("get_vlan_binding() called")
session = db.get_session()
try:
binding = session.query(l2network_models.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):
"""Adds a vlan to network association"""
LOG.debug("add_vlan_binding() called")
session = db.get_session()
try:
binding = session.query(l2network_models.VlanBinding).\
filter_by(vlan_id=vlanid).\
one()
raise c_exc.NetworkVlanBindingAlreadyExists(vlan_id=vlanid,
network_id=netid)
except exc.NoResultFound:
binding = l2network_models.VlanBinding(vlanid, netid)
session.add(binding)
session.flush()
return binding
def remove_vlan_binding(netid):
"""Removes a vlan to network association"""
LOG.debug("remove_vlan_binding() called")
session = db.get_session()
try:
binding = session.query(l2network_models.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):
"""Updates a vlan to network association"""
LOG.debug("update_vlan_binding() called")
session = db.get_session()
try:
binding = session.query(l2network_models.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)

View File

@ -0,0 +1,57 @@
# 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 uuid
from sqlalchemy import Column, Integer, String, Boolean
from sqlalchemy.orm import relation, object_mapper
from quantum.db.models import BASE
from quantum.db.models import QuantumBase
from quantum.db import models
class VlanID(BASE, QuantumBase):
"""Represents a vlan_id usage"""
__tablename__ = 'vlan_ids'
vlan_id = Column(Integer, primary_key=True)
vlan_used = Column(Boolean)
def __init__(self, vlan_id):
self.vlan_id = vlan_id
self.vlan_used = False
def __repr__(self):
return "<VlanID(%d,%s)>" % \
(self.vlan_id, self.vlan_used)
class VlanBinding(BASE, QuantumBase):
"""Represents a binding of vlan_id to network_id"""
__tablename__ = 'vlan_bindings'
vlan_id = Column(Integer, primary_key=True)
network_id = Column(String(255), nullable=False)
def __init__(self, vlan_id, network_id):
self.vlan_id = vlan_id
self.network_id = network_id
def __repr__(self):
return "<VlanBinding(%d,%s,%s)>" % \
(self.vlan_id, self.network_id)

View File

@ -0,0 +1,117 @@
# 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.
#
# Extends the linux_net.py kvm/linux network driver in Nova,
# borrows structure and code
# @author: Sumit Naiksatam, Cisco Systems, Inc.
#
"""Extends the linux_net driver when using the Linux Bridge plugin with
QuantumManager"""
from nova import exception
from nova import log as logging
from nova import utils
from nova.network.linux_net import *
LOG = logging.getLogger(__name__)
BRDIGE_NAME_PREFIX = "brq"
GATEWAY_INTERFACE_PREFIX = "gw-"
def _device_exists(device):
"""Check if ethernet device exists."""
(_out, err) = utils.execute('ip', 'link', 'show', 'dev', device,
check_exit_code=False)
return not err
# plugs interfaces using Linux Bridge when using QuantumManager
class QuantumLibvirtLinuxBridgeDriver(LinuxNetInterfaceDriver):
def plug(self, network, mac_address, gateway=True):
LOG.debug(_("inside plug()"))
dev = self.get_dev(network)
bridge = self.get_bridge(network)
if not gateway:
# If we weren't instructed to act as a gateway then add the
# appropriate flows to block all non-dhcp traffic.
# .. and make sure iptbles won't forward it as well.
iptables_manager.ipv4['filter'].add_rule('FORWARD',
'--in-interface %s -j DROP' % bridge)
iptables_manager.ipv4['filter'].add_rule('FORWARD',
'--out-interface %s -j DROP' % bridge)
return bridge
else:
iptables_manager.ipv4['filter'].add_rule('FORWARD',
'--in-interface %s -j ACCEPT' % bridge)
iptables_manager.ipv4['filter'].add_rule('FORWARD',
'--out-interface %s -j ACCEPT' % bridge)
if not _device_exists(dev):
try:
# First, try with 'ip'
utils.execute('ip', 'tuntap', 'add', dev, 'mode', 'tap',
run_as_root=True)
except exception.ProcessExecutionError:
# Second option: tunctl
utils.execute('tunctl', '-b', '-t', dev, run_as_root=True)
utils.execute('ip', 'link', 'set', dev, "address", mac_address,
run_as_root=True)
utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
if not _device_exists(bridge):
LOG.debug(_("Starting bridge %s "), bridge)
utils.execute('brctl', 'addbr', bridge, run_as_root=True)
utils.execute('brctl', 'setfd', bridge, str(0), run_as_root=True)
utils.execute('brctl', 'stp', bridge, 'off', run_as_root=True)
utils.execute('ip', 'link', 'set', bridge, "address", mac_address,
run_as_root=True)
utils.execute('ip', 'link', 'set', bridge, 'up', run_as_root=True)
LOG.debug(_("Done starting bridge %s"), bridge)
full_ip = '%s/%s' % (network['dhcp_server'],
network['cidr'].rpartition('/')[2])
utils.execute('ip', 'address', 'add', full_ip, 'dev', bridge,
run_as_root=True)
return dev
def unplug(self, network):
LOG.debug(_("inside unplug()"))
dev = self.get_dev(network)
try:
utils.execute('ip', 'link', 'delete', dev, run_as_root=True)
except exception.ProcessExecutionError:
LOG.warning(_("Failed while unplugging gateway interface '%s'"),
dev)
raise
LOG.debug(_("Unplugged gateway interface '%s'"), dev)
return dev
def get_dev(self, network):
dev = GATEWAY_INTERFACE_PREFIX + str(network['uuid'][0:11])
return dev
def get_bridge(self, network):
bridge = BRDIGE_NAME_PREFIX + str(network['uuid'][0:11])
return bridge

View File

@ -0,0 +1,73 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (C) 2012 Midokura KK
# Copyright (C) 2012 Nicira, Inc
# Copyright (C) 2012 Cisco Systems, Inc
# Copyright 2012 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
VIF driver for libvirt when QuantumManager is configured with Linux Bridge
plugin
"""
from nova import flags
from nova import log as logging
from nova.network import linux_net
from nova.virt import netutils
from nova import utils
from nova.virt.vif import VIFDriver
from nova import exception
LOG = logging.getLogger('nova.virt.libvirt.vif_linuxbridge_quantum')
FLAGS = flags.FLAGS
class QuantumLibvirtLinuxBridgeDriver(VIFDriver):
"""VIF driver for Linux Bridge."""
def get_dev_name(_self, iface_id):
return "tap" + iface_id[0:11]
def plug(self, instance, network, mapping):
iface_id = mapping['vif_uuid']
dev = self.get_dev_name(iface_id)
if not linux_net._device_exists(dev):
try:
# First, try with 'ip'
utils.execute('ip', 'tuntap', 'add', dev, 'mode', 'tap',
run_as_root=True)
except exception.ProcessExecutionError:
# Second option: tunctl
utils.execute('tunctl', '-b', '-t', dev, run_as_root=True)
utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
result = {
'script': '',
'name': dev,
'mac_address': mapping['mac']}
return result
def unplug(self, instance, network, mapping):
"""Unplug the VIF from the network by deleting the port from
the bridge."""
dev = self.get_dev_name(mapping['vif_uuid'])
try:
utils.execute('ip', 'link', 'delete', dev, run_as_root=True)
except exception.ProcessExecutionError:
LOG.warning(_("Failed while unplugging vif of instance '%s'"),
instance['name'])
raise

View File

@ -0,0 +1,48 @@
"""
# 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.
"""
import os
from quantum.common.config import find_config_file
from quantum.plugins.linuxbridge.common import configparser as confp
CONF_FILE = find_config_file({'plugin': 'linuxbridge'}, None,
"linuxbridge_conf.ini")
CONF_PARSER_OBJ = confp.ConfigParser(CONF_FILE)
"""
Reading the conf for the linuxbridge_plugin
"""
SECTION_CONF = CONF_PARSER_OBJ['VLANS']
VLAN_START = SECTION_CONF['vlan_start']
VLAN_END = SECTION_CONF['vlan_end']
SECTION_CONF = CONF_PARSER_OBJ['DATABASE']
DB_CONNECTION = SECTION_CONF['connection']
if DB_CONNECTION != 'sqlite':
DB_NAME = SECTION_CONF['name']
DB_USER = SECTION_CONF['user']
DB_PASS = SECTION_CONF['pass']
DB_HOST = SECTION_CONF['host']
DB_PORT = SECTION_CONF['port']

View File

@ -0,0 +1,81 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 OpenStack, LLC
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unittest runner for quantum Linux Bridge plugin
This file should be run from the top dir in the quantum directory
To run all tests::
PLUGIN_DIR=quantum/plugins/linuxbridge ./run_tests.sh
"""
import gettext
import logging
import os
import unittest
import sys
from nose import config
from nose import core
sys.path.append(os.getcwd())
sys.path.append(os.path.dirname(__file__))
from quantum.api.api_common import OperationalStatus
from quantum.common.test_lib import run_tests, test_config
from quantum.plugins.linuxbridge.LinuxBridgePlugin import LinuxBridgePlugin
import quantum.tests.unit
from tests.unit.test_database import L2networkDBTest
if __name__ == '__main__':
exit_status = False
# if a single test case was specified,
# we should only invoked the tests once
invoke_once = len(sys.argv) > 1
test_config['plugin_name'] = "LinuxBridgePlugin.LinuxBridgePlugin"
test_config['default_net_op_status'] = OperationalStatus.UP
test_config['default_port_op_status'] = OperationalStatus.DOWN
cwd = os.getcwd()
c = config.Config(stream=sys.stdout,
env=os.environ,
verbosity=3,
includeExe=True,
traverseNamespace=True,
plugins=core.DefaultPluginManager())
c.configureWhere(quantum.tests.unit.__path__)
exit_status = run_tests(c)
if invoke_once:
sys.exit(0)
os.chdir(cwd)
working_dir = os.path.abspath("quantum/plugins/linuxbridge")
c = config.Config(stream=sys.stdout,
env=os.environ,
verbosity=3,
workingDir=working_dir)
exit_status = exit_status or run_tests(c)
sys.exit(exit_status)

View File

@ -0,0 +1,451 @@
# 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: Shweta Padubidri, Cisco Systems, Inc.
#
import ConfigParser
import logging as LOG
import unittest
import sys
import os
import signal
from subprocess import *
import quantum.plugins.linuxbridge.agent.linuxbridge_quantum_agent\
as linux_agent
from quantum.plugins.linuxbridge.common import constants as lconst
from quantum.plugins.linuxbridge import LinuxBridgePlugin
from quantum.plugins.linuxbridge.db import l2network_db as cdb
import quantum.db.api as db
LOG.getLogger(__name__)
class LinuxBridgeAgentTest(unittest.TestCase):
def test_add_gateway_interface(
self, tenant_id="test_tenant", network_name="test_network",
interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc',
mac_address='fe:16:3e:51:60:dd'):
LOG.debug("test_tap_gateway_interface - START")
new_network =\
self._linuxbridge_plugin.create_network(tenant_id, network_name)
new_port = self._linuxbridge_plugin.create_port(
tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
self._linuxbridge_plugin.plug_interface(
tenant_id, new_network[lconst.NET_ID],
new_port[lconst.PORT_ID], interface_id)
bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
self.create_bridge(bridge_name)
device_name = self.gw_name_prefix + new_network[lconst.NET_ID][0:11]
self.create_device(device_name, mac_address)
vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
vlan_id = vlan_bind[lconst.VLANID]
self._linuxbridge_quantum_agent.process_port_binding(
new_port[lconst.PORT_ID], new_network[lconst.NET_ID],
device_name, str(vlan_id))
list_interface = self._linuxbridge_quantum_agent.linux_br.\
get_interfaces_on_bridge(bridge_name)
self.assertTrue(device_name in list_interface)
for interface in list_interface:
self._linuxbridge_quantum_agent.linux_br.remove_interface(
bridge_name, interface)
self.delete_device(interface)
self.delete_bridge(bridge_name)
self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
new_port[lconst.PORT_ID])
LOG.debug("test_add_gateway_interface - END")
def test_add_tap_interface(
self, tenant_id="test_tenant", network_name="test_network",
interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc',
mac_address='fe:16:3e:51:60:dd'):
LOG.debug("test_add_tap_interface - START")
new_network =\
self._linuxbridge_plugin.create_network(tenant_id, network_name)
new_port = self._linuxbridge_plugin.create_port(
tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
self._linuxbridge_plugin.plug_interface(
tenant_id, new_network[lconst.NET_ID],
new_port[lconst.PORT_ID], interface_id)
bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
self.create_bridge(bridge_name)
device_name = self.tap_name_prefix + interface_id[0:11]
self.create_device(device_name, mac_address)
vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
vlan_id = vlan_bind[lconst.VLANID]
self._linuxbridge_quantum_agent.process_port_binding(
new_port[lconst.PORT_ID], new_network[lconst.NET_ID],
interface_id, str(vlan_id))
list_interface = self._linuxbridge_quantum_agent.linux_br.\
get_interfaces_on_bridge(bridge_name)
self.assertTrue(device_name in list_interface)
for interface in list_interface:
self._linuxbridge_quantum_agent.linux_br.remove_interface(
bridge_name, interface)
self.delete_device(interface)
self.delete_bridge(bridge_name)
self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
new_port[lconst.PORT_ID])
LOG.debug("test_add_tap_interface -END")
def test_remove_interface(
self, tenant_id="test_tenant", network_name="test_network",
interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc',
mac_address='fe:16:3e:51:60:dd'):
LOG.debug("test_remove_interface - START")
new_network =\
self._linuxbridge_plugin.create_network(tenant_id, network_name)
new_port = self._linuxbridge_plugin.create_port(
tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
self._linuxbridge_plugin.plug_interface(
tenant_id, new_network[lconst.NET_ID],
new_port[lconst.PORT_ID], interface_id)
bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
self.create_bridge(bridge_name)
device_name = self.tap_name_prefix + interface_id[0:11]
self.create_device(device_name, mac_address)
vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
vlan_id = vlan_bind[lconst.VLANID]
self._linuxbridge_quantum_agent.process_port_binding(
new_port[lconst.PORT_ID], new_network[lconst.NET_ID],
interface_id, str(vlan_id))
list_interface = self._linuxbridge_quantum_agent.linux_br.\
get_interfaces_on_bridge(bridge_name)
self._linuxbridge_quantum_agent.linux_br.remove_interface(bridge_name,
device_name)
list_interface = self._linuxbridge_quantum_agent.linux_br.\
get_interfaces_on_bridge(bridge_name)
self.assertFalse(device_name in list_interface)
for interface in list_interface:
self._linuxbridge_quantum_agent.linux_br.remove_interface(
bridge_name, interface)
self.delete_device(interface)
self.delete_device(device_name)
self.delete_bridge(bridge_name)
self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
new_port[lconst.PORT_ID])
LOG.debug("test_remove_interface -END")
def test_ensure_vlan_bridge(
self, tenant_id="test_tenant", network_name="test_network",
interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc'):
LOG.debug("test_ensure_vlan_bridge - START")
new_network =\
self._linuxbridge_plugin.create_network(tenant_id, network_name)
new_port = self._linuxbridge_plugin.create_port(
tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
self._linuxbridge_plugin.plug_interface(
tenant_id, new_network[lconst.NET_ID],
new_port[lconst.PORT_ID], interface_id)
bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
vlan_id = vlan_bind[lconst.VLANID]
vlan_subinterface = self.physical_interface + '.' + str(vlan_id)
self._linuxbridge_quantum_agent.linux_br.ensure_vlan_bridge(
new_network[lconst.NET_ID], str(vlan_id))
list_quantum_bridges = self._linuxbridge_quantum_agent.linux_br.\
get_all_quantum_bridges()
self.assertTrue(bridge_name in list_quantum_bridges)
list_interface = self._linuxbridge_quantum_agent.linux_br.\
get_interfaces_on_bridge(bridge_name)
self.assertTrue(vlan_subinterface in list_interface)
for interface in list_interface:
self._linuxbridge_quantum_agent.linux_br.remove_interface(
bridge_name, interface)
self.delete_device(interface)
self.delete_bridge(bridge_name)
self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
new_port[lconst.PORT_ID])
LOG.debug("test_ensure_vlan_bridge -END")
def test_delete_vlan_bridge(
self, tenant_id="test_tenant", network_name="test_network",
interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc'):
LOG.debug("test_delete_vlan_bridge - START")
new_network =\
self._linuxbridge_plugin.create_network(tenant_id, network_name)
new_port = self._linuxbridge_plugin.create_port(
tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
self._linuxbridge_plugin.plug_interface(
tenant_id, new_network[lconst.NET_ID],
new_port[lconst.PORT_ID], interface_id)
bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
vlan_id = vlan_bind[lconst.VLANID]
vlan_subinterface = self.physical_interface + '.' + str(vlan_id)
self._linuxbridge_quantum_agent.linux_br.ensure_vlan_bridge(
new_network[lconst.NET_ID], str(vlan_id))
self._linuxbridge_quantum_agent.linux_br.delete_vlan_bridge(
bridge_name)
self.assertEquals(self.device_exists(vlan_subinterface), False)
self.assertEquals(self.device_exists(bridge_name), False)
self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
new_port[lconst.PORT_ID])
LOG.debug("test_delete_vlan_bridge - END")
def test_process_deleted_networks(
self, tenant_id="test_tenant", network_name="test_network",
interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc'):
LOG.debug("test_delete_vlan_bridge - START")
new_network =\
self._linuxbridge_plugin.create_network(tenant_id, network_name)
new_port = self._linuxbridge_plugin.create_port(
tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
self._linuxbridge_plugin.plug_interface(
tenant_id, new_network[lconst.NET_ID],
new_port[lconst.PORT_ID], interface_id)
bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
vlan_bindings = {}
vlan_bindings[new_network[lconst.NET_ID]] =\
cdb.get_vlan_binding(new_network[lconst.NET_ID])
vlan_id = vlan_bindings[new_network[lconst.NET_ID]][lconst.VLANID]
vlan_subinterface = self.physical_interface + '.' + str(vlan_id)
self._linuxbridge_quantum_agent.linux_br.ensure_vlan_bridge(
new_network[lconst.NET_ID], str(vlan_id))
self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
new_port[lconst.PORT_ID])
vlan_bindings = {}
self._linuxbridge_quantum_agent.process_deleted_networks(vlan_bindings)
self.assertEquals(self.device_exists(vlan_subinterface), False)
self.assertEquals(self.device_exists(bridge_name), False)
LOG.debug("test_delete_vlan_bridge - END")
def test_process_unplugged_tap_interface(
self, tenant_id="test_tenant", network_name="test_network",
interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc',
mac_address='fe:16:3e:51:60:dd'):
LOG.debug("test_process_unplugged_tap_interface - START")
new_network =\
self._linuxbridge_plugin.create_network(tenant_id, network_name)
new_port = self._linuxbridge_plugin.create_port(
tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
self._linuxbridge_plugin.plug_interface(
tenant_id, new_network[lconst.NET_ID],
new_port[lconst.PORT_ID], interface_id)
bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
self.create_bridge(bridge_name)
device_name = self.tap_name_prefix + interface_id[0:11]
self.create_device(device_name, mac_address)
vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
vlan_id = vlan_bind[lconst.VLANID]
self._linuxbridge_quantum_agent.process_port_binding(
new_port[lconst.PORT_ID], new_network[lconst.NET_ID],
interface_id, str(vlan_id))
list_interface = self._linuxbridge_quantum_agent.linux_br.\
get_interfaces_on_bridge(bridge_name)
self._linuxbridge_plugin.unplug_interface(tenant_id,
new_network[lconst.NET_ID],
new_port[lconst.PORT_ID])
plugged_interface = []
self._linuxbridge_quantum_agent.process_unplugged_interfaces(
plugged_interface)
list_interface = self._linuxbridge_quantum_agent.linux_br.\
get_interfaces_on_bridge(bridge_name)
self.assertFalse(device_name in list_interface)
for interface in list_interface:
self._linuxbridge_quantum_agent.linux_br.remove_interface(
bridge_name, interface)
self.delete_device(interface)
self.delete_device(device_name)
self.delete_bridge(bridge_name)
self.tearDownNetworkPort(tenant_id, new_network[lconst.NET_ID],
new_port[lconst.PORT_ID])
LOG.debug("test_test_process_unplugged_tap_interface -END")
def test_process_unplugged_gw_interface(
self, tenant_id="test_tenant", network_name="test_network",
interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc',
mac_address='fe:16:3e:51:60:dd'):
LOG.debug("test_process_unplugged_gw_interface - START")
new_network =\
self._linuxbridge_plugin.create_network(tenant_id, network_name)
new_port = self._linuxbridge_plugin.create_port(
tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
self._linuxbridge_plugin.plug_interface(
tenant_id, new_network[lconst.NET_ID],
new_port[lconst.PORT_ID], interface_id)
bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
self.create_bridge(bridge_name)
device_name = self.gw_name_prefix + new_network[lconst.NET_ID][0:11]
self.create_device(device_name, mac_address)
vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
vlan_id = vlan_bind[lconst.VLANID]
self._linuxbridge_quantum_agent.process_port_binding(
new_port[lconst.PORT_ID], new_network[lconst.NET_ID],
interface_id, str(vlan_id))
list_interface = self._linuxbridge_quantum_agent.linux_br.\
get_interfaces_on_bridge(bridge_name)
self._linuxbridge_plugin.unplug_interface(tenant_id,
new_network[lconst.NET_ID],
new_port[lconst.PORT_ID])
plugged_interface = []
self._linuxbridge_quantum_agent.process_unplugged_interfaces(
plugged_interface)
list_interface = self._linuxbridge_quantum_agent.linux_br.\
get_interfaces_on_bridge(bridge_name)
self.assertFalse(device_name in list_interface)
for interface in list_interface:
self._linuxbridge_quantum_agent.linux_br.remove_interface(
bridge_name, interface)
self.delete_device(interface)
self.delete_device(device_name)
self.delete_bridge(bridge_name)
self.tearDownNetworkPort(tenant_id, new_network[lconst.NET_ID],
new_port[lconst.PORT_ID])
LOG.debug("test_test_process_unplugged_gw_interface -END")
def create_bridge(self, bridge_name):
"""
Create a bridge
"""
self.run_cmd(['brctl', 'addbr', bridge_name])
self.run_cmd(['brctl', 'setfd', bridge_name, str(0)])
self.run_cmd(['brctl', 'stp', bridge_name, 'off'])
self.run_cmd(['ip', 'link', 'set', bridge_name, 'up'])
def delete_bridge(self, bridge_name):
"""
Delete a bridge
"""
self.run_cmd(['ip', 'link', 'set', bridge_name, 'down'])
self.run_cmd(['brctl', 'delbr', bridge_name])
def create_device(self, dev, mac_address):
self.run_cmd(['ip', 'tuntap', 'add', dev, 'mode', 'tap'])
self.run_cmd(['ip', 'link', 'set', dev, "address", mac_address])
self.run_cmd(['ip', 'link', 'set', dev, 'up'])
def delete_device(self, dev):
self.run_cmd(['ip', 'link', 'delete', dev])
def setUp(self):
"""
Set up function
"""
self.tenant_id = "test_tenant"
self.network_name = "test_network"
self.config_file = os.path.join(os.path.dirname(__file__), os.pardir,
os.pardir, os.pardir, os.pardir,
os.pardir, "etc", "quantum",
"plugins", "linuxbridge",
"linuxbridge_conf.ini")
config = ConfigParser.ConfigParser()
self.br_name_prefix = "brq"
self.gw_name_prefix = "gw-"
self.tap_name_prefix = "tap"
self._linuxbridge_plugin = LinuxBridgePlugin.LinuxBridgePlugin()
try:
fh = open(self.config_file)
fh.close()
config.read(self.config_file)
self.physical_interface = config.get("LINUX_BRIDGE",
"physical_interface")
self.polling_interval = config.get("AGENT", "polling_interval")
except Exception, e:
LOG.error("Unable to parse config file \"%s\": \nException%s"
% (self.config_file, str(e)))
sys.exit(1)
self._linuxbridge = linux_agent.LinuxBridge(self.br_name_prefix,
self.physical_interface)
self._linuxbridge_quantum_agent = linux_agent.LinuxBridgeQuantumAgent(
self.br_name_prefix,
self.physical_interface,
self.polling_interval)
def run_cmd(self, args):
LOG.debug("Running command: " + " ".join(args))
p = Popen(args, stdout=PIPE)
retval = p.communicate()[0]
if p.returncode == -(signal.SIGALRM):
LOG.debug("Timeout running command: " + " ".join(args))
if retval:
LOG.debug("Command returned: %s" % retval)
return retval
def device_exists(self, device):
"""Check if ethernet device exists."""
retval = self.run_cmd(['ip', 'link', 'show', 'dev', device])
if retval:
return True
else:
return False
"""
Clean up functions after the tests
"""
def tearDown(self):
"""Clear the test environment(Clean the Database)"""
db.clear_db()
def tearDownNetwork(self, tenant_id, network_dict_id):
"""
Tear down the Network
"""
self._linuxbridge_plugin.delete_network(tenant_id, network_dict_id)
def tearDownUnplugInterface(self, tenant_id, network_dict_id, port_id):
"""
Tear down the port
"""
self._linuxbridge_plugin.unplug_interface(tenant_id, network_dict_id,
port_id)
self.tearDownNetworkPort(tenant_id, network_dict_id, port_id)
def tearDownNetworkPort(self, tenant_id, network_dict_id, port_id):
"""
Tear down Network Port
"""
self._linuxbridge_plugin.delete_port(tenant_id, network_dict_id,
port_id)
self.tearDownNetwork(tenant_id, network_dict_id)

View File

@ -0,0 +1,362 @@
"""
# 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.
"""
"""
test_database.py is an independent test suite
that tests the database api method calls
"""
import logging as LOG
import unittest
from common import constants as const
import quantum.db.api as db
import db.l2network_db as l2network_db
LOG.getLogger(__name__)
class L2networkDB(object):
"""Class conisting of methods to call L2network db methods"""
def get_all_vlan_bindings(self):
"""Get all vlan binding into a list of dict"""
vlans = []
try:
for vlan_bind in l2network_db.get_all_vlan_bindings():
LOG.debug("Getting vlan bindings for vlan: %s" %
vlan_bind.vlan_id)
vlan_dict = {}
vlan_dict["vlan-id"] = str(vlan_bind.vlan_id)
vlan_dict["net-id"] = str(vlan_bind.network_id)
vlans.append(vlan_dict)
except Exception, exc:
LOG.error("Failed to get all vlan bindings: %s" % str(exc))
return vlans
def get_vlan_binding(self, network_id):
"""Get a vlan binding"""
vlan = []
try:
for vlan_bind in l2network_db.get_vlan_binding(network_id):
LOG.debug("Getting vlan binding for vlan: %s"
% vlan_bind.vlan_id)
vlan_dict = {}
vlan_dict["vlan-id"] = str(vlan_bind.vlan_id)
vlan_dict["net-id"] = str(vlan_bind.network_id)
vlan.append(vlan_dict)
except Exception, exc:
LOG.error("Failed to get vlan binding: %s" % str(exc))
return vlan
def create_vlan_binding(self, vlan_id, network_id):
"""Create a vlan binding"""
vlan_dict = {}
try:
res = l2network_db.add_vlan_binding(vlan_id, network_id)
LOG.debug("Created vlan binding for vlan: %s" % res.vlan_id)
vlan_dict["vlan-id"] = str(res.vlan_id)
vlan_dict["net-id"] = str(res.network_id)
return vlan_dict
except Exception, exc:
LOG.error("Failed to create vlan binding: %s" % str(exc))
def delete_vlan_binding(self, network_id):
"""Delete a vlan binding"""
try:
res = l2network_db.remove_vlan_binding(network_id)
LOG.debug("Deleted vlan binding for vlan: %s" % res.vlan_id)
vlan_dict = {}
vlan_dict["vlan-id"] = str(res.vlan_id)
return vlan_dict
except Exception, exc:
raise Exception("Failed to delete vlan binding: %s" % str(exc))
def update_vlan_binding(self, network_id, vlan_id):
"""Update a vlan binding"""
try:
res = l2network_db.update_vlan_binding(network_id, vlan_id)
LOG.debug("Updating vlan binding for vlan: %s" % res.vlan_id)
vlan_dict = {}
vlan_dict["vlan-id"] = str(res.vlan_id)
vlan_dict["net-id"] = str(res.network_id)
return vlan_dict
except Exception, exc:
raise Exception("Failed to update vlan binding: %s" % str(exc))
class QuantumDB(object):
"""Class conisting of methods to call Quantum db methods"""
def get_all_networks(self, tenant_id):
"""Get all networks"""
nets = []
try:
for net in db.network_list(tenant_id):
LOG.debug("Getting network: %s" % net.uuid)
net_dict = {}
net_dict["tenant-id"] = net.tenant_id
net_dict["net-id"] = str(net.uuid)
net_dict["net-name"] = net.name
nets.append(net_dict)
except Exception, exc:
LOG.error("Failed to get all networks: %s" % str(exc))
return nets
def get_network(self, network_id):
"""Get a network"""
net = []
try:
for net in db.network_get(network_id):
LOG.debug("Getting network: %s" % net.uuid)
net_dict = {}
net_dict["tenant-id"] = net.tenant_id
net_dict["net-id"] = str(net.uuid)
net_dict["net-name"] = net.name
net.append(net_dict)
except Exception, exc:
LOG.error("Failed to get network: %s" % str(exc))
return net
def create_network(self, tenant_id, net_name):
"""Create a network"""
net_dict = {}
try:
res = db.network_create(tenant_id,
net_name,
op_status="UP")
LOG.debug("Created network: %s" % res.uuid)
net_dict["tenant-id"] = res.tenant_id
net_dict["net-id"] = str(res.uuid)
net_dict["net-name"] = res.name
return net_dict
except Exception, exc:
LOG.error("Failed to create network: %s" % str(exc))
def delete_network(self, net_id):
"""Delete a network"""
try:
net = db.network_destroy(net_id)
LOG.debug("Deleted network: %s" % net.uuid)
net_dict = {}
net_dict["net-id"] = str(net.uuid)
return net_dict
except Exception, exc:
raise Exception("Failed to delete port: %s" % str(exc))
def update_network(self, tenant_id, net_id, **kwargs):
"""Update a network"""
try:
net = db.network_update(net_id, tenant_id, **kwargs)
LOG.debug("Updated network: %s" % net.uuid)
net_dict = {}
net_dict["net-id"] = str(net.uuid)
net_dict["net-name"] = net.name
return net_dict
except Exception, exc:
raise Exception("Failed to update network: %s" % str(exc))
def get_all_ports(self, net_id):
"""Get all ports"""
ports = []
try:
for port in db.port_list(net_id):
LOG.debug("Getting port: %s" % port.uuid)
port_dict = {}
port_dict["port-id"] = str(port.uuid)
port_dict["net-id"] = str(port.network_id)
port_dict["int-id"] = port.interface_id
port_dict["state"] = port.state
port_dict["net"] = port.network
ports.append(port_dict)
return ports
except Exception, exc:
LOG.error("Failed to get all ports: %s" % str(exc))
def get_port(self, net_id, port_id):
"""Get a port"""
port_list = []
port = db.port_get(net_id, port_id)
try:
LOG.debug("Getting port: %s" % port.uuid)
port_dict = {}
port_dict["port-id"] = str(port.uuid)
port_dict["net-id"] = str(port.network_id)
port_dict["int-id"] = port.interface_id
port_dict["state"] = port.state
port_list.append(port_dict)
return port_list
except Exception, exc:
LOG.error("Failed to get port: %s" % str(exc))
def create_port(self, net_id):
"""Add a port"""
port_dict = {}
try:
port = db.port_create(net_id)
LOG.debug("Creating port %s" % port.uuid)
port_dict["port-id"] = str(port.uuid)
port_dict["net-id"] = str(port.network_id)
port_dict["int-id"] = port.interface_id
port_dict["state"] = port.state
return port_dict
except Exception, exc:
LOG.error("Failed to create port: %s" % str(exc))
def delete_port(self, net_id, port_id):
"""Delete a port"""
try:
port = db.port_destroy(net_id, port_id)
LOG.debug("Deleted port %s" % port.uuid)
port_dict = {}
port_dict["port-id"] = str(port.uuid)
return port_dict
except Exception, exc:
raise Exception("Failed to delete port: %s" % str(exc))
def update_port(self, net_id, port_id, port_state):
"""Update a port"""
try:
port = db.port_set_state(net_id, port_id, port_state)
LOG.debug("Updated port %s" % port.uuid)
port_dict = {}
port_dict["port-id"] = str(port.uuid)
port_dict["net-id"] = str(port.network_id)
port_dict["int-id"] = port.interface_id
port_dict["state"] = port.state
return port_dict
except Exception, exc:
raise Exception("Failed to update port state: %s" % str(exc))
def plug_interface(self, net_id, port_id, int_id):
"""Plug interface to a port"""
try:
port = db.port_set_attachment(net_id, port_id, int_id)
LOG.debug("Attached interface to port %s" % port.uuid)
port_dict = {}
port_dict["port-id"] = str(port.uuid)
port_dict["net-id"] = str(port.network_id)
port_dict["int-id"] = port.interface_id
port_dict["state"] = port.state
return port_dict
except Exception, exc:
raise Exception("Failed to plug interface: %s" % str(exc))
def unplug_interface(self, net_id, port_id):
"""Unplug interface to a port"""
try:
port = db.port_unset_attachment(net_id, port_id)
LOG.debug("Detached interface from port %s" % port.uuid)
port_dict = {}
port_dict["port-id"] = str(port.uuid)
port_dict["net-id"] = str(port.network_id)
port_dict["int-id"] = port.interface_id
port_dict["state"] = port.state
return port_dict
except Exception, exc:
raise Exception("Failed to unplug interface: %s" % str(exc))
class L2networkDBTest(unittest.TestCase):
"""Class conisting of L2network DB unit tests"""
def setUp(self):
"""Setup for tests"""
l2network_db.initialize()
l2network_db.create_vlanids()
self.dbtest = L2networkDB()
self.quantum = QuantumDB()
LOG.debug("Setup")
def tearDown(self):
"""Tear Down"""
db.clear_db()
def testa_create_vlanbinding(self):
"""test add vlan binding"""
net1 = self.quantum.create_network("t1", "netid1")
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
self.assertTrue(vlan1["vlan-id"] == "10")
self.teardown_vlanbinding()
self.teardown_network()
def testb_getall_vlanbindings(self):
"""test get all vlan binding"""
net1 = self.quantum.create_network("t1", "netid1")
net2 = self.quantum.create_network("t1", "netid2")
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
self.assertTrue(vlan1["vlan-id"] == "10")
vlan2 = self.dbtest.create_vlan_binding(20, net2["net-id"])
self.assertTrue(vlan2["vlan-id"] == "20")
vlans = self.dbtest.get_all_vlan_bindings()
self.assertTrue(len(vlans) == 2)
self.teardown_vlanbinding()
self.teardown_network()
def testc_delete_vlanbinding(self):
"""test delete vlan binding"""
net1 = self.quantum.create_network("t1", "netid1")
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
self.assertTrue(vlan1["vlan-id"] == "10")
self.dbtest.delete_vlan_binding(net1["net-id"])
vlans = self.dbtest.get_all_vlan_bindings()
count = 0
for vlan in vlans:
if vlan["vlan-id"] is "10":
count += 1
self.assertTrue(count == 0)
self.teardown_vlanbinding()
self.teardown_network()
def testd_update_vlanbinding(self):
"""test update vlan binding"""
net1 = self.quantum.create_network("t1", "netid1")
vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
self.assertTrue(vlan1["vlan-id"] == "10")
vlan1 = self.dbtest.update_vlan_binding(net1["net-id"], 11)
self.assertTrue(vlan1["vlan-id"] == "11")
self.teardown_vlanbinding()
self.teardown_network()
def teste_test_vlanids(self):
"""test vlanid methods"""
l2network_db.create_vlanids()
vlanids = l2network_db.get_all_vlanids()
self.assertTrue(len(vlanids) > 0)
vlanid = l2network_db.reserve_vlanid()
used = l2network_db.is_vlanid_used(vlanid)
self.assertTrue(used)
used = l2network_db.release_vlanid(vlanid)
self.assertFalse(used)
#counting on default teardown here to clear db
def teardown_network(self):
"""tearDown Network table"""
LOG.debug("Tearing Down Network")
nets = self.quantum.get_all_networks("t1")
for net in nets:
netid = net["net-id"]
self.quantum.delete_network(netid)
def teardown_vlanbinding(self):
"""tearDown VlanBinding table"""
LOG.debug("Tearing Down Vlan Binding")
vlans = self.dbtest.get_all_vlan_bindings()
for vlan in vlans:
netid = vlan["net-id"]
self.dbtest.delete_vlan_binding(netid)

View File

@ -70,6 +70,7 @@ config_path = 'etc/quantum/'
init_path = 'etc/init.d'
ovs_plugin_config_path = 'etc/quantum/plugins/openvswitch'
cisco_plugin_config_path = 'etc/quantum/plugins/cisco'
linuxbridge_plugin_config_path = 'etc/quantum/plugins/linuxbridge'
DataFiles = [
(config_path,
@ -84,6 +85,8 @@ DataFiles = [
'etc/quantum/plugins/cisco/ucs.ini',
'etc/quantum/plugins/cisco/cisco_plugins.ini',
'etc/quantum/plugins/cisco/db_conn.ini']),
(linuxbridge_plugin_config_path,
['etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini']),
]
setup(