Commit 5de064e6 authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Optical Controller:

- Enhance optical connectivity candidate computation with contiguous range handling and modulation format support
parent 0d427071
Loading
Loading
Loading
Loading
+33 −16
Original line number Diff line number Diff line
@@ -177,26 +177,36 @@ def _available_slots(


def _first_contiguous_range(slots: Set[int], required_slots: int) -> Optional[Tuple[int, int]]:
    if required_slots <= 0:
    ranges = _contiguous_ranges(slots, required_slots)
    if len(ranges) == 0:
        return None
    return ranges[0]


def _contiguous_ranges(slots: Set[int], required_slots: int) -> List[Tuple[int, int]]:
    if required_slots <= 0:
        return []
    ordered_slots = sorted(slots)
    if len(ordered_slots) < required_slots:
        return None
        return []
    ranges = []
    streak_start = ordered_slots[0]
    previous = ordered_slots[0]
    streak_len = 1
    if required_slots == 1:
        return streak_start, streak_start
    for slot in ordered_slots[1:]:
        if slot == previous + 1:
            streak_len += 1
            previous = slot
        else:
            if previous - streak_start + 1 >= required_slots:
                ranges.append((streak_start, previous))
            streak_start = slot
            streak_len = 1
            previous = slot
        if streak_len >= required_slots:
            return streak_start, slot
    return None
    if previous - streak_start + 1 >= required_slots:
        ranges.append((streak_start, previous))
    return ranges


def _range_dicts(ranges: Iterable[Tuple[int, int]]) -> List[Dict[str, int]]:
    return [{"n_start": n_start, "n_end": n_end} for n_start, n_end in ranges]


def _preferred_slots(request_json: Dict, required_slots: int) -> Optional[Set[int]]:
@@ -272,7 +282,7 @@ def _path_hops(link_path: List[str], link_by_uuid: Dict[str, Dict]) -> List[Dict
def _candidate_for_path(
    link_path: List[str], link_by_uuid: Dict[str, Dict], reservations: Iterable,
    required_slots: int, preferred_band: Optional[str], preferred_slots: Optional[Set[int]],
    include_reserved_slots: bool,
    include_reserved_slots: bool, modulation_format: Optional[str],
) -> Tuple[Optional[Dict], List[Dict]]:
    rejected = []
    band_order = (preferred_band,) if preferred_band is not None else BAND_ORDER
@@ -293,9 +303,10 @@ def _candidate_for_path(
                continue
            n_start = min(preferred_slots)
            n_end = max(preferred_slots)
            available_ranges = [(n_start, n_end)]
        else:
            contiguous = _first_contiguous_range(path_slots, required_slots)
            if contiguous is None:
            available_ranges = _contiguous_ranges(path_slots, required_slots)
            if len(available_ranges) == 0:
                rejected.append({
                    "code": "INSUFFICIENT_CONTIGUOUS_SPECTRUM",
                    "message": "No contiguous spectrum block is available on every optical link in path",
@@ -303,7 +314,8 @@ def _candidate_for_path(
                    "optical_link_ids": link_path,
                })
                continue
            n_start, n_end = contiguous
            n_start = available_ranges[0][0]
            n_end = n_start + required_slots - 1
        candidate_uuid = str(uuid.uuid5(
            uuid.NAMESPACE_URL, "{:s}|{:s}|{:d}|{:d}".format(",".join(link_path), band, n_start, n_end)
        ))
@@ -314,12 +326,15 @@ def _candidate_for_path(
            "n_start": n_start,
            "n_end": n_end,
            "required_slots": required_slots,
            "modulation_format": modulation_format,
            "available_slot_ranges": _range_dicts(available_ranges),
            "optical_link_ids": link_path,
            "path_hops": _path_hops(link_path, link_by_uuid),
            "path_metric": len(link_path),
            "available_slots_summary": {
                "common_available_slots": len(path_slots),
                "selected_range": "{:d}-{:d}".format(n_start, n_end),
                "available_range_count": len(available_ranges),
            },
        }, rejected
    return None, rejected
@@ -361,12 +376,13 @@ def compute_optical_connectivity_candidates(
    preferred_band = _normalize_band(request_json.get("preferred_band"))
    preferred_range = _preferred_slots(request_json, required_slots)
    include_reserved_slots = bool(request_json.get("include_reserved_slots", False))
    modulation_format = request_json.get("modulation_format")
    candidates = []
    rejected_reasons = []
    for link_path in paths:
        candidate, rejected = _candidate_for_path(
            link_path, link_by_uuid, reservations, required_slots, preferred_band,
            preferred_range, include_reserved_slots
            preferred_range, include_reserved_slots, modulation_format
        )
        rejected_reasons.extend(rejected)
        if candidate is not None:
@@ -393,6 +409,7 @@ def _request_summary(request_json: Dict, src_endpoint, dst_endpoint) -> Dict:
        "explicit_channel_width_ghz": request_json.get(
            "explicit_channel_width_ghz", request_json.get("channel_width_ghz")
        ),
        "requested_modulation_format": request_json.get("modulation_format"),
        "preferred_band": request_json.get("preferred_band"),
        "preferred_n_start": request_json.get("preferred_n_start"),
        "preferred_n_end": request_json.get("preferred_n_end"),
+31 −0
Original line number Diff line number Diff line
@@ -119,6 +119,37 @@ def test_compute_optical_connectivity_candidates_filters_active_reservations():
    assert status == 200
    assert reply['candidates'][0]['n_start'] == 7
    assert reply['candidates'][0]['n_end'] == 10
    assert reply['candidates'][0]['available_slot_ranges'][0] == {'n_start': 7, 'n_end': 319}


def test_compute_optical_connectivity_candidates_returns_all_feasible_ranges():
    link_uuid = 'OL:A==B'
    reply, status = compute_optical_connectivity_candidates(
        {
            'src_endpoint_id': {'device_id': {'device_uuid': {'uuid': 'A'}}},
            'dst_endpoint_id': {'device_id': {'device_uuid': {'uuid': 'B'}}},
            'capacity_gbps': 100,
            'modulation_format': 'dp-qpsk',
            'preferred_band': 'c_slots',
        },
        [_device('A'), _device('B')],
        [_optical_link('A', 'B')],
        [
            _reservation('held-a', link_uuid, 3, 6),
            _reservation('held-b', link_uuid, 11, 14),
        ],
    )

    assert status == 200
    candidate = reply['candidates'][0]
    assert candidate['n_start'] == 7
    assert candidate['n_end'] == 10
    assert candidate['modulation_format'] == 'dp-qpsk'
    assert reply['request_summary']['requested_modulation_format'] == 'dp-qpsk'
    assert candidate['available_slot_ranges'][:2] == [
        {'n_start': 7, 'n_end': 10},
        {'n_start': 15, 'n_end': 319},
    ]


def test_compute_optical_connectivity_candidates_rejects_unavailable_preferred_range():