Commit e2f44851 authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Optical Controller component:

- Added optional spectrum reservation
parent a4d4e990
Loading
Loading
Loading
Loading
+11 −2
Original line number Diff line number Diff line
@@ -13,7 +13,7 @@
# limitations under the License.

import logging, time
from flask import Flask
from flask import Flask, request
from flask import render_template
from common.DeviceTypes import DeviceTypeEnum
from flask_restplus import Resource, Api
@@ -88,7 +88,16 @@ class AddFlexLightpath(Resource):
        #    rsa.g.printGraph()

        if rsa is not None:
            flow_id, optical_band_id = rsa.rsa_fs_computation(src, dst, bitrate, bidir, band, obx_idx, pref)
            reservation = request.args.get("reservation")
            if reservation is None and request.args.get("reservation_band") is not None:
                reservation = {
                    "band": request.args.get("reservation_band"),
                    "n_start": request.args.get("reservation_start"),
                    "n_end": request.args.get("reservation_end"),
                }
            flow_id, optical_band_id = rsa.rsa_fs_computation(
                src, dst, bitrate, bidir, band, obx_idx, pref, reservation=reservation
            )
            if flow_id is not None:
                if rsa.db_flows[flow_id]["op-mode"] == 0:
                    return 'No path found', 404
+26 −15
Original line number Diff line number Diff line
@@ -614,10 +614,13 @@ class RSA():
        return fiber_list

    #function invoked for lightpaths and OB
    def select_slots_and_ports(self, links, n_slots, c, l, s, bidir, preferred=None):
    def select_slots_and_ports(self, links, n_slots, c, l, s, bidir, preferred=None, reservation=None):
        if debug:
            print (links, n_slots, c, l, s, bidir, self.c_slot_number, self.l_slot_number, self.s_slot_number)
        band, slots = slot_selection(c, l, s, n_slots, self.c_slot_number, self.l_slot_number, self.s_slot_number, preferred)
        band, slots = slot_selection(
            c, l, s, n_slots, self.c_slot_number, self.l_slot_number, self.s_slot_number,
            preferred, reservation=reservation
        )
        if debug:
            print (band, slots)
        if band is None:
@@ -686,10 +689,13 @@ class RSA():
        return t_flows, band, slots, {}, {}

    #function ivoked for fs lightpaths only
    def select_slots_and_ports_fs(self, links, n_slots, c, l, s, bidir, o_band_id):
    def select_slots_and_ports_fs(self, links, n_slots, c, l, s, bidir, o_band_id, reservation=None):
        if debug:
            print(self.links_dict)
        band, slots = slot_selection(c, l, s, n_slots, self.c_slot_number, self.l_slot_number, self.s_slot_number)
        band, slots = slot_selection(
            c, l, s, n_slots, self.c_slot_number, self.l_slot_number, self.s_slot_number,
            reservation=reservation
        )
        if band is None:
            print("ERROR: No slots available in the three bands")
            return None, None, None, None, None
@@ -953,7 +959,7 @@ class RSA():
        #self.db_flows[flow_id]["parent_opt_band"] = 0
        #self.db_flows[flow_id]["new_optical_band"] = 0

    def create_optical_band(self, links, path, bidir, num_slots, old_band_x=None, preferred=None):
    def create_optical_band(self, links, path, bidir, num_slots, old_band_x=None, preferred=None, reservation=None):
        print("INFO: Creating optical-band of {} slots".format(num_slots))
        if self.opt_band_id == 0:
            self.opt_band_id += 1
@@ -1008,7 +1014,9 @@ class RSA():
            print(l_slots)
            print(s_slots)
        if len(c_slots) > 0 or len(l_slots) > 0 or len(s_slots) > 0:
            flow_list, band_range, slots, fiber_f, fiber_b = self.select_slots_and_ports(links, num_slots, c_slots, l_slots, s_slots, bidir, preferred)
            flow_list, band_range, slots, fiber_f, fiber_b = self.select_slots_and_ports(
                links, num_slots, c_slots, l_slots, s_slots, bidir, preferred, reservation=reservation
            )
            if debug:
                print(flow_list, band_range, slots, fiber_f, fiber_b)
            f0, band = frequency_converter(band_range, slots)
@@ -1203,7 +1211,7 @@ class RSA():
            self.null_values(self.flow_id)
            return self.flow_id
            
    def rsa_fs_computation(self, src, dst, rate, bidir, band, bandx_id, preferred=None):
    def rsa_fs_computation(self, src, dst, rate, bidir, band, bandx_id, preferred=None, reservation=None):
        if band is not None:
            num_slots_ob = map_band_to_slot(band)
            print(band, num_slots_ob)
