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