Resolve "(TID) Planner to support Green Network Slices"

Closes #13 (closed)

Merge request reports

Loading
+156 −0
Changes for src/planner/energy_ddbb.json: 156 added lines, 0 removed lines.
Original line number Diff line number Diff line
[
    {
        "name": "A",
        "typical-power": 3500,
        "maximum-traffic": 100.0,
        "max-power": 150.0,
        "efficiency": 0.9,
        "nominal-power": 130.0,
        "carbon-emissions": 130.0,
        "renewable-energy-usage": 0.7,
        "power-supply": [
            {"name": "PSU-1", "type": "AC", "capacity": 200.0, "typical-power": 100.0, "nominal-power": 110.0}
        ],
        "boards": [
            {"name": "Board-1", "type": "Linecard", "capacity": 40.0, "typical-power": 20.0, "nominal-power": 25.0}
        ],
        "components": [
            {"name": "CPU", "type": "Control", "capacity": 10.0, "typical-power": 5.0, "nominal-power": 6.0}
        ],
        "transceivers": [
            {"name": "XFP1", "type": "10G", "capacity": 10.0, "typical-power": 2.0, "nominal-power": 2.5}
        ]
    },
    {
        "name": "B",
        "typical-power": 6000,
        "maximum-traffic": 120.0,
        "max-power": 180.0,
        "efficiency": 1,
        "nominal-power": 160.0,
        "carbon-emissions": 180.0,
        "renewable-energy-usage": 0.6,
        "power-supply": [
            {"name": "PSU-1", "type": "DC", "capacity": 250.0, "typical-power": 120.0, "nominal-power": 130.0}
        ],
        "boards": [
            {"name": "Board-2", "type": "Switching", "capacity": 60.0, "typical-power": 30.0, "nominal-power": 35.0}
        ],
        "components": [
            {"name": "ASIC", "type": "Forwarding", "capacity": 15.0, "typical-power": 7.0, "nominal-power": 8.0}
        ],
        "transceivers": [
            {"name": "SFP1", "type": "1G", "capacity": 1.0, "typical-power": 1.0, "nominal-power": 1.2}
        ]
    },
    {
        "name": "C",
        "typical-power": 3200,
        "maximum-traffic": 150.0,
        "max-power": 250.0,
        "efficiency": 1.2,
        "nominal-power": 210.0,
        "carbon-emissions": 160.0,
        "renewable-energy-usage": 0.5,
        "power-supply": [
            {"name": "PSU-2", "type": "AC", "capacity": 300.0, "typical-power": 150.0, "nominal-power": 160.0}
        ],
        "boards": [
            {"name": "Board-3", "type": "Routing", "capacity": 80.0, "typical-power": 40.0, "nominal-power": 45.0}
        ],
        "components": [
            {"name": "FPGA", "type": "Processing", "capacity": 20.0, "typical-power": 10.0, "nominal-power": 12.0}
        ],
        "transceivers": [
            {"name": "QSFP1", "type": "40G", "capacity": 40.0, "typical-power": 4.0, "nominal-power": 5.0}
        ]
    },
    {
        "name": "D",
        "typical-power": 6000,
        "maximum-traffic": 200.0,
        "max-power": 300.0,
        "efficiency": 0.9,
        "nominal-power": 260.0,
        "carbon-emissions": 100.0,
        "renewable-energy-usage": 0.5,
        "power-supply": [
            {"name": "PSU-3", "type": "DC", "capacity": 350.0, "typical-power": 180.0, "nominal-power": 190.0}
        ],
        "boards": [
            {"name": "Board-4", "type": "Access", "capacity": 100.0, "typical-power": 50.0, "nominal-power": 55.0}
        ],
        "components": [
            {"name": "GPU", "type": "Accelerator", "capacity": 25.0, "typical-power": 12.0, "nominal-power": 14.0}
        ],
        "transceivers": [
            {"name": "CFP1", "type": "100G", "capacity": 100.0, "typical-power": 8.0, "nominal-power": 10.0}
        ]
    },
    {
        "name": "E",
        "typical-power": 4500,
        "maximum-traffic": 250.0,
        "max-power": 350.0,
        "efficiency": 1.4,
        "nominal-power": 310.0,
        "carbon-emissions": 180,
        "renewable-energy-usage": 0.6,
        "power-supply": [
            {"name": "PSU-4", "type": "AC", "capacity": 400.0, "typical-power": 200.0, "nominal-power": 210.0}
        ],
        "boards": [
            {"name": "Board-5", "type": "Core", "capacity": 120.0, "typical-power": 60.0, "nominal-power": 65.0}
        ],
        "components": [
            {"name": "TPU", "type": "Neural Processing", "capacity": 30.0, "typical-power": 15.0, "nominal-power": 18.0}
        ],
        "transceivers": [
            {"name": "OSFP1", "type": "400G", "capacity": 400.0, "typical-power": 12.0, "nominal-power": 15.0}
        ]
    },
    {
        "name": "F",
        "typical-power": 3000,
        "maximum-traffic": 300.0,
        "max-power": 400.0,
        "efficiency": 1.3,
        "nominal-power": 360.0,
        "carbon-emissions": 140,
        "renewable-energy-usage": 0.6,
        "power-supply": [
            {"name": "PSU-5", "type": "DC", "capacity": 450.0, "typical-power": 220.0, "nominal-power": 230.0}
        ],
        "boards": [
            {"name": "Board-6", "type": "Edge", "capacity": 150.0, "typical-power": 75.0, "nominal-power": 80.0}
        ],
        "components": [
            {"name": "ASIC2", "type": "Edge Processing", "capacity": 40.0, "typical-power": 20.0, "nominal-power": 22.0}
        ],
        "transceivers": [
            {"name": "QSFP-DD1", "type": "800G", "capacity": 800.0, "typical-power": 16.0, "nominal-power": 20.0}
        ]
    },
    {
        "name": "G",
        "typical-power": 3100,
        "maximum-traffic": 350.0,
        "max-power": 450.0,
        "efficiency": 1.1,
        "nominal-power": 410.0,
        "carbon-emissions": 105.0,
        "renewable-energy-usage": 0.6,
        "power-supply": [
            {"name": "PSU-6", "type": "AC", "capacity": 500.0, "typical-power": 250.0, "nominal-power": 260.0}
        ],
        "boards": [
            {"name": "Board-7", "type": "Virtualization", "capacity": 180.0, "typical-power": 90.0, "nominal-power": 95.0}
        ],
        "components": [
            {"name": "FPGA2", "type": "Reconfigurable Processing", "capacity": 50.0, "typical-power": 25.0, "nominal-power": 28.0}
        ],
        "transceivers": [
            {"name": "CFP2", "type": "1.6T", "capacity": 1600.0, "typical-power": 20.0, "nominal-power": 25.0}
        ]
    }
]
 No newline at end of file
