From 5c347de020d205066d2f7aa8eacf715940bc0138 Mon Sep 17 00:00:00 2001 From: Roshin Rajan Panackal Date: Mon, 8 Jun 2026 17:42:43 +0200 Subject: [PATCH 1/5] Add unit test for resolving to running deployment --- .../sap/ai/sdk/core/DeploymentResolver.java | 3 ++ .../ai/sdk/core/DeploymentResolverTest.java | 20 +++++++ .../__files/hasStoppedDeployment.json | 54 +++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 core/src/test/resources/__files/hasStoppedDeployment.json diff --git a/core/src/main/java/com/sap/ai/sdk/core/DeploymentResolver.java b/core/src/main/java/com/sap/ai/sdk/core/DeploymentResolver.java index 78c233b61..d6bd27934 100644 --- a/core/src/main/java/com/sap/ai/sdk/core/DeploymentResolver.java +++ b/core/src/main/java/com/sap/ai/sdk/core/DeploymentResolver.java @@ -1,5 +1,7 @@ package com.sap.ai.sdk.core; +import static com.sap.ai.sdk.core.model.AiDeployment.TargetStatusEnum.RUNNING; + import com.sap.ai.sdk.core.client.DeploymentApi; import com.sap.ai.sdk.core.model.AiDeployment; import java.util.HashSet; @@ -118,6 +120,7 @@ private Optional getCachedDeployment( @Nonnull final String resourceGroup, @Nonnull final Predicate predicate) { return cache.getOrDefault(resourceGroup, new HashSet<>()).stream() .filter(predicate) + .filter(deployment -> deployment.getTargetStatus().equals(RUNNING)) .findFirst() .map(AiDeployment::getId); } diff --git a/core/src/test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java b/core/src/test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java index e765c6ca3..f90093b2a 100644 --- a/core/src/test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java +++ b/core/src/test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java @@ -10,6 +10,7 @@ import static com.sap.ai.sdk.core.AiCoreService.DEFAULT_RESOURCE_GROUP; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.tuple; import com.sap.ai.sdk.core.client.WireMockTestServer; import com.sap.ai.sdk.core.model.AiDeployment; @@ -169,6 +170,25 @@ void testIsDeploymentOfModel() { assertThat(DeploymentResolver.isDeploymentOfModel(gpt4VersionLatest, deployment)).isTrue(); } + @Test + void testDeploymentResolvesToRunningTarget() { + wireMockServer.stubFor( + get(anyUrl()) + .willReturn( + aResponse() + .withBodyFile("hasStoppedDeployment.json") + .withHeader("content-type", "application/json"))); + + var deployment = resolver.getDeploymentIdByScenario(DEFAULT_RESOURCE_GROUP, "orchestration"); + + assertThat(deployment).isEqualTo("d4b1396b84c1944d"); + assertThat(cache.get("default")) + .extracting(AiDeployment::getId, AiDeployment::getTargetStatus) + .contains( + tuple("d2a491b5010620b0", AiDeployment.TargetStatusEnum.STOPPED), + tuple("d4b1396b84c1944d", AiDeployment.TargetStatusEnum.RUNNING)); + } + private record TestModel(String name, String version) implements AiModel {} private static void stubResponse(String resourceGroup, String fileName) { diff --git a/core/src/test/resources/__files/hasStoppedDeployment.json b/core/src/test/resources/__files/hasStoppedDeployment.json new file mode 100644 index 000000000..1f66b47f4 --- /dev/null +++ b/core/src/test/resources/__files/hasStoppedDeployment.json @@ -0,0 +1,54 @@ +{ + "count": 8, + "resources": [ + { + "id": "d2a491b5010620b0", + "createdAt": "2026-06-08T14:56:25Z", + "modifiedAt": "2026-06-08T15:10:18Z", + "status": "STOPPED", + "details": { + "scaling": { + "backendDetails": {}, + "backend_details": {} + }, + "resources": { + "backendDetails": {}, + "backend_details": {} + } + }, + "scenarioId": "orchestration", + "configurationId": "0f0fb4c1-cf2c-441f-98d1-1f983d1b1756", + "targetStatus": "STOPPED", + "submissionTime": "2026-06-08T15:03:35Z", + "startTime": "2026-06-08T15:05:27Z", + "completionTime": "2026-06-08T15:27:23Z", + "configurationName": "orchestration-config", + "deploymentUrl": "https://api.ai.intprod-eu12.eu-central-1.aws.ml.hana.ondemand.com/v2/inference/deployments/d2a491b5010620b0" + }, + { + "id": "d4b1396b84c1944d", + "createdAt": "2026-04-16T10:03:39Z", + "modifiedAt": "2026-04-16T10:03:39Z", + "status": "RUNNING", + "details": { + "scaling": { + "backendDetails": {}, + "backend_details": {} + }, + "resources": { + "backendDetails": {}, + "backend_details": {} + } + }, + "scenarioId": "orchestration", + "configurationId": "e88f7dba-fd9c-4068-ab18-3306f2aa46bd", + "latestRunningConfigurationId": "e88f7dba-fd9c-4068-ab18-3306f2aa46bd", + "lastOperation": "CREATE", + "targetStatus": "RUNNING", + "submissionTime": "2026-04-17T08:32:01Z", + "startTime": "2026-04-17T08:32:59Z", + "configurationName": "orchestration-config", + "deploymentUrl": "https://api.ai.intprod-eu12.eu-central-1.aws.ml.hana.ondemand.com/v2/inference/deployments/d4b1396b84c1944d" + } + ] +} \ No newline at end of file From 522855c1118b5577044ebd11d69e4ba118bf30f1 Mon Sep 17 00:00:00 2001 From: Roshin Rajan Panackal Date: Tue, 9 Jun 2026 10:35:20 +0200 Subject: [PATCH 2/5] minor fix --- .../test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java b/core/src/test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java index f90093b2a..c9351a05f 100644 --- a/core/src/test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java +++ b/core/src/test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java @@ -179,10 +179,10 @@ void testDeploymentResolvesToRunningTarget() { .withBodyFile("hasStoppedDeployment.json") .withHeader("content-type", "application/json"))); - var deployment = resolver.getDeploymentIdByScenario(DEFAULT_RESOURCE_GROUP, "orchestration"); + final var deployment = resolver.getDeploymentIdByScenario(DEFAULT_RESOURCE_GROUP, "orchestration"); assertThat(deployment).isEqualTo("d4b1396b84c1944d"); - assertThat(cache.get("default")) + assertThat(cache.get(DEFAULT_RESOURCE_GROUP)) .extracting(AiDeployment::getId, AiDeployment::getTargetStatus) .contains( tuple("d2a491b5010620b0", AiDeployment.TargetStatusEnum.STOPPED), From 2c21ce24670866390d3d3f36d5a4d5950715a256 Mon Sep 17 00:00:00 2001 From: SAP Cloud SDK Bot Date: Tue, 9 Jun 2026 08:41:42 +0000 Subject: [PATCH 3/5] Formatting --- .../test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java b/core/src/test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java index c9351a05f..0bb14c747 100644 --- a/core/src/test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java +++ b/core/src/test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java @@ -179,7 +179,8 @@ void testDeploymentResolvesToRunningTarget() { .withBodyFile("hasStoppedDeployment.json") .withHeader("content-type", "application/json"))); - final var deployment = resolver.getDeploymentIdByScenario(DEFAULT_RESOURCE_GROUP, "orchestration"); + final var deployment = + resolver.getDeploymentIdByScenario(DEFAULT_RESOURCE_GROUP, "orchestration"); assertThat(deployment).isEqualTo("d4b1396b84c1944d"); assertThat(cache.get(DEFAULT_RESOURCE_GROUP)) From 615627a0afc5ccdd496cd88cffdcf63c2b3b56bc Mon Sep 17 00:00:00 2001 From: Roshin Rajan Panackal Date: Tue, 9 Jun 2026 11:57:03 +0200 Subject: [PATCH 4/5] improve comparison --- core/src/main/java/com/sap/ai/sdk/core/DeploymentResolver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/sap/ai/sdk/core/DeploymentResolver.java b/core/src/main/java/com/sap/ai/sdk/core/DeploymentResolver.java index d6bd27934..075486525 100644 --- a/core/src/main/java/com/sap/ai/sdk/core/DeploymentResolver.java +++ b/core/src/main/java/com/sap/ai/sdk/core/DeploymentResolver.java @@ -120,7 +120,7 @@ private Optional getCachedDeployment( @Nonnull final String resourceGroup, @Nonnull final Predicate predicate) { return cache.getOrDefault(resourceGroup, new HashSet<>()).stream() .filter(predicate) - .filter(deployment -> deployment.getTargetStatus().equals(RUNNING)) + .filter(deployment -> RUNNING.equals(deployment.getTargetStatus())) .findFirst() .map(AiDeployment::getId); } From 31279c7b3c3fe7c9a8a817ee10fab5be79135da1 Mon Sep 17 00:00:00 2001 From: Roshin Rajan Panackal Date: Tue, 9 Jun 2026 15:31:26 +0200 Subject: [PATCH 5/5] Filter in running deployments at loadtime --- .../sap/ai/sdk/core/DeploymentResolver.java | 21 ++++++++++++------- .../sap/ai/sdk/core/AiCoreServiceTest.java | 3 ++- .../ai/sdk/core/DeploymentResolverTest.java | 8 +++---- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/com/sap/ai/sdk/core/DeploymentResolver.java b/core/src/main/java/com/sap/ai/sdk/core/DeploymentResolver.java index 075486525..41c1c9640 100644 --- a/core/src/main/java/com/sap/ai/sdk/core/DeploymentResolver.java +++ b/core/src/main/java/com/sap/ai/sdk/core/DeploymentResolver.java @@ -1,9 +1,8 @@ package com.sap.ai.sdk.core; -import static com.sap.ai.sdk.core.model.AiDeployment.TargetStatusEnum.RUNNING; - import com.sap.ai.sdk.core.client.DeploymentApi; import com.sap.ai.sdk.core.model.AiDeployment; +import com.sap.ai.sdk.core.model.AiDeploymentStatus; import java.util.HashSet; import java.util.Map; import java.util.Objects; @@ -11,6 +10,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; +import java.util.stream.Collectors; import javax.annotation.Nonnull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -29,7 +29,7 @@ class DeploymentResolver { @Nonnull private final AiCoreService service; - /** Cache for deployment ids. The key is the model name and the value is the deployment id. */ + /** Cache for deployments. The key is the resource group and the value a set of deployments. */ @Nonnull private final Map> cache; DeploymentResolver(@Nonnull final AiCoreService service) { @@ -37,7 +37,7 @@ class DeploymentResolver { } /** - * Remove all entries from the cache then load all deployments into the cache. + * Remove cached deployments for the resource group and reload running deployments into the cache. * *

Call this whenever a deployment is deleted. * @@ -48,8 +48,16 @@ void reloadDeployments(@Nonnull final String resourceGroup) { try { val apiClient = new DeploymentApi(service); val deployments = new HashSet<>(apiClient.query(resourceGroup).getResources()); - log.info("Found {} deployments in resource group '{}'.", deployments.size(), resourceGroup); - cache.put(resourceGroup, deployments); + val runningDeployments = + deployments.stream() + .filter(deployment -> AiDeploymentStatus.RUNNING.equals(deployment.getStatus())) + .collect(Collectors.toSet()); + log.info( + "Found {} of {} deployments running in resource group '{}'.", + runningDeployments.size(), + deployments.size(), + resourceGroup); + cache.put(resourceGroup, runningDeployments); } catch (final RuntimeException e) { throw new DeploymentResolutionException( "Failed to load deployments for resource group " + resourceGroup, e); @@ -120,7 +128,6 @@ private Optional getCachedDeployment( @Nonnull final String resourceGroup, @Nonnull final Predicate predicate) { return cache.getOrDefault(resourceGroup, new HashSet<>()).stream() .filter(predicate) - .filter(deployment -> RUNNING.equals(deployment.getTargetStatus())) .findFirst() .map(AiDeployment::getId); } diff --git a/core/src/test/java/com/sap/ai/sdk/core/AiCoreServiceTest.java b/core/src/test/java/com/sap/ai/sdk/core/AiCoreServiceTest.java index 7def53292..56b7457c1 100644 --- a/core/src/test/java/com/sap/ai/sdk/core/AiCoreServiceTest.java +++ b/core/src/test/java/com/sap/ai/sdk/core/AiCoreServiceTest.java @@ -81,7 +81,8 @@ void testGetInferenceDestination() { assertThat(destination.getHeaders()).containsExactly(new Header("AI-Resource-Group", "foo")); // scenario-based destination - val d = "{\"count\":1,\"resources\":[{\"id\":\"0123456789abcdef\",\"scenarioId\":\"foobar\"}]}"; + val d = + "{\"count\":1,\"resources\":[{\"id\":\"0123456789abcdef\",\"scenarioId\":\"foobar\", \"status\":\"RUNNING\"}]}"; val server = new WireMockServer(wireMockConfig().dynamicPort()); server.start(); server.stubFor(get(urlEqualTo("/v2/lm/deployments")).willReturn(okJson(d))); diff --git a/core/src/test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java b/core/src/test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java index 0bb14c747..abc9596f4 100644 --- a/core/src/test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java +++ b/core/src/test/java/com/sap/ai/sdk/core/DeploymentResolverTest.java @@ -171,7 +171,7 @@ void testIsDeploymentOfModel() { } @Test - void testDeploymentResolvesToRunningTarget() { + void testCacheContainsRunningDeployments() { wireMockServer.stubFor( get(anyUrl()) .willReturn( @@ -184,10 +184,8 @@ void testDeploymentResolvesToRunningTarget() { assertThat(deployment).isEqualTo("d4b1396b84c1944d"); assertThat(cache.get(DEFAULT_RESOURCE_GROUP)) - .extracting(AiDeployment::getId, AiDeployment::getTargetStatus) - .contains( - tuple("d2a491b5010620b0", AiDeployment.TargetStatusEnum.STOPPED), - tuple("d4b1396b84c1944d", AiDeployment.TargetStatusEnum.RUNNING)); + .extracting(AiDeployment::getId, AiDeployment::getStatus) + .containsExactly(tuple("d4b1396b84c1944d", AiDeploymentStatus.RUNNING)); } private record TestModel(String name, String version) implements AiModel {}