From 11916563f3a2489658da9f0fe6554680342faf98 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 28 Feb 2022 09:34:05 +0100 Subject: [PATCH 1/6] fix: refactor samples to dependent resources --- .../operator/sample/WebPageReconciler.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java index beef42599c..1748bdadf6 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java @@ -20,12 +20,11 @@ import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml; import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER; -@ControllerConfiguration(finalizerName = NO_FINALIZER) +@ControllerConfiguration(finalizerName = NO_FINALIZER, labelSelector = "!low-level") public class WebPageReconciler implements Reconciler, ErrorStatusHandler, EventSourceInitializer { private final Logger log = LoggerFactory.getLogger(getClass()); - private final KubernetesClient kubernetesClient; private KubernetesDependentResource configMapDR; @@ -48,6 +47,7 @@ public List prepareEventSources(EventSourceContext context @Override public UpdateControl reconcile(WebPage webPage, Context context) { if (webPage.getSpec().getHtml().contains("error")) { + // special case just to showcase error if doing a demo throw new ErrorSimulationException("Simulating error"); } @@ -55,14 +55,16 @@ public UpdateControl reconcile(WebPage webPage, Context context) { deploymentDR.reconcile(webPage, context); serviceDR.reconcile(webPage, context); - WebPageStatus status = new WebPageStatus(); + webPage.setStatus(createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName())); + return UpdateControl.updateStatus(webPage); + } - status.setHtmlConfigMap(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName()); + private WebPageStatus createStatus(String configMapName) { + WebPageStatus status = new WebPageStatus(); + status.setHtmlConfigMap(configMapName); status.setAreWeGood("Yes!"); status.setErrorMessage(null); - webPage.setStatus(status); - - return UpdateControl.updateStatus(webPage); + return status; } @Override From 2f037df83066c22901dc29f3414cb46dbf536193 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 28 Feb 2022 09:39:42 +0100 Subject: [PATCH 2/6] docs: comment --- .../io/javaoperatorsdk/operator/sample/WebPageReconciler.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java index 1748bdadf6..b67e3ea72e 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java @@ -20,6 +20,9 @@ import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml; import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER; +/** + * Shows how to implement reconciler using standalone dependent resources. + */ @ControllerConfiguration(finalizerName = NO_FINALIZER, labelSelector = "!low-level") public class WebPageReconciler implements Reconciler, ErrorStatusHandler, EventSourceInitializer { From 5520451d6440e7bcec51970586545733ce456a0b Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 28 Feb 2022 11:13:52 +0100 Subject: [PATCH 3/6] fix: wip --- .../operator/sample/WebPage.java | 8 + .../operator/sample/WebPageOperator.java | 1 + .../sample/WebPageReconcilerLowLevel.java | 257 ++++++++++++++++++ .../operator/sample/WebPageSpec.java | 7 + .../operator/sample/WebPageStatus.java | 9 + 5 files changed, 282 insertions(+) create mode 100644 sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerLowLevel.java diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPage.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPage.java index 6c10ffe3af..67cd8aa7e8 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPage.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPage.java @@ -14,4 +14,12 @@ public class WebPage extends CustomResource protected WebPageStatus initStatus() { return new WebPageStatus(); } + + @Override + public String toString() { + return "WebPage{" + + "spec=" + spec + + ", status=" + status + + '}'; + } } diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java index 768fd26a72..91ea9f1987 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java @@ -27,6 +27,7 @@ public static void main(String[] args) throws IOException { KubernetesClient client = new DefaultKubernetesClient(config); Operator operator = new Operator(client, DefaultConfigurationService.instance()); operator.register(new WebPageReconciler(client)); + operator.register(new WebPageReconcilerLowLevel(client)); operator.installShutdownHook(); operator.start(); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerLowLevel.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerLowLevel.java new file mode 100644 index 0000000000..c3a4c9fb0b --- /dev/null +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerLowLevel.java @@ -0,0 +1,257 @@ +package io.javaoperatorsdk.operator.sample; + +import java.io.IOException; +import java.io.InputStream; +import java.util.*; + +import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.fabric8.kubernetes.api.model.*; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.dsl.Resource; +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.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.Context; + +import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER; + +/** Shows how to implement reconciler using the low level api directly. */ +@ControllerConfiguration( + finalizerName = NO_FINALIZER, + labelSelector = WebPageReconcilerLowLevel.LOW_LEVEL_LABEL_KEY) +public class WebPageReconcilerLowLevel + implements Reconciler, ErrorStatusHandler, EventSourceInitializer { + + public static final String LOW_LEVEL_LABEL_KEY = "low-level"; + public static final String INDEX_HTML = "index.html"; + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private final KubernetesClient kubernetesClient; + + public WebPageReconcilerLowLevel(KubernetesClient kubernetesClient) { + this.kubernetesClient = kubernetesClient; + } + + InformerEventSource configMapEventSource; + @Override + public List prepareEventSources(EventSourceContext context) { + + configMapEventSource = new InformerEventSource<>(InformerConfiguration.from(context,ConfigMap.class) + .withLabelSelector(LOW_LEVEL_LABEL_KEY) + .build(), context); + var deploymentEventSource = new InformerEventSource<>(InformerConfiguration.from(context,Deployment.class) + .withLabelSelector(LOW_LEVEL_LABEL_KEY) + .build(),context); + var serviceEventSource = new InformerEventSource<>(InformerConfiguration.from(context,Service.class) + .withLabelSelector(LOW_LEVEL_LABEL_KEY) + .build(),context); + return List.of(configMapEventSource,deploymentEventSource,serviceEventSource); + } + + @Override + public UpdateControl reconcile(WebPage webPage, Context context) { + log.info("Reconciling web page: {}", webPage); + if (webPage.getSpec().getHtml().contains("error")) { + throw new ErrorSimulationException("Simulating error"); + } + String ns = webPage.getMetadata().getNamespace(); + String configMapName = configMapName(webPage); + String deploymentName = deploymentName(webPage); + + + ConfigMap desiredHtmlConfigMap = makeDesiredHtmlConfigMap(ns, configMapName, webPage); + Deployment desiredDeployment = makeDesiredDeployment(webPage,deploymentName, ns, configMapName); + Service desiredService = makeDesiredService(webPage, ns, desiredDeployment); + + var previousConfigMap = context.getSecondaryResource(ConfigMap.class).orElse(null); + if (!match(desiredHtmlConfigMap, previousConfigMap)) { + log.info( + "Creating or updating ConfigMap {} in {}", + desiredHtmlConfigMap.getMetadata().getName(), + ns); + kubernetesClient.configMaps().inNamespace(ns).createOrReplace(desiredHtmlConfigMap); + } + + var existingDeployment = context.getSecondaryResource(Deployment.class).orElse(null); + if (!match(desiredDeployment, existingDeployment)) { + log.info( + "Creating or updating Deployment {} in {}", + desiredDeployment.getMetadata().getName(), + ns); + kubernetesClient.apps().deployments().inNamespace(ns).createOrReplace(desiredDeployment); + } + + var existingService = context.getSecondaryResource(Service.class).orElse(null); + if (!match(desiredService, existingService)) { + log.info( + "Creating or updating Deployment {} in {}", + desiredDeployment.getMetadata().getName(), + ns); + kubernetesClient.services().inNamespace(ns).createOrReplace(desiredService); + } + + if (previousConfigMap != null && !StringUtils.equals( + previousConfigMap.getData().get(INDEX_HTML), + desiredHtmlConfigMap.getData().get(INDEX_HTML))) { + log.info("Restarting pods because HTML has changed in {}", ns); + kubernetesClient.pods().inNamespace(ns).withLabel("app", deploymentName(webPage)).delete(); + } + webPage.setStatus(createStatus(desiredHtmlConfigMap.getMetadata().getName())); + return UpdateControl.updateStatus(webPage); + } + + private WebPageStatus createStatus(String configMapName) { + WebPageStatus status = new WebPageStatus(); + status.setHtmlConfigMap(configMapName); + status.setAreWeGood("Yes!"); + status.setErrorMessage(null); + return status; + } + + // todo + private boolean match(Deployment desiredDeployment, Deployment deployment) { + if (deployment == null) { + return false; + } + return true; + } + + // todo + private boolean match(Service desiredService, Service service) { + if (service == null) { + return false; + } + return true; + } + + private boolean match(ConfigMap desiredHtmlConfigMap, ConfigMap existingConfigMap) { + if (existingConfigMap == null) { + return false; + } else { + return desiredHtmlConfigMap.getData().equals(existingConfigMap.getData()); + } + } + + private Service makeDesiredService(WebPage webPage, String ns, Deployment desiredDeployment) { + Service desiredService = loadYaml(Service.class, "service.yaml"); + desiredService.getMetadata().setName(serviceName(webPage)); + desiredService.getMetadata().setNamespace(ns); + desiredService + .getSpec() + .setSelector(desiredDeployment.getSpec().getTemplate().getMetadata().getLabels()); + return desiredService; + } + + private Deployment makeDesiredDeployment(WebPage webPage, String deploymentName, String ns, String configMapName) { + Deployment desiredDeployment = loadYaml(Deployment.class, "deployment.yaml"); + desiredDeployment.getMetadata().setName(deploymentName); + desiredDeployment.getMetadata().setNamespace(ns); + desiredDeployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName); + desiredDeployment.getSpec().getTemplate().getMetadata().getLabels().put("app", deploymentName); + desiredDeployment + .getSpec() + .getTemplate() + .getSpec() + .getVolumes() + .get(0) + .setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName).build()); + desiredDeployment.addOwnerReference(webPage); + return desiredDeployment; + } + + @Override + public DeleteControl cleanup(WebPage nginx, Context context) { + log.info("Cleaning up for: {}", nginx.getMetadata().getName()); + + log.info("Deleting ConfigMap {}", configMapName(nginx)); + Resource configMap = + kubernetesClient + .configMaps() + .inNamespace(nginx.getMetadata().getNamespace()) + .withName(configMapName(nginx)); + if (configMap.get() != null) { + configMap.delete(); + } + + log.info("Deleting Deployment {}", deploymentName(nginx)); + RollableScalableResource deployment = + kubernetesClient + .apps() + .deployments() + .inNamespace(nginx.getMetadata().getNamespace()) + .withName(deploymentName(nginx)); + if (deployment.get() != null) { + deployment.cascading(true).delete(); + } + + log.info("Deleting Service {}", serviceName(nginx)); + ServiceResource service = + kubernetesClient + .services() + .inNamespace(nginx.getMetadata().getNamespace()) + .withName(serviceName(nginx)); + if (service.get() != null) { + service.delete(); + } + return DeleteControl.defaultDelete(); + } + + private ConfigMap makeDesiredHtmlConfigMap(String ns, String configMapName, WebPage webPage) { + Map data = new HashMap<>(); + data.put("index.html", webPage.getSpec().getHtml()); + ConfigMap configMap = + new ConfigMapBuilder() + .withMetadata( + new ObjectMetaBuilder() + .withName(configMapName) + .withNamespace(ns) + .withLabels(lowLevelLabel()) + .build()) + .withData(data) + .build(); + configMap.addOwnerReference(webPage); + return configMap; + } + + private Map lowLevelLabel() { + Map labels = new HashMap<>(); + labels.put(LOW_LEVEL_LABEL_KEY, "true"); + return labels; + } + + private static String configMapName(WebPage nginx) { + return nginx.getMetadata().getName() + "-html"; + } + + private static String deploymentName(WebPage nginx) { + return nginx.getMetadata().getName(); + } + + private static String serviceName(WebPage nginx) { + return nginx.getMetadata().getName(); + } + + 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); + } + } + + @Override + public Optional updateErrorStatus( + WebPage resource, RetryInfo retryInfo, RuntimeException e) { + resource.getStatus().setErrorMessage("Error: " + e.getMessage()); + return Optional.of(resource); + } +} diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageSpec.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageSpec.java index db50be6c2d..6f05d6e2e8 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageSpec.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageSpec.java @@ -11,4 +11,11 @@ public String getHtml() { public void setHtml(String html) { this.html = html; } + + @Override + public String toString() { + return "WebPageSpec{" + + "html='" + html + '\'' + + '}'; + } } diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStatus.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStatus.java index 2b2e2a23c8..31bc52282c 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStatus.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStatus.java @@ -34,4 +34,13 @@ public WebPageStatus setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; return this; } + + @Override + public String toString() { + return "WebPageStatus{" + + "htmlConfigMap='" + htmlConfigMap + '\'' + + ", areWeGood='" + areWeGood + '\'' + + ", errorMessage='" + errorMessage + '\'' + + '}'; + } } From 282053e835051e497676ffccced64b49d7a66985 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 28 Feb 2022 13:18:43 +0100 Subject: [PATCH 4/6] fix: renamings etc --- .../operator/sample/WebPageOperator.java | 2 +- .../operator/sample/WebPageReconciler.java | 274 ++++++++++-------- .../WebPageReconcilerDependentResources.java | 190 ++++++++++++ .../sample/WebPageReconcilerLowLevel.java | 257 ---------------- 4 files changed, 342 insertions(+), 381 deletions(-) create mode 100644 sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerDependentResources.java delete mode 100644 sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerLowLevel.java diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java index 91ea9f1987..2a6dd8da98 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java @@ -26,8 +26,8 @@ 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()); + operator.register(new WebPageReconcilerDependentResources(client)); operator.register(new WebPageReconciler(client)); - operator.register(new WebPageReconcilerLowLevel(client)); operator.installShutdownHook(); operator.start(); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java index b67e3ea72e..d09662fe1b 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java @@ -1,7 +1,12 @@ package io.javaoperatorsdk.operator.sample; +import java.io.IOException; +import java.io.InputStream; import java.util.*; +import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -9,56 +14,95 @@ import io.fabric8.kubernetes.api.model.*; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.utils.Serialization; import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.dependent.Updater; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; -import io.javaoperatorsdk.operator.processing.event.ResourceID; -import io.javaoperatorsdk.operator.processing.event.source.AssociatedSecondaryResourceIdentifier; -import io.javaoperatorsdk.operator.processing.event.source.EventSource; -import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml; import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER; -/** - * Shows how to implement reconciler using standalone dependent resources. - */ -@ControllerConfiguration(finalizerName = NO_FINALIZER, labelSelector = "!low-level") +/** Shows how to implement reconciler using the low level api directly. */ +@ControllerConfiguration( + finalizerName = NO_FINALIZER, + labelSelector = WebPageReconciler.LOW_LEVEL_LABEL_KEY) public class WebPageReconciler implements Reconciler, ErrorStatusHandler, EventSourceInitializer { + public static final String LOW_LEVEL_LABEL_KEY = "low-level"; + public static final String INDEX_HTML = "index.html"; + private final Logger log = LoggerFactory.getLogger(getClass()); - private final KubernetesClient kubernetesClient; - private KubernetesDependentResource configMapDR; - private KubernetesDependentResource deploymentDR; - private KubernetesDependentResource serviceDR; + private final KubernetesClient kubernetesClient; public WebPageReconciler(KubernetesClient kubernetesClient) { this.kubernetesClient = kubernetesClient; - createDependentResources(kubernetesClient); } + InformerEventSource configMapEventSource; @Override public List prepareEventSources(EventSourceContext context) { - return List.of( - configMapDR.eventSource(context), - deploymentDR.eventSource(context), - serviceDR.eventSource(context)); + + configMapEventSource = new InformerEventSource<>(InformerConfiguration.from(context,ConfigMap.class) + .withLabelSelector(LOW_LEVEL_LABEL_KEY) + .build(), context); + var deploymentEventSource = new InformerEventSource<>(InformerConfiguration.from(context,Deployment.class) + .withLabelSelector(LOW_LEVEL_LABEL_KEY) + .build(),context); + var serviceEventSource = new InformerEventSource<>(InformerConfiguration.from(context,Service.class) + .withLabelSelector(LOW_LEVEL_LABEL_KEY) + .build(),context); + return List.of(configMapEventSource,deploymentEventSource,serviceEventSource); } @Override public UpdateControl reconcile(WebPage webPage, Context context) { + log.info("Reconciling web page: {}", webPage); if (webPage.getSpec().getHtml().contains("error")) { - // special case just to showcase error if doing a demo throw new ErrorSimulationException("Simulating error"); } + String ns = webPage.getMetadata().getNamespace(); + String configMapName = configMapName(webPage); + String deploymentName = deploymentName(webPage); + + + ConfigMap desiredHtmlConfigMap = makeDesiredHtmlConfigMap(ns, configMapName, webPage); + Deployment desiredDeployment = makeDesiredDeployment(webPage,deploymentName, ns, configMapName); + Service desiredService = makeDesiredService(webPage, ns, desiredDeployment); + + var previousConfigMap = context.getSecondaryResource(ConfigMap.class).orElse(null); + if (!match(desiredHtmlConfigMap, previousConfigMap)) { + log.info( + "Creating or updating ConfigMap {} in {}", + desiredHtmlConfigMap.getMetadata().getName(), + ns); + kubernetesClient.configMaps().inNamespace(ns).createOrReplace(desiredHtmlConfigMap); + } - configMapDR.reconcile(webPage, context); - deploymentDR.reconcile(webPage, context); - serviceDR.reconcile(webPage, context); + var existingDeployment = context.getSecondaryResource(Deployment.class).orElse(null); + if (!match(desiredDeployment, existingDeployment)) { + log.info( + "Creating or updating Deployment {} in {}", + desiredDeployment.getMetadata().getName(), + ns); + kubernetesClient.apps().deployments().inNamespace(ns).createOrReplace(desiredDeployment); + } + + var existingService = context.getSecondaryResource(Service.class).orElse(null); + if (!match(desiredService, existingService)) { + log.info( + "Creating or updating Deployment {} in {}", + desiredDeployment.getMetadata().getName(), + ns); + kubernetesClient.services().inNamespace(ns).createOrReplace(desiredService); + } - webPage.setStatus(createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName())); + if (previousConfigMap != null && !StringUtils.equals( + previousConfigMap.getData().get(INDEX_HTML), + desiredHtmlConfigMap.getData().get(INDEX_HTML))) { + log.info("Restarting pods because HTML has changed in {}", ns); + kubernetesClient.pods().inNamespace(ns).withLabel("app", deploymentName(webPage)).delete(); + } + webPage.setStatus(createStatus(desiredHtmlConfigMap.getMetadata().getName())); return UpdateControl.updateStatus(webPage); } @@ -70,69 +114,80 @@ private WebPageStatus createStatus(String configMapName) { return status; } - @Override - public Optional updateErrorStatus( - WebPage resource, RetryInfo retryInfo, RuntimeException e) { - resource.getStatus().setErrorMessage("Error: " + e.getMessage()); - return Optional.of(resource); + private boolean match(Deployment desiredDeployment, Deployment deployment) { + if (deployment == null) { + return false; + } else { + return desiredDeployment.getSpec().getReplicas().equals(deployment.getSpec().getReplicas()) && + desiredDeployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + .equals(deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage()); + } + } + + private boolean match(Service desiredService, Service service) { + if (service == null) { + return false; + } + return desiredService.getSpec().getSelector().equals(service.getSpec().getSelector()); } - private void createDependentResources(KubernetesClient client) { - this.configMapDR = new ConfigMapDependentResource(); - - this.deploymentDR = - new KubernetesDependentResource<>() { - - @Override - protected Deployment desired(WebPage webPage, Context context) { - var deploymentName = deploymentName(webPage); - Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml"); - deployment.getMetadata().setName(deploymentName); - deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); - deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName); - - deployment - .getSpec() - .getTemplate() - .getMetadata() - .getLabels() - .put("app", deploymentName); - deployment - .getSpec() - .getTemplate() - .getSpec() - .getVolumes() - .get(0) - .setConfigMap( - new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build()); - return deployment; - } - - @Override - protected Class resourceType() { - return Deployment.class; - } - }; - - this.serviceDR = - new KubernetesDependentResource<>() { - - @Override - protected Service desired(WebPage webPage, Context context) { - Service service = loadYaml(Service.class, getClass(), "service.yaml"); - service.getMetadata().setName(serviceName(webPage)); - service.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); - Map labels = new HashMap<>(); - labels.put("app", deploymentName(webPage)); - service.getSpec().setSelector(labels); - return service; - } - - @Override - protected Class resourceType() { - return Service.class; - } - }; + private boolean match(ConfigMap desiredHtmlConfigMap, ConfigMap existingConfigMap) { + if (existingConfigMap == null) { + return false; + } else { + return desiredHtmlConfigMap.getData().equals(existingConfigMap.getData()); + } + } + + private Service makeDesiredService(WebPage webPage, String ns, Deployment desiredDeployment) { + Service desiredService = loadYaml(Service.class, "service.yaml"); + desiredService.getMetadata().setName(serviceName(webPage)); + desiredService.getMetadata().setNamespace(ns); + desiredService + .getSpec() + .setSelector(desiredDeployment.getSpec().getTemplate().getMetadata().getLabels()); + desiredService.addOwnerReference(webPage); + return desiredService; + } + + private Deployment makeDesiredDeployment(WebPage webPage, String deploymentName, String ns, String configMapName) { + Deployment desiredDeployment = loadYaml(Deployment.class, "deployment.yaml"); + desiredDeployment.getMetadata().setName(deploymentName); + desiredDeployment.getMetadata().setNamespace(ns); + desiredDeployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName); + desiredDeployment.getSpec().getTemplate().getMetadata().getLabels().put("app", deploymentName); + desiredDeployment + .getSpec() + .getTemplate() + .getSpec() + .getVolumes() + .get(0) + .setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName).build()); + desiredDeployment.addOwnerReference(webPage); + return desiredDeployment; + } + + private ConfigMap makeDesiredHtmlConfigMap(String ns, String configMapName, WebPage webPage) { + Map data = new HashMap<>(); + data.put("index.html", webPage.getSpec().getHtml()); + ConfigMap configMap = + new ConfigMapBuilder() + .withMetadata( + new ObjectMetaBuilder() + .withName(configMapName) + .withNamespace(ns) + .withLabels(lowLevelLabel()) + .build()) + .withData(data) + .build(); + configMap.addOwnerReference(webPage); + return configMap; + } + + private Map lowLevelLabel() { + Map labels = new HashMap<>(); + labels.put(LOW_LEVEL_LABEL_KEY, "true"); + return labels; } private static String configMapName(WebPage nginx) { @@ -147,45 +202,18 @@ private static String serviceName(WebPage nginx) { return nginx.getMetadata().getName(); } - private class ConfigMapDependentResource extends KubernetesDependentResource - implements - AssociatedSecondaryResourceIdentifier, Updater { - - @Override - protected ConfigMap desired(WebPage webPage, Context context) { - Map data = new HashMap<>(); - data.put("index.html", webPage.getSpec().getHtml()); - return new ConfigMapBuilder() - .withMetadata( - new ObjectMetaBuilder() - .withName(WebPageReconciler.configMapName(webPage)) - .withNamespace(webPage.getMetadata().getNamespace()) - .build()) - .withData(data) - .build(); - } - - @Override - public boolean match(ConfigMap actual, ConfigMap target, Context context) { - return StringUtils.equals( - actual.getData().get("index.html"), target.getData().get("index.html")); - } - - @Override - public void update(ConfigMap actual, ConfigMap target, WebPage primary, Context context) { - super.update(actual, target, primary, context); - var ns = actual.getMetadata().getNamespace(); - log.info("Restarting pods because HTML has changed in {}", ns); - kubernetesClient - .pods() - .inNamespace(ns) - .withLabel("app", deploymentName(primary)) - .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); } + } - @Override - public ResourceID associatedSecondaryID(WebPage primary) { - return new ResourceID(configMapName(primary), primary.getMetadata().getNamespace()); - } + @Override + public Optional updateErrorStatus( + WebPage resource, RetryInfo retryInfo, RuntimeException e) { + resource.getStatus().setErrorMessage("Error: " + e.getMessage()); + return Optional.of(resource); } } diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerDependentResources.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerDependentResources.java new file mode 100644 index 0000000000..927d1b768b --- /dev/null +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerDependentResources.java @@ -0,0 +1,190 @@ +package io.javaoperatorsdk.operator.sample; + +import java.util.*; + +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.fabric8.kubernetes.api.model.*; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Updater; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.AssociatedSecondaryResourceIdentifier; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; + +import static io.javaoperatorsdk.operator.ReconcilerUtils.loadYaml; +import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER; + +/** + * Shows how to implement reconciler using standalone dependent resources. + */ +@ControllerConfiguration(finalizerName = NO_FINALIZER, labelSelector = WebPageReconcilerDependentResources.DEPENDENT_RESOURCE_LABEL_SELECTOR) +public class WebPageReconcilerDependentResources + implements Reconciler, ErrorStatusHandler, EventSourceInitializer { + + public static final String DEPENDENT_RESOURCE_LABEL_SELECTOR = "!low-level"; + private final Logger log = LoggerFactory.getLogger(getClass()); + private final KubernetesClient kubernetesClient; + + private KubernetesDependentResource configMapDR; + private KubernetesDependentResource deploymentDR; + private KubernetesDependentResource serviceDR; + + public WebPageReconcilerDependentResources(KubernetesClient kubernetesClient) { + this.kubernetesClient = kubernetesClient; + createDependentResources(kubernetesClient); + } + + @Override + public List prepareEventSources(EventSourceContext context) { + return List.of( + configMapDR.eventSource(context), + deploymentDR.eventSource(context), + serviceDR.eventSource(context)); + } + + @Override + public UpdateControl reconcile(WebPage webPage, Context context) { + if (webPage.getSpec().getHtml().contains("error")) { + // special case just to showcase error if doing a demo + throw new ErrorSimulationException("Simulating error"); + } + + configMapDR.reconcile(webPage, context); + deploymentDR.reconcile(webPage, context); + serviceDR.reconcile(webPage, context); + + webPage.setStatus(createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName())); + return UpdateControl.updateStatus(webPage); + } + + private WebPageStatus createStatus(String configMapName) { + WebPageStatus status = new WebPageStatus(); + status.setHtmlConfigMap(configMapName); + status.setAreWeGood("Yes!"); + status.setErrorMessage(null); + return status; + } + + @Override + public Optional updateErrorStatus( + WebPage resource, RetryInfo retryInfo, RuntimeException e) { + resource.getStatus().setErrorMessage("Error: " + e.getMessage()); + return Optional.of(resource); + } + + private void createDependentResources(KubernetesClient client) { + this.configMapDR = new ConfigMapDependentResource(); + configMapDR.configureWith(new KubernetesDependentResourceConfig() + .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); + + this.deploymentDR = + new KubernetesDependentResource<>() { + + @Override + protected Deployment desired(WebPage webPage, Context context) { + var deploymentName = deploymentName(webPage); + Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml"); + deployment.getMetadata().setName(deploymentName); + deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); + deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName); + + deployment + .getSpec() + .getTemplate() + .getMetadata() + .getLabels() + .put("app", deploymentName); + deployment + .getSpec() + .getTemplate() + .getSpec() + .getVolumes() + .get(0) + .setConfigMap( + new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build()); + return deployment; + } + + @Override + protected Class resourceType() { + return Deployment.class; + } + }; + deploymentDR.configureWith(new KubernetesDependentResourceConfig().setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); + + this.serviceDR = + new KubernetesDependentResource<>() { + + @Override + protected Service desired(WebPage webPage, Context context) { + Service service = loadYaml(Service.class, getClass(), "service.yaml"); + service.getMetadata().setName(serviceName(webPage)); + service.getMetadata().setNamespace(webPage.getMetadata().getNamespace()); + Map labels = new HashMap<>(); + labels.put("app", deploymentName(webPage)); + service.getSpec().setSelector(labels); + return service; + } + + @Override + protected Class resourceType() { + return Service.class; + } + }; + serviceDR.configureWith(new KubernetesDependentResourceConfig().setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); + } + + private static String configMapName(WebPage nginx) { + return nginx.getMetadata().getName() + "-html"; + } + + private static String deploymentName(WebPage nginx) { + return nginx.getMetadata().getName(); + } + + private static String serviceName(WebPage nginx) { + return nginx.getMetadata().getName(); + } + + private class ConfigMapDependentResource extends KubernetesDependentResource + implements + AssociatedSecondaryResourceIdentifier, Updater { + + @Override + protected ConfigMap desired(WebPage webPage, Context context) { + Map data = new HashMap<>(); + data.put("index.html", webPage.getSpec().getHtml()); + return new ConfigMapBuilder() + .withMetadata( + new ObjectMetaBuilder() + .withName(WebPageReconcilerDependentResources.configMapName(webPage)) + .withNamespace(webPage.getMetadata().getNamespace()) + .build()) + .withData(data) + .build(); + } + + @Override + public void update(ConfigMap actual, ConfigMap target, WebPage primary, Context context) { + super.update(actual, target, primary, context); + var ns = actual.getMetadata().getNamespace(); + log.info("Restarting pods because HTML has changed in {}", ns); + kubernetesClient + .pods() + .inNamespace(ns) + .withLabel("app", deploymentName(primary)) + .delete(); + } + + @Override + public ResourceID associatedSecondaryID(WebPage primary) { + return new ResourceID(configMapName(primary), primary.getMetadata().getNamespace()); + } + } +} diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerLowLevel.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerLowLevel.java deleted file mode 100644 index c3a4c9fb0b..0000000000 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerLowLevel.java +++ /dev/null @@ -1,257 +0,0 @@ -package io.javaoperatorsdk.operator.sample; - -import java.io.IOException; -import java.io.InputStream; -import java.util.*; - -import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; -import io.javaoperatorsdk.operator.processing.event.source.EventSource; -import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.fabric8.kubernetes.api.model.*; -import io.fabric8.kubernetes.api.model.apps.Deployment; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.dsl.Resource; -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.reconciler.*; -import io.javaoperatorsdk.operator.api.reconciler.Context; - -import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER; - -/** Shows how to implement reconciler using the low level api directly. */ -@ControllerConfiguration( - finalizerName = NO_FINALIZER, - labelSelector = WebPageReconcilerLowLevel.LOW_LEVEL_LABEL_KEY) -public class WebPageReconcilerLowLevel - implements Reconciler, ErrorStatusHandler, EventSourceInitializer { - - public static final String LOW_LEVEL_LABEL_KEY = "low-level"; - public static final String INDEX_HTML = "index.html"; - - private final Logger log = LoggerFactory.getLogger(getClass()); - - private final KubernetesClient kubernetesClient; - - public WebPageReconcilerLowLevel(KubernetesClient kubernetesClient) { - this.kubernetesClient = kubernetesClient; - } - - InformerEventSource configMapEventSource; - @Override - public List prepareEventSources(EventSourceContext context) { - - configMapEventSource = new InformerEventSource<>(InformerConfiguration.from(context,ConfigMap.class) - .withLabelSelector(LOW_LEVEL_LABEL_KEY) - .build(), context); - var deploymentEventSource = new InformerEventSource<>(InformerConfiguration.from(context,Deployment.class) - .withLabelSelector(LOW_LEVEL_LABEL_KEY) - .build(),context); - var serviceEventSource = new InformerEventSource<>(InformerConfiguration.from(context,Service.class) - .withLabelSelector(LOW_LEVEL_LABEL_KEY) - .build(),context); - return List.of(configMapEventSource,deploymentEventSource,serviceEventSource); - } - - @Override - public UpdateControl reconcile(WebPage webPage, Context context) { - log.info("Reconciling web page: {}", webPage); - if (webPage.getSpec().getHtml().contains("error")) { - throw new ErrorSimulationException("Simulating error"); - } - String ns = webPage.getMetadata().getNamespace(); - String configMapName = configMapName(webPage); - String deploymentName = deploymentName(webPage); - - - ConfigMap desiredHtmlConfigMap = makeDesiredHtmlConfigMap(ns, configMapName, webPage); - Deployment desiredDeployment = makeDesiredDeployment(webPage,deploymentName, ns, configMapName); - Service desiredService = makeDesiredService(webPage, ns, desiredDeployment); - - var previousConfigMap = context.getSecondaryResource(ConfigMap.class).orElse(null); - if (!match(desiredHtmlConfigMap, previousConfigMap)) { - log.info( - "Creating or updating ConfigMap {} in {}", - desiredHtmlConfigMap.getMetadata().getName(), - ns); - kubernetesClient.configMaps().inNamespace(ns).createOrReplace(desiredHtmlConfigMap); - } - - var existingDeployment = context.getSecondaryResource(Deployment.class).orElse(null); - if (!match(desiredDeployment, existingDeployment)) { - log.info( - "Creating or updating Deployment {} in {}", - desiredDeployment.getMetadata().getName(), - ns); - kubernetesClient.apps().deployments().inNamespace(ns).createOrReplace(desiredDeployment); - } - - var existingService = context.getSecondaryResource(Service.class).orElse(null); - if (!match(desiredService, existingService)) { - log.info( - "Creating or updating Deployment {} in {}", - desiredDeployment.getMetadata().getName(), - ns); - kubernetesClient.services().inNamespace(ns).createOrReplace(desiredService); - } - - if (previousConfigMap != null && !StringUtils.equals( - previousConfigMap.getData().get(INDEX_HTML), - desiredHtmlConfigMap.getData().get(INDEX_HTML))) { - log.info("Restarting pods because HTML has changed in {}", ns); - kubernetesClient.pods().inNamespace(ns).withLabel("app", deploymentName(webPage)).delete(); - } - webPage.setStatus(createStatus(desiredHtmlConfigMap.getMetadata().getName())); - return UpdateControl.updateStatus(webPage); - } - - private WebPageStatus createStatus(String configMapName) { - WebPageStatus status = new WebPageStatus(); - status.setHtmlConfigMap(configMapName); - status.setAreWeGood("Yes!"); - status.setErrorMessage(null); - return status; - } - - // todo - private boolean match(Deployment desiredDeployment, Deployment deployment) { - if (deployment == null) { - return false; - } - return true; - } - - // todo - private boolean match(Service desiredService, Service service) { - if (service == null) { - return false; - } - return true; - } - - private boolean match(ConfigMap desiredHtmlConfigMap, ConfigMap existingConfigMap) { - if (existingConfigMap == null) { - return false; - } else { - return desiredHtmlConfigMap.getData().equals(existingConfigMap.getData()); - } - } - - private Service makeDesiredService(WebPage webPage, String ns, Deployment desiredDeployment) { - Service desiredService = loadYaml(Service.class, "service.yaml"); - desiredService.getMetadata().setName(serviceName(webPage)); - desiredService.getMetadata().setNamespace(ns); - desiredService - .getSpec() - .setSelector(desiredDeployment.getSpec().getTemplate().getMetadata().getLabels()); - return desiredService; - } - - private Deployment makeDesiredDeployment(WebPage webPage, String deploymentName, String ns, String configMapName) { - Deployment desiredDeployment = loadYaml(Deployment.class, "deployment.yaml"); - desiredDeployment.getMetadata().setName(deploymentName); - desiredDeployment.getMetadata().setNamespace(ns); - desiredDeployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName); - desiredDeployment.getSpec().getTemplate().getMetadata().getLabels().put("app", deploymentName); - desiredDeployment - .getSpec() - .getTemplate() - .getSpec() - .getVolumes() - .get(0) - .setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName).build()); - desiredDeployment.addOwnerReference(webPage); - return desiredDeployment; - } - - @Override - public DeleteControl cleanup(WebPage nginx, Context context) { - log.info("Cleaning up for: {}", nginx.getMetadata().getName()); - - log.info("Deleting ConfigMap {}", configMapName(nginx)); - Resource configMap = - kubernetesClient - .configMaps() - .inNamespace(nginx.getMetadata().getNamespace()) - .withName(configMapName(nginx)); - if (configMap.get() != null) { - configMap.delete(); - } - - log.info("Deleting Deployment {}", deploymentName(nginx)); - RollableScalableResource deployment = - kubernetesClient - .apps() - .deployments() - .inNamespace(nginx.getMetadata().getNamespace()) - .withName(deploymentName(nginx)); - if (deployment.get() != null) { - deployment.cascading(true).delete(); - } - - log.info("Deleting Service {}", serviceName(nginx)); - ServiceResource service = - kubernetesClient - .services() - .inNamespace(nginx.getMetadata().getNamespace()) - .withName(serviceName(nginx)); - if (service.get() != null) { - service.delete(); - } - return DeleteControl.defaultDelete(); - } - - private ConfigMap makeDesiredHtmlConfigMap(String ns, String configMapName, WebPage webPage) { - Map data = new HashMap<>(); - data.put("index.html", webPage.getSpec().getHtml()); - ConfigMap configMap = - new ConfigMapBuilder() - .withMetadata( - new ObjectMetaBuilder() - .withName(configMapName) - .withNamespace(ns) - .withLabels(lowLevelLabel()) - .build()) - .withData(data) - .build(); - configMap.addOwnerReference(webPage); - return configMap; - } - - private Map lowLevelLabel() { - Map labels = new HashMap<>(); - labels.put(LOW_LEVEL_LABEL_KEY, "true"); - return labels; - } - - private static String configMapName(WebPage nginx) { - return nginx.getMetadata().getName() + "-html"; - } - - private static String deploymentName(WebPage nginx) { - return nginx.getMetadata().getName(); - } - - private static String serviceName(WebPage nginx) { - return nginx.getMetadata().getName(); - } - - 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); - } - } - - @Override - public Optional updateErrorStatus( - WebPage resource, RetryInfo retryInfo, RuntimeException e) { - resource.getStatus().setErrorMessage("Error: " + e.getMessage()); - return Optional.of(resource); - } -} From fd5ae7d7a38315ba4ec160d15cd34d36bfec6087 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 28 Feb 2022 14:08:29 +0100 Subject: [PATCH 5/6] fix: refactor labels --- .../CrudKubernetesDependentResource.java | 17 ++++++ sample-operators/webpage/k8s/webpage.yaml | 4 +- .../operator/sample/WebPage.java | 6 +-- .../operator/sample/WebPageOperator.java | 8 ++- .../operator/sample/WebPageReconciler.java | 52 +++++++++++-------- .../WebPageReconcilerDependentResources.java | 33 +++++++----- .../operator/sample/WebPageSpec.java | 4 +- .../operator/sample/WebPageStatus.java | 8 +-- 8 files changed, 86 insertions(+), 46 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CrudKubernetesDependentResource.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CrudKubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CrudKubernetesDependentResource.java new file mode 100644 index 0000000000..88e9bdc7ae --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/CrudKubernetesDependentResource.java @@ -0,0 +1,17 @@ +package io.javaoperatorsdk.operator.processing.dependent.kubernetes; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Creator; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Deleter; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Updater; + +/** + * Adaptor Class for standalone mode for resources that manages Create, Update and Delete + * + * @param Managed resource + * @param

Primary Resource + */ +public abstract class CrudKubernetesDependentResource + extends + KubernetesDependentResource implements Creator, Updater, Deleter

{ +} diff --git a/sample-operators/webpage/k8s/webpage.yaml b/sample-operators/webpage/k8s/webpage.yaml index 382da972cb..d59c67eee1 100644 --- a/sample-operators/webpage/k8s/webpage.yaml +++ b/sample-operators/webpage/k8s/webpage.yaml @@ -1,6 +1,8 @@ apiVersion: "sample.javaoperatorsdk/v1" kind: WebPage metadata: + labels: + low-level: "true" name: hellows spec: html: | @@ -9,6 +11,6 @@ spec: Hello Operator World - Hello World! + Hello World! diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPage.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPage.java index 67cd8aa7e8..52ef6a938b 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPage.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPage.java @@ -18,8 +18,8 @@ protected WebPageStatus initStatus() { @Override public String toString() { return "WebPage{" + - "spec=" + spec + - ", status=" + status + - '}'; + "spec=" + spec + + ", status=" + status + + '}'; } } diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java index 2a6dd8da98..b61b34334e 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageOperator.java @@ -1,6 +1,7 @@ package io.javaoperatorsdk.operator.sample; import java.io.IOException; +import java.util.Arrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,8 +27,11 @@ 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()); - operator.register(new WebPageReconcilerDependentResources(client)); - operator.register(new WebPageReconciler(client)); + if (Arrays.stream(args).anyMatch(arg -> arg.equals("--classic"))) { + operator.register(new WebPageReconciler(client)); + } else { + operator.register(new WebPageReconcilerDependentResources(client)); + } operator.installShutdownHook(); operator.start(); diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java index d09662fe1b..46b29c4564 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java @@ -4,9 +4,6 @@ import java.io.InputStream; import java.util.*; -import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; -import io.javaoperatorsdk.operator.processing.event.source.EventSource; -import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,8 +12,11 @@ import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.utils.Serialization; +import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.event.source.EventSource; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER; @@ -30,7 +30,7 @@ public class WebPageReconciler public static final String LOW_LEVEL_LABEL_KEY = "low-level"; public static final String INDEX_HTML = "index.html"; - private final Logger log = LoggerFactory.getLogger(getClass()); + private static final Logger log = LoggerFactory.getLogger(WebPageReconciler.class); private final KubernetesClient kubernetesClient; @@ -39,19 +39,22 @@ public WebPageReconciler(KubernetesClient kubernetesClient) { } InformerEventSource configMapEventSource; + @Override public List prepareEventSources(EventSourceContext context) { - - configMapEventSource = new InformerEventSource<>(InformerConfiguration.from(context,ConfigMap.class) + configMapEventSource = + new InformerEventSource<>(InformerConfiguration.from(context, ConfigMap.class) .withLabelSelector(LOW_LEVEL_LABEL_KEY) .build(), context); - var deploymentEventSource = new InformerEventSource<>(InformerConfiguration.from(context,Deployment.class) + var deploymentEventSource = + new InformerEventSource<>(InformerConfiguration.from(context, Deployment.class) .withLabelSelector(LOW_LEVEL_LABEL_KEY) - .build(),context); - var serviceEventSource = new InformerEventSource<>(InformerConfiguration.from(context,Service.class) + .build(), context); + var serviceEventSource = + new InformerEventSource<>(InformerConfiguration.from(context, Service.class) .withLabelSelector(LOW_LEVEL_LABEL_KEY) - .build(),context); - return List.of(configMapEventSource,deploymentEventSource,serviceEventSource); + .build(), context); + return List.of(configMapEventSource, deploymentEventSource, serviceEventSource); } @Override @@ -66,7 +69,8 @@ public UpdateControl reconcile(WebPage webPage, Context context) { ConfigMap desiredHtmlConfigMap = makeDesiredHtmlConfigMap(ns, configMapName, webPage); - Deployment desiredDeployment = makeDesiredDeployment(webPage,deploymentName, ns, configMapName); + Deployment desiredDeployment = + makeDesiredDeployment(webPage, deploymentName, ns, configMapName); Service desiredService = makeDesiredService(webPage, ns, desiredDeployment); var previousConfigMap = context.getSecondaryResource(ConfigMap.class).orElse(null); @@ -90,17 +94,17 @@ public UpdateControl reconcile(WebPage webPage, Context context) { var existingService = context.getSecondaryResource(Service.class).orElse(null); if (!match(desiredService, existingService)) { log.info( - "Creating or updating Deployment {} in {}", - desiredDeployment.getMetadata().getName(), - ns); + "Creating or updating Deployment {} in {}", + desiredDeployment.getMetadata().getName(), + ns); kubernetesClient.services().inNamespace(ns).createOrReplace(desiredService); } if (previousConfigMap != null && !StringUtils.equals( - previousConfigMap.getData().get(INDEX_HTML), - desiredHtmlConfigMap.getData().get(INDEX_HTML))) { - log.info("Restarting pods because HTML has changed in {}", ns); - kubernetesClient.pods().inNamespace(ns).withLabel("app", deploymentName(webPage)).delete(); + previousConfigMap.getData().get(INDEX_HTML), + desiredHtmlConfigMap.getData().get(INDEX_HTML))) { + log.info("Restarting pods because HTML has changed in {}", ns); + kubernetesClient.pods().inNamespace(ns).withLabel("app", deploymentName(webPage)).delete(); } webPage.setStatus(createStatus(desiredHtmlConfigMap.getMetadata().getName())); return UpdateControl.updateStatus(webPage); @@ -119,8 +123,9 @@ private boolean match(Deployment desiredDeployment, Deployment deployment) { return false; } else { return desiredDeployment.getSpec().getReplicas().equals(deployment.getSpec().getReplicas()) && - desiredDeployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() - .equals(deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage()); + desiredDeployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage() + .equals( + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage()); } } @@ -143,6 +148,7 @@ private Service makeDesiredService(WebPage webPage, String ns, Deployment desire Service desiredService = loadYaml(Service.class, "service.yaml"); desiredService.getMetadata().setName(serviceName(webPage)); desiredService.getMetadata().setNamespace(ns); + desiredService.getMetadata().setLabels(lowLevelLabel()); desiredService .getSpec() .setSelector(desiredDeployment.getSpec().getTemplate().getMetadata().getLabels()); @@ -150,10 +156,12 @@ private Service makeDesiredService(WebPage webPage, String ns, Deployment desire return desiredService; } - private Deployment makeDesiredDeployment(WebPage webPage, String deploymentName, String ns, String configMapName) { + private Deployment makeDesiredDeployment(WebPage webPage, String deploymentName, String ns, + String configMapName) { Deployment desiredDeployment = loadYaml(Deployment.class, "deployment.yaml"); desiredDeployment.getMetadata().setName(deploymentName); desiredDeployment.getMetadata().setNamespace(ns); + desiredDeployment.getMetadata().setLabels(lowLevelLabel()); desiredDeployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName); desiredDeployment.getSpec().getTemplate().getMetadata().getLabels().put("app", deploymentName); desiredDeployment diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerDependentResources.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerDependentResources.java index 927d1b768b..a313a0bf3d 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerDependentResources.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconcilerDependentResources.java @@ -2,7 +2,6 @@ import java.util.*; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,8 +10,9 @@ import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.dependent.Updater; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CrudKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig; import io.javaoperatorsdk.operator.processing.event.ResourceID; import io.javaoperatorsdk.operator.processing.event.source.AssociatedSecondaryResourceIdentifier; import io.javaoperatorsdk.operator.processing.event.source.EventSource; @@ -23,12 +23,14 @@ /** * Shows how to implement reconciler using standalone dependent resources. */ -@ControllerConfiguration(finalizerName = NO_FINALIZER, labelSelector = WebPageReconcilerDependentResources.DEPENDENT_RESOURCE_LABEL_SELECTOR) +@ControllerConfiguration(finalizerName = NO_FINALIZER, + labelSelector = WebPageReconcilerDependentResources.DEPENDENT_RESOURCE_LABEL_SELECTOR) public class WebPageReconcilerDependentResources implements Reconciler, ErrorStatusHandler, EventSourceInitializer { public static final String DEPENDENT_RESOURCE_LABEL_SELECTOR = "!low-level"; - private final Logger log = LoggerFactory.getLogger(getClass()); + private static final Logger log = + LoggerFactory.getLogger(WebPageReconcilerDependentResources.class); private final KubernetesClient kubernetesClient; private KubernetesDependentResource configMapDR; @@ -59,7 +61,8 @@ public UpdateControl reconcile(WebPage webPage, Context context) { deploymentDR.reconcile(webPage, context); serviceDR.reconcile(webPage, context); - webPage.setStatus(createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName())); + webPage.setStatus( + createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName())); return UpdateControl.updateStatus(webPage); } @@ -80,11 +83,12 @@ public Optional updateErrorStatus( private void createDependentResources(KubernetesClient client) { this.configMapDR = new ConfigMapDependentResource(); + this.configMapDR.setKubernetesClient(client); configMapDR.configureWith(new KubernetesDependentResourceConfig() - .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); + .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); this.deploymentDR = - new KubernetesDependentResource<>() { + new CrudKubernetesDependentResource<>() { @Override protected Deployment desired(WebPage webPage, Context context) { @@ -116,10 +120,12 @@ protected Class resourceType() { return Deployment.class; } }; - deploymentDR.configureWith(new KubernetesDependentResourceConfig().setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); + deploymentDR.setKubernetesClient(client); + deploymentDR.configureWith(new KubernetesDependentResourceConfig() + .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); this.serviceDR = - new KubernetesDependentResource<>() { + new CrudKubernetesDependentResource<>() { @Override protected Service desired(WebPage webPage, Context context) { @@ -137,7 +143,9 @@ protected Class resourceType() { return Service.class; } }; - serviceDR.configureWith(new KubernetesDependentResourceConfig().setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); + serviceDR.setKubernetesClient(client); + serviceDR.configureWith(new KubernetesDependentResourceConfig() + .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR)); } private static String configMapName(WebPage nginx) { @@ -152,9 +160,10 @@ private static String serviceName(WebPage nginx) { return nginx.getMetadata().getName(); } - private class ConfigMapDependentResource extends KubernetesDependentResource + private class ConfigMapDependentResource + extends CrudKubernetesDependentResource implements - AssociatedSecondaryResourceIdentifier, Updater { + AssociatedSecondaryResourceIdentifier { @Override protected ConfigMap desired(WebPage webPage, Context context) { diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageSpec.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageSpec.java index 6f05d6e2e8..562cda291d 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageSpec.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageSpec.java @@ -15,7 +15,7 @@ public void setHtml(String html) { @Override public String toString() { return "WebPageSpec{" + - "html='" + html + '\'' + - '}'; + "html='" + html + '\'' + + '}'; } } diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStatus.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStatus.java index 31bc52282c..3750e2f7c7 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStatus.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStatus.java @@ -38,9 +38,9 @@ public WebPageStatus setErrorMessage(String errorMessage) { @Override public String toString() { return "WebPageStatus{" + - "htmlConfigMap='" + htmlConfigMap + '\'' + - ", areWeGood='" + areWeGood + '\'' + - ", errorMessage='" + errorMessage + '\'' + - '}'; + "htmlConfigMap='" + htmlConfigMap + '\'' + + ", areWeGood='" + areWeGood + '\'' + + ", errorMessage='" + errorMessage + '\'' + + '}'; } } From 773222122fa3fd3be7b57cfda7283cb84ffe02dc Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 28 Feb 2022 16:00:57 +0100 Subject: [PATCH 6/6] fix: from code review --- .../operator/sample/WebPageReconciler.java | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java index 46b29c4564..b2727e065b 100644 --- a/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java +++ b/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java @@ -1,7 +1,5 @@ package io.javaoperatorsdk.operator.sample; -import java.io.IOException; -import java.io.InputStream; import java.util.*; import org.apache.commons.lang3.StringUtils; @@ -11,7 +9,7 @@ import io.fabric8.kubernetes.api.model.*; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.utils.Serialization; +import io.javaoperatorsdk.operator.ReconcilerUtils; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.*; import io.javaoperatorsdk.operator.api.reconciler.Context; @@ -145,7 +143,7 @@ private boolean match(ConfigMap desiredHtmlConfigMap, ConfigMap existingConfigMa } private Service makeDesiredService(WebPage webPage, String ns, Deployment desiredDeployment) { - Service desiredService = loadYaml(Service.class, "service.yaml"); + Service desiredService = ReconcilerUtils.loadYaml(Service.class, getClass(), "service.yaml"); desiredService.getMetadata().setName(serviceName(webPage)); desiredService.getMetadata().setNamespace(ns); desiredService.getMetadata().setLabels(lowLevelLabel()); @@ -158,7 +156,8 @@ private Service makeDesiredService(WebPage webPage, String ns, Deployment desire private Deployment makeDesiredDeployment(WebPage webPage, String deploymentName, String ns, String configMapName) { - Deployment desiredDeployment = loadYaml(Deployment.class, "deployment.yaml"); + Deployment desiredDeployment = + ReconcilerUtils.loadYaml(Deployment.class, getClass(), "deployment.yaml"); desiredDeployment.getMetadata().setName(deploymentName); desiredDeployment.getMetadata().setNamespace(ns); desiredDeployment.getMetadata().setLabels(lowLevelLabel()); @@ -210,14 +209,6 @@ private static String serviceName(WebPage nginx) { return nginx.getMetadata().getName(); } - 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); - } - } - @Override public Optional updateErrorStatus( WebPage resource, RetryInfo retryInfo, RuntimeException e) {