Loading src/open_exposure_gateway/adapters/http/callback_client.py +0 −2 Changes for src/open_exposure_gateway/adapters/http/callback_client.py: 0 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -19,8 +19,6 @@ class HttpCallbackClient: self.timeout = settings.callback_settings.timeout async def deliver(self, sink: str, event: CallbackEvent) -> None: # onAppInstanceStatusChange -> one CloudEvent object; # onAppDeploymentStatusChange -> a JSON array of CloudEvents (ADR-0008). content: bytes = ( _DEPLOYMENT_EVENTS_ADAPTER.dump_json(event) if isinstance(event, list) Loading src/open_exposure_gateway/api/camara/edge_application_management/v0_1_0_alpha_1/schemas.py +1 −7 Changes for src/open_exposure_gateway/api/camara/edge_application_management/v0_1_0_alpha_1/schemas.py: 1 added line, 7 removed lines. Original line number Diff line number Diff line Loading @@ -12,23 +12,19 @@ from pydantic import ( field_validator, ) # Pattern shared by AppId, AppInstanceId, AppDeploymentId, EdgeCloudZoneId and # KubernetesClusterRef in the spec: lowercase, hyphenated, version 1-5, RFC variant. _CAMARA_UUID_PATTERN = re.compile( r"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" ) def _check_camara_uuid(value: Any) -> Any: # pydantic's UUID alone also accepts uppercase, unhyphenated, urn:uuid:, braced, # nil and version-0 forms, all of which the spec pattern rejects. # pydantic's UUID also accepts uppercase, unhyphenated and nil forms the spec rejects. text = str(value) if isinstance(value, UUID) else value if not isinstance(text, str) or not _CAMARA_UUID_PATTERN.fullmatch(text): raise ValueError(f"must match pattern {_CAMARA_UUID_PATTERN.pattern}") return value # Input-side UUID for EAM request bodies, path and query params. Still a UUID downstream. CamaraUuid = Annotated[UUID, BeforeValidator(_check_camara_uuid)] Loading Loading @@ -283,7 +279,6 @@ class SubscriptionConfig(BaseModel): model_config = ConfigDict(extra="forbid") subscriptionDetail: dict[str, Any] # The spec requires a time zone, so naive timestamps are rejected. subscriptionExpireTime: Optional[AwareDatetime] = None subscriptionMaxEvents: Optional[int] = Field(default=None, ge=1, le=2147483647) initialEvent: Optional[bool] = None Loading @@ -299,7 +294,6 @@ SubscriptionEventType = Literal[ class SubscriptionRequest(BaseModel): model_config = ConfigDict(extra="forbid") # The spec only requires sink; it must be an https URI (pattern ^https:\/\/.+$). sink: str = Field(max_length=2048, pattern=r"^https://.+$") sinkCredential: Optional[dict[str, Any]] = None types: Optional[list[SubscriptionEventType]] = Field(default=None, min_length=1, max_length=1) Loading src/open_exposure_gateway/application/mappers/edge_application_mapper.py +1 −6 Changes for src/open_exposure_gateway/application/mappers/edge_application_mapper.py: 1 added line, 6 removed lines. Original line number Diff line number Diff line Loading @@ -748,9 +748,7 @@ def build_deploy_command( def build_deploy_targets( request: CreateAppDeploymentRequest, app_instance_ids: list[UUID] ) -> list[SRMDeployTarget]: """One targets[] entry per edgeCloudZones[i]; kubernetesClusterRefs[i] pairs positionally (OQ-D1, oeg/app-deployment-flow.md). Zones beyond the length of kubernetesClusterRefs get no domain pin (SRM auto-places).""" # kubernetesClusterRefs[i] pins edgeCloudZones[i]; zones past its end are unpinned. cluster_refs = request.kubernetesClusterRefs or [] if len(cluster_refs) > len(request.edgeCloudZones): raise ValueError("kubernetesClusterRefs must not be longer than edgeCloudZones") Loading Loading @@ -831,9 +829,6 @@ def build_app_deployment_status_change_events( app_id: UUID, occurred_at: str, ) -> list[AppDeploymentStatusChangeCloudEvent]: """One CloudEvent per changed instance for the onAppDeploymentStatusChange callback array (ADR-0008). All instances of a deployment share one app_registration_id, hence one resolved app_id.""" return [ AppDeploymentStatusChangeCloudEvent( id=str(uuid4()), Loading src/open_exposure_gateway/application/services/edge_application_management_service.py +24 −174 File changed.Preview size limit exceeded, changes collapsed. Show changes src/open_exposure_gateway/core/config.py +2 −11 Changes for src/open_exposure_gateway/core/config.py: 2 added lines, 11 removed lines. Original line number Diff line number Diff line Loading @@ -75,20 +75,11 @@ class FmSettings(BaseModel): class ReconciliationSettings(BaseModel): """Sweep that force-fails EAM operations stranded by a lost completion event. event.srm.operation.completed rides an at-most-once NATS subject, so a single dropped event leaves an operation in PENDING/IN_PROGRESS forever -- which 409s the zone (exists_in_zone) and blocks a re-DELETE. This sweep is the recovery until the subject becomes a durable JetStream consumer. """ """Force-fails EAM operations stranded by a lost completion event.""" enabled: bool = True # How often the sweep runs. interval_seconds: int = 60 # An operation still non-terminal this long after creation is force-failed. # Keep it comfortably longer than the slowest real SRM deploy/terminate so # only genuine stalls are hit. # Keep well above the slowest real SRM deploy/terminate. operation_deadline_seconds: int = 1800 Loading Loading
src/open_exposure_gateway/adapters/http/callback_client.py +0 −2 Changes for src/open_exposure_gateway/adapters/http/callback_client.py: 0 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -19,8 +19,6 @@ class HttpCallbackClient: self.timeout = settings.callback_settings.timeout async def deliver(self, sink: str, event: CallbackEvent) -> None: # onAppInstanceStatusChange -> one CloudEvent object; # onAppDeploymentStatusChange -> a JSON array of CloudEvents (ADR-0008). content: bytes = ( _DEPLOYMENT_EVENTS_ADAPTER.dump_json(event) if isinstance(event, list) Loading
src/open_exposure_gateway/api/camara/edge_application_management/v0_1_0_alpha_1/schemas.py +1 −7 Changes for src/open_exposure_gateway/api/camara/edge_application_management/v0_1_0_alpha_1/schemas.py: 1 added line, 7 removed lines. Original line number Diff line number Diff line Loading @@ -12,23 +12,19 @@ from pydantic import ( field_validator, ) # Pattern shared by AppId, AppInstanceId, AppDeploymentId, EdgeCloudZoneId and # KubernetesClusterRef in the spec: lowercase, hyphenated, version 1-5, RFC variant. _CAMARA_UUID_PATTERN = re.compile( r"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" ) def _check_camara_uuid(value: Any) -> Any: # pydantic's UUID alone also accepts uppercase, unhyphenated, urn:uuid:, braced, # nil and version-0 forms, all of which the spec pattern rejects. # pydantic's UUID also accepts uppercase, unhyphenated and nil forms the spec rejects. text = str(value) if isinstance(value, UUID) else value if not isinstance(text, str) or not _CAMARA_UUID_PATTERN.fullmatch(text): raise ValueError(f"must match pattern {_CAMARA_UUID_PATTERN.pattern}") return value # Input-side UUID for EAM request bodies, path and query params. Still a UUID downstream. CamaraUuid = Annotated[UUID, BeforeValidator(_check_camara_uuid)] Loading Loading @@ -283,7 +279,6 @@ class SubscriptionConfig(BaseModel): model_config = ConfigDict(extra="forbid") subscriptionDetail: dict[str, Any] # The spec requires a time zone, so naive timestamps are rejected. subscriptionExpireTime: Optional[AwareDatetime] = None subscriptionMaxEvents: Optional[int] = Field(default=None, ge=1, le=2147483647) initialEvent: Optional[bool] = None Loading @@ -299,7 +294,6 @@ SubscriptionEventType = Literal[ class SubscriptionRequest(BaseModel): model_config = ConfigDict(extra="forbid") # The spec only requires sink; it must be an https URI (pattern ^https:\/\/.+$). sink: str = Field(max_length=2048, pattern=r"^https://.+$") sinkCredential: Optional[dict[str, Any]] = None types: Optional[list[SubscriptionEventType]] = Field(default=None, min_length=1, max_length=1) Loading
src/open_exposure_gateway/application/mappers/edge_application_mapper.py +1 −6 Changes for src/open_exposure_gateway/application/mappers/edge_application_mapper.py: 1 added line, 6 removed lines. Original line number Diff line number Diff line Loading @@ -748,9 +748,7 @@ def build_deploy_command( def build_deploy_targets( request: CreateAppDeploymentRequest, app_instance_ids: list[UUID] ) -> list[SRMDeployTarget]: """One targets[] entry per edgeCloudZones[i]; kubernetesClusterRefs[i] pairs positionally (OQ-D1, oeg/app-deployment-flow.md). Zones beyond the length of kubernetesClusterRefs get no domain pin (SRM auto-places).""" # kubernetesClusterRefs[i] pins edgeCloudZones[i]; zones past its end are unpinned. cluster_refs = request.kubernetesClusterRefs or [] if len(cluster_refs) > len(request.edgeCloudZones): raise ValueError("kubernetesClusterRefs must not be longer than edgeCloudZones") Loading Loading @@ -831,9 +829,6 @@ def build_app_deployment_status_change_events( app_id: UUID, occurred_at: str, ) -> list[AppDeploymentStatusChangeCloudEvent]: """One CloudEvent per changed instance for the onAppDeploymentStatusChange callback array (ADR-0008). All instances of a deployment share one app_registration_id, hence one resolved app_id.""" return [ AppDeploymentStatusChangeCloudEvent( id=str(uuid4()), Loading
src/open_exposure_gateway/application/services/edge_application_management_service.py +24 −174 File changed.Preview size limit exceeded, changes collapsed. Show changes
src/open_exposure_gateway/core/config.py +2 −11 Changes for src/open_exposure_gateway/core/config.py: 2 added lines, 11 removed lines. Original line number Diff line number Diff line Loading @@ -75,20 +75,11 @@ class FmSettings(BaseModel): class ReconciliationSettings(BaseModel): """Sweep that force-fails EAM operations stranded by a lost completion event. event.srm.operation.completed rides an at-most-once NATS subject, so a single dropped event leaves an operation in PENDING/IN_PROGRESS forever -- which 409s the zone (exists_in_zone) and blocks a re-DELETE. This sweep is the recovery until the subject becomes a durable JetStream consumer. """ """Force-fails EAM operations stranded by a lost completion event.""" enabled: bool = True # How often the sweep runs. interval_seconds: int = 60 # An operation still non-terminal this long after creation is force-failed. # Keep it comfortably longer than the slowest real SRM deploy/terminate so # only genuine stalls are hit. # Keep well above the slowest real SRM deploy/terminate. operation_deadline_seconds: int = 1800 Loading