Commit 6c87d2df authored by Pablo Armingol's avatar Pablo Armingol
Browse files

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

parents be514b78 1c9c24fe
Loading
Loading
Loading
Loading
+24 −20
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
from flask import Flask
from flask_restx import Api
from flask_cors import CORS
from swagger.slice_namespace import slice_ns
from src.constants import NSC_PORT

# Configuración de logging básica para fichero
logging.basicConfig(
    filename='nsc_controller.log',
    level=logging.INFO,
    format='%(asctime)s %(levelname)s %(name)s: %(message)s',
    filemode='w'
)

# Obtener logger raíz y logger Flask
logger = logging.getLogger()
flask_logger = logging.getLogger('werkzeug')  # logger que usa Flask para accesos HTTP

# Añadir handler de fichero a logger de Flask para asegurarnos que escribe en el log
file_handler = logging.FileHandler('nsc_controller.log')
file_handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s %(levelname)s %(name)s: %(message)s')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
flask_logger.addHandler(file_handler)

app = Flask(__name__)
CORS(app)

# Create API instance
api = Api(
    app,
    version="1.0",
    title="Network Slice Controller (NSC) API",
    description="API for orchestrating and realizing transport network slice requests",
    doc="/nsc"  # Swagger UI URL
    doc="/nsc"
)

# Register namespaces
api.add_namespace(slice_ns, path="/slice")

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=NSC_PORT, debug=True)
    logger.info("Running  Network Slice Controller in %s:%s", "192.168.202.50", NSC_PORT)
    app.run(host="192.168.202.50", port=NSC_PORT, debug=True)
+31 −19
Original line number Diff line number Diff line
@@ -180,10 +180,20 @@ def send_network_slice_request(data: str, action: str = "create") -> dict:
        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):
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": [
@@ -234,25 +244,24 @@ def generate_rules(connectivity_service, intent, action):
        if dest == "T1.1": 
            name = "channel-1"
            freq = 195006250
        elif dest == "T1.2": 
        if dest == "T1.2": 
            name =   "channel-3"
            freq =   195018750
        else: 
        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)]
            "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",
@@ -270,8 +279,7 @@ def generate_rules(connectivity_service, intent, action):
            for ac in attachments:
                ip = ac.get('ac-ipv4-address', None)
                prefix = ac.get('ac-ipv4-prefix-length', None)
                last_octet = int(ip.split('.')[-1]) if ip else 0
                vlan = 100 + last_octet
                vlan = 500
                nodes[node] = {
                    "ip-address": ip,
                    "ip-mask":    prefix,
@@ -294,7 +302,11 @@ def generate_rules(connectivity_service, intent, action):
                "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"]
                "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"
        })
+24 −5
Original line number Diff line number Diff line
@@ -236,6 +236,9 @@ class NSController:
                logging.info("DELETE REQUEST RECEIVED: %s", intent_json)
                rules = []
                # rules = self.__planner(intent_json ,action = action)

                rules = self.__planner(intent_json ,action = action)
                logging.info(f"Rules generated by planner: \n {rules}")
                tfs_request = self.__realizer(rules=rules)
            else:
                self.start_time = time.perf_counter()
@@ -539,7 +542,7 @@ class NSController:
            content = json.load(file)

        # Determine the actual ID to use
        slice_id = service_uuid if service_uuid is not None else slice_id
        slice_id = "T2.1_to_T1.1,T1.2,T1.3"
        if slice_id is None:
            # If neither given, fallback to intent's internal id
            slice_id = intent["ietf-network-slice-service"]["slice_id"] if "slice_id" in intent["ietf-network-slice-service"] \
