コード例 #1
0
ファイル: main.py プロジェクト: workfloworchestrator/SuPA
def delete(port_id: Optional[UUID], name: Optional[str]) -> None:
    """Delete Orchestrator port if not in use (or previously used).

    A port can only be deleted if it was never used in a reservation.
    Once used  a port cannot be deleted again.
    A port can be disabled though!
    This will take it out of the pool of ports
    reservations (and hence connections) are made against.
    See the `disable` command.
    """
    init_app(with_scheduler=False)
    from supa.db.model import Port
    from supa.db.session import db_session

    if port_id is None and name is None:
        click.echo("Please specify either --port-id or --name.", err=True)
    try:
        with db_session() as session:
            port = session.query(Port)
            if port_id is not None:
                port = port.get(port_id)
            else:
                port = port.filter(Port.name == name).one()
            session.delete(port)
    except sqlalchemy.exc.IntegrityError:
        click.echo(
            "Port is in use. Could not delete it. (You could disable it instead to prevent further use).",
            err=True)
    except sqlalchemy.orm.exc.NoResultFound:
        click.echo("Port could not be found.", err=True)
コード例 #2
0
    def recover(cls: Type[ActivateJob]) -> List[Job]:
        """Recover ActivationJob's that did not get to run before SuPA was terminated.

        Returns:
            List of ActivationJob's that still need to be run.
        """
        from supa.db.session import db_session

        with db_session() as session:
            connection_ids: List[UUID] = list(
                flatten(
                    session.query(Reservation.connection_id).filter(
                        Reservation.lifecycle_state ==
                        LifecycleStateMachine.Created.value,
                        Reservation.provision_state ==
                        ProvisionStateMachine.Provisioned.value,
                        or_(
                            Reservation.data_plane_state ==
                            DataPlaneStateMachine.Inactive.value,
                            Reservation.data_plane_state ==
                            DataPlaneStateMachine.Activating.value,
                        ),
                    ).all()))
        for cid in connection_ids:
            logger.debug("Recover job",
                         job="ActivateJob",
                         connection_id=str(cid))

        return [ActivateJob(cid) for cid in connection_ids]
コード例 #3
0
ファイル: conftest.py プロジェクト: workfloworchestrator/SuPA
def connection_id() -> Column:
    """Create new reservation in db and return connection ID."""
    with db_session() as session:
        reservation = Reservation(
            correlation_id=uuid4(),
            protocol_version="application/vnd.ogf.nsi.cs.v2.provider+soap",
            requester_nsa="urn:ogf:network:example.domain:2021:requester",
            provider_nsa="urn:ogf:network:example.domain:2021:provider",
            reply_to=None,
            session_security_attributes=None,
            global_reservation_id="global reservation id",
            description="reservation 1",
            version=0,
            start_time=datetime.now(timezone.utc) + timedelta(minutes=10),
            end_time=datetime.now(timezone.utc) + timedelta(minutes=20),
            bandwidth=10,
            symmetric=True,
            src_domain="test.domain:2001",
            src_network_type="topology",
            src_port="port1",
            src_vlans=1783,
            dst_domain="test.domain:2001",
            dst_network_type="topology",
            dst_port="port2",
            dst_vlans=1783,
            lifecycle_state="CREATED",
        )
        session.add(reservation)
        session.flush()  # let db generate connection_id

        yield reservation.connection_id

        session.delete(reservation)
