support for hardware_type
[sface.git] / sface / screens / mainscreen.py
index 96a9b06..a435185 100644 (file)
@@ -1,15 +1,19 @@
 
 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 sfa.util.xrn import Xrn
 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
+
+from sface.clislicemgr import ClientSliceManager
 
 already_in_nodes = []
 
 
 already_in_nodes = []
 
@@ -23,13 +27,18 @@ 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']
 
 NAME_COLUMN = 0
 default_tags = "Default tags"
 settable_tags = ['delegations', 'initscript']
 
 NAME_COLUMN = 0
-NODE_STATUS_COLUMN = 1
-MEMBERSHIP_STATUS_COLUMN = 2
-KIND_COLUMN = 3
+NODE_TYPE_COLUMN = 1
+NODE_STATUS_COLUMN = 2
+MEMBERSHIP_STATUS_COLUMN = 3
+KIND_COLUMN = 4
 
 # maximum length of a name to display before clipping
 NAME_MAX_LEN = 48
 
 # maximum length of a name to display before clipping
 NAME_MAX_LEN = 48
@@ -97,6 +106,17 @@ class NodeView(QTreeView):
 
         model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
 
 
         model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
 
+    def appendRow(self, parent, name, nodeStatus="", nodeType="", membership="", kind = ""):
+        # row: name nodeStatus nodeType membership kind
+        item = QStandardItem(QString(str(name)))
+        row = [item,
+                QStandardItem(QString(str(nodeType))),
+                QStandardItem(QString(str(nodeStatus))),
+                QStandardItem(QString(str(membership))),
+                QStandardItem(QString(str(kind)))]
+        parent.appendRow(row)
+        return item
+
     def mousePressEvent(self, event):
         QTreeView.mousePressEvent(self, event)
         if event.button() == Qt.LeftButton:
     def mousePressEvent(self, event):
         QTreeView.mousePressEvent(self, event)
         if event.button() == Qt.LeftButton:
@@ -120,23 +140,12 @@ class NodeView(QTreeView):
                     value, ok = QInputDialog.getText(self, "Add tag",
                                                      "Value for tag '%s'" % tagname)
                     if ok:
                     value, ok = QInputDialog.getText(self, "Add tag",
                                                      "Value for tag '%s'" % tagname)
                     if ok:
-                        # Add a new row to the model for the tag
-
-                        # For testing with the QStandardItemModel
-                        #nodeItem = model.itemFromIndex(index)
-                        #tagstring = QString("%s: %s" % (tagname, value))
-                        #tagItem = QStandardItem(tagstring)
-                        #status = QStandardItem(QString(tag_status['add']))
-                        #nodeItem.appendRow([tagItem, status])
-
                         # We're using the QSortFilterProxyModel here
                         src_index = model.mapToSource(index)
                         src_model = src_index.model()
                         nodeItem = src_model.itemFromIndex(src_index)
                         # We're using the QSortFilterProxyModel here
                         src_index = model.mapToSource(index)
                         src_model = src_index.model()
                         nodeItem = src_model.itemFromIndex(src_index)
-                        tagstring = QString("%s: %s" % (tagname, value))
-                        tagItem = QStandardItem(tagstring)
-                        status = QStandardItem(QString(tag_status['add']))
-                        nodeItem.appendRow([tagItem, QStandardItem(QString("")), status])
+
+                        self.appendRow(nodeItem, "%s: %s" % (tagname, value), membership=tag_status['add'], kind="attribute")
 
             elif status_data in (node_status['out'], node_status['remove']):
                 QMessageBox.warning(self, "Not selected", "Can only add tags to selected nodes")
 
             elif status_data in (node_status['out'], node_status['remove']):
                 QMessageBox.warning(self, "Not selected", "Can only add tags to selected nodes")
