Skip to content

Commit

Permalink
Merge pull request #100 from scc-digitalhub/python-serve
Browse files Browse the repository at this point in the history
feat: python serve task
  • Loading branch information
matteo-s authored Jun 5, 2024
2 parents 6f2b90a + a5a9ee9 commit 44afe15
Show file tree
Hide file tree
Showing 14 changed files with 593 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.apis.AppsV1Api;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1Container;
import io.kubernetes.client.openapi.models.V1Deployment;
import io.kubernetes.client.openapi.models.V1DeploymentSpec;
Expand All @@ -19,16 +20,25 @@
import io.kubernetes.client.openapi.models.V1VolumeMount;
import it.smartcommunitylabdhub.commons.annotations.infrastructure.FrameworkComponent;
import it.smartcommunitylabdhub.commons.models.enums.State;
import it.smartcommunitylabdhub.commons.utils.MapUtils;
import it.smartcommunitylabdhub.framework.k8s.exceptions.K8sFrameworkException;
import it.smartcommunitylabdhub.framework.k8s.model.ContextRef;
import it.smartcommunitylabdhub.framework.k8s.model.ContextSource;
import it.smartcommunitylabdhub.framework.k8s.objects.CoreVolume;
import it.smartcommunitylabdhub.framework.k8s.runnables.K8sDeploymentRunnable;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.Assert;

@Slf4j
Expand All @@ -42,6 +52,9 @@ public class K8sDeploymentFramework extends K8sBaseFramework<K8sDeploymentRunnab
>() {};
private final AppsV1Api appsV1Api;

@Value("${kaniko.init-image}")
private String initImage;

public K8sDeploymentFramework(ApiClient apiClient) {
super(apiClient);
appsV1Api = new AppsV1Api(apiClient);
Expand All @@ -55,12 +68,67 @@ public K8sDeploymentRunnable run(K8sDeploymentRunnable runnable) throws K8sFrame
}

V1Deployment deployment = build(runnable);

//secrets
V1Secret secret = buildRunSecret(runnable);
if (secret != null) {
storeRunSecret(secret);
}

//check context refs and build config
if (runnable.getContextRefs() != null || runnable.getContextSources() != null) {
//build and create configMap
//TODO move to shared method
try {
// Generate Config map
Optional<List<ContextRef>> contextRefsOpt = Optional.ofNullable(runnable.getContextRefs());
Optional<List<ContextSource>> contextSourcesOpt = Optional.ofNullable(runnable.getContextSources());
V1ConfigMap configMap = new V1ConfigMap()
.metadata(
new V1ObjectMeta().name("init-config-map-" + runnable.getId()).labels(buildLabels(runnable))
)
.data(
MapUtils.mergeMultipleMaps(
// Generate context-refs.txt if exist
contextRefsOpt
.map(contextRefsList ->
Map.of(
"context-refs.txt",
contextRefsList
.stream()
.map(v ->
v.getProtocol() + "," + v.getDestination() + "," + v.getSource() + "\n"
)
.collect(Collectors.joining(""))
)
)
.orElseGet(Map::of),
// Generate context-sources.txt if exist
contextSourcesOpt
.map(contextSources ->
contextSources
.stream()
.collect(
Collectors.toMap(
ContextSource::getName,
c ->
new String(
Base64.getUrlDecoder().decode(c.getBase64()),
StandardCharsets.UTF_8
)
)
)
)
.orElseGet(Map::of)
)
);

coreV1Api.createNamespacedConfigMap(namespace, configMap, null, null, null, null);
} catch (ApiException | NullPointerException e) {
throw new K8sFrameworkException(e.getMessage());
}
}

log.info("create deployment for {}", String.valueOf(deployment.getMetadata().getName()));
deployment = create(deployment);

Expand Down Expand Up @@ -95,12 +163,24 @@ public K8sDeploymentRunnable delete(K8sDeploymentRunnable runnable) throws K8sFr
runnable.setState(State.DELETED.name());
return runnable;
}
//secrets
cleanRunSecret(runnable);

log.info("delete deployment for {}", String.valueOf(deployment.getMetadata().getName()));
delete(deployment);

//secrets
cleanRunSecret(runnable);

//init config map
try {
String configMapName = "init-config-map-" + runnable.getId();
V1ConfigMap initConfigMap = coreV1Api.readNamespacedConfigMap(configMapName, namespace, null);
if (initConfigMap != null) {
coreV1Api.deleteNamespacedConfigMap(configMapName, namespace, null, null, null, null, null, null);
}
} catch (ApiException | NullPointerException e) {
//ignore, not existing or error
}

