Exemplo n.º 1
0
    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)
Exemplo n.º 2
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)
Exemplo n.º 3
0
    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)
Exemplo n.º 4
0
    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)
Exemplo n.º 5
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)
Exemplo n.º 6
0
    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)
Exemplo n.º 7
0
    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.debug("Received message.", request_message=pb_reserve_request)

        # Sanity check on start and end time, in case of problem return ServiceException
        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)
        extra_info = ""
        if is_specified(end_time):
            if end_time <= start_time:
                extra_info = f"End time cannot come before start time. {start_time=!s}, {end_time=!s}"
            elif end_time <= current_timestamp():
                extra_info = f"End time lies in the past. {end_time=!s}"
        if extra_info:
            log.info(extra_info)
            reserve_response = ReserveResponse(
                header=to_response_header(pb_reserve_request.header),
                service_exception=to_service_exception(
                    NsiException(MissingParameter, extra_info,
                                 {Variable.END_TIME: end_time.isoformat()})),
            )
            log.debug("Sending response.", response_message=reserve_response)
            return reserve_response

        if not pb_reserve_request.connection_id:  # new reservation
            pb_header: Header = pb_reserve_request.header
            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(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.

            log = log.bind(connection_id=str(connection_id))

        else:
            log = log.bind(connection_id=pb_reserve_request.connection_id)
            # TODO modify reservation (else clause)

        from supa import scheduler

        job: Job

        scheduler.add_job(job := ReserveJob(connection_id),
                          trigger=job.trigger())
        reserve_response = ReserveResponse(header=to_response_header(
            pb_reserve_request.header),
                                           connection_id=str(connection_id))
        #
        # TODO: - make timeout delta configurable
        #       - add reservation version to timeout job so we do not accidentally timeout a modify
        #
        log.info("Schedule reserve timeout",
                 connection_id=str(connection_id),
                 timeout=30)
        scheduler.add_job(job := ReserveTimeoutJob(connection_id),
                          trigger=job.trigger())

        log.debug("Sending response.", response_message=reserve_response)
        return reserve_response
Exemplo n.º 8
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
Exemplo n.º 9
0
    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)