コード例 #4
0
    def __call__(self) -> None:
        """Provision reservation request.

        The reservation will be provisioned and
        a ProvisionConfirmed message will be sent to the NSA/AG.
        If the provision state machine is not in the correct state for a Provision
        an NSI exception is returned leaving the state machine unchanged.
        """
        self.log.info("Provisioning reservation")

        from supa.db.session import db_session

        with db_session() as session:
            reservation = (session.query(Reservation).filter(
                Reservation.connection_id == self.connection_id).one_or_none())
            psm = ProvisionStateMachine(reservation,
                                        state_field="provision_state")
            dpsm = DataPlaneStateMachine(reservation,
                                         state_field="data_plane_state")
            try:
                #
                # TODO:  If there is a Network Resource Manager that needs to be contacted
                #        to provision the reservation request then this is the place.
                #        If this is a recovered job then try to recover the reservation state
                #        from the NRM.
                #
                pass
            except NsiException as nsi_exc:
                self.log.info("Provision failed.", reason=nsi_exc.text)
                send_error(
                    to_header(reservation),
                    nsi_exc,
                    self.connection_id,
                )
            except Exception as exc:
                self.log.exception("Unexpected error occurred.",
                                   reason=str(exc))
                send_error(
                    to_header(reservation),
                    NsiException(
                        GenericInternalError,
                        str(exc),
                        {
                            Variable.PROVISION_STATE:
                            reservation.provsion_state,
                            Variable.CONNECTION_ID: str(self.connection_id),
                        },
                    ),
                    self.connection_id,
                )
            else:
                from supa import scheduler

                psm.provision_confirmed()
                dpsm.data_plane_up()
                scheduler.add_job(job := ActivateJob(self.connection_id),
                                  trigger=job.trigger())
                self._send_provision_confirmed(session)
コード例 #5
0
ファイル: reserve.py プロジェクト: workfloworchestrator/SuPA
    def __call__(self) -> None:
        """Commit reservation request.

        If the reservation can be committed
        a ReserveCommitConfirmed message will be send to the NSA/AG.
        If for whatever reason the reservation cannot be committed
        a ReserveCommitFailed message will be sent instead.
        If the reservation state machine is not in the correct state for a ReserveCommit
        an NSI error is returned leaving the state machine unchanged.
        """
        self.log.info("Committing reservation")

        from supa.db.session import db_session

        with db_session() as session:
            reservation = (session.query(Reservation).filter(
                Reservation.connection_id == self.connection_id).one_or_none())
            rsm = ReservationStateMachine(reservation,
                                          state_field="reservation_state")
            try:
                if reservation.reservation_timeout:
                    # we use this column because the reserve state machine is actually missing a state
                    raise NsiException(
                        GenericServiceError,
                        "Cannot commit a timed out reservation.")
                else:
                    #
                    # TODO:  If there is a Network Resource Manager that needs to be contacted
                    #        to commit the reservation request then this is the place.
                    #        If this is a recovered job then try to recover the reservation state
                    #        from the NRM.
                    #
                    pass
            except NsiException as nsi_exc:
                self.log.info("Reserve commit failed.", reason=nsi_exc.text)
                rsm.reserve_commit_failed()
                self._send_reserve_commit_failed(session, nsi_exc)
            except Exception as exc:
                self.log.exception("Unexpected error occurred.",
                                   reason=str(exc))
                rsm.reserve_commit_failed()
                nsi_exc = NsiException(GenericInternalError,
                                       str(exc))  # type: ignore[misc]
                self._send_reserve_commit_failed(session,
                                                 nsi_exc)  # type: ignore[misc]
            else:
                # create new psm just before the reserveCommitConfirmed,
                # this will set initial provision_state on reservation
                psm = ProvisionStateMachine(
                    reservation, state_field="provision_state")  # noqa: F841
                rsm.reserve_commit_confirmed()
                self._send_reserve_commit_confirmed(session)
コード例 #6
0
    def trigger(self) -> DateTrigger:
        """Trigger for ActivateJob's.

        Returns:
            DateTrigger set to start_time of reservation.
        """
        from supa.db.session import db_session

        with db_session() as session:
            reservation = (session.query(Reservation).filter(
                Reservation.connection_id == self.connection_id).one_or_none())
            # If start_time is in the past then the job will run immediately.
            return DateTrigger(run_date=reservation.start_time)