//update results
try {
runnable.setResults(Map.of("deployment", mapper.convertValue(deployment, typeRef)));
Expand Down Expand Up @@ -190,6 +270,41 @@ public V1Deployment build(K8sDeploymentRunnable runnable) throws K8sFrameworkExc
List<String> command = buildCommand(runnable);
List<String> args = buildArgs(runnable);

//check if context build is required
if (runnable.getContextRefs() != null || runnable.getContextSources() != null) {
// Create sharedVolume
CoreVolume sharedVolume = new CoreVolume(
CoreVolume.VolumeType.empty_dir,
"/shared",
"shared-dir",
Map.of("sizeLimit", "100Mi")
);

// Create config map volume
CoreVolume configMapVolume = new CoreVolume(
CoreVolume.VolumeType.config_map,
"/init-config-map",
"init-config-map",
Map.of("name", "init-config-map-" + runnable.getId())
);

List<V1Volume> initVolumes = List.of(
k8sBuilderHelper.getVolume(sharedVolume),
k8sBuilderHelper.getVolume(configMapVolume)
);
List<V1VolumeMount> initVolumesMounts = List.of(
k8sBuilderHelper.getVolumeMount(sharedVolume),
k8sBuilderHelper.getVolumeMount(configMapVolume)
);

//add volume
volumes = Stream.concat(buildVolumes(runnable).stream(), initVolumes.stream()).collect(Collectors.toList());
volumeMounts =
Stream
.concat(buildVolumeMounts(runnable).stream(), initVolumesMounts.stream())
.collect(Collectors.toList());
}

// Build Container
V1Container container = new V1Container()
.name(containerName)
Expand All @@ -213,6 +328,23 @@ public V1Deployment build(K8sDeploymentRunnable runnable) throws K8sFrameworkExc
.restartPolicy("Always")
.imagePullSecrets(buildImagePullSecrets(runnable));

//check if context build is required
if (runnable.getContextRefs() != null || runnable.getContextSources() != null) {
// Add Init container to the PodTemplateSpec
// Build the Init Container
V1Container initContainer = new V1Container()
.name("init-container-" + runnable.getId())
.image(initImage)
.volumeMounts(volumeMounts)
.resources(resources)
.env(env)
.envFrom(envFrom)
//TODO below execute a command that is a Go script
.command(List.of("/bin/bash", "-c", "/app/builder-tool.sh"));

podSpec.setInitContainers(Collections.singletonList(initContainer));
}

// Create a PodTemplateSpec with the PodSpec
V1PodTemplateSpec podTemplateSpec = new V1PodTemplateSpec().metadata(metadata).spec(podSpec);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,19 @@ public K8sJobRunnable stop(K8sJobRunnable runnable) throws K8sFrameworkException
//stop by deleting
log.info("delete job for {}", String.valueOf(job.getMetadata().getName()));
delete(job);
//secrets
cleanRunSecret(runnable);

//init config map
try {
String configMapName = "init-config-map-" + runnable.getId();
V1ConfigMap initConfigMap = coreV1Api.readNamespacedConfigMap(configMapName, namespace, null);
if (initConfigMap != null) {
coreV1Api.deleteNamespacedConfigMap(configMapName, namespace, null, null, null, null, null, null);
}
} catch (ApiException | NullPointerException e) {
//ignore, not existing or error
}

//update state
runnable.setState(State.STOPPED.name());
Expand All @@ -200,11 +213,22 @@ public K8sJobRunnable delete(K8sJobRunnable runnable) throws K8sFrameworkExcepti
return runnable;
}

log.info("delete job for {}", String.valueOf(job.getMetadata().getName()));
delete(job);

//secrets
cleanRunSecret(runnable);

log.info("delete job for {}", String.valueOf(job.getMetadata().getName()));
delete(job);
//init config map
try {
String configMapName = "init-config-map-" + runnable.getId();
V1ConfigMap initConfigMap = coreV1Api.readNamespacedConfigMap(configMapName, namespace, null);
if (initConfigMap != null) {
coreV1Api.deleteNamespacedConfigMap(configMapName, namespace, null, null, null, null, null, null);
}
} catch (ApiException | NullPointerException e) {
//ignore, not existing or error
}