+195 −0
Changes for src/planner/ext_topo_ddbb.json: 195 added lines, 0 removed lines.
Original line number Diff line number Diff line
{
"nodes": [
  {
    "nodeId": 1,
    "name": "A",
    "type": "SITE",
    "footprint": { "cpu": 2, "memory": 1024, "disk": 5000 },
    "vulnerability": 1
  },
  {
    "nodeId": 2,
    "name": "B",
    "type": "SITE",
    "footprint": { "cpu": 1, "memory": 512, "disk": 3000 },
    "vulnerability": 2
  },
  {
    "nodeId": 3,
    "name": "C",
    "type": "SITE",
    "footprint": { "cpu": 2, "memory": 2048, "disk": 6000 },
    "vulnerability": 3
  },
  {
    "nodeId": 4,
    "name": "D",
    "type": "SITE",
    "footprint": { "cpu": 1, "memory": 1024, "disk": 4000 },
    "vulnerability": 1
  },
  {
    "nodeId": 5,
    "name": "E",
    "type": "SITE",
    "footprint": { "cpu": 2, "memory": 1024, "disk": 5000 },
    "vulnerability": 2
  },
  {
    "nodeId": 6,
    "name": "F",
    "type": "SITE",
    "footprint": { "cpu": 1, "memory": 512, "disk": 3000 },
    "vulnerability": 2
  },
  {
    "nodeId": 7,
    "name": "G",
    "type": "SITE",
    "footprint": { "cpu": 2, "memory": 1024, "disk": 7000 },
    "vulnerability": 3
  }
],
"links":[
  {
    "linkId": "A-E",
    "sourceNodeId": "1",
    "destNodeId": "5",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 5 }]
  },
  {
    "linkId": "E-A",
    "sourceNodeId": "5",
    "destNodeId": "1",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 5 }]
  },
  {
    "linkId": "A-D",
    "sourceNodeId": "1",
    "destNodeId": "4",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 4 }]
  },
  {
    "linkId": "D-A",
    "sourceNodeId": "4",
    "destNodeId": "1",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 4 }]
  },
  {
    "linkId": "A-C",
    "sourceNodeId": "1",
    "destNodeId": "3",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 3 }]
  },
  {
    "linkId": "C-A",
    "sourceNodeId": "3",
    "destNodeId": "1",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 3 }]
  },
  {
    "linkId": "B-C",
    "sourceNodeId": "2",
    "destNodeId": "3",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 6 }]
  },
  {
    "linkId": "C-B",
    "sourceNodeId": "3",
    "destNodeId": "2",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 6 }]
  },
  {
    "linkId": "B-D",
    "sourceNodeId": "2",
    "destNodeId": "4",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 7 }]
  },
  {
    "linkId": "D-B",
    "sourceNodeId": "4",
    "destNodeId": "2",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 7 }]
  },
  {
    "linkId": "B-F",
    "sourceNodeId": "2",
    "destNodeId": "6",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 5 }]
  },
  {
    "linkId": "F-B",
    "sourceNodeId": "6",
    "destNodeId": "2",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 5 }]
  },
  {
    "linkId": "D-E",
    "sourceNodeId": "4",
    "destNodeId": "5",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 2 }]
  },
  {
    "linkId": "E-D",
    "sourceNodeId": "5",
    "destNodeId": "4",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 2 }]
  },
  {
    "linkId": "D-F",
    "sourceNodeId": "4",
    "destNodeId": "6",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 6 }]
  },
  {
    "linkId": "F-D",
    "sourceNodeId": "6",
    "destNodeId": "4",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 6 }]
  },
  {
    "linkId": "E-G",
    "sourceNodeId": "5",
    "destNodeId": "7",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 3 }]
  },
  {
    "linkId": "G-E",
    "sourceNodeId": "7",
    "destNodeId": "5",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 3 }]
  },
  {
    "linkId": "F-G",
    "sourceNodeId": "6",
    "destNodeId": "7",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 4 }]
  },
  {
    "linkId": "G-F",
    "sourceNodeId": "7",
    "destNodeId": "6",
    "bandwidth": 1000000000,
    "metrics": [{ "metric": "DELAY", "value": 4 }]
  }
]
}
 No newline at end of file