コード例 #7
0
ファイル: reserve.py プロジェクト: workfloworchestrator/SuPA
    def __call__(self) -> None:
        """Abort reservation request.

        The reservation will be aborted and
        a ReserveAbortConfirmed message will be sent to the NSA/AG.
        If the reservation state machine is not in the correct state for a ReserveCommit
        an NSI error is returned leaving the state machine unchanged.
        """
        self.log.info("Aborting reservation")

        from supa.db.session import db_session

        with db_session() as session:
            reservation = (session.query(Reservation).filter(
                Reservation.connection_id == self.connection_id).one_or_none())
            rsm = ReservationStateMachine(reservation,
                                          state_field="reservation_state")
            try:
                #
                # TODO:  If there is a Network Resource Manager that needs to be contacted
                #        to abort the reservation request then this is the place.
                #        If this is a recovered job then try to recover the reservation state
                #        from the NRM.
                #
                pass
            except NsiException as nsi_exc:
                self.log.info("Reserve abort failed.", reason=nsi_exc.text)
                send_error(
                    to_header(reservation),
                    nsi_exc,
                    self.connection_id,
                )
            except Exception as exc:
                self.log.exception("Unexpected error occurred.",
                                   reason=str(exc))
                send_error(
                    to_header(reservation),
                    NsiException(
                        GenericInternalError,
                        str(exc),
                        {
                            Variable.RESERVATION_STATE:
                            reservation.reservation_state,
                            Variable.CONNECTION_ID: str(self.connection_id),
                        },
                    ),
                    self.connection_id,
                )
            else:
                rsm.reserve_abort_confirmed()
                self._send_reserve_abort_confirmed(session)
コード例 #8
0
    def __call__(self) -> None:
        """Activate data plane request.

        Activate the data plane according to the reservation criteria and
        send a data plane status notitication to the NSA/AG.
        If the data plane state machine is not in the correct state for a Activate
        an NSI exception is returned leaving the state machine unchanged.
        """
        self.log.info("Activating data plane")

        from supa.db.session import db_session

        with db_session() as session:
            reservation = (session.query(Reservation).filter(
                Reservation.connection_id == self.connection_id).one_or_none())
            dpsm = DataPlaneStateMachine(reservation,
                                         state_field="data_plane_state")
            try:
                #
                # TODO:  Call the Network Resource Manager to activate the data plane.
                #        If this is a recovered job then try to recover the data plane state
                #        from the NRM.
                #
                pass
            except NsiException as nsi_exc:
                self.log.info("Data plane activation failed",
                              reason=nsi_exc.text)
                send_error(
                    to_header(reservation),
                    nsi_exc,
                    self.connection_id,
                )
            except Exception as exc:
                self.log.exception("Unexpected error occurred",
                                   reason=str(exc))
                send_error(
                    to_header(reservation),
                    NsiException(
                        GenericInternalError,
                        str(exc),
                        {
                            Variable.CONNECTION_ID: str(self.connection_id),
                        },
                    ),
                    self.connection_id,
                )
            else:
                dpsm.data_plane_activated()
                send_data_plane_state_change(session, self.connection_id)
コード例 #9
0
    def recover(cls: Type[ProvisionJob]) -> List[Job]:
        """Recover ProvisionJob's that did not get to run before SuPA was terminated.

        Returns:
            List of ProvisionJob's that still need to be run.
        """
        from supa.db.session import db_session

        with db_session() as session:
            connection_ids: List[UUID] = list(
                flatten(
                    session.query(Reservation.connection_id).filter(
                        Reservation.provision_state ==
                        ProvisionStateMachine.Provisioning.value).all()))
        return [ProvisionJob(cid) for cid in connection_ids]
コード例 #10
0
def test_reserve_timeout_job_reserve_timeout_notification(
        connection_id: Column, reserve_held: None) -> None:
    """Test ReserveTimeoutJob to transition to ReserveTimeout.

    Verify that a ReserveTimeoutJob will transition the reserve state machine
    to state ReserveTimeout when in state ReserveHeld.
    """
    reserve_timeout_job = ReserveTimeoutJob(connection_id)
    reserve_timeout_job.__call__()

    # verify that reservation is transitioned to ReserveTimeout
    with db_session() as session:
        reservation = session.query(Reservation).filter(
            Reservation.connection_id == connection_id).one()
        assert reservation.reservation_state == ReservationStateMachine.ReserveTimeout.value
コード例 #11
0
def test_reserve_abort_job_reserve_abort_confirmed(connection_id: Column,
                                                   reserve_aborting: None,
                                                   get_stub: None) -> None:
    """Test ReserveAbortJob to transition to ReserveStart.

    Verify that a ReserveAbortJob will transition the reserve state machine
    to state ReserveStart when in state ReserveAborting.
    """
    reserve_abort_job = ReserveAbortJob(connection_id)
    reserve_abort_job.__call__()

    # verify that reservation is transitioned to ReserveStart
    with db_session() as session:
        reservation = session.query(Reservation).filter(
            Reservation.connection_id == connection_id).one()
        assert reservation.reservation_state == ReservationStateMachine.ReserveStart.value
