Repository: fabric-testbed/InformationModel (language: python)

Issue:
For rel 0.25 
- Test/debug Neo4j code processing ads
- Move build deep sliver functions to ABCPropertyGraph
- Add index  by type to AttachedComponentInfo
- Need host names and actual PCI ids to support provisioning (in the ads)
- Up version of matplotlib, now that jupyter hub issue with the latest has been resolved

For rel 0.25 
- Test/debug Neo4j code processing ads
- Move build deep sliver functions to ABCPropertyGraph
- Add index  by type to AttachedComponentInfo
- Need host names and actual PCI ids to support provisioning (in the ads)
- Up version of matplotlib, now that jupyter hub issue with the latest has been resolved



Discussion / hints:

Fix patch:
diff --git a/fim/graph/abc_property_graph.py b/fim/graph/abc_property_graph.py
index 3b214fd..940e49f 100644
--- a/fim/graph/abc_property_graph.py
+++ b/fim/graph/abc_property_graph.py
@@ -34,8 +34,18 @@
 from abc import ABC, abstractmethod
 from typing import List, Dict, Any, Set
 
+import logging
 
-class ABCPropertyGraph(ABC):
+from fim.slivers.attached_components import ComponentSliver, AttachedComponentsInfo
+from fim.slivers.capacities_labels import Capacities, Labels
+from fim.slivers.delegations import Delegation
+from fim.slivers.interface_info import InterfaceSliver, InterfaceInfo
+from fim.slivers.network_node import NodeSliver
+from fim.slivers.switch_fabric import SwitchFabricSliver, SwitchFabricInfo
+from fim.graph.abc_property_graph_constants import ABCPropertyGraphConstants
+
+
+class ABCPropertyGraph(ABCPropertyGraphConstants):
     """
     Abstract Base class representing operations on a property graph of resources.
     Could be a delegation, a broker view of resources or a slice.
@@ -44,47 +54,8 @@ class ABCPropertyGraph(ABC):
     Graphs are distinguished by GraphID property on each node.
     """
 
-    FIELD_POOL = "pool"
-    FIELD_CAPACITY_POOL = "capacity_pool"
-    FIELD_LABEL_POOL = "label_pool"
-    FIELD_DELEGATION = "delegation"
-    PROP_CAPACITY_DELEGATIONS = "CapacityDelegations"
-    PROP_LABEL_DELEGATIONS = "LabelDelegations"
-    PROP_CAPACITIES = "Capacities"
-    PROP_LABELS = "Labels"
-    JSON_PROPERTY_NAMES = [PROP_LABELS, PROP_CAPACITIES, PROP_LABEL_DELEGATIONS, PROP_CAPACITY_DELEGATIONS]
-    GRAPH_ID = 'GraphID'
-    NODE_ID = 'NodeID'
-    PROP_NAME = 'Name'
-    PROP_CLASS = 'Class'
-    PROP_TYPE = 'Type'
-    PROP_MODEL = 'Model'
-    PROP_LAYER = 'Layer'
-    PROP_TECHNOLOGY = 'Technology'
-    PROP_SITE = 'Site'
-    PROP_IMAGE_REF = 'ImageRef'
-    PROP_MGMT_IP = 'MgmtIp'
-    PROP_ALLOCATION_CONSTRAINTS = 'AllocationConstraints'
-    PROP_SERVICE_ENDPOINT = 'ServiceEndpoint'
-    PROP_DETAILS = 'Details'
-
-    CLASS_NetworkNode = 'NetworkNode'
-    CLASS_Component = 'Component'
-    CLASS_SwitchFabric = 'SwitchFabric'
-    CLASS_ConnectionPoint = 'ConnectionPoint'
-    CLASS_Link = 'Link'
-    CLASS_CompositeLink = 'CompositeLink'
-    CLASS_CompositeNode = 'CompositeNode'
-    CLASS_MeasurementPoint = 'MeasurementPoint'
-
-    REL_HAS = 'has'
-    REL_CONNECTS = 'connects'
-    REL_DEPENDS = 'depends'
-    REL_ADAPTS = 'adapts'
-    REL_PEERS = 'peers'
-
     @abstractmethod
-    def __init__(self, *, graph_id: str, importer):
+    def __init__(self, *, graph_id: str, importer, logger=None):
         """
         New graph with an id
         :param graph_id:
@@ -93,6 +64,10 @@ def __init__(self, *, graph_id: str, importer):
         assert importer is not None
         self.graph_id = graph_id
         self.importer = importer
+        if logger is None:
+            self.log = logging.getLogger(__name__)
+        else:
+            self.log = logger
 
     def get_graph_id(self):
         return self.graph_id
@@ -370,6 +345,164 @@ def _validate_all_json_properties(self) -> None:
                     raise PropertyGraphImportException(graph_id=self.graph_id, node_id=node,
                                                        msg=f"Unable to parse JSON property {prop} with value {prop_val}")
 
+    @staticmethod
+    def node_sliver_from_graph_properties_dict(d: Dict[str, str]) -> NodeSliver:
+        n = NodeSliver()
+        if d.get(ABCPropertyGraph.PROP_IMAGE_REF, None) is None:
+            image_ref = None
+            image_type = None
+        else:
+            image_ref, image_type = d[ABCPropertyGraph.PROP_IMAGE_REF].split(',')
+        n.set_properties(resource_name=d.get(ABCPropertyGraph.PROP_NAME, None),
+                         resource_type=n.type_from_str(d.get(ABCPropertyGraph.PROP_TYPE, None)),
+                         capacities=Capacities().from_json(d.get(ABCPropertyGraph.PROP_CAPACITIES, None)),
+                         labels=Labels().from_json(d.get(ABCPropertyGraph.PROP_LABELS, None)),
+                         capacity_delegations=Delegation.from_json_to_list(
+                             d.get(ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS, None)),
+                         label_delegations=Delegation.from_json_to_list(
+                             d.get(ABCPropertyGraph.PROP_LABEL_DELEGATIONS, None)),
+                         site=d.get(ABCPropertyGraph.PROP_SITE, None),
+                         image_ref=image_ref,
+                         image_type=image_type,
+                         management_ip=d.get(ABCPropertyGraph.PROP_MGMT_IP, None),
+                         allocation_constraints=d.get(ABCPropertyGraph.PROP_ALLOCATION_CONSTRAINTS, None),
+                         service_endpoint=d.get(ABCPropertyGraph.PROP_SERVICE_ENDPOINT, None),
+                         details=d.get(ABCPropertyGraph.PROP_DETAILS, None)
+                         )
+        return n
+
+    @staticmethod
+    def component_sliver_from_graph_properties_dict(d: Dict[str, str]) -> ComponentSliver:
+        """
+        Create component sliver from node graph properties
+        :param d:
+        :return:
+        """
+        cs = ComponentSliver()
+        cs.set_properties(name=d.get(ABCPropertyGraph.PROP_NAME, None),
+                          resource_type=cs.type_from_str(d.get(ABCPropertyGraph.PROP_TYPE, None)),
+                          capacities=Capacities().from_json(d.get(ABCPropertyGraph.PROP_CAPACITIES, None)),
+                          labels=Labels().from_json(d.get(ABCPropertyGraph.PROP_LABELS, None)),
+                          capacity_delegations=Delegation.from_json_to_list(
+                              d.get(ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS, None)),
+                          label_delegations=Delegation.from_json_to_list(
+                              d.get(ABCPropertyGraph.PROP_LABEL_DELEGATIONS, None)),
+                          details=d.get(ABCPropertyGraph.PROP_DETAILS, None)
+                          )
+        return cs
+
+    @staticmethod
+    def switch_fabric_sliver_from_graph_properties_dict(d: Dict[str, str]) -> SwitchFabricSliver:
+        """
+        SwitchFabric sliver from node graph properties
+        :param d:
+        :return:
+        """
+        sf = SwitchFabricSliver()
+        sf.set_properties(name=d.get(ABCPropertyGraph.PROP_NAME, None),
+                          resource_type=sf.type_from_str(d.get(ABCPropertyGraph.PROP_TYPE, None)),
+                          capacities=Capacities().from_json(d.get(ABCPropertyGraph.PROP_CAPACITIES, None)),
+                          labels=Labels().from_json(d.get(ABCPropertyGraph.PROP_LABELS, None)),
+                          capacity_delegations=Delegation.from_json_to_list(
+                              d.get(ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS, None)),
+                          label_delegations=Delegation.from_json_to_list(
+                              d.get(ABCPropertyGraph.PROP_LABEL_DELEGATIONS, None)),
+                          layer=sf.layer_from_str(d.get(ABCPropertyGraph.PROP_LAYER, None)),
+                          details=d.get(ABCPropertyGraph.PROP_DETAILS, None)
+                          )
+        return sf
+
+    @staticmethod
+    def interface_sliver_from_graph_properties_dict(d: Dict[str, str]) -> InterfaceSliver:
+        """
+        Interface sliver from node graph properties
+        :param d:
+        :return:
+        """
+        isl = InterfaceSliver()
+        isl.set_properties(name=d.get(ABCPropertyGraph.PROP_NAME, None),
+                           resource_type=isl.type_from_str(d.get(ABCPropertyGraph.PROP_TYPE, None)),
+                           capacities=Capacities().from_json(d.get(ABCPropertyGraph.PROP_CAPACITIES, None)),
+                           labels=Labels().from_json(d.get(ABCPropertyGraph.PROP_LABELS, None)),
+                           capacity_delegations=Delegation.from_json_to_list(
+                               d.get(ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS, None)),
+                           label_delegations=Delegation.from_json_to_list(
+                               d.get(ABCPropertyGraph.PROP_LABEL_DELEGATIONS, None)),
+                           details=d.get(ABCPropertyGraph.PROP_DETAILS, None)
+                           )
+        return isl
+
+    def build_deep_node_sliver(self, *, node_id: str) -> NodeSliver:
+        """
+        Build a deep NetworkNode or other similar (e.g.
+        network-attached storage) sliver from a graph node
+        :param node_id:
+        :return:
+        """
+        clazzes, props = self.get_node_properties(node_id=node_id)
+        if ABCPropertyGraph.CLASS_NetworkNode not in clazzes:
+            raise PropertyGraphQueryException(node_id=node_id, graph_id=self.graph_id,
+                                              msg="Node is not of class NetworkNode")
+        # create top-level sliver
+        ns = self.node_sliver_from_graph_properties_dict(props)
+        # find and build deep slivers of switch fabrics (if any) and components (if any)
+        comps = self.get_first_neighbor(node_id=node_id, rel=ABCPropertyGraph.REL_HAS,
+                                        node_label=ABCPropertyGraph.CLASS_Component)
+        if comps is not None and len(comps) > 0:
+            aci = AttachedComponentsInfo()
+            for c in comps:
+                cs = self.build_deep_component_sliver(node_id=c)
+                aci.add_device(cs)
+            ns.attached_components_info = aci
+
+        sfs = self.get_first_neighbor(node_id=node_id, rel=ABCPropertyGraph.REL_HAS,
+                                      node_label=ABCPropertyGraph.CLASS_SwitchFabric)
+        if sfs is not None and len(sfs) > 0:
+            sfi = SwitchFabricInfo()
+            for s in sfs:
+                sfsl = self.build_deep_sf_sliver(node_id=s)
+                sfi.add_switch_fabric(sfsl)
+            ns.switch_fabric_info = sfi
+
+        return ns
+
+    def build_deep_sf_sliver(self, *, node_id: str) -> SwitchFabricSliver:
+        clazzes, props = self.get_node_properties(node_id=node_id)
+        if ABCPropertyGraph.CLASS_SwitchFabric not in clazzes:
+            raise PropertyGraphQueryException(node_id=node_id, graph_id=self.graph_id,
+                                              msg="Node is not of class SwitchFabric")
+        # create top-level sliver
+        sfs = self.switch_fabric_sliver_from_graph_properties_dict(props)
+        # find interfaces and attach
+        ifs = self.get_first_neighbor(node_id=node_id, rel=ABCPropertyGraph.REL_CONNECTS,
+                                      node_label=ABCPropertyGraph.CLASS_ConnectionPoint)
+        if ifs is not None and len(ifs) > 0:
+            ifi = InterfaceInfo()
+            for i in ifs:
+                _, iprops = self.get_node_properties(node_id=i)
+                ifsl = self.interface_sliver_from_graph_properties_dict(iprops)
+                ifi.add_interface(ifsl)
+            sfs.interface_info = ifi
+        return sfs
+
+    def build_deep_component_sliver(self, *, node_id: str) -> ComponentSliver:
+        clazzes, props = self.get_node_properties(node_id=node_id)
+        if ABCPropertyGraph.CLASS_Component not in clazzes:
+            raise PropertyGraphQueryException(node_id=node_id, graph_id=self.graph_id,
+                                              msg="Node is not of class Component")
+        # create top-level sliver
+        cs = self.component_sliver_from_graph_properties_dict(props)
+        # find any switch fabrics, build and attach
+        sfs = self.get_first_neighbor(node_id=node_id, rel=ABCPropertyGraph.REL_HAS,
+                                      node_label=ABCPropertyGraph.CLASS_SwitchFabric)
+        if sfs is not None and len(sfs) > 0:
+            sfi = SwitchFabricInfo()
+            for s in sfs:
+                sfsl = self.build_deep_sf_sliver(node_id=s)
+                sfi.add_switch_fabric(sfsl)
+            cs.switch_fabric_info = sfi
+        return cs
+
 
 class ABCGraphImporter(ABC):
     @abstractmethod
diff --git a/fim/graph/abc_property_graph_constants.py b/fim/graph/abc_property_graph_constants.py
new file mode 100644
index 0000000..53f5c1a
--- /dev/null
+++ b/fim/graph/abc_property_graph_constants.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+# MIT License
+#
+# Copyright (c) 2020 FABRIC Testbed
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+#
+# Author: Ilya Baldin (ibaldin@renci.org)
+from abc import ABC
+
+
+class ABCPropertyGraphConstants(ABC):
+
+    # When a property is present, but not field, Neo4j returns word 'None'
+    # NetworkX just has python None
+    NEO4j_NONE = "None"
+
+    FIELD_POOL = "pool"
+    FIELD_CAPACITY_POOL = "capacity_pool"
+    FIELD_LABEL_POOL = "label_pool"
+    FIELD_DELEGATION = "delegation"
+    PROP_CAPACITY_DELEGATIONS = "CapacityDelegations"
+    PROP_LABEL_DELEGATIONS = "LabelDelegations"
+    PROP_CAPACITIES = "Capacities"
+    PROP_LABELS = "Labels"
+    JSON_PROPERTY_NAMES = [PROP_LABELS, PROP_CAPACITIES, PROP_LABEL_DELEGATIONS, PROP_CAPACITY_DELEGATIONS]
+    GRAPH_ID = 'GraphID'
+    NODE_ID = 'NodeID'
+    PROP_NAME = 'Name'
+    PROP_CLASS = 'Class'
+    PROP_TYPE = 'Type'
+    PROP_MODEL = 'Model'
+    PROP_LAYER = 'Layer'
+    PROP_TECHNOLOGY = 'Technology'
+    PROP_SITE = 'Site'
+    PROP_IMAGE_REF = 'ImageRef'
+    PROP_MGMT_IP = 'MgmtIp'
+    PROP_ALLOCATION_CONSTRAINTS = 'AllocationConstraints'
+    PROP_SERVICE_ENDPOINT = 'ServiceEndpoint'
+    PROP_DETAILS = 'Details'
+
+    CLASS_NetworkNode = 'NetworkNode'
+    CLASS_Component = 'Component'
+    CLASS_SwitchFabric = 'SwitchFabric'
+    CLASS_ConnectionPoint = 'ConnectionPoint'
+    CLASS_Link = 'Link'
+    CLASS_CompositeLink = 'CompositeLink'
+    CLASS_CompositeNode = 'CompositeNode'
+    CLASS_MeasurementPoint = 'MeasurementPoint'
+
+    REL_HAS = 'has'
+    REL_CONNECTS = 'connects'
+    REL_DEPENDS = 'depends'
+    REL_ADAPTS = 'adapts'
+    REL_PEERS = 'peers'
\ No newline at end of file
diff --git a/fim/graph/neo4j_property_graph.py b/fim/graph/neo4j_property_graph.py
index 797920b..021c403 100644
--- a/fim/graph/neo4j_property_graph.py
+++ b/fim/graph/neo4j_property_graph.py
@@ -50,8 +50,6 @@ class Neo4jPropertyGraph(ABCPropertyGraph):
     Neo4j-specific implementation of property graph abstraction
     """
 
-    NEO4j_NONE = "None"
-
     def __init__(self, *, graph_id: str, importer, logger=None):
         """
         Initialize a property graph object. Logger is optional.
@@ -59,13 +57,9 @@ def __init__(self, *, graph_id: str, importer, logger=None):
         :param importer:
         :param logger:
         """
-        super().__init__(graph_id=graph_id, importer=importer)
+        super().__init__(graph_id=graph_id, importer=importer, logger=logger)
         assert isinstance(importer, Neo4jGraphImporter)
         self.driver = importer.driver
-        if logger is None:
-            self.log = logging.getLogger(__name__)
-        else:
-            self.log = logger
 
     def _validate_graph(self, rules_file: str) -> None:
         """ validate the graph imported in Neo4j according to a set of given Cypher rules"""
diff --git a/fim/graph/networkx_property_graph.py b/fim/graph/networkx_property_graph.py
index bb96e2a..5a07b18 100644
--- a/fim/graph/networkx_property_graph.py
+++ b/fim/graph/networkx_property_graph.py
@@ -55,13 +55,9 @@ def __init__(self, *, graph_id: str, importer, logger=None):
         :param importer: should be an instance of NetworkXGraphImporter
         :param logger: optional
         """
-        super().__init__(graph_id=graph_id, importer=importer)
+        super().__init__(graph_id=graph_id, importer=importer, logger=logger)
         assert isinstance(importer, NetworkXGraphImporter)
         self.storage = importer.storage
-        if logger is None:
-            self.log = logging.getLogger(__name__)
-        else:
-            self.log = logger
 
     def validate_graph(self) -> None:
         """
diff --git a/fim/graph/resources/abc_adm.py b/fim/graph/resources/abc_adm.py
index fd0661c..c05b717 100644
--- a/fim/graph/resources/abc_adm.py
+++ b/fim/graph/resources/abc_adm.py
@@ -26,20 +26,22 @@
 """
 Abstract definition of ADM (Aggregate Delegation Model) functionality
 """
+import json
 
 from abc import ABCMeta, abstractmethod
+from ..abc_property_graph import ABCPropertyGraph
+from ...slivers.delegations import ARMDelegations, ARMPools, DelegationType
 
 
-class ABCADMMixin(metaclass=ABCMeta):
+class ABCADMPropertyGraph(ABCPropertyGraph):
     """
     Interface for an ADM Mixin on top of a property graph
     """
-    @classmethod
-    def __subclasshook__(cls, subclass):
-        return (hasattr(subclass, 'rewrite_delegations') and
-                callable(subclass.rewrite_delegations) or NotImplemented)
 
     @abstractmethod
+    def __init__(self, *, graph_id: str, importer, logger=None):
+        super().__init__(graph_id=graph_id, importer=importer, logger=logger)
+
     def rewrite_delegations(self, *, real_adm_id: str = None) -> None:
         """
         Rewrite label and capacity delegations on all nodes to be dictionaries
@@ -50,3 +52,99 @@ def rewrite_delegations(self, *, real_adm_id: str = None) -> None:
         :param real_adm_id:
         :return:
         """
+        for node_id in self.list_all_node_ids():
+            _, node_props = self.get_node_properties(node_id=node_id)
+            props_modified = False
+            for delegation_prop_name in [self.PROP_LABEL_DELEGATIONS, self.PROP_CAPACITY_DELEGATIONS]:
+                if delegation_prop_name in node_props.keys():
+                    if node_props.get(delegation_prop_name, None) is None or \
+                            node_props[delegation_prop_name] == self.NEO4j_NONE:
+                        continue
+                    props_modified = True
+                    # turn delegation into Python object
+                    delegation = json.loads(node_props[delegation_prop_name])
+                    new_delegation = dict()
+                    if real_adm_id is None:
+                        new_delegation[self.graph_id] = delegation
+                    else:
+                        new_delegation[real_adm_id] = delegation
+                    new_delegation_json = json.dumps(new_delegation)
+                    node_props[delegation_prop_name] = new_delegation_json
+            if props_modified:
+                # write back
+                self.update_node_properties(node_id=node_id, props=node_props)
+
+    def annotate_delegations_and_pools(self, *, dels: ARMDelegations, pools: ARMPools):
+        """
+        Take delegations and pools and write them into the graph as appropriate.
+        This assumes that pools.build_index_by_delegation_id() has been run.
+        Pools and delegations are assumed to be of the same type (Capacity or Label).
+        :param dels:
+        :param pools:
+        :return:
+        """
+        assert dels is not None
+        assert pools is not None
+        assert pools.pool_type == dels.type
+
+        delegations_and_pools_per_node = dict()
+
+        # delegations are organized in lists by delegation id, each delegation
+        # is a label or capacity specification with delegation id
+        # need to break them up by node to attach to appropriate nodes
+        for k, v in dels.delegations.items():
+            # each entry is a list
+            for d in v:
+                # multiple (capacity or label) delegations can be defined on the same node
+                node_delegations = delegations_and_pools_per_node.get(d.on_, list())
+                full_details = d.get_full_details()
+                if full_details is not None:
+                    node_delegations.append(full_details)
+                    delegations_and_pools_per_node[d.on_] = node_delegations
+
+        # pools are more complicated than delegations.
+        # A pool definition includes capacities or labels,
+        # a delegation id and a pool id. Pool mentions must be present on all nodes
+        # included in the pool (including the node where pool is defined).
+
+        # we need a place to put pool mentions for now
+        pool_mentions_per_node = dict()
+
+        if pools.pools_by_delegation is not None:
+            for k, pool_list in pools.pools_by_delegation.items():
+                # each entry is a list
+                for pool in pool_list:
+                    node_delegations = delegations_and_pools_per_node.get(pool.on_, list())
+                    full_details = pool.get_full_pool_details()
+                    if full_details is not None:
+                        node_delegations.append(full_details)
+                        delegations_and_pools_per_node[pool.on_] = node_delegations
+                        node_mentions = set()
+                        node_mentions.update(pool.get_defined_for())
+                        node_mentions.add(pool.on_)
+                        # add self on_ and for_ mentions into pool_mentions_per_node
+                        for for_node in node_mentions:
+                            node_pool_list = pool_mentions_per_node.get(for_node, list())
+                            node_pool_list.append(pool.pool_id)
+                            pool_mentions_per_node[for_node] = node_pool_list
+
+        # update graph from delegations_and_pools and pool_mentions per node
+        # get the combined set of affected node ids
+        affected_nodes = set()
+        affected_nodes.update(delegations_and_pools_per_node.keys())
+        affected_nodes.update(pool_mentions_per_node.keys())
+
+        for node in affected_nodes:
+            node_delegations = list()
+            if delegations_and_pools_per_node.get(node, None) is not None:
+                node_delegations.extend(delegations_and_pools_per_node[node])
+            if pool_mentions_per_node.get(node, None) is not None:
+                for pool_id in pool_mentions_per_node[node]:
+                    node_delegations.append({ABCPropertyGraph.FIELD_POOL: pool_id})
+
+            if dels.type == DelegationType.CAPACITY:
+                self.update_node_property(node_id=node, prop_name=ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS,
+                                          prop_val=json.dumps(node_delegations))
+            else:
+                self.update_node_property(node_id=node, prop_name=ABCPropertyGraph.PROP_LABEL_DELEGATIONS,
+                                          prop_val=json.dumps(node_delegations))
diff --git a/fim/graph/resources/abc_arm.py b/fim/graph/resources/abc_arm.py
index 66ae478..1a92c9e 100644
--- a/fim/graph/resources/abc_arm.py
+++ b/fim/graph/resources/abc_arm.py
@@ -26,38 +26,400 @@
 """
 Abstract definition of ARM (Aggregate Resource Model) functionality
 """
