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

Issue:
Pass MAC addresses and PCI addresses to connection points in component catalog
Pass them from the parent 
Add code to set up indexes programmatically. 
Provide programmatic entry point to set up indexes in an idempotent manner.
Pass MAC addresses and PCI addresses to connection points in component catalog
Pass them from the parent 
Adding ability to export NetworkX graphs in JSON and Cytoscape
Implemented exports to JSON node-link and Cytoscape. Added test code 
Pass MAC addresses and PCI addresses to connection points in component catalog
Pass them from the parent 


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 19483cd..8a005bd 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,10 @@ Control Framework, and generally are not exposed to end-users.
 
 ## Structure of the code
 
+This figure reflects the overall structure of the code:
+
+![Code structure](figs/fim-structure.png)
+
 Follow this link into [fim/README.md](fim/README.md) to explore.
 
 ## Development environment
diff --git a/figs/fim-structure.png b/figs/fim-structure.png
new file mode 100644
index 0000000..47dafa9
Binary files /dev/null and b/figs/fim-structure.png differ
diff --git a/fim/graph/abc_property_graph.py b/fim/graph/abc_property_graph.py
index e7df9d1..336cb29 100644
--- a/fim/graph/abc_property_graph.py
+++ b/fim/graph/abc_property_graph.py
@@ -33,12 +33,13 @@
 import networkx as nx
 from abc import ABC, abstractmethod
 from typing import List, Dict, Any, Set, Tuple
+from enum import Enum
 
 import logging
 
 from fim.slivers.attached_components import ComponentSliver, AttachedComponentsInfo
-from fim.slivers.capacities_labels import Capacities, Labels, ReservationInfo
-from fim.slivers.delegations import Delegation
+from fim.slivers.capacities_labels import Capacities, Labels, ReservationInfo, StructuralInfo
+from fim.slivers.delegations import Delegations, DelegationType
 from fim.slivers.interface_info import InterfaceSliver, InterfaceInfo
 from fim.slivers.base_sliver import BaseSliver
 from fim.slivers.network_node import NodeSliver
@@ -47,6 +48,14 @@
 from fim.graph.abc_property_graph_constants import ABCPropertyGraphConstants
 
 
+class GraphFormat(Enum):
+    # default, works in Neo4j and NetworkX
+    GRAPHML = 1
+    # JSON formats are specific to NetworkX
+    JSON_NODELINK = 2
+    CYTOSCAPE = 3
+
+
 class ABCPropertyGraph(ABCPropertyGraphConstants):
     """
     Abstract Base class representing operations on a property graph of resources.
@@ -76,7 +85,8 @@ class ABCPropertyGraph(ABCPropertyGraphConstants):
         "layer": ABCPropertyGraphConstants.PROP_LAYER,
         "technology": ABCPropertyGraphConstants.PROP_TECHNOLOGY,
         "model": ABCPropertyGraphConstants.PROP_MODEL,
-        "node_map": ABCPropertyGraphConstants.PROP_NODE_MAP
+        "node_map": ABCPropertyGraphConstants.PROP_NODE_MAP,
+        "structural_info": ABCPropertyGraphConstants.PROP_STRUCTURAL_INFO
     }
 
     @abstractmethod
@@ -135,7 +145,6 @@ def get_link_properties(self, *, node_a: str, node_b: str) -> (str, Dict[str, An
         return a tuple of link kind and all properties of a link between two nodes node_a and node_b
         :param node_a:
         :param node_b:
-        :param kind: kind of link/edge
         :return: (str, dictionary[string, Any])
         """
 
@@ -231,9 +240,10 @@ def get_all_nodes_by_class(self, *, label: str) -> List[str]:
         """
 
     @abstractmethod
-    def serialize_graph(self) -> str:
+    def serialize_graph(self, format: GraphFormat = GraphFormat.GRAPHML) -> str:
         """
         Serialize a given graph into a string
+        :param format: indicate format (defaults to GraphML)
         :return:
         """
 
@@ -246,7 +256,8 @@ def clone_graph(self, *, new_graph_id: str):
         assert new_graph_id is not None
         graph_string = self.serialize_graph()
         if graph_string is None:
-            raise PropertyGraphQueryException(msg=f"Unable to find graph with id {self.graph_id} for cloning")
+            raise PropertyGraphQueryException(graph_id=self.graph_id, node_id=None,
+                                              msg=f"Unable to find graph with id {self.graph_id} for cloning")
         return self.importer.import_graph_from_string(graph_string=graph_string,
                                                       graph_id=new_graph_id)
 
@@ -360,7 +371,7 @@ def _validate_json_property(self, node_id: str, props: Dict, strict: bool = Fals
         a JSONDecodeError exception. Strict set to true causes exception if property is not found.
         Default strict is set to False. If property is not a valid JSON it's value is returned.
         :param node_id:
-        :param prop_name:
+        :param props:
         :param strict
         :return:
         """
@@ -430,20 +441,24 @@ def base_sliver_to_graph_properties_dict(sliver: BaseSliver) -> Dict[str, str]:
             prop_dict[ABCPropertyGraph.PROP_LABELS] = sliver.labels.to_json()
         if sliver.capacity_delegations is not None:
             prop_dict[ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS] = \
-                Delegation.from_sliver_field_to_json(sliver.capacity_delegations)
+                sliver.capacity_delegations.to_json()
         if sliver.label_delegations is not None:
             prop_dict[ABCPropertyGraph.PROP_LABEL_DELEGATIONS] = \
-                Delegation.from_sliver_field_to_json(sliver.label_delegations)
+                sliver.label_delegations.to_json()
         if sliver.capacity_allocations is not None:
             prop_dict[ABCPropertyGraph.PROP_CAPACITY_ALLOCATIONS] = sliver.capacity_allocations.to_json()
         if sliver.label_allocations is not None:
             prop_dict[ABCPropertyGraph.PROP_LABEL_ALLOCATIONS] = sliver.label_allocations.to_json()
         if sliver.reservation_info is not None:
             prop_dict[ABCPropertyGraph.PROP_RESERVATION_INFO] = sliver.reservation_info.to_json()
+        if sliver.structural_info is not None:
+            prop_dict[ABCPropertyGraph.PROP_STRUCTURAL_INFO] = sliver.structural_info.to_json()
         if sliver.details is not None:
             prop_dict[ABCPropertyGraph.PROP_DETAILS] = sliver.details
         if sliver.node_map is not None:
             prop_dict[ABCPropertyGraph.PROP_NODE_MAP] = json.dumps(sliver.node_map)
+        # boolean is always there. use json dumps for simplicity
+        prop_dict[ABCPropertyGraph.PROP_STITCH_NODE] = json.dumps(sliver.stitch_node)
 
         return prop_dict
 
