diff --git a/platform-api/src/main/java/io/datacater/core/config/ConfigUtilities.java b/platform-api/src/main/java/io/datacater/core/config/ConfigUtilities.java index b2f49f9d..5a81739d 100644 --- a/platform-api/src/main/java/io/datacater/core/config/ConfigUtilities.java +++ b/platform-api/src/main/java/io/datacater/core/config/ConfigUtilities.java @@ -20,6 +20,11 @@ private ConfigUtilities() {} public static Uni> getMappedConfigs( Map configs, DataCaterSessionFactory dsf) { + + if (configs == null) { + return Uni.createFrom().item(new ArrayList<>()); + } + return dsf.withTransaction( (session, transaction) -> session diff --git a/platform-api/src/main/java/io/datacater/core/deployment/DeploymentEndpoint.java b/platform-api/src/main/java/io/datacater/core/deployment/DeploymentEndpoint.java index 34dc05d9..bb621436 100644 --- a/platform-api/src/main/java/io/datacater/core/deployment/DeploymentEndpoint.java +++ b/platform-api/src/main/java/io/datacater/core/deployment/DeploymentEndpoint.java @@ -9,10 +9,9 @@ import io.datacater.core.pipeline.PipelineEntity; import io.datacater.core.stream.StreamEntity; import io.datacater.core.utilities.StringUtilities; -import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.dsl.ContainerResource; import io.fabric8.kubernetes.client.dsl.LogWatch; -import io.fabric8.kubernetes.client.dsl.RollableScalableResource; import io.quarkus.security.Authenticated; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.unchecked.Unchecked; @@ -66,7 +65,8 @@ public Uni getDeployment(@PathParam("uuid") UUID deploymentId) @GET @Path("{uuid}/logs") @Produces(MediaType.APPLICATION_JSON) - public Uni> getLogs(@PathParam("uuid") UUID deploymentId) { + public Uni> getLogs( + @PathParam("uuid") UUID deploymentId, @DefaultValue("1") @QueryParam("replica") int replica) { return dsf.withTransaction( ((session, transaction) -> session.find(DeploymentEntity.class, deploymentId))) .onItem() @@ -74,13 +74,15 @@ public Uni> getLogs(@PathParam("uuid") UUID deploymentId) { .failWith(new DeploymentNotFoundException(StaticConfig.LoggerMessages.DEPLOYMENT_NOT_FOUND)) .onItem() .ifNotNull() - .transform(Unchecked.function(deployment -> getDeploymentLogsAsList(deployment.getId()))); + .transform( + Unchecked.function(deployment -> getDeploymentLogsAsList(deployment.getId(), replica))); } @GET @Path("{uuid}/health") @Produces(MediaType.APPLICATION_JSON) - public Uni getHealth(@PathParam("uuid") UUID deploymentId) { + public Uni getHealth( + @PathParam("uuid") UUID deploymentId, @DefaultValue("1") @QueryParam("replica") int replica) { return dsf.withTransaction( ((session, transaction) -> session.find(DeploymentEntity.class, deploymentId))) .onItem() @@ -94,7 +96,9 @@ public Uni getHealth(@PathParam("uuid") UUID deploymentId) { HttpClient httpClient = HttpClient.newHttpClient(); HttpRequest req = buildDeploymentServiceRequest( - deploymentId, StaticConfig.EnvironmentVariables.DEPLOYMENT_HEALTH_PATH); + deploymentId, + StaticConfig.EnvironmentVariables.DEPLOYMENT_HEALTH_PATH, + replica); HttpResponse response = httpClient.send(req, HttpResponse.BodyHandlers.ofString()); return Response.ok().entity(response.body()).build(); @@ -104,7 +108,8 @@ public Uni getHealth(@PathParam("uuid") UUID deploymentId) { @GET @Path("{uuid}/metrics") @Produces(MediaType.TEXT_PLAIN) - public Uni getMetrics(@PathParam("uuid") UUID deploymentId) { + public Uni getMetrics( + @PathParam("uuid") UUID deploymentId, @DefaultValue("1") @QueryParam("replica") int replica) { return dsf.withTransaction( ((session, transaction) -> session.find(DeploymentEntity.class, deploymentId))) .onItem() @@ -118,7 +123,9 @@ public Uni getMetrics(@PathParam("uuid") UUID deploymentId) { HttpClient httpClient = HttpClient.newHttpClient(); HttpRequest req = buildDeploymentServiceRequest( - deploymentId, StaticConfig.EnvironmentVariables.DEPLOYMENT_METRICS_PATH); + deploymentId, + StaticConfig.EnvironmentVariables.DEPLOYMENT_METRICS_PATH, + replica); HttpResponse response = httpClient.send(req, HttpResponse.BodyHandlers.ofString()); return Response.ok().entity(response.body()).build(); @@ -129,7 +136,10 @@ public Uni getMetrics(@PathParam("uuid") UUID deploymentId) { @Path("{uuid}/watch-logs") @Produces(MediaType.SERVER_SENT_EVENTS) public Uni watchLogs( - @PathParam("uuid") UUID deploymentId, @Context Sse sse, @Context SseEventSink eventSink) { + @PathParam("uuid") UUID deploymentId, + @DefaultValue("1") @QueryParam("replica") int replica, + @Context Sse sse, + @Context SseEventSink eventSink) { return dsf.withTransaction( ((session, transaction) -> session.find(DeploymentEntity.class, deploymentId))) .onItem() @@ -140,7 +150,7 @@ public Uni watchLogs( .transform( deployment -> { try { - watchLogsRunner(deployment.getId(), sse, eventSink); + watchLogsRunner(deployment.getId(), replica, sse, eventSink); } catch (IOException e) { throw new DatacaterException(StringUtilities.wrapString(e.getMessage())); } @@ -336,21 +346,20 @@ private Uni getStream( String.format(StaticConfig.LoggerMessages.STREAM_NOT_FOUND, key)))); } - private List getDeploymentLogsAsList(UUID deploymentId) { + private List getDeploymentLogsAsList(UUID deploymentId, int replica) { K8Deployment k8Deployment = new K8Deployment(client); - return Arrays.asList(k8Deployment.getLogs(deploymentId).split("\n")); + return Arrays.asList(k8Deployment.getLogs(deploymentId, replica).split("\n")); } - private HttpRequest buildDeploymentServiceRequest(UUID deploymentId, String path) { + private HttpRequest buildDeploymentServiceRequest(UUID deploymentId, String path, int replica) { K8Deployment k8Deployment = new K8Deployment(client); - String clusterIp = k8Deployment.getClusterIp(deploymentId); + String ip = k8Deployment.getDeploymentReplicaIp(deploymentId, replica).replace(".", "-"); + String namespace = StaticConfig.EnvironmentVariables.NAMESPACE; + int port = StaticConfig.EnvironmentVariables.DEPLOYMENT_CONTAINER_PORT; + String protocol = StaticConfig.EnvironmentVariables.DEPLOYMENT_CONTAINER_PROTOCOL; + String uriReady = - String.format( - "%s://%s:%d%s", - StaticConfig.EnvironmentVariables.DEPLOYMENT_CONTAINER_PROTOCOL, - clusterIp, - StaticConfig.EnvironmentVariables.DEPLOYMENT_CONTAINER_PORT, - path); + String.format("%s://%s.%s.pod.cluster.local:%d%s", protocol, ip, namespace, port, path); return HttpRequest.newBuilder() .GET() @@ -359,9 +368,9 @@ private HttpRequest buildDeploymentServiceRequest(UUID deploymentId, String path .build(); } - private RollableScalableResource watchDeploymentLogs(UUID deploymentId) { + private ContainerResource watchDeploymentLogs(UUID deploymentId, int replica) { K8Deployment k8Deployment = new K8Deployment(client); - return k8Deployment.watchLogs(deploymentId); + return k8Deployment.watchLogs(deploymentId, replica); } private List getK8Deployments(List deployments) { @@ -452,12 +461,10 @@ private UUID getPipelineUUIDFromMap(Map map) { } } - private void watchLogsRunner(UUID deploymentId, @Context Sse sse, @Context SseEventSink eventSink) + private void watchLogsRunner( + UUID deploymentId, int replica, @Context Sse sse, @Context SseEventSink eventSink) throws IOException { - LogWatch lw = - watchDeploymentLogs(deploymentId) - .inContainer(StaticConfig.DEPLOYMENT_NAME_PREFIX + deploymentId) - .watchLog(); + LogWatch lw = watchDeploymentLogs(deploymentId, replica).watchLog(); InputStream is = lw.getOutput(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is)); String line; diff --git a/platform-api/src/main/java/io/datacater/core/deployment/K8Deployment.java b/platform-api/src/main/java/io/datacater/core/deployment/K8Deployment.java index 02bba6ae..32895b68 100644 --- a/platform-api/src/main/java/io/datacater/core/deployment/K8Deployment.java +++ b/platform-api/src/main/java/io/datacater/core/deployment/K8Deployment.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.datacater.core.exceptions.CreateDeploymentException; import io.datacater.core.exceptions.DeploymentNotFoundException; +import io.datacater.core.exceptions.DeploymentReplicaMismatchException; import io.datacater.core.pipeline.PipelineEntity; import io.datacater.core.stream.StreamEntity; import io.datacater.core.utilities.StringUtilities; @@ -14,7 +15,7 @@ import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientException; -import io.fabric8.kubernetes.client.dsl.RollableScalableResource; +import io.fabric8.kubernetes.client.dsl.ContainerResource; import java.util.*; import javax.inject.Singleton; import org.jboss.logging.Logger; @@ -24,12 +25,10 @@ public class K8Deployment { private static final Logger LOGGER = Logger.getLogger(K8Deployment.class); private final KubernetesClient client; private final K8ConfigMap k8ConfigMap; - private final K8Service k8Service; public K8Deployment(KubernetesClient client) { this.client = client; this.k8ConfigMap = new K8ConfigMap(client); - this.k8Service = new K8Service(client); } public Map create( @@ -38,11 +37,12 @@ public Map create( StreamEntity streamOut, DeploymentSpec deploymentSpec, UUID deploymentId) { + final String name = StaticConfig.DEPLOYMENT_NAME_PREFIX + deploymentId; final String configmapName = StaticConfig.CONFIGMAP_NAME_PREFIX + deploymentId; final String configmapVolumeName = StaticConfig.CONFIGMAP_VOLUME_NAME_PREFIX + deploymentId; final String dataShareVolumeName = StaticConfig.DATA_SHARE_VOLUME_NAME_PREFIX + deploymentId; - final String serviceName = StaticConfig.SERVICE_NAME_PREFIX + deploymentId; + final int replicaCount = getDeploymentReplicaOrDefault(deploymentSpec.deployment()); List variables = getEnvironmentVariables(streamIn, streamOut, deploymentSpec, deploymentId); @@ -52,17 +52,17 @@ public Map create( new DeploymentBuilder() .withNewMetadata() .withName(name) - .addToLabels(getLabels(deploymentId, deploymentSpec.name(), serviceName)) + .addToLabels(getLabels(deploymentId, deploymentSpec.name())) .endMetadata() .withNewSpec() - .withReplicas(StaticConfig.EnvironmentVariables.REPLICAS) + .withReplicas(replicaCount) .withMinReadySeconds(StaticConfig.EnvironmentVariables.READY_SECONDS) .withNewSelector() - .addToMatchLabels(getLabels(deploymentId, deploymentSpec.name(), serviceName)) + .addToMatchLabels(getLabels(deploymentId, deploymentSpec.name())) .endSelector() .withNewTemplate() .withNewMetadata() - .addToLabels(getLabels(deploymentId, deploymentSpec.name(), serviceName)) + .addToLabels(getLabels(deploymentId, deploymentSpec.name())) .endMetadata() .withNewSpec() .addAllToContainers( @@ -89,12 +89,10 @@ public Map create( throw new CreateDeploymentException(StaticConfig.LoggerMessages.DEPLOYMENT_NOT_CREATED); } k8ConfigMap.getOrCreate(configmapName, pe); - k8Service.create(serviceName); return getDeployment(deploymentId); } - private static Map getLabels( - UUID deploymentId, String prettyName, String serviceName) { + private static Map getLabels(UUID deploymentId, String prettyName) { return Map.of( StaticConfig.APP, StaticConfig.DATACATER_PIPELINE, @@ -105,9 +103,7 @@ private static Map getLabels( StaticConfig.UUID_TEXT, deploymentId.toString(), StaticConfig.DEPLOYMENT_NAME_TEXT, - prettyName, - StaticConfig.DEPLOYMENT_SERVICE_TEXT, - serviceName); + prettyName); } private Container pythonRunnerContainer(String configmapVolumeName, String dataShareVolumeName) { @@ -170,32 +166,32 @@ private static ConfigMapVolumeSource configMapVolumeSource(String deploymentName return new ConfigMapVolumeSourceBuilder().withName(deploymentName).build(); } - public String getClusterIp(UUID deploymentId) { - final String serviceName = StaticConfig.SERVICE_NAME_PREFIX + deploymentId; - return k8Service.getClusterIp(serviceName); - } + public String getLogs(UUID deploymentId, int replica) { + + String deploymentName = getDeploymentName(deploymentId); + Pod pod = getDeploymentPodByReplica(deploymentName, replica); - public String getLogs(UUID deploymentId) { return client - .apps() - .deployments() + .pods() .inNamespace(StaticConfig.EnvironmentVariables.NAMESPACE) - .withName(getDeploymentName(deploymentId)) + .withName(pod.getMetadata().getName()) .inContainer(StaticConfig.DEPLOYMENT_NAME_PREFIX + deploymentId) .getLog(true); } - public RollableScalableResource watchLogs(UUID deploymentId) { + public ContainerResource watchLogs(UUID deploymentId, int replica) { + String deploymentName = getDeploymentName(deploymentId); + Pod pod = getDeploymentPodByReplica(deploymentName, replica); + return client - .apps() - .deployments() + .pods() .inNamespace(StaticConfig.EnvironmentVariables.NAMESPACE) - .withName(getDeploymentName(deploymentId)); + .withName(pod.getMetadata().getName()) + .inContainer(StaticConfig.DEPLOYMENT_NAME_PREFIX + deploymentId); } public void delete(UUID deploymentId) { String name = getDeploymentName(deploymentId); - String serviceName = StaticConfig.SERVICE_NAME_PREFIX + deploymentId; String configMapName = StaticConfig.CONFIGMAP_NAME_PREFIX + deploymentId; List status = client @@ -209,7 +205,6 @@ public void delete(UUID deploymentId) { // deployment to be exactly // one and continue only if that is true. if (status.size() == 1) { - k8Service.delete(serviceName); k8ConfigMap.delete(configMapName); LOGGER.info(String.format(StaticConfig.LoggerMessages.DEPLOYMENT_DELETED, name)); } else { @@ -260,6 +255,13 @@ public Map getDeployment(UUID deploymentId) { } } + public String getDeploymentReplicaIp(UUID deploymentId, int replica) { + String deploymentName = getDeploymentName(deploymentId); + Pod pod = getDeploymentPodByReplica(deploymentName, replica); + + return pod.getStatus().getPodIP(); + } + private boolean exists(UUID deploymentId) { return !client .apps() @@ -271,7 +273,7 @@ private boolean exists(UUID deploymentId) { .isEmpty(); } - private String getDeploymentName(UUID deploymentId) { + public String getDeploymentName(UUID deploymentId) { List deployments = client .apps() @@ -456,4 +458,69 @@ private String getEnvVariableFromNode(JsonNode node, String field) { } return StaticConfig.EMPTY_STRING; } + + private int getDeploymentReplicaOrDefault(Map map) { + int replica = StaticConfig.EnvironmentVariables.REPLICAS; + try { + replica = (int) map.get(StaticConfig.REPLICAS_TEXT); + } catch (Exception e) { + return replica; + } + return replica; + } + + private int replicaNumberToArrayPosition(int replica) { + int replicaPosition = replica; + if (replicaPosition <= 0) { + final String errorMessage = + "The deployment replica you are searching for can not be less than 1"; + throw new DeploymentReplicaMismatchException(errorMessage); + } + // map replica number to array position + replicaPosition--; + return replicaPosition; + } + + private Pod getDeploymentPodByReplica(String deploymentName, int replica) { + int replicaPosition = replicaNumberToArrayPosition(replica); + + final Map matchLabels = + client + .apps() + .deployments() + .inNamespace(StaticConfig.EnvironmentVariables.NAMESPACE) + .withName(deploymentName) + .get() + .getSpec() + .getSelector() + .getMatchLabels(); + + Pod searchedPod; + List allDeploymentPods = new ArrayList<>(); + try { + allDeploymentPods = + client + .pods() + .inNamespace(StaticConfig.EnvironmentVariables.NAMESPACE) + .withLabels(matchLabels) + .list() + .getItems(); + + searchedPod = + allDeploymentPods.stream() + .sorted((Comparator.comparing(o -> o.getMetadata().getName()))) + .toList() + .get(replicaPosition); + } catch (ArrayIndexOutOfBoundsException e) { + LOGGER.info( + String.format( + "An error occurred while trying to get replica %s: %s", replica, e.getMessage())); + final String errorMessage = + String.format( + "The deployment replica you are searching for, %s, does not match the defined replica amount of %s.", + replica, allDeploymentPods.size()); + throw new DeploymentReplicaMismatchException(errorMessage); + } + return searchedPod; + } } diff --git a/platform-api/src/main/java/io/datacater/core/deployment/K8Service.java b/platform-api/src/main/java/io/datacater/core/deployment/K8Service.java deleted file mode 100644 index a4cf1da9..00000000 --- a/platform-api/src/main/java/io/datacater/core/deployment/K8Service.java +++ /dev/null @@ -1,79 +0,0 @@ -package io.datacater.core.deployment; - -import io.fabric8.kubernetes.api.model.*; -import io.fabric8.kubernetes.client.KubernetesClient; -import java.util.List; -import java.util.Map; -import javax.enterprise.context.ApplicationScoped; - -@ApplicationScoped -public class K8Service { - private final KubernetesClient client; - - K8Service(KubernetesClient client) { - this.client = client; - } - - private boolean exists(String name) { - return !getListByLabel(name).isEmpty(); - } - - public void delete(String name) { - client.services().inNamespace(StaticConfig.EnvironmentVariables.NAMESPACE).delete(get(name)); - } - - public String getClusterIp(String serviceName) { - return client - .services() - .inNamespace(StaticConfig.EnvironmentVariables.NAMESPACE) - .withName(serviceName) - .get() - .getSpec() - .getClusterIP(); - } - - private Service get(String name) { - return getListByLabel(name).get(0); - } - - private List getListByLabel(String name) { - return client - .services() - .inNamespace(StaticConfig.EnvironmentVariables.NAMESPACE) - .withLabel(StaticConfig.DEPLOYMENT_SERVICE_TEXT, name) - .list() - .getItems(); - } - - public void create(String name) { - if (!exists(name)) { - createService(name); - } - } - - private Service createService(String name) { - Service service = - new ServiceBuilder() - .withNewMetadata() - .withName(name) - .withLabels(Map.of(StaticConfig.DEPLOYMENT_SERVICE_TEXT, name)) - .endMetadata() - .withNewSpec() - .withSelector(Map.of(StaticConfig.DEPLOYMENT_SERVICE_TEXT, name)) - .withPorts(port()) - .endSpec() - .build(); - client - .services() - .inNamespace(StaticConfig.EnvironmentVariables.NAMESPACE) - .createOrReplace(service); - return service; - } - - private ServicePort port() { - return new ServicePortBuilder() - .withProtocol(StaticConfig.TCP_TAG) - .withPort(StaticConfig.EnvironmentVariables.DEPLOYMENT_CONTAINER_PORT) - .build(); - } -} diff --git a/platform-api/src/main/java/io/datacater/core/deployment/StaticConfig.java b/platform-api/src/main/java/io/datacater/core/deployment/StaticConfig.java index 41ff3b96..613af981 100644 --- a/platform-api/src/main/java/io/datacater/core/deployment/StaticConfig.java +++ b/platform-api/src/main/java/io/datacater/core/deployment/StaticConfig.java @@ -52,7 +52,6 @@ static Map getLimits() { static final String CONFIGMAP_VOLUME_NAME_PREFIX = "datacater-volume-"; static final String DATA_SHARE_VOLUME_NAME_PREFIX = "datacater-volume-data-"; static final String SERVICE_NAME_PREFIX = "datacater-service-"; - static final String NONE = "None"; static final String TCP_TAG = "TCP"; static final String SPEC = "spec"; static final String STREAM_OUT = "stream-out"; @@ -74,12 +73,11 @@ static Map getLimits() { static final String VALUE_SERIALIZER = "value.serializer"; static final String STREAMIN_CONFIG_TEXT = "stream-in-config"; static final String STREAMOUT_CONFIG_TEXT = "stream-out-config"; - static final String DC_STREAMIN_CONFIG_TEXT = "DATACATER_STREAMIN_CONFIG"; - static final String DC_STREAMOUT_CONFIG_TEXT = "DATACATER_STREAM_OUTCONFIG"; static final String HTTP = "http"; static final String CONDITIONS = "Conditions"; static final String ERROR_TAG = "error"; static final String MESSAGE_TAG = "message"; + static final String REPLICAS_TEXT = "replicas"; static class EnvironmentVariables { private EnvironmentVariables() {} @@ -109,10 +107,6 @@ private EnvironmentVariables() {} ConfigProvider.getConfig() .getOptionalValue("datacater.deployment.metrics-path", String.class) .orElse("/q/metrics"); - public static final long DEPLOYMENT_STATS_TIMEOUT = - ConfigProvider.getConfig() - .getOptionalValue("datacater.deployment.stats.timeout", Long.class) - .orElse(10000L); static final int DEPLOYMENT_CONTAINER_PORT = ConfigProvider.getConfig() .getOptionalValue("datacater.deployment.image.container.port", Integer.class) @@ -130,7 +124,7 @@ private EnvironmentVariables() {} static final String PYTHON_RUNNER_IMAGE_TAG = ConfigProvider.getConfig() .getOptionalValue("datacater.pythonrunner.image.version", String.class) - .orElse("alpha-20221117"); + .orElse("2023.1"); static final int PYTHON_RUNNER_CONTAINER_PORT = ConfigProvider.getConfig() .getOptionalValue("datacater.pythonrunner.image.containerPort", Integer.class) diff --git a/platform-api/src/main/java/io/datacater/core/exceptions/DeploymentReplicaMismatchException.java b/platform-api/src/main/java/io/datacater/core/exceptions/DeploymentReplicaMismatchException.java new file mode 100644 index 00000000..e9df9f77 --- /dev/null +++ b/platform-api/src/main/java/io/datacater/core/exceptions/DeploymentReplicaMismatchException.java @@ -0,0 +1,10 @@ +package io.datacater.core.exceptions; + +import io.datacater.core.ExcludeFromGeneratedCoverageReport; + +@ExcludeFromGeneratedCoverageReport +public class DeploymentReplicaMismatchException extends RuntimeException { + public DeploymentReplicaMismatchException(String message) { + super(message); + } +} diff --git a/platform-api/src/main/java/io/datacater/core/exceptions/DeploymentReplicaMismatchExceptionMapper.java b/platform-api/src/main/java/io/datacater/core/exceptions/DeploymentReplicaMismatchExceptionMapper.java new file mode 100644 index 00000000..3f1e8f09 --- /dev/null +++ b/platform-api/src/main/java/io/datacater/core/exceptions/DeploymentReplicaMismatchExceptionMapper.java @@ -0,0 +1,21 @@ +package io.datacater.core.exceptions; + +import io.datacater.core.ExcludeFromGeneratedCoverageReport; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +@ExcludeFromGeneratedCoverageReport +@Provider +public class DeploymentReplicaMismatchExceptionMapper + implements ExceptionMapper { + @Override + public Response toResponse(DeploymentReplicaMismatchException exception) { + Error error = + Error.from( + Response.Status.BAD_REQUEST.getStatusCode(), + Response.Status.BAD_REQUEST, + exception.getMessage()); + return Response.status(Response.Status.BAD_REQUEST).entity(error).build(); + } +} diff --git a/platform-api/src/main/java/io/datacater/core/exceptions/Error.java b/platform-api/src/main/java/io/datacater/core/exceptions/Error.java index 3ed1a672..dbf1ae1e 100644 --- a/platform-api/src/main/java/io/datacater/core/exceptions/Error.java +++ b/platform-api/src/main/java/io/datacater/core/exceptions/Error.java @@ -26,4 +26,15 @@ private Error(int statusCode, Response.Status status, String message) { public static Error from(int statusCode, Response.Status status, String message) { return new Error(statusCode, status, message); } + + @Override + public String toString() { + return "{\"statusCode\":" + + this.statusCode + + ",\"status\":\"" + + this.status.toString() + + "\",\"message\":\"" + + this.message + + "\"}"; + } } diff --git a/platform-api/src/test/java/io/datacater/core/deployment/DatacaterDeploymentEndpointTest.java b/platform-api/src/test/java/io/datacater/core/deployment/DatacaterDeploymentEndpointTest.java index c6d2c9f6..7a144064 100644 --- a/platform-api/src/test/java/io/datacater/core/deployment/DatacaterDeploymentEndpointTest.java +++ b/platform-api/src/test/java/io/datacater/core/deployment/DatacaterDeploymentEndpointTest.java @@ -7,7 +7,9 @@ import com.fasterxml.jackson.databind.json.JsonMapper; import io.datacater.core.pipeline.PipelineEntity; import io.datacater.core.stream.StreamEntity; +import io.fabric8.kubernetes.client.server.mock.KubernetesServer; import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.kubernetes.client.KubernetesTestServer; import io.quarkus.test.kubernetes.client.WithKubernetesTestServer; import io.restassured.RestAssured; import io.restassured.http.ContentType; @@ -24,6 +26,7 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class DatacaterDeploymentEndpointTest { private static final Logger LOGGER = Logger.getLogger(DatacaterDeploymentEndpointTest.class); + @KubernetesTestServer KubernetesServer mockServer; final String baseURI = "http://localhost:8081"; final String deploymentsPath = "/deployments"; @@ -125,21 +128,8 @@ void testGetDeployments() { given().baseUri(baseURI).get(deploymentsPath).then().statusCode(200); } - // only testing that logs can be fetched. - // Log validity ist harder to test, since logs will always change @Test @Order(4) - void testGetDeploymentLogs() { - given() - .pathParam("uuid", deploymentId) - .baseUri(baseURI) - .get(deploymentsPath + "/{uuid}/logs") - .then() - .statusCode(200); - } - - @Test - @Order(5) void testUpdateDeployment() throws IOException { String pipelineUUIDPlaceholder = "pipelineUUIDPlaceholder"; URL JsonURL = ClassLoader.getSystemClassLoader().getResource(deploymentPath); @@ -160,7 +150,7 @@ void testUpdateDeployment() throws IOException { } @Test - @Order(6) + @Order(5) void testDeleteDeployment() { Response response = RestAssured.given() @@ -173,7 +163,7 @@ void testDeleteDeployment() { } @Test - @Order(7) + @Order(6) void testGetDeletedDeployment() { given() .pathParam("uuid", deploymentId) @@ -184,7 +174,7 @@ void testGetDeletedDeployment() { } @Test - @Order(8) + @Order(7) void testGetUnknownDeployment() { given() .pathParam("uuid", UUID.randomUUID()) @@ -195,7 +185,7 @@ void testGetUnknownDeployment() { } @Test - @Order(9) + @Order(8) void testGetUnknownDeploymentLogs() { given() .pathParam("uuid", UUID.randomUUID()) @@ -206,7 +196,7 @@ void testGetUnknownDeploymentLogs() { } @Test - @Order(10) + @Order(9) void testWatchUnknownDeploymentLogs() { given() .pathParam("uuid", UUID.randomUUID()) @@ -260,7 +250,7 @@ void testCreateDeploymentWithNoStreamsInPipeline() throws IOException { PipelineEntity pipeline = mapper.readValue(responsePipeline.body().asString(), PipelineEntity.class); - pipelineId = pipeline.getId(); + UUID pipelineId = pipeline.getId(); // add deployment JsonURL = ClassLoader.getSystemClassLoader().getResource(deploymentPath); @@ -295,7 +285,7 @@ void testCreateDeploymentWithNoStream() throws IOException { PipelineEntity pipeline = mapper.readValue(responsePipeline.body().asString(), PipelineEntity.class); - pipelineId = pipeline.getId(); + UUID pipelineId = pipeline.getId(); // add deployment JsonURL = ClassLoader.getSystemClassLoader().getResource(deploymentPath); @@ -345,7 +335,7 @@ void testCreateDeploymentWithNoStreamOut() throws IOException { PipelineEntity pipeline = mapper.readValue(responsePipeline.body().asString(), PipelineEntity.class); - pipelineId = pipeline.getId(); + UUID pipelineId = pipeline.getId(); // add deployment JsonURL = ClassLoader.getSystemClassLoader().getResource(deploymentPath); @@ -365,7 +355,7 @@ void testCreateDeploymentWithNoStreamOut() throws IOException { } @Test - @Order(13) + @Order(14) void testCreateDeploymentWithEmptySpec() throws IOException { String deploymentPath = "deploymentTests/deployment_with_empty_spec.json"; @@ -384,4 +374,43 @@ void testCreateDeploymentWithEmptySpec() throws IOException { Assertions.assertEquals(400, responseDeployment.getStatusCode()); } + + @Test + @Order(15) + void testCreateDeploymentWithCustomReplicas() throws IOException { + String deploymentPath = "deploymentTests/deployment_with_custom_replicas.json"; + + // add deployment + URL JsonURL = ClassLoader.getSystemClassLoader().getResource(deploymentPath); + ObjectMapper mapper = new JsonMapper(); + JsonNode json = mapper.readTree(JsonURL); + String jsonString = json.toString(); + jsonString = jsonString.replace(pipelineUUIDPlaceholder, pipelineId.toString()); + + Response responseDeployment = + given() + .contentType(ContentType.JSON) + .baseUri(baseURI) + .body(jsonString) + .post(deploymentsPath); + + DeploymentEntity deployment = + mapper.readValue(responseDeployment.body().asString(), DeploymentEntity.class); + + String k8DeploymentString = + mockServer + .getClient() + .apps() + .deployments() + .inAnyNamespace() + .withLabel("datacater.io/uuid", deployment.getId().toString()) + .list() + .toString(); + + LOGGER.info( + "testCreateDeploymentWithCustomReplicas response: " + responseDeployment.body().asString()); + + Assertions.assertEquals(200, responseDeployment.getStatusCode()); + Assertions.assertTrue(k8DeploymentString.contains("replicas=3")); + } } diff --git a/platform-api/src/test/java/io/datacater/core/deployment/K8DeploymentTest.java b/platform-api/src/test/java/io/datacater/core/deployment/K8DeploymentTest.java index 3c88612d..b874a7ef 100644 --- a/platform-api/src/test/java/io/datacater/core/deployment/K8DeploymentTest.java +++ b/platform-api/src/test/java/io/datacater/core/deployment/K8DeploymentTest.java @@ -107,13 +107,6 @@ void testCreateDeployment() Assertions.assertEquals(true, getDeploymentExistsMethod().invoke(k8Deployment, deploymentId)); } - @Test - @Order(3) - void testGetLogs() { - String logs = k8Deployment.getLogs(deploymentId); - Assertions.assertNotNull(logs); - } - private Method getConfigMapExistsMethod() throws NoSuchMethodException { final String methodName = "exists"; Method method = K8ConfigMap.class.getDeclaredMethod(methodName, String.class); diff --git a/platform-api/src/test/resources/deploymentTests/deployment_with_custom_replicas.json b/platform-api/src/test/resources/deploymentTests/deployment_with_custom_replicas.json new file mode 100644 index 00000000..90538c3b --- /dev/null +++ b/platform-api/src/test/resources/deploymentTests/deployment_with_custom_replicas.json @@ -0,0 +1,6 @@ +{ + "spec": { + "pipeline": "pipelineUUIDPlaceholder", + "replicas": 3 + } +}