//update state
runnable.setState(State.DELETED.name());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,32 @@
import io.kubernetes.client.custom.IntOrString;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1Deployment;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.openapi.models.V1Service;
import io.kubernetes.client.openapi.models.V1ServicePort;
import io.kubernetes.client.openapi.models.V1ServiceSpec;
import it.smartcommunitylabdhub.commons.annotations.infrastructure.FrameworkComponent;
import it.smartcommunitylabdhub.commons.models.enums.State;
import it.smartcommunitylabdhub.commons.utils.MapUtils;
import it.smartcommunitylabdhub.framework.k8s.exceptions.K8sFrameworkException;
import it.smartcommunitylabdhub.framework.k8s.model.ContextRef;
import it.smartcommunitylabdhub.framework.k8s.model.ContextSource;
import it.smartcommunitylabdhub.framework.k8s.runnables.K8sDeploymentRunnable;
import it.smartcommunitylabdhub.framework.k8s.runnables.K8sServeRunnable;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.Assert;

@Slf4j
Expand All @@ -34,6 +41,9 @@ public class K8sServeFramework extends K8sBaseFramework<K8sServeRunnable, V1Serv
HashMap<String, Serializable>
>() {};

@Value("${kaniko.init-image}")
private String initImage;

@Autowired
private K8sDeploymentFramework deploymentFramework;

Expand All @@ -51,6 +61,60 @@ public K8sServeRunnable run(K8sServeRunnable runnable) throws K8sFrameworkExcept
// Create a deployment from a Deployment+Service
V1Deployment deployment = buildDeployment(runnable);

//check context refs and build config
if (runnable.getContextRefs() != null || runnable.getContextSources() != null) {
//build and create configMap
//TODO move to shared method
try {
// Generate Config map
Optional<List<ContextRef>> contextRefsOpt = Optional.ofNullable(runnable.getContextRefs());
Optional<List<ContextSource>> contextSourcesOpt = Optional.ofNullable(runnable.getContextSources());
V1ConfigMap configMap = new V1ConfigMap()
.metadata(
new V1ObjectMeta().name("init-config-map-" + runnable.getId()).labels(buildLabels(runnable))
)
.data(
MapUtils.mergeMultipleMaps(
// Generate context-refs.txt if exist
contextRefsOpt
.map(contextRefsList ->
Map.of(
"context-refs.txt",
contextRefsList
.stream()
.map(v ->
v.getProtocol() + "," + v.getDestination() + "," + v.getSource() + "\n"
)
.collect(Collectors.joining(""))
)
)
.orElseGet(Map::of),
// Generate context-sources.txt if exist
contextSourcesOpt
.map(contextSources ->
contextSources
.stream()
.collect(
Collectors.toMap(
ContextSource::getName,
c ->
new String(
Base64.getUrlDecoder().decode(c.getBase64()),
StandardCharsets.UTF_8
)
)
)
)
.orElseGet(Map::of)
)
);

coreV1Api.createNamespacedConfigMap(namespace, configMap, null, null, null, null);
} catch (ApiException | NullPointerException e) {
throw new K8sFrameworkException(e.getMessage());
}
}

log.info("create deployment for {}", String.valueOf(deployment.getMetadata().getName()));
deployment = deploymentFramework.create(deployment);

Expand Down Expand Up @@ -106,6 +170,20 @@ public K8sServeRunnable delete(K8sServeRunnable runnable) throws K8sFrameworkExc
deploymentFramework.delete(deployment);
}

//secrets
cleanRunSecret(runnable);

//init config map
try {
String configMapName = "init-config-map-" + runnable.getId();
V1ConfigMap initConfigMap = coreV1Api.readNamespacedConfigMap(configMapName, namespace, null);
if (initConfigMap != null) {
coreV1Api.deleteNamespacedConfigMap(configMapName, namespace, null, null, null, null, null, null);
}
} catch (ApiException | NullPointerException e) {
//ignore, not existing or error
}

V1Service service;
try {
// Retrieve the service
Expand Down Expand Up @@ -352,6 +430,8 @@ private K8sDeploymentRunnable getDeployment(K8sServeRunnable runnable) {
.state(runnable.getState())
.tolerations(runnable.getTolerations())
.volumes(runnable.getVolumes())
.contextRefs(runnable.getContextRefs())
.contextSources(runnable.getContextSources())
.build();
}
}
Loading

0 comments on commit 44afe15

Please sign in to comment.