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

Merge branch 'TID_ECOC25_DEMOS' of https://labs.etsi.org/rep/tfs/nsc into...

Merge branch 'TID_ECOC25_DEMOS' of https://labs.etsi.org/rep/tfs/nsc into feat/15-tid-new-ipowdm-realizer
parents 6883420e 1c9c24fe
Loading
Loading
Loading
Loading

src/constants.py

0 → 100644
+62 −0
Original line number Diff line number Diff line
# Copyright 2022-2025 ETSI SDG TeraFlowSDN (TFS) (https://tfs.etsi.org/)

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# 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.
""" This file contains constants used throughout the NSC application. """

<<<<<<<< HEAD:src/utils/dump_templates.py
import json, os
from src.config.constants import TEMPLATES_PATH
from flask import current_app

def dump_templates(nbi_file, ietf_file, realizer_file):
    """
    Dump multiple templates as JSON into the templates path.
    """
    if not current_app.config["DUMP_TEMPLATES"]:
        return
========
import logging
import os
# Default logging level
DEFAULT_LOGGING_LEVEL = logging.INFO

# Default port for NSC deployment
NSC_PORT = 8086
>>>>>>>> 1c9c24fe98b87f87d11c79af0904b705c97a9ded:src/constants.py

    templates = {
        "nbi_template.json": nbi_file,
        "ietf_template.json": ietf_file,
        "realizer_template.json": realizer_file,
    }

<<<<<<<< HEAD:src/utils/dump_templates.py
    for filename, content in templates.items():
        path = os.path.join(TEMPLATES_PATH, filename)
        with open(path, "w") as f:
            json.dump(content, f, indent=2)
========
# Create the path to the desired file relative to the current file
TEMPLATES_PATH = os.path.join(SRC_PATH, "templates")

# TFS Flags
# Flag to determine if configurations should be uploaded to Teraflow
TFS_UPLOAD = False
# Teraflow IP
TFS_IP = "10.95.86.58"
# Flag to determine if additional L2VPN configuration support is
# required for deploying L2VPNs with path selection
TFS_L2VPN_SUPPORT = False
>>>>>>>> 1c9c24fe98b87f87d11c79af0904b705c97a9ded:src/constants.py
+264 −10
Original line number Diff line number Diff line
@@ -11,22 +11,79 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# 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. """

<<<<<<< HEAD:src/realizer/tfs/helpers/cisco_connector.py
# This file is an original contribution from Telefonica Innovación Digital S.L.

import logging
from netmiko import ConnectHandler

class cisco_connector():
=======
import json
import logging
import os
import uuid
import requests
from netmiko import ConnectHandler
from src.constants import DEFAULT_LOGGING_LEVEL

logging.basicConfig(
    level=DEFAULT_LOGGING_LEVEL,
    format='%(levelname)s - %(message)s')

#Teraflow
class TFSConnector():
    """Connector class for interacting with Teraflow SDN.
       This class provides methods to send requests to
       Teraflow SDN for network service configuration."""

    def simple_post(self, tfs_ip, service):
        """Send a simple POST request to Teraflow SDN.
           This method sends a JSON payload to the Teraflow SDN
           service endpoint to configure network services."""

        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")}
        token={'csrf_token':token}
        response = session.post(url,files=files,data=token,timeout=60)
        logging.debug("Http response: %s",response.text)
        return response

#CISCO
class CiscoConnector():
    """Connector class for interacting with Cisco devices.
       This class provides methods to execute configuration commands
       on Cisco devices via SSH."""

