Commit c393cc9c authored by Pablo Armingol's avatar Pablo Armingol
Browse files

feat: update IETF SAP topology YANG mappings and remove redundant README

parent 1af18efc
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -13,8 +13,10 @@
# limitations under the License.

from typing import Dict, Tuple

from common.proto.context_pb2 import Device, DeviceId, EndPoint, EndPointId


class NameMappings:
    def __init__(self) -> None:
        self._device_uuid_to_name   : Dict[str,             str] = dict()
+8 −7
Original line number Diff line number Diff line
@@ -12,19 +12,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import enum, json, logging
import pyangbind.lib.pybindJSON as pybindJSON
from flask import request
from flask.json import jsonify
from flask_restful import Resource
from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME
from common.Settings import get_setting
import enum
import logging

from common.proto.context_pb2 import ContextId, Empty
from common.Settings import get_setting
from common.tools.context_queries.Topology import get_topology_details
from common.tools.object_factory.Context import json_context_id
from context.client.ContextClient import ContextClient
from flask import request
from flask.json import jsonify
from flask_restful import Resource
from nbi.service._tools.Authentication import HTTP_AUTH
from nbi.service._tools.HttpStatusCodes import HTTP_OK, HTTP_SERVERERROR

from .YangHandler import YangHandler

LOGGER = logging.getLogger(__name__)
+0 −14
Original line number Diff line number Diff line
# IETF Network

This NBI plugin implements a basic skeleton for the IETF Network data model.

## DISCLAIMER
__USE WITH CARE! This plugin is NOT production ready, it contains many hardcodings based on ongoing tests.__