src/planner/planner.py

0 → 100644
+286 −0
Changes for src/planner/planner.py: 286 added lines, 0 removed lines.
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.

import logging, random, os, json, heapq  
from src.Constants import SRC_PATH, PCE_EXTERNAL, DEFAULT_LOGGING_LEVEL

# Configure logging to provide clear and informative log messages
logging.basicConfig(
    level=DEFAULT_LOGGING_LEVEL,
    format='%(levelname)s - %(message)s')

class Planner:
    """
    Planner class to compute the optimal path for a network slice based on energy consumption and topology.
    """

    def planner(self, intent):
        """
        Plan the optimal path for a network slice based on energy consumption and topology.
        """
        energy_metrics = self.__retrieve_energy()
        topology = self.__retrieve_topology()
        source = intent.get("ietf-network-slice-service:network-slice-services", {}).get("slice-service", [])[0].get("sdps", {}).get("sdp", [])[0].get("id") or "A"
        destination = intent.get("ietf-network-slice-service:network-slice-services", {}).get("slice-service", [])[0].get("sdps", {}).get("sdp", [])[1].get("id") or "B"
        optimal_path = []
        # If using an external PCE
        if PCE_EXTERNAL:
            logging.info("Using external PCE for path planning")    
            def build_slice_input(node_source, node_destination):
                return {
                    "clientName": "demo-client",
                    "requestId": random.randint(1000, 9999),
                    "sites": [node_source["nodeId"], node_destination["nodeId"]],
                    "graph": {
                        "nodes": [
                            {
                                "nodeId": node_source["nodeId"],
                                "name": node_source["name"],
                                "footprint": node_source["footprint"],
                                "sticky": [node_source["nodeId"]]
                            },
                            {
                                "nodeId": node_destination["nodeId"],
                                "name": node_destination["name"],
                                "footprint": node_destination["footprint"],
                                "sticky": [node_destination["nodeId"]]
                            }
                        ],
                        "links": [
                            {
                                "fromNodeId": node_source["nodeId"],
                                "toNodeId": node_destination["nodeId"],
                                "bandwidth": 1000000000,
                                "metrics": [
                                    {
                                        "metric": "DELAY",
                                        "value": 10,
                                        "bound": True,
                                        "required": True
                                    }
                                ]
                            }
                        ],
                        "constraints": {
                            "maxVulnerability": 3,
                            "maxDeployedServices": 10,
                            "metricLimits": []
                        }
                    }
                }
            source = next((node for node in topology["nodes"] if node["name"] == source), None)
            destination = next((node for node in topology["nodes"] if node["name"] == destination), None)
            slice_input = build_slice_input(source, destination)

            # POST /sss/v1/slice/compute
            def simulate_slice_output(input_data):
                return {
                    "input": input_data,
                    "slice": {
                        "nodes": [
                            {"site": 1, "service": 1},
                            {"site": 2, "service": 2}
                        ],
                        "links": [
                            {
                                "fromNodeId": 1,
                                "toNodeId": 2,
                                "lspId": 500,
                                "path": {
                                    "ingressNodeId": 1,
                                    "egressNodeId": 2,
                                    "hops": [
                                        {"nodeId": 3, "linkId": "A-C", "portId": 1},
                                        {"nodeId": 2, "linkId": "C-B", "portId": 2}
                                    ]
                                }
                            }
                        ],
                        "metric": {"value": 9}
                    },
                    "error": None
                }
            slice_output = simulate_slice_output(slice_input)
            # Mostrar resultado
            optimal_path.append(source["name"])
            for link in slice_output["slice"]["links"]:
                for hop in link["path"]["hops"]:
                    optimal_path.append(next((node for node in topology["nodes"] if node["nodeId"] == hop['nodeId']), None)["name"])
        
        else:
            logging.info("Using internal PCE for path planning")
            ietf_dlos = intent["ietf-network-slice-service:network-slice-services"]["slo-sle-templates"]["slo-sle-template"][0]["slo-policy"]["metric-bound"]
            logging.info(ietf_dlos),
            # Solo asigna los DLOS que existan, el resto a None
            dlos = {
                "EC": next((item.get("bound") for item in ietf_dlos if item.get("metric-type") == "energy_consumption"), None),
                "CE": next((item.get("bound") for item in ietf_dlos if item.get("metric-type") == "carbon_emission"), None),
                "EE": next((item.get("bound") for item in ietf_dlos if item.get("metric-type") == "energy_efficiency"), None),
                "URE": next((item.get("bound") for item in ietf_dlos if item.get("metric-type") == "renewable_energy_usage"), None)
            }
            logging.debug(f"Planning optimal path from {source} to {destination} with DLOS: {dlos}")
            optimal_path = self.__calculate_optimal_path(topology, energy_metrics, source, destination, dlos)

        if not optimal_path:
            logging.error("No valid path found")
            raise Exception("No valid energy path found")

        return optimal_path

    def __retrieve_energy(self):
        # TODO : Implement the logic to retrieve energy consumption data from controller
        # Taking it from static file
        with open(os.path.join(SRC_PATH, "planner/energy_ddbb.json"), "r") as archivo:
            energy_metrics = json.load(archivo)
        return energy_metrics

    def __retrieve_topology(self):
        if PCE_EXTERNAL:
            # TODO : Implement the logic to retrieve topology data from external PCE
            # GET /sss/v1/topology/node and /sss/v1/topology/link
            with open(os.path.join(SRC_PATH, "planner/ext_topo_ddbb.json"), "r") as archivo:
                topology = json.load(archivo)
        else:
            # TODO : Implement the logic to retrieve topology data from controller
            # Taking it from static file
            with open(os.path.join(SRC_PATH, "planner/topo_ddbb.json"), "r") as archivo:
                topology = json.load(archivo)
        return topology



    def __calculate_optimal_path(self, topology, energy_metrics, source, destination, dlos):
        logging.debug("Starting optimal path calculation...")
        
        # Create a dictionary with the weights of each node
        node_data_map = {}
        for node_data in energy_metrics:
            node_id = node_data["name"]
            ec = node_data["typical-power"]
            ce = node_data["carbon-emissions"]
            ee = node_data["efficiency"]
            ure = node_data["renewable-energy-usage"]

            total_power_supply = sum(ps["typical-power"] for ps in node_data["power-supply"])
            total_power_boards = sum(b["typical-power"] for b in node_data["boards"])
            total_power_components = sum(c["typical-power"] for c in node_data["components"])
            total_power_transceivers = sum(t["typical-power"] for t in node_data["transceivers"])

            logging.debug(f"Node {node_id}: EC={ec}, CE={ce}, EE={ee}, URE={ure}")
            logging.debug(f"Node {node_id}: PS={total_power_supply}, BO={total_power_boards}, CO={total_power_components}, TR={total_power_transceivers}")

            weight = self.__compute_node_weight(ec, ce, ee, ure,
                                                total_power_supply,
                                                total_power_boards,
                                                total_power_components,
                                                total_power_transceivers)
            logging.debug(f"Weight for node {node_id}: {weight}")
            
            node_data_map[node_id] = {
                "weight": weight,
                "ec": ec,
                "ce": ce,
                "ee": ee,
                "ure": ure
            }

        # Create a graph representation of the topology
        graph = {}
        for node in topology["ietf-network:networks"]["network"][0]["node"]:
            graph[node["node-id"]] = []
        for link in topology["ietf-network:networks"]["network"][0]["link"]:
            src = link["source"]["source-node"]
            dst = link["destination"]["dest-node"]
            graph[src].append((dst, node_data_map[dst]["weight"]))
            logging.debug(f"Added link: {src} -> {dst} with weight {node_data_map[dst]['weight']}")

        # Dijkstra's algorithm with restrictions
        queue = [(0, source, [], 0, 0, 0, 1)]  # (accumulated cost, current node, path, sum_ec, sum_ce, sum_ee, min_ure)
        visited = set()

        logging.debug(f"Starting search from {source} to {destination} with restrictions: {dlos}")
        

        while queue:
            cost, node, path, sum_ec, sum_ce, sum_ee, min_ure = heapq.heappop(queue)
            logging.debug(f"Exploring node {node} with cost {cost} and path {path + [node]}")
            
            if node in visited:
                logging.debug(f"Node {node} already visited, skipped.")
                continue
            visited.add(node)
            path = path + [node]

            node_metrics = node_data_map[node]
            sum_ec += node_metrics["ec"]
            sum_ce += node_metrics["ce"]
            sum_ee += node_metrics["ee"]
            min_ure = min(min_ure, node_metrics["ure"]) if path[:-1] else node_metrics["ure"]

            logging.debug(f"Accumulated -> EC: {sum_ec}, CE: {sum_ce}, EE: {sum_ee}, URE min: {min_ure}")

            if dlos["EC"] is not None and sum_ec > dlos["EC"]:
                logging.debug(f"Discarded path {path} for exceeding EC ({sum_ec} > {dlos['EC']})")
                continue
            if dlos["CE"] is not None and sum_ce > dlos["CE"]:
                logging.debug(f"Discarded path {path} for exceeding CE ({sum_ce} > {dlos['CE']})")
                continue
            if dlos["EE"] is not None and sum_ee > dlos["EE"]:
                logging.debug(f"Discarded path {path} for exceeding EE ({sum_ee} > {dlos['EE']})")
                continue
            if dlos["URE"] is not None and min_ure < dlos["URE"]:
                logging.debug(f"Discarded path {path} for not reaching minimum URE ({min_ure} < {dlos['URE']})")
                continue

            if node == destination:
                logging.debug(f"Destination {destination} reached with a valid path: {path}")
                return path

            for neighbor, weight in graph.get(node, []):
                if neighbor not in visited:
                    logging.debug(f"Qeue -> neighbour: {neighbor}, weight: {weight}")
                    heapq.heappush(queue, (
                        cost + weight,
                        neighbor,
                        path,
                        sum_ec,
                        sum_ce,
                        sum_ee,
                        min_ure
                    ))
        logging.debug("No valid path found that meets the restrictions.")
        return []


    def __compute_node_weight(self, ec, ce, ee, ure, total_power_supply, total_power_boards, total_power_components, total_power_transceivers, alpha=1, beta=1, gamma=1, delta=1):
        """
        Calcula el peso de un nodo con la fórmula:
        w(v) = α·EC + β·CE + γ/EE + δ·(1 - URE)
        """
        traffic = 100
        # Measure one hour of traffic
        time = 1

        power_idle = ec + total_power_supply + total_power_boards + total_power_components + total_power_transceivers
        power_traffic = traffic * ee

        power_total = (power_idle + power_traffic)

        green_index = power_total * time / 1000 * (1 - ure) * ce

        return green_index 
    