-
-from abc import ABCMeta, abstractmethod
+from typing import Dict
+from abc import abstractmethod
 from typing import List
+from recordclass import recordclass
+import json
+import uuid
 
-from ..abc_property_graph import ABCPropertyGraph
-from fim.slivers.delegations import DelegationType
+from ..abc_property_graph import ABCPropertyGraph, PropertyGraphQueryException
+from fim.slivers.delegations import Delegation, DelegationType, Pool, ARMPools, ARMDelegations
+from ..typed_tuples import Label, Capacity, LabelOrCapacityException
 
 
-class ABCARMMixin(metaclass=ABCMeta):
+class ABCARMPropertyGraph(ABCPropertyGraph):
     """
     Interface for an ARM Mixin on top of a property graph
     """
-    @classmethod
-    def __subclasshook__(cls, subclass):
-        return (hasattr(subclass, 'generate_adms') and
-                callable(subclass.generate_adms) or NotImplemented)
+    POOL_TYPE_TO_CLASS = {DelegationType.LABEL: (ABCPropertyGraph.PROP_LABEL_DELEGATIONS,
+                                                 ABCPropertyGraph.FIELD_LABEL_POOL, Label),
+                          DelegationType.CAPACITY: (ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS,
+                                                    ABCPropertyGraph.FIELD_CAPACITY_POOL, Capacity)}
+
+    DEFAULT_DELEGATION = "default"
+    LabelsAndProperties = recordclass('LabelsAndProperties', ['labels', 'properties'])
+    DelegationInfo = recordclass('DelegationInfo', ['graph_id',
+                                                    'graph',
+                                                    'keep_nodes',
+                                                    'remove_nodes'])
 
     @abstractmethod
-    def generate_adms(self) -> List[ABCPropertyGraph]:
+    def __init__(self, *, graph: ABCPropertyGraph, logger=None):
+        """
+        Initialize ARM - supply an implementation of a graph and a graph id
+        :param graph:
+        :param graph:
+        """
+        super().__init__(graph_id=graph.graph_id, importer=graph.importer, logger=logger)
+        self.label_pools = ARMPools(atype=DelegationType.LABEL)
+        self.capacity_pools = ARMPools(atype=DelegationType.CAPACITY)
+        self.label_delegations = ARMDelegations(atype=DelegationType.LABEL)
+        self.capacity_delegations = ARMDelegations(atype=DelegationType.CAPACITY)
+        self.pools = {DelegationType.LABEL: self.label_pools,
+                      DelegationType.CAPACITY: self.capacity_pools}
+        self.delegations = {DelegationType.LABEL: self.label_delegations,
+                            DelegationType.CAPACITY: self.capacity_delegations}
+        self.nodes = dict()
+        self.node_ids = None
+
+    def _find_pool_definition(self, *, delegation: Dict, pool_type: DelegationType) -> (str, str):
         """
-        Partition an already loaded ARM model into multiple ADMs, return the GUIDs
-        of the ADM partitions
+        Inspect a single delegation to find pool definition and delegation fields, return as a Tuple
+        <pool_id, delegation_id>. When delegation is not specified, delegation_id is set to "default".
+        :param delegation: single dictionary of a delegation
+        :param pool_type: type of a pool (label or capacity)
         :return:
         """
+        _, pool_field, pool_cls = self.POOL_TYPE_TO_CLASS[pool_type]
+        self.log.debug(f"Looking for pool definition in {delegation} of type {pool_type.name}")
+        if pool_field in delegation.keys():
+            # parse the delegation pool
+            pool = pool_cls(atype=pool_field, aval=delegation[pool_field])
+            if self.FIELD_DELEGATION in delegation.keys():
+                delegation_id = delegation[self.FIELD_DELEGATION]
+            else:
+                delegation_id = self.DEFAULT_DELEGATION
+            return pool.get_val(), delegation_id
+        return None, None
+
+    def _find_pool_mentions(self, *, delegation: Dict, pool_type: DelegationType) -> List[str]:
+        """
+        Inspect a single delegation to find pool mentions and return a list of all mentioned pool_ids
+        or empty list
+        :param delegation: single dictionary of a delegation
+        :param pool_type:
+        :return:
+        """
+        self.log.debug(f"Looking for pool mention in {delegation} of type {pool_type.name}")
+        if ABCARMPropertyGraph.FIELD_POOL in delegation.keys():
+            # return the list
+            mention = delegation[ABCARMPropertyGraph.FIELD_POOL]
+            if isinstance(mention, list):
+                return mention
+            return [mention]
+        return []
+
+    def _process_delegation_no_pools(self, *, delegation: Dict, dele_type: DelegationType, node_id: str) -> Delegation:
+        """
+        process a delegation with no pools, get a delegation id from delegation dictionary or
+        assign 'default' and return a fully formed Delegation object
+        :param delegation:
+        :param dele_type:
+        :return: delegation id
+        """
+        # extract the delegation id
+        if self.FIELD_DELEGATION in delegation.keys():
+            delegation_id = delegation[self.FIELD_DELEGATION]
+        else:
+            delegation_id = self.DEFAULT_DELEGATION
+        delegation_obj = Delegation(atype=dele_type, defined_on=node_id, delegation_id=delegation_id)
+        delegation_obj.set_details(dele_dict=delegation)
+        return delegation_obj
+
+    def _process_single_delegation(self, *, delegation: Dict, dele_type: DelegationType, node_id: str):
+        """
+        Process a single delegation dictionary for pool definitions, pool mentions or individual delegations.
+        Update pools and delegations fields of this object
+        :param delegation:
+        :param dele_type:
+        :param node_id
+        :return:
+        """
+        # check for pool definitions
+        pool_id, del_id = self._find_pool_definition(delegation=delegation,
+                                                     pool_type=dele_type)
+        self.log.debug(f"Single: Decoded pool {pool_id}, delegation {del_id}")
+        with_pool = False
+
+        if pool_id is not None:
+            # if the pool is not defined, create a new one, if it is defined augment the definition
+            pool = self.pools[dele_type].get_pool_by_id(pool_id=pool_id)
+            pool.set_delegation_id(delegation_id=del_id)
+            pool.set_defined_on(node_id=node_id)
+            pool.set_pool_details(pool_dict=delegation)
+            with_pool = True
+
+        # check for pool mentions
+        pool_mentions = self._find_pool_mentions(delegation=delegation, pool_type=dele_type)
+        for pool_id in pool_mentions:
+            # if this pool already defined, add node id to list for which it is defined
+            pool = self.pools[dele_type].get_pool_by_id(pool_id=pool_id)
+            pool.add_defined_for(node_ids=node_id)
+            with_pool = True
+
+        # check for plain delegation (without a pool)
+        if not with_pool:
+            delegation_obj = self._process_delegation_no_pools(delegation=delegation,
+                                                               dele_type=dele_type,
+                                                               node_id=node_id)
+            self.delegations[dele_type].add_delegation(delegation=delegation_obj)
+
+    def _find_delegations_in_properties(self, *, props: Dict, pool_type: DelegationType, node_id: str) -> None:
+        """
+        Check if a delegation property of a node ( label or capacity) contains a
+        pool (label or capacity) declaration, return a list of (pool_id, delegation_id, JSON delegation)
+        tuples or empty list. Also process single delegations not included in pool definitions
+        :param props: all properties of a node
+        :param pool_type
+        :param node_id
+        :return:
+        """
+        if pool_type not in self.POOL_TYPE_TO_CLASS.keys():
+            raise LabelOrCapacityException(f"Invalid pool type {pool_type}")
+
+        delegation_prop_name, _, _ = self.POOL_TYPE_TO_CLASS[pool_type]
+        self.log.debug(f"Checking properties {props} for {delegation_prop_name} pool definitions")
+        if delegation_prop_name in props.keys():
+            # get the list of delegations as a JSON string
+            # note that Neo4j returns string 'None' if property is not filled, but present
+            if props.get(delegation_prop_name, None) is None or \
+                    props[delegation_prop_name] == self.NEO4j_NONE:
+                return
+
+            self.log.debug(f"Decoding {delegation_prop_name} delegations {props[delegation_prop_name]} on node {node_id}")
+            delegations = json.loads(props[delegation_prop_name])
+
+            if not isinstance(delegations, list):
+                if not isinstance(delegations, dict):
+                    raise LabelOrCapacityException(f"Delegation {delegations} must be a list "
+                                                   "of dictionaries or a dictionary")
+                # just a single dictionary
+                self._process_single_delegation(delegation=delegations,
+                                                dele_type=pool_type, node_id=node_id)
+            else:
+                # a list of dictionaries
+                for delegation in delegations:
+                    self._process_single_delegation(delegation=delegation,
+                                                    dele_type=pool_type, node_id=node_id)
+
+    def _catalog_delegations(self) -> None:
+        """
+        Locate all nodes with delegations in them, taking into account pools.
+        :return:
+        """
+
+        # collect information about pools
+        for node_id in self.node_ids:
+            self.nodes[node_id].labels, self.nodes[node_id].properties = self.get_node_properties(node_id=node_id)
+            self.log.debug(f"Processing node {node_id} for {self.PROP_LABEL_DELEGATIONS} "
+                           f"and {self.PROP_CAPACITY_DELEGATIONS}")
+            # if a node doesn't have delegations we skip it
+            if self.PROP_LABEL_DELEGATIONS not in self.nodes[node_id].properties.keys() and \
+                    self.PROP_CAPACITY_DELEGATIONS not in self.nodes[node_id].properties.keys():
+                continue
+            # look for label and capacity pool definitions and mentions
+            self._find_delegations_in_properties(props=self.nodes[node_id].properties,
+                                                 pool_type=DelegationType.LABEL, node_id=node_id)
+
+            self._find_delegations_in_properties(props=self.nodes[node_id].properties,
+                                                 pool_type=DelegationType.CAPACITY, node_id=node_id)
+            # also look for delegations without pools
+
+        # build indices on pools so we can find them from a delegation id
+        for _, pools in self.pools.items():
+            pools.build_index_by_delegation_id()
+
+    def _update_delegation_properties(self, *, del_id: str, del_type: DelegationType,
+                                      graph: ABCPropertyGraph,
+                                      node_ids: List[str]):
+        """
+        Update capacity or label delegations on graph nodes based on previously collection
+        delegations information. Use the information about kept nodes to speed things up.
+        :param del_id:
+        :param del_type:
+        :param graph:
+        :param node_ids:
+        :return:
+        """
+        assert node_ids is not None
+        assert graph is not None
+        assert del_id is not None
+
+        delegation_prop_name, _, _ = self.POOL_TYPE_TO_CLASS[del_type]
+        collected_delegations = self.delegations[del_type]
+        delegations_list = collected_delegations.get_by_delegation_id(delegation_id=del_id)
+        collected_pools = self.pools[del_type]
+        pools_list = collected_pools.get_pools_by_delegation_id(delegation_id=del_id)
+
+        # first fill in a temporary dict with delegation info, then write it into properties
+        new_delegations = dict()
+        # collect pool mentions separately, then merge
+        new_pool_mentions = dict()
+        # walk through all node_ids and check delegations and pools
+        for node_id in node_ids:
+            # each entry matches a node id and is a list of dictionaries
+            new_delegations[node_id] = list()
+
+            if delegations_list is not None:
+                # find if there is a delegation on this node
+                for delegation in delegations_list:
+                    if delegation.on_ == node_id:
+                        new_delegations[node_id].append(delegation.get_details())
+
+            if pools_list is not None:
+                for pool in pools_list:
+                    if pool.on_ == node_id:
+                        # find if there was a pool definition on this node
+                        new_delegations[node_id].append(pool.get_pool_details())
+                        # pool definition includes a pool mention
+                        if new_pool_mentions.get(node_id, None) is None:
+                            new_pool_mentions[node_id] = list()
+                        new_pool_mentions[node_id].append(pool.pool_id)
+                    elif node_id in pool.for_:
+                        # find if there was a pool mention on this node
+                        # add it to 'pool' (if exists, create otherwise)
+                        if new_pool_mentions.get(node_id, None) is None:
+                            new_pool_mentions[node_id] = list()
+                        new_pool_mentions[node_id].append(pool.pool_id)
+
+        # merge pool mentions
+        for node_id, pool in new_pool_mentions.items():
+            new_delegations[node_id].append({ABCARMPropertyGraph.FIELD_POOL: pool})
+
+        # write appropriate property name into the node on appropriate graph
+        for node_id in node_ids:
+            node_delegation_info = new_delegations.get(node_id, None)
+            if node_delegation_info is not None and len(node_delegation_info) != 0:
+                node_delegation_str = json.dumps(node_delegation_info)
+                graph.update_node_property(node_id=node_id,
+                                           prop_name=delegation_prop_name,
+                                           prop_val=node_delegation_str)
+
+    def generate_adms(self) -> Dict:
+        """
+        Generate delegation models from the current ARM graph and return
+        the dictionary delegation_id: Neo4jPropertyGraph for generated ADMs
+        :return:
+        """
+        self.node_ids = self.list_all_node_ids()
+        if len(self.node_ids) == 0:
+            raise PropertyGraphQueryException(graph_id=self.graph_id,
+                                              node_id=None, msg="Unable to list nodes of ARM graph")
+        # build a dictionary to hold node labels and properties
+        self.nodes = {k: self.LabelsAndProperties(None, None) for k in self.node_ids}
+
+        # collect information on delegated pools and individual delegations
+        self._catalog_delegations()
+
+        # number of distinct delegation keys across label and capacity pools and plain delegations
+        # is the number of ADMs we will generate
+        unique_delegation_ids = self.label_pools.\
+            get_delegation_ids().union(self.capacity_pools.get_delegation_ids(),
+                                       self.label_delegations.get_delegation_ids(),
+                                       self.capacity_delegations.get_delegation_ids())
+
+        # generate unique graph ids and empty node sets for delegation graphs
+        # remember these tuples and the fields are not mutable
+        delegations_info = {del_id: self.DelegationInfo(graph_id=str(uuid.uuid4()),
+                                                        graph=None,
+                                                        keep_nodes=set(),
+                                                        remove_nodes=set())
+                            for del_id in unique_delegation_ids}
+
+        node_ids_to_delegations = {node_id: list() for node_id in self.node_ids}
+        for del_id in unique_delegation_ids:
+            # build up lists of node ids that definitely belong to each delegation
+            # as a union of all .for_ fields on pools and on all delegations
+            delegations_info[del_id].keep_nodes.update(
+                self.label_pools.get_node_ids(delegation_id=del_id),
+                self.capacity_pools.get_node_ids(delegation_id=del_id),
+                self.label_delegations.get_node_ids(delegation_id=del_id),
+                self.capacity_delegations.get_node_ids(delegation_id=del_id)
+            )
+            # also build a dictionary from node_id to a list of delegation ids
+            for node_id in delegations_info[del_id].keep_nodes:
+                node_ids_to_delegations[node_id].append(del_id)
+
+            # clone the original ARM graph into ADM graphs for each new graph ID
+            delegations_info[del_id].graph = self.clone_graph(new_graph_id=delegations_info[del_id].graph_id)
+
+        # in addition to definite keep nodes, we should also keep nodes that
+        # a) lie on shortest paths between two connection points we are keeping (to include the links)
+        # b) switch fabrics that connect to connection points
+        # c) network nodes that have switch fabrics
+        # Determine nodes to delete by doing a difference between all nodes and keep nodes
+        # Remove nodes
+        # Annotate keep nodes with proper delegation information
+
+        for del_id in unique_delegation_ids:
+            # get a list of connection points we are keeping
+            keep_cps = set()
+            for node_id in delegations_info[del_id].keep_nodes:
+                if 'ConnectionPoint' in self.nodes[node_id].labels:
+                    keep_cps.add(node_id)
+
+            # trace from connection points over links to other connection points
+            # remember connection points
+            new_cps = set()
+            for cp in keep_cps:
+                cp_neighbors = self.get_first_and_second_neighbor(node_id=cp,
+                                                                  rel1="connects",
+                                                                  node1_label="Link",
+                                                                  rel2="connects",
+                                                                  node2_label="ConnectionPoint")
+                for pair in cp_neighbors:
+                    # pair is a 2-element list
+                    delegations_info[del_id].keep_nodes.update(pair)
+                    new_cps.add(pair[1])
+
+            keep_cps.update(new_cps)
+            # from all keep cps (original and added), find switch fabrics and components or
+            # network nodes
+            for cp in keep_cps:
+                cp_neighbors = self.get_first_and_second_neighbor(node_id=cp,
+                                                                  rel1="connects",
+                                                                  node1_label="SwitchFabric",
+                                                                  rel2="has",
+                                                                  node2_label="NetworkNode")
+                for pair in cp_neighbors:
+                    delegations_info[del_id].keep_nodes.update(pair)
+
+                cp_neighbors = self.get_first_and_second_neighbor(node_id=cp,
+                                                                  rel1="connects",
+                                                                  node1_label="SwitchFabric",
+                                                                  rel2="has",
+                                                                  node2_label="Component")
+                for pair in cp_neighbors:
+                    delegations_info[del_id].keep_nodes.update(pair)
+
+            delegations_info[del_id].remove_nodes = set(self.node_ids)
+            delegations_info[del_id].remove_nodes.difference_update(delegations_info[del_id].keep_nodes)
+            for node_id in delegations_info[del_id].remove_nodes:
+                delegations_info[del_id].graph.delete_node(node_id=node_id)
+
+            # go through capacity delegations under this delegation id, replace annotations on kept nodes
+            # of approprate graph
+            self._update_delegation_properties(del_id=del_id,
+                                               del_type=DelegationType.LABEL,
+                                               graph=delegations_info[del_id].graph,
+                                               node_ids=delegations_info[del_id].keep_nodes)
+            self._update_delegation_properties(del_id=del_id,
+                                               del_type=DelegationType.CAPACITY,
+                                               graph=delegations_info[del_id].graph,
+                                               node_ids=delegations_info[del_id].keep_nodes)
+
+        return {k: delegations_info[k].graph for k in unique_delegation_ids}
 
-    @abstractmethod
     def get_delegations(self, *, node_id: str, delegation_type: DelegationType) -> List:
         """
-        Get Label or Capacity delegation from an ARM as a Python object. They are represented
-        as lists of dictionaries, with fields like ipv4, vlan or vlan-pool. May also include fields
-        to denote specific delegations.
+        Get Label or Capacity delegations of a given node. They are represented
+        as lists of dictionaries. Dictionaries may include delegation and pool
+        designators. Returns None if delegation property is not defined.
         :param node_id:
         :param delegation_type:
         :return:
-        """
\ No newline at end of file
+        """
+        assert node_id is not None
+        prop_field_name, _, _ = self.POOL_TYPE_TO_CLASS[delegation_type]
+        return self.get_node_json_property_as_object(node_id=node_id, prop_name=prop_field_name)
\ No newline at end of file
diff --git a/fim/graph/resources/neo4j_adm.py b/fim/graph/resources/neo4j_adm.py
index 45fe700..536967c 100644
--- a/fim/graph/resources/neo4j_adm.py
+++ b/fim/graph/resources/neo4j_adm.py
@@ -28,13 +28,12 @@
 """
 
 import uuid
-import json
 
 from ..neo4j_property_graph import Neo4jPropertyGraph, Neo4jGraphImporter
