io.javaoperatorsdk.operator.sample.WebappReconciler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sample-tomcat-operator Show documentation
Show all versions of sample-tomcat-operator Show documentation
Provisions Tomcat Pods and deploys Webapplications in them
package io.javaoperatorsdk.operator.sample;
import java.io.ByteArrayOutputStream;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.ExecListener;
import io.fabric8.kubernetes.client.dsl.ExecWatch;
import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Cleaner;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.DeleteControl;
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import io.javaoperatorsdk.operator.processing.event.ResourceID;
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper;
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
@ControllerConfiguration
public class WebappReconciler
implements Reconciler, Cleaner, EventSourceInitializer {
private static final Logger log = LoggerFactory.getLogger(WebappReconciler.class);
private final KubernetesClient kubernetesClient;
public WebappReconciler(KubernetesClient kubernetesClient) {
this.kubernetesClient = kubernetesClient;
}
@Override
public Map prepareEventSources(EventSourceContext context) {
/*
* To create an event to a related WebApp resource and trigger the reconciliation we need to
* find which WebApp this Tomcat custom resource is related to. To find the related
* customResourceId of the WebApp resource we traverse the cache and identify it based on naming
* convention.
*/
final SecondaryToPrimaryMapper webappsMatchingTomcatName =
(Tomcat t) -> context.getPrimaryCache()
.list(webApp -> webApp.getSpec().getTomcat().equals(t.getMetadata().getName()))
.map(ResourceID::fromResource)
.collect(Collectors.toSet());
InformerConfiguration configuration =
InformerConfiguration.from(Tomcat.class, context)
.withSecondaryToPrimaryMapper(webappsMatchingTomcatName)
.build();
return EventSourceInitializer
.nameEventSources(new InformerEventSource<>(configuration, context));
}
/**
* This method will be called not only on changes to Webapp objects but also when Tomcat objects
* change.
*/
@Override
public UpdateControl reconcile(Webapp webapp, Context context) {
if (webapp.getStatus() != null
&& Objects.equals(webapp.getSpec().getUrl(), webapp.getStatus().getDeployedArtifact())) {
return UpdateControl.noUpdate();
}
Tomcat tomcat = context.getSecondaryResource(Tomcat.class)
.orElseThrow(
() -> new IllegalStateException("Cannot find Tomcat " + webapp.getSpec().getTomcat()
+ " for Webapp " + webapp.getMetadata().getName() + " in namespace "
+ webapp.getMetadata().getNamespace()));
if (tomcat.getStatus() != null
&& Objects.equals(tomcat.getSpec().getReplicas(), tomcat.getStatus().getReadyReplicas())) {
log.info(
"Tomcat is ready and webapps not yet deployed. Commencing deployment of {} in Tomcat {}",
webapp.getMetadata().getName(), tomcat.getMetadata().getName());
String[] command = new String[] {"wget", "-O",
"/data/" + webapp.getSpec().getContextPath() + ".war", webapp.getSpec().getUrl()};
if (log.isInfoEnabled()) {
command = new String[] {"time", "wget", "-O",
"/data/" + webapp.getSpec().getContextPath() + ".war", webapp.getSpec().getUrl()};
}
String[] commandStatusInAllPods = executeCommandInAllPods(kubernetesClient, webapp, command);
if (webapp.getStatus() == null) {
webapp.setStatus(new WebappStatus());
}
webapp.getStatus().setDeployedArtifact(webapp.getSpec().getUrl());
webapp.getStatus().setDeploymentStatus(commandStatusInAllPods);
return UpdateControl.patchStatus(webapp);
} else {
log.info("WebappController invoked but Tomcat not ready yet ({}/{})",
tomcat.getStatus() != null ? tomcat.getStatus().getReadyReplicas() : 0,
tomcat.getSpec().getReplicas());
return UpdateControl.noUpdate();
}
}
@Override
public DeleteControl cleanup(Webapp webapp, Context context) {
String[] command = new String[] {"rm", "/data/" + webapp.getSpec().getContextPath() + ".war"};
String[] commandStatusInAllPods = executeCommandInAllPods(kubernetesClient, webapp, command);
if (webapp.getStatus() != null) {
webapp.getStatus().setDeployedArtifact(null);
webapp.getStatus().setDeploymentStatus(commandStatusInAllPods);
}
return DeleteControl.defaultDelete();
}
private String[] executeCommandInAllPods(
KubernetesClient kubernetesClient, Webapp webapp, String[] command) {
String[] status = new String[0];
Deployment deployment =
kubernetesClient
.apps()
.deployments()
.inNamespace(webapp.getMetadata().getNamespace())
.withName(webapp.getSpec().getTomcat())
.get();
if (deployment != null) {
List pods =
kubernetesClient
.pods()
.inNamespace(webapp.getMetadata().getNamespace())
.withLabels(deployment.getSpec().getSelector().getMatchLabels())
.list()
.getItems();
status = new String[pods.size()];
for (int i = 0; i < pods.size(); i++) {
Pod pod = pods.get(i);
log.info(
"Executing command {} in Pod {}",
String.join(" ", command),
pod.getMetadata().getName());
CompletableFuture data = new CompletableFuture<>();
try (ExecWatch execWatch = execCmd(pod, data, command)) {
status[i] = "" + pod.getMetadata().getName() + ":" + data.get(30, TimeUnit.SECONDS);
} catch (ExecutionException e) {
status[i] = "" + pod.getMetadata().getName() + ": ExecutionException - " + e.getMessage();
} catch (InterruptedException e) {
status[i] =
"" + pod.getMetadata().getName() + ": InterruptedException - " + e.getMessage();
} catch (TimeoutException e) {
status[i] = "" + pod.getMetadata().getName() + ": TimeoutException - " + e.getMessage();
}
}
}
return status;
}
private ExecWatch execCmd(Pod pod, CompletableFuture data, String... command) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
return kubernetesClient.pods()
.inNamespace(pod.getMetadata().getNamespace())
.withName(pod.getMetadata().getName())
.inContainer("war-downloader")
.writingOutput(baos)
.writingError(baos)
.usingListener(new SimpleListener(data, baos))
.exec(command);
}
static class SimpleListener implements ExecListener {
private final CompletableFuture data;
private final ByteArrayOutputStream baos;
private final Logger log = LoggerFactory.getLogger(getClass());
public SimpleListener(CompletableFuture data, ByteArrayOutputStream baos) {
this.data = data;
this.baos = baos;
}
@Override
public void onOpen() {
log.debug("Reading data... ");
}
@Override
public void onFailure(Throwable t, Response response) {
log.debug(t.getMessage());
data.completeExceptionally(t);
}
@Override
public void onClose(int code, String reason) {
log.debug("Exit with: {} and with reason: {}", code, reason);
data.complete(baos.toString());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy