def send_error(request_header: Header, nsi_exc: NsiException,
               connection_id: UUID) -> None:
    """Send a NSI Error referencing the request correlation_id together with details from the NsiException.

    The error message is sent from a PA to an RA in response to an outstanding operation request
    when an error condition encountered, and as a result, the operation cannot be successfully completed.
    The correlationId carried in the NSI CS header structure will identify the original request associated
    with this error message.
    """
    from supa.util.converter import to_service_exception

    pb_e_req = ErrorRequest()
    pb_e_req.header.CopyFrom(request_header)
    pb_e_req.service_exception.CopyFrom(
        to_service_exception(nsi_exc, connection_id))

    stub = get_stub()
    stub.Error(pb_e_req)
Exemple #2
0
    def _send_reserve_commit_failed(self, session: orm.Session,
                                    nsi_exc: NsiException) -> None:
        # the reservation is still in the session, hence no actual query will be performed
        reservation: Reservation = session.query(Reservation).get(
            self.connection_id)
        pb_rcf_req = ReserveCommitFailedRequest()

        pb_rcf_req.header.CopyFrom(
            to_header(reservation, add_path_segment=False))
        pb_rcf_req.connection_id = str(reservation.connection_id)
        pb_rcf_req.connection_states.CopyFrom(
            to_connection_states(reservation, data_plane_active=False))
        pb_rcf_req.service_exception.CopyFrom(
            to_service_exception(nsi_exc, reservation.connection_id))

        self.log.info("Sending message.",
                      method="ReserveCommitFailed",
                      request_message=pb_rcf_req)
        stub = requester.get_stub()
        stub.ReserveCommitFailed(pb_rcf_req)
Exemple #3
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
Exemple #4
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