コード例 #12
0
def test_reserve_timeout_job_invalid_transition(
        caplog: Any, connection_id: Column, reserve_committing: None) -> None:
    """Test ReserveTimeoutJob to detect an invalid transition.

    Verify that a ReserveTimeoutJob will detect an invalid transition attempt
    when the reservation reserve state machine is not in a state that accepts an timeout.
    """
    reserve_timeout_job = ReserveTimeoutJob(connection_id)
    caplog.clear()
    reserve_timeout_job.__call__()
    assert "Reservation not timed out" in caplog.text

    # verify that reservation is still in state ReserveHeld
    with db_session() as session:
        reservation = session.query(Reservation).filter(
            Reservation.connection_id == connection_id).one()
        assert reservation.reservation_state == ReservationStateMachine.ReserveCommitting.value
コード例 #13
0
ファイル: reserve.py プロジェクト: gkoller/SuPA
    def __call__(self) -> None:
        """Commit Reservation."""
        self.log.info("Committing reservation")

        from supa.db.session import db_session

        with db_session() as session:
            reservation = (session.query(Reservation).filter(
                Reservation.connection_id == self.connection_id).one_or_none())
            if reservation is None:
                raise NsiException(
                    ReservationNonExistent, str(self.connection_id),
                    {Variable.CONNECTION_ID: str(self.connection_id)})
            try:
                rsm = ReservationStateMachine(reservation,
                                              state_field="reservation_state")
                rsm.reserve_commit_request()
            except NsiException as nsi_exc:
                self.log.info("Reserve commit failed.", reason=nsi_exc.text)
                rsm.reserve_commit_failed()
                self._send_reserve_commit_failed(session, nsi_exc)
            except TransitionNotAllowed as tna:
                self.log.info("Invalid state transition", reason=str(tna))
                rsm.reserve_commit_failed()
                nsi_exc = NsiException(
                    InvalidTransition,
                    str(tna),
                    {
                        Variable.RESERVATION_STATE:
                        reservation.reservation_state
                    },
                )  # type: ignore[misc]
                self._send_reserve_commit_failed(session,
                                                 nsi_exc)  # type: ignore[misc]
            except Exception as exc:
                self.log.exception("Unexpected error occurred.",
                                   reason=str(exc))
                rsm.reserve_commit_failed()
                nsi_exc = NsiException(GenericInternalError,
                                       str(exc))  # type: ignore[misc]
                self._send_reserve_commit_failed(session,
                                                 nsi_exc)  # type: ignore[misc]
            else:
                self._send_reserve_commit_confirmed(session)
コード例 #14
0
ファイル: reserve.py プロジェクト: workfloworchestrator/SuPA
    def recover(cls: Type[ReserveTimeoutJob]) -> List[Job]:
        """Recover ReserveTimeoutJob's that did not get to run before SuPA was terminated.

        The current implementation just re-adds a new reservation timeout
        for all reservations that are still in ReserveHeld,
        potentially almost doubling the original reservation hold time.

        Returns:
            List of ReserveTimeoutJob's that still need to be run.
        """
        from supa.db.session import db_session

        with db_session() as session:
            connection_ids: List[UUID] = list(
                flatten(
                    session.query(Reservation.connection_id).filter(
                        Reservation.reservation_state ==
                        ReservationStateMachine.ReserveHeld.value).all()))
        return [ReserveTimeoutJob(cid) for cid in connection_ids]
コード例 #15
0
ファイル: main.py プロジェクト: workfloworchestrator/SuPA
def add(port_id: UUID, name: str, vlans: str, remote_stp: Optional[str],
        bandwidth: int, enabled: bool) -> None:
    """Add Orchestrator port to SuPA."""
    init_app(with_scheduler=False)

    # Safe to import, now that `init_app()` has been called
    from supa.db.model import Port
    from supa.db.session import db_session

    port = Port(
        port_id=port_id,
        name=name,
        vlans=str(VlanRanges(vlans)),
        remote_stp=remote_stp,
        bandwidth=bandwidth,
        enabled=enabled,
    )

    with db_session() as session:
        session.add(port)
