Fix bug with timeouts

This commit is contained in:
andreas.dinauer 2025-10-30 20:56:13 +01:00
parent b99cbe1caa
commit ac3c75dbdf
14 changed files with 417 additions and 110 deletions

View File

@ -1,51 +0,0 @@
package dev.dinauer;
import dev.dinauer.service.DeploymentService;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.quarkus.security.Authenticated;
import io.smallrye.common.annotation.Blocking;
import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import java.util.List;
@Path("/deployments")
@Blocking
@Authenticated
@ApplicationScoped
public class DeploymentResource
{
@Inject
DeploymentService deploymentService;
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Deployment> getDeployments(@QueryParam("namespace") String namespace)
{
if(namespace != null && !namespace.isBlank())
{
return deploymentService.findByNamespace(namespace);
}
return deploymentService.findAll();
}
@PATCH
@Path("/{namespace}/{name}")
@Consumes(MediaType.TEXT_PLAIN)
@Produces
@RolesAllowed("admin")
public void rescaleDeployment(@PathParam("namespace") String namespace, @PathParam("name") String name, Integer replicaCount)
{
if(namespace != null && !namespace.isBlank() && name != null && !name.isBlank() && replicaCount != null && replicaCount > 0)
{
deploymentService.rescale(namespace, name, replicaCount);
}
else
{
throw new RuntimeException("Namespace, name cannot be null or empty. Replica count cannot be null and must be greater than 0.");
}
}
}

View File

@ -3,13 +3,9 @@ package dev.dinauer;
import dev.dinauer.service.PodService;
import dev.dinauer.utils.ClientProvider;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.quarkus.runtime.Startup;
import io.quarkus.security.Authenticated;
import io.smallrye.common.annotation.Blocking;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ -19,7 +15,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Optional;
@Path("/pods")
@Startup
@ -28,41 +23,16 @@ import java.util.Optional;
@Authenticated
public class PodResource
{
private static final Logger LOG = LoggerFactory.getLogger(PodResource.class);
@Inject
ClientProvider clientProvider;
@Inject
PodService podService;
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Pod> getPods(@QueryParam("namespace") String namespace)
{
if(namespace != null && !namespace.isBlank())
{
return podService.findByNamespace(namespace);
}
return podService.findAll();
}
@DELETE
@Produces
@Consumes
@Path("/{pod-id}")
@Path("/{namespace}/{name}")
@RolesAllowed({"admin", "maintainer"})
public void deletePod(@PathParam("pod-id") String id)
public void getEnv()
{
Optional<Pod> podOptional = podService.findPodById(id);
if(podOptional.isPresent())
{
Pod pod = podOptional.get();
clientProvider.getClient()
.pods()
.inNamespace(pod.getMetadata().getNamespace())
.withName(pod.getMetadata().getName())
.delete();
}
}
}

View File

@ -9,6 +9,7 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ApplicationScoped
@ -34,11 +35,20 @@ public class ProcessRunner
text.add(line);
}
}
int exitCode = p.waitFor();
boolean endedInTime = p.waitFor(10, TimeUnit.SECONDS);
p.destroy();
if (endedInTime)
{
int exitCode = p.exitValue();
if(exitCode == 0)
{
return String.join("\n", text);
}
}
else
{
LOG.error("Process did not end in time.");
}
throw new RuntimeException("Error executing command: " + command);
}
}

View File

