fabric-testbed / InformationModel

FABRIC Information Model library
MIT License
7 stars 1 forks source link

Modify property diff #160

Closed ibaldin closed 1 year ago

ibaldin commented 1 year ago

Support for diffing properties between elements of topologies (by Labels, Capacities and UserData) and same for diffing slivers (NetworkNode and NetworkService).

To diff topologies and see if any elements have changed Labels, Capacities or UserData properties:

        diff_res = self.topoA.diff(self.topoB)
        # check for modified properties
        print(f'Modified Nodes {diff_res.modified.nodes}')
        print(f'Modified Network Services {diff_res.modified.services}')
        self.assertIn((self.topoA.network_services['bridge2'], WhatsModifiedFlag.LABELS | WhatsModifiedFlag.CAPACITIES),
                      diff_res.modified.services)
        self.assertIn((self.topoA.nodes['NodeA'], WhatsModifiedFlag.LABELS | WhatsModifiedFlag.USER_DATA),
                      diff_res.modified.nodes)
        for n, flag in diff_res.modified.nodes:
            if flag & WhatsModifiedFlag.LABELS:
                # note that elements returned are bound to the original topology object
                # so n and self.topoA.nodes[n.name] point to the same node
                print(f'Node {n.name} has modified labels from {n.labels} to {self.topoB.nodes[n.name].labels}')
            if flag & WhatsModifiedFlag.CAPACITIES:
                print(f'Node {n.name} has modified capacities from {n.capacities} to {self.topoB.nodes[n.name].capacities}')
            if flag & WhatsModifiedFlag.USER_DATA:
                print(f'Node {n.name} has modified user data from {n.user_data} to {self.topoB.nodes[n.name].user_data}')
        for n, flag in diff_res.modified.services:
            if flag & WhatsModifiedFlag.LABELS:
                print(f'Service {n.name} has modified labels from {n.labels} to {self.topoB.network_services[n.name].labels}')
            if flag & WhatsModifiedFlag.CAPACITIES:
                print(f'Service {n.name} has modified capacities from {n.capacities} to {self.topoB.network_services[n.name].capacities}')
            if flag & WhatsModifiedFlag.USER_DATA:
                print(f'Service {n.name} has modified user data from {n.user_data} to {self.topoB.network_services[n.name].user_data}')
        # note that for component and interface changes you need to find the
        # parent before you can find them in the topology
        for n, flag in diff_res.modified.components:
            parent_node = self.topoB.get_owner_node(n)
            if flag & WhatsModifiedFlag.LABELS:
                print(f'Component {n.name} has modified labels from {n.labels} to {self.topoB.nodes[parent_node.name].components[n.name].labels}')
            if flag & WhatsModifiedFlag.CAPACITIES:
                print(f'Component {n.name} has modified capacities from {n.capacities} to {self.topoB.nodes[parent_node.name].components[n.name].capacities}')
            if flag & WhatsModifiedFlag.USER_DATA:
                print(f'Component {n.name} has modified user data from {n.user_data} to {self.topoB.nodes[parent_node.name].components[n.name].user_data}')

To check if two slivers (and their subtending slivers like Network Services for NetworkNodes or Interfaces for Network Services) have different properties:

        # nAAs and nABs are slivers
        diff = nAAs.diff(nABs)
        self.assertEqual(len(diff.added.components), 1)
        self.assertIn(self.topoB.nodes['NodeA'].components['gpu1'].get_sliver(), diff.added.components)
        self.assertIn((self.topoA.nodes['NodeA'].get_sliver(),
                       WhatsModifiedFlag.LABELS | WhatsModifiedFlag.USER_DATA),
                      diff.modified.nodes)
        self.assertEqual(len(diff.modified.nodes), 1)
        self.assertEqual(len(diff.modified.services), 0)
        self.assertEqual(len(diff.modified.interfaces), 0)
        self.assertEqual(len(diff.modified.components), 0)

        sA1 = self.topoA.network_services['bridge2']
        sB1 = self.topoB.network_services['bridge2']
        sA1s = sA1.get_sliver()
        sB1s = sB1.get_sliver()

        diff1 = sA1s.diff(sB1s)

        self.assertIn((self.topoA.network_services['bridge2'].get_sliver(),
                       WhatsModifiedFlag.LABELS | WhatsModifiedFlag.CAPACITIES),
                       diff1.modified.services)
        self.assertEqual(len(diff1.modified.nodes), 0)
        self.assertEqual(len(diff1.modified.services), 1)
        self.assertEqual(len(diff1.modified.interfaces), 0)
        self.assertEqual(len(diff1.modified.components), 0)

Note that TopologyDiff is now defined as:

class TopologyDiff:
    """
    Note that for added and removed the sets are just tuple of sets of Elements,
    while for modified it is a tuple of lists of tuples [Element, WhatsModifiedFlag]
    """
    added: TopologyDiffTuple
    removed: TopologyDiffTuple
    modified: TopologyDiffModifiedTuple

(added modified field and a new dataclass TopologyDiffModifiedTuple):

class TopologyDiffModifiedTuple:
    """
    Note that in case of diffs between topologies these lists contain
    network elements. In case of diffs between slivers they contain slivers
    """
    nodes: list[Tuple[Any, WhatsModifiedFlag]]
    components: list[Tuple[Any, WhatsModifiedFlag]]
    services: list[Tuple[Any, WhatsModifiedFlag]]
    interfaces: list[Tuple[Any, WhatsModifiedFlag]]

Also note the change to sliver diffing, before it was returning sets of added/removed names of slivers, now it returns slivers themselves. Same is true for the modified lists - they include ModelElements when you diff topologies and slivers when you diff slivers.

More examples in test/modify_test.py

This is available in PyPi as fabric_fim==1.4.1