Commit e70da6c8 authored by Javier Velázquez's avatar Javier Velázquez
Browse files

Merge branch 'feat/26-tid-frr-topology-use-case' into 'develop'

Merge feat/26-tid-frr-topology-use-case

See merge request !25
parents c7286876 be2f1061
Loading
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -69,6 +69,8 @@ TFS_E2E_IP=127.0.0.1
RESTCONF_IP=127.0.0.1
# Options: TFS or IXIA
SDN_CONTROLLER_TYPE=TFS
# Options: FRR, CISCO
DATAPLANE_SUPPORT=FRR

# -------------------------
# WebUI
+1 −0
Original line number Diff line number Diff line
@@ -67,5 +67,6 @@ def create_config(app: Flask):
    # Restconf Controller
    app.config["RESTCONF_IP"] = os.getenv("RESTCONF_IP", "127.0.0.1")
    app.config["SDN_CONTROLLER_TYPE"] = os.getenv("SDN_CONTROLLER_TYPE", "TFS")
    app.config["DATAPLANE_SUPPORT"] = os.getenv("DATAPLANE_SUPPORT", "FRR")

    return app
+89 −0
Original line number Diff line number Diff line
# frr_connector.py

import logging
from netmiko import ConnectHandler

class frr_connector():
    """Class to interact with FRR devices via SSH using Netmiko."""

    def __init__(self, address):
        self.address = address

    def execute_commands(self, commands):
        try:
            device = {
                'device_type': 'linux',
                'host': self.address,
                'username': 'root',
                'password': 'root',
            }
            connection = ConnectHandler(**device)
            output = connection.send_config_set(commands)
            logging.debug(output)
            connection.disconnect()

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

    def setup_slice(self, config: dict, assignments: dict[int, int]) -> list[str]:
        """
        Build PBR commands dynamically based on active slot assignments.

        Args:
            config: Device config (interfaces, addresses, etc.)
            assignments: {slot_number: dscp_value} for currently active slices.
                         e.g. {1: 52, 2: 51}
        Returns:
            List of FRR/vtysh commands.
        """
        commands = ["vtysh", "conf te"]

        # Nexthop groups - one per non-default slice + DEFAULT
        for i, iface in enumerate(config['output_interfaces'], start=1):
            commands += [
                f"nexthop-group SLICE-{i}",
                f"nexthop {iface}",
                "exit",
            ]

        # PBR rules - one per active slot, ordered by seq
        seq = 10
        for slot, dscp in sorted(assignments.items()):
            commands += [
                f"pbr-map PBR-DSCP seq {seq}",
                f"match mark {dscp}",
                f"set nexthop-group SLICE-{slot}",
                "exit",
            ]
            seq += 10

        # Default catch-all rule always last
        commands += [
            "pbr-map PBR-DSCP seq 100",
            "match dst-ip 0.0.0.0/0",
            "set nexthop-group DEFAULT",
            "exit",
        ]

        # Apply PBR policy to input interface
        commands += [
            f"interface {config['input_interface']}",
            f"ip address {config['input_address']}/24",
            "pbr-policy PBR-DSCP",
            "exit",
            "end",
        ]

        return commands

    def delete_all_slices(self) -> list[str]:
        return [
            "vtysh",
            "conf te",
            "no pbr-map PBR-DSCP",
            "no nexthop-group SLICE-1",
            "no nexthop-group SLICE-2",
            "no nexthop-group DEFAULT",
            "end",
        ]
 No newline at end of file
+48 −0
Original line number Diff line number Diff line
from src.utils.slice_manager import SliceManager
from .connectors.tfs_connector import tfs_connector
from .connectors.frr_connector import frr_connector
from src.utils.send_response import send_response
from src.utils.safe_get import safe_get
from flask import current_app
from src.config.constants import NBI_L2_PATH, NBI_L3_PATH
import logging


FRR_DEVICES = [
    {
        "management_address": "10.60.125.20",
        "input_address": "192.168.24.2",
        "input_interface": "eth2",
        "output_interfaces": ["192.168.23.3", "192.168.25.5", "192.168.12.1"],
    },
    {
        "management_address": "10.60.125.50",
        "input_address": "192.168.56.5",
        "input_interface": "eth4",
        "output_interfaces": ["192.168.35.3", "192.168.25.2", "192.168.15.1"],
    },
]