@ -0,0 +1,132 @@
package dev.dinauer;
import dev.dinauer.service.*;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.common.NotImplementedYet;
import java.util.List;
import java.util.Optional;
@Path("/resources/{resource}")
public class ResourceResource
{
@Inject
Logger LOG;
@Inject
StatefulSetService statefulSetService;
@Inject
DeploymentService deploymentService;
@Inject
PodService podService;
@Inject
CustomResourceDefinitionService customResourceDefinitionService;
@Inject
IngressService ingressService;
@Inject
ServiceService serviceService;
@Inject
NodeService nodeService;
@GET
public List<?> get(@PathParam("resource") String resourceType)
{
return getService(resourceType).findAll();
}
@GET
@Path("/{namespace}")
public List<?> getByNamespace(@PathParam("resource") String resourceType, @PathParam("namespace") String namespace)
{
return getService(resourceType).findByNamespace(namespace);
}
@GET
@Path("/{namespace}/{name}")
public Object getByNamespaceAndName(@PathParam("resource") String resourceType, @PathParam("namespace") String namespace, @PathParam("name") String name)
{
Optional<?> resourceOptional = getService(resourceType).findOptionalByNameAndNamespace(name, namespace);
if (resourceOptional.isPresent())
{
return resourceOptional.get();
}
throw new NotFoundException();
}
@DELETE
@Path("/{namespace}/{name}")
public void deleteByNamespaceAndName(@PathParam("resource") String resourceType, @PathParam("namespace") String namespace, @PathParam("name") String name)
{
getService(resourceType).delete(name, namespace);
}
@PATCH
@Path("/{namespace}/{name}")
@Consumes(MediaType.TEXT_PLAIN)
@Produces
@RolesAllowed("admin")
public void rescaleDeployment(@PathParam("resource") String resourceType, @PathParam("namespace") String namespace, @PathParam("name") String name, Integer replicaCount)
{
if (ResourceType.DEPLOYMENT.equals(resourceType))
{
if(namespace != null && !namespace.isBlank() && name != null && !name.isBlank() && replicaCount != null && replicaCount > 0)
{
deploymentService.rescale(namespace, name, replicaCount);
}
}
else
{
throw new RuntimeException("Must be a deployment or namespace or name cannot be null or empty. Replica count cannot be null and must be greater than 0.");
}
}
private ResourceService<?> getService(String resourceType)
{
switch (resourceType)
{
case ResourceType.STATEFUL_SET ->
{
return statefulSetService;
}
case ResourceType.DEPLOYMENT ->
{
return deploymentService;
}
case ResourceType.SERVICE ->
{
return serviceService;
}
case ResourceType.INGRESS ->
{
return ingressService;
}
case ResourceType.POD ->
{
return podService;
}
case ResourceType.CUSTOM_RESOURCE_DEFINITION ->
{
return customResourceDefinitionService;
}
case ResourceType.NODE ->
{
return nodeService;
}
default ->
{
LOG.errorf("Invalid resource type %s.", resourceType);
throw new BadRequestException();
}
}
}
}

View File

@ -0,0 +1,12 @@
package dev.dinauer;
public class ResourceType
{
public static final String STATEFUL_SET = "stateful-sets";
public static final String DEPLOYMENT = "deployments";
public static final String SERVICE = "services";
public static final String INGRESS = "ingresses";
public static final String POD = "pods";
public static final String CUSTOM_RESOURCE_DEFINITION = "custom-resource-definitions";
public static final String NODE = "nodes";
}

View File

@ -3,6 +3,7 @@ package dev.dinauer.monitoring.nodes;
import dev.dinauer.ProcessRunner;
import dev.dinauer.monitoring.indexing.IndexingService;
import dev.dinauer.utils.ClientProvider;
import io.smallrye.common.annotation.Blocking;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.io.IOException;

View File

@ -0,0 +1,51 @@
package dev.dinauer.service;
import dev.dinauer.utils.ClientProvider;
import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition;
import io.fabric8.kubernetes.client.dsl.ApiextensionsAPIGroupDSL;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.resteasy.reactive.common.NotImplementedYet;
import java.util.List;
import java.util.Optional;
@ApplicationScoped
public class CustomResourceDefinitionService implements ResourceService<CustomResourceDefinition>
{
@Inject
ClientProvider clientProvider;
@Override
public void delete(String name, String namespace)
{
throw new NotImplementedYet();
}
@Override
public CustomResourceDefinition findByNameAndNamespace(String name, String namespace)
{
throw new UnsupportedOperationException();
}
@Override
public List<CustomResourceDefinition> findByNamespace(String namespace)
{
throw new UnsupportedOperationException();
}
@Override
public List<CustomResourceDefinition> findAll()
{
try (ApiextensionsAPIGroupDSL apiextensions = clientProvider.getClient().apiextensions())
{
return apiextensions.v1().customResourceDefinitions().list().getItems();
}
}
@Override
public Optional<CustomResourceDefinition> findOptionalByNameAndNamespace(String name, String namespace)
{
throw new UnsupportedOperationException();
}
}

