Commit fc16b406 authored by rahhal's avatar rahhal
Browse files

Challenge #3 – L2VPN Support - Change L2VPN realizer to use IETF L2NM YANG model

parent a86f79df
Loading
Loading
Loading
Loading
+83 −0
Original line number Diff line number Diff line
{
    "ietf-l3vpn-svc:l3vpn-svc": {
        "vpn-services": {"vpn-service": [{"vpn-id": "ietf-l3vpn-svc"}]},
        "sites": {
            "site": [
                {
                    "site-id": "site_DC1",
                    "management": {"type": "ietf-l3vpn-svc:provider-managed"},
                    "locations": {"location": [{"location-id": "DC1"}]},
                    "devices": {"device": [{"device-id": "dc1", "location": "DC1"}]},
                    "site-network-accesses": {
                        "site-network-access": [
                            {
                                "site-network-access-id": "eth1",
                                "site-network-access-type": "ietf-l3vpn-svc:multipoint",
                                "device-reference": "dc1",
                                "vpn-attachment": {"vpn-id": "ietf-l3vpn-svc", "site-role": "ietf-l3vpn-svc:spoke-role"},
                                "ip-connection": {
                                    "ipv4": {
                                        "address-allocation-type": "ietf-l3vpn-svc:static-address",
                                        "addresses": {
                                            "provider-address": "192.168.1.1",
                                            "customer-address": "192.168.1.10",
                                            "prefix-length": 24
                                        }
                                    }
                                },
                                "service": {
                                    "svc-mtu": 1500,
                                    "svc-input-bandwidth": 1000000000,
                                    "svc-output-bandwidth": 1000000000,
                                    "qos": {"qos-profile": {"classes": {"class": [{
                                        "class-id": "qos-realtime",
                                        "direction": "ietf-l3vpn-svc:both",
                                        "latency": {"latency-boundary": 10},
                                        "bandwidth": {"guaranteed-bw-percent": 100}
                                    }]}}}
                                }
                            }
                        ]
                    }
                },
                {
                    "site-id": "site_DC2",
                    "management": {"type": "ietf-l3vpn-svc:provider-managed"},
                    "locations": {"location": [{"location-id": "DC2"}]},
                    "devices": {"device": [{"device-id": "dc2", "location": "DC2"}]},
                    "site-network-accesses": {
                        "site-network-access": [
                            {
                                "site-network-access-id": "eth1",
                                "site-network-access-type": "ietf-l3vpn-svc:multipoint",
                                "device-reference": "dc2",
                                "vpn-attachment": {"vpn-id": "ietf-l3vpn-svc", "site-role": "ietf-l3vpn-svc:hub-role"},
                                "ip-connection": {
                                    "ipv4": {
                                        "address-allocation-type": "ietf-l3vpn-svc:static-address",
                                        "addresses": {
                                            "provider-address": "192.168.2.1",
                                            "customer-address": "192.168.2.10",
                                            "prefix-length": 24
                                        }
                                    }
                                },
                                "service": {
                                    "svc-mtu": 1500,
                                    "svc-input-bandwidth": 1000000000,
                                    "svc-output-bandwidth": 1000000000,
                                    "qos": {"qos-profile": {"classes": {"class": [{
                                        "class-id": "qos-realtime",
                                        "direction": "ietf-l3vpn-svc:both",
                                        "latency": {"latency-boundary": 10},
                                        "bandwidth": {"guaranteed-bw-percent": 100}
                                    }]}}}
                                }
                            }
                        ]
                    }
                }
            ]
        }
    }
}
+6 −3
Original line number Diff line number Diff line
# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
# Copyright 2025 Telefonica Innovación Digital S.L.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,8 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# This file is an original contribution from Telefonica Innovación Digital S.L.

import logging, os
# Default logging level
DEFAULT_LOGGING_LEVEL = logging.INFO
@@ -33,5 +31,10 @@ TEMPLATES_PATH = os.path.join(SRC_PATH, "templates")
TFS_UPLOAD = False
# Teraflow IP
TFS_IP = "192.168.165.10"
path = "restconf/data/ietf-l2vpn-svc:l2vpn-svc/vpn-services"
#path_l3 = "restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services"

