#! /usr/bin/python2
#
# Copyright (c) 2017 eBay 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.

import getopt
import os
import re
import sys
import time

try:
    from ovs.db import idl
    from ovs import jsonrpc
    from ovs.poller import Poller
    from ovs.stream import Stream
except Exception:
    print("ERROR: Please install the correct Open vSwitch python support")
    print("       libraries (2.11.1).")
    print("       Alternatively, check that your PYTHONPATH is pointing to")
    print("       the correct location.")
    sys.exit(1)


argv0 = sys.argv[0]


def usage():
    print """\
%(argv0)s:
usage: %(argv0)s < FILE
where FILE is output from ovs-appctl ofproto/trace.

The following options are also available:
  -h, --help                  display this help message
  -V, --version               display version information
  --ovnsb=DATABASE            use DATABASE as southbound DB
  --ovnnb=DATABASE            use DATABASE as northbound DB\
""" % {'argv0': argv0}
    sys.exit(0)


class OVSDB(object):
    @staticmethod
    def wait_for_db_change(idl):
        seq = idl.change_seqno
        stop = time.time() + 10
        while idl.change_seqno == seq and not idl.run():
            poller = Poller()
            idl.wait(poller)
            poller.block()
            if time.time() >= stop:
                raise Exception('Retry Timeout')

    def __init__(self, db_sock, schema_name):
        self._db_sock = db_sock
        self._txn = None
        schema = self._get_schema(schema_name)
        schema.register_all()
        self._idl_conn = idl.Idl(db_sock, schema)
        OVSDB.wait_for_db_change(self._idl_conn)  # Initial Sync with DB

    def _get_schema(self, schema_name):
        error, strm = Stream.open_block(Stream.open(self._db_sock))
        if error:
            raise Exception("Unable to connect to %s" % self._db_sock)
        rpc = jsonrpc.Connection(strm)
        req = jsonrpc.Message.create_request('get_schema', [schema_name])
        error, resp = rpc.transact_block(req)
        rpc.close()

        if error or resp.error:
            raise Exception('Unable to retrieve schema.')
        return idl.SchemaHelper(None, resp.result)

    def get_table(self, table_name):
        return self._idl_conn.tables[table_name]

    def _find_row(self, table_name, find):
        return next(
            (row for row in self.get_table(table_name).rows.values()
             if find(row)), None)

    def _find_row_by_name(self, table_name, value):
        return self._find_row(table_name, lambda row: row.name == value)

    def find_row_by_partial_uuid(self, table_name, value):
        return self._find_row(table_name, lambda row: value in str(row.uuid))


def get_lflow_from_cookie(ovnsb_db, cookie):
    return ovnsb_db.find_row_by_partial_uuid('Logical_Flow', cookie)


def print_lflow(lflow, prefix):
    ldp_uuid = lflow.logical_datapath.uuid
    ldp_name = str(lflow.logical_datapath.external_ids.get('name'))

    print '%sLogical datapath: "%s" (%s) [%s]' % (prefix,
                                                  ldp_name,
                                                  ldp_uuid,
                                                  lflow.pipeline)
    print "%sLogical flow: table=%s (%s), priority=%s, " \
          "match=(%s), actions=(%s)" % (prefix,
                                        lflow.table_id,
                                        lflow.external_ids.get('stage-name'),
                                        lflow.priority,
                                        str(lflow.match).strip('"'),
                                        str(lflow.actions).strip('"'))


def print_lflow_nb_hint(lflow, prefix, ovnnb_db):
    external_ids = lflow.external_ids
    if external_ids.get('stage-name') in ['ls_in_acl',
                                          'ls_out_acl']:
        acl_hint = external_ids.get('stage-hint')
        if not acl_hint:
            return
        acl = ovnnb_db.find_row_by_partial_uuid('ACL', acl_hint)
        if not acl:
            return
        output = "%sACL: %s, priority=%s, " \
                 "match=(%s), %s" % (prefix,
                                     acl.direction,
                                     acl.priority,
                                     acl.match.strip('"'),
                                     acl.action)
        if acl.log:
            output += ' (log)'
        print output


def main():
    try:
        options, args = getopt.gnu_getopt(sys.argv[1:], 'hV',
                                          ['help', 'version', 'ovnsb=', 'ovnnb='])
    except getopt.GetoptError, geo:
        sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
        sys.exit(1)

    ovnsb_db = None
    ovnnb_db = None

    for key, value in options:
        if key in ['-h', '--help']:
            usage()
        elif key in ['-V', '--version']:
            print "%s (Open vSwitch) 2.11.1" % argv0
        elif key in ['--ovnsb']:
            ovnsb_db = value
        elif key in ['--ovnnb']:
            ovnnb_db = value
        else:
            sys.exit(0)

    if len(args) != 0:
        sys.stderr.write("%s: non-option argument not supported "
                         "(use --help for help)\n" % argv0)
        sys.exit(1)

    ovs_rundir = os.getenv('OVS_RUNDIR', '/run/openvswitch')
    if not ovnsb_db:
        ovnsb_db = os.getenv('OVN_SB_DB')
        if not ovnsb_db:
            ovnsb_db = 'unix:%s/ovnsb_db.sock' % ovs_rundir

    if not ovnnb_db:
        ovnnb_db = os.getenv('OVN_NB_DB')
        if not ovnnb_db:
            ovnnb_db = 'unix:%s/ovnnb_db.sock' % ovs_rundir

    ovsdb_ovnsb = OVSDB(ovnsb_db, 'OVN_Southbound')
    ovsdb_ovnnb = OVSDB(ovnnb_db, 'OVN_Northbound')

    regex_cookie = re.compile(r'^.*cookie 0x([0-9a-fA-F]+)')
    regex_table_id = re.compile(r'^[0-9]+\.')
    cookie = None
    while True:
        line = sys.stdin.readline()
        if cookie:
            # print lflow info when the current flow block ends
            if regex_table_id.match(line) or line.strip() == '':
                lflow = get_lflow_from_cookie(ovsdb_ovnsb, cookie)
                print_lflow(lflow, "  * ")
                print_lflow_nb_hint(lflow, "    * ", ovsdb_ovnnb)
                cookie = None

        print line.strip()
        if line == "":
            break

        m = regex_cookie.match(line)
        if not m:
            continue
        cookie = m.group(1)


if __name__ == "__main__":
    main()


# Local variables:
# mode: python
# End:
