Add ovsdbmonitor GUI tool by Andy Southgate, contributed by Citrix.
authorAndy Southgate <andy.southgate@citrix.com>
Tue, 11 May 2010 18:46:52 +0000 (11:46 -0700)
committerBen Pfaff <blp@nicira.com>
Thu, 13 May 2010 21:53:39 +0000 (14:53 -0700)
With Makefiles and Autoconfiscation by Ben Pfaff.

Signed-off-by: Thomas Lacroix <thomas.lacroix@citrix.com>
Signed-off-by: Ben Pfaff <blp@nicira.com>
34 files changed:
INSTALL.Linux
Makefile.am
README
configure.ac
m4/openvswitch.m4
ovsdb/automake.mk
ovsdb/ovsdbmonitor/.gitignore [new file with mode: 0644]
ovsdb/ovsdbmonitor/COPYING [new file with mode: 0644]
ovsdb/ovsdbmonitor/ConfigWindow.ui [new file with mode: 0644]
ovsdb/ovsdbmonitor/FlowWindow.ui [new file with mode: 0644]
ovsdb/ovsdbmonitor/HostWindow.ui [new file with mode: 0644]
ovsdb/ovsdbmonitor/LogWindow.ui [new file with mode: 0644]
ovsdb/ovsdbmonitor/MainWindow.ui [new file with mode: 0644]
ovsdb/ovsdbmonitor/OVEApp.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/OVECommonWindow.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/OVEConfig.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/OVEConfigWindow.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/OVEFetch.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/OVEFlowWindow.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/OVEHostWindow.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/OVELogWindow.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/OVELogger.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/OVEMainWindow.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/OVEStandard.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/OVEUtil.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/Ui_ConfigWindow.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/Ui_FlowWindow.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/Ui_HostWindow.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/Ui_LogWindow.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/Ui_MainWindow.py [new file with mode: 0644]
ovsdb/ovsdbmonitor/automake.mk [new file with mode: 0644]
ovsdb/ovsdbmonitor/ovsdbmonitor.in [new file with mode: 0755]
ovsdb/ovsdbmonitor/ovsdbmonitor.py.in [new file with mode: 0755]
ovsdb/ovsdbmonitor/qt4reactor.py [new file with mode: 0644]

index 177a9cd13b7d2eb4df426a2f41be5e13949543d7..20a0dd806f542220f8a876b6c7409ed77fca3fb0 100644 (file)
@@ -69,6 +69,11 @@ you will also need the following software:
 
     - Python 2.x, for x >= 4.
 
+If you modify the ovsdbmonitor tool, then you will also need the
+following:
+
+    - pyuic4 from PyQt4 (http://www.riverbankcomputing.co.uk).
+
 Installation Requirements
 -------------------------
 
@@ -91,6 +96,22 @@ following software:
       if it is installed in a different location, then some Open
       vSwitch log messages will not be as detailed.
 
+To run the ovsdmonitor tool, the machine must also have the following
+software:
+
+    - Python 2.x, for x >= 4.
+
+    - Python Twisted Conch.
+
+    - Python JSON.
+
+    - PySide or PyQt4.
+
+    - Python Zope interface module.
+
+(On Debian "lenny" the above can be installed with "apt-get install
+python-json python-qt4 python-zopeinterface python-twisted-conch".)
+
 Building and Installing Open vSwitch for Linux
 ==============================================
 
index f8dfd16c1c4aa968777c5e1da9329d0e727aaf81..79323da8a75f2cb135625b155839ca5b602b23a7 100644 (file)
@@ -69,6 +69,7 @@ SUFFIXES += .in
                 -e 's,[@]pkgdatadir[@],$(pkgdatadir),g' \
                 -e 's,[@]sysconfdir[@],$(sysconfdir),g' \
                 -e 's,[@]abs_top_srcdir[@],$(abs_top_srcdir),g' \
+                -e 's,[@]ovsdbmonitordir[@],$(ovsdbmonitordir),g' \
             > $@.tmp
        @if head -n 1 $@.tmp | grep -q '#!'; then \
            echo chmod +x $@.tmp; \
diff --git a/README b/README
index 5b8099890682be2a9e8f14a919b76d8be96052da..f1367fa8526735bdd4ff1d25e5f98faaecfe30ef 100644 (file)
--- a/README
+++ b/README
@@ -67,6 +67,9 @@ The main components of this distribution are:
     * ovs-appctl, a utility that sends commands to running Open
       vSwitch daemons.
 
+    * ovsdbmonitor, a GUI tool for remotely viewing OVS databases and
+      OpenFlow flow tables.
+
 Open vSwitch also provides an OpenFlow implementation and tools for
 those interested in OpenFlow but not additional Open vSwitch features:
 
index e8497e1f30fdb2f068b49dc87c97fb2f2028ddb7..1284a7839d50e13df5c2558c333df033a8a851a9 100644 (file)
@@ -49,6 +49,8 @@ OVS_CHECK_CURSES
 OVS_CHECK_LINUX_VT_H
 OVS_CHECK_PCRE
 OVS_CHECK_PYTHON
+OVS_CHECK_PYUIC4
+OVS_CHECK_OVSDBMONITOR
 OVS_CHECK_IF_PACKET
 OVS_CHECK_STRTOK_R
 AC_CHECK_MEMBERS([struct stat.st_mtim.tv_nsec, struct stat.st_mtimensec],
@@ -95,4 +97,6 @@ tests/atlocal])
 dnl This makes sure that include/openflow gets created in the build directory.
 AC_CONFIG_COMMANDS([include/openflow/openflow.h.stamp])
 
+AC_CONFIG_COMMANDS([ovsdb/ovsdbmonitor/dummy], [:])
+
 AC_OUTPUT
index 58e4b64e0255c8f4f61df4f4b592d70bc2fa7483..621d69a90e90e896398eda4dcc0bc88d4cd52825 100644 (file)
@@ -264,3 +264,60 @@ else:
      HAVE_PYTHON=no
    fi
    AM_CONDITIONAL([HAVE_PYTHON], [test "$HAVE_PYTHON" = yes])])