+43 −0
Changes for src/planner/topo_ddbb.json: 43 added lines, 0 removed lines.
Original line number Diff line number Diff line
{
    "ietf-network:networks":{
       "network":[
          {
             "network-id":"example_network",
             "network-types":{
                
             },
             "node":[
                {"node-id":"A"},
                {"node-id":"B"},
                {"node-id":"C"},
                {"node-id":"D"},
                {"node-id":"E"},
                {"node-id":"F"},
                {"node-id":"G"}
             ],
             "link":[
                {"link-id":"A-E","source":{"source-node":"A"},"destination":{"dest-node":"E"}},
                {"link-id":"E-A","source":{"source-node":"E"},"destination":{"dest-node":"A"}},
                {"link-id":"A-D","source":{"source-node":"A"},"destination":{"dest-node":"D"}},
                {"link-id":"D-A","source":{"source-node":"D"},"destination":{"dest-node":"A"}},
                {"link-id":"A-C","source":{"source-node":"A"},"destination":{"dest-node":"C"}},
                {"link-id":"C-A","source":{"source-node":"C"},"destination":{"dest-node":"A"}},
                {"link-id":"B-C","source":{"source-node":"B"},"destination":{"dest-node":"C"}},
                {"link-id":"C-B","source":{"source-node":"C"},"destination":{"dest-node":"B"}},
                {"link-id":"B-D","source":{"source-node":"B"},"destination":{"dest-node":"D"}},
                {"link-id":"D-B","source":{"source-node":"D"},"destination":{"dest-node":"B"}},
                {"link-id":"B-F","source":{"source-node":"B"},"destination":{"dest-node":"F"}},
                {"link-id":"F-B","source":{"source-node":"F"},"destination":{"dest-node":"B"}},
                {"link-id":"D-E","source":{"source-node":"D"},"destination":{"dest-node":"E"}},
                {"link-id":"E-D","source":{"source-node":"E"},"destination":{"dest-node":"D"}},
                {"link-id":"D-F","source":{"source-node":"D"},"destination":{"dest-node":"F"}},
                {"link-id":"F-D","source":{"source-node":"F"},"destination":{"dest-node":"D"}},
                {"link-id":"E-G","source":{"source-node":"E"},"destination":{"dest-node":"G"}},
                {"link-id":"G-E","source":{"source-node":"G"},"destination":{"dest-node":"E"}},
                {"link-id":"F-G","source":{"source-node":"F"},"destination":{"dest-node":"G"}},
                {"link-id":"G-F","source":{"source-node":"G"},"destination":{"dest-node":"F"}}
             ]
          }
       ]
    }
 }
 No newline at end of file
+15 −2
Changes for src/realizers/ixia/NEII_V4.py: 15 added lines, 2 removed lines.
Original line number Diff line number Diff line
###SPDX-FileCopyrightText: © 2024 Telefónica Innovación Digital S.L.
###SPDX-License-Identifier: AGPL-3.0-or-later
# 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.

from .automatizacion_ne2v4 import automatizacion
import ipaddress, logging
Loading
Loading