コード例 #16
0
ファイル: main.py プロジェクト: workfloworchestrator/SuPA
def list_cmd(only: Optional[str]) -> None:
    """List Orchestrator ports made available to SuPA."""
    init_app(with_scheduler=False)
    from supa.db.model import Port
    from supa.db.session import db_session

    with db_session() as session:
        ports = session.query(Port)
        if only == "enabled":
            ports = ports.filter(Port.enabled.is_(True))
        elif only == "disabled":
            ports = ports.filter(Port.enabled.is_(False))
        ports = ports.values(Port.port_id, Port.name, Port.vlans,
                             Port.bandwidth, Port.remote_stp, Port.enabled)
        click.echo(
            tabulate(
                tuple(ports),
                headers=("port_id", "name", "vlans", "bandwidth", "remote_stp",
                         "enabled"),
                tablefmt="psql",
            ))
コード例 #17
0
ファイル: main.py プロジェクト: workfloworchestrator/SuPA
def _set_enable(port_id: Optional[UUID], name: Optional[str],
                enabled: bool) -> None:
    """Enable or disable a specific port."""
    init_app(with_scheduler=False)
    from supa.db.model import Port
    from supa.db.session import db_session

    if port_id is None and name is None:
        click.echo("Please specify either --port-id or --name.", err=True)
    try:
        with db_session() as session:
            port = session.query(Port)
            if port_id is not None:
                port = port.get(port_id)
            else:
                port = port.filter(Port.name == name).one()
            port.enabled = enabled
            click.echo(
                f"Port '{port.name}' has been {'enabled' if enabled else 'disabled'}."
            )
    except sqlalchemy.orm.exc.NoResultFound:
        click.echo("Port could not be found.", err=True)
コード例 #18
0
ファイル: server.py プロジェクト: gkoller/SuPA
    def Reserve(self, pb_reserve_request: ReserveRequest, context: ServicerContext) -> ReserveResponse:
        """Request new reservation, or modify existing reservation.

        The reserve message is sent from an RA to a PA when a new reservation is being requested, or
        a modification to an existing reservation is required. The :class:`ReserveResponse` indicates that the PA
        has accepted the reservation request for processing and has assigned it the returned
        connectionId. The original ``connection_id`` will be returned for the :class:`ReserveResponse`` of a
        modification. A :class:`ReserveConfirmed` or :class:`ReserveFailed` message will be sent asynchronously to the
        RA when reserve operation has completed processing.

        Args:
            pb_reserve_request: All the details about the requested reservation.
            context: gRPC server context object.

        Returns:
            A :class:`ReserveResponse` message containing the PA assigned ``connection_id`` for this reservation
            request. This value will be unique within the context of the PA.

        """
        log = logger.bind(method="Reserve")
        log.info("Received message.", request_message=pb_reserve_request)

        if not pb_reserve_request.connection_id:  # new reservation
            pb_header: Header = pb_reserve_request.header
            pb_criteria: ReservationRequestCriteria = pb_reserve_request.criteria
            pb_schedule: Schedule = pb_criteria.schedule
            start_time = as_utc_timestamp(pb_schedule.start_time)
            end_time = as_utc_timestamp(pb_schedule.end_time)
            pb_ptps: PointToPointService = pb_criteria.ptps
            pb_path_trace: PathTrace = pb_header.path_trace

            reservation = model.Reservation(
                correlation_id=UUID(pb_header.correlation_id),
                protocol_version=pb_header.protocol_version,
                requester_nsa=pb_header.requester_nsa,
                provider_nsa=pb_header.provider_nsa,
                reply_to=pb_header.reply_to if pb_header.reply_to else None,
                session_security_attributes=pb_header.session_security_attributes
                if pb_header.session_security_attributes
                else None,
                global_reservation_id=pb_reserve_request.global_reservation_id,
                description=pb_reserve_request.description if pb_reserve_request.description else None,
                version=pb_criteria.version,
            )
            if is_specified(end_time):
                if end_time <= start_time:
                    raise Exception(f"End time cannot come before the start time. {start_time=!s}, {end_time=!s}")
                elif end_time <= current_timestamp():
                    raise Exception(f"End time lies in the past. {end_time=!s}")
            if is_specified(start_time):
                reservation.start_time = start_time
            if is_specified(end_time):
                reservation.end_time = end_time
            reservation.bandwidth = pb_ptps.capacity
            reservation.directionality = Directionality.Name(pb_ptps.directionality)
            reservation.symmetric = pb_ptps.symmetric_path

            src_stp = parse_stp(pb_ptps.source_stp)
            reservation.src_domain = src_stp.domain
            reservation.src_network_type = src_stp.network_type
            reservation.src_port = src_stp.port
            reservation.src_vlans = str(src_stp.vlan_ranges)

            dst_stp = parse_stp(pb_ptps.dest_stp)
            reservation.dst_domain = dst_stp.domain
            reservation.dst_network_type = dst_stp.network_type
            reservation.dst_port = dst_stp.port
            reservation.dst_vlans = str(dst_stp.vlan_ranges)

            for k, v in pb_ptps.parameters.items():
                reservation.parameters.append(model.Parameter(key=k, value=v))

            if pb_path_trace.id:
                path_trace = model.PathTrace(
                    path_trace_id=pb_path_trace.id, ag_connection_id=pb_path_trace.connection_id
                )
                for pb_path in pb_path_trace.paths:
                    path = model.Path()
                    for pb_segment in pb_path.segments:
                        segment = model.Segment(segment_id=pb_segment.id, upa_connection_id=pb_segment.connection_id)
                        for pb_stp in pb_segment.stps:
                            segment.stps.append(model.Stp(stp_id=pb_stp))
                        path.segments.append(segment)
                    path_trace.paths.append(path)
                reservation.path_trace = path_trace

            rsm = ReservationStateMachine(reservation, state_field="reservation_state")
            rsm.reserve_request()

            from supa.db.session import db_session

            with db_session() as session:
                session.add(reservation)
                session.flush()  # Let DB (actually SQLAlchemy) generate the connection_id for us.
                connection_id = reservation.connection_id  # Can't reference it outside of the session, hence new var.

        # TODO modify reservation (else clause)

        from supa import scheduler

        scheduler.add_job(ReserveJob(connection_id))
        reserve_response = ReserveResponse(header=pb_reserve_request.header, connection_id=str(connection_id))

        log.info("Sending response.", response_message=reserve_response)
        return reserve_response
