Skip to content

Commit

Permalink
feat: add extra parameters to deployment endpoint (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
Oleksii-Klimov authored Jan 8, 2025
1 parent e2f4095 commit 9eaedec
Show file tree
Hide file tree
Showing 10 changed files with 314 additions and 200 deletions.
124 changes: 63 additions & 61 deletions src/main/java/com/epam/aidial/controller/DeploymentController.java
Original file line number Diff line number Diff line change
@@ -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<ServerSentEvent<Object>> create(
@PathVariable("name") String name,
@RequestBody CreateDeploymentRequestDto request) {
Mono<CreateDeploymentResponseDto> 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<ServerSentEvent<Object>> delete(@PathVariable("name") String name) {
Mono<DeleteImageResponseDto> 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<GetApplicationLogsResponseDto> 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<ServerSentEvent<Object>> create(
@PathVariable("name") String name,
@RequestBody CreateDeploymentRequestDto request) {
Map<String, String> env = Objects.requireNonNullElse(request.env(), Map.of());
Mono<CreateDeploymentResponseDto> 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<ServerSentEvent<Object>> delete(@PathVariable("name") String name) {
Mono<DeleteImageResponseDto> 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<GetApplicationLogsResponseDto> logs(@PathVariable("name") String name) {
return deployService.logs(name)
.map(GetApplicationLogsResponseDto::new)
.doOnError(e -> log.error("Failed to retrieve logs for {}", name, e));
}
}
19 changes: 13 additions & 6 deletions src/main/java/com/epam/aidial/dto/CreateDeploymentRequestDto.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package com.epam.aidial.dto;

import java.util.Map;

public record CreateDeploymentRequestDto(Map<String, String> env) {
}
package com.epam.aidial.dto;

import org.jetbrains.annotations.Nullable;

import java.util.Map;

public record CreateDeploymentRequestDto(
@Nullable Map<String, String> env,
@Nullable String image,
@Nullable Integer initialScale,
@Nullable Integer minScale,
@Nullable Integer maxScale) {
}
12 changes: 8 additions & 4 deletions src/main/java/com/epam/aidial/dto/CreateImageRequestDto.java
Original file line number Diff line number Diff line change
@@ -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) {
}
33 changes: 29 additions & 4 deletions src/main/java/com/epam/aidial/service/ConfigService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@

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;
import io.kubernetes.client.openapi.models.V1Container;
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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -144,19 +149,39 @@ public V1Job buildJobConfig(String name, String sources, String runtime) {
return config.data();
}

public V1Service appServiceConfig(String name, Map<String, String> env) {
public V1Service appServiceConfig(
String name,
Map<String, String> env,
@Nullable String image,
@Nullable Integer initScale,
@Nullable Integer minScale,
@Nullable Integer maxScale) {
MappingChain<V1Service> config = new MappingChain<>(this.appconfig.cloneServiceConfig());
config.get(SERVICE_METADATA_FIELD)
.data()
.setName(appName(name));
MappingChain<V1Container> container = config.get(SERVICE_SPEC_FIELD)
.get(SERVICE_TEMPLATE_FIELD)
MappingChain<V1RevisionTemplateSpec> 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<V1Container> 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<V1EnvVar> containerEnv = container.getList(CONTAINER_ENV_FIELD, ENV_VAR_NAME);

env.forEach((key, value) -> containerEnv.get(key)
Expand Down
11 changes: 9 additions & 2 deletions src/main/java/com/epam/aidial/service/DeployService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,9 +31,15 @@ public class DeployService {
@Value("${app.service-setup-timeout-sec}")
private final int serviceSetupTimeoutSec;

public Mono<String> deploy(String name, Map<String, String> env) {
public Mono<String> deploy(
String name,
Map<String, String> 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));
}

Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/epam/aidial/util/mapping/Mappers.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ public class Mappers {
V1ServiceSpec::getTemplate,
V1ServiceSpec::setTemplate);

public static final FieldMapper<V1RevisionTemplateSpec, V1ObjectMeta> SERVICE_TEMPLATE_METADATA_FIELD = new FieldMapper<>(
V1ObjectMeta::new,
V1RevisionTemplateSpec::getMetadata,
V1RevisionTemplateSpec::setMetadata);

public static final FieldMapper<V1RevisionTemplateSpec, V1RevisionSpec> SERVICE_TEMPLATE_SPEC_FIELD = new FieldMapper<>(
V1RevisionSpec::new,
V1RevisionTemplateSpec::getSpec,
Expand Down
Loading

0 comments on commit 9eaedec

Please sign in to comment.