View File

@ -1,21 +1,34 @@
package dev.dinauer.service;
import dev.dinauer.utils.ClientProvider;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.client.dsl.AppsAPIGroupDSL;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.resteasy.reactive.common.NotImplementedYet;
import java.util.List;
import java.util.Optional;
@ApplicationScoped
public class DeploymentService
public class DeploymentService implements ResourceService<Deployment>
{
@Inject
ClientProvider clientProvider;
@Override
public void delete(String name, String namespace)
{
throw new NotImplementedYet();
}
@Override
public Deployment findByNameAndNamespace(String name, String namespace)
{
return null;
}
@Override
public List<Deployment> findByNamespace(String namespace)
{
try(AppsAPIGroupDSL dsl = clientProvider.getClient().apps())
@ -32,6 +45,12 @@ public class DeploymentService
}
}
@Override
public Optional<Deployment> findOptionalByNameAndNamespace(String name, String namespace)
{
return Optional.empty();
}
public void rescale(String namespace, String name, int replicaCount)
{
try(AppsAPIGroupDSL dsl = clientProvider.getClient().apps())

View File

@ -1,21 +1,48 @@
package dev.dinauer.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.dinauer.utils.ClientProvider;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
import io.fabric8.kubernetes.client.dsl.NetworkAPIGroupDSL;
import io.fabric8.kubernetes.client.impl.NetworkAPIGroupClient;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.NotFoundException;
import org.jboss.resteasy.reactive.common.NotImplementedYet;
import java.util.List;
import java.util.Optional;
@ApplicationScoped
public class IngressService
public class IngressService implements ResourceService<Ingress>
{
@Inject
ClientProvider clientProvider;
@Override
public void delete(String name, String namespace)
{
throw new NotImplementedYet();
}
@Override
public Ingress findByNameAndNamespace(String name, String namespace)
{
try(NetworkAPIGroupDSL dsl = clientProvider.getClient().network())
{
List<Ingress> ingresses = dsl.v1().ingresses().list().getItems();
for (Ingress ingress : ingresses)
{
ObjectMeta meta = ingress.getMetadata();
if (name.equals(meta.getName()) && namespace.equals(meta.getNamespace()))
{
return ingress;
}
}
return null;
}
}
public List<Ingress> findByNamespace(String namespace)
{
try(NetworkAPIGroupDSL dsl = clientProvider.getClient().network())
@ -31,4 +58,10 @@ public class IngressService
return dsl.v1().ingresses().inAnyNamespace().list().getItems();
}
}
@Override
public Optional<Ingress> findOptionalByNameAndNamespace(String name, String namespace)
{
return Optional.ofNullable(findByNameAndNamespace(name, namespace));
}
}

View File