#json_file = "/home/tfs/nsc-hackfest7/src/hackfest7/slice_request.json"
#/home/tfs/nsc-hackfest7/src/hackfest7/slice_request.json
# Flag to determine if additional L2VPN configuration support is required for deploying L2VPNs with path selection
TFS_L2VPN_SUPPORT = False
 No newline at end of file
+31 −16
Original line number Diff line number Diff line
# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
# Copyright 2025 Telefonica Innovación Digital S.L.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
import logging, requests, json
from netmiko import ConnectHandler
from src.Constants import DEFAULT_LOGGING_LEVEL
from requests.auth import HTTPBasicAuth

# Configure logging to provide clear and informative log messages
logging.basicConfig(
@@ -23,25 +24,39 @@ logging.basicConfig(

#Teraflow
class tfs_connector():
    #curl -X POST -H "Content-Type: application/json" --user "admin:admin" --data @ietf-l3vpn-service.json http://10.1.7.197/restconf/data/ietf-l3vpn-svc:l3vpn-svc/vpn-services

    def simple_post(self, tfs_ip, service):
    def simple_post(self, tfs_ip,service,path):
        user="admin"
        password="admin"
        token=""
        session = requests.Session()
        session.auth = (user, password)
        url=f'http://{tfs_ip}/webui'
        response=session.get(url=url)
        for item in response.iter_lines():
            if("csrf_token" in str(item)):
                string=str(item).split('<input id="csrf_token" name="csrf_token" type="hidden" value=')[1]
                token=string.split(">")[0].strip('"')
        logging.debug("csrf token %s",token)

        files = {'descriptors': ("data.json", json.dumps(service).encode("utf-8"), "application/json")}
        #session = requests.Session()
        #session.auth = (user, password)
        #url = f'http://{tfs_ip}/{path}'
        url_l2 = f'http://{user}:{password}@{tfs_ip}/{path}'
        #url_l3 = f'http://{user}:{password}@{tfs_ip}/{path_l3}'
        logging.info(service)
        #response=session.get(url=url)
        #for item in response.iter_lines():
        #    if("csrf_token" in str(item)):
        #        string=str(item).split('<input id="csrf_token" name="csrf_token" type="hidden" value=')[1]
        #        token=string.split(">")[0].strip('"')
        #logging.debug("csrf token %s",token)

        #files = {'descriptors': ("data.json", json.dumps(service).encode("utf-8"), "application/json")}
        headers = {'Content-Type': 'application/json'}
        json_data = json.dumps(service)
        #json_url="/home/tfs/nsc-hackfest7/our-temp/ietf-l3vpn-service.json"
        #with open(json_url, 'r', encoding="UTF-8") as file:
        #    json_data = json.load(file)
        #    logging.debug("this is the JSON data that we have")
        #    logging.debug(json_data)
        #    json_decoded = json.dumps(json_data)
        json_decoded = json_data
        token={'csrf_token':token}
        response = session.post(url,files=files,data=token,timeout=60)
        logging.debug("Http response: %s",response.text)
        response = requests.post(url_l2, headers=headers, data=json_decoded, timeout=60)
        logging.info(response.text)
        #logging.debug("Http response: %s",response.text)
        return response

#CISCO
+135 −74
Original line number Diff line number Diff line
# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)
# Copyright 2025 Telefonica Innovación Digital S.L.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
import json, time, os, logging, uuid
from datetime import datetime
from src.helpers import tfs_connector, cisco_connector
from src.Constants import DEFAULT_LOGGING_LEVEL, TFS_UPLOAD, TFS_IP, TFS_L2VPN_SUPPORT, SRC_PATH, TEMPLATES_PATH
from src.Constants import DEFAULT_LOGGING_LEVEL, TFS_UPLOAD, TFS_IP, TFS_L2VPN_SUPPORT, SRC_PATH, TEMPLATES_PATH, path, path_l3

# Configure logging to provide clear and informative log messages
logging.basicConfig(
@@ -38,7 +38,7 @@ class NSController:
    - Slice Realization: Convert intents to specific network configurations (L2VPN, L3VPN)
    """

    def __init__(self, upload_to_tfs = TFS_UPLOAD, tfs_ip=TFS_IP, need_l2vpn_support=TFS_L2VPN_SUPPORT):
    def __init__(self, upload_to_tfs = TFS_UPLOAD, tfs_ip=TFS_IP,path=path, need_l2vpn_support=TFS_L2VPN_SUPPORT):
        """
        Initialize the Network Slice Controller.

@@ -58,6 +58,9 @@ class NSController:
        """
        self.upload_to_tfs = upload_to_tfs
        self.tfs_ip = tfs_ip
        self.path = path
        self.path_l3 = path_l3
        #self.json_file = json_file
        self.answer = {}
        self.cool_answer = {}
        self.start_time = 0
@@ -257,7 +260,8 @@ class NSController:

            # Reset requests and load IETF template
            self.__load_template(1, os.path.join(TEMPLATES_PATH, "ietf_template_empty.json"))  
            tfs_requests = {"services":[]}
            #tfs_requests = {"services":[]}
            tfs_requests = {}
            
            # Process intent (translate if 3GPP)
            ietf_intents = self.__nbi_processor(intent_json)
@@ -271,23 +275,24 @@ class NSController:
                    self.__mapper(intent)
                    # Realizer
                    tfs_request = self.__realizer(intent)
                    tfs_requests["services"].append(tfs_request)
                    #tfs_requests.append(tfs_request)
                    tfs_requests = tfs_request
            else:
                return self.__send_response(False, code=404, message="No intents found")

            # Generated service
            logging.debug(json.dumps(tfs_requests, indent=2))
            #logging.debug(json.dumps(tfs_requests, indent=2))
            
            # Optional: Upload template to Teraflow
            if self.upload_to_tfs == True:
                response = tfs_connector().simple_post(self.tfs_ip, tfs_requests)
                response = tfs_connector().simple_post(self.tfs_ip, tfs_requests, self.path)

                if not response.ok:
                    return self.__send_response(False, code=response.status_code, message=f"Teraflow upload failed. Response: {response.text}")
                # if not response.ok:
                #     return self.__send_response(False, code=response.status_code, message=f"Teraflow upload failed. Response: {response.text}")
                
                # For deploying an L2VPN with path selection (not supported by Teraflow)
                if self.need_l2vpn_support:
                    self.__tfs_l2vpn_support(tfs_requests["services"])
                # if self.need_l2vpn_support:
                #     self.__tfs_l2vpn_support(tfs_requests["services"])

                logging.info("Request sent to Teraflow")

@@ -405,7 +410,7 @@ class NSController:
            self.__nrp(order, nrp)
        else:
            # Select slice service method
            return self.__select_way("L2VPN", ietf_intent)
            return self.__select_way("L3VPN", ietf_intent)

    ### Generic functionalities
    def __load_template(self, which, dir_t):
@@ -461,6 +466,7 @@ class NSController:
                "setup_time": self.setup_time
            }
            # Add slice details to the response
            logging.info(self.answer)
            for subnet in self.answer:
                slice_info = {
                    "id": subnet,
@@ -847,7 +853,6 @@ class NSController:
        elif way == "L3VPN":
            realizing_request = self.__tfs_l3vpn(ietf_intent)
        return realizing_request
    
    def __tfs_l2vpn(self, ietf_intent):
       """
       Translate slice intent into a TeraFlow service request.
@@ -870,53 +875,109 @@ class NSController:
        # Hardcoded router endpoints
        # TODO (should be dynamically determined)
        origin_router_id = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][0]["attachment-circuits"]["attachment-circuit"][0]["sdp-peering"]["peer-sap-id"]
        origin_router_if = '0/0/0-GigabitEthernet0/0/0/0'
        origin_router_if = 'eth2'
        destination_router_id = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][1]["attachment-circuits"]["attachment-circuit"][0]["sdp-peering"]["peer-sap-id"]
        destination_router_if = '0/0/3-GigabitEthernet0/0/0/3'
        destination_router_if = 'eth2'

       # Extract QoS Profile from intent
       QoSProfile = ietf_intent["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["id"]

       vlan_value = 0
        
        # Load L2VPN service template
        self.__load_template(2, os.path.join(TEMPLATES_PATH, "L2-VPN_template_empty.json"))
        tfs_request = json.loads(str(self.__teraflow_template))["services"][0]

        # Generate unique service UUID
        tfs_request["service_id"]["service_uuid"]["uuid"] += "-" + str(int(datetime.now().timestamp() * 1e7))

        # Configure service endpoints
        for endpoint in tfs_request["service_endpoint_ids"]:
            endpoint["device_id"]["device_uuid"]["uuid"] = origin_router_id if endpoint is tfs_request["service_endpoint_ids"][0] else destination_router_id
            endpoint["endpoint_uuid"]["uuid"] = origin_router_if if endpoint is tfs_request["service_endpoint_ids"][0] else destination_router_if
       self.__load_template(2, os.path.join(TEMPLATES_PATH, "ietfL2VPN_template.json"))
       tfs_request = json.loads(str(self.__teraflow_template))
       full_id = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["id"]
       uuid_only = full_id.split("slice-service-")[-1]
       tfs_request["ietf-l2vpn-svc:vpn-service"][0]["vpn-id"] = uuid_only
       for site in tfs_request["ietf-l2vpn-svc:vpn-service"][0]["site"]:
           if site is tfs_request["ietf-l2vpn-svc:vpn-service"][0]["site"][0]:
               site["site-id"] = origin_router_id
               site["site-location"] = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][0]["node-id"]
               site["site-network-access"]["interface"]["ip-address"] = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][0]["sdp-ip-address"]
           else:
               site["site-id"] = destination_router_id
               site["site-location"] = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][1]["node-id"]
               site["site-network-access"]["interface"]["ip-address"] = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][1]["sdp-ip-address"]

       self.answer[self.subnet]["QoS Requirements"] = []
        # Add service constraints
        for i, constraint in enumerate(tfs_request["service_constraints"]):
            bound = ietf_intent["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["slo-policy"]["metric-bound"][i]["bound"]
            self.answer[self.subnet]["QoS Requirements"].append(bound)
            constraint["custom"]["constraint_value"] = str(bound)

        # Add configuration rules
        for i, config_rule in enumerate(tfs_request["service_config"]["config_rules"][1:], start=1):
            router_id = origin_router_id if i == 1 else destination_router_id
            router_if = origin_router_if if i == 1 else destination_router_if
            resource_value = config_rule["custom"]["resource_value"]

            sdp_index = i - 1
            vlan_value = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][sdp_index]["service-match-criteria"]["match-criterion"][0]["value"]
            resource_value["vlan_id"] = int(vlan_value)
            resource_value["circuit_id"] = vlan_value
            resource_value["remote_router"] = destination_router_id if i == 1 else origin_router_id
            resource_value["ni_name"] = 'ELAN{:s}'.format(str(vlan_value))
            config_rule["custom"]["resource_key"] = f"/device[{router_id}]/endpoint[{router_if}]/settings"
       for i in range(len(ietf_intent["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["slo-policy"]["metric-bound"])):
            logging.info(self.answer[self.subnet])
            self.answer[self.subnet]["QoS Requirements"].append(ietf_intent["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["slo-policy"]["metric-bound"][i]["bound"])

        # Log and store VLAN information
       logging.info(f"Intent with VLAN {vlan_value} realized\n")
       self.answer[self.subnet]["VLAN"] = vlan_value
       logging.info(tfs_request)
       with open(os.path.join(TEMPLATES_PATH, "l2vpn_request.json"), "w") as archivo:
           archivo.write(json.dumps(tfs_request,indent=2))
       return tfs_request
 
#    def __tfs_l2vpn(self, ietf_intent):
#        """
#        Translate slice intent into a TeraFlow service request.
#
#        This method prepares a L2VPN service request by:
#        1. Defining endpoint routers
#        2. Loading a service template
#        3. Generating a unique service UUID
#        4. Configuring service endpoints
#        5. Adding QoS constraints
#        6. Preparing configuration rules for network interfaces
#
#        Args:
#            ietf_intent (dict): IETF-formatted network slice intent.
#
#        Returns:
#            dict: A TeraFlow service request for L2VPN configuration.
#
#        """
#        # Hardcoded router endpoints
#        # TODO (should be dynamically determined)
#        origin_router_id = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][0]["attachment-circuits"]["attachment-circuit"][0]["sdp-peering"]["peer-sap-id"]
#        origin_router_if = 'eth2'
#        destination_router_id = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][1]["attachment-circuits"]["attachment-circuit"][0]["sdp-peering"]["peer-sap-id"]
#        destination_router_if = 'eth2'
#
#        # Extract QoS Profile from intent
#        QoSProfile = ietf_intent["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["id"]
#
#        vlan_value = 0
#        
#        # Load L2VPN service template
#        self.__load_template(2, os.path.join(TEMPLATES_PATH, "ietfL2VPN_template"))
#        tfs_request = json.loads(str(self.__teraflow_template))["services"][0]
#
#        # Generate unique service UUID
#        tfs_request["service_id"]["service_uuid"]["uuid"] += "-" + str(int(datetime.now().timestamp() * 1e7))
#
#        # Configure service endpoints
#        for endpoint in tfs_request["service_endpoint_ids"]:
#            endpoint["device_id"]["device_uuid"]["uuid"] = origin_router_id if endpoint is tfs_request["service_endpoint_ids"][0] else destination_router_id
#            endpoint["endpoint_uuid"]["uuid"] = origin_router_if if endpoint is tfs_request["service_endpoint_ids"][0] else destination_router_if
#
#        self.answer[self.subnet]["QoS Requirements"] = []
#        # Add service constraints
#        for i, constraint in enumerate(tfs_request["service_constraints"]):
#            bound = ietf_intent["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["slo-policy"]["metric-bound"][i]["bound"]
#            self.answer[self.subnet]["QoS Requirements"].append(bound)
#            constraint["custom"]["constraint_value"] = str(bound)
#
#        # Add configuration rules
#        for i, config_rule in enumerate(tfs_request["service_config"]["config_rules"][1:], start=1):
#            router_id = origin_router_id if i == 1 else destination_router_id
#            router_if = origin_router_if if i == 1 else destination_router_if
#            resource_value = config_rule["custom"]["resource_value"]
#
#            sdp_index = i - 1
#            vlan_value = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][sdp_index]["service-match-criteria"]["match-criterion"][0]["value"]
#            resource_value["vlan_id"] = int(vlan_value)
#            resource_value["circuit_id"] = vlan_value
#            resource_value["remote_router"] = destination_router_id if i == 1 else origin_router_id
#            resource_value["ni_name"] = 'ELAN{:s}'.format(str(vlan_value))
#            config_rule["custom"]["resource_key"] = f"/device[{router_id}]/endpoint[{router_if}]/settings"
#
#        # Log and store VLAN information
#        logging.info(f"Intent with VLAN {vlan_value} realized\n")
#        self.answer[self.subnet]["VLAN"] = vlan_value
#        return tfs_request
#    
    def __tfs_l2vpn_support(self, requests):
        """
        Configuration support for L2VPN with path selection based on MPLS traffic-engineering tunnels
@@ -1006,11 +1067,11 @@ class NSController:
            dict: A TeraFlow service request for L3VPN configuration.
        """
        # Hardcoded router endpoints
        # TODO should be dynamically determined
        # TODO (should be dynamically determined)
        origin_router_id = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][0]["attachment-circuits"]["attachment-circuit"][0]["sdp-peering"]["peer-sap-id"]
        origin_router_if = '0/0/0-GigabitEthernet0/0/0/0'
        destination_router_id = ietf_intent["ietf-network-slice-service:network-slice-services"]["slice-service"][0]["sdps"]["sdp"][1]["attachment-circuits"]["attachment-circuit"][0]["sdp-peering"]["peer-sap-id"]
        destination_router_if = '0/0/3-GigabitEthernet0/0/0/3'
        destination_router_if = 'eth2'
        
        # Load L3VPN service template
        self.__load_template(2, os.path.join(TEMPLATES_PATH, "L3-VPN_template_empty.json"))
+34 −0
Original line number Diff line number Diff line
{
  "ietf-l2vpn-svc:vpn-service": [
    {
      "vpn-id": "11327140-7361-41b3-aa45-e84a7fb40be9",
      "customer-name": "osm",
      "vpn-svc-type": "vpws",
      "svc-topo": "any-to-any",
      "site": [
        {
          "site-id": "1.1.1.1",
          "site-role": "hub",
          "site-location": "CU-N2",
          "site-network-access": {
            "interface": {
              "ip-address": "10.60.11.3",
              "encapsulation": "ethernet"
            }
          }
        },
        {
          "site-id": "3.3.3.3",
          "site-role": "spoke",
          "site-location": "AMF-N2",
          "site-network-access": {
            "interface": {
              "ip-address": "10.60.60.105",
              "encapsulation": "ethernet"
            }
          }
        }
      ]
    }
  ]
}
 No newline at end of file