@@ -739,13 +742,14 @@ class NSController:
        return True, score  # Si pasó todas las verificaciones, la NRP es viable

    def __planner(self, intent, action,  nrp_view= None):

        if action == 'delete':
            logging.info("DELETE REQUEST RECEIVED: %s", intent)
            with open(os.path.join(SRC_PATH, "slice_ddbb.json"), 'r', encoding='utf-8') as file:
                slices = json.load(file)

            for slice_obj in slices:
                if 'slice_id' in slice_obj and slice_obj['slice_id'] == intent:
                    logging.info("Slice found: %s", slice_obj['slice_id'])
                    source = None
                    destination = None
                    services = slice_obj['intent']['ietf-network-slice-service:network-slice-services']['slice-service']
@@ -790,6 +794,7 @@ class NSController:
                "destination": destination,
                "connectivity-service": response
            }
            logging.info(summary)
            rules = generate_rules(summary, intent,action)
        return rules

@@ -819,6 +824,7 @@ class NSController:
            "band": 200,
            "subcarriers_per_source": [4] * len(sources_list)
        }
        logging.info(f"Payload for path computation: {json.dumps(payload, indent=2)}")

        response = requests.post(url, headers=headers, data=json.dumps(payload))
        return json.loads(response.text)
@@ -993,6 +999,7 @@ class NSController:
            dict: A TeraFlow service request for deleting the optical slice.
        """
        transceiver_params = []
        response = None
        for rule in rules["actions"]:
            if rule["type"] == "DEACTIVATE_TRANSCEIVER":
                params = {
@@ -1023,13 +1030,13 @@ class NSController:
                    'dst_router_tp' : transceiver_params[1]["router_tp"],
                }
                url = f'http://10.95.86.58/restconf/E2E/v1/service/ipowdm={rule["content"]["tunnel-uuid"]}:{data}'
                response = requests.delete(url, timeout=10)
                response = requests.delete(url, timeout=30)
                logging.info("Response: %s", response)

            elif rule["type"] == "DEACTIVATE_XR_AGENT_TRANSCEIVER":
                logging.info("Sending DELETE XR AGENT service request to Orchestrator")
                url = f'http://10.95.89.50/restconf/E2E/v1/service/ipowdm={rule["uuid"]}[[{rule["nodes"]}]]:{json.dumps(rule["content"])}'
                response = requests.delete(url, timeout=10)
                response = requests.delete(url, timeout=30)
                logging.info("Response: %s", response)

    def __optic_slice(self, ietf_intent, rules=None):
@@ -1244,8 +1251,13 @@ class NSController:
                dst2_ip_address = rule["content"]["dest2-ip-address"]
                dst2_ip_mask    = rule["content"]["dest2-ip-mask"]
                dst2_vlan_id    = rule["content"]["dest2-vlan-id"]
                dst3_router_id  = rule["content"]["dest3-node-uuid"]
                dst3_ip_address = rule["content"]["dest3-ip-address"]
                dst3_ip_mask    = rule["content"]["dest3-ip-mask"]
                dst3_vlan_id    = rule["content"]["dest3-vlan-id"]

                service_uuid = rule["content"]["tunnel-uuid"] + '-' + src_router_id + '-' + dst1_router_id + '-' + dst2_router_id

                service_uuid = rule["content"]["tunnel-uuid"] + '-' + src_router_id + '-' + dst1_router_id + '-' + dst2_router_id+ '-' + dst3_router_id
                self.__load_template(2, os.path.join(TEMPLATES_PATH, "IPoWDM_orchestrator.json"))
                tfs_request = json.loads(str(self.__teraflow_template))
                tfs_request["services"][0]["service_id"]["service_uuid"]["uuid"] = service_uuid
@@ -1272,6 +1284,13 @@ class NSController:
                    'ip_mask': dst2_ip_mask,
                    'vlan_id': dst2_vlan_id
                })
                dst.append({
                    'uuid': dst3_router_id,
                    'ip_address': dst3_ip_address,
                    'ip_mask': dst3_ip_mask,
                    'vlan_id': dst3_vlan_id
                })

                config_rules["ipowdm"]["rule_set"]["dst"]  = dst
                bandwidth = rule.get("bandwidth", 0)
                config_rules["ipowdm"]["rule_set"]["bw"]   = bandwidth