[ovs-dev] [PATCH 2/2] ovn-detrace: A tool decoding ofproto/trace output for ovn debugging.
Han Zhou
zhouhan at gmail.com
Thu Mar 23 06:15:16 UTC 2017
A python script to decode ofproto/trace output to add ovn lflow
information inline. It expands lflow further to ACLs when relevant.
$ ovs-appctl ofproto/trace ... | ovn-decode
Signed-off-by: Han Zhou <zhouhan at gmail.com>
---
manpages.mk | 8 ++
ovn/utilities/automake.mk | 15 ++-
ovn/utilities/ovn-detrace.1.in | 37 +++++++
ovn/utilities/ovn-detrace.in | 215 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 271 insertions(+), 4 deletions(-)
create mode 100644 ovn/utilities/ovn-detrace.1.in
create mode 100755 ovn/utilities/ovn-detrace.in
diff --git a/manpages.mk b/manpages.mk
index 742bd66..38b9468 100644
--- a/manpages.mk
+++ b/manpages.mk
@@ -1,5 +1,13 @@
# Generated automatically -- do not modify! -*- buffer-read-only: t -*-
+ovn/utilities/ovn-detrace.1: \
+ ovn/utilities/ovn-detrace.1.in \
+ lib/common-syn.man \
+ lib/common.man
+ovn/utilities/ovn-detrace.1.in:
+lib/common-syn.man:
+lib/common.man:
+
ovn/utilities/ovn-sbctl.8: \
ovn/utilities/ovn-sbctl.8.in \
lib/common.man \
diff --git a/ovn/utilities/automake.mk b/ovn/utilities/automake.mk
index 08e48ea..b96f9bf 100644
--- a/ovn/utilities/automake.mk
+++ b/ovn/utilities/automake.mk
@@ -6,14 +6,18 @@ man_MANS += \
ovn/utilities/ovn-ctl.8 \
ovn/utilities/ovn-nbctl.8 \
ovn/utilities/ovn-sbctl.8 \
- ovn/utilities/ovn-trace.8
+ ovn/utilities/ovn-trace.8 \
+ ovn/utilities/ovn-detrace.1
-MAN_ROOTS += ovn/utilities/ovn-sbctl.8.in
+MAN_ROOTS += \
+ ovn/utilities/ovn-sbctl.8.in \
+ ovn/utilities/ovn-detrace.1.in
# Docker drivers
bin_SCRIPTS += \
ovn/utilities/ovn-docker-overlay-driver \
- ovn/utilities/ovn-docker-underlay-driver
+ ovn/utilities/ovn-docker-underlay-driver \
+ ovn/utilities/ovn-detrace
EXTRA_DIST += \
ovn/utilities/ovn-ctl \
@@ -22,13 +26,16 @@ EXTRA_DIST += \
ovn/utilities/ovn-docker-underlay-driver \
ovn/utilities/ovn-nbctl.8.xml \
ovn/utilities/ovn-trace.8.xml \
+ ovn/utilities/ovn-detrace.in \
ovn/utilities/ovndb-servers.ocf
CLEANFILES += \
ovn/utilities/ovn-ctl.8 \
ovn/utilities/ovn-nbctl.8 \
ovn/utilities/ovn-sbctl.8 \
- ovn/utilities/ovn-trace.8
+ ovn/utilities/ovn-trace.8 \
+ ovn/utilities/ovn-detrace.1 \
+ ovn/utilities/ovn-detrace
# ovn-nbctl
bin_PROGRAMS += ovn/utilities/ovn-nbctl
diff --git a/ovn/utilities/ovn-detrace.1.in b/ovn/utilities/ovn-detrace.1.in
new file mode 100644
index 0000000..2e5e514
--- /dev/null
+++ b/ovn/utilities/ovn-detrace.1.in
@@ -0,0 +1,37 @@
+.TH ovn\-detrace 1 "@VERSION@" "Open vSwitch" "Open vSwitch Manual"
+.
+.SH NAME
+ovn\-detrace \- convert ``ovs\-appctl ofproto/trace'' output to combine
+OVN logical flow information.
+.
+.SH SYNOPSIS
+\fBovn\-detrace < \fIfile\fR
+.so lib/common-syn.man
+.
+.SH DESCRIPTION
+The \fBovn\-detrace\fR program reads \fBovs\-appctl ofproto/trace\fR output on
+stdin, looking for flow cookies, and expand each cookie with corresponding OVN
+logical flows. It expands logical flow further with the north-bound information
+e.g. the ACL that generated the logical flow, when relevant.
+.PP
+.
+.SH "OPTIONS"
+.so lib/common.man
+.
+.IP "\fB\-\-ovnsb=\fIserver\fR"
+The OVN Southbound DB remote to contact. If the \fBOVN_SB_DB\fR
+environment variable is set, its value is used as the default.
+Otherwise, the default is \fBunix:@RUNDIR@/ovnsb_db.sock\fR, but this
+default is unlikely to be useful outside of single-machine OVN test
+environments.
+.
+.IP "\fB\-\-ovnnb=\fIserver\fR"
+The OVN Northbound DB remote to contact. If the \fBOVN_NB_DB\fR
+environment variable is set, its value is used as the default.
+Otherwise, the default is \fBunix:@RUNDIR@/ovnnb_db.sock\fR, but this
+default is unlikely to be useful outside of single-machine OVN test
+environments.
+.
+.SH "SEE ALSO"
+.
+.BR ovs\-appctl (8), ovn\-sbctl (8), ovn-\-nbctl (8), ovn\-trace (8)
diff --git a/ovn/utilities/ovn-detrace.in b/ovn/utilities/ovn-detrace.in
new file mode 100755
index 0000000..3d8e433
--- /dev/null
+++ b/ovn/utilities/ovn-detrace.in
@@ -0,0 +1,215 @@
+#! /usr/bin/env @PYTHON@
+#
+# 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 (@VERSION@).")
+ 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) @VERSION@" % 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', '/usr/local/var/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, "\t* ")
+ print_lflow_nb_hint(lflow, "\t\t* ", 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:
--
2.1.0
More information about the dev
mailing list