Loading src/open_exposure_gateway/adapters/database/core.py +2 −0 Changes for src/open_exposure_gateway/adapters/database/core.py: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -29,6 +29,8 @@ _ADDED_COLUMNS = ( "ALTER TABLE qod_sessions ADD COLUMN IF NOT EXISTS started_at TIMESTAMP WITH TIME ZONE", "ALTER TABLE qod_sessions ADD COLUMN IF NOT EXISTS ended_at TIMESTAMP WITH TIME ZONE", "ALTER TABLE qod_sessions ADD COLUMN IF NOT EXISTS status_info VARCHAR(32)", "ALTER TABLE qod_sessions ADD COLUMN IF NOT EXISTS device JSONB", "ALTER TABLE qod_sessions ADD COLUMN IF NOT EXISTS application_server JSONB", ) Loading src/open_exposure_gateway/adapters/database/mappers.py +4 −0 Changes for src/open_exposure_gateway/adapters/database/mappers.py: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -157,6 +157,8 @@ class QodSessionMapper: external_ref=row.external_ref, device_ports=row.device_ports, application_server_ports=row.application_server_ports, device=row.device, application_server=row.application_server, started_at=row.started_at, ended_at=row.ended_at, status_info=cast(QodStatusInfo | None, row.status_info), Loading @@ -176,6 +178,8 @@ class QodSessionMapper: external_ref=domain.external_ref, device_ports=domain.device_ports, application_server_ports=domain.application_server_ports, device=domain.device, application_server=domain.application_server, started_at=domain.started_at, ended_at=domain.ended_at, status_info=domain.status_info, Loading src/open_exposure_gateway/adapters/database/sql.py +2 −0 Changes for src/open_exposure_gateway/adapters/database/sql.py: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -243,6 +243,8 @@ class QodSessionRow(AuditedMixin, Base): external_ref: Mapped[str | None] = mapped_column(String(255)) device_ports: Mapped[dict[str, object] | None] = mapped_column(JSONB) application_server_ports: Mapped[dict[str, object] | None] = mapped_column(JSONB) device: Mapped[dict[str, object] | None] = mapped_column(JSONB) application_server: Mapped[dict[str, object] | None] = mapped_column(JSONB) started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) status_info: Mapped[str | None] = mapped_column(String(32)) src/open_exposure_gateway/application/mappers/quality_on_demand_mapper.py +26 −8 Changes for src/open_exposure_gateway/application/mappers/quality_on_demand_mapper.py: 26 added lines, 8 removed lines. Original line number Diff line number Diff line Loading @@ -104,14 +104,22 @@ def build_activate_command( ) def build_session_info( qod_session: QodSession, capability: SRMNetworkCapability, sink: str | None = None ) -> SessionInfo: """Combines `qod_sessions` (identity, ownership, cached `qosStatus`) with SRM's live capability detail (target device/application server) into the CAMARA response -- the read-side counterpart of `build_activate_command`, and the boundary that keeps SRM's internal shape from leaking onto the CAMARA surface directly. """ SessionTarget = tuple[DeviceResponse | None, ApplicationServer] def session_target_from_row(qod_session: QodSession) -> SessionTarget | None: """The device/application server persisted at creation; None for rows that predate it.""" if qod_session.application_server is None: return None device = ( build_device_response(Device.model_validate(qod_session.device)) if qod_session.device is not None else None ) return device, ApplicationServer.model_validate(qod_session.application_server) def session_target_from_capability(capability: SRMNetworkCapability) -> SessionTarget: device_target = capability.parameters_snapshot.target.device server_target = capability.parameters_snapshot.target.application_server Loading @@ -135,6 +143,16 @@ def build_session_info( ipv4Address=server_target.ipv4, ipv6Address=server_target.ipv6, ) return device, application_server def build_session_info( qod_session: QodSession, target: SessionTarget, sink: str | None = None ) -> SessionInfo: """Builds the CAMARA response from `qod_sessions` (identity, ownership, cached `qosStatus`) and the session's device/application server. """ device, application_server = target device_ports = ( PortsSpec.model_validate(qod_session.device_ports) if qod_session.device_ports is not None Loading src/open_exposure_gateway/application/services/quality_on_demand_service.py +20 −4 Changes for src/open_exposure_gateway/application/services/quality_on_demand_service.py: 20 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -13,11 +13,14 @@ from open_exposure_gateway.api.camara.quality_on_demand.v1_1_0.schemas import ( ) from open_exposure_gateway.application.callback_secrets import encrypt_secret, load_bearer_token from open_exposure_gateway.application.mappers.quality_on_demand_mapper import ( SessionTarget, build_activate_command, build_deactivate_command, build_device_response, build_qos_status_changed_event, build_session_info, session_target_from_capability, session_target_from_row, ) from open_exposure_gateway.core.config import ( DEFAULT_QOD_SERVICE_SPECIFICATION_ID, Loading Loading @@ -232,6 +235,10 @@ class QualityOnDemandService: if request.applicationServerPorts is not None else None ), device=device.model_dump(mode="json", exclude_none=True), application_server=request.applicationServer.model_dump( mode="json", exclude_none=True ), ) ) Loading Loading @@ -555,9 +562,8 @@ class QualityOnDemandService: ) -> SessionInfo: qod_session = await self._get_owned_session(session_id, app_provider_id) capability = await self.srm_client.get_network_capability( service_instance_id=session_id, x_correlator=x_correlator, target = session_target_from_row(qod_session) or await self._legacy_session_target( session_id, x_correlator ) sink: Optional[str] = None if self._callback_registration_repo is not None: Loading @@ -565,7 +571,17 @@ class QualityOnDemandService: qod_session.operation_id ) sink = registration.sink if registration is not None else None return build_session_info(qod_session, capability, sink=sink) return build_session_info(qod_session, target, sink=sink) async def _legacy_session_target( self, session_id: str, x_correlator: Optional[str] ) -> SessionTarget: """Rows created before device/application_server were persisted.""" capability = await self.srm_client.get_network_capability( service_instance_id=session_id, x_correlator=x_correlator, ) return session_target_from_capability(capability) async def delete_session( self, Loading Loading
src/open_exposure_gateway/adapters/database/core.py +2 −0 Changes for src/open_exposure_gateway/adapters/database/core.py: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -29,6 +29,8 @@ _ADDED_COLUMNS = ( "ALTER TABLE qod_sessions ADD COLUMN IF NOT EXISTS started_at TIMESTAMP WITH TIME ZONE", "ALTER TABLE qod_sessions ADD COLUMN IF NOT EXISTS ended_at TIMESTAMP WITH TIME ZONE", "ALTER TABLE qod_sessions ADD COLUMN IF NOT EXISTS status_info VARCHAR(32)", "ALTER TABLE qod_sessions ADD COLUMN IF NOT EXISTS device JSONB", "ALTER TABLE qod_sessions ADD COLUMN IF NOT EXISTS application_server JSONB", ) Loading
src/open_exposure_gateway/adapters/database/mappers.py +4 −0 Changes for src/open_exposure_gateway/adapters/database/mappers.py: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -157,6 +157,8 @@ class QodSessionMapper: external_ref=row.external_ref, device_ports=row.device_ports, application_server_ports=row.application_server_ports, device=row.device, application_server=row.application_server, started_at=row.started_at, ended_at=row.ended_at, status_info=cast(QodStatusInfo | None, row.status_info), Loading @@ -176,6 +178,8 @@ class QodSessionMapper: external_ref=domain.external_ref, device_ports=domain.device_ports, application_server_ports=domain.application_server_ports, device=domain.device, application_server=domain.application_server, started_at=domain.started_at, ended_at=domain.ended_at, status_info=domain.status_info, Loading
src/open_exposure_gateway/adapters/database/sql.py +2 −0 Changes for src/open_exposure_gateway/adapters/database/sql.py: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -243,6 +243,8 @@ class QodSessionRow(AuditedMixin, Base): external_ref: Mapped[str | None] = mapped_column(String(255)) device_ports: Mapped[dict[str, object] | None] = mapped_column(JSONB) application_server_ports: Mapped[dict[str, object] | None] = mapped_column(JSONB) device: Mapped[dict[str, object] | None] = mapped_column(JSONB) application_server: Mapped[dict[str, object] | None] = mapped_column(JSONB) started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) status_info: Mapped[str | None] = mapped_column(String(32))
src/open_exposure_gateway/application/mappers/quality_on_demand_mapper.py +26 −8 Changes for src/open_exposure_gateway/application/mappers/quality_on_demand_mapper.py: 26 added lines, 8 removed lines. Original line number Diff line number Diff line Loading @@ -104,14 +104,22 @@ def build_activate_command( ) def build_session_info( qod_session: QodSession, capability: SRMNetworkCapability, sink: str | None = None ) -> SessionInfo: """Combines `qod_sessions` (identity, ownership, cached `qosStatus`) with SRM's live capability detail (target device/application server) into the CAMARA response -- the read-side counterpart of `build_activate_command`, and the boundary that keeps SRM's internal shape from leaking onto the CAMARA surface directly. """ SessionTarget = tuple[DeviceResponse | None, ApplicationServer] def session_target_from_row(qod_session: QodSession) -> SessionTarget | None: """The device/application server persisted at creation; None for rows that predate it.""" if qod_session.application_server is None: return None device = ( build_device_response(Device.model_validate(qod_session.device)) if qod_session.device is not None else None ) return device, ApplicationServer.model_validate(qod_session.application_server) def session_target_from_capability(capability: SRMNetworkCapability) -> SessionTarget: device_target = capability.parameters_snapshot.target.device server_target = capability.parameters_snapshot.target.application_server Loading @@ -135,6 +143,16 @@ def build_session_info( ipv4Address=server_target.ipv4, ipv6Address=server_target.ipv6, ) return device, application_server def build_session_info( qod_session: QodSession, target: SessionTarget, sink: str | None = None ) -> SessionInfo: """Builds the CAMARA response from `qod_sessions` (identity, ownership, cached `qosStatus`) and the session's device/application server. """ device, application_server = target device_ports = ( PortsSpec.model_validate(qod_session.device_ports) if qod_session.device_ports is not None Loading
src/open_exposure_gateway/application/services/quality_on_demand_service.py +20 −4 Changes for src/open_exposure_gateway/application/services/quality_on_demand_service.py: 20 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -13,11 +13,14 @@ from open_exposure_gateway.api.camara.quality_on_demand.v1_1_0.schemas import ( ) from open_exposure_gateway.application.callback_secrets import encrypt_secret, load_bearer_token from open_exposure_gateway.application.mappers.quality_on_demand_mapper import ( SessionTarget, build_activate_command, build_deactivate_command, build_device_response, build_qos_status_changed_event, build_session_info, session_target_from_capability, session_target_from_row, ) from open_exposure_gateway.core.config import ( DEFAULT_QOD_SERVICE_SPECIFICATION_ID, Loading Loading @@ -232,6 +235,10 @@ class QualityOnDemandService: if request.applicationServerPorts is not None else None ), device=device.model_dump(mode="json", exclude_none=True), application_server=request.applicationServer.model_dump( mode="json", exclude_none=True ), ) ) Loading Loading @@ -555,9 +562,8 @@ class QualityOnDemandService: ) -> SessionInfo: qod_session = await self._get_owned_session(session_id, app_provider_id) capability = await self.srm_client.get_network_capability( service_instance_id=session_id, x_correlator=x_correlator, target = session_target_from_row(qod_session) or await self._legacy_session_target( session_id, x_correlator ) sink: Optional[str] = None if self._callback_registration_repo is not None: Loading @@ -565,7 +571,17 @@ class QualityOnDemandService: qod_session.operation_id ) sink = registration.sink if registration is not None else None return build_session_info(qod_session, capability, sink=sink) return build_session_info(qod_session, target, sink=sink) async def _legacy_session_target( self, session_id: str, x_correlator: Optional[str] ) -> SessionTarget: """Rows created before device/application_server were persisted.""" capability = await self.srm_client.get_network_capability( service_instance_id=session_id, x_correlator=x_correlator, ) return session_target_from_capability(capability) async def delete_session( self, Loading