@@ -1227,7 +1235,9 @@ class RSA():
            if len(path) < 1:
                self.null_values_ob(self.opt_band_id)
                return self.opt_band_id, []
            optical_band_id, temp_links = self.create_optical_band(links, path, bidir, num_slots_ob, old_band_x, preferred)
            optical_band_id, temp_links = self.create_optical_band(
                links, path, bidir, num_slots_ob, old_band_x, preferred, reservation=reservation
            )
            return None, optical_band_id
        print("INFO: TP to TP connection")
        if self.flow_id == 0:
@@ -1285,7 +1295,7 @@ class RSA():
                                    flow_list, band_range, slots, fiber_f, fiber_b = self.select_slots_and_ports_fs(temp_links2, num_slots,
                                                                                                                    c_slots,
                                                                                                                    l_slots, s_slots, bidir,
                                                                                                                    ob_id)
                                                                                                                    ob_id, reservation=reservation)
                                    f0, band = frequency_converter(band_range, slots)
                                    if debug:
                                        print(f0, band)
@@ -1345,7 +1355,7 @@ class RSA():
                                                temp_links2, num_slots,
                                                c_slots,
                                                l_slots, s_slots, bidir,
                                                ob_id)
                                                ob_id, reservation=reservation)
                                            f0, band = frequency_converter(band_range, slots)
                                            if debug:
                                                print(f0, band)
@@ -1399,7 +1409,7 @@ class RSA():
                            flow_list, band_range, slots, fiber_f, fiber_b = self.select_slots_and_ports_fs(temp_links2, num_slots,
                                                                                                            c_slots,
                                                                                                            l_slots, s_slots, bidir,
                                                                                                            ob_id)
                                                                                                            ob_id, reservation=reservation)
                            f0, band = frequency_converter(band_range, slots)
                            if debug:
                                print(f0, band)
@@ -1465,7 +1475,7 @@ class RSA():
                                        temp_links2, num_slots,
                                        c_slots,
                                        l_slots, s_slots, bidir,
                                        ob_id)
                                        ob_id, reservation=reservation)
                                    f0, band = frequency_converter(band_range, slots)
                                    if debug:
                                        print(f0, band)
@@ -1510,7 +1520,7 @@ class RSA():
            print("INFO: optical-band width specified")
        #if no OB I create a new one
        links, path = self.compute_path(src, dst)
        optical_band_id, temp_links = self.create_optical_band(links, path, bidir, num_slots_ob)
        optical_band_id, temp_links = self.create_optical_band(links, path, bidir, num_slots_ob, reservation=reservation)
        op, num_slots = map_rate_to_slot(rate)
        if debug:
            print(temp_links)
@@ -1520,8 +1530,9 @@ class RSA():
            print(l_slots)
            print(s_slots)
        if len(c_slots) > 0 or len(l_slots) > 0 or len(s_slots) > 0:
            flow_list, band_range, slots, fiber_f, fiber_b = self.select_slots_and_ports_fs(temp_links, num_slots, c_slots,
                                                                                            l_slots, s_slots, bidir, optical_band_id)
            flow_list, band_range, slots, fiber_f, fiber_b = self.select_slots_and_ports_fs(
                temp_links, num_slots, c_slots, l_slots, s_slots, bidir, optical_band_id, reservation=reservation
            )
            f0, band = frequency_converter(band_range, slots)
            if debug:
                print(f0, band)
+90 −0
Original line number Diff line number Diff line
# Copyright 2022-2026 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.

import pytest

from opticalcontroller.tools import parse_slot_reservation, slot_selection


def test_parse_compact_optical_spectrum_reservation():
    reservation = parse_slot_reservation("C_BAND:51-66")

    assert reservation == {"band": "c_slots", "n_start": 51, "n_end": 66}


def test_parse_dict_optical_spectrum_reservation():
    reservation = parse_slot_reservation({"band": "l_slots", "n_start": "4", "n_end": "11"})

    assert reservation == {"band": "l_slots", "n_start": 4, "n_end": 11}


def test_slot_selection_without_reservation_keeps_first_fit():
    band, slots = slot_selection(
        c=list(range(0, 10)),
        l=list(range(20, 30)),
        s=list(range(40, 50)),
        n_slots=4,
        Nc=10,
        Nl=10,
        Ns=10,
    )

    assert band == "c_slots"
    assert slots == [0, 1, 2, 3]


def test_slot_selection_with_reservation_consumes_requested_band_and_range():
    band, slots = slot_selection(
        c=list(range(0, 100)),
        l=list(range(0, 100)),
        s=list(range(0, 100)),
        n_slots=4,
        Nc=100,
        Nl=100,
        Ns=100,
        reservation={"band": "l_slots", "n_start": 20, "n_end": 30},
    )

    assert band == "l_slots"
    assert slots == [20, 21, 22, 23]