コード例 #19
0
    def Provision(self, pb_provision_request: ProvisionRequest,
                  context: ServicerContext) -> ProvisionResponse:
        """Provision reservation.

        Check if the connection ID exists, if the provision state machine exists (as an indication
        that the reservation was committed, and if the provision state machine transition is
        allowed, all real work for provisioning the reservation is done asynchronously by
        :class:`~supa.job.reserve.ProvisionJob`

        Args:
            pb_provision_request: Basically the connection id wrapped in a request like object
            context: gRPC server context object.

        Returns:
            A response telling the caller we have received its provision request.
        """
        connection_id = UUID(pb_provision_request.connection_id)
        log = logger.bind(method="Provision", connection_id=str(connection_id))
        log.debug("Received message.", request_message=pb_provision_request)

        from supa.db.session import db_session

        with db_session() as session:
            reservation = (session.query(
                model.Reservation).filter(model.Reservation.connection_id ==
                                          connection_id).one_or_none())
            if reservation is None:
                log.info("Connection ID does not exist")
                provision_response = ProvisionResponse(
                    header=to_response_header(pb_provision_request.header),
                    service_exception=to_service_exception(
                        NsiException(
                            ReservationNonExistent, str(connection_id),
                            {Variable.CONNECTION_ID: str(connection_id)}),
                        connection_id,
                    ),
                )
            elif not reservation.provision_state:
                log.info("First version of reservation not committed yet")
                provision_response = ProvisionResponse(
                    header=to_response_header(pb_provision_request.header),
                    service_exception=to_service_exception(
                        NsiException(
                            InvalidTransition,
                            "First version of reservation not committed yet",
                            {
                                Variable.RESERVATION_STATE:
                                reservation.reservation_state,
                                Variable.CONNECTION_ID: str(connection_id),
                            },
                        ),
                        connection_id,
                    ),
                )
            else:
                try:
                    rsm = ProvisionStateMachine(reservation,
                                                state_field="provision_state")
                    rsm.provision_request()
                except TransitionNotAllowed as tna:
                    log.info("Not scheduling ProvisionJob", reason=str(tna))
                    provision_response = ProvisionResponse(
                        header=to_response_header(pb_provision_request.header),
                        service_exception=to_service_exception(
                            NsiException(
                                InvalidTransition,
                                str(tna),
                                {
                                    Variable.PROVISION_STATE:
                                    reservation.provision_state,
                                    Variable.CONNECTION_ID: str(connection_id),
                                },
                            ),
                            connection_id,
                        ),
                    )
                else:
                    from supa import scheduler

                    scheduler.add_job(job := ProvisionJob(connection_id),
                                      trigger=job.trigger())
                    provision_response = ProvisionResponse(
                        header=to_response_header(pb_provision_request.header))

        log.debug("Sending response.", response_message=provision_response)
        return provision_response
