#! /usr/bin/python
#
# Copyright (c) 2009 Nicira Networks.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import getopt
import os
import re
import subprocess
import sys

argv0 = sys.argv[0]

BRCTL = "/root/vswitch/xs-original/brctl"
VSWITCHD_CONF = "/etc/ovs-vswitchd.conf"

# Execute the real brctl program, passing the same arguments that were passed
# to us.
def delegate():
    os.execl(BRCTL, BRCTL, *sys.argv[1:])
    # execl should never return.  We only arrive here if brctl failed to exec.
    sys.exit(1)

# Read the ovs-vswitchd.conf file named 'filename' and return its contents as a
# dictionary that maps from string keys to lists of string values.  (Even
# singleton values are represented as lists.)
def cfg_read(filename):
    try:
        f = open(filename)
    except IOError, e:
        sys.stderr.write("%s: could not open %s (%s)\n"
                         % (argv0, filename, e.strerror))
        sys.exit(1)

    cfg = {}
    rx = re.compile('([-._@$:+a-zA-Z0-9]+)(?:[ \t\r\n\v]*)=(?:[ \t\r\n\v]*)(.*)$')
    for line in f:
        line = line.strip()
        if len(line) == 0 or line[0] == '#':
            continue

        match = rx.match(line)
        if match == None:
            continue

        key, value = match.groups()
        if key not in cfg:
            cfg[key] = []
        cfg[key].append(value)
    return cfg

# Returns a set of the immediate subsections of 'section' within 'cfg'.  For
# example, if 'section' is "bridge" and keys bridge.a, bridge.b, bridge.b.c,
# and bridge.c.x.y.z exist, returns set(['a', 'b', 'c']).
def cfg_get_subsections(cfg, section):
    subsections = set()
    for key in cfg:
        if key.startswith(section + "."):
            dot = key.find(".", len(section) + 1)
            if dot == -1:
                dot = len(key)
            subsections.add(key[len(section) + 1:dot])
    return subsections

# Returns True if 'cfg' contains a key whose single value is 'true'.  Otherwise
# returns False.
def cfg_get_bool(cfg, name):
    return name in cfg and cfg[name] == ['true']

# If 'cfg' has a port named 'port' configured with an implicit VLAN, returns
# that VLAN number.  Otherwise, returns 0.
def get_port_vlan(cfg, port):
    try:
        return int(cfg["vlan.%s.tag" % port][0])
    except (ValueError, KeyError):
        return 0

# Returns all the ports within 'bridge' in 'cfg'.  If 'vlan' is nonnegative,
# the ports returned are only those configured with implicit VLAN 'vlan'.
def get_bridge_ports(cfg, bridge, vlan):
    ports = []
    for port in cfg["bridge.%s.port" % bridge]:
        if vlan < 0 or get_port_vlan(cfg, port) == vlan:
            ports.append(port)
    return ports

# Returns all the interfaces within 'bridge' in 'cfg'.  If 'vlan' is
# nonnegative, the interfaces returned are only those whose ports are
# configured with implicit VLAN 'vlan'.
def get_bridge_ifaces(cfg, bridge, vlan):
    ifaces = []
    for port in get_bridge_ports(cfg, bridge, vlan):
        ifaces.extend(cfg.get("bonding.%s.slave" % port, [port]))
    return ifaces

# Returns the first line of the file named 'name', with the trailing new-line
# (if any) stripped off.
def read_first_line_of_file(name):
    file = None
    try:
        file = open(name, 'r')
        return file.readline().rstrip('\n')
    finally:
        if file != None:
            file.close()

# Returns a bridge ID constructed from the MAC address of network device
# 'netdev', in the format "8000.000102030405".
def get_bridge_id(netdev):
    try:
        hwaddr = read_first_line_of_file("/sys/class/net/%s/address" % netdev)
        return "8000.%s" % (hwaddr.replace(":", ""))
    except:
        return "8000.002320ffffff"

def cmd_show():
    print "bridge name\tbridge id\t\tSTP enabled\tinterfaces"
    cfg = cfg_read(VSWITCHD_CONF)

    # Find all the bridges.
    real_bridges = [(br, br, 0) for br in cfg_get_subsections(cfg, "bridge")]
    fake_bridges = []
    for linux_bridge, ovs_bridge, vlan in real_bridges:
        for iface in get_bridge_ifaces(cfg, ovs_bridge, -1):
            if cfg_get_bool(cfg, "iface.%s.fake-bridge" % iface):
                fake_bridges.append((iface, ovs_bridge,
                                     get_port_vlan(cfg, iface)))
    bridges = real_bridges + fake_bridges

    # Find all the interfaces on each bridge.
    for linux_bridge, ovs_bridge, vlan in bridges:
        bridge_ports = get_bridge_ports(cfg, ovs_bridge, vlan)
        if linux_bridge in bridge_ports:
            bridge_ports.remove(linux_bridge)
        bridge_ports.sort()
        bridge_id = get_bridge_id(linux_bridge)
        first_port = ""
        if bridge_ports:
            first_port = bridge_ports[0]
        print "%s\t\t%s\t%s\t\t%s" % (linux_bridge, bridge_id, "no", first_port)
        for port in bridge_ports[1:]:
            print "\t\t\t\t\t\t\t%s" % port

def main():
    # Parse the command line.
    try:
        options, args = getopt.gnu_getopt(sys.argv[1:],
                                          "hV", ["help", "version"])
    except getopt.GetoptError, msg:
        sys.stderr.write("%s: %s (use --help for help)\n" % (argv0, msg))
        sys.exit(1)

    # Handle command-line options.
    for opt, optarg in options:
        if opt == "-h" or opt == "--help":
            delegate()
        elif opt == "-V" or opt == "--version":
            subprocess.call([BRCTL, "--version"])
            print "Open vSwitch brctl wrapper"
            sys.exit(0)

    # Execute commands.  Most commands are delegated to the brctl binary that
    # we are wrapping, but we implement the "show" command ourselves.
    if args and args[0] == "show":
        cmd_show()
    else:
        delegate()

if __name__ == "__main__":
    main()