-from .abc_adm import ABCADMMixin
+from .abc_adm import ABCADMPropertyGraph
 
 
-class Neo4jADMGraph(Neo4jPropertyGraph, ABCADMMixin):
+class Neo4jADMGraph(ABCADMPropertyGraph, Neo4jPropertyGraph):
 
     def __init__(self, *, graph_id: str = None, importer: Neo4jGraphImporter, logger=None):
         """
@@ -47,33 +46,3 @@ def __init__(self, *, graph_id: str = None, importer: Neo4jGraphImporter, logger
 
         super().__init__(graph_id=graph_id, importer=importer, logger=logger)
 
-    def rewrite_delegations(self, *, real_adm_id: str = None) -> None:
-        """
-        Rewrite label and capacity delegations on all nodes to be dictionaries
-        referenced by ADM graph id. Note that external code should
-        not interact with delegations on ADM graphs.
-        Sometimes ADMs are cloned into temporary graphs so the method provides
-        a way to pass original ADM id as an option.
-        :param real_adm_id:
-        :return:
-        """
-        for node_id in self.list_all_node_ids():
-            _, node_props = self.get_node_properties(node_id=node_id)
-            props_modified = False
-            for delegation_prop_name in [self.PROP_LABEL_DELEGATIONS, self.PROP_CAPACITY_DELEGATIONS]:
-                if delegation_prop_name in node_props.keys():
-                    if node_props[delegation_prop_name] == self.NEO4j_NONE:
-                        continue
-                    props_modified = True
-                    # turn delegation into Python object
-                    delegation = json.loads(node_props[delegation_prop_name])
-                    new_delegation = dict()
-                    if real_adm_id is None:
-                        new_delegation[self.graph_id] = delegation
-                    else:
-                        new_delegation[real_adm_id] = delegation
-                    new_delegation_json = json.dumps(new_delegation)
-                    node_props[delegation_prop_name] = new_delegation_json
-            if props_modified:
-                # write back
-                self.update_node_properties(node_id=node_id, props=node_props)
diff --git a/fim/graph/resources/neo4j_arm.py b/fim/graph/resources/neo4j_arm.py
index 459b773..de11e8c 100644
--- a/fim/graph/resources/neo4j_arm.py
+++ b/fim/graph/resources/neo4j_arm.py
@@ -26,404 +26,22 @@
 """
 Implementation of ARM (Aggregate Resource Model) functionality
 """
-import json
-import logging
-import uuid
-from typing import List, Dict
-from recordclass import recordclass
-
-
 from ..neo4j_property_graph import Neo4jPropertyGraph
-from ..abc_property_graph import ABCPropertyGraph, PropertyGraphQueryException
-from .abc_arm import ABCARMMixin
-from ..typed_tuples import Label, Capacity, LabelOrCapacityException
+from .abc_arm import ABCARMPropertyGraph
 
-from fim.slivers.delegations import DelegationType, Pools, Delegations, Delegation
 
-
-class Neo4jARMGraph(Neo4jPropertyGraph, ABCARMMixin):
+class Neo4jARMGraph(ABCARMPropertyGraph, Neo4jPropertyGraph):
     """
     Implementation of ARM management
     """
-    POOL_TYPE_TO_CLASS = {DelegationType.LABEL: (ABCPropertyGraph.PROP_LABEL_DELEGATIONS,
-                                                 ABCPropertyGraph.FIELD_LABEL_POOL, Label),
-                          DelegationType.CAPACITY: (ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS,
-                                                    ABCPropertyGraph.FIELD_CAPACITY_POOL, Capacity)}
-
-    DEFAULT_DELEGATION = "default"
-    LabelsAndProperties = recordclass('LabelsAndProperties', ['labels', 'properties'])
-    DelegationInfo = recordclass('DelegationInfo', ['graph_id',
-                                                    'graph',
-                                                    'keep_nodes',
-                                                    'remove_nodes'])
-
-    def __init__(self, *, graph: Neo4jPropertyGraph):
-        """
-        Initialize ARM - supply an implementation of a graph and a graph id
-        :param graph:
-        :param graph:
-        """
-        super().__init__(graph_id=graph.graph_id, importer=graph.importer)
-        self.label_pools = Pools(atype=DelegationType.LABEL)
-        self.capacity_pools = Pools(atype=DelegationType.CAPACITY)
-        self.label_delegations = Delegations(atype=DelegationType.LABEL)
-        self.capacity_delegations = Delegations(atype=DelegationType.CAPACITY)
-        self.pools = {DelegationType.LABEL: self.label_pools,
-                      DelegationType.CAPACITY: self.capacity_pools}
-        self.delegations = {DelegationType.LABEL: self.label_delegations,
-                            DelegationType.CAPACITY: self.capacity_delegations}
-        self.log = logging.getLogger(__name__)
-        self.nodes = dict()
-        self.node_ids = None
-
-    def _find_pool_definition(self, *, delegation: Dict, pool_type: DelegationType) -> (str, str):
-        """
-        Inspect a single delegation to find pool definition and delegation fields, return as a Tuple
-        <pool_id, delegation_id>. When delegation is not specified, delegation_id is set to "default".
-        :param delegation: single dictionary of a delegation
-        :param pool_type: type of a pool (label or capacity)
-        :return:
-        """
-        _, pool_field, pool_cls = self.POOL_TYPE_TO_CLASS[pool_type]
-        self.log.debug(f"Looking for pool definition in {delegation} of type {pool_type.name}")
-        if pool_field in delegation.keys():
-            # parse the delegation pool
-            pool = pool_cls(atype=pool_field, aval=delegation[pool_field])
-            if self.FIELD_DELEGATION in delegation.keys():
-                delegation_id = delegation[self.FIELD_DELEGATION]
-            else:
-                delegation_id = self.DEFAULT_DELEGATION
-            return pool.get_val(), delegation_id
-        return None, None
-
-    def _find_pool_mentions(self, *, delegation: Dict, pool_type: DelegationType) -> List[str]:
-        """
-        Inspect a single delegation to find pool mentions and return a list of all mentioned pool_ids
-        or empty list
-        :param delegation: single dictionary of a delegation
-        :param pool_type:
-        :return:
-        """
-        self.log.debug(f"Looking for pool mention in {delegation} of type {pool_type.name}")
-        if Neo4jPropertyGraph.FIELD_POOL in delegation.keys():
-            # return the list
-            mention = delegation[Neo4jPropertyGraph.FIELD_POOL]
-            if isinstance(mention, list):
-                return mention
-            return [mention]
-        return []
-
-    def _process_delegation_no_pools(self, *, delegation: Dict, dele_type: DelegationType, node_id: str) -> Delegation:
-        """
-        process a delegation with no pools, get a delegation id from delegation dictionary or
-        assign 'default' and return a fully formed Delegation object
-        :param delegation:
-        :param dele_type:
-        :return: delegation id
-        """
-        # extract the delegation id
-        if self.FIELD_DELEGATION in delegation.keys():
-            delegation_id = delegation[self.FIELD_DELEGATION]
-        else:
-            delegation_id = self.DEFAULT_DELEGATION
-        delegation_obj = Delegation(atype=dele_type, defined_on=node_id, delegation_id=delegation_id)
-        delegation_obj.set_details(dele_dict=delegation)
-        return delegation_obj
-
-    def _process_single_delegation(self, *, delegation: Dict, dele_type: DelegationType, node_id: str):
-        """
-        Process a single delegation dictionary for pool definitions, pool mentions or individual delegations.
-        Update pools and delegations fields of this object
-        :param delegation:
-        :param dele_type:
-        :param node_id
-        :return:
-        """
-        # check for pool definitions
-        pool_id, del_id = self._find_pool_definition(delegation=delegation,
-                                                     pool_type=dele_type)
-        self.log.debug(f"Single: Decoded pool {pool_id}, delegation {del_id}")
-        with_pool = False
-
-        if pool_id is not None:
-            # if the pool is not defined, create a new one, if it is defined augment the definition
-            pool = self.pools[dele_type].get_pool_by_id(pool_id=pool_id)
-            pool.set_delegation_id(delegation_id=del_id)
-            pool.set_defined_on(node_id=node_id)
-            pool.set_pool_details(pool_dict=delegation)
-            with_pool = True
-
-        # check for pool mentions
-        pool_mentions = self._find_pool_mentions(delegation=delegation, pool_type=dele_type)
-        for pool_id in pool_mentions:
-            # if this pool already defined, add node id to list for which it is defined
-            pool = self.pools[dele_type].get_pool_by_id(pool_id=pool_id)
-            pool.add_defined_for(node_ids=node_id)
-            with_pool = True
-
-        # check for plain delegation (without a pool)
-        if not with_pool:
-            delegation_obj = self._process_delegation_no_pools(delegation=delegation,
-                                                               dele_type=dele_type,
-                                                               node_id=node_id)
-            self.delegations[dele_type].add_delegation(delegation=delegation_obj)
-
-    def _find_delegations_in_properties(self, *, props: Dict, pool_type: DelegationType, node_id: str) -> None:
-        """
-        Check if a delegation property of a node ( label or capacity) contains a
-        pool (label or capacity) declaration, return a list of (pool_id, delegation_id, JSON delegation)
-        tuples or empty list. Also process single delegations not included in pool definitions
-        :param props: all properties of a node
-        :param pool_type
-        :param node_id
-        :return:
-        """
-        if pool_type not in self.POOL_TYPE_TO_CLASS.keys():
-            raise LabelOrCapacityException(f"Invalid pool type {pool_type}")
-
-        delegation_prop_name, _, _ = self.POOL_TYPE_TO_CLASS[pool_type]
-        self.log.debug(f"Checking properties {props} for {delegation_prop_name} pool definitions")
-        if delegation_prop_name in props.keys():
-            # get the list of delegations as a JSON string
-            # note that Neo4j returns string 'None' if property is not filled, but present
-            if props[delegation_prop_name] == self.NEO4j_NONE:
-                return
-
-            self.log.debug(f"Decoding {delegation_prop_name} delegations {props[delegation_prop_name]} on node {node_id}")
-            delegations = json.loads(props[delegation_prop_name])
 
-            if not isinstance(delegations, list):
-                if not isinstance(delegations, dict):
-                    raise LabelOrCapacityException(f"Delegation {delegations} must be a list "
-                                                   "of dictionaries or a dictionary")
-                # just a single dictionary
-                self._process_single_delegation(delegation=delegations,
-                                                dele_type=pool_type, node_id=node_id)
-            else:
-                # a list of dictionaries
-                for delegation in delegations:
-                    self._process_single_delegation(delegation=delegation,
-                                                    dele_type=pool_type, node_id=node_id)
-
-    def _catalog_delegations(self) -> None:
-        """
-        Locate all nodes with delegations in them, taking into account pools.
-        :param graph_id:
-        :return:
-        """
-
-        # collect information about pools
-        for node_id in self.node_ids:
-            self.nodes[node_id].labels, self.nodes[node_id].properties = self.get_node_properties(node_id=node_id)
-            self.log.debug(f"Processing node {node_id} for {self.PROP_LABEL_DELEGATIONS} "
-                           f"and {self.PROP_CAPACITY_DELEGATIONS}")
-            # if a node doesn't have delegations we skip it
-            if self.PROP_LABEL_DELEGATIONS not in self.nodes[node_id].properties.keys() and \
-                    self.PROP_CAPACITY_DELEGATIONS not in self.nodes[node_id].properties.keys():
-                continue
-            # look for label and capacity pool definitions and mentions
-            self._find_delegations_in_properties(props=self.nodes[node_id].properties,
-                                                 pool_type=DelegationType.LABEL, node_id=node_id)
-
-            self._find_delegations_in_properties(props=self.nodes[node_id].properties,
-                                                 pool_type=DelegationType.CAPACITY, node_id=node_id)
-            # also look for delegations without pools
-
-        # build indices on pools so we can find them from a delegation id
-        for _, pools in self.pools.items():
-            pools.build_index_by_delegation_id()
-
-    def _update_delegation_properties(self, *, del_id: str, del_type: DelegationType,
-                                      graph: Neo4jPropertyGraph,
-                                      node_ids: List[str]):
+    def __init__(self, *, graph: Neo4jPropertyGraph, logger=None):
         """
-        Update capacity or label delegations on graph nodes based on previously collection
-        delegations information. Use the information about kept nodes to speed things up.
-        :param del_id:
-        :param del_type:
+        Initialize Neo4j ARM - supply an implementation of a graph
         :param graph:
-        :param node_ids:
-        :return:
         """
-        assert node_ids is not None
-        assert graph is not None
-        assert del_id is not None
-
-        delegation_prop_name, _, _ = self.POOL_TYPE_TO_CLASS[del_type]
-        collected_delegations = self.delegations[del_type]
-        delegations_list = collected_delegations.get_by_delegation_id(delegation_id=del_id)
-        collected_pools = self.pools[del_type]
-        pools_list = collected_pools.get_pools_by_delegation_id(delegation_id=del_id)
+        super().__init__(graph=graph, logger=logger)
 
-        # first fill in a temporary dict with delegation info, then write it into properties
-        new_delegations = dict()
-        # collect pool mentions separately, then merge
-        new_pool_mentions = dict()
-        # walk through all node_ids and check delegations and pools
-        for node_id in node_ids:
-            # each entry matches a node id and is a list of dictionaries
-            new_delegations[node_id] = list()
-
-            if delegations_list is not None:
-                # find if there is a delegation on this node
-                for delegation in delegations_list:
-                    if delegation.on_ == node_id:
-                        new_delegations[node_id].append(delegation.get_details())
-
-            if pools_list is not None:
-                for pool in pools_list:
-                    if pool.on_ == node_id:
-                        # find if there was a pool definition on this node
-                        new_delegations[node_id].append(pool.get_pool_details())
-                        # pool definition includes a pool mention
-                        if new_pool_mentions.get(node_id, None) is None:
-                            new_pool_mentions[node_id] = list()
-                        new_pool_mentions[node_id].append(pool.pool_id)
-                    elif node_id in pool.for_:
-                        # find if there was a pool mention on this node
-                        # add it to 'pool' (if exists, create otherwise)
-                        if new_pool_mentions.get(node_id, None) is None:
-                            new_pool_mentions[node_id] = list()
-                        new_pool_mentions[node_id].append(pool.pool_id)
-
-        # merge pool mentions
-        for node_id, pool in new_pool_mentions.items():
-            new_delegations[node_id].append({Neo4jPropertyGraph.FIELD_POOL: pool})
-
-        # write appropriate property name into the node on appropriate graph
-        for node_id in node_ids:
-            node_delegation_info = new_delegations[node_id]
-            if node_delegation_info is not None and len(node_delegation_info) != 0:
-                node_delegation_str = json.dumps(node_delegation_info)
-                graph.update_node_property(node_id=node_id,
-                                           prop_name=delegation_prop_name,
-                                           prop_val=node_delegation_str)
-
-    def generate_adms(self) -> Dict:
-        """
-        Generate delegation models from the current ARM graph and return
-        the dictionary delegation_id: Neo4jPropertyGraph for generated ADMs
-        :return:
-        """
-        self.node_ids = self.list_all_node_ids()
-        if len(self.node_ids) == 0:
-            raise PropertyGraphQueryException(graph_id=self.graph_id,
-                                              node_id=None, msg="Unable to list nodes of ARM graph")
-        # build a dictionary to hold node labels and properties
-        self.nodes = {k: self.LabelsAndProperties(None, None) for k in self.node_ids}
 
-        # collect information on delegated pools and individual delegations
-        self._catalog_delegations()
 
-        # number of distinct delegation keys across label and capacity pools and plain delegations
-        # is the number of ADMs we will generate
-        unique_delegation_ids = self.label_pools.\
-            get_delegation_ids().union(self.capacity_pools.get_delegation_ids(),
-                                       self.label_delegations.get_delegation_ids(),
-                                       self.capacity_delegations.get_delegation_ids())
-
-        # generate unique graph ids and empty node sets for delegation graphs
-        # remember these tuples and the fields are not mutable
-        delegations_info = {del_id: self.DelegationInfo(graph_id=str(uuid.uuid4()),
-                                                        graph=None,
-                                                        keep_nodes=set(),
-                                                        remove_nodes=set())
-                            for del_id in unique_delegation_ids}
-
-        node_ids_to_delegations = {node_id: list() for node_id in self.node_ids}
-        for del_id in unique_delegation_ids:
-            # build up lists of node ids that definitely belong to each delegation
-            # as a union of all .for_ fields on pools and on all delegations
-            delegations_info[del_id].keep_nodes.update(
-                self.label_pools.get_node_ids(delegation_id=del_id),
-                self.capacity_pools.get_node_ids(delegation_id=del_id),
-                self.label_delegations.get_node_ids(delegation_id=del_id),
-                self.capacity_delegations.get_node_ids(delegation_id=del_id)
-            )
-            # also build a dictionary from node_id to a list of delegation ids
-            for node_id in delegations_info[del_id].keep_nodes:
-                node_ids_to_delegations[node_id].append(del_id)
-
-            # clone the original ARM graph into ADM graphs for each new graph ID
-            delegations_info[del_id].graph = self.clone_graph(new_graph_id=delegations_info[del_id].graph_id)
-
-        # in addition to definite keep nodes, we should also keep nodes that
-        # a) lie on shortest paths between two connection points we are keeping (to include the links)
-        # b) switch fabrics that connect to connection points
-        # c) network nodes that have switch fabrics
-        # Determine nodes to delete by doing a difference between all nodes and keep nodes
-        # Remove nodes
-        # Annotate keep nodes with proper delegation information
-
-        for del_id in unique_delegation_ids:
-            # get a list of connection points we are keeping
-            keep_cps = set()
-            for node_id in delegations_info[del_id].keep_nodes:
-                if 'ConnectionPoint' in self.nodes[node_id].labels:
-                    keep_cps.add(node_id)
-
-            # trace from connection points over links to other connection points
-            # remember connection points
-            new_cps = set()
-            for cp in keep_cps:
-                cp_neighbors = self.get_first_and_second_neighbor(node_id=cp,
-                                                                  rel1="connects",
-                                                                  node1_label="Link",
-                                                                  rel2="connects",
-                                                                  node2_label="ConnectionPoint")
-                for pair in cp_neighbors:
-                    # pair is a 2-element list
-                    delegations_info[del_id].keep_nodes.update(pair)
-                    new_cps.add(pair[1])
-
-            keep_cps.update(new_cps)
-            # from all keep cps (original and added), find switch fabrics and components or
-            # network nodes
-            for cp in keep_cps:
-                cp_neighbors = self.get_first_and_second_neighbor(node_id=cp,
-                                                                  rel1="connects",
-                                                                  node1_label="SwitchFabric",
-                                                                  rel2="has",
-                                                                  node2_label="NetworkNode")
-                for pair in cp_neighbors:
-                    delegations_info[del_id].keep_nodes.update(pair)
-
-                cp_neighbors = self.get_first_and_second_neighbor(node_id=cp,
-                                                                  rel1="connects",
-                                                                  node1_label="SwitchFabric",
-                                                                  rel2="has",
-                                                                  node2_label="Component")
-                for pair in cp_neighbors:
-                    delegations_info[del_id].keep_nodes.update(pair)
-
-            delegations_info[del_id].remove_nodes = set(self.node_ids)
-            delegations_info[del_id].remove_nodes.difference_update(delegations_info[del_id].keep_nodes)
-            for node_id in delegations_info[del_id].remove_nodes:
-                delegations_info[del_id].graph.delete_node(node_id=node_id)
-
-            # go through capacity delegations under this delegation id, replace annotations on kept nodes
-            # of approprate graph
-            self._update_delegation_properties(del_id=del_id,
-                                               del_type=DelegationType.LABEL,
-                                               graph=delegations_info[del_id].graph,
-                                               node_ids=delegations_info[del_id].keep_nodes)
-            self._update_delegation_properties(del_id=del_id,
-                                               del_type=DelegationType.CAPACITY,
-                                               graph=delegations_info[del_id].graph,
-                                               node_ids=delegations_info[del_id].keep_nodes)
-
-        return {k: delegations_info[k].graph for k in unique_delegation_ids}
-
-    def get_delegations(self, *, node_id: str, delegation_type: DelegationType) -> List:
-        """
-        Get Label or Capacity delegations of a given node. They are represented
-        as lists of dictionaries. Dictionaries may include delegation and pool
-        designators. Returns None if delegation property is not defined.
-        :param node_id:
-        :param delegation_type:
-        :return:
-        """
-        assert node_id is not None
-        prop_field_name, _, _ = self.POOL_TYPE_TO_CLASS[delegation_type]
-        return self.get_node_json_property_as_object(node_id=node_id, prop_name=prop_field_name)
 
diff --git a/fim/graph/resources/neo4j_cbm.py b/fim/graph/resources/neo4j_cbm.py
index 886c61e..9d125a9 100644
--- a/fim/graph/resources/neo4j_cbm.py
+++ b/fim/graph/resources/neo4j_cbm.py
@@ -75,13 +75,13 @@ def _update_node_delegations(self, *, node_id: str, adm: ABCPropertyGraph) -> No
         props_modified = False
         for delegation_prop_name in [self.PROP_LABEL_DELEGATIONS, self.PROP_CAPACITY_DELEGATIONS]:
             # one or both of properties can have data
-            if delegation_prop_name not in cbm_node_props.keys() or \
+            if cbm_node_props.get(delegation_prop_name, None) is None or \
                     cbm_node_props[delegation_prop_name] == self.NEO4j_NONE:
                 cbm_delegation_dict = dict()
             else:
                 cbm_delegation_dict = json.loads(cbm_node_props[delegation_prop_name])
 
-            if delegation_prop_name not in adm_node_props.keys() or \
+            if adm_node_props.get(delegation_prop_name, None) is None or \
                     adm_node_props[delegation_prop_name] == self.NEO4j_NONE:
                 continue
             props_modified = True
@@ -105,6 +105,8 @@ def merge_adm(self, *, adm: ABCPropertyGraph) -> None:
 
         # clone ADM with temporary ID - we will be changing its properties
         temp_adm_graph_id = str(uuid.uuid4())
+        self.log.info('CREATED TEMPORARY ADM GRAPH ID ' + temp_adm_graph_id +
+                      ' when merging graph ' + adm.graph_id + ' into ' + self.graph_id)
         temp_graph = adm.clone_graph(new_graph_id=temp_adm_graph_id)
         # crude typecasting
         temp_adm_graph = Neo4jADMGraph(graph_id=temp_graph.graph_id,
diff --git a/fim/graph/resources/networkx_adm.py b/fim/graph/resources/networkx_adm.py
new file mode 100644
index 0000000..ff09b6e
--- /dev/null
+++ b/fim/graph/resources/networkx_adm.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+# MIT License
+#
+# Copyright (c) 2020 FABRIC Testbed
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+#
+# Author: Ilya Baldin (ibaldin@renci.org)
+"""
+NetworkX implementation of ADM (Aggregate Delegation Model) functionality.
+"""
+
+import uuid
+
+from ..networkx_property_graph import NetworkXPropertyGraph, NetworkXGraphImporter
+from .abc_adm import ABCADMPropertyGraph
+
+
+class NetworkXADMGraph(ABCADMPropertyGraph, NetworkXPropertyGraph):
+
+    def __init__(self, *, graph_id: str = None, importer: NetworkXGraphImporter, logger=None):
+        """
+        Initialize CBM either from existing ID or generate new graph id
+        :param graph_id:
+        :param importer:
+        """
+        if graph_id is None:
+            graph_id = str(uuid.uuid4())
+
+        super().__init__(graph_id=graph_id, importer=importer, logger=logger)
\ No newline at end of file
diff --git a/fim/graph/slices/abc_asm.py b/fim/graph/slices/abc_asm.py
index 84744d1..b0e7420 100644
--- a/fim/graph/slices/abc_asm.py
+++ b/fim/graph/slices/abc_asm.py
@@ -31,10 +31,11 @@
 from abc import ABCMeta, abstractmethod
 
 from fim.slivers.network_node import NodeSliver
-from fim.slivers.attached_components import ComponentSliver, AttachedComponentsInfo
-from fim.slivers.switch_fabric import SwitchFabricSliver, SwitchFabricInfo
-from fim.slivers.interface_info import InterfaceSliver, InterfaceInfo
+from fim.slivers.attached_components import ComponentSliver
+from fim.slivers.switch_fabric import SwitchFabricSliver
+from fim.slivers.interface_info import InterfaceSliver
 from fim.slivers.capacities_labels import Capacities, Labels
+from fim.slivers.delegations import Delegation
 from fim.slivers.network_link import NetworkLinkSliver
 from fim.graph.abc_property_graph import ABCPropertyGraph, PropertyGraphQueryException
 
@@ -43,6 +44,10 @@ class ABCASMPropertyGraph(ABCPropertyGraph, metaclass=ABCMeta):
     """
     Interface for an ASM Mixin on top of a property graph
     """
+    @abstractmethod
+    def __init__(self, *, graph_id=str, importer, logger=None):
+        super().__init__(graph_id=graph_id, importer=importer, logger=logger)
+
     @classmethod
     def __subclasshook__(cls, subclass):
         return (hasattr(subclass, 'get_all_network_nodes') and
@@ -51,7 +56,19 @@ def __subclasshook__(cls, subclass):
     @abstractmethod
     def check_node_unique(self, *, label: str, name: str):
         """
