def __init__(self, parent=None): QtGui.QGraphicsView.__init__(self, parent) self.parent = parent # topologyInterface exchanges json messages with monitoring server self.topologyInterface = TopologyInterface(self) self.topologyInterface.start() #asyncore.loop() self.setStyleSheet("background: black") self.topoScene = QtGui.QGraphicsScene(self) self.topoScene.setItemIndexMethod(QtGui.QGraphicsScene.NoIndex) self.topoScene.setSceneRect(-300, -300, 600, 600) self.setScene(self.topoScene) self.setCacheMode(QtGui.QGraphicsView.CacheBackground) self.setRenderHint(QtGui.QPainter.Antialiasing) self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse) self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter) self.drawAccess = 'Default' #(utilization/te/et/etc) self.scale(0.9, 0.9) self.setMinimumSize(400, 400) # Pan self.setDragMode(self.ScrollHandDrag) self.setCursor(QtCore.Qt.ArrowCursor) # Connect signals to slots self.topologyInterface.topology_received_signal[str].connect \ (self.got_topo_msg) self.updateAllSignal.connect(self.updateAll) # Dictionaries holding node and link QGraphicsItems self.nodes = {} self.links = {} # Get an initial current snapshot of the topology self.get_topology() # Subscribe to LAVI for topology changes self.subscribe_to_topo_changes()
class TopologyView(QtGui.QGraphicsView): updateAllSignal = QtCore.pyqtSignal() def __init__(self, parent=None): QtGui.QGraphicsView.__init__(self, parent) self.parent = parent # topologyInterface exchanges json messages with monitoring server self.topologyInterface = TopologyInterface(self) self.topologyInterface.start() #asyncore.loop() self.setStyleSheet("background: black") self.topoScene = QtGui.QGraphicsScene(self) self.topoScene.setItemIndexMethod(QtGui.QGraphicsScene.NoIndex) self.topoScene.setSceneRect(-300, -300, 600, 600) self.setScene(self.topoScene) self.setCacheMode(QtGui.QGraphicsView.CacheBackground) self.setRenderHint(QtGui.QPainter.Antialiasing) self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse) self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter) self.drawAccess = 'Default' #(utilization/te/et/etc) self.scale(0.9, 0.9) self.setMinimumSize(400, 400) # Pan self.setDragMode(self.ScrollHandDrag) self.setCursor(QtCore.Qt.ArrowCursor) # Connect signals to slots self.topologyInterface.topology_received_signal[str].connect \ (self.got_topo_msg) self.updateAllSignal.connect(self.updateAll) # Dictionaries holding node and link QGraphicsItems self.nodes = {} self.links = {} # Get an initial current snapshot of the topology self.get_topology() # Subscribe to LAVI for topology changes self.subscribe_to_topo_changes() def subscribe_to_topo_changes(self): ''' Subscribe to LAVI for topology changes ''' msg = {} msg["type"] = "lavi" msg["command"] = "subscribe" msg["node_type"] = "all" self.topologyInterface.send(msg) msg = {} msg["type"] = "lavi" msg["command"] = "subscribe" msg["link_type"] = "all" self.topologyInterface.send(msg) #(see what else. eg. link/node removals?) def get_nodes(self): ''' Ask lavi for an updated nodes set ''' queryMsg = {} queryMsg["type"] = "lavi" queryMsg["command"] = "request" queryMsg["node_type"] = "all" self.topologyInterface.send(queryMsg) def get_links(self): ''' Ask lavi for an updated links set ''' queryMsg = {} queryMsg["type"] = "lavi" queryMsg["command"] = "request" queryMsg["link_type"] = "all" self.topologyInterface.send(queryMsg) def get_topology(self): ''' Ask lavi for updated nodes and links sets ''' self.get_nodes() self.get_links() def got_topo_msg(self, msg): ''' Handle received links/nodes message ''' jsonmsg = json.loads(str(msg)) if "node_id" in jsonmsg: if jsonmsg["command"] == "add": nodes = jsonmsg["node_id"] new_nodes = [] # Populate nodes for nodeID in nodes: # prepend 0s until len = 12 "CHECK" while len(nodeID) < 12 : nodeID = "0"+nodeID # If nodeItem doesn't already exist if nodeID not in self.nodes.keys(): nodeItem = Node(self, nodeID) self.nodes[nodeID] = nodeItem new_nodes.append(nodeItem) self.addNodes(new_nodes) self.positionNodes(new_nodes) ''' elif jsonmsg["command"] == "delete": nodes = jsonmsg["node_id"] deleted_nodes = [] for nodeID in deleted_nodes: if int(nodeID) in self.nodes.keys(): print "deleting node", nodeID dpid = int(nodeID) del self.nodes[dpid] ''' elif "links" in msg: if jsonmsg["command"] == "add": links = jsonmsg["links"] new_links = [] # Populate Links linkid = len(self.links) for link in links: # If linkItem doesn't already exist # (stupid, expensive full match check as there is no linkID) exists = False for l in self.links.values(): if link["src id"]==l.source.id: if link["dst id"]==l.dest.id: if link["src port"]==l.sport: if link["dst port"]==l.dport: exists = True if exists: continue linkid = linkid+1 linkItem = Link(self,\ self.nodes[link["src id"]],\ self.nodes[link["dst id"]],\ link["src port"],\ link["dst port"],\ link["src type"],\ link["dst type"],\ linkid) self.links[linkItem.uid] = linkItem new_links.append(linkItem) self.addLinks(new_links) ''' elif jsonmsg["command"] == "delete": links = jsonmsg["links"] for link in links: print "deleting link" ''' self.updateAll() def addNodes(self, new_nodes): ''' Add nodes to topology Scene ''' for nodeItem in new_nodes: self.topoScene.addItem(nodeItem) def addLinks(self, new_links): ''' Add links to topology Scene ''' for linkItem in new_links: self.topoScene.addItem(linkItem) def positionNodes(self, new_nodes): ''' Position nodes according to current loaded layout (or random if none) ''' minX, maxX = -300, 300 minY, maxY = -200, 200 layout = self.parent.parent.settings.current_topo_layout print layout if layout == "random": for node in new_nodes: node.setPos(randint(minX,maxX), randint(minY,maxY)) else: ''' If node position is described in current layout file, choose that, otherwise place randomly ''' # Optimize: scan file into a dictionary. same for load. f = QtCore.QFile("gui/layouts/"+layout) f.open(QtCore.QIODevice.ReadOnly) for node in new_nodes: line = f.readLine() found = False while not line.isNull(): nodeid,x,y = str(line).split() line = f.readLine() if str(node.id) == nodeid: node.setPos(float(x), float(y)) found = True if not found: node.setPos(randint(minX,maxX), randint(minY,maxY)) f.close() def itemMoved(self): pass def disableAllLinks(self): for e in self.links.values(): e.bringLinkDown() e.update() def enableAllLinks(self): for e in self.links.values(): e.bringLinkUp() e.update() def disableAllNodes(self): for n in self.nodes.values(): n.bringSwitchDown() n.update() def enableAllNodes(self): for n in self.nodes.values(): n.bringSwitchUp() n.update() def updateAll(self): ''' Refresh all Items # see if there is a auto way to updateall (updateScene()?) ''' for n in self.nodes.values(): n.update() for e in self.links.values(): e.update() e.adjust() def keyPressEvent(self, event): ''' Topology View hotkeys ''' key = event.key() if key == QtCore.Qt.Key_Plus: self.scaleView(1.2) elif key == QtCore.Qt.Key_Minus: self.scaleView(1 / 1.2) elif key == QtCore.Qt.Key_N: self.toggleNodes() elif key == QtCore.Qt.Key_I: self.toggleNodeIDs() elif key == QtCore.Qt.Key_K: self.toggleLinks() elif key == QtCore.Qt.Key_L: # LAVI counts a biderctional link as 2 separate links, so IDs overlap self.toggleLinkIDs() self.updateAll() elif key == QtCore.Qt.Key_P: self.togglePorts() self.updateAll() elif key == QtCore.Qt.Key_H: self.toggleHosts() self.updateAll() elif key == QtCore.Qt.Key_R: # Refresh topology self.get_topology() elif key == QtCore.Qt.Key_Space or key == QtCore.Qt.Key_Enter: # Redraw topology self.positionNodes(self.nodes.values()) self.updateAll() else: QtGui.QGraphicsView.keyPressEvent(self, event) ''' Toggle display of drawn items ''' def toggleNodes(self): for node in self.nodes.values(): node.showNode = not node.showNode node.update() def toggleNodeIDs(self): for node in self.nodes.values(): node.showID = not node.showID node.update() def toggleLinks(self): for link in self.links.values(): link.showLink = not link.showLink link.update() def toggleLinkIDs(self): for link in self.links.values(): link.showID = not link.showID link.update() def togglePorts(self): for link in self.links.values(): link.showPorts = not link.showPorts link.update() def toggleHosts(self): for node in self.nodes.values(): if node.layer == 3: for l in node.linkList: l.showLink = not l.showLink l.update() node.showID = not node.showID node.showNode = not node.showNode node.update() def wheelEvent(self, event): ''' Zoom ''' self.scaleView(math.pow(2.0, event.delta() / 300.0)) def drawBackground(self, painter, rect): ''' Draw background. For now just some text ''' sceneRect = self.sceneRect() textRect = QtCore.QRectF(sceneRect.left() -5, sceneRect.top() + 60, sceneRect.width() - 4, sceneRect.height() - 4) message = self.tr("Topology") font = painter.font() font.setPointSize(12) painter.setFont(font) painter.setPen(QtCore.Qt.darkGray) painter.drawText(textRect.translated(0.8, 0.8), message) painter.setPen(QtCore.Qt.white) painter.setPen(QtGui.QColor(QtCore.Qt.gray).light(130)) painter.drawText(textRect, message) def scaleView(self, scaleFactor): factor = self.matrix().scale(scaleFactor, scaleFactor).mapRect(QtCore.QRectF(0, 0, 1, 1)).width() if factor < 0.07 or factor > 100: return self.scale(scaleFactor, scaleFactor) def mouseReleaseEvent(self, event): ''' Show context menu when right-clicking on empty space on the scene. ''' if not self.itemAt(event.pos()): if event.button() == QtCore.Qt.RightButton: popup = QtGui.QMenu() popup.addAction("Save Layout", self.save_layout) popup.addAction("Load Layout", self.load_layout) popup.addAction("Refresh Topology", self.get_topology) popup.exec_(event.globalPos()) QtGui.QGraphicsView.mouseReleaseEvent(self, event) def save_layout(self): ''' Saves the current node positioning ''' title = "Specify file to store topology layout" filename = QtGui.QFileDialog.getSaveFileName(self,title,"gui/layouts") f = QtCore.QFile(filename) f.open(QtCore.QIODevice.WriteOnly) for node in self.nodes.values(): line = QtCore.QByteArray(str(node.id)+" "+\ str(round(int(node.x()),-1))+" "+\ str(round(int(node.y()),-1))+" \n") f.write(line) f.close() layout = str(filename).split("/") layout = layout[len(layout)-1] self.parent.parent.settings.set_current_topo_layout(layout) def load_layout(self): ''' Loads a custom node positioning for this topology ''' title = "Load topology layout from file" filename = QtGui.QFileDialog.getOpenFileName(self,title,"gui/layouts") f = QtCore.QFile(filename) f.open(QtCore.QIODevice.ReadOnly) line = f.readLine() while not line.isNull(): nodeid,x,y = str(line).split() line = f.readLine() if not nodeid in self.nodes: print "Layout mismatch (node", nodeid, "exists in conf file but has not been discovered on the network)" else: self.nodes[nodeid].setX(float(x)) self.nodes[nodeid].setY(float(y)) f.close() layout = str(filename).split("/") layout = layout[len(layout)-1] self.parent.parent.settings.set_current_topo_layout(layout) self.updateAll()
class TopologyView(QtGui.QGraphicsView): updateAllSignal = QtCore.pyqtSignal() def __init__(self, parent=None): QtGui.QGraphicsView.__init__(self, parent) self.parent = parent # topologyInterface exchanges json messages with monitoring server self.topologyInterface = TopologyInterface(self) self.topologyInterface.start() #asyncore.loop() self.setStyleSheet("background: black") self.topoScene = QtGui.QGraphicsScene(self) self.topoScene.setItemIndexMethod(QtGui.QGraphicsScene.NoIndex) self.topoScene.setSceneRect(-300, -300, 600, 600) self.setScene(self.topoScene) self.setCacheMode(QtGui.QGraphicsView.CacheBackground) self.setRenderHint(QtGui.QPainter.Antialiasing) self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse) self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter) self.drawAccess = 'Default' #(utilization/te/et/etc) self.scale(0.9, 0.9) self.setMinimumSize(400, 400) # Pan self.setDragMode(self.ScrollHandDrag) self.setCursor(QtCore.Qt.ArrowCursor) # Connect signals to slots self.topologyInterface.topology_received_signal[str].connect \ (self.got_topo_msg) self.updateAllSignal.connect(self.updateAll) # Dictionaries holding node and link QGraphicsItems self.nodes = {} self.links = {} # Get an initial current snapshot of the topology self.get_topology() # Subscribe to LAVI for topology changes self.subscribe_to_topo_changes() def subscribe_to_topo_changes(self): ''' Subscribe to LAVI for topology changes ''' msg = {} msg["type"] = "lavi" msg["command"] = "subscribe" msg["node_type"] = "all" self.topologyInterface.send(msg) msg = {} msg["type"] = "lavi" msg["command"] = "subscribe" msg["link_type"] = "all" self.topologyInterface.send(msg) def get_nodes(self): ''' Ask lavi for an updated nodes set ''' queryMsg = {} queryMsg["type"] = "lavi" queryMsg["command"] = "request" queryMsg["node_type"] = "all" self.topologyInterface.send(queryMsg) def get_links(self): ''' Ask lavi for an updated links set ''' queryMsg = {} queryMsg["type"] = "lavi" queryMsg["command"] = "request" queryMsg["link_type"] = "all" self.topologyInterface.send(queryMsg) def get_topology(self): ''' Ask lavi for updated nodes and links sets ''' self.get_nodes() self.get_links() def got_topo_msg(self, msg): ''' Handle received links/nodes message ''' jsonmsg = json.loads(str(msg)) if "node_id" in jsonmsg: if jsonmsg["command"] == "add": nodes = jsonmsg["node_id"] new_nodes = [] # Populate nodes for nodeID in nodes: # prepend 0s until len = 12 while len(nodeID) < 12: nodeID = "0" + nodeID # If nodeItem doesn't already exist if nodeID not in self.nodes.keys(): nodeItem = Node(self, nodeID, jsonmsg["node_type"]) self.nodes[nodeID] = nodeItem new_nodes.append(nodeItem) self.addNodes(new_nodes) self.positionNodes(new_nodes) elif jsonmsg["command"] == "delete": nodes = jsonmsg["node_id"] for nodeID in nodes: if not jsonmsg["node_type"] == "host": # prepend 0s until len = 12 while len(nodeID) < 12: nodeID = "0" + nodeID if nodeID in self.nodes.keys(): #un-draw node n = self.nodes[nodeID] self.topoScene.removeItem(n) n.update() '''Should I delete nodes or store in 'down' state?''' del self.nodes[nodeID] elif "links" in msg: if jsonmsg["command"] == "add": links = jsonmsg["links"] new_links = [] # Populate Links linkid = len(self.links) for link in links: # Lavi advertises 1 link for each direction # We'll add a single object for a biderectional link # We'll always use 'minend-maxend' as the key srcid = link["src id"] while len(srcid) < 12: srcid = "0" + srcid dstid = link["dst id"] while len(srcid) < 12: srcid = "0" + dstid minend = str(min(srcid, dstid)) maxend = str(max(srcid, dstid)) key = minend + '-' + maxend ''' if key in self.links: # re-draw link (assuming it has been removed) l = self.links[key] self.topoScene.addItem(l) l.update() ''' if key in self.links: continue # If src_port is missing, default to 1 if not "src port" in link: link["src port"] = 1 linkid = linkid + 1 # Create new linkItem linkItem = Link(self, self.nodes[srcid], self.nodes[dstid],\ link["src port"], link["dst port"], link["src type"],\ link["dst type"], linkid) self.links[key] = linkItem new_links.append(linkItem) self.addLinks(new_links) elif jsonmsg["command"] == "delete": links = jsonmsg["links"] for link in links: # Only do this once (for both directions) if link["src id"] > link["dst id"]: continue # First, check if link exists key = str(link["src id"]) + "-" + str(link["dst id"]) if key in self.links: #un-draw link l = self.links[key] self.topoScene.removeItem(l) l.update() '''Should I delete links or store in 'down' state?''' del self.links[key] else: print "Attempted to removed inexistent link:", key self.updateAll() def addNodes(self, new_nodes): ''' Add nodes to topology Scene ''' for nodeItem in new_nodes: self.topoScene.addItem(nodeItem) def addLinks(self, new_links): ''' Add links to topology Scene ''' for linkItem in new_links: self.topoScene.addItem(linkItem) def positionNodes(self, new_nodes): ''' Position nodes according to current loaded layout (or random if none) ''' minX, maxX = -300, 300 minY, maxY = -200, 200 layout = self.parent.parent.settings.current_topo_layout if layout == "random": for node in new_nodes: node.setPos(randint(minX, maxX), randint(minY, maxY)) else: ''' If node position is described in current layout file, choose that, otherwise place randomly ''' # Optimize: scan file into a dictionary. same for load. f = QtCore.QFile("gui/layouts/" + layout) f.open(QtCore.QIODevice.ReadOnly) for node in new_nodes: line = f.readLine() found = False while not line.isNull(): nodeid, x, y = str(line).split() line = f.readLine() if str(node.id) == nodeid: node.setPos(float(x), float(y)) found = True if not found: node.setPos(randint(minX, maxX), randint(minY, maxY)) f.close() def itemMoved(self): pass def disableAllLinks(self): for e in self.links.values(): e.bringLinkDown() e.update() def enableAllLinks(self): for e in self.links.values(): e.bringLinkUp() e.update() def disableAllNodes(self): for n in self.nodes.values(): n.bringSwitchDown() n.update() def enableAllNodes(self): for n in self.nodes.values(): n.bringSwitchUp() n.update() def updateAllNodes(self): ''' Refresh all Nodes ''' for n in self.nodes.values(): n.update() def updateAllLinks(self): ''' Refresh all Links ''' for e in self.links.values(): e.update() e.adjust() def updateAll(self): ''' Refresh all Items # see if there is a auto way to updateall (updateScene()?) ''' self.updateAllNodes() self.updateAllLinks() def keyPressEvent(self, event): ''' Topology View hotkeys ''' key = event.key() if key == QtCore.Qt.Key_Plus: self.scaleView(1.2) elif key == QtCore.Qt.Key_Minus: self.scaleView(1 / 1.2) elif key == QtCore.Qt.Key_N: self.toggleNodes() elif key == QtCore.Qt.Key_I: self.toggleNodeIDs() elif key == QtCore.Qt.Key_K: self.toggleLinks() elif key == QtCore.Qt.Key_L: # LAVI counts a biderctional link as 2 separate links, so IDs overlap self.toggleLinkIDs() self.updateAllLinks() elif key == QtCore.Qt.Key_P: self.togglePorts() self.updateAllLinks() elif key == QtCore.Qt.Key_H: self.toggleHosts() self.updateAllNodes() #elif key == QtCore.Qt.Key_R: # # Refresh topology # self.get_topology() elif key == QtCore.Qt.Key_Space or key == QtCore.Qt.Key_Enter: # Redraw topology self.positionNodes(self.nodes.values()) self.updateAll() else: QtGui.QGraphicsView.keyPressEvent(self, event) ''' Toggle display of drawn items ''' def toggleNodes(self): for node in self.nodes.values(): node.showNode = not node.showNode node.update() def toggleNodeIDs(self): for node in self.nodes.values(): node.showID = not node.showID node.update() def toggleLinks(self): for link in self.links.values(): link.showLink = not link.showLink link.update() def toggleLinkIDs(self): for link in self.links.values(): link.showID = not link.showID link.update() def togglePorts(self): for link in self.links.values(): link.showPorts = not link.showPorts link.update() def toggleHosts(self): for node in self.nodes.values(): if node.layer == 3: for l in node.linkList: l.showLink = not l.showLink l.update() node.showID = not node.showID node.showNode = not node.showNode node.update() def wheelEvent(self, event): ''' Zoom ''' self.scaleView(math.pow(2.0, event.delta() / 300.0)) def drawBackground(self, painter, rect): ''' Draw background. For now just some text ''' sceneRect = self.sceneRect() textRect = QtCore.QRectF(sceneRect.left() - 5, sceneRect.top() + 60, sceneRect.width() - 4, sceneRect.height() - 4) message = self.tr("Topology") font = painter.font() font.setPointSize(12) painter.setFont(font) painter.setPen(QtCore.Qt.darkGray) painter.drawText(textRect.translated(20.8, 5.8), message) painter.setPen(QtCore.Qt.white) painter.setPen(QtGui.QColor(QtCore.Qt.gray).light(130)) painter.drawText(textRect.translated(20, 5), message) def scaleView(self, scaleFactor): factor = self.matrix().scale(scaleFactor, scaleFactor).mapRect( QtCore.QRectF(0, 0, 1, 1)).width() if factor < 0.07 or factor > 100: return self.scale(scaleFactor, scaleFactor) def mouseReleaseEvent(self, event): ''' Show context menu when right-clicking on empty space on the scene. ''' if not self.itemAt(event.pos()): if event.button() == QtCore.Qt.RightButton: popup = QtGui.QMenu() popup.addAction("Load Layout", self.load_layout) popup.addAction("Save Layout", self.save_layout) popup.exec_(event.globalPos()) QtGui.QGraphicsView.mouseReleaseEvent(self, event) def save_layout(self): ''' Saves the current node positioning ''' title = "Specify file to store topology layout" filename = QtGui.QFileDialog.getSaveFileName(self, title, "gui/layouts") f = QtCore.QFile(filename) f.open(QtCore.QIODevice.WriteOnly) for node in self.nodes.values(): line = QtCore.QByteArray(str(node.id)+" "+\ str(round(int(node.x()),-1))+" "+\ str(round(int(node.y()),-1))+" \n") f.write(line) f.close() layout = str(filename).split("/") layout = layout[len(layout) - 1] self.parent.parent.settings.set_current_topo_layout(layout) def load_layout(self): ''' Loads a custom node positioning for this topology ''' title = "Load topology layout from file" filename = QtGui.QFileDialog.getOpenFileName(self, title, "gui/layouts") f = QtCore.QFile(filename) f.open(QtCore.QIODevice.ReadOnly) line = f.readLine() while not line.isNull(): nodeid, x, y = str(line).split() line = f.readLine() if not nodeid in self.nodes: print "Layout mismatch (node", nodeid, "exists in conf file but has not been discovered on the network)" else: self.nodes[nodeid].setX(float(x)) self.nodes[nodeid].setY(float(y)) f.close() layout = str(filename).split("/") layout = layout[len(layout) - 1] self.parent.parent.settings.set_current_topo_layout(layout) self.updateAll()
class TopologyView(QtGui.QGraphicsView): updateAllSignal = QtCore.pyqtSignal() def __init__(self, parent=None): QtGui.QGraphicsView.__init__(self, parent) self.parent = parent # topologyInterface exchanges json messages with monitoring server self.topologyInterface = TopologyInterface(self) self.topologyInterface.start() # asyncore.loop() self.setStyleSheet("background: black") self.topoScene = QtGui.QGraphicsScene(self) self.topoScene.setItemIndexMethod(QtGui.QGraphicsScene.NoIndex) self.topoScene.setSceneRect(-300, -300, 600, 600) self.setScene(self.topoScene) self.setCacheMode(QtGui.QGraphicsView.CacheBackground) self.setRenderHint(QtGui.QPainter.Antialiasing) self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse) self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter) self.drawAccess = "Default" # (utilization/te/et/etc) self.scale(0.9, 0.9) self.setMinimumSize(400, 400) # Pan self.setDragMode(self.ScrollHandDrag) self.setCursor(QtCore.Qt.ArrowCursor) # Connect signals to slots self.topologyInterface.topology_received_signal[str].connect(self.got_topo_msg) self.updateAllSignal.connect(self.updateAll) # Dictionaries holding node and link QGraphicsItems self.nodes = {} self.links = {} # Get an initial current snapshot of the topology self.get_topology() # Subscribe to LAVI for topology changes self.subscribe_to_topo_changes() def subscribe_to_topo_changes(self): """ Subscribe to LAVI for topology changes """ msg = {} msg["type"] = "lavi" msg["command"] = "subscribe" msg["node_type"] = "all" self.topologyInterface.send(msg) msg = {} msg["type"] = "lavi" msg["command"] = "subscribe" msg["link_type"] = "all" self.topologyInterface.send(msg) def get_nodes(self): """ Ask lavi for an updated nodes set """ queryMsg = {} queryMsg["type"] = "lavi" queryMsg["command"] = "request" queryMsg["node_type"] = "all" self.topologyInterface.send(queryMsg) def get_links(self): """ Ask lavi for an updated links set """ queryMsg = {} queryMsg["type"] = "lavi" queryMsg["command"] = "request" queryMsg["link_type"] = "all" self.topologyInterface.send(queryMsg) def get_topology(self): """ Ask lavi for updated nodes and links sets """ self.get_nodes() self.get_links() def got_topo_msg(self, msg): """ Handle received links/nodes message """ jsonmsg = json.loads(str(msg)) if "node_id" in jsonmsg: if jsonmsg["command"] == "add": nodes = jsonmsg["node_id"] new_nodes = [] # Populate nodes for nodeID in nodes: # prepend 0s until len = 12 while len(nodeID) < 12: nodeID = "0" + nodeID # If nodeItem doesn't already exist if nodeID not in self.nodes.keys(): nodeItem = Node(self, nodeID, jsonmsg["node_type"]) self.nodes[nodeID] = nodeItem new_nodes.append(nodeItem) self.addNodes(new_nodes) self.positionNodes(new_nodes) elif jsonmsg["command"] == "delete": nodes = jsonmsg["node_id"] for nodeID in nodes: if not jsonmsg["node_type"] == "host": # prepend 0s until len = 12 while len(nodeID) < 12: nodeID = "0" + nodeID if nodeID in self.nodes.keys(): # un-draw node n = self.nodes[nodeID] self.topoScene.removeItem(n) n.update() """Should I delete nodes or store in 'down' state?""" del self.nodes[nodeID] elif "links" in msg: if jsonmsg["command"] == "add": links = jsonmsg["links"] new_links = [] # Populate Links linkid = len(self.links) for link in links: # Lavi advertises 1 link for each direction # We'll add a single object for a biderectional link # We'll always use 'minend-maxend' as the key srcid = link["src id"] while len(srcid) < 12: srcid = "0" + srcid dstid = link["dst id"] while len(srcid) < 12: srcid = "0" + dstid minend = str(min(srcid, dstid)) maxend = str(max(srcid, dstid)) key = minend + "-" + maxend """ if key in self.links: # re-draw link (assuming it has been removed) l = self.links[key] self.topoScene.addItem(l) l.update() """ if key in self.links: continue # If src_port is missing, default to 1 if not "src port" in link: link["src port"] = 1 linkid = linkid + 1 # Create new linkItem linkItem = Link( self, self.nodes[srcid], self.nodes[dstid], link["src port"], link["dst port"], link["src type"], link["dst type"], linkid, ) self.links[key] = linkItem new_links.append(linkItem) self.addLinks(new_links) elif jsonmsg["command"] == "delete": links = jsonmsg["links"] for link in links: # Only do this once (for both directions) if link["src id"] > link["dst id"]: continue # First, check if link exists key = str(link["src id"]) + "-" + str(link["dst id"]) if key in self.links: # un-draw link l = self.links[key] self.topoScene.removeItem(l) l.update() """Should I delete links or store in 'down' state?""" del self.links[key] else: print "Attempted to removed inexistent link:", key self.updateAll() def addNodes(self, new_nodes): """ Add nodes to topology Scene """ for nodeItem in new_nodes: self.topoScene.addItem(nodeItem) def addLinks(self, new_links): """ Add links to topology Scene """ for linkItem in new_links: self.topoScene.addItem(linkItem) def positionNodes(self, new_nodes): """ Position nodes according to current loaded layout (or random if none) """ minX, maxX = -300, 300 minY, maxY = -200, 200 layout = self.parent.parent.settings.current_topo_layout if layout == "random": for node in new_nodes: node.setPos(randint(minX, maxX), randint(minY, maxY)) else: """ If node position is described in current layout file, choose that, otherwise place randomly """ # Optimize: scan file into a dictionary. same for load. f = QtCore.QFile("gui/layouts/" + layout) f.open(QtCore.QIODevice.ReadOnly) for node in new_nodes: line = f.readLine() found = False while not line.isNull(): nodeid, x, y = str(line).split() line = f.readLine() if str(node.id) == nodeid: node.setPos(float(x), float(y)) found = True if not found: node.setPos(randint(minX, maxX), randint(minY, maxY)) f.close() def itemMoved(self): pass def disableAllLinks(self): for e in self.links.values(): e.bringLinkDown() e.update() def enableAllLinks(self): for e in self.links.values(): e.bringLinkUp() e.update() def disableAllNodes(self): for n in self.nodes.values(): n.bringSwitchDown() n.update() def enableAllNodes(self): for n in self.nodes.values(): n.bringSwitchUp() n.update() def updateAllNodes(self): """ Refresh all Nodes """ for n in self.nodes.values(): n.update() def updateAllLinks(self): """ Refresh all Links """ for e in self.links.values(): e.update() e.adjust() def updateAll(self): """ Refresh all Items # see if there is a auto way to updateall (updateScene()?) """ self.updateAllNodes() self.updateAllLinks() def keyPressEvent(self, event): """ Topology View hotkeys """ key = event.key() if key == QtCore.Qt.Key_Plus: self.scaleView(1.2) elif key == QtCore.Qt.Key_Minus: self.scaleView(1 / 1.2) elif key == QtCore.Qt.Key_N: self.toggleNodes() elif key == QtCore.Qt.Key_I: self.toggleNodeIDs() elif key == QtCore.Qt.Key_K: self.toggleLinks() elif key == QtCore.Qt.Key_L: # LAVI counts a biderctional link as 2 separate links, so IDs overlap self.toggleLinkIDs() self.updateAllLinks() elif key == QtCore.Qt.Key_P: self.togglePorts() self.updateAllLinks() elif key == QtCore.Qt.Key_H: self.toggleHosts() self.updateAllNodes() # elif key == QtCore.Qt.Key_R: # # Refresh topology # self.get_topology() elif key == QtCore.Qt.Key_Space or key == QtCore.Qt.Key_Enter: # Redraw topology self.positionNodes(self.nodes.values()) self.updateAll() else: QtGui.QGraphicsView.keyPressEvent(self, event) """ Toggle display of drawn items """ def toggleNodes(self): for node in self.nodes.values(): node.showNode = not node.showNode node.update() def toggleNodeIDs(self): for node in self.nodes.values(): node.showID = not node.showID node.update() def toggleLinks(self): for link in self.links.values(): link.showLink = not link.showLink link.update() def toggleLinkIDs(self): for link in self.links.values(): link.showID = not link.showID link.update() def togglePorts(self): for link in self.links.values(): link.showPorts = not link.showPorts link.update() def toggleHosts(self): for node in self.nodes.values(): if node.layer == 3: for l in node.linkList: l.showLink = not l.showLink l.update() node.showID = not node.showID node.showNode = not node.showNode node.update() def wheelEvent(self, event): """ Zoom """ self.scaleView(math.pow(2.0, event.delta() / 300.0)) def drawBackground(self, painter, rect): """ Draw background. For now just some text """ sceneRect = self.sceneRect() textRect = QtCore.QRectF( sceneRect.left() - 5, sceneRect.top() + 60, sceneRect.width() - 4, sceneRect.height() - 4 ) message = self.tr("Topology") font = painter.font() font.setPointSize(12) painter.setFont(font) painter.setPen(QtCore.Qt.darkGray) painter.drawText(textRect.translated(20.8, 5.8), message) painter.setPen(QtCore.Qt.white) painter.setPen(QtGui.QColor(QtCore.Qt.gray).light(130)) painter.drawText(textRect.translated(20, 5), message) def scaleView(self, scaleFactor): factor = self.matrix().scale(scaleFactor, scaleFactor).mapRect(QtCore.QRectF(0, 0, 1, 1)).width() if factor < 0.07 or factor > 100: return self.scale(scaleFactor, scaleFactor) def mouseReleaseEvent(self, event): """ Show context menu when right-clicking on empty space on the scene. """ if not self.itemAt(event.pos()): if event.button() == QtCore.Qt.RightButton: popup = QtGui.QMenu() popup.addAction("Load Layout", self.load_layout) popup.addAction("Save Layout", self.save_layout) popup.exec_(event.globalPos()) QtGui.QGraphicsView.mouseReleaseEvent(self, event) def save_layout(self): """ Saves the current node positioning """ title = "Specify file to store topology layout" filename = QtGui.QFileDialog.getSaveFileName(self, title, "gui/layouts") f = QtCore.QFile(filename) f.open(QtCore.QIODevice.WriteOnly) for node in self.nodes.values(): line = QtCore.QByteArray( str(node.id) + " " + str(round(int(node.x()), -1)) + " " + str(round(int(node.y()), -1)) + " \n" ) f.write(line) f.close() layout = str(filename).split("/") layout = layout[len(layout) - 1] self.parent.parent.settings.set_current_topo_layout(layout) def load_layout(self): """ Loads a custom node positioning for this topology """ title = "Load topology layout from file" filename = QtGui.QFileDialog.getOpenFileName(self, title, "gui/layouts") f = QtCore.QFile(filename) f.open(QtCore.QIODevice.ReadOnly) line = f.readLine() while not line.isNull(): nodeid, x, y = str(line).split() line = f.readLine() if not nodeid in self.nodes: print "Layout mismatch (node", nodeid, "exists in conf file but has not been discovered on the network)" else: self.nodes[nodeid].setX(float(x)) self.nodes[nodeid].setY(float(y)) f.close() layout = str(filename).split("/") layout = layout[len(layout) - 1] self.parent.parent.settings.set_current_topo_layout(layout) self.updateAll()