Commit 60bdb07d authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

Context component:

- Add optical link validation for spectrum reservations
parent de5e2e86
Loading
Loading
Loading
Loading
+44 −1
Original line number Diff line number Diff line
@@ -23,7 +23,9 @@ from common.proto.context_pb2 import (
    ContextId, Empty, OpticalSpectrumReservation, OpticalSpectrumReservationId, OpticalSpectrumReservationList,
    OpticalSpectrumReservationStatusEnum
)
from context.service.database.models.OpticalSpectrumReservationModel import OpticalSpectrumReservationModel
from .models.OpticalLinkModel import OpticalLinkModel
from .models.OpticalSpectrumReservationModel import OpticalSpectrumReservationModel
from .models.TopologyModel import TopologyOpticalLinkModel
from .uuids._Builder import get_uuid_from_string, get_uuid_random
from .uuids.Context import context_get_uuid
from .uuids.Link import link_get_uuid
@@ -141,6 +143,45 @@ def _conflicts(
    return _ranges_overlap(n_start, n_end, obj.n_start, obj.n_end)


def _validate_optical_link_slots(
    session : Session, topology_uuid : str, link_uuids : List[str], band : str, n_start : int, n_end : int
) -> None:
    optical_links : List[OpticalLinkModel] = session.query(OpticalLinkModel)\
        .filter(OpticalLinkModel.opticallink_uuid.in_(link_uuids)).all()
    optical_link_by_uuid = {optical_link.opticallink_uuid: optical_link for optical_link in optical_links}

    for link_uuid in link_uuids:
        if link_uuid not in optical_link_by_uuid:
            raise NotFoundException('OpticalLink', link_uuid)

    topology_link_rows : List[TopologyOpticalLinkModel] = session.query(TopologyOpticalLinkModel)\
        .filter(TopologyOpticalLinkModel.topology_uuid == topology_uuid)\
        .filter(TopologyOpticalLinkModel.optical_link_uuid.in_(link_uuids)).all()
    topology_link_uuids = {topology_link.optical_link_uuid for topology_link in topology_link_rows}

    missing_topology_links = sorted(set(link_uuids) - topology_link_uuids)
    if len(missing_topology_links) > 0:
        raise InvalidArgumentException(
            'optical_link_ids', str(missing_topology_links),
            extra_details='optical links are not part of the requested topology'
        )

    for link_uuid in link_uuids:
        optical_link = optical_link_by_uuid[link_uuid]
        slots = getattr(optical_link, band)
        if slots is None:
            raise AlreadyExistsException(
                'OpticalSpectrumReservation', link_uuid,
                extra_details='requested band {:s} is not available on optical link'.format(band)
            )
        for slot_index in range(n_start, n_end + 1):
            if int(slots.get(str(slot_index), 0)) != 1:
                raise AlreadyExistsException(
                    'OpticalSpectrumReservation', link_uuid,
                    extra_details='slot {:s}:{:d} is not available on optical link'.format(band, slot_index)
                )


def optical_spectrum_reservation_set(
    db_engine : Engine, request : OpticalSpectrumReservation
) -> OpticalSpectrumReservationId:
@@ -183,6 +224,8 @@ def optical_spectrum_reservation_set(
    }

    def callback(session : Session) -> Dict:
        _validate_optical_link_slots(session, topology_uuid, link_uuids, band, n_start, n_end)

        active_objects : List[OpticalSpectrumReservationModel] = session.query(OpticalSpectrumReservationModel).all()
        for obj in active_objects:
            if _conflicts(obj, reservation_uuid, context_uuid, topology_uuid, set(link_uuids), band, n_start, n_end, now):
+107 −9
Original line number Diff line number Diff line
@@ -12,10 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import grpc, pytest
import datetime, grpc, pytest
from sqlalchemy.orm import sessionmaker
from common.proto.context_pb2 import (
    Context, ContextId, LinkId, OpticalSpectrumReservation, OpticalSpectrumReservationId,
    OpticalSpectrumReservationStatusEnum, ServiceId, Topology
    OpticalSpectrumReservationStatusEnum, ServiceId, Topology, TopologyId
)
from common.tools.object_factory.Context import json_context, json_context_id
from common.tools.object_factory.Link import json_link_id
@@ -26,6 +27,8 @@ from context.service.database.uuids.Context import context_get_uuid
from context.service.database.uuids.Link import link_get_uuid
from context.service.database.uuids.Service import service_get_uuid
from context.service.database.uuids.Topology import topology_get_uuid
from context.service.database.models.OpticalLinkModel import OpticalLinkModel
from context.service.database.models.TopologyModel import TopologyOpticalLinkModel


CONTEXT_ID = json_context_id('spectrum-admin')
@@ -34,15 +37,68 @@ LINK_1_ID = json_link_id('roadm-a-roadm-b')
LINK_2_ID = json_link_id('roadm-b-roadm-c')


def _reservation(name, n_start, n_end, link_ids=None):
def _slot_map(available_slots):
    return {str(slot_index): 1 for slot_index in available_slots}


def _set_context_topology(context_client : ContextClient) -> None:
    context_client.SetContext(Context(**json_context('spectrum-admin', name='spectrum-admin')))
    context_client.SetTopology(Topology(**json_topology('spectrum-topology', context_id=CONTEXT_ID)))


def _set_optical_link(context_db_mb, link_name : str, available_slots=None, topology_id=None):
    if available_slots is None:
        available_slots = range(0, 80)
    if topology_id is None:
        topology_id = TOPOLOGY_ID

    link_id = LinkId(**json_link_id(link_name))
    link_uuid = link_get_uuid(link_id, allow_random=False)
    _, topology_uuid = topology_get_uuid(TopologyId(**topology_id), allow_random=False)
    now = datetime.datetime.now(datetime.timezone.utc)

    db_engine, _ = context_db_mb
    Session = sessionmaker(bind=db_engine)
    with Session() as session:
        optical_link = session.query(OpticalLinkModel).filter_by(opticallink_uuid=link_uuid).one_or_none()
        if optical_link is None:
            optical_link = OpticalLinkModel(
                opticallink_uuid=link_uuid,
                name=link_name,
                created_at=now,
            )
            session.add(optical_link)
        optical_link.updated_at = now
        optical_link.length = 1
        optical_link.src_port = 'src'
        optical_link.dst_port = 'dst'
        optical_link.local_peer_port = 'local'
        optical_link.remote_peer_port = 'remote'
        optical_link.used = False
        optical_link.c_slots = _slot_map(available_slots)
        optical_link.l_slots = {}
        optical_link.s_slots = {}

        topology_link = session.query(TopologyOpticalLinkModel)\
            .filter_by(topology_uuid=topology_uuid, optical_link_uuid=link_uuid).one_or_none()
        if topology_link is None:
            session.add(TopologyOpticalLinkModel(topology_uuid=topology_uuid, optical_link_uuid=link_uuid))
        session.commit()

    return link_id


def _reservation(name, n_start, n_end, link_ids=None, topology_id=None):
    if link_ids is None:
        link_ids = [LINK_1_ID, LINK_2_ID]
    if topology_id is None:
        topology_id = TOPOLOGY_ID
    return OpticalSpectrumReservation(
        reservation_id=OpticalSpectrumReservationId(
            context_id=ContextId(**CONTEXT_ID),
            reservation_uuid={'uuid': name},
        ),
        topology_id=TOPOLOGY_ID,
        topology_id=topology_id,
        optical_link_ids=[LinkId(**link_id) for link_id in link_ids],
        band='c_slots',
        n_start=n_start,
@@ -53,14 +109,15 @@ def _reservation(name, n_start, n_end, link_ids=None):
    )


def test_optical_spectrum_reservation_lifecycle(context_client : ContextClient) -> None:
    context_client.SetContext(Context(**json_context('spectrum-admin', name='spectrum-admin')))
    context_client.SetTopology(Topology(**json_topology('spectrum-topology', context_id=CONTEXT_ID)))
def test_optical_spectrum_reservation_lifecycle(context_client : ContextClient, context_db_mb) -> None:
    _set_context_topology(context_client)
    link_1_id = _set_optical_link(context_db_mb, 'roadm-a-roadm-b')
    link_2_id = _set_optical_link(context_db_mb, 'roadm-b-roadm-c')

    context_uuid = context_get_uuid(ContextId(**CONTEXT_ID), allow_random=False)
    _, topology_uuid = topology_get_uuid(Topology(**json_topology('spectrum-topology', context_id=CONTEXT_ID)).topology_id)
    link_1_uuid = link_get_uuid(LinkId(**LINK_1_ID), allow_random=False)
    link_2_uuid = link_get_uuid(LinkId(**LINK_2_ID), allow_random=False)
    link_1_uuid = link_get_uuid(link_1_id, allow_random=False)
    link_2_uuid = link_get_uuid(link_2_id, allow_random=False)

    reservation = _reservation('reservation-a', 10, 25)
    reservation_id = context_client.SetOpticalSpectrumReservation(reservation)
@@ -101,3 +158,44 @@ def test_optical_spectrum_reservation_lifecycle(context_client : ContextClient)
    replacement_id = context_client.SetOpticalSpectrumReservation(_reservation('reservation-replacement', 10, 25))
    replacement = context_client.GetOpticalSpectrumReservation(replacement_id)
    assert replacement.status == OpticalSpectrumReservationStatusEnum.OPTICALSPECTRUMRESERVATIONSTATUS_RESERVED


def test_optical_spectrum_reservation_rejects_missing_optical_link(context_client : ContextClient) -> None:
    _set_context_topology(context_client)

    with pytest.raises(grpc.RpcError) as e:
        context_client.SetOpticalSpectrumReservation(_reservation(
            'reservation-missing-link', 10, 25, link_ids=[json_link_id('missing-optical-link')]
        ))
    assert e.value.code() == grpc.StatusCode.NOT_FOUND


def test_optical_spectrum_reservation_rejects_link_outside_topology(context_client : ContextClient, context_db_mb) -> None:
    _set_context_topology(context_client)
    context_client.SetTopology(Topology(**json_topology('other-topology', context_id=CONTEXT_ID)))
    link_id = _set_optical_link(context_db_mb, 'roadm-outside-topology')
    other_topology_id = json_topology_id('other-topology', context_id=CONTEXT_ID)

    with pytest.raises(grpc.RpcError) as e:
        context_client.SetOpticalSpectrumReservation(_reservation(
            'reservation-outside-topology', 10, 25,
            link_ids=[{'link_uuid': {'uuid': link_id.link_uuid.uuid}}],
            topology_id=other_topology_id,
        ))
    assert e.value.code() == grpc.StatusCode.INVALID_ARGUMENT


def test_optical_spectrum_reservation_rejects_occupied_optical_link_slot(
    context_client : ContextClient, context_db_mb
) -> None:
    _set_context_topology(context_client)
    link_id = _set_optical_link(
        context_db_mb, 'roadm-occupied-slots', available_slots=list(range(0, 12)) + list(range(13, 80))
    )

    with pytest.raises(grpc.RpcError) as e:
        context_client.SetOpticalSpectrumReservation(_reservation(
            'reservation-occupied-slot', 10, 25,
            link_ids=[{'link_uuid': {'uuid': link_id.link_uuid.uuid}}],
        ))
    assert e.value.code() == grpc.StatusCode.ALREADY_EXISTS