>>>>>>> 1c9c24fe98b87f87d11c79af0904b705c97a9ded:src/helpers.py
    def __init__(self, address, configs=None):
        self.address=address
        self.configs=configs

    def execute_commands(self, commands):
        """Execute a list of commands on the Cisco device via SSH.
           Connects to the device and sends configuration commands.
           This method is used to send configuration commands to the Cisco device."""

        try:
            # Configuración del dispositivo
            device = {
                'device_type': 'cisco_xr',  # Esto depende del tipo de dispositivo (ej: 'cisco_ios', 'cisco_xr', 'linux', etc.)
                'device_type': 'cisco_xr',
                'host': self.address,
                'username': 'cisco',
                'password': 'cisco12345',
@@ -42,10 +99,13 @@ class cisco_connector():
            # Cerrar la conexión
            connection.disconnect()

        except Exception as e:
            logging.error(f"Failed to execute commands on {self.address}: {str(e)}")
        except EOFError as e:
            logging.error("Failed to execute commands on %s: %s",self.address, str(e))

    def create_command_template(self, config):
        """Create a command template for configuring L2VPN on a Cisco device.
           This method generates the necessary commands to configure L2VPN
           based on the provided configuration parameters."""

        commands = [
            "l2vpn",
@@ -73,10 +133,13 @@ class cisco_connector():
            f"pw-class l2vpn_vpws_profile_example_{config['number']}"
        ])


        return commands

    def full_create_command_template(self):
        """Create a full command template for configuring L2VPN on a Cisco device.
           This method generates all necessary commands to configure L2VPN based on
           the provided configurations."""

        commands =[]
        for config in self.configs:
            commands_temp = self.create_command_template(config)
@@ -86,11 +149,202 @@ class cisco_connector():
        return commands

    def create_command_template_delete(self):
        """Create a command template for deleting L2VPN configuration on a Cisco device.
           This method generates the necessary commands to remove L2VPN configurations."""

        commands = [
            "no l2vpn",
        ]
    
        commands.append("commit")
        commands.append("end")

        return commands

def send_network_slice_request(data: str, action: str = "create") -> dict:

    url = 'http://192.168.1.143:9090/api/resource-allocation/transport-network-slice-l3'
    headers = {'Content-Type': 'application/json'}
    try:
        if action == "delete":
            data = {
                "ietf-network-slice-service:network-slice-services": {
                    "slice-service": [
                        {
                            "id": data
                        }
                    ]
                }
                }
            response = requests.delete(url, headers=headers, json=data, timeout=15)
        elif action == "create":
            response = requests.post(url, headers=headers, json=data, timeout=15)
        else:
            raise ValueError("Invalid action. Use 'create' or 'delete'.")
    except requests.exceptions.RequestException as e:
        logging.error(f"HTTP request failed: {e}")
        return {}

    if response.ok:
        return response.json()
    else:
        print(f"Request failed with status code {response.status_code}: {response.text}")
        response.raise_for_status()

def group_block(group, action, group_id_override=None, node = None):
    active   = "true" if action == 'create' else "false"
    group_id = group_id_override if group_id_override is not None else group["digital_sub_carriers_group_id"]
    if node == "leaf":
        return {
            "digital_sub_carriers_group_id": group_id,
            "digital_sub_carrier_id": [
                {'sub_carrier_id': 1, 'active': active}, 
                {'sub_carrier_id': 2, 'active': active}, 
                {'sub_carrier_id': 3, 'active': active},
                {'sub_carrier_id': 4, 'active': active}
                ]
            }
    else:
        return {
            "digital_sub_carriers_group_id": group_id,
            "digital_sub_carrier_id": [
                {
                    "sub_carrier_id": sid,
                    "active": active,
                }
                for sid in group["subcarrier-id"]
            ]
        }