## IETF RFCs/drafts:
- RFC 8795 - YANG Data Model for Traffic Engineering (TE) Topologies (https://datatracker.ietf.org/doc/html/rfc8795)
- RFC 8776 - Common YANG Data Types for Traffic Engineering (https://datatracker.ietf.org/doc/html/rfc8776)
- RFC 8345 - A YANG Data Model for Network Topologies (https://datatracker.ietf.org/doc/html/rfc8345)
- RFC 6991 - Common YANG Data Types (https://datatracker.ietf.org/doc/html/rfc6991)
- RFC draft-ietf-ccamp-eth-client-te-topo-yang-05 - A YANG Data Model for Ethernet TE Topology (https://datatracker.ietf.org/doc/draft-ietf-ccamp-eth-client-te-topo-yang/)
- RFC draft-ietf-ccamp-client-signal-yang-10 - A YANG Data Model for Transport Network Client Signals (https://datatracker.ietf.org/doc/draft-ietf-ccamp-client-signal-yang/)
+6 −4
Original line number Diff line number Diff line
@@ -12,19 +12,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import enum, json, logging
import pyangbind.lib.pybindJSON as pybindJSON
import enum
import logging

from flask import request
from flask.json import jsonify
from flask_restful import Resource
from common.Constants import DEFAULT_CONTEXT_NAME, DEFAULT_TOPOLOGY_NAME
from common.Settings import get_setting

from common.proto.context_pb2 import ContextId, Empty
from common.Settings import get_setting
from common.tools.context_queries.Topology import get_topology_details
from common.tools.object_factory.Context import json_context_id
from context.client.ContextClient import ContextClient
from nbi.service._tools.Authentication import HTTP_AUTH
from nbi.service._tools.HttpStatusCodes import HTTP_OK, HTTP_SERVERERROR

from .YangHandler import YangHandler

LOGGER = logging.getLogger(__name__)
+59 −55
Original line number Diff line number Diff line
@@ -13,13 +13,17 @@
# limitations under the License.

import json
import libyang, logging, os
import logging
import os
import re
from typing import Any
from common.proto.context_pb2 import TopologyDetails, Device, Link
from .NameMapping import NameMappings
from context.client.ContextClient import ContextClient

import libyang
from common.proto.context_pb2 import Device, DeviceId, Link, TopologyDetails
from common.tools.object_factory.Device import json_device_id
from common.proto.context_pb2 import DeviceId
from context.client.ContextClient import ContextClient

from .NameMapping import NameMappings

LOGGER = logging.getLogger(__name__)

@@ -36,24 +40,39 @@ class YangHandler:
    def compose_network(self, te_topology_name: str, topology_details: TopologyDetails) -> dict:
        networks = self._yang_context.create_data_path('/ietf-network:networks')


        network = networks.create_path(f'network[network-id="{te_topology_name}"]')
        network.create_path('network-id', te_topology_name)

        # Add supporting-network pointing to urn:tfs:network:admin
        supporting_network_id = "urn:tfs:network:admin"
        supporting_nw = network.create_path(f'supporting-network[network-ref="{supporting_network_id}"]')
        supporting_nw.create_path('network-ref', supporting_network_id)

        network_types = network.create_path('network-types')
        sap_network = network_types.create_path('ietf-sap-ntw:sap-network')


        available_services = ["ietf-vpn-common:l3vpn"] #no se muy bien como coger todos los servicios disponibles
        
        for service in available_services:
            sap_network.create_path('service-type', service)  #no se muy bien como coger todos los servicios disponibles

        connected_tps = set()  # Conjunto local para TPs conectados
        available_services = [
            "ietf-vpn-common:l3vpn",
            "ietf-vpn-common:vpls",
            "ietf-vpn-common:vpws",
            "ietf-vpn-common:vpws-evpn",
            "ietf-vpn-common:mpls-evpn",
            "ietf-vpn-common:vxlan-evpn",
            "ietf-vpn-common:pbb-evpn",
            "ietf-sap-ntw:network-slice",
            "ietf-sap-ntw:virtual-network",
            "ietf-sap-ntw:enhanced-vpn",
            "ietf-sap-ntw:sdwan",
            "ietf-sap-ntw:basic-connectivity"
        ]
        service = available_services[0] #TO DO: get services from config
        if service in available_services:
            sap_network.create_path('service-type', service)

        connected_tps = set() 
        name_mappings = NameMappings()

        # Depuración inicial
        LOGGER.info(f"Procesando topology_details: {len(topology_details.devices)} devices, {len(topology_details.links)} links.")
        LOGGER.info(f"Processing topology_details: {len(topology_details.devices)} devices, {len(topology_details.links)} links.")

        if topology_details.links:
            for link in topology_details.links:
@@ -62,29 +81,19 @@ class YangHandler:
                connected_tps.add(source_name)
                connected_tps.add(dest_name)


        if topology_details.devices:
            for device in topology_details.devices:
                self.compose_node(device, name_mappings, network, connected_tps)




        return json.loads(networks.print_mem('json'))


        
    def compose_node(self, dev: Device, name_mappings: NameMappings, network: Any, connected_tps: set) -> None:      



        device_name = dev.name
        name_mappings.store_device_name(dev)

        context_client = ContextClient()
        device = context_client.GetDevice(DeviceId(**json_device_id(device_name)))
    
        
        for config in device.device_config.config_rules:
            if config.WhichOneof('config_rule') == 'custom' and config.custom.resource_key == '_connect/settings':
                try:
@@ -98,67 +107,63 @@ class YangHandler:
        node = network.create_path(f'node[node-id="{sap_device}"]')
        node.create_path('node-id', sap_device)

    

        # Add supporting-node pointing to urn:tfs:network:admin and device node name (using urn:tfs:node:<dev.name>)
        supporting_network_id = "urn:tfs:network:admin"
        supporting_node_id = f"urn:tfs:node:{device_name}"
        supporting_nd = node.create_path(f'supporting-node[network-ref="{supporting_network_id}"][node-ref="{supporting_node_id}"]')
        supporting_nd.create_path('network-ref', supporting_network_id)
        supporting_nd.create_path('node-ref', supporting_node_id)

        for endpoint in device.device_endpoints:
            name_mappings.store_endpoint_name(dev, endpoint)
            #LOGGER.info(f"Métodos disponibles en 'endpoint': {dir(endpoint)}")

        

        self._process_device_config(device, node, connected_tps, sap_device)
    

   
    
    def _process_device_config(self, device: Device, node: Any, connected_tps: set, sap_device: str) -> None:
        #sLOGGER.info(f"Métodos disponibles en 'node': {dir(node)}")
        #LOGGER.info(f"Métodos disponibles en 'nedevice': {dir(device)}")
        sap_device = sap_device

                
        for config in device.device_config.config_rules:

            if config.WhichOneof('config_rule') != 'custom' or '/interface[' not in config.custom.resource_key:
                continue     
            # Extract interface_name from key: e.g. "/interface[eth-1/0/10.41]" -> "eth-1/0/10.41"
            match = re.search(r'/interface\[([^\]]+)\]', config.custom.resource_key)
            if not match:
                continue
            interface_name = match.group(1)
            
            for endpoint in device.device_endpoints:
                endpoint_name = endpoint.name
                if endpoint.endpoint_id.endpoint_uuid.uuid in connected_tps:
                    continue
                if f'/interface[{endpoint_name}]' in config.custom.resource_key or f'/interface[{endpoint_name}.' in config.custom.resource_key:
                    self._create_termination_point(node, sap_device, config.custom.resource_value)

                    self._create_termination_point(node, sap_device, config.custom.resource_value, interface_name)

    
    def _create_termination_point(self, node: Any, sap_device: str, resource_value: str) -> None:
    def _create_termination_point(self, node: Any, sap_device: str, resource_value: str, interface_name: str) -> None:
        import base64
        config_data = json.loads(resource_value)
        ip_addresses = self._extract_ip_addresses(config_data)
        if ip_addresses:
            #tp = node.create_path(f'ietf-network-topology:termination-point[tp-id="{interface_name}"]')
            service = node.create_path(f'ietf-sap-ntw:service[service-type="ietf-vpn-common:l3vpn"]')

            sap_id_value = config_data.get('sap_id', "ERROR")
            final_sap_id = f"SAP{sap_device}-{sap_id_value}"
            # Proprietary obfuscated ID of the interface name:
            encoded_if = base64.b64encode(interface_name.encode('utf-8')).decode('utf-8').replace('=', '')
            final_sap_id = f"{sap_device}-{encoded_if}"

            sap = service.create_path(f'sap[sap-id="{final_sap_id}"]')
            #sap.create_path('peer-sap-id', interface_name)
            sap.create_path('peer-sap-id', "NOT IMPLEMENTED")
            
            # Add parent-termination-point pointing to the physical tp-id
            parent_tp = f"urn:tfs:tp:{interface_name}"
            sap.create_path('parent-termination-point', parent_tp)
            # sap.create_path('peer-sap-id', "NOT_IMPLEMENTED") # OPTIONAL FIELD
            sap_status = sap.create_path('sap-status')
            sap_status.create_path('status', "ietf-vpn-common:op-up") #NOT IMPLEMENTED

            sap_status.create_path('status', "ietf-vpn-common:op-up") 
            service_status = sap.create_path('service-status')
            admin_status = service_status.create_path('admin-status')
            oper_status = service_status.create_path('oper-status')
            admin_status.create_path('status', "ietf-vpn-common:admin-up") #NOT IMPLEMENTED
            oper_status.create_path('status', "ietf-vpn-common:op-up") #NOT IMPLEMENTED


            admin_status.create_path('status', "ietf-vpn-common:admin-up") # TO DO: Dynamic verification needed
            oper_status.create_path('status', "ietf-vpn-common:op-up") # TO DO: Dynamic verification needed

    @staticmethod

    def _extract_ip_addresses(resource_value: dict) -> list:
        ip_addresses = []
        if 'address_ip' in resource_value:
@@ -167,6 +172,5 @@ class YangHandler:
            ip_addresses.append(resource_value['address_ipv6'])
        return ip_addresses


    def destroy(self) -> None:
        self._yang_context.destroy()
Loading