@@ -188,44 +197,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))
+            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['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 == 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))
-
-            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))
+            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['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()
 
@@ -302,6 +287,20 @@ class NodeFilterProxyModel(QSortFilterProxyModel):
                     return False
         return True
 
                     return False
         return True
 
+    def lessThan(self, left, right):
+        l_str = str(left.data().toString())
+        r_str = str(right.data().toString())
+
+        # make sure default_tags appears before everything else
+        if l_str.startswith(default_tags):
+            return True
+
+        if r_str.startswith(default_tags):
+            return False
+
+        return (l_str < r_str)
+
+
 class SliceWidget(QWidget):
     def __init__(self, parent):
         QWidget.__init__(self, parent)
 class SliceWidget(QWidget):
     def __init__(self, parent):
         QWidget.__init__(self, parent)
@@ -357,7 +356,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 +365,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 +416,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, rspec_node_names, resource_node_names, item, depth = 0):
         change = False
         model = self.nodeModel
 
         change = False
         model = self.nodeModel
 
@@ -424,37 +434,51 @@ 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 = resource_node_names.get(hostname, None)
+
+                if resource_node==None:
+                    print "Error: Failed to find %s in resources rspec" % hostname
+                else:
+                    if not (hostname in rspec_node_names):
+                        network_name = Xrn(resource_node['component_manager_id']).get_hrn()
+                        rspec.version.add_network(network_name)
+                        rspec.version.add_nodes([resource_node])
+                    rspec.version.add_slivers([str(hostname)])
+                    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.version.remove_slivers([str(hostname)])
                 change = True
         elif depth == 3: # Tag
             tag, value = self.itemText(item).split(": ")
             status = self.itemStatus(item)
             tag = "%s" % tag     # Prevent weird error from lxml
             value = "%s" % value # Prevent weird error from lxml
                 change = True
         elif depth == 3: # Tag
             tag, value = self.itemText(item).split(": ")
             status = self.itemStatus(item)
             tag = "%s" % tag     # Prevent weird error from lxml
             value = "%s" % value # Prevent weird error from lxml
-            node = self.itemText(item.parent())
+            hostname = self.itemText(item.parent())
             testbed = self.itemText(item.parent().parent())
             if status == tag_status['add']:
             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):
-                    rspec.add_default_sliver_attribute(tag, value, testbed)
+                print "Add tag to (%s, %s): %s/%s " % (testbed, hostname, tag, value)
+                if hostname.startswith(default_tags):
+                    rspec.version.add_default_sliver_attribute(tag, value, testbed)
                 else:
                 else:
-                    rspec.add_sliver_attribute(node, tag, value, testbed)
+                    node = rspec_node_names.get(hostname, None)
+                    if node:
+                        rspec.version.add_sliver_attribute(node['component_id'], tag, value, testbed)
                 change = True
             elif status == tag_status['remove']:
                 change = True
             elif status == tag_status['remove']:
-                print "Remove tag from (%s, %s): %s/%s " % (testbed, node, tag, value)
-                if node.startsWith(default_tags):
-                    rspec.remove_default_sliver_attribute(tag, value, testbed)
+                print "Remove tag from (%s, %s): %s/%s " % (testbed, hostname, tag, value)
+                if hostname.startswith(default_tags):
+                    rspec.version.remove_default_sliver_attribute(tag, value, testbed)
                 else:
                 else:
-                    rspec.remove_sliver_attribute(node, tag, value, testbed)
+                    node = rspec_node_names.get(hostname, None)
+                    if node:
+                        rspec.version.remove_sliver_attribute(node['component_id'], tag, value, testbed)
                 change = True
 
         children = item.rowCount()
         for row in range(0, children):
                 change = True
 
         children = item.rowCount()
         for row in range(0, children):
-            status = self.process_subtree(rspec, item.child(row), depth + 1)
+            status = self.process_subtree(rspec, rspec_node_names, resource_node_names, item.child(row), depth + 1)
             change = change or status
 
         return change
             change = change or status
 
         return change
