Global replace of Nicira Networks.
[sliver-openvswitch.git] / ovsdb / ovsdbmonitor / OVEFlowWindow.py
1 # Copyright (c) 2011 Nicira, Inc.
2 # Copyright (c) 2010 Citrix Systems, Inc.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 from OVEStandard import *
17 from OVEConfig import *
18 from OVEFetch import *
19 from OVELogger import *
20 from OVEUtil import *
21
22 from OVECommonWindow import *
23
24 from Ui_FlowWindow import *
25
26 import re
27
28 class OVEFlowWindow(QtGui.QMainWindow, OVECommonWindow):
29     LOAD_KEY = 'FlowWindow/window'
30     COMMAND_OVS_DPCTL='/usr/bin/ovs-dpctl'
31     BASE_REF=200000
32     
33     def __init__(self, app, loadIndex = None):
34         QtGui.QMainWindow.__init__(self)
35         self.ui = Ui_FlowWindow()
36         self.dpNames = []
37         self.dpTables = []
38         self.currentOpIndex = None
39         self.resizeCount = []
40         self.ssgChecked = False
41         self.ssgText = ''
42         self.lastTime = None
43         self.lastByteCount = 0
44         OVECommonWindow.__init__(self, app, loadIndex)
45
46         self.updateSsgList()
47         self.updateDatapaths()
48         self.updateSsgState()
49
50         self.connect(self.ui.fetchPathsButton, QtCore.SIGNAL("clicked()"), self.xon_fetchPathsButton_clicked)
51         self.connect(self.ui.ssgSaveButton, QtCore.SIGNAL("clicked()"), self.xon_ssgSaveButton_clicked)
52         self.connect(self.ui.ssgDeleteButton, QtCore.SIGNAL("clicked()"), self.xon_ssgDeleteButton_clicked)
53         self.connect(self.ui.ssgComboBox, QtCore.SIGNAL("activated(int)"), self.xon_ssgComboBox_activated)
54         self.connect(self.ui.ssgComboBox, QtCore.SIGNAL("editTextChanged(QString)"), self.xon_ssgComboBox_editTextChanged)
55         self.connect(self.ui.ssgCheckBox, QtCore.SIGNAL("stateChanged(int)"), self.xon_ssgCheckBox_stateChanged)
56         
57     
58     def xon_fetchPathsButton_clicked(self):
59         self.updateDatapaths()
60     
61     def xon_hostComboBox_currentIndexChanged(self, index):
62         OVECommonWindow.xon_hostComboBox_currentIndexChanged(self, index)
63         if (index >= 0):
64             self.updateDatapaths()
65     
66     def xon_ssgSaveButton_clicked(self):
67         if self.ssgText not in OVEConfig.Inst().ssgList:
68             OVEConfig.Inst().ssgList.append(self.ssgText)
69             OVEConfig.Inst().saveConfig()
70         self.updateSsgList()
71         
72     def updateSsgList(self):
73         currentSsgText = self.ssgText
74         self.ui.ssgComboBox.clear()
75         isFound = False
76         for i, ssgText in enumerate(OVEConfig.Inst().ssgList):
77             self.ui.ssgComboBox.addItem(ssgText)
78             if ssgText == currentSsgText:
79                 # This is the currently selected item
80                 self.ui.ssgComboBox.setCurrentIndex(i)
81                 isFound = True
82         
83         if not isFound:
84             self.ui.ssgComboBox.setCurrentIndex(-1)
85             self.ui.ssgComboBox.lineEdit().setText(currentSsgText)
86
87     def xon_ssgDeleteButton_clicked(self):
88         if self.ssgText in OVEConfig.Inst().ssgList:
89             OVEConfig.Inst().ssgList.remove(self.ssgText)
90             self.ssgText = ''
91             OVEConfig.Inst().saveConfig()
92         self.updateSsgList()
93
94     def xon_ssgComboBox_activated(self, index):
95         if (index >= 0):
96             itemData = self.ui.ssgComboBox.itemText(index)
97             self.ssgText = str(itemData)
98             self.updateTable()
99     
100     def xon_ssgComboBox_editTextChanged(self, text):
101         self.ssgText = str(text)
102         self.statusBar().showMessage('Remote command is: '+self.updateCommand())
103         present = (self.ssgText in OVEConfig.Inst().ssgList)
104         self.ui.ssgDeleteButton.setEnabled(present)
105         self.ui.ssgSaveButton.setEnabled(not present)
106     
107     def xon_ssgCheckBox_stateChanged(self, state):
108         self.ssgChecked = (state == Qt.Checked)
109         self.updateTable()
110     
111     def xon_configUpdated(self):
112         OVECommonWindow.xon_configUpdated(self)
113         self.updateSsgList()
114         self.updateDatapaths()
115     
116     def timerEvent(self, event):
117         OVECommonWindow.timerEvent(self, event)
118
119     def customEvent(self, event):
120         OVECommonWindow.customEvent(self, event)
121     
122     def updateDatapaths(self):
123         if self.hostUuid == '':
124             self.statusBar().showMessage('No host selected')
125         else:
126             self.currentRef += 1
127             self.currentOp = 'dump-dps'
128             command = self.COMMAND_OVS_DPCTL+' dump-dps'
129             OVEFetch.Inst(self.hostUuid).execCommandFramed(self, self.currentRef, command)
130     
131     def rebuildTables(self):
132         self.ui.tabWidget.clear() # Let the garbage collector delete the pages
133         self.dpTables = []
134         self.dpFlows = []
135         self.resizeCount = []
136         headings = OVEUtil.flowDecodeHeadings()
137         
138         for dpName in self.dpNames:
139             pageWidget = QtGui.QWidget()
140             pageWidget.setObjectName(dpName+'_page')
141             gridLayout = QtGui.QGridLayout(pageWidget)
142             gridLayout.setObjectName(dpName+"_gridLayout")
143             table = QtGui.QTableWidget(pageWidget)
144             table.setObjectName(dpName+"_table")
145             table.setColumnCount(len(headings))
146             table.setRowCount(0)
147             gridLayout.addWidget(table, 0, 0, 1, 1)
148             self.dpTables.append(table)
149             self.ui.tabWidget.addTab(pageWidget, dpName)
150             self.dpFlows.append([])
151             self.resizeCount.append(0)
152             for i, heading in enumerate(headings):
153                 table.setHorizontalHeaderItem(i, QtGui.QTableWidgetItem(heading))
154
155             table.setSortingEnabled(True)
156             
157             table.sortItems(OVEUtil.getFlowColumn('source mac'))
158             table.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
159     
160     def updateSsgState(self):
161         self.ui.ssgCheckBox.setChecked(self.ssgChecked)
162     
163     def updateCommand(self, overrideText = None):
164         command = self.COMMAND_OVS_DPCTL+' dump-flows '
165         if self.currentOpIndex is not None:
166             command += self.dpNames[self.currentOpIndex]
167         exp = None
168         if overrideText is not None:
169                 exp = overrideText
170         elif self.ssgChecked:
171                 exp = self.ssgText
172                 
173         if exp is not None:
174             opts='-E '
175             if exp.startswith('!'):
176                 exp =exp[1:]
177                 opts += '-v '
178             command += " | grep "+opts+"'"+exp+"' ; test ${PIPESTATUS[0]} -eq 0 "
179
180         return command
181     
182     def updateTable(self):
183         if self.hostUuid == '':
184             self.statusBar().showMessage('No host selected')
185             self.setWindowTitle('OVS Flows')
186         elif len(self.dpNames) > 0:
187             config = OVEConfig.Inst().hostFromUuid(self.hostUuid)
188             self.setWindowTitle('OVS Flows - '+config.get('address', ''))
189             try:
190                 self.setFetchSkip()
191                 self.statusBar().showMessage('Fetching data...') 
192                 self.currentRef += 1
193                 self.currentOp = 'dump-flows'
194                 self.currentOpIndex = self.ui.tabWidget.currentIndex()
195                 OVEFetch.Inst(self.hostUuid).execCommandFramed(self, self.currentRef, self.updateCommand())
196             except Exception, e:
197                 message = 'Update failed: '+str(e)
198                 OVELog(message)
199                 self.statusBar().showMessage(message)
200     
201     def writeCurrentTable(self):
202         index = self.ui.tabWidget.currentIndex()
203         actionsColumn = OVEUtil.getFlowColumn('actions')
204         usedColumn = OVEUtil.getFlowColumn('used')
205         srcMacColumn = OVEUtil.getFlowColumn('source mac')
206         destMacColumn = OVEUtil.getFlowColumn('destination mac')
207         srcIPColumn = OVEUtil.getFlowColumn('source ip')
208         destIPColumn = OVEUtil.getFlowColumn('destination ip')
209         inportColumn = OVEUtil.getFlowColumn('inport')
210         vlanColumn = OVEUtil.getFlowColumn('vlan')
211         bytesColumn = OVEUtil.getFlowColumn('bytes')
212         
213         byteCount = 0
214         try:
215             table = self.dpTables[index]
216             table.setUpdatesEnabled(False)
217             table.setSortingEnabled(False)
218             try:
219                 flows = self.dpFlows[index]
220                 table.setRowCount(len(flows))
221                 if len(flows) > 0:
222                     table.setColumnCount(len(flows[0]))
223                 for rowNum, flow in enumerate(flows):
224                     
225                     inport = flow[inportColumn]
226                     if flow[actionsColumn] == 'drop':
227                         baseLum=172
228                     else:
229                         baseLum=239
230                     background = QtGui.QColor(baseLum+16*(inport % 2), baseLum+8*(inport % 3), baseLum+4*(inport % 5))
231                     if flow[usedColumn] == 'never':
232                         colour = QtGui.QColor(112,112,112)
233                     else:
234                         colour = Qt.black
235                         
236                     for colNum, data in enumerate(flow):
237                         item = None
238                         try:
239                             item = table.takeItem(rowNum, colNum)
240                         except:
241                             pass
242                         if item is None:
243                             item = QtGui.QTableWidgetItem('')
244
245                         if colNum == vlanColumn:
246                             item.setBackground(QtGui.QColor(255-(10*data % 192), 255-((17*data) % 192), 255-((37*data) % 192)))
247                         elif colNum == srcMacColumn or colNum == destMacColumn:
248                             cols = [int(x, 16) for x in data.split(':')]
249                             item.setBackground(QtGui.QColor(255-cols[2]*cols[3] % 192, 255-cols[3]*cols[4] % 192, 255-cols[4]*cols[5] % 192))
250                         elif re.match(r'[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+', str(data)):
251                             cols = [int(x) for x in data.split('.')]
252                             item.setBackground(QtGui.QColor(255-cols[1]*cols[2] % 192, 255-cols[2]*cols[3] % 192, 255-cols[3]*cols[0] % 192))
253                         else:
254                             item.setBackground(background)
255                             item.setForeground(colour)
256                             
257                         if colNum == bytesColumn:
258                             byteCount += int(data)
259                             
260                         # PySide 0.2.3 fails to convert long ints to QVariants and logs 'long int too large to convert to int' errors
261                         try:
262                             item.setData(Qt.DisplayRole, QVariant(data))
263                             item.setToolTip(str(data))
264                         except Exception, e:
265                             item.setText('Error: See tooltip')
266                             item.setToolTip(str(e))
267                         table.setItem(rowNum, colNum, item)
268                 
269                 if self.resizeCount[index] < 2:
270                     self.resizeCount[index] += 1
271                     for i in range(0, table.columnCount()):
272                         table.resizeColumnToContents(i)
273
274             finally:
275                 table.setUpdatesEnabled(True)
276                 table.setSortingEnabled(True)
277
278             message = 'Updated at '+str(QtCore.QTime.currentTime().toString())
279             
280             if self.lastTime is not None:
281                 timeDiff = time.time() - self.lastTime
282                 byteDiff = byteCount - self.lastByteCount
283                 bitRate = long(8 * byteDiff / timeDiff)
284                 if abs(bitRate) < 10*2**20:
285                     message += ' ('+str(bitRate/2**10)+' kbit/s)'
286                 elif abs(bitRate) < 10*2**30:
287                     message += ' ('+str(bitRate/2**20)+' Mbit/s)'
288                 else:
289                     message += ' ('+str(bitRate/2**30)+' Gbit/s)'
290                 
291             self.lastByteCount = byteCount
292             self.lastTime = time.time()
293             if table.rowCount() == 0:
294                 message += ' - Table is empty'
295             self.statusBar().showMessage(message)
296
297         except Exception, e:
298             message = 'Table update failed: '+str(e)
299             OVELog(message)
300             self.statusBar().showMessage(message)
301
302     def handleFetchEvent(self, ref, values):
303         if self.currentOp == 'dump-dps':
304             self.dpNames =values.strip().split('\n')
305             self.rebuildTables()
306             self.updateTable()
307         elif self.currentOp == 'dump-flows':
308             self.dpFlows[self.currentOpIndex] = OVEUtil.decodeFlows(values)
309             self.writeCurrentTable()
310
311     def handleFetchFailEvent(self, ref, message):
312         self.statusBar().showMessage(message)
313         OVELog('Fetch ('+self.currentOp+') failed')
314
315     def customEvent(self, event):
316         OVECommonWindow.customEvent(self, event)
317         
318     def saveSettings(self, index):
319         settings, key = OVECommonWindow.saveSettings(self, index)
320         settings.setValue(key+"/ssgText", QVariant(self.ssgText))
321         settings.setValue(key+"/ssgChecked", QVariant(self.ssgChecked))
322         
323     def loadSettings(self, index):
324         settings, key = OVECommonWindow.loadSettings(self, index)
325         self.ssgText = str(settings.value(key+"/ssgText", QVariant('10\.80\.226\..*')).toString())
326         self.ssgChecked = settings.value(key+"/ssgChecked", QVariant(False)).toBool()
327         self.ssgRe = re.compile(self.ssgText)