def generate_rules(connectivity_service, intent, action):
    src_name     = connectivity_service.get("source", "FALTA VALOR")
    dest_list    = connectivity_service.get("destination", ["FALTA VALOR"])
    dest_str     = ",".join(dest_list)
    config_rules = []

    network_slice_uuid_str = f"{src_name}_to_{dest_str}"
    tunnel_uuid = str(uuid.uuid5(uuid.NAMESPACE_DNS, network_slice_uuid_str))
    provisionamiento = {
        "network-slice-uuid": network_slice_uuid_str,
        "viability": True,
        "actions": []
    }

    attributes = connectivity_service["connectivity-service"]["tapi-connectivity:connectivity-service"]["connection"][0]["optical-connection-attributes"]
    groups = attributes["subcarrier-attributes"]["digital-subcarrier-group"]
    # central_freq = (attributes["central-frequency"])
    # tx_power = (attributes["Tx-power"])
    # spacing = attributes["digital-subcarrier-spacing"]
    operational_mode = attributes["modulation"]["operational-mode"]
    # port = attributes["modulation"]["port"]
    hub_groups = [
        group_block(group, action, group_id_override=index + 1)
        for index, group in enumerate(groups)
    ]
    hub = {
        "name":      "channel-1",
        "frequency": 195000000,
        "target_output_power": 0,
        "operational_mode": operational_mode,
        "operation" :       "merge",
        "digital_sub_carriers_group": hub_groups
    }

    leaves = []
    for dest, group in zip(connectivity_service["destination"], groups):
        if dest == "T1.1": 
            name = "channel-1"
            freq = 195006250
        if dest == "T1.2": 
            name =   "channel-3"
            freq =   195018750
        if dest == "T1.3": 
            name = "channel-5"
            freq = 195031250
        leaf = {
            "name": name,
            "frequency": freq,
            "target_output_power": group["Tx-power"],
            "operational_mode": int(group["operational-mode"]),
            "operation" : "merge",
            "digital_sub_carriers_group": [group_block(group, action, group_id_override=1, node = "leaf")]
        }

        leaves.append(leaf)

    final_json = {"components": [hub] + leaves}
    if action == 'create':
        provisionamiento["actions"].append({
            "type":   "XR_AGENT_ACTIVATE_TRANSCEIVER",
            "layer":  "OPTICAL",
            "content": final_json,
            "controller-uuid": "IPoWDM Controller"
        })

        nodes = {}
        sdp_list = intent['ietf-network-slice-service:network-slice-services']['slice-service'][0]['sdps']['sdp']

        for sdp in sdp_list:
            node = sdp['node-id']
            attachments = sdp['attachment-circuits']['attachment-circuit']
            for ac in attachments:
                ip = ac.get('ac-ipv4-address', None)
                prefix = ac.get('ac-ipv4-prefix-length', None)
                vlan = 500
                nodes[node] = {
                    "ip-address": ip,
                    "ip-mask":    prefix,
                    "vlan-id":    vlan
                }
            
        provisionamiento["actions"].append({
            "type": "CONFIG_VPNL3",
            "layer": "IP",
            "content": {
                "tunnel-uuid":      tunnel_uuid,
                "src-node-uuid":    src_name,
                "src-ip-address":   nodes[src_name]["ip-address"],
                "src-ip-mask":      str(nodes[src_name]["ip-mask"]),
                "src-vlan-id":      nodes[src_name]["vlan-id"],
                "dest1-node-uuid":  dest_list[0],
                "dest1-ip-address": nodes[dest_list[0]]["ip-address"],
                "dest1-ip-mask":    str(nodes[dest_list[0]]["ip-mask"]),
                "dest1-vlan-id":    nodes[dest_list[0]]["vlan-id"],
                "dest2-node-uuid":  dest_list[1],
                "dest2-ip-address": nodes[dest_list[1]]["ip-address"],
                "dest2-ip-mask":    str(nodes[dest_list[1]]["ip-mask"]),
                "dest2-vlan-id":    nodes[dest_list[1]]["vlan-id"],
                "dest3-node-uuid":  dest_list[2],
                "dest3-ip-address": nodes[dest_list[2]]["ip-address"],
                "dest3-ip-mask":    str(nodes[dest_list[2]]["ip-mask"]),
                "dest3-vlan-id":    nodes[dest_list[2]]["vlan-id"]
        },
            "controller-uuid": "IP Controller"
        })
            
        config_rules.append(provisionamiento)
    else:
        nodes = []
        nodes.append(src_name)
        for dst in dest_list: nodes.append(dst)
        aux = tunnel_uuid + '-' + src_name + '-' + '-'.join(dest_list)
        provisionamiento["actions"].append({
        "type":  "DEACTIVATE_XR_AGENT_TRANSCEIVER",
        "layer": "OPTICAL",
        "content": final_json,
        "controller-uuid": "IPoWDM Controller",
        "uuid" : aux,
        "nodes": nodes
        })
        config_rules.append(provisionamiento)

    return config_rules