@@ -463,19 +487,50 @@ 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()
+
+        resource_node_names = self.nodesByName(resources.version.get_nodes())
+        rspec_node_names = self.nodesByName(rspec.version.get_nodes_with_slivers())
+
+        change = self.process_subtree(rspec, rspec_node_names, resource_node_names, 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...")
 
+    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
+
+        dlg = ClientSliceManager(self)
+        dlg.submit_pg_compat(rspec)
+        dlg.exec_()
+
+        self.setStatus("<font color='green'>Finished submitting. %d/%d aggs succeeded.</font>" %
+                      (dlg.submit_aggSuccessCount,dlg.submit_aggSuccessCount+dlg.submit_aggFailCount))
+        QTimer.singleShot(2500, self.refresh)
+
     def renew(self):
         dlg = RenewWindow(parent=self)
         dlg.exec_()
     def renew(self):
         dlg = RenewWindow(parent=self)
         dlg.exec_()
@@ -489,11 +544,33 @@ 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.retrieveResources()
+        self.setStatus("Refreshing resources. This will take some time...")
+
+    def nodesByNetwork(self, nodeList):
+        netDict = {}
+        for node in nodeList:
+            network_name = Xrn(node['component_manager_id']).get_hrn()
+            if network_name:
+                net = netDict.get(network_name, [])
+                if net == []:
+                    netDict[network_name] = net
+
+                net.append(node)
+
+        return netDict
 
 
-        self.process.retrieveRspec()
-        self.setStatus("Refreshing slice data. This will take some time...")
+    def nodesByName(self, nodeList, nameDict=None):
+        if nameDict==None:
+            nameDict = {}
+        for node in nodeList:
+            hostname = node.get("component_name", None)
+            if hostname and (not hostname in nameDict):
+                nameDict[hostname] = node
+
+        return nameDict
 
     def updateView(self):
         global already_in_nodes
 
     def updateView(self):
         global already_in_nodes
@@ -501,69 +578,95 @@ 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 = []
+        for network in rspec.version.get_networks():
+            network_name = network.get("name", None)
+            if (network_name != None) and (not network_name in networks):
+                networks.append(network_name)
+        for network in resources.version.get_networks():
+            network_name = network.get("name", None)
+            if (network_name != None) and (not network_name in networks):
+                networks.append(network_name)
+
+        resources_nodes = self.nodesByNetwork(resources.version.get_nodes())
+        rspec_nodes = self.nodesByNetwork(rspec.version.get_nodes_with_slivers())
+
         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)
-            sliver_nodes = rspec.get_nodes_with_slivers(network)
-            available_nodes = [ node for node in all_nodes if node not in sliver_nodes ]
+            all_nodes = resources_nodes.get(network, [])
+            sliver_nodes = rspec_nodes.get(network, [])
+
+            sliver_node_names = self.nodesByName(sliver_nodes)
+
+            available_nodes = [ node for node in all_nodes if node["component_name"] not in sliver_node_names ]
 
 
-            networkItem = QStandardItem(QString(network))
             msg = "%s Nodes\t%s Selected" % (len(all_nodes), len(sliver_nodes))
             msg = "%s Nodes\t%s Selected" % (len(all_nodes), len(sliver_nodes))
-            rootItem.appendRow([networkItem, QStandardItem(QString("")), QStandardItem(QString(msg)), QStandardItem(QString("network"))])
+            networkItem = self.nodeView.appendRow(rootItem, network, membership=msg, kind="network")
 
 
-            already_in_nodes += sliver_nodes
+            already_in_nodes += sliver_node_names.keys()
 
             # Add default slice tags
 
             # Add default slice tags
-            nodeItem = QStandardItem(QString("%s for %s" % (default_tags, network)))
-            statusItem = QStandardItem(QString(""))
-            nodeStatus = QStandardItem(QString(""))
-            networkItem.appendRow([nodeItem, nodeStatus, statusItem, QStandardItem(QString("defaults"))])
-            attrs = rspec.get_default_sliver_attributes(network)
-            for (name, value) in attrs:
+            nodeItem = self.nodeView.appendRow(networkItem, "%s for %s" % (default_tags, network), kind="defaults")
+            attrs = rspec.version.get_default_sliver_attributes(network)
+            for attr in attrs:
+                    name = attr.get("name", None)
+                    value = attr.get("value", None)
                     tagstring = QString("%s: %s" % (name, value))
                     tagstring = QString("%s: %s" % (name, value))
