With Makefiles and Autoconfiscation by Ben Pfaff.
Signed-off-by: Thomas Lacroix <thomas.lacroix@citrix.com>
Signed-off-by: Ben Pfaff <blp@nicira.com>
- 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
-------------------------
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
==============================================
-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; \
* 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:
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],
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
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])])
noinst_SCRIPTS += ovsdb/ovsdb-doc
DISTCLEANFILES += ovsdb/ovsdb-doc
OVSDB_DOC = $(PYTHON) $(srcdir)/ovsdb/ovsdb-doc.in
+
+include ovsdb/ovsdbmonitor/automake.mk
--- /dev/null
+/ovsdbmonitor.py
--- /dev/null
+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.
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+# 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
--- /dev/null
+# 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()
+
--- /dev/null
+# 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()"))
--- /dev/null
+# 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()
+
+
--- /dev/null
+# 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')
--- /dev/null
+# 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)
--- /dev/null
+# 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())
+ }
+
--- /dev/null
+# 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()
--- /dev/null
+# 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)
+
--- /dev/null
+# 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)
--- /dev/null
+# 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
+
--- /dev/null
+# 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)]
--- /dev/null
+# -*- 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))
+
--- /dev/null
+# -*- 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))
+
--- /dev/null
+# -*- 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))
+
--- /dev/null
+# -*- 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))
+
--- /dev/null
+# -*- 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))
+
--- /dev/null
+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/' > $@
--- /dev/null
+#! @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)
--- /dev/null
+#! @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)
--- /dev/null
+# 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)