def get_sip_from_name(context, node_name):

    context = context.json()
    topologies = context.get("tapi-topology:topology-context", {}).get("topology", [])
    for topology in topologies:
        nodes = topology.get("nodes", [])
        for node in nodes:
            if node.get("name") == node_name:
                return node.get("uuid")
    return None  
+31 −0
Original line number Diff line number Diff line
{
    "services": [
        {
            "service_id": {
                "context_id": {"context_uuid": {"uuid": "admin"}},
                "service_uuid": {"uuid": "TAPI LSP"}
            },
            "service_type": 12,
            "service_status": {"service_status": 1},
            "service_endpoint_ids": [
                {"device_id": {"device_uuid": {"uuid": "TFS-OPTICAL"}},"endpoint_uuid": {"uuid": "mgmt"}},
                {"device_id": {"device_uuid": {"uuid": "TFS-PACKET"}},"endpoint_uuid": {"uuid": "mgmt"}}

            ],
            "service_constraints": [],

            "service_config": {"config_rules": [
                {"action": 1, "ipowdm": {
                    "endpoint_id": {
                        "device_id": {"device_uuid": {"uuid": "TFS-PACKET"}},
                        "endpoint_uuid": {"uuid": "mgmt"}
                    },
                    "rule_set": {
                        "src"  : [],
                        "dst"  : []
                    }
                }}
            ]}
        }
    ]
}
 No newline at end of file
+28 −0
Original line number Diff line number Diff line
{
   "tapi-common:context" : {
      "name" : [
         {
            "value" : ""
         }
      ],
      "service-interface-point" : [
         {
            "uuid" : ""
         },
         {
            "uuid" : ""
         }
      ],
      "tapi-topology:topology-context" : {
         "topology" : [
            {
               "link" : [
               ],
               "node" : [
               ]
            }
         ]
      },
      "uuid" : ""
   }
}
+43 −0
Original line number Diff line number Diff line
{
    "services": [
        {
            "service_id": {
                "context_id": {"context_uuid": {"uuid": "admin"}},
                "service_uuid": {"uuid": "TAPI LSP"}
            },
            "service_type": 11,
            "service_status": {"service_status": 1},
            "service_endpoint_ids": [
                {"device_id": {"device_uuid": {"uuid": "TFS-OPTICAL"}},"endpoint_uuid": {"uuid": "mgmt"}},
                {"device_id": {"device_uuid": {"uuid": "TFS-PACKET"}},"endpoint_uuid": {"uuid": "mgmt"}}

            ],
            "service_constraints": [],

            "service_config": {"config_rules": [
                {"action": 1, "tapi_lsp": {
                    "endpoint_id": {
                        "device_id": {"device_uuid": {"uuid": "TFS-OPTICAL"}},
                        "endpoint_uuid": {"uuid": "mgmt"}
                    },
                    "rule_set": {
                        "src": "",
                        "dst": "",
                        "uuid": "",
                        "bw": "",
                        "tenant_uuid": "",
                        "direction": "",
                        "layer_protocol_name": "",
                        "layer_protocol_qualifier": "",
                        "lower_frequency_mhz": "",
                        "upper_frequency_mhz": "",
                        "link_uuid_path": [
                        ],
                        "granularity": "",
                        "grid_type": ""
                    }
                }}
            ]}
        }
    ]
}
 No newline at end of file
Loading