Files
vmware-nsx/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py
Sumit Naiksatam 25e22bb750 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
2012-02-08 13:32:55 -08:00

471 lines
18 KiB
Python
Executable File

#!/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)