コード例 #20
0
ファイル: conftest.py プロジェクト: workfloworchestrator/SuPA
def released(connection_id: Column) -> None:
    """Set provision state machine of reservation identified by connection_id to state Released."""
    with db_session() as session:
        reservation = session.query(Reservation).filter(Reservation.connection_id == connection_id).one()
        reservation.provision_state = ProvisionStateMachine.Released.value
コード例 #21
0
ファイル: conftest.py プロジェクト: workfloworchestrator/SuPA
def reserve_aborting(connection_id: Column) -> None:
    """Set reserve state machine of reservation identified by connection_id to state ReserveAborting."""
    with db_session() as session:
        reservation = session.query(Reservation).filter(Reservation.connection_id == connection_id).one()
        reservation.reservation_state = ReservationStateMachine.ReserveAborting.value
コード例 #22
0
ファイル: reserve.py プロジェクト: workfloworchestrator/SuPA
    def __call__(self) -> None:
        """Timeout reservation request.

        The reservation will be timed out
        if the ReservationStateMachine is still in the ReserveHeld state and
        a ReserveTimeoutNotification message will be sent to the NSA/AG.
        If another transition already moved the state beyond ReserveHeld
        then this is practically a no-op.
        """
        self.log.info("Timeout reservation")

        from supa.db.session import db_session

        with db_session() as session:
            reservation = (session.query(Reservation).filter(
                Reservation.connection_id == self.connection_id).one_or_none())
            rsm = ReservationStateMachine(reservation,
                                          state_field="reservation_state")
            try:
                rsm.reserve_timeout_notification()
                #
                # TODO:  If there is a Network Resource Manager that needs to be contacted
                #        to timeout the reservation request then this is the place.
                #        If this is a recovered job then try to recover the reservation state
                #        from the NRM.
                #
            except TransitionNotAllowed:
                # Reservation is already in another state turning this into a no-op.
                self.log.info(
                    "Reservation not timed out",
                    state=rsm.current_state.identifier,
                    connection_id=str(self.connection_id),
                )
            except NsiException as nsi_exc:
                self.log.info("Reserve timeout failed.", reason=nsi_exc.text)
                send_error(
                    to_header(reservation),
                    nsi_exc,
                    self.connection_id,
                )
            except Exception as exc:
                self.log.exception("Unexpected error occurred.",
                                   reason=str(exc))
                send_error(
                    to_header(reservation),
                    NsiException(
                        GenericInternalError,
                        str(exc),
                        {
                            Variable.RESERVATION_STATE:
                            reservation.reservation_state,
                            Variable.CONNECTION_ID: str(self.connection_id),
                        },
                    ),
                    self.connection_id,
                )
            else:
                #
                # TODO: release reserved resources(?)
                #
                self.log.debug(
                    "setting reservation.reservation_timeout to true in db")
                reservation.reservation_timeout = True
                self._send_reserve_timeout_notification(session)
