From 33fbfebd380879470a342da4061e3e8f6820addb Mon Sep 17 00:00:00 2001
From: gifrerenom <lluis.gifre@cttc.es>
Date: Fri, 8 Nov 2024 11:26:56 +0000
Subject: [PATCH] Service component - L3NM gNMI OpenConfig:

- StaticRouteGenerator: Fixed link endpoints compuotation
- ConfigRuleComposer: Reverted to default network instance as cEOS does not work properly with VRFs
- ConfigRuleComposer: Added interface deactivation
- Improved logging
- Updated unitary tests
---
 .../ConfigRuleComposer.py                     | 32 +++++++++++++----
 .../L3NMGnmiOpenConfigServiceHandler.py       |  9 ++---
 .../StaticRouteGenerator.py                   | 12 +++++--
 .../MockServiceHandler.py                     | 36 +++++++++++++++++--
 .../test_unitary_sns4sns.py                   | 20 +++++------
 5 files changed, 83 insertions(+), 26 deletions(-)

diff --git a/src/service/service/service_handlers/l3nm_gnmi_openconfig/ConfigRuleComposer.py b/src/service/service/service_handlers/l3nm_gnmi_openconfig/ConfigRuleComposer.py
index c8227975f..54101d041 100644
--- a/src/service/service/service_handlers/l3nm_gnmi_openconfig/ConfigRuleComposer.py
+++ b/src/service/service/service_handlers/l3nm_gnmi_openconfig/ConfigRuleComposer.py
@@ -21,7 +21,8 @@ from service.service.service_handler_api.AnyTreeTools import TreeNode
 
 LOGGER = logging.getLogger(__name__)
 
-NETWORK_INSTANCE = 'teraflowsdn'
+#NETWORK_INSTANCE = 'teraflowsdn'
+NETWORK_INSTANCE = 'default'
 
 RE_IF    = re.compile(r'^\/interface\[([^\]]+)\]$')
 RE_SUBIF = re.compile(r'^\/interface\[([^\]]+)\]\/subinterface\[([^\]]+)\]$')
@@ -109,11 +110,18 @@ class EndpointComposer:
         if self.ipv4_prefix_len is None: return []
         json_config_rule = json_config_rule_delete if delete else json_config_rule_set
         config_rules = [
-            json_config_rule(*_network_instance_interface(
-                network_instance_name, self.objekt.name, self.sub_interface_index
-            )),
+            #json_config_rule(*_network_instance_interface(
+            #    network_instance_name, self.objekt.name, self.sub_interface_index
+            #)),
         ]
