Beispiel #1
0
    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()
Beispiel #2
0
    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()
Beispiel #3
0
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()
Beispiel #4
0
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()
Beispiel #5
0
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()