From 231608ddbf918d1cf68d986975cb05b5377f92af Mon Sep 17 00:00:00 2001 From: Adam Sandor <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 21 Oct 2021 16:43:35 +0200 Subject: [PATCH 01/54] initial commit for moving tomcat sample back to main repo --- .github/workflows/sample-operator-tests.yml | 84 ++++++++ sample-operators/tomcat-operator/README.md | 67 +++++++ sample-operators/tomcat-operator/k8s/crd.yaml | 96 +++++++++ .../tomcat-operator/k8s/operator.yaml | 91 +++++++++ .../tomcat-operator/k8s/tomcat-sample1.yaml | 7 + .../tomcat-operator/k8s/tomcat-sample2.yaml | 7 + .../tomcat-operator/k8s/webapp-sample1.yaml | 8 + .../tomcat-operator/k8s/webapp-sample2.yaml | 8 + sample-operators/tomcat-operator/pom.xml | 72 +++++++ .../operator/sample/DeploymentEvent.java | 51 +++++ .../sample/DeploymentEventSource.java | 77 ++++++++ .../operator/sample/Tomcat.java | 10 + .../operator/sample/TomcatController.java | 186 ++++++++++++++++++ .../operator/sample/TomcatEvent.java | 49 +++++ .../operator/sample/TomcatEventSource.java | 80 ++++++++ .../operator/sample/TomcatOperator.java | 35 ++++ .../operator/sample/TomcatSpec.java | 23 +++ .../operator/sample/TomcatStatus.java | 14 ++ .../operator/sample/Webapp.java | 13 ++ .../operator/sample/WebappController.java | 173 ++++++++++++++++ .../operator/sample/WebappSpec.java | 34 ++++ .../operator/sample/WebappStatus.java | 24 +++ .../operator/sample/deployment.yaml | 39 ++++ .../operator/sample/service.yaml | 12 ++ .../src/main/resources/log4j2.xml | 13 ++ .../operator/sample/IntegrationTest.java | 122 ++++++++++++ .../src/test/resources/log4j2.xml | 13 ++ 27 files changed, 1408 insertions(+) create mode 100644 .github/workflows/sample-operator-tests.yml create mode 100644 sample-operators/tomcat-operator/README.md create mode 100644 sample-operators/tomcat-operator/k8s/crd.yaml create mode 100644 sample-operators/tomcat-operator/k8s/operator.yaml create mode 100644 sample-operators/tomcat-operator/k8s/tomcat-sample1.yaml create mode 100644 sample-operators/tomcat-operator/k8s/tomcat-sample2.yaml create mode 100644 sample-operators/tomcat-operator/k8s/webapp-sample1.yaml create mode 100644 sample-operators/tomcat-operator/k8s/webapp-sample2.yaml create mode 100644 sample-operators/tomcat-operator/pom.xml create mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEvent.java create mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEventSource.java create mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java create mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java create mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEvent.java create mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEventSource.java create mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatOperator.java create mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatSpec.java create mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatStatus.java create mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Webapp.java create mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java create mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappSpec.java create mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappStatus.java create mode 100644 sample-operators/tomcat-operator/src/main/resources/io/javaoperatorsdk/operator/sample/deployment.yaml create mode 100644 sample-operators/tomcat-operator/src/main/resources/io/javaoperatorsdk/operator/sample/service.yaml create mode 100644 sample-operators/tomcat-operator/src/main/resources/log4j2.xml create mode 100644 sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/IntegrationTest.java create mode 100644 sample-operators/tomcat-operator/src/test/resources/log4j2.xml diff --git a/.github/workflows/sample-operator-tests.yml b/.github/workflows/sample-operator-tests.yml new file mode 100644 index 0000000000..5d9839ea80 --- /dev/null +++ b/.github/workflows/sample-operator-tests.yml @@ -0,0 +1,84 @@ +# End to end integration test which deploys the Tomcat operator to a Kubernetes +# (Kind) cluster and creates custom resources to verify the operator's functionality +name: Tomcat integration test +on: + push: + branches: + - "*" +jobs: + tomcat_integration_test: + runs-on: ubuntu-latest + env: + KIND_CL_NAME: tomcat-integration-test + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: clean resident local docker + if: ${{ env.ACT }} + continue-on-error: true + run: | + for DIMG in "$KIND_CL_NAME-control-plane "; do + docker stop $DIMG ; docker rm $DIMG ; + done ; + sleep 1 + + - name: Create Kubernetes KinD Cluster + uses: container-tools/kind-action@v1.5.0 + with: + cluster_name: tomcat-integration-test + registry: false + + - name: Apply CRDs + run: kubectl apply -f k8s/crd.yaml + working-directory: sample-operators/tomcat-operator + + - name: Set up Java and Maven + uses: actions/setup-java@v2 + with: + java-version: 15 + distribution: adopt-hotspot + + - name: cache + uses: actions/cache@v2 + if: ${{ !env.ACT }} + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven-m2 + + - name: Set up Maven + uses: stCarolas/setup-maven@v4 + if: ${{ env.ACT }} + with: + maven-version: 3.8.1 + + - name: build jib + working-directory: sample-operators/tomcat-operator + run: | + mvn --version + mvn -B package jib:dockerBuild jib:buildTar -Djib-maven-image=tomcat-operator --file tomcat/pom.xml -DskipTests + kind load image-archive tomcat/target/jib-image.tar --name=${{ env.KIND_CL_NAME }} + + - name: Apply CRDs + working-directory: sample-operators/tomcat-operator + run: kubectl apply -f k8s/crd.yaml + + - name: install tomcat operator + working-directory: sample-operators/tomcat-operator + run: | + kubectl apply -f k8s/operator.yaml + + - name: create ns tomcatoperator-sample + run: kubectl create ns tomcatoperator-sample + + - name: Run unit tests + working-directory: sample-operators/tomcat-operator + run: mvn -B test -q --file tomcat/pom.xml + + - name: Dump state + if: ${{ failure() }} + run: | + kubectl get all -n tomcat-test -o yaml + kubectl logs curl -n tomcat-test \ No newline at end of file diff --git a/sample-operators/tomcat-operator/README.md b/sample-operators/tomcat-operator/README.md new file mode 100644 index 0000000000..6fc793198c --- /dev/null +++ b/sample-operators/tomcat-operator/README.md @@ -0,0 +1,67 @@ +# Tomcat Operator + +Creates a Tomcat deployment from a Custom Resource, while keeping the WAR separated with another Custom Resource. + +This sample demonstrates the following capabilities of the Java Operator SDK: +* Multiple Controllers in a single Operator. The Tomcat resource is managed by the TomcatController while the Webapp +resource is managed by the WebappController. +* Reacting to events about resources created by the controller. The TomcatController will receive events about the +Deployment resources it created. See EventSource section below for more detail. + +## Example input for creating a Tomcat instance +``` +apiVersion: "tomcatoperator.io/v1" +kind: Tomcat +metadata: + name: test-tomcat1 +spec: + version: 9.0 + replicas: 2 +``` + +## Example input for the Webapp +``` +apiVersion: "tomcatoperator.io/v1" +kind: Webapp +metadata: + name: sample-webapp1 +spec: + tomcat: test-tomcat1 + url: http://tomcat.apache.org/tomcat-7.0-doc/appdev/sample/sample.war + contextPath: mysample +``` + +## Getting started / Testing + +The quickest way to try the operator is to run it on your local machine, while it connects to a local or remote +Kubernetes cluster. When you start it, it will use the current kubectl context on your machine to connect to the cluster. + +Before you run it you have to install the CRD on your cluster by running `kubectl apply -f k8s/crd.yaml`. + +When the Operator is running you can create some Tomcat Custom Resources. You can find a sample custom resources +in the k8s folder. + +If you want the Operator to be running as a deployment in your cluster, follow the below steps. + +## Build +You can build the sample using `mvn install jib:dockerBuild` this will produce a Docker image you can push to the +registry of your choice. The JAR file is built using your local Maven and JDK and then copied into the Docker image. + +## Install Operator into cluster + +Run `kubectl apply -f k8s/crd.yaml` if you haven't already, then run `kubectl apply -f k8s/operator.yaml`. +Now you can create Tomcat instances with CRs (see examples above). + +## EventSources +The TomcatController is listening to events about Deployments created by the TomcatOperator by registering a +DeploymentEventSource with the EventSourceManager. The DeploymentEventSource will in turn register a watch on +all Deployments managed by the Controller (identified by the `app.kubernetes.io/managed-by` label). +When an event from a Deployment is received we have to identify which Tomcat object does the Deployment +belong to. This is done when the DeploymentEventSource creates the DeploymentEvent. + +The TomcatController has to take care of setting the `app.kubernetes.io/managed-by` label on the Deployment so the +DeploymentEventSource can watch the right Deployments. +The TomcatController also has to set `ownerReference` on the Deployment so later the DeploymentEventSource can +identify which Tomcat does the Deployment belong to. This is necessary so the frameowork can call the Controller +`createOrUpdate` method correctly. + diff --git a/sample-operators/tomcat-operator/k8s/crd.yaml b/sample-operators/tomcat-operator/k8s/crd.yaml new file mode 100644 index 0000000000..509c77b542 --- /dev/null +++ b/sample-operators/tomcat-operator/k8s/crd.yaml @@ -0,0 +1,96 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + # name must match the spec fields below, and be in the form: . + name: tomcats.tomcatoperator.io +spec: + # group name to use for REST API: /apis// + group: tomcatoperator.io + # list of versions supported by this CustomResourceDefinition + versions: + - name: v1 + # Each version can be enabled/disabled by Served flag. + served: true + # One and only one version must be marked as the storage version. + storage: true + subresources: + status: { } + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + version: + type: integer + replicas: + type: integer + status: + type: object + properties: + readyReplicas: + type: integer + required: [spec] + # either Namespaced or Cluster + scope: Namespaced + names: + # plural name to be used in the URL: /apis/// + plural: tomcats + # singular name to be used as an alias on the CLI and for display + singular: tomcat + # kind is normally the CamelCased singular type. Your resource manifests use this. + kind: Tomcat + # shortNames allow shorter string to match your resource on the CLI + shortNames: + - tc +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + # name must match the spec fields below, and be in the form: . + name: webapps.tomcatoperator.io +spec: + # group name to use for REST API: /apis// + group: tomcatoperator.io + # list of versions supported by this CustomResourceDefinition + versions: + - name: v1 + # Each version can be enabled/disabled by Served flag. + served: true + # One and only one version must be marked as the storage version. + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + tomcat: + type: string + url: + type: string + contextPath: + type: string + status: + type: object + properties: + deployedArtifact: + type: string + deploymentStatus: + type: array + items: + type: string + required: [spec] + # either Namespaced or Cluster + scope: Namespaced + names: + # plural name to be used in the URL: /apis/// + plural: webapps + # singular name to be used as an alias on the CLI and for display + singular: webapp + # kind is normally the CamelCased singular type. Your resource manifests use this. + kind: Webapp \ No newline at end of file diff --git a/sample-operators/tomcat-operator/k8s/operator.yaml b/sample-operators/tomcat-operator/k8s/operator.yaml new file mode 100644 index 0000000000..a88b6514e0 --- /dev/null +++ b/sample-operators/tomcat-operator/k8s/operator.yaml @@ -0,0 +1,91 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: tomcat-operator + +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tomcat-operator + namespace: tomcat-operator + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: tomcat-operator + namespace: tomcat-operator +spec: + selector: + matchLabels: + app: tomcat-operator + template: + metadata: + labels: + app: tomcat-operator + spec: + serviceAccountName: tomcat-operator + containers: + - name: operator + image: tomcat-operator + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 1 + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 30 + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: tomcat-operator-admin +subjects: +- kind: ServiceAccount + name: tomcat-operator + namespace: tomcat-operator +roleRef: + kind: ClusterRole + name: tomcat-operator + apiGroup: "" + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: tomcat-operator +rules: +- apiGroups: + - "" + - "extensions" + - "apps" + resources: + - deployments + - services + - pods + - pods/exec + verbs: + - '*' +- apiGroups: + - "apiextensions.k8s.io" + resources: + - customresourcedefinitions + verbs: + - '*' +- apiGroups: + - "tomcatoperator.io" + resources: + - tomcats + - tomcats/status + - webapps + - webapps/status + verbs: + - '*' \ No newline at end of file diff --git a/sample-operators/tomcat-operator/k8s/tomcat-sample1.yaml b/sample-operators/tomcat-operator/k8s/tomcat-sample1.yaml new file mode 100644 index 0000000000..ddd30663cd --- /dev/null +++ b/sample-operators/tomcat-operator/k8s/tomcat-sample1.yaml @@ -0,0 +1,7 @@ +apiVersion: "tomcatoperator.io/v1" +kind: Tomcat +metadata: + name: test-tomcat1 +spec: + version: 9.0 + replicas: 2 diff --git a/sample-operators/tomcat-operator/k8s/tomcat-sample2.yaml b/sample-operators/tomcat-operator/k8s/tomcat-sample2.yaml new file mode 100644 index 0000000000..2dec431734 --- /dev/null +++ b/sample-operators/tomcat-operator/k8s/tomcat-sample2.yaml @@ -0,0 +1,7 @@ +apiVersion: "tomcatoperator.io/v1" +kind: Tomcat +metadata: + name: test-tomcat2 +spec: + version: 8.0 + replicas: 4 diff --git a/sample-operators/tomcat-operator/k8s/webapp-sample1.yaml b/sample-operators/tomcat-operator/k8s/webapp-sample1.yaml new file mode 100644 index 0000000000..4913eb2444 --- /dev/null +++ b/sample-operators/tomcat-operator/k8s/webapp-sample1.yaml @@ -0,0 +1,8 @@ +apiVersion: "tomcatoperator.io/v1" +kind: Webapp +metadata: + name: sample-webapp1 +spec: + tomcat: test-tomcat1 + url: http://tomcat.apache.org/tomcat-7.0-doc/appdev/sample/sample.war + contextPath: mysample diff --git a/sample-operators/tomcat-operator/k8s/webapp-sample2.yaml b/sample-operators/tomcat-operator/k8s/webapp-sample2.yaml new file mode 100644 index 0000000000..e0415f9ce5 --- /dev/null +++ b/sample-operators/tomcat-operator/k8s/webapp-sample2.yaml @@ -0,0 +1,8 @@ +apiVersion: "tomcatoperator.io/v1" +kind: Webapp +metadata: + name: sample-webapp2 +spec: + tomcat: test-tomcat2 + url: charlottemach.com/assets/jax.war + contextPath: othercontext diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml new file mode 100644 index 0000000000..49caa55378 --- /dev/null +++ b/sample-operators/tomcat-operator/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + tomcat + Operator SDK - Samples - Tomcat + Provisions Tomcat Pods and deploys Webapplications in them + 1.9.9-SNAPSHOT + jar + + + 11 + 11 + 3.1.4 + + + + + io.javaoperatorsdk + operator-framework + ${project.version} + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.13.3 + + + org.takes + takes + 1.19 + + + junit + junit + 4.13.1 + test + + + org.awaitility + awaitility + 4.1.0 + test + + + + + + + com.google.cloud.tools + jib-maven-plugin + ${jib-maven-plugin.version} + + + gcr.io/distroless/java:11 + + + tomcat-operator + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + + + \ No newline at end of file diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEvent.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEvent.java new file mode 100644 index 0000000000..d8e9190a98 --- /dev/null +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEvent.java @@ -0,0 +1,51 @@ +package io.javaoperatorsdk.operator.sample; + +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.client.Watcher; +import io.javaoperatorsdk.operator.processing.event.DefaultEvent; + +public class DeploymentEvent extends DefaultEvent { + + private final Watcher.Action action; + private final Deployment deployment; + + public DeploymentEvent( + Watcher.Action action, Deployment resource, DeploymentEventSource deploymentEventSource) { + // TODO: this mapping is really critical and should be made more explicit + super(resource.getMetadata().getOwnerReferences().get(0).getUid(), deploymentEventSource); + this.action = action; + this.deployment = resource; + } + + public Watcher.Action getAction() { + return action; + } + + public String resourceUid() { + return getDeployment().getMetadata().getUid(); + } + + @Override + public String toString() { + return "CustomResourceEvent{" + + "action=" + + action + + ", resource=[ name=" + + getDeployment().getMetadata().getName() + + ", kind=" + + getDeployment().getKind() + + ", apiVersion=" + + getDeployment().getApiVersion() + + " ,resourceVersion=" + + getDeployment().getMetadata().getResourceVersion() + + ", markedForDeletion: " + + (getDeployment().getMetadata().getDeletionTimestamp() != null + && !getDeployment().getMetadata().getDeletionTimestamp().isEmpty()) + + " ]" + + '}'; + } + + public Deployment getDeployment() { + return deployment; + } +} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEventSource.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEventSource.java new file mode 100644 index 0000000000..55a85c89b0 --- /dev/null +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEventSource.java @@ -0,0 +1,77 @@ +package io.javaoperatorsdk.operator.sample; + +import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID; +import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion; + +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.Watcher; +import io.fabric8.kubernetes.client.WatcherException; +import io.javaoperatorsdk.operator.processing.event.AbstractEventSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Used by the TomcatController to watch changes on Deployment objects. As the Pods of the Deployment start up + * the TomcatController updates the status.readyReplicas field. + */ +public class DeploymentEventSource extends AbstractEventSource implements Watcher { + private static final Logger log = LoggerFactory.getLogger(DeploymentEventSource.class); + + private final KubernetesClient client; + + public static DeploymentEventSource createAndRegisterWatch(KubernetesClient client) { + DeploymentEventSource deploymentEventSource = new DeploymentEventSource(client); + deploymentEventSource.registerWatch(); + return deploymentEventSource; + } + + private DeploymentEventSource(KubernetesClient client) { + this.client = client; + } + + private void registerWatch() { + client + .apps() + .deployments() + .inAnyNamespace() + .withLabel("app.kubernetes.io/managed-by", "tomcat-operator") + .watch(this); + } + + @Override + public void eventReceived(Action action, Deployment deployment) { + log.info( + "Event received for action: {}, Deployment: {} (rr={})", + action.name(), + deployment.getMetadata().getName(), + deployment.getStatus().getReadyReplicas()); + + if (action == Action.ERROR) { + log.warn( + "Skipping {} event for custom resource uid: {}, version: {}", + action, + getUID(deployment), + getVersion(deployment)); + return; + } + + eventHandler.handleEvent(new DeploymentEvent(action, deployment, this)); + } + + @Override + public void onClose(WatcherException e) { + if (e == null) { + return; + } + if (e.isHttpGone()) { + log.warn("Received error for watch, will try to reconnect.", e); + registerWatch(); + } else { + // Note that this should not happen normally, since fabric8 client handles reconnect. + // In case it tries to reconnect this method is not called. + log.error("Unexpected error happened with watch. Will exit.", e); + System.exit(1); + } + } +} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java new file mode 100644 index 0000000000..2792781009 --- /dev/null +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java @@ -0,0 +1,10 @@ +package io.javaoperatorsdk.operator.sample; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("tomcatoperator.io") +@Version("v1") +public class Tomcat extends CustomResource implements Namespaced {} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java new file mode 100644 index 0000000000..12f1a1e692 --- /dev/null +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java @@ -0,0 +1,186 @@ +package io.javaoperatorsdk.operator.sample; + +import io.fabric8.kubernetes.api.model.OwnerReference; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.apps.DeploymentStatus; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.dsl.RollableScalableResource; +import io.fabric8.kubernetes.client.dsl.ServiceResource; +import io.fabric8.kubernetes.client.utils.Serialization; +import io.javaoperatorsdk.operator.api.*; +import io.javaoperatorsdk.operator.processing.event.EventSourceManager; +import io.javaoperatorsdk.operator.processing.event.internal.CustomResourceEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; +import java.util.Optional; + +/** + * Runs a specified number of Tomcat app server Pods. It uses a Deployment to create the Pods. Also creates a + * Service over which the Pods can be accessed. + */ +@Controller +public class TomcatController implements ResourceController { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final KubernetesClient kubernetesClient; + + private DeploymentEventSource deploymentEventSource; + + public TomcatController(KubernetesClient client) { + this.kubernetesClient = client; + } + + @Override + public void init(EventSourceManager eventSourceManager) { + this.deploymentEventSource = DeploymentEventSource.createAndRegisterWatch(kubernetesClient); + eventSourceManager.registerEventSource("deployment-event-source", this.deploymentEventSource); + } + + @Override + public UpdateControl createOrUpdateResource(Tomcat tomcat, Context context) { + Optional latestCREvent = + context.getEvents().getLatestOfType(CustomResourceEvent.class); + if (latestCREvent.isPresent()) { + createOrUpdateDeployment(tomcat); + createOrUpdateService(tomcat); + } + + Optional latestDeploymentEvent = + context.getEvents().getLatestOfType(DeploymentEvent.class); + if (latestDeploymentEvent.isPresent()) { + Tomcat updatedTomcat = + updateTomcatStatus(tomcat, latestDeploymentEvent.get().getDeployment()); + log.info( + "Updating status of Tomcat {} in namespace {} to {} ready replicas", + tomcat.getMetadata().getName(), + tomcat.getMetadata().getNamespace(), + tomcat.getStatus().getReadyReplicas()); + return UpdateControl.updateStatusSubResource(updatedTomcat); + } + + return UpdateControl.noUpdate(); + } + + @Override + public DeleteControl deleteResource(Tomcat tomcat, Context context) { + deleteDeployment(tomcat); + deleteService(tomcat); + return DeleteControl.DEFAULT_DELETE; + } + + private Tomcat updateTomcatStatus(Tomcat tomcat, Deployment deployment) { + DeploymentStatus deploymentStatus = + Objects.requireNonNullElse(deployment.getStatus(), new DeploymentStatus()); + int readyReplicas = Objects.requireNonNullElse(deploymentStatus.getReadyReplicas(), 0); + TomcatStatus status = new TomcatStatus(); + status.setReadyReplicas(readyReplicas); + tomcat.setStatus(status); + return tomcat; + } + + private void createOrUpdateDeployment(Tomcat tomcat) { + String ns = tomcat.getMetadata().getNamespace(); + Deployment existingDeployment = + kubernetesClient + .apps() + .deployments() + .inNamespace(ns) + .withName(tomcat.getMetadata().getName()) + .get(); + if (existingDeployment == null) { + Deployment deployment = loadYaml(Deployment.class, "deployment.yaml"); + deployment.getMetadata().setName(tomcat.getMetadata().getName()); + deployment.getMetadata().setNamespace(ns); + deployment.getMetadata().getLabels().put("app.kubernetes.io/part-of", tomcat.getMetadata().getName()); + deployment.getMetadata().getLabels().put("app.kubernetes.io/managed-by", "tomcat-operator"); + // set tomcat version + deployment + .getSpec() + .getTemplate() + .getSpec() + .getContainers() + .get(0) + .setImage("tomcat:" + tomcat.getSpec().getVersion()); + deployment.getSpec().setReplicas(tomcat.getSpec().getReplicas()); + + // make sure label selector matches label (which has to be matched by service selector too) + deployment + .getSpec() + .getTemplate() + .getMetadata() + .getLabels() + .put("app", tomcat.getMetadata().getName()); + deployment + .getSpec() + .getSelector() + .getMatchLabels() + .put("app", tomcat.getMetadata().getName()); + + OwnerReference ownerReference = deployment.getMetadata().getOwnerReferences().get(0); + ownerReference.setName(tomcat.getMetadata().getName()); + ownerReference.setUid(tomcat.getMetadata().getUid()); + + log.info("Creating or updating Deployment {} in {}", deployment.getMetadata().getName(), ns); + kubernetesClient.apps().deployments().inNamespace(ns).create(deployment); + } else { + existingDeployment + .getSpec() + .getTemplate() + .getSpec() + .getContainers() + .get(0) + .setImage("tomcat:" + tomcat.getSpec().getVersion()); + existingDeployment.getSpec().setReplicas(tomcat.getSpec().getReplicas()); + kubernetesClient.apps().deployments().inNamespace(ns).createOrReplace(existingDeployment); + } + } + + private void deleteDeployment(Tomcat tomcat) { + log.info("Deleting Deployment {}", tomcat.getMetadata().getName()); + RollableScalableResource deployment = + kubernetesClient + .apps() + .deployments() + .inNamespace(tomcat.getMetadata().getNamespace()) + .withName(tomcat.getMetadata().getName()); + if (deployment.get() != null) { + deployment.delete(); + } + } + + private void createOrUpdateService(Tomcat tomcat) { + Service service = loadYaml(Service.class, "service.yaml"); + service.getMetadata().setName(tomcat.getMetadata().getName()); + String ns = tomcat.getMetadata().getNamespace(); + service.getMetadata().setNamespace(ns); + service.getSpec().getSelector().put("app", tomcat.getMetadata().getName()); + log.info("Creating or updating Service {} in {}", service.getMetadata().getName(), ns); + kubernetesClient.services().inNamespace(ns).createOrReplace(service); + } + + private void deleteService(Tomcat tomcat) { + log.info("Deleting Service {}", tomcat.getMetadata().getName()); + ServiceResource service = + kubernetesClient + .services() + .inNamespace(tomcat.getMetadata().getNamespace()) + .withName(tomcat.getMetadata().getName()); + if (service.get() != null) { + service.delete(); + } + } + + private T loadYaml(Class clazz, String yaml) { + try (InputStream is = getClass().getResourceAsStream(yaml)) { + return Serialization.unmarshal(is, clazz); + } catch (IOException ex) { + throw new IllegalStateException("Cannot find yaml on classpath: " + yaml); + } + } +} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEvent.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEvent.java new file mode 100644 index 0000000000..b10f56da68 --- /dev/null +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEvent.java @@ -0,0 +1,49 @@ +package io.javaoperatorsdk.operator.sample; + +import io.fabric8.kubernetes.client.Watcher; +import io.javaoperatorsdk.operator.processing.event.DefaultEvent; + +public class TomcatEvent extends DefaultEvent { + + private final Watcher.Action action; + private final Tomcat tomcat; + + public TomcatEvent( + Watcher.Action action, Tomcat resource, TomcatEventSource tomcatEventSource, String webappUid) { + super(webappUid, tomcatEventSource); + this.action = action; + this.tomcat = resource; + } + + public Watcher.Action getAction() { + return action; + } + + public String resourceUid() { + return getTomcat().getMetadata().getUid(); + } + + @Override + public String toString() { + return "CustomResourceEvent{" + + "action=" + + action + + ", resource=[ name=" + + getTomcat().getMetadata().getName() + + ", kind=" + + getTomcat().getKind() + + ", apiVersion=" + + getTomcat().getApiVersion() + + " ,resourceVersion=" + + getTomcat().getMetadata().getResourceVersion() + + ", markedForDeletion: " + + (getTomcat().getMetadata().getDeletionTimestamp() != null + && !getTomcat().getMetadata().getDeletionTimestamp().isEmpty()) + + " ]" + + '}'; + } + + public Tomcat getTomcat() { + return tomcat; + } +} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEventSource.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEventSource.java new file mode 100644 index 0000000000..fe9bf6f9d1 --- /dev/null +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEventSource.java @@ -0,0 +1,80 @@ +package io.javaoperatorsdk.operator.sample; + +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.Watcher; +import io.fabric8.kubernetes.client.WatcherException; +import io.javaoperatorsdk.operator.processing.event.AbstractEventSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; + +import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID; +import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion; + +/** + * Used by the WebappController to watch changes on Tomcat objects + */ +public class TomcatEventSource extends AbstractEventSource implements Watcher { + private static final Logger log = LoggerFactory.getLogger(TomcatEventSource.class); + + private final KubernetesClient client; + + public static TomcatEventSource createAndRegisterWatch(KubernetesClient client) { + TomcatEventSource tomcatEventSource = new TomcatEventSource(client); + tomcatEventSource.registerWatch(); + return tomcatEventSource; + } + + private TomcatEventSource(KubernetesClient client) { + this.client = client; + } + + private void registerWatch() { + var tomcatClient = client.customResources(Tomcat.class); + tomcatClient.inAnyNamespace().watch(this); + } + + @Override + public void eventReceived(Action action, Tomcat tomcat) { + log.info("Event received for action: {}, Tomcat: {}", action.name(), tomcat.getMetadata().getName()); + + if (action == Action.ERROR) { + log.warn( + "Skipping {} event for custom resource uid: {}, version: {}", + action, + getUID(tomcat), + getVersion(tomcat)); + return; + } + + var webappClient = client.customResources(Webapp.class); + Optional webapp = webappClient.inNamespace(tomcat.getMetadata().getNamespace()) + .list().getItems().stream() + .filter(wapp -> wapp.getSpec().getTomcat().equals(tomcat.getMetadata().getName())) + .findFirst(); + + if (webapp.isPresent()) { + eventHandler.handleEvent(new TomcatEvent(action, tomcat, this, + webapp.get().getMetadata().getUid())); + } else { + log.debug("Webapp not found for Tomcat {}", tomcat.getMetadata().getName()); + } + } + + @Override + public void onClose(WatcherException e) { + if (e == null) { + return; + } + if (e.isHttpGone()) { + log.warn("Received error for watch, will try to reconnect.", e); + registerWatch(); + } else { + // Note that this should not happen normally, since fabric8 client handles reconnect. + // In case it tries to reconnect this method is not called. + log.error("Unexpected error happened with watch. Will exit.", e); + System.exit(1); + } + } +} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatOperator.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatOperator.java new file mode 100644 index 0000000000..bb94af5c1f --- /dev/null +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatOperator.java @@ -0,0 +1,35 @@ +package io.javaoperatorsdk.operator.sample; + +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.ConfigBuilder; +import io.fabric8.kubernetes.client.DefaultKubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.operator.Operator; +import io.javaoperatorsdk.operator.config.runtime.DefaultConfigurationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.takes.facets.fork.FkRegex; +import org.takes.facets.fork.TkFork; +import org.takes.http.Exit; +import org.takes.http.FtBasic; + +import java.io.IOException; + +public class TomcatOperator { + + private static final Logger log = LoggerFactory.getLogger(TomcatOperator.class); + + public static void main(String[] args) throws IOException { + + Config config = new ConfigBuilder().withNamespace(null).build(); + KubernetesClient client = new DefaultKubernetesClient(config); + Operator operator = new Operator(client, DefaultConfigurationService.instance()); + + TomcatController tomcatController = new TomcatController(client); + operator.register(tomcatController); + + operator.register(new WebappController(client)); + + new FtBasic(new TkFork(new FkRegex("/health", "ALL GOOD.")), 8080).start(Exit.NEVER); + } +} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatSpec.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatSpec.java new file mode 100644 index 0000000000..fbd22f30f9 --- /dev/null +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatSpec.java @@ -0,0 +1,23 @@ +package io.javaoperatorsdk.operator.sample; + +public class TomcatSpec { + + private Integer version; + private Integer replicas; + + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public Integer getReplicas() { + return replicas; + } + + public void setReplicas(Integer replicas) { + this.replicas = replicas; + } +} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatStatus.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatStatus.java new file mode 100644 index 0000000000..3bf3d2ab4b --- /dev/null +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatStatus.java @@ -0,0 +1,14 @@ +package io.javaoperatorsdk.operator.sample; + +public class TomcatStatus { + + private Integer readyReplicas = 0; + + public Integer getReadyReplicas() { + return readyReplicas; + } + + public void setReadyReplicas(Integer readyReplicas) { + this.readyReplicas = readyReplicas; + } +} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Webapp.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Webapp.java new file mode 100644 index 0000000000..1238264b37 --- /dev/null +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Webapp.java @@ -0,0 +1,13 @@ +package io.javaoperatorsdk.operator.sample; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +/** + * Represents a web application deployed in a Tomcat deployment + */ +@Group("tomcatoperator.io") +@Version("v1") +public class Webapp extends CustomResource implements Namespaced {} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java new file mode 100644 index 0000000000..682ca2a5b9 --- /dev/null +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java @@ -0,0 +1,173 @@ +package io.javaoperatorsdk.operator.sample; + +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.*; +import io.javaoperatorsdk.operator.processing.event.EventSourceManager; +import okhttp3.Response; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +@Controller +public class WebappController implements ResourceController { + + private KubernetesClient kubernetesClient; + + private final Logger log = LoggerFactory.getLogger(getClass()); + + public WebappController(KubernetesClient kubernetesClient) { + this.kubernetesClient = kubernetesClient; + } + + @Override + public void init(EventSourceManager eventSourceManager) { + TomcatEventSource tomcatEventSource = TomcatEventSource.createAndRegisterWatch(kubernetesClient); + eventSourceManager.registerEventSource("tomcat-event-source", tomcatEventSource); + } + + /** + * This method will be called not only on changes to Webapp objects but also when Tomcat objects change. + */ + @Override + public UpdateControl createOrUpdateResource(Webapp webapp, Context context) { + if (webapp.getStatus() != null && Objects.equals(webapp.getSpec().getUrl(), webapp.getStatus().getDeployedArtifact())) { + return UpdateControl.noUpdate(); + } + + var tomcatClient = kubernetesClient.customResources(Tomcat.class); + Tomcat tomcat = tomcatClient.inNamespace(webapp.getMetadata().getNamespace()).withName(webapp.getSpec().getTomcat()).get(); + if (tomcat == null) { + throw 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.updateStatusSubResource(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 deleteResource(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.DEFAULT_DELETE; + } + + 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 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 CompletableFuture data; + private 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(Response response) { + log.debug("Reading data... " + response.message()); + } + + @Override + public void onFailure(Throwable t, Response response) { + log.debug(t.getMessage() + " " + response.message()); + data.completeExceptionally(t); + } + + @Override + public void onClose(int code, String reason) { + log.debug("Exit with: " + code + " and with reason: " + reason); + data.complete(baos.toString()); + } + } + +} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappSpec.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappSpec.java new file mode 100644 index 0000000000..a34621c35b --- /dev/null +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappSpec.java @@ -0,0 +1,34 @@ +package io.javaoperatorsdk.operator.sample; + +public class WebappSpec { + + private String url; + + private String contextPath; + + private String tomcat; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getContextPath() { + return contextPath; + } + + public void setContextPath(String contextPath) { + this.contextPath = contextPath; + } + + public String getTomcat() { + return tomcat; + } + + public void setTomcat(String tomcat) { + this.tomcat = tomcat; + } +} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappStatus.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappStatus.java new file mode 100644 index 0000000000..8267abe24c --- /dev/null +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappStatus.java @@ -0,0 +1,24 @@ +package io.javaoperatorsdk.operator.sample; + +public class WebappStatus { + + private String deployedArtifact; + + public String getDeployedArtifact() { + return deployedArtifact; + } + + public void setDeployedArtifact(String deployedArtifact) { + this.deployedArtifact = deployedArtifact; + } + + private String[] deploymentStatus; + + public String[] getDeploymentStatus() { + return deploymentStatus; + } + + public void setDeploymentStatus(String[] deploymentStatus) { + this.deploymentStatus = deploymentStatus; + } +} diff --git a/sample-operators/tomcat-operator/src/main/resources/io/javaoperatorsdk/operator/sample/deployment.yaml b/sample-operators/tomcat-operator/src/main/resources/io/javaoperatorsdk/operator/sample/deployment.yaml new file mode 100644 index 0000000000..2f6f373c6c --- /dev/null +++ b/sample-operators/tomcat-operator/src/main/resources/io/javaoperatorsdk/operator/sample/deployment.yaml @@ -0,0 +1,39 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "" + labels: + app.kubernetes.io/part-of: "" + app.kubernetes.io/managed-by: "" # used for filtering of Deployments created by the controller + ownerReferences: # used for finding which Tomcat does this Deployment belong to + - apiVersion: apps/v1 + kind: Tomcat + name: "" + uid: "" +spec: + selector: + matchLabels: + app: "" + replicas: 1 + template: + metadata: + labels: + app: "" + spec: + containers: + - name: tomcat + image: tomcat:8.0 + ports: + - containerPort: 8080 + volumeMounts: + - mountPath: /usr/local/tomcat/webapps + name: webapps-volume + - name: war-downloader + image: busybox:1.28 + command: ['tail', '-f', '/dev/null'] + volumeMounts: + - name: webapps-volume + mountPath: /data + volumes: + - name: webapps-volume + emptydir: {} diff --git a/sample-operators/tomcat-operator/src/main/resources/io/javaoperatorsdk/operator/sample/service.yaml b/sample-operators/tomcat-operator/src/main/resources/io/javaoperatorsdk/operator/sample/service.yaml new file mode 100644 index 0000000000..ab198643ed --- /dev/null +++ b/sample-operators/tomcat-operator/src/main/resources/io/javaoperatorsdk/operator/sample/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: "" +spec: + selector: + app: "" + ports: + - protocol: TCP + port: 80 + targetPort: 8080 + type: NodePort diff --git a/sample-operators/tomcat-operator/src/main/resources/log4j2.xml b/sample-operators/tomcat-operator/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..a99aaf31b6 --- /dev/null +++ b/sample-operators/tomcat-operator/src/main/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/IntegrationTest.java b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/IntegrationTest.java new file mode 100644 index 0000000000..a35052f280 --- /dev/null +++ b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/IntegrationTest.java @@ -0,0 +1,122 @@ +package io.javaoperatorsdk.operator.sample; + +import io.fabric8.kubernetes.api.model.*; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.ConfigBuilder; +import io.fabric8.kubernetes.client.*; +import io.fabric8.kubernetes.client.extended.run.RunConfigBuilder; +import io.javaoperatorsdk.operator.Operator; +import io.javaoperatorsdk.operator.config.runtime.DefaultConfigurationService; +import org.junit.AfterClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +import static java.util.concurrent.TimeUnit.*; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; + +public class IntegrationTest { + + final static String TEST_NS = "tomcat-test"; + + final static Logger log = LoggerFactory.getLogger(IntegrationTest.class); + + @Test + public void test() { + Config config = new ConfigBuilder().withNamespace(null).build(); + KubernetesClient client = new DefaultKubernetesClient(config); + + if ("true".equals(System.getenv("RUN_OPERATOR_IN_TEST"))) { + Operator operator = new Operator(client, DefaultConfigurationService.instance()); + operator.register(new TomcatController(client)); + operator.register(new WebappController(client)); + } + + Tomcat tomcat = new Tomcat(); + tomcat.setMetadata(new ObjectMetaBuilder() + .withName("test-tomcat1") + .withNamespace(TEST_NS) + .build()); + tomcat.setSpec(new TomcatSpec()); + tomcat.getSpec().setReplicas(3); + tomcat.getSpec().setVersion(9); + + Webapp webapp1 = new Webapp(); + webapp1.setMetadata(new ObjectMetaBuilder() + .withName("test-webapp1") + .withNamespace(TEST_NS) + .build()); + webapp1.setSpec(new WebappSpec()); + webapp1.getSpec().setContextPath("webapp1"); + webapp1.getSpec().setTomcat(tomcat.getMetadata().getName()); + webapp1.getSpec().setUrl("http://tomcat.apache.org/tomcat-7.0-doc/appdev/sample/sample.war"); + + var tomcatClient = client.customResources(Tomcat.class); + var webappClient = client.customResources(Webapp.class); + + Namespace testNs = new NamespaceBuilder().withMetadata( + new ObjectMetaBuilder().withName(TEST_NS).build()).build(); + + if (testNs != null && client.namespaces().withName(TEST_NS).isReady()) { + // We perform a pre-run cleanup instead of a post-run cleanup. This is to help with debugging test results + // when running against a persistent cluster. The test namespace would stay after the test run so we can + // check what's there, but it would be cleaned up during the next test run. + log.info("Cleanup: deleting test namespace {}", TEST_NS); + client.namespaces().delete(testNs); + await().atMost(5, MINUTES).until(() -> client.namespaces().withName("tomcat-test").get() == null); + } + + log.info("Creating test namespace {}", TEST_NS); + client.namespaces().create(testNs); + + log.info("Creating test resources"); + tomcatClient.inNamespace(TEST_NS).create(tomcat); + webappClient.inNamespace(TEST_NS).create(webapp1); + + log.info("Waiting 5 minutes for Tomcat and Webapp CR statuses to be updated"); + await().atMost(5, MINUTES).untilAsserted(() -> { + Tomcat updatedTomcat = tomcatClient.inNamespace(TEST_NS).withName(tomcat.getMetadata().getName()).get(); + Webapp updatedWebapp = webappClient.inNamespace(TEST_NS).withName(webapp1.getMetadata().getName()).get(); + assertThat(updatedTomcat.getStatus(), is(notNullValue())); + assertThat(updatedTomcat.getStatus().getReadyReplicas(), equalTo(3)); + assertThat(updatedWebapp.getStatus(), is(notNullValue())); + assertThat(updatedWebapp.getStatus().getDeployedArtifact(), is(notNullValue())); + }); + + String url = "http://" + tomcat.getMetadata().getName() + "/" + webapp1.getSpec().getContextPath() + "/"; + log.info("Starting curl Pod and waiting 5 minutes for GET of {} to return 200", url); + + int timeoutMinutes = 5; + await("wait-for-webapp").atMost(timeoutMinutes, MINUTES).untilAsserted(() -> { + try { + + log.info("Starting curl Pod to test if webapp was deployed correctly"); + Pod curlPod = client.run().inNamespace(TEST_NS) + .withRunConfig(new RunConfigBuilder() + .withArgs("-s", "-o", "/dev/null", "-w", "%{http_code}", url) + .withName("curl") + .withImage("curlimages/curl:7.78.0") + .withRestartPolicy("Never") + .build()).done(); + log.info("Waiting for curl Pod to finish running"); + await("wait-for-curl-pod-run").atMost(timeoutMinutes, MINUTES).until(() -> client.pods().inNamespace(TEST_NS).withName("curl").get().getStatus().getPhase().equals("Succeeded")); + + String curlOutput = client.pods().inNamespace(TEST_NS).withName(curlPod.getMetadata().getName()).getLog(); + log.info("Output from curl: '{}'", curlOutput); + assertThat(curlOutput, equalTo("200")); + } catch (KubernetesClientException ex) { + throw new AssertionError(ex); + } finally { + client.pods().inNamespace(TEST_NS).withName("curl").delete(); + log.info("Waiting for curl Pod to be deleted"); + await("wait-for-curl-pod-stop").atMost(timeoutMinutes, MINUTES).until(() -> client.pods().inNamespace(TEST_NS).withName("curl").get() == null); + } + }); + } + +} diff --git a/sample-operators/tomcat-operator/src/test/resources/log4j2.xml b/sample-operators/tomcat-operator/src/test/resources/log4j2.xml new file mode 100644 index 0000000000..a99aaf31b6 --- /dev/null +++ b/sample-operators/tomcat-operator/src/test/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + From 7b5616f7e7527f12887cd96b07d6a95238670862 Mon Sep 17 00:00:00 2001 From: Adam Sandor <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 21 Oct 2021 16:47:54 +0200 Subject: [PATCH 02/54] improve build pipeline based on PR feedback --- .github/workflows/sample-operator-tests.yml | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/.github/workflows/sample-operator-tests.yml b/.github/workflows/sample-operator-tests.yml index 5d9839ea80..425e373f43 100644 --- a/.github/workflows/sample-operator-tests.yml +++ b/.github/workflows/sample-operator-tests.yml @@ -36,24 +36,9 @@ jobs: - name: Set up Java and Maven uses: actions/setup-java@v2 with: - java-version: 15 + java-version: 11 distribution: adopt-hotspot - - name: cache - uses: actions/cache@v2 - if: ${{ !env.ACT }} - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven-m2 - - - name: Set up Maven - uses: stCarolas/setup-maven@v4 - if: ${{ env.ACT }} - with: - maven-version: 3.8.1 - - name: build jib working-directory: sample-operators/tomcat-operator run: | From b64ea776d19baef33634a309f7ec761db0a6b1a7 Mon Sep 17 00:00:00 2001 From: Adam Sandor <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 21 Oct 2021 16:49:37 +0200 Subject: [PATCH 03/54] fix bug in pipeline --- .github/workflows/sample-operator-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sample-operator-tests.yml b/.github/workflows/sample-operator-tests.yml index 425e373f43..076bca30c8 100644 --- a/.github/workflows/sample-operator-tests.yml +++ b/.github/workflows/sample-operator-tests.yml @@ -58,9 +58,9 @@ jobs: - name: create ns tomcatoperator-sample run: kubectl create ns tomcatoperator-sample - - name: Run unit tests + - name: Run tests working-directory: sample-operators/tomcat-operator - run: mvn -B test -q --file tomcat/pom.xml + run: mvn -B test -q --file pom.xml - name: Dump state if: ${{ failure() }} From ab3d81829e6d664a56dbaa0b33cc10d55f9e2a1f Mon Sep 17 00:00:00 2001 From: Adam Sandor <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 21 Oct 2021 16:55:24 +0200 Subject: [PATCH 04/54] fix bug in pipeline --- .github/workflows/sample-operator-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sample-operator-tests.yml b/.github/workflows/sample-operator-tests.yml index 076bca30c8..f4b4996e90 100644 --- a/.github/workflows/sample-operator-tests.yml +++ b/.github/workflows/sample-operator-tests.yml @@ -43,8 +43,8 @@ jobs: working-directory: sample-operators/tomcat-operator run: | mvn --version - mvn -B package jib:dockerBuild jib:buildTar -Djib-maven-image=tomcat-operator --file tomcat/pom.xml -DskipTests - kind load image-archive tomcat/target/jib-image.tar --name=${{ env.KIND_CL_NAME }} + mvn -B package jib:dockerBuild jib:buildTar -Djib-maven-image=tomcat-operator -DskipTests + kind load image-archive target/jib-image.tar --name=${{ env.KIND_CL_NAME }} - name: Apply CRDs working-directory: sample-operators/tomcat-operator From 5be04f92c69be73ffea0e77718477ceb66da0bbe Mon Sep 17 00:00:00 2001 From: Adam Sandor <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 21 Oct 2021 17:05:52 +0200 Subject: [PATCH 05/54] add groupid --- sample-operators/tomcat-operator/pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml index 49caa55378..b760ea4123 100644 --- a/sample-operators/tomcat-operator/pom.xml +++ b/sample-operators/tomcat-operator/pom.xml @@ -4,7 +4,8 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - tomcat + sample-tomcat-operator + io.javaoperatorsdk Operator SDK - Samples - Tomcat Provisions Tomcat Pods and deploys Webapplications in them 1.9.9-SNAPSHOT From a5e569c84571b01dd6ca73bccf08603f58ea6993 Mon Sep 17 00:00:00 2001 From: Adam Sandor <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 21 Oct 2021 17:17:57 +0200 Subject: [PATCH 06/54] set sdk version to 1.9.8 --- sample-operators/tomcat-operator/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml index b760ea4123..d3105087ec 100644 --- a/sample-operators/tomcat-operator/pom.xml +++ b/sample-operators/tomcat-operator/pom.xml @@ -21,7 +21,7 @@ io.javaoperatorsdk operator-framework - ${project.version} + 1.9.8 org.apache.logging.log4j From d98be670db427accc37a5099d5543ec576f90f4b Mon Sep 17 00:00:00 2001 From: Adam Sandor <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 21 Oct 2021 17:47:07 +0200 Subject: [PATCH 07/54] remove unnecessary namespace creation, list namespaces during the dump --- .github/workflows/sample-operator-tests.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/sample-operator-tests.yml b/.github/workflows/sample-operator-tests.yml index f4b4996e90..90ebc1025f 100644 --- a/.github/workflows/sample-operator-tests.yml +++ b/.github/workflows/sample-operator-tests.yml @@ -55,9 +55,6 @@ jobs: run: | kubectl apply -f k8s/operator.yaml - - name: create ns tomcatoperator-sample - run: kubectl create ns tomcatoperator-sample - - name: Run tests working-directory: sample-operators/tomcat-operator run: mvn -B test -q --file pom.xml @@ -65,5 +62,6 @@ jobs: - name: Dump state if: ${{ failure() }} run: | + kubectl get ns kubectl get all -n tomcat-test -o yaml kubectl logs curl -n tomcat-test \ No newline at end of file From 3ae2de631aed20794867bc0e56518928c074b4bd Mon Sep 17 00:00:00 2001 From: Adam Sandor <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 21 Oct 2021 18:38:27 +0200 Subject: [PATCH 08/54] more diagnostic dumping --- .github/workflows/sample-operator-tests.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sample-operator-tests.yml b/.github/workflows/sample-operator-tests.yml index 90ebc1025f..2cd3e00a1b 100644 --- a/.github/workflows/sample-operator-tests.yml +++ b/.github/workflows/sample-operator-tests.yml @@ -63,5 +63,11 @@ jobs: if: ${{ failure() }} run: | kubectl get ns + echo "All objects in tomcat-test" kubectl get all -n tomcat-test -o yaml - kubectl logs curl -n tomcat-test \ No newline at end of file + echo "Output of curl command" + kubectl logs curl -n tomcat-test + echo "All objects in tomcat-operator" + kubectl get all -n tomcat-operator -o yaml + echo "Output of tomcat-operator pod" + kubectl logs -l app=tomcat-operator \ No newline at end of file From cce5e49d5b0afb7bc5d796e11fac416ea5fd535c Mon Sep 17 00:00:00 2001 From: Adam Sandor <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 21 Oct 2021 18:48:02 +0200 Subject: [PATCH 09/54] reorganize dump and prevent stop on error --- .github/workflows/sample-operator-tests.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sample-operator-tests.yml b/.github/workflows/sample-operator-tests.yml index 2cd3e00a1b..c245e7a9ba 100644 --- a/.github/workflows/sample-operator-tests.yml +++ b/.github/workflows/sample-operator-tests.yml @@ -62,12 +62,14 @@ jobs: - name: Dump state if: ${{ failure() }} run: | + set +e + echo "All namespaces" kubectl get ns + echo "All objects in tomcat-operator" + kubectl get all -n tomcat-operator -o yaml + echo "Output of tomcat-operator pod" + kubectl logs -l app=tomcat-operator echo "All objects in tomcat-test" kubectl get all -n tomcat-test -o yaml echo "Output of curl command" kubectl logs curl -n tomcat-test - echo "All objects in tomcat-operator" - kubectl get all -n tomcat-operator -o yaml - echo "Output of tomcat-operator pod" - kubectl logs -l app=tomcat-operator \ No newline at end of file From 6f3ecb22dbf3616cf113fd3dfc825b795b760797 Mon Sep 17 00:00:00 2001 From: Adam Sandor <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 21 Oct 2021 23:37:35 +0200 Subject: [PATCH 10/54] fix bug in printing tomcat-operator output --- .github/workflows/sample-operator-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sample-operator-tests.yml b/.github/workflows/sample-operator-tests.yml index c245e7a9ba..2ece0d56de 100644 --- a/.github/workflows/sample-operator-tests.yml +++ b/.github/workflows/sample-operator-tests.yml @@ -68,7 +68,7 @@ jobs: echo "All objects in tomcat-operator" kubectl get all -n tomcat-operator -o yaml echo "Output of tomcat-operator pod" - kubectl logs -l app=tomcat-operator + kubectl logs -l app=tomcat-operator -n tomcat-operator echo "All objects in tomcat-test" kubectl get all -n tomcat-test -o yaml echo "Output of curl command" From 6b78630ca313cb419f5eb1f6954513a848b0764c Mon Sep 17 00:00:00 2001 From: Adam Sandor <10675791+adam-sandor@users.noreply.github.com> Date: Fri, 22 Oct 2021 12:35:29 +0200 Subject: [PATCH 11/54] use framework version 1.9.2 --- sample-operators/tomcat-operator/pom.xml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml index d3105087ec..1f9af3745f 100644 --- a/sample-operators/tomcat-operator/pom.xml +++ b/sample-operators/tomcat-operator/pom.xml @@ -4,11 +4,16 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + io.javaoperatorsdk + java-operator-sdk + 1.9.9-SNAPSHOT + ../../pom.xml + + sample-tomcat-operator - io.javaoperatorsdk Operator SDK - Samples - Tomcat Provisions Tomcat Pods and deploys Webapplications in them - 1.9.9-SNAPSHOT jar @@ -21,7 +26,7 @@ io.javaoperatorsdk operator-framework - 1.9.8 + 1.9.2 org.apache.logging.log4j From 7fdfefaa67e964c62fc42f3a6241fcc0550f7d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 28 Oct 2021 10:06:52 +0200 Subject: [PATCH 12/54] Reformat TomcatOperator & add to pom structure --- .github/workflows/sample-operator-tests.yml | 2 +- pom.xml | 25 +++- sample-operators/pom.xml | 79 +++++++++++ sample-operators/tomcat-operator/pom.xml | 4 +- .../sample/DeploymentEventSource.java | 13 +- .../operator/sample/Tomcat.java | 3 +- .../operator/sample/TomcatController.java | 22 +-- .../operator/sample/TomcatEvent.java | 3 +- .../operator/sample/TomcatEventSource.java | 110 ++++++++------- .../operator/sample/TomcatOperator.java | 15 +- .../operator/sample/Webapp.java | 3 +- .../operator/sample/WebappController.java | 84 ++++++----- .../operator/sample/IntegrationTest.java | 122 ---------------- .../sample/TomcatOperatorEndToEndTest.java | 132 ++++++++++++++++++ 14 files changed, 376 insertions(+), 241 deletions(-) create mode 100644 sample-operators/pom.xml delete mode 100644 sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/IntegrationTest.java create mode 100644 sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorEndToEndTest.java diff --git a/.github/workflows/sample-operator-tests.yml b/.github/workflows/sample-operator-tests.yml index 2ece0d56de..e59372566f 100644 --- a/.github/workflows/sample-operator-tests.yml +++ b/.github/workflows/sample-operator-tests.yml @@ -57,7 +57,7 @@ jobs: - name: Run tests working-directory: sample-operators/tomcat-operator - run: mvn -B test -q --file pom.xml + run: mvn -B test -P end-to-end-tests - name: Dump state if: ${{ failure() }} diff --git a/pom.xml b/pom.xml index 401ba48b11..3c2ed4aeb8 100644 --- a/pom.xml +++ b/pom.xml @@ -78,7 +78,8 @@ operator-framework-junit5 operator-framework samples - micrometer-support + micrometer-support + sample-operators @@ -334,6 +335,7 @@ **/*Test.java **/*IT.java + **/*E2E.java @@ -353,6 +355,27 @@ **/*Test.java + **/*E2E.java + + + + + + + + end-to-end-tests + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*E2E.java + + + **/*Test.java + **/*IT.java diff --git a/sample-operators/pom.xml b/sample-operators/pom.xml new file mode 100644 index 0000000000..78411d7c2e --- /dev/null +++ b/sample-operators/pom.xml @@ -0,0 +1,79 @@ + + + 4.0.0 + + + io.javaoperatorsdk + java-operator-sdk + 1.9.9-SNAPSHOT + ../pom.xml + + + sample-operators + Operator SDK - Samples + pom + + + 3.1.4 + + + + tomcat-operator + + + + + io.javaoperatorsdk + operator-framework + 1.9.2 + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.13.3 + + + org.takes + takes + 1.19 + + + junit + junit + 4.13.1 + test + + + org.awaitility + awaitility + 4.1.0 + test + + + + + + + com.google.cloud.tools + jib-maven-plugin + ${jib-maven-plugin.version} + + + gcr.io/distroless/java:11 + + + tomcat-operator + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + + + + \ No newline at end of file diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml index 1f9af3745f..c66da6d9e3 100644 --- a/sample-operators/tomcat-operator/pom.xml +++ b/sample-operators/tomcat-operator/pom.xml @@ -6,9 +6,9 @@ io.javaoperatorsdk - java-operator-sdk + sample-operators 1.9.9-SNAPSHOT - ../../pom.xml + ../pom.xml sample-tomcat-operator diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEventSource.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEventSource.java index 55a85c89b0..a88357e8cf 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEventSource.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEventSource.java @@ -1,19 +1,20 @@ package io.javaoperatorsdk.operator.sample; -import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID; -import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.Watcher; import io.fabric8.kubernetes.client.WatcherException; import io.javaoperatorsdk.operator.processing.event.AbstractEventSource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID; +import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion; /** - * Used by the TomcatController to watch changes on Deployment objects. As the Pods of the Deployment start up - * the TomcatController updates the status.readyReplicas field. + * Used by the TomcatController to watch changes on Deployment objects. As the Pods of the + * Deployment start up the TomcatController updates the status.readyReplicas field. */ public class DeploymentEventSource extends AbstractEventSource implements Watcher { private static final Logger log = LoggerFactory.getLogger(DeploymentEventSource.class); diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java index 2792781009..7a91f1510e 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java @@ -7,4 +7,5 @@ @Group("tomcatoperator.io") @Version("v1") -public class Tomcat extends CustomResource implements Namespaced {} +public class Tomcat extends CustomResource implements Namespaced { +} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java index 12f1a1e692..e82439bb33 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java @@ -1,5 +1,13 @@ package io.javaoperatorsdk.operator.sample; +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import io.fabric8.kubernetes.api.model.OwnerReference; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; @@ -11,17 +19,10 @@ import io.javaoperatorsdk.operator.api.*; import io.javaoperatorsdk.operator.processing.event.EventSourceManager; import io.javaoperatorsdk.operator.processing.event.internal.CustomResourceEvent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Objects; -import java.util.Optional; /** - * Runs a specified number of Tomcat app server Pods. It uses a Deployment to create the Pods. Also creates a - * Service over which the Pods can be accessed. + * Runs a specified number of Tomcat app server Pods. It uses a Deployment to create the Pods. Also + * creates a Service over which the Pods can be accessed. */ @Controller public class TomcatController implements ResourceController { @@ -97,7 +98,8 @@ private void createOrUpdateDeployment(Tomcat tomcat) { Deployment deployment = loadYaml(Deployment.class, "deployment.yaml"); deployment.getMetadata().setName(tomcat.getMetadata().getName()); deployment.getMetadata().setNamespace(ns); - deployment.getMetadata().getLabels().put("app.kubernetes.io/part-of", tomcat.getMetadata().getName()); + deployment.getMetadata().getLabels().put("app.kubernetes.io/part-of", + tomcat.getMetadata().getName()); deployment.getMetadata().getLabels().put("app.kubernetes.io/managed-by", "tomcat-operator"); // set tomcat version deployment diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEvent.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEvent.java index b10f56da68..d08a68c9f0 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEvent.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEvent.java @@ -9,7 +9,8 @@ public class TomcatEvent extends DefaultEvent { private final Tomcat tomcat; public TomcatEvent( - Watcher.Action action, Tomcat resource, TomcatEventSource tomcatEventSource, String webappUid) { + Watcher.Action action, Tomcat resource, TomcatEventSource tomcatEventSource, + String webappUid) { super(webappUid, tomcatEventSource); this.action = action; this.tomcat = resource; diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEventSource.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEventSource.java index fe9bf6f9d1..5229b56878 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEventSource.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEventSource.java @@ -1,13 +1,14 @@ package io.javaoperatorsdk.operator.sample; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.Watcher; import io.fabric8.kubernetes.client.WatcherException; import io.javaoperatorsdk.operator.processing.event.AbstractEventSource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Optional; import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID; import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion; @@ -16,65 +17,66 @@ * Used by the WebappController to watch changes on Tomcat objects */ public class TomcatEventSource extends AbstractEventSource implements Watcher { - private static final Logger log = LoggerFactory.getLogger(TomcatEventSource.class); + private static final Logger log = LoggerFactory.getLogger(TomcatEventSource.class); - private final KubernetesClient client; + private final KubernetesClient client; - public static TomcatEventSource createAndRegisterWatch(KubernetesClient client) { - TomcatEventSource tomcatEventSource = new TomcatEventSource(client); - tomcatEventSource.registerWatch(); - return tomcatEventSource; - } + public static TomcatEventSource createAndRegisterWatch(KubernetesClient client) { + TomcatEventSource tomcatEventSource = new TomcatEventSource(client); + tomcatEventSource.registerWatch(); + return tomcatEventSource; + } - private TomcatEventSource(KubernetesClient client) { - this.client = client; - } + private TomcatEventSource(KubernetesClient client) { + this.client = client; + } - private void registerWatch() { - var tomcatClient = client.customResources(Tomcat.class); - tomcatClient.inAnyNamespace().watch(this); - } + private void registerWatch() { + var tomcatClient = client.customResources(Tomcat.class); + tomcatClient.inAnyNamespace().watch(this); + } - @Override - public void eventReceived(Action action, Tomcat tomcat) { - log.info("Event received for action: {}, Tomcat: {}", action.name(), tomcat.getMetadata().getName()); + @Override + public void eventReceived(Action action, Tomcat tomcat) { + log.info("Event received for action: {}, Tomcat: {}", action.name(), + tomcat.getMetadata().getName()); - if (action == Action.ERROR) { - log.warn( - "Skipping {} event for custom resource uid: {}, version: {}", - action, - getUID(tomcat), - getVersion(tomcat)); - return; - } + if (action == Action.ERROR) { + log.warn( + "Skipping {} event for custom resource uid: {}, version: {}", + action, + getUID(tomcat), + getVersion(tomcat)); + return; + } - var webappClient = client.customResources(Webapp.class); - Optional webapp = webappClient.inNamespace(tomcat.getMetadata().getNamespace()) - .list().getItems().stream() - .filter(wapp -> wapp.getSpec().getTomcat().equals(tomcat.getMetadata().getName())) - .findFirst(); + var webappClient = client.customResources(Webapp.class); + Optional webapp = webappClient.inNamespace(tomcat.getMetadata().getNamespace()) + .list().getItems().stream() + .filter(wapp -> wapp.getSpec().getTomcat().equals(tomcat.getMetadata().getName())) + .findFirst(); - if (webapp.isPresent()) { - eventHandler.handleEvent(new TomcatEvent(action, tomcat, this, - webapp.get().getMetadata().getUid())); - } else { - log.debug("Webapp not found for Tomcat {}", tomcat.getMetadata().getName()); - } + if (webapp.isPresent()) { + eventHandler.handleEvent(new TomcatEvent(action, tomcat, this, + webapp.get().getMetadata().getUid())); + } else { + log.debug("Webapp not found for Tomcat {}", tomcat.getMetadata().getName()); } + } - @Override - public void onClose(WatcherException e) { - if (e == null) { - return; - } - if (e.isHttpGone()) { - log.warn("Received error for watch, will try to reconnect.", e); - registerWatch(); - } else { - // Note that this should not happen normally, since fabric8 client handles reconnect. - // In case it tries to reconnect this method is not called. - log.error("Unexpected error happened with watch. Will exit.", e); - System.exit(1); - } + @Override + public void onClose(WatcherException e) { + if (e == null) { + return; + } + if (e.isHttpGone()) { + log.warn("Received error for watch, will try to reconnect.", e); + registerWatch(); + } else { + // Note that this should not happen normally, since fabric8 client handles reconnect. + // In case it tries to reconnect this method is not called. + log.error("Unexpected error happened with watch. Will exit.", e); + System.exit(1); } + } } diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatOperator.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatOperator.java index bb94af5c1f..e29738b50d 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatOperator.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatOperator.java @@ -1,11 +1,7 @@ package io.javaoperatorsdk.operator.sample; -import io.fabric8.kubernetes.client.Config; -import io.fabric8.kubernetes.client.ConfigBuilder; -import io.fabric8.kubernetes.client.DefaultKubernetesClient; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.javaoperatorsdk.operator.Operator; -import io.javaoperatorsdk.operator.config.runtime.DefaultConfigurationService; +import java.io.IOException; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.takes.facets.fork.FkRegex; @@ -13,7 +9,12 @@ import org.takes.http.Exit; import org.takes.http.FtBasic; -import java.io.IOException; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.ConfigBuilder; +import io.fabric8.kubernetes.client.DefaultKubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.operator.Operator; +import io.javaoperatorsdk.operator.config.runtime.DefaultConfigurationService; public class TomcatOperator { diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Webapp.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Webapp.java index 1238264b37..7ef06f5600 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Webapp.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Webapp.java @@ -10,4 +10,5 @@ */ @Group("tomcatoperator.io") @Version("v1") -public class Webapp extends CustomResource implements Namespaced {} +public class Webapp extends CustomResource implements Namespaced { +} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java index 682ca2a5b9..58a0393796 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java @@ -1,5 +1,16 @@ package io.javaoperatorsdk.operator.sample; +import java.io.ByteArrayOutputStream; +import java.util.List; +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 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; @@ -7,19 +18,8 @@ import io.fabric8.kubernetes.client.dsl.ExecWatch; import io.javaoperatorsdk.operator.api.*; import io.javaoperatorsdk.operator.processing.event.EventSourceManager; -import okhttp3.Response; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.List; -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 okhttp3.Response; @Controller public class WebappController implements ResourceController { @@ -34,30 +34,41 @@ public WebappController(KubernetesClient kubernetesClient) { @Override public void init(EventSourceManager eventSourceManager) { - TomcatEventSource tomcatEventSource = TomcatEventSource.createAndRegisterWatch(kubernetesClient); + TomcatEventSource tomcatEventSource = + TomcatEventSource.createAndRegisterWatch(kubernetesClient); eventSourceManager.registerEventSource("tomcat-event-source", tomcatEventSource); } /** - * This method will be called not only on changes to Webapp objects but also when Tomcat objects change. + * This method will be called not only on changes to Webapp objects but also when Tomcat objects + * change. */ @Override public UpdateControl createOrUpdateResource(Webapp webapp, Context context) { - if (webapp.getStatus() != null && Objects.equals(webapp.getSpec().getUrl(), webapp.getStatus().getDeployedArtifact())) { + if (webapp.getStatus() != null + && Objects.equals(webapp.getSpec().getUrl(), webapp.getStatus().getDeployedArtifact())) { return UpdateControl.noUpdate(); } var tomcatClient = kubernetesClient.customResources(Tomcat.class); - Tomcat tomcat = tomcatClient.inNamespace(webapp.getMetadata().getNamespace()).withName(webapp.getSpec().getTomcat()).get(); + Tomcat tomcat = tomcatClient.inNamespace(webapp.getMetadata().getNamespace()) + .withName(webapp.getSpec().getTomcat()).get(); if (tomcat == null) { - throw new IllegalStateException("Cannot find Tomcat " + webapp.getSpec().getTomcat() + " for Webapp " + webapp.getMetadata().getName() + " in namespace " + webapp.getMetadata().getNamespace()); + throw 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()}; + 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); @@ -70,7 +81,8 @@ public UpdateControl createOrUpdateResource(Webapp webapp, Context data = new CompletableFuture<>(); try (ExecWatch execWatch = execCmd(pod, data, command)) { - status[i] = ""+pod.getMetadata().getName()+":"+data.get(30, TimeUnit.SECONDS);; + status[i] = "" + pod.getMetadata().getName() + ":" + data.get(30, TimeUnit.SECONDS);; } catch (ExecutionException e) { - status[i] = ""+pod.getMetadata().getName()+": ExecutionException - "+e.getMessage(); + status[i] = "" + pod.getMetadata().getName() + ": ExecutionException - " + e.getMessage(); } catch (InterruptedException e) { - status[i] = ""+pod.getMetadata().getName()+": InterruptedException - "+e.getMessage(); + status[i] = + "" + pod.getMetadata().getName() + ": InterruptedException - " + e.getMessage(); } catch (TimeoutException e) { - status[i] = ""+pod.getMetadata().getName()+": TimeoutException - "+e.getMessage(); + status[i] = "" + pod.getMetadata().getName() + ": TimeoutException - " + e.getMessage(); } } } @@ -133,13 +146,13 @@ private String[] executeCommandInAllPods( 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); + .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 { @@ -147,6 +160,7 @@ static class SimpleListener implements ExecListener { private CompletableFuture data; private ByteArrayOutputStream baos; private final Logger log = LoggerFactory.getLogger(getClass()); + public SimpleListener(CompletableFuture data, ByteArrayOutputStream baos) { this.data = data; this.baos = baos; diff --git a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/IntegrationTest.java b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/IntegrationTest.java deleted file mode 100644 index a35052f280..0000000000 --- a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/IntegrationTest.java +++ /dev/null @@ -1,122 +0,0 @@ -package io.javaoperatorsdk.operator.sample; - -import io.fabric8.kubernetes.api.model.*; -import io.fabric8.kubernetes.client.Config; -import io.fabric8.kubernetes.client.ConfigBuilder; -import io.fabric8.kubernetes.client.*; -import io.fabric8.kubernetes.client.extended.run.RunConfigBuilder; -import io.javaoperatorsdk.operator.Operator; -import io.javaoperatorsdk.operator.config.runtime.DefaultConfigurationService; -import org.junit.AfterClass; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -import static java.util.concurrent.TimeUnit.*; -import static org.awaitility.Awaitility.await; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.notNullValue; - -public class IntegrationTest { - - final static String TEST_NS = "tomcat-test"; - - final static Logger log = LoggerFactory.getLogger(IntegrationTest.class); - - @Test - public void test() { - Config config = new ConfigBuilder().withNamespace(null).build(); - KubernetesClient client = new DefaultKubernetesClient(config); - - if ("true".equals(System.getenv("RUN_OPERATOR_IN_TEST"))) { - Operator operator = new Operator(client, DefaultConfigurationService.instance()); - operator.register(new TomcatController(client)); - operator.register(new WebappController(client)); - } - - Tomcat tomcat = new Tomcat(); - tomcat.setMetadata(new ObjectMetaBuilder() - .withName("test-tomcat1") - .withNamespace(TEST_NS) - .build()); - tomcat.setSpec(new TomcatSpec()); - tomcat.getSpec().setReplicas(3); - tomcat.getSpec().setVersion(9); - - Webapp webapp1 = new Webapp(); - webapp1.setMetadata(new ObjectMetaBuilder() - .withName("test-webapp1") - .withNamespace(TEST_NS) - .build()); - webapp1.setSpec(new WebappSpec()); - webapp1.getSpec().setContextPath("webapp1"); - webapp1.getSpec().setTomcat(tomcat.getMetadata().getName()); - webapp1.getSpec().setUrl("http://tomcat.apache.org/tomcat-7.0-doc/appdev/sample/sample.war"); - - var tomcatClient = client.customResources(Tomcat.class); - var webappClient = client.customResources(Webapp.class); - - Namespace testNs = new NamespaceBuilder().withMetadata( - new ObjectMetaBuilder().withName(TEST_NS).build()).build(); - - if (testNs != null && client.namespaces().withName(TEST_NS).isReady()) { - // We perform a pre-run cleanup instead of a post-run cleanup. This is to help with debugging test results - // when running against a persistent cluster. The test namespace would stay after the test run so we can - // check what's there, but it would be cleaned up during the next test run. - log.info("Cleanup: deleting test namespace {}", TEST_NS); - client.namespaces().delete(testNs); - await().atMost(5, MINUTES).until(() -> client.namespaces().withName("tomcat-test").get() == null); - } - - log.info("Creating test namespace {}", TEST_NS); - client.namespaces().create(testNs); - - log.info("Creating test resources"); - tomcatClient.inNamespace(TEST_NS).create(tomcat); - webappClient.inNamespace(TEST_NS).create(webapp1); - - log.info("Waiting 5 minutes for Tomcat and Webapp CR statuses to be updated"); - await().atMost(5, MINUTES).untilAsserted(() -> { - Tomcat updatedTomcat = tomcatClient.inNamespace(TEST_NS).withName(tomcat.getMetadata().getName()).get(); - Webapp updatedWebapp = webappClient.inNamespace(TEST_NS).withName(webapp1.getMetadata().getName()).get(); - assertThat(updatedTomcat.getStatus(), is(notNullValue())); - assertThat(updatedTomcat.getStatus().getReadyReplicas(), equalTo(3)); - assertThat(updatedWebapp.getStatus(), is(notNullValue())); - assertThat(updatedWebapp.getStatus().getDeployedArtifact(), is(notNullValue())); - }); - - String url = "http://" + tomcat.getMetadata().getName() + "/" + webapp1.getSpec().getContextPath() + "/"; - log.info("Starting curl Pod and waiting 5 minutes for GET of {} to return 200", url); - - int timeoutMinutes = 5; - await("wait-for-webapp").atMost(timeoutMinutes, MINUTES).untilAsserted(() -> { - try { - - log.info("Starting curl Pod to test if webapp was deployed correctly"); - Pod curlPod = client.run().inNamespace(TEST_NS) - .withRunConfig(new RunConfigBuilder() - .withArgs("-s", "-o", "/dev/null", "-w", "%{http_code}", url) - .withName("curl") - .withImage("curlimages/curl:7.78.0") - .withRestartPolicy("Never") - .build()).done(); - log.info("Waiting for curl Pod to finish running"); - await("wait-for-curl-pod-run").atMost(timeoutMinutes, MINUTES).until(() -> client.pods().inNamespace(TEST_NS).withName("curl").get().getStatus().getPhase().equals("Succeeded")); - - String curlOutput = client.pods().inNamespace(TEST_NS).withName(curlPod.getMetadata().getName()).getLog(); - log.info("Output from curl: '{}'", curlOutput); - assertThat(curlOutput, equalTo("200")); - } catch (KubernetesClientException ex) { - throw new AssertionError(ex); - } finally { - client.pods().inNamespace(TEST_NS).withName("curl").delete(); - log.info("Waiting for curl Pod to be deleted"); - await("wait-for-curl-pod-stop").atMost(timeoutMinutes, MINUTES).until(() -> client.pods().inNamespace(TEST_NS).withName("curl").get() == null); - } - }); - } - -} diff --git a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorEndToEndTest.java b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorEndToEndTest.java new file mode 100644 index 0000000000..053f0a7612 --- /dev/null +++ b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorEndToEndTest.java @@ -0,0 +1,132 @@ +package io.javaoperatorsdk.operator.sample; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.fabric8.kubernetes.api.model.*; +import io.fabric8.kubernetes.client.*; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.ConfigBuilder; +import io.fabric8.kubernetes.client.extended.run.RunConfigBuilder; +import io.javaoperatorsdk.operator.Operator; +import io.javaoperatorsdk.operator.config.runtime.DefaultConfigurationService; + +import static java.util.concurrent.TimeUnit.*; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; + +public class TomcatOperatorEndToEndTest { + + final static String TEST_NS = "tomcat-test"; + + final static Logger log = LoggerFactory.getLogger(TomcatOperatorEndToEndTest.class); + + @Test + public void test() { + Config config = new ConfigBuilder().withNamespace(null).build(); + KubernetesClient client = new DefaultKubernetesClient(config); + + if ("true".equals(System.getenv("RUN_OPERATOR_IN_TEST"))) { + Operator operator = new Operator(client, DefaultConfigurationService.instance()); + operator.register(new TomcatController(client)); + operator.register(new WebappController(client)); + } + + Tomcat tomcat = new Tomcat(); + tomcat.setMetadata(new ObjectMetaBuilder() + .withName("test-tomcat1") + .withNamespace(TEST_NS) + .build()); + tomcat.setSpec(new TomcatSpec()); + tomcat.getSpec().setReplicas(3); + tomcat.getSpec().setVersion(9); + + Webapp webapp1 = new Webapp(); + webapp1.setMetadata(new ObjectMetaBuilder() + .withName("test-webapp1") + .withNamespace(TEST_NS) + .build()); + webapp1.setSpec(new WebappSpec()); + webapp1.getSpec().setContextPath("webapp1"); + webapp1.getSpec().setTomcat(tomcat.getMetadata().getName()); + webapp1.getSpec().setUrl("http://tomcat.apache.org/tomcat-7.0-doc/appdev/sample/sample.war"); + + var tomcatClient = client.customResources(Tomcat.class); + var webappClient = client.customResources(Webapp.class); + + Namespace testNs = new NamespaceBuilder().withMetadata( + new ObjectMetaBuilder().withName(TEST_NS).build()).build(); + + if (testNs != null && client.namespaces().withName(TEST_NS).isReady()) { + // We perform a pre-run cleanup instead of a post-run cleanup. This is to help with debugging + // test results + // when running against a persistent cluster. The test namespace would stay after the test run + // so we can + // check what's there, but it would be cleaned up during the next test run. + log.info("Cleanup: deleting test namespace {}", TEST_NS); + client.namespaces().delete(testNs); + await().atMost(5, MINUTES) + .until(() -> client.namespaces().withName("tomcat-test").get() == null); + } + + log.info("Creating test namespace {}", TEST_NS); + client.namespaces().create(testNs); + + log.info("Creating test resources"); + tomcatClient.inNamespace(TEST_NS).create(tomcat); + webappClient.inNamespace(TEST_NS).create(webapp1); + + log.info("Waiting 5 minutes for Tomcat and Webapp CR statuses to be updated"); + await().atMost(5, MINUTES).untilAsserted(() -> { + Tomcat updatedTomcat = + tomcatClient.inNamespace(TEST_NS).withName(tomcat.getMetadata().getName()).get(); + Webapp updatedWebapp = + webappClient.inNamespace(TEST_NS).withName(webapp1.getMetadata().getName()).get(); + assertThat(updatedTomcat.getStatus(), is(notNullValue())); + assertThat(updatedTomcat.getStatus().getReadyReplicas(), equalTo(3)); + assertThat(updatedWebapp.getStatus(), is(notNullValue())); + assertThat(updatedWebapp.getStatus().getDeployedArtifact(), is(notNullValue())); + }); + + String url = + "http://" + tomcat.getMetadata().getName() + "/" + webapp1.getSpec().getContextPath() + "/"; + log.info("Starting curl Pod and waiting 5 minutes for GET of {} to return 200", url); + + int timeoutMinutes = 5; + await("wait-for-webapp").atMost(timeoutMinutes, MINUTES).untilAsserted(() -> { + try { + + log.info("Starting curl Pod to test if webapp was deployed correctly"); + Pod curlPod = client.run().inNamespace(TEST_NS) + .withRunConfig(new RunConfigBuilder() + .withArgs("-s", "-o", "/dev/null", "-w", "%{http_code}", url) + .withName("curl") + .withImage("curlimages/curl:7.78.0") + .withRestartPolicy("Never") + .build()) + .done(); + log.info("Waiting for curl Pod to finish running"); + await("wait-for-curl-pod-run").atMost(timeoutMinutes, MINUTES) + .until(() -> client.pods().inNamespace(TEST_NS).withName("curl").get().getStatus() + .getPhase().equals("Succeeded")); + + String curlOutput = + client.pods().inNamespace(TEST_NS).withName(curlPod.getMetadata().getName()).getLog(); + log.info("Output from curl: '{}'", curlOutput); + assertThat(curlOutput, equalTo("200")); + } catch (KubernetesClientException ex) { + throw new AssertionError(ex); + } finally { + client.pods().inNamespace(TEST_NS).withName("curl").delete(); + log.info("Waiting for curl Pod to be deleted"); + await("wait-for-curl-pod-stop").atMost(timeoutMinutes, MINUTES) + .until(() -> client.pods().inNamespace(TEST_NS).withName("curl").get() == null); + } + }); + } + +} From 6d21f6a947fdeabf7793e43e77575e65b096b036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 28 Oct 2021 16:55:10 +0200 Subject: [PATCH 13/54] Build SDK before running Tomcat JIB build --- .github/workflows/sample-operator-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/sample-operator-tests.yml b/.github/workflows/sample-operator-tests.yml index e59372566f..01b64b9dc6 100644 --- a/.github/workflows/sample-operator-tests.yml +++ b/.github/workflows/sample-operator-tests.yml @@ -39,6 +39,9 @@ jobs: java-version: 11 distribution: adopt-hotspot + - name: Build SDK + run: mvn install -DskipTests + - name: build jib working-directory: sample-operators/tomcat-operator run: | From 0c2613e800def0a93a996888a681a442b640cbcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 28 Oct 2021 17:05:50 +0200 Subject: [PATCH 14/54] Rename E2E test --- .github/workflows/sample-operator-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sample-operator-tests.yml b/.github/workflows/sample-operator-tests.yml index 01b64b9dc6..0bf86a3d58 100644 --- a/.github/workflows/sample-operator-tests.yml +++ b/.github/workflows/sample-operator-tests.yml @@ -53,12 +53,12 @@ jobs: working-directory: sample-operators/tomcat-operator run: kubectl apply -f k8s/crd.yaml - - name: install tomcat operator + - name: Deploy Tomcat Operator working-directory: sample-operators/tomcat-operator run: | kubectl apply -f k8s/operator.yaml - - name: Run tests + - name: Run E2E Tests working-directory: sample-operators/tomcat-operator run: mvn -B test -P end-to-end-tests From 83e61ce5a758e594672b4208351182e33619e175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 28 Oct 2021 17:13:04 +0200 Subject: [PATCH 15/54] Rename E2E test --- ...OperatorEndToEndTest.java => TomcatOperatorEndToEnd.java} | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/{TomcatOperatorEndToEndTest.java => TomcatOperatorEndToEnd.java} (97%) diff --git a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorEndToEndTest.java b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorEndToEnd.java similarity index 97% rename from sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorEndToEndTest.java rename to sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorEndToEnd.java index 053f0a7612..dde0ee4528 100644 --- a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorEndToEndTest.java +++ b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorEndToEnd.java @@ -19,17 +19,18 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.notNullValue; -public class TomcatOperatorEndToEndTest { +public class TomcatOperatorEndToEnd { final static String TEST_NS = "tomcat-test"; - final static Logger log = LoggerFactory.getLogger(TomcatOperatorEndToEndTest.class); + final static Logger log = LoggerFactory.getLogger(TomcatOperatorEndToEnd.class); @Test public void test() { Config config = new ConfigBuilder().withNamespace(null).build(); KubernetesClient client = new DefaultKubernetesClient(config); + // Use this if you want to run the test without deploying the Operator to Kubernetes if ("true".equals(System.getenv("RUN_OPERATOR_IN_TEST"))) { Operator operator = new Operator(client, DefaultConfigurationService.instance()); operator.register(new TomcatController(client)); From cfbb66ce558b36cda32cccef245bf90f250f8f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 28 Oct 2021 17:17:53 +0200 Subject: [PATCH 16/54] Rename E2E test --- .../{TomcatOperatorEndToEnd.java => TomcatOperatorE2E.java} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/{TomcatOperatorEndToEnd.java => TomcatOperatorE2E.java} (98%) diff --git a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorEndToEnd.java b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java similarity index 98% rename from sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorEndToEnd.java rename to sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java index dde0ee4528..b6c08314a5 100644 --- a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorEndToEnd.java +++ b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java @@ -19,11 +19,11 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.notNullValue; -public class TomcatOperatorEndToEnd { +public class TomcatOperatorE2E { final static String TEST_NS = "tomcat-test"; - final static Logger log = LoggerFactory.getLogger(TomcatOperatorEndToEnd.class); + final static Logger log = LoggerFactory.getLogger(TomcatOperatorE2E.class); @Test public void test() { From 4e7ade84746b41d8c6199eabc4c07c9207b64c9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 28 Oct 2021 17:34:36 +0200 Subject: [PATCH 17/54] also print status of tomcat and webapp objects --- .github/workflows/sample-operator-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sample-operator-tests.yml b/.github/workflows/sample-operator-tests.yml index 0bf86a3d58..b89fdf2979 100644 --- a/.github/workflows/sample-operator-tests.yml +++ b/.github/workflows/sample-operator-tests.yml @@ -73,6 +73,6 @@ jobs: echo "Output of tomcat-operator pod" kubectl logs -l app=tomcat-operator -n tomcat-operator echo "All objects in tomcat-test" - kubectl get all -n tomcat-test -o yaml + kubectl get deployment,pod,tomcat,webapp -n tomcat-test -o yaml echo "Output of curl command" kubectl logs curl -n tomcat-test From 3c96fb7b5cf599d602c9ba158e14b39010a0411a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 28 Oct 2021 17:47:27 +0200 Subject: [PATCH 18/54] add start method --- .../io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java | 1 + 1 file changed, 1 insertion(+) diff --git a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java index b6c08314a5..6eb5c02563 100644 --- a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java +++ b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java @@ -35,6 +35,7 @@ public void test() { Operator operator = new Operator(client, DefaultConfigurationService.instance()); operator.register(new TomcatController(client)); operator.register(new WebappController(client)); + operator.start(); } Tomcat tomcat = new Tomcat(); From c8316d29d9850f8dd225837f705c4ac6c124f5ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Oct 2021 09:13:50 +0200 Subject: [PATCH 19/54] chore(deps): bump spring-boot.version from 2.5.5 to 2.5.6 (#617) Bumps `spring-boot.version` from 2.5.5 to 2.5.6. Updates `spring-boot-dependencies` from 2.5.5 to 2.5.6 - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v2.5.5...v2.5.6) Updates `spring-boot-maven-plugin` from 2.5.5 to 2.5.6 - [Release notes](https://github.com/spring-projects/spring-boot/releases) - [Commits](https://github.com/spring-projects/spring-boot/compare/v2.5.5...v2.5.6) --- updated-dependencies: - dependency-name: org.springframework.boot:spring-boot-dependencies dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.springframework.boot:spring-boot-maven-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3c2ed4aeb8..c5431c33f7 100644 --- a/pom.xml +++ b/pom.xml @@ -51,7 +51,7 @@ 1.13.0 3.21.0 4.1.0 - 2.5.5 + 2.5.6 1.7.5 2.11 From db4b4d667fd470403c827b66c85c60dfc2b27f12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Oct 2021 03:11:08 +0000 Subject: [PATCH 20/54] chore(deps): bump awaitility from 4.1.0 to 4.1.1 Bumps [awaitility](https://github.com/awaitility/awaitility) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/awaitility/awaitility/releases) - [Changelog](https://github.com/awaitility/awaitility/blob/master/changelog.txt) - [Commits](https://github.com/awaitility/awaitility/compare/awaitility-4.1.0...awaitility-4.1.1) --- updated-dependencies: - dependency-name: org.awaitility:awaitility dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- operator-framework-junit5/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/operator-framework-junit5/pom.xml b/operator-framework-junit5/pom.xml index 6256cc7de3..45b4f86c89 100644 --- a/operator-framework-junit5/pom.xml +++ b/operator-framework-junit5/pom.xml @@ -39,7 +39,7 @@ org.awaitility awaitility - 4.1.0 + 4.1.1 diff --git a/pom.xml b/pom.xml index c5431c33f7..729ac49177 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ 0.19 1.13.0 3.21.0 - 4.1.0 + 4.1.1 2.5.6 1.7.5 From dbc83c838d205168d59b3ff4fdb52da630703e66 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Tue, 26 Oct 2021 19:19:39 +0200 Subject: [PATCH 21/54] fix: prevent double registration of same CR with different controllers Fixes #626 --- .../io/javaoperatorsdk/operator/Operator.java | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java index 31bf1a80ec..5b23e3817a 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java @@ -3,9 +3,10 @@ import java.io.Closeable; import java.io.IOException; import java.net.ConnectException; -import java.util.Collections; -import java.util.LinkedList; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,7 +51,7 @@ public ConfigurationService getConfigurationService() { } public List getControllers() { - return Collections.unmodifiableList(controllers.controllers); + return new ArrayList<>(controllers.controllers.values()); } /** @@ -159,7 +160,7 @@ public void register( } private static class ControllerManager implements Closeable { - private final List controllers = new LinkedList<>(); + private final Map controllers = new HashMap<>(); private boolean started = false; @@ -173,7 +174,7 @@ public synchronized void shouldStart() { } public synchronized void start() { - controllers.parallelStream().forEach(ConfiguredController::start); + controllers.values().parallelStream().forEach(ConfiguredController::start); started = true; } @@ -183,7 +184,7 @@ public synchronized void close() { return; } - this.controllers.parallelStream().forEach(closeable -> { + this.controllers.values().parallelStream().forEach(closeable -> { try { log.debug("closing {}", closeable); closeable.close(); @@ -196,7 +197,15 @@ public synchronized void close() { } public synchronized void add(ConfiguredController configuredController) { - this.controllers.add(configuredController); + final var configuration = configuredController.getConfiguration(); + final var crdName = configuration.getCRDName(); + final var existing = controllers.get(crdName); + if (existing != null) { + throw new OperatorException("Cannot register controller " + configuration.getName() + + ": another controller (" + existing.getConfiguration().getName() + + ") is already registered for CRD " + crdName); + } + this.controllers.put(crdName, configuredController); if (started) { configuredController.start(); } From 1be67ddfc7fb097a67c8fa140db421b62755a08c Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 27 Oct 2021 10:37:50 +0000 Subject: [PATCH 22/54] Set new SNAPSHOT version into pom files. --- micrometer-support/pom.xml | 2 +- operator-framework-core/pom.xml | 2 +- operator-framework-junit5/pom.xml | 2 +- operator-framework/pom.xml | 2 +- pom.xml | 2 +- samples/common/pom.xml | 2 +- samples/pom.xml | 2 +- samples/pure-java/pom.xml | 2 +- samples/spring-boot-plain/pom.xml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/micrometer-support/pom.xml b/micrometer-support/pom.xml index a12c83738b..8f74d3f30e 100644 --- a/micrometer-support/pom.xml +++ b/micrometer-support/pom.xml @@ -5,7 +5,7 @@ java-operator-sdk io.javaoperatorsdk - 1.9.9-SNAPSHOT + 1.9.10-SNAPSHOT 4.0.0 diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml index e578810de5..87db534cbb 100644 --- a/operator-framework-core/pom.xml +++ b/operator-framework-core/pom.xml @@ -6,7 +6,7 @@ io.javaoperatorsdk java-operator-sdk - 1.9.9-SNAPSHOT + 1.9.10-SNAPSHOT ../pom.xml diff --git a/operator-framework-junit5/pom.xml b/operator-framework-junit5/pom.xml index 45b4f86c89..527a4ac30f 100644 --- a/operator-framework-junit5/pom.xml +++ b/operator-framework-junit5/pom.xml @@ -5,7 +5,7 @@ java-operator-sdk io.javaoperatorsdk - 1.9.9-SNAPSHOT + 1.9.10-SNAPSHOT 4.0.0 diff --git a/operator-framework/pom.xml b/operator-framework/pom.xml index 996c29bcae..43cf5fedc6 100644 --- a/operator-framework/pom.xml +++ b/operator-framework/pom.xml @@ -5,7 +5,7 @@ java-operator-sdk io.javaoperatorsdk - 1.9.9-SNAPSHOT + 1.9.10-SNAPSHOT 4.0.0 diff --git a/pom.xml b/pom.xml index 729ac49177..bc9514eee5 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk java-operator-sdk - 1.9.9-SNAPSHOT + 1.9.10-SNAPSHOT Operator SDK for Java Java SDK for implementing Kubernetes operators pom diff --git a/samples/common/pom.xml b/samples/common/pom.xml index 066f5007b3..e1023bf55f 100644 --- a/samples/common/pom.xml +++ b/samples/common/pom.xml @@ -6,7 +6,7 @@ io.javaoperatorsdk java-operator-sdk-samples - 1.9.9-SNAPSHOT + 1.9.10-SNAPSHOT operator-framework-samples-common diff --git a/samples/pom.xml b/samples/pom.xml index 73528a4b20..55e6706c24 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -6,7 +6,7 @@ io.javaoperatorsdk java-operator-sdk - 1.9.9-SNAPSHOT + 1.9.10-SNAPSHOT java-operator-sdk-samples diff --git a/samples/pure-java/pom.xml b/samples/pure-java/pom.xml index c53735c45b..6d75734f85 100644 --- a/samples/pure-java/pom.xml +++ b/samples/pure-java/pom.xml @@ -6,7 +6,7 @@ io.javaoperatorsdk java-operator-sdk-samples - 1.9.9-SNAPSHOT + 1.9.10-SNAPSHOT operator-framework-samples-pure-java diff --git a/samples/spring-boot-plain/pom.xml b/samples/spring-boot-plain/pom.xml index e361ca1cfc..a68cb86e10 100644 --- a/samples/spring-boot-plain/pom.xml +++ b/samples/spring-boot-plain/pom.xml @@ -6,7 +6,7 @@ io.javaoperatorsdk java-operator-sdk-samples - 1.9.9-SNAPSHOT + 1.9.10-SNAPSHOT operator-framework-samples-spring-boot-plain From 101db14b3be0531433e0df52cef98775077b0004 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Thu, 28 Oct 2021 13:04:59 +0200 Subject: [PATCH 23/54] fix: restart event handler (#632) Properly start event handler when starting the event source. Minor clean-ups. Fixes #630 Co-authored-by: csviri --- .../api/config/ExecutorServiceManager.java | 2 +- .../processing/DefaultEventHandler.java | 15 +++++- .../processing/event/EventHandler.java | 2 + .../internal/CustomResourceEventSource.java | 2 + .../processing/DefaultEventHandlerTest.java | 1 - .../CustomResourceEventSourceTest.java | 52 +++++++++++++++++++ 6 files changed, 71 insertions(+), 3 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManager.java index 682b004c3d..f593b3c755 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManager.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManager.java @@ -46,7 +46,7 @@ public static void close() { public static ExecutorServiceManager instance() { if (instance == null) { throw new IllegalStateException( - "ExecutorServiceManager hasn't been started. Call start method before using!"); + "ExecutorServiceManager hasn't been started. Call init method before using!"); } return instance; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/DefaultEventHandler.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/DefaultEventHandler.java index 27843a669a..bcaf395136 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/DefaultEventHandler.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/DefaultEventHandler.java @@ -60,11 +60,15 @@ public DefaultEventHandler(ConfiguredController controller) { controller.getConfiguration().getConfigurationService().getMetrics().getEventMonitor()); } - DefaultEventHandler(EventDispatcher eventDispatcher, String relatedControllerName, + public DefaultEventHandler(EventDispatcher eventDispatcher, String relatedControllerName, Retry retry) { this(null, relatedControllerName, eventDispatcher, retry, null); } + public boolean isRunning() { + return running; + } + private DefaultEventHandler(ExecutorService executor, String relatedControllerName, EventDispatcher eventDispatcher, Retry retry, EventMonitor monitor) { this.running = true; @@ -142,6 +146,15 @@ public void handleEvent(Event event) { } } + public void start() { + try { + lock.lock(); + this.running = true; + } finally { + lock.unlock(); + } + } + @Override public void close() { try { diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventHandler.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventHandler.java index e0a657e1d1..3bab14c2e0 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventHandler.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventHandler.java @@ -9,4 +9,6 @@ public interface EventHandler extends Closeable { @Override default void close() throws IOException {} + + default void start() {} } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSource.java index c7a959061b..360f57bdca 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSource.java @@ -48,6 +48,8 @@ public CustomResourceEventSource(ConfiguredController controller) { @Override public void start() { + eventHandler.start(); + final var configuration = controller.getConfiguration(); final var targetNamespaces = configuration.getEffectiveNamespaces(); final var client = controller.getCRClient(); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/DefaultEventHandlerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/DefaultEventHandlerTest.java index a03f4bd8a1..36269ddd57 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/DefaultEventHandlerTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/DefaultEventHandlerTest.java @@ -63,7 +63,6 @@ public void setup() { // todo: remove when(defaultEventSourceManagerMock.getCache()).thenReturn(customResourceCache); doCallRealMethod().when(defaultEventSourceManagerMock).getLatestResource(any()); - doCallRealMethod().when(defaultEventSourceManagerMock).getLatestResource(any()); doCallRealMethod().when(defaultEventSourceManagerMock).getLatestResources(any()); doCallRealMethod().when(defaultEventSourceManagerMock).getLatestResourceUids(any()); doCallRealMethod().when(defaultEventSourceManagerMock).cacheResource(any(), any()); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSourceTest.java index 727caf3be1..cefa720833 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSourceTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSourceTest.java @@ -1,5 +1,6 @@ package io.javaoperatorsdk.operator.processing.event.internal; +import java.io.IOException; import java.time.LocalDateTime; import java.util.List; @@ -7,7 +8,10 @@ import org.junit.jupiter.api.Test; import io.fabric8.kubernetes.api.model.KubernetesResourceList; +import io.fabric8.kubernetes.api.model.ListOptions; +import io.fabric8.kubernetes.client.Watch; import io.fabric8.kubernetes.client.Watcher; +import io.fabric8.kubernetes.client.dsl.FilterWatchListMultiDeletable; import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.Resource; import io.javaoperatorsdk.operator.Metrics; @@ -15,11 +19,19 @@ import io.javaoperatorsdk.operator.api.config.ConfigurationService; import io.javaoperatorsdk.operator.api.config.DefaultControllerConfiguration; import io.javaoperatorsdk.operator.processing.ConfiguredController; +import io.javaoperatorsdk.operator.processing.CustomResourceCache; +import io.javaoperatorsdk.operator.processing.DefaultEventHandler; +import io.javaoperatorsdk.operator.processing.EventDispatcher; +import io.javaoperatorsdk.operator.processing.event.DefaultEventSourceManager; import io.javaoperatorsdk.operator.processing.event.EventHandler; import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -102,6 +114,46 @@ public void eventNotMarkedForLastGenerationIfNoFinalizer() { verify(eventHandler, times(2)).handleEvent(any()); } + @Test + public void restartingShouldResumeEventHandling() throws IOException { + final var cr = TestUtils.testCustomResource(); + + CustomResourceCache customResourceCache = new CustomResourceCache(); + customResourceCache.cacheResource(cr); + DefaultEventSourceManager defaultEventSourceManagerMock = + mock(DefaultEventSourceManager.class); + EventDispatcher eventDispatcherMock = mock(EventDispatcher.class); + DefaultEventHandler local = new DefaultEventHandler(eventDispatcherMock, "Test", + null); + local.setEventSourceManager(defaultEventSourceManagerMock); + when(defaultEventSourceManagerMock.getCache()).thenReturn(customResourceCache); + doCallRealMethod().when(defaultEventSourceManagerMock).getLatestResource(any()); + doCallRealMethod().when(defaultEventSourceManagerMock).getLatestResources(any()); + doCallRealMethod().when(defaultEventSourceManagerMock).getLatestResourceUids(any()); + doCallRealMethod().when(defaultEventSourceManagerMock).cacheResource(any(), any()); + + final var mock = mock(FilterWatchListMultiDeletable.class); + when(mock.watch((ListOptions) any(), any())).thenReturn(mock(Watch.class)); + when(client.inAnyNamespace()).thenReturn(mock); + + customResourceEventSource.setEventHandler(local); + + customResourceEventSource.eventReceived(Watcher.Action.MODIFIED, cr); + verify(eventDispatcherMock, timeout(50).times(1)).handleExecution(any()); + + customResourceEventSource.close(); + assertFalse(local.isRunning()); + customResourceEventSource.eventReceived(Watcher.Action.MODIFIED, cr); + // mockito times method is not reset and keeps increasing so here we stay at 1 call + verify(eventDispatcherMock, timeout(50).times(1)).handleExecution(any()); + + customResourceEventSource.start(); + assertTrue(local.isRunning()); + customResourceEventSource.eventReceived(Watcher.Action.MODIFIED, cr); + // we're expecting another call to the dispatcher, so total number of calls should now be 2 + verify(eventDispatcherMock, timeout(50).times(2)).handleExecution(any()); + } + private static class TestConfiguredController extends ConfiguredController { public TestConfiguredController(boolean generationAware) { From 0bb296e77d5bc55493b4fda2ae01565c3717a67b Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 28 Oct 2021 11:08:18 +0000 Subject: [PATCH 24/54] Set new SNAPSHOT version into pom files. --- micrometer-support/pom.xml | 2 +- operator-framework-core/pom.xml | 2 +- operator-framework-junit5/pom.xml | 2 +- operator-framework/pom.xml | 2 +- pom.xml | 2 +- samples/common/pom.xml | 2 +- samples/pom.xml | 2 +- samples/pure-java/pom.xml | 2 +- samples/spring-boot-plain/pom.xml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/micrometer-support/pom.xml b/micrometer-support/pom.xml index 8f74d3f30e..5e54be30f5 100644 --- a/micrometer-support/pom.xml +++ b/micrometer-support/pom.xml @@ -5,7 +5,7 @@ java-operator-sdk io.javaoperatorsdk - 1.9.10-SNAPSHOT + 1.9.11-SNAPSHOT 4.0.0 diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml index 87db534cbb..5cc9bf1d4b 100644 --- a/operator-framework-core/pom.xml +++ b/operator-framework-core/pom.xml @@ -6,7 +6,7 @@ io.javaoperatorsdk java-operator-sdk - 1.9.10-SNAPSHOT + 1.9.11-SNAPSHOT ../pom.xml diff --git a/operator-framework-junit5/pom.xml b/operator-framework-junit5/pom.xml index 527a4ac30f..70570a7d15 100644 --- a/operator-framework-junit5/pom.xml +++ b/operator-framework-junit5/pom.xml @@ -5,7 +5,7 @@ java-operator-sdk io.javaoperatorsdk - 1.9.10-SNAPSHOT + 1.9.11-SNAPSHOT 4.0.0 diff --git a/operator-framework/pom.xml b/operator-framework/pom.xml index 43cf5fedc6..8f76543592 100644 --- a/operator-framework/pom.xml +++ b/operator-framework/pom.xml @@ -5,7 +5,7 @@ java-operator-sdk io.javaoperatorsdk - 1.9.10-SNAPSHOT + 1.9.11-SNAPSHOT 4.0.0 diff --git a/pom.xml b/pom.xml index bc9514eee5..256ea8a5a1 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk java-operator-sdk - 1.9.10-SNAPSHOT + 1.9.11-SNAPSHOT Operator SDK for Java Java SDK for implementing Kubernetes operators pom diff --git a/samples/common/pom.xml b/samples/common/pom.xml index e1023bf55f..939ad59404 100644 --- a/samples/common/pom.xml +++ b/samples/common/pom.xml @@ -6,7 +6,7 @@ io.javaoperatorsdk java-operator-sdk-samples - 1.9.10-SNAPSHOT + 1.9.11-SNAPSHOT operator-framework-samples-common diff --git a/samples/pom.xml b/samples/pom.xml index 55e6706c24..a1e72542a1 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -6,7 +6,7 @@ io.javaoperatorsdk java-operator-sdk - 1.9.10-SNAPSHOT + 1.9.11-SNAPSHOT java-operator-sdk-samples diff --git a/samples/pure-java/pom.xml b/samples/pure-java/pom.xml index 6d75734f85..2a2d4315c4 100644 --- a/samples/pure-java/pom.xml +++ b/samples/pure-java/pom.xml @@ -6,7 +6,7 @@ io.javaoperatorsdk java-operator-sdk-samples - 1.9.10-SNAPSHOT + 1.9.11-SNAPSHOT operator-framework-samples-pure-java diff --git a/samples/spring-boot-plain/pom.xml b/samples/spring-boot-plain/pom.xml index a68cb86e10..e5691cd78b 100644 --- a/samples/spring-boot-plain/pom.xml +++ b/samples/spring-boot-plain/pom.xml @@ -6,7 +6,7 @@ io.javaoperatorsdk java-operator-sdk-samples - 1.9.10-SNAPSHOT + 1.9.11-SNAPSHOT operator-framework-samples-spring-boot-plain From 9a5aa5be84486a3837d052f5f0e70d57a6abf228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 28 Oct 2021 17:54:52 +0200 Subject: [PATCH 25/54] update to framework version 1.9.11-SNAPSHOT --- sample-operators/pom.xml | 2 +- sample-operators/tomcat-operator/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sample-operators/pom.xml b/sample-operators/pom.xml index 78411d7c2e..d85a0efc84 100644 --- a/sample-operators/pom.xml +++ b/sample-operators/pom.xml @@ -7,7 +7,7 @@ io.javaoperatorsdk java-operator-sdk - 1.9.9-SNAPSHOT + 1.9.11-SNAPSHOT ../pom.xml diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml index c66da6d9e3..fe0dacdccf 100644 --- a/sample-operators/tomcat-operator/pom.xml +++ b/sample-operators/tomcat-operator/pom.xml @@ -7,7 +7,7 @@ io.javaoperatorsdk sample-operators - 1.9.9-SNAPSHOT + 1.9.11-SNAPSHOT ../pom.xml From 464f892c0208e5e2d5bbbf867488e9d0f1504f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 28 Oct 2021 17:56:52 +0200 Subject: [PATCH 26/54] add start call to operator --- .../io/javaoperatorsdk/operator/sample/TomcatOperator.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatOperator.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatOperator.java index e29738b50d..e23dab07e1 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatOperator.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatOperator.java @@ -25,11 +25,9 @@ public static void main(String[] args) throws IOException { Config config = new ConfigBuilder().withNamespace(null).build(); KubernetesClient client = new DefaultKubernetesClient(config); Operator operator = new Operator(client, DefaultConfigurationService.instance()); - - TomcatController tomcatController = new TomcatController(client); - operator.register(tomcatController); - + operator.register(new TomcatController(client)); operator.register(new WebappController(client)); + operator.start(); new FtBasic(new TkFork(new FkRegex("/health", "ALL GOOD.")), 8080).start(Exit.NEVER); } From 2e5e60e99e696867fb59bedff9631d23a95c1671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 28 Oct 2021 18:20:34 +0200 Subject: [PATCH 27/54] fix dependency version of operator-framework --- sample-operators/tomcat-operator/pom.xml | 2 +- .../javaoperatorsdk/operator/sample/TomcatOperatorE2E.java | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml index fe0dacdccf..80edee0274 100644 --- a/sample-operators/tomcat-operator/pom.xml +++ b/sample-operators/tomcat-operator/pom.xml @@ -26,7 +26,7 @@ io.javaoperatorsdk operator-framework - 1.9.2 + ${project.version} org.apache.logging.log4j diff --git a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java index 6eb5c02563..2545644836 100644 --- a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java +++ b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java @@ -65,10 +65,8 @@ public void test() { if (testNs != null && client.namespaces().withName(TEST_NS).isReady()) { // We perform a pre-run cleanup instead of a post-run cleanup. This is to help with debugging - // test results - // when running against a persistent cluster. The test namespace would stay after the test run - // so we can - // check what's there, but it would be cleaned up during the next test run. + // test results when running against a persistent cluster. The test namespace would stay + // after the test run so we can check what's there, but it would be cleaned up during the next test run. log.info("Cleanup: deleting test namespace {}", TEST_NS); client.namespaces().delete(testNs); await().atMost(5, MINUTES) From d118ab6e9e1c04e644a55541ed09058f3b651c46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 28 Oct 2021 18:39:18 +0200 Subject: [PATCH 28/54] removing ready check from namespce as it's causing problems --- .../io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java index 2545644836..39bc9b2498 100644 --- a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java +++ b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java @@ -63,7 +63,7 @@ public void test() { Namespace testNs = new NamespaceBuilder().withMetadata( new ObjectMetaBuilder().withName(TEST_NS).build()).build(); - if (testNs != null && client.namespaces().withName(TEST_NS).isReady()) { + if (testNs != null) { // We perform a pre-run cleanup instead of a post-run cleanup. This is to help with debugging // test results when running against a persistent cluster. The test namespace would stay // after the test run so we can check what's there, but it would be cleaned up during the next test run. From 579a65e770de082d4705ed7c651602b610b7880c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Fri, 29 Oct 2021 08:57:31 +0200 Subject: [PATCH 29/54] enable maven cache --- .github/workflows/sample-operator-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sample-operator-tests.yml b/.github/workflows/sample-operator-tests.yml index b89fdf2979..ec07cf1e0b 100644 --- a/.github/workflows/sample-operator-tests.yml +++ b/.github/workflows/sample-operator-tests.yml @@ -38,6 +38,7 @@ jobs: with: java-version: 11 distribution: adopt-hotspot + cache: 'maven' - name: Build SDK run: mvn install -DskipTests From ff98e0cb1431b05903ff9679f5fe7c15ea5734c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Fri, 29 Oct 2021 13:05:40 +0200 Subject: [PATCH 30/54] improve logging --- .../io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java index 39bc9b2498..39a8ef2493 100644 --- a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java +++ b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java @@ -76,8 +76,9 @@ public void test() { log.info("Creating test namespace {}", TEST_NS); client.namespaces().create(testNs); - log.info("Creating test resources"); + log.info("Creating test Tomcat object: {}", tomcat); tomcatClient.inNamespace(TEST_NS).create(tomcat); + log.info("Creating test Webapp object: {}", webapp1); webappClient.inNamespace(TEST_NS).create(webapp1); log.info("Waiting 5 minutes for Tomcat and Webapp CR statuses to be updated"); From f2e481e0e69e152f58a39f2bc12efb95833f649a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Fri, 29 Oct 2021 13:19:09 +0200 Subject: [PATCH 31/54] add nicers tostring methods --- .../java/io/javaoperatorsdk/operator/sample/Tomcat.java | 6 ++++++ .../java/io/javaoperatorsdk/operator/sample/Webapp.java | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java index 7a91f1510e..cac046d116 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java @@ -4,8 +4,14 @@ import io.fabric8.kubernetes.client.CustomResource; import io.fabric8.kubernetes.model.annotation.Group; import io.fabric8.kubernetes.model.annotation.Version; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; @Group("tomcatoperator.io") @Version("v1") public class Tomcat extends CustomResource implements Namespaced { + + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); + } } diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Webapp.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Webapp.java index 7ef06f5600..f9999633e3 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Webapp.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Webapp.java @@ -4,6 +4,8 @@ import io.fabric8.kubernetes.client.CustomResource; import io.fabric8.kubernetes.model.annotation.Group; import io.fabric8.kubernetes.model.annotation.Version; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; /** * Represents a web application deployed in a Tomcat deployment @@ -11,4 +13,8 @@ @Group("tomcatoperator.io") @Version("v1") public class Webapp extends CustomResource implements Namespaced { + + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); + } } From e3bbc750a701ccfe1e26f880c73bf1cd32b0a9c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 4 Nov 2021 18:02:17 +0100 Subject: [PATCH 32/54] rename integration test to e2e test --- .../{sample-operator-tests.yml => end-to-end-tests.yml} | 2 +- pom.xml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) rename .github/workflows/{sample-operator-tests.yml => end-to-end-tests.yml} (98%) diff --git a/.github/workflows/sample-operator-tests.yml b/.github/workflows/end-to-end-tests.yml similarity index 98% rename from .github/workflows/sample-operator-tests.yml rename to .github/workflows/end-to-end-tests.yml index ec07cf1e0b..1d06364eb4 100644 --- a/.github/workflows/sample-operator-tests.yml +++ b/.github/workflows/end-to-end-tests.yml @@ -1,6 +1,6 @@ # End to end integration test which deploys the Tomcat operator to a Kubernetes # (Kind) cluster and creates custom resources to verify the operator's functionality -name: Tomcat integration test +name: TomcatOperator End to End test on: push: branches: diff --git a/pom.xml b/pom.xml index 256ea8a5a1..128fa05591 100644 --- a/pom.xml +++ b/pom.xml @@ -291,6 +291,7 @@ **/*IT.java + **/*E2E.java @@ -392,6 +393,7 @@ **/*IT.java + **/*E2E.java From 820278728c35efe6841727f9b89f0f6ad50b0e3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 4 Nov 2021 18:26:05 +0100 Subject: [PATCH 33/54] better handling of curl pod at the end of the test --- .../operator/sample/TomcatOperatorE2E.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java index 39a8ef2493..7e77079440 100644 --- a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java +++ b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java @@ -97,8 +97,7 @@ public void test() { "http://" + tomcat.getMetadata().getName() + "/" + webapp1.getSpec().getContextPath() + "/"; log.info("Starting curl Pod and waiting 5 minutes for GET of {} to return 200", url); - int timeoutMinutes = 5; - await("wait-for-webapp").atMost(timeoutMinutes, MINUTES).untilAsserted(() -> { + await("wait-for-webapp").atMost(6, MINUTES).untilAsserted(() -> { try { log.info("Starting curl Pod to test if webapp was deployed correctly"); @@ -111,9 +110,11 @@ public void test() { .build()) .done(); log.info("Waiting for curl Pod to finish running"); - await("wait-for-curl-pod-run").atMost(timeoutMinutes, MINUTES) - .until(() -> client.pods().inNamespace(TEST_NS).withName("curl").get().getStatus() - .getPhase().equals("Succeeded")); + await("wait-for-curl-pod-run").atMost(2, MINUTES) + .until(() -> { + String phase = client.pods().inNamespace(TEST_NS).withName("curl").get().getStatus().getPhase(); + return phase.equals("Succeeded") || phase.equals("Failed"); + }); String curlOutput = client.pods().inNamespace(TEST_NS).withName(curlPod.getMetadata().getName()).getLog(); @@ -122,9 +123,9 @@ public void test() { } catch (KubernetesClientException ex) { throw new AssertionError(ex); } finally { + log.info("Deleting curl Pod"); client.pods().inNamespace(TEST_NS).withName("curl").delete(); - log.info("Waiting for curl Pod to be deleted"); - await("wait-for-curl-pod-stop").atMost(timeoutMinutes, MINUTES) + await("wait-for-curl-pod-stop").atMost(1, MINUTES) .until(() -> client.pods().inNamespace(TEST_NS).withName("curl").get() == null); } }); From 6585101db2b614b4e1e868eac07bfc72fda5d128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Thu, 4 Nov 2021 18:40:57 +0100 Subject: [PATCH 34/54] remove integration test references --- .github/workflows/end-to-end-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/end-to-end-tests.yml b/.github/workflows/end-to-end-tests.yml index 1d06364eb4..679fc448a0 100644 --- a/.github/workflows/end-to-end-tests.yml +++ b/.github/workflows/end-to-end-tests.yml @@ -9,7 +9,7 @@ jobs: tomcat_integration_test: runs-on: ubuntu-latest env: - KIND_CL_NAME: tomcat-integration-test + KIND_CL_NAME: e2e-test steps: - name: Checkout uses: actions/checkout@v2 @@ -26,7 +26,7 @@ jobs: - name: Create Kubernetes KinD Cluster uses: container-tools/kind-action@v1.5.0 with: - cluster_name: tomcat-integration-test + cluster_name: e2e-test registry: false - name: Apply CRDs From c0004c5ce91c5f42a448557862b6bee0a2f1d677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 29 Oct 2021 10:12:39 +0200 Subject: [PATCH 35/54] docs: webpage docs skeleton (#631) --- README.md | 1 + docs/Gemfile.lock | 4 ++- docs/_data/sidebar.yml | 11 ++++++-- docs/documentation/faq.md | 7 +++++ docs/documentation/features.md | 39 +++++++++++++++++++++++++++ docs/documentation/intro-operators.md | 12 +++++++++ 6 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 docs/documentation/faq.md create mode 100644 docs/documentation/features.md create mode 100644 docs/documentation/intro-operators.md diff --git a/README.md b/README.md index 2988e55408..d42c45b7a0 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Build Kubernetes Operators in Java without hassle. Inspired by [operator-sdk](https://github.com/operator-framework/operator-sdk). +Our webpage with documentation is getting better every day: https://javaoperatorsdk.io/ Table of Contents ========== diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index e8ee3a5427..605cd1d411 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -74,11 +74,13 @@ GEM unicode-display_width (1.7.0) PLATFORMS + ruby universal-darwin-20 + x86_64-linux DEPENDENCIES jekyll (~> 4.2) jekyll-github-metadata BUNDLED WITH - 2.2.22 + 2.2.30 diff --git a/docs/_data/sidebar.yml b/docs/_data/sidebar.yml index 96266dd842..7d88cde491 100644 --- a/docs/_data/sidebar.yml +++ b/docs/_data/sidebar.yml @@ -1,7 +1,14 @@ # Navbar menu navigation links + - title: Intro to Operators + url: /docs/intro-operators - title: Getting Started url: /docs/getting-started - - title: Contributing - url: /docs/contributing - title: How to use Samples url: /docs/using-samples + - title: Features + url: /docs/features + - title: FAQ + url: /docs/faq + - title: Contributing + url: /docs/contributing + diff --git a/docs/documentation/faq.md b/docs/documentation/faq.md new file mode 100644 index 0000000000..f4127b39fe --- /dev/null +++ b/docs/documentation/faq.md @@ -0,0 +1,7 @@ +--- +title: FAQ +description: Frequently asked questions +layout: docs +permalink: /docs/faq +--- + diff --git a/docs/documentation/features.md b/docs/documentation/features.md new file mode 100644 index 0000000000..7f77bcbbde --- /dev/null +++ b/docs/documentation/features.md @@ -0,0 +1,39 @@ +--- +title: Features +description: Features of the SDK +layout: docs +permalink: /docs/features +--- + +# Features + +## Controller Registration + +## Configurations + +## Finalizers + +### When not to Use Finalizers + +## Automatic Retries on Error + +### Correctness and automatic retry + +## Re-Scheduling Execution + +## Retry and Re-Scheduling Common Behavior + +## Handling Related Events with Event Sources + +### Caching and Event Sources + +### The CustomResourceEventSource + +### Built-in Event Sources + +## Monitoring with Micrometer + + + + + diff --git a/docs/documentation/intro-operators.md b/docs/documentation/intro-operators.md new file mode 100644 index 0000000000..13a9812df1 --- /dev/null +++ b/docs/documentation/intro-operators.md @@ -0,0 +1,12 @@ +--- +title: Introduction to Operators +description: Introduction to Operators +layout: docs +permalink: /docs/intro-operators +--- + +# Introduction To Operators + +On this page we selected a collection of resources to introduce you to the concepts of Kubernetes Operators. + + From 13e1f91d2c44fe22ba67bb5ebd34bfb41596a402 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Thu, 28 Oct 2021 20:30:49 +0200 Subject: [PATCH 36/54] fix: replace jandex plugin by Quarkus detection of beans.xml files This allows to not run into compatibility between index versions. --- .../src/main/resources/META-INF/beans.xml | 0 pom.xml | 18 ------------------ 2 files changed, 18 deletions(-) create mode 100644 operator-framework-core/src/main/resources/META-INF/beans.xml diff --git a/operator-framework-core/src/main/resources/META-INF/beans.xml b/operator-framework-core/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pom.xml b/pom.xml index 128fa05591..603a56d622 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,6 @@ 2.8.2 2.5.2 5.0.0 - 1.2.1 2.16.0 1.0 1.6.2 @@ -225,11 +224,6 @@ maven-install-plugin ${maven-install-plugin.version} - - org.jboss.jandex - jandex-maven-plugin - ${jandex-maven-plugin.version} - net.revelc.code.formatter formatter-maven-plugin @@ -270,18 +264,6 @@ - - org.jboss.jandex - jandex-maven-plugin - - - make-index - - jandex - - - - org.apache.maven.plugins maven-surefire-plugin From 53a6178f9d770fbf17ead9e0d6b0bcbc54070641 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Thu, 28 Oct 2021 21:02:03 +0200 Subject: [PATCH 37/54] fix: improve duplicated controller detection, add tests While we originally planned to make it possible to register controllers with the same CR but with different version (see #637), that behavior should actually be forbidden since only one CR version can be served, see #94 for more details. --- .../io/javaoperatorsdk/operator/Operator.java | 9 +-- .../operator/ControllerManagerTest.java | 76 +++++++++++++++++++ .../sample/simple/DuplicateCRController.java | 16 ++++ .../TestCustomResourceControllerV2.java | 16 ++++ .../sample/simple/TestCustomResourceV2.java | 12 +++ 5 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java create mode 100644 operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/DuplicateCRController.java create mode 100644 operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceControllerV2.java create mode 100644 operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceV2.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java index 5b23e3817a..7350bafce4 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java @@ -159,11 +159,10 @@ public void register( } } - private static class ControllerManager implements Closeable { + static class ControllerManager implements Closeable { private final Map controllers = new HashMap<>(); private boolean started = false; - public synchronized void shouldStart() { if (started) { return; @@ -201,9 +200,9 @@ public synchronized void add(ConfiguredController configuredController) { final var crdName = configuration.getCRDName(); final var existing = controllers.get(crdName); if (existing != null) { - throw new OperatorException("Cannot register controller " + configuration.getName() - + ": another controller (" + existing.getConfiguration().getName() - + ") is already registered for CRD " + crdName); + throw new OperatorException("Cannot register controller '" + configuration.getName() + + "': another controller named '" + existing.getConfiguration().getName() + + "' is already registered for CRD '" + crdName + "'"); } this.controllers.put(crdName, configuredController); if (started) { diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java new file mode 100644 index 0000000000..d25378499e --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java @@ -0,0 +1,76 @@ +package io.javaoperatorsdk.operator; + +import org.junit.Test; + +import io.fabric8.kubernetes.client.CustomResource; +import io.javaoperatorsdk.operator.Operator.ControllerManager; +import io.javaoperatorsdk.operator.api.ResourceController; +import io.javaoperatorsdk.operator.api.config.DefaultControllerConfiguration; +import io.javaoperatorsdk.operator.processing.ConfiguredController; +import io.javaoperatorsdk.operator.sample.simple.DuplicateCRController; +import io.javaoperatorsdk.operator.sample.simple.TestCustomResource; +import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceController; +import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceControllerV2; +import io.javaoperatorsdk.operator.sample.simple.TestCustomResourceV2; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ControllerManagerTest { + + @Test + public void shouldNotAddMultipleControllersForSameCustomResource() { + final var registered = new TestControllerConfiguration<>(new TestCustomResourceController(null), + TestCustomResource.class); + final var duplicated = + new TestControllerConfiguration<>(new DuplicateCRController(), TestCustomResource.class); + + checkException(registered, duplicated); + } + + @Test + public void addingMultipleControllersForCustomResourcesWithDifferentVersionsShouldNotWork() { + final var registered = new TestControllerConfiguration<>(new TestCustomResourceController(null), + TestCustomResource.class); + final var duplicated = new TestControllerConfiguration<>(new TestCustomResourceControllerV2(), + TestCustomResourceV2.class); + + checkException(registered, duplicated); + + } + + private , U extends CustomResource> void checkException( + TestControllerConfiguration registered, + TestControllerConfiguration duplicated) { + final var exception = assertThrows(OperatorException.class, () -> { + final var controllerManager = new ControllerManager(); + controllerManager.add(new ConfiguredController<>(registered.controller, registered, null)); + controllerManager.add(new ConfiguredController<>(duplicated.controller, duplicated, null)); + }); + final var msg = exception.getMessage(); + assertTrue( + msg.contains("Cannot register controller '" + duplicated.getControllerName() + "'") + && msg.contains(registered.getControllerName()) + && msg.contains(registered.getCRDName())); + } + + private static class TestControllerConfiguration> + extends DefaultControllerConfiguration { + private final ResourceController controller; + + public TestControllerConfiguration(ResourceController controller, Class crClass) { + super(null, getControllerName(controller), + CustomResource.getCRDName(crClass), null, false, null, null, null, null, crClass, null); + this.controller = controller; + } + + static > String getControllerName( + ResourceController controller) { + return controller.getClass().getSimpleName() + "Controller"; + } + + private String getControllerName() { + return getControllerName(controller); + } + } +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/DuplicateCRController.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/DuplicateCRController.java new file mode 100644 index 0000000000..e61909fafe --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/DuplicateCRController.java @@ -0,0 +1,16 @@ +package io.javaoperatorsdk.operator.sample.simple; + +import io.javaoperatorsdk.operator.api.Context; +import io.javaoperatorsdk.operator.api.Controller; +import io.javaoperatorsdk.operator.api.ResourceController; +import io.javaoperatorsdk.operator.api.UpdateControl; + +@Controller +public class DuplicateCRController implements ResourceController { + + @Override + public UpdateControl createOrUpdateResource(TestCustomResource resource, + Context context) { + return UpdateControl.noUpdate(); + } +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceControllerV2.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceControllerV2.java new file mode 100644 index 0000000000..a5a3d2c935 --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceControllerV2.java @@ -0,0 +1,16 @@ +package io.javaoperatorsdk.operator.sample.simple; + +import io.javaoperatorsdk.operator.api.Context; +import io.javaoperatorsdk.operator.api.Controller; +import io.javaoperatorsdk.operator.api.ResourceController; +import io.javaoperatorsdk.operator.api.UpdateControl; + +@Controller +public class TestCustomResourceControllerV2 implements ResourceController { + + @Override + public UpdateControl createOrUpdateResource(TestCustomResourceV2 resource, + Context context) { + return UpdateControl.noUpdate(); + } +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceV2.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceV2.java new file mode 100644 index 0000000000..e02e359bcc --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/sample/simple/TestCustomResourceV2.java @@ -0,0 +1,12 @@ +package io.javaoperatorsdk.operator.sample.simple; + +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.javaoperatorsdk.io") +@Version("v2") +public class TestCustomResourceV2 + extends CustomResource { + +} From 6ed14d5c9ab14566e4fb7fa144db40a7c3d7c76d Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Fri, 29 Oct 2021 16:12:42 +0000 Subject: [PATCH 38/54] Set new SNAPSHOT version into pom files. --- micrometer-support/pom.xml | 2 +- operator-framework-core/pom.xml | 2 +- operator-framework-junit5/pom.xml | 2 +- operator-framework/pom.xml | 2 +- pom.xml | 2 +- samples/common/pom.xml | 2 +- samples/pom.xml | 2 +- samples/pure-java/pom.xml | 2 +- samples/spring-boot-plain/pom.xml | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/micrometer-support/pom.xml b/micrometer-support/pom.xml index 5e54be30f5..779bed3369 100644 --- a/micrometer-support/pom.xml +++ b/micrometer-support/pom.xml @@ -5,7 +5,7 @@ java-operator-sdk io.javaoperatorsdk - 1.9.11-SNAPSHOT + 1.9.12-SNAPSHOT 4.0.0 diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml index 5cc9bf1d4b..e20cac3aef 100644 --- a/operator-framework-core/pom.xml +++ b/operator-framework-core/pom.xml @@ -6,7 +6,7 @@ io.javaoperatorsdk java-operator-sdk - 1.9.11-SNAPSHOT + 1.9.12-SNAPSHOT ../pom.xml diff --git a/operator-framework-junit5/pom.xml b/operator-framework-junit5/pom.xml index 70570a7d15..1c05135126 100644 --- a/operator-framework-junit5/pom.xml +++ b/operator-framework-junit5/pom.xml @@ -5,7 +5,7 @@ java-operator-sdk io.javaoperatorsdk - 1.9.11-SNAPSHOT + 1.9.12-SNAPSHOT 4.0.0 diff --git a/operator-framework/pom.xml b/operator-framework/pom.xml index 8f76543592..f5010675b5 100644 --- a/operator-framework/pom.xml +++ b/operator-framework/pom.xml @@ -5,7 +5,7 @@ java-operator-sdk io.javaoperatorsdk - 1.9.11-SNAPSHOT + 1.9.12-SNAPSHOT 4.0.0 diff --git a/pom.xml b/pom.xml index 603a56d622..c41aa5329b 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk java-operator-sdk - 1.9.11-SNAPSHOT + 1.9.12-SNAPSHOT Operator SDK for Java Java SDK for implementing Kubernetes operators pom diff --git a/samples/common/pom.xml b/samples/common/pom.xml index 939ad59404..781073b774 100644 --- a/samples/common/pom.xml +++ b/samples/common/pom.xml @@ -6,7 +6,7 @@ io.javaoperatorsdk java-operator-sdk-samples - 1.9.11-SNAPSHOT + 1.9.12-SNAPSHOT operator-framework-samples-common diff --git a/samples/pom.xml b/samples/pom.xml index a1e72542a1..f24d4ff71a 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -6,7 +6,7 @@ io.javaoperatorsdk java-operator-sdk - 1.9.11-SNAPSHOT + 1.9.12-SNAPSHOT java-operator-sdk-samples diff --git a/samples/pure-java/pom.xml b/samples/pure-java/pom.xml index 2a2d4315c4..86b88f1d06 100644 --- a/samples/pure-java/pom.xml +++ b/samples/pure-java/pom.xml @@ -6,7 +6,7 @@ io.javaoperatorsdk java-operator-sdk-samples - 1.9.11-SNAPSHOT + 1.9.12-SNAPSHOT operator-framework-samples-pure-java diff --git a/samples/spring-boot-plain/pom.xml b/samples/spring-boot-plain/pom.xml index e5691cd78b..3f4d241312 100644 --- a/samples/spring-boot-plain/pom.xml +++ b/samples/spring-boot-plain/pom.xml @@ -6,7 +6,7 @@ io.javaoperatorsdk java-operator-sdk-samples - 1.9.11-SNAPSHOT + 1.9.12-SNAPSHOT operator-framework-samples-spring-boot-plain From 944e08a463d7bd90c609d36a56b05bc6be9fdd52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 08:04:31 +0100 Subject: [PATCH 39/54] chore(deps): bump formatter-maven-plugin from 2.16.0 to 2.17.0 (#644) Bumps [formatter-maven-plugin](https://github.com/revelc/formatter-maven-plugin) from 2.16.0 to 2.17.0. - [Release notes](https://github.com/revelc/formatter-maven-plugin/releases) - [Changelog](https://github.com/revelc/formatter-maven-plugin/blob/main/CHANGELOG.md) - [Commits](https://github.com/revelc/formatter-maven-plugin/compare/formatter-maven-plugin-2.16.0...formatter-maven-plugin-2.17.0) --- updated-dependencies: - dependency-name: net.revelc.code.formatter:formatter-maven-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c41aa5329b..ccd9c709cb 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,7 @@ 2.8.2 2.5.2 5.0.0 - 2.16.0 + 2.17.0 1.0 1.6.2 From 28e40b076762b037eee0fd543ab96f04d233505e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 1 Nov 2021 08:54:52 +0100 Subject: [PATCH 40/54] docs: link for basic operato articles (#643) --- docs/documentation/intro-operators.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/documentation/intro-operators.md b/docs/documentation/intro-operators.md index 13a9812df1..f69cb2895a 100644 --- a/docs/documentation/intro-operators.md +++ b/docs/documentation/intro-operators.md @@ -7,6 +7,12 @@ permalink: /docs/intro-operators # Introduction To Operators -On this page we selected a collection of resources to introduce you to the concepts of Kubernetes Operators. +This page provides a selection of articles that gives an introduction to Kubernetes operators. +## Operators in General + - [Introduction of the concept of Kubernetes Operators](https://blog.container-solutions.com/kubernetes-operators-explained) + - [Operator pattern explained in Kubernetes documentation](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) + - [An explanation why Java Operators makes sense](https://blog.container-solutions.com/cloud-native-java-infrastructure-automation-with-kubernetes-operators) + - [What are the problems an operator framework is solving](https://csviri.medium.com/deep-dive-building-a-kubernetes-operator-sdk-for-java-developers-5008218822cb) + From c34aa6555bab8671cd6f51c6cbe295140eea407d Mon Sep 17 00:00:00 2001 From: Bobae Kim Date: Tue, 2 Nov 2021 18:57:03 +0900 Subject: [PATCH 41/54] fix: use `close` as destroyMethod of Bean (#645) --- .../main/java/io/javaoperatorsdk/operator/sample/Config.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/spring-boot-plain/src/main/java/io/javaoperatorsdk/operator/sample/Config.java b/samples/spring-boot-plain/src/main/java/io/javaoperatorsdk/operator/sample/Config.java index 40c5ecefb9..b6713e9ed4 100644 --- a/samples/spring-boot-plain/src/main/java/io/javaoperatorsdk/operator/sample/Config.java +++ b/samples/spring-boot-plain/src/main/java/io/javaoperatorsdk/operator/sample/Config.java @@ -18,7 +18,7 @@ public CustomServiceController customServiceController() { } // Register all controller beans - @Bean(initMethod = "start", destroyMethod = "stop") + @Bean(initMethod = "start", destroyMethod = "close") public Operator operator(List controllers) { Operator operator = new Operator(DefaultConfigurationService.instance()); controllers.forEach(operator::register); From a97c3dd33c30ba3d459f770fc2cf0b94c25154b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 3 Nov 2021 13:28:45 +0100 Subject: [PATCH 42/54] docs: finalizers and best braticies page (#647) --- docs/_data/sidebar.yml | 2 + docs/documentation/features.md | 64 +++++++++++++++++-- docs/documentation/patterns-best-practices.md | 28 ++++++++ 3 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 docs/documentation/patterns-best-practices.md diff --git a/docs/_data/sidebar.yml b/docs/_data/sidebar.yml index 7d88cde491..a9a43907a2 100644 --- a/docs/_data/sidebar.yml +++ b/docs/_data/sidebar.yml @@ -7,6 +7,8 @@ url: /docs/using-samples - title: Features url: /docs/features + - title: Patterns and Best Practices + url: /docs/patterns-best-practices - title: FAQ url: /docs/faq - title: Contributing diff --git a/docs/documentation/features.md b/docs/documentation/features.md index 7f77bcbbde..b0b639ebbb 100644 --- a/docs/documentation/features.md +++ b/docs/documentation/features.md @@ -5,15 +5,69 @@ layout: docs permalink: /docs/features --- -# Features +# Features -## Controller Registration +Java Operator SDK is a high level framework and related tooling in order to facilitate implementation of Kubernetes +operators. The features are by default following the best practices in an opinionated way. However, feature flags and +other configuration options are provided to fine tune or turn off these features. -## Configurations +## Controller Execution in a Nutshell -## Finalizers +Controller execution is always triggered by an event. Events typically come from the custom resource +(i.e. custom resource is created, updated or deleted) that the controller is watching, but also from different sources +(see event sources). When an event is received reconciliation is executed, unless there is already a reconciliation +happening for a particular custom resource. In other words it is guaranteed by the framework that no concurrent +reconciliation happens for a custom resource. -### When not to Use Finalizers +After a reconciliation ( +i.e. [ResourceController](https://github.com/java-operator-sdk/java-operator-sdk/blob/master/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ResourceController.java) +called), a post-processing phase follows, where typically framework checks if: + +- an exception was thrown during execution, if yes schedules a retry. +- there are new events received during the controller execution, if yes schedule the execution again. +- there is an instruction to re-schedule the execution for the future, if yes schedule a timer event with the specified + delay. +- if none above, the reconciliation is finished. + +Briefly, in the hearth of the execution is an eventing system, where events are the triggers of the reconciliation +execution. + +## Finalizer Support + +[Kubernetes finalizers](https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers/) +make sure that a reconciliation happens when a custom resource is instructed to be deleted. Typical case when it's +useful, when an operator is down (pod not running). Without a finalizer the reconciliation - thus the cleanup +i.e. [`ResourceController.deleteResource(...)`](https://github.com/java-operator-sdk/java-operator-sdk/blob/master/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ResourceController.java) + +- would not happen if a custom resource is deleted. + +Finalizers are automatically added by the framework as the first step, thus when a custom resource is created, but +before the first reconciliation, the custom resource is updated via a Kubernetes API call. As a result of this update, the +finalizer will be present. The subsequent event will be received, which will trigger the first reconciliation. + +The finalizer that is automatically added will be also removed after the `deleteResource` is executed on the controller. +However, the removal behavior can be further customized, and can be instructed to "not remove yet" - this is useful just +in some specific corner cases, when there would be a long waiting period for some dependent resource cleanup. + +The name of the finalizers can be specified, in case it is not, a name will be generated. + +This behavior can be turned off, so when configured no finalizer will be added or removed. +See [`@Controller`](https://github.com/java-operator-sdk/java-operator-sdk/blob/master/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/Controller.java) +annotation for more details. + +### When not to Use Finalizers? + +Typically, automated finalizer handling should be turned off, when **all** the cleanup of the dependent resources is +handled by Kubernetes itself. This is handled by +Kubernetes [garbage collection](https://kubernetes.io/docs/concepts/architecture/garbage-collection/#owners-dependents). +Setting the owner reference and related fields are not in the scope of the SDK for now, it's up to the user to have them +configured properly when creating the objects. + +When automatic finalizer handling is turned off, the `ResourceController.deleteResource(...)` method is not called, in +case of a delete event received. So it does not make sense to implement this method and turn off finalizer at the same +time. + +## Separating `createOrUpdate` from `delete` ## Automatic Retries on Error diff --git a/docs/documentation/patterns-best-practices.md b/docs/documentation/patterns-best-practices.md new file mode 100644 index 0000000000..c5103de3ff --- /dev/null +++ b/docs/documentation/patterns-best-practices.md @@ -0,0 +1,28 @@ +--- +title: Patterns and Best Practices +description: Patterns and Best Practices Implementing a Controller +layout: docs +permalink: /docs/patterns-best-practices +--- + +# Patterns and Best Practices + +This document describes patters and best practices, to build and run operators, and how to implement them in terms +of Java Operator SDK. + +## Implementing a Controller + +### Sync of Async Way of Resource Handling + +### Idempotency + +## Why to Have Automated Retries? + +## Managing State + +## Dependent Resources + +### EventSources and Caching + +### Why are Events Irrelevant? + From 9097659f5d93026fe01b16149cc93c2b5d5d5b35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 3 Nov 2021 13:44:08 +0100 Subject: [PATCH 43/54] fix: typo in docs (#649) --- docs/documentation/features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/documentation/features.md b/docs/documentation/features.md index b0b639ebbb..6040ac39bd 100644 --- a/docs/documentation/features.md +++ b/docs/documentation/features.md @@ -21,7 +21,7 @@ reconciliation happens for a custom resource. After a reconciliation ( i.e. [ResourceController](https://github.com/java-operator-sdk/java-operator-sdk/blob/master/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ResourceController.java) -called), a post-processing phase follows, where typically framework checks if: +called, a post-processing phase follows, where typically framework checks if: - an exception was thrown during execution, if yes schedules a retry. - there are new events received during the controller execution, if yes schedule the execution again. From dc2a5434e16ed3863db714c6d204ed32b125495c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 3 Nov 2021 13:48:45 +0100 Subject: [PATCH 44/54] fix: jekyll build warning with highliting (#650) --- docs/_config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_config.yml b/docs/_config.yml index 0e349390ae..a2b8fba679 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -13,7 +13,7 @@ images: /assets/images/ # Rouge highlighter markdown: kramdown -highlighter: none +highlighter: rouge kramdown: parse_block_html: true From 7af99002aa87415d0571de82793f1203a1c055ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Nov 2021 08:57:43 +0100 Subject: [PATCH 45/54] chore(deps): bump auto-service from 1.0 to 1.0.1 (#652) Bumps [auto-service](https://github.com/google/auto) from 1.0 to 1.0.1. - [Release notes](https://github.com/google/auto/releases) - [Commits](https://github.com/google/auto/compare/auto-value-1.0...auto-common-1.0.1) --- updated-dependencies: - dependency-name: com.google.auto.service:auto-service dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ccd9c709cb..063b712684 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ 2.14.1 4.0.0 3.12.0 - 1.0 + 1.0.1 0.19 1.13.0 3.21.0 From 7f97b44e0131fc6859f18d40316db4d249bd4823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20S=C3=A1ndor?= <10675791+adam-sandor@users.noreply.github.com> Date: Fri, 5 Nov 2021 15:06:30 +0100 Subject: [PATCH 46/54] remove integration test references --- .github/workflows/end-to-end-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/end-to-end-tests.yml b/.github/workflows/end-to-end-tests.yml index 679fc448a0..ee7d0e52da 100644 --- a/.github/workflows/end-to-end-tests.yml +++ b/.github/workflows/end-to-end-tests.yml @@ -6,7 +6,7 @@ on: branches: - "*" jobs: - tomcat_integration_test: + tomcat_e2e_test: runs-on: ubuntu-latest env: KIND_CL_NAME: e2e-test From 715126bc82d74aca6c3394d8a504f2146d34a74b Mon Sep 17 00:00:00 2001 From: csviri Date: Fri, 5 Nov 2021 15:19:34 +0100 Subject: [PATCH 47/54] fix: parent version update from master --- sample-operators/pom.xml | 3 +-- sample-operators/tomcat-operator/pom.xml | 3 +-- .../io/javaoperatorsdk/operator/sample/Tomcat.java | 11 ++++++----- .../io/javaoperatorsdk/operator/sample/Webapp.java | 11 ++++++----- .../operator/sample/TomcatOperatorE2E.java | 6 ++++-- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/sample-operators/pom.xml b/sample-operators/pom.xml index d85a0efc84..0e2bfc8df0 100644 --- a/sample-operators/pom.xml +++ b/sample-operators/pom.xml @@ -7,8 +7,7 @@ io.javaoperatorsdk java-operator-sdk - 1.9.11-SNAPSHOT - ../pom.xml + 1.9.12-SNAPSHOT sample-operators diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml index 80edee0274..d15411f0b7 100644 --- a/sample-operators/tomcat-operator/pom.xml +++ b/sample-operators/tomcat-operator/pom.xml @@ -7,8 +7,7 @@ io.javaoperatorsdk sample-operators - 1.9.11-SNAPSHOT - ../pom.xml + 1.9.12-SNAPSHOT sample-tomcat-operator diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java index cac046d116..4aadf77a5f 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java @@ -1,17 +1,18 @@ package io.javaoperatorsdk.operator.sample; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + import io.fabric8.kubernetes.api.model.Namespaced; import io.fabric8.kubernetes.client.CustomResource; import io.fabric8.kubernetes.model.annotation.Group; import io.fabric8.kubernetes.model.annotation.Version; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; @Group("tomcatoperator.io") @Version("v1") public class Tomcat extends CustomResource implements Namespaced { - public String toString() { - return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); - } + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); + } } diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Webapp.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Webapp.java index f9999633e3..d61f8791f7 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Webapp.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Webapp.java @@ -1,11 +1,12 @@ package io.javaoperatorsdk.operator.sample; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + import io.fabric8.kubernetes.api.model.Namespaced; import io.fabric8.kubernetes.client.CustomResource; import io.fabric8.kubernetes.model.annotation.Group; import io.fabric8.kubernetes.model.annotation.Version; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; /** * Represents a web application deployed in a Tomcat deployment @@ -14,7 +15,7 @@ @Version("v1") public class Webapp extends CustomResource implements Namespaced { - public String toString() { - return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); - } + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); + } } diff --git a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java index 7e77079440..110e8aaf93 100644 --- a/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java +++ b/sample-operators/tomcat-operator/src/test/java/io/javaoperatorsdk/operator/sample/TomcatOperatorE2E.java @@ -66,7 +66,8 @@ public void test() { if (testNs != null) { // We perform a pre-run cleanup instead of a post-run cleanup. This is to help with debugging // test results when running against a persistent cluster. The test namespace would stay - // after the test run so we can check what's there, but it would be cleaned up during the next test run. + // after the test run so we can check what's there, but it would be cleaned up during the next + // test run. log.info("Cleanup: deleting test namespace {}", TEST_NS); client.namespaces().delete(testNs); await().atMost(5, MINUTES) @@ -112,7 +113,8 @@ public void test() { log.info("Waiting for curl Pod to finish running"); await("wait-for-curl-pod-run").atMost(2, MINUTES) .until(() -> { - String phase = client.pods().inNamespace(TEST_NS).withName("curl").get().getStatus().getPhase(); + String phase = + client.pods().inNamespace(TEST_NS).withName("curl").get().getStatus().getPhase(); return phase.equals("Succeeded") || phase.equals("Failed"); }); From 2115de407b25bd092b73d470ebae8ec0a4ce7e97 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 8 Nov 2021 13:43:22 +0100 Subject: [PATCH 48/54] fix: migrated tomcat operator to informers, some minor changes to make it easy to write --- .../processing/CustomResourceCache.java | 5 +- .../event/DefaultEventSourceManager.java | 2 +- .../internal/CustomResourceEventSource.java | 1 - .../event/internal/InformerEventSource.java | 4 +- .../operator/sample/DeploymentEvent.java | 51 ------------ .../sample/DeploymentEventSource.java | 78 ------------------ .../operator/sample/TomcatController.java | 42 ++++++---- .../operator/sample/TomcatEvent.java | 50 ----------- .../operator/sample/TomcatEventSource.java | 82 ------------------- .../operator/sample/WebappController.java | 20 ++++- 10 files changed, 51 insertions(+), 284 deletions(-) delete mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEvent.java delete mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEventSource.java delete mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEvent.java delete mode 100644 sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatEventSource.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/CustomResourceCache.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/CustomResourceCache.java index 0e6d313f37..78b5c7eb25 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/CustomResourceCache.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/CustomResourceCache.java @@ -76,7 +76,7 @@ public Optional getLatestResource(String uuid) { return Optional.ofNullable(resources.get(uuid)).map(this::clone); } - public List getLatestResources(Predicate selector) { + public List getLatestResources(Predicate selector) { try { lock.lock(); return resources.values().stream() @@ -88,7 +88,7 @@ public List getLatestResources(Predicate selector) { } } - public Set getLatestResourcesUids(Predicate selector) { + public Set getLatestResourcesUids(Predicate selector) { try { lock.lock(); return resources.values().stream() @@ -113,4 +113,5 @@ private T clone(CustomResource customResource) { public T cleanup(String customResourceUid) { return resources.remove(customResourceUid); } + } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/DefaultEventSourceManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/DefaultEventSourceManager.java index 0ca4ffd289..6cfe02a37b 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/DefaultEventSourceManager.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/DefaultEventSourceManager.java @@ -26,8 +26,8 @@ public class DefaultEventSourceManager> implements EventSourceManager { + public static final String CUSTOM_RESOURCE_EVENT_SOURCE_NAME = "custom-resource-event-source"; public static final String RETRY_TIMER_EVENT_SOURCE_NAME = "retry-timer-event-source"; - private static final String CUSTOM_RESOURCE_EVENT_SOURCE_NAME = "custom-resource-event-source"; private static final Logger log = LoggerFactory.getLogger(DefaultEventSourceManager.class); private final ReentrantLock lock = new ReentrantLock(); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSource.java index 360f57bdca..e254cf5aa8 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSource.java @@ -159,7 +159,6 @@ public void onClose(WatcherException e) { } } - // todo: remove public CustomResourceCache getCache() { return customResourceCache; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/InformerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/InformerEventSource.java index 37e1add66e..2bdb9c198e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/InformerEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/InformerEventSource.java @@ -81,7 +81,9 @@ private void propagateEvent(InformerEvent.Action action, T object, T oldObject) } uids.forEach(uid -> { InformerEvent event = new InformerEvent(uid, this, action, object, oldObject); - this.eventHandler.handleEvent(event); + if (this.eventHandler != null) { + this.eventHandler.handleEvent(event); + } }); } diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEvent.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEvent.java deleted file mode 100644 index d8e9190a98..0000000000 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEvent.java +++ /dev/null @@ -1,51 +0,0 @@ -package io.javaoperatorsdk.operator.sample; - -import io.fabric8.kubernetes.api.model.apps.Deployment; -import io.fabric8.kubernetes.client.Watcher; -import io.javaoperatorsdk.operator.processing.event.DefaultEvent; - -public class DeploymentEvent extends DefaultEvent { - - private final Watcher.Action action; - private final Deployment deployment; - - public DeploymentEvent( - Watcher.Action action, Deployment resource, DeploymentEventSource deploymentEventSource) { - // TODO: this mapping is really critical and should be made more explicit - super(resource.getMetadata().getOwnerReferences().get(0).getUid(), deploymentEventSource); - this.action = action; - this.deployment = resource; - } - - public Watcher.Action getAction() { - return action; - } - - public String resourceUid() { - return getDeployment().getMetadata().getUid(); - } - - @Override - public String toString() { - return "CustomResourceEvent{" - + "action=" - + action - + ", resource=[ name=" - + getDeployment().getMetadata().getName() - + ", kind=" - + getDeployment().getKind() - + ", apiVersion=" - + getDeployment().getApiVersion() - + " ,resourceVersion=" - + getDeployment().getMetadata().getResourceVersion() - + ", markedForDeletion: " - + (getDeployment().getMetadata().getDeletionTimestamp() != null - && !getDeployment().getMetadata().getDeletionTimestamp().isEmpty()) - + " ]" - + '}'; - } - - public Deployment getDeployment() { - return deployment; - } -} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEventSource.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEventSource.java deleted file mode 100644 index a88357e8cf..0000000000 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/DeploymentEventSource.java +++ /dev/null @@ -1,78 +0,0 @@ -package io.javaoperatorsdk.operator.sample; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.fabric8.kubernetes.api.model.apps.Deployment; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.Watcher; -import io.fabric8.kubernetes.client.WatcherException; -import io.javaoperatorsdk.operator.processing.event.AbstractEventSource; - -import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getUID; -import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion; - -/** - * Used by the TomcatController to watch changes on Deployment objects. As the Pods of the - * Deployment start up the TomcatController updates the status.readyReplicas field. - */ -public class DeploymentEventSource extends AbstractEventSource implements Watcher { - private static final Logger log = LoggerFactory.getLogger(DeploymentEventSource.class); - - private final KubernetesClient client; - - public static DeploymentEventSource createAndRegisterWatch(KubernetesClient client) { - DeploymentEventSource deploymentEventSource = new DeploymentEventSource(client); - deploymentEventSource.registerWatch(); - return deploymentEventSource; - } - - private DeploymentEventSource(KubernetesClient client) { - this.client = client; - } - - private void registerWatch() { - client - .apps() - .deployments() - .inAnyNamespace() - .withLabel("app.kubernetes.io/managed-by", "tomcat-operator") - .watch(this); - } - - @Override - public void eventReceived(Action action, Deployment deployment) { - log.info( - "Event received for action: {}, Deployment: {} (rr={})", - action.name(), - deployment.getMetadata().getName(), - deployment.getStatus().getReadyReplicas()); - - if (action == Action.ERROR) { - log.warn( - "Skipping {} event for custom resource uid: {}, version: {}", - action, - getUID(deployment), - getVersion(deployment)); - return; - } - - eventHandler.handleEvent(new DeploymentEvent(action, deployment, this)); - } - - @Override - public void onClose(WatcherException e) { - if (e == null) { - return; - } - if (e.isHttpGone()) { - log.warn("Received error for watch, will try to reconnect.", e); - registerWatch(); - } else { - // Note that this should not happen normally, since fabric8 client handles reconnect. - // In case it tries to reconnect this method is not called. - log.error("Unexpected error happened with watch. Will exit.", e); - System.exit(1); - } - } -} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java index e82439bb33..43a48d81cb 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java @@ -3,7 +3,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.Objects; -import java.util.Optional; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,10 +15,13 @@ import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.RollableScalableResource; import io.fabric8.kubernetes.client.dsl.ServiceResource; +import io.fabric8.kubernetes.client.informers.SharedIndexInformer; import io.fabric8.kubernetes.client.utils.Serialization; import io.javaoperatorsdk.operator.api.*; import io.javaoperatorsdk.operator.processing.event.EventSourceManager; -import io.javaoperatorsdk.operator.processing.event.internal.CustomResourceEvent; +import io.javaoperatorsdk.operator.processing.event.internal.InformerEventSource; + +import static java.util.Collections.EMPTY_SET; /** * Runs a specified number of Tomcat app server Pods. It uses a Deployment to create the Pods. Also @@ -31,7 +34,7 @@ public class TomcatController implements ResourceController { private final KubernetesClient kubernetesClient; - private DeploymentEventSource deploymentEventSource; + private volatile InformerEventSource informerEventSource; public TomcatController(KubernetesClient client) { this.kubernetesClient = client; @@ -39,24 +42,32 @@ public TomcatController(KubernetesClient client) { @Override public void init(EventSourceManager eventSourceManager) { - this.deploymentEventSource = DeploymentEventSource.createAndRegisterWatch(kubernetesClient); - eventSourceManager.registerEventSource("deployment-event-source", this.deploymentEventSource); + SharedIndexInformer deploymentInformer = + kubernetesClient.apps().deployments().inAnyNamespace() + .withLabel("app.kubernetes.io/managed-by", "tomcat-operator") + .inform(); + + this.informerEventSource = new InformerEventSource<>(deploymentInformer, d -> { + var ownerReferences = d.getMetadata().getOwnerReferences(); + if (!ownerReferences.isEmpty()) { + return Set.of(ownerReferences.get(0).getUid()); + } else { + return EMPTY_SET; + } + }); + eventSourceManager.registerEventSource("deployment-event-source", this.informerEventSource); } @Override public UpdateControl createOrUpdateResource(Tomcat tomcat, Context context) { - Optional latestCREvent = - context.getEvents().getLatestOfType(CustomResourceEvent.class); - if (latestCREvent.isPresent()) { - createOrUpdateDeployment(tomcat); - createOrUpdateService(tomcat); - } + createOrUpdateDeployment(tomcat); + createOrUpdateService(tomcat); - Optional latestDeploymentEvent = - context.getEvents().getLatestOfType(DeploymentEvent.class); - if (latestDeploymentEvent.isPresent()) { + Deployment deployment = informerEventSource.getAssociated(tomcat); + + if (deployment != null) { Tomcat updatedTomcat = - updateTomcatStatus(tomcat, latestDeploymentEvent.get().getDeployment()); + updateTomcatStatus(tomcat, deployment); log.info( "Updating status of Tomcat {} in namespace {} to {} ready replicas", tomcat.getMetadata().getName(), @@ -64,7 +75,6 @@ public UpdateControl createOrUpdateResource(Tomcat tomcat, Context { - private static final Logger log = LoggerFactory.getLogger(TomcatEventSource.class); - - private final KubernetesClient client; - - public static TomcatEventSource createAndRegisterWatch(KubernetesClient client) { - TomcatEventSource tomcatEventSource = new TomcatEventSource(client); - tomcatEventSource.registerWatch(); - return tomcatEventSource; - } - - private TomcatEventSource(KubernetesClient client) { - this.client = client; - } - - private void registerWatch() { - var tomcatClient = client.customResources(Tomcat.class); - tomcatClient.inAnyNamespace().watch(this); - } - - @Override - public void eventReceived(Action action, Tomcat tomcat) { - log.info("Event received for action: {}, Tomcat: {}", action.name(), - tomcat.getMetadata().getName()); - - if (action == Action.ERROR) { - log.warn( - "Skipping {} event for custom resource uid: {}, version: {}", - action, - getUID(tomcat), - getVersion(tomcat)); - return; - } - - var webappClient = client.customResources(Webapp.class); - Optional webapp = webappClient.inNamespace(tomcat.getMetadata().getNamespace()) - .list().getItems().stream() - .filter(wapp -> wapp.getSpec().getTomcat().equals(tomcat.getMetadata().getName())) - .findFirst(); - - if (webapp.isPresent()) { - eventHandler.handleEvent(new TomcatEvent(action, tomcat, this, - webapp.get().getMetadata().getUid())); - } else { - log.debug("Webapp not found for Tomcat {}", tomcat.getMetadata().getName()); - } - } - - @Override - public void onClose(WatcherException e) { - if (e == null) { - return; - } - if (e.isHttpGone()) { - log.warn("Received error for watch, will try to reconnect.", e); - registerWatch(); - } else { - // Note that this should not happen normally, since fabric8 client handles reconnect. - // In case it tries to reconnect this method is not called. - log.error("Unexpected error happened with watch. Will exit.", e); - System.exit(1); - } - } -} diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java index 58a0393796..b1eb967301 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java @@ -7,6 +7,7 @@ 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; @@ -18,9 +19,13 @@ import io.fabric8.kubernetes.client.dsl.ExecWatch; import io.javaoperatorsdk.operator.api.*; import io.javaoperatorsdk.operator.processing.event.EventSourceManager; +import io.javaoperatorsdk.operator.processing.event.internal.CustomResourceEventSource; +import io.javaoperatorsdk.operator.processing.event.internal.InformerEventSource; import okhttp3.Response; +import static io.javaoperatorsdk.operator.processing.event.DefaultEventSourceManager.CUSTOM_RESOURCE_EVENT_SOURCE_NAME; + @Controller public class WebappController implements ResourceController { @@ -34,8 +39,19 @@ public WebappController(KubernetesClient kubernetesClient) { @Override public void init(EventSourceManager eventSourceManager) { - TomcatEventSource tomcatEventSource = - TomcatEventSource.createAndRegisterWatch(kubernetesClient); + InformerEventSource tomcatEventSource = + new InformerEventSource<>(kubernetesClient, Tomcat.class, t -> { + var cache = + ((CustomResourceEventSource) eventSourceManager + .getRegisteredEventSources() + .get(CUSTOM_RESOURCE_EVENT_SOURCE_NAME)).getCache(); + + var latestResourceIds = cache.getLatestResources( + (Webapp webApp) -> webApp.getSpec().getTomcat().equals(t.getMetadata().getName())); + return latestResourceIds.stream().map(webApp -> webApp.getMetadata().getUid()) + .collect(Collectors.toSet()); + }); + eventSourceManager.registerEventSource("tomcat-event-source", tomcatEventSource); } From 9e135364c7c628ea2c00785fe2096941f360c995 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 8 Nov 2021 13:47:03 +0100 Subject: [PATCH 49/54] refactor: rename samples to smoke test samples --- pom.xml | 2 +- {samples => smoke-test-samples}/README.md | 0 {samples => smoke-test-samples}/common/crd/test_object.yaml | 0 {samples => smoke-test-samples}/common/pom.xml | 2 +- .../io/javaoperatorsdk/operator/sample/CustomService.java | 0 .../operator/sample/CustomServiceController.java | 0 .../io/javaoperatorsdk/operator/sample/ServiceSpec.java | 0 .../common/src/main/resources/log4j2.xml | 0 {samples => smoke-test-samples}/pom.xml | 6 +++--- {samples => smoke-test-samples}/pure-java/pom.xml | 2 +- .../operator/sample/PureJavaApplicationRunner.java | 0 {samples => smoke-test-samples}/spring-boot-plain/pom.xml | 2 +- .../java/io/javaoperatorsdk/operator/sample/Config.java | 0 .../operator/sample/SpringBootStarterSampleApplication.java | 0 .../spring-boot-plain/src/main/resources/application.yaml | 0 15 files changed, 7 insertions(+), 7 deletions(-) rename {samples => smoke-test-samples}/README.md (100%) rename {samples => smoke-test-samples}/common/crd/test_object.yaml (100%) rename {samples => smoke-test-samples}/common/pom.xml (95%) rename {samples => smoke-test-samples}/common/src/main/java/io/javaoperatorsdk/operator/sample/CustomService.java (100%) rename {samples => smoke-test-samples}/common/src/main/java/io/javaoperatorsdk/operator/sample/CustomServiceController.java (100%) rename {samples => smoke-test-samples}/common/src/main/java/io/javaoperatorsdk/operator/sample/ServiceSpec.java (100%) rename {samples => smoke-test-samples}/common/src/main/resources/log4j2.xml (100%) rename {samples => smoke-test-samples}/pom.xml (85%) rename {samples => smoke-test-samples}/pure-java/pom.xml (92%) rename {samples => smoke-test-samples}/pure-java/src/main/java/io/javaoperatorsdk/operator/sample/PureJavaApplicationRunner.java (100%) rename {samples => smoke-test-samples}/spring-boot-plain/pom.xml (97%) rename {samples => smoke-test-samples}/spring-boot-plain/src/main/java/io/javaoperatorsdk/operator/sample/Config.java (100%) rename {samples => smoke-test-samples}/spring-boot-plain/src/main/java/io/javaoperatorsdk/operator/sample/SpringBootStarterSampleApplication.java (100%) rename {samples => smoke-test-samples}/spring-boot-plain/src/main/resources/application.yaml (100%) diff --git a/pom.xml b/pom.xml index 063b712684..85fe1dfc82 100644 --- a/pom.xml +++ b/pom.xml @@ -76,7 +76,7 @@ operator-framework-core operator-framework-junit5 operator-framework - samples + smoke-test-samples micrometer-support sample-operators diff --git a/samples/README.md b/smoke-test-samples/README.md similarity index 100% rename from samples/README.md rename to smoke-test-samples/README.md diff --git a/samples/common/crd/test_object.yaml b/smoke-test-samples/common/crd/test_object.yaml similarity index 100% rename from samples/common/crd/test_object.yaml rename to smoke-test-samples/common/crd/test_object.yaml diff --git a/samples/common/pom.xml b/smoke-test-samples/common/pom.xml similarity index 95% rename from samples/common/pom.xml rename to smoke-test-samples/common/pom.xml index 781073b774..211f06094f 100644 --- a/samples/common/pom.xml +++ b/smoke-test-samples/common/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk - java-operator-sdk-samples + java-operator-sdk-smoke-test-samples 1.9.12-SNAPSHOT diff --git a/samples/common/src/main/java/io/javaoperatorsdk/operator/sample/CustomService.java b/smoke-test-samples/common/src/main/java/io/javaoperatorsdk/operator/sample/CustomService.java similarity index 100% rename from samples/common/src/main/java/io/javaoperatorsdk/operator/sample/CustomService.java rename to smoke-test-samples/common/src/main/java/io/javaoperatorsdk/operator/sample/CustomService.java diff --git a/samples/common/src/main/java/io/javaoperatorsdk/operator/sample/CustomServiceController.java b/smoke-test-samples/common/src/main/java/io/javaoperatorsdk/operator/sample/CustomServiceController.java similarity index 100% rename from samples/common/src/main/java/io/javaoperatorsdk/operator/sample/CustomServiceController.java rename to smoke-test-samples/common/src/main/java/io/javaoperatorsdk/operator/sample/CustomServiceController.java diff --git a/samples/common/src/main/java/io/javaoperatorsdk/operator/sample/ServiceSpec.java b/smoke-test-samples/common/src/main/java/io/javaoperatorsdk/operator/sample/ServiceSpec.java similarity index 100% rename from samples/common/src/main/java/io/javaoperatorsdk/operator/sample/ServiceSpec.java rename to smoke-test-samples/common/src/main/java/io/javaoperatorsdk/operator/sample/ServiceSpec.java diff --git a/samples/common/src/main/resources/log4j2.xml b/smoke-test-samples/common/src/main/resources/log4j2.xml similarity index 100% rename from samples/common/src/main/resources/log4j2.xml rename to smoke-test-samples/common/src/main/resources/log4j2.xml diff --git a/samples/pom.xml b/smoke-test-samples/pom.xml similarity index 85% rename from samples/pom.xml rename to smoke-test-samples/pom.xml index f24d4ff71a..9be1f71635 100644 --- a/samples/pom.xml +++ b/smoke-test-samples/pom.xml @@ -9,9 +9,9 @@ 1.9.12-SNAPSHOT - java-operator-sdk-samples - Operator SDK - Samples - Sample usage of the operator sdk + java-operator-sdk-smoke-test-samples + Operator SDK - Smoke Test Samples + Samples to manually smoke the sdk pom diff --git a/samples/pure-java/pom.xml b/smoke-test-samples/pure-java/pom.xml similarity index 92% rename from samples/pure-java/pom.xml rename to smoke-test-samples/pure-java/pom.xml index 86b88f1d06..5c88d9b253 100644 --- a/samples/pure-java/pom.xml +++ b/smoke-test-samples/pure-java/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk - java-operator-sdk-samples + java-operator-sdk-smoke-test-samples 1.9.12-SNAPSHOT diff --git a/samples/pure-java/src/main/java/io/javaoperatorsdk/operator/sample/PureJavaApplicationRunner.java b/smoke-test-samples/pure-java/src/main/java/io/javaoperatorsdk/operator/sample/PureJavaApplicationRunner.java similarity index 100% rename from samples/pure-java/src/main/java/io/javaoperatorsdk/operator/sample/PureJavaApplicationRunner.java rename to smoke-test-samples/pure-java/src/main/java/io/javaoperatorsdk/operator/sample/PureJavaApplicationRunner.java diff --git a/samples/spring-boot-plain/pom.xml b/smoke-test-samples/spring-boot-plain/pom.xml similarity index 97% rename from samples/spring-boot-plain/pom.xml rename to smoke-test-samples/spring-boot-plain/pom.xml index 3f4d241312..b7b0e8150b 100644 --- a/samples/spring-boot-plain/pom.xml +++ b/smoke-test-samples/spring-boot-plain/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk - java-operator-sdk-samples + java-operator-sdk-smoke-test-samples 1.9.12-SNAPSHOT diff --git a/samples/spring-boot-plain/src/main/java/io/javaoperatorsdk/operator/sample/Config.java b/smoke-test-samples/spring-boot-plain/src/main/java/io/javaoperatorsdk/operator/sample/Config.java similarity index 100% rename from samples/spring-boot-plain/src/main/java/io/javaoperatorsdk/operator/sample/Config.java rename to smoke-test-samples/spring-boot-plain/src/main/java/io/javaoperatorsdk/operator/sample/Config.java diff --git a/samples/spring-boot-plain/src/main/java/io/javaoperatorsdk/operator/sample/SpringBootStarterSampleApplication.java b/smoke-test-samples/spring-boot-plain/src/main/java/io/javaoperatorsdk/operator/sample/SpringBootStarterSampleApplication.java similarity index 100% rename from samples/spring-boot-plain/src/main/java/io/javaoperatorsdk/operator/sample/SpringBootStarterSampleApplication.java rename to smoke-test-samples/spring-boot-plain/src/main/java/io/javaoperatorsdk/operator/sample/SpringBootStarterSampleApplication.java diff --git a/samples/spring-boot-plain/src/main/resources/application.yaml b/smoke-test-samples/spring-boot-plain/src/main/resources/application.yaml similarity index 100% rename from samples/spring-boot-plain/src/main/resources/application.yaml rename to smoke-test-samples/spring-boot-plain/src/main/resources/application.yaml From 7eb3aa7f26e72ab59be04d87a8937440935d3945 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 8 Nov 2021 15:49:16 +0100 Subject: [PATCH 50/54] feature: log warning when passing a running event source --- .../event/internal/InformerEventSource.java | 11 +++++++++++ .../operator/sample/TomcatController.java | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/InformerEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/InformerEventSource.java index 2bdb9c198e..6570968a49 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/InformerEventSource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/InformerEventSource.java @@ -5,6 +5,9 @@ import java.util.Set; import java.util.function.Function; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.informers.ResourceEventHandler; @@ -15,6 +18,8 @@ public class InformerEventSource extends AbstractEventSource { + private static final Logger log = LoggerFactory.getLogger(InformerEventSource.class); + private final SharedInformer sharedInformer; private final Function> resourceToUIDs; private final Function associatedWith; @@ -41,6 +46,11 @@ public InformerEventSource(SharedInformer sharedInformer, Function> resourceToUIDs, Function associatedWith, boolean skipUpdateEventPropagationIfNoChange) { + if (sharedInformer.isRunning()) { + log.warn( + "Informer is already running on event source creation, this is not desirable and may " + + "lead to non deterministic behavior."); + } this.sharedInformer = sharedInformer; this.resourceToUIDs = resourceToUIDs; this.skipUpdateEventPropagationIfNoChange = skipUpdateEventPropagationIfNoChange; @@ -81,6 +91,7 @@ private void propagateEvent(InformerEvent.Action action, T object, T oldObject) } uids.forEach(uid -> { InformerEvent event = new InformerEvent(uid, this, action, object, oldObject); + // In case user passes an already running informer this would fail. if (this.eventHandler != null) { this.eventHandler.handleEvent(event); } diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java index 43a48d81cb..fff8c00470 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/TomcatController.java @@ -45,7 +45,7 @@ public void init(EventSourceManager eventSourceManager) { SharedIndexInformer deploymentInformer = kubernetesClient.apps().deployments().inAnyNamespace() .withLabel("app.kubernetes.io/managed-by", "tomcat-operator") - .inform(); + .runnableInformer(0); this.informerEventSource = new InformerEventSource<>(deploymentInformer, d -> { var ownerReferences = d.getMetadata().getOwnerReferences(); From 289841f3a2d4e312d59abd7633af0e86cdb34986 Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 11 Nov 2021 10:34:13 +0100 Subject: [PATCH 51/54] docs: tomcat sample DeploymentEventSource to InformerEventSource in docs --- sample-operators/tomcat-operator/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sample-operators/tomcat-operator/README.md b/sample-operators/tomcat-operator/README.md index 6fc793198c..ea2689b499 100644 --- a/sample-operators/tomcat-operator/README.md +++ b/sample-operators/tomcat-operator/README.md @@ -54,14 +54,14 @@ Now you can create Tomcat instances with CRs (see examples above). ## EventSources The TomcatController is listening to events about Deployments created by the TomcatOperator by registering a -DeploymentEventSource with the EventSourceManager. The DeploymentEventSource will in turn register a watch on +InformerEventSource with the EventSourceManager. The InformerEventSource will in turn register a watch on all Deployments managed by the Controller (identified by the `app.kubernetes.io/managed-by` label). When an event from a Deployment is received we have to identify which Tomcat object does the Deployment -belong to. This is done when the DeploymentEventSource creates the DeploymentEvent. +belong to. This is done when the InformerEventSource creates the event. -The TomcatController has to take care of setting the `app.kubernetes.io/managed-by` label on the Deployment so the -DeploymentEventSource can watch the right Deployments. -The TomcatController also has to set `ownerReference` on the Deployment so later the DeploymentEventSource can +The TomcatController has to take care of setting the `app.kubernetes.io/managed-by` label on the Deployment so the +InformerEventSource can watch the right Deployments. +The TomcatController also has to set `ownerReference` on the Deployment so later the InformerEventSource can identify which Tomcat does the Deployment belong to. This is necessary so the frameowork can call the Controller `createOrUpdate` method correctly. From f23a78d009a4d693d5bff6a7ba2dfdb51c094c6b Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 11 Nov 2021 10:40:52 +0100 Subject: [PATCH 52/54] docs: explanation for cache usage --- .../io/javaoperatorsdk/operator/sample/WebappController.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java index b1eb967301..2c3cda0aec 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappController.java @@ -41,6 +41,10 @@ public WebappController(KubernetesClient kubernetesClient) { public void init(EventSourceManager eventSourceManager) { InformerEventSource tomcatEventSource = new InformerEventSource<>(kubernetesClient, Tomcat.class, t -> { + // 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 UID of the WebApp resource we traverse the cache to + // and identify it based on naming convention. var cache = ((CustomResourceEventSource) eventSourceManager .getRegisteredEventSources() From 116134f0d4d72a0fa513fa4a31345a4887cb775f Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Fri, 12 Nov 2021 10:51:56 +0100 Subject: [PATCH 53/54] chore(docs): update to point to `sample-operators` director --- smoke-test-samples/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/smoke-test-samples/README.md b/smoke-test-samples/README.md index 3af900da4f..12daaa70f6 100644 --- a/smoke-test-samples/README.md +++ b/smoke-test-samples/README.md @@ -1,4 +1,4 @@ -This samples folder contains simple artificial samples used for testing the framework rather -than showing off its real-world usage. +This samples folder contains simple artificial samples used for testing the framework rather than +showing off its real-world usage. -More realistic samples can be found here: https://github.com/java-operator-sdk/samples +More realistic samples can be found in the `sample-operators` directory. From 114062fbf6ce563f85061f78c7ec07329d936178 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Fri, 12 Nov 2021 11:18:33 +0100 Subject: [PATCH 54/54] refactor: generate CRDs automatically and use them --- .github/workflows/end-to-end-tests.yml | 8 +- sample-operators/tomcat-operator/README.md | 27 ++++-- sample-operators/tomcat-operator/k8s/crd.yaml | 96 ------------------- sample-operators/tomcat-operator/pom.xml | 5 + .../operator/sample/Tomcat.java | 2 + 5 files changed, 28 insertions(+), 110 deletions(-) delete mode 100644 sample-operators/tomcat-operator/k8s/crd.yaml diff --git a/.github/workflows/end-to-end-tests.yml b/.github/workflows/end-to-end-tests.yml index ee7d0e52da..404be8c102 100644 --- a/.github/workflows/end-to-end-tests.yml +++ b/.github/workflows/end-to-end-tests.yml @@ -29,10 +29,6 @@ jobs: cluster_name: e2e-test registry: false - - name: Apply CRDs - run: kubectl apply -f k8s/crd.yaml - working-directory: sample-operators/tomcat-operator - - name: Set up Java and Maven uses: actions/setup-java@v2 with: @@ -52,7 +48,9 @@ jobs: - name: Apply CRDs working-directory: sample-operators/tomcat-operator - run: kubectl apply -f k8s/crd.yaml + run: | + kubectl apply -f target/classes/META-INF/fabric8/tomcats.tomcatoperator.io-v1.yml + kubectl apply -f target/classes/META-INF/fabric8/webapps.tomcatoperator.io-v1.yml - name: Deploy Tomcat Operator working-directory: sample-operators/tomcat-operator diff --git a/sample-operators/tomcat-operator/README.md b/sample-operators/tomcat-operator/README.md index ea2689b499..c16335ae80 100644 --- a/sample-operators/tomcat-operator/README.md +++ b/sample-operators/tomcat-operator/README.md @@ -33,24 +33,33 @@ spec: ## Getting started / Testing -The quickest way to try the operator is to run it on your local machine, while it connects to a local or remote -Kubernetes cluster. When you start it, it will use the current kubectl context on your machine to connect to the cluster. +The quickest way to try the operator is to run it on your local machine, while it connects to a +local or remote Kubernetes cluster. When you start it, it will use the current kubectl context on +your machine to connect to the cluster. -Before you run it you have to install the CRD on your cluster by running `kubectl apply -f k8s/crd.yaml`. +Before you run it you have to install the CRDs on your cluster by running: +- `kubectl apply -f target/classes/META-INF/fabric8/tomcats.tomcatoperator.io-v1.yml` +- `kubectl apply -f target/classes/META-INF/fabric8/webapps.tomcatoperator.io-v1.yml` -When the Operator is running you can create some Tomcat Custom Resources. You can find a sample custom resources -in the k8s folder. +The CRDs are generated automatically from your code by simply adding the `crd-generator-apt` +dependency to your `pom.xml` file. + +When the Operator is running you can create some Tomcat Custom Resources. You can find a sample +custom resources in the k8s folder. If you want the Operator to be running as a deployment in your cluster, follow the below steps. ## Build -You can build the sample using `mvn install jib:dockerBuild` this will produce a Docker image you can push to the -registry of your choice. The JAR file is built using your local Maven and JDK and then copied into the Docker image. + +You can build the sample using `mvn install jib:dockerBuild` this will produce a Docker image you +can push to the registry of your choice. The JAR file is built using your local Maven and JDK and +then copied into the Docker image. ## Install Operator into cluster -Run `kubectl apply -f k8s/crd.yaml` if you haven't already, then run `kubectl apply -f k8s/operator.yaml`. -Now you can create Tomcat instances with CRs (see examples above). +Install the CRDs as shown above if you haven't already, then +run `kubectl apply -f k8s/operator.yaml`. Now you can create Tomcat instances with CRs (see examples +above). ## EventSources The TomcatController is listening to events about Deployments created by the TomcatOperator by registering a diff --git a/sample-operators/tomcat-operator/k8s/crd.yaml b/sample-operators/tomcat-operator/k8s/crd.yaml deleted file mode 100644 index 509c77b542..0000000000 --- a/sample-operators/tomcat-operator/k8s/crd.yaml +++ /dev/null @@ -1,96 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - # name must match the spec fields below, and be in the form: . - name: tomcats.tomcatoperator.io -spec: - # group name to use for REST API: /apis// - group: tomcatoperator.io - # list of versions supported by this CustomResourceDefinition - versions: - - name: v1 - # Each version can be enabled/disabled by Served flag. - served: true - # One and only one version must be marked as the storage version. - storage: true - subresources: - status: { } - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - version: - type: integer - replicas: - type: integer - status: - type: object - properties: - readyReplicas: - type: integer - required: [spec] - # either Namespaced or Cluster - scope: Namespaced - names: - # plural name to be used in the URL: /apis/// - plural: tomcats - # singular name to be used as an alias on the CLI and for display - singular: tomcat - # kind is normally the CamelCased singular type. Your resource manifests use this. - kind: Tomcat - # shortNames allow shorter string to match your resource on the CLI - shortNames: - - tc ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - # name must match the spec fields below, and be in the form: . - name: webapps.tomcatoperator.io -spec: - # group name to use for REST API: /apis// - group: tomcatoperator.io - # list of versions supported by this CustomResourceDefinition - versions: - - name: v1 - # Each version can be enabled/disabled by Served flag. - served: true - # One and only one version must be marked as the storage version. - storage: true - subresources: - status: {} - schema: - openAPIV3Schema: - type: object - properties: - spec: - type: object - properties: - tomcat: - type: string - url: - type: string - contextPath: - type: string - status: - type: object - properties: - deployedArtifact: - type: string - deploymentStatus: - type: array - items: - type: string - required: [spec] - # either Namespaced or Cluster - scope: Namespaced - names: - # plural name to be used in the URL: /apis/// - plural: webapps - # singular name to be used as an alias on the CLI and for display - singular: webapp - # kind is normally the CamelCased singular type. Your resource manifests use this. - kind: Webapp \ No newline at end of file diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml index d15411f0b7..a46f275991 100644 --- a/sample-operators/tomcat-operator/pom.xml +++ b/sample-operators/tomcat-operator/pom.xml @@ -27,6 +27,11 @@ operator-framework ${project.version} + + io.fabric8 + crd-generator-apt + provided + org.apache.logging.log4j log4j-slf4j-impl diff --git a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java index 4aadf77a5f..7f60bd00d5 100644 --- a/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java +++ b/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/Tomcat.java @@ -6,10 +6,12 @@ import io.fabric8.kubernetes.api.model.Namespaced; import io.fabric8.kubernetes.client.CustomResource; import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.ShortNames; import io.fabric8.kubernetes.model.annotation.Version; @Group("tomcatoperator.io") @Version("v1") +@ShortNames("tc") public class Tomcat extends CustomResource implements Namespaced { public String toString() {