def test_slot_selection_with_unavailable_reservation_returns_none():
    band, slots = slot_selection(
        c=[0, 1, 2, 3],
        l=[10, 11, 13, 14],
        s=[20, 21, 22, 23],
        n_slots=3,
        Nc=4,
        Nl=4,
        Ns=4,
        reservation={"band": "l_slots", "n_start": 10, "n_end": 12},
    )

    assert band is None
    assert slots is None


def test_slot_selection_rejects_too_narrow_reservation():
    with pytest.raises(ValueError):
        slot_selection(
            c=list(range(0, 100)),
            l=[],
            s=[],
            n_slots=4,
            Nc=100,
            Nl=0,
            Ns=0,
            reservation={"band": "c_slots", "n_start": 20, "n_end": 22},
        )
+75 −4
Original line number Diff line number Diff line
@@ -12,7 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np
import json, logging, numpy as np
from typing import Dict, List, Optional, Tuple
from opticalcontroller.variables import  *
import json , logging
from context.client.ContextClient import ContextClient
@@ -251,9 +252,68 @@ def get_links_to_node(topology, node):
    return result


def slot_selection(c, l, s, n_slots, Nc, Nl, Ns, preferred=None):
    # First Fit
def _normalize_reservation_band(band: str) -> str:
    normalized_band = str(band).strip().lower().replace("-", "_")
    if normalized_band in {"c", "c_band", "c_slots"}:
        return "c_slots"
    if normalized_band in {"l", "l_band", "l_slots"}:
        return "l_slots"
    if normalized_band in {"s", "s_band", "s_slots"}:
        return "s_slots"
    raise ValueError("Unsupported optical spectrum band: {:s}".format(str(band)))


def parse_slot_reservation(reservation) -> Optional[Dict[str, int]]:
    if reservation is None:
        return None

    if isinstance(reservation, dict):
        if len(reservation) == 0:
            return None
        band = reservation.get("band")
        n_start = reservation.get("n_start", reservation.get("start"))
        n_end = reservation.get("n_end", reservation.get("end"))
    else:
        reservation_text = str(reservation).strip()
        if len(reservation_text) == 0:
            return None
        if reservation_text.startswith("{"):
            return parse_slot_reservation(json.loads(reservation_text))
        if ":" not in reservation_text or "-" not in reservation_text:
            raise ValueError(
                "Optical spectrum reservation must be '<band>:<start>-<end>' or JSON"
            )
        band, slot_range = reservation_text.split(":", maxsplit=1)
        n_start, n_end = slot_range.split("-", maxsplit=1)

    band = _normalize_reservation_band(band)
    n_start = int(n_start)
    n_end = int(n_end)
    if n_start < 0 or n_end < n_start:
        raise ValueError(
            "Invalid optical spectrum reservation range: {:d}-{:d}".format(n_start, n_end)
        )
    return {"band": band, "n_start": n_start, "n_end": n_end}


def _select_reserved_slots(available_slots: List[int], n_slots: int, reservation: Dict[str, int]) -> Optional[List[int]]:
    requested_slots = list(range(reservation["n_start"], reservation["n_end"] + 1))
    if len(requested_slots) < n_slots:
        raise ValueError(
            "Optical spectrum reservation provides {:d} slots, but {:d} are required".format(
                len(requested_slots), n_slots
            )
        )

    selected_slots = requested_slots[0:n_slots]
    if not list_in_list(selected_slots, sorted(available_slots)):
        return None
    return selected_slots


def slot_selection(c, l, s, n_slots, Nc, Nl, Ns, preferred=None, reservation=None):
    # First Fit
    reservation = parse_slot_reservation(reservation)
    if isinstance(n_slots, int):
        slot_c = n_slots
        slot_l = n_slots
@@ -262,6 +322,18 @@ def slot_selection(c, l, s, n_slots, Nc, Nl, Ns, preferred=None):
        slot_c = Nc
        slot_l = Nl
        slot_s = Ns
    if reservation is not None:
        band = reservation["band"]
        if band == "c_slots":
            selected_slots = _select_reserved_slots(c, slot_c, reservation)
        elif band == "l_slots":
            selected_slots = _select_reserved_slots(l, slot_l, reservation)
        else:
            selected_slots = _select_reserved_slots(s, slot_s, reservation)
        if selected_slots is None:
            return None, None
        return band, selected_slots

    if preferred == None or preferred == "ANY":
        if len(c) >= slot_c:
            return "c_slots", c[0: slot_c]
@@ -371,4 +443,3 @@ def set_link_update (fib:dict,link:dict,test="updating"):
    except Exception as err:
        print (f"setOpticalLink {err}")