Loading src/context/service/database/OpticalSpectrumReservation.py +44 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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: Loading Loading @@ -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): Loading src/context/tests/test_optical_spectrum_reservation.py +107 −9 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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') Loading @@ -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, Loading @@ -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) Loading Loading @@ -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 Loading
src/context/service/database/OpticalSpectrumReservation.py +44 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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: Loading Loading @@ -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): Loading
src/context/tests/test_optical_spectrum_reservation.py +107 −9 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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') Loading @@ -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, Loading @@ -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) Loading Loading @@ -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