-                    tagItem = QStandardItem(tagstring)
-                    status = QStandardItem(QString(tag_status['in']))
-                    nodeStatus = QStandardItem(QString(""))
-                    nodeItem.appendRow([tagItem, nodeStatus, status, QStandardItem(QString("attribute"))])
+                    self.nodeView.appendRow(nodeItem, tagstring, membership=tag_status['in'], kind = "attribute")
 
             for node in sliver_nodes:
 
             for node in sliver_nodes:
-                nodeItem = QStandardItem(QString(node))
-                statusItem = QStandardItem(QString(node_status['in']))
-                nodeStatus = QStandardItem(QString(rspec.get_node_element(node, network).attrib.get("boot_state","")))
-                networkItem.appendRow([nodeItem, nodeStatus, statusItem, QStandardItem(QString("node"))])
-
-                attrs = rspec.get_sliver_attributes(node, network)
-                for (name, value) in attrs:
-                    tagstring = QString("%s: %s" % (name, value))
-                    tagItem = QStandardItem(tagstring)
-                    statusItem = QStandardItem(QString(tag_status['in']))
-                    nodeStatus = QStandardItem(QString(""))
-                    nodeItem.appendRow([tagItem, nodeStatus, statusItem, QStandardItem(QString("attribute"))])
+                nodeType = None
+                if ("hardware_types" in node):
+                    hardware_types = [x["name"] for x in node["hardware_types"]]
+                    nodeType = ",".join(hardware_types)
+                nodeItem = self.nodeView.appendRow(networkItem,
+                               node["component_name"],
+                               nodeStatus=node.get("boot_state", ""),
+                               nodeType=nodeType,
+                               membership=node_status['in'],
+                               kind="node")
+
+                attrs = rspec.version.get_sliver_attributes(node['component_id'], network)
+                for attr in attrs:
+                    name = attr.get("name", None)
+                    value = attr.get("value", None)
+                    self.nodeView.appendRow(nodeItem,
+                                            "%s: %s" % (name, value),
+                                            membership=tag_status['in'],
+                                            kind="attribute")
 
             for node in available_nodes:
 
             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","")))
-                networkItem.appendRow([nodeItem, nodeStatus, statusItem, QStandardItem(QString("node"))])
+                nodeType = None
+                if ("hardware_types" in node):
+                    hardware_types = [x["name"] for x in node["hardware_types"]]
+                    nodeType = ",".join(hardware_types)
+                self.nodeView.appendRow(networkItem,
+                               node["component_name"],
+                               nodeStatus = node.get("boot_state", ""),
+                               nodeType=nodeType,
+                               membership=node_status['out'],
+                               kind="node")
 
         self.filterModel.setSourceModel(self.nodeModel)
         self.filterModel.setDynamicSortFilter(True)
 
         self.filterModel.setSourceModel(self.nodeModel)
         self.filterModel.setDynamicSortFilter(True)
+        self.filterModel.sort(NAME_COLUMN)
 
 
-        headers = QStringList() << "Hostname or Tag" << "Node Status" << "Membership Status" << "Kind"
+        headers = QStringList() << "Hostname or Tag" << "Node Type" << "Node Status" << "Membership Status" << "Kind"
         self.nodeModel.setHorizontalHeaderLabels(headers)
 
         self.nodeModel.setHorizontalHeaderLabels(headers)
 
-        self.nodeView.setItemDelegateForColumn(0, self.nodeNameDelegate)
-        self.nodeView.setItemDelegateForColumn(1, self.nodeStatusDelegate)
+        self.nodeView.setItemDelegateForColumn(NAME_COLUMN, self.nodeNameDelegate)
+        self.nodeView.setItemDelegateForColumn(NODE_STATUS_COLUMN, self.nodeStatusDelegate)
         self.nodeView.setModel(self.filterModel)
         self.nodeView.hideColumn(KIND_COLUMN)
         self.nodeView.expandAll()
         self.nodeView.setModel(self.filterModel)
         self.nodeView.hideColumn(KIND_COLUMN)
         self.nodeView.expandAll()