From 2b2ade7ce06c98623ee870f147eb3825cb9f3667 Mon Sep 17 00:00:00 2001
From: gifrerenom <lluis.gifre@cttc.es>
Date: Sat, 11 May 2024 17:14:59 +0000
Subject: [PATCH] Service component - L3NM gNMI OpenConfig Service Handler:

- Corrected static_routes structure and metrics management
---
 .../ConfigRuleComposer.py                     | 30 +++++++---------
 .../StaticRouteGenerator.py                   | 34 +++++++++++++------
 2 files changed, 37 insertions(+), 27 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 5db2c5b2f..42747a1ae 100644
--- a/src/service/service/service_handlers/l3nm_gnmi_openconfig/ConfigRuleComposer.py
+++ b/src/service/service/service_handlers/l3nm_gnmi_openconfig/ConfigRuleComposer.py
@@ -22,7 +22,7 @@ from service.service.service_handler_api.AnyTreeTools import TreeNode
 NETWORK_INSTANCE = 'teraflowsdn'
 
 RE_IF = re.compile(r'^\/interface\[([^\]]+)\]\/subinterface\[([^\]]+)\]$')
-RE_SR = re.compile(r'^\/network_instance\[([^\]]+)\]\/protocols\[STATIC\]/route\[ ([^\:]+)\:([^\]]+)\]$')
+RE_SR = re.compile(r'^\/network_instance\[([^\]]+)\]\/protocols\[STATIC\]/route\[([^\:]+)\:([^\]]+)\]$')
 
 def _interface(
     interface : str, if_type : Optional[str] = 'l3ipvlan', index : int = 0, vlan_id : Optional[int] = None,
@@ -52,15 +52,15 @@ def _network_instance_protocol_static(ni_name : str) -> Tuple[str, Dict]:
     return _network_instance_protocol(ni_name, 'STATIC')
 
 def _network_instance_protocol_static_route(
-    ni_name : str, prefix : str, next_hop : str, index : int = 0, metric : Optional[int] = None
+    ni_name : str, prefix : str, next_hop : str, metric : int
 ) -> Tuple[str, Dict]:
     protocol = 'STATIC'
-    path = '/network_instance[{:s}]/protocols[{:s}]/static_route[{:s}:{:d}]'.format(ni_name, protocol, prefix, index)
+    path = '/network_instance[{:s}]/protocols[{:s}]/static_route[{:s}:{:d}]'.format(ni_name, protocol, prefix, metric)
+    index = 'AUTO_{:d}_{:s}'.format(metric, next_hop.replace('.', '-'))
     data = {
         'name': ni_name, 'identifier': protocol, 'protocol_name': protocol,
-        'prefix': prefix, 'index': index, 'next_hop': next_hop
+        'prefix': prefix, 'index': index, 'next_hop': next_hop, 'metric': metric
     }
-    if metric is not None: data['metric'] = metric
     return path, data
 
 def _network_instance_interface(ni_name : str, interface : str, sub_interface_index : int) -> Tuple[str, Dict]:
@@ -113,9 +113,7 @@ class DeviceComposer:
         self.objekt : Optional[Device] = None
         self.endpoints : Dict[str, EndpointComposer] = dict()
         self.connected : Set[str] = set()
-        
-        # {prefix => {index => (next_hop, metric)}}
-        self.static_routes : Dict[str, Dict[int, Tuple[str, Optional[int]]]] = dict()
+        self.static_routes : Dict[str, Dict[int, str]] = dict() # {prefix => {metric => next_hop}}
     
     def get_endpoint(self, endpoint_uuid : str) -> EndpointComposer:
         if endpoint_uuid not in self.endpoints:
@@ -147,22 +145,20 @@ class DeviceComposer:
 
             match = RE_SR.match(config_rule_custom.resource_key)
             if match is not None:
-                ni_name, prefix, index = match.groups()
+                ni_name, prefix, metric = match.groups()
                 if ni_name != NETWORK_INSTANCE: continue
                 resource_value : Dict = json.loads(config_rule_custom.resource_value)
                 next_hop = resource_value['next_hop']
-                metric   = resource_value.get('metric')
-                self.static_routes.setdefault(prefix, dict())[index] = (next_hop, metric)
+                self.static_routes.setdefault(prefix, dict())[metric] = next_hop
 
         if settings is None: return
         json_settings : Dict = settings.value
         static_routes : List[Dict] = json_settings.get('static_routes', [])
         for static_route in static_routes:
             prefix   = static_route['prefix']
-            index    = static_route.get('index', 0)
             next_hop = static_route['next_hop']
-            metric   = static_route.get('metric')
-            self.static_routes.setdefault(prefix, dict())[index] = (next_hop, metric)
+            metric   = static_route.get('metric', 0)
+            self.static_routes.setdefault(prefix, dict())[metric] = next_hop
 
     def get_config_rules(self, network_instance_name : str, delete : bool = False) -> List[Dict]:
         SELECTED_DEVICES = {DeviceTypeEnum.PACKET_ROUTER.value, DeviceTypeEnum.EMULATED_PACKET_ROUTER.value}
@@ -178,11 +174,11 @@ class DeviceComposer:
             config_rules.append(
                 json_config_rule(*_network_instance_protocol_static(network_instance_name))
             )
-        for prefix, indexed_static_rule in self.static_routes.items():
-            for index, (next_hop, metric) in indexed_static_rule.items():
+        for prefix, metric_next_hop in self.static_routes.items():
+            for metric, next_hop in metric_next_hop.items():
                 config_rules.append(
                     json_config_rule(*_network_instance_protocol_static_route(
-                        network_instance_name, prefix, next_hop, index=index, metric=metric
+                        network_instance_name, prefix, next_hop, metric
                     ))
                 )
         if delete: config_rules = list(reversed(config_rules))
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 6479a07fe..a16e4d5b1 100644
--- a/src/service/service/service_handlers/l3nm_gnmi_openconfig/StaticRouteGenerator.py
+++ b/src/service/service/service_handlers/l3nm_gnmi_openconfig/StaticRouteGenerator.py
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import json, logging, netaddr
+import json, logging, netaddr, sys
 from typing import List, Optional, Tuple
 from .ConfigRuleComposer import ConfigRuleComposer
 
@@ -153,7 +153,8 @@ class StaticRouteGenerator:
                 if ip_network_a in ROOT_NEIGHBOR_ROUTING_NETWORK: continue
                 endpoint_a_ip_network = _compose_ipv4_network(endpoint_a.ipv4_address, endpoint_a.ipv4_prefix_len)
                 next_hop = str(endpoint_a_ip_network.ip)
-                device_b.static_routes.setdefault(ip_network_a, dict())[0] = (next_hop, None)
+                metric = 1
+                device_b.static_routes.setdefault(ip_network_a, dict())[metric] = next_hop
 
             # Compute static routes from networks connected in device_b
             for ip_network_b in device_b.connected:
@@ -162,22 +163,35 @@ class StaticRouteGenerator:
                 if ip_network_b in ROOT_NEIGHBOR_ROUTING_NETWORK: continue
                 endpoint_b_ip_network = _compose_ipv4_network(endpoint_b.ipv4_address, endpoint_b.ipv4_prefix_len)
                 next_hop = str(endpoint_b_ip_network.ip)
-                device_a.static_routes.setdefault(ip_network_b, dict())[0] = (next_hop, None)
+                metric = 1
+                device_a.static_routes.setdefault(ip_network_b, dict())[metric] = next_hop
 
             # Propagate static routes from networks connected in device_a
-            for ip_network_a in device_a.static_routes.keys():
+            for ip_network_a, metric_next_hop in device_a.static_routes.items():
                 if ip_network_a in device_b.connected: continue
-                if ip_network_a in device_b.static_routes: continue
                 if ip_network_a in ROOT_NEIGHBOR_ROUTING_NETWORK: continue
                 endpoint_a_ip_network = _compose_ipv4_network(endpoint_a.ipv4_address, endpoint_a.ipv4_prefix_len)
-                next_hop = str(endpoint_a_ip_network.ip)
-                device_b.static_routes.setdefault(ip_network_a, dict())[0] = (next_hop, None)
+                if ip_network_a in device_b.static_routes:
+                    current_metric = min(device_b.static_routes[ip_network_a].keys())
+                else:
+                    current_metric = int(sys.float_info.max)
+                for metric, next_hop in metric_next_hop.items():
+                    new_metric = metric + 1
+                    if new_metric >= current_metric: continue
+                    next_hop_a = str(endpoint_a_ip_network.ip)
+                    device_b.static_routes.setdefault(ip_network_a, dict())[metric] = next_hop_a
 
             # Propagate static routes from networks connected in device_b
             for ip_network_b in device_b.static_routes.keys():
                 if ip_network_b in device_a.connected: continue
-                if ip_network_b in device_a.static_routes: continue
                 if ip_network_b in ROOT_NEIGHBOR_ROUTING_NETWORK: continue
                 endpoint_b_ip_network = _compose_ipv4_network(endpoint_b.ipv4_address, endpoint_b.ipv4_prefix_len)
-                next_hop = str(endpoint_b_ip_network.ip)
-                device_a.static_routes.setdefault(ip_network_b, dict())[0] = (next_hop, None)
+                if ip_network_b in device_a.static_routes:
+                    current_metric = min(device_a.static_routes[ip_network_b].keys())
+                else:
+                    current_metric = int(sys.float_info.max)
+                for metric, next_hop in metric_next_hop.items():
+                    new_metric = metric + 1
+                    if new_metric >= current_metric: continue
+                    next_hop_b = str(endpoint_b_ip_network.ip)
+                    device_a.static_routes.setdefault(ip_network_b, dict())[metric] = next_hop_b
-- 
GitLab