コード例 #23
0
ファイル: reserve.py プロジェクト: gkoller/SuPA
    def __call__(self) -> None:
        """Check reservation request.

        If the reservation can be made
        a ReserveConfirmed message will be send to the NSA/AG.
        If not, a ReserveFailed message will be send instead.
        """
        self.log.info("Checking reservation request.")
        from supa.db.session import db_session

        with db_session() as session:
            reservation: Reservation = (session.query(Reservation).options(
                joinedload(Reservation.parameters),
                joinedload(Reservation.path_trace).joinedload(
                    PathTrace.paths).joinedload(Path.segments).joinedload(
                        Segment.stps),
            ).get(self.connection_id))
            rsm = ReservationStateMachine(reservation,
                                          state_field="reservation_state")
            port_resources_in_use = self._port_resources_in_use(session)

            try:
                if rsm.current_state != ReservationStateMachine.ReserveChecking:
                    raise NsiException(
                        InvalidTransition,
                        rsm.current_state.name,
                        {Variable.RESERVATION_STATE: rsm.current_state.value},
                    )
                if reservation.src_port == reservation.dst_port:
                    raise NsiException(
                        # Not sure if this is the correct error to use.
                        # As its descriptive text refers to path computation
                        # it suggests its an error typically returned by an aggregator.
                        # On the other hand it is the only error related to a path/connection as a whole
                        # and that is what is at issue here.
                        NoServiceplanePathFound,
                        "source and destination ports are the same",
                        {
                            Variable.PROVIDER_NSA: settings.nsa_id,
                            Variable.SOURCE_STP: str(reservation.src_stp()),
                            Variable.DEST_STP: str(reservation.dst_stp()),
                        },
                    )
                for target, var in (("src", Variable.SOURCE_STP),
                                    ("dst", Variable.DEST_STP)):
                    # Dynamic attribute lookups as we want to use the same code for
                    # both src and dst ports/stps
                    res_port = getattr(reservation, f"{target}_port")
                    stp = str(
                        getattr(reservation,
                                f"{target}_stp")())  # <-- mind the func call
                    domain = getattr(reservation, f"{target}_domain")
                    network_type = getattr(reservation,
                                           f"{target}_network_type")
                    requested_vlans = VlanRanges(
                        getattr(reservation, f"{target}_vlans"))
                    port = session.query(Port).filter(
                        Port.name == res_port).one_or_none()
                    if (port is None or not port.enabled or domain != settings.
                            domain  # only process requests for our domain
                            or network_type != settings.
                            network_type  # only process requests for our network
                        ):
                        raise NsiException(UnknownStp, stp, {var: stp})
                    if not requested_vlans:
                        raise NsiException(InvalidLabelFormat,
                                           "missing VLANs label on STP",
                                           {var: stp})
                    if port.name in port_resources_in_use:
                        bandwidth_available = port.bandwidth - port_resources_in_use[
                            port.name].bandwidth
                        available_vlans = VlanRanges(
                            port.vlans) - port_resources_in_use[
                                port.name].vlans
                    else:
                        bandwidth_available = port.bandwidth
                        available_vlans = VlanRanges(port.vlans)
                    if bandwidth_available < reservation.bandwidth:
                        raise NsiException(
                            CapacityUnavailable,
                            f"requested: {format_bandwidth(reservation.bandwidth)}, "
                            f"available: {format_bandwidth(bandwidth_available)}",
                            {
                                Variable.CAPACITY: str(reservation.bandwidth),
                                var: stp,
                            },
                        )
                    if not available_vlans:
                        raise NsiException(StpUnavailable, "all VLANs in use",
                                           {var: stp})
                    candidate_vlans = requested_vlans & available_vlans
                    if not candidate_vlans:
                        raise NsiException(
                            StpUnavailable,
                            f"no matching VLAN found (requested: {requested_vlans!s}, available: {available_vlans!s}",
                            {var: stp},
                        )
                    selected_vlan = random.choice(list(candidate_vlans))
                    setattr(reservation, f"{target}_selected_vlan",
                            selected_vlan)
            except NsiException as nsi_exc:
                self.log.info("Reservation failed.", reason=nsi_exc.text)
                rsm.reserve_failed()
                self._send_reserve_failed(session, nsi_exc)
            except Exception as exc:
                self.log.exception("Unexpected error occurred.",
                                   reason=str(exc))
                rsm.reserve_failed()
                nsi_exc = NsiException(GenericInternalError,
                                       str(exc))  # type: ignore[misc]
                self._send_reserve_failed(session,
                                          nsi_exc)  # type: ignore[misc]
            else:
                rsm.reserve_confirmed()
                self._send_reserve_confirmed(session)