Commit 1be9e509 authored by Sergio Gimenez's avatar Sergio Gimenez
Browse files

feat(fm): terminate inbound CreateFederation

parent 022d21d0
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -23,6 +23,18 @@ class PostgresFederationContextRepo:
            .limit(1)
        )

    async def find_active_inbound(self, partner_id: UUID) -> FederationContext | None:
        return await self._one(
            select(contexts)
            .where(
                contexts.c.partner_op_id == partner_id,
                contexts.c.direction == "inbound",
                contexts.c.status == "available",
            )
            .order_by(contexts.c.created_at.desc())
            .limit(1)
        )

    async def find_inbound(
        self, partner_id: UUID, federation_context_id: str
    ) -> FederationContext | None:
+11 −0
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ from federation_manager.domain.errors import (
    AgreementExpired,
    AgreementViolation,
    AuthenticationFailed,
    FederationAlreadyExists,
    FederationContextMissing,
    FederationContextUnknown,
    NoRouteMatched,
@@ -170,6 +171,16 @@ def register_exception_handlers(app: FastAPI) -> None:
            request.url.path,
        )

    @app.exception_handler(FederationAlreadyExists)
    async def _federation_exists(request: Request, exc: FederationAlreadyExists) -> JSONResponse:
        return problem(
            409,
            "federation-exists",
            "Federation Already Exists",
            "An active federation context already exists with this partner operator.",
            request.url.path,
        )

    @app.exception_handler(FederationContextUnknown)
    async def _context_unknown(request: Request, exc: FederationContextUnknown) -> JSONResponse:
        return problem(
+23 −2
Original line number Diff line number Diff line
from typing import Annotated

from fastapi import APIRouter, Depends, Path
from fastapi import APIRouter, Depends, Path, Response

from federation_manager.api.errors import EWBI_ERROR_RESPONSES
from federation_manager.api.security import get_bearer_token
from federation_manager.application.authentication import PartnerAuthenticator
from federation_manager.application.federation import InboundFederationService
from federation_manager.contracts.ewbi import (
    FederationHealthInfo,
    FederationHealthResponse,
    FederationRequestData,
    FederationResponseData,
    FederationStatus,
)
from federation_manager.dependencies import get_federation_context_repo, get_partner_authenticator
from federation_manager.dependencies import (
    get_federation_context_repo,
    get_inbound_federation_service,
    get_partner_authenticator,
)
from federation_manager.domain.errors import FederationContextUnknown
from federation_manager.domain.ewbi import EWBI_BASE_PATH
from federation_manager.domain.ports import FederationContextRepositoryPort
@@ -51,3 +58,17 @@ async def get_federation_health(
            num_of_accepted_zones="0",
        )
    )


@router.post("/partner", operation_id="CreateFederation", responses=EWBI_ERROR_RESPONSES)
async def create_federation(
    body: FederationRequestData,
    response: Response,
    auth: Annotated[PartnerAuthenticator, Depends(get_partner_authenticator)],
    service: Annotated[InboundFederationService, Depends(get_inbound_federation_service)],
    token: Annotated[str, Depends(get_bearer_token)],
) -> FederationResponseData:
    partner = await auth.authenticate(token)
    context, accepted = await service.create(partner, body)
    response.headers["Location"] = f"{EWBI_BASE_PATH}/{context.federation_context_id}/partner"
    return accepted
+42 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ from federation_manager.contracts.ewbi import (
    MobileNetworkIds,
)
from federation_manager.domain.errors import (
    FederationAlreadyExists,
    FederationEstablishmentFailed,
    PartnerNotActive,
    PartnerResponseInvalid,
@@ -24,6 +25,7 @@ from federation_manager.domain.ports import (
)

OUTBOUND = "outbound"
INBOUND = "inbound"
AVAILABLE = "available"


@@ -38,6 +40,7 @@ class LocalOperator:
    mcc: str
    mncs: tuple[str, ...]
    partner_status_link: str
    platform_caps: tuple[str, ...] = ("serviceAPIs",)


class FederationEstablishmentService:
@@ -104,3 +107,42 @@ class FederationEstablishmentService:
            if await self._contexts.find_active_outbound(partner.id) is None:
                established.append(await self.establish(partner))
        return established


class InboundFederationService:
    def __init__(
        self,
        contexts: FederationContextRepositoryPort,
        local: LocalOperator,
        *,
        clock: Callable[[], datetime] = _utcnow,
        id_factory: Callable[[], UUID] = uuid4,
        context_id_factory: Callable[[], str] = lambda: uuid4().hex,
    ) -> None:
        self._contexts = contexts
        self._local = local
        self._clock = clock
        self._new_id = id_factory
        self._new_context_id = context_id_factory

    async def create(
        self, partner: PartnerOP, request: FederationRequestData
    ) -> tuple[FederationContext, FederationResponseData]:
        if await self._contexts.find_active_inbound(partner.id) is not None:
            raise FederationAlreadyExists(partner.id)

        context = FederationContext(
            id=self._new_id(),
            partner_op_id=partner.id,
            direction=INBOUND,
            federation_context_id=self._new_context_id(),
            status=AVAILABLE,
            created_at=self._clock(),
            status_callback_url=request.partner_status_link,
        )
        await self._contexts.add(context)
        return context, FederationResponseData(
            federation_context_id=context.federation_context_id,
            platform_caps=list(self._local.platform_caps),
            partner_op_federation_id=self._local.federation_id,
        )
+1 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ class Settings(BaseSettings):
    mcc: str = "214"
    mncs: tuple[str, ...] = ("07",)
    partner_status_link: str = "https://localhost/operatorplatform/federation/v1/partner-status"
    platform_caps: tuple[str, ...] = ("serviceAPIs",)
    # ADR-0043 bootstrap path: federate with every active partner that has no outbound context.
    bootstrap_federation: bool = False

Loading