@@ -531,18 +546,19 @@ def node_sliver_from_graph_properties_dict(d: Dict[str, str]) -> NodeSliver:
         n.set_properties(name=d.get(ABCPropertyGraph.PROP_NAME, None),
                          type=n.type_from_str(d.get(ABCPropertyGraph.PROP_TYPE, None)),
                          model=d.get(ABCPropertyGraphConstants.PROP_MODEL, None),
-                         capacities=Capacities().from_json(d.get(ABCPropertyGraph.PROP_CAPACITIES, None)),
-                         labels=Labels().from_json(d.get(ABCPropertyGraph.PROP_LABELS, None)),
-                         capacity_allocations=Capacities().from_json(d.get(ABCPropertyGraph.PROP_CAPACITY_ALLOCATIONS,
+                         capacities=Capacities.from_json(d.get(ABCPropertyGraph.PROP_CAPACITIES, None)),
+                         labels=Labels.from_json(d.get(ABCPropertyGraph.PROP_LABELS, None)),
+                         capacity_allocations=Capacities.from_json(d.get(ABCPropertyGraph.PROP_CAPACITY_ALLOCATIONS,
                                                                            None)),
-                         label_allocations=Labels().from_json(d.get(ABCPropertyGraph.PROP_LABEL_ALLOCATIONS,
+                         label_allocations=Labels.from_json(d.get(ABCPropertyGraph.PROP_LABEL_ALLOCATIONS,
                                                                     None)),
-                         reservation_info=ReservationInfo().from_json(d.get(ABCPropertyGraph.PROP_RESERVATION_INFO,
+                         reservation_info=ReservationInfo.from_json(d.get(ABCPropertyGraph.PROP_RESERVATION_INFO,
                                                                             None)),
-                         capacity_delegations=Delegation.from_json_to_sliver_field(
-                             d.get(ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS, None)),
-                         label_delegations=Delegation.from_json_to_sliver_field(
-                             d.get(ABCPropertyGraph.PROP_LABEL_DELEGATIONS, None)),
+                         structural_info=StructuralInfo.from_json(d.get(ABCPropertyGraph.PROP_STRUCTURAL_INFO, None)),
+                         capacity_delegations=Delegations.from_json(atype=DelegationType.CAPACITY,
+                             json_str=d.get(ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS, None)),
+                         label_delegations=Delegations.from_json(atype=DelegationType.LABEL,
+                             json_str=d.get(ABCPropertyGraph.PROP_LABEL_DELEGATIONS, None)),
                          site=d.get(ABCPropertyGraph.PROP_SITE, None),
                          image_ref=image_ref,
                          image_type=image_type,
@@ -551,7 +567,8 @@ def node_sliver_from_graph_properties_dict(d: Dict[str, str]) -> NodeSliver:
                          service_endpoint=d.get(ABCPropertyGraph.PROP_SERVICE_ENDPOINT, None),
                          details=d.get(ABCPropertyGraph.PROP_DETAILS, None),
                          node_map=json.loads(d[ABCPropertyGraph.PROP_NODE_MAP]) if
-                         d.get(ABCPropertyGraph.PROP_NODE_MAP, None) is not None else None
+                         d.get(ABCPropertyGraph.PROP_NODE_MAP, None) is not None else None,
+                         stitch_node=json.loads(d[ABCPropertyGraph.PROP_STITCH_NODE])
                          )
         return n
 
@@ -561,24 +578,26 @@ def link_sliver_from_graph_properties_dict(d: Dict[str, str]) -> NetworkLinkSliv
         n.set_properties(name=d.get(ABCPropertyGraph.PROP_NAME, None),
                          type=n.type_from_str(d.get(ABCPropertyGraph.PROP_TYPE, None)),
                          model=d.get(ABCPropertyGraphConstants.PROP_MODEL, None),
-                         capacities=Capacities().from_json(d.get(ABCPropertyGraph.PROP_CAPACITIES, None)),
-                         labels=Labels().from_json(d.get(ABCPropertyGraph.PROP_LABELS, None)),
-                         capacity_allocations=Capacities().from_json(d.get(ABCPropertyGraph.PROP_CAPACITY_ALLOCATIONS,
+                         capacities=Capacities.from_json(d.get(ABCPropertyGraph.PROP_CAPACITIES, None)),
+                         labels=Labels.from_json(d.get(ABCPropertyGraph.PROP_LABELS, None)),
+                         capacity_allocations=Capacities.from_json(d.get(ABCPropertyGraph.PROP_CAPACITY_ALLOCATIONS,
                                                                            None)),
-                         label_allocations=Labels().from_json(d.get(ABCPropertyGraph.PROP_LABEL_ALLOCATIONS,
+                         label_allocations=Labels.from_json(d.get(ABCPropertyGraph.PROP_LABEL_ALLOCATIONS,
                                                                     None)),
-                         reservation_info=ReservationInfo().from_json(d.get(ABCPropertyGraph.PROP_RESERVATION_INFO,
+                         reservation_info=ReservationInfo.from_json(d.get(ABCPropertyGraph.PROP_RESERVATION_INFO,
                                                                             None)),
-                         capacity_delegations=Delegation.from_json_to_sliver_field(
-                             d.get(ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS, None)),
-                         label_delegations=Delegation.from_json_to_sliver_field(
-                             d.get(ABCPropertyGraph.PROP_LABEL_DELEGATIONS, None)),
+                         structural_info=StructuralInfo.from_json(d.get(ABCPropertyGraph.PROP_STRUCTURAL_INFO, None)),
+                         capacity_delegations=Delegations.from_json(atype=DelegationType.CAPACITY,
+                             json_str=d.get(ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS, None)),
+                         label_delegations=Delegations.from_json(atype=DelegationType.LABEL,
+                             json_str=d.get(ABCPropertyGraph.PROP_LABEL_DELEGATIONS, None)),
                          allocation_constraints=d.get(ABCPropertyGraph.PROP_ALLOCATION_CONSTRAINTS, None),
                          details=d.get(ABCPropertyGraph.PROP_DETAILS, None),
                          layer=d.get(ABCPropertyGraph.PROP_LAYER),
                          technology=d.get(ABCPropertyGraph.PROP_TECHNOLOGY),
                          node_map=json.loads(d[ABCPropertyGraph.PROP_NODE_MAP]) if
-                         d.get(ABCPropertyGraph.PROP_NODE_MAP, None) is not None else None
+                         d.get(ABCPropertyGraph.PROP_NODE_MAP, None) is not None else None,
+                         stitch_node=json.loads(d[ABCPropertyGraph.PROP_STITCH_NODE])
                          )
         return n
 
@@ -593,19 +612,20 @@ def component_sliver_from_graph_properties_dict(d: Dict[str, str]) -> ComponentS
         cs.set_properties(name=d.get(ABCPropertyGraph.PROP_NAME, None),
                           type=cs.type_from_str(d.get(ABCPropertyGraph.PROP_TYPE, None)),
                           model=d.get(ABCPropertyGraphConstants.PROP_MODEL, 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_sliver_field(
-                              d.get(ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS, None)),
-                          label_delegations=Delegation.from_json_to_sliver_field(
-                              d.get(ABCPropertyGraph.PROP_LABEL_DELEGATIONS, None)),
-                          capacity_allocations=Capacities().from_json(d.get(ABCPropertyGraph.PROP_CAPACITY_ALLOCATIONS,
+                          capacities=Capacities.from_json(d.get(ABCPropertyGraph.PROP_CAPACITIES, None)),
+                          labels=Labels.from_json(d.get(ABCPropertyGraph.PROP_LABELS, None)),
+                          capacity_delegations=Delegations.from_json(atype=DelegationType.CAPACITY,
+                              json_str=d.get(ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS, None)),
+                          label_delegations=Delegations.from_json(atype=DelegationType.LABEL,
+                              json_str=d.get(ABCPropertyGraph.PROP_LABEL_DELEGATIONS, None)),
+                          capacity_allocations=Capacities.from_json(d.get(ABCPropertyGraph.PROP_CAPACITY_ALLOCATIONS,
                                                                             None)),
-                          label_allocations=Labels().from_json(d.get(ABCPropertyGraph.PROP_LABEL_ALLOCATIONS,
+                          label_allocations=Labels.from_json(d.get(ABCPropertyGraph.PROP_LABEL_ALLOCATIONS,
                                                                      None)),
                           details=d.get(ABCPropertyGraph.PROP_DETAILS, None),
                           node_map=json.loads(d[ABCPropertyGraph.PROP_NODE_MAP]) if
-                          d.get(ABCPropertyGraph.PROP_NODE_MAP, None) is not None else None
+                          d.get(ABCPropertyGraph.PROP_NODE_MAP, None) is not None else None,
+                          stitch_node=json.loads(d[ABCPropertyGraph.PROP_STITCH_NODE])
                           )
         return cs
 
@@ -620,20 +640,21 @@ def switch_fabric_sliver_from_graph_properties_dict(d: Dict[str, str]) -> Switch
         sf.set_properties(name=d.get(ABCPropertyGraph.PROP_NAME, None),
                           type=sf.type_from_str(d.get(ABCPropertyGraph.PROP_TYPE, None)),
                           model=d.get(ABCPropertyGraphConstants.PROP_MODEL, 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_sliver_field(
-                              d.get(ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS, None)),
-                          label_delegations=Delegation.from_json_to_sliver_field(
-                              d.get(ABCPropertyGraph.PROP_LABEL_DELEGATIONS, None)),
-                          capacity_allocations=Capacities().from_json(d.get(ABCPropertyGraph.PROP_CAPACITY_ALLOCATIONS,
+                          capacities=Capacities.from_json(d.get(ABCPropertyGraph.PROP_CAPACITIES, None)),
+                          labels=Labels.from_json(d.get(ABCPropertyGraph.PROP_LABELS, None)),
+                          capacity_delegations=Delegations.from_json(atype=DelegationType.CAPACITY,
+                              json_str=d.get(ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS, None)),
+                          label_delegations=Delegations.from_json(atype=DelegationType.LABEL,
+                              json_str=d.get(ABCPropertyGraph.PROP_LABEL_DELEGATIONS, None)),
+                          capacity_allocations=Capacities.from_json(d.get(ABCPropertyGraph.PROP_CAPACITY_ALLOCATIONS,
                                                                             None)),
-                          label_allocations=Labels().from_json(d.get(ABCPropertyGraph.PROP_LABEL_ALLOCATIONS,
+                          label_allocations=Labels.from_json(d.get(ABCPropertyGraph.PROP_LABEL_ALLOCATIONS,
                                                                      None)),
                           layer=sf.layer_from_str(d.get(ABCPropertyGraph.PROP_LAYER, None)),
                           details=d.get(ABCPropertyGraph.PROP_DETAILS, None),
                           node_map=json.loads(d[ABCPropertyGraph.PROP_NODE_MAP]) if
-                          d.get(ABCPropertyGraph.PROP_NODE_MAP, None) is not None else None
+                          d.get(ABCPropertyGraph.PROP_NODE_MAP, None) is not None else None,
+                          stitch_node=json.loads(d[ABCPropertyGraph.PROP_STITCH_NODE])
                           )
         return sf
 
@@ -648,19 +669,20 @@ def interface_sliver_from_graph_properties_dict(d: Dict[str, str]) -> InterfaceS
         isl.set_properties(name=d.get(ABCPropertyGraph.PROP_NAME, None),
                            type=isl.type_from_str(d.get(ABCPropertyGraph.PROP_TYPE, None)),
                            model=d.get(ABCPropertyGraphConstants.PROP_MODEL, 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_sliver_field(
-                               d.get(ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS, None)),
-                           label_delegations=Delegation.from_json_to_sliver_field(
-                               d.get(ABCPropertyGraph.PROP_LABEL_DELEGATIONS, None)),
-                           capacity_allocations=Capacities().from_json(d.get(ABCPropertyGraph.PROP_CAPACITY_ALLOCATIONS,
+                           capacities=Capacities.from_json(d.get(ABCPropertyGraph.PROP_CAPACITIES, None)),
+                           labels=Labels.from_json(d.get(ABCPropertyGraph.PROP_LABELS, None)),
+                           capacity_delegations=Delegations.from_json(atype=DelegationType.CAPACITY,
+                               json_str=d.get(ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS, None)),
+                           label_delegations=Delegations.from_json(atype=DelegationType.LABEL,
+                               json_str=d.get(ABCPropertyGraph.PROP_LABEL_DELEGATIONS, None)),
+                           capacity_allocations=Capacities.from_json(d.get(ABCPropertyGraph.PROP_CAPACITY_ALLOCATIONS,
                                                                              None)),
-                           label_allocations=Labels().from_json(d.get(ABCPropertyGraph.PROP_LABEL_ALLOCATIONS,
+                           label_allocations=Labels.from_json(d.get(ABCPropertyGraph.PROP_LABEL_ALLOCATIONS,
                                                                       None)),
                            details=d.get(ABCPropertyGraph.PROP_DETAILS, None),
                            node_map=json.loads(d[ABCPropertyGraph.PROP_NODE_MAP]) if
-                           d.get(ABCPropertyGraph.PROP_NODE_MAP, None) is not None else None
+                           d.get(ABCPropertyGraph.PROP_NODE_MAP, None) is not None else None,
+                           stitch_node=json.loads(d[ABCPropertyGraph.PROP_STITCH_NODE])
                            )
         return isl
 
@@ -776,6 +798,14 @@ def get_all_link_interfaces(self, link_id: str) -> List[str]:
         return self.get_first_neighbor(node_id=link_id, rel=ABCPropertyGraph.REL_CONNECTS,
                                        node_label=ABCPropertyGraph.CLASS_ConnectionPoint)
 
+    @abstractmethod
+    def get_stitch_nodes(self) -> List[str]:
+        """
+        Find out and return a list of nodes with StitchNode property
+        set to 'true' (in JSON).
+        :return:
+        """
+
 
 class ABCGraphImporter(ABC):
     @abstractmethod
diff --git a/fim/graph/abc_property_graph_constants.py b/fim/graph/abc_property_graph_constants.py
index 90c3161..ea7d6a8 100644
--- a/fim/graph/abc_property_graph_constants.py
+++ b/fim/graph/abc_property_graph_constants.py
@@ -32,10 +32,11 @@ class ABCPropertyGraphConstants(ABC):
     # NetworkX just has python None
     NEO4j_NONE = "None"
 
+    SINGLE_POOL_NAME = "_"
     FIELD_POOL = "pool"
-    FIELD_CAPACITY_POOL = "capacity_pool"
-    FIELD_LABEL_POOL = "label_pool"
-    FIELD_DELEGATION = "delegation"
+    FIELD_POOL_ID = "pool_id"
+    FIELD_CAPACITIES = "capacities"
+    FIELD_LABELS = "labels"
     PROP_CAPACITY_DELEGATIONS = "CapacityDelegations"
     PROP_LABEL_DELEGATIONS = "LabelDelegations"
     PROP_CAPACITIES = "Capacities"
@@ -58,10 +59,12 @@ class ABCPropertyGraphConstants(ABC):
     PROP_DETAILS = 'Details'
     PROP_RESERVATION_INFO = 'ReservationInfo'
     PROP_NODE_MAP = "NodeMap"
+    PROP_STRUCTURAL_INFO = "StructuralInfo"
+    PROP_STITCH_NODE = "StitchNode"
     # these properties get validated to be valid JSON objects whenever someone validates the graph
     JSON_PROPERTY_NAMES = [PROP_LABELS, PROP_CAPACITIES, PROP_LABEL_DELEGATIONS,
                            PROP_CAPACITY_DELEGATIONS, PROP_LABEL_ALLOCATIONS,
-                           PROP_CAPACITY_ALLOCATIONS, PROP_RESERVATION_INFO]
+                           PROP_CAPACITY_ALLOCATIONS, PROP_RESERVATION_INFO, PROP_STRUCTURAL_INFO]
     # these properties cannot be unset
     NO_UNSET_PROPERTIES = [GRAPH_ID, NODE_ID, PROP_TYPE, PROP_CLASS]
 
diff --git a/fim/graph/data/neo4j_indexes.json b/fim/graph/data/neo4j_indexes.json
new file mode 100644
index 0000000..02fd65c
--- /dev/null
+++ b/fim/graph/data/neo4j_indexes.json
@@ -0,0 +1,6 @@
+{
+  "graphid": "CREATE INDEX graphid FOR (n:GraphNode) ON (n.GraphID)",
+  "graphid_nodeid": "CREATE INDEX graphid_nodeid FOR (n:GraphNode) ON (n.GraphID, n.NodeID)",
+  "graphid_node_id_type": "CREATE INDEX graphid_nodeid_type FOR (n:GraphNode) ON (n.GraphID, n.NodeID, n.Type)",
+  "graphid_type": "CREATE INDEX graphid_type FOR (n:GraphNode) ON (n.GraphID, n.Type)"
+}
\ No newline at end of file
diff --git a/fim/graph/neo4j_property_graph.py b/fim/graph/neo4j_property_graph.py
index 57cabbb..49c82d3 100644
--- a/fim/graph/neo4j_property_graph.py
+++ b/fim/graph/neo4j_property_graph.py
@@ -39,7 +39,7 @@
 from neo4j import GraphDatabase
 
 from .abc_property_graph import ABCPropertyGraph, PropertyGraphImportException, \
-    PropertyGraphQueryException, ABCGraphImporter
+    PropertyGraphQueryException, ABCGraphImporter, GraphFormat
 
 # to deal with intermittent APOC problems on MAC
 APOC_RETRY_COUNT = 10
@@ -322,11 +322,14 @@ def update_link_properties(self, *, node_a: str, node_b: str, kind: str,
                                                   node_b=node_b, kind=kind,
                                                   msg="Unable to set properties on a link")
 
-    def serialize_graph(self) -> str:
+    def serialize_graph(self, format: GraphFormat = GraphFormat.GRAPHML) -> str:
         """
         Serialize a given graph into GraphML string or return None if graph not found
         :return:
         """
+        if format != GraphFormat.GRAPHML:
+            PropertyGraphQueryException(graph_id=self.graph_id, node_id=None,
+                                        msg=f"Unsupported export graph format {format.name}")
         inner_query = f'match(n:GraphNode {{GraphID: "{self.graph_id}"}}) optional match(n) -[r]- (m) return n, r, m'
         # run inner query to check the graph has anything in it
         with self.driver.session() as session:
@@ -568,9 +571,20 @@ def merge_nodes(self, node_id: str, other_graph, merge_properties: Dict = None):
                         graphId1=other_graph.graph_id,
                         nodeId=node_id).single()
 
+    def get_stitch_nodes(self) -> List[str]:
+        query = f"MATCH (n:GraphNode {{GraphID: $graphId, StitchNode: 'true'}}) RETURN collect(n.NodeID) as nodeids"
+        with self.driver.session() as session:
+            val = session.run(query, graphId=self.graph_id).single()
+            if val is None:
+                raise PropertyGraphQueryException(graph_id=self.graph_id,
+                                                  node_id=None, msg="Unable to find stitch nodes")
+            return val.data()['nodeids']
+
 
 class Neo4jGraphImporter(ABCGraphImporter):
 
+    index_initialized = False
+
     def __init__(self, *, url: str, user: str, pswd: str, import_host_dir: str, import_dir: str, logger=None):
         """
         URL of Neo4j instance, credentials and directory
@@ -596,6 +610,28 @@ def __init__(self, *, url: str, user: str, pswd: str, import_host_dir: str, impo
             self.log = logging.getLogger(__name__)
         else:
             self.log = logger
+        self._add_indexes()
+
+    def _add_indexes(self):
+        """
+        Add required indexes in idempotent manner
+        :return:
+        """
+        if Neo4jGraphImporter.index_initialized:
+            return
+        Neo4jGraphImporter.index_initialized = True
+        index_file = os.path.join(os.path.dirname(__file__), 'data', 'neo4j_indexes.json')
+        f = open(index_file)
+        index_dict = json.load(f)
+        f.close()
+        self.log.info('Adding Neo4j indexes')
+        for index_name, index_cmd in index_dict.items():
+            with self.driver.session() as session:
+                try:
+                    session.run(index_cmd)
+                except:
+                    # ignore exceptions
+                    pass
 
     def _prep_graph(self, graph: str, graph_id: str = None) -> Tuple[str, str, str]:
         """
diff --git a/fim/graph/networkx_property_graph.py b/fim/graph/networkx_property_graph.py
index 4f189c3..2faf28e 100644
--- a/fim/graph/networkx_property_graph.py
+++ b/fim/graph/networkx_property_graph.py
@@ -36,7 +36,7 @@
 import networkx_query as nxq
 
 from .abc_property_graph import ABCPropertyGraph, PropertyGraphImportException, \
-    PropertyGraphQueryException, ABCGraphImporter
+    PropertyGraphQueryException, ABCGraphImporter, GraphFormat
 from .networkx_mixin import NetworkXMixin
 
 
@@ -342,7 +342,7 @@ def clone_graph(self, *, new_graph_id: str):
         self.storage.add_graph(new_graph_id, new_graph)
         return NetworkXPropertyGraph(graph_id=new_graph_id, importer=self.importer, logger=self.log)
 
-    def serialize_graph(self) -> str:
+    def serialize_graph(self, format: GraphFormat = GraphFormat.GRAPHML) -> str:
         """
         Serialize a given graph into GraphML string or return None
         if graph is not found
@@ -351,7 +351,17 @@ def serialize_graph(self) -> str:
         graph = self.storage.extract_graph(self.graph_id)
         graph_string = None
         if graph is not None:
-            graph_string = '\n'.join(nx.generate_graphml(graph))
+            if format == GraphFormat.GRAPHML:
+                graph_string = '\n'.join(nx.generate_graphml(graph))
+            elif format == GraphFormat.JSON_NODELINK:
+                json_object = nx.readwrite.node_link_data(graph)
+                graph_string = json.dumps(json_object)
+            elif format == GraphFormat.CYTOSCAPE:
+                json_object = nx.readwrite.cytoscape_data(graph)
+                graph_string = json.dumps(json_object)
+            else:
+                raise PropertyGraphQueryException(graph_id=self.graph_id, node_id=None,
+                                                  msg=f"Unsupported export graph format {format.name}")
         return graph_string
 
     def graph_exists(self) -> bool:
@@ -607,6 +617,19 @@ def merge_nodes(self, node_id: str, other_graph, merge_properties=None):
                     new_props[k] = node_props[k]
         self.storage.get_graph(self.graph_id).nodes[real_node].update(new_props)
 
+    def get_stitch_nodes(self) -> List[str]:
+        my_graph = self.storage.get_graph(self.graph_id)
+        graph_nodes = list(nxq.search_nodes(my_graph,
+                                            {'and': [
+                                                {'eq': [ABCPropertyGraph.GRAPH_ID, self.graph_id]},
+                                                {'eq': [ABCPropertyGraph.PROP_STITCH_NODE, 'true']}
+                                            ]
+                                            }))
+        ret = list()
+        for n in graph_nodes:
+            ret.append(my_graph.nodes[n][ABCPropertyGraph.NODE_ID])
+        return ret
+
 
 class NetworkXGraphStorage:
     """
diff --git a/fim/graph/resources/abc_adm.py b/fim/graph/resources/abc_adm.py
index ba8cbd7..4afd1f7 100644
--- a/fim/graph/resources/abc_adm.py
+++ b/fim/graph/resources/abc_adm.py
@@ -31,8 +31,8 @@
 from collections import defaultdict
 
 from abc import ABCMeta, abstractmethod
-from ..abc_property_graph import ABCPropertyGraph
-from ...slivers.delegations import ARMDelegations, ARMPools, DelegationType
+from ..abc_property_graph import ABCPropertyGraph, PropertyGraphQueryException
+from ...slivers.delegations import Delegations, Pools, DelegationType
 
 
 class ABCADMPropertyGraph(ABCPropertyGraph):
@@ -64,85 +64,24 @@ def rewrite_delegations(self, *, real_adm_id: str = None) -> None:
                         continue
                     props_modified = True
                     # turn delegation into Python object
-                    delegation = json.loads(node_props[delegation_prop_name])
-                    new_delegation = dict()
+                    delegations = Delegations.from_json(json_str=node_props[delegation_prop_name],
+                                                        atype=DelegationType.LABEL if
+                                                        delegation_prop_name == self.PROP_LABEL_DELEGATIONS else
+                                                        DelegationType.CAPACITY)
+                    if len(delegations.get_delegation_ids()) != 1:
+                        raise PropertyGraphQueryException(graph_id=self.graph_id, node_id=node_id,
+                                                          msg=f'This node contains delegation structure with more'
+                                                              f'than one entry {delegations}')
+                    # replace the one delegation id that should be present here with a graph id
+                    # CAUTION: We directly manipulate the delegations object innards here for efficiency
+                    del_id = list(delegations.get_delegation_ids())[0]
+                    delegation = delegations.get_by_delegation_id(del_id)
                     if real_adm_id is None:
-                        new_delegation[self.graph_id] = delegation
+                        delegation.delegation_id = self.graph_id
                     else:
-                        new_delegation[real_adm_id] = delegation
-                    new_delegation_json = json.dumps(new_delegation)
-                    node_props[delegation_prop_name] = new_delegation_json
+                        delegation.delegation_id = real_adm_id
+                    delegations.delegations[delegation.delegation_id] = delegations.delegations.pop(del_id)
+                    node_props[delegation_prop_name] = delegations.to_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 = defaultdict(list)
-
-        # 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
-                full_details = d.get_full_details()
-                if full_details is not None:
-                    delegations_and_pools_per_node[d.on_].append(full_details)
-
-        # 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 = defaultdict(list)
-
-        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:
-                    full_details = pool.get_full_pool_details()
-                    if full_details is not None:
-                        delegations_and_pools_per_node[pool.on_].append(full_details)
-                        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 1a92c9e..5251759 100644
--- a/fim/graph/resources/abc_arm.py
+++ b/fim/graph/resources/abc_arm.py
@@ -26,29 +26,26 @@
 """
 Abstract definition of ARM (Aggregate Resource Model) functionality
 """
-from typing import Dict
+from typing import Dict, Tuple, Set,  List
 from abc import abstractmethod
-from typing import List
 from recordclass import recordclass
-import json
+from collections import defaultdict
 import uuid
 
 from ..abc_property_graph import ABCPropertyGraph, PropertyGraphQueryException
-from fim.slivers.delegations import Delegation, DelegationType, Pool, ARMPools, ARMDelegations
-from ..typed_tuples import Label, Capacity, LabelOrCapacityException
+from fim.slivers.delegations import DelegationType, Pools, Delegations
+from ..typed_tuples import Label, Capacity
 
 
 class ABCARMPropertyGraph(ABCPropertyGraph):
     """
     Interface for an ARM Mixin on top of a property graph
     """
-    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)}
+    DELEGATION_TYPE_TO_CLASS = {DelegationType.LABEL: (ABCPropertyGraph.PROP_LABEL_DELEGATIONS,
+                                                       Label),
+                                DelegationType.CAPACITY: (ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS,
+                                                          Capacity)}
 
-    DEFAULT_DELEGATION = "default"
-    LabelsAndProperties = recordclass('LabelsAndProperties', ['labels', 'properties'])
     DelegationInfo = recordclass('DelegationInfo', ['graph_id',
                                                     'graph',
                                                     'keep_nodes',
@@ -62,239 +59,52 @@ def __init__(self, *, graph: ABCPropertyGraph, logger=None):
         :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):
+    def catalog_delegations(self) -> Tuple[set[str], Dict[str, Set[str]],
+                                           Dict[str, Dict[DelegationType, Delegations]]]:
         """
-        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)
+        Collect all delegations from nodes in the graph.
+        Return a set of unique delegation IDs, a dictionary of sets of definite 'keep nodes' by delegation
+        and athe dictionary of Delegations organized by node id and delegation type.
+        { 'node1': { LABEL: <delegation object>, CAPACITY: <delegation object> } }
         :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:
+        unique_delegation_ids = set()
+        ret = dict()
+        keep_node_sets = defaultdict(set)
+        # for efficiency loop over nodes first
+        for node in self.node_ids:
+            by_type = dict()
+            do_add = False
+            for atype in [DelegationType.LABEL, DelegationType.CAPACITY]:
+                ds = self.get_delegations(node_id=node, delegation_type=atype)
+                if ds is None:
+                    continue
+                unique_delegation_ids.update(ds.get_delegation_ids())
+                by_type[atype] = ds
+                do_add = True
+                for d in ds.get_delegations_as_list():
+                    keep_node_sets[d.get_delegation_id()].add(node)
+            if do_add:
+                ret[node] = by_type
+        return unique_delegation_ids, keep_node_sets, ret
+
+    @staticmethod
+    def _update_delegations_on_node(*, graph, node_id: str, prop_name: str, prop_val: Delegations or None) -> None:
+        """
+        Update delegation for labels or capacities (delete if node_val is none)
+        :param graph: graph to make changes to
+        :param node_id:
+        :param prop_name:
+        :param prop_val: a delegations object or None
         :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]
+        if prop_val is None:
+            graph.unset_node_property(node_id=node_id, prop_name=prop_name)
         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)
+            graph.update_node_property(node_id=node_id, prop_name=prop_name,
+                                       prop_val=prop_val.to_json())
 
     def generate_adms(self) -> Dict:
         """
@@ -306,57 +116,53 @@ def generate_adms(self) -> Dict:
         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()
+        # collect stitch nodes between graphs
+        stitch_nodes = self.get_stitch_nodes()
 
-        # 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())
+        # catalog delegation IDs across all nodes to determine all unique delegation IDs and the nodes on
+        # which delegations are specified.
+        unique_delegation_ids, keep_nodes_sets, delegations_by_node = self.catalog_delegations()
 
-        # 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()),
+        # generate unique graph ids and empty node sets for ADM graphs
+        # add stitch nodes as keep nodes to all ADMs
+        delegations_info = {del_id: ABCARMPropertyGraph.DelegationInfo(graph_id=str(uuid.uuid4()),
                                                         graph=None,
-                                                        keep_nodes=set(),
+                                                        keep_nodes=keep_nodes_sets[del_id].union(stitch_nodes),
                                                         remove_nodes=set())
                             for del_id in unique_delegation_ids}
 
-        node_ids_to_delegations = {node_id: list() for node_id in self.node_ids}
+        # At this point we need to write back delegations for specific delegation ID
+        # into separate ADM graphs, then determine additional keep nodes and remove
+        # unneeded nodes from each.
         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)
+            for node in self.node_ids:
+                if delegations_by_node.get(node, None) is None:
+                    continue
+                for atype in [DelegationType.LABEL, DelegationType.CAPACITY]:
+                    prop_field_name, _ = self.DELEGATION_TYPE_TO_CLASS[atype]
+                    ds = None
+                    if delegations_by_node[node].get(atype, None) is not None:
+                        ds = delegations_by_node[node][atype].return_delegations_for_id(del_id)
+                    # rewrite delegations
+                    self._update_delegations_on_node(graph=delegations_info[del_id].graph, node_id=node,
+                                                     prop_name=prop_field_name, prop_val=ds)
+
+            # 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
 
-        # 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()
+            all_cp_ids = self.get_all_nodes_by_class(label=ABCPropertyGraph.CLASS_ConnectionPoint)
             for node_id in delegations_info[del_id].keep_nodes:
-                if 'ConnectionPoint' in self.nodes[node_id].labels:
+                if node_id in all_cp_ids:
                     keep_cps.add(node_id)
 
             # trace from connection points over links to other connection points
@@ -364,10 +170,10 @@ def generate_adms(self) -> Dict:
             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")
+                                                                  rel1=ABCPropertyGraph.REL_CONNECTS,
+                                                                  node1_label=ABCPropertyGraph.CLASS_Link,
+                                                                  rel2=ABCPropertyGraph.REL_CONNECTS,
+                                                                  node2_label=ABCPropertyGraph.CLASS_ConnectionPoint)
                 for pair in cp_neighbors:
                     # pair is a 2-element list
                     delegations_info[del_id].keep_nodes.update(pair)
@@ -378,18 +184,18 @@ def generate_adms(self) -> Dict:
             # 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")
+                                                                  rel1=ABCPropertyGraph.REL_CONNECTS,
+                                                                  node1_label=ABCPropertyGraph.CLASS_SwitchFabric,
+                                                                  rel2=ABCPropertyGraph.REL_HAS,
+                                                                  node2_label=ABCPropertyGraph.CLASS_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")
+                                                                  rel1=ABCPropertyGraph.REL_CONNECTS,
+                                                                  node1_label=ABCPropertyGraph.CLASS_SwitchFabric,
+                                                                  rel2=ABCPropertyGraph.REL_HAS,
+                                                                  node2_label=ABCPropertyGraph.CLASS_Component)
                 for pair in cp_neighbors:
                     delegations_info[del_id].keep_nodes.update(pair)
 
@@ -398,28 +204,52 @@ def generate_adms(self) -> Dict:
             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:
+    def get_delegations(self, *, node_id: str, delegation_type: DelegationType) -> Delegations or None:
         """
-        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.
+        Get Label or Capacity delegations of a given node.
+        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)
\ No newline at end of file
+        prop_field_name, _ = self.DELEGATION_TYPE_TO_CLASS[delegation_type]
+        _, props = self.get_node_properties(node_id=node_id)
+        if props.get(prop_field_name, None) is not None and \
+                props[prop_field_name] != ABCARMPropertyGraph.NEO4j_NONE:
+            return Delegations.from_json(json_str=props[prop_field_name], atype=delegation_type)
+        return None
+
+    def annotate_delegations_and_pools(self, *, dels: Dict[str, Delegations], pools: Pools):
+        """
+        Take delegations by node id and pools of the same type (Capacity or Label) and write them
+        into the graph as appropriate. This assumes that pools.build_index_by_delegation_id()
+        has been run.
+        :param dels:
+        :param pools:
+        :return:
+        """
+        assert dels is not None
+        assert pools is not None
+        # Pools object knows how to transform itself into a dictionary of Delegations
+        # objects of appropriate formats (pool definitions and pool references) organized
+        # by node id. Delegations object can be directly serialized
+        delegations_per_node = pools.generate_delegations_by_node_id()
+
+        # incorporate single element delegations
+        for node, ds in dels.items():
+            if delegations_per_node.get(node, None) is not None:
+                raise PropertyGraphQueryException(graph_id=self.graph_id, node_id=node,
+                                                  msg=f"Node {node} already has delegations defined")
+            delegations_per_node[node] = ds
+
+        # update graph properties
+        for node, v in delegations_per_node.items():
+            if pools.get_type() == DelegationType.CAPACITY:
+                self.update_node_property(node_id=node, prop_name=ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS,
+                                          prop_val=v.to_json())
+            else:
+                self.update_node_property(node_id=node, prop_name=ABCPropertyGraph.PROP_LABEL_DELEGATIONS,
+                                          prop_val=v.to_json())
diff --git a/fim/graph/resources/neo4j_arm.py b/fim/graph/resources/neo4j_arm.py
index fe3f40a..631bb4a 100644
--- a/fim/graph/resources/neo4j_arm.py
+++ b/fim/graph/resources/neo4j_arm.py
@@ -41,3 +41,16 @@ def __init__(self, *, graph: Neo4jPropertyGraph, logger=None):
         :param graph:
         """
         super().__init__(graph=graph, logger=logger)
+
+
+class Neo4jARMFactory:
+    """
+    Help convert graphs between formats so long as they are rooted in Neo4jPropertyGraph
+    """
+    @staticmethod
+    def create(graph: Neo4jPropertyGraph) -> Neo4jARMGraph:
+        assert graph is not None
+        assert isinstance(graph.importer, Neo4jGraphImporter)
+
+        return Neo4jARMGraph(graph=graph,
+                             logger=graph.log)
\ No newline at end of file
diff --git a/fim/graph/resources/neo4j_cbm.py b/fim/graph/resources/neo4j_cbm.py
index 17bb2f0..4e4bc47 100644
--- a/fim/graph/resources/neo4j_cbm.py
+++ b/fim/graph/resources/neo4j_cbm.py
@@ -37,8 +37,9 @@
 from ..neo4j_property_graph import Neo4jPropertyGraph, Neo4jGraphImporter
 from .abc_cbm import ABCCBMPropertyGraph
 from .neo4j_adm import Neo4jADMGraph
-from fim.slivers.delegations import DelegationType
-from fim.slivers.attached_components import AttachedComponentsInfo, ComponentSliver, ComponentType
+from fim.slivers.capacities_labels import StructuralInfo, StructuralInfoException
+from fim.slivers.delegations import DelegationType, Delegations
+from fim.slivers.attached_components import AttachedComponentsInfo
 
 from ...pluggable import PluggableRegistry, BrokerPluggable, PluggableType
 
@@ -47,7 +48,6 @@ class Neo4jCBMGraph(Neo4jPropertyGraph, ABCCBMPropertyGraph):
     """
     Neo4j implementation of CBM
     """
-    PROP_ADM_GRAPH_IDS = "ADMGraphIDs"
     BQM_MERGED_FIELDS = ['LabelDelegations', 'CapacityDelegations']
     DELEGATION_TYPE_TO_PROP_NAME = {DelegationType.LABEL: ABCPropertyGraph.PROP_LABEL_DELEGATIONS,
                                     DelegationType.CAPACITY: ABCPropertyGraph.PROP_CAPACITY_DELEGATIONS
@@ -70,29 +70,41 @@ def _update_node_delegations(self, *, node_id: str, adm: ABCPropertyGraph) -> No
         on common nodes) from corresponding ADM node.
         Must be invoked before ADM is merged into CBM.
         :param node_id:
-        :param adm_id:
+        :param adm:
         :return:
         """
         _, cbm_node_props = self.get_node_properties(node_id=node_id)
         _, adm_node_props = adm.get_node_properties(node_id=node_id)
         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 cbm_node_props.get(delegation_prop_name, None) is None or \
-                    cbm_node_props[delegation_prop_name] == self.NEO4j_NONE:
-                cbm_delegation_dict = dict()
+            # one or both of properties can have data, however only CBM or ADM
+            # can have information about delegations - only one aggregate can speak for a resource
+            if cbm_node_props.get(delegation_prop_name, None) is None:
+                cbm_delegations = None
+            else:
+                cbm_delegations = Delegations.from_json(json_str=cbm_node_props[delegation_prop_name],
+                                                        atype=DelegationType.LABEL if
+                                                        delegation_prop_name == self.PROP_LABEL_DELEGATIONS else
+                                                        DelegationType.CAPACITY)
+
+            if adm_node_props.get(delegation_prop_name, None) is None:
+                adm_delegations = None
             else:
-                cbm_delegation_dict = json.loads(cbm_node_props[delegation_prop_name])
+                adm_delegations = Delegations.from_json(json_str=adm_node_props[delegation_prop_name],
+                                                        atype=DelegationType.LABEL if
+                                                        delegation_prop_name == self.PROP_LABEL_DELEGATIONS else
+                                                        DelegationType.CAPACITY)
 
-            if adm_node_props.get(delegation_prop_name, None) is None or \
-                    adm_node_props[delegation_prop_name] == self.NEO4j_NONE:
+            if adm_delegations is None and cbm_delegations is None:
                 continue
+            if adm_delegations is not None and cbm_delegations is not None:
+                raise PropertyGraphQueryException(graph_id=self.graph_id, node_id=node_id,
+                                                  msg=f'This node contains delegations from both CBM and ADM graph,'
+                                                      f'which is not allowed.')
             props_modified = True
-            # turn delegation into Python object by merging two dictionaries
-            adm_delegation = json.loads(adm_node_props[delegation_prop_name])
-            cbm_delegation_dict.update(adm_delegation)
-            new_delegation_json = json.dumps(cbm_delegation_dict)
-            cbm_node_props[delegation_prop_name] = new_delegation_json
+            # take the non-None dictionary and write back
+            new_delegations = cbm_delegations if cbm_delegations is not None else adm_delegations
+            cbm_node_props[delegation_prop_name] = new_delegations.to_json()
         if props_modified:
             # write back
             self.update_node_properties(node_id=node_id, props=cbm_node_props)
@@ -103,7 +115,6 @@ def merge_adm(self, *, adm: ABCPropertyGraph) -> None:
         :param adm:
         :return:
         """
-        print(f'ADM GraphID is {adm.graph_id}')
         assert adm is not None
         assert adm.graph_exists()
 
@@ -121,9 +132,10 @@ def merge_adm(self, *, adm: ABCPropertyGraph) -> None:
         # but be sure to use original ADM graph ID when rewriting
         temp_adm_graph.rewrite_delegations(real_adm_id=adm.graph_id)
 
-        # update the ADMGraphIDs property to a single member list on all CBM nodes
-        temp_adm_graph.update_nodes_property(prop_name=self.PROP_ADM_GRAPH_IDS,
-                                             prop_val=json.dumps([adm.graph_id]))
+        # update the structural info adm_graph_ids field to a single member list on all CBM nodes
+        si = StructuralInfo().set_fields(adm_graph_ids=[adm.graph_id])
+        temp_adm_graph.update_nodes_property(prop_name=self.PROP_STRUCTURAL_INFO,
+                                             prop_val=si.to_json())
 
         # if CBM is empty, just force ADM into it
         if not self.graph_exists():
@@ -143,11 +155,12 @@ def merge_adm(self, *, adm: ABCPropertyGraph) -> None:
         # Labels: use CBM - may have information from reservations already
         # Capacities: use CBM - may have information from reservations already - should be same
         # in both graphs initially
-        # LabelDelegations: use CBM, edit/add from ADM
-        # CapacityDelegations: use CBM, edit/add from ADM
+        # LabelDelegations: delegations can be provided only by one graph - either CBM or ADM
+        # CapacityDelegations: delegations can be provided only by one graph - either CBM or ADM
         for node_id in common_node_ids:
-            # update Label and Capacity delegation (now they are dictionaries, not lists)
-            # prior to merge
+            # update Label and Capacity delegation prior to merge
+            # when nodes are merged, these properties are kept, ADM properties
+            # are discarded
             self._update_node_delegations(node_id=node_id, adm=temp_adm_graph)
 
             # default behavior - keep CBM node properties, discard
@@ -157,10 +170,10 @@ def merge_adm(self, *, adm: ABCPropertyGraph) -> None:
 
             # add delegation graph id to ADMGraphIDs property on common (merged) nodes
             _, cbm_node_props = self.get_node_properties(node_id=node_id)
-            adm_list = json.loads(cbm_node_props[self.PROP_ADM_GRAPH_IDS])
-            adm_list.append(adm.graph_id)
-            self.update_node_property(node_id=node_id, prop_name=self.PROP_ADM_GRAPH_IDS,
-                                      prop_val=json.dumps(adm_list))
+            si = StructuralInfo.from_json(cbm_node_props[self.PROP_STRUCTURAL_INFO])
+            si.adm_graph_ids.append(adm.graph_id)
+            self.update_node_property(node_id=node_id, prop_name=self.PROP_STRUCTURAL_INFO,
+                                      prop_val=si.to_json())
 
         # rewrite GraphID on the remaining nodes of
         # temporary ADM graph (after that it ceases to exist)
@@ -175,39 +188,47 @@ def unmerge_adm(self, *, graph_id: str) -> None:
         # to remove.
         delete_nodes = list()
         for node in self.list_all_node_ids():
-            adm_graph_ids = self.get_node_json_property_as_object(node_id=node,
-                                                                  prop_name=self.PROP_ADM_GRAPH_IDS)
-            if adm_graph_ids is None:
+            _, adm_node_props = self.get_node_properties(node_id=node)
+            si = StructuralInfo.from_json(adm_node_props[self.PROP_STRUCTURAL_INFO])
+
+            if si.adm_graph_ids is None:
                 self.log.warn(f'When unmerging ADMs encountered node {node} '
-                              f'without {self.PROP_ADM_GRAPH_IDS} property')
+                              f'without adm_graph_ids field in property {adm_node_props[self.PROP_STRUCTURAL_INFO]}')
                 continue
-            if not isinstance(adm_graph_ids, list):
+            if not isinstance(si.adm_graph_ids, list):
                 raise PropertyGraphQueryException(node_id=node, graph_id=self.graph_id,
                                                   msg=f"When unmerging graph {graph_id}, encountered wrongly"
-                                                      f"formatted {self.PROP_ADM_GRAPH_IDS} field")
-            if graph_id in adm_graph_ids:
+                                                      f"formatted {self.PROP_STRUCTURAL_INFO} field "
+                                                      f"{adm_node_props[self.PROP_STRUCTURAL_INFO]}")
+            if graph_id in si.adm_graph_ids:
                 # update the ADM Graph IDs field
-                adm_graph_ids.remove(graph_id)
-                if len(adm_graph_ids) == 0:
+                si.adm_graph_ids.remove(graph_id)
+                if len(si.adm_graph_ids) == 0:
                     delete_nodes.append(node)
                 else:
                     # update
-                    self.update_node_property(node_id=node, prop_name=self.PROP_ADM_GRAPH_IDS,
-                                              prop_val=json.dumps(adm_graph_ids))
+                    self.update_node_property(node_id=node, prop_name=self.PROP_STRUCTURAL_INFO,
+                                              prop_val=si.to_json())
             # also unmerge delegations
-            for del_type in [self.PROP_CAPACITY_DELEGATIONS, self.PROP_LABEL_DELEGATIONS]:
-                delegations = self.get_node_json_property_as_object(node_id=node,
-                                                                    prop_name=del_type)
+            for del_prop in [self.PROP_CAPACITY_DELEGATIONS, self.PROP_LABEL_DELEGATIONS]:
+                if del_prop not in adm_node_props.keys():
+                    continue
+                delegations = Delegations.from_json(json_str=adm_node_props[del_prop],
+                                                    atype=DelegationType.LABEL if
+                                                    del_prop == self.PROP_LABEL_DELEGATIONS
+                                                    else DelegationType.CAPACITY)
                 if delegations is None:
                     continue
-                if not isinstance(delegations, dict):
-                    raise PropertyGraphQueryException(node_id=node, graph_id=self.graph_id,
-                                                      msg=f"When unmerging graph {graph_id}, encountered wrongly"
-                                                          f"formatted {del_type} field")
-                if graph_id in delegations.keys():
-                    delegations.pop(graph_id)
-                    self.update_node_property(node_id=node, prop_name=del_type,
-                                              prop_val=json.dumps(delegations))
+                if graph_id in delegations.get_delegation_ids():
+                    delegations.remove_by_id(graph_id)
+                    if len(delegations.get_delegation_ids()) != 0:
+                        raise PropertyGraphQueryException(graph_id=self.graph_id, node_id=node,
+                                                          msg=f'This node had more than one delegation, remaining:'
+                                                              f'{delegations}')
+                    # under normal circumstances we should erase the delegation after unmerge if
+                    # it belonged to the graph being removed
+                    self.update_node_property(node_id=node, prop_name=del_prop,
+                                              prop_val='')
 
         # remove the merged nodes
         for node in delete_nodes:
diff --git a/fim/graph/resources/networkx_arm.py b/fim/graph/resources/networkx_arm.py
new file mode 100644
index 0000000..15ae2a4
--- /dev/null
+++ b/fim/graph/resources/networkx_arm.py
@@ -0,0 +1,56 @@
+#!/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_arm import ABCARMPropertyGraph
+
+
+class NetworkXARMGraph(ABCARMPropertyGraph, NetworkXPropertyGraph):
+
+    def __init__(self, *, graph: NetworkXPropertyGraph, logger=None):
+        """
+        Initialize NetworkX ARM - supply an implementation of a graph
+        :param graph:
+        """
+        super().__init__(graph=graph, logger=logger)
+
+
+class NetworkXARMFactory:
+    """
+    Help convert graphs between formats so long as they are rooted in NetworkXPropertyGraph
+    """
+    @staticmethod
+    def create(graph: NetworkXPropertyGraph) -> NetworkXARMGraph:
+        assert graph is not None
+        assert isinstance(graph.importer, NetworkXGraphImporter)
+
+        return NetworkXARMGraph(graph=graph,
+                                logger=graph.log)
diff --git a/fim/slivers/base_sliver.py b/fim/slivers/base_sliver.py
index ac9501c..45691aa 100644
--- a/fim/slivers/base_sliver.py
+++ b/fim/slivers/base_sliver.py
@@ -29,8 +29,8 @@
 from typing import Any, Tuple, List, Dict
 from abc import ABC, abstractmethod
 
-from fim.slivers.capacities_labels import Capacities, Labels, ReservationInfo
-from fim.slivers.delegations import Delegation
+from fim.slivers.capacities_labels import Capacities, Labels, ReservationInfo, StructuralInfo
+from fim.slivers.delegations import Delegations
 
 
 class BaseSliver(ABC):
@@ -48,9 +48,11 @@ def __init__(self):
         self.capacity_allocations = None
         self.label_allocations = None
         self.reservation_info = None
+        self.structural_info = None
         self.node_id = None
         self.details = None
         self.node_map = None
+        self.stitch_node = False
 
     def set_type(self, resource_type):
         self.resource_type = resource_type
@@ -71,47 +73,61 @@ def get_model(self):
         return self.resource_model
 
     def set_capacities(self, cap: Capacities) -> None:
+        assert(cap is None or isinstance(cap, Capacities))
         self.capacities = cap
 
     def get_capacities(self) -> Capacities:
         return self.capacities
 
     def set_labels(self, lab: Labels) -> None:
+        assert(lab is None or isinstance(lab, Labels))
         self.labels = lab
 
     def get_labels(self) -> Labels:
         return self.labels
 
-    def set_capacity_delegations(self, cdel):
+    def set_capacity_delegations(self, cdel: Delegations):
+        assert(cdel is None or isinstance(cdel, Delegations))
         self.capacity_delegations = cdel
 
-    def get_capacity_delegations(self) -> List or Dict:
+    def get_capacity_delegations(self) -> Delegations:
         return self.capacity_delegations
 
-    def set_label_delegations(self, ldel):
+    def set_label_delegations(self, ldel: Delegations):
+        assert(ldel is None or isinstance(ldel, Delegations))
         self.label_delegations = ldel
 
-    def get_label_delegations(self) -> List or Dict:
+    def get_label_delegations(self) -> Delegations:
         return self.label_delegations
 
     def set_label_allocations(self, lab: Labels) -> None:
+        assert(lab is None or isinstance(lab, Labels))
         self.label_allocations = lab
 
     def get_label_allocations(self) -> Labels:
         return self.label_allocations
 
     def set_capacity_allocations(self, cap: Capacities) -> None:
+        assert(cap is None or isinstance(cap, Capacities))
         self.capacity_allocations = cap
 
     def get_capacity_allocations(self) -> Capacities:
         return self.capacity_allocations
 
     def set_reservation_info(self, ri: ReservationInfo) -> None:
+        assert(ri is None or isinstance(ri, ReservationInfo))
         self.reservation_info = ri
 
     def get_reservation_info(self) -> ReservationInfo:
         return self.reservation_info
 
+    def set_structural_info(self, si: StructuralInfo) -> None:
+        assert (si is None or isinstance(si, StructuralInfo))
+        self.structural_info = si
+
+    def get_structural_info(self) -> StructuralInfo:
+        return self.structural_info
+
     def set_details(self, desc: str) -> None:
         self.details = desc
 
@@ -124,6 +140,12 @@ def set_node_map(self, node_map: Tuple[str, str]) -> None:
     def get_node_map(self) -> Tuple[str, str] or None:
         return tuple(self.node_map) if self.node_map is not None else None
 
+    def set_stitch_node(self, stitch_node: bool) -> None:
+        self.stitch_node = stitch_node
+
+    def get_stitch_node(self) -> bool:
+        return self.stitch_node
+
     def set_properties(self, **kwargs):
         """
         Lets you set multiple properties exposed via setter methods
diff --git a/fim/slivers/capacities_labels.py b/fim/slivers/capacities_labels.py
index 0412bfb..4c1e3b4 100644
--- a/fim/slivers/capacities_labels.py
+++ b/fim/slivers/capacities_labels.py
@@ -56,18 +56,19 @@ class should only be those that can be present in JSON output.
             return ''
         return json.dumps(d, skipkeys=True, sort_keys=True)
 
-    def from_json(self, json_string: str):
+    @classmethod
+    def from_json(cls, json_string: str):
         """
-        Set fields from json string (if string is none or empty,
-        nothing happens)
+        Set fields from json string and returns a new object
         :param json_string:
-        :return: self for call chaining
+        :return: object
         """
         if json_string is None or len(json_string) == 0 or json_string == ABCPropertyGraphConstants.NEO4j_NONE:
-            return self
+            return None
         d = json.loads(json_string)
-        self.set_fields(**d)
-        return self
+        ret = cls()
+        ret.set_fields(**d)
+        return ret
 
     def to_dict(self) -> Dict[str, str] or None:
         """
@@ -100,7 +101,7 @@ class Capacities(JSONField):
     Implements basic capacity field handling - encoding and decoding
     from JSON dictionaries of properties
     """
-    UNITS = {'cpu': '', 'unit': '', 'core': '', 'ram': 'G', 'disk': 'G', 'bw': 'G'}
+    UNITS = {'cpu': '', 'unit': '', 'core': '', 'ram': 'G', 'disk': 'G', 'bw': 'Gbps'}
 
     def __init__(self):
         self.cpu = 0
@@ -179,7 +180,7 @@ def __str__(self):
             return ''
         ret = "{ "
         for i, v in d.items():
-            ret = ret + i + ": " + str(v) + self.UNITS[i] + ", "
+            ret = ret + i + ": " + f'{v:,} ' + self.UNITS[i] + ", "
         return ret + "}"
 
 
@@ -207,7 +208,7 @@ def __str__(self):
             return ''
         ret = '{ '
         for k in d1:
-            ret = ret + k + ": " + str(d1[k]) + "/" + str(d2[k]) + Capacities.UNITS[k] + ", "
+            ret = ret + k + ": " + f'{d1[k]:,}' + "/" + f'{d2[k]:,} ' + Capacities.UNITS[k] + ", "
         return ret + '}'
 
 
@@ -228,6 +229,7 @@ def __init__(self):
         self.vlan_range = None
         self.instance = None
         self.instance_parent = None
+        self.local_name = None
 
     def set_fields(self, **kwargs):
         """
@@ -277,8 +279,7 @@ def __init__(self):
 
     def set_fields(self, **kwargs):
         """
-        Universal setter for all fields (just strip the l_ from the field
-        name of the field). Values should be strings or lists of strings.
+        Universal setter for all fields. Values should be strings or lists of strings.
         Throws a ReservationInfoException if you try to set a non-existent field.
         :param kwargs:
         :return: self to support call chaining
@@ -296,9 +297,39 @@ def set_fields(self, **kwargs):
         return self
 
 
+class StructuralInfo(JSONField):
+    """
+    Structural info on the for sliver objects (things like - is it a stitching node,
+    what is parent graph or subgraph)
+    """
+    def __init__(self):
+        self.sub_graph_id = None
+        self.parent_graph_id = None
+        self.adm_graph_ids = None
+
+    def set_fields(self, **kwargs):
+        """
+        Universal setter for all fields. Values are strings or boolean.
+        Throws a
+        :param kwargs:
+        :return:
+        """
+        for k, v in kwargs.items():
+            assert v is not None  # could be strings or lists of strings
+            assert isinstance(v, str) or isinstance(v, list)
+            try:
+                # will toss an exception if field is not defined
+                self.__getattribute__(k)
+                self.__setattr__(k, v)
+            except AttributeError:
+                raise StructuralInfoException(f"Unable to set field {k} of structural info, no such field available")
+        # to support call chaining
+        return self
+
+
 class CapacityException(Exception):
     """
-    Exception with a pool
+    Exception with a capacity
     """
     def __init__(self, msg: str):
         assert msg is not None
@@ -307,7 +338,7 @@ def __init__(self, msg: str):
 
 class LabelException(Exception):
     """
-    Exception with a pool
+    Exception with a label
     """
     def __init__(self, msg: str):
         assert msg is not None
@@ -316,8 +347,18 @@ def __init__(self, msg: str):
 
 class ReservationInfoException(Exception):
     """
-    Exception with a pool
+    Exception with a reservation info
+    """
+    def __init__(self, msg: str):
+        assert msg is not None
+        super().__init__(f"ReservationInfo exception: {msg}")
+
+
+class StructuralInfoException(Exception):
+    """
+    Exception with a structural info
     """
+
     def __init__(self, msg: str):
         assert msg is not None
         super().__init__(f"ReservationInfo exception: {msg}")
\ No newline at end of file
diff --git a/fim/slivers/component_catalog.py b/fim/slivers/component_catalog.py
index 9f75b7f..8223edd 100644
--- a/fim/slivers/component_catalog.py
+++ b/fim/slivers/component_catalog.py
@@ -32,7 +32,7 @@
 from .attached_components import ComponentSliver, ComponentType
 from .interface_info import InterfaceInfo, InterfaceSliver, InterfaceType
 from .switch_fabric import SwitchFabricSliver, SwitchFabricInfo, SFLayer, SFType
-from .capacities_labels import Capacities
+from .capacities_labels import Capacities, Labels
 
 
 class ComponentCatalog:
@@ -54,9 +54,8 @@ def __read_catalog() -> List[Any]:
 
     def generate_component(self, *, name: str, ctype: ComponentType, model: str,
                            switch_fabric_node_id: str = None,
-                           interface_node_ids: List[str] = None) -> ComponentSliver:
-        # FIXME: need to pass labels (PCI and MAC) for substrate ads so they can be
-        # FIXME: propagated to ConnectionPoints/interfaces as part of creation
+                           interface_node_ids: List[str] = None,
+                           interface_labels: List[Labels] = None) -> ComponentSliver:
         """
         Generate a component sliver with this name and model and properties, and interfaces as needed
         based on the catalog description. Interfaces if present are named
@@ -67,7 +66,8 @@ def generate_component(self, *, name: str, ctype: ComponentType, model: str,
         :param ctype: type of the component
         :param model:
         :param switch_fabric_node_id: if specified and if component has a switch fabric, put that there
-        :param interface_node_ids: list of node ids for expected interfacaes if component has any
+        :param interface_node_ids: list of node ids for expected interfaces if component has any
+        :param interface_labels: list of labels for expected interfaces if component has any
         :return:
         """
         assert name is not None
@@ -89,9 +89,14 @@ def generate_component(self, *, name: str, ctype: ComponentType, model: str,
         cs.set_details(component_dict['Details'])
         if 'Interfaces' in component_dict.keys():
             interfaces_dict = component_dict['Interfaces']
-            if interface_node_ids is not None and len(interface_node_ids) != len(interfaces_dict.keys()):
-                raise RuntimeError("The number of interface IDs provided is insufficient for this component "
-                                   f"(need {len(interfaces_dict.keys())} instead of {len(interface_node_ids)}")
+            if interface_node_ids is not None:
+                if len(interface_node_ids) != len(interfaces_dict.keys()):
+                    raise RuntimeError("The number of interface IDs provided is insufficient for this component "
+                                       f"(need {len(interfaces_dict.keys())} instead of {len(interface_node_ids)}")
+                if len(interface_labels) != len(interfaces_dict.keys()):
+                    raise RuntimeError("The number of PCI labels and MAC addresses provided is insufficient for this"
+                                       f" component (need {len(interfaces_dict.keys())} instead of "
+                                       f"{len(interface_labels)}")
             iinfo = InterfaceInfo()
             id_index = 0
             for interface_name in interfaces_dict.keys():
@@ -102,6 +107,8 @@ def generate_component(self, *, name: str, ctype: ComponentType, model: str,
                     isliver.node_id = interface_node_ids[id_index]
                 else:
                     isliver.node_id = str(uuid.uuid4())
+                if interface_labels is not None:
+                    isliver.set_labels(interface_labels[id_index])
                 id_index = id_index + 1
                 cap = Capacities()
                 # set port speed
diff --git a/fim/slivers/delegations.py b/fim/slivers/delegations.py
index 7d9d37a..400098d 100644
--- a/fim/slivers/delegations.py
+++ b/fim/slivers/delegations.py
@@ -43,219 +43,121 @@ class DelegationType(Enum):
 
 
 class DelegationFormat(Enum):
-    """
-    Delegations properties on graph nodes have different interpretation
-    depending on which model graph they are in.
-    """
-    ARMDelegation = 1 # List of dictionaries with delegation and pool identifiers
-    ADMDelegation = 2 # List of dictionaries without delegation id but with pool id
-    CBMDelegation = 3 # Dictionary keyed by ADM graph id with value matching ADMDelegation format
+    PoolDefinition = 1
+    PoolReference = 2
+    SinglePool = 3
 
 
 class Delegation:
-    """Label and capacity delegation. Only on individual resources.
-    Pools are used to define delegations across multiple resources.
     """
-    def __init__(self, atype: DelegationType, defined_on: str, delegation_id: str):
+    Single label and capacity delegation (pool definition or reference).
+    Encodes a single entry in a dictionary describing multiple delegations.
+    This object by itself is not JSON serializable, for that look at Delegations object.
+    """
+    def __init__(self, *, atype: DelegationType, delegation_id: str,
+                 aformat: DelegationFormat = DelegationFormat.SinglePool,
+                 pool_id: str = None):
         """
-        Define a delegation of resources on a given node
+        Define a delegation of resources on a given node (for a given pool - either
+        as definition or reference)
         :param atype:
-        :param defined_on:
         :param delegation_id:
+        :param aformat:
+        :param pool_id:
         """
         assert atype is not None
-        assert defined_on is not None
         assert delegation_id is not None
         self.type = atype
-        self.on_ = defined_on
+        self.format = aformat
         self.delegation_id = delegation_id
         self.delegation_details = None
+        self.pool_id = pool_id
+        if aformat != DelegationFormat.SinglePool:
+            assert pool_id is not None
 
-    def get_defined_on(self) -> str:
-        return self.on_
+    def get_delegation_type(self) -> DelegationType:
+        return self.type
 
-    def set_details(self, dele_dict: Dict) -> None:
-        """
-        set details of the delegation dictionary, removing the delegation
-        identifier field from dictionary
-        :param dele_dict:
-        :return:
-        """
-        assert dele_dict is not None
-        # pop the delegation field
-        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_pool_name(self) -> str:
+        return self.pool_id
 
-    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 get_format(self) -> DelegationFormat:
+        return self.format
 
-    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_delegation_id(self) -> str:
+        return self.delegation_id
 
-    def get_details(self) -> Dict or None:
+    def set_details(self, caporlab) -> None:
         """
-        get delegation details as a dictionary (minus the delegation id)
+        set details of the delegation dictionary from either a Labels or Capacities
+        object
+        :param caporlab: either Labels or Capacities
         :return:
         """
-        if self.delegation_details is None:
-            return None
-        return self.delegation_details.copy()
+        assert caporlab is not None
+        assert (isinstance(caporlab, Labels) or isinstance(caporlab, Capacities))
+        if self.format == DelegationFormat.PoolReference:
+            raise DelegationException(msg=f'Trying to add Labels or Capacities object to PoolReference delegation')
+        if (isinstance(caporlab, Labels) and self.type == DelegationType.CAPACITY) or \
+            (isinstance(caporlab, Capacities) and self.type == DelegationType.LABEL):
+            raise DelegationException(msg=f'Trying to add Capacities to a LABEL type delegation or vice versa')
+        self.delegation_details = caporlab
 
-    def get_full_details(self) -> Dict or None:
+    def get_details(self) -> Labels or Capacities:
         """
-        Get delegation details as a dictionary, including the delegation id
+        get delegation details as either Labels or Capacities object
         :return:
         """
-        ret = self.get_details()
-        if ret is None:
+        if self.delegation_details 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
+        return self.delegation_details
 
-    def to_json(self) -> str or None:
+    def get_details_as_dict(self) -> Dict[str, str] or None:
         """
-        Convert to a JSON representation adding back delegation id
+        get details of labels/capacities as a dictionary
         :return:
         """
-        ret_dict = self.get_full_details()
-        if ret_dict is None:
+        if self.delegation_details is None:
             return None
-        return json.dumps(ret_dict, skipkeys=True, sort_keys=True)
+        return self.delegation_details.to_dict()
 
     def __repr__(self) -> str:
         return f"{self.type} delegation delegated to {self.delegation_id}: " \
-               f"{self.on_} with {self.delegation_details} "
+               f"format {self.format} for pool {self.pool_id} with {self.delegation_details} "
 
-    @staticmethod
-    def from_json_to_sliver_field(j: str or None) -> List[Dict] or Dict[str, List] or None:
-        """
-        Take JSON and produce an object for sliver field that is
-        one of the accepted DelegationFormats. Return None if corresponding
-        property in graph node is empty. Throw a DelegationException if format
-        is not what is expected.
-        :param j:
-        :return:
-        """
-        if j is None or j == ABCPropertyGraphConstants.NEO4j_NONE:
-            return None
-        sliver_field = json.loads(j)
-        if isinstance(sliver_field, list):
-            # either ARM or ADM
-            for e in sliver_field:
-                if not isinstance(e, dict):
-                    raise DelegationException(msg=f'Unknown object in delegation property {sliver_field}')
-            return sliver_field
-        elif isinstance(sliver_field, dict):
-            # CBM
-            for k, v in sliver_field.items():
-                if not isinstance(v, list):
-                    raise DelegationException(msg=f'Unknown object in delegation property {sliver_field}')
-            return sliver_field
-        raise DelegationException(msg=f'Unknown object in delegation property {sliver_field}')
-
-    @staticmethod
-    def from_sliver_field_to_json(l: List[Dict] or None) -> str or None:
-        """
-        Produce a JSON expression sliver field encoding the delegation or None
-        :param l:
-        :return:
-        """
-        if l is None:
-            return None
-        return json.dumps(l)
 
-    @staticmethod
-    def get_delegation_format(j: str or None) -> DelegationFormat or None:
-        """
-        Determine the format of the encoded delegation - ARM, ADM or CBM
-        or None if string doesn't specify a delegation.
-        Throw exception if not what is expected.
-        :param j:
-        :return:
-        """
-        if j is None or j == ABCPropertyGraphConstants.NEO4j_NONE:
-            return None
-        sliver_field = json.loads(j)
-        if isinstance(sliver_field, list):
-            # either ARM or ADM
-            # ARM delegation have 'delegation' field present, ADM don't,
-            # but they both are lists of dicts
-            for e in sliver_field:
-                if not isinstance(e, dict):
-                    raise DelegationException(msg=f'Unknown object in delegation property {sliver_field}')
-                if e.get(ABCPropertyGraphConstants.FIELD_DELEGATION, None) is not None:
-                    return DelegationFormat.ARMDelegation
-            return DelegationFormat.ADMDelegation
-        elif isinstance(sliver_field, dict):
-            # CBM is a dict of lists of dicts, one for each ADM for that node.
-            # only stitch nodes have two entries, everyone else has one for corresponding
-            # ADM graph
-            for k, v in sliver_field.items():
-                if not isinstance(v, list):
-                    raise DelegationException(msg=f'Unknown object in delegation property {sliver_field}')
-            return DelegationFormat.CBMDelegation
-        raise DelegationException(msg=f'Unknown object in delegation property {sliver_field}')
-
-
-class ARMDelegations:
+class Delegations:
     """
-    Multiple delegations references by delegation id within an ARM
+    Multiple delegations referenced by delegation id within them on a single
+    model element. Serializable to and from JSON so used in models and slivers.
+    Delegation id can be graph id or a unique string (depending on whether this
+    is on CBM, or ADM/ARM)
     """
-    def __init__(self, atype: DelegationType):
+    def __init__(self, *, atype: DelegationType):
+
         self.type = atype
-        self.delegations = {}
+        self.delegations = dict()
 
-    def add_delegation(self, delegation: Delegation) -> None:
+    def add_delegations(self, *args) -> None:
         """
-        Append a well-formed delegation to the list of delegations matching
-        this id
-        :param delegation:
+        Include well-formed Delegation objects into the dictionary.
+        Delegation types must match the type of this container object.
+        :param args: sequence of Delegation objects
         :return:
         """
-        assert delegation.delegation_id is not None
-        if self.delegations.get(delegation.delegation_id, None) is None:
-            self.delegations[delegation.delegation_id] = []
+        for delegation in args:
+            assert delegation.delegation_id is not None
+            assert delegation.get_delegation_type() == self.type
 
-        self.delegations[delegation.delegation_id].append(delegation)
+            if self.delegations.get(delegation.delegation_id, None) is not None:
+                raise DelegationException(msg=f'Delegation with id {delegation.delegation_id} is already present')
+            if self.type != delegation.get_delegation_type():
+                raise DelegationException(msg=f'Delegation with id {delegation.delegation_id} type '
+                                              f'{delegation.get_delegation_type()} does not match delegations '
+                                              f'type {self.type}')
+            self.delegations[delegation.delegation_id] = delegation
 
-    def get_by_delegation_id(self, delegation_id: str) -> List[Delegation]:
+    def get_by_delegation_id(self, delegation_id: str) -> Delegation:
         """
         retrieve a list of delegations by their id or None
         :param delegation_id:
@@ -264,6 +166,24 @@ def get_by_delegation_id(self, delegation_id: str) -> List[Delegation]:
         assert delegation_id is not None
         return self.delegations.get(delegation_id, None)
 
+    def get_sole_delegation(self) -> Tuple[str, Delegation]:
+        """
+        Checks that delegations object has just one delegation and returns
+        a tuple of delegation id and delegation object. Useful primarily on CBMs.
+        :return:
+        """
+        if len(self.delegations) != 1:
+            raise DelegationException(msg=f"Expected to find only one delegation in Delegations object {self}")
+        for k, v in self.delegations.items():
+            return k, v
+
+    def get_delegations_as_list(self) -> List[Delegation]:
+        """
+        Return individual delegation objects as a list
+        :return:
+        """
+        return list(self.delegations.values())
+
     def get_delegation_ids(self) -> Set:
         """
         get a set of all delegation ids
@@ -271,28 +191,119 @@ def get_delegation_ids(self) -> Set:
         """
         return set(self.delegations.keys())
 
-    def get_node_ids(self, delegation_id: str) -> Set:
+    def remove_by_id(self, delegation_id: str) -> None:
         """
-        return a set of nodes ids for a given delegation id
+        Remove a delegation with this id from container
         :param delegation_id:
         :return:
         """
         assert delegation_id is not None
-        ret = set()
         if delegation_id not in self.delegations.keys():
-            return ret
-        for delegation in self.delegations[delegation_id]:
-            ret.add(delegation.on_)
-        return ret
+            return
+        self.delegations.pop(delegation_id)
+
+    def return_delegations_for_id(self, delegation_id: str):
+        """
+        Return a delegations object limited to this delegation id or None.
+        :param delegation_id:
+        :return:
+        """
+        if delegation_id not in self.delegations.keys():
+            return None
+        ds = Delegations(atype=self.type)
+        ds.add_delegations(self.delegations[delegation_id])
+        return ds
+
+    def to_json(self) -> str:
+        """
+        convert object to JSON. Example objects:
+        { "del1": {
+            "pool_name": "_",
+            "capacities": { <capacities dictionary> } }
+        or
+        { "del1": {
+            "pool_name": "pool1",
+            "labels": { <labels dictionary> } }
+        or
+        { "del1": {
+            "pool": "pool1" }
+        :return:
+        """
+        json_dict = {}
+        for k, v in self.delegations.items():
+            inner_dict = {}
+            if v.get_format() == DelegationFormat.SinglePool:
+                assert v.get_details_as_dict() is not None
+                inner_dict[ABCPropertyGraphConstants.FIELD_POOL_ID] = ABCPropertyGraphConstants.SINGLE_POOL_NAME
+                if self.type == DelegationType.CAPACITY:
+                    inner_dict[ABCPropertyGraphConstants.FIELD_CAPACITIES] = v.get_details_as_dict()
+                else:
+                    inner_dict[ABCPropertyGraphConstants.FIELD_LABELS] = v.get_details_as_dict()
+            elif v.get_format() == DelegationFormat.PoolDefinition:
+                assert v.get_pool_name() is not None
+                assert v.get_details_as_dict() is not None
+                inner_dict[ABCPropertyGraphConstants.FIELD_POOL_ID] = v.get_pool_name()
+                if self.type == DelegationType.CAPACITY:
+                    inner_dict[ABCPropertyGraphConstants.FIELD_CAPACITIES] = v.get_details_as_dict()
+                else:
+                    inner_dict[ABCPropertyGraphConstants.FIELD_LABELS] = v.get_details_as_dict()
+            elif v.get_format() == DelegationFormat.PoolReference:
+                assert v.get_pool_name() is not None
+                inner_dict[ABCPropertyGraphConstants.FIELD_POOL] = v.get_pool_name()
+            json_dict[k] = inner_dict
+
+        return json.dumps(json_dict)
+
+    @classmethod
+    def from_json(cls, *, json_str: str, atype: DelegationType):
+        """
+        convert from a JSON representation producing a Delegations object. Returns None
+        if json_str is None
+        :param json_str:
+        :param atype: one of DelegationType
+        :return:
+        """
+        if json_str is None or len(json_str) == 0 or json_str == ABCPropertyGraphConstants.NEO4j_NONE:
+            return None
+        ds = Delegations(atype=atype)
+        json_dict = json.loads(json_str)
+        for k, v in json_dict.items():
+            if ABCPropertyGraphConstants.FIELD_POOL_ID in v.keys():
+                # single element pool or pool definition
+                if v[ABCPropertyGraphConstants.FIELD_POOL_ID] == ABCPropertyGraphConstants.SINGLE_POOL_NAME:
+                    format = DelegationFormat.SinglePool
+                    pool_id = None
+                else:
+                    format = DelegationFormat.PoolDefinition
+                    pool_id = v[ABCPropertyGraphConstants.FIELD_POOL_ID]
+                if atype == DelegationType.CAPACITY:
+                    caporlabdict = v[ABCPropertyGraphConstants.FIELD_CAPACITIES]
+                    caporlab = Capacities().set_fields(**caporlabdict)
+                else:
+                    caporlabdict = v[ABCPropertyGraphConstants.FIELD_LABELS]
+                    caporlab = Labels().set_fields(**caporlabdict)
+            elif ABCPropertyGraphConstants.FIELD_POOL in v.keys():
+                # pool reference
+                format = DelegationFormat.PoolReference
+                pool_id = v[ABCPropertyGraphConstants.FIELD_POOL]
+                caporlab = None
+            else:
+                raise DelegationException(msg='Invalid dictionary format when parsing delegation from JSON')
+            d = Delegation(atype=atype, delegation_id=k, aformat=format, pool_id=pool_id)
+            if caporlab is not None:
+                d.set_details(caporlab)
+            ds.add_delegations(d)
+        return ds
 
     def __repr__(self):
-        return f"{self.delegations}"
+        return f"{self.type} consisting of {self.delegations}"
 
 
 class Pool:
     """
     Label and capacity pools. Each pool knows which node defined it and which nodes it
-    applies to and can be set/queried for that information.
+    applies to and can be set/queried for that information. These structures help
+    transform ARM into ADM, but themselves don't serialize.
     """
     def __init__(self, *, atype: DelegationType, pool_id: str, delegation_id: str = None,
                  defined_on: str = None, defined_for: List[str] = None):
@@ -311,9 +322,11 @@ def __init__(self, *, atype: DelegationType, pool_id: str, delegation_id: str =
         self.on_ = defined_on
         self.delegation_id = delegation_id
         if defined_for is None:
-            self.for_ = []
+            self.for_ = set()
         else:
-            self.for_ = defined_for
+            self.for_ = set(defined_for)
+        if defined_on in self.for_:
+            self.for_.remove(defined_on)
         self.pool_id = pool_id
         self.pool_details = None
 
@@ -335,7 +348,7 @@ def set_defined_for(self, node_id_list: List[str]) -> None:
         assert node_id_list is not None
         assert len(node_id_list) != 0
 
-        self.for_ = node_id_list
+        self.for_ = set(node_id_list)
 
     def add_defined_for(self, node_ids: str or List) -> None:
         """
@@ -346,105 +359,25 @@ def add_defined_for(self, node_ids: str or List) -> None:
         """
         assert node_ids is not None
         if isinstance(node_ids, str):
-            self.for_.append(node_ids)
+            self.for_.add(node_ids)
         elif isinstance(node_ids, list):
-            self.for_.extend(node_ids)
+            self.for_.update(node_ids)
 
-    def set_pool_details(self, pool_dict: Dict) ->None:
+    def set_pool_details(self, caporlab: Capacities or Labels) ->None:
         """
-        set details of the pool dictionary, removing delegation
-        identifier field from dictionary
-        :param pool_dict:
+        set details of the pool to a Capacities or Labels object
+        :param caporlab:
         :return:
         """
-        assert pool_dict is not 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
+        assert (isinstance(caporlab, Labels) or isinstance(caporlab, Capacities))
+        self.pool_details = caporlab
 
-    def set_pool_details_from_labels(self, l: Labels) -> None:
+    def get_pool_details(self) -> Capacities or Labels:
         """
-        Set details from a Labels object
-        :param l:
+        Return label or capacity pool details as Capacities or Labels object
         :return:
         """
-        assert l is not None
-        assert self.type is not DelegationType.CAPACITY
-        self.pool_details = l.to_dict()
-
-    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 get_pool_details(self) -> Dict or None:
-        """
-        Return label or capacity pool details as a dictionary
-        along with pool identifier.
-        :return:
-        """
-        # 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)
+        return self.pool_details
 
     def set_delegation_id(self, *, delegation_id: str) -> None:
         assert delegation_id is not None
@@ -469,7 +402,7 @@ def is_defined_for(self, node_id: str) -> bool:
     def get_defined_on(self) -> str:
         return self.on_
 
-    def get_defined_for(self) -> List[str]:
+    def get_defined_for(self) -> Set[str]:
         return self.for_
 
     def get_pool_id(self) -> str:
@@ -492,48 +425,17 @@ def validate_pool(self):
             raise PoolException(f"Pool {self.pool_id} is not defined on any node")
         if self.get_defined_for() is None or len(self.get_defined_for()) == 0:
             raise PoolException(f"Pool {self.pool_id} is not mentioned on any nodes")
-        if self.get_defined_on() not in self.get_defined_for():
-            raise PoolException(f"Pool {self.pool_id} is not mentioned on the node where it is defined")
         if self.get_pool_details() is None:
             raise PoolException(f"Pool {self.pool_id} does not have any resource details")
 
-    @staticmethod
-    def ispoolmention(d: Dict) -> bool:
-        """
-        This method helps distinguish pool mention from capacity or label delegations by
-        looking for 'pool' field in a dictionary.
-        :param self:
-        :param d:
-        :return:
-        """
-        assert d is not None
-        if d.get(ABCPropertyGraphConstants.FIELD_POOL, None) is not None:
-            return True
-        return False
-
-    @staticmethod
-    def ispooldefinition(d: Dict) -> bool:
-        """
-        This method helps distinguish pool definitions from capacity or label delegations
-        by looking for 'label_pool' or 'capacity_pool' fields in the dictionary.
-        :param self:
-        :param d:
-        :return:
-        """
-        assert d is not None
-        if d.get(ABCPropertyGraphConstants.FIELD_CAPACITY_POOL, None) is not None or \
-                d.get(ABCPropertyGraphConstants.FIELD_LABEL_POOL, None) is not None:
-            return True
-        return False
-
     def __repr__(self) -> str:
         return f"{self.type} pool {self.pool_id} delegated to {self.delegation_id}: " \
                f"{self.on_}=> {self.for_} with {self.pool_details} "
 
 
-class ARMPools:
+class Pools:
     """
-    Map between node ids, pools and delegations
+    Define/extract multiple pools for a given ARM, ADM or CBM model
     """
     def __init__(self, atype: DelegationType):
         self.pool_by_id = {}
@@ -576,7 +478,7 @@ def build_index_by_delegation_id(self) -> None:
 
     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
+        return a list of Pool(s) for a delegation. Raises PoolException if the index based on
         delegation ids have not been built yet
         :param delegation_id:
         :return:
@@ -594,8 +496,73 @@ def add_pool(self, *, pool: Pool) -> None:
         """
         assert pool is not None
         assert pool.get_pool_id() is not None
+        if pool.get_pool_type() != self.pool_type:
+            raise PoolException(msg=f'Pool type {pool.get_pool_type()} does not match Pools '
+                                    f'container type {self.pool_type}')
         self.pool_by_id[pool.get_pool_id()] = pool
 
+    def incorporate_delegation(self, *, node_id: str, deleg: Delegations):
+        """
+        Take a Delegations object (e.g. extracted from a model element) and
+        add its information to construct pools. Ignores single element pools.
+        Node id is the id of the node where it was found.
+        :param node_id:
+        :param deleg:
+        :return:
+        """
+        assert node_id is not None
+        assert deleg is not None
+        if deleg.type != self.pool_type:
+            raise PoolException(msg=f'Delegation type {deleg.type} does not match '
+                                    f'Pools container type {self.pool_type}')
+        # ignore single element pools in delegations
+        for k, d in deleg.delegations.items():
+            if d.get_format() == DelegationFormat.SinglePool:
+                continue
+            p = self.get_pool_by_id(pool_id=d.get_pool_name())
+            if d.get_format() == DelegationFormat.PoolDefinition:
+                if p.get_defined_on() is not None:
+                    raise PoolException(msg=f'Pool {d.get_pool_name} has already been defined')
+                p.set_defined_on(node_id)
+                p.set_pool_details(d.get_details())
+                p.set_delegation_id(delegation_id=d.get_delegation_id())
+            else:
+                # PoolReference
+                p.add_defined_for(node_id)
+                p.set_delegation_id(delegation_id=d.get_delegation_id())
+
+    def generate_delegations_by_node_id(self) -> Dict[str, Delegations]:
+        """
+        Produce a dictionary of delegations objects indexed
+        by node id they pertain to (to support serialization onto the model).
+        :return:
+        """
+        ret = dict()
+        if self.pools_by_delegation is None:
+            return ret
+        for delegation_id, pool_list in self.pools_by_delegation.items():
+            for pool in pool_list:
+                # pool definition
+                pd = Delegation(atype=self.pool_type, delegation_id=delegation_id,
+                                aformat=DelegationFormat.PoolDefinition, pool_id=pool.get_pool_id())
+                pd.set_details(pool.get_pool_details())
+                node = pool.get_defined_on()
+                ds = ret.get(node, None)
+                if ds is None:
+                    ret[node] = Delegations(atype=self.pool_type)
+                    ds = ret[node]
+                ds.add_delegations(pd)
+                # pool references
+                for node in pool.get_defined_for():
+                    pr = Delegation(atype=self.pool_type, delegation_id=delegation_id,
+                                    aformat=DelegationFormat.PoolReference, pool_id=pool.get_pool_id())
+                    ds = ret.get(node, None)
+                    if ds is None:
+                        ret[node] = Delegations(atype=self.pool_type)
+                        ds = ret[node]
+                    ds.add_delegations(pr)
+        return ret
+
     def get_delegation_ids(self) -> Set:
         """
         return a set of all delegation ids
diff --git a/fim/user/__init__.py b/fim/user/__init__.py
index 3038407..be89d69 100644
--- a/fim/user/__init__.py
+++ b/fim/user/__init__.py
@@ -32,4 +32,4 @@
 Delegation = dlg.Delegation
 DelegationType = dlg.DelegationType
 Pool = dlg.Pool
-ARMPools = dlg.ARMPools
+Pools = dlg.Pools
diff --git a/fim/user/component.py b/fim/user/component.py
index fa43224..f442cf4 100644
--- a/fim/user/component.py
+++ b/fim/user/component.py
@@ -33,6 +33,7 @@
 from .model_element import ModelElement, ElementType
 from .interface import Interface
 from .switch_fabric import SwitchFabric, SFLayer
+from ..slivers.capacities_labels import Labels
 from ..slivers.attached_components import ComponentSliver, ComponentType
 from ..slivers.component_catalog import ComponentCatalog
 
@@ -51,7 +52,7 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any,
                  etype: ElementType = ElementType.EXISTING, model: str = None,
                  ctype: ComponentType = None, parent_node_id: str = None,
                  switch_fabric_node_id: str = None, interface_node_ids: List[str] = None,
-                 **kwargs):
+                 interface_labels: List[Labels] = None, **kwargs):
         """
         Don't call this yourself, use Node.add_component(). Instantiates components based on
         catalog resource file.
@@ -64,6 +65,7 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any,
         :param parent_node_id: node_id of the parent Node (for new components)
         :param switch_fabric_node_id: node id of switch fabric if one needs to be added (for substrate models only)
         :param interface_node_ids: a list of node ids for expected interfaces (for substrate models only)
+        :param interface_labels: a list of Labels structure to associate with each interface
         """
 
         assert name is not None
@@ -81,14 +83,17 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any,
                 raise RuntimeError("Model and component type must be specified for new components")
             if str(topo.__class__) == "<class 'fim.user.topology.SubstrateTopology'>" and \
                     (ctype == ComponentType.SharedNIC or ctype == ComponentType.SmartNIC) and \
-                    (switch_fabric_node_id is None or interface_node_ids is None):
+                    (switch_fabric_node_id is None or interface_node_ids is None or interface_labels is None):
                 raise RuntimeError('For substrate topologies and components with network interfaces '
-                                   'static switch_fabric node id and interface node ids must be specified')
+                                   'static switch_fabric node id, interface node ids and interface labels'
+                                   '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,
-                                                  interface_node_ids=interface_node_ids)
+                                                  interface_node_ids=interface_node_ids,
+                                                  interface_labels=interface_labels)
             comp_sliver.node_id = node_id
             comp_sliver.set_properties(**kwargs)
 
diff --git a/fim/user/model_element.py b/fim/user/model_element.py
index 38073cb..6ddf2a1 100644
--- a/fim/user/model_element.py
+++ b/fim/user/model_element.py
@@ -88,9 +88,11 @@ def unset_property(self, pname: str):
 
     def __repr__(self):
         labels, node_props = self.topo.graph_model.get_node_properties(node_id=self.node_id)
+        # filter out some properties we don't need
         node_props.pop(ABCPropertyGraph.GRAPH_ID)
         if str(self.topo.__class__) == "<class 'fim.user.topology.ExperimentTopology'>":
             node_props.pop(ABCPropertyGraph.NODE_ID)
+            node_props.pop(ABCPropertyGraph.PROP_STITCH_NODE)
         ret = str(labels) + str(node_props)
         return ret
 
diff --git a/fim/user/node.py b/fim/user/node.py
index c90d936..24a136e 100644
--- a/fim/user/node.py
+++ b/fim/user/node.py
@@ -144,8 +144,8 @@ def list_properties() -> Tuple[str]:
         return tuple(NodeSliver.list_properties())
 
     def add_component(self, *, name: str, node_id: str = None, ctype: ComponentType,
-                      model: str,  switch_fabric_node_id: str = None, interface_node_ids=None,
-                      **kwargs) -> Component:
+                      model: str,  switch_fabric_node_id: str=None, interface_node_ids=None,
+                      interface_labels=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.
@@ -154,8 +154,9 @@ def add_component(self, *, name: str, node_id: str = None, ctype: ComponentType,
         :param ctype:
         :param model:
         :param name:
-        :param switch_fabric_node_id:
-        :param interface_node_ids:
+        :param switch_fabric_node_id: switch fabric identifier for substrate models
+        :param interface_node_ids: interface identifiers for substrate models
+        :param interface_labels: list of labels for interfaces in substrate models
         :param kwargs: additional properties of the component
         :return:
         """
@@ -166,15 +167,17 @@ def add_component(self, *, name: str, node_id: str = None, ctype: ComponentType,
         # 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,
-                      interface_node_ids=interface_node_ids, parent_node_id=self.node_id, **kwargs)
+                      interface_node_ids=interface_node_ids, interface_labels=interface_labels,
+                      parent_node_id=self.node_id, **kwargs)
         return c
 
-    def add_switch_fabric(self, *, name: str, node_id: str = None, layer: SFLayer):
+    def add_switch_fabric(self, *, name: str, node_id: str = None, layer: SFLayer, **kwargs):
         """
         Add a switch fabric to node (mostly needed in substrate topologies)
         :param name:
         :param node_id:
         :param layer:
+        :param kwargs:
         :return:
         """
         assert name is not None
@@ -182,7 +185,7 @@ def add_switch_fabric(self, *, name: str, node_id: str = None, layer: SFLayer):
         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)
+                          etype=ElementType.NEW, topo=self.topo, **kwargs)
         return sf
 
     def remove_component(self, name: str) -> None:
diff --git a/fim/user/switch_fabric.py b/fim/user/switch_fabric.py
index 8375db8..cd16141 100644
--- a/fim/user/switch_fabric.py
+++ b/fim/user/switch_fabric.py
@@ -40,7 +40,8 @@ class SwitchFabric(ModelElement):
     """
     def __init__(self, *, name: str, node_id: str = None, topo: Any,
                  etype: ElementType = ElementType.EXISTING,
-                 layer: SFLayer = SFLayer.L2, parent_node_id: str = None):
+                 layer: SFLayer = SFLayer.L2, parent_node_id: str = None,
+                 **kwargs):
         """
         Create a new switch fabric. Don't call this yourself, call Node.add_switch_fabric().
         NodeID is mandatory in substrate models.
@@ -50,6 +51,7 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any,
         :param layer: layer of the switch fabric (defaults to L2)
         :param parent_node_id: id of parent node for new nodes
         :param etype:
+        :param kwargs:
         """
         assert name is not None
         assert topo is not None
@@ -72,6 +74,7 @@ def __init__(self, *, name: str, node_id: str = None, topo: Any,
             sfsliver.set_name(name)
             sfsliver.set_type(SFType.SwitchFabric)
             sfsliver.set_layer(layer)
+            sfsliver.set_properties(**kwargs)
             self.topo.graph_model.add_switch_fabric_sliver(parent_node_id=parent_node_id, switch_fabric=sfsliver)
         else:
             assert node_id is not None
diff --git a/fim/user/topology.py b/fim/user/topology.py
index 43aafbf..5bd6668 100644
--- a/fim/user/topology.py
+++ b/fim/user/topology.py
@@ -24,7 +24,7 @@
 #
 # Author: Ilya Baldin (ibaldin@renci.org)
 
-from abc import ABC, abstractmethod
+from abc import ABC
 from typing import List, Dict, Tuple, Any
 import enum
 
@@ -42,8 +42,8 @@
 from ..graph.slices.abc_asm import ABCASMPropertyGraph
 from ..graph.slices.networkx_asm import NetworkxASM
 from ..graph.abc_property_graph import ABCPropertyGraph
-from ..graph.resources.networkx_adm import NetworkXADMGraph, NetworkXGraphImporter
-from ..slivers.delegations import Delegation, ARMDelegations, ARMPools, DelegationType
+from ..graph.resources.networkx_arm import NetworkXARMGraph, NetworkXGraphImporter
+from ..slivers.delegations import Delegation, Delegations, Pools, DelegationType, DelegationFormat
 from fim.graph.resources.networkx_abqm import NetworkXAggregateBQM, NetworkXABQMFactory
 from fim.slivers.capacities_labels import Capacities, CapacityTuple
 
@@ -399,45 +399,48 @@ 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):
+    def as_arm(self):
         """
-        Return model 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 model as ARM graph built on top of internal model. Model is not cloned or copied,
+        rather recast into ARM, 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,
+        return NetworkXARMGraph(graph=self.graph_model,
                                 logger=self.graph_model.log)
 
     @staticmethod
-    def __copy_to_delegation(e: ModelElement, atype: DelegationType,
-                             delegation_id: str) -> Delegation or None:
+    def __copy_to_delegations(e: ModelElement, atype: DelegationType,
+                              delegation_id: str) -> Delegations or None:
         """
-        From a model element (node, components, switch_fabric, interface), copy
-        capacities or labels to delegation and return delegation)
+        From a model element (node, component, switch_fabric, interface), copy
+        capacities or labels to a Delegations and return Delegations or none if
+        no capacities or labels specified. Return None if element is a stitch node.
         :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 e.get_property("stitch_node"):
+            return None
         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
+        if caps_or_labels is not None:
+            d = Delegation(atype=atype, delegation_id=delegation_id, aformat=DelegationFormat.SinglePool)
+            d.set_details(caps_or_labels)
+            # encapsulate in Delegations - this is a single delegation case, no others will be added
+            ret = Delegations(atype=atype)
+            ret.add_delegations(d)
+            return ret
         return None
 
-    def single_delegation(self, *, delegation_id: str, label_pools: ARMPools, capacity_pools: ARMPools):
+    def single_delegation(self, *, delegation_id: str, label_pools: Pools, capacity_pools: Pools):
         """
         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.
+        into delegations. Pools must be specified externally. There is no way to specify on
+        the same node a single element delegation and a pool definition or reference.
         :param delegation_id:
         :param label_pools:
         :param capacity_pools:
@@ -446,42 +449,42 @@ def single_delegation(self, *, delegation_id: str, label_pools: ARMPools, capaci
         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)}
+        delegations_dicts = {DelegationType.CAPACITY: dict(),
+                             DelegationType.LABEL: dict()}
         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)
+                # delegate every NetworkNode and its component by copying their capacities and labels
+                # if present. Each delegations object is intended for one node.
+                delegations = self.__copy_to_delegations(e=n, atype=t,
+                                                         delegation_id=delegation_id)
+                if delegations is not None:
+                    delegations_dicts[t][n.node_id] = delegations
                 # components
                 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)
+                    delegations = self.__copy_to_delegations(e=c, atype=t,
+                                                             delegation_id=delegation_id)
+                    if delegations is not None:
+                        delegations_dicts[t][c.node_id] = delegations
                     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)
+                        delegations = self.__copy_to_delegations(e=i, atype=t,
+                                                                 delegation_id=delegation_id)
+                        if delegations is not None:
+                            delegations_dicts[t][i.node_id] = delegations
                 # switchfabrics (for eg switches)
                 for sf in n.switch_fabrics.values():
-                    delegation = self.__copy_to_delegation(e=sf, atype=t,
-                                                           delegation_id=delegation_id)
-                    if delegation is not None:
-                        delegations[t].add_delegation(delegation)
+                    delegations = self.__copy_to_delegations(e=sf, atype=t,
+                                                             delegation_id=delegation_id)
+                    if delegations is not None:
+                        delegations_dicts[t][sf.node_id] = delegations
                     for i in sf.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)
+                        delegations = self.__copy_to_delegations(e=i, atype=t,
+                                                                 delegation_id=delegation_id)
+                        if delegations is not None:
+                            delegations_dicts[t][i.node_id] = delegations
 
-            self.as_adm().annotate_delegations_and_pools(dels=delegations[t],
+            self.as_arm().annotate_delegations_and_pools(dels=delegations_dicts[t],
                                                          pools=pool_dict[t])
 
 
diff --git a/setup.py b/setup.py
index 53d6816..3b2eb0a 100644
--- a/setup.py
+++ b/setup.py
@@ -8,7 +8,7 @@
 
 setuptools.setup(
   name="fabric_fim",
-  version="0.40",
+  version="0.45",
   author="Ilya Baldin, Komal Thareja",
   description="FABRIC Information Model Library",
   url="https://github.com/fabric-testbed/InformationModel",
diff --git a/util/fim_util.py b/util/fim_util.py
index b3b5624..cb3e17f 100644
--- a/util/fim_util.py
+++ b/util/fim_util.py
@@ -52,6 +52,7 @@
 import sys
 import yaml
 import logging
+import traceback
 
 from fim.graph.abc_property_graph import ABCPropertyGraph
 from fim.graph.neo4j_property_graph import Neo4jPropertyGraph, Neo4jGraphImporter, PropertyGraphImportException
@@ -62,18 +63,6 @@
 FIM_CONFIG_YAML = "fim_config.yaml"
 
 
-class MyPlug:
-    """
-    Fake pluggable BQM broker class for FIM testing
-    """
-    def __init__(self):
-        print("Creating MyPlug")
-
-    def plug_produce_bqm(self, *, cbm: ABCPropertyGraph, **kwargs) -> ABCPropertyGraph:
-        print(f"Producing CBM as BQM {kwargs}")
-        return cbm
-
-
 def load_file(*, filename, graph_id, neo4j_config):
     """
     load graph from file
@@ -162,89 +151,6 @@ def save_graph(*, outfile, graph_id, neo4j_config):
         f.write(graph_string)
 
 
-def test_graph(*, graph_ids, neo4j_config):
-    """
-    crutch for running test functions quickly
-    :param graph_id:
-    :param neo4j_config:
-    :return:
-    """
-    neo4j_graph_importer = Neo4jGraphImporter(url=neo4j_config["url"], user=neo4j_config["user"],
-                                              pswd=neo4j_config["pass"],
-                                              import_host_dir=neo4j_config["import_host_dir"],
-                                              import_dir=neo4j_config["import_dir"])
-
-    r = PluggableRegistry()
-    #r.register_pluggable(t=PluggableType.Broker, p=MyPlug)
-
-    cbm = Neo4jCBMGraph(importer=neo4j_graph_importer)
-    print(f"CBM graph ID is {cbm.graph_id}")
-
-    net_graph = Neo4jARMGraph(graph=Neo4jPropertyGraph(graph_id=graph_ids[0], importer=neo4j_graph_importer))
-    #for node_id in net_graph.list_all_node_ids():
-    #    print(net_graph.get_delegations(node_id=node_id, delegation_type=DelegationType.LABEL))
-    #    print(net_graph.get_delegations(node_id=node_id, delegation_type=DelegationType.CAPACITY))
-
-    net_adms = net_graph.generate_adms()
-    print(f"Delegation graph(s) for net AM {net_adms}")
-    net_adm = net_adms['del1']
-    print("Merging Net ADM")
-    cbm.merge_adm(adm=net_adm)
-    for graph in net_adms.values():
-        graph.delete_graph()
-
-    arm_graph = Neo4jARMGraph(graph=Neo4jPropertyGraph(graph_id=graph_ids[1], importer=neo4j_graph_importer))
-    am_adms = arm_graph.generate_adms()
-    print(f"Delegation graph(s) for site AM {am_adms}")
-    # use del1
-    am_adm = am_adms['del1']
-    to_unmerge = am_adm.graph_id
-    print("Merging AM ADM")
-    cbm.merge_adm(adm=am_adm)
-
-    # unmerge then merge again
-    print('Unmerging ' + to_unmerge)
-    cbm.unmerge_adm(graph_id=to_unmerge)
-
-    print("Merging AM ADM again")
-    cbm.merge_adm(adm=am_adm)
-
-    snapshot = cbm.snapshot()
-
-    cbm.unmerge_adm(graph_id=to_unmerge)
-
-    cbm.rollback(graph_id=snapshot)
-
-    for graph in am_adms.values():
-        graph.delete_graph()
-
-    arm_graph = Neo4jARMGraph(graph=Neo4jPropertyGraph(graph_id=graph_ids[2], importer=neo4j_graph_importer))
-    am_adms = arm_graph.generate_adms()
-    print(f"Delegation graph(s) for site AM {am_adms}")
-    # use del1
-    am_adm = am_adms['del1']
-    print("Merging AM ADM")
-    cbm.merge_adm(adm=am_adm)
-    for graph in am_adms.values():
-        graph.delete_graph()
-
-    arm_graph = Neo4jARMGraph(graph=Neo4jPropertyGraph(graph_id=graph_ids[3], importer=neo4j_graph_importer))
-    am_adms = arm_graph.generate_adms()
-    print(f"Delegation graph(s) for site AM {am_adms}")
-    # use del1
-    am_adm = am_adms['del1']
-    print("Merging AM ADM")
-    cbm.merge_adm(adm=am_adm)
-    for graph in am_adms.values():
-        graph.delete_graph()
-
-    #for node_id in cbm.list_all_node_ids():
-    #    print(cbm.get_delegations(node_id=node_id, delegation_type=DelegationType.LABEL, adm_id=am_adm.graph_id))
-    #    print(cbm.get_delegations(node_id=node_id, delegation_type=DelegationType.CAPACITY, adm_id=am_adm.graph_id))
-
-    cbm.get_bqm(some=5)
-
-
 if __name__ == "__main__":
 
     parser = argparse.ArgumentParser()
@@ -258,8 +164,6 @@ def test_graph(*, graph_ids, neo4j_config):
     operations.add_argument("-e", "--enumerate", action="store_true",
                             help="enumerate all nodes in a graph in specified file"
                                  " assigning them unique GUIDs and save (requires -f and -o)")
-    operations.add_argument("-t", "--test", action="store_true",
-                            help="invoke test action (-g)")
     operations.add_argument("-s", "--save", action="store_true",
                             help="save a specific graph into a file (requires -o and -g)")
     parser.add_argument("-c", "--config", action="store", default=FIM_CONFIG_YAML,
@@ -305,6 +209,7 @@ def test_graph(*, graph_ids, neo4j_config):
                 sys.exit(-1)
         except Exception as e:
             print(f"Unable to load file {args.file} to Neo4j due to {e.args}", file=sys.stderr)
+            traceback.print_exc(file=sys.stderr)
             sys.exit(-1)
     elif args.wipe:
         # wipe one graph or whole database
@@ -333,15 +238,5 @@ def test_graph(*, graph_ids, neo4j_config):
 
         print(f"Saving graph {graph} into file {outfile}")
         save_graph(outfile=outfile, graph_id=graph, neo4j_config=yaml_config["neo4j"])
-    elif args.test:
-        # some test action
-        graph = args.graph
-        print(f"Running test command on {graph}")
-        #test_graph(graph_id=graph, neo4j_config=yaml_config["neo4j"])
-        test_graph(graph_ids=["936bbc55-b3c0-43b7-aee5-fbfbdeffd513",
-                              "0394b803-e0ec-41c4-b7e7-4769c9d9873b",
-                              "24f6ce3f-5860-4dcb-bf88-acc77a663f8f",
-                              "e8f66ca9-90d3-4b0f-8f55-2c734a2b60ab"],
-                   neo4j_config=yaml_config["neo4j"])
     else:
         print("Please specify one of -h, -l, -e or -w", file=sys.stderr)


Test patch:
diff --git a/test/delegation_label_test.py b/test/delegation_label_test.py
index 216fd7a..dd28864 100644
--- a/test/delegation_label_test.py
+++ b/test/delegation_label_test.py
@@ -1,12 +1,13 @@
 import unittest
-from typing import Dict
 
 from fim.slivers.capacities_labels import Labels, Capacities
+from fim.slivers.delegations import Delegation, Delegations, DelegationType, \
+    DelegationFormat, DelegationException, Pool, Pools, PoolException
 
 
 class DelegationTests(unittest.TestCase):
 
-    def testDelegationAssignment(self):
+    def testCapacityAssignment(self):
         c = Capacities()
 
         c.set_fields(cpu=1, core=2)
@@ -15,8 +16,7 @@ def testDelegationAssignment(self):
         self.assertEqual(c.core, 2)
 
         s = '{"core": 32, "disk": 3000, "ram": 384, "unit": 1}'
-        c1 = Capacities()
-        c1.from_json(s)
+        c1 = Capacities.from_json(s)
         self.assertEqual(c1.unit, 1)
         self.assertEqual(c1.ram, 384)
         # because we sort the dict
@@ -33,11 +33,76 @@ def testLabelAssignment(self):
         s = '{"bdf": ["0000:41:00.0", "0000:41:00.1"], ' \
             '"ipv4": ["192.168.1.1", "192.168.1.2"], ' \
             '"vlan": ["100", "101", "102"]}'
-        l1 = Labels()
-        l1.from_json(s)
+        l1 = Labels.from_json(s)
         self.assertEqual(l1.vlan, ["100", "101", "102"])
         print(s)
         print(l1.to_json())
         self.assertEqual(l1.to_json(), s)
 
+    def testDelegationsSerDes(self):
+        d1 = Delegation(atype=DelegationType.LABEL, aformat=DelegationFormat.SinglePool,
+                       delegation_id='del1')
+        d2 = Delegation(atype=DelegationType.LABEL, aformat=DelegationFormat.PoolDefinition,
+                        delegation_id='del2', pool_id='pool1')
+        d3 = Delegation(atype=DelegationType.LABEL, aformat=DelegationFormat.PoolReference,
+                        delegation_id='del3', pool_id='pool1')
+        ds = Delegations(atype=DelegationType.LABEL)
+        d1.set_details(Labels().set_fields(vlan_range='1-100'))
+        d2.set_details(Labels().set_fields(vlan_range='101-200'))
+        with self.assertRaises(DelegationException) as de:
+            d2.set_details(Capacities().set_fields(unit=1))
+
+        with self.assertRaises(DelegationException) as de:
+            d3.set_details(Labels().set_fields(vlan_range='something'))
+
+        ds.add_delegations(d1)
+        ds.add_delegations(d2, d3)
+
+        json_str = ds.to_json()
+
+        #print(json_str)
+
+        ds1 = Delegations.from_json(json_str=json_str, atype=DelegationType.LABEL)
+
+        self.assertEqual(ds1.delegations['del1'].get_details().vlan_range, '1-100')
+        self.assertEqual(ds1.delegations['del2'].get_format(), DelegationFormat.PoolDefinition)
+        self.assertEqual(ds1.delegations['del3'].get_format(), DelegationFormat.PoolReference)
+
+        with self.assertRaises(AssertionError) as de:
+            d3 = Delegation(atype=DelegationType.CAPACITY, aformat=DelegationFormat.PoolReference,
+                            delegation_id='del3', pool_id='pool1')
+            ds.add_delegations(d3)
+
+    def testPools(self):
+        p1 = Pool(atype=DelegationType.LABEL, pool_id='pool1', delegation_id='del1',
+                  defined_on='node1', defined_for=['node2', 'node3'])
+        p1.set_pool_details(Labels().set_fields(vlan_range='1-100'))
+        p2 = Pool(atype=DelegationType.LABEL, pool_id='pool2', delegation_id='del2',
+                  defined_on='node2', defined_for=['node1', 'node4', 'node5'])
+        p2.set_pool_details(Labels().set_fields(vlan_range='101-200'))
+        ps = Pools(atype=DelegationType.CAPACITY)
+        with self.assertRaises(PoolException) as pe:
+            ps.add_pool(pool=p1)
+        ps = Pools(atype=DelegationType.LABEL)
+        ps.add_pool(pool=p1)
+        ps.add_pool(pool=p2)
+        ps.build_index_by_delegation_id()
+        ps.validate_pools()
+
+        delegs = ps.generate_delegations_by_node_id()
+        d1 = Delegation(atype=DelegationType.LABEL, delegation_id='del3', aformat=DelegationFormat.SinglePool)
+        d1.set_details(Labels().set_fields(ipv4='192.168.1.1-192.168.1.10'))
+        delegs['node1'].add_delegations(d1)
+
+        print(delegs)
+
+        ps1 = Pools(atype=DelegationType.LABEL)
+
+        for n, d in delegs.items():
+            ps1.incorporate_delegation(deleg=d, node_id=n)
+
+        self.assertEqual(ps1.get_pool_by_id(pool_id='pool2').get_delegation_id(), 'del2')
+        self.assertEqual(ps1.get_pool_by_id(pool_id='pool2').get_pool_details().vlan_range, '101-200')
+        print(ps1)
+
 
diff --git a/test/networkxx_pg_disjoint_test.py b/test/networkxx_pg_disjoint_test.py
index b4f6321..51245f0 100644
--- a/test/networkxx_pg_disjoint_test.py
+++ b/test/networkxx_pg_disjoint_test.py
@@ -2,9 +2,10 @@
 from typing import Dict
 
 import uuid
+import json
 
 import fim.graph.networkx_property_graph_disjoint as nx_graph
-from fim.graph.abc_property_graph import ABCPropertyGraph, ABCPropertyGraphConstants
+from fim.graph.abc_property_graph import ABCPropertyGraph, ABCPropertyGraphConstants, GraphFormat
 
 
 class NetworkXPropertyGraphDisjointTests(unittest.TestCase):
@@ -277,3 +278,15 @@ def test_string_serialize(self):
         self.assertTrue((new_favs.get('Worker1'), None) is not None)
         self.assertTrue((new_favs.get('GPU1'), None) is not None)
         self.assertEqual(new_favs['Worker1'], favs['Worker1'])
+
+    def test_json_serialize(self):
+        graph_string = self.g.serialize_graph(format=GraphFormat.JSON_NODELINK)
+        json_object = json.loads(graph_string)
+        assert(json_object["directed"] is False)
+        assert(len(json_object["nodes"]) == 17)
+
+    def test_cytoscape_serialize(self):
+        graph_string = self.g.serialize_graph(format=GraphFormat.CYTOSCAPE)
+        json_object = json.loads(graph_string)
+        assert(json_object["directed"] is False)
+        assert(len(json_object["elements"]["nodes"]) == 17)
diff --git a/test/networkxx_pg_test.py b/test/networkxx_pg_test.py
index 9a8260a..b383619 100644
--- a/test/networkxx_pg_test.py
+++ b/test/networkxx_pg_test.py
@@ -2,9 +2,10 @@
 from typing import Dict
 
 import uuid
+import json
 
 import fim.graph.networkx_property_graph as nx_graph
-from fim.graph.abc_property_graph import ABCPropertyGraphConstants, ABCPropertyGraph
+from fim.graph.abc_property_graph import ABCPropertyGraphConstants, ABCPropertyGraph, GraphFormat
 
 
 class NetworkXPropertyGraphTests(unittest.TestCase):
@@ -299,4 +300,16 @@ def test_string_serialize(self):
         self.assertTrue((new_favs.get('GPU1'), None) is not None)
         self.assertEqual(new_favs['Worker1'], favs['Worker1'])
 
+    def test_json_serialize(self):
+        graph_string = self.g.serialize_graph(format=GraphFormat.JSON_NODELINK)
+        json_object = json.loads(graph_string)
+        assert(json_object["directed"] is False)
+        assert(len(json_object["nodes"]) == 17)
+
+    def test_cytoscape_serialize(self):
+        graph_string = self.g.serialize_graph(format=GraphFormat.CYTOSCAPE)
+        json_object = json.loads(graph_string)
+        assert(json_object["directed"] is False)
+        assert(len(json_object["elements"]["nodes"]) == 17)
+
 
diff --git a/test/substrate_topology_test.py b/test/substrate_topology_test.py
index fb2132a..f29eea4 100644
--- a/test/substrate_topology_test.py
+++ b/test/substrate_topology_test.py
@@ -42,71 +42,74 @@ def testRENCSiteAd(self):
         gpuw = self.topo.add_node(name='renc-w1',
                                   model=worker_model, site=site,
                                   node_id='HX6VQ53',
-                                  ntype=f.NodeType.Server, capacities=gpu_worker_cap)
+                                  ntype=f.NodeType.Server,
+                                  capacities=gpu_worker_cap)
+
         fnw = self.topo.add_node(name='renc-w2',
                                  model=worker_model, site=site,
                                  node_id='HX7LQ53',
-                                 ntype=f.NodeType.Server, capacities=network_worker_cap)
+                                 ntype=f.NodeType.Server,
+                                 capacities=network_worker_cap)
         snw = self.topo.add_node(name='renc-w3',
                                  model=worker_model, site=site,
                                  node_id='HX7KQ53',
-                                 ntype=f.NodeType.Server, capacities=network_worker_cap)
+                                 ntype=f.NodeType.Server,
+                                 capacities=network_worker_cap)
 
         #
         # Disks
         #
 
+        nvme_cap = f.Capacities().set_fields(unit=1, disk=1000)
         gpu_nvme1 = gpuw.add_component(name=gpuw.name + '-nvme1', model='P4510',
                                        node_id='PHLJ016004CC1P0FGN',
                                        ctype=f.ComponentType.NVME,
-                                       capacities=f.Capacities().set_fields(unit=1, disk=1000),
+                                       capacities=nvme_cap,
                                        labels=f.Labels().set_fields(bdf='0000:21:00.0'))
         gpu_nvme2 = gpuw.add_component(name=gpuw.name + '-nvme2', model='P4510',
                                        node_id='PHLJ0160047K1P0FGN',
                                        ctype=f.ComponentType.NVME,
-                                       capacities=f.Capacities().set_fields(unit=1, disk=1000),
+                                       capacities=nvme_cap,
                                        labels=f.Labels().set_fields(bdf='0000:22:00.0'))
-
         fn_nvme1 = fnw.add_component(name=fnw.name + '-nvme1', model='P4510',
                                      node_id='PHLJ015301TU1P0FGN',
                                      ctype=f.ComponentType.NVME,
-                                     capacities=f.Capacities().set_fields(unit=1, disk=1000),
+                                     capacities=nvme_cap,
                                      labels=f.Labels().set_fields(bdf='000:21:00.0'))
-        fn_nvme1 = fnw.add_component(name=fnw.name + '-nvme2', model='P4510',
+        fn_nvme2 = fnw.add_component(name=fnw.name + '-nvme2', model='P4510',
                                      node_id='PHLJ016004CK1P0FGN',
                                      ctype=f.ComponentType.NVME,
-                                     capacities=f.Capacities().set_fields(unit=1, disk=1000),
+                                     capacities=nvme_cap,
                                      labels=f.Labels().set_fields(bdf='0000:22:00.0'))
-        fn_nvme1 = fnw.add_component(name=fnw.name + '-nvme3', model='P4510',
+        fn_nvme3 = fnw.add_component(name=fnw.name + '-nvme3', model='P4510',
                                      node_id='PHLJ016002P61P0FGN',
                                      ctype=f.ComponentType.NVME,
-                                     capacities=f.Capacities().set_fields(unit=1, disk=1000),
+                                     capacities=nvme_cap,
                                      labels=f.Labels().set_fields(bdf='0000:23:00.0'))
-        fn_nvme1 = fnw.add_component(name=fnw.name + '-nvme4', model='P4510',
+        fn_nvme4 = fnw.add_component(name=fnw.name + '-nvme4', model='P4510',
                                      node_id='PHLJ016004CL1P0FGN',
                                      ctype=f.ComponentType.NVME,
-                                     capacities=f.Capacities().set_fields(unit=1, disk=1000),
+                                     capacities=nvme_cap,
                                      labels=f.Labels().set_fields(bdf='0000:24:00.0'))
-
         sn_nvme1 = snw.add_component(name=snw.name + '-nvme1', model='P4510',
                                      node_id='PHLJ015301V81P0FGN',
                                      ctype=f.ComponentType.NVME,
-                                     capacities=f.Capacities().set_fields(unit=1, disk=1000),
+                                     capacities=nvme_cap,
                                      labels=f.Labels().set_fields(bdf='0000:21:00.0'))
-        sn_nvme1 = snw.add_component(name=snw.name + '-nvme2', model='P4510',
+        sn_nvme2 = snw.add_component(name=snw.name + '-nvme2', model='P4510',
                                      node_id='PHLJ0160047L1P0FGN',
                                      ctype=f.ComponentType.NVME,
-                                     capacities=f.Capacities().set_fields(unit=1, disk=1000),
+                                     capacities=nvme_cap,
                                      labels=f.Labels().set_fields(bdf='0000:22:00.0'))
-        sn_nvme1 = snw.add_component(name=snw.name + '-nvme3', model='P4510',
+        sn_nvme3 = snw.add_component(name=snw.name + '-nvme3', model='P4510',
                                      node_id='PHLJ016004CJ1P0FGN',
                                      ctype=f.ComponentType.NVME,
-                                     capacities=f.Capacities().set_fields(unit=1, disk=1000),
+                                     capacities=nvme_cap,
                                      labels=f.Labels().set_fields(bdf='0000:23:00.0'))
-        sn_nvme1 = snw.add_component(name=snw.name + '-nvme4', model='P4510',
+        sn_nvme4 = snw.add_component(name=snw.name + '-nvme4', model='P4510',
                                      node_id='PHLJ016004C91P0FGN',
                                      ctype=f.ComponentType.NVME,
-                                     capacities=f.Capacities().set_fields(unit=1, disk=1000),
+                                     capacities=nvme_cap,
                                      labels=f.Labels().set_fields(bdf='0000:24:00.0'))
 
         #
@@ -114,37 +117,38 @@ def testRENCSiteAd(self):
         # consistent string
         #
 
+        unit_cap = f.Capacities().set_fields(unit=1)
         gpu_gpu1 = gpuw.add_component(name=gpuw.name + '-gpu1', model='RTX6000',
                                       node_id=gpuw.node_id + '-gpu1',
                                       ctype=f.ComponentType.GPU,
-                                      capacities=f.Capacities().set_fields(unit=1),
+                                      capacities=unit_cap,
                                       labels=f.Labels().set_fields(bdf='0000:25:00.0'))
         gpu_gpu2 = gpuw.add_component(name=gpuw.name + '-gpu2', model='RTX6000',
                                       node_id=gpuw.node_id + '-gpu2',
                                       ctype=f.ComponentType.GPU,
-                                      capacities=f.Capacities().set_fields(unit=1),
+                                      capacities=unit_cap,
                                       labels=f.Labels().set_fields(bdf='0000:81:00.0'))
 
         fn_gpu1 = fnw.add_component(name=fnw.name + '-gpu1', model='Tesla T4',
                                     node_id=fnw.node_id + '-gpu1',
                                     ctype=f.ComponentType.GPU,
-                                    capacities=f.Capacities().set_fields(unit=1),
+                                    capacities=unit_cap,
                                     labels=f.Labels().set_fields(bdf='0000:25:00.0'))
         fn_gpu2 = fnw.add_component(name=fnw.name + '-gpu2', model='Tesla T4',
                                     node_id=fnw.node_id + '-gpu2',
                                     ctype=f.ComponentType.GPU,
-                                    capacities=f.Capacities().set_fields(unit=1),
+                                    capacities=unit_cap,
                                     labels=f.Labels().set_fields(bdf='0000:81:00.0'))
 
         sn_gpu1 = snw.add_component(name=snw.name + '-gpu1', model='Tesla T4',
                                     node_id=snw.node_id + '-gpu1',
                                     ctype=f.ComponentType.GPU,
-                                    capacities=f.Capacities().set_fields(unit=1),
+                                    capacities=unit_cap,
                                     labels=f.Labels().set_fields(bdf='0000:25:00.0'))
         sn_gpu2 = snw.add_component(name=snw.name + '-gpu2', model='Tesla T4',
                                     node_id=snw.node_id + '-gpu2',
                                     ctype=f.ComponentType.GPU,
-                                    capacities=f.Capacities().set_fields(unit=1),
+                                    capacities=unit_cap,
                                     labels=f.Labels().set_fields(bdf='0000:81:00.0'))
 
         #
@@ -157,9 +161,10 @@ def testRENCSiteAd(self):
                                         node_id=gpuw.node_id + '-shnic',
                                         switch_fabric_node_id=gpuw.node_id + '-shnic-sf',
                                         interface_node_ids=[mac_to_node_id('04:3F:72:B7:14:EC')],
+                                        interface_labels=[f.Labels().set_fields(mac='04:3F:72:B7:14:EC',
+                                                                                vlan_range='1-4096')],
                                         capacities=f.Capacities().set_fields(unit=1),
-                                        labels=f.Labels().set_fields(bdf='0000:e2:00.0',
-                                                                     mac='04:3F:72:B7:14:EC'),
+                                        labels=f.Labels().set_fields(bdf='0000:e2:00.0'),
                                         ctype=f.ComponentType.SharedNIC,
                                         details='Shared NIC: Mellanox Technologies MT28908 Family [ConnectX-6]')
 
@@ -167,9 +172,10 @@ def testRENCSiteAd(self):
                                       node_id=fnw.node_id + '-shnic',
                                       switch_fabric_node_id=fnw.node_id + '-shnic-sf',
                                       interface_node_ids=[mac_to_node_id('04:3F:72:B7:18:B4')],
+                                      interface_labels=[f.Labels().set_fields(mac='04:3F:72:B7:18:B4',
+                                                                              vlan_range='1-4096')],
                                       capacities=f.Capacities().set_fields(unit=1),
-                                      labels=f.Labels().set_fields(bdf='0000:e2:00.0',
-                                                                   mac='04:3F:72:B7:18:B4'),
+                                      labels=f.Labels().set_fields(bdf='0000:e2:00.0'),
                                       ctype=f.ComponentType.SharedNIC,
                                       details='Shared NIC: Mellanox Technologies MT28908 Family [ConnectX-6]')
 
@@ -177,9 +183,10 @@ def testRENCSiteAd(self):
                                       node_id=snw.node_id + '-shnic',
                                       switch_fabric_node_id=snw.node_id + '-shnic-sf',
                                       interface_node_ids=[mac_to_node_id('04:3F:72:B7:16:14')],
+                                      interface_labels=[f.Labels().set_fields(mac='04:3F:72:B7:16:14',
+                                                                              vlan_range='1-4096')],
                                       capacities=f.Capacities().set_fields(unit=1),
-                                      labels=f.Labels().set_fields(bdf='0000:e2:00.0',
-                                                                   mac='04:3F:72:B7:16:14'),
+                                      labels=f.Labels().set_fields(bdf='0000:e2:00.0'),
                                       ctype=f.ComponentType.SharedNIC,
                                       details='Shared NIC: Mellanox Technologies MT28908 Family [ConnectX-6]')
 
@@ -192,10 +199,14 @@ def testRENCSiteAd(self):
                                      switch_fabric_node_id=fnw.node_id + '-nic1-sf',
                                      interface_node_ids=[mac_to_node_id('04:3F:72:B7:15:74'),
                                                          mac_to_node_id('04:3F:72:B7:15:75')],
+                                     interface_labels=[f.Labels().set_fields(mac='04:3F:72:B7:15:74',
+                                                                             vlan_range='1-4096'),
+                                                       f.Labels().set_fields(mac='04:3F:72:B7:15:75',
+                                                                             vlan_range='1-4096')
+                                                       ],
                                      ctype=f.ComponentType.SmartNIC,
                                      capacities=f.Capacities().set_fields(unit=1),
-                                     labels=f.Labels().set_fields(bdf=['0000:41:00.0', '0000:41:00.1'],
-                                                                  mac=['04:3F:72:B7:15:74', '04:3F:72:B7:15:75']),
+                                     labels=f.Labels().set_fields(bdf=['0000:41:00.0', '0000:41:00.1']),
                                      details='Mellanox Technologies MT28908 Family [ConnectX-6]')
 
         fnw_nic2 = fnw.add_component(name=fnw.name + '-nic2', model='ConnectX-6',
@@ -203,10 +214,14 @@ def testRENCSiteAd(self):
                                      switch_fabric_node_id=fnw.node_id + '-nic2-sf',
                                      interface_node_ids=[mac_to_node_id('04:3F:72:B7:19:5C'),
                                                          mac_to_node_id('04:3F:72:B7:19:5D')],
+                                     interface_labels=[f.Labels().set_fields(mac='04:3F:72:B7:19:5C',
+                                                                             vlan_range='1-4096'),
+                                                       f.Labels().set_fields(mac='04:3F:72:B7:19:5D',
+                                                                             vlan_range='1-4096')
+                                                       ],
                                      ctype=f.ComponentType.SmartNIC,
                                      capacities=f.Capacities().set_fields(unit=1),
-                                     labels=f.Labels().set_fields(bdf=['0000:a1:00.0', '0000:a1:00.1'],
-                                                                  mac=['04:3F:72:B7:19:5C', '04:3F:72:B7:19:5D']),
+                                     labels=f.Labels().set_fields(bdf=['0000:a1:00.0', '0000:a1:00.1']),
                                      details='Mellanox Technologies MT28908 Family [ConnectX-6]')
 
         snw_nic1 = snw.add_component(name=snw.name + '-nic1', model='ConnectX-5',
@@ -214,10 +229,14 @@ def testRENCSiteAd(self):
                                      switch_fabric_node_id=snw.node_id + '-nic1-sf',
                                      interface_node_ids=[mac_to_node_id('0C:42:A1:BE:8F:D4'),
                                                          mac_to_node_id('0C:42:A1:BE:8F:D5')],
+                                     interface_labels=[f.Labels().set_fields(mac='0C:42:A1:BE:8F:D4',
+                                                                             vlan_range='1-4096'),
+                                                       f.Labels().set_fields(mac='0C:42:A1:BE:8F:D5',
+                                                                             vlan_range='1-4096')
+                                                       ],
                                      ctype=f.ComponentType.SmartNIC,
                                      capacities=f.Capacities().set_fields(unit=1),
-                                     labels=f.Labels().set_fields(bdf=['0000:41:00.0', '0000:41:00.1'],
-                                                                  mac=['0C:42:A1:BE:8F:D4', '0C:42:A1:BE:8F:D5']),
+                                     labels=f.Labels().set_fields(bdf=['0000:41:00.0', '0000:41:00.1']),
                                      details='Mellanox Technologies MT27800 Family [ConnectX-5]')
 
         snw_nic2 = snw.add_component(name=snw.name + '-nic2', model='ConnectX-5',
@@ -225,10 +244,14 @@ def testRENCSiteAd(self):
                                      switch_fabric_node_id=snw.node_id + '-nic2-sf',
                                      interface_node_ids=[mac_to_node_id('0C:42:A1:BE:8F:E8'),
                                                          mac_to_node_id('0C:42:A1:BE:8F:E9')],
+                                     interface_labels=[f.Labels().set_fields(mac='0C:42:A1:BE:8F:E8',
+                                                                             vlan_range='1-4096'),
+                                                       f.Labels().set_fields(mac='0C:42:A1:BE:8F:E9',
+                                                                             vlan_range='1-4096')
+                                                       ],
                                      ctype=f.ComponentType.SmartNIC,
                                      capacities=f.Capacities().set_fields(unit=1),
-                                     labels=f.Labels().set_fields(bdf=['0000:a1:00.0', '0000:a1:00.1'],
-                                                                  mac=['0C:42:A1:BE:8F:E8', '0C:42:A1:BE:8F:E9']),
+                                     labels=f.Labels().set_fields(bdf=['0000:a1:00.0', '0000:a1:00.1']),
                                      details='Mellanox Technologies MT27800 Family [ConnectX-5]')
 
         # NAS
@@ -249,10 +272,7 @@ def testRENCSiteAd(self):
         port_caps1 = f.Capacities()
         port_caps.set_fields(bw=100)
         port_caps1.set_fields(bw=25)
-        # labels should be part of pool delegations, generally not defined on substrate ports
-        # as native labels
-        #port_labs = f.Labels()
-        #port_labs.set_fields(vlan_range='1000-2000')
+
         # FIXME: don't have port MAC addresses yet - placeholders
         sp1 = dp_sf.add_interface(name='HundredGigE 0/0/0/5', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('00:00:00:00:00:00'),
@@ -345,15 +365,15 @@ def testRENCSiteAd(self):
         delegation1 = 'primary'
 
         # define the pools for interfaces on the switch
-        pools = f.ARMPools(atype=f.DelegationType.LABEL)
+        pools = f.Pools(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['HundredGigE 0/0/0/5'].node_id,
                              defined_for=[switch.interfaces['HundredGigE 0/0/0/5'].node_id,
                                           switch.interfaces['HundredGigE 0/0/0/13'].node_id,
                                           switch.interfaces['HundredGigE 0/0/0/21'].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'))
+        shared_pool.set_pool_details(f.Labels().set_fields(vlan_range='100-200',
+                                                           ipv4_range='192.168.1.1-192.168.10.255'))
 
         pools.add_pool(pool=shared_pool)
 
@@ -369,24 +389,25 @@ def testRENCSiteAd(self):
                                            switch.interfaces['HundredGigE 0/0/0/25.4'].node_id
                                            ]
                               )
-        datanic_pool.set_pool_details_from_labels(f.Labels().set_fields(vlan_range='1500-2000'))
+        datanic_pool.set_pool_details(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))
+        # pools are blank - all delegations for interfaces are in the network ad
+        self.topo.single_delegation(delegation_id=delegation1,
+                                    label_pools=pools,
+                                    capacity_pools=f.Pools(atype=f.DelegationType.CAPACITY))
 
         _, node_props = self.topo.graph_model.get_node_properties(node_id=switch.interfaces['HundredGigE 0/0/0/25.2'].node_id)
-        self.assertEqual(node_props[ABCPropertyGraph.PROP_LABEL_DELEGATIONS], '[{"pool": "datanic_pool"}]')
+        self.assertEqual(node_props[ABCPropertyGraph.PROP_LABEL_DELEGATIONS], '{"primary": {"pool": "datanic_pool"}}')
         _, node_props = self.topo.graph_model.get_node_properties(node_id=switch.interfaces['HundredGigE 0/0/0/5'].node_id)
-        self.assertEqual(node_props[ABCPropertyGraph.PROP_LABEL_DELEGATIONS], '[{"ipv4_range": '
+        self.assertEqual(node_props[ABCPropertyGraph.PROP_LABEL_DELEGATIONS], '{"primary": {"pool_id": '
+                                                                              '"shared_pool", "labels": '
+                                                                              '{"ipv4_range": '
                                                                               '"192.168.1.1-192.168.10.255", '
-                                                                              '"vlan_range": "100-200", '
-                                                                              '"label_pool": "shared_pool", '
-                                                                              '"delegation": "primary"}, '
-                                                                              '{"pool": "shared_pool"}]')
+                                                                             '"vlan_range": "100-200"}}}')
         self.topo.serialize(file_name='RENCI-ad.graphml')
         # print('\n\nALL NODES')
         # for n in self.topo.nodes.values():
@@ -543,19 +564,25 @@ def testUKYSiteAd(self):
                                         node_id=gpuw.node_id + '-shnic',
                                         switch_fabric_node_id=gpuw.node_id + '-shnic-sf',
                                         interface_node_ids=[mac_to_node_id('0C:42:A1:EA:C7:60')],
+                                        interface_labels=[f.Labels().set_fields(mac='0C:42:A1:EA:C7:60',
+                                                                                vlan_range='1-4096')],
                                         capacities=f.Capacities().set_fields(unit=1),
-                                        labels=f.Labels().set_fields(bdf='0000:a1:00.0',
-                                                                     mac='0C:42:A1:EA:C7:60'),
+                                        labels=f.Labels().set_fields(bdf='0000:a1:00.0'),
                                         ctype=f.ComponentType.SharedNIC,
                                         details='Shared NIC: Mellanox Technologies MT28908 Family [ConnectX-6]')
+        # test that labels propagated to the port
+        ilabs = gpuw_shnic.interface_list[0].get_property('labels')
+        assert(ilabs.vlan_range == '1-4096')
+        assert(ilabs.mac == '0C:42:A1:EA:C7:60')
 
         fnw_shnic = fnw.add_component(name=fnw.name + '-shnic', model='ConnectX-6',
                                       node_id=fnw.node_id + '-shnic',
                                       switch_fabric_node_id=fnw.node_id + '-shnic-sf',
                                       interface_node_ids=[mac_to_node_id('0C:42:A1:EA:C7:E8')],
+                                      interface_labels=[f.Labels().set_fields(mac='0C:42:A1:EA:C7:E8',
+                                                                              vlan_range='1-4096')],
                                       capacities=f.Capacities().set_fields(unit=1),
-                                      labels=f.Labels().set_fields(bdf='0000:e2:00.0',
-                                                                   mac='0C:42:A1:EA:C7:E8'),
+                                      labels=f.Labels().set_fields(bdf='0000:e2:00.0'),
                                       ctype=f.ComponentType.SharedNIC,
                                       details='Shared NIC: Mellanox Technologies MT28908 Family [ConnectX-6]')
 
@@ -563,9 +590,10 @@ def testUKYSiteAd(self):
                                       node_id=snw.node_id + '-shnic',
                                       switch_fabric_node_id=snw.node_id + '-shnic-sf',
                                       interface_node_ids=[mac_to_node_id('0C:42:A1:78:F8:1C')],
+                                      interface_labels=[f.Labels().set_fields(mac='0C:42:A1:78:F8:1C',
+                                                                              vlan_range='1-4096')],
                                       capacities=f.Capacities().set_fields(unit=1),
-                                      labels=f.Labels().set_fields(bdf='0000:e2:00.0',
-                                                                   mac='0C:42:A1:78:F8:1C'),
+                                      labels=f.Labels().set_fields(bdf='0000:e2:00.0'),
                                       ctype=f.ComponentType.SharedNIC,
                                       details='Shared NIC: Mellanox Technologies MT28908 Family [ConnectX-6]')
 
@@ -578,21 +606,36 @@ def testUKYSiteAd(self):
                                      switch_fabric_node_id=fnw.node_id + '-nic1-sf',
                                      interface_node_ids=[mac_to_node_id('0C:42:A1:EA:C7:50'),
                                                          mac_to_node_id('0C:42:A1:EA:C7:51')],
+                                     interface_labels=[f.Labels().set_fields(mac='0C:42:A1:EA:C7:50',
+                                                                             vlan_range='1-4096'),
+                                                       f.Labels().set_fields(mac='0C:42:A1:EA:C7:51',
+                                                                             vlan_range='1-4096')
+                                                       ],
                                      ctype=f.ComponentType.SmartNIC,
                                      capacities=f.Capacities().set_fields(unit=1),
-                                     labels=f.Labels().set_fields(bdf=['0000:41:00.0', '0000:41:00.1'],
-                                                                  mac=['0C:42:A1:EA:C7:50', '0C:42:A1:EA:C7:51']),
+                                     labels=f.Labels().set_fields(bdf=['0000:41:00.0', '0000:41:00.1']),
                                      details='Mellanox Technologies MT28908 Family [ConnectX-6]')
 
+        ilabs = fnw_nic1.interface_list[0].get_property('labels')
+        assert(ilabs.vlan_range == '1-4096')
+        assert(ilabs.mac == '0C:42:A1:EA:C7:50')
+        ilabs = fnw_nic1.interface_list[1].get_property('labels')
+        assert (ilabs.vlan_range == '1-4096')
+        assert (ilabs.mac == '0C:42:A1:EA:C7:51')
+
         fnw_nic2 = fnw.add_component(name=fnw.name + '-nic2', model='ConnectX-6',
                                      node_id=fnw.node_id + '-nic2',
                                      switch_fabric_node_id=fnw.node_id + '-nic2-sf',
                                      interface_node_ids=[mac_to_node_id('0C:42:A1:78:F8:04'),
                                                          mac_to_node_id('0C:42:A1:78:F8:05')],
+                                     interface_labels=[f.Labels().set_fields(mac='0C:42:A1:78:F8:04',
+                                                                             vlan_range='1-4096'),
+                                                       f.Labels().set_fields(mac='0C:42:A1:78:F8:05',
+                                                                             vlan_range='1-4096')
+                                                       ],
                                      ctype=f.ComponentType.SmartNIC,
                                      capacities=f.Capacities().set_fields(unit=1),
-                                     labels=f.Labels().set_fields(bdf=['0000:a1:00.0', '0000:a1:00.1'],
-                                                                  mac=['0C:42:A1:78:F8:04', '0C:42:A1:78:F8:05']),
+                                     labels=f.Labels().set_fields(bdf=['0000:a1:00.0', '0000:a1:00.1']),
                                      details='Mellanox Technologies MT28908 Family [ConnectX-6]')
 
         snw_nic1 = snw.add_component(name=snw.name + '-nic1', model='ConnectX-5',
@@ -600,10 +643,14 @@ def testUKYSiteAd(self):
                                      switch_fabric_node_id=snw.node_id + '-nic1-sf',
                                      interface_node_ids=[mac_to_node_id('0C:42:A1:BE:8F:F8'),
                                                          mac_to_node_id('0C:42:A1:BE:8F:F9')],
+                                     interface_labels=[f.Labels().set_fields(mac='0C:42:A1:BE:8F:F8',
+                                                                             vlan_range='1-4096'),
+                                                       f.Labels().set_fields(mac='0C:42:A1:BE:8F:F9',
+                                                                             vlan_range='1-4096')
+                                                       ],
                                      ctype=f.ComponentType.SmartNIC,
                                      capacities=f.Capacities().set_fields(unit=1),
-                                     labels=f.Labels().set_fields(bdf=['0000:41:00.0', '0000:41:00.1'],
-                                                                  mac=['00C:42:A1:BE:8F:F8', '0C:42:A1:BE:8F:F9']),
+                                     labels=f.Labels().set_fields(bdf=['0000:41:00.0', '0000:41:00.1']),
                                      details='Mellanox Technologies MT27800 Family [ConnectX-5]')
 
         snw_nic2 = snw.add_component(name=snw.name + '-nic2', model='ConnectX-5',
@@ -611,10 +658,14 @@ def testUKYSiteAd(self):
                                      switch_fabric_node_id=snw.node_id + '-nic2-sf',
                                      interface_node_ids=[mac_to_node_id('0C:42:A1:BE:8F:DC'),
                                                          mac_to_node_id('0C:42:A1:BE:8F:DD')],
+                                     interface_labels=[f.Labels().set_fields(mac='0C:42:A1:BE:8F:DC',
+                                                                             vlan_range='1-4096'),
+                                                       f.Labels().set_fields(mac='0C:42:A1:BE:8F:DD',
+                                                                             vlan_range='1-4096')
+                                                       ],
                                      ctype=f.ComponentType.SmartNIC,
                                      capacities=f.Capacities().set_fields(unit=1),
-                                     labels=f.Labels().set_fields(bdf=['0000:a1:00.0', '0000:a1:00.1'],
-                                                                  mac=['0C:42:A1:BE:8F:DC', '0C:42:A1:BE:8F:DD']),
+                                     labels=f.Labels().set_fields(bdf=['0000:a1:00.0', '0000:a1:00.1']),
                                      details='Mellanox Technologies MT27800 Family [ConnectX-5]')
 
         # NAS
@@ -635,10 +686,7 @@ def testUKYSiteAd(self):
         port_caps1 = f.Capacities()
         port_caps.set_fields(bw=100)
         port_caps1.set_fields(bw=25)
-        # labels should be part of pool delegations, generally not defined on substrate ports
-        # as native labels
-        #port_labs = f.Labels()
-        #port_labs.set_fields(vlan_range='1000-2000')
+
         # FIXME: don't have port MAC addresses yet - placeholders
         sp1 = dp_sf.add_interface(name='HundredGigE 0/0/0/5', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('10:00:00:00:00:00'),
@@ -731,15 +779,15 @@ def testUKYSiteAd(self):
         delegation1 = 'primary'
 
         # define the pools for interfaces on the switch
-        pools = f.ARMPools(atype=f.DelegationType.LABEL)
+        pools = f.Pools(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['HundredGigE 0/0/0/5'].node_id,
                              defined_for=[switch.interfaces['HundredGigE 0/0/0/5'].node_id,
                                           switch.interfaces['HundredGigE 0/0/0/13'].node_id,
                                           switch.interfaces['HundredGigE 0/0/0/21'].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'))
+        shared_pool.set_pool_details(f.Labels().set_fields(vlan_range='100-200',
+                                                           ipv4_range='192.168.1.1-192.168.10.255'))
 
         pools.add_pool(pool=shared_pool)
 
@@ -755,24 +803,22 @@ def testUKYSiteAd(self):
                                            switch.interfaces['HundredGigE 0/0/0/25.4'].node_id
                                            ]
                               )
-        datanic_pool.set_pool_details_from_labels(f.Labels().set_fields(vlan_range='1500-2000'))
+        datanic_pool.set_pool_details(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))
+                                    capacity_pools=f.Pools(atype=f.DelegationType.CAPACITY))
 
         _, node_props = self.topo.graph_model.get_node_properties(node_id=switch.interfaces['HundredGigE 0/0/0/25.2'].node_id)
-        self.assertEqual(node_props[ABCPropertyGraph.PROP_LABEL_DELEGATIONS], '[{"pool": "datanic_pool"}]')
+        self.assertEqual(node_props[ABCPropertyGraph.PROP_LABEL_DELEGATIONS], '{"primary": {"pool": "datanic_pool"}}')
         _, node_props = self.topo.graph_model.get_node_properties(node_id=switch.interfaces['HundredGigE 0/0/0/5'].node_id)
-        self.assertEqual(node_props[ABCPropertyGraph.PROP_LABEL_DELEGATIONS], '[{"ipv4_range": '
+        self.assertEqual(node_props[ABCPropertyGraph.PROP_LABEL_DELEGATIONS], '{"primary": {"pool_id": "shared_pool", '
+                                                                              '"labels": {"ipv4_range": '
                                                                               '"192.168.1.1-192.168.10.255", '
-                                                                              '"vlan_range": "100-200", '
-                                                                              '"label_pool": "shared_pool", '
-                                                                              '"delegation": "primary"}, '
-                                                                              '{"pool": "shared_pool"}]')
+                                                                              '"vlan_range": "100-200"}}}')
         self.topo.serialize(file_name='UKY-ad.graphml')
         # print('\n\nALL NODES')
         # for n in self.topo.nodes.values():
@@ -929,9 +975,10 @@ def testLBNLSiteAd(self):
                                         node_id=gpuw.node_id + '-shnic',
                                         switch_fabric_node_id=gpuw.node_id + '-shnic-sf',
                                         interface_node_ids=[mac_to_node_id('04:3F:72:B7:19:8C')],
+                                        interface_labels=[f.Labels().set_fields(mac='04:3F:72:B7:19:8C',
+                                                                                vlan_range='1-4096')],
                                         capacities=f.Capacities().set_fields(unit=1),
-                                        labels=f.Labels().set_fields(bdf='0000:a1:00.0',
-                                                                     mac='04:3F:72:B7:19:8C'),
+                                        labels=f.Labels().set_fields(bdf='0000:a1:00.0'),
                                         ctype=f.ComponentType.SharedNIC,
                                         details='Shared NIC: Mellanox Technologies MT28908 Family [ConnectX-6]')
 
@@ -939,9 +986,10 @@ def testLBNLSiteAd(self):
                                       node_id=fnw.node_id + '-shnic',
                                       switch_fabric_node_id=fnw.node_id + '-shnic-sf',
                                       interface_node_ids=[mac_to_node_id('04:3F:72:B7:15:8C')],
+                                      interface_labels=[f.Labels().set_fields(mac='04:3F:72:B7:15:8C',
+                                                                              vlan_range='1-4096')],
                                       capacities=f.Capacities().set_fields(unit=1),
-                                      labels=f.Labels().set_fields(bdf='0000:e2:00.0',
-                                                                   mac='04:3F:72:B7:15:8C'),
+                                      labels=f.Labels().set_fields(bdf='0000:e2:00.0'),
                                       ctype=f.ComponentType.SharedNIC,
                                       details='Shared NIC: Mellanox Technologies MT28908 Family [ConnectX-6]')
 
@@ -949,9 +997,10 @@ def testLBNLSiteAd(self):
                                       node_id=snw.node_id + '-shnic',
                                       switch_fabric_node_id=snw.node_id + '-shnic-sf',
                                       interface_node_ids=[mac_to_node_id('04:3F:72:B7:15:7C')],
+                                      interface_labels=[f.Labels().set_fields(mac='04:3F:72:B7:15:7C',
+                                                                              vlan_range='1-4096')],
                                       capacities=f.Capacities().set_fields(unit=1),
-                                      labels=f.Labels().set_fields(bdf='0000:e2:00.0',
-                                                                   mac='04:3F:72:B7:15:7C'),
+                                      labels=f.Labels().set_fields(bdf='0000:e2:00.0'),
                                       ctype=f.ComponentType.SharedNIC,
                                       details='Shared NIC: Mellanox Technologies MT28908 Family [ConnectX-6]')
 
@@ -964,10 +1013,14 @@ def testLBNLSiteAd(self):
                                      switch_fabric_node_id=fnw.node_id + '-nic1-sf',
                                      interface_node_ids=[mac_to_node_id('04:3F:72:B7:15:6C'),
                                                          mac_to_node_id('04:3F:72:B7:15:6D')],
+                                     interface_labels=[f.Labels().set_fields(mac='04:3F:72:B7:15:6C',
+                                                                             vlan_range='1-4096'),
+                                                       f.Labels().set_fields(mac='04:3F:72:B7:15:6D',
+                                                                             vlan_range='1-4096')
+                                                       ],
                                      ctype=f.ComponentType.SmartNIC,
                                      capacities=f.Capacities().set_fields(unit=1),
-                                     labels=f.Labels().set_fields(bdf=['0000:41:00.0', '0000:41:00.1'],
-                                                                  mac=['04:3F:72:B7:15:6C', '04:3F:72:B7:15:6D']),
+                                     labels=f.Labels().set_fields(bdf=['0000:41:00.0', '0000:41:00.1']),
                                      details='Mellanox Technologies MT28908 Family [ConnectX-6]')
 
         fnw_nic2 = fnw.add_component(name=fnw.name + '-nic2', model='ConnectX-6',
@@ -975,10 +1028,14 @@ def testLBNLSiteAd(self):
                                      switch_fabric_node_id=fnw.node_id + '-nic2-sf',
                                      interface_node_ids=[mac_to_node_id('04:3F:72:B7:18:AC'),
                                                          mac_to_node_id('04:3F:72:B7:18:AD')],
+                                     interface_labels=[f.Labels().set_fields(mac='04:3F:72:B7:18:AC',
+                                                                             vlan_range='1-4096'),
+                                                       f.Labels().set_fields(mac='04:3F:72:B7:18:AD',
+                                                                             vlan_range='1-4096')
+                                                       ],
                                      ctype=f.ComponentType.SmartNIC,
                                      capacities=f.Capacities().set_fields(unit=1),
-                                     labels=f.Labels().set_fields(bdf=['0000:a1:00.0', '0000:a1:00.1'],
-                                                                  mac=['04:3F:72:B7:18:AC', '04:3F:72:B7:18:AD']),
+                                     labels=f.Labels().set_fields(bdf=['0000:a1:00.0', '0000:a1:00.1']),
                                      details='Mellanox Technologies MT28908 Family [ConnectX-6]')
 
         snw_nic1 = snw.add_component(name=snw.name + '-nic1', model='ConnectX-5',
@@ -986,10 +1043,14 @@ def testLBNLSiteAd(self):
                                      switch_fabric_node_id=snw.node_id + '-nic1-sf',
                                      interface_node_ids=[mac_to_node_id('0C:42:A1:91:75:12'),
                                                          mac_to_node_id('0C:42:A1:91:75:13')],
+                                     interface_labels=[f.Labels().set_fields(mac='0C:42:A1:91:75:12',
+                                                                             vlan_range='1-4096'),
+                                                       f.Labels().set_fields(mac='0C:42:A1:91:75:13',
+                                                                             vlan_range='1-4096')
+                                                       ],
                                      ctype=f.ComponentType.SmartNIC,
                                      capacities=f.Capacities().set_fields(unit=1),
-                                     labels=f.Labels().set_fields(bdf=['0000:41:00.0', '0000:41:00.1'],
-                                                                  mac=['0C:42:A1:91:75:12', '0C:42:A1:91:75:13']),
+                                     labels=f.Labels().set_fields(bdf=['0000:41:00.0', '0000:41:00.1']),
                                      details='Mellanox Technologies MT27800 Family [ConnectX-5]')
 
         snw_nic2 = snw.add_component(name=snw.name + '-nic2', model='ConnectX-5',
@@ -997,10 +1058,14 @@ def testLBNLSiteAd(self):
                                      switch_fabric_node_id=snw.node_id + '-nic2-sf',
                                      interface_node_ids=[mac_to_node_id('0C:42:A1:91:75:0E'),
                                                          mac_to_node_id('0C:42:A1:91:75:0F')],
+                                     interface_labels=[f.Labels().set_fields(mac='0C:42:A1:91:75:0E',
+                                                                             vlan_range='1-4096'),
+                                                       f.Labels().set_fields(mac='0C:42:A1:91:75:0F',
+                                                                             vlan_range='1-4096')
+                                                       ],
                                      ctype=f.ComponentType.SmartNIC,
                                      capacities=f.Capacities().set_fields(unit=1),
-                                     labels=f.Labels().set_fields(bdf=['0000:a1:00.0', '0000:a1:00.1'],
-                                                                  mac=['0C:42:A1:91:75:0E', '0C:42:A1:91:75:0F']),
+                                     labels=f.Labels().set_fields(bdf=['0000:a1:00.0', '0000:a1:00.1']),
                                      details='Mellanox Technologies MT27800 Family [ConnectX-5]')
 
         # NAS
@@ -1021,10 +1086,7 @@ def testLBNLSiteAd(self):
         port_caps1 = f.Capacities()
         port_caps.set_fields(bw=100)
         port_caps1.set_fields(bw=25)
-        # labels should be part of pool delegations, generally not defined on substrate ports
-        # as native labels
-        #port_labs = f.Labels()
-        #port_labs.set_fields(vlan_range='1000-2000')
+
         # FIXME: don't have port MAC addresses yet - placeholders
         sp1 = dp_sf.add_interface(name='HundredGigE 0/0/0/5', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('20:00:00:00:00:00'),
@@ -1117,15 +1179,15 @@ def testLBNLSiteAd(self):
         delegation1 = 'primary'
 
         # define the pools for interfaces on the switch
-        pools = f.ARMPools(atype=f.DelegationType.LABEL)
+        pools = f.Pools(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['HundredGigE 0/0/0/5'].node_id,
                              defined_for=[switch.interfaces['HundredGigE 0/0/0/5'].node_id,
                                           switch.interfaces['HundredGigE 0/0/0/13'].node_id,
                                           switch.interfaces['HundredGigE 0/0/0/21'].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'))
+        shared_pool.set_pool_details(f.Labels().set_fields(vlan_range='100-200',
+                                                           ipv4_range='192.168.1.1-192.168.10.255'))
 
         pools.add_pool(pool=shared_pool)
 
@@ -1141,24 +1203,23 @@ def testLBNLSiteAd(self):
                                            switch.interfaces['HundredGigE 0/0/0/25.4'].node_id
                                            ]
                               )
-        datanic_pool.set_pool_details_from_labels(f.Labels().set_fields(vlan_range='1500-2000'))
+        datanic_pool.set_pool_details(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))
+                                    capacity_pools=f.Pools(atype=f.DelegationType.CAPACITY))
 
         _, node_props = self.topo.graph_model.get_node_properties(node_id=switch.interfaces['HundredGigE 0/0/0/25.2'].node_id)
-        self.assertEqual(node_props[ABCPropertyGraph.PROP_LABEL_DELEGATIONS], '[{"pool": "datanic_pool"}]')
+        self.assertEqual(node_props[ABCPropertyGraph.PROP_LABEL_DELEGATIONS], '{"primary": {"pool": "datanic_pool"}}')
         _, node_props = self.topo.graph_model.get_node_properties(node_id=switch.interfaces['HundredGigE 0/0/0/5'].node_id)
-        self.assertEqual(node_props[ABCPropertyGraph.PROP_LABEL_DELEGATIONS], '[{"ipv4_range": '
+        self.assertEqual(node_props[ABCPropertyGraph.PROP_LABEL_DELEGATIONS], '{"primary": {"pool_id": "shared_pool", '
+                                                                              '"labels": '
+                                                                              '{"ipv4_range": '
                                                                               '"192.168.1.1-192.168.10.255", '
-                                                                              '"vlan_range": "100-200", '
-                                                                              '"label_pool": "shared_pool", '
-                                                                              '"delegation": "primary"}, '
-                                                                              '{"pool": "shared_pool"}]')
+                                                                              '"vlan_range": "100-200"}}}')
         self.topo.serialize(file_name='LBNL-ad.graphml')
         # print('\n\nALL NODES')
         # for n in self.topo.nodes.values():
@@ -1185,9 +1246,9 @@ def testNetworkAd(self):
         switch = self.topo.add_node(name=site + '-data-sw', model=switch_model, site=site,
                                     node_id='FOC2450R1BL',
                                     capacities=f.Capacities().set_fields(unit=1),
-                                    ntype=f.NodeType.Switch)
+                                    ntype=f.NodeType.Switch, stitch_node=True)
         dp_sf = switch.add_switch_fabric(name=switch.name+'-sf', layer=f.Layer.L2,
-                                         node_id=switch.node_id + '-sf')
+                                         node_id=switch.node_id + '-sf', stitch_node=True)
         # add ports
         port_caps = f.Capacities()
         port_caps1 = f.Capacities()
@@ -1200,42 +1261,42 @@ def testNetworkAd(self):
         # FIXME: don't have port MAC addresses yet - placeholders
         sp1 = dp_sf.add_interface(name='HundredGigE 0/0/0/5', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('00:00:00:00:00:00'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
 
         sp2 = dp_sf.add_interface(name='HundredGigE 0/0/0/13', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('00:00:00:00:00:01'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
 
         sp3 = dp_sf.add_interface(name='HundredGigE 0/0/0/15', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('00:00:00:00:00:02'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
         sp4 = dp_sf.add_interface(name='HundredGigE 0/0/0/9', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('00:00:00:00:00:03'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
         sp5 = dp_sf.add_interface(name='HundredGigE 0/0/0/17', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('00:00:00:00:00:04'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
         sp6 = dp_sf.add_interface(name='HundredGigE 0/0/0/19', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('00:00:00:00:00:05'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
 
         sp7 = dp_sf.add_interface(name='HundredGigE 0/0/0/21', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('00:00:00:00:00:06'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
 
         # FIXME: what to do about breakout ports in slownets?
         sp8 = dp_sf.add_interface(name='HundredGigE 0/0/0/25.1', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('00:00:00:00:00:07'),
-                                  capacities=port_caps1)
+                                  capacities=port_caps1, stitch_node=True)
         sp9 = dp_sf.add_interface(name='HundredGigE 0/0/0/25.2', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('00:00:00:00:00:08'),
-                                  capacities=port_caps1)
+                                  capacities=port_caps1, stitch_node=True)
         sp10 = dp_sf.add_interface(name='HundredGigE 0/0/0/25.3', itype=f.InterfaceType.TrunkPort,
                                    node_id=mac_to_node_id('00:00:00:00:00:09'),
-                                   capacities=port_caps1)
+                                   capacities=port_caps1, stitch_node=True)
         sp11 = dp_sf.add_interface(name='HundredGigE 0/0/0/25.4', itype=f.InterfaceType.TrunkPort,
                                    node_id=mac_to_node_id('00:00:00:00:00:0A'),
-                                   capacities=port_caps1)
+                                   capacities=port_caps1, stitch_node=True)
 
         # FIXME: fake ports towards UKY and LBNL
         renc_uky = dp_sf.add_interface(name='HundredGigE 0/0/0/26', itype=f.InterfaceType.TrunkPort,
@@ -1252,9 +1313,10 @@ def testNetworkAd(self):
         switch = self.topo.add_node(name=site + '-data-sw', model=switch_model, site=site,
                                     node_id='FOC2451R0XZ',
                                     capacities=f.Capacities().set_fields(unit=1),
-                                    ntype=f.NodeType.Switch)
+                                    ntype=f.NodeType.Switch, stitch_node=True)
         dp_sf = switch.add_switch_fabric(name=switch.name+'-sf', layer=f.Layer.L2,
-                                         node_id=switch.node_id + '-sf')
+                                         node_id=switch.node_id + '-sf',
+                                         stitch_node=True)
         # add ports
         port_caps = f.Capacities()
         port_caps1 = f.Capacities()
@@ -1267,42 +1329,42 @@ def testNetworkAd(self):
         # FIXME: don't have port MAC addresses yet - placeholders
         sp1 = dp_sf.add_interface(name='HundredGigE 0/0/0/5', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('10:00:00:00:00:00'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
 
         sp2 = dp_sf.add_interface(name='HundredGigE 0/0/0/13', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('10:00:00:00:00:01'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
 
         sp3 = dp_sf.add_interface(name='HundredGigE 0/0/0/15', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('10:00:00:00:00:02'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
         sp4 = dp_sf.add_interface(name='HundredGigE 0/0/0/9', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('10:00:00:00:00:03'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
         sp5 = dp_sf.add_interface(name='HundredGigE 0/0/0/17', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('10:00:00:00:00:04'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
         sp6 = dp_sf.add_interface(name='HundredGigE 0/0/0/19', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('10:00:00:00:00:05'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
 
         sp7 = dp_sf.add_interface(name='HundredGigE 0/0/0/21', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('10:00:00:00:00:06'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
 
         # FIXME: what to do about breakout ports in slownets?
         sp8 = dp_sf.add_interface(name='HundredGigE 0/0/0/25.1', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('10:00:00:00:00:07'),
-                                  capacities=port_caps1)
+                                  capacities=port_caps1, stitch_node=True)
         sp9 = dp_sf.add_interface(name='HundredGigE 0/0/0/25.2', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('10:00:00:00:00:08'),
-                                  capacities=port_caps1)
+                                  capacities=port_caps1, stitch_node=True)
         sp10 = dp_sf.add_interface(name='HundredGigE 0/0/0/25.3', itype=f.InterfaceType.TrunkPort,
                                    node_id=mac_to_node_id('10:00:00:00:00:09'),
-                                   capacities=port_caps1)
+                                   capacities=port_caps1, stitch_node=True)
         sp11 = dp_sf.add_interface(name='HundredGigE 0/0/0/25.4', itype=f.InterfaceType.TrunkPort,
                                    node_id=mac_to_node_id('10:00:00:00:00:0A'),
-                                   capacities=port_caps1)
+                                   capacities=port_caps1, stitch_node=True)
 
         # FIXME: fake ports towards RENC and LBNL
         uky_renc = dp_sf.add_interface(name='HundredGigE 0/0/0/26', itype=f.InterfaceType.TrunkPort,
@@ -1320,9 +1382,9 @@ def testNetworkAd(self):
         switch = self.topo.add_node(name=site + '-data-sw', model=switch_model, site=site,
                                     node_id='FOC2450R1AR',
                                     capacities=f.Capacities().set_fields(unit=1),
-                                    ntype=f.NodeType.Switch)
+                                    ntype=f.NodeType.Switch, stitch_node=True)
         dp_sf = switch.add_switch_fabric(name=switch.name+'-sf', layer=f.Layer.L2,
-                                         node_id=switch.node_id + '-sf')
+                                         node_id=switch.node_id + '-sf', stitch_node=True)
         # add ports
         port_caps = f.Capacities()
         port_caps1 = f.Capacities()
@@ -1335,42 +1397,42 @@ def testNetworkAd(self):
         # FIXME: don't have port MAC addresses yet - placeholders
         sp1 = dp_sf.add_interface(name='HundredGigE 0/0/0/5', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('20:00:00:00:00:00'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
 
         sp2 = dp_sf.add_interface(name='HundredGigE 0/0/0/13', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('20:00:00:00:00:01'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
 
         sp3 = dp_sf.add_interface(name='HundredGigE 0/0/0/15', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('20:00:00:00:00:02'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
         sp4 = dp_sf.add_interface(name='HundredGigE 0/0/0/9', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('20:00:00:00:00:03'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
         sp5 = dp_sf.add_interface(name='HundredGigE 0/0/0/17', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('20:00:00:00:00:04'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
         sp6 = dp_sf.add_interface(name='HundredGigE 0/0/0/19', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('20:00:00:00:00:05'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
 
         sp7 = dp_sf.add_interface(name='HundredGigE 0/0/0/21', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('20:00:00:00:00:06'),
-                                  capacities=port_caps)
+                                  capacities=port_caps, stitch_node=True)
 
         # FIXME: what to do about breakout ports in slownets?
         sp8 = dp_sf.add_interface(name='HundredGigE 0/0/0/25.1', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('20:00:00:00:00:07'),
-                                  capacities=port_caps1)
+                                  capacities=port_caps1, stitch_node=True)
         sp9 = dp_sf.add_interface(name='HundredGigE 0/0/0/25.2', itype=f.InterfaceType.TrunkPort,
                                   node_id=mac_to_node_id('20:00:00:00:00:08'),
-                                  capacities=port_caps1)
+                                  capacities=port_caps1, stitch_node=True)
         sp10 = dp_sf.add_interface(name='HundredGigE 0/0/0/25.3', itype=f.InterfaceType.TrunkPort,
                                    node_id=mac_to_node_id('20:00:00:00:00:09'),
-                                   capacities=port_caps1)
+                                   capacities=port_caps1, stitch_node=True)
         sp11 = dp_sf.add_interface(name='HundredGigE 0/0/0/25.4', itype=f.InterfaceType.TrunkPort,
                                    node_id=mac_to_node_id('20:00:00:00:00:0A'),
-                                   capacities=port_caps1)
+                                   capacities=port_caps1, stitch_node=True)
 
         # FIXME: fake ports towards UKY and RENC
         lbnl_renc = dp_sf.add_interface(name='HundredGigE 0/0/0/26', itype=f.InterfaceType.TrunkPort,
@@ -1396,7 +1458,7 @@ def testNetworkAd(self):
 
         delegation1 = 'primary'
         self.topo.single_delegation(delegation_id=delegation1,
-                                    label_pools=f.ARMPools(atype=f.DelegationType.LABEL),
-                                    capacity_pools=f.ARMPools(atype=f.DelegationType.CAPACITY))
+                                    label_pools=f.Pools(atype=f.DelegationType.LABEL),
+                                    capacity_pools=f.Pools(atype=f.DelegationType.CAPACITY))
 
         self.topo.serialize(file_name='Network-ad.graphml')
diff --git a/test/testrenciad.py b/test/testrenciad.py
deleted file mode 100644
index 1d6b580..0000000
--- a/test/testrenciad.py
+++ /dev/null
@@ -1,232 +0,0 @@
-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 1457e7f..ab86bc7 100644
--- a/test/tuple_test.py
+++ b/test/tuple_test.py
@@ -1,7 +1,9 @@
 import unittest
 
 from fim.graph.typed_tuples import Label, Capacity, Location
-from fim.slivers.delegations import Pool, ARMPools, DelegationType
+from fim.slivers.capacities_labels import Capacities, Labels
+from fim.slivers.delegations import Pool, Pools, DelegationType
+
 
 class TupleTests(unittest.TestCase):
 
@@ -51,15 +53,14 @@ def testPools(self):
         p.set_defined_on(node_id="node1")
         p.set_defined_for(node_id_list=["node4"])
         p.add_defined_for(node_ids=["node2", "node3", "node1"])
-        p.set_pool_details(pool_dict={"blah": "blahblah"})
+        p.set_pool_details(caporlab=Capacities().set_fields(cpu=2, ram=1024))
 
-        pp = ARMPools(atype=DelegationType.LABEL)
+        pp = Pools(atype=DelegationType.LABEL)
         pp.add_pool(pool=p)
         pp.build_index_by_delegation_id()
         p1l = pp.get_pools_by_delegation_id(delegation_id="del1")
         p2 = pp.get_pool_by_id(pool_id="pool1")
         assert p2 in p1l
 
-
 if __name__ == "__main__":
     unittest.main()
diff --git a/test/zz_neo4j_pg_test.py b/test/zz_neo4j_pg_test.py
index 2ae7a8d..0a6617d 100644
--- a/test/zz_neo4j_pg_test.py
+++ b/test/zz_neo4j_pg_test.py
@@ -6,7 +6,7 @@
 from fim.graph.resources.neo4j_cbm import Neo4jCBMGraph
 from fim.graph.slices.neo4j_asm import Neo4jASM, Neo4jASMFactory
 from fim.graph.resources.neo4j_arm import Neo4jARMGraph
-from fim.slivers.delegations import Delegation, DelegationFormat
+from fim.slivers.delegations import Delegation
 
 from fim.slivers.attached_components import AttachedComponentsInfo, ComponentSliver, ComponentType
 
@@ -167,21 +167,7 @@ def test_arm_load(self):
         list_of_nodes = cbm.get_matching_nodes_with_components(
             label=ABCPropertyGraphConstants.CLASS_NetworkNode,
             props={'Name': 'renc-w3'})
-        _, props = cbm.get_node_properties(node_id=list_of_nodes[0])
-        assert Delegation.get_delegation_format(props[ABCPropertyGraphConstants.PROP_CAPACITY_DELEGATIONS]) == \
-               DelegationFormat.CBMDelegation
-        print(f'Delegation in CBM: {props[ABCPropertyGraphConstants.PROP_CAPACITY_DELEGATIONS]}')
-        delegation_info = Delegation.from_json_to_sliver_field(props[ABCPropertyGraphConstants.PROP_CAPACITY_DELEGATIONS])
-        print(f'Delegation as object {delegation_info}')
-        for k, d in delegation_info.items():
-            print(f'Delegation value type is {d.__class__}')
-
-        _, props = site_arm.get_node_properties(node_id=list_of_nodes[0])
-        assert Delegation.get_delegation_format(props[ABCPropertyGraphConstants.PROP_CAPACITY_DELEGATIONS]) == \
-               DelegationFormat.ARMDelegation
-        _, props = site_adms['primary'].get_node_properties(node_id=list_of_nodes[0])
-        assert Delegation.get_delegation_format(props[ABCPropertyGraphConstants.PROP_CAPACITY_DELEGATIONS]) == \
-               DelegationFormat.ADMDelegation
+
 
         print('Deleting ADM and ARM graphs')
         for adm in site_adms.values():
@@ -280,17 +266,20 @@ def test_3_site_load(self):
         cbm = Neo4jCBMGraph(importer=self.n4j_imp)
 
         adm_ids = dict()
+        site_arms = dict()
 
         for ad in site_ads:
             plain_neo4j = self.n4j_imp.import_graph_from_file_direct(graph_file=ad)
-            print(f"Validating ARM graph {ad}")
+            print(f"Validating ARM graph {ad} with id {plain_neo4j.graph_id}")
             plain_neo4j.validate_graph()
 
-            site_arm = Neo4jARMGraph(graph=Neo4jPropertyGraph(graph_id=plain_neo4j.graph_id,
-                                                              importer=self.n4j_imp))
+            site_arms[ad] = Neo4jARMGraph(graph=Neo4jPropertyGraph(graph_id=plain_neo4j.graph_id,
+                                                                   importer=self.n4j_imp))
             # generate a dict of ADMs from site graph ARM
-            site_adms = site_arm.generate_adms()
-            print('ADMS' + str(site_adms.keys()))
+            site_adms = site_arms[ad].generate_adms()
+            print('ADMS ' + str(site_adms.keys()))
+            for adm_id in site_adms.keys():
+                print(f'  ADM id {site_adms[adm_id].graph_id}')
 
             # desired ADM is under 'primary'
             site_adm = site_adms['primary']
@@ -300,7 +289,7 @@ def test_3_site_load(self):
             for adm in site_adms.values():
                 adm_ids[ad] = adm.graph_id
                 adm.delete_graph()
-            site_arm.delete_graph()
+            #site_arms[ad].delete_graph()
 
         cbm.validate_graph()
         print('CBM ID is ' + cbm.graph_id)
@@ -317,5 +306,24 @@ def test_3_site_load(self):
                'node_id-00-00-00-00-00-10-Wave' in ls)
         print('Done')
 
+
+        ad = 'Network-ad.graphml'
+        print(f"Unmerging graph {adm_ids[ad]}")
+        cbm.unmerge_adm(graph_id=adm_ids[ad])
+        print(f"Merging back")
+        site_adms = site_arms[ad].generate_adms()
+        site_adm = site_adms['primary']
+        cbm.merge_adm(adm=site_adm)
+
+        ad = 'RENCI-ad.graphml'
+        print(f"Unmerging graph {adm_ids[ad]}")
+        cbm.unmerge_adm(graph_id=adm_ids[ad])
+        print(f"Merging back")
+        site_adms = site_arms[ad].generate_adms()
+        site_adm = site_adms['primary']
+        cbm.merge_adm(adm=site_adm)
+
         self.n4j_imp.delete_all_graphs()
-        #cbm.unmerge_adm(graph_id=adm_ids['../Network-ad.graphml'])
+
+
+