@ -1,19 +1,15 @@
package dev.dinauer;
package dev.dinauer.service;
import dev.dinauer.monitoring.nodes.NodeStats;
import dev.dinauer.utils.ClientProvider;
import io.fabric8.kubernetes.api.model.Node;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.quarkus.security.Authenticated;
import io.smallrye.common.annotation.Blocking;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.jboss.resteasy.reactive.common.NotImplementedYet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -22,25 +18,36 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Path("/nodes")
@ApplicationScoped
@Blocking
@Authenticated
public class NodeResource
public class NodeService implements ResourceService<NodeStats>
{
private static final Logger LOG = LoggerFactory.getLogger(NodeResource.class);
private static final Logger LOG = LoggerFactory.getLogger(NodeService.class);
@Inject
ClientProvider clientProvider;
@GET
@Blocking
@Produces(MediaType.APPLICATION_JSON)
public List<NodeStats> getMonitoring() throws IOException, InterruptedException
public List<NodeStats> findAll()
{
List<NodeStats> result = new ArrayList<>();
List<String> stats = getTopNodes();
List<String> stats = null;
try
{
stats = getTopNodes();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
catch (InterruptedException e)
{
throw new RuntimeException(e);
}
for(String nodeName : stats)
{
@ -105,4 +112,28 @@ public class NodeResource
}
return Integer.parseInt(input);
}
@Override
public void delete(String name, String namespace)
{
throw new UnsupportedOperationException();
}
@Override
public NodeStats findByNameAndNamespace(String name, String namespace)
{
throw new NotImplementedYet();
}
@Override
public List<NodeStats> findByNamespace(String namespace)
{
throw new NotImplementedYet();
}
@Override
public Optional<NodeStats> findOptionalByNameAndNamespace(String name, String namespace)
{
throw new NotImplementedYet();
}
}

View File

@ -3,10 +3,7 @@ package dev.dinauer.service;
import dev.dinauer.utils.ClientProvider;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.kubernetes.client.dsl.AppsAPIGroupDSL;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ -15,11 +12,32 @@ import java.util.Map;
import java.util.Optional;
@ApplicationScoped
public class PodService
public class PodService implements ResourceService<Pod>
{
@Inject
ClientProvider clientProvider;
@Override
public void delete(String name, String namespace)
{
Optional<Pod> podOptional = findOptionalByNameAndNamespace(name, namespace);
if(podOptional.isPresent())
{
Pod pod = podOptional.get();
clientProvider.getClient()
.pods()
.inNamespace(pod.getMetadata().getNamespace())
.withName(pod.getMetadata().getName())
.delete();
}
}
@Override
public Pod findByNameAndNamespace(String name, String namespace)
{
return clientProvider.getClient().pods().inNamespace(namespace).withName(name).get();
}
public List<Pod> findByNamespace(String namespace)
{
return clientProvider.getClient().pods().inNamespace(namespace).list().getItems();
@ -48,6 +66,12 @@ public class PodService
return clientProvider.getClient().pods().inAnyNamespace().list().getItems();
}
@Override
public Optional<Pod> findOptionalByNameAndNamespace(String name, String namespace)
{
return Optional.ofNullable(findByNameAndNamespace(name, namespace));
}
public Optional<Pod> findPodById(String id)
{
for(Pod pod : clientProvider.getClient().pods().inAnyNamespace().list().getItems())

View File

@ -0,0 +1,13 @@
package dev.dinauer.service;
import java.util.List;
import java.util.Optional;
public interface ResourceService<T>
{
void delete(String name, String namespace);
T findByNameAndNamespace(String name, String namespace);
List<T> findByNamespace(String namespace);
List<T> findAll();
Optional<T> findOptionalByNameAndNamespace(String name, String namespace);
}

View File

@ -8,16 +8,29 @@ import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import jakarta.annotation.PostConstruct;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.resteasy.reactive.common.NotImplementedYet;
import java.util.List;
import java.util.Optional;
@ApplicationScoped
public class ServiceService
public class ServiceService implements ResourceService<Service>
{
@Inject
ClientProvider clientProvider;
@Override
public void delete(String name, String namespace)
{
throw new NotImplementedYet();
}
@Override
public Service findByNameAndNamespace(String name, String namespace)
{
return clientProvider.getClient().services().inNamespace(namespace).withName(name).get();
}
public List<Service> findByNamespace(String namespace)
{
return clientProvider.getClient().services().inNamespace(namespace).list().getItems();
@ -28,6 +41,12 @@ public class ServiceService
return clientProvider.getClient().services().inAnyNamespace().list().getItems();
}
@Override
public Optional<Service> findOptionalByNameAndNamespace(String name, String namespace)
{
return Optional.ofNullable(findByNameAndNamespace(name, namespace));
}
public Optional<Service> findById(String id)
{
for(Service service : clientProvider.getClient().services().list().getItems())

View File

@ -0,0 +1,43 @@
package dev.dinauer.service;
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
import jakarta.enterprise.context.ApplicationScoped;
import org.jboss.resteasy.reactive.common.NotImplementedYet;
import java.util.List;
import java.util.Optional;
@ApplicationScoped
public class StatefulSetService implements ResourceService<StatefulSet>
{
@Override
public void delete(String name, String namespace)
{
throw new NotImplementedYet();
}
@Override
public StatefulSet findByNameAndNamespace(String name, String namespace)
{
return null;
}
@Override
public List<StatefulSet> findByNamespace(String namespace)
{
return List.of();
}
@Override
public List<StatefulSet> findAll()
{
return List.of();
}
@Override
public Optional<StatefulSet> findOptionalByNameAndNamespace(String name, String namespace)
{
return Optional.empty();
}
}