-        Check no other node of this class/label and name exists
+        Check no other node of this class/label and name exists. Doesn't
+        apply universally as e.g. Component names are only unique
+        within node, interface names are only unique within SF.
+        :param label:
+        :param name:
+        :return:
+        """
+
+    @abstractmethod
+    def check_node_name(self, *, node_id: str, label: str, name: str) -> bool:
+        """
+        Check if a node with this ID of this class/label has this name
+        :param node_id:
         :param label:
         :param name:
         :return:
@@ -72,7 +89,7 @@ def get_all_network_links(self) -> List[str]:
         """
 
     @abstractmethod
-    def find_node_by_name(self, node_name: str, label: str) -> str:
+    def find_node_by_name(self, *, node_name: str, label: str) -> str:
         """
         Get node id of node based on its name and class/label. Throw
         exception if multiple matches found.
@@ -81,27 +98,30 @@ def find_node_by_name(self, node_name: str, label: str) -> str:
         :return:
         """
 
-    @staticmethod
-    def node_sliver_from_graph_properties_dict(d: Dict[str, str]) -> NodeSliver:
-        n = NodeSliver()
-        if d.get(ABCPropertyGraph.PROP_IMAGE_REF, None) is None:
-            image_ref = None
-            image_type = None
-        else:
-            image_ref, image_type = d[ABCPropertyGraph.PROP_IMAGE_REF].split(',')
-        n.set_properties(resource_name=d.get(ABCPropertyGraph.PROP_NAME, None),
-                         resource_type=n.type_from_str(d.get(ABCPropertyGraph.PROP_TYPE, None)),
-                         capacities=Capacities().from_json(d.get(ABCPropertyGraph.PROP_CAPACITIES, None)),
-                         labels=Labels().from_json(d.get(ABCPropertyGraph.PROP_LABELS, None)),
-                         site=d.get(ABCPropertyGraph.PROP_SITE, None),
-                         image_ref=image_ref,
-                         image_type=image_type,
-                         management_ip=d.get(ABCPropertyGraph.PROP_MGMT_IP, None),
-                         allocation_constraints=d.get(ABCPropertyGraph.PROP_ALLOCATION_CONSTRAINTS, None),
-                         service_endpoint=d.get(ABCPropertyGraph.PROP_SERVICE_ENDPOINT, None),
-                         details=d.get(ABCPropertyGraph.PROP_DETAILS, None)
-                         )
-        return n
+    def find_node_by_name_as_child(self, *, node_name: str, label: str, rel: str, parent_node_id: str) -> str or None:
+        """
+        Get node id of node based on its name, class label, as child of a parent node via a specified relationship
+        or None
+        :param node_name: 
+        :param label: 
+        :param rel: 
+        :param parent_node_id: 
+        :return: 
+        """
+        assert node_name is not None
+        assert label is not None
+        assert rel is not None
+        assert parent_node_id is not None
+
+        neighbs = self.get_first_neighbor(node_id=parent_node_id, rel=rel, node_label=label)
+        for n in neighbs:
+            _, props = self.get_node_properties(node_id=n)
+            if props.get(ABCPropertyGraph.PROP_NAME, None) is not None and \
+                props[ABCPropertyGraph.PROP_NAME] == node_name:
+                return n
+        raise PropertyGraphQueryException(graph_id=self.graph_id, node_id=None,
+                                          msg=f"Unable to find node with name {node_name} "
+                                              f"class {label} as child of {parent_node_id}")
 
     @staticmethod
     def link_sliver_from_graph_properties_dict(d: Dict[str, str]) -> NetworkLinkSliver:
@@ -110,61 +130,16 @@ def link_sliver_from_graph_properties_dict(d: Dict[str, str]) -> NetworkLinkSliv
                          resource_type=n.type_from_str(d.get(ABCPropertyGraph.PROP_TYPE, None)),
                          capacities=Capacities().from_json(d.get(ABCPropertyGraph.PROP_CAPACITIES, None)),
                          labels=Labels().from_json(d.get(ABCPropertyGraph.PROP_LABELS, None)),
+                         capacity_delegations=Delegation.from_json_to_list(
+                             d.get(ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS, None)),
+                         label_delegations=Delegation.from_json_to_list(
+                             d.get(ABCPropertyGraph.PROP_LABEL_DELEGATIONS, None)),
                          layer=n.layer_from_str(d.get(ABCPropertyGraph.PROP_LAYER, None)),
                          technology=d.get(ABCPropertyGraph.PROP_TECHNOLOGY, None),
                          details=d.get(ABCPropertyGraph.PROP_DETAILS, None)
                          )
         return n
 
-    @staticmethod
-    def component_sliver_from_graph_properties_dict(d: Dict[str, str]) -> ComponentSliver:
-        """
-        Create component sliver from node graph properties
-        :param d:
-        :return:
-        """
-        cs = ComponentSliver()
-        cs.set_properties(name=d.get(ABCPropertyGraph.PROP_NAME, None),
-                          resource_type=cs.type_from_str(d.get(ABCPropertyGraph.PROP_TYPE, None)),
-                          capacities=Capacities().from_json(d.get(ABCPropertyGraph.PROP_CAPACITIES, None)),
-                          labels=Labels().from_json(d.get(ABCPropertyGraph.PROP_LABELS, None)),
-                          details=d.get(ABCPropertyGraph.PROP_DETAILS, None)
-                          )
-        return cs
-
-    @staticmethod
-    def switch_fabric_sliver_from_graph_properties_dict(d: Dict[str, str]) -> SwitchFabricSliver:
-        """
-        SwitchFabric sliver from node graph properties
-        :param d:
-        :return:
-        """
-        sf = SwitchFabricSliver()
-        sf.set_properties(name=d.get(ABCPropertyGraph.PROP_NAME, None),
-                          resource_type=sf.type_from_str(d.get(ABCPropertyGraph.PROP_TYPE, None)),
-                          capacities=Capacities().from_json(d.get(ABCPropertyGraph.PROP_CAPACITIES, None)),
-                          labels=Labels().from_json(d.get(ABCPropertyGraph.PROP_LABELS, None)),
-                          layer=sf.layer_from_str(d.get(ABCPropertyGraph.PROP_LAYER, None)),
-                          details=d.get(ABCPropertyGraph.PROP_DETAILS, None)
-                          )
-        return sf
-
-    @staticmethod
-    def interface_sliver_from_graph_properties_dict(d: Dict[str, str]) -> InterfaceSliver:
-        """
-        Interface sliver from node graph properties
-        :param d:
-        :return:
-        """
-        isl = InterfaceSliver()
-        isl.set_properties(name=d.get(ABCPropertyGraph.PROP_NAME, None),
-                           resource_type=isl.type_from_str(d.get(ABCPropertyGraph.PROP_TYPE, None)),
-                           capacities=Capacities().from_json(d.get(ABCPropertyGraph.PROP_CAPACITIES, None)),
-                           labels=Labels().from_json(d.get(ABCPropertyGraph.PROP_LABELS, None)),
-                           details=d.get(ABCPropertyGraph.PROP_DETAILS, None)
-                           )
-        return isl
-
     @staticmethod
     def node_sliver_to_graph_properties_dict(sliver: NodeSliver) -> Dict[str, str]:
         """
@@ -182,6 +157,12 @@ def node_sliver_to_graph_properties_dict(sliver: NodeSliver) -> Dict[str, str]:
             prop_dict[ABCPropertyGraph.PROP_CAPACITIES] = sliver.capacities.to_json()
         if sliver.labels is not None:
             prop_dict[ABCPropertyGraph.PROP_LABELS] = sliver.labels.to_json()
+        if sliver.capacity_delegations is not None:
+            prop_dict[ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS] = \
+                Delegation.from_list_to_json(sliver.capacity_delegations)
+        if sliver.label_delegations is not None:
+            prop_dict[ABCPropertyGraph.PROP_LABEL_DELEGATIONS] = \
+                Delegation.from_list_to_json(sliver.label_delegations)
         if sliver.site is not None:
             prop_dict[ABCPropertyGraph.PROP_SITE] = sliver.site
         if sliver.image_ref is not None and sliver.image_type is not None:
