deleteslivers for the emulab aggregate
[sface.git] / sface / screens / mainscreen.py
index 96a9b06..3c1be13 100644 (file)
@@ -1,15 +1,16 @@
 
 import datetime
 import os
 
 import datetime
 import os
+import urlparse
 from PyQt4.QtCore import *
 from PyQt4.QtGui import *
 
 #from sfa.util.rspecHelper import RSpec
 from PyQt4.QtCore import *
 from PyQt4.QtGui import *
 
 #from sfa.util.rspecHelper import RSpec
-from sfa.rspecs.rspec_parser import parse_rspec
 from sface.config import config
 from sface.sfirenew import RenewWindow
 from sface.sfiprocess import SfiProcess
 from sface.screens.sfascreen import SfaScreen
 from sface.config import config
 from sface.sfirenew import RenewWindow
 from sface.sfiprocess import SfiProcess
 from sface.screens.sfascreen import SfaScreen
+from sface.sfidata import SfiData
 
 already_in_nodes = []
 
 
 already_in_nodes = []
 
@@ -23,6 +24,10 @@ tag_status = { "in": "Already Set",
                 "add": "To be Added",
                 "remove": "To be Removed"}
 
                 "add": "To be Added",
                 "remove": "To be Removed"}
 
+color_status = { "in": QColor.fromRgb(0, 250, 250),
+                 "add": QColor.fromRgb(0, 250, 0),
+                 "remove": QColor.fromRgb(250, 0, 0) }
+
 default_tags = "Default tags"
 settable_tags = ['delegations', 'initscript']
 
 default_tags = "Default tags"
 settable_tags = ['delegations', 'initscript']
 
@@ -188,44 +193,20 @@ class NodeNameDelegate(QStyledItemDelegate):
             painter.fillRect(option.rect, option.palette.color(QPalette.Active, QPalette.Highlight))
 
         if itemType(index) == "node":
             painter.fillRect(option.rect, option.palette.color(QPalette.Active, QPalette.Highlight))
 
         if itemType(index) == "node":
-            if status_data == node_status['in']: # already in the slice
-                painter.fillPath(path, QColor.fromRgb(0, 250, 250))
-                painter.setPen(QColor.fromRgb(0, 0, 0))
-                painter.drawText(rect, 0, QString(data))
-
-            elif status_data == node_status['add']: # newly added to the slice
-                painter.fillPath(path, QColor.fromRgb(0, 250, 0))
-                painter.setPen(QColor.fromRgb(0, 0, 0))
-                painter.drawText(rect, 0, QString(data))
+            for x in node_status.keys():
+                if (node_status[x] == status_data) and (x in color_status):
+                    painter.fillPath(path, color_status[x])
 
 
-            elif status_data == node_status['remove']: # removed from the slice
-                painter.fillPath(path, QColor.fromRgb(250, 0, 0))
-                painter.setPen(QColor.fromRgb(0, 0, 0))
-                painter.drawText(rect, 0, QString(data))
-
-            else:
-                painter.setPen(QColor.fromRgb(0, 0, 0))
-                painter.drawText(rect, 0, QString(data))
+            painter.setPen(QColor.fromRgb(0, 0, 0))
+            painter.drawText(rect, 0, QString(data))
 
         else:
 
         else:
-            if status_data == tag_status['in']: # already in the slice
-                painter.fillPath(path, QColor.fromRgb(0, 250, 250))
-                painter.setPen(QColor.fromRgb(0, 0, 0))
-                painter.drawText(rect, 0, QString(data))
+            for x in tag_status.keys():
+                if (tag_status[x] == status_data) and (x in color_status):
+                    painter.fillPath(path, color_status[x])
 
 
-            elif status_data == tag_status['add']: # newly added to the slice
-                painter.fillPath(path, QColor.fromRgb(0, 250, 0))
-                painter.setPen(QColor.fromRgb(0, 0, 0))
-                painter.drawText(rect, 0, QString(data))
-
-            elif status_data == tag_status['remove']: # removed from the slice
-                painter.fillPath(path, QColor.fromRgb(250, 0, 0))
-                painter.setPen(QColor.fromRgb(0, 0, 0))
-                painter.drawText(rect, 0, QString(data))
-
-            else:
-                painter.setPen(QColor.fromRgb(0, 0, 0))
-                painter.drawText(rect, 0, QString(data))
+            painter.setPen(QColor.fromRgb(0, 0, 0))
+            painter.drawText(rect, 0, QString(data))
 
         painter.restore()
 
 
         painter.restore()
 
