Loading src/open_exposure_gateway/application/mappers/edge_application_mapper.py +2 −2 Original line number Diff line number Diff line Loading @@ -227,7 +227,7 @@ def build_app_manifest(catalog: SRMCatalogPayload) -> AppManifest: required_resources = KubernetesResources( infraKind="kubernetes", applicationResources=CamaraApplicationResources.model_validate(app_res), isStandalone=compute.standalone if compute else False, isStandalone=unit.resource_requirements.standalone, ) elif unit.runtime_kind == "container": millicores = compute.cpu_millicores if compute and compute.cpu_millicores is not None else 0 Loading Loading @@ -502,7 +502,6 @@ def build_catalog_payload(translation: AppRegistrationTranslation) -> SRMCatalog compute = SRMComputeResources( cpu_millicores=cpu_millicores, memory_mb=memory_mb, standalone=standalone, accelerator=accelerator, storage=storage, ) Loading @@ -523,6 +522,7 @@ def build_catalog_payload(translation: AppRegistrationTranslation) -> SRMCatalog compute=compute, topology=topology, interfaces=interfaces or None, standalone=standalone, ) repo = translation.app_repo Loading src/open_exposure_gateway/application/services/edge_application_management_service.py +2 −1 Original line number Diff line number Diff line Loading @@ -218,7 +218,8 @@ class EdgeApplicationManagementService: ) catalog_payload = build_catalog_payload(translation) created = await self.srm_client.create_catalog_service_specification( payload=catalog_payload.model_dump(mode="json"), x_correlator=x_correlator payload=catalog_payload.model_dump(mode="json", exclude_none=True), x_correlator=x_correlator, ) if created.id != translation.app_registration_id: raise DownstreamServiceException( Loading src/open_exposure_gateway/domain/edge_application_management.py +1 −1 Original line number Diff line number Diff line Loading @@ -128,7 +128,6 @@ class SRMStorageVolume(BaseModel): class SRMComputeResources(BaseModel): cpu_millicores: int | None = None memory_mb: int | None = None standalone: bool = False accelerator: SRMAccelerator | None = None storage: list[SRMStorageVolume] | None = None Loading @@ -153,6 +152,7 @@ class SRMComputeIntent(BaseModel): compute: SRMComputeResources | None = None topology: SRMTopologyConstraints | None = None interfaces: list[SRMNetworkInterface] | None = None standalone: bool = False class SRMServiceSpecDescriptor(BaseModel): Loading tests/unit/test_eam_mapper.py +23 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,7 @@ def _make_srm_catalog( cpu_millicores: int = 2000, memory_mb: int = 4096, interfaces: list[SRMNetworkInterface] | None = None, standalone: bool = False, ) -> SRMCatalogPayload: return SRMCatalogPayload( service_specification=SRMServiceSpecEntry( Loading Loading @@ -94,6 +95,7 @@ def _make_srm_catalog( min_node_memory_mb=1024, ), interfaces=interfaces, standalone=standalone, ), ) ], Loading Loading @@ -274,6 +276,14 @@ class TestBuildAppManifest: assert result.componentSpec[0].componentName == "frontend" assert len(result.componentSpec[0].networkInterfaces) == 2 def test_standalone_read_from_top_level_field(self) -> None: # standalone lives on resource_requirements itself, not nested under # resource_requirements.compute (srm/canonical-parameters-schema.md). catalog = _make_srm_catalog(standalone=True) result = build_app_manifest(catalog) assert isinstance(result.requiredResources, KubernetesResources) assert result.requiredResources.isStandalone is True def test_helm_topology_reconstructed(self) -> None: catalog = _make_srm_catalog() catalog.service_deployment_units[0].resource_requirements.topology = SRMTopologyConstraints( Loading Loading @@ -576,6 +586,19 @@ class TestBuildCatalogPayload: assert compute.cpu_millicores == 2000 assert compute.memory_mb == 4096 def test_standalone_is_top_level_not_nested_in_compute(self) -> None: # SRM's srm.compute/v1 shape puts `standalone` as a sibling of `compute`, # not inside it (srm/canonical-parameters-schema.md); SRM rejects unknown # fields, so a nested `compute.standalone` fails app registration outright. manifest = _make_helm_manifest(standalone=True) translation = build_app_registration_translation( manifest, APP_ID, APP_REGISTRATION_ID, "t", "p" ) catalog = build_catalog_payload(translation) resource_requirements = catalog.service_deployment_units[0].resource_requirements assert resource_requirements.standalone is True assert not hasattr(resource_requirements.compute, "standalone") def test_spec_ref_is_app_id(self) -> None: manifest = _make_helm_manifest() translation = build_app_registration_translation( Loading tests/unit/test_eam_service.py +20 −0 Original line number Diff line number Diff line Loading @@ -382,6 +382,26 @@ class TestSubmitApp: assert payload["service_specification"]["id"] == str(stored.app_registration_id) assert payload["service_specification"]["id"] != str(APP_ID) async def test_catalog_payload_omits_unset_fields_instead_of_sending_null( self, service: EdgeApplicationManagementService, srm_client: AsyncMock, ) -> None: # SRM rejects unknown/null fields outright; unset optional fields must be # absent from the JSON body, not present with a `null` value. await service.submit_app( manifest=_make_manifest(), app_id=APP_ID, tenant_id="tenant-1", app_provider_id="provider-1", ) call_kwargs = srm_client.create_catalog_service_specification.call_args.kwargs payload = call_kwargs["payload"] resource_requirements = payload["service_deployment_units"][0]["resource_requirements"] assert "accelerator" not in resource_requirements["compute"] assert "standalone" in resource_requirements assert "standalone" not in resource_requirements["compute"] async def test_raises_when_srm_confirms_a_different_id( self, service: EdgeApplicationManagementService, srm_client: AsyncMock ) -> None: Loading Loading
src/open_exposure_gateway/application/mappers/edge_application_mapper.py +2 −2 Original line number Diff line number Diff line Loading @@ -227,7 +227,7 @@ def build_app_manifest(catalog: SRMCatalogPayload) -> AppManifest: required_resources = KubernetesResources( infraKind="kubernetes", applicationResources=CamaraApplicationResources.model_validate(app_res), isStandalone=compute.standalone if compute else False, isStandalone=unit.resource_requirements.standalone, ) elif unit.runtime_kind == "container": millicores = compute.cpu_millicores if compute and compute.cpu_millicores is not None else 0 Loading Loading @@ -502,7 +502,6 @@ def build_catalog_payload(translation: AppRegistrationTranslation) -> SRMCatalog compute = SRMComputeResources( cpu_millicores=cpu_millicores, memory_mb=memory_mb, standalone=standalone, accelerator=accelerator, storage=storage, ) Loading @@ -523,6 +522,7 @@ def build_catalog_payload(translation: AppRegistrationTranslation) -> SRMCatalog compute=compute, topology=topology, interfaces=interfaces or None, standalone=standalone, ) repo = translation.app_repo Loading
src/open_exposure_gateway/application/services/edge_application_management_service.py +2 −1 Original line number Diff line number Diff line Loading @@ -218,7 +218,8 @@ class EdgeApplicationManagementService: ) catalog_payload = build_catalog_payload(translation) created = await self.srm_client.create_catalog_service_specification( payload=catalog_payload.model_dump(mode="json"), x_correlator=x_correlator payload=catalog_payload.model_dump(mode="json", exclude_none=True), x_correlator=x_correlator, ) if created.id != translation.app_registration_id: raise DownstreamServiceException( Loading
src/open_exposure_gateway/domain/edge_application_management.py +1 −1 Original line number Diff line number Diff line Loading @@ -128,7 +128,6 @@ class SRMStorageVolume(BaseModel): class SRMComputeResources(BaseModel): cpu_millicores: int | None = None memory_mb: int | None = None standalone: bool = False accelerator: SRMAccelerator | None = None storage: list[SRMStorageVolume] | None = None Loading @@ -153,6 +152,7 @@ class SRMComputeIntent(BaseModel): compute: SRMComputeResources | None = None topology: SRMTopologyConstraints | None = None interfaces: list[SRMNetworkInterface] | None = None standalone: bool = False class SRMServiceSpecDescriptor(BaseModel): Loading
tests/unit/test_eam_mapper.py +23 −0 Original line number Diff line number Diff line Loading @@ -64,6 +64,7 @@ def _make_srm_catalog( cpu_millicores: int = 2000, memory_mb: int = 4096, interfaces: list[SRMNetworkInterface] | None = None, standalone: bool = False, ) -> SRMCatalogPayload: return SRMCatalogPayload( service_specification=SRMServiceSpecEntry( Loading Loading @@ -94,6 +95,7 @@ def _make_srm_catalog( min_node_memory_mb=1024, ), interfaces=interfaces, standalone=standalone, ), ) ], Loading Loading @@ -274,6 +276,14 @@ class TestBuildAppManifest: assert result.componentSpec[0].componentName == "frontend" assert len(result.componentSpec[0].networkInterfaces) == 2 def test_standalone_read_from_top_level_field(self) -> None: # standalone lives on resource_requirements itself, not nested under # resource_requirements.compute (srm/canonical-parameters-schema.md). catalog = _make_srm_catalog(standalone=True) result = build_app_manifest(catalog) assert isinstance(result.requiredResources, KubernetesResources) assert result.requiredResources.isStandalone is True def test_helm_topology_reconstructed(self) -> None: catalog = _make_srm_catalog() catalog.service_deployment_units[0].resource_requirements.topology = SRMTopologyConstraints( Loading Loading @@ -576,6 +586,19 @@ class TestBuildCatalogPayload: assert compute.cpu_millicores == 2000 assert compute.memory_mb == 4096 def test_standalone_is_top_level_not_nested_in_compute(self) -> None: # SRM's srm.compute/v1 shape puts `standalone` as a sibling of `compute`, # not inside it (srm/canonical-parameters-schema.md); SRM rejects unknown # fields, so a nested `compute.standalone` fails app registration outright. manifest = _make_helm_manifest(standalone=True) translation = build_app_registration_translation( manifest, APP_ID, APP_REGISTRATION_ID, "t", "p" ) catalog = build_catalog_payload(translation) resource_requirements = catalog.service_deployment_units[0].resource_requirements assert resource_requirements.standalone is True assert not hasattr(resource_requirements.compute, "standalone") def test_spec_ref_is_app_id(self) -> None: manifest = _make_helm_manifest() translation = build_app_registration_translation( Loading
tests/unit/test_eam_service.py +20 −0 Original line number Diff line number Diff line Loading @@ -382,6 +382,26 @@ class TestSubmitApp: assert payload["service_specification"]["id"] == str(stored.app_registration_id) assert payload["service_specification"]["id"] != str(APP_ID) async def test_catalog_payload_omits_unset_fields_instead_of_sending_null( self, service: EdgeApplicationManagementService, srm_client: AsyncMock, ) -> None: # SRM rejects unknown/null fields outright; unset optional fields must be # absent from the JSON body, not present with a `null` value. await service.submit_app( manifest=_make_manifest(), app_id=APP_ID, tenant_id="tenant-1", app_provider_id="provider-1", ) call_kwargs = srm_client.create_catalog_service_specification.call_args.kwargs payload = call_kwargs["payload"] resource_requirements = payload["service_deployment_units"][0]["resource_requirements"] assert "accelerator" not in resource_requirements["compute"] assert "standalone" in resource_requirements assert "standalone" not in resource_requirements["compute"] async def test_raises_when_srm_confirms_a_different_id( self, service: EdgeApplicationManagementService, srm_client: AsyncMock ) -> None: Loading