@@ -213,6 +194,12 @@ def link_sliver_to_graph_properties_dict(sliver: NetworkLinkSliver) -> Dict[str,
             prop_dict[ABCPropertyGraph.PROP_CAPACITIES] = sliver.capacities.to_json()
         if sliver.labels is not None:
             prop_dict[ABCPropertyGraph.PROP_LABELS] = sliver.labels.to_json()
+        if sliver.capacity_delegations is not None:
+            prop_dict[ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS] = \
+                Delegation.from_list_to_json(sliver.capacity_delegations)
+        if sliver.label_delegations is not None:
+            prop_dict[ABCPropertyGraph.PROP_LABEL_DELEGATIONS] = \
+                Delegation.from_list_to_json(sliver.label_delegations)
         if sliver.layer is not None:
             prop_dict[ABCPropertyGraph.PROP_LAYER] = str(sliver.layer)
         if sliver.technology is not None:
@@ -239,6 +226,12 @@ def component_sliver_to_graph_properties_dict(sliver: ComponentSliver) -> Dict[s
             prop_dict[ABCPropertyGraph.PROP_CAPACITIES] = sliver.capacities.to_json()
         if sliver.labels is not None:
             prop_dict[ABCPropertyGraph.PROP_LABELS] = sliver.labels.to_json()
+        if sliver.capacity_delegations is not None:
+            prop_dict[ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS] = \
+                Delegation.from_list_to_json(sliver.capacity_delegations)
+        if sliver.label_delegations is not None:
+            prop_dict[ABCPropertyGraph.PROP_LABEL_DELEGATIONS] = \
+                Delegation.from_list_to_json(sliver.label_delegations)
         if sliver.details is not None:
             prop_dict[ABCPropertyGraph.PROP_DETAILS] = sliver.details
 
@@ -261,6 +254,12 @@ def switch_fabric_sliver_to_graph_properties_dict(sliver: SwitchFabricSliver) ->
             prop_dict[ABCPropertyGraph.PROP_CAPACITIES] = sliver.capacities.to_json()
         if sliver.labels is not None:
             prop_dict[ABCPropertyGraph.PROP_LABELS] = sliver.labels.to_json()
+        if sliver.capacity_delegations is not None:
+            prop_dict[ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS] = \
+                Delegation.from_list_to_json(sliver.capacity_delegations)
+        if sliver.label_delegations is not None:
+            prop_dict[ABCPropertyGraph.PROP_LABEL_DELEGATIONS] = \
+                Delegation.from_list_to_json(sliver.label_delegations)
         if sliver.layer is not None:
             prop_dict[ABCPropertyGraph.PROP_LAYER] = str(sliver.layer)
         if sliver.details is not None:
@@ -285,6 +284,12 @@ def interface_sliver_to_graph_properties_dict(sliver: InterfaceSliver) -> Dict[s
             prop_dict[ABCPropertyGraph.PROP_CAPACITIES] = sliver.capacities.to_json()
         if sliver.labels is not None:
             prop_dict[ABCPropertyGraph.PROP_LABELS] = sliver.labels.to_json()
+        if sliver.capacity_delegations is not None:
+            prop_dict[ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS] = \
+                Delegation.from_list_to_json(sliver.capacity_delegations)
+        if sliver.label_delegations is not None:
+            prop_dict[ABCPropertyGraph.PROP_LABEL_DELEGATIONS] = \
+                Delegation.from_list_to_json(sliver.label_delegations)
         if sliver.details is not None:
             prop_dict[ABCPropertyGraph.PROP_DETAILS] = sliver.details
 
@@ -548,7 +553,7 @@ def add_component_sliver(self, *, parent_node_id: str, component: ComponentSlive
         """
         assert component.node_id is not None
         assert parent_node_id is not None
-        assert self.check_node_unique(label=ABCPropertyGraph.CLASS_Component, name=component.resource_name)
+
         props = self.component_sliver_to_graph_properties_dict(component)
         self.add_node(node_id=component.node_id, label=ABCPropertyGraph.CLASS_Component, props=props)
         self.add_link(node_a=parent_node_id, rel=ABCPropertyGraph.REL_HAS, node_b=component.node_id)
@@ -567,8 +572,7 @@ def add_switch_fabric_sliver(self, *, parent_node_id: str, switch_fabric: Switch
         """
         assert parent_node_id is not None
         assert switch_fabric.node_id is not None
-        assert self.check_node_unique(label=ABCPropertyGraph.CLASS_SwitchFabric,
-                                      name=switch_fabric.resource_name)
+
         props = self.switch_fabric_sliver_to_graph_properties_dict(switch_fabric)
         self.add_node(node_id=switch_fabric.node_id, label=ABCPropertyGraph.CLASS_SwitchFabric, props=props)
         self.add_link(node_a=parent_node_id, rel=ABCPropertyGraph.REL_HAS, node_b=switch_fabric.node_id)
@@ -587,78 +591,7 @@ def add_interface_sliver(self, *, parent_node_id: str, interface: InterfaceSlive
         """
         assert interface.node_id is not None
         assert parent_node_id is not None
-        assert self.check_node_unique(label=ABCPropertyGraph.CLASS_ConnectionPoint, name=interface.resource_name)
+
         props = self.interface_sliver_to_graph_properties_dict(interface)
         self.add_node(node_id=interface.node_id, label=ABCPropertyGraph.CLASS_ConnectionPoint, props=props)
         self.add_link(node_a=parent_node_id, rel=ABCPropertyGraph.REL_CONNECTS, node_b=interface.node_id)
-
-    def build_deep_node_sliver(self, *, node_id: str) -> NodeSliver:
-        """
-        Build a deep NetworkNode or other similar (e.g.
-        network-attached storage) sliver from a graph node
-        :param node_id:
-        :return:
-        """
-        clazzes, props = self.get_node_properties(node_id=node_id)
-        if ABCPropertyGraph.CLASS_NetworkNode not in clazzes:
-            raise PropertyGraphQueryException(node_id=node_id, graph_id=self.graph_id,
-                                              msg="Node is not of class NetworkNode")
-        # create top-level sliver
-        ns = self.node_sliver_from_graph_properties_dict(props)
-        # find and build deep slivers of switch fabrics (if any) and components (if any)
-        comps = self.get_first_neighbor(node_id=node_id, rel=ABCPropertyGraph.REL_HAS,
-                                        node_label=ABCPropertyGraph.CLASS_Component)
-        if comps is not None and len(comps) > 0:
-            aci = AttachedComponentsInfo()
-            for c in comps:
-                cs = self.build_deep_component_sliver(node_id=c)
-                aci.add_device(cs)
-            ns.attached_components_info = aci
-
-        sfs = self.get_first_neighbor(node_id=node_id, rel=ABCPropertyGraph.REL_HAS,
-                                      node_label=ABCPropertyGraph.CLASS_SwitchFabric)
-        if sfs is not None and len(sfs) > 0:
-            sfi = SwitchFabricInfo()
-            for s in sfs:
-                sfsl = self.build_deep_sf_sliver(node_id=s)
-                sfi.add_switch_fabric(sfsl)
-            ns.switch_fabric_info = sfi
-
-        return ns
-
-    def build_deep_sf_sliver(self, *, node_id: str) -> SwitchFabricSliver:
-        clazzes, props = self.get_node_properties(node_id=node_id)
-        if ABCPropertyGraph.CLASS_SwitchFabric not in clazzes:
-            raise PropertyGraphQueryException(node_id=node_id, graph_id=self.graph_id,
-                                              msg="Node is not of class SwitchFabric")
-        # create top-level sliver
-        sfs = self.switch_fabric_sliver_from_graph_properties_dict(props)
-        # find interfaces and attach
-        ifs = self.get_first_neighbor(node_id=node_id, rel=ABCPropertyGraph.REL_CONNECTS,
-                                      node_label=ABCPropertyGraph.CLASS_ConnectionPoint)
-        if ifs is not None and len(ifs) > 0:
-            ifi = InterfaceInfo()
-            for i in ifs:
-                _, iprops = self.get_node_properties(node_id=i)
-                ifsl = self.interface_sliver_from_graph_properties_dict(iprops)
-                ifi.add_interface(ifsl)
-            sfs.interface_info = ifi
-        return sfs
-
-    def build_deep_component_sliver(self, *, node_id: str) -> ComponentSliver:
-        clazzes, props = self.get_node_properties(node_id=node_id)
-        if ABCPropertyGraph.CLASS_Component not in clazzes:
-            raise PropertyGraphQueryException(node_id=node_id, graph_id=self.graph_id,
-                                              msg="Node is not of class Component")
-        # create top-level sliver
-        cs = self.component_sliver_from_graph_properties_dict(props)
-        # find any switch fabrics, build and attach
-        sfs = self.get_first_neighbor(node_id=node_id, rel=ABCPropertyGraph.REL_HAS,
-                                      node_label=ABCPropertyGraph.CLASS_SwitchFabric)
-        if sfs is not None and len(sfs) > 0:
-            sfi = SwitchFabricInfo()
-            for s in sfs:
-                sfsl = self.build_deep_sf_sliver(node_id=s)
-                sfi.add_switch_fabric(sfsl)
-            cs.switch_fabric_info = sfi
-        return cs
diff --git a/fim/graph/slices/networkx_asm.py b/fim/graph/slices/networkx_asm.py
index 00d032a..cbe513a 100644
--- a/fim/graph/slices/networkx_asm.py
+++ b/fim/graph/slices/networkx_asm.py
@@ -30,7 +30,7 @@
 from ..networkx_property_graph import NetworkXPropertyGraph, NetworkXGraphImporter
 
 
-class NetworkxASM(NetworkXPropertyGraph, ABCASMPropertyGraph):
+class NetworkxASM(ABCASMPropertyGraph, NetworkXPropertyGraph):
     """
     Class implementing Abstract Slice Model on top of NetworkX
     """
@@ -108,3 +108,18 @@ def check_node_unique(self, *, label: str, name: str):
                                             ]}))
         return len(graph_nodes) == 0
 
+    def check_node_name(self, *, node_id: str, label: str, name: str) -> bool:
+        assert node_id is not None
+        assert name is not None
+        assert label is not None
+
+        graph_nodes = list(nxq.search_nodes(self.storage.get_graph(self.graph_id),
+                                            {'and': [
+                                                {'eq': [ABCPropertyGraph.GRAPH_ID, self.graph_id]},
+                                                {'eq': [ABCPropertyGraph.PROP_NAME, name]},
+                                                {'eq': [ABCPropertyGraph.PROP_CLASS, label]},
+                                                {'eq': [ABCPropertyGraph.NODE_ID, node_id]}
+                                            ]}))
+        return len(graph_nodes) > 0
+
+
diff --git a/fim/slivers/attached_components.py b/fim/slivers/attached_components.py
index d9f4b55..618752a 100644
--- a/fim/slivers/attached_components.py
+++ b/fim/slivers/attached_components.py
@@ -23,7 +23,7 @@
 #
 #
 # Author: Komal Thareja (kthare10@renci.org)
-
+from typing import Any, List
 import enum
 
 from .base_sliver import BaseSliver
@@ -69,13 +69,36 @@ class AttachedComponentsInfo:
     """
     def __init__(self):
         self.devices = {}
+        self.by_type = {}
+
+    def add_device(self, device_info: ComponentSliver) -> None:
+        assert device_info.resource_name is not None
+        assert device_info.resource_type is not None
 
-    def add_device(self, device_info: ComponentSliver):
         self.devices[device_info.resource_name] = device_info
+        devices = self.by_type.get(device_info.resource_type, list())
+        devices.append(device_info)
+        self.by_type[device_info.resource_type] = devices
+
+    def remove_device(self, name: str) -> None:
+        assert name is not None
 
-    def remove_device(self, name: str):
         if name in self.devices:
-            self.devices.pop(name)
+            device_info = self.devices.pop(name)
+            devices = self.by_type.get(device_info.resource_type, list())
+            devices.remove(device_info)
+
+    def get_device(self, name: str) -> ComponentSliver or None:
+        assert name is not None
 
-    def get_device(self, name: str):
         return self.devices.get(name, None)
+
+    def get_devices_by_type(self, resource_type: Any) -> List[ComponentSliver]:
+        """
+        Returns a copy of list of devices of this type
+        :param resource_type:
+        :return:
+        """
+        assert resource_type is not None
+
+        return list(self.by_type.get(resource_type, list()))
diff --git a/fim/slivers/base_sliver.py b/fim/slivers/base_sliver.py
index 95a7907..a228b0e 100644
--- a/fim/slivers/base_sliver.py
+++ b/fim/slivers/base_sliver.py
@@ -30,6 +30,7 @@
 from abc import ABC, abstractmethod
 
 from fim.slivers.capacities_labels import Capacities, Labels
+from fim.slivers.delegations import Delegation, Pool
 
 
 class BaseSliver(ABC):
@@ -42,6 +43,8 @@ def __init__(self):
         self.resource_model = None
         self.capacities = None
         self.labels = None
+        self.capacity_delegations = None
+        self.label_delegations = None
         self.node_id = None
         self.details = None
 
@@ -104,6 +107,18 @@ def set_labels(self, lab: Labels) -> None:
     def get_labels(self) -> Labels:
         return self.labels
 
+    def set_capacity_delegations(self, cdel: Delegation):
+        self.capacity_delegations = cdel
+
+    def get_capacity_delegations(self) -> Delegation:
+        return self.capacity_delegations
+
+    def set_label_delegations(self, ldel: Delegation):
+        self.label_delegations = ldel
+
+    def get_label_delegations(self) -> Delegation:
+        return self.label_delegations
+
     def set_details(self, desc: str) -> None:
         self.details = desc
 
@@ -170,7 +185,9 @@ def __repr__(self):
         print_set.sort()
         print_vals = dict()
         for p in print_set:
-            print_vals[p] = self.get_property(p)
+            pval = self.get_property(p)
+            if pval is not None:
+                print_vals[p] = pval
         return str(print_vals)
 
     def __str__(self):
diff --git a/fim/slivers/capacities_labels.py b/fim/slivers/capacities_labels.py
index 8753b47..1296cf0 100644
--- a/fim/slivers/capacities_labels.py
+++ b/fim/slivers/capacities_labels.py
@@ -23,7 +23,7 @@
 #
 #
 # Author: Ilya Baldin (ibaldin@renci.org)
-from typing import List
+from typing import List, Dict
 import json
 
 from abc import ABC, abstractmethod
@@ -39,17 +39,19 @@ def set_fields(self, **kwargs):
         :return:
         """
 
-    def to_json(self) -> str or None:
+    def to_json(self) -> str:
         """
         Dumps to JSON the __dict__ of the instance. Be careful as the fields in this
         class should only be those that can be present in JSON output.
-        If there are no values in the object, returns None
+        If there are no values in the object, returns empty string.
         :return:
         """
         d = self.__dict__.copy()
         for k in self.__dict__:
             if d[k] is None or d[k] == 0:
                 d.pop(k)
+        if len(d) == 0:
+            return ''
         return json.dumps(d, skipkeys=True, sort_keys=True)
 
     def from_json(self, json_string: str):
@@ -65,6 +67,20 @@ def from_json(self, json_string: str):
         self.set_fields(**d)
         return self
 
+    def to_dict(self) -> Dict[str, str] or None:
+        """
+        Convert to a dictionary skipping empty fields. Returns None
+        if all fields are empty
+        :return:
+        """
+        d = self.__dict__.copy()
+        for k in self.__dict__:
+            if d[k] is None or d[k] == 0:
+                d.pop(k)
+        if len(d) == 0:
+            return None
+        return d
+
     def __repr__(self):
         return self.to_json()
 
diff --git a/fim/slivers/delegations.py b/fim/slivers/delegations.py
index 6032828..8d0d46b 100644
--- a/fim/slivers/delegations.py
+++ b/fim/slivers/delegations.py
@@ -33,7 +33,8 @@
 from typing import List, Dict, Set
 import json
 
-from fim.graph.abc_property_graph import ABCPropertyGraph
+from fim.graph.abc_property_graph_constants import ABCPropertyGraphConstants
+from .capacities_labels import Capacities, Labels
 
 
 class DelegationType(Enum):
@@ -60,7 +61,10 @@ def __init__(self, atype: DelegationType, defined_on: str, delegation_id: str):
         self.delegation_id = delegation_id
         self.delegation_details = None
 
-    def set_details(self, *, dele_dict: Dict) -> None:
+    def get_defined_on(self) -> str:
+        return self.on_
+
+    def set_details(self, dele_dict: Dict) -> None:
         """
         set details of the delegation dictionary, removing the delegation
         identifier field from dictionary
@@ -69,30 +73,119 @@ def set_details(self, *, dele_dict: Dict) -> None:
         """
         assert dele_dict is not None
         # pop the delegation field
-        dele_dict.pop(ABCPropertyGraph.FIELD_DELEGATION, None)
+        if dele_dict.get(ABCPropertyGraphConstants.FIELD_DELEGATION, None) is not None:
+            dele_dict.pop(ABCPropertyGraphConstants.FIELD_DELEGATION, None)
         self.delegation_details = dele_dict
 
-    def get_details(self) -> Dict:
+    def set_details_from_labels(self, l: Labels) -> None:
+        """
+        Set details from a Labels object
+        :param l:
+        :return:
+        """
+        assert l is not None
+        assert self.type is not DelegationType.CAPACITY
+        self.delegation_details = l.to_dict()
+
+    def set_details_from_capacities(self, c: Capacities) -> None:
+        """
+        Set details from a Capacities object
+        :param c:
+        :return:
+        """
+        assert c is not None
+        assert self.type is not DelegationType.LABEL
+        self.delegation_details = c.to_dict()
+
+    def get_details(self) -> Dict or None:
         """
         get delegation details as a dictionary (minus the delegation id)
         :return:
         """
-        return self.delegation_details
+        if self.delegation_details is None:
+            return None
+        return self.delegation_details.copy()
+
+    def get_full_details(self) -> Dict or None:
+        """
+        Get delegation details as a dictionary, including the delegation id
+        :return:
+        """
+        ret = self.get_details()
+        if ret is None:
+            return None
+        ret[ABCPropertyGraphConstants.FIELD_DELEGATION] = self.delegation_id
+        return ret
+
+    def get_details_as_capacities(self) -> Capacities:
+        """
+        Get delegation details as a Capacities object
+        :return:
+        """
+        assert self.type is DelegationType.CAPACITY
+        c = Capacities()
+        if self.delegation_details is not None:
+            c.set_fields(**self.delegation_details)
+        return c
+
+    def get_details_as_labels(self) -> Labels:
+        """
+        Get delegation details as Labels object
+        :return:
+        """
+        assert self.type is DelegationType.LABEL
+        l = Labels()
+        if self.delegation_details is not None:
+            l.set_fields(**self.delegation_details)
+        return l
+
+    def to_json(self) -> str or None:
+        """
+        Convert to a JSON representation adding back delegation id
+        :return:
+        """
+        ret_dict = self.get_full_details()
+        if ret_dict is None:
+            return None
+        return json.dumps(ret_dict, skipkeys=True, sort_keys=True)
 
     def __repr__(self) -> str:
         return f"{self.type} delegation delegated to {self.delegation_id}: " \
                f"{self.on_} with {self.delegation_details} "
 
+    @staticmethod
+    def from_json_to_list(j: str or None) -> List[Dict] or None:
+        """
+        Produce a list of dictionaries representing delegations. Does NOT
+        properly parse into Delegation or Pool objects
+        :param j:
+        :return:
+        """
+        if j is None:
+            return None
+        return json.loads(j)
+
+    @staticmethod
+    def from_list_to_json(l: List[Dict] or None) -> str or None:
+        """
+        Produce a JSON expression of list of dictionaries or None.
+        :param l:
+        :return:
+        """
+        if l is None:
+            return None
+        return json.dumps(l)
+
 
-class Delegations:
+class ARMDelegations:
     """
-    Multiple delegations references by delegation id
+    Multiple delegations references by delegation id within an ARM
     """
     def __init__(self, atype: DelegationType):
         self.type = atype
         self.delegations = {}
 
-    def add_delegation(self, *, delegation: Delegation) -> None:
+    def add_delegation(self, delegation: Delegation) -> None:
         """
         Append a well-formed delegation to the list of delegations matching
         this id
@@ -105,7 +198,7 @@ def add_delegation(self, *, delegation: Delegation) -> None:
 
         self.delegations[delegation.delegation_id].append(delegation)
 
-    def get_by_delegation_id(self, *, delegation_id: str) -> List[Delegation]:
+    def get_by_delegation_id(self, delegation_id: str) -> List[Delegation]:
         """
         retrieve a list of delegations by their id or None
         :param delegation_id:
@@ -121,7 +214,7 @@ def get_delegation_ids(self) -> Set:
         """
         return set(self.delegations.keys())
 
-    def get_node_ids(self, *, delegation_id: str) -> Set:
+    def get_node_ids(self, delegation_id: str) -> Set:
         """
         return a set of nodes ids for a given delegation id
         :param delegation_id:
@@ -167,7 +260,7 @@ def __init__(self, *, atype: DelegationType, pool_id: str, delegation_id: str =
         self.pool_id = pool_id
         self.pool_details = None
 
-    def set_defined_on(self, *, node_id: str) -> None:
+    def set_defined_on(self, node_id: str) -> None:
         """
         Set ID of node on which this pool is defined
         :param node_id:
@@ -176,7 +269,7 @@ def set_defined_on(self, *, node_id: str) -> None:
         assert node_id is not None
         self.on_ = node_id
 
-    def set_defined_for(self, *, node_id_list: List[str]) -> None:
+    def set_defined_for(self, node_id_list: List[str]) -> None:
         """
         set/replace the pool's defined_for list of nodes
         :param node_id_list:
@@ -187,7 +280,7 @@ def set_defined_for(self, *, node_id_list: List[str]) -> None:
 
         self.for_ = node_id_list
 
-    def add_defined_for(self, *, node_ids) -> None:
+    def add_defined_for(self, node_ids: str or List) -> None:
         """
         Add to the list of nodes for which the pool is defined for.
         Individual items or lists
@@ -200,7 +293,7 @@ def add_defined_for(self, *, node_ids) -> None:
         elif isinstance(node_ids, list):
             self.for_.extend(node_ids)
 
-    def set_pool_details(self, *, pool_dict: Dict) ->None:
+    def set_pool_details(self, pool_dict: Dict) ->None:
         """
         set details of the pool dictionary, removing delegation
         identifier field from dictionary
@@ -208,33 +301,99 @@ def set_pool_details(self, *, pool_dict: Dict) ->None:
         :return:
         """
         assert pool_dict is not None
-        pool_dict.pop(ABCPropertyGraph.FIELD_DELEGATION, None)
+        if pool_dict.get(ABCPropertyGraphConstants.FIELD_DELEGATION, None) is not None:
+            pool_dict.pop(ABCPropertyGraphConstants.FIELD_DELEGATION, None)
+        if pool_dict.get(ABCPropertyGraphConstants.FIELD_LABEL_POOL, None) is not None:
+            pool_dict.pop(ABCPropertyGraphConstants.FIELD_LABEL_POOL)
+        if pool_dict.get(ABCPropertyGraphConstants.FIELD_CAPACITY_POOL, None) is not None:
+            pool_dict.pop(ABCPropertyGraphConstants.FIELD_CAPACITY_POOL)
         self.pool_details = pool_dict
 
-    def get_pool_details(self) -> Dict:
+    def set_pool_details_from_labels(self, l: Labels) -> None:
         """
-        Return pool label or capacity details as a dictionary.
+        Set details from a Labels object
+        :param l:
         :return:
         """
-        return self.pool_details
+        assert l is not None
+        assert self.type is not DelegationType.CAPACITY
+        self.pool_details = l.to_dict()
 
-    def set_delegation_id(self, *, delegation_id: str) -> None:
-        assert delegation_id is not None
-        self.delegation_id = delegation_id
+    def set_pool_details_from_capacities(self, c: Capacities) -> None:
+        """
+        Set details from a Capacities object
+        :param c:
+        :return:
+        """
+        assert c is not None
+        assert self.type is not DelegationType.LABEL
+        self.pool_details = c.to_dict()
 
-    def add_defined_for(self, *, node_ids) -> None:
+    def get_pool_details(self) -> Dict or None:
         """
-        add a single element or a list
-        :param node_ids: single node id or list
+        Return label or capacity pool details as a dictionary
+        along with pool identifier.
         :return:
         """
-        assert node_ids is not None
-        if isinstance(node_ids, list):
-            self.for_.extend(node_ids)
-        elif isinstance(node_ids, str):
-            self.for_.append(node_ids)
+        # don't forget to add pool id back
+        if self.pool_details is None:
+            return None
+        ret = self.pool_details.copy()
+        if self.type == DelegationType.CAPACITY:
+            ret[ABCPropertyGraphConstants.FIELD_CAPACITY_POOL] = self.pool_id
+        else:
+            ret[ABCPropertyGraphConstants.FIELD_LABEL_POOL] = self.pool_id
+        return ret
+
+    def get_full_pool_details(self) -> Dict or None:
+        """
+        Return label or capacity pool details as a dictionary
+        with pool identifier and delegation identifier
+        :return:
+        """
+        ret = self.get_pool_details()
+        if ret is None:
+            return None
+        ret[ABCPropertyGraphConstants.FIELD_DELEGATION] = self.delegation_id
+        return ret
+
+    def get_pool_details_as_capacities(self) -> Capacities:
+        """
+        Get delegation details as a Capacities object
+        :return:
+        """
+        assert self.type is DelegationType.CAPACITY
+        c = Capacities()
+        if self.pool_details is not None:
+            c.set_fields(**self.pool_details)
+        return c
+
+    def get_pool_details_as_labels(self) -> Labels:
+        """
+        Get delegation details as Labels object
+        :return:
+        """
+        assert self.type is DelegationType.LABEL
+        l = Labels()
+        if self.pool_details is not None:
+            l.set_fields(**self.pool_details)
+        return l
+
+    def to_json(self) -> str or None:
+        """
+        Add back pool and delegation details and return JSON string
+        :return:
+        """
+        ret_dict = self.get_full_pool_details()
+        if ret_dict is None:
+            return None
+        return json.dumps(ret_dict, skipkeys=True, sort_keys=True)
+
+    def set_delegation_id(self, *, delegation_id: str) -> None:
+        assert delegation_id is not None
+        self.delegation_id = delegation_id
 
-    def is_defined_on(self, *, node_id: str) -> bool:
+    def is_defined_on(self, node_id: str) -> bool:
         """
         Is this pool defined on this node?
         :param node_id:
@@ -242,7 +401,7 @@ def is_defined_on(self, *, node_id: str) -> bool:
         """
         return node_id == self.on_
 
-    def is_defined_for(self, *, node_id: str) -> bool:
+    def is_defined_for(self, node_id: str) -> bool:
         """
         Is this pool defined for this node?
         :param node_id:
@@ -286,7 +445,7 @@ def __repr__(self) -> str:
                f"{self.on_}=> {self.for_} with {self.pool_details} "
 
 
-class Pools:
+class ARMPools:
     """
     Map between node ids, pools and delegations
     """
@@ -325,11 +484,11 @@ def build_index_by_delegation_id(self) -> None:
         self.pools_by_delegation = {}
         for pool in self.pool_by_id.values():
             pool.validate_pool()
-            if self.pools_by_delegation.get(pool.get_delegation_id(), None) is None:
-                self.pools_by_delegation[pool.get_delegation_id()] = []
-            self.pools_by_delegation[pool.get_delegation_id()].append(pool)
+            el = self.pools_by_delegation.get(pool.get_delegation_id(), list())
+            el.append(pool)
+            self.pools_by_delegation[pool.get_delegation_id()] = el
 
-    def get_pools_by_delegation_id(self, *, delegation_id: str) -> List[Pool]:
+    def get_pools_by_delegation_id(self, delegation_id: str) -> List[Pool]:
         """
         return a list of Pool(s) for a delegation. raises PoolException if the index based on
         delegation ids have not been built yet
@@ -360,7 +519,7 @@ def get_delegation_ids(self) -> Set:
             raise PoolException("Pools are not indexed by delegation, unable to get set of delegation ids")
         return set(self.pools_by_delegation.keys())
 
-    def get_node_ids(self, *, delegation_id: str) -> Set:
+    def get_node_ids(self, delegation_id: str) -> Set:
         """
         Get a set of nodes for a given delegation across all pools
         :param delegation_id:
diff --git a/fim/slivers/network_node.py b/fim/slivers/network_node.py
index 3782aa5..b9299af 100644
--- a/fim/slivers/network_node.py
+++ b/fim/slivers/network_node.py
@@ -37,6 +37,7 @@ class NodeType(enum.Enum):
     VM = enum.auto()
     Container = enum.auto()
     Switch = enum.auto()
+    NAS = enum.auto()
 
     def __repr__(self):
         return self.name
diff --git a/fim/slivers/switch_fabric.py b/fim/slivers/switch_fabric.py
index e355098..cecc23f 100644
--- a/fim/slivers/switch_fabric.py
+++ b/fim/slivers/switch_fabric.py
@@ -94,8 +94,12 @@ def add_switch_fabric(self, sf_info: SwitchFabricSliver):
         self.switch_fabrics[sf_info.resource_name] = sf_info
 
     def remove_switch_fabric(self, name: str):
+        assert name is not None
+
         if name in self.switch_fabrics.keys():
             self.switch_fabrics.pop(name)
 
     def get_switch_fabric(self, name: str):
+        assert name is not None
+
         return self.switch_fabrics.get(name, None)
diff --git a/fim/user/README.md b/fim/user/README.md
index 8fa6290..c717134 100644
--- a/fim/user/README.md
+++ b/fim/user/README.md
@@ -37,7 +37,7 @@ t.draw()
 t.serialize('test_slice.graphml')
 ```
 
-Parsing on controller side
+Parsing on orchestrator side
 ```python
 import fim.graph.slices.networkx_asm as nx_asm
 
@@ -54,9 +54,10 @@ The following scripts show the general idea of how FIM is to be used but the imp
 be different in package paths, function names and parameters. Use only for planning/design purposes.
 
 ### Create topology
+
 ```python
 import time
-from fabric_cf.orchestrator import OrchestratorProxy 
+from fabric_cf.orchestrator import OrchestratorProxy
 
 from fim.experiment.topology import ExperimentTopology
 from fim.experiment.node import Node
@@ -80,11 +81,11 @@ c3 = node2.add_component(Component.GPU, 'RTX6000', 'gpu2')
 # add shared dataplane interface
 c4 = node.add_component(Component.SharedNIC, 'ConnectX6', 'snic1')
 # declare port to be access on it
-i1 = node.components['snic1'].interfaces[0]
+i1 = node.components['snic1']._interfaces[0]
 i1.set_mode(InterfaceMode.ACCESS)
 
 # get the second port of the SmartNIC
-i2 = c2.interfaces[1]
+i2 = c2._interfaces[1]
 i2.set_mode(InterfaceMode.TRUNK)
 # create a child interface on the trunk
 i3 = i2.add_child_interface()
@@ -106,6 +107,7 @@ t.draw()
 # node1 -- nic1 --10Gbps-- node2 
 ```
 ### Query or update model without interacting with orchestrator
+
 ```python
 node = t.nodes['Node1']
 components = node.components()
@@ -115,12 +117,12 @@ for c in components:
 
 node.add_component(Component.FPGA, 'Alveo U280', 'fpga1')
 
-all_interfaces = t.interfaces
-node1_interfaces = node.interfaces
-node2_interfaces = t.nodes['Node2'].interfaces
+all_interfaces = t._interfaces
+node1_interfaces = node._interfaces
+node2_interfaces = t.nodes['Node2']._interfaces
 
-nodes = t.nodes # list all nodes
-links = t.links # list all links as tuples of interface names (probably concatenations
+nodes = t.nodes  # list all nodes
+links = t.links  # list all links as tuples of interface names (probably concatenations
 # of node_name.interface_name.child_interface or node_name.component_name.interface_name)
 node1_node2_link = t.links([node1_interfaces[1], node2_interfaces[0]])
 ```
@@ -134,14 +136,16 @@ t.serialize_to_file('filename')
 ```
 
 ### Submit and modify slice
+
 ```python
 # credential handling
 # 
 from fabric_cm.credmgr.credmgr_proxy import CredmgrProxy
+
 credmgr_proxy = CredmgrProxy(credmgr_host)
-tokens = credmgr_proxy.refresh_token(project_name=’p1’, 
-            scope=’cf’, 
-            refresh_token=’value’)
+tokens = credmgr_proxy.refresh_token(project_name=’p1’,
+scope =’cf’,
+refresh_token =’value’)
 
 id_token = tokens.get(‘id_token’)
 
@@ -166,15 +170,15 @@ if status != OrchestratorProxy.Status.OK:
 
 # check status
 while orchestrator.status('MyExperimentX') == OrchestratorProxy.Status.Waiting:
-  time.sleep(10)
-  print('Still waiting')
+    time.sleep(10)
+    print('Still waiting')
 
 slice = orchestrator.query('MyExperimentX')
 
 new_topology = slice.topology
 
-new_topology.nodes # list nodes
-new_topology.nodes['Node1'] # list properties of the node in concise form
+new_topology.nodes  # list nodes
+new_topology.nodes['Node1']  # list properties of the node in concise form
 
 node3 = new_topology.add_node('Node3', site='RENC')
 nic2 = node3.add_component(Component.SmartNIC, 'Connectx6', 'nic1')
@@ -182,9 +186,9 @@ nic2 = node3.add_component(Component.SmartNIC, 'Connectx6', 'nic1')
 # connect port of nic1 of node3 to port of nic1 of node1
 # by default ports are in access mode
 # add another child to Node1's trunk port on nic1
-i2 = new_topology.nodes['Node1'].components['nic1'].interfaces[1]
+i2 = new_topology.nodes['Node1'].components['nic1']._interfaces[1]
 i4 = i2.add_child_interface()
-new_topology.add_link(node3.components['nic1'].interfaces[0], i4)
+new_topology.add_link(node3.components['nic1']._interfaces[0], i4)
 
 status = orchestrator.modify(slice)
 
diff --git a/fim/user/__init__.py b/fim/user/__init__.py
index 17ac940..3676ef2 100644
--- a/fim/user/__init__.py
+++ b/fim/user/__init__.py
@@ -9,6 +9,7 @@
 import fim.slivers.component_catalog as cata
 import fim.slivers.interface_info as iinfo
 import fim.slivers.capacities_labels as caplab
+import fim.slivers.delegations as dlg
 
 # push some definitions up to simplify import management
 
@@ -26,5 +27,7 @@
 Link = link.Link
 LinkType = nlink.LinkType
 TopologyDetail = topo.TopologyDetail
-
-
+Delegation = dlg.Delegation
+DelegationType = dlg.DelegationType
+Pool = dlg.Pool
+ARMPools = dlg.ARMPools
diff --git a/fim/user/component.py b/fim/user/component.py
index f0827a2..ae85479 100644
--- a/fim/user/component.py
+++ b/fim/user/component.py
@@ -24,7 +24,7 @@
 #
 # Author: Ilya Baldin (ibaldin@renci.org)
 
-from typing import Any, Dict, List
+from typing import Any, Dict, List, Tuple
 
 import uuid
 
@@ -62,15 +62,13 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any,
 
         assert name is not None
         assert topo is not None
-        # cant use isinstance as it would create circular import dependencies
-        if str(topo.__class__) == "<class 'fim.user.topology.SubstrateTopology'>" and node_id is None:
-            raise RuntimeError("When adding components to substrate topology nodes you must specify static Node ID")
-        if etype == ElementType.NEW and model is None:
-            raise RuntimeError("When creating a new component, model must be specified")
-        if node_id is None:
-            node_id = str(uuid.uuid4())
-        super().__init__(name=name, node_id=node_id, topo=topo)
+
         if etype == ElementType.NEW:
+            # cant use isinstance as it would create circular import dependencies
+            if str(topo.__class__) == "<class 'fim.user.topology.SubstrateTopology'>" and node_id is None:
+                raise RuntimeError("When adding components to substrate topology nodes you must specify static Node ID")
+            if node_id is None:
+                node_id = str(uuid.uuid4())
             if parent_node_id is None:
                 raise RuntimeError("Parent node id must be specified for new components")
             if model is None or ctype is None:
@@ -80,6 +78,7 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any,
                     (switch_fabric_node_id is None or interface_node_ids is None):
                 raise RuntimeError('For substrate topologies and components with network interfaces '
                                    'static switch_fabric node id and interface node ids must be specified')
+            super().__init__(name=name, node_id=node_id, topo=topo)
             cata = ComponentCatalog()
             comp_sliver = cata.generate_component(name=name, model=model, ctype=ctype,
                                                   switch_fabric_node_id=switch_fabric_node_id,
@@ -89,13 +88,11 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any,
 
             self.topo.graph_model.add_component_sliver(parent_node_id=parent_node_id, component=comp_sliver)
         else:
-            # check that this node exists
-            existing_node_id = self.topo.graph_model.find_node_by_name(node_name=name,
-                                                                       label=str(ABCPropertyGraph.CLASS_Component))
-            if node_id is not None and existing_node_id != node_id:
-                raise RuntimeError("Existing node id does not match provided. "
-                                   "In general you shouldn't need to specify node id for existing nodes.")
-            self.node_id = node_id
+            assert node_id is not None
+            super().__init__(name=name, node_id=node_id, topo=topo)
+            if not self.topo.graph_model.check_node_name(node_id=node_id, name=name,
+                                                         label=ABCPropertyGraph.CLASS_Component):
+                raise RuntimeError(f"Component with this id and name {name} doesn't exist")
 
     def get_property(self, pname: str) -> Any:
         """
@@ -148,9 +145,21 @@ def __get_interface_by_id(self, node_id: str) -> Interface:
         return Interface(name=node_props[ABCPropertyGraph.PROP_NAME], node_id=node_id,
                          topo=self.topo)
 
+    def __get_sf_by_id(self, node_id: str) -> SwitchFabric:
+        """
+        Get an switch fabric of a node by its node_id, return SwitchFabric object
+        :param node_id:
+        :return:
+        """
+        assert node_id is not None
+        _, node_props = self.topo.graph_model.get_node_properties(node_id=node_id)
+        assert node_props.get(ABCPropertyGraph.PROP_NAME, None) is not None
+        return SwitchFabric(name=node_props[ABCPropertyGraph.PROP_NAME], node_id=node_id,
+                            topo=self.topo)
+
     def __list_interfaces(self) -> Dict[str, Interface]:
         """
-        List all interfaces of the node as a dictionary
+        List all interfaces of the component as a dictionary
         :return:
         """
         node_id_list = self.topo.graph_model.get_all_node_or_component_connection_points(parent_node_id=self.node_id)
@@ -161,6 +170,19 @@ def __list_interfaces(self) -> Dict[str, Interface]:
             ret[i.name] = i
         return ret
 
+    def __list_of_interfaces(self) -> Tuple[Interface]:
+        """
+        List all interfaces of the component
+        :return:
+        """
+        node_id_list = self.topo.graph_model.get_all_node_or_component_connection_points(parent_node_id=self.node_id)
+        # Could consider using frozendict here
+        ret = list()
+        for nid in node_id_list:
+            i = self.__get_interface_by_id(nid)
+            ret.append(i)
+        return tuple(ret)
+
     def __list_switch_fabrics(self) -> Dict[str, SwitchFabric]:
         """
         List all switch fabric children of a node as a dictionary organized
@@ -187,8 +209,11 @@ def __getattr__(self, item):
         """
         if item == 'interfaces':
             return self.__list_interfaces()
-        if item == 'switchfabrics':
+        if item == 'interface_list':
+            return self.__list_of_interfaces()
+        if item == 'switch_fabrics':
             return self.__list_switch_fabrics()
+        raise RuntimeError(f'Attribute {item} not available')
 
     def __repr__(self):
         _, node_properties = self.topo.graph_model.get_node_properties(node_id=self.node_id)
diff --git a/fim/user/interface.py b/fim/user/interface.py
index a4913fc..9b83381 100644
--- a/fim/user/interface.py
+++ b/fim/user/interface.py
@@ -48,7 +48,7 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any,
         :param node_id:
         :param topo:
         :param etype: is this supposed to exist or new should be created
-        :param parent_node_id: parent switch fabric when creating new interface
+        :param parent_node_id: parent switch fabric or parent interface
         :param itype: node type if it is new
         :param kwargs: any additional properties
         """
@@ -63,11 +63,9 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any,
                 raise RuntimeError("When adding new nodes to substrate topology nodes you must specify static Node ID")
             if node_id is None:
                 node_id = str(uuid.uuid4())
-            super().__init__(name=name, node_id=node_id, topo=topo)
             if itype is None:
                 raise RuntimeError("When creating interfaces you must specify InterfaceType")
-
-            self.node_id = node_id
+            super().__init__(name=name, node_id=node_id, topo=topo)
             sliver = InterfaceSliver()
             sliver.node_id = self.node_id
             sliver.set_resource_name(self.name)
@@ -76,15 +74,11 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any,
 
             self.topo.graph_model.add_interface_sliver(parent_node_id=parent_node_id, interface=sliver)
         else:
+            assert node_id is not None
             super().__init__(name=name, node_id=node_id, topo=topo)
-            # check that this node exists
-            existing_node_id = self.topo.\
-                graph_model.find_node_by_name(node_name=name,
-                                              label=str(ABCPropertyGraph.CLASS_ConnectionPoint))
-            if node_id is not None and existing_node_id != node_id:
-                raise RuntimeError("Existing node id does not match provided. "
-                                   "In general you shouldn't need to specify node id for existing interfaces.")
-            self.node_id = node_id
+            if not self.topo.graph_model.check_node_name(node_id=node_id, name=name,
+                                                         label=ABCPropertyGraph.CLASS_ConnectionPoint):
+                raise RuntimeError(f"Interface with this id and name {name} doesn't exist")
 
     def add_child_interface(self):
         raise RuntimeError("Not implemented")
diff --git a/fim/user/link.py b/fim/user/link.py
index 656c29f..857f77f 100644
--- a/fim/user/link.py
+++ b/fim/user/link.py
@@ -71,11 +71,11 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any,
             if ltype is None:
                 raise RuntimeError("When creating new links you must specify LinkType")
             # FIXME isinstance
-            if interfaces is None or len(interfaces) == 0 or not isinstance(interfaces, list):
+            if interfaces is None or len(interfaces) == 0 or (not isinstance(interfaces, tuple) and
+                                                              not isinstance(interfaces, list)):
                 raise RuntimeError("When creating new links you must specify the list of interfaces to connect")
 
-            self.node_id = node_id
-            self.interfaces = interfaces
+            self._interfaces = interfaces
             sliver = NetworkLinkSliver()
             sliver.node_id = self.node_id
             sliver.set_resource_name(self.name)
@@ -88,15 +88,14 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any,
             interface_ids = (iff.node_id for iff in interfaces)
             self.topo.graph_model.add_network_link_sliver(interfaces=interface_ids, lsliver=sliver)
         else:
+            assert node_id is not None
             super().__init__(name=name, node_id=node_id, topo=topo)
             # check that this node exists
             existing_node_id = self.topo.\
                 graph_model.find_node_by_name(node_name=name,
-                                              label=str(ABCPropertyGraph.CLASS_Link))
-            if node_id is not None and existing_node_id != node_id:
-                raise RuntimeError("Existing node id does not match provided. "
-                                   "In general you shouldn't need to specify node id for existing interfaces.")
-            self.node_id = node_id
+                                              label=ABCPropertyGraph.CLASS_Link)
+            if existing_node_id != node_id:
+                raise RuntimeError(f'Link name {name} is not unique within the topology.')
             # collect a list of interfaces it attaches to
             interface_list = self.topo.graph_model.get_all_link_interfaces(link_id=self.node_id)
             name_id_tuples = list()
@@ -104,7 +103,7 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any,
             for iff in interface_list:
                 _, props = self.topo.graph_model.get_node_properties(node_id=iff)
                 name_id_tuples.append((props[ABCPropertyGraph.PROP_NAME], iff))
-            self.interfaces = [Interface(node_id=tup[1], topo=topo, name=tup[0]) for tup in name_id_tuples]
+            self._interfaces = [Interface(node_id=tup[1], topo=topo, name=tup[0]) for tup in name_id_tuples]
 
     def get_property(self, pname: str) -> Any:
         """
@@ -145,12 +144,33 @@ def set_properties(self, **kwargs):
     def list_properties() -> List[str]:
         return NetworkLinkSliver.list_properties()
 
+    def __list_of_interfaces(self) -> List[Interface] or None:
+        """
+        Make a copy and return a list of interface objects
+        :return:
+        """
+        if self._interfaces is not None:
+            return self._interfaces.copy()
+        return None
+
+    def __getattr__(self, item):
+        """
+        Special handling for attributes like 'nodes' and 'links' -
+        which query into the model. They return dicts and list
+        containers. Modifying containers does not affect the underlying
+        graph mode, but modifying elements of lists or values of dicts does.
+        :param item:
+        :return:
+        """
+
+        if item == 'interface_list':
+            return self.__list_of_interfaces()
+        raise RuntimeError(f'Attribute {item} not available')
+
     def __repr__(self):
         _, node_properties = self.topo.graph_model.get_node_properties(node_id=self.node_id)
         link_sliver = self.topo.graph_model.link_sliver_from_graph_properties_dict(node_properties)
-        interface_names = list()
-        for iff in self.interfaces:
-            interface_names.append(iff.name)
+        interface_names = [iff.name for iff in self._interfaces]
         return link_sliver.__repr__() + str(interface_names)
 
     def __str__(self):
diff --git a/fim/user/node.py b/fim/user/node.py
index 3b345a1..8c9f820 100644
--- a/fim/user/node.py
+++ b/fim/user/node.py
@@ -24,7 +24,7 @@
 #
 # Author: Ilya Baldin (ibaldin@renci.org)
 
-from typing import Dict, Any, List
+from typing import Dict, Any, List, Tuple
 
 import uuid
 
@@ -33,7 +33,7 @@
 from .switch_fabric import SwitchFabric
 from .interface import Interface
 
-from ..graph.abc_property_graph import ABCPropertyGraph
+from ..graph.abc_property_graph import ABCPropertyGraph, PropertyGraphQueryException
 
 from ..slivers.network_node import NodeSliver
 from ..slivers.network_node import NodeType
@@ -76,7 +76,6 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any, etype: ElementT
             if site is None:
                 raise RuntimeError("When creating nodes you must specify site")
 
-            self.node_id = node_id
             sliver = NodeSliver()
             sliver.node_id = self.node_id
             sliver.set_resource_name(self.name)
@@ -86,14 +85,13 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any, etype: ElementT
 
             self.topo.graph_model.add_network_node_sliver(sliver=sliver)
         else:
+            assert node_id is not None
             super().__init__(name=name, node_id=node_id, topo=topo)
             # check that this node exists
             existing_node_id = self.topo.graph_model.find_node_by_name(node_name=name,
                                                                        label=str(ABCPropertyGraph.CLASS_NetworkNode))
-            if node_id is not None and existing_node_id != node_id:
-                raise RuntimeError("Existing node id does not match provided. "
-                                   "In general you shouldn't need to specify node id for existing nodes.")
-            self.node_id = node_id
+            if existing_node_id != node_id:
+                raise RuntimeError(f'Node name {name} is not unique within the topology')
 
     def get_property(self, pname: str) -> Any:
         """
@@ -101,6 +99,7 @@ def get_property(self, pname: str) -> Any:
         :param pname:
         :return:
         """
+        assert pname is not None
         _, node_properties = self.topo.graph_model.get_node_properties(node_id=self.node_id)
         node_sliver = self.topo.graph_model.node_sliver_from_graph_properties_dict(node_properties)
         return node_sliver.get_property(pname)
@@ -135,7 +134,8 @@ def list_properties() -> List[str]:
         return NodeSliver.list_properties()
 
     def add_component(self, *, name: str, node_id: str = None, ctype: ComponentType,
-                      model: str,  switch_fabric_node_id: str = None, **kwargs) -> Component:
+                      model: str,  switch_fabric_node_id: str = None, interface_node_ids=None,
+                      **kwargs) -> Component:
         """
         Add a component of specified type, model and name to this node. When working with substrate
         topologies you must specify the switch_fabric_node_id and provide a list of interface node ids.
@@ -145,13 +145,18 @@ def add_component(self, *, name: str, node_id: str = None, ctype: ComponentType,
         :param model:
         :param name:
         :param switch_fabric_node_id:
+        :param interface_node_ids:
         :param kwargs: additional properties of the component
         :return:
         """
+        assert name is not None
+        # make sure name is unique within the node
+        if name in self.__list_components().keys():
+            raise RuntimeError('Component names must be unique within node.')
         # add component node and populate properties
         c = Component(name=name, node_id=node_id, topo=self.topo, etype=ElementType.NEW,
                       ctype=ctype, model=model, switch_fabric_node_id=switch_fabric_node_id,
-                      parent_node_id=self.node_id, **kwargs)
+                      interface_node_ids=interface_node_ids, parent_node_id=self.node_id, **kwargs)
         return c
 
     def add_switch_fabric(self, *, name: str, node_id: str = None, layer: SFLayer):
@@ -162,6 +167,10 @@ def add_switch_fabric(self, *, name: str, node_id: str = None, layer: SFLayer):
         :param layer:
         :return:
         """
+        assert name is not None
+        # make sure name is unique within the node
+        if name in self.__list_switch_fabrics().keys():
+            raise RuntimeError('SwitchFabric names must be unique within node.')
         sf = SwitchFabric(name=name, node_id=node_id, layer=layer, parent_node_id=self.node_id,
                           etype=ElementType.NEW, topo=self.topo)
         return sf
@@ -196,7 +205,8 @@ def __get_component_by_name(self, name: str) -> Component:
         :return:
         """
         assert name is not None
-        node_id = self.topo.graph_model.find_component_by_name(parent_node_id=self.node_id, component_name=name)
+        node_id = self.topo.graph_model.find_component_by_name(parent_node_id=self.node_id,
+                                                               component_name=name)
         return Component(name=name, node_id=node_id, topo=self.topo)
 
     def __get_component_by_id(self, node_id: str) -> Component:
@@ -211,6 +221,16 @@ def __get_component_by_id(self, node_id: str) -> Component:
         return Component(name=node_props[ABCPropertyGraph.PROP_NAME], node_id=node_id,
                          topo=self.topo)
 
+    def __get_sf_by_name(self, name: str) -> SwitchFabric:
+        """
+        Find SwitchFabric of a node by its name, return SwitchFabric object
+        :param name:
+        :return:
+        """
+        assert name is not None
+        node_id = self.topo.graph_model.find_sf_by_name(parent_node_id=self.node_id, component_name=name)
+        return SwitchFabric(name=name, node_id=node_id, topo=self.topo)
+
     def __get_sf_by_id(self, node_id: str) -> SwitchFabric:
         """
         Get a switch fabric of a node by its node_id, return SwitchFabric object
@@ -230,8 +250,9 @@ def __get_interface_by_id(self, node_id: str) -> Interface:
         :return:
         """
         assert node_id is not None
-        _, node_props = self.topo.graph_model.get_node_properties(node_id=node_id)
+        clazzes, node_props = self.topo.graph_model.get_node_properties(node_id=node_id)
         assert node_props.get(ABCPropertyGraph.PROP_NAME, None) is not None
+        assert ABCPropertyGraph.CLASS_ConnectionPoint in clazzes
         return Interface(name=node_props[ABCPropertyGraph.PROP_NAME], node_id=node_id,
                          topo=self.topo)
 
@@ -291,6 +312,18 @@ def __list_interfaces(self) -> Dict[str, Interface]:
             direct_interfaces.update(comp_interfaces)
         return direct_interfaces
 
+    def __list_of_interfaces(self) -> Tuple[Interface]:
+        """
+        List all interfaces of node and its components
+        :return:
+        """
+        direct_interfaces = self.__list_direct_interfaces()
+        cdict = self.__list_components()
+        for k, v in cdict.items():
+            comp_interfaces = v.interfaces
+            direct_interfaces.update(comp_interfaces)
+        return Tuple(direct_interfaces.values())
+
     def get_component(self, name: str):
         """
         Get a component by this name
@@ -312,10 +345,13 @@ def __getattr__(self, item):
             return self.__list_components()
         if item == 'interfaces':
             return self.__list_interfaces()
+        if item == 'interface_list':
+            return self.__list_of_interfaces()
         if item == 'direct_interfaces':
             return self.__list_direct_interfaces()
         if item == 'switch_fabrics':
             return self.__list_switch_fabrics()
+        raise RuntimeError(f'Attribute {item} not available')
 
     def __repr__(self):
         _, node_properties = self.topo.graph_model.get_node_properties(node_id=self.node_id)
diff --git a/fim/user/switch_fabric.py b/fim/user/switch_fabric.py
index 27534c9..a557c58 100644
--- a/fim/user/switch_fabric.py
+++ b/fim/user/switch_fabric.py
@@ -23,7 +23,7 @@
 #
 #
 # Author: Ilya Baldin (ibaldin@renci.org)
-from typing import Any, List, Dict
+from typing import Any, List, Dict, Tuple
 import uuid
 
 from .model_element import ModelElement, ElementType
@@ -52,20 +52,20 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any,
         """
         assert name is not None
         assert topo is not None
-        # cant use isinstance as it would create circular import dependencies
-        if str(topo.__class__) == "<class 'fim.user.topology.SubstrateTopology'>" and node_id is None:
-            raise RuntimeError("When adding switch fabrics to substrate topology nodes you must specify static Node ID")
-        if node_id is None:
-            node_id = str(uuid.uuid4())
-        super().__init__(name=name, node_id=node_id, topo=topo)
+
         if etype == ElementType.NEW:
+            # cant use isinstance as it would create circular import dependencies
+            if str(topo.__class__) == "<class 'fim.user.topology.SubstrateTopology'>" and node_id is None:
+                raise RuntimeError(
+                    "When adding switch fabrics to substrate topology nodes you must specify static Node ID")
+            if node_id is None:
+                node_id = str(uuid.uuid4())
             if parent_node_id is None:
                 raise RuntimeError("For new switch fabrics parent node id must be specified")
             if str(topo.__class__) == "<class 'fim.user.topology.SubstrateTopology'>" and node_id is None:
                 raise RuntimeError("When adding switch fabrics to substrate topology nodes "
                                    "you must specify static Node ID")
-            if node_id is None:
-                node_id = str(uuid.uuid4())
+            super().__init__(name=name, node_id=node_id, topo=topo)
             sfsliver = SwitchFabricSliver()
             sfsliver.node_id = node_id
             sfsliver.set_resource_name(name)
@@ -73,24 +73,29 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any,
             sfsliver.set_layer(layer)
             self.topo.graph_model.add_switch_fabric_sliver(parent_node_id=parent_node_id, switch_fabric=sfsliver)
         else:
-            # check that this node exists
-            existing_node_id = self.topo.graph_model.find_node_by_name(node_name=name,
-                                                                       label=str(ABCPropertyGraph.CLASS_SwitchFabric))
-            if node_id is not None and existing_node_id != node_id:
-                raise RuntimeError("Existing node id does not match provided. "
-                                   "In general you shouldn't need to specify node id for existing nodes.")
-            self.node_id = node_id
+            assert node_id is not None
+            super().__init__(name=name, node_id=node_id, topo=topo)
+            if not self.topo.graph_model.check_node_name(node_id=node_id, name=name,
+                                                         label=ABCPropertyGraph.CLASS_SwitchFabric):
+                raise RuntimeError(f"SwitchFabric with this id and name {name} doesn't exist")
 
-    def add_interface(self, *, name: str, node_id: str = None, itype: InterfaceType = InterfaceType.TrunkPort):
+    def add_interface(self, *, name: str, node_id: str = None, itype: InterfaceType = InterfaceType.TrunkPort,
+                      **kwargs):
         """
         Add an interface to node (mostly needed in substrate topologies)
         :param name:
         :param node_id:
         :param itype: interface type e.g. TrunkPort, AccessPort or VINT
+        :param kwargs: additional parameters
         :return:
         """
+        assert name is not None
+        # check uniqueness
+        if name in self.__list_interfaces().keys():
+            raise RuntimeError('Interface names must be unique within a switch fabric')
         iff = Interface(name=name, node_id=node_id, parent_node_id=self.node_id,
-                        etype=ElementType.NEW, topo=self.topo, itype=itype)
+                        etype=ElementType.NEW, topo=self.topo, itype=itype,
+                        **kwargs)
         return iff
 
     def remove_interface(self, *, name: str) -> None:
@@ -157,9 +162,20 @@ def __get_interface_by_id(self, node_id: str) -> Interface:
         return Interface(name=node_props[ABCPropertyGraph.PROP_NAME], node_id=node_id,
                          topo=self.topo)
 
+    def __get_interface_by_name(self, name: str) -> Interface:
+        """
+        Get an interface of switch fabric by its name
+        :param name:
+        :return:
+        """
+        assert name is not None
+        node_id = self.topo.graph_model.find_connection_point_by_name(parent_node_id=self.node_id,
+                                                                      iname=name)
+        return Interface(name=name, node_id=node_id, topo=self.topo)
+
     def __list_interfaces(self) -> Dict[str, Interface]:
         """
-        List all interfaces of the topology as a dictionary
+        List all interfaces of the switch fabric as a dictionary
         :return:
         """
         node_id_list = self.topo.graph_model.get_all_sf_connection_points(parent_node_id=self.node_id)
@@ -170,6 +186,18 @@ def __list_interfaces(self) -> Dict[str, Interface]:
             ret[c.name] = c
         return ret
 
+    def __list_of_interfaces(self) -> Tuple[Interface]:
+        """
+        Return a list of all interfaces of switch fabric
+        :return:
+        """
+        node_id_list = self.topo.graph_model.get_all_sf_connection_points(parent_node_id=self.node_id)
+        ret = list()
+        for nid in node_id_list:
+            c = self.__get_interface_by_id(nid)
+            ret.append(c)
+        return tuple(ret)
+
     def __getattr__(self, item):
         """
         Special handling for attributes like 'components' and 'interfaces' -
@@ -181,6 +209,9 @@ def __getattr__(self, item):
         """
         if item == 'interfaces':
             return self.__list_interfaces()
+        if item == 'interface_list':
+            return self.__list_of_interfaces()
+        raise RuntimeError(f'Attribute {item} not available')
 
     def __repr__(self):
         _, node_properties = self.topo.graph_model.get_node_properties(node_id=self.node_id)
diff --git a/fim/user/topology.py b/fim/user/topology.py
index d6fe2b9..9026716 100644
--- a/fim/user/topology.py
+++ b/fim/user/topology.py
@@ -25,19 +25,21 @@
 # Author: Ilya Baldin (ibaldin@renci.org)
 
 from abc import ABC, abstractmethod
-from typing import List, Dict, Any
+from typing import List, Dict, Any, Tuple
 import enum
 
 import uuid
 import networkx as nx
 import matplotlib.pyplot as plt
 
+from .model_element import ModelElement
 from ..slivers.network_node import NodeType, NodeSliver
 from ..slivers.network_link import LinkType, NetworkLinkSliver
 from ..slivers.switch_fabric import SFLayer
 from ..graph.slices.networkx_asm import NetworkxASM
-from ..graph.networkx_property_graph import NetworkXGraphImporter
 from ..graph.abc_property_graph import ABCPropertyGraph
+from ..graph.resources.networkx_adm import NetworkXADMGraph, NetworkXGraphImporter
+from ..slivers.delegations import Delegation, ARMDelegations, Pool, ARMPools, DelegationType
 
 from .model_element import ElementType
 from .node import Node
@@ -199,15 +201,15 @@ def __list_links(self) -> Dict[str, Link]:
             ret[n.name] = n
         return ret
 
-    def __list_interfaces(self) -> Dict[str, Interface]:
+    def __list_of_interfaces(self) -> Tuple[Interface]:
         """
         List all interfaces of the topology as a dictionary
         :return:
         """
-        ret = dict()
+        ret = list()
         for n in self.nodes.values():
-            ret.update(n.interfaces)
-        return ret
+            ret.extend(n.interfaces.values())
+        return tuple(ret)
 
     def __getattr__(self, item):
         """
@@ -222,8 +224,9 @@ def __getattr__(self, item):
             return self.__list_nodes()
         if item == 'links':
             return self.__list_links()
-        if item == 'interfaces':
-            return self.__list_interfaces()
+        if item == 'interface_list':
+            return self.__list_of_interfaces()
+        raise RuntimeError(f'Attribute {item} not available')
 
     def serialize(self, file_name=None) -> str or None:
         """
@@ -313,7 +316,7 @@ def draw(self, *, file_name: str = None, interactive: bool = False,
                 for nint in node_ints:
                     for l in self.links.values():
                         # this works because of custom ModelElement.__eq__()
-                        if nint in l.interfaces:
+                        if nint in l.interface_list:
                             derived_graph.add_edge(n.name, l.name)
 
             pos = layout(derived_graph)
@@ -342,3 +345,74 @@ class SubstrateTopology(Topology):
     def __init__(self, graph_file: str = None, graph_string: str = None, logger=None):
         super().__init__(graph_file=graph_file, graph_string=graph_string, logger=logger)
 
+    def as_adm(self):
+        """
+        Return mode as ADM graph built on top of internal model. Model is not cloned or copied,
+        rather recast into ADM, so any changes to the model propagate back to the topology.
+        :return:
+        """
+        return NetworkXADMGraph(graph_id=self.graph_model.graph_id,
+                                importer=self.graph_model.importer,
+                                logger=self.graph_model.log)
+
+    @staticmethod
+    def __copy_to_delegation(e: ModelElement, atype: DelegationType,
+                             delegation_id: str) -> Delegation or None:
+        """
+        From a model element (node, components, switch_fabric, interface), copy
+        capacities or labels to delegation and return delegation)
+        :param e:
+        :return:
+        """
+        assert e is not None
+        assert delegation_id is not None
+        ret = Delegation(atype=atype, defined_on=e.node_id, delegation_id=delegation_id)
+        if atype == DelegationType.CAPACITY:
+            caps_or_labels = e.get_property(pname='capacities')
+            if caps_or_labels is not None:
+                ret.set_details_from_capacities(caps_or_labels)
+                return ret
+        else:
+            caps_or_labels = e.get_property(pname='labels')
+            if caps_or_labels is not None:
+                ret.set_details_from_labels(caps_or_labels)
+                return ret
+        return None
+
+    def single_delegation(self, *, delegation_id: str, label_pools: ARMPools, capacity_pools: ARMPools):
+        """
+        For simple cases when there is one delegation that delegates everything. Be sure to have
+        capacities and labels specified on individual elements where needed - they get copied
+        into delegations. Pools must be specified externally.
+        :param delegation_id:
+        :param label_pools:
+        :param capacity_pools:
+        :return:
+        """
+        assert label_pools.pool_type == DelegationType.LABEL
+        assert capacity_pools.pool_type == DelegationType.CAPACITY
+
+        delegations = {DelegationType.CAPACITY: ARMDelegations(DelegationType.CAPACITY),
+                       DelegationType.LABEL: ARMDelegations(DelegationType.LABEL)}
+        pool_dict = {DelegationType.CAPACITY: capacity_pools,
+                     DelegationType.LABEL: label_pools}
+        for t in DelegationType:
+            for n in self.nodes.values():
+                # delegate every node and its component by copying their capacities and labels
+                # if present
+                delegation = self.__copy_to_delegation(e=n, atype=t,
+                                                       delegation_id=delegation_id)
+                if delegation is not None:
+                    delegations[t].add_delegation(delegation)
+                for c in n.components.values():
+                    delegation = self.__copy_to_delegation(e=c, atype=t,
+                                                           delegation_id=delegation_id)
+                    if delegation is not None:
+                        delegations[t].add_delegation(delegation)
+                    for i in c.interfaces.values():
+                        delegation = self.__copy_to_delegation(e=i, atype=t,
+                                                               delegation_id=delegation_id)
+                        if delegation is not None:
+                            delegations[t].add_delegation(delegation)
+            self.as_adm().annotate_delegations_and_pools(dels=delegations[t],
+                                                         pools=pool_dict[t])
diff --git a/requirements.txt b/requirements.txt
index bd0b3a8..d716c03 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -13,7 +13,7 @@ isort==4.3.21
 keyring==21.5.0
 kiwisolver==1.3.1
 lazy-object-proxy==1.4.3
-matplotlib==3.2.2
+matplotlib==3.3.4
 mccabe==0.6.1
 neo4j==4.1.0
 networkx==2.5
diff --git a/setup.py b/setup.py
index a2fa68e..7188b4e 100644
--- a/setup.py
+++ b/setup.py
@@ -8,7 +8,7 @@
 
 setuptools.setup(
   name="fabric_fim",
-  version="0.24",
+  version="0.25",
   author="Ilya Baldin, Komal Thareja",
   description="FABRIC Information Model Library",
   url="https://github.com/fabric-testbed/InformationModel",


Test patch:
diff --git a/test/slice_topology_test.py b/test/slice_topology_test.py
index 5f53445..7d15fcb 100644
--- a/test/slice_topology_test.py
+++ b/test/slice_topology_test.py
@@ -34,13 +34,31 @@ def testNodesAndComponents(self):
         # component checks
         n1.add_component(ctype=f.ComponentType.GPU, model='RTX6000', name='gpu1')
         n1.add_component(ctype=f.ComponentType.SharedNIC, model='ConnectX-6', name='nic1')
-        n2.add_component(ctype=f.ComponentType.SharedNIC, model='ConnectX-6', name='nic2')
+        n2.add_component(ctype=f.ComponentType.SmartNIC, model='ConnectX-6', name='nic2')
         n3.add_component(ctype=f.ComponentType.SharedNIC, model='ConnectX-6', name='nic3')
 
         self.assertEqual(len(n1.components), 2)
         self.assertEqual(len(n2.components), 1)
         gpu1 = n1.components['gpu1']
         nic1 = n1.components['nic1']
+        nic2 = n2.components['nic2']
+
+        # name uniqueness enforcement
+        with self.assertRaises(AssertionError) as e:
+            self.topo.add_node(name='Node1', site='UKY')
+
+        gpu1_1 = n2.add_component(ctype=f.ComponentType.GPU, model='RTX6000', name='gpu1')
+        n2.remove_component(name='gpu1')
+        nic1_1 = n2.add_component(ctype=f.ComponentType.SmartNIC, model='ConnectX-6', name='nic1')
+        n2.remove_component(name='nic1')
+
+        # check various modes of access
+        assert(self.topo.nodes['Node1'] == n1)
+        assert(self.topo.nodes['Node1'].components['gpu1'] == gpu1)
+        assert(len(self.topo.interface_list) == 4)
+        assert(len(nic1.interface_list) == 1)
+        assert(len(nic2.interface_list) == 2)
+        assert(len(list(nic1.switch_fabrics.values())[0].interface_list) == 1)
 
         self.assertTrue(len(gpu1.interfaces) == 0)
         self.assertTrue(len(nic1.interfaces) == 1)
@@ -60,9 +78,12 @@ def testNodesAndComponents(self):
         self.assertEqual(nic1, nic11)
 
         # interfaces and links
-        self.assertEqual(len(self.topo.interfaces), 3)
+        self.assertEqual(len(self.topo.interface_list), 4)
+
+        self.topo.add_link(name='l1', ltype=f.LinkType.Wave, interfaces=self.topo.interface_list)
 
-        self.topo.add_link(name='l1', ltype=f.LinkType.Wave, interfaces=list(self.topo.interfaces.values()))
+        with self.assertRaises(AssertionError) as e:
+            self.topo.add_link(name='l1', ltype=f.LinkType.Wave, interfaces=self.topo.interface_list)
 
         self.assertEqual(len(self.topo.links), 1)
         # removal checks
@@ -76,12 +97,12 @@ def testNodesAndComponents(self):
         self.assertEqual(len(self.topo.links), 0)
         # GPU left
         self.assertTrue(len(n1.components), 1)
-        self.assertTrue(len(self.topo.interfaces), 1)
+        self.assertTrue(len(self.topo.interface_list), 1)
         # remove remaining nodes
         self.topo.remove_node(name='node3')
         self.topo.remove_node(name='Node1')
         self.assertEqual(len(self.topo.nodes), 0)
-        self.assertEqual(len(self.topo.interfaces), 0)
+        self.assertEqual(len(self.topo.interface_list), 0)
         self.assertEqual(len(self.topo.links), 0)
 
     def testDeepSliver(self):
diff --git a/test/sliver_test.py b/test/sliver_test.py
new file mode 100644
index 0000000..2cc783d
--- /dev/null
+++ b/test/sliver_test.py
@@ -0,0 +1,47 @@
+import unittest
+
+from fim.slivers.attached_components import ComponentSliver, ComponentType, AttachedComponentsInfo
+from fim.slivers.network_node import NodeSliver, NodeType
+from fim.slivers.network_link import NetworkLinkSliver
+from fim.slivers.switch_fabric import SwitchFabricSliver, SwitchFabricInfo
+
+
+class TestSlivers(unittest.TestCase):
+
+    def testComponentSliver(self):
+        cs = ComponentSliver()
+        cs.set_property('details', 'these are details')
+        cs.set_properties(resource_type=ComponentType.GPU, resource_name='name')
+
+        self.assertEqual(cs.get_details(), 'these are details')
+        self.assertEqual(cs.get_resource_type(), ComponentType.GPU)
+        self.assertEqual(cs.get_resource_name(), 'name')
+
+        cs1 = ComponentSliver()
+        cs1.set_property('details', 'these are other details')
+        cs1.set_properties(resource_type=ComponentType.GPU, resource_name='name1')
+
+        cs2 = ComponentSliver()
+        cs2.set_property('details', 'these are some details')
+        cs2.set_properties(resource_type=ComponentType.SmartNIC, resource_name='name2')
+
+        acs = AttachedComponentsInfo()
+        acs.add_device(cs)
+        acs.add_device(cs1)
+        acs.add_device(cs2)
+
+        self.assertEqual(len(acs.by_type[ComponentType.GPU]), 2)
+        self.assertEqual(len(acs.by_type[ComponentType.SmartNIC]), 1)
+        self.assertEqual(len(acs.get_devices_by_type(ComponentType.SharedNIC)), 0)
+
+        acs.remove_device(name='name')
+        acs.remove_device(name='name1')
+        self.assertEqual(len(acs.get_devices_by_type(ComponentType.GPU)), 0)
+        self.assertEqual(len(acs.get_devices_by_type(ComponentType.SmartNIC)), 1)
+
+    def testNodeSliver(self):
+        ns = NodeSliver()
+        ns.set_properties(resource_name='node1', resource_type=NodeType.Server,
+                          management_ip='192.168.1.1')
+        with self.assertRaises(ValueError) as ve:
+            ns.set_property('management_ip', '192.168.1.x')
diff --git a/test/substrate_topology_test.py b/test/substrate_topology_test.py
index e69de29..374b5cf 100644
--- a/test/substrate_topology_test.py
+++ b/test/substrate_topology_test.py
@@ -0,0 +1,271 @@
+import unittest
+
+import fim.user as f
+from fim.graph.abc_property_graph import ABCPropertyGraph
+
+
+class AdTest(unittest.TestCase):
+
+    def setUp(self) -> None:
+        self.topo = f.SubstrateTopology()
+
+    def tearDown(self) -> None:
+        pass
+
+    def testSiteAd(self):
+        # create a site advertisement
+        site = 'RENC'
+        head_model = 'R7515'
+        worker_model = 'R7525'
+        hn_cap = f.Capacities()
+        hn_cap.set_fields(core=32, cpu=1, unit=1, ram=128, disk=100000)
+        network_worker_cap = f.Capacities()
+        # only internal storage
+        network_worker_cap.set_fields(core=32, cpu=2, unit=1, ram=512, disk=4800)
+        gpu_worker_cap = f.Capacities()
+        # has access to SAS NAS
+        gpu_worker_cap.set_fields(core=32, cpu=2, unit=1, ram=512, disk=100000)
+        hn = self.topo.add_node(name='headnode', model=head_model, site=site,
+                                node_id='702C4409-6635-4051-91A0-9C5A45CA28EC',
+                                ntype=f.NodeType.Server, capacities=hn_cap)
+        gpuw = self.topo.add_node(name='gpuworker', model=worker_model, site=site,
+                                        node_id='8CA64353-182D-4CF2-8A07-2ADFF7F9C42A',
+                                        ntype=f.NodeType.Server, capacities=gpu_worker_cap)
+        fnw = self.topo.add_node(name='fastnetworker', model=worker_model, site=site,
+                                            node_id='B87900A2-5225-432A-92F8-60DD961597BE',
+                                            ntype=f.NodeType.Server, capacities=network_worker_cap)
+        snw = self.topo.add_node(name='slownetworker', model=worker_model, site=site,
+                                            node_id='169A2327-D6BC-4B9D-AE3A-3396CFF57138',
+                                            ntype=f.NodeType.Server, capacities=network_worker_cap)
+
+        gpu_nvme1 = gpuw.add_component(name='gpunvme1', model='P4510',
+                                             node_id='55E8A23A-EAB9-41BF-BE9F-F42BB5920046',
+                                             ctype=f.ComponentType.NVME,
+                                             capacities=f.Capacities().set_fields(unit=1, disk=1000),
+                                             labels=f.Labels().set_fields(bdf='00:00:01'))
+        gpu_nvme2 = gpuw.add_component(name='gpunvme2', model='P4510',
+                                             node_id='7BD64A0E-DBE0-4BA1-89B0-CE9D8E6959B0',
+                                             ctype=f.ComponentType.NVME,
+                                             capacities=f.Capacities().set_fields(unit=1, disk=1000),
+                                             labels=f.Labels().set_fields(bdf='00:00:02'))
+
+        gpu_gpu1 = gpuw.add_component(name='gpugpu1', model='RTX6000',
+                                            node_id='49B5D670-64BD-4E90-8915-73CDACAB0EB5',
+                                            ctype=f.ComponentType.GPU,
+                                      capacities=f.Capacities().set_fields(unit=1),
+                                            labels=f.Labels().set_fields(bdf='00:01:0f'))
+        gpu_gpu2 = gpuw.add_component(name='gpugpu2', model='RTX6000',
+                                            node_id='EEC1F433-F102-4E68-A6DD-C20059C71B76',
+                                            ctype=f.ComponentType.GPU,
+                                        capacities = f.Capacities().set_fields(unit=1),
+                                            labels=f.Labels().set_fields(bdf='00:02:0f'))
+        gpuw_shnic = gpuw.add_component(name='gpushnic', model='ConnectX-6',
+                                             node_id='B6D4ED83-03C0-4B37-90AD-966769952F66',
+                                             switch_fabric_node_id='4E3B7A5E-5DC0-4D65-94B1-06B2E9519B08',
+                                             interface_node_ids=['0FD7CD42-00F9-45A1-9813-2E39AEF25243'],
+                                            capacities=f.Capacities().set_fields(unit=1),
+                                             labels=f.Labels().set_fields(bdf='00:03:0f'),
+                                             ctype=f.ComponentType.SharedNIC,
+                                             details='Shared NIC')
+        fnw_shnic = fnw.add_component(name='fnshnic', model='ConnectX-6',
+                                                node_id='E84F69AB-D9BB-4F6E-99CF-CC08BF26B50E',
+                                                switch_fabric_node_id='A5624045-C315-4FE5-A719-98E7891E7DBE',
+                                                interface_node_ids=['752D4D79-B91B-4657-B744-6EDBE331D539'],
+                                                ctype=f.ComponentType.SharedNIC,
+                                                capacities=f.Capacities().set_fields(unit=1),
+                                                labels=f.Labels().set_fields(bdf='00:01:0f'),
+                                                details='Dedicated SmartNIC')
+        fnw_nic1 = fnw.add_component(name='fnnic1', model='ConnectX-6',
+                                               node_id='631210E4-2064-4102-A6DB-1E6E33A7DA8A',
+                                               switch_fabric_node_id='2ED64BEB-DCB1-4E80-87EF-E46960A7444D',
+                                               interface_node_ids=['AEBC1418-37B8-438E-B752-E8C1304E7C33',
+                                                                   'B6615811-6DDC-43CC-B012-A46E8A1A21E6'],
+                                               ctype=f.ComponentType.SmartNIC,
+                                               capacities=f.Capacities().set_fields(unit=1),
+                                               labels=f.Labels().set_fields(bdf='00:02:0f'),
+                                               details='Dedicated SmartNIC')
+        fnw_nic2 = fnw.add_component(name='fnnic2', model='ConnectX-6',
+                                            node_id='86F4EE34-6FD3-4CF5-A3BB-A2BA03884833',
+                                            switch_fabric_node_id='49766FAD-9813-4E0B-B254-75012AF3ABE2',
+                                            interface_node_ids=['E36A4783-D526-46E6-9833-5145C9D684CF',
+                                                                'E3BBD95E-CA6D-400C-A3BF-4FE1A196F50D'],
+                                            ctype=f.ComponentType.SmartNIC,
+                                            capacities=f.Capacities().set_fields(unit=1),
+                                            labels=f.Labels().set_fields(bdf='00:03:0f'),
+                                            details='Dedicated SmartNIC')
+        snw_shnic = snw.add_component(name='snshnic', model='ConnectX-6',
+                                            node_id='40372F80-3517-4C5A-82A7-14782A513001',
+                                            switch_fabric_node_id='64921F73-3719-4323-8B29-0A59329FA1E3',
+                                            interface_node_ids=['0DD43A8F-B84F-44C0-87AC-878D9C30CA16'],
+                                            ctype=f.ComponentType.SharedNIC,
+                                            capacities=f.Capacities().set_fields(unit=1),
+                                            labels=f.Labels().set_fields(bdf='00:04:0f'),
+                                            details='Shared NIC')
+        snw_nic1 = snw.add_component(name='snnic1', model='ConnectX-5',
+                                            node_id='054D0FF8-7D1F-403E-8900-87FDC6AF644A',
+                                            switch_fabric_node_id='72F4189E-507A-4B01-B39E-D0080EBCF9FC',
+                                            interface_node_ids=['5A104AFD-2C84-4E30-9BCE-59149531C310',
+                                                                '8E0722EB-AD3E-4858-BD2E-2607C6F52405'],
+                                            ctype=f.ComponentType.SmartNIC,
+                                            capacities=f.Capacities().set_fields(unit=1),
+                                            labels=f.Labels().set_fields(bdf='00:05:0f'),
+                                            details='Dedicated SmartNIC')
+        snw_nic2 = snw.add_component(name='snnic2', model='ConnectX-5',
+                                            node_id='D3AC3C42-8D0B-47C0-8420-D6D16A59DF10',
+                                            switch_fabric_node_id='23FB9D1D-4026-4C52-A32A-8ED73FBCC6AD',
+                                            interface_node_ids=['D1EEB715-69A3-4CC3-A90C-2F497B1358AD',
+                                                                '900D122A-080D-4D29-9186-24AF4D53361B'],
+                                            ctype=f.ComponentType.SmartNIC,
+                                            capacities=f.Capacities().set_fields(unit=1),
+                                            labels=f.Labels().set_fields(bdf='00:06:0f'),
+                                            details='Dedicated SmartNIC')
+        # NAS
+        nas_model = 'ME4084'
+        nas = self.topo.add_node(name='nas', model=nas_model, site=site, ntype=f.NodeType.NAS,
+                                 node_id='D6787DF8-D2AE-4C3C-B8DE-577CE69C4AEC',
+                                 capacities=f.Capacities().set_fields(unit=1, disk=100000))
+        # DP switch
+        switch_model = 'NCS500-24'
+        switch = self.topo.add_node(name='dp_switch', model=switch_model, site=site,
+                                    node_id='8D1D8073-71D5-4B71-86CD-1A8D5272926F',
+                                    capacities=f.Capacities().set_fields(unit=1),
+                                    ntype=f.NodeType.Switch)
+        dp_sf = switch.add_switch_fabric(name='sp_sf', layer=f.Layer.L2,
+                                         node_id='DBA3C158-45B8-4DB0-B023-DAA52CE589FD')
+        # add ports
+        port_caps = f.Capacities()
+        port_caps.set_fields(bw=100)
+        port_labs = f.Labels()
+        port_labs.set_fields(vlan_range='1000-2000')
+        sp1 = dp_sf.add_interface(name='p1', itype=f.InterfaceType.TrunkPort,
+                            node_id='62FCEC64-9C21-489F-96A9-0A74702CFB6F',
+                            capacities=port_caps, labels=port_labs)
+        sp2 = dp_sf.add_interface(name='p2', itype=f.InterfaceType.TrunkPort,
+                            node_id='A8174EA2-BBD0-4C8B-ABC1-8615FC30C8E6',
+                            capacities=port_caps, labels=port_labs)
+        sp3 = dp_sf.add_interface(name='p3', itype=f.InterfaceType.TrunkPort,
+                            node_id='4B594B5F-2FA1-41FA-BF9E-7AB0EC970514',
+                            capacities=port_caps, labels=port_labs)
+        sp4 = dp_sf.add_interface(name='p4', itype=f.InterfaceType.TrunkPort,
+                            node_id='1A2DFA26-1F21-41E9-A97D-ABFB65BE5AF2',
+                            capacities=port_caps, labels=port_labs)
+        sp5 = dp_sf.add_interface(name='p5', itype=f.InterfaceType.TrunkPort,
+                            node_id='130FA579-BC0B-43BB-99AE-B7619955475B',
+                            capacities=port_caps, labels=port_labs)
+        sp6 = dp_sf.add_interface(name='p6', itype=f.InterfaceType.TrunkPort,
+                            node_id='9F906AA1-BC4A-40F3-8AE2-FCE8D00C54C0',
+                            capacities=port_caps, labels=port_labs)
+        sp7 = dp_sf.add_interface(name='p7', itype=f.InterfaceType.TrunkPort,
+                            node_id='0EDE076D-1979-4C85-B580-7A7563D7B71F',
+                            capacities=port_caps, labels=port_labs)
+        sp8 = dp_sf.add_interface(name='p8', itype=f.InterfaceType.TrunkPort,
+                            node_id='A286EAE5-3CEA-4D8F-BB1A-685957334917',
+                            capacities=port_caps, labels=port_labs)
+        sp9 = dp_sf.add_interface(name='p9', itype=f.InterfaceType.TrunkPort,
+                            node_id='0D2B98C0-80A1-47BB-B656-EDF4815E635C',
+                            capacities=port_caps, labels=port_labs)
+        sp10 = dp_sf.add_interface(name='p10', itype=f.InterfaceType.TrunkPort,
+                            node_id='71B46263-7723-45BC-A9EA-2AD090DC39AA',
+                            capacities=port_caps, labels=port_labs)
+
+        # add links
+        l1 = self.topo.add_link(name='l1', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                                interfaces=[gpuw_shnic.interfaces['gpushnic_p1'], switch.interfaces['p1']],
+                                node_id='D17E4DB8-73AE-442A-A645-41EB8419E485')
+        l2 = self.topo.add_link(name='l2', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                                interfaces=[fnw_shnic.interfaces['fnshnic_p1'], switch.interfaces['p2']],
+                                node_id='3A1C21B5-6B18-466B-87D2-D5FEA0E54404')
+        l3 = self.topo.add_link(name='l3', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                                interfaces=[fnw_nic1.interfaces['fnnic1_p1'], switch.interfaces['p3']],
+                                node_id='27746574-2AC9-40E8-926D-DB27FFE17BDF')
+        l4 = self.topo.add_link(name='l4', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                                interfaces=[fnw_nic1.interfaces['fnnic1_p2'], switch.interfaces['p4']],
+                                node_id='DC647092-5E8F-4A63-BC10-D3D24F8F5C40')
+        l5 = self.topo.add_link(name='l5', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                                interfaces=[fnw_nic2.interfaces['fnnic2_p1'], switch.interfaces['p5']],
+                                node_id='4981B23C-063B-4335-BF7A-D597CE549757')
+        l6 = self.topo.add_link(name='l6', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                                interfaces=[fnw_nic2.interfaces['fnnic2_p2'], switch.interfaces['p6']],
+                                node_id='D9BE7849-15A1-4BE6-ACCE-B29A9608B0A4')
+        l8 = self.topo.add_link(name='l7', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                                interfaces=[snw_shnic.interfaces['snshnic_p1'], switch.interfaces['p7']],
+                                node_id='4EEF35D3-D549-457B-9C3E-9359B6194482')
+        l9 = self.topo.add_link(name='l8', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                                interfaces=[snw_nic1.interfaces['snnic1_p1'], switch.interfaces['p8']],
+                                node_id='AE47D890-72DF-413F-83C5-710E78CDAE22')
+        # shows different ways of addressing interfaces
+        l10 = self.topo.add_link(name='l9', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                                 interfaces=[self.topo.nodes['slownetworker'].interfaces['snnic1_p2'],
+                                             switch.interfaces['p9']],
+                                 node_id='7F3B02EB-ED52-442C-888A-FEF54BE8E296')
+        l11 = self.topo.add_link(name='l10', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                                 interfaces=[list(filter(lambda iff: (iff.name == 'snnic2_p1'),
+                                                         self.topo.interface_list))[0],
+                                             switch.interfaces['p10']],
+                                 node_id='4E767FC3-9428-46B1-8143-B52B74889261')
+        #
+        # delegations
+        #
+        # Capacity delegations go on network nodes (workers), components and interfaces.
+        # They are not going on switches, switch fabrics. They are typically not pooled.
+        # Label delegations and pools go on interfaces.
+        #
+        delegation1 = 'primary'
+
+        # define the pools for interfaces on the switch
+        pools = f.ARMPools(atype=f.DelegationType.LABEL)
+        # define two pools - one shared between shared NIC ports and one shared between all dataplane ports
+        shared_pool = f.Pool(atype=f.DelegationType.LABEL, pool_id='shared_pool', delegation_id=delegation1,
+                             defined_on=switch.interfaces['p1'].node_id,
+                             defined_for=[switch.interfaces['p1'].node_id,
+                                          switch.interfaces['p2'].node_id,
+                                          switch.interfaces['p7'].node_id])
+        shared_pool.set_pool_details_from_labels(f.Labels().set_fields(vlan_range='100-200',
+                                                                       ipv4_range='192.168.1.1-192.168.10.255'))
+        pools.add_pool(pool=shared_pool)
+        datanic_pool = f.Pool(atype=f.DelegationType.LABEL, pool_id='datanic_pool', delegation_id=delegation1,
+                              defined_on=switch.interfaces['p3'].node_id,
+                              defined_for=[switch.interfaces['p3'].node_id,
+                                           switch.interfaces['p4'].node_id,
+                                           switch.interfaces['p5'].node_id,
+                                           switch.interfaces['p6'].node_id,
+                                           switch.interfaces['p8'].node_id,
+                                           switch.interfaces['p9'].node_id,
+                                           switch.interfaces['p10'].node_id
+                                           ]
+                              )
+        datanic_pool.set_pool_details_from_labels(f.Labels().set_fields(vlan_range='1500-2000'))
+        pools.add_pool(pool=datanic_pool)
+        # have to reindex pools by delegation
+        pools.build_index_by_delegation_id()
+        pools.validate_pools()
+        self.topo.single_delegation(delegation_id=delegation1, label_pools=pools,
+                                    capacity_pools=f.ARMPools(atype=f.DelegationType.CAPACITY))
+
+        _, node_props = self.topo.graph_model.get_node_properties(node_id=switch.interfaces['p9'].node_id)
+        self.assertEqual(node_props[ABCPropertyGraph.PROP_LABEL_DELEGATIONS], '[{"pool": "datanic_pool"}]')
+        _, node_props = self.topo.graph_model.get_node_properties(node_id=switch.interfaces['p1'].node_id)
+        self.assertEqual(node_props[ABCPropertyGraph.PROP_LABEL_DELEGATIONS], '[{"ipv4_range": '
+                                                                              '"192.168.1.1-192.168.10.255", '
+                                                                              '"vlan_range": "100-200", '
+                                                                              '"label_pool": "shared_pool", '
+                                                                              '"delegation": "primary"}, '
+                                                                              '{"pool": "shared_pool"}]')
+        # print('\n\nALL NODES')
+        # for n in self.topo.nodes.values():
+        #     print(f'Node {n.name}:')
+        #     print(n)
+        #     for c in n.components.values():
+        #        print(f'Component {c.name}')
+        #        print(c)
+        #        for i in c.interfaces.values():
+        #            print(f'Interface {i.name}')
+        #            print(i)
+        #
+        # print('\n\nSwitch interfaces')
+        # for i in self.topo.nodes['dp_switch'].interfaces.values():
+        #     print(f'Interface {i.name}')
+        #     print(i)
+
+
+
diff --git a/test/testrenciad.py b/test/testrenciad.py
new file mode 100644
index 0000000..1d6b580
--- /dev/null
+++ b/test/testrenciad.py
@@ -0,0 +1,232 @@
+import fim.user as f
+
+
+if __name__ == "__main__":
+    # create a site advertisement
+    topo = f.SubstrateTopology()
+
+    site = 'RENC'
+    head_model = 'R7515'
+    worker_model = 'R7525'
+    hn_cap = f.Capacities()
+    hn_cap.set_fields(core=32, cpu=1, unit=1, ram=128, disk=100000)
+    network_worker_cap = f.Capacities()
+    # only internal storage
+    network_worker_cap.set_fields(core=32, cpu=2, unit=1, ram=512, disk=4800)
+    gpu_worker_cap = f.Capacities()
+    # has access to SAS NAS
+    gpu_worker_cap.set_fields(core=32, cpu=2, unit=1, ram=512, disk=100000)
+    hn = topo.add_node(name='headnode', model=head_model, site=site,
+                            node_id='702C4409-6635-4051-91A0-9C5A45CA28EC',
+                            ntype=f.NodeType.Server, capacities=hn_cap)
+    gpuw = topo.add_node(name='gpuworker', model=worker_model, site=site,
+                              node_id='8CA64353-182D-4CF2-8A07-2ADFF7F9C42A',
+                              ntype=f.NodeType.Server, capacities=gpu_worker_cap)
+    fnw = topo.add_node(name='fastnetworker', model=worker_model, site=site,
+                             node_id='B87900A2-5225-432A-92F8-60DD961597BE',
+                             ntype=f.NodeType.Server, capacities=network_worker_cap)
+    snw = topo.add_node(name='slownetworker', model=worker_model, site=site,
+                             node_id='169A2327-D6BC-4B9D-AE3A-3396CFF57138',
+                             ntype=f.NodeType.Server, capacities=network_worker_cap)
+
+    gpu_nvme1 = gpuw.add_component(name='gpunvme1', model='P4510',
+                                   node_id='55E8A23A-EAB9-41BF-BE9F-F42BB5920046',
+                                   ctype=f.ComponentType.NVME,
+                                   capacities=f.Capacities().set_fields(unit=1, disk=1000),
+                                   labels=f.Labels().set_fields(bdf='00:00:01'))
+    gpu_nvme2 = gpuw.add_component(name='gpunvme2', model='P4510',
+                                   node_id='7BD64A0E-DBE0-4BA1-89B0-CE9D8E6959B0',
+                                   ctype=f.ComponentType.NVME,
+                                   capacities=f.Capacities().set_fields(unit=1, disk=1000),
+                                   labels=f.Labels().set_fields(bdf='00:00:02'))
+
+    gpu_gpu1 = gpuw.add_component(name='gpugpu1', model='RTX6000',
+                                  node_id='49B5D670-64BD-4E90-8915-73CDACAB0EB5',
+                                  ctype=f.ComponentType.GPU,
+                                  capacities=f.Capacities().set_fields(unit=1),
+                                  labels=f.Labels().set_fields(bdf='00:01:0f'))
+    gpu_gpu2 = gpuw.add_component(name='gpugpu2', model='RTX6000',
+                                  node_id='EEC1F433-F102-4E68-A6DD-C20059C71B76',
+                                  ctype=f.ComponentType.GPU,
+                                  capacities=f.Capacities().set_fields(unit=1),
+                                  labels=f.Labels().set_fields(bdf='00:02:0f'))
+    gpuw_shnic = gpuw.add_component(name='gpushnic', model='ConnectX-6',
+                                    node_id='B6D4ED83-03C0-4B37-90AD-966769952F66',
+                                    switch_fabric_node_id='4E3B7A5E-5DC0-4D65-94B1-06B2E9519B08',
+                                    interface_node_ids=['0FD7CD42-00F9-45A1-9813-2E39AEF25243'],
+                                    capacities=f.Capacities().set_fields(unit=1),
+                                    labels=f.Labels().set_fields(bdf='00:03:0f'),
+                                    ctype=f.ComponentType.SharedNIC,
+                                    details='Shared NIC')
+    fnw_shnic = fnw.add_component(name='fnshnic', model='ConnectX-6',
+                                  node_id='E84F69AB-D9BB-4F6E-99CF-CC08BF26B50E',
+                                  switch_fabric_node_id='A5624045-C315-4FE5-A719-98E7891E7DBE',
+                                  interface_node_ids=['752D4D79-B91B-4657-B744-6EDBE331D539'],
+                                  ctype=f.ComponentType.SharedNIC,
+                                  capacities=f.Capacities().set_fields(unit=1),
+                                  labels=f.Labels().set_fields(bdf='00:01:0f'),
+                                  details='Dedicated SmartNIC')
+    fnw_nic1 = fnw.add_component(name='fnnic1', model='ConnectX-6',
+                                 node_id='631210E4-2064-4102-A6DB-1E6E33A7DA8A',
+                                 switch_fabric_node_id='2ED64BEB-DCB1-4E80-87EF-E46960A7444D',
+                                 interface_node_ids=['AEBC1418-37B8-438E-B752-E8C1304E7C33',
+                                                     'B6615811-6DDC-43CC-B012-A46E8A1A21E6'],
+                                 ctype=f.ComponentType.SmartNIC,
+                                 capacities=f.Capacities().set_fields(unit=1),
+                                 labels=f.Labels().set_fields(bdf='00:02:0f'),
+                                 details='Dedicated SmartNIC')
+    fnw_nic2 = fnw.add_component(name='fnnic2', model='ConnectX-6',
+                                 node_id='86F4EE34-6FD3-4CF5-A3BB-A2BA03884833',
+                                 switch_fabric_node_id='49766FAD-9813-4E0B-B254-75012AF3ABE2',
+                                 interface_node_ids=['E36A4783-D526-46E6-9833-5145C9D684CF',
+                                                     'E3BBD95E-CA6D-400C-A3BF-4FE1A196F50D'],
+                                 ctype=f.ComponentType.SmartNIC,
+                                 capacities=f.Capacities().set_fields(unit=1),
+                                 labels=f.Labels().set_fields(bdf='00:03:0f'),
+                                 details='Dedicated SmartNIC')
+    snw_shnic = snw.add_component(name='snshnic', model='ConnectX-6',
+                                  node_id='40372F80-3517-4C5A-82A7-14782A513001',
+                                  switch_fabric_node_id='64921F73-3719-4323-8B29-0A59329FA1E3',
+                                  interface_node_ids=['0DD43A8F-B84F-44C0-87AC-878D9C30CA16'],
+                                  ctype=f.ComponentType.SharedNIC,
+                                  capacities=f.Capacities().set_fields(unit=1),
+                                  labels=f.Labels().set_fields(bdf='00:04:0f'),
+                                  details='Shared NIC')
+    snw_nic1 = snw.add_component(name='snnic1', model='ConnectX-5',
+                                 node_id='054D0FF8-7D1F-403E-8900-87FDC6AF644A',
+                                 switch_fabric_node_id='72F4189E-507A-4B01-B39E-D0080EBCF9FC',
+                                 interface_node_ids=['5A104AFD-2C84-4E30-9BCE-59149531C310',
+                                                     '8E0722EB-AD3E-4858-BD2E-2607C6F52405'],
+                                 ctype=f.ComponentType.SmartNIC,
+                                 capacities=f.Capacities().set_fields(unit=1),
+                                 labels=f.Labels().set_fields(bdf='00:05:0f'),
+                                 details='Dedicated SmartNIC')
+    snw_nic2 = snw.add_component(name='snnic2', model='ConnectX-5',
+                                 node_id='D3AC3C42-8D0B-47C0-8420-D6D16A59DF10',
+                                 switch_fabric_node_id='23FB9D1D-4026-4C52-A32A-8ED73FBCC6AD',
+                                 interface_node_ids=['D1EEB715-69A3-4CC3-A90C-2F497B1358AD',
+                                                     '900D122A-080D-4D29-9186-24AF4D53361B'],
+                                 ctype=f.ComponentType.SmartNIC,
+                                 capacities=f.Capacities().set_fields(unit=1),
+                                 labels=f.Labels().set_fields(bdf='00:06:0f'),
+                                 details='Dedicated SmartNIC')
+    # NAS
+    nas_model = 'ME4084'
+    nas = topo.add_node(name='nas', model=nas_model, site=site, ntype=f.NodeType.NAS,
+                             node_id='D6787DF8-D2AE-4C3C-B8DE-577CE69C4AEC',
+                             capacities=f.Capacities().set_fields(unit=1, disk=100000))
+    # DP switch
+    switch_model = 'NCS500-24'
+    switch = topo.add_node(name='dp_switch', model=switch_model, site=site,
+                                node_id='8D1D8073-71D5-4B71-86CD-1A8D5272926F',
+                                capacities=f.Capacities().set_fields(unit=1),
+                                ntype=f.NodeType.Switch)
+    dp_sf = switch.add_switch_fabric(name='sp_sf', layer=f.Layer.L2,
+                                     node_id='DBA3C158-45B8-4DB0-B023-DAA52CE589FD')
+    # add ports
+    port_caps = f.Capacities()
+    port_caps.set_fields(bw=100)
+    port_labs = f.Labels()
+    port_labs.set_fields(vlan_range='1000-2000')
+    sp1 = dp_sf.add_interface(name='p1', itype=f.InterfaceType.TrunkPort,
+                              node_id='62FCEC64-9C21-489F-96A9-0A74702CFB6F',
+                              capacities=port_caps, labels=port_labs)
+    sp2 = dp_sf.add_interface(name='p2', itype=f.InterfaceType.TrunkPort,
+                              node_id='A8174EA2-BBD0-4C8B-ABC1-8615FC30C8E6',
+                              capacities=port_caps, labels=port_labs)
+    sp3 = dp_sf.add_interface(name='p3', itype=f.InterfaceType.TrunkPort,
+                              node_id='4B594B5F-2FA1-41FA-BF9E-7AB0EC970514',
+                              capacities=port_caps, labels=port_labs)
+    sp4 = dp_sf.add_interface(name='p4', itype=f.InterfaceType.TrunkPort,
+                              node_id='1A2DFA26-1F21-41E9-A97D-ABFB65BE5AF2',
+                              capacities=port_caps, labels=port_labs)
+    sp5 = dp_sf.add_interface(name='p5', itype=f.InterfaceType.TrunkPort,
+                              node_id='130FA579-BC0B-43BB-99AE-B7619955475B',
+                              capacities=port_caps, labels=port_labs)
+    sp6 = dp_sf.add_interface(name='p6', itype=f.InterfaceType.TrunkPort,
+                              node_id='9F906AA1-BC4A-40F3-8AE2-FCE8D00C54C0',
+                              capacities=port_caps, labels=port_labs)
+    sp7 = dp_sf.add_interface(name='p7', itype=f.InterfaceType.TrunkPort,
+                              node_id='0EDE076D-1979-4C85-B580-7A7563D7B71F',
+                              capacities=port_caps, labels=port_labs)
+    sp8 = dp_sf.add_interface(name='p8', itype=f.InterfaceType.TrunkPort,
+                              node_id='A286EAE5-3CEA-4D8F-BB1A-685957334917',
+                              capacities=port_caps, labels=port_labs)
+    sp9 = dp_sf.add_interface(name='p9', itype=f.InterfaceType.TrunkPort,
+                              node_id='0D2B98C0-80A1-47BB-B656-EDF4815E635C',
+                              capacities=port_caps, labels=port_labs)
+    sp10 = dp_sf.add_interface(name='p10', itype=f.InterfaceType.TrunkPort,
+                               node_id='71B46263-7723-45BC-A9EA-2AD090DC39AA',
+                               capacities=port_caps, labels=port_labs)
+
+    # add links
+    l1 = topo.add_link(name='l1', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                            interfaces=[gpuw_shnic.interfaces['gpushnic_p1'], switch.interfaces['p1']],
+                            node_id='D17E4DB8-73AE-442A-A645-41EB8419E485')
+    l2 = topo.add_link(name='l2', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                            interfaces=[fnw_shnic.interfaces['fnshnic_p1'], switch.interfaces['p2']],
+                            node_id='3A1C21B5-6B18-466B-87D2-D5FEA0E54404')
+    l3 = topo.add_link(name='l3', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                            interfaces=[fnw_nic1.interfaces['fnnic1_p1'], switch.interfaces['p3']],
+                            node_id='27746574-2AC9-40E8-926D-DB27FFE17BDF')
+    l4 = topo.add_link(name='l4', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                            interfaces=[fnw_nic1.interfaces['fnnic1_p2'], switch.interfaces['p4']],
+                            node_id='DC647092-5E8F-4A63-BC10-D3D24F8F5C40')
+    l5 = topo.add_link(name='l5', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                            interfaces=[fnw_nic2.interfaces['fnnic2_p1'], switch.interfaces['p5']],
+                            node_id='4981B23C-063B-4335-BF7A-D597CE549757')
+    l6 = topo.add_link(name='l6', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                            interfaces=[fnw_nic2.interfaces['fnnic2_p2'], switch.interfaces['p6']],
+                            node_id='D9BE7849-15A1-4BE6-ACCE-B29A9608B0A4')
+    l8 = topo.add_link(name='l7', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                            interfaces=[snw_shnic.interfaces['snshnic_p1'], switch.interfaces['p7']],
+                            node_id='4EEF35D3-D549-457B-9C3E-9359B6194482')
+    l9 = topo.add_link(name='l8', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                            interfaces=[snw_nic1.interfaces['snnic1_p1'], switch.interfaces['p8']],
+                            node_id='AE47D890-72DF-413F-83C5-710E78CDAE22')
+    # shows different ways of addressing interfaces
+    l10 = topo.add_link(name='l9', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                             interfaces=[topo.nodes['slownetworker']._interfaces['snnic1_p2'],
+                                         switch.interfaces['p9']],
+                             node_id='7F3B02EB-ED52-442C-888A-FEF54BE8E296')
+    l11 = topo.add_link(name='l10', ltype=f.LinkType.DAC, layer=f.Layer.L2,
+                             interfaces=[topo.interfaces['snnic2_p1'], switch.interfaces['p10']],
+                             node_id='4E767FC3-9428-46B1-8143-B52B74889261')
+    #
+    # delegations
+    #
+    # Capacity delegations go on network nodes (workers), components and interfaces.
+    # They are not going on switches, switch fabrics. They are typically not pooled.
+    # Label delegations and pools go on interfaces.
+    #
+    delegation1 = 'primary'
+
+    # define the pools for interfaces on the switch
+    pools = f.ARMPools(atype=f.DelegationType.LABEL)
+    # define two pools - one shared between shared NIC ports and one shared between all dataplane ports
+    shared_pool = f.Pool(atype=f.DelegationType.LABEL, pool_id='shared_pool', delegation_id=delegation1,
+                         defined_on=switch.interfaces['p1'].node_id,
+                         defined_for=[switch.interfaces['p1'].node_id,
+                                      switch.interfaces['p2'].node_id,
+                                      switch.interfaces['p7'].node_id])
+    shared_pool.set_pool_details_from_labels(f.Labels().set_fields(vlan_range='100-200',
+                                                                   ipv4_range='192.168.1.1-192.168.10.255'))
+    pools.add_pool(pool=shared_pool)
+    datanic_pool = f.Pool(atype=f.DelegationType.LABEL, pool_id='datanic_pool', delegation_id=delegation1,
+                          defined_on=switch.interfaces['p3'].node_id,
+                          defined_for=[switch.interfaces['p3'].node_id,
+                                       switch.interfaces['p4'].node_id,
+                                       switch.interfaces['p5'].node_id,
+                                       switch.interfaces['p6'].node_id,
+                                       switch.interfaces['p8'].node_id,
+                                       switch.interfaces['p9'].node_id,
+                                       switch.interfaces['p10'].node_id
+                                       ]
+                          )
+    datanic_pool.set_pool_details_from_labels(f.Labels().set_fields(vlan_range='1500-2000'))
+    pools.add_pool(pool=datanic_pool)
+    # have to reindex pools by delegation
+    pools.build_index_by_delegation_id()
+    pools.validate_pools()
+    topo.single_delegation(delegation_id=delegation1, label_pools=pools,
+                           capacity_pools=f.ARMPools(atype=f.DelegationType.CAPACITY))
\ No newline at end of file
diff --git a/test/tuple_test.py b/test/tuple_test.py
index df21c2b..1457e7f 100644
--- a/test/tuple_test.py
+++ b/test/tuple_test.py
@@ -1,7 +1,7 @@
 import unittest
 
 from fim.graph.typed_tuples import Label, Capacity, Location
-from fim.slivers.delegations import Pool, Pools, DelegationType
+from fim.slivers.delegations import Pool, ARMPools, DelegationType
 
 class TupleTests(unittest.TestCase):
 
@@ -53,7 +53,7 @@ def testPools(self):
         p.add_defined_for(node_ids=["node2", "node3", "node1"])
         p.set_pool_details(pool_dict={"blah": "blahblah"})
 
-        pp = Pools(atype=DelegationType.LABEL)
+        pp = ARMPools(atype=DelegationType.LABEL)
         pp.add_pool(pool=p)
         pp.build_index_by_delegation_id()
         p1l = pp.get_pools_by_delegation_id(delegation_id="del1")