@@ -357,7 +338,7 @@ class SliceWidget(QWidget):
 
         self.connect(refresh, SIGNAL('clicked()'), self.refresh)
         self.connect(renew, SIGNAL('clicked()'), self.renew)
 
         self.connect(refresh, SIGNAL('clicked()'), self.refresh)
         self.connect(renew, SIGNAL('clicked()'), self.renew)
-        self.connect(submit, SIGNAL('clicked()'), self.submit)
+        self.connect(submit, SIGNAL('clicked()'), self.submit_pg_compat)
         self.connect(searchbox, SIGNAL('textChanged(QString)'), self.search)
         self.connect(filterbox, SIGNAL('currentIndexChanged(QString)'), self.filter)
         self.connect(self.nodeView, SIGNAL('hostnameClicked(QString)'),
         self.connect(searchbox, SIGNAL('textChanged(QString)'), self.search)
         self.connect(filterbox, SIGNAL('currentIndexChanged(QString)'), self.filter)
         self.connect(self.nodeView, SIGNAL('hostnameClicked(QString)'),
@@ -366,29 +347,40 @@ class SliceWidget(QWidget):
         self.updateView()
 
     def submitFinished(self):
         self.updateView()
 
     def submitFinished(self):
+        self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
+
         faultString = self.process.getFaultString()
         if not faultString:
             self.setStatus("<font color='green'>Slice data submitted.</font>")
         else:
             self.setStatus("<font color='red'>Slice submit failed: %s</font>" % (faultString))
 
         faultString = self.process.getFaultString()
         if not faultString:
             self.setStatus("<font color='green'>Slice data submitted.</font>")
         else:
             self.setStatus("<font color='red'>Slice submit failed: %s</font>" % (faultString))
 
-        # no need to do that anymore
-        # QTimer.singleShot(1000, self.refresh)
         self.updateView()
         self.parent().signalAll("rspecUpdated")
 
         self.updateView()
         self.parent().signalAll("rspecUpdated")
 
-    def refreshFinished(self):
-        self.setStatus("<font color='green'>Slice data refreshed.</font>", timeout=5000)
+    def refreshResourcesFinished(self):
+        self.disconnect(self.process, SIGNAL('finished()'), self.refreshResourcesFinished)
+
+        faultString = self.process.getFaultString()
+        if not faultString:
+            self.setStatus("Refreshing slice RSpec.")
+            self.connect(self.process, SIGNAL('finished()'), self.refreshRSpecFinished)
+            self.process.retrieveRspec()
+        else:
+            self.setStatus("<font color='red'>Resources refresh failed: %s</font>" % (faultString))
+
+    def refreshRSpecFinished(self):
+        self.disconnect(self.process, SIGNAL('finished()'), self.refreshRSpecFinished)
+
+        faultString = self.process.getFaultString()
+        if not faultString:
+            self.setStatus("<font color='green'>Slice data refreshed.</font>", timeout=5000)
+        else:
+            self.setStatus("<font color='red'>Slice refresh failed: %s</font>" % (faultString))
+
         self.updateView()
         self.parent().signalAll("rspecUpdated")
 
         self.updateView()
         self.parent().signalAll("rspecUpdated")
 
-    def readSliceRSpec(self):
-        rspec_file = config.getSliceRSpecFile()
-        if os.path.exists(rspec_file):
-            xml = open(rspec_file).read()
-            return parse_rspec(xml)
-        return None
-
     def setStatus(self, msg, timeout=None):
         self.parent().setStatus(msg, timeout)
 
     def setStatus(self, msg, timeout=None):
         self.parent().setStatus(msg, timeout)
 
@@ -406,13 +398,13 @@ class SliceWidget(QWidget):
 
     def itemStatus(self, item):
         statusItem = item.parent().child(item.row(), MEMBERSHIP_STATUS_COLUMN)
 
     def itemStatus(self, item):
         statusItem = item.parent().child(item.row(), MEMBERSHIP_STATUS_COLUMN)
-        return statusItem.data(Qt.DisplayRole).toString()
+        return str(statusItem.data(Qt.DisplayRole).toString())
 
     def itemText(self, item):
 
     def itemText(self, item):
-        return item.data(Qt.DisplayRole).toString()
+        return str(item.data(Qt.DisplayRole).toString())
 
     # Recursively walk the tree, making changes to the RSpec
 
     # Recursively walk the tree, making changes to the RSpec
-    def process_subtree(self, rspec, item, depth = 0):
+    def process_subtree(self, rspec, resources, item, depth = 0):
         change = False
         model = self.nodeModel
 
         change = False
         model = self.nodeModel
 
@@ -424,11 +416,18 @@ class SliceWidget(QWidget):
             status = self.itemStatus(item)
             if status == node_status['add']:
                 print "Add hostname: %s" % hostname
             status = self.itemStatus(item)
             if status == node_status['add']:
                 print "Add hostname: %s" % hostname
-                rspec.add_slivers(str(hostname), testbed)
-                change = True
+
+                resource_node = resources.get_node_element(hostname)
+
+                if resource_node==None:
+                    print "Error: Failed to find %s in resources rspec" % hostname
+                else:
+                    rspec.merge_node(resource_node, testbed)
+                    rspec.add_slivers([{"hostname": str(hostname)}], testbed)
+                    change = True
             elif status == node_status['remove']:
                 print "Remove hostname: %s" % hostname
             elif status == node_status['remove']:
                 print "Remove hostname: %s" % hostname
-                rspec.remove_slivers(str(hostname), testbed)
+                rspec.remove_slivers([{"hostname": str(hostname)}], testbed)
                 change = True
         elif depth == 3: # Tag
             tag, value = self.itemText(item).split(": ")
                 change = True
         elif depth == 3: # Tag
             tag, value = self.itemText(item).split(": ")
@@ -439,14 +438,14 @@ class SliceWidget(QWidget):
             testbed = self.itemText(item.parent().parent())
             if status == tag_status['add']:
                 print "Add tag to (%s, %s): %s/%s " % (testbed, node, tag, value)
             testbed = self.itemText(item.parent().parent())
             if status == tag_status['add']:
                 print "Add tag to (%s, %s): %s/%s " % (testbed, node, tag, value)
-                if node.startsWith(default_tags):
+                if node.startswith(default_tags):
                     rspec.add_default_sliver_attribute(tag, value, testbed)
                 else:
                     rspec.add_sliver_attribute(node, tag, value, testbed)
                 change = True
             elif status == tag_status['remove']:
                 print "Remove tag from (%s, %s): %s/%s " % (testbed, node, tag, value)
                     rspec.add_default_sliver_attribute(tag, value, testbed)
                 else:
                     rspec.add_sliver_attribute(node, tag, value, testbed)
                 change = True
             elif status == tag_status['remove']:
                 print "Remove tag from (%s, %s): %s/%s " % (testbed, node, tag, value)
-                if node.startsWith(default_tags):
+                if node.startswith(default_tags):
                     rspec.remove_default_sliver_attribute(tag, value, testbed)
                 else:
                     rspec.remove_sliver_attribute(node, tag, value, testbed)
                     rspec.remove_default_sliver_attribute(tag, value, testbed)
                 else:
                     rspec.remove_sliver_attribute(node, tag, value, testbed)
@@ -454,7 +453,7 @@ class SliceWidget(QWidget):
 
         children = item.rowCount()
         for row in range(0, children):
 
         children = item.rowCount()
         for row in range(0, children):
-            status = self.process_subtree(rspec, item.child(row), depth + 1)
+            status = self.process_subtree(rspec, resources, item.child(row), depth + 1)
             change = change or status
 
         return change
             change = change or status
 
         return change
@@ -463,19 +462,128 @@ class SliceWidget(QWidget):
         if self.checkRunningProcess():
             return
 
         if self.checkRunningProcess():
             return
 
-        rspec = self.readSliceRSpec()
-        change = self.process_subtree(rspec, self.nodeModel.invisibleRootItem())
+        rspec = SfiData().getSliceRSpec()
+        resources = SfiData().getResourcesRSpec()
+        change = self.process_subtree(rspec, resources, self.nodeModel.invisibleRootItem())
 
         if not change:
             self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
             return
 
 
         if not change:
             self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
             return
 
-        self.disconnect(self.process, SIGNAL('finished()'), self.refreshFinished)
-        self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
+        # Several aggregates have issues with the <statistics> section in the
+        # rspec, so make sure it's not there.
+        stats_elems = rspec.xml.xpath("//statistics")
+        if len(stats_elems)>0:
+            stats_elem = stats_elems[0]
+            parent = stats_elem.xpath("..")[0]
+            parent.remove(stats_elem)
 
 
+        self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
         self.process.applyRSpec(rspec)
         self.setStatus("Sending slice data (RSpec). This will take some time...")
 
         self.process.applyRSpec(rspec)
         self.setStatus("Sending slice data (RSpec). This will take some time...")
 
+    # ProtoGENI-compatible submit. Contact each aggregate individually rather
+    # than using the slice manager.
+    # This code will be removed when ProtoGENI slicemanager is patched.
+    def submit_pg_compat(self):
+        if self.checkRunningProcess():
+            return
+
+        rspec = SfiData().getSliceRSpec()
+        resources = SfiData().getResourcesRSpec()
+        change = self.process_subtree(rspec, resources, self.nodeModel.invisibleRootItem())
+
+        if not change:
+            self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
+            return
+
+        # Several aggregates have issues with the <statistics> section in the
+        # rspec, so make sure it's not there.
+        stats_elems = rspec.xml.xpath("//statistics")
+        if len(stats_elems)>0:
+            stats_elem = stats_elems[0]
+            parent = stats_elem.xpath("..")[0]
+            parent.remove(stats_elem)
+
+        self.submit_aggSuccessCount = 0
+        self.submit_aggFailCount = 0
+        self.submit_rspec = rspec
+        self.connect(self.process, SIGNAL('finished()'), self.getVersionFinished)
+        self.process.getSliceMgrVersion()
+        self.setStatus("Getting aggregate directory...")
+
+    def getVersionFinished(self):
+        self.disconnect(self.process, SIGNAL('finished()'), self.getVersionFinished)
+
+        faultString = self.process.getFaultString()
+        if not faultString:
+            peers = SfiData().getSliceMgrVersion()["peers"]
+            self.submit_aggs = [(key, peers[key]) for key in peers.keys()]
+            self.delete_aggs = [(key, peers[key]) for key in peers.keys() if key.startswith("emulab")]
+            str = "<font color='green'>Successfully retrieved agg list.</font> "
+            #self.submitNextAgg(str)
+            self.deleteNextAgg(str)
+        else:
+            self.setStatus("<font color='red'>getSliceMgrVersion failed: %s</font>" % (faultString))
+
+    def deleteNextAgg(self, statusStr=""):
+        if (self.delete_aggs == []):
+            self.submitNextAgg(statusStr)
+            return
+
+        self.delete_agg = self.delete_aggs.pop()
+
+        urlParts = urlparse.urlsplit(self.delete_agg[1])
+        amPort = urlParts.port
+        amAddr = urlParts.hostname+urlParts.path
+
+        self.setStatus(statusStr + "Deleting slivers on %s..." % (self.delete_agg[0]))
+
+        self.connect(self.process, SIGNAL('finished()'), self.deleteNextAggFinished)
+        self.process.deleteSlivers(aggAddr = amAddr, aggPort = amPort)
+
+    def submitNextAgg(self, statusStr=""):
+        if (self.submit_aggs == []):
+            self.setStatus(statusStr + "<font color='green'>Finished submitting. %d/%d aggs succeeded.</font>" %
+                           (self.submit_aggSuccessCount,self.submit_aggSuccessCount+self.submit_aggFailCount))
+            QTimer.singleShot(2500, self.refresh)
+            return
+
+        self.submit_agg = self.submit_aggs.pop()
+
+        urlParts = urlparse.urlsplit(self.submit_agg[1])
+        amPort = urlParts.port
+        amAddr = urlParts.hostname+urlParts.path
+
+        self.setStatus(statusStr + "Submitting to %s..." % (self.submit_agg[0]))
+
+        self.connect(self.process, SIGNAL('finished()'), self.submitNextAggFinished)
+        self.process.applyRSpec(self.submit_rspec, aggAddr = amAddr, aggPort = amPort, saveObtained=False)
+
+    def submitNextAggFinished(self):
+        self.disconnect(self.process, SIGNAL('finished()'), self.submitNextAggFinished)
+
+        faultString = self.process.getFaultString()
+        if not faultString:
+            self.submit_aggSuccessCount+=1
+            str = "<font color='green'>Succeeded on %s.</font> " % (self.submit_agg[0])
+        else:
+            self.submit_aggFailCount+=1
+            str = "<font color='red'>Failed on %s.</font> " % (self.submit_agg[0])  # , faultString)
+
+        self.submitNextAgg(str)
+
+    def deleteNextAggFinished(self):
+        self.disconnect(self.process, SIGNAL('finished()'), self.deleteNextAggFinished)
+
+        faultString = self.process.getFaultString()
+        if not faultString:
+            str = "<font color='green'>Succeeded deleteslivers on %s.</font> " % (self.delete_agg[0])
+        else:
+            str = "<font color='red'>Failed deleteslivers on %s.</font> " % (self.delete_agg[0])  # , faultString)
+
+        self.deleteNextAgg(str)
+
     def renew(self):
         dlg = RenewWindow(parent=self)
         dlg.exec_()
     def renew(self):
         dlg = RenewWindow(parent=self)
         dlg.exec_()
@@ -489,11 +597,10 @@ class SliceWidget(QWidget):
             self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
             return
 
             self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
             return
 
-        self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
-        self.connect(self.process, SIGNAL('finished()'), self.refreshFinished)
+        self.connect(self.process, SIGNAL('finished()'), self.refreshResourcesFinished)
 
 
-        self.process.retrieveRspec()
-        self.setStatus("Refreshing slice data. This will take some time...")
+        self.process.retrieveResources()
+        self.setStatus("Refreshing resources. This will take some time...")
 
     def updateView(self):
         global already_in_nodes
 
     def updateView(self):
         global already_in_nodes
@@ -501,20 +608,27 @@ class SliceWidget(QWidget):
         self.network_names = []
         self.nodeModel.clear()
 
         self.network_names = []
         self.nodeModel.clear()
 
-        rspec = self.readSliceRSpec()
+        rspec = SfiData().getSliceRSpec()
         if not rspec:
             return None
 
         if not rspec:
             return None
 
+        resources = SfiData().getResourcesRSpec()
+        if not resources:
+            return None
+
         rootItem = self.nodeModel.invisibleRootItem()
         rootItem = self.nodeModel.invisibleRootItem()
-        #networks = sorted(rspec.get_network_list())
         networks = rspec.get_networks()
         networks = rspec.get_networks()
+
+        for network in resources.get_networks():
+            if not network in networks:
+                networks.append(network)
+
         for network in networks:
             self.network_names.append(network)
 
         for network in networks:
             self.network_names.append(network)
 
-            #all_nodes = rspec.get_node_list(network)
-            #sliver_nodes = rspec.get_sliver_list(network)
-            all_nodes = rspec.get_nodes(network)
+            all_nodes = resources.get_nodes(network)
             sliver_nodes = rspec.get_nodes_with_slivers(network)
             sliver_nodes = rspec.get_nodes_with_slivers(network)
+
             available_nodes = [ node for node in all_nodes if node not in sliver_nodes ]
 
             networkItem = QStandardItem(QString(network))
             available_nodes = [ node for node in all_nodes if node not in sliver_nodes ]
 
             networkItem = QStandardItem(QString(network))
@@ -553,7 +667,7 @@ class SliceWidget(QWidget):
             for node in available_nodes:
                 nodeItem = QStandardItem(QString(node))
                 statusItem = QStandardItem(QString(node_status['out']))
             for node in available_nodes:
                 nodeItem = QStandardItem(QString(node))
                 statusItem = QStandardItem(QString(node_status['out']))
-                nodeStatus = QStandardItem(QString(rspec.get_node_element(node, network).attrib.get("boot_state","")))
+                nodeStatus = QStandardItem(QString(resources.get_node_element(node, network).attrib.get("boot_state","")))
                 networkItem.appendRow([nodeItem, nodeStatus, statusItem, QStandardItem(QString("node"))])
 
         self.filterModel.setSourceModel(self.nodeModel)
                 networkItem.appendRow([nodeItem, nodeStatus, statusItem, QStandardItem(QString("node"))])
 
         self.filterModel.setSourceModel(self.nodeModel)