-        if not delete:
+        if delete:
+            config_rules.extend([
+                json_config_rule(*_interface(
+                    self.objekt.name, index=self.sub_interface_index, address_ip=None,
+                    address_prefix=None, enabled=False
+                )),
+            ])
+        else:
             config_rules.extend([
                 json_config_rule(*_interface(
                     self.objekt.name, index=self.sub_interface_index, address_ip=self.ipv4_address,
@@ -128,6 +136,12 @@ class EndpointComposer:
             'address_ip'    : self.ipv4_address,
             'address_prefix': self.ipv4_prefix_len,
         }
+    
+    def __str__(self):
+        data = {'uuid': self.uuid}
+        if self.objekt is not None: data['name'] = self.objekt.name
+        data.update(self.dump())
+        return json.dumps(data)
 
 class DeviceComposer:
     def __init__(self, device_uuid : str) -> None:
@@ -212,7 +226,7 @@ class DeviceComposer:
 
         json_config_rule = json_config_rule_delete if delete else json_config_rule_set
         config_rules = [
-            json_config_rule(*_network_instance(network_instance_name, 'L3VRF'))
+            #json_config_rule(*_network_instance(network_instance_name, 'L3VRF'))
         ]
         for endpoint in self.endpoints.values():
             config_rules.extend(endpoint.get_config_rules(network_instance_name, delete=delete))
@@ -240,6 +254,12 @@ class DeviceComposer:
             'static_routes' : self.static_routes,
         }
 
+    def __str__(self):
+        data = {'uuid': self.uuid}
+        if self.objekt is not None: data['name'] = self.objekt.name
+        data.update(self.dump())
+        return json.dumps(data)
+
 class ConfigRuleComposer:
     def __init__(self) -> None:
         self.objekt : Optional[Service] = None
diff --git a/src/service/service/service_handlers/l3nm_gnmi_openconfig/L3NMGnmiOpenConfigServiceHandler.py b/src/service/service/service_handlers/l3nm_gnmi_openconfig/L3NMGnmiOpenConfigServiceHandler.py
index 8aa3781a4..4099675fa 100644
--- a/src/service/service/service_handlers/l3nm_gnmi_openconfig/L3NMGnmiOpenConfigServiceHandler.py
+++ b/src/service/service/service_handlers/l3nm_gnmi_openconfig/L3NMGnmiOpenConfigServiceHandler.py
@@ -65,8 +65,9 @@ class L3NMGnmiOpenConfigServiceHandler(_ServiceHandler):
 
             self.__endpoint_map[(device_uuid, endpoint_uuid)] = (device_obj.name, endpoint_obj.name)
 
+        LOGGER.debug('[pre] config_rule_composer = {:s}'.format(json.dumps(self.__config_rule_composer.dump())))
         self.__static_route_generator.compose(endpoints)
-        LOGGER.debug('config_rule_composer = {:s}'.format(json.dumps(self.__config_rule_composer.dump())))
+        LOGGER.debug('[post] config_rule_composer = {:s}'.format(json.dumps(self.__config_rule_composer.dump())))
 
     def _do_configurations(
         self, config_rules_per_device : Dict[str, List[Dict]], endpoints : List[Tuple[str, str, Optional[str]]],
@@ -110,8 +111,8 @@ class L3NMGnmiOpenConfigServiceHandler(_ServiceHandler):
         #network_instance_name = service_uuid.split('-')[0]
         #config_rules_per_device = self.__config_rule_composer.get_config_rules(network_instance_name, delete=False)
         config_rules_per_device = self.__config_rule_composer.get_config_rules(delete=False)
-        LOGGER.debug('config_rules_per_device={:s}'.format(str(config_rules_per_device)))
-        results = self._do_configurations(config_rules_per_device, endpoints)
+        LOGGER.debug('config_rules_per_device={:s}'.format(json.dumps(config_rules_per_device)))
+        results = self._do_configurations(config_rules_per_device, endpoints, delete=False)
         LOGGER.debug('results={:s}'.format(str(results)))
         return results
 
@@ -128,7 +129,7 @@ class L3NMGnmiOpenConfigServiceHandler(_ServiceHandler):
         #network_instance_name = service_uuid.split('-')[0]
         #config_rules_per_device = self.__config_rule_composer.get_config_rules(network_instance_name, delete=True)
         config_rules_per_device = self.__config_rule_composer.get_config_rules(delete=True)
-        LOGGER.debug('config_rules_per_device={:s}'.format(str(config_rules_per_device)))
+        LOGGER.debug('config_rules_per_device={:s}'.format(json.dumps(config_rules_per_device)))
         results = self._do_configurations(config_rules_per_device, endpoints, delete=True)
         LOGGER.debug('results={:s}'.format(str(results)))
         return results
diff --git a/src/service/service/service_handlers/l3nm_gnmi_openconfig/StaticRouteGenerator.py b/src/service/service/service_handlers/l3nm_gnmi_openconfig/StaticRouteGenerator.py
index 201f22e63..c52321473 100644
--- a/src/service/service/service_handlers/l3nm_gnmi_openconfig/StaticRouteGenerator.py
+++ b/src/service/service/service_handlers/l3nm_gnmi_openconfig/StaticRouteGenerator.py
@@ -63,12 +63,20 @@ class StaticRouteGenerator:
     def _compute_link_endpoints(
         self, connection_hop_list : List[Tuple[str, str, Optional[str]]]
     ) -> List[Tuple[Tuple[str, str, Optional[str]], Tuple[str, str, Optional[str]]]]:
+        # In some cases connection_hop_list might contain repeated endpoints, remove them here.
+        added_connection_hops = set()
+        filtered_connection_hop_list = list()
+        for connection_hop in connection_hop_list:
+            if connection_hop in added_connection_hops: continue
+            filtered_connection_hop_list.append(connection_hop)
+            added_connection_hops.add(connection_hop)
+        connection_hop_list = filtered_connection_hop_list
+
         num_connection_hops = len(connection_hop_list)
         if num_connection_hops % 2 != 0: raise Exception('Number of connection hops must be even')
         if num_connection_hops < 4: raise Exception('Number of connection hops must be >= 4')
 
-        # Skip service endpoints (first and last)
-        it_connection_hops = iter(connection_hop_list[1:-1])
+        it_connection_hops = iter(connection_hop_list)
         return list(zip(it_connection_hops, it_connection_hops))
 
     def _compute_link_addresses(
diff --git a/src/service/tests/test_l3nm_gnmi_static_rule_gen/MockServiceHandler.py b/src/service/tests/test_l3nm_gnmi_static_rule_gen/MockServiceHandler.py
index 22da218ab..a480f6b31 100644
--- a/src/service/tests/test_l3nm_gnmi_static_rule_gen/MockServiceHandler.py
+++ b/src/service/tests/test_l3nm_gnmi_static_rule_gen/MockServiceHandler.py
@@ -19,6 +19,7 @@ from common.tools.object_factory.Connection import json_connection_id
 from common.tools.object_factory.Device import json_device_id
 from common.type_checkers.Checkers import chk_type
 from service.service.service_handler_api._ServiceHandler import _ServiceHandler
+#from service.service.service_handler_api.AnyTreeTools import TreeNode
 from service.service.service_handler_api.SettingsHandler import SettingsHandler
 from service.service.service_handler_api.Tools import get_device_endpoint_uuids, get_endpoint_matching
 from .MockTaskExecutor import MockTaskExecutor
@@ -45,6 +46,10 @@ class MockServiceHandler(_ServiceHandler):
         service_settings = self.__settings_handler.get_service_settings()
         self.__config_rule_composer.configure(self.__service, service_settings)
 
+        #prev_endpoint_obj = None
+        #prev_endpoint     = None
+        #settings_for_next = None
+        #for i,endpoint in enumerate(endpoints):
         for endpoint in endpoints:
             device_uuid, endpoint_uuid = get_device_endpoint_uuids(endpoint)
 
@@ -60,8 +65,35 @@ class MockServiceHandler(_ServiceHandler):
             _endpoint = _device.get_endpoint(endpoint_obj.name)
             _endpoint.configure(endpoint_obj, endpoint_settings)
 
+            #if settings_for_next is not None:
+            #    _endpoint.configure(endpoint_obj, settings_for_next)
+            #    settings_for_next = None
+
+            #if endpoint_settings is not None and 'neighbor_address' in endpoint_settings.value:
+            #    _neighbor_settings = {'address_ip': endpoint_settings.value['neighbor_address']}
+            #
+            #    if 'address_prefix' in endpoint_settings.value:
+            #        _neighbor_settings['address_prefix'] = endpoint_settings.value['address_prefix']
+            #    elif 'prefix_length' in endpoint_settings.value:
+            #        _neighbor_settings['address_prefix'] = endpoint_settings.value['prefix_length']
+            #    else:
+            #        MSG = 'IP Address Prefix not found. Tried: address_prefix and prefix_length. endpoint_settings.value={:s}'
+            #        raise Exception(MSG.format(str(endpoint_settings.value)))
+            #
+            #    neighbor_settings = TreeNode('.')
+            #    neighbor_settings.value = _neighbor_settings
+            #    if i % 2 == 0:
+            #        # configure in next endpoint
+            #        settings_for_next = neighbor_settings
+            #    else:
+            #        # configure in previous endpoint
+            #        prev_endpoint.configure(prev_endpoint_obj, neighbor_settings)
+
             self.__endpoint_map[(device_uuid, endpoint_uuid)] = (device_obj.name, endpoint_obj.name)
 
+            #prev_endpoint = _endpoint
+            #prev_endpoint_obj = endpoint_obj
+
         self.__static_route_generator.compose(endpoints)
         LOGGER.debug('config_rule_composer = {:s}'.format(json.dumps(self.__config_rule_composer.dump())))
 
@@ -106,7 +138,7 @@ class MockServiceHandler(_ServiceHandler):
         #network_instance_name = service_uuid.split('-')[0]
         #config_rules_per_device = self.__config_rule_composer.get_config_rules(network_instance_name, delete=False)
         config_rules_per_device = self.__config_rule_composer.get_config_rules(delete=False)
-        LOGGER.debug('config_rules_per_device={:s}'.format(str(config_rules_per_device)))
+        LOGGER.debug('config_rules_per_device={:s}'.format(json.dumps(config_rules_per_device)))
         results = self._do_configurations(config_rules_per_device, endpoints)
         LOGGER.debug('results={:s}'.format(str(results)))
         return results
@@ -123,7 +155,7 @@ class MockServiceHandler(_ServiceHandler):
         #network_instance_name = service_uuid.split('-')[0]
         #config_rules_per_device = self.__config_rule_composer.get_config_rules(network_instance_name, delete=True)
         config_rules_per_device = self.__config_rule_composer.get_config_rules(delete=True)
-        LOGGER.debug('config_rules_per_device={:s}'.format(str(config_rules_per_device)))
+        LOGGER.debug('config_rules_per_device={:s}'.format(json.dumps(config_rules_per_device)))
         results = self._do_configurations(config_rules_per_device, endpoints, delete=True)
         LOGGER.debug('results={:s}'.format(str(results)))
         return results
diff --git a/src/service/tests/test_l3nm_gnmi_static_rule_gen/test_unitary_sns4sns.py b/src/service/tests/test_l3nm_gnmi_static_rule_gen/test_unitary_sns4sns.py
index 0177500e2..64035f1bb 100644
--- a/src/service/tests/test_l3nm_gnmi_static_rule_gen/test_unitary_sns4sns.py
+++ b/src/service/tests/test_l3nm_gnmi_static_rule_gen/test_unitary_sns4sns.py
@@ -37,27 +37,23 @@ SERVICE = Service(**json_service_l3nm_planned(
         json_endpoint_id(json_device_id('edge-net'), 'eth1'),
     ],
     config_rules=[
+        json_config_rule_set('/settings', {'address_families': ['IPV4'], 'mtu': 1500}),
+        json_config_rule_set('/static_routing', {}),
+
         json_config_rule_set('/device[core-net]/endpoint[eth1]/settings', {
-            'address_ip': '10.10.10.0', 'address_prefix': 24, 'index': 0
-        }),
-        json_config_rule_set('/device[r1]/endpoint[eth10]/settings', {
-            'address_ip': '10.10.10.229', 'address_prefix': 24, 'index': 0
-        }),
-        json_config_rule_set('/device[r2]/endpoint[eth10]/settings', {
-            'address_ip': '10.158.72.229', 'address_prefix': 24, 'index': 0
+            'address_ip': '10.10.10.0', 'neighbor_address': '10.10.10.229', 'address_prefix': 24, 'index': 0
         }),
         json_config_rule_set('/device[edge-net]/endpoint[eth1]/settings', {
-            'address_ip': '10.158.72.0', 'address_prefix': 24, 'index': 0
+            'address_ip': '10.158.72.0', 'neighbor_address': '10.158.72.229', 'address_prefix': 24, 'index': 0
         }),
     ]
 ))
 
 CONNECTION_ENDPOINTS : List[Tuple[str, str, Optional[str]]] = [
     #('core-net', 'int',   None),
-    ('core-net', 'eth1',  None),
-    ('r1',       'eth10', None), ('r1',       'eth2',  None),
-    ('r2',       'eth1',  None), ('r2',       'eth10', None),
-    ('edge-net', 'eth1',  None),
+    ('core-net', 'eth1',  None), ('r1',       'eth10', None),
+    ('r1',       'eth2',  None), ('r2',       'eth1',  None),
+    ('r2',       'eth10', None), ('edge-net', 'eth1',  None),
     #('edge-net', 'int',   None),
 ]
 
-- 
GitLab