_slice_manager = SliceManager()

def restconf_connect(requests, restconf_ip):
    """
    Connect to controller and upload services.
@@ -24,6 +44,34 @@ def restconf_connect(requests, restconf_ip):

            elif key == "ietf-l3vpn-svc:l3vpn-svc":
                path = NBI_L3_PATH
                dscp = safe_get(intent, [
                    "sites", 0, "site-network-accesses", [0],
                    "service", "qos", "qos-classification-policy",
                    "rule", 0, "match-flow", "dscp"
                ])

                if dscp is not None and current_app.config["DATAPLANE_SUPPORT"] == "FRR":
                    if _slice_manager.is_full():
                        return send_response(
                            False, code=429,
                            message=f"No available slices. Current assignments: {_slice_manager.get_active_assignments()}"
                        )

                    slot = _slice_manager.assign_slot(dscp)
                    if slot is None:
                        return send_response(False, code=429, message=f"Could not assign slot for DSCP {dscp}")

                    logging.info(f"Assigned DSCP {dscp} to SLICE-{slot}")

                    assignments = _slice_manager.get_active_assignments()
                    for device_config in FRR_DEVICES:
                        connector = frr_connector(device_config["management_address"])
                        commands = connector.setup_slice(device_config, assignments)
                        try:
                            connector.execute_commands(commands)
                        except Exception as e:
                            return send_response(False, code=500,
                                                 message=f"FRR config failed on {device_config['management_address']}: {str(e)}")

            else:
                return send_response(False, code=400, message=f"Unsupported service type: {key}")
+296 −0
Original line number Diff line number Diff line
{
  "ietf-network-slice-service:network-slice-services": {
    "slo-sle-templates": {
      "slo-sle-template": [
        {
          "id": "gold",
          "description": "",
          "slo-policy": {
            "metric-bound": [
              {
                "metric-type": "two-way-bandwidth",
                "metric-unit": "Mbps",
                "bound": 100
              },
              {
                "metric-type": "two-way-delay-maximum",
                "metric-unit": "milliseconds",
                "bound": 5
              }
            ]
          },
          "sle-policy": {
            "security": "",
            "isolation": "",
            "path-constraints": {
              "service-functions": {},
              "diversity": {
                "diversity-type": ""
              }
            }
          }
        },
        {
          "id": "silver",
          "description": "",
          "slo-policy": {
            "metric-bound": [
              {
                "metric-type": "two-way-bandwidth",
                "metric-unit": "Mbps",
                "bound": 10
              },
              {
                "metric-type": "two-way-delay-maximum",
                "metric-unit": "milliseconds",
                "bound": 20
              }
            ]
          },
          "sle-policy": {
            "security": "",
            "isolation": "",
            "path-constraints": {
              "service-functions": {},
              "diversity": {
                "diversity-type": ""
              }
            }
          }
        },
        {
          "id": "bronze",
          "description": "",
          "slo-policy": {
            "metric-bound": [
              {
                "metric-type": "two-way-bandwidth",
                "metric-unit": "Mbps",
                "bound": 1
              },
              {
                "metric-type": "two-way-delay-maximum",
                "metric-unit": "milliseconds",
                "bound": 20
              }
            ]
          },
          "sle-policy": {
            "security": "",
            "isolation": "",
            "path-constraints": {
              "service-functions": {},
              "diversity": {
                "diversity-type": ""
              }
            }
          }
        }
      ]
    },
    "slice-service": [
      {
        "id": "unity-transport-slice",
        "description": "Transport network slice service comprising three paths with three different slo templates. SDPs are matched by DSCP of incoming traffic",
        "service-tags": {
            "tag-type": [
              {
                "tag-type": "service",
                "tag-type-value": [
                  "L3"
                ]
              }
            ]
        },
        "slo-sle-template": "bronze",
        "status": {},
        "sdps": {
          "sdp": [
            {
              "id": "SDP1",
              "geo-location": {},
              "node-id": "CU",
              "sdp-ip-address": "",
              "tp-ref": "",
              "service-match-criteria": {
                "match-criterion": [
                  {
                    "index": 1,
                    "match-type": [
                      {
                        "type": "dscp",
                        "dscp": [51]
                      }
                    ],
                    "target-connection-group-id": "CU_DU_1"
                  },
                  {
                    "index": 2,
                    "match-type": [
                      {
                        "type": "dscp",
                        "dscp": [52]
                      }
                    ],
                    "target-connection-group-id": "CU_DU_2"
                  },
                  {
                    "index": 3,
                    "match-type": [
                      {
                        "type": "any"
                      }
                    ],
                    "target-connection-group-id": "CU_DU_3"
                  }
                ]
              },
              "incoming-qos-policy": {},
              "outgoing-qos-policy": {},
              "sdp-peering": {
                "peer-sap-id": "",
                "protocols": {}
              },
              "ac-svc-ref": [],
              "attachment-circuits": {
                "attachment-circuit": [
                  {
                    "id": "acSDP1",
                    "ac-node-id": "PE-A",
                    "ac-tp-id": "GigabitEthernet5/0/0/0",
                    "ac-ipv4-address": "4.4.4.4",
                    "ac-ipv4-prefix-length": 24,
                    "status": {}
                  }
                ]
              },
              "status": {},
              "sdp-monitoring": {}
            },
            {
              "id": "SDP2",
              "geo-location": {},
              "node-id": "DU",
              "sdp-ip-address": "",
              "tp-ref": "",
              "service-match-criteria": {
                "match-criterion": [
                  {
                    "index": 1,
                    "match-type": [
                      {
                        "type": "dscp",
                        "dscp": [51]
                      }
                    ],
                    "target-connection-group-id": "CU_DU_1"
                  },
                  {
                    "index": 2,
                    "match-type": [
                      {
                        "type": "dscp",
                        "dscp": [52]
                      }
                    ],
                    "target-connection-group-id": "CU_DU_2"
                  },
                  {
                    "index": 3,
                    "match-type": [
                      {
                        "type": "any"
                      }
                    ],
                    "target-connection-group-id": "CU_DU_3"
                  }
                ]
              },
              "incoming-qos-policy": {},
              "outgoing-qos-policy": {},
              "sdp-peering": {
                "peer-sap-id": "",
                "protocols": {}
              },
              "ac-svc-ref": [],
              "attachment-circuits": {
                "attachment-circuit": [
                  {
                    "id": "acSDP2",
                    "ac-node-id": "PE-B",
                    "ac-tp-id": "GigabitEthernet5/0/0/0",
                    "ac-ipv4-address": "10.60.60.105",
                    "ac-ipv4-prefix-length": 24,
                    "status": {}
                  }
                ]
              },
              "status": {},
              "sdp-monitoring": {}
            }
          ]
        },
        "connection-groups": {
          "connection-group": [
            {
              "id": "CU_DU_1",
              "connectivity-type": "point-to-point",
              "slo-sle-template": "silver",
              "connectivity-construct": [
                {
                  "id": "1",
                  "p2p-sender-sdp": "SDP1",
                  "p2p-receiver-sdp": "SDP2"
                },
                {
                  "id": "2",
                  "p2p-sender-sdp": "SDP2",
                  "p2p-receiver-sdp": "SDP1"

                }
              ],
              "status": {}
            },
            {
              "id": "CU_DU_2",
              "connectivity-type": "point-to-point",
              "slo-sle-template": "gold",
              "connectivity-construct": [
                {
                  "id": "3",
                  "p2p-sender-sdp": "SDP1",
                  "p2p-receiver-sdp": "SDP2"
                },
                {
                  "id": "4",
                  "p2p-sender-sdp": "SDP2",
                  "p2p-receiver-sdp": "SDP1"

                }
              ],
              "status": {}
            },
            {
              "id": "CU_DU_3",
              "connectivity-type": "point-to-point",
              "connectivity-construct": [
                {
                  "id": "5",
                  "p2p-sender-sdp": "SDP1",
                  "p2p-receiver-sdp": "SDP2"
                },
                {
                  "id": "6",
                  "p2p-sender-sdp": "SDP2",
                  "p2p-receiver-sdp": "SDP1"

                }
              ],
              "status": {}
            }
          ]
        }
      }
    ]
  }
}
Loading