+
+dnl Checks for pyuic4.
+AC_DEFUN([OVS_CHECK_PYUIC4],
+  [AC_CACHE_CHECK(
+    [for pyuic4],
+    [ovs_cv_pyuic4],
+    [if (pyuic4 --version) >/dev/null 2>&1; then
+       ovs_cv_pyuic4=pyuic4
+     else
+       ovs_cv_pyuic4=no
+     fi])
+   AM_MISSING_PROG([PYUIC4], [pyuic4])
+   if test $ovs_cv_pyuic4 != no; then
+     PYUIC4=$ovs_cv_pyuic4
+   fi])
+
+dnl Checks whether $PYTHON supports the module given as $1
+AC_DEFUN([OVS_CHECK_PYTHON_MODULE],
+  [AC_REQUIRE([OVS_CHECK_PYTHON])
+   AC_CACHE_CHECK(
+     [for $1 Python module],
+     [ovs_cv_py_[]AS_TR_SH([$1])],
+     [ovs_cv_py_[]AS_TR_SH([$1])=no
+      if test $HAVE_PYTHON = yes; then
+        AS_ECHO(["running $PYTHON -c 'import $1
+import sys
+sys.exit(0)'..."]) >AS_MESSAGE_LOG_FD 2>&1
+        if $PYTHON -c 'import $1
+import sys
+sys.exit(0)' >AS_MESSAGE_LOG_FD 2>&1; then
+          ovs_cv_py_[]AS_TR_SH([$1])=yes
+        fi
+      fi])])
+
+dnl Checks for Python modules needed by ovsdbmonitor.
+AC_DEFUN([OVS_CHECK_OVSDBMONITOR],
+  [OVS_CHECK_PYTHON_MODULE([PySide.QtCore])
+   OVS_CHECK_PYTHON_MODULE([PyQt4.QtCore])
+   OVS_CHECK_PYTHON_MODULE([twisted.conch.ssh])
+   OVS_CHECK_PYTHON_MODULE([twisted.internet])
+   OVS_CHECK_PYTHON_MODULE([twisted.application])
+   OVS_CHECK_PYTHON_MODULE([json])
+   OVS_CHECK_PYTHON_MODULE([zope.interface])
+   if (test $ovs_cv_py_PySide_QtCore = yes \
+       || test $ovs_cv_py_PyQt4_QtCore = yes) \
+      && test $ovs_cv_py_twisted_conch_ssh = yes \
+      && test $ovs_cv_py_twisted_internet = yes \
+      && test $ovs_cv_py_twisted_application = yes \
+      && test $ovs_cv_py_json = yes \
+      && test $ovs_cv_py_zope_interface = yes; then
+     BUILD_OVSDBMONITOR=yes
+   else
+     BUILD_OVSDBMONITOR=no
+   fi
+   AC_MSG_CHECKING([whether to build ovsdbmonitor])
+   AC_MSG_RESULT([$BUILD_OVSDBMONITOR])
+   AM_CONDITIONAL([BUILD_OVSDBMONITOR], [test $BUILD_OVSDBMONITOR = yes])])
index 7d578a8c8c1576ed54bd25b5555d4dbbea1f4427..055e47bbd8d65952b2b41cf1ee26d5b9a8fa9218 100644 (file)
@@ -114,3 +114,5 @@ EXTRA_DIST += ovsdb/ovsdb-doc.in
 noinst_SCRIPTS += ovsdb/ovsdb-doc
 DISTCLEANFILES += ovsdb/ovsdb-doc
 OVSDB_DOC = $(PYTHON) $(srcdir)/ovsdb/ovsdb-doc.in
+
+include ovsdb/ovsdbmonitor/automake.mk
diff --git a/ovsdb/ovsdbmonitor/.gitignore b/ovsdb/ovsdbmonitor/.gitignore
new file mode 100644 (file)
index 0000000..d6f433b
--- /dev/null
@@ -0,0 +1 @@
+/ovsdbmonitor.py
diff --git a/ovsdb/ovsdbmonitor/COPYING b/ovsdb/ovsdbmonitor/COPYING
new file mode 100644 (file)
index 0000000..f1c6e1c
--- /dev/null
@@ -0,0 +1,13 @@
+Copyright (c) 2010 Citrix 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.
diff --git a/ovsdb/ovsdbmonitor/ConfigWindow.ui b/ovsdb/ovsdbmonitor/ConfigWindow.ui
new file mode 100644 (file)
index 0000000..6a1316e
--- /dev/null
@@ -0,0 +1,185 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigWindow</class>
+ <widget class="QDialog" name="ConfigWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>386</width>
+    <height>303</height>
+   </rect>
+  </property>
+  <property name="focusPolicy">
+   <enum>Qt::TabFocus</enum>
+  </property>
+  <property name="windowTitle">
+   <string>OVSDB Monitor Configuration</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <item>
+      <widget class="QTabWidget" name="tabWidget">
+       <property name="currentIndex">
+        <number>0</number>
+       </property>
+       <widget class="QWidget" name="hosts">
+        <attribute name="title">
+         <string>Hosts</string>
+        </attribute>
+        <widget class="QWidget" name="layoutWidget">
+         <property name="geometry">
+          <rect>
+           <x>10</x>
+           <y>10</y>
+           <width>341</width>
+           <height>194</height>
+          </rect>
+         </property>
+         <layout class="QHBoxLayout" name="horizontalLayout_2">
+          <item>
+           <widget class="QListWidget" name="hostList"/>
+          </item>
+          <item>
+           <layout class="QVBoxLayout" name="verticalLayout_2">
+            <item>
+             <widget class="QPushButton" name="hostAddButton">
+              <property name="text">
+               <string>Add</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QPushButton" name="hostEditButton">
+              <property name="text">
+               <string>Edit</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QPushButton" name="hostDeleteButton">
+              <property name="text">
+               <string>Delete</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <spacer name="verticalSpacer">
+              <property name="orientation">
+               <enum>Qt::Vertical</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+               <size>
+                <width>20</width>
+                <height>40</height>
+               </size>
+              </property>
+             </spacer>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </widget>
+       <widget class="QWidget" name="logging">
+        <attribute name="title">
+         <string>Logging</string>
+        </attribute>
+        <layout class="QGridLayout" name="gridLayout_2">
+         <item row="0" column="0">
+          <widget class="QCheckBox" name="logTrafficCheckBox">
+           <property name="toolTip">
+            <string>Whether to log traffic exchanges in the log window</string>
+           </property>
+           <property name="text">
+            <string>Log traffic</string>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="0">
+          <spacer name="verticalSpacer_2">
+           <property name="orientation">
+            <enum>Qt::Vertical</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>20</width>
+             <height>164</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+        </layout>
+       </widget>
+       <widget class="QWidget" name="view">
+        <attribute name="title">
+         <string>View</string>
+        </attribute>
+        <layout class="QVBoxLayout" name="verticalLayout_3">
+         <item>
+          <widget class="QCheckBox" name="truncateUuidsCheckBox">
+           <property name="toolTip">
+            <string>Replaces UUIDs with a shorter string of the first few characters.  The tooltip still contains the full value</string>
+           </property>
+           <property name="text">
+            <string>Truncate UUIDs</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="verticalSpacer_3">
+           <property name="orientation">
+            <enum>Qt::Vertical</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>20</width>
+             <height>164</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+        </layout>
+       </widget>
+      </widget>
+     </item>
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout">
+       <item>
+        <spacer name="horizontalSpacer">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item>
+        <widget class="QDialogButtonBox" name="buttonBox">
+         <property name="standardButtons">
+          <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>hostList</tabstop>
+  <tabstop>hostAddButton</tabstop>
+  <tabstop>hostEditButton</tabstop>
+  <tabstop>hostDeleteButton</tabstop>
+  <tabstop>buttonBox</tabstop>
+  <tabstop>tabWidget</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/ovsdb/ovsdbmonitor/FlowWindow.ui b/ovsdb/ovsdbmonitor/FlowWindow.ui
new file mode 100644 (file)
index 0000000..f3605b4
--- /dev/null
@@ -0,0 +1,216 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>FlowWindow</class>
+ <widget class="QMainWindow" name="FlowWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>800</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>OVSDB Monitor</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QGridLayout" name="gridLayout">
+    <item row="0" column="0">
+     <widget class="QTabWidget" name="tabWidget">
+      <property name="currentIndex">
+       <number>0</number>
+      </property>
+      <widget class="QWidget" name="unset">
+       <attribute name="title">
+        <string>Awaiting update...</string>
+       </attribute>
+       <layout class="QGridLayout" name="gridLayout_10"/>
+      </widget>
+     </widget>
+    </item>
+    <item row="1" column="0">
+     <layout class="QHBoxLayout" name="horizontalLayout_2">
+      <item>
+       <widget class="QCheckBox" name="ssgCheckBox">
+        <property name="text">
+         <string>Server-side grep</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QComboBox" name="ssgComboBox">
+        <property name="editable">
+         <bool>true</bool>
+        </property>
+        <property name="maxVisibleItems">
+         <number>20</number>
+        </property>
+        <property name="insertPolicy">
+         <enum>QComboBox::NoInsert</enum>
+        </property>
+        <property name="minimumContentsLength">
+         <number>32</number>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="ssgSaveButton">
+        <property name="text">
+         <string>Save</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="ssgDeleteButton">
+        <property name="text">
+         <string>Delete</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <spacer name="horizontalSpacer_2">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>40</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+     </layout>
+    </item>
+    <item row="3" column="0">
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <item>
+       <widget class="QLabel" name="hostLabel">
+        <property name="text">
+         <string>Host</string>
+        </property>
+        <property name="buddy">
+         <cstring>hostComboBox</cstring>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QComboBox" name="hostComboBox">
+        <property name="sizeAdjustPolicy">
+         <enum>QComboBox::AdjustToContents</enum>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QCheckBox" name="intervalCheckBox">
+        <property name="text">
+         <string>Auto-refetch every</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QSpinBox" name="intervalSpinBox">
+        <property name="suffix">
+         <string>s</string>
+        </property>
+        <property name="minimum">
+         <number>1</number>
+        </property>
+        <property name="maximum">
+         <number>1000000</number>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>40</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item>
+       <widget class="QPushButton" name="fetchPathsButton">
+        <property name="toolTip">
+         <string>Refetches the datapath names and rebuilds the window tabs to reflect them.  Use when the network has been reconfigured, e.g. a bond has been created</string>
+        </property>
+        <property name="text">
+         <string>Refetch Datapath List</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="fetchButton">
+        <property name="text">
+         <string>Refetch</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </item>
+    <item row="2" column="0">
+     <widget class="Line" name="line">
+      <property name="orientation">
+       <enum>Qt::Horizontal</enum>
+      </property>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>800</width>
+     <height>28</height>
+    </rect>
+   </property>
+   <widget class="QMenu" name="menuFile">
+    <property name="title">
+     <string>File</string>
+    </property>
+    <addaction name="actionNew_DB_Window"/>
+    <addaction name="actionNew_Flow_Window"/>
+    <addaction name="actionShow_Log"/>
+    <addaction name="actionPreferences"/>
+    <addaction name="separator"/>
+    <addaction name="actionQuit"/>
+   </widget>
+   <addaction name="menuFile"/>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <action name="actionShow_Log">
+   <property name="text">
+    <string>Show Log</string>
+   </property>
+  </action>
+  <action name="actionNew_DB_Window">
+   <property name="text">
+    <string>New DB Window</string>
+   </property>
+  </action>
+  <action name="actionPreferences">
+   <property name="text">
+    <string>Preferences</string>
+   </property>
+  </action>
+  <action name="actionQuit">
+   <property name="text">
+    <string>Quit</string>
+   </property>
+  </action>
+  <action name="actionNew_Flow_Window">
+   <property name="text">
+    <string>New Flow Window</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/ovsdb/ovsdbmonitor/HostWindow.ui b/ovsdb/ovsdbmonitor/HostWindow.ui
new file mode 100644 (file)
index 0000000..e72ac02
--- /dev/null
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>HostWindow</class>
+ <widget class="QDialog" name="HostWindow">
+  <property name="windowModality">
+   <enum>Qt::WindowModal</enum>
+  </property>
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>Host Properties</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout_2">
+   <item row="0" column="0">
+    <layout class="QGridLayout" name="gridLayout">
+     <item row="0" column="0">
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>Host name or IP</string>
+       </property>
+       <property name="buddy">
+        <cstring>hostAddressEdit</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QLineEdit" name="hostAddressEdit">
+       <property name="minimumSize">
+        <size>
+         <width>256</width>
+         <height>0</height>
+        </size>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="0">
+      <widget class="QLabel" name="label_2">
+       <property name="text">
+        <string>SSH Password</string>
+       </property>
+       <property name="buddy">
+        <cstring>hostPasswordEdit</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QLineEdit" name="hostPasswordEdit">
+       <property name="minimumSize">
+        <size>
+         <width>256</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="echoMode">
+        <enum>QLineEdit::Password</enum>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="0">
+      <widget class="QLabel" name="label_3">
+       <property name="text">
+        <string>Connect target</string>
+       </property>
+       <property name="buddy">
+        <cstring>hostConnectTarget</cstring>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1">
+      <widget class="QLineEdit" name="hostConnectTarget">
+       <property name="minimumSize">
+        <size>
+         <width>256</width>
+         <height>0</height>
+        </size>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item row="1" column="0">
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>hostAddressEdit</tabstop>
+  <tabstop>hostPasswordEdit</tabstop>
+  <tabstop>buttonBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>HostWindow</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>HostWindow</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
diff --git a/ovsdb/ovsdbmonitor/LogWindow.ui b/ovsdb/ovsdbmonitor/LogWindow.ui
new file mode 100644 (file)
index 0000000..f2ac7cd
--- /dev/null
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>LogWindow</class>
+ <widget class="QDialog" name="LogWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>735</width>
+    <height>558</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>OVSDB Monitor Log</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <item>
+      <widget class="QTextBrowser" name="textBrowser"/>
+     </item>
+     <item>
+      <widget class="QDialogButtonBox" name="buttonBox">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="standardButtons">
+        <set>QDialogButtonBox::Close|QDialogButtonBox::Reset</set>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>LogWindow</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>LogWindow</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
diff --git a/ovsdb/ovsdbmonitor/MainWindow.ui b/ovsdb/ovsdbmonitor/MainWindow.ui
new file mode 100644 (file)
index 0000000..cda1943
--- /dev/null
@@ -0,0 +1,233 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>800</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>OVSDB Monitor</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QGridLayout" name="gridLayout">
+    <item row="0" column="0">
+     <layout class="QVBoxLayout" name="verticalLayout">
+      <item>
+       <widget class="QTabWidget" name="tabWidget">
+        <property name="currentIndex">
+         <number>0</number>
+        </property>
+        <widget class="QWidget" name="Bridge">
+         <attribute name="title">
+          <string>Bridge</string>
+         </attribute>
+         <layout class="QGridLayout" name="gridLayout_2">
+          <item row="0" column="0">
+           <widget class="QTableWidget" name="BridgeTable"/>
+          </item>
+         </layout>
+        </widget>
+        <widget class="QWidget" name="Controller">
+         <attribute name="title">
+          <string>Controller</string>
+         </attribute>
+         <layout class="QGridLayout" name="gridLayout_3">
+          <item row="0" column="0">
+           <widget class="QTableWidget" name="ControllerTable"/>
+          </item>
+         </layout>
+        </widget>
+        <widget class="QWidget" name="Interface">
+         <attribute name="title">
+          <string>Interface</string>
+         </attribute>
+         <layout class="QGridLayout" name="gridLayout_4">
+          <item row="0" column="0">
+           <widget class="QTableWidget" name="InterfaceTable"/>
+          </item>
+         </layout>
+        </widget>
+        <widget class="QWidget" name="Mirror">
+         <attribute name="title">
+          <string>Mirror</string>
+         </attribute>
+         <layout class="QGridLayout" name="gridLayout_5">
+          <item row="0" column="0">
+           <widget class="QTableWidget" name="MirrorTable"/>
+          </item>
+         </layout>
+        </widget>
+        <widget class="QWidget" name="NetFlow">
+         <attribute name="title">
+          <string>NetFlow</string>
+         </attribute>
+         <layout class="QGridLayout" name="gridLayout_6">
+          <item row="0" column="0">
+           <widget class="QTableWidget" name="NetFlowTable"/>
+          </item>
+         </layout>
+        </widget>
+        <widget class="QWidget" name="Open_vSwitch">
+         <attribute name="title">
+          <string>Open_vSwitch</string>
+         </attribute>
+         <layout class="QGridLayout" name="gridLayout_7">
+          <item row="0" column="0">
+           <widget class="QTableWidget" name="Open_vSwitchTable"/>
+          </item>
+         </layout>
+        </widget>
+        <widget class="QWidget" name="Port">
+         <attribute name="title">
+          <string>Port</string>
+         </attribute>
+         <layout class="QGridLayout" name="gridLayout_8">
+          <item row="0" column="0">
+           <widget class="QTableWidget" name="PortTable"/>
+          </item>
+         </layout>
+        </widget>
+        <widget class="QWidget" name="sFlow">
+         <attribute name="title">
+          <string>sFlow</string>
+         </attribute>
+         <layout class="QGridLayout" name="gridLayout_9">
+          <item row="0" column="0">
+           <widget class="QTableWidget" name="sFlowTable"/>
+          </item>
+         </layout>
+        </widget>
+        <widget class="QWidget" name="SSL">
+         <attribute name="title">
+          <string>SSL</string>
+         </attribute>
+         <layout class="QGridLayout" name="gridLayout_10">
+          <item row="0" column="0">
+           <widget class="QTableWidget" name="SSLTable"/>
+          </item>
+         </layout>
+        </widget>
+       </widget>
+      </item>
+      <item>
+       <layout class="QHBoxLayout" name="horizontalLayout">
+        <item>
+         <widget class="QLabel" name="hostLabel">
+          <property name="text">
+           <string>Host</string>
+          </property>
+          <property name="buddy">
+           <cstring>hostComboBox</cstring>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QComboBox" name="hostComboBox">
+          <property name="sizeAdjustPolicy">
+           <enum>QComboBox::AdjustToContents</enum>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QCheckBox" name="intervalCheckBox">
+          <property name="text">
+           <string>Auto-refetch every</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QSpinBox" name="intervalSpinBox">
+          <property name="suffix">
+           <string>s</string>
+          </property>
+          <property name="minimum">
+           <number>1</number>
+          </property>
+          <property name="maximum">
+           <number>1000000</number>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <spacer name="horizontalSpacer">
+          <property name="orientation">
+           <enum>Qt::Horizontal</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>40</width>
+            <height>20</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+        <item>
+         <widget class="QPushButton" name="fetchButton">
+          <property name="text">
+           <string>Refetch</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>800</width>
+     <height>28</height>
+    </rect>
+   </property>
+   <widget class="QMenu" name="menuFile">
+    <property name="title">
+     <string>File</string>
+    </property>
+    <addaction name="actionNew_DB_Window"/>
+    <addaction name="actionNew_Flow_Window"/>
+    <addaction name="actionShow_Log"/>
+    <addaction name="actionPreferences"/>
+    <addaction name="separator"/>
+    <addaction name="actionQuit"/>
+   </widget>
+   <addaction name="menuFile"/>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <action name="actionShow_Log">
+   <property name="text">
+    <string>Show Log</string>
+   </property>
+  </action>
+  <action name="actionNew_DB_Window">
+   <property name="text">
+    <string>New DB Window</string>
+   </property>
+  </action>
+  <action name="actionPreferences">
+   <property name="text">
+    <string>Preferences</string>
+   </property>
+  </action>
+  <action name="actionQuit">
+   <property name="text">
+    <string>Quit</string>
+   </property>
+  </action>
+  <action name="actionNew_Flow_Window">
+   <property name="text">
+    <string>New Flow Window</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/ovsdb/ovsdbmonitor/OVEApp.py b/ovsdb/ovsdbmonitor/OVEApp.py
new file mode 100644 (file)
index 0000000..56b6b17
--- /dev/null
@@ -0,0 +1,105 @@
+# Copyright (c) 2010 Citrix 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.
+#
+
+from OVEStandard import *
+from OVEConfig import *
+from OVEFetch import *
+
+from OVEConfigWindow import *
+from OVEFlowWindow import *
+from OVELogWindow import *
+from OVEMainWindow import *
+
+class OVEApp:
+    def __init__(self):
+        self.app = globalApp
+        self.app.setOrganizationName("Citrix_Systems_Inc")
+        self.app.setOrganizationDomain("citrix.com")
+        self.app.setApplicationName("ovsdbmonitor")
+        self.mainWindows = []
+        self.flowWindows = []
+        self.configWindow = None
+
+    def enter(self):
+        if len(OVEConfig.Inst().hosts) < 1:
+            self.showConfig(True)
+            QtGui.QMessageBox.information(
+                    None, "OVSDB Monitor",
+                    "This application browses openvswitch databases on remote hosts.  Please add one or more openvswitch hosts to continue")
+        self.loadMainWindows()
+        self.loadFlowWindows()
+        if len(self.mainWindows) == 0 and len(self.flowWindows) == 0:
+            self.newMainWindow()
+        self.newLogWindow()
+        # Reactor must be started after the event loop is running, so use a zero timeout
+        QtCore.QTimer.singleShot(0, OVEFetch.startReactor)
+        OVELog("Application started")
+        retCode = self.app.exec_()
+        index = 0
+        for mainWindow in self.mainWindows:
+            if mainWindow.isVisible():
+                mainWindow.saveSettings(index)
+                index += 1 # Indent intentional
+        OVEMainWindow.terminateSettings(index)
+        index = 0
+        for flowWindow in self.flowWindows:
+            if flowWindow.isVisible():
+                flowWindow.saveSettings(index)
+                index += 1 # Indent intentional            
+        OVEFlowWindow.terminateSettings(index)
+        self.logWindow.saveSettings()
+    
+    def quit(self):
+        self.app.quit()
+    
+    def showLog(self, value):
+        if value:
+            self.logWindow.hide()
+            self.logWindow.show()
+        else:
+            self.logWindow.hide()
+
+    def showConfig(self, value):
+        if value:
+            del self.configWindow
+            self.configWindow = OVEConfigWindow(self)
+            self.configWindow.show()
+        else:
+            self.configWindow.hide()
+
+    def newMainWindow(self, loadIndex = None):
+        self.mainWindows.append(OVEMainWindow(self, loadIndex))
+        self.mainWindows[-1].show()
+
+    def newFlowWindow(self, loadIndex = None):
+        self.flowWindows.append(OVEFlowWindow(self, loadIndex))
+        self.flowWindows[-1].show()
+
+    def newLogWindow(self):
+        self.logWindow = OVELogWindow(self)
+
+    def loadMainWindows(self):
+        for loadIndex in range(0, 100):
+            if OVEMainWindow.isLoadable(loadIndex):
+                self.newMainWindow(loadIndex)
+            else:
+                break
+
+    def loadFlowWindows(self):
+        for loadIndex in range(0, 100):
+            if OVEFlowWindow.isLoadable(loadIndex):
+                self.newFlowWindow(loadIndex)
+            else:
+                break
diff --git a/ovsdb/ovsdbmonitor/OVECommonWindow.py b/ovsdb/ovsdbmonitor/OVECommonWindow.py
new file mode 100644 (file)
index 0000000..5c014b7
--- /dev/null
@@ -0,0 +1,221 @@
+# Copyright (c) 2010 Citrix 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.
+
+from OVEStandard import *
+from OVEConfig import *
+from OVEFetch import *
+from OVELogger import *
+from OVEUtil import *
+
+from Ui_MainWindow import *
+
+class OVECommonWindow:
+    def __init__(self, app, loadIndex = None):
+        self.app = app
+        self.intervalTimerId = None        
+        self.hostUuid = ''
+        self.intervalChecked = True
+        self.intervalSeconds = 5
+        self.fetchSkip = 0
+        self.currentRef = self.BASE_REF
+                
+        self.ui.setupUi(self)
+        
+        if loadIndex is not None:
+            self.loadSettings(loadIndex)
+        
+        self.connect(self.ui.actionNew_DB_Window, QtCore.SIGNAL("triggered()"), self.xon_actionNew_DB_Window_triggered)
+        self.connect(self.ui.actionNew_Flow_Window, QtCore.SIGNAL("triggered()"), self.xon_actionNew_Flow_Window_triggered)
+        self.connect(self.ui.actionShow_Log, QtCore.SIGNAL("triggered()"), self.xon_actionShow_Log_triggered)
+        self.connect(self.ui.actionPreferences, QtCore.SIGNAL("triggered()"), self.xon_actionPreferences_triggered)
+        self.connect(self.ui.actionQuit, QtCore.SIGNAL("triggered()"), self.xon_actionQuit_triggered)
+        self.connect(self.ui.fetchButton, QtCore.SIGNAL("clicked()"), self.xon_fetchButton_clicked)
+        self.connect(self.ui.tabWidget, QtCore.SIGNAL("currentChanged(int)"), self.xon_tabWidget_currentChanged)
+        self.connect(self.ui.hostComboBox, QtCore.SIGNAL("currentIndexChanged(int)"), self.xon_hostComboBox_currentIndexChanged)
+        self.connect(self.ui.intervalCheckBox, QtCore.SIGNAL("stateChanged(int)"), self.xon_intervalCheckBox_stateChanged)
+        self.connect(self.ui.intervalSpinBox, QtCore.SIGNAL("valueChanged(int)"), self.xon_intervalSpinBox_valueChanged)
+        self.connect(OVEConfig.Inst(), QtCore.SIGNAL("configUpdated()"), self.xon_configUpdated)
+        
+        self.updateHosts()
+        self.updateInterval()
+        self.updateIntervalState()
+        self.updateTable()
+        
+    def xon_actionNew_DB_Window_triggered(self):
+        self.app.newMainWindow()
+    def xon_actionNew_Flow_Window_triggered(self):
+        self.app.newFlowWindow()
+
+    def xon_actionShow_Log_triggered(self):
+        self.app.showLog(True)
+
+    def xon_actionPreferences_triggered(self):
+        self.app.showConfig(True)
+
+    def xon_actionQuit_triggered(self):
+        self.app.quit()
+    
+    def xon_tabWidget_currentChanged(self, value):
+        self.updateTable()
+        
+    def xon_fetchButton_clicked(self):
+        self.updateTable()
+    
+    def xon_configUpdated(self):
+        self.updateHosts()
+    
+    def xon_hostComboBox_currentIndexChanged(self, index):
+        if (index >= 0):
+            itemData = self.ui.hostComboBox.itemData(index)
+            self.hostUuid = str(itemData.toString())
+            self.deleteCurrentTable()
+            self.updateTable()
+    
+    def xon_intervalCheckBox_stateChanged(self, state):
+        self.intervalChecked = (state == Qt.Checked)
+        self.updateIntervalState()
+    
+    def xon_intervalSpinBox_valueChanged(self, value):
+        self.intervalSeconds = value
+        self.updateIntervalState()
+    
+    def updateIntervalState(self):
+        if self.intervalTimerId is not None:
+            self.killTimer(self.intervalTimerId)
+        if self.intervalChecked:
+            self.intervalTimerId = self.startTimer(1000*self.intervalSeconds)
+    
+    def updateHosts(self):
+        currentHostUuid = self.hostUuid # self.hostUuid will change due to currentIndexChanged events as we rebuild the combo box
+        self.hostUuid = ''
+        self.ui.hostComboBox.clear()
+        for i, host in enumerate(OVEConfig.Inst().hosts):
+            self.ui.hostComboBox.addItem(host['address'], QVariant(host['uuid']))
+            if host['uuid'] == currentHostUuid:
+                # This is the currently selected host
+                self.ui.hostComboBox.setCurrentIndex(i)
+        if len(OVEConfig.Inst().hosts) == 0:
+            self.ui.hostComboBox.addItem('(No hosts configured)', QVariant(''))
+            
+    def updateInterval(self):
+        self.ui.intervalCheckBox.setChecked(self.intervalChecked)
+        self.ui.intervalSpinBox.setValue(self.intervalSeconds)
+    def handleFetchEvent(self, ref, values):
+        OVELog('Unhandled FetchEvent')
+    def handleFetchFailEvent(self, ref, message):
+        OVELog('Unhandled FetchFailEvent')
+    def setFetchSkip(self):
+        # Call before sending a request via OVEFetch
+        self.fetchSkip = 6
+    def timerEvent(self, event):
+        if event.timerId() == self.intervalTimerId:
+            if self.fetchSkip > 0:
+                self.statusBar().showMessage('Fetch stalled... resend in '+str(self.fetchSkip*self.intervalSeconds)+'s') 
+                self.fetchSkip -= 1
+                if self.fetchSkip == 0:
+                    # Stall has timed out. The connection might have hung so reset.  Seems to happen with PySide only
+                    OVEFetch.Inst(self.hostUuid).resetTransport()
+            else:
+                self.updateTable()
+        else:
+            QtGui.QMainWindow.timerEvent(self, event)
+    def customEvent(self, event):
+        if event.type() == OVEFetchEvent.TYPE:
+            if isinstance(event, OVEFetchEvent):
+                # The right way to get data
+                ref = event.ref
+                values = event.data
+            else:
+                # Workaround for PySide issue
+                ref = OVEFetch.Inst(self.hostUuid).snoopRef(self)
+                values = OVEFetch.Inst(self.hostUuid).snoopValues(self)
+            try:
+                if ref == self.currentRef:
+                    self.fetchSkip = 0
+                    self.currentRef += 1 # PySide workaround
+                    self.handleFetchEvent(ref, values)
+                else:
+                    # If refs don't match this event relates to a request before the current one.  We've moved
+                    # on since then, e.g. changed the table we've viewing, so ignore it
+                    if OVEConfig.Inst().logTraffic:
+                        OVELog('FetchEvent ref mismatch '+str(ref)+' != '+str(self.currentRef))
+            except Exception, e:
+                OVELog("Error during data handling: "+str(e))
+
+        elif event.type() == OVEFetchFailEvent.TYPE:
+            if isinstance(event, OVEFetchFailEvent):
+                # The right way to get data
+                ref = event.ref
+                message = event.message
+            else:
+                # Workaround for PySide issue
+                ref = OVEFetch.Inst(self.hostUuid).snoopRef(self)
+                message = OVEFetch.Inst(self.hostUuid).snoopMessage(self)
+            if message is not None:
+                OVELog(message)
+            if ref == self.currentRef:
+                self.fetchSkip = 0
+                self.currentRef += 1 # PySide workaround
+                self.handleFetchFailEvent(ref, message)
+            else:
+                if OVEConfig.Inst().logTraffic:
+                    OVELog('FetchFailEvent ref mismatch '+str(ref)+' != '+str(self.currentRef))
+    def deleteCurrentTable(self):
+        pass
+    def saveSettings(self, index):
+        key = self.LOAD_KEY+str(index)
+        settings = QtCore.QSettings()
+        settings.setValue(key+"/loadable", QVariant(True))
+        settings.setValue(key+"/pos", QVariant(self.pos()))
+        settings.setValue(key+"/size", QVariant(self.size()))
+        settings.setValue(key+"/hostUuid", QVariant(self.hostUuid))
+        settings.setValue(key+"/intervalChecked", QVariant(self.intervalChecked))
+        settings.setValue(key+"/intervalSeconds", QVariant(self.intervalSeconds))
+
+        return settings, key
+    
+    def loadSettings(self, index):
+        key = self.LOAD_KEY+str(index)
+        settings = QtCore.QSettings()
+        pos = settings.value(key+"/pos", QVariant(QtCore.QPoint(200, 200))).toPoint()
+        size = settings.value(key+"/size", QVariant(QtCore.QSize(400, 400))).toSize();
+
+        self.hostUuid = str(settings.value(key+"/hostUuid", QVariant('Unloaded')).toString())
+        self.intervalChecked = settings.value(key+"/intervalChecked", QVariant(True)).toBool()
+        self.intervalSeconds = settings.value(key+"/intervalSeconds", QVariant(5)).toInt()[0]
+        self.resize(size)
+        self.move(pos)
+        return settings, key
+
+    @classmethod
+    def terminateSettings(self, index):
+        key = self.LOAD_KEY+str(index)
+        settings = QtCore.QSettings()
+        settings.setValue(key+"/loadable", QVariant(False))
+        settings.sync()
+        
+    @classmethod
+    def isLoadable(cls, index):
+        key = cls.LOAD_KEY+str(index)
+        settings = QtCore.QSettings()
+        return settings.value(key+"/loadable", QVariant(False)).toBool()
+        
diff --git a/ovsdb/ovsdbmonitor/OVEConfig.py b/ovsdb/ovsdbmonitor/OVEConfig.py
new file mode 100644 (file)
index 0000000..7cc18eb
--- /dev/null
@@ -0,0 +1,85 @@
+# Copyright (c) 2010 Citrix 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.
+
+from OVEStandard import *
+from OVELogger import *
+
+class OVEConfig(QtCore.QObject):
+    instance = None
+    def __init__(self):
+        QtCore.QObject.__init__(self)
+        self.hosts = []
+        self.logTraffic = True
+        self.truncateUuids = True
+        self.ssgList = []
+        
+    @classmethod
+    def Inst(cls):
+        if cls.instance is None:
+            cls.instance = OVEConfig()
+            cls.instance.loadConfig()
+        return cls.instance
+
+    def hostFromUuid(self, uuid):
+        for host in self.hosts:
+            if host['uuid'] == uuid:
+                return host
+        OVELog("+++ Couldn't find host '"+str(uuid)+"' in "+str([x['uuid'] for x in self.hosts]))
+        return None
+
+    def saveConfig(self):
+        settings = QtCore.QSettings()
+        settings.setValue('config/hosts', QVariant(json.JsonWriter().write(self.hosts)))
+        settings.setValue('config/logTraffic', QVariant(self.logTraffic))
+        settings.setValue('config/truncateUuids', QVariant(self.truncateUuids))
+        settings.setValue('config/ssgList', QVariant(json.JsonWriter().write(self.ssgList)))
+        settings.sync()
+        self.emitUpdated()
+        
+    def loadConfig(self):
+        settings = QtCore.QSettings()
+        jsonText = unicode(settings.value('config/hosts', QVariant('[]')).toString())
+        self.hosts = json.JsonReader().read(str(jsonText))
+        self.logTraffic = settings.value('config/logTraffic', QVariant(False)).toBool()
+        self.truncateUuids = settings.value('config/truncateUuids', QVariant(False)).toBool()
+        jsonText = unicode(settings.value('config/ssgList', QVariant('[]')).toString())
+        self.ssgList = json.JsonReader().read(str(jsonText))
+        if len(self.ssgList) == 0:
+            self.ssgList = [
+                r'in_port0000',
+                r'in_port0001',
+                r'in_port0002',
+                r'in_port0003',
+                r'vlan65535',
+                r'type0800',
+                r'type0806',
+                r'proto0',
+                r'proto6',
+                r'proto17',
+                r'ff:ff:ff:ff:ff:ff',
+                r'!ff:ff:ff:ff:ff:ff',
+                r'0\.0\.0\.0',
+                r'!0\.0\.0\.0',
+                r'255\.255\.255\.255',
+                r'!255\.255\.255\.255',
+                r'never',
+                r'drop',
+                r'!never',
+                r'!drop',
+                r'(never|drop)',
+                r'!(never|drop)'
+            ]
+        
+    def emitUpdated(self):
+        self.emit(QtCore.SIGNAL("configUpdated()"))
diff --git a/ovsdb/ovsdbmonitor/OVEConfigWindow.py b/ovsdb/ovsdbmonitor/OVEConfigWindow.py
new file mode 100644 (file)
index 0000000..b5b8d70
--- /dev/null
@@ -0,0 +1,127 @@
+# Copyright (c) 2010 Citrix 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.
+
+from OVEStandard import *
+from OVEConfig import *
+from OVELogger import *
+from Ui_ConfigWindow import *
+
+from OVEHostWindow import *
+
+class OVEConfigWindow(QtGui.QDialog):
+    def __init__(self, app):
+        QtGui.QDialog.__init__(self)
+        self.app = app
+        self.ui = Ui_ConfigWindow()
+        self.ui.setupUi(self)
+
+        self.connect(self.ui.hostAddButton, QtCore.SIGNAL("clicked()"), self.xon_hostAddButton_clicked)
+        self.connect(self.ui.hostEditButton, QtCore.SIGNAL("clicked()"), self.xon_hostEditButton_clicked)
+        self.connect(self.ui.hostDeleteButton, QtCore.SIGNAL("clicked()"), self.xon_hostDeleteButton_clicked)
+        self.connect(self.ui.buttonBox, QtCore.SIGNAL("clicked(QAbstractButton *)"), self.xon_actionButton_Box_clicked)
+        self.connect(self.ui.hostList, QtCore.SIGNAL("currentItemChanged(QListWidgetItem *,  QListWidgetItem *)"), self.xon_hostList_currentItemChanged)
+        self.connect(self.ui.logTrafficCheckBox, QtCore.SIGNAL("stateChanged(int)"), self.xon_logTrafficCheckBox_stateChanged)
+        self.connect(self.ui.truncateUuidsCheckBox, QtCore.SIGNAL("stateChanged(int)"), self.xon_truncateUuidsCheckBox_stateChanged)
+        self.readConfig()
+        self.updateWidgets()
+    
+    def handleHostWindowRecord(self, record, isEdit):
+        if record['accepted'] and record['address'].strip() != '':
+            currentRow = self.ui.hostList.currentRow()
+            if isEdit:
+                self.configHosts[currentRow] = record
+            else:
+                self.configHosts.append(record)
+
+        self.updateWidgets()
+
+    def xon_hostAddButton_clicked(self):
+        hostWindow = OVEHostWindow(self)
+        hostWindow.exec_()
+        self.handleHostWindowRecord(hostWindow.record(), False)
+
+    def xon_hostEditButton_clicked(self):
+        if self.ui.hostList.currentItem() is None:
+            pass # OVELog('No item to edit')
+        else:
+            currentRow = self.ui.hostList.currentRow()
+            hostWindow = OVEHostWindow(self, self.configHosts[currentRow])
+            hostWindow.exec_()
+            self.handleHostWindowRecord(hostWindow.record(), True)
+
+    def xon_hostDeleteButton_clicked(self):
+        if self.ui.hostList.currentItem() is not None:
+            currentRow = self.ui.hostList.currentRow()
+            del self.configHosts[currentRow]
+            self.updateWidgets()
+
+    def xon_actionButton_Box_clicked(self, button):
+        role = self.ui.buttonBox.buttonRole(button)
+        if role == QtGui.QDialogButtonBox.AcceptRole:
+            self.writeConfig()
+            self.close()
+        elif role == QtGui.QDialogButtonBox.ApplyRole:
+            self.writeConfig()
+        elif role == QtGui.QDialogButtonBox.RejectRole:
+            if self.configChanged():
+                self.close()
+            else:
+                ret = QtGui.QMessageBox.warning(
+                    self, "OVSDB Monitor",
+                    "Changes not applied. Discard?",
+                    QtGui.QMessageBox.Discard | QtGui.QMessageBox.Cancel | QtGui.QMessageBox.Apply,
+                    QtGui.QMessageBox.Discard)
+                
+                if ret == QtGui.QMessageBox.Apply:
+                    self.writeConfig()
+                if ret != QtGui.QMessageBox.Cancel:
+                    self.close()
+
+    def xon_hostList_currentItemChanged(self, current, previous):
+        editable = (current is not None)
+        self.ui.hostEditButton.setEnabled(editable)
+        self.ui.hostDeleteButton.setEnabled(editable)
+
+    def xon_logTrafficCheckBox_stateChanged(self, value):
+        self.configLogTraffic = (value == Qt.Checked)
+    
+    def xon_truncateUuidsCheckBox_stateChanged(self, value):
+        self.configTruncateUuids = (value == Qt.Checked)
+    
+    def updateWidgets(self):
+        self.ui.hostList.clear()
+        for host in self.configHosts:
+            self.ui.hostList.addItem(host['address'])
+        self.ui.logTrafficCheckBox.setChecked(self.configLogTraffic)
+        self.ui.truncateUuidsCheckBox.setChecked(self.configTruncateUuids)
+
+    def configChanged(self):
+        return (
+            (self.configHosts == OVEConfig.Inst().hosts) and
+            (self.configLogTraffic == (OVEConfig.Inst().logTraffic))and
+            (self.configTruncateUuids == (OVEConfig.Inst().truncateUuids))
+        )
+        
+    def readConfig(self):
+        self.configHosts = deepcopy(OVEConfig.Inst().hosts)
+        self.configLogTraffic = OVEConfig.Inst().logTraffic
+        self.configTruncateUuids = OVEConfig.Inst().truncateUuids
+        
+    def writeConfig(self):
+        OVEConfig.Inst().hosts = deepcopy(self.configHosts)
+        OVEConfig.Inst().logTraffic = self.configLogTraffic
+        OVEConfig.Inst().truncateUuids = self.configTruncateUuids
+        OVEConfig.Inst().saveConfig()
+
+    
diff --git a/ovsdb/ovsdbmonitor/OVEFetch.py b/ovsdb/ovsdbmonitor/OVEFetch.py
new file mode 100644 (file)
index 0000000..9dd1118
--- /dev/null
@@ -0,0 +1,386 @@
+# Copyright (c) 2010 Citrix 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.
+
+from OVEStandard import *
+from OVEConfig import *
+from OVELogger import *
+
+# This sequence installs the qt4reactor before twisted gets a chance to install its reactor
+import qt4reactor
+globalApp = QtGui.QApplication([])
+qt4reactor.install()
+
+try:
+    from twisted.conch.ssh import transport, userauth, connection, common, keys, channel
+    from twisted.internet import defer, protocol, reactor
+    from twisted.application import reactors
+except Exception, e:
+    print('+++ Python Twisted Conch module is required\n')
+    raise
+
+class OVEFetchUserAuth(userauth.SSHUserAuthClient):
+    def __init__(self, fetch, *params):
+        userauth.SSHUserAuthClient.__init__(self, *params)
+        self.fetch = fetch
+        self.authFails = 0
+    
+    def getPassword(self):
+        return defer.succeed(self.fetch.config()['password'])
+
+    def ssh_USERAUTH_FAILURE(self, packet):
+        if self.authFails > 0: # We normally get one so ignore.  Real failures send these repeatedly
+            OVELog('Authentication failure for '+self.fetch.config()['address'])
+        self.authFails += 1
+        userauth.SSHUserAuthClient.ssh_USERAUTH_FAILURE(self, packet)
+
+class OVEFetchConnection(connection.SSHConnection, QtCore.QObject):
+    def __init__(self, fetch, *params):
+        connection.SSHConnection.__init__(self, *params)
+        QtCore.QObject.__init__(self)
+        self.fetch = fetch
+        self._channel = None
+        self._oldChannels = []
+        
+    def serviceStarted(self):
+        self.emit(QtCore.SIGNAL('connectionService(QObject)'), self)
+
+    def serviceStopped(self):
+        self.emit(QtCore.SIGNAL('connectionService(QObject)'), None)
+
+    def execCommand(self, requester, ref, command, commandType):
+        if self._channel is not None:
+            # Don't delete old channels immediately in case they're e.g. going to time out with a failure
+            self._oldChannels.append(self._channel)
+            if len(self._oldChannels) > 90:
+                # For 30 second timeouts at 1 second refresh interval and three windows open on a single host, need 90 channels
+                del self._oldChannels[1]
+        self._channel = OVECommandChannel(self.fetch, requester, ref, command, commandType, 2**16, 2**15, self)
+        self.openChannel(self._channel)
+
+    def connectionLost(self, reason):
+        if self._channel is not None:
+            self._channel.connectionLost(reason)
+
+class OVEFetchTransport(transport.SSHClientTransport, QtCore.QObject):
+    def __init__(self, fetch, *params):
+        # There is no __init__ method for this class
+        # transport.SSHClientTransport.__init__(self, *params)
+        
+        QtCore.QObject.__init__(self)
+        self.fetch = fetch
+        self._connection = None
+        self.connect(self, QtCore.SIGNAL('channelFailure(QObject, int, QString, QString, QString)'), self.fetch.xon_channelFailure)
+        
+    def verifyHostKey(self, hostKey, fingerprint):
+        return defer.succeed(1)
+
+    def connectionSecure(self):
+        self._connection = OVEFetchConnection(self.fetch)
+        QtCore.QObject.connect(self._connection, QtCore.SIGNAL('connectionService(QObject)'), self.fetch.xon_connectionService)
+        self.requestService(
+            OVEFetchUserAuth(self.fetch, self.fetch.config().get('username', 'root'),
+                self._connection))
+
+    def connectionLost(self, reason):
+        if self._connection is not None:
+            self._connection.connectionLost(reason)
+
+class OVEFetchWrapper:
+    def __init__(self, contents):
+        self.contents = contents
+
+class OVECommandChannel(channel.SSHChannel, QtCore.QObject):
+    name = 'session'
+    MSEC_TIMEOUT=10000
+    STATUS_CONNECTION_LOST = 100001
+    STATUS_TIMEOUT = 100002
+    END_MARKER='END-MARKER'
+    END_MARKER_RE=re.compile(r'^END-MARKER$', re.MULTILINE)
+    
+    def __init__(self, fetch, requester, ref, command, commandType, *params):
+        channel.SSHChannel.__init__(self, *params)
+        QtCore.QObject.__init__(self)        
+        self.fetch = fetch
+        self.requester = requester
+        self.ref = ref
+        self.command = command
+        self.commandType= commandType
+        self._data = ''
+        self._extData = ''
+        self._jsonValues = None
+        self._timerId = None
+        self._status = None
+        self.connect(self, QtCore.SIGNAL('channelData(QObject, int, QString)'), self.fetch.xon_channelData)
+        self.connect(self, QtCore.SIGNAL('channelExtData(QObject, int, QString)'), self.fetch.xon_channelExtData)
+        self.connect(self, QtCore.SIGNAL('channelSuccess(QObject, int, QString, QString, QVariant)'), self.fetch.xon_channelSuccess)
+        self.connect(self, QtCore.SIGNAL('channelFailure(QObject, int, QString, QString, QString)'), self.fetch.xon_channelFailure)
+        
+    def openFailed(self, reason):
+        if self._timerId is not None:
+            self.killTimer(self._timerId)
+        self.emit(QtCore.SIGNAL('channelFailure(QObject, int, QString, QString, QString)'), self.requester, self.ref,
+            'Open failed:'+str(reason), '', '')
+
+    def channelOpen(self, ignoredData):
+        try:
+            nsCommand = common.NS(str(self.command))
+            self._timerId = self.startTimer(self.MSEC_TIMEOUT)
+            self.conn.sendRequest(self, 'exec', nsCommand, wantReply=1)
+        except Exception, e:
+            self.emit(QtCore.SIGNAL('channelFailure(QObject, int, QString, QString, QString)'), self.requester, self.ref,
+                'Open failed:'+str(e), self._data, self._extData)
+            
+    def dataReceived(self, data):
+        self._data += data
+        if OVEConfig.Inst().logTraffic:
+            self.emit(QtCore.SIGNAL('channelData(QObject, int, QString)'), self.requester, self.ref, data)
+        self.testIfDone()
+        
+    def extDataReceived(self, extData):
+        self._extData += extData
+        if OVEConfig.Inst().logTraffic:
+            self.emit(QtCore.SIGNAL('channelExtData(QObject, int, QString)'), self.requester, self.ref, extData)
+
+    def request_exit_status(self, data):
+        # We can get the exit status before the data, so delay calling sendResult until we get both
+        self._status = struct.unpack('>L', data)[0]
+        self.testIfDone()
+        
+    def testIfDone(self):
+        if self._status is not None:
+            if self._status != 0:
+                self.sendResult() # Failed, so send what we have
+            elif len(self._data) > 0:
+                # Status == success and we have some data
+                if self.commandType == 'JSON':
+                    try:
+                        # Decode the JSON data, to confirm that we have all of the data
+                        self._jsonValues = json.read(str(self._data)) # FIXME: Should handle unicode
+                        self.sendResult()
+                    except:
+                        pass # Wait for more data
+                elif self.commandType == 'framed':
+                    match = self.END_MARKER_RE.search(self._data)
+                    if match:
+                        self._data = self._data[:match.start()] # Remove end marker
+                        self.sendResult()
+                else:
+                    OVELog('Bad command type')
+
+    def sendResult(self):
+        if self._timerId is not None:
+            self.killTimer(self._timerId)
+        if self.commandType == 'JSON' and self._status == 0 and self._jsonValues is not None:
+            self.emit(QtCore.SIGNAL('channelSuccess(QObject, int, QString, QString, QVariant)'), self.requester, self.ref, self._data, self._extData, QVariant(OVEFetchWrapper(self._jsonValues)))
+        elif self.commandType != 'JSON' and self._status == 0:
+            self.emit(QtCore.SIGNAL('channelSuccess(QObject, int, QString, QString, QVariant)'), self.requester, self.ref, self._data, self._extData, QVariant(None))
+        else:
+            self.emit(QtCore.SIGNAL('channelFailure(QObject, int, QString, QString, QString)'), self.requester, self.ref, 'Remote command failed (rc='+str(self._status)+')', self._data, self._extData)
+        if self._status != self.STATUS_CONNECTION_LOST:
+            try:
+                self.loseConnection()
+            except Exception, e:
+                OVELog('OVECommandChannel.sendResult loseConnection error: '+str(e))
+
+    def connectionLost(self, reason):
+        self._extData += '+++ Connection lost'
+        self._status = self.STATUS_CONNECTION_LOST
+        self.sendResult()
+
+    def timerEvent(self, event):
+        if event.timerId() == self._timerId:
+            self._extData += '+++ Timeout'
+            self._status = self.STATUS_TIMEOUT
+            self.sendResult()
+        else:
+            QtCore.QObject.timerEvent(self, event)
+
+class OVEFetchEvent(QtCore.QEvent):
+    TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
+    def __init__(self, ref, data):
+        QtCore.QEvent.__init__(self, self.TYPE)
+        self.ref = ref
+        self.data = data
+
+class OVEFetchFailEvent(QtCore.QEvent):
+    TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType())
+    def __init__(self, ref, message):
+        QtCore.QEvent.__init__(self, self.TYPE)
+        self.ref = ref
+        self.message = str(message)
+
+class OVEFetch(QtCore.QObject):
+    instances = {}
+    SEC_TIMEOUT = 10.0
+    
+    def __init__(self, uuid):
+        QtCore.QObject.__init__(self)
+        self._hostUuid = uuid
+        self._config = None
+        self._transport = None
+        self._connection = None
+        self._commandQueue = []
+        self._timerRef = 0
+        self.refs = {}
+        self.messages = {}
+        self.values = {}
+        self.connect(OVEConfig.Inst(), QtCore.SIGNAL("configUpdated()"), self.xon_configUpdated)
+        
+    @classmethod
+    def Inst(cls, uuid):
+        if uuid not in cls.instances:
+            cls.instances[uuid] = OVEFetch(uuid)
+        return cls.instances[uuid]
+
+    @classmethod
+    def startReactor(cls):
+        reactor.runReturn()
+
+    def xon_configUpdated(self):
+        self._config = None
+        self.resetTransport()
+        
+    def xon_connectionService(self, connection):
+        self._connection = connection
+        if self._connection is not None:
+            OVELog('SSH connection to '+self.config()['address'] +' established')
+            for command in self._commandQueue:
+                # OVELog('Unqueueing '+str(command))
+                self.execCommand2(*command)
+            self._commandQueue = []
+
+    def xon_channelData(self, requester, ref, data):
+        if OVEConfig.Inst().logTraffic:
+            OVELog('Channel data received: '+str(data))
+
+    def xon_channelExtData(self, requester, ref, data):
+        if OVEConfig.Inst().logTraffic:
+            OVELog('+++ Channel extData (stderr) received: '+str(data))
+
+    def xon_channelFailure(self, requester, ref, message, data, extData):
+        if OVEConfig.Inst().logTraffic:
+            OVELog('+++ Channel failure: '+str(message))
+            OVELog("Closing SSH session due to failure")
+
+        errMessage = message
+        if len(data) > 0:
+            errMessage += '\n+++ Failed command output: '+data
+        if len(extData) > 0:
+            errMessage += '\n+++ Failed command output (stderr): '+extData
+
+        self.refs[requester] = ref # For PySide workaround
+        self.messages[requester] = errMessage # For PySide workaround
+        event = OVEFetchFailEvent(ref, errMessage)
+        QtCore.QCoreApplication.postEvent(requester, event)
+        self.resetTransport()
+        
+    def xon_channelSuccess(self, requester, ref, data, extData, jsonValueVariant):
+        jsonValues = jsonValueVariant.toPyObject()
+        if OVEConfig.Inst().logTraffic:
+            OVELog('--- Channel success')
+        try:
+            if jsonValues is not None:
+                values = jsonValues.contents
+            else:
+                values = str(data)
+
+            self.refs[requester] = ref # For PySide workaround
+            self.values[requester] = values # For PySide workaround
+            event = OVEFetchEvent(ref, values)
+            QtCore.QCoreApplication.postEvent(requester, event)
+        except Exception, e:
+            message = ('+++ Failed to decode JSON reply: '+str(e))
+            if len(data) > 0: message += "\n++++++ Data (stdout): "+str(data)
+            if len(extData) > 0: message += '\n++++++ Error (stderr): '+str(extData)
+            self.refs[requester] = ref # For PySide workaround
+            self.messages[requester] = message # For PySide workaround
+            event = OVEFetchFailEvent(ref, message)
+            QtCore.QCoreApplication.postEvent(requester, event)
+
+    # Use for workaround only
+    def snoopRef(self, requester):
+        return self.refs.get(requester, None)
+
+    # Use for workaround only
+    def snoopValues(self, requester):
+        return self.values.get(requester, None)
+
+    # Use for workaround only
+    def snoopMessage(self, requester):
+        return self.messages.get(requester, None)
+
+    def config(self):
+        if self._config is None:
+            self._config = OVEConfig.Inst().hostFromUuid(self._hostUuid)
+
+        return self._config
+    
+    def resetTransport(self):
+        if OVEConfig.Inst().logTraffic:
+            OVELog('Transport reset for '+self.config()['address'])
+        del self._connection
+        del self._transport
+        self._connection = None
+        self._transport = None
+        
+    def transportErrback(self, failure, requester, ref, address):
+        self._timerRef += 1 # Prevent timeout handling
+        self.resetTransport()
+        message = 'Failure connecting to '+address+': '+failure.getErrorMessage()
+        self.refs[requester] = ref # For PySide workaround
+        self.messages[requester] = message # For PySide workaround
+        event = OVEFetchFailEvent(ref, message)
+        QtCore.QCoreApplication.postEvent(requester, event)        
+        
+    def transportTimeout(self, timerRef, requester, ref, address):
+        if self._timerRef == timerRef and self._transport is not None and self._connection is None:
+            message = 'Connection attempt to ' +address+' timed out'
+            self.refs[requester] = ref # For PySide workaround
+            self.messages[requester] = message # For PySide workaround
+            event = OVEFetchFailEvent(ref, message)
+            QtCore.QCoreApplication.postEvent(requester, event)        
+            self.resetTransport()
+
+    def execCommand(self, requester, ref, command, commandType):
+        if OVEConfig.Inst().logTraffic:
+            hostName = (self.config() or {}).get('address', '<Address not set>')
+            OVELog(str(QtCore.QTime.currentTime().toString())+' '+hostName+': Executing '+command)
+        if self._transport is None:
+            self._connection = None
+            self._commandQueue.append((requester, ref, command, commandType))
+            config = self.config()
+            creator = protocol.ClientCreator(reactor, OVEFetchTransport, self)
+            self._transport = creator.connectTCP(config['address'], config.get('port', 22), timeout = self.SEC_TIMEOUT)
+            self._transport.addErrback(self.transportErrback, requester, ref, config['address'])
+            self._timerRef += 1
+            # Set this timer slightly longer than the twisted.conch timeout, as transportErrback can cancel
+            # the timeout and prevent double handling
+            # lambda timerRef = self._timerRef: takes a copy of self._timerRef
+            QtCore.QTimer.singleShot(int((1+self.SEC_TIMEOUT) * 1000), lambda timerRef = self._timerRef: self.transportTimeout(timerRef, requester, ref, config['address']))
+        else:
+            self.execCommand2(requester, ref, command, commandType)
+
+    def execCommand2(self, requester, ref, command, commandType):
+        if self._connection is None:
+            self._commandQueue.append((requester, ref, command, commandType))
+        else:
+            self._connection.execCommand(requester, ref, command, commandType)
+
+    def getTable(self, requester, tableName, ref = QtCore.QObject()):
+        command = '/usr/bin/ovsdb-client transact '+self.config()['connectTarget']+' \'["Open_vSwitch", {"op":"select","table":"'+tableName+'", "where":[]}]\''
+
+        self.execCommand(requester, ref, command, 'JSON')
+        
+    def execCommandFramed(self, requester, ref, command):
+        self.execCommand(requester, ref, command + ' && echo ' + OVECommandChannel.END_MARKER, 'framed')
diff --git a/ovsdb/ovsdbmonitor/OVEFlowWindow.py b/ovsdb/ovsdbmonitor/OVEFlowWindow.py
new file mode 100644 (file)
index 0000000..ebcf466
--- /dev/null
@@ -0,0 +1,325 @@
+# Copyright (c) 2010 Citrix 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.
+
+from OVEStandard import *
+from OVEConfig import *
+from OVEFetch import *
+from OVELogger import *
+from OVEUtil import *
+
+from OVECommonWindow import *
+
+from Ui_FlowWindow import *
+
+class OVEFlowWindow(QtGui.QMainWindow, OVECommonWindow):
+    LOAD_KEY = 'FlowWindow/window'
+    COMMAND_OVS_DPCTL='/usr/bin/ovs-dpctl'
+    BASE_REF=200000
+    
+    def __init__(self, app, loadIndex = None):
+        QtGui.QMainWindow.__init__(self)
+        self.ui = Ui_FlowWindow()
+        self.dpNames = []
+        self.dpTables = []
+        self.currentOpIndex = None
+        self.resizeCount = []
+        self.ssgChecked = False
+        self.ssgText = ''
+        self.lastTime = None
+        self.lastByteCount = 0
+        OVECommonWindow.__init__(self, app, loadIndex)
+
+        self.updateSsgList()
+        self.updateDatapaths()
+        self.updateSsgState()
+
+        self.connect(self.ui.fetchPathsButton, QtCore.SIGNAL("clicked()"), self.xon_fetchPathsButton_clicked)
+        self.connect(self.ui.ssgSaveButton, QtCore.SIGNAL("clicked()"), self.xon_ssgSaveButton_clicked)
+        self.connect(self.ui.ssgDeleteButton, QtCore.SIGNAL("clicked()"), self.xon_ssgDeleteButton_clicked)
+        self.connect(self.ui.ssgComboBox, QtCore.SIGNAL("activated(int)"), self.xon_ssgComboBox_activated)
+        self.connect(self.ui.ssgComboBox, QtCore.SIGNAL("editTextChanged(QString)"), self.xon_ssgComboBox_editTextChanged)
+        self.connect(self.ui.ssgCheckBox, QtCore.SIGNAL("stateChanged(int)"), self.xon_ssgCheckBox_stateChanged)
+        
+    
+    def xon_fetchPathsButton_clicked(self):
+        self.updateDatapaths()
+    
+    def xon_hostComboBox_currentIndexChanged(self, index):
+        OVECommonWindow.xon_hostComboBox_currentIndexChanged(self, index)
+        if (index >= 0):
+            self.updateDatapaths()
+    
+    def xon_ssgSaveButton_clicked(self):
+        if self.ssgText not in OVEConfig.Inst().ssgList:
+            OVEConfig.Inst().ssgList.append(self.ssgText)
+            OVEConfig.Inst().saveConfig()
+        self.updateSsgList()
+        
+    def updateSsgList(self):
+        currentSsgText = self.ssgText
+        self.ui.ssgComboBox.clear()
+        isFound = False
+        for i, ssgText in enumerate(OVEConfig.Inst().ssgList):
+            self.ui.ssgComboBox.addItem(ssgText)
+            if ssgText == currentSsgText:
+                # This is the currently selected item
+                self.ui.ssgComboBox.setCurrentIndex(i)
+                isFound = True
+        
+        if not isFound:
+            self.ui.ssgComboBox.setCurrentIndex(-1)
+            self.ui.ssgComboBox.lineEdit().setText(currentSsgText)
+
+    def xon_ssgDeleteButton_clicked(self):
+        if self.ssgText in OVEConfig.Inst().ssgList:
+            OVEConfig.Inst().ssgList.remove(self.ssgText)
+            self.ssgText = ''
+            OVEConfig.Inst().saveConfig()
+        self.updateSsgList()
+
+    def xon_ssgComboBox_activated(self, index):
+        if (index >= 0):
+            itemData = self.ui.ssgComboBox.itemText(index)
+            self.ssgText = str(itemData)
+            self.updateTable()
+    
+    def xon_ssgComboBox_editTextChanged(self, text):
+        self.ssgText = str(text)
+        self.statusBar().showMessage('Remote command is: '+self.updateCommand())
+        present = (self.ssgText in OVEConfig.Inst().ssgList)
+        self.ui.ssgDeleteButton.setEnabled(present)
+        self.ui.ssgSaveButton.setEnabled(not present)
+    
+    def xon_ssgCheckBox_stateChanged(self, state):
+        self.ssgChecked = (state == Qt.Checked)
+        self.updateTable()
+    
+    def xon_configUpdated(self):
+        OVECommonWindow.xon_configUpdated(self)
+        self.updateSsgList()
+        self.updateDatapaths()
+    
+    def timerEvent(self, event):
+        OVECommonWindow.timerEvent(self, event)
+
+    def customEvent(self, event):
+        OVECommonWindow.customEvent(self, event)
+    
+    def updateDatapaths(self):
+        if self.hostUuid == '':
+            self.statusBar().showMessage('No host selected')
+        else:
+            self.currentRef += 1
+            self.currentOp = 'dump-dps'
+            command = self.COMMAND_OVS_DPCTL+' dump-dps'
+            OVEFetch.Inst(self.hostUuid).execCommandFramed(self, self.currentRef, command)
+    
+    def rebuildTables(self):
+        self.ui.tabWidget.clear() # Let the garbage collector delete the pages
+        self.dpTables = []
+        self.dpFlows = []
+        self.resizeCount = []
+        headings = OVEUtil.flowDecodeHeadings()
+        
+        for dpName in self.dpNames:
+            pageWidget = QtGui.QWidget()
+            pageWidget.setObjectName(dpName+'_page')
+            gridLayout = QtGui.QGridLayout(pageWidget)
+            gridLayout.setObjectName(dpName+"_gridLayout")
+            table = QtGui.QTableWidget(pageWidget)
+            table.setObjectName(dpName+"_table")
+            table.setColumnCount(len(headings))
+            table.setRowCount(0)
+            gridLayout.addWidget(table, 0, 0, 1, 1)
+            self.dpTables.append(table)
+            self.ui.tabWidget.addTab(pageWidget, dpName)
+            self.dpFlows.append([])
+            self.resizeCount.append(0)
+            for i, heading in enumerate(headings):
+                table.setHorizontalHeaderItem(i, QtGui.QTableWidgetItem(heading))
+
+            table.setSortingEnabled(True)
+            
+            table.sortItems(OVEUtil.getFlowColumn('source mac'))
+            table.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
+    
+    def updateSsgState(self):
+        self.ui.ssgCheckBox.setChecked(self.ssgChecked)
+    
+    def updateCommand(self, overrideText = None):
+        command = self.COMMAND_OVS_DPCTL+' dump-flows '
+        if self.currentOpIndex is not None:
+            command += self.dpNames[self.currentOpIndex]
+        exp = None
+        if overrideText is not None:
+                exp = overrideText
+        elif self.ssgChecked:
+                exp = self.ssgText
+                
+        if exp is not None:
+            opts='-E '
+            if exp.startswith('!'):
+                exp =exp[1:]
+                opts += '-v '
+            command += " | grep "+opts+"'"+exp+"' ; test ${PIPESTATUS[0]} -eq 0 "
+
+        return command
+    
+    def updateTable(self):
+        if self.hostUuid == '':
+            self.statusBar().showMessage('No host selected')
+            self.setWindowTitle('OVS Flows')
+        elif len(self.dpNames) > 0:
+            config = OVEConfig.Inst().hostFromUuid(self.hostUuid)
+            self.setWindowTitle('OVS Flows - '+config.get('address', ''))
+            try:
+                self.setFetchSkip()
+                self.statusBar().showMessage('Fetching data...') 
+                self.currentRef += 1
+                self.currentOp = 'dump-flows'
+                self.currentOpIndex = self.ui.tabWidget.currentIndex()
+                OVEFetch.Inst(self.hostUuid).execCommandFramed(self, self.currentRef, self.updateCommand())
+            except Exception, e:
+                message = 'Update failed: '+str(e)
+                OVELog(message)
+                self.statusBar().showMessage(message)
+    
+    def writeCurrentTable(self):
+        index = self.ui.tabWidget.currentIndex()
+        actionsColumn = OVEUtil.getFlowColumn('actions')
+        usedColumn = OVEUtil.getFlowColumn('used')
+        srcMacColumn = OVEUtil.getFlowColumn('source mac')
+        destMacColumn = OVEUtil.getFlowColumn('destination mac')
+        srcIPColumn = OVEUtil.getFlowColumn('source ip')
+        destIPColumn = OVEUtil.getFlowColumn('destination ip')
+        inportColumn = OVEUtil.getFlowColumn('inport')
+        vlanColumn = OVEUtil.getFlowColumn('vlan')
+        bytesColumn = OVEUtil.getFlowColumn('bytes')
+        
+        byteCount = 0
+        try:
+            table = self.dpTables[index]
+            table.setUpdatesEnabled(False)
+            table.setSortingEnabled(False)
+            try:
+                flows = self.dpFlows[index]
+                table.setRowCount(len(flows))
+                if len(flows) > 0:
+                    table.setColumnCount(len(flows[0]))
+                for rowNum, flow in enumerate(flows):
+                    
+                    inport = flow[inportColumn]
+                    if flow[actionsColumn] == 'drop':
+                        baseLum=172
+                    else:
+                        baseLum=239
+                    background = QtGui.QColor(baseLum+16*(inport % 2), baseLum+8*(inport % 3), baseLum+4*(inport % 5))
+                    if flow[usedColumn] == 'never':
+                        colour = QtGui.QColor(112,112,112)
+                    else:
+                        colour = Qt.black
+                        
+                    for colNum, data in enumerate(flow):
+                        
+                        item = None
+                        try:
+                            item = table.takeItem(rowNum, colNum)
+                        except:
+                            pass
+                        if item is None:
+                            item = QtGui.QTableWidgetItem('')
+
+                        if colNum == vlanColumn:
+                            item.setBackground(QtGui.QColor(255-(10*data % 192), 255-((17*data) % 192), 255-((37*data) % 192)))
+                        elif colNum == srcMacColumn or colNum == destMacColumn:
+                            cols = [int(x, 16) for x in data.split(':')]
+                            item.setBackground(QtGui.QColor(255-cols[2]*cols[3] % 192, 255-cols[3]*cols[4] % 192, 255-cols[4]*cols[5] % 192))
+                        elif colNum == srcIPColumn or colNum == destIPColumn:
+                            cols = [int(x) for x in data.split('.')]
+                            item.setBackground(QtGui.QColor(255-cols[1]*cols[2] % 192, 255-cols[2]*cols[3] % 192, 255-cols[3]*cols[0] % 192))
+                        else:
+                            item.setBackground(background)
+                            item.setForeground(colour)
+                            
+                        if colNum == bytesColumn:
+                            byteCount += int(data)
+                            
+                        # PySide 0.2.3 fails to convert long ints to QVariants and logs 'long int too large to convert to int' errors
+                        try:
+                            item.setData(Qt.DisplayRole, QVariant(data))
+                            item.setToolTip(str(data))
+                        except Exception, e:
+                            item.setText('Error: See tooltip')
+                            item.setToolTip(str(e))
+                        table.setItem(rowNum, colNum, item)
+                
+                if self.resizeCount[index] < 2:
+                    self.resizeCount[index] += 1
+                    for i in range(0, table.columnCount()):
+                        table.resizeColumnToContents(i)
+
+            finally:
+                table.setUpdatesEnabled(True)
+                table.setSortingEnabled(True)
+
+            message = 'Updated at '+str(QtCore.QTime.currentTime().toString())
+            
+            if self.lastTime is not None:
+                timeDiff = time.time() - self.lastTime
+                byteDiff = byteCount - self.lastByteCount
+                bitRate = long(8 * byteDiff / timeDiff)
+                if abs(bitRate) < 10*2**20:
+                    message += ' ('+str(bitRate/2**10)+' kbit/s)'
+                elif abs(bitRate) < 10*2**30:
+                    message += ' ('+str(bitRate/2**20)+' Mbit/s)'
+                else:
+                    message += ' ('+str(bitRate/2**30)+' Gbit/s)'
+                
+            self.lastByteCount = byteCount
+            self.lastTime = time.time()
+            if table.rowCount() == 0:
+                message += ' - Table is empty'
+            self.statusBar().showMessage(message)
+
+        except Exception, e:
+            message = 'Table update failed: '+str(e)
+            OVELog(message)
+            self.statusBar().showMessage(message)
+
+    def handleFetchEvent(self, ref, values):
+        if self.currentOp == 'dump-dps':
+            self.dpNames =values.strip().split('\n')
+            self.rebuildTables()
+            self.updateTable()
+        elif self.currentOp == 'dump-flows':
+            self.dpFlows[self.currentOpIndex] = OVEUtil.decodeFlows(values)
+            self.writeCurrentTable()
+
+    def handleFetchFailEvent(self, ref, message):
+        self.statusBar().showMessage(message)
+        OVELog('Fetch ('+self.currentOp+') failed')
+
+    def customEvent(self, event):
+        OVECommonWindow.customEvent(self, event)
+        
+    def saveSettings(self, index):
+        settings, key = OVECommonWindow.saveSettings(self, index)
+        settings.setValue(key+"/ssgText", QVariant(self.ssgText))
+        settings.setValue(key+"/ssgChecked", QVariant(self.ssgChecked))
+        
+    def loadSettings(self, index):
+        settings, key = OVECommonWindow.loadSettings(self, index)
+        self.ssgText = str(settings.value(key+"/ssgText", QVariant('10\.80\.226\..*')).toString())
+        self.ssgChecked = settings.value(key+"/ssgChecked", QVariant(False)).toBool()
+        self.ssgRe = re.compile(self.ssgText)
diff --git a/ovsdb/ovsdbmonitor/OVEHostWindow.py b/ovsdb/ovsdbmonitor/OVEHostWindow.py
new file mode 100644 (file)
index 0000000..e56b981
--- /dev/null
@@ -0,0 +1,54 @@
+# Copyright (c) 2010 Citrix 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.
+
+from OVEStandard import *
+from OVELogger import *
+from Ui_HostWindow import *
+
+class OVEHostWindow(QtGui.QDialog):
+    DEFAULT_CONNECT_TARGET = 'unix:/var/run/openvswitch/db.sock'
+    def __init__(self, parent, currentValues = None):
+        QtGui.QDialog.__init__(self, parent)
+        self.ui = Ui_HostWindow()
+        self.ui.setupUi(self)
+        self.resize(-1, -1)
+        self.connect(self.ui.buttonBox, QtCore.SIGNAL("clicked(QAbstractButton *)"), self.xon_actionButton_Box_clicked)
+        if currentValues is not None:
+            self.ui.hostAddressEdit.setText(currentValues['address'])
+            self.ui.hostPasswordEdit.setText(currentValues['password'])
+            self.ui.hostConnectTarget.setText(currentValues.get('connectTarget', self.DEFAULT_CONNECT_TARGET))
+            self.uuid = currentValues.get('uuid', str(uuid.uuid4()))
+        else:
+            self.ui.hostConnectTarget.setText(self.DEFAULT_CONNECT_TARGET)
+            self.uuid = str(uuid.uuid4())
+            self.accepted = None
+    
+    def xon_actionButton_Box_clicked(self, button):
+        role = self.ui.buttonBox.buttonRole(button)
+        if role == QtGui.QDialogButtonBox.AcceptRole:
+            self.accepted = True
+            self.close()
+        elif role == QtGui.QDialogButtonBox.RejectRole:
+            self.accepted = False
+            self.close()
+
+    def record(self):
+        return {
+            'accepted' : self.accepted,
+            'uuid' : self.uuid,
+            'address' : str(self.ui.hostAddressEdit.text()),
+            'password' : str(self.ui.hostPasswordEdit.text()),
+            'connectTarget' : str(self.ui.hostConnectTarget.text())
+            }
+
diff --git a/ovsdb/ovsdbmonitor/OVELogWindow.py b/ovsdb/ovsdbmonitor/OVELogWindow.py
new file mode 100644 (file)
index 0000000..3c1a2ef
--- /dev/null
@@ -0,0 +1,64 @@
+# Copyright (c) 2010 Citrix 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.
+
+from OVEStandard import *
+from OVELogger import *
+from Ui_LogWindow import *
+
+class OVELogWindow(QtGui.QDialog):
+    LOAD_KEY = 'LogWindow/window'
+    def __init__(self, app):
+        QtGui.QDialog.__init__(self)
+        self.app = app
+        self.ui = Ui_LogWindow()
+        self.ui.setupUi(self)
+        if self.isLoadable():
+            self.loadSettings()
+        self.connect(OVELogger.Inst(), QtCore.SIGNAL("logUpdated()"), self.logUpdated)
+        self.connect(self.ui.buttonBox, QtCore.SIGNAL("clicked(QAbstractButton *)"), self.xon_actionButton_Box_clicked)
+        
+    def xon_actionButton_Box_clicked(self, button):
+        role = self.ui.buttonBox.buttonRole(button)
+        if role == QtGui.QDialogButtonBox.ResetRole:
+            OVELogger.Inst().reset()
+            OVELog("Log reset")
+        
+    def logUpdated(self):
+        self.ui.textBrowser.setText("\n".join(OVELogger.Inst().contents))
+        self.ui.textBrowser.moveCursor(QtGui.QTextCursor.End)
+        self.ui.textBrowser.ensureCursorVisible()
+
+    def saveSettings(self):
+        key = self.LOAD_KEY
+        settings = QtCore.QSettings()
+        settings.setValue(key+"/loadable", QVariant(True))
+        settings.setValue(key+"/pos", QVariant(self.pos()))
+        settings.setValue(key+"/size", QVariant(self.size()))
+        settings.setValue(key+"/visible", QVariant(self.isVisible()))
+    
+    def loadSettings(self):
+        key = self.LOAD_KEY
+        settings = QtCore.QSettings()
+        pos = settings.value(key+"/pos", QVariant(QtCore.QPoint(200, 200))).toPoint()
+        size = settings.value(key+"/size", QVariant(QtCore.QSize(400, 400))).toSize()
+        visible = settings.value(key+"/visible", QVariant(True)).toBool()
+        self.resize(size)
+        self.move(pos)
+        self.setVisible(visible)
+
+    @classmethod
+    def isLoadable(cls):
+        key = cls.LOAD_KEY
+        settings = QtCore.QSettings()
+        return settings.value(key+"/loadable", QVariant(False)).toBool()
diff --git a/ovsdb/ovsdbmonitor/OVELogger.py b/ovsdb/ovsdbmonitor/OVELogger.py
new file mode 100644 (file)
index 0000000..5f39762
--- /dev/null
@@ -0,0 +1,45 @@
+# Copyright (c) 2010 Citrix 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.
+
+from OVEStandard import *
+
+class OVELogger(QtCore.QObject):
+    instance = None
+    def __init__(self):
+        QtCore.QObject.__init__(self)
+        self.contents = []
+        self.loggers = []
+        
+    @classmethod
+    def Inst(cls):
+        if cls.instance is None:
+            cls.instance = OVELogger()
+        return cls.instance
+    
+    def reset(self):
+        self.contents = []
+        self.update()
+    
+    def logString(self, message):
+        self.contents += [str(message)]
+        if len(self.contents) > 500:
+            self.contents = ['+++ Log truncated', ''] + self.contents[50:]
+        self.update()
+
+    def update(self):
+        self.emit(QtCore.SIGNAL("logUpdated()"))
+        
+def OVELog(message):
+    OVELogger.Inst().logString(message)
+    
diff --git a/ovsdb/ovsdbmonitor/OVEMainWindow.py b/ovsdb/ovsdbmonitor/OVEMainWindow.py
new file mode 100644 (file)
index 0000000..8d3d830
--- /dev/null
@@ -0,0 +1,138 @@
+# Copyright (c) 2010 Citrix 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.
+
+from OVEStandard import *
+from OVEConfig import *
+from OVEFetch import *
+from OVELogger import *
+from OVEUtil import *
+
+from OVECommonWindow import *
+
+from Ui_MainWindow import *
+
+class OVEMainWindow(QtGui.QMainWindow, OVECommonWindow):
+    LOAD_KEY = 'MainWindow/window'
+    BASE_REF=100000
+    
+    def __init__(self, app, loadIndex = None):
+        QtGui.QMainWindow.__init__(self)
+        self.ui = Ui_MainWindow()
+        OVECommonWindow.__init__(self, app, loadIndex)
+    
+    def xon_tabWidget_currentChanged(self, value):
+        self.deleteCurrentTable()
+        OVECommonWindow.xon_tabWidget_currentChanged(self, value)
+
+    def updateTable(self):
+        if self.hostUuid == '':
+            self.setWindowTitle('OVS Database')
+            self.deleteCurrentTable()
+            self.statusBar().showMessage('No host selected.  Choose File->Preferences to add a host')
+        else:
+            config = OVEConfig.Inst().hostFromUuid(self.hostUuid)
+            self.setWindowTitle('OVS Database - '+config.get('address', ''))
+            self.invalidateCurrentTable('Fetching data...')
+            tabName = self.ui.tabWidget.currentWidget().objectName()
+            try:
+                self.setFetchSkip()
+                self.currentRef += 1
+                OVEFetch.Inst(self.hostUuid).getTable(self, tabName, self.currentRef)
+            except Exception, e:
+                OVELog("Error fetching data: "+str(e))
+                self.invalidateCurrentTable(str(e))
+
+    def timerEvent(self, event):
+        OVECommonWindow.timerEvent(self, event)
+
+    def customEvent(self, event):
+        OVECommonWindow.customEvent(self, event)
+
+    def handleFetchEvent(self, ref, values):
+        tabName = self.ui.tabWidget.currentWidget().objectName()
+        self.structToTable(getattr(self.ui, str(tabName)+'Table'), values)
+    def handleFetchFailEvent(self, ref, message):
+        self.invalidateCurrentTable(str(message))
+
+    def structToTable(self, table, values):
+        
+        table.setUpdatesEnabled(False)
+        table.setSortingEnabled(False)
+        
+        for result in values:
+            rowNum = 0
+            table.setRowCount(len(result['rows']))
+            for row in result['rows']:
+                table.setColumnCount(len(row))
+                colNum=0
+                for k in sorted(row.keys()):
+                    v = row[k]
+                    headerItem = QtGui.QTableWidgetItem(k)
+                    table.setHorizontalHeaderItem(colNum, headerItem)
+                    text = OVEUtil.paramToString(v)
+                    item = QtGui.QTableWidgetItem(text)
+                    longText = OVEUtil.paramToLongString(v)
+                    item.setToolTip(longText)
+
+                    table.setItem(rowNum, colNum, item)
+                    colNum+=1
+
+                rowNum+=1
+                
+        for i in range(0, table.columnCount()):
+            table.resizeColumnToContents(i)
+        for i in range(0, table.rowCount()):
+            table.resizeRowToContents(i)
+        
+        # table.setSortingEnabled(True)
+        table.setUpdatesEnabled(True)
+        
+        message = 'Updated at '+str(QtCore.QTime.currentTime().toString())
+        if table.rowCount() == 0:
+            message += ' - Table is empty'
+        self.statusBar().showMessage(message)
+
+    def invalidateCurrentTable(self, message):
+        tabName = self.ui.tabWidget.currentWidget().objectName()
+        self.invalidateTable(getattr(self.ui, str(tabName)+'Table'), message)
+
+    def invalidateTable(self, table, message):
+        table.setUpdatesEnabled(False)
+        table.setSortingEnabled(False)
+        
+        for rowNum in range(0, table.rowCount()):
+            for colNum in range(0, table.columnCount()):
+                item = table.takeItem(rowNum, colNum)
+                if item is not None:
+                    item.setForeground(Qt.darkGray)
+                    table.setItem(rowNum, colNum, item)
+        self.statusBar().showMessage(message)
+        # table.setSortingEnabled(True)
+        table.setUpdatesEnabled(True)
+
+    def deleteCurrentTable(self):
+        tabName = self.ui.tabWidget.currentWidget().objectName()
+        self.deleteTable(getattr(self.ui, str(tabName)+'Table'))
+
+    def deleteTable(self, table):
+        table.clear()
+        table.setRowCount(0)
+        table.setColumnCount(0)
+        
+    def saveSettings(self, index):
+        settings = OVECommonWindow.saveSettings(self, index)
+    
+    def loadSettings(self, index):
+        settings = OVECommonWindow.loadSettings(self, index)
diff --git a/ovsdb/ovsdbmonitor/OVEStandard.py b/ovsdb/ovsdbmonitor/OVEStandard.py
new file mode 100644 (file)
index 0000000..b9bc419
--- /dev/null
@@ -0,0 +1,41 @@
+# Copyright (c) 2010 Citrix 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.
+
+import os, re, struct, sys, time, types, uuid
+from copy import deepcopy
+from pprint import pprint
+
+# Set globalForcePySide to True to use PySide instead of PyQt if both are installed
+globalForcePySide = False
+
+try:
+    import json
+except Exception, e:
+    print('+++ Python JSON module is required\n')
+    raise
+
+try:
+    if globalForcePySide:
+        print('Forcing use of PySide')
+        raise Exception()
+    from PyQt4.QtCore import Qt, QVariant
+    from PyQt4 import QtCore, QtGui
+except:
+    try:
+        from PySide.QtCore import Qt, QVariant
+        from PySide import QtCore, QtGui
+    except Exception, e:
+        print('+++ This application requires either PyQt4 or PySide\n')
+        raise
+
diff --git a/ovsdb/ovsdbmonitor/OVEUtil.py b/ovsdb/ovsdbmonitor/OVEUtil.py
new file mode 100644 (file)
index 0000000..340d9b1
--- /dev/null
@@ -0,0 +1,139 @@
+# Copyright (c) 2010 Citrix 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.
+
+from OVEStandard import *
+
+from OVEConfig import *
+
+class OVEUtil:
+    UUID_RE = re.compile(r'([a-f0-9]{8}-[a-f0-9]{2})[a-f0-9]{2}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}')
+    
+    @classmethod
+    def paramToLongString(cls, param):
+        if isinstance(param, (types.ListType, types.TupleType)) and len(param) > 1:
+            text = str(param[1])
+        else:
+            text = str(param)
+
+        return text.replace(', ', ',\n')
+        
+    @classmethod
+    def paramToString(cls, param):
+        if isinstance(param, (types.ListType, types.TupleType)) and len(param) > 1:
+            text = str(param[1])
+        else:
+            text = str(param)
+        if OVEConfig.Inst().truncateUuids:
+            text = cls.UUID_RE.sub('\\1...', text)
+            
+        return text.replace(', ', ',\n')
+
+    @classmethod
+    def flowDecodeHeadings(self):
+        return [
+                'Type',
+                'Proto',
+                'Inport',
+                'VLAN',
+                'Source MAC',
+                'Destination MAC',
+                'Source IP',
+                'Destination IP',
+                'Src port',
+                'Dest port',
+                'Packet count',
+                'Bytes',
+                'Used',
+                'Tos',
+                'PCP',
+                'Tunnel',
+                'Actions',
+                ]
+
+    @classmethod
+    def getFlowColumn(cls, name):
+        lowerName = name.lower()
+        for i, columnName in enumerate(cls.flowDecodeHeadings()):
+            if lowerName == columnName.lower():
+                return i
+        return None
+
+    ETHERTYPE_TRANS = {
+        '05ff':'ESX probe',
+        '0800':'IP',
+        '0806':'ARP',
+        '86dd':'IPv6',
+        '88cc':'LLDP'
+    }
+                  
+    ETHERPROTO_TRANS = {
+        '1':'ICMP',
+        '6':'TCP',
+        '17':'UDP'
+    }
+    
+    # Parsing of ovs-dpctl dump-flows output should be localised in this method and flowDecodeHeadings
+    @classmethod
+    def decodeFlows(cls, srcLines):
+        retVal = []
+        flowRe = re.compile(
+            # To fix this regexp:
+            #  Comment out lines, starting from the bottom, until it works, then fix the one you stopped at
+            '^' +
+            r'tunnel([^:]*):'+ # Tunnel: tunnel00000000
+            r'in_port([^:]+):' + # in_port: in_port0002
+            r'vlan([^:]+):' + #VLAN: vlan65535
+            r'([^ ]*) ' + # PCP: pcp0
+            r'mac(.{17})->' + # Source MAC: mac00:16:76:c8:1f:c9->
+            r'(.{17}) ' + # Dest MAC: mac00:16:76:c8:1f:c9
+            r'type([^ ]+) ' + #Type: type05ff
+            r'proto([^ ]+) ' + #Proto: proto0
+            r'(tos[^ ]+) ' + #Tos: tos0
+            r'ip(\d+\.\d+\.\d+\.\d+)->' + # Source IP: ip1.2.3.4->
+            r'(\d+\.\d+\.\d+\.\d+) ' + # Dest IP: 1.2.3.4
+            r'port(\d+)->' + # Source port: port0->
+            r'(\d+),\s*' + # Dest port: 0
+            r'packets:(\d*),\s*' + # Packets: packets:3423,
+            r'bytes:(\d*),\s*' + # Bytes: bytes:272024,
+            r'used:([^,]+),\s*' + # Used: used:0.870s,
+            r'actions:(\w+)\s*' + # Actions: actions:drop
+            ''
+            )
+        for line in srcLines.split('\n'):
+            if line != '':
+                match = flowRe.match(line)
+                if not match:
+                    OVELog("Could not decode flow record '"+line+"'.  Abandoning")
+                    return retVal
+                else:
+                    tunnel, inport, vlan, pcp, srcmac, destmac, type, proto, tos, srcip, destip, srcport, destport, packets, bytes, used, actions = match.groups()
+                    tunnel = int(tunnel)
+                    inport = int(inport)
+                    vlan = int(vlan)
+                    type = cls.ETHERTYPE_TRANS.get(type, type)
+                    proto = cls.ETHERPROTO_TRANS.get(proto, proto)
+                    srcport = int(srcport)
+                    destport = int(destport)
+                    packets = long(packets)
+                    bytes = long(bytes)
+                    # Order below needs to match that in flowDecodeHeadings
+                    retVal.append((type, proto, inport, vlan, srcmac, destmac, srcip, destip, srcport, destport, packets, bytes, used, tos, pcp, tunnel, actions))
+                    
+        return retVal
+        
+    COLOURS = [Qt.black, Qt.darkBlue,  Qt.darkRed, Qt.darkGreen, Qt.darkMagenta, Qt.darkCyan, Qt.darkGray, Qt.darkYellow, Qt.blue, Qt.gray, Qt.magenta, Qt.red]
+        
+    @classmethod
+    def intToColour(cls, value):
+        return cls.COLOURS[value % len(cls.COLOURS)]
diff --git a/ovsdb/ovsdbmonitor/Ui_ConfigWindow.py b/ovsdb/ovsdbmonitor/Ui_ConfigWindow.py
new file mode 100644 (file)
index 0000000..461edc5
--- /dev/null
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'ConfigWindow.ui'
+#
+# Created: Fri May  7 17:20:33 2010
+#      by: PyQt4 UI code generator 4.4.2
+#
+# WARNING! All changes made in this file will be lost!
+
+try:
+    from OVEStandard import globalForcePySide
+    if globalForcePySide: raise Exception()
+    from PyQt4 import QtCore, QtGui
+except:
+    from PySide import QtCore, QtGui
+
+class Ui_ConfigWindow(object):
+    def setupUi(self, ConfigWindow):
+        ConfigWindow.setObjectName("ConfigWindow")
+        ConfigWindow.resize(386,303)
+        ConfigWindow.setFocusPolicy(QtCore.Qt.TabFocus)
+        self.gridLayout = QtGui.QGridLayout(ConfigWindow)
+        self.gridLayout.setObjectName("gridLayout")
+        self.verticalLayout = QtGui.QVBoxLayout()
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.tabWidget = QtGui.QTabWidget(ConfigWindow)
+        self.tabWidget.setObjectName("tabWidget")
+        self.hosts = QtGui.QWidget()
+        self.hosts.setObjectName("hosts")
+        self.layoutWidget = QtGui.QWidget(self.hosts)
+        self.layoutWidget.setGeometry(QtCore.QRect(10,10,341,194))
+        self.layoutWidget.setObjectName("layoutWidget")
+        self.horizontalLayout_2 = QtGui.QHBoxLayout(self.layoutWidget)
+        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+        self.hostList = QtGui.QListWidget(self.layoutWidget)
+        self.hostList.setObjectName("hostList")
+        self.horizontalLayout_2.addWidget(self.hostList)
+        self.verticalLayout_2 = QtGui.QVBoxLayout()
+        self.verticalLayout_2.setObjectName("verticalLayout_2")
+        self.hostAddButton = QtGui.QPushButton(self.layoutWidget)
+        self.hostAddButton.setObjectName("hostAddButton")
+        self.verticalLayout_2.addWidget(self.hostAddButton)
+        self.hostEditButton = QtGui.QPushButton(self.layoutWidget)
+        self.hostEditButton.setObjectName("hostEditButton")
+        self.verticalLayout_2.addWidget(self.hostEditButton)
+        self.hostDeleteButton = QtGui.QPushButton(self.layoutWidget)
+        self.hostDeleteButton.setObjectName("hostDeleteButton")
+        self.verticalLayout_2.addWidget(self.hostDeleteButton)
+        spacerItem = QtGui.QSpacerItem(20,40,QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Expanding)
+        self.verticalLayout_2.addItem(spacerItem)
+        self.horizontalLayout_2.addLayout(self.verticalLayout_2)
+        self.tabWidget.addTab(self.hosts,"")
+        self.logging = QtGui.QWidget()
+        self.logging.setObjectName("logging")
+        self.gridLayout_2 = QtGui.QGridLayout(self.logging)
+        self.gridLayout_2.setObjectName("gridLayout_2")
+        self.logTrafficCheckBox = QtGui.QCheckBox(self.logging)
+        self.logTrafficCheckBox.setObjectName("logTrafficCheckBox")
+        self.gridLayout_2.addWidget(self.logTrafficCheckBox,0,0,1,1)
+        spacerItem1 = QtGui.QSpacerItem(20,164,QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Expanding)
+        self.gridLayout_2.addItem(spacerItem1,1,0,1,1)
+        self.tabWidget.addTab(self.logging,"")
+        self.view = QtGui.QWidget()
+        self.view.setObjectName("view")
+        self.verticalLayout_3 = QtGui.QVBoxLayout(self.view)
+        self.verticalLayout_3.setObjectName("verticalLayout_3")
+        self.truncateUuidsCheckBox = QtGui.QCheckBox(self.view)
+        self.truncateUuidsCheckBox.setObjectName("truncateUuidsCheckBox")
+        self.verticalLayout_3.addWidget(self.truncateUuidsCheckBox)
+        spacerItem2 = QtGui.QSpacerItem(20,164,QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Expanding)
+        self.verticalLayout_3.addItem(spacerItem2)
+        self.tabWidget.addTab(self.view,"")
+        self.verticalLayout.addWidget(self.tabWidget)
+        self.horizontalLayout = QtGui.QHBoxLayout()
+        self.horizontalLayout.setObjectName("horizontalLayout")
+        spacerItem3 = QtGui.QSpacerItem(40,20,QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Minimum)
+        self.horizontalLayout.addItem(spacerItem3)
+        self.buttonBox = QtGui.QDialogButtonBox(ConfigWindow)
+        self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Apply|QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
+        self.buttonBox.setObjectName("buttonBox")
+        self.horizontalLayout.addWidget(self.buttonBox)
+        self.verticalLayout.addLayout(self.horizontalLayout)
+        self.gridLayout.addLayout(self.verticalLayout,0,0,1,1)
+
+        self.retranslateUi(ConfigWindow)
+        self.tabWidget.setCurrentIndex(0)
+        QtCore.QMetaObject.connectSlotsByName(ConfigWindow)
+        ConfigWindow.setTabOrder(self.hostList,self.hostAddButton)
+        ConfigWindow.setTabOrder(self.hostAddButton,self.hostEditButton)
+        ConfigWindow.setTabOrder(self.hostEditButton,self.hostDeleteButton)
+        ConfigWindow.setTabOrder(self.hostDeleteButton,self.buttonBox)
+        ConfigWindow.setTabOrder(self.buttonBox,self.tabWidget)
+
+    def retranslateUi(self, ConfigWindow):
+        ConfigWindow.setWindowTitle(QtGui.QApplication.translate("ConfigWindow", "OVSDB Monitor Configuration", None, QtGui.QApplication.UnicodeUTF8))
+        self.hostAddButton.setText(QtGui.QApplication.translate("ConfigWindow", "Add", None, QtGui.QApplication.UnicodeUTF8))
+        self.hostEditButton.setText(QtGui.QApplication.translate("ConfigWindow", "Edit", None, QtGui.QApplication.UnicodeUTF8))
+        self.hostDeleteButton.setText(QtGui.QApplication.translate("ConfigWindow", "Delete", None, QtGui.QApplication.UnicodeUTF8))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.hosts), QtGui.QApplication.translate("ConfigWindow", "Hosts", None, QtGui.QApplication.UnicodeUTF8))
+        self.logTrafficCheckBox.setToolTip(QtGui.QApplication.translate("ConfigWindow", "Whether to log traffic exchanges in the log window", None, QtGui.QApplication.UnicodeUTF8))
+        self.logTrafficCheckBox.setText(QtGui.QApplication.translate("ConfigWindow", "Log traffic", None, QtGui.QApplication.UnicodeUTF8))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.logging), QtGui.QApplication.translate("ConfigWindow", "Logging", None, QtGui.QApplication.UnicodeUTF8))
+        self.truncateUuidsCheckBox.setToolTip(QtGui.QApplication.translate("ConfigWindow", "Replaces UUIDs with a shorter string of the first few characters.  The tooltip still contains the full value", None, QtGui.QApplication.UnicodeUTF8))
+        self.truncateUuidsCheckBox.setText(QtGui.QApplication.translate("ConfigWindow", "Truncate UUIDs", None, QtGui.QApplication.UnicodeUTF8))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.view), QtGui.QApplication.translate("ConfigWindow", "View", None, QtGui.QApplication.UnicodeUTF8))
+
diff --git a/ovsdb/ovsdbmonitor/Ui_FlowWindow.py b/ovsdb/ovsdbmonitor/Ui_FlowWindow.py
new file mode 100644 (file)
index 0000000..351a0ca
--- /dev/null
@@ -0,0 +1,136 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'FlowWindow.ui'
+#
+# Created: Fri May  7 17:20:33 2010
+#      by: PyQt4 UI code generator 4.4.2
+#
+# WARNING! All changes made in this file will be lost!
+
+try:
+    from OVEStandard import globalForcePySide
+    if globalForcePySide: raise Exception()
+    from PyQt4 import QtCore, QtGui
+except:
+    from PySide import QtCore, QtGui
+
+class Ui_FlowWindow(object):
+    def setupUi(self, FlowWindow):
+        FlowWindow.setObjectName("FlowWindow")
+        FlowWindow.resize(800,600)
+        self.centralwidget = QtGui.QWidget(FlowWindow)
+        self.centralwidget.setObjectName("centralwidget")
+        self.gridLayout = QtGui.QGridLayout(self.centralwidget)
+        self.gridLayout.setObjectName("gridLayout")
+        self.tabWidget = QtGui.QTabWidget(self.centralwidget)
+        self.tabWidget.setObjectName("tabWidget")
+        self.unset = QtGui.QWidget()
+        self.unset.setObjectName("unset")
+        self.gridLayout_10 = QtGui.QGridLayout(self.unset)
+        self.gridLayout_10.setObjectName("gridLayout_10")
+        self.tabWidget.addTab(self.unset,"")
+        self.gridLayout.addWidget(self.tabWidget,0,0,1,1)
+        self.horizontalLayout_2 = QtGui.QHBoxLayout()
+        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+        self.ssgCheckBox = QtGui.QCheckBox(self.centralwidget)
+        self.ssgCheckBox.setObjectName("ssgCheckBox")
+        self.horizontalLayout_2.addWidget(self.ssgCheckBox)
+        self.ssgComboBox = QtGui.QComboBox(self.centralwidget)
+        self.ssgComboBox.setEditable(True)
+        self.ssgComboBox.setMaxVisibleItems(20)
+        self.ssgComboBox.setInsertPolicy(QtGui.QComboBox.NoInsert)
+        self.ssgComboBox.setMinimumContentsLength(32)
+        self.ssgComboBox.setObjectName("ssgComboBox")
+        self.horizontalLayout_2.addWidget(self.ssgComboBox)
+        self.ssgSaveButton = QtGui.QPushButton(self.centralwidget)
+        self.ssgSaveButton.setObjectName("ssgSaveButton")
+        self.horizontalLayout_2.addWidget(self.ssgSaveButton)
+        self.ssgDeleteButton = QtGui.QPushButton(self.centralwidget)
+        self.ssgDeleteButton.setObjectName("ssgDeleteButton")
+        self.horizontalLayout_2.addWidget(self.ssgDeleteButton)
+        spacerItem = QtGui.QSpacerItem(40,20,QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Minimum)
+        self.horizontalLayout_2.addItem(spacerItem)
+        self.gridLayout.addLayout(self.horizontalLayout_2,1,0,1,1)
+        self.horizontalLayout = QtGui.QHBoxLayout()
+        self.horizontalLayout.setObjectName("horizontalLayout")
+        self.hostLabel = QtGui.QLabel(self.centralwidget)
+        self.hostLabel.setObjectName("hostLabel")
+        self.horizontalLayout.addWidget(self.hostLabel)
+        self.hostComboBox = QtGui.QComboBox(self.centralwidget)
+        self.hostComboBox.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
+        self.hostComboBox.setObjectName("hostComboBox")
+        self.horizontalLayout.addWidget(self.hostComboBox)
+        self.intervalCheckBox = QtGui.QCheckBox(self.centralwidget)
+        self.intervalCheckBox.setObjectName("intervalCheckBox")
+        self.horizontalLayout.addWidget(self.intervalCheckBox)
+        self.intervalSpinBox = QtGui.QSpinBox(self.centralwidget)
+        self.intervalSpinBox.setMinimum(1)
+        self.intervalSpinBox.setMaximum(1000000)
+        self.intervalSpinBox.setObjectName("intervalSpinBox")
+        self.horizontalLayout.addWidget(self.intervalSpinBox)
+        spacerItem1 = QtGui.QSpacerItem(40,20,QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Minimum)
+        self.horizontalLayout.addItem(spacerItem1)
+        self.fetchPathsButton = QtGui.QPushButton(self.centralwidget)
+        self.fetchPathsButton.setObjectName("fetchPathsButton")
+        self.horizontalLayout.addWidget(self.fetchPathsButton)
+        self.fetchButton = QtGui.QPushButton(self.centralwidget)
+        self.fetchButton.setObjectName("fetchButton")
+        self.horizontalLayout.addWidget(self.fetchButton)
+        self.gridLayout.addLayout(self.horizontalLayout,3,0,1,1)
+        self.line = QtGui.QFrame(self.centralwidget)
+        self.line.setFrameShape(QtGui.QFrame.HLine)
+        self.line.setFrameShadow(QtGui.QFrame.Sunken)
+        self.line.setObjectName("line")
+        self.gridLayout.addWidget(self.line,2,0,1,1)
+        FlowWindow.setCentralWidget(self.centralwidget)
+        self.menubar = QtGui.QMenuBar(FlowWindow)
+        self.menubar.setGeometry(QtCore.QRect(0,0,800,28))
+        self.menubar.setObjectName("menubar")
+        self.menuFile = QtGui.QMenu(self.menubar)
+        self.menuFile.setObjectName("menuFile")
+        FlowWindow.setMenuBar(self.menubar)
+        self.statusbar = QtGui.QStatusBar(FlowWindow)
+        self.statusbar.setObjectName("statusbar")
+        FlowWindow.setStatusBar(self.statusbar)
+        self.actionShow_Log = QtGui.QAction(FlowWindow)
+        self.actionShow_Log.setObjectName("actionShow_Log")
+        self.actionNew_DB_Window = QtGui.QAction(FlowWindow)
+        self.actionNew_DB_Window.setObjectName("actionNew_DB_Window")
+        self.actionPreferences = QtGui.QAction(FlowWindow)
+        self.actionPreferences.setObjectName("actionPreferences")
+        self.actionQuit = QtGui.QAction(FlowWindow)
+        self.actionQuit.setObjectName("actionQuit")
+        self.actionNew_Flow_Window = QtGui.QAction(FlowWindow)
+        self.actionNew_Flow_Window.setObjectName("actionNew_Flow_Window")
+        self.menuFile.addAction(self.actionNew_DB_Window)
+        self.menuFile.addAction(self.actionNew_Flow_Window)
+        self.menuFile.addAction(self.actionShow_Log)
+        self.menuFile.addAction(self.actionPreferences)
+        self.menuFile.addSeparator()
+        self.menuFile.addAction(self.actionQuit)
+        self.menubar.addAction(self.menuFile.menuAction())
+        self.hostLabel.setBuddy(self.hostComboBox)
+
+        self.retranslateUi(FlowWindow)
+        self.tabWidget.setCurrentIndex(0)
+        QtCore.QMetaObject.connectSlotsByName(FlowWindow)
+
+    def retranslateUi(self, FlowWindow):
+        FlowWindow.setWindowTitle(QtGui.QApplication.translate("FlowWindow", "OVSDB Monitor", None, QtGui.QApplication.UnicodeUTF8))
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.unset), QtGui.QApplication.translate("FlowWindow", "Awaiting update...", None, QtGui.QApplication.UnicodeUTF8))
+        self.ssgCheckBox.setText(QtGui.QApplication.translate("FlowWindow", "Server-side grep", None, QtGui.QApplication.UnicodeUTF8))
+        self.ssgSaveButton.setText(QtGui.QApplication.translate("FlowWindow", "Save", None, QtGui.QApplication.UnicodeUTF8))
+        self.ssgDeleteButton.setText(QtGui.QApplication.translate("FlowWindow", "Delete", None, QtGui.QApplication.UnicodeUTF8))
+        self.hostLabel.setText(QtGui.QApplication.translate("FlowWindow", "Host", None, QtGui.QApplication.UnicodeUTF8))
+        self.intervalCheckBox.setText(QtGui.QApplication.translate("FlowWindow", "Auto-refetch every", None, QtGui.QApplication.UnicodeUTF8))
+        self.intervalSpinBox.setSuffix(QtGui.QApplication.translate("FlowWindow", "s", None, QtGui.QApplication.UnicodeUTF8))
+        self.fetchPathsButton.setToolTip(QtGui.QApplication.translate("FlowWindow", "Refetches the datapath names and rebuilds the window tabs to reflect them.  Use when the network has been reconfigured, e.g. a bond has been created", None, QtGui.QApplication.UnicodeUTF8))
+        self.fetchPathsButton.setText(QtGui.QApplication.translate("FlowWindow", "Refetch Datapath List", None, QtGui.QApplication.UnicodeUTF8))
+        self.fetchButton.setText(QtGui.QApplication.translate("FlowWindow", "Refetch", None, QtGui.QApplication.UnicodeUTF8))
+        self.menuFile.setTitle(QtGui.QApplication.translate("FlowWindow", "File", None, QtGui.QApplication.UnicodeUTF8))
+        self.actionShow_Log.setText(QtGui.QApplication.translate("FlowWindow", "Show Log", None, QtGui.QApplication.UnicodeUTF8))
+        self.actionNew_DB_Window.setText(QtGui.QApplication.translate("FlowWindow", "New DB Window", None, QtGui.QApplication.UnicodeUTF8))
+        self.actionPreferences.setText(QtGui.QApplication.translate("FlowWindow", "Preferences", None, QtGui.QApplication.UnicodeUTF8))
+        self.actionQuit.setText(QtGui.QApplication.translate("FlowWindow", "Quit", None, QtGui.QApplication.UnicodeUTF8))
+        self.actionNew_Flow_Window.setText(QtGui.QApplication.translate("FlowWindow", "New Flow Window", None, QtGui.QApplication.UnicodeUTF8))
+
diff --git a/ovsdb/ovsdbmonitor/Ui_HostWindow.py b/ovsdb/ovsdbmonitor/Ui_HostWindow.py
new file mode 100644 (file)
index 0000000..3cab49d
--- /dev/null
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'HostWindow.ui'
+#
+# Created: Fri May  7 17:20:33 2010
+#      by: PyQt4 UI code generator 4.4.2
+#
+# WARNING! All changes made in this file will be lost!
+
+try:
+    from OVEStandard import globalForcePySide
+    if globalForcePySide: raise Exception()
+    from PyQt4 import QtCore, QtGui
+except:
+    from PySide import QtCore, QtGui
+
+class Ui_HostWindow(object):
+    def setupUi(self, HostWindow):
+        HostWindow.setObjectName("HostWindow")
+        HostWindow.setWindowModality(QtCore.Qt.WindowModal)
+        HostWindow.resize(400,300)
+        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum,QtGui.QSizePolicy.Minimum)
+        sizePolicy.setHorizontalStretch(0)
+        sizePolicy.setVerticalStretch(0)
+        sizePolicy.setHeightForWidth(HostWindow.sizePolicy().hasHeightForWidth())
+        HostWindow.setSizePolicy(sizePolicy)
+        self.gridLayout_2 = QtGui.QGridLayout(HostWindow)
+        self.gridLayout_2.setObjectName("gridLayout_2")
+        self.gridLayout = QtGui.QGridLayout()
+        self.gridLayout.setObjectName("gridLayout")
+        self.label = QtGui.QLabel(HostWindow)
+        self.label.setObjectName("label")
+        self.gridLayout.addWidget(self.label,0,0,1,1)
+        self.hostAddressEdit = QtGui.QLineEdit(HostWindow)
+        self.hostAddressEdit.setMinimumSize(QtCore.QSize(256,0))
+        self.hostAddressEdit.setObjectName("hostAddressEdit")
+        self.gridLayout.addWidget(self.hostAddressEdit,0,1,1,1)
+        self.label_2 = QtGui.QLabel(HostWindow)
+        self.label_2.setObjectName("label_2")
+        self.gridLayout.addWidget(self.label_2,1,0,1,1)
+        self.hostPasswordEdit = QtGui.QLineEdit(HostWindow)
+        self.hostPasswordEdit.setMinimumSize(QtCore.QSize(256,0))
+        self.hostPasswordEdit.setEchoMode(QtGui.QLineEdit.Password)
+        self.hostPasswordEdit.setObjectName("hostPasswordEdit")
+        self.gridLayout.addWidget(self.hostPasswordEdit,1,1,1,1)
+        self.label_3 = QtGui.QLabel(HostWindow)
+        self.label_3.setObjectName("label_3")
+        self.gridLayout.addWidget(self.label_3,2,0,1,1)
+        self.hostConnectTarget = QtGui.QLineEdit(HostWindow)
+        self.hostConnectTarget.setMinimumSize(QtCore.QSize(256,0))
+        self.hostConnectTarget.setObjectName("hostConnectTarget")
+        self.gridLayout.addWidget(self.hostConnectTarget,2,1,1,1)
+        self.gridLayout_2.addLayout(self.gridLayout,0,0,1,1)
+        self.buttonBox = QtGui.QDialogButtonBox(HostWindow)
+        self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+        self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
+        self.buttonBox.setObjectName("buttonBox")
+        self.gridLayout_2.addWidget(self.buttonBox,1,0,1,1)
+        self.label.setBuddy(self.hostAddressEdit)
+        self.label_2.setBuddy(self.hostPasswordEdit)
+        self.label_3.setBuddy(self.hostConnectTarget)
+
+        self.retranslateUi(HostWindow)
+        QtCore.QObject.connect(self.buttonBox,QtCore.SIGNAL("accepted()"),HostWindow.accept)
+        QtCore.QObject.connect(self.buttonBox,QtCore.SIGNAL("rejected()"),HostWindow.reject)
+        QtCore.QMetaObject.connectSlotsByName(HostWindow)
+        HostWindow.setTabOrder(self.hostAddressEdit,self.hostPasswordEdit)
+        HostWindow.setTabOrder(self.hostPasswordEdit,self.buttonBox)
+
+    def retranslateUi(self, HostWindow):
+        HostWindow.setWindowTitle(QtGui.QApplication.translate("HostWindow", "Host Properties", None, QtGui.QApplication.UnicodeUTF8))
+        self.label.setText(QtGui.QApplication.translate("HostWindow", "Host name or IP", None, QtGui.QApplication.UnicodeUTF8))
+        self.label_2.setText(QtGui.QApplication.translate("HostWindow", "SSH Password", None, QtGui.QApplication.UnicodeUTF8))
+        self.label_3.setText(QtGui.QApplication.translate("HostWindow", "Connect target", None, QtGui.QApplication.UnicodeUTF8))
+
diff --git a/ovsdb/ovsdbmonitor/Ui_LogWindow.py b/ovsdb/ovsdbmonitor/Ui_LogWindow.py
new file mode 100644 (file)
index 0000000..0ddd651
--- /dev/null
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'LogWindow.ui'
+#
+# Created: Fri May  7 17:20:33 2010
+#      by: PyQt4 UI code generator 4.4.2
+#
+# WARNING! All changes made in this file will be lost!
+
+try:
+    from OVEStandard import globalForcePySide
+    if globalForcePySide: raise Exception()
+    from PyQt4 import QtCore, QtGui
+except:
+    from PySide import QtCore, QtGui
+
+class Ui_LogWindow(object):
+    def setupUi(self, LogWindow):
+        LogWindow.setObjectName("LogWindow")
+        LogWindow.resize(735,558)
+        self.gridLayout = QtGui.QGridLayout(LogWindow)
+        self.gridLayout.setObjectName("gridLayout")
+        self.verticalLayout = QtGui.QVBoxLayout()
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.textBrowser = QtGui.QTextBrowser(LogWindow)
+        self.textBrowser.setObjectName("textBrowser")
+        self.verticalLayout.addWidget(self.textBrowser)
+        self.buttonBox = QtGui.QDialogButtonBox(LogWindow)
+        self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+        self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Close|QtGui.QDialogButtonBox.Reset)
+        self.buttonBox.setObjectName("buttonBox")
+        self.verticalLayout.addWidget(self.buttonBox)
+        self.gridLayout.addLayout(self.verticalLayout,0,0,1,1)
+
+        self.retranslateUi(LogWindow)
+        QtCore.QObject.connect(self.buttonBox,QtCore.SIGNAL("accepted()"),LogWindow.accept)
+        QtCore.QObject.connect(self.buttonBox,QtCore.SIGNAL("rejected()"),LogWindow.reject)
+        QtCore.QMetaObject.connectSlotsByName(LogWindow)
+
+    def retranslateUi(self, LogWindow):
+        LogWindow.setWindowTitle(QtGui.QApplication.translate("LogWindow", "OVSDB Monitor Log", None, QtGui.QApplication.UnicodeUTF8))
+
diff --git a/ovsdb/ovsdbmonitor/Ui_MainWindow.py b/ovsdb/ovsdbmonitor/Ui_MainWindow.py
new file mode 100644 (file)
index 0000000..393fc7f
--- /dev/null
@@ -0,0 +1,207 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'MainWindow.ui'
+#
+# Created: Fri May  7 17:20:33 2010
+#      by: PyQt4 UI code generator 4.4.2
+#
+# WARNING! All changes made in this file will be lost!
+
+try:
+    from OVEStandard import globalForcePySide
+    if globalForcePySide: raise Exception()
+    from PyQt4 import QtCore, QtGui
+except:
+    from PySide import QtCore, QtGui
+
+class Ui_MainWindow(object):
+    def setupUi(self, MainWindow):
+        MainWindow.setObjectName("MainWindow")
+        MainWindow.resize(800,600)
+        self.centralwidget = QtGui.QWidget(MainWindow)
+        self.centralwidget.setObjectName("centralwidget")
+        self.gridLayout = QtGui.QGridLayout(self.centralwidget)
+        self.gridLayout.setObjectName("gridLayout")
+        self.verticalLayout = QtGui.QVBoxLayout()
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.tabWidget = QtGui.QTabWidget(self.centralwidget)
+        self.tabWidget.setObjectName("tabWidget")
+        self.Bridge = QtGui.QWidget()
+        self.Bridge.setObjectName("Bridge")
+        self.gridLayout_2 = QtGui.QGridLayout(self.Bridge)
+        self.gridLayout_2.setObjectName("gridLayout_2")
+        self.BridgeTable = QtGui.QTableWidget(self.Bridge)
+        self.BridgeTable.setObjectName("BridgeTable")
+        self.gridLayout_2.addWidget(self.BridgeTable,0,0,1,1)
+        self.tabWidget.addTab(self.Bridge,"")
+        self.Controller = QtGui.QWidget()
+        self.Controller.setObjectName("Controller")
+        self.gridLayout_3 = QtGui.QGridLayout(self.Controller)
+        self.gridLayout_3.setObjectName("gridLayout_3")
+        self.ControllerTable = QtGui.QTableWidget(self.Controller)
+        self.ControllerTable.setObjectName("ControllerTable")
+        self.gridLayout_3.addWidget(self.ControllerTable,0,0,1,1)
+        self.tabWidget.addTab(self.Controller,"")
+        self.Interface = QtGui.QWidget()
+        self.Interface.setObjectName("Interface")
+        self.gridLayout_4 = QtGui.QGridLayout(self.Interface)
+        self.gridLayout_4.setObjectName("gridLayout_4")
+        self.InterfaceTable = QtGui.QTableWidget(self.Interface)
+        self.InterfaceTable.setObjectName("InterfaceTable")
+        self.gridLayout_4.addWidget(self.InterfaceTable,0,0,1,1)
+        self.tabWidget.addTab(self.Interface,"")
+        self.Mirror = QtGui.QWidget()
+        self.Mirror.setObjectName("Mirror")
+        self.gridLayout_5 = QtGui.QGridLayout(self.Mirror)
+        self.gridLayout_5.setObjectName("gridLayout_5")
+        self.MirrorTable = QtGui.QTableWidget(self.Mirror)
+        self.MirrorTable.setObjectName("MirrorTable")
+        self.gridLayout_5.addWidget(self.MirrorTable,0,0,1,1)
+        self.tabWidget.addTab(self.Mirror,"")
+        self.NetFlow = QtGui.QWidget()
+        self.NetFlow.setObjectName("NetFlow")
+        self.gridLayout_6 = QtGui.QGridLayout(self.NetFlow)
+        self.gridLayout_6.setObjectName("gridLayout_6")
+        self.NetFlowTable = QtGui.QTableWidget(self.NetFlow)
+        self.NetFlowTable.setObjectName("NetFlowTable")
+        self.gridLayout_6.addWidget(self.NetFlowTable,0,0,1,1)
+        self.tabWidget.addTab(self.NetFlow,"")
+        self.Open_vSwitch = QtGui.QWidget()
+        self.Open_vSwitch.setObjectName("Open_vSwitch")
+        self.gridLayout_7 = QtGui.QGridLayout(self.Open_vSwitch)
+        self.gridLayout_7.setObjectName("gridLayout_7")
+        self.Open_vSwitchTable = QtGui.QTableWidget(self.Open_vSwitch)
+        self.Open_vSwitchTable.setObjectName("Open_vSwitchTable")
+        self.gridLayout_7.addWidget(self.Open_vSwitchTable,0,0,1,1)
+        self.tabWidget.addTab(self.Open_vSwitch,"")
+        self.Port = QtGui.QWidget()
+        self.Port.setObjectName("Port")
+        self.gridLayout_8 = QtGui.QGridLayout(self.Port)
+        self.gridLayout_8.setObjectName("gridLayout_8")
+        self.PortTable = QtGui.QTableWidget(self.Port)
+        self.PortTable.setObjectName("PortTable")
+        self.gridLayout_8.addWidget(self.PortTable,0,0,1,1)
+        self.tabWidget.addTab(self.Port,"")
+        self.sFlow = QtGui.QWidget()
+        self.sFlow.setObjectName("sFlow")
+        self.gridLayout_9 = QtGui.QGridLayout(self.sFlow)
+        self.gridLayout_9.setObjectName("gridLayout_9")
+        self.sFlowTable = QtGui.QTableWidget(self.sFlow)
+        self.sFlowTable.setObjectName("sFlowTable")
+        self.gridLayout_9.addWidget(self.sFlowTable,0,0,1,1)
+        self.tabWidget.addTab(self.sFlow,"")
+        self.SSL = QtGui.QWidget()
+        self.SSL.setObjectName("SSL")
+        self.gridLayout_10 = QtGui.QGridLayout(self.SSL)
+        self.gridLayout_10.setObjectName("gridLayout_10")
+        self.SSLTable = QtGui.QTableWidget(self.SSL)
+        self.SSLTable.setObjectName("SSLTable")
+        self.gridLayout_10.addWidget(self.SSLTable,0,0,1,1)
+        self.tabWidget.addTab(self.SSL,"")
+        self.verticalLayout.addWidget(self.tabWidget)
+        self.horizontalLayout = QtGui.QHBoxLayout()
+        self.horizontalLayout.setObjectName("horizontalLayout")
+        self.hostLabel = QtGui.QLabel(self.centralwidget)
+        self.hostLabel.setObjectName("hostLabel")
+        self.horizontalLayout.addWidget(self.hostLabel)
+        self.hostComboBox = QtGui.QComboBox(self.centralwidget)
+        self.hostComboBox.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
+        self.hostComboBox.setObjectName("hostComboBox")
+        self.horizontalLayout.addWidget(self.hostComboBox)
+        self.intervalCheckBox = QtGui.QCheckBox(self.centralwidget)
+        self.intervalCheckBox.setObjectName("intervalCheckBox")
+        self.horizontalLayout.addWidget(self.intervalCheckBox)
+        self.intervalSpinBox = QtGui.QSpinBox(self.centralwidget)
+        self.intervalSpinBox.setMinimum(1)
+        self.intervalSpinBox.setMaximum(1000000)
+        self.intervalSpinBox.setObjectName("intervalSpinBox")
+        self.horizontalLayout.addWidget(self.intervalSpinBox)
+        spacerItem = QtGui.QSpacerItem(40,20,QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Minimum)
+        self.horizontalLayout.addItem(spacerItem)
+        self.fetchButton = QtGui.QPushButton(self.centralwidget)
+        self.fetchButton.setObjectName("fetchButton")
+        self.horizontalLayout.addWidget(self.fetchButton)
+        self.verticalLayout.addLayout(self.horizontalLayout)
+        self.gridLayout.addLayout(self.verticalLayout,0,0,1,1)
+        MainWindow.setCentralWidget(self.centralwidget)
+        self.menubar = QtGui.QMenuBar(MainWindow)
+        self.menubar.setGeometry(QtCore.QRect(0,0,800,28))
+        self.menubar.setObjectName("menubar")
+        self.menuFile = QtGui.QMenu(self.menubar)
+        self.menuFile.setObjectName("menuFile")
+        MainWindow.setMenuBar(self.menubar)
+        self.statusbar = QtGui.QStatusBar(MainWindow)
+        self.statusbar.setObjectName("statusbar")
+        MainWindow.setStatusBar(self.statusbar)
+        self.actionShow_Log = QtGui.QAction(MainWindow)
+        self.actionShow_Log.setObjectName("actionShow_Log")
+        self.actionNew_DB_Window = QtGui.QAction(MainWindow)
+        self.actionNew_DB_Window.setObjectName("actionNew_DB_Window")
+        self.actionPreferences = QtGui.QAction(MainWindow)
+        self.actionPreferences.setObjectName("actionPreferences")
+        self.actionQuit = QtGui.QAction(MainWindow)
+        self.actionQuit.setObjectName("actionQuit")
+        self.actionNew_Flow_Window = QtGui.QAction(MainWindow)
+        self.actionNew_Flow_Window.setObjectName("actionNew_Flow_Window")
+        self.menuFile.addAction(self.actionNew_DB_Window)
+        self.menuFile.addAction(self.actionNew_Flow_Window)
+        self.menuFile.addAction(self.actionShow_Log)
+        self.menuFile.addAction(self.actionPreferences)
+        self.menuFile.addSeparator()
+        self.menuFile.addAction(self.actionQuit)
+        self.menubar.addAction(self.menuFile.menuAction())
+        self.hostLabel.setBuddy(self.hostComboBox)
+
+        self.retranslateUi(MainWindow)
+        self.tabWidget.setCurrentIndex(0)
+        QtCore.QMetaObject.connectSlotsByName(MainWindow)
+
+    def retranslateUi(self, MainWindow):
+        MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "OVSDB Monitor", None, QtGui.QApplication.UnicodeUTF8))
+        self.BridgeTable.clear()
+        self.BridgeTable.setColumnCount(0)
+        self.BridgeTable.setRowCount(0)
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.Bridge), QtGui.QApplication.translate("MainWindow", "Bridge", None, QtGui.QApplication.UnicodeUTF8))
+        self.ControllerTable.clear()
+        self.ControllerTable.setColumnCount(0)
+        self.ControllerTable.setRowCount(0)
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.Controller), QtGui.QApplication.translate("MainWindow", "Controller", None, QtGui.QApplication.UnicodeUTF8))
+        self.InterfaceTable.clear()
+        self.InterfaceTable.setColumnCount(0)
+        self.InterfaceTable.setRowCount(0)
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.Interface), QtGui.QApplication.translate("MainWindow", "Interface", None, QtGui.QApplication.UnicodeUTF8))
+        self.MirrorTable.clear()
+        self.MirrorTable.setColumnCount(0)
+        self.MirrorTable.setRowCount(0)
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.Mirror), QtGui.QApplication.translate("MainWindow", "Mirror", None, QtGui.QApplication.UnicodeUTF8))
+        self.NetFlowTable.clear()
+        self.NetFlowTable.setColumnCount(0)
+        self.NetFlowTable.setRowCount(0)
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.NetFlow), QtGui.QApplication.translate("MainWindow", "NetFlow", None, QtGui.QApplication.UnicodeUTF8))
+        self.Open_vSwitchTable.clear()
+        self.Open_vSwitchTable.setColumnCount(0)
+        self.Open_vSwitchTable.setRowCount(0)
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.Open_vSwitch), QtGui.QApplication.translate("MainWindow", "Open_vSwitch", None, QtGui.QApplication.UnicodeUTF8))
+        self.PortTable.clear()
+        self.PortTable.setColumnCount(0)
+        self.PortTable.setRowCount(0)
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.Port), QtGui.QApplication.translate("MainWindow", "Port", None, QtGui.QApplication.UnicodeUTF8))
+        self.sFlowTable.clear()
+        self.sFlowTable.setColumnCount(0)
+        self.sFlowTable.setRowCount(0)
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.sFlow), QtGui.QApplication.translate("MainWindow", "sFlow", None, QtGui.QApplication.UnicodeUTF8))
+        self.SSLTable.clear()
+        self.SSLTable.setColumnCount(0)
+        self.SSLTable.setRowCount(0)
+        self.tabWidget.setTabText(self.tabWidget.indexOf(self.SSL), QtGui.QApplication.translate("MainWindow", "SSL", None, QtGui.QApplication.UnicodeUTF8))
+        self.hostLabel.setText(QtGui.QApplication.translate("MainWindow", "Host", None, QtGui.QApplication.UnicodeUTF8))
+        self.intervalCheckBox.setText(QtGui.QApplication.translate("MainWindow", "Auto-refetch every", None, QtGui.QApplication.UnicodeUTF8))
+        self.intervalSpinBox.setSuffix(QtGui.QApplication.translate("MainWindow", "s", None, QtGui.QApplication.UnicodeUTF8))
+        self.fetchButton.setText(QtGui.QApplication.translate("MainWindow", "Refetch", None, QtGui.QApplication.UnicodeUTF8))
+        self.menuFile.setTitle(QtGui.QApplication.translate("MainWindow", "File", None, QtGui.QApplication.UnicodeUTF8))
+        self.actionShow_Log.setText(QtGui.QApplication.translate("MainWindow", "Show Log", None, QtGui.QApplication.UnicodeUTF8))
+        self.actionNew_DB_Window.setText(QtGui.QApplication.translate("MainWindow", "New DB Window", None, QtGui.QApplication.UnicodeUTF8))
+        self.actionPreferences.setText(QtGui.QApplication.translate("MainWindow", "Preferences", None, QtGui.QApplication.UnicodeUTF8))
+        self.actionQuit.setText(QtGui.QApplication.translate("MainWindow", "Quit", None, QtGui.QApplication.UnicodeUTF8))
+        self.actionNew_Flow_Window.setText(QtGui.QApplication.translate("MainWindow", "New Flow Window", None, QtGui.QApplication.UnicodeUTF8))
+
diff --git a/ovsdb/ovsdbmonitor/automake.mk b/ovsdb/ovsdbmonitor/automake.mk
new file mode 100644 (file)
index 0000000..b78920f
--- /dev/null
@@ -0,0 +1,48 @@
+ovsdbmonitor_pyfiles = \
+       ovsdb/ovsdbmonitor/OVEApp.py \
+       ovsdb/ovsdbmonitor/OVECommonWindow.py \
+       ovsdb/ovsdbmonitor/OVEConfig.py \
+       ovsdb/ovsdbmonitor/OVEConfigWindow.py \
+       ovsdb/ovsdbmonitor/OVEFetch.py \
+       ovsdb/ovsdbmonitor/OVEFlowWindow.py \
+       ovsdb/ovsdbmonitor/OVEHostWindow.py \
+       ovsdb/ovsdbmonitor/OVELogWindow.py \
+       ovsdb/ovsdbmonitor/OVELogger.py \
+       ovsdb/ovsdbmonitor/OVEMainWindow.py \
+       ovsdb/ovsdbmonitor/OVEStandard.py \
+       ovsdb/ovsdbmonitor/OVEUtil.py \
+       ovsdb/ovsdbmonitor/Ui_ConfigWindow.py \
+       ovsdb/ovsdbmonitor/Ui_FlowWindow.py \
+       ovsdb/ovsdbmonitor/Ui_HostWindow.py \
+       ovsdb/ovsdbmonitor/Ui_LogWindow.py \
+       ovsdb/ovsdbmonitor/Ui_MainWindow.py \
+       ovsdb/ovsdbmonitor/qt4reactor.py
+EXTRA_DIST += \
+       $(ovsdbmonitor_pyfiles) \
+       ovsdb/ovsdbmonitor/ConfigWindow.ui \
+       ovsdb/ovsdbmonitor/FlowWindow.ui \
+       ovsdb/ovsdbmonitor/HostWindow.ui \
+       ovsdb/ovsdbmonitor/LogWindow.ui \
+       ovsdb/ovsdbmonitor/MainWindow.ui \
+       ovsdb/ovsdbmonitor/ovsdbmonitor.in
+
+ovsdbmonitordir = ${pkgdatadir}/ovsdbmonitor
+if BUILD_OVSDBMONITOR
+noinst_SCRIPTS += ovsdb/ovsdbmonitor/ovsdbmonitor
+ovsdbmonitor_DATA = $(ovsdbmonitor_pyfiles)
+install-exec-local:
+       sed -e '/NOINSTALL/d' < ovsdb/ovsdbmonitor/ovsdbmonitor > ovsdbmonitor.tmp
+       chmod +x ovsdbmonitor.tmp
+       $(INSTALL_PROGRAM) ovsdbmonitor.tmp $(DESTDIR)$(bindir)/ovsdbmonitor
+endif
+
+SUFFIXES += .ui .py
+.ui.py:
+       $(PYUIC4) $< | sed 's/from PyQt4 import QtCore, QtGui/\
+try:\
+    from OVEStandard import globalForcePySide\
+    if globalForcePySide:\
+        raise Exception()\
+    from PyQt4 import QtCore, QtGui\
+except:\
+    from PySide import QtCore, QtGui/' > $@
diff --git a/ovsdb/ovsdbmonitor/ovsdbmonitor.in b/ovsdb/ovsdbmonitor/ovsdbmonitor.in
new file mode 100755 (executable)
index 0000000..e26130a
--- /dev/null
@@ -0,0 +1,39 @@
+#! @PYTHON@
+# Copyright (c) 2010 Citrix 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.
+#
+# Version 1.51
+# 2010-05-07
+
+import sys
+sys.path.insert(0, "@ovsdbmonitordir@")
+sys.path.insert(0, "@abs_top_srcdir@/ovsdb/ovsdbmonitor") # NOINSTALL
+
+import sys, traceback
+from pprint import pprint
+from OVEApp import *
+
+app = OVEApp()
+try:
+    retVal = app.enter()
+except Exception, e:
+    print str(e)
+    try:
+        trace = traceback.format_tb(sys.exc_info()[2])
+    except:
+        trace = ['Traceback not available']
+    print("".join(trace))
+    retVal = 1
+
+sys.exit(retVal)
diff --git a/ovsdb/ovsdbmonitor/ovsdbmonitor.py.in b/ovsdb/ovsdbmonitor/ovsdbmonitor.py.in
new file mode 100755 (executable)
index 0000000..29057f1
--- /dev/null
@@ -0,0 +1,37 @@
+#! @PYTHON@
+# Copyright (c) 2010 Citrix 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.
+#
+# Version 1.51
+# 2010-05-07
+
+sys.path.insert(0, "@ovsdbmonitordir@")
+
+import sys, traceback
+from pprint import pprint
+from OVEApp import *
+
+app = OVEApp()
+try:
+    retVal = app.enter()
+except Exception, e:
+    print str(e)
+    try:
+        trace = traceback.format_tb(sys.exc_info()[2])
+    except:
+        trace = ['Traceback not available']
+    print("".join(trace))
+    retVal = 1
+
+sys.exit(retVal)
diff --git a/ovsdb/ovsdbmonitor/qt4reactor.py b/ovsdb/ovsdbmonitor/qt4reactor.py
new file mode 100644 (file)
index 0000000..1379da7
--- /dev/null
@@ -0,0 +1,331 @@
+# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+# The referred licence file contains:
+# 
+#Copyright (c) 2001-2010
+#Allen Short
+#Andy Gayton
+#Andrew Bennetts
+#Antoine Pitrou
+#Apple Computer, Inc.
+#Benjamin Bruheim
+#Bob Ippolito
+#Canonical Limited
+#Christopher Armstrong
+#David Reid
+#Donovan Preston
+#Eric Mangold
+#Eyal Lotem
+#Itamar Shtull-Trauring
+#James Knight
+#Jason A. Mobarak
+#Jean-Paul Calderone
+#Jessica McKellar
+#Jonathan Jacobs
+#Jonathan Lange
+#Jonathan D. Simms
+#Jurgen Hermann
+#Kevin Horn
+#Kevin Turner
+#Mary Gardiner
+#Matthew Lefkowitz
+#Massachusetts Institute of Technology
+#Moshe Zadka
+#Paul Swartz
+#Pavel Pergamenshchik
+#Ralph Meijer
+#Sean Riley
+#Software Freedom Conservancy
+#Travis B. Hartwell
+#Thijs Triemstra
+#Thomas Herve
+#Timothy Allen
+#
+#Permission is hereby granted, free of charge, to any person obtaining
+#a copy of this software and associated documentation files (the
+#"Software"), to deal in the Software without restriction, including
+#without limitation the rights to use, copy, modify, merge, publish,
+#distribute, sublicense, and/or sell copies of the Software, and to
+#permit persons to whom the Software is furnished to do so, subject to
+#the following conditions:
+#
+#The above copyright notice and this permission notice shall be
+#included in all copies or substantial portions of the Software.
+#
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+#EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+#MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+#NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+#LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+#OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+#WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""
+This module provides support for Twisted to be driven by the Qt mainloop.
+
+In order to use this support, simply do the following::
+    |  app = QApplication(sys.argv) # your code to init Qt
+    |  import qt4reactor
+    |  qt4reactor.install()
+    
+alternatively:
+
+    |  from twisted.application import reactors
+    |  reactors.installReactor('qt4')
+
+Then use twisted.internet APIs as usual.  The other methods here are not
+intended to be called directly.
+
+If you don't instantiate a QApplication or QCoreApplication prior to
+installing the reactor, a QCoreApplication will be constructed
+by the reactor.  QCoreApplication does not require a GUI so trial testing
+can occur normally.
+
+Twisted can be initialized after QApplication.exec_() with a call to
+reactor.runReturn().  calling reactor.stop() will unhook twisted but
+leave your Qt application running
+
+API Stability: stable
+
+Maintainer: U{Glenn H Tarbox, PhD<mailto:glenn@tarbox.org>}
+
+Previous maintainer: U{Itamar Shtull-Trauring<mailto:twisted@itamarst.org>}
+Original port to QT4: U{Gabe Rudy<mailto:rudy@goldenhelix.com>}
+Subsequent port by therve
+"""
+
+__all__ = ['install']
+
+
+import sys, time
+
+try:
+    from zope.interface import implements
+except:
+    print('+++ Python Zope interface module is required\n')
+    raise
+
+
+try:
+    from OVEStandard import globalForcePySide
+    if globalForcePySide: raise Exception()
+    from PyQt4.QtCore import QSocketNotifier, QObject, SIGNAL, QTimer, QCoreApplication
+    from PyQt4.QtCore import QEventLoop
+except:
+    from PySide.QtCore import QSocketNotifier, QObject, SIGNAL, QTimer, QCoreApplication
+    from PySide.QtCore import QEventLoop
+
+try:
+    from twisted.internet.interfaces import IReactorFDSet
+    from twisted.python import log
+    from twisted.internet.posixbase import PosixReactorBase
+except:
+    print('+++ Python Twisted Conch module is required\n')
+    raise
+    
+class TwistedSocketNotifier(QSocketNotifier):
+    """
+    Connection between an fd event and reader/writer callbacks.
+    """
+
+    def __init__(self, reactor, watcher, type):
+        QSocketNotifier.__init__(self, watcher.fileno(), type)
+        self.reactor = reactor
+        self.watcher = watcher
+        self.fn = None
+        if type == QSocketNotifier.Read:
+            self.fn = self.read
+        elif type == QSocketNotifier.Write:
+            self.fn = self.write
+        QObject.connect(self, SIGNAL("activated(int)"), self.fn)
+
+
+    def shutdown(self):
+        QObject.disconnect(self, SIGNAL("activated(int)"), self.fn)
+        self.setEnabled(False)
+        self.fn = self.watcher = None
+        self.deleteLater()
+
+
+    def read(self, sock):
+        w = self.watcher
+        #self.setEnabled(False)    # ??? do I need this?            
+        def _read():
+            why = None
+            try:
+                why = w.doRead()
+            except:
+                log.err()
+                why = sys.exc_info()[1]
+            if why:
+                self.reactor._disconnectSelectable(w, why, True)
+            elif self.watcher:
+                pass
+                #self.setEnabled(True)
+        log.callWithLogger(w, _read)
+        self.reactor.reactorInvocation()
+
+    def write(self, sock):
+        w = self.watcher
+        self.setEnabled(False)
+        def _write():
+            why = None
+            try:
+                why = w.doWrite()
+            except:
+                log.err()
+                why = sys.exc_info()[1]
+            if why:
+                self.reactor._disconnectSelectable(w, why, False)
+            elif self.watcher:
+                self.setEnabled(True)
+        log.callWithLogger(w, _write)
+        self.reactor.reactorInvocation()
+
+class fakeApplication(QEventLoop):
+    def __init__(self):
+        QEventLoop.__init__(self)
+        
+    def exec_(self):
+        QEventLoop.exec_(self)
+        
+class QTReactor(PosixReactorBase):
+    """
+    Qt based reactor.
+    """
+    implements(IReactorFDSet)
+
+    _timer = None
+
+    def __init__(self):
+        self._reads = {}
+        self._writes = {}
+        self._timer=QTimer()
+        self._timer.setSingleShot(True)
+        if QCoreApplication.startingUp():
+            self.qApp=QCoreApplication([])
+            self._ownApp=True
+        else:
+            self.qApp = QCoreApplication.instance()
+            self._ownApp=False
+        self._blockApp = None
+        self._readWriteQ=[]
+        
+        """ some debugging instrumentation """
+        self._doSomethingCount=0
+        
+        PosixReactorBase.__init__(self)
+
+    def addReader(self, reader):
+        if not reader in self._reads:
+            self._reads[reader] = TwistedSocketNotifier(self, reader,
+                                                       QSocketNotifier.Read)
+
+
+    def addWriter(self, writer):
+        if not writer in self._writes:
+            self._writes[writer] = TwistedSocketNotifier(self, writer,
+                                                        QSocketNotifier.Write)
+
+
+    def removeReader(self, reader):
+        if reader in self._reads:
+            #self._reads[reader].shutdown()
+            #del self._reads[reader]
+            self._reads.pop(reader).shutdown()
+
+    def removeWriter(self, writer):
+        if writer in self._writes:
+            self._writes[writer].shutdown()
+            #del self._writes[writer]
+            self._writes.pop(writer)
+
+
+    def removeAll(self):
+        return self._removeAll(self._reads, self._writes)
+
+
+    def getReaders(self):
+        return self._reads.keys()
+
+
+    def getWriters(self):
+        return self._writes.keys()
+    
+    def callLater(self,howlong, *args, **kargs):
+        rval = super(QTReactor,self).callLater(howlong, *args, **kargs)
+        self.reactorInvocation()
+        return rval
+    
+    def crash(self):
+        super(QTReactor,self).crash()
+        
+    def iterate(self,delay=0.0):
+        t=self.running # not sure I entirely get the state of running
+        self.running=True
+        self._timer.stop() # in case its not (rare?)
+        try:
+            if delay == 0.0:
+                self.reactorInvokePrivate()
+                self._timer.stop() # supports multiple invocations
+            else:
+                endTime = delay + time.time()
+                self.reactorInvokePrivate()
+                while True:
+                    t = endTime - time.time()
+                    if t <= 0.0: return
+                    self.qApp.processEvents(QEventLoop.AllEvents | 
+                                      QEventLoop.WaitForMoreEvents,t*1010)
+        finally:
+            self.running=t
+            
+    def addReadWrite(self,t):
+        self._readWriteQ.append(t)
+        
+    def runReturn(self, installSignalHandlers=True):
+        QObject.connect(self._timer, SIGNAL("timeout()"), 
+                        self.reactorInvokePrivate)
+        self.startRunning(installSignalHandlers=installSignalHandlers)
+        self._timer.start(0)
+        
+    def run(self, installSignalHandlers=True):
+        try:
+            if self._ownApp:
+                self._blockApp=self.qApp
+            else:
+                self._blockApp = fakeApplication()
+            self.runReturn(installSignalHandlers)
+            self._blockApp.exec_()
+        finally:
+            self._timer.stop() # should already be stopped
+
+    def reactorInvocation(self):
+        self._timer.setInterval(0)
+        
+    def reactorInvokePrivate(self):
+        if not self.running:
+            if self._blockApp is None:
+                # Andy's fix for Ctrl-C quit
+                self.qApp.quit()
+            else:
+                self._blockApp.quit()
+        self._doSomethingCount += 1
+        self.runUntilCurrent()
+        t = self.timeout()
+        if t is None: t=0.1
+        else: t = min(t,0.1)
+        self._timer.setInterval(int(t*1010))
+        self.qApp.processEvents() # could change interval
+        self._timer.start()
+                
+    def doIteration(self):
+        assert False, "doiteration is invalid call"
+            
+def install():
+    """
+    Configure the twisted mainloop to be run inside the qt mainloop.
+    """
+    from twisted.internet import main
+    reactor = QTReactor()
+    main.installReactor(reactor)