From 9eaedec2de1b2bfb820c46c87d0eff4af947eb61 Mon Sep 17 00:00:00 2001 From: Oleksii-Klimov <133792808+Oleksii-Klimov@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:04:02 +0000 Subject: [PATCH] feat: add extra parameters to deployment endpoint (#15) --- .../controller/DeploymentController.java | 124 ++++----- .../dto/CreateDeploymentRequestDto.java | 19 +- .../aidial/dto/CreateImageRequestDto.java | 12 +- .../epam/aidial/service/ConfigService.java | 33 ++- .../epam/aidial/service/DeployService.java | 11 +- .../com/epam/aidial/util/mapping/Mappers.java | 5 + .../controller/DeploymentControllerTest.java | 242 +++++++++--------- .../aidial/service/ConfigServiceTest.java | 26 +- .../aidial/service/DeployServiceTest.java | 10 +- .../expected-configs/app-service-extra.yaml | 32 +++ 10 files changed, 314 insertions(+), 200 deletions(-) create mode 100644 src/test/resources/expected-configs/app-service-extra.yaml diff --git a/src/main/java/com/epam/aidial/controller/DeploymentController.java b/src/main/java/com/epam/aidial/controller/DeploymentController.java index e897ac9..e1d55c3 100644 --- a/src/main/java/com/epam/aidial/controller/DeploymentController.java +++ b/src/main/java/com/epam/aidial/controller/DeploymentController.java @@ -1,61 +1,63 @@ -package com.epam.aidial.controller; - -import com.epam.aidial.dto.CreateDeploymentRequestDto; -import com.epam.aidial.dto.CreateDeploymentResponseDto; -import com.epam.aidial.dto.DeleteImageResponseDto; -import com.epam.aidial.dto.GetApplicationLogsResponseDto; -import com.epam.aidial.service.DeployService; -import com.epam.aidial.service.HeartbeatService; -import com.epam.aidial.util.SseUtils; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.MediaType; -import org.springframework.http.codec.ServerSentEvent; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import java.util.Map; -import java.util.Objects; - -@Slf4j -@RestController -@RequestMapping("/v1/deployment") -@RequiredArgsConstructor -public class DeploymentController { - private final DeployService deployService; - private final HeartbeatService heartbeatService; - - @PostMapping(value = "{name}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) - public Flux> create( - @PathVariable("name") String name, - @RequestBody CreateDeploymentRequestDto request) { - Mono result = deployService.deploy(name, Objects.requireNonNullElse(request.env(), Map.of())) - .doOnError(e -> log.error("Failed to deploy service {}", name, e)) - .map(CreateDeploymentResponseDto::new); - - return heartbeatService.setupHeartbeats(SseUtils.mapToSseEvent(result)); - } - - @DeleteMapping(value = "{name}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) - public Flux> delete(@PathVariable("name") String name) { - Mono result = deployService.undeploy(name) - .doOnError(e -> log.error("Failed to delete service {}", name, e)) - .map(DeleteImageResponseDto::new); - - return heartbeatService.setupHeartbeats(SseUtils.mapToSseEvent(result)); - } - - @GetMapping(value = "{name}/logs") - public Mono logs(@PathVariable("name") String name) { - return deployService.logs(name) - .map(GetApplicationLogsResponseDto::new) - .doOnError(e -> log.error("Failed to retrieve logs for {}", name, e)); - } -} +package com.epam.aidial.controller; + +import com.epam.aidial.dto.CreateDeploymentRequestDto; +import com.epam.aidial.dto.CreateDeploymentResponseDto; +import com.epam.aidial.dto.DeleteImageResponseDto; +import com.epam.aidial.dto.GetApplicationLogsResponseDto; +import com.epam.aidial.service.DeployService; +import com.epam.aidial.service.HeartbeatService; +import com.epam.aidial.util.SseUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.http.codec.ServerSentEvent; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.Map; +import java.util.Objects; + +@Slf4j +@RestController +@RequestMapping("/v1/deployment") +@RequiredArgsConstructor +public class DeploymentController { + private final DeployService deployService; + private final HeartbeatService heartbeatService; + + @PostMapping(value = "{name}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public Flux> create( + @PathVariable("name") String name, + @RequestBody CreateDeploymentRequestDto request) { + Map env = Objects.requireNonNullElse(request.env(), Map.of()); + Mono result = deployService.deploy( + name, env, request.image(), request.initialScale(), request.minScale(), request.maxScale()) + .doOnError(e -> log.error("Failed to deploy service {}", name, e)) + .map(CreateDeploymentResponseDto::new); + + return heartbeatService.setupHeartbeats(SseUtils.mapToSseEvent(result)); + } + + @DeleteMapping(value = "{name}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public Flux> delete(@PathVariable("name") String name) { + Mono result = deployService.undeploy(name) + .doOnError(e -> log.error("Failed to delete service {}", name, e)) + .map(DeleteImageResponseDto::new); + + return heartbeatService.setupHeartbeats(SseUtils.mapToSseEvent(result)); + } + + @GetMapping(value = "{name}/logs") + public Mono logs(@PathVariable("name") String name) { + return deployService.logs(name) + .map(GetApplicationLogsResponseDto::new) + .doOnError(e -> log.error("Failed to retrieve logs for {}", name, e)); + } +} diff --git a/src/main/java/com/epam/aidial/dto/CreateDeploymentRequestDto.java b/src/main/java/com/epam/aidial/dto/CreateDeploymentRequestDto.java index fbb9f07..a912111 100644 --- a/src/main/java/com/epam/aidial/dto/CreateDeploymentRequestDto.java +++ b/src/main/java/com/epam/aidial/dto/CreateDeploymentRequestDto.java @@ -1,6 +1,13 @@ -package com.epam.aidial.dto; - -import java.util.Map; - -public record CreateDeploymentRequestDto(Map env) { -} +package com.epam.aidial.dto; + +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +public record CreateDeploymentRequestDto( + @Nullable Map env, + @Nullable String image, + @Nullable Integer initialScale, + @Nullable Integer minScale, + @Nullable Integer maxScale) { +} diff --git a/src/main/java/com/epam/aidial/dto/CreateImageRequestDto.java b/src/main/java/com/epam/aidial/dto/CreateImageRequestDto.java index 018930e..5b5684b 100644 --- a/src/main/java/com/epam/aidial/dto/CreateImageRequestDto.java +++ b/src/main/java/com/epam/aidial/dto/CreateImageRequestDto.java @@ -1,4 +1,8 @@ -package com.epam.aidial.dto; - -public record CreateImageRequestDto(String sources, String runtime) { -} +package com.epam.aidial.dto; + +import org.jetbrains.annotations.Nullable; + +public record CreateImageRequestDto( + String sources, + @Nullable String runtime) { +} diff --git a/src/main/java/com/epam/aidial/service/ConfigService.java b/src/main/java/com/epam/aidial/service/ConfigService.java index 47f0aed..eec5e5c 100644 --- a/src/main/java/com/epam/aidial/service/ConfigService.java +++ b/src/main/java/com/epam/aidial/service/ConfigService.java @@ -3,6 +3,7 @@ import com.epam.aidial.config.AppConfiguration; import com.epam.aidial.config.DockerAuthScheme; +import com.epam.aidial.kubernetes.knative.V1RevisionTemplateSpec; import com.epam.aidial.kubernetes.knative.V1Service; import com.epam.aidial.util.mapping.ListMapper; import com.epam.aidial.util.mapping.MappingChain; @@ -10,6 +11,7 @@ import io.kubernetes.client.openapi.models.V1EnvFromSource; import io.kubernetes.client.openapi.models.V1EnvVar; import io.kubernetes.client.openapi.models.V1Job; +import io.kubernetes.client.openapi.models.V1ObjectMeta; import io.kubernetes.client.openapi.models.V1PodSpec; import io.kubernetes.client.openapi.models.V1Secret; import io.kubernetes.client.openapi.models.V1SecretEnvSource; @@ -18,12 +20,14 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import static com.epam.aidial.util.NamingUtils.appName; import static com.epam.aidial.util.NamingUtils.buildJobName; @@ -45,6 +49,7 @@ import static com.epam.aidial.util.mapping.Mappers.SERVICE_METADATA_FIELD; import static com.epam.aidial.util.mapping.Mappers.SERVICE_SPEC_FIELD; import static com.epam.aidial.util.mapping.Mappers.SERVICE_TEMPLATE_FIELD; +import static com.epam.aidial.util.mapping.Mappers.SERVICE_TEMPLATE_METADATA_FIELD; import static com.epam.aidial.util.mapping.Mappers.SERVICE_TEMPLATE_SPEC_FIELD; import static com.epam.aidial.util.mapping.Mappers.TEMPLATE_CONTAINERS_FIELD; import static com.epam.aidial.util.mapping.Mappers.VOLUME_MOUNT_PATH; @@ -144,19 +149,39 @@ public V1Job buildJobConfig(String name, String sources, String runtime) { return config.data(); } - public V1Service appServiceConfig(String name, Map env) { + public V1Service appServiceConfig( + String name, + Map env, + @Nullable String image, + @Nullable Integer initScale, + @Nullable Integer minScale, + @Nullable Integer maxScale) { MappingChain config = new MappingChain<>(this.appconfig.cloneServiceConfig()); config.get(SERVICE_METADATA_FIELD) .data() .setName(appName(name)); - MappingChain container = config.get(SERVICE_SPEC_FIELD) - .get(SERVICE_TEMPLATE_FIELD) + MappingChain template = config.get(SERVICE_SPEC_FIELD) + .get(SERVICE_TEMPLATE_FIELD); + + V1ObjectMeta templateMetadata = template.get(SERVICE_TEMPLATE_METADATA_FIELD) + .data(); + if (initScale != null) { + templateMetadata.putAnnotationsItem("autoscaling.knative.dev/initial-scale", String.valueOf(initScale)); + } + if (minScale != null) { + templateMetadata.putAnnotationsItem("autoscaling.knative.dev/min-scale", String.valueOf(minScale)); + } + if (maxScale != null) { + templateMetadata.putAnnotationsItem("autoscaling.knative.dev/max-scale", String.valueOf(maxScale)); + } + + MappingChain container = template .get(SERVICE_TEMPLATE_SPEC_FIELD) .getList(TEMPLATE_CONTAINERS_FIELD, CONTAINER_NAME) .get(serviceContainer); container.data() - .setImage(registryService.fullImageName(name)); + .setImage(Objects.requireNonNullElse(image, registryService.fullImageName(name))); ListMapper containerEnv = container.getList(CONTAINER_ENV_FIELD, ENV_VAR_NAME); env.forEach((key, value) -> containerEnv.get(key) diff --git a/src/main/java/com/epam/aidial/service/DeployService.java b/src/main/java/com/epam/aidial/service/DeployService.java index 9aaebca..5e38ceb 100644 --- a/src/main/java/com/epam/aidial/service/DeployService.java +++ b/src/main/java/com/epam/aidial/service/DeployService.java @@ -6,6 +6,7 @@ import io.kubernetes.client.openapi.models.V1PodList; import io.kubernetes.client.openapi.models.V1PodStatus; import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; @@ -30,9 +31,15 @@ public class DeployService { @Value("${app.service-setup-timeout-sec}") private final int serviceSetupTimeoutSec; - public Mono deploy(String name, Map env) { + public Mono deploy( + String name, + Map env, + @Nullable String image, + @Nullable Integer initialScale, + @Nullable Integer minScale, + @Nullable Integer maxScale) { KubernetesClient kubernetesClient = kubernetesService.deployClient(); - return Mono.fromCallable(() -> templateService.appServiceConfig(name, env)) + return Mono.fromCallable(() -> templateService.appServiceConfig(name, env, image, initialScale, minScale, maxScale)) .flatMap(service -> kubernetesClient.createKnativeService(namespace, service, serviceSetupTimeoutSec)); } diff --git a/src/main/java/com/epam/aidial/util/mapping/Mappers.java b/src/main/java/com/epam/aidial/util/mapping/Mappers.java index 5f96cd4..53612cf 100644 --- a/src/main/java/com/epam/aidial/util/mapping/Mappers.java +++ b/src/main/java/com/epam/aidial/util/mapping/Mappers.java @@ -97,6 +97,11 @@ public class Mappers { V1ServiceSpec::getTemplate, V1ServiceSpec::setTemplate); + public static final FieldMapper SERVICE_TEMPLATE_METADATA_FIELD = new FieldMapper<>( + V1ObjectMeta::new, + V1RevisionTemplateSpec::getMetadata, + V1RevisionTemplateSpec::setMetadata); + public static final FieldMapper SERVICE_TEMPLATE_SPEC_FIELD = new FieldMapper<>( V1RevisionSpec::new, V1RevisionTemplateSpec::getSpec, diff --git a/src/test/java/com/epam/aidial/controller/DeploymentControllerTest.java b/src/test/java/com/epam/aidial/controller/DeploymentControllerTest.java index ca0df0c..cae9a3f 100644 --- a/src/test/java/com/epam/aidial/controller/DeploymentControllerTest.java +++ b/src/test/java/com/epam/aidial/controller/DeploymentControllerTest.java @@ -1,120 +1,124 @@ -package com.epam.aidial.controller; - -import com.epam.aidial.dto.CreateDeploymentRequestDto; -import com.epam.aidial.dto.CreateDeploymentResponseDto; -import com.epam.aidial.dto.DeleteDeploymentResponseDto; -import com.epam.aidial.service.DeployService; -import com.epam.aidial.service.HeartbeatService; -import com.epam.aidial.util.SseUtils; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; -import org.springframework.http.codec.ServerSentEvent; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.function.BodyInserters; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@WebFluxTest(DeploymentController.class) -class DeploymentControllerTest { - private static final String TEST_NAME = "test-name"; - private static final String TEST_URL = "test-url"; - - @Autowired - private WebTestClient webTestClient; - - @MockitoBean - private DeployService deployService; - - @MockitoBean - private HeartbeatService heartbeatService; - - @Captor - private ArgumentCaptor deployCaptor; - - @Captor - private ArgumentCaptor undeployCaptor; - - @Captor - private ArgumentCaptor>> setupHeartbeatsCaptor; - - @Test - @SuppressWarnings("unchecked") - void testDeploymentCreate() { - // Arrange - when(deployService.deploy( - (String) deployCaptor.capture(), - (Map) deployCaptor.capture())) - .thenReturn(Mono.just(TEST_URL)); - CreateDeploymentResponseDto response = new CreateDeploymentResponseDto(TEST_URL); - ServerSentEvent result = SseUtils.result(response); - when(heartbeatService.setupHeartbeats( - setupHeartbeatsCaptor.capture())) - .thenReturn(Mono.just(result).flux()); - Map env = Map.of("test-env-name", "test-env-value"); - - // Act - Flux actual = webTestClient.post() - .uri("/v1/deployment/" + TEST_NAME) - .body(BodyInserters.fromValue(new CreateDeploymentRequestDto(env))) - .exchange() - .expectStatus() - .isOk() - .returnResult(CreateDeploymentResponseDto.class) - .getResponseBody(); - - // Assert - StepVerifier.create(actual) - .expectNext(response) - .verifyComplete(); - - StepVerifier.create(setupHeartbeatsCaptor.getValue()) - .expectNext(result); - - assertThat(deployCaptor.getAllValues()) - .isEqualTo(List.of(TEST_NAME, env)); - } - - @Test - void testDeploymentDelete() { - // Arrange - when(deployService.undeploy( - undeployCaptor.capture())) - .thenReturn(Mono.just(true)); - DeleteDeploymentResponseDto response = new DeleteDeploymentResponseDto(true); - ServerSentEvent result = SseUtils.result(response); - when(heartbeatService.setupHeartbeats( - setupHeartbeatsCaptor.capture())) - .thenReturn(Mono.just(result).flux()); - - // Act - Flux actual = webTestClient.delete() - .uri("/v1/deployment/" + TEST_NAME) - .exchange() - .expectStatus() - .isOk() - .returnResult(DeleteDeploymentResponseDto.class) - .getResponseBody(); - - // Assert - StepVerifier.create(actual) - .expectNext(response) - .verifyComplete(); - - StepVerifier.create(setupHeartbeatsCaptor.getValue()) - .expectNext(result); - - verify(deployService).undeploy(TEST_NAME); - } +package com.epam.aidial.controller; + +import com.epam.aidial.dto.CreateDeploymentRequestDto; +import com.epam.aidial.dto.CreateDeploymentResponseDto; +import com.epam.aidial.dto.DeleteDeploymentResponseDto; +import com.epam.aidial.service.DeployService; +import com.epam.aidial.service.HeartbeatService; +import com.epam.aidial.util.SseUtils; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.http.codec.ServerSentEvent; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.function.BodyInserters; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@WebFluxTest(DeploymentController.class) +class DeploymentControllerTest { + private static final String TEST_NAME = "test-name"; + private static final String TEST_URL = "test-url"; + + @Autowired + private WebTestClient webTestClient; + + @MockitoBean + private DeployService deployService; + + @MockitoBean + private HeartbeatService heartbeatService; + + @Captor + private ArgumentCaptor deployCaptor; + + @Captor + private ArgumentCaptor undeployCaptor; + + @Captor + private ArgumentCaptor>> setupHeartbeatsCaptor; + + @Test + @SuppressWarnings("unchecked") + void testDeploymentCreate() { + // Arrange + when(deployService.deploy( + (String) deployCaptor.capture(), + (Map) deployCaptor.capture(), + (String) deployCaptor.capture(), + (Integer) deployCaptor.capture(), + (Integer) deployCaptor.capture(), + (Integer) deployCaptor.capture())) + .thenReturn(Mono.just(TEST_URL)); + CreateDeploymentResponseDto response = new CreateDeploymentResponseDto(TEST_URL); + ServerSentEvent result = SseUtils.result(response); + when(heartbeatService.setupHeartbeats( + setupHeartbeatsCaptor.capture())) + .thenReturn(Mono.just(result).flux()); + Map env = Map.of("test-env-name", "test-env-value"); + + // Act + Flux actual = webTestClient.post() + .uri("/v1/deployment/" + TEST_NAME) + .body(BodyInserters.fromValue(new CreateDeploymentRequestDto(env, "image-name", 1, 2, 3))) + .exchange() + .expectStatus() + .isOk() + .returnResult(CreateDeploymentResponseDto.class) + .getResponseBody(); + + // Assert + StepVerifier.create(actual) + .expectNext(response) + .verifyComplete(); + + StepVerifier.create(setupHeartbeatsCaptor.getValue()) + .expectNext(result); + + assertThat(deployCaptor.getAllValues()) + .isEqualTo(List.of(TEST_NAME, env, "image-name", 1, 2, 3)); + } + + @Test + void testDeploymentDelete() { + // Arrange + when(deployService.undeploy( + undeployCaptor.capture())) + .thenReturn(Mono.just(true)); + DeleteDeploymentResponseDto response = new DeleteDeploymentResponseDto(true); + ServerSentEvent result = SseUtils.result(response); + when(heartbeatService.setupHeartbeats( + setupHeartbeatsCaptor.capture())) + .thenReturn(Mono.just(result).flux()); + + // Act + Flux actual = webTestClient.delete() + .uri("/v1/deployment/" + TEST_NAME) + .exchange() + .expectStatus() + .isOk() + .returnResult(DeleteDeploymentResponseDto.class) + .getResponseBody(); + + // Assert + StepVerifier.create(actual) + .expectNext(response) + .verifyComplete(); + + StepVerifier.create(setupHeartbeatsCaptor.getValue()) + .expectNext(result); + + verify(deployService).undeploy(TEST_NAME); + } } \ No newline at end of file diff --git a/src/test/java/com/epam/aidial/service/ConfigServiceTest.java b/src/test/java/com/epam/aidial/service/ConfigServiceTest.java index d14bb7f..1651450 100644 --- a/src/test/java/com/epam/aidial/service/ConfigServiceTest.java +++ b/src/test/java/com/epam/aidial/service/ConfigServiceTest.java @@ -62,7 +62,31 @@ void testAppServiceConfig() throws IOException { V1Service expected = readExpected("app-service", V1Service.class); // Act - V1Service actual = configService.appServiceConfig(TEST_NAME, Map.of("test-env-name", "test-env-value")); + V1Service actual = configService.appServiceConfig( + TEST_NAME, + Map.of("test-env-name", "test-env-value"), + null, + null, + null, + null); + + // Assert + assertThat(Yaml.dump(actual)).isEqualTo(Yaml.dump(expected)); + } + + @Test + void testAppServiceConfigWithExtraParameters() throws IOException { + // Arrange + V1Service expected = readExpected("app-service-extra", V1Service.class); + + // Act + V1Service actual = configService.appServiceConfig( + TEST_NAME, + Map.of("test-env-name", "test-env-value"), + "image-name", + 1, + 2, + 3); // Assert assertThat(Yaml.dump(actual)).isEqualTo(Yaml.dump(expected)); diff --git a/src/test/java/com/epam/aidial/service/DeployServiceTest.java b/src/test/java/com/epam/aidial/service/DeployServiceTest.java index c275aa2..06cd4a9 100644 --- a/src/test/java/com/epam/aidial/service/DeployServiceTest.java +++ b/src/test/java/com/epam/aidial/service/DeployServiceTest.java @@ -66,7 +66,11 @@ void testDeploy() { when(kubernetesService.deployClient()).thenReturn(kubernetesClient); when(templateService.appServiceConfig( (String) appServiceConfigCaptor.capture(), - (Map) appServiceConfigCaptor.capture())) + (Map) appServiceConfigCaptor.capture(), + (String) appServiceConfigCaptor.capture(), + (Integer) appServiceConfigCaptor.capture(), + (Integer) appServiceConfigCaptor.capture(), + (Integer) appServiceConfigCaptor.capture())) .thenReturn(testService); when(kubernetesClient.createKnativeService( (String) createServiceCaptor.capture(), @@ -75,7 +79,7 @@ void testDeploy() { .thenReturn(Mono.just(TEST_URL)); // Act - Mono actual = deployService.deploy(TEST_NAME, TEST_ENV); + Mono actual = deployService.deploy(TEST_NAME, TEST_ENV, "image-name", 1, 2, 3); // Assert StepVerifier.create(actual) @@ -83,7 +87,7 @@ void testDeploy() { .verifyComplete(); assertThat(appServiceConfigCaptor.getAllValues()) - .isEqualTo(List.of(TEST_NAME, TEST_ENV)); + .isEqualTo(List.of(TEST_NAME, TEST_ENV, "image-name", 1, 2, 3)); assertThat(createServiceCaptor.getAllValues()) .isEqualTo(List.of(TEST_NAMESPACE, testService)); } diff --git a/src/test/resources/expected-configs/app-service-extra.yaml b/src/test/resources/expected-configs/app-service-extra.yaml new file mode 100644 index 0000000..f46894c --- /dev/null +++ b/src/test/resources/expected-configs/app-service-extra.yaml @@ -0,0 +1,32 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: app-ctrl-app-test-name +spec: + template: + metadata: + annotations: + autoscaling.knative.dev/window: '300s' + autoscaling.knative.dev/initial-scale: '1' + autoscaling.knative.dev/max-scale: '3' + autoscaling.knative.dev/min-scale: '2' + spec: + idleTimeoutSeconds: 300 + containerConcurrency: 50 + automountServiceAccountToken: false + containers: + - env: + - name: test-env-name + value: test-env-value + image: image-name + imagePullPolicy: Always + name: test-container + resources: + requests: + cpu: 500m + memory: 1G + ephemeral-storage: 500M + limits: + cpu: 1000m + memory: 4G + ephemeral-storage: 1G