diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f60d22017c..008ebb5ec1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * Added support for Strimzi Metrics Reporter to Kafka Connect, Mirror Maker 2 and Kafka Bridge. * Add new feature gate `ServerSideApplyPhase1` (disabled by default) that adds support for Server Side Apply for `ConfigMap`, `Ingress`, `PVC`, `Service`, and `ServiceAccount` according to [Strimzi Proposal #105](https://github.com/strimzi/proposals/blob/main/105-server-side-apply-implementation-fg-timelines.md). * Added distinction between changes of "cluster-wide" broker properties applied dynamically at cluster level, and "per-broker" broker properties applied dynamically at broker level. +* Extend the EntityOperator, Cruise Control and KafkaExporter deployment to support PDB via the template section in the CR spec. ### Major changes, deprecations and removals diff --git a/api/src/main/java/io/strimzi/api/kafka/model/kafka/entityoperator/EntityOperatorTemplate.java b/api/src/main/java/io/strimzi/api/kafka/model/kafka/entityoperator/EntityOperatorTemplate.java index deadf213266..9c9f323f485 100644 --- a/api/src/main/java/io/strimzi/api/kafka/model/kafka/entityoperator/EntityOperatorTemplate.java +++ b/api/src/main/java/io/strimzi/api/kafka/model/kafka/entityoperator/EntityOperatorTemplate.java @@ -11,6 +11,7 @@ import io.strimzi.api.kafka.model.common.UnknownPropertyPreserving; import io.strimzi.api.kafka.model.common.template.ContainerTemplate; import io.strimzi.api.kafka.model.common.template.DeploymentTemplate; +import io.strimzi.api.kafka.model.common.template.PodDisruptionBudgetTemplate; import io.strimzi.api.kafka.model.common.template.PodTemplate; import io.strimzi.api.kafka.model.common.template.ResourceTemplate; import io.strimzi.crdgenerator.annotations.Description; @@ -29,7 +30,7 @@ builderPackage = Constants.FABRIC8_KUBERNETES_API ) @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({"deployment", "pod", "topicOperatorContainer", "userOperatorContainer", "tlsSidecarContainer", "serviceAccount", "entityOperatorRole", "topicOperatorRoleBinding", "userOperatorRoleBinding"}) +@JsonPropertyOrder({"deployment", "pod", "topicOperatorContainer", "userOperatorContainer", "tlsSidecarContainer", "serviceAccount", "podDisruptionBudget", "entityOperatorRole", "topicOperatorRoleBinding", "userOperatorRoleBinding"}) @EqualsAndHashCode @ToString public class EntityOperatorTemplate implements UnknownPropertyPreserving { @@ -42,6 +43,7 @@ public class EntityOperatorTemplate implements UnknownPropertyPreserving { private ContainerTemplate userOperatorContainer; private ContainerTemplate tlsSidecarContainer; private ResourceTemplate serviceAccount; + private PodDisruptionBudgetTemplate podDisruptionBudget; private Map additionalProperties; @Description("Template for Entity Operator `Deployment`.") @@ -136,6 +138,15 @@ public void setServiceAccount(ResourceTemplate serviceAccount) { this.serviceAccount = serviceAccount; } + @Description("Template for the Entity Operator Pod Disruption Budget.") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public PodDisruptionBudgetTemplate getPodDisruptionBudget() { + return podDisruptionBudget; + } + public void setPodDisruptionBudget(PodDisruptionBudgetTemplate podDisruptionBudget) { + this.podDisruptionBudget = podDisruptionBudget; + } + @Override public Map getAdditionalProperties() { return this.additionalProperties != null ? this.additionalProperties : Map.of(); diff --git a/api/src/main/java/io/strimzi/api/kafka/model/kafka/exporter/KafkaExporterTemplate.java b/api/src/main/java/io/strimzi/api/kafka/model/kafka/exporter/KafkaExporterTemplate.java index 3ff612739ab..0f55467d0d6 100644 --- a/api/src/main/java/io/strimzi/api/kafka/model/kafka/exporter/KafkaExporterTemplate.java +++ b/api/src/main/java/io/strimzi/api/kafka/model/kafka/exporter/KafkaExporterTemplate.java @@ -11,6 +11,7 @@ import io.strimzi.api.kafka.model.common.UnknownPropertyPreserving; import io.strimzi.api.kafka.model.common.template.ContainerTemplate; import io.strimzi.api.kafka.model.common.template.DeploymentTemplate; +import io.strimzi.api.kafka.model.common.template.PodDisruptionBudgetTemplate; import io.strimzi.api.kafka.model.common.template.PodTemplate; import io.strimzi.api.kafka.model.common.template.ResourceTemplate; import io.strimzi.crdgenerator.annotations.Description; @@ -30,7 +31,7 @@ builderPackage = Constants.FABRIC8_KUBERNETES_API ) @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonPropertyOrder({"deployment", "pod", "service", "container", "serviceAccount"}) +@JsonPropertyOrder({"deployment", "pod", "service", "container", "serviceAccount", "podDisruptionBudget"}) @EqualsAndHashCode @ToString public class KafkaExporterTemplate implements UnknownPropertyPreserving { @@ -39,6 +40,7 @@ public class KafkaExporterTemplate implements UnknownPropertyPreserving { private ResourceTemplate service; private ContainerTemplate container; private ResourceTemplate serviceAccount; + private PodDisruptionBudgetTemplate podDisruptionBudget; private Map additionalProperties; @Description("Template for Kafka Exporter `Deployment`.") @@ -94,6 +96,15 @@ public void setServiceAccount(ResourceTemplate serviceAccount) { this.serviceAccount = serviceAccount; } + @Description("Template for the Pod Disruption Budget for Kafka Exporter pods.") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + public PodDisruptionBudgetTemplate getPodDisruptionBudget() { + return podDisruptionBudget; + } + public void setPodDisruptionBudget(PodDisruptionBudgetTemplate podDisruptionBudget) { + this.podDisruptionBudget = podDisruptionBudget; + } + @Override public Map getAdditionalProperties() { return this.additionalProperties != null ? this.additionalProperties : Map.of(); @@ -106,4 +117,5 @@ public void setAdditionalProperty(String name, Object value) { } this.additionalProperties.put(name, value); } + } diff --git a/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/CruiseControl.java b/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/CruiseControl.java index fd85054b633..5eb57e5bb0f 100644 --- a/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/CruiseControl.java +++ b/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/CruiseControl.java @@ -20,11 +20,13 @@ import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyIngressRule; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyPeer; +import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget; import io.strimzi.api.kafka.model.common.JvmOptions; import io.strimzi.api.kafka.model.common.metrics.JmxPrometheusExporterMetrics; import io.strimzi.api.kafka.model.common.metrics.StrimziMetricsReporter; import io.strimzi.api.kafka.model.common.template.DeploymentTemplate; import io.strimzi.api.kafka.model.common.template.InternalServiceTemplate; +import io.strimzi.api.kafka.model.common.template.PodDisruptionBudgetTemplate; import io.strimzi.api.kafka.model.common.template.PodTemplate; import io.strimzi.api.kafka.model.kafka.Kafka; import io.strimzi.api.kafka.model.kafka.KafkaClusterSpec; @@ -140,6 +142,7 @@ public class CruiseControl extends AbstractModel implements SupportsMetrics, Sup private DeploymentTemplate templateDeployment; private PodTemplate templatePod; private InternalServiceTemplate templateService; + private PodDisruptionBudgetTemplate templatePodDisruptionBudget; private static final Map DEFAULT_POD_LABELS = new HashMap<>(); static { @@ -235,6 +238,7 @@ public static CruiseControl fromCrd( result.templateService = template.getApiService(); result.templateServiceAccount = template.getServiceAccount(); result.templateContainer = template.getCruiseControlContainer(); + result.templatePodDisruptionBudget = template.getPodDisruptionBudget(); } return result; @@ -542,4 +546,19 @@ public ConfigMap generateConfigMap(MetricsAndLogging metricsAndLogging) { configMapData ); } + /** + * Generates the PodDisruptionBudget for Cruise Control + * + * @return The PodDisruptionBudget for Cruise Control + */ + public PodDisruptionBudget generatePodDisruptionBudget() { + return PodDisruptionBudgetUtils.createCustomControllerPodDisruptionBudget( + componentName, + namespace, + labels, + ownerReference, + templatePodDisruptionBudget, + 1 + ); + } } \ No newline at end of file diff --git a/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/EntityOperator.java b/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/EntityOperator.java index b83f1ff8749..074391c71d6 100644 --- a/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/EntityOperator.java +++ b/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/EntityOperator.java @@ -13,11 +13,13 @@ import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyIngressRule; +import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget; import io.fabric8.kubernetes.api.model.rbac.ClusterRole; import io.fabric8.kubernetes.api.model.rbac.PolicyRule; import io.fabric8.kubernetes.api.model.rbac.Role; import io.strimzi.api.kafka.model.common.Probe; import io.strimzi.api.kafka.model.common.template.DeploymentTemplate; +import io.strimzi.api.kafka.model.common.template.PodDisruptionBudgetTemplate; import io.strimzi.api.kafka.model.common.template.PodTemplate; import io.strimzi.api.kafka.model.common.template.ResourceTemplate; import io.strimzi.api.kafka.model.kafka.Kafka; @@ -81,6 +83,7 @@ public class EntityOperator extends AbstractModel { private ResourceTemplate templateRole; private DeploymentTemplate templateDeployment; private PodTemplate templatePod; + private PodDisruptionBudgetTemplate templatePodDisruptionBudget; private static final Map DEFAULT_POD_LABELS = new HashMap<>(); static { @@ -135,6 +138,7 @@ public static EntityOperator fromCrd(Reconciliation reconciliation, result.templateDeployment = template.getDeployment(); result.templatePod = template.getPod(); result.templateServiceAccount = template.getServiceAccount(); + result.templatePodDisruptionBudget = template.getPodDisruptionBudget(); if (topicOperator != null) { topicOperator.templateContainer = template.getTopicOperatorContainer(); @@ -305,4 +309,19 @@ public NetworkPolicy generateNetworkPolicy() { rules ); } + /** + * Generates the PodDisruptionBudget for the Entity Operator + * + * @return The PodDisruptionBudget for the Entity Operator + */ + public PodDisruptionBudget generatePodDisruptionBudget() { + return PodDisruptionBudgetUtils.createCustomControllerPodDisruptionBudget( + componentName, + namespace, + labels, + ownerReference, + templatePodDisruptionBudget, + 1 + ); + } } diff --git a/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/KafkaExporter.java b/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/KafkaExporter.java index 19864b111df..45d6f8ba126 100644 --- a/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/KafkaExporter.java +++ b/cluster-operator/src/main/java/io/strimzi/operator/cluster/model/KafkaExporter.java @@ -15,9 +15,11 @@ import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyIngressRule; +import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget; import io.strimzi.api.kafka.model.common.Probe; import io.strimzi.api.kafka.model.common.ProbeBuilder; import io.strimzi.api.kafka.model.common.template.DeploymentTemplate; +import io.strimzi.api.kafka.model.common.template.PodDisruptionBudgetTemplate; import io.strimzi.api.kafka.model.common.template.PodTemplate; import io.strimzi.api.kafka.model.kafka.Kafka; import io.strimzi.api.kafka.model.kafka.KafkaClusterSpec; @@ -83,6 +85,7 @@ public class KafkaExporter extends AbstractModel { private DeploymentTemplate templateDeployment; private PodTemplate templatePod; + private PodDisruptionBudgetTemplate templatePodDisruptionBudget; private static final Map DEFAULT_POD_LABELS = new HashMap<>(); static { String value = System.getenv(CO_ENV_VAR_CUSTOM_KAFKA_EXPORTER_POD_LABELS); @@ -148,6 +151,7 @@ public static KafkaExporter fromCrd(Reconciliation reconciliation, Kafka kafkaAs result.templatePod = template.getPod(); result.templateServiceAccount = template.getServiceAccount(); result.templateContainer = template.getContainer(); + result.templatePodDisruptionBudget = template.getPodDisruptionBudget(); } result.version = versions.supportedVersion(kafkaAssembly.getSpec().getKafka().getVersion()).version(); @@ -313,4 +317,19 @@ public NetworkPolicy generateNetworkPolicy() { rules ); } + /** + * Generates the PodDisruptionBudget for Kafka Exporter + * + * @return The PodDisruptionBudget. + */ + public PodDisruptionBudget generatePodDisruptionBudget() { + return PodDisruptionBudgetUtils.createCustomControllerPodDisruptionBudget( + componentName, + namespace, + labels, + ownerReference, + templatePodDisruptionBudget, + 1 + ); + } } diff --git a/cluster-operator/src/main/java/io/strimzi/operator/cluster/operator/assembly/CruiseControlReconciler.java b/cluster-operator/src/main/java/io/strimzi/operator/cluster/operator/assembly/CruiseControlReconciler.java index 167ecaaaa18..93a2ff8b584 100644 --- a/cluster-operator/src/main/java/io/strimzi/operator/cluster/operator/assembly/CruiseControlReconciler.java +++ b/cluster-operator/src/main/java/io/strimzi/operator/cluster/operator/assembly/CruiseControlReconciler.java @@ -24,6 +24,7 @@ import io.strimzi.operator.cluster.operator.resource.kubernetes.ConfigMapOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.DeploymentOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.NetworkPolicyOperator; +import io.strimzi.operator.cluster.operator.resource.kubernetes.PodDisruptionBudgetOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.SecretOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.ServiceAccountOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.ServiceOperator; @@ -63,6 +64,9 @@ public class CruiseControlReconciler { private final NetworkPolicyOperator networkPolicyOperator; private final ConfigMapOperator configMapOperator; private final PasswordGenerator passwordGenerator; + private final boolean isPodDisruptionBudgetGeneration; + private final PodDisruptionBudgetOperator podDisruptionBudgetOperator; + private String certificateHash = ""; private String serverConfigurationHash = ""; @@ -114,6 +118,8 @@ public CruiseControlReconciler( this.serviceOperator = supplier.serviceOperations; this.networkPolicyOperator = supplier.networkPolicyOperator; this.configMapOperator = supplier.configMapOperations; + this.isPodDisruptionBudgetGeneration = config.isPodDisruptionBudgetGeneration(); + this.podDisruptionBudgetOperator = supplier.podDisruptionBudgetOperator; } /** @@ -135,6 +141,7 @@ public Future reconcile(boolean isOpenShift, ImagePullPolicy imagePullPoli .compose(i -> certificatesSecret(clock)) .compose(i -> apiSecret()) .compose(i -> service()) + .compose(i -> podDisruptionBudget()) .compose(i -> deployment(isOpenShift, imagePullPolicy, imagePullSecrets)) .compose(i -> waitForDeploymentReadiness()); } @@ -173,7 +180,24 @@ protected Future serviceAccount() { cruiseControl != null ? cruiseControl.generateServiceAccount() : null ).mapEmpty(); } - + /** + * Manages the Cruise Control Pod Disruption Budget + * + * @return Future which completes when the reconciliation is done + */ + protected Future podDisruptionBudget() { + if (isPodDisruptionBudgetGeneration) { + return podDisruptionBudgetOperator + .reconcile( + reconciliation, + reconciliation.namespace(), + CruiseControlResources.componentName(reconciliation.name()), + cruiseControl != null ? cruiseControl.generatePodDisruptionBudget() : null + ).mapEmpty(); + } else { + return Future.succeededFuture(); + } + } /** * Manages the Cruise Control ConfigMap which contains the following: * (1) Cruise Control server configuration diff --git a/cluster-operator/src/main/java/io/strimzi/operator/cluster/operator/assembly/EntityOperatorReconciler.java b/cluster-operator/src/main/java/io/strimzi/operator/cluster/operator/assembly/EntityOperatorReconciler.java index e872f6d8b83..3869aeb1fe5 100644 --- a/cluster-operator/src/main/java/io/strimzi/operator/cluster/operator/assembly/EntityOperatorReconciler.java +++ b/cluster-operator/src/main/java/io/strimzi/operator/cluster/operator/assembly/EntityOperatorReconciler.java @@ -19,6 +19,7 @@ import io.strimzi.operator.cluster.operator.resource.kubernetes.ConfigMapOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.DeploymentOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.NetworkPolicyOperator; +import io.strimzi.operator.cluster.operator.resource.kubernetes.PodDisruptionBudgetOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.RoleBindingOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.RoleOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.SecretOperator; @@ -50,11 +51,13 @@ public class EntityOperatorReconciler { private final SecretOperator secretOperator; private final ServiceAccountOperator serviceAccountOperator; private final boolean isNetworkPolicyGeneration; + private final boolean isPodDisruptionBudgetGeneration; private final RoleOperator roleOperator; private final RoleBindingOperator roleBindingOperator; private final ConfigMapOperator configMapOperator; private final NetworkPolicyOperator networkPolicyOperator; private final boolean isCruiseControlEnabled; + private final PodDisruptionBudgetOperator podDistruptionBudgetOperator; private String toCertificateHash = ""; private String uoCertificateHash = ""; @@ -83,6 +86,7 @@ public EntityOperatorReconciler( this.maintenanceWindows = kafkaAssembly.getSpec().getMaintenanceTimeWindows(); this.isNetworkPolicyGeneration = config.isNetworkPolicyGeneration(); this.isCruiseControlEnabled = kafkaAssembly.getSpec().getCruiseControl() != null; + this.isPodDisruptionBudgetGeneration = config.isPodDisruptionBudgetGeneration(); this.deploymentOperator = supplier.deploymentOperations; this.secretOperator = supplier.secretOperations; @@ -91,6 +95,7 @@ public EntityOperatorReconciler( this.roleBindingOperator = supplier.roleBindingOperations; this.configMapOperator = supplier.configMapOperations; this.networkPolicyOperator = supplier.networkPolicyOperator; + this.podDistruptionBudgetOperator = supplier.podDisruptionBudgetOperator; } /** @@ -111,6 +116,7 @@ public Future reconcile(boolean isOpenShift, ImagePullPolicy imagePullPoli .compose(i -> topicOperatorRole()) .compose(i -> userOperatorRole()) .compose(i -> networkPolicy()) + .compose(i -> podDistruptionBudget()) .compose(i -> topicOperatorRoleBindings()) .compose(i -> userOperatorRoleBindings()) .compose(i -> topicOperatorConfigMap()) @@ -416,7 +422,24 @@ protected Future networkPolicy() { return Future.succeededFuture(); } } - + /** + * Manages the Entity Operator Pod Disruption Budget + * + * @return Future which completes when the reconciliation is done + */ + protected Future podDistruptionBudget() { + if (isPodDisruptionBudgetGeneration) { + return podDistruptionBudgetOperator + .reconcile( + reconciliation, + reconciliation.namespace(), + KafkaResources.entityOperatorDeploymentName(reconciliation.name()), + entityOperator != null ? entityOperator.generatePodDisruptionBudget() : null + ).mapEmpty(); + } else { + return Future.succeededFuture(); + } + } /** * Manages the Entity Operator Deployment. * diff --git a/cluster-operator/src/main/java/io/strimzi/operator/cluster/operator/assembly/KafkaExporterReconciler.java b/cluster-operator/src/main/java/io/strimzi/operator/cluster/operator/assembly/KafkaExporterReconciler.java index 8f6adc2c777..ce6891c6d5e 100644 --- a/cluster-operator/src/main/java/io/strimzi/operator/cluster/operator/assembly/KafkaExporterReconciler.java +++ b/cluster-operator/src/main/java/io/strimzi/operator/cluster/operator/assembly/KafkaExporterReconciler.java @@ -18,6 +18,7 @@ import io.strimzi.operator.cluster.operator.resource.ResourceOperatorSupplier; import io.strimzi.operator.cluster.operator.resource.kubernetes.DeploymentOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.NetworkPolicyOperator; +import io.strimzi.operator.cluster.operator.resource.kubernetes.PodDisruptionBudgetOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.SecretOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.ServiceAccountOperator; import io.strimzi.operator.common.Annotations; @@ -42,10 +43,12 @@ public class KafkaExporterReconciler { private final ClusterCa clusterCa; private final List maintenanceWindows; private final boolean isNetworkPolicyGeneration; + private final boolean isPodDisruptionBudgetGeneration; private final DeploymentOperator deploymentOperator; private final SecretOperator secretOperator; private final ServiceAccountOperator serviceAccountOperator; private final NetworkPolicyOperator networkPolicyOperator; + private final PodDisruptionBudgetOperator podDisruptionBudgetOperator; private String certificateHash = ""; @@ -73,11 +76,13 @@ public KafkaExporterReconciler( this.clusterCa = clusterCa; this.maintenanceWindows = kafkaAssembly.getSpec().getMaintenanceTimeWindows(); this.isNetworkPolicyGeneration = config.isNetworkPolicyGeneration(); + this.isPodDisruptionBudgetGeneration = config.isPodDisruptionBudgetGeneration(); this.deploymentOperator = supplier.deploymentOperations; this.secretOperator = supplier.secretOperations; this.serviceAccountOperator = supplier.serviceAccountOperations; this.networkPolicyOperator = supplier.networkPolicyOperator; + this.podDisruptionBudgetOperator = supplier.podDisruptionBudgetOperator; } /** @@ -96,6 +101,7 @@ public Future reconcile(boolean isOpenShift, ImagePullPolicy imagePullPoli return serviceAccount() .compose(i -> certificatesSecret(clock)) .compose(i -> networkPolicy()) + .compose(i -> podDisruptionBudget()) .compose(i -> deployment(isOpenShift, imagePullPolicy, imagePullSecrets)) .compose(i -> waitForDeploymentReadiness()); } @@ -163,6 +169,24 @@ protected Future networkPolicy() { } } + /** + * Manages the Kafka Exporter Pod Disruption Budget + * + * @return Future which completes when the reconciliation is done + */ + protected Future podDisruptionBudget() { + if (isPodDisruptionBudgetGeneration) { + return podDisruptionBudgetOperator + .reconcile( + reconciliation, + reconciliation.namespace(), + KafkaExporterResources.componentName(reconciliation.name()), + kafkaExporter != null ? kafkaExporter.generatePodDisruptionBudget() : null + ).mapEmpty(); + } else { + return Future.succeededFuture(); + } + } /** * Manages the Kafka Exporter deployment. * diff --git a/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/CruiseControlTest.java b/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/CruiseControlTest.java index de4c386480e..d0348b3daf4 100644 --- a/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/CruiseControlTest.java +++ b/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/CruiseControlTest.java @@ -35,6 +35,7 @@ import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyIngressRule; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyPeer; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyPeerBuilder; +import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget; import io.strimzi.api.kafka.model.common.JvmOptions; import io.strimzi.api.kafka.model.common.SystemPropertyBuilder; import io.strimzi.api.kafka.model.common.metrics.JmxPrometheusExporterMetricsBuilder; @@ -128,6 +129,10 @@ public class CruiseControlTest { .withConfig(Map.of(CruiseControl.MIN_INSYNC_REPLICAS, MIN_INSYNC_REPLICAS, KafkaConfiguration.DEFAULT_REPLICATION_FACTOR, REPLICATION_FACTOR)) .endKafka() .withNewCruiseControl() + .withNewTemplate() + .withNewPodDisruptionBudget() + .endPodDisruptionBudget() + .endTemplate() .endCruiseControl() .endSpec() .build(); @@ -392,6 +397,9 @@ public void testTemplate() { Map saLabels = Map.of("l7", "v7", "l8", "v8"); Map saAnnotations = Map.of("a7", "v7", "a8", "v8"); + Map pbdLabels = Map.of("l9", "v9", "l10", "v10"); + Map pbdAnnotations = Map.of("a9", "v9", "a10", "v10"); + Affinity affinity = new AffinityBuilder() .withNewNodeAffinity() .withNewRequiredDuringSchedulingIgnoredDuringExecution() @@ -489,6 +497,12 @@ public void testTemplate() { .withAnnotations(saAnnotations) .endMetadata() .endServiceAccount() + .withNewPodDisruptionBudget() + .withNewMetadata() + .withLabels(pbdLabels) + .withAnnotations(pbdAnnotations) + .endMetadata() + .endPodDisruptionBudget() .endTemplate() .endCruiseControl() .endSpec() @@ -530,6 +544,12 @@ public void testTemplate() { ServiceAccount sa = cc.generateServiceAccount(); assertThat(sa.getMetadata().getLabels().entrySet().containsAll(saLabels.entrySet()), is(true)); assertThat(sa.getMetadata().getAnnotations().entrySet().containsAll(saAnnotations.entrySet()), is(true)); + + // Check Pod Disruption Budget + PodDisruptionBudget pbd = cc.generatePodDisruptionBudget(); + assertThat(pbd.getMetadata().getLabels().entrySet().containsAll(pbdLabels.entrySet()), is(true)); + assertThat(pbd.getMetadata().getAnnotations().entrySet().containsAll(pbdAnnotations.entrySet()), is(true)); + assertThat(pbd.getSpec().getMinAvailable(), is(new IntOrString(0))); } @ParallelTest diff --git a/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/EntityOperatorTest.java b/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/EntityOperatorTest.java index 24c20059816..cbd959f31b4 100644 --- a/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/EntityOperatorTest.java +++ b/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/EntityOperatorTest.java @@ -27,6 +27,7 @@ import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyIngressRule; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyPeer; +import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget; import io.fabric8.kubernetes.api.model.rbac.PolicyRule; import io.fabric8.kubernetes.api.model.rbac.PolicyRuleBuilder; import io.fabric8.kubernetes.api.model.rbac.Role; @@ -102,6 +103,8 @@ public class EntityOperatorTest { .withNewPod() .withTmpDirSizeLimit("100Mi") .endPod() + .withNewPodDisruptionBudget() + .endPodDisruptionBudget() .endTemplate() .endEntityOperator() .endSpec() @@ -220,6 +223,10 @@ public void testTemplate() { Map rLabels = Map.of("l7", "v7", "l8", "v8"); Map rAnnotations = Map.of("a7", "v7", "a8", "v8"); + + Map pdbLabels = Map.of("l9", "v9", "l10", "v10"); + Map pdbAnnotations = Map.of("a9", "a9", "a10", "10"); + Toleration toleration = new TolerationBuilder() .withEffect("NoSchedule") .withValue("") @@ -294,6 +301,12 @@ public void testTemplate() { .withAnnotations(saAnnotations) .endMetadata() .endServiceAccount() + .withNewPodDisruptionBudget() + .withNewMetadata() + .withLabels(pdbLabels) + .withAnnotations(pdbAnnotations) + .endMetadata() + .endPodDisruptionBudget() .endTemplate() .endEntityOperator() .endSpec() @@ -327,6 +340,12 @@ public void testTemplate() { ServiceAccount sa = entityOperator.generateServiceAccount(); assertThat(sa.getMetadata().getLabels().entrySet().containsAll(saLabels.entrySet()), is(true)); assertThat(sa.getMetadata().getAnnotations().entrySet().containsAll(saAnnotations.entrySet()), is(true)); + + // Check Pod Disruption Budget + PodDisruptionBudget pdb = entityOperator.generatePodDisruptionBudget(); + assertThat(pdb.getMetadata().getLabels().entrySet().containsAll(pdbLabels.entrySet()), is(true)); + assertThat(pdb.getMetadata().getAnnotations().entrySet().containsAll(pdbAnnotations.entrySet()), is(true)); + assertThat(pdb.getSpec().getMinAvailable(), is(new IntOrString(0))); } @ParallelTest diff --git a/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/KafkaExporterTest.java b/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/KafkaExporterTest.java index 8534b8ded76..0003d7134b3 100644 --- a/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/KafkaExporterTest.java +++ b/cluster-operator/src/test/java/io/strimzi/operator/cluster/model/KafkaExporterTest.java @@ -26,6 +26,7 @@ import io.fabric8.kubernetes.api.model.VolumeMountBuilder; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; +import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget; import io.strimzi.api.kafka.model.common.template.AdditionalVolume; import io.strimzi.api.kafka.model.common.template.AdditionalVolumeBuilder; import io.strimzi.api.kafka.model.common.template.ContainerEnvVar; @@ -93,6 +94,8 @@ public class KafkaExporterTest { .withNewPod() .withTmpDirSizeLimit("100Mi") .endPod() + .withNewPodDisruptionBudget() + .endPodDisruptionBudget() .endTemplate() .endKafkaExporter() .endSpec() @@ -309,6 +312,9 @@ public void testTemplate() { Map saLabels = Map.of("l5", "v5", "l6", "v6"); Map saAnots = Map.of("a5", "v5", "a6", "v6"); + Map pdbLabels = Map.of("l7", "v7", "l8", "v8"); + Map pdbAnots = Map.of("a7", "v7", "a8", "v8"); + Affinity affinity = new AffinityBuilder() .withNewNodeAffinity() .withNewRequiredDuringSchedulingIgnoredDuringExecution() @@ -391,6 +397,12 @@ public void testTemplate() { .withAnnotations(saAnots) .endMetadata() .endServiceAccount() + .withNewPodDisruptionBudget() + .withNewMetadata() + .withLabels(pdbLabels) + .withAnnotations(pdbAnots) + .endMetadata() + .endPodDisruptionBudget() .endTemplate() .endKafkaExporter() .endSpec() @@ -418,6 +430,12 @@ public void testTemplate() { ServiceAccount sa = ke.generateServiceAccount(); assertThat(sa.getMetadata().getLabels().entrySet().containsAll(saLabels.entrySet()), is(true)); assertThat(sa.getMetadata().getAnnotations().entrySet().containsAll(saAnots.entrySet()), is(true)); + + // Check Pod Disruption Budget + PodDisruptionBudget pdb = ke.generatePodDisruptionBudget(); + assertThat(pdb.getMetadata().getLabels().entrySet().containsAll(pdbLabels.entrySet()), is(true)); + assertThat(pdb.getMetadata().getAnnotations().entrySet().containsAll(pdbAnots.entrySet()), is(true)); + assertThat(pdb.getSpec().getMinAvailable(), is(new IntOrString(0))); } @ParallelTest diff --git a/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/CruiseControlReconcilerTest.java b/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/CruiseControlReconcilerTest.java index 889fc50aaae..bdb17903129 100644 --- a/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/CruiseControlReconcilerTest.java +++ b/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/CruiseControlReconcilerTest.java @@ -5,12 +5,14 @@ package io.strimzi.operator.cluster.operator.assembly; import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretKeySelectorBuilder; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServiceAccount; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; +import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget; import io.strimzi.api.kafka.model.kafka.JbodStorageBuilder; import io.strimzi.api.kafka.model.kafka.Kafka; import io.strimzi.api.kafka.model.kafka.KafkaBuilder; @@ -34,6 +36,7 @@ import io.strimzi.operator.cluster.operator.resource.kubernetes.ConfigMapOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.DeploymentOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.NetworkPolicyOperator; +import io.strimzi.operator.cluster.operator.resource.kubernetes.PodDisruptionBudgetOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.SecretOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.ServiceAccountOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.ServiceOperator; @@ -128,6 +131,7 @@ public void reconcileEnabledCruiseControl(boolean topicOperatorEnabled, boolean NetworkPolicyOperator mockNetPolicyOps = supplier.networkPolicyOperator; ConfigMapOperator mockCmOps = supplier.configMapOperations; PasswordGenerator mockPasswordGenerator = new PasswordGenerator(10, "a", "a"); + PodDisruptionBudgetOperator mockPodDisruptionBudget = supplier.podDisruptionBudgetOperator; ArgumentCaptor saCaptor = ArgumentCaptor.forClass(ServiceAccount.class); when(mockSaOps.reconcile(any(), eq(NAMESPACE), eq(CruiseControlResources.serviceAccountName(NAME)), saCaptor.capture())).thenReturn(Future.succeededFuture()); @@ -162,6 +166,9 @@ public void reconcileEnabledCruiseControl(boolean topicOperatorEnabled, boolean when(mockDepOps.waitForObserved(any(), eq(NAMESPACE), eq(CruiseControlResources.componentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); when(mockDepOps.readiness(any(), eq(NAMESPACE), eq(CruiseControlResources.componentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); + ArgumentCaptor pdbCaptor = ArgumentCaptor.forClass(PodDisruptionBudget.class); + when(mockPodDisruptionBudget.reconcile(any(), eq(NAMESPACE), eq(CruiseControlResources.componentName(NAME)), pdbCaptor.capture())).thenReturn(Future.succeededFuture()); + Kafka kafka = new KafkaBuilder(KAFKA) .editSpec() .withNewCruiseControl() @@ -255,6 +262,10 @@ public void reconcileEnabledCruiseControl(boolean topicOperatorEnabled, boolean } else { assertThat(deployCaptor.getAllValues().get(0).getSpec().getTemplate().getMetadata().getAnnotations().get(Annotations.ANNO_STRIMZI_AUTH_HASH), is("5a188d9a")); } + assertThat(pdbCaptor.getAllValues().size(), is(1)); + assertThat(pdbCaptor.getValue(), is(notNullValue())); + assertThat(pdbCaptor.getValue().getMetadata().getName(), is(CruiseControlResources.componentName(NAME))); + assertThat(pdbCaptor.getValue().getSpec().getMinAvailable(), is(new IntOrString(0))); async.flag(); }))); @@ -269,6 +280,7 @@ public void reconcileDisabledCruiseControl(VertxTestContext context) { ServiceOperator mockServiceOps = supplier.serviceOperations; NetworkPolicyOperator mockNetPolicyOps = supplier.networkPolicyOperator; ConfigMapOperator mockCmOps = supplier.configMapOperations; + PodDisruptionBudgetOperator mockPodDisruptionBudget = supplier.podDisruptionBudgetOperator; ArgumentCaptor saCaptor = ArgumentCaptor.forClass(ServiceAccount.class); when(mockSaOps.reconcile(any(), eq(NAMESPACE), eq(CruiseControlResources.serviceAccountName(NAME)), saCaptor.capture())).thenReturn(Future.succeededFuture()); @@ -291,6 +303,9 @@ public void reconcileDisabledCruiseControl(VertxTestContext context) { when(mockDepOps.waitForObserved(any(), eq(NAMESPACE), eq(CruiseControlResources.componentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); when(mockDepOps.readiness(any(), eq(NAMESPACE), eq(CruiseControlResources.componentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); + ArgumentCaptor pdbCaptor = ArgumentCaptor.forClass(PodDisruptionBudget.class); + when(mockPodDisruptionBudget.reconcile(any(), eq(NAMESPACE), eq(CruiseControlResources.componentName(NAME)), pdbCaptor.capture())).thenReturn(Future.succeededFuture()); + ClusterCa clusterCa = new ClusterCa( Reconciliation.DUMMY_RECONCILIATION, new MockCertManager(), @@ -335,6 +350,9 @@ public void reconcileDisabledCruiseControl(VertxTestContext context) { assertThat(depCaptor.getAllValues().size(), is(1)); assertThat(depCaptor.getValue(), is(nullValue())); + assertThat(pdbCaptor.getAllValues().size(), is(1)); + assertThat(pdbCaptor.getValue(), is(nullValue())); + async.flag(); }))); } diff --git a/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/EntityOperatorReconcilerTest.java b/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/EntityOperatorReconcilerTest.java index cf4cd189189..d03910e3583 100644 --- a/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/EntityOperatorReconcilerTest.java +++ b/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/EntityOperatorReconcilerTest.java @@ -5,10 +5,12 @@ package io.strimzi.operator.cluster.operator.assembly; import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.ServiceAccount; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; +import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget; import io.fabric8.kubernetes.api.model.rbac.Role; import io.fabric8.kubernetes.api.model.rbac.RoleBinding; import io.strimzi.api.kafka.model.kafka.Kafka; @@ -24,6 +26,7 @@ import io.strimzi.operator.cluster.operator.resource.kubernetes.ConfigMapOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.DeploymentOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.NetworkPolicyOperator; +import io.strimzi.operator.cluster.operator.resource.kubernetes.PodDisruptionBudgetOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.RoleBindingOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.RoleOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.SecretOperator; @@ -94,6 +97,8 @@ public void reconcileWithToAndUo(VertxTestContext context) { RoleBindingOperator mockRoleBindingOps = supplier.roleBindingOperations; NetworkPolicyOperator mockNetPolicyOps = supplier.networkPolicyOperator; ConfigMapOperator mockCmOps = supplier.configMapOperations; + PodDisruptionBudgetOperator mockPodDisruptionBudgetOps = supplier.podDisruptionBudgetOperator; + ArgumentCaptor saCaptor = ArgumentCaptor.forClass(ServiceAccount.class); when(mockSaOps.reconcile(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), saCaptor.capture())).thenReturn(Future.succeededFuture()); @@ -126,6 +131,9 @@ public void reconcileWithToAndUo(VertxTestContext context) { when(mockDepOps.waitForObserved(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); when(mockDepOps.readiness(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); + ArgumentCaptor pdbCaptor = ArgumentCaptor.forClass(PodDisruptionBudget.class); + when(mockPodDisruptionBudgetOps.reconcile(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), pdbCaptor.capture())).thenReturn(Future.succeededFuture()); + Kafka kafka = new KafkaBuilder(KAFKA) .editSpec() .withNewEntityOperator() @@ -178,6 +186,10 @@ public void reconcileWithToAndUo(VertxTestContext context) { assertThat(depCaptor.getValue().getSpec().getTemplate().getMetadata().getAnnotations().get(Ca.ANNO_STRIMZI_IO_CLUSTER_CA_KEY_GENERATION), is("0")); assertThat(depCaptor.getValue().getSpec().getTemplate().getMetadata().getAnnotations().get(Annotations.ANNO_STRIMZI_SERVER_CERT_HASH), is("4d715cdd4d715cdd")); + assertThat(pdbCaptor.getAllValues().size(), is(1)); + assertThat(pdbCaptor.getValue(), is(notNullValue())); + assertThat(pdbCaptor.getValue().getSpec().getMinAvailable(), is(new IntOrString(0))); + async.flag(); }))); } @@ -195,6 +207,7 @@ public void reconcileWithToAndUoAndWatchNamespaces(VertxTestContext context) { RoleBindingOperator mockRoleBindingOps = supplier.roleBindingOperations; NetworkPolicyOperator mockNetPolicyOps = supplier.networkPolicyOperator; ConfigMapOperator mockCmOps = supplier.configMapOperations; + PodDisruptionBudgetOperator mockPodDisruptionBudgetOps = supplier.podDisruptionBudgetOperator; ArgumentCaptor saCaptor = ArgumentCaptor.forClass(ServiceAccount.class); when(mockSaOps.reconcile(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), saCaptor.capture())).thenReturn(Future.succeededFuture()); @@ -233,6 +246,9 @@ public void reconcileWithToAndUoAndWatchNamespaces(VertxTestContext context) { when(mockDepOps.waitForObserved(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); when(mockDepOps.readiness(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); + ArgumentCaptor pdbCaptor = ArgumentCaptor.forClass(PodDisruptionBudget.class); + when(mockPodDisruptionBudgetOps.reconcile(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), pdbCaptor.capture())).thenReturn(Future.succeededFuture()); + Kafka kafka = new KafkaBuilder(KAFKA) .editSpec() .withNewEntityOperator() @@ -294,6 +310,10 @@ public void reconcileWithToAndUoAndWatchNamespaces(VertxTestContext context) { assertThat(depCaptor.getAllValues().size(), is(1)); assertThat(depCaptor.getValue(), is(notNullValue())); + assertThat(pdbCaptor.getAllValues().size(), is(1)); + assertThat(pdbCaptor.getValue(), is(notNullValue())); + assertThat(pdbCaptor.getValue().getSpec().getMinAvailable(), is(new IntOrString(0))); + async.flag(); }))); } @@ -309,6 +329,7 @@ public void reconcileWithToOnly(boolean cruiseControlEnabled, VertxTestContext c RoleBindingOperator mockRoleBindingOps = supplier.roleBindingOperations; NetworkPolicyOperator mockNetPolicyOps = supplier.networkPolicyOperator; ConfigMapOperator mockCmOps = supplier.configMapOperations; + PodDisruptionBudgetOperator mockPodDisruptionBudgetOps = supplier.podDisruptionBudgetOperator; ArgumentCaptor saCaptor = ArgumentCaptor.forClass(ServiceAccount.class); when(mockSaOps.reconcile(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), saCaptor.capture())).thenReturn(Future.succeededFuture()); @@ -343,6 +364,9 @@ public void reconcileWithToOnly(boolean cruiseControlEnabled, VertxTestContext c when(mockDepOps.waitForObserved(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); when(mockDepOps.readiness(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); + ArgumentCaptor pdbCaptor = ArgumentCaptor.forClass(PodDisruptionBudget.class); + when(mockPodDisruptionBudgetOps.reconcile(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), pdbCaptor.capture())).thenReturn(Future.succeededFuture()); + Kafka kafka = new KafkaBuilder(KAFKA) .editSpec() .withNewEntityOperator() @@ -403,6 +427,10 @@ public void reconcileWithToOnly(boolean cruiseControlEnabled, VertxTestContext c assertThat(depCaptor.getValue().getSpec().getTemplate().getMetadata().getAnnotations().get(Ca.ANNO_STRIMZI_IO_CLUSTER_CA_KEY_GENERATION), is("0")); assertThat(depCaptor.getValue().getSpec().getTemplate().getMetadata().getAnnotations().get(Annotations.ANNO_STRIMZI_SERVER_CERT_HASH), is("4d715cdd")); + assertThat(pdbCaptor.getAllValues().size(), is(1)); + assertThat(pdbCaptor.getValue(), is(notNullValue())); + assertThat(pdbCaptor.getValue().getSpec().getMinAvailable(), is(new IntOrString(0))); + async.flag(); }))); } @@ -417,6 +445,7 @@ public void reconcileWithUoOnly(VertxTestContext context) { RoleBindingOperator mockRoleBindingOps = supplier.roleBindingOperations; NetworkPolicyOperator mockNetPolicyOps = supplier.networkPolicyOperator; ConfigMapOperator mockCmOps = supplier.configMapOperations; + PodDisruptionBudgetOperator mockPodDisruptionBudgetOps = supplier.podDisruptionBudgetOperator; ArgumentCaptor saCaptor = ArgumentCaptor.forClass(ServiceAccount.class); when(mockSaOps.reconcile(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), saCaptor.capture())).thenReturn(Future.succeededFuture()); @@ -448,6 +477,9 @@ public void reconcileWithUoOnly(VertxTestContext context) { when(mockDepOps.waitForObserved(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); when(mockDepOps.readiness(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); + ArgumentCaptor pdbCaptor = ArgumentCaptor.forClass(PodDisruptionBudget.class); + when(mockPodDisruptionBudgetOps.reconcile(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), pdbCaptor.capture())).thenReturn(Future.succeededFuture()); + Kafka kafka = new KafkaBuilder(KAFKA) .editSpec() .withNewEntityOperator() @@ -498,6 +530,10 @@ public void reconcileWithUoOnly(VertxTestContext context) { assertThat(depCaptor.getValue().getSpec().getTemplate().getMetadata().getAnnotations().get(Ca.ANNO_STRIMZI_IO_CLUSTER_CA_KEY_GENERATION), is("0")); assertThat(depCaptor.getValue().getSpec().getTemplate().getMetadata().getAnnotations().get(Annotations.ANNO_STRIMZI_SERVER_CERT_HASH), is("4d715cdd")); + assertThat(pdbCaptor.getAllValues().size(), is(1)); + assertThat(pdbCaptor.getValue(), is(notNullValue())); + assertThat(pdbCaptor.getValue().getSpec().getMinAvailable(), is(new IntOrString(0))); + async.flag(); }))); } @@ -512,6 +548,7 @@ public void reconcileWithoutUoAndTo(VertxTestContext context) { RoleBindingOperator mockRoleBindingOps = supplier.roleBindingOperations; NetworkPolicyOperator mockNetPolicyOps = supplier.networkPolicyOperator; ConfigMapOperator mockCmOps = supplier.configMapOperations; + PodDisruptionBudgetOperator mockPodDisruptionBudgetOps = supplier.podDisruptionBudgetOperator; ArgumentCaptor saCaptor = ArgumentCaptor.forClass(ServiceAccount.class); when(mockSaOps.reconcile(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), saCaptor.capture())).thenReturn(Future.succeededFuture()); @@ -542,6 +579,9 @@ public void reconcileWithoutUoAndTo(VertxTestContext context) { when(mockDepOps.waitForObserved(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); when(mockDepOps.readiness(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); + ArgumentCaptor pdbCaptor = ArgumentCaptor.forClass(PodDisruptionBudget.class); + when(mockPodDisruptionBudgetOps.reconcile(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), pdbCaptor.capture())).thenReturn(Future.succeededFuture()); + Kafka kafka = new KafkaBuilder(KAFKA) .editSpec() .withNewEntityOperator() @@ -587,6 +627,9 @@ public void reconcileWithoutUoAndTo(VertxTestContext context) { assertThat(depCaptor.getAllValues().size(), is(1)); assertThat(depCaptor.getValue(), is(nullValue())); + assertThat(pdbCaptor.getAllValues().size(), is(1)); + assertThat(pdbCaptor.getValue(), is(nullValue())); + async.flag(); }))); } @@ -601,6 +644,7 @@ public void reconcileWithoutEo(VertxTestContext context) { RoleBindingOperator mockRoleBindingOps = supplier.roleBindingOperations; NetworkPolicyOperator mockNetPolicyOps = supplier.networkPolicyOperator; ConfigMapOperator mockCmOps = supplier.configMapOperations; + PodDisruptionBudgetOperator mockPodDisruptionBudgetOps = supplier.podDisruptionBudgetOperator; ArgumentCaptor saCaptor = ArgumentCaptor.forClass(ServiceAccount.class); when(mockSaOps.reconcile(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), saCaptor.capture())).thenReturn(Future.succeededFuture()); @@ -631,6 +675,9 @@ public void reconcileWithoutEo(VertxTestContext context) { when(mockDepOps.waitForObserved(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); when(mockDepOps.readiness(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); + ArgumentCaptor pdbCaptor = ArgumentCaptor.forClass(PodDisruptionBudget.class); + when(mockPodDisruptionBudgetOps.reconcile(any(), eq(NAMESPACE), eq(KafkaResources.entityOperatorDeploymentName(NAME)), pdbCaptor.capture())).thenReturn(Future.succeededFuture()); + EntityOperatorReconciler rcnclr = new EntityOperatorReconciler( Reconciliation.DUMMY_RECONCILIATION, ResourceUtils.dummyClusterOperatorConfig(), @@ -669,6 +716,9 @@ public void reconcileWithoutEo(VertxTestContext context) { assertThat(depCaptor.getAllValues().size(), is(1)); assertThat(depCaptor.getValue(), is(nullValue())); + assertThat(pdbCaptor.getAllValues().size(), is(1)); + assertThat(pdbCaptor.getValue(), is(nullValue())); + async.flag(); }))); } diff --git a/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/KafkaAssemblyOperatorTest.java b/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/KafkaAssemblyOperatorTest.java index 8b64da71389..1a41c64c6ed 100644 --- a/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/KafkaAssemblyOperatorTest.java +++ b/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/KafkaAssemblyOperatorTest.java @@ -663,8 +663,28 @@ private void createCluster(VertxTestContext context, Kafka kafka, List expectedPdbNames = new HashSet<>(); + expectedPdbNames.add(KafkaResources.kafkaComponentName(CLUSTER_NAME)); + String entityOperatorName = KafkaResources.entityOperatorDeploymentName(CLUSTER_NAME); + String exporterName = KafkaExporterResources.componentName(CLUSTER_NAME); + String cruiseControlName = CruiseControlResources.componentName(CLUSTER_NAME); + if (kafka.getSpec().getEntityOperator() != null) { + expectedPdbNames.add(entityOperatorName); + } + if (kafka.getSpec().getKafkaExporter() != null) { + expectedPdbNames.add(exporterName); + } + if (kafka.getSpec().getCruiseControl() != null) { + expectedPdbNames.add(cruiseControlName); + } + assertThat( + pdbCaptor.getAllValues().stream() + .map(pdb -> pdb != null && pdb.getMetadata() != null ? pdb.getMetadata().getName() : null) + .filter(Objects::nonNull) + .collect(Collectors.toSet()), + is(expectedPdbNames) + ); // Check PVCs assertThat(pvcCaptor.getAllValues(), hasSize(expectedPvcs.size())); diff --git a/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/KafkaExporterReconcilerTest.java b/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/KafkaExporterReconcilerTest.java index dcf057207aa..40772715465 100644 --- a/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/KafkaExporterReconcilerTest.java +++ b/cluster-operator/src/test/java/io/strimzi/operator/cluster/operator/assembly/KafkaExporterReconcilerTest.java @@ -4,10 +4,12 @@ */ package io.strimzi.operator.cluster.operator.assembly; +import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.ServiceAccount; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; +import io.fabric8.kubernetes.api.model.policy.v1.PodDisruptionBudget; import io.strimzi.api.kafka.model.kafka.Kafka; import io.strimzi.api.kafka.model.kafka.KafkaBuilder; import io.strimzi.api.kafka.model.kafka.exporter.KafkaExporterResources; @@ -22,6 +24,7 @@ import io.strimzi.operator.cluster.operator.resource.ResourceOperatorSupplier; import io.strimzi.operator.cluster.operator.resource.kubernetes.DeploymentOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.NetworkPolicyOperator; +import io.strimzi.operator.cluster.operator.resource.kubernetes.PodDisruptionBudgetOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.SecretOperator; import io.strimzi.operator.cluster.operator.resource.kubernetes.ServiceAccountOperator; import io.strimzi.operator.common.Annotations; @@ -115,6 +118,10 @@ public void reconcileWithEnabledExporter(VertxTestContext context) { when(mockDepOps.waitForObserved(any(), eq(NAMESPACE), eq(KafkaExporterResources.componentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); when(mockDepOps.readiness(any(), eq(NAMESPACE), eq(KafkaExporterResources.componentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); + PodDisruptionBudgetOperator mockPodDisruptionBudgetOps = supplier.podDisruptionBudgetOperator; + ArgumentCaptor pdbCaptor = ArgumentCaptor.forClass(PodDisruptionBudget.class); + when(mockPodDisruptionBudgetOps.reconcile(any(), eq(NAMESPACE), eq(KafkaExporterResources.componentName(NAME)), pdbCaptor.capture())).thenReturn(Future.succeededFuture()); + Kafka kafka = new KafkaBuilder(KAFKA) .editSpec() .withNewKafkaExporter() @@ -153,6 +160,10 @@ public void reconcileWithEnabledExporter(VertxTestContext context) { assertThat(depCaptor.getValue().getSpec().getTemplate().getMetadata().getAnnotations().get(Ca.ANNO_STRIMZI_IO_CLUSTER_CA_KEY_GENERATION), is("0")); assertThat(depCaptor.getValue().getSpec().getTemplate().getMetadata().getAnnotations().get(Annotations.ANNO_STRIMZI_SERVER_CERT_HASH), is("4d715cdd")); + assertThat(pdbCaptor.getAllValues().size(), is(1)); + assertThat(pdbCaptor.getValue(), is(notNullValue())); + assertThat(pdbCaptor.getValue().getSpec().getMinAvailable(), is(new IntOrString(0))); + async.flag(); }))); } @@ -185,6 +196,10 @@ public void reconcileWithEnabledExporterWithoutNetworkPolicies(VertxTestContext when(mockDepOps.waitForObserved(any(), eq(NAMESPACE), eq(KafkaExporterResources.componentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); when(mockDepOps.readiness(any(), eq(NAMESPACE), eq(KafkaExporterResources.componentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); + PodDisruptionBudgetOperator mockPodDisruptionBudgetOps = supplier.podDisruptionBudgetOperator; + ArgumentCaptor pdbCaptor = ArgumentCaptor.forClass(PodDisruptionBudget.class); + when(mockPodDisruptionBudgetOps.reconcile(any(), eq(NAMESPACE), eq(KafkaExporterResources.componentName(NAME)), pdbCaptor.capture())).thenReturn(Future.succeededFuture()); + Kafka kafka = new KafkaBuilder(KAFKA) .editSpec() .withNewKafkaExporter() @@ -216,6 +231,10 @@ public void reconcileWithEnabledExporterWithoutNetworkPolicies(VertxTestContext assertThat(depCaptor.getAllValues().size(), is(1)); assertThat(depCaptor.getValue(), is(notNullValue())); + assertThat(pdbCaptor.getAllValues().size(), is(1)); + assertThat(pdbCaptor.getValue(), is(notNullValue())); + assertThat(pdbCaptor.getValue().getSpec().getMinAvailable(), is(new IntOrString(0))); + async.flag(); }))); } @@ -246,6 +265,10 @@ public void reconcileWithDisabledExporter(VertxTestContext context) { when(mockDepOps.waitForObserved(any(), eq(NAMESPACE), eq(KafkaExporterResources.componentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); when(mockDepOps.readiness(any(), eq(NAMESPACE), eq(KafkaExporterResources.componentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); + PodDisruptionBudgetOperator mockPodDisruptionBudgetOps = supplier.podDisruptionBudgetOperator; + ArgumentCaptor pdbCaptor = ArgumentCaptor.forClass(PodDisruptionBudget.class); + when(mockPodDisruptionBudgetOps.reconcile(any(), eq(NAMESPACE), eq(KafkaExporterResources.componentName(NAME)), pdbCaptor.capture())).thenReturn(Future.succeededFuture()); + KafkaExporterReconciler reconciler = new KafkaExporterReconciler( Reconciliation.DUMMY_RECONCILIATION, ResourceUtils.dummyClusterOperatorConfig(), @@ -270,6 +293,9 @@ public void reconcileWithDisabledExporter(VertxTestContext context) { assertThat(depCaptor.getAllValues().size(), is(1)); assertThat(depCaptor.getValue(), is(nullValue())); + assertThat(pdbCaptor.getAllValues().size(), is(1)); + assertThat(pdbCaptor.getValue(), is(nullValue())); + async.flag(); }))); } @@ -300,6 +326,10 @@ public void reconcileWithDisabledExporterWithoutNetworkPolicies(VertxTestContext when(mockDepOps.waitForObserved(any(), eq(NAMESPACE), eq(KafkaExporterResources.componentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); when(mockDepOps.readiness(any(), eq(NAMESPACE), eq(KafkaExporterResources.componentName(NAME)), anyLong(), anyLong())).thenReturn(Future.succeededFuture()); + PodDisruptionBudgetOperator mockPodDisruptionBudgetOps = supplier.podDisruptionBudgetOperator; + ArgumentCaptor pdbCaptor = ArgumentCaptor.forClass(PodDisruptionBudget.class); + when(mockPodDisruptionBudgetOps.reconcile(any(), eq(NAMESPACE), eq(KafkaExporterResources.componentName(NAME)), pdbCaptor.capture())).thenReturn(Future.succeededFuture()); + KafkaExporterReconciler reconciler = new KafkaExporterReconciler( Reconciliation.DUMMY_RECONCILIATION, new ClusterOperatorConfig.ClusterOperatorConfigBuilder(ResourceUtils.dummyClusterOperatorConfig(), KafkaVersionTestUtils.getKafkaVersionLookup()) @@ -324,6 +354,9 @@ public void reconcileWithDisabledExporterWithoutNetworkPolicies(VertxTestContext assertThat(depCaptor.getAllValues().size(), is(1)); assertThat(depCaptor.getValue(), is(nullValue())); + assertThat(pdbCaptor.getAllValues().size(), is(1)); + assertThat(pdbCaptor.getValue(), is(nullValue())); + async.flag(); }))); } diff --git a/documentation/api/io.strimzi.api.kafka.model.common.template.PodDisruptionBudgetTemplate.adoc b/documentation/api/io.strimzi.api.kafka.model.common.template.PodDisruptionBudgetTemplate.adoc index 011b4199970..84f5c1d9e65 100644 --- a/documentation/api/io.strimzi.api.kafka.model.common.template.PodDisruptionBudgetTemplate.adoc +++ b/documentation/api/io.strimzi.api.kafka.model.common.template.PodDisruptionBudgetTemplate.adoc @@ -1,16 +1,19 @@ A `PodDisruptionBudget` is a Kubernetes resource that helps maintain high availability during planned maintenance or upgrades. It defines the minimum number of pods that must remain available at all times. -Strimzi creates one `PodDisruptionBudget` per component: +Strimzi creates one `PodDisruptionBudget` per component: * Kafka cluster * Kafka Connect * MirrorMaker 2 * Kafka Bridge +* Entity Operator +* Kafka Exporter +* Cruise Control Each budget applies across all pods deployed for that component. -A single `PodDisruptionBudget` applies to all associated node pool pods managed by the `Kafka` resource. +A single `PodDisruptionBudget` applies to all associated node pool pods managed by the `Kafka` resource. This is true regardless of how many `StrimziPodSet` resources are created for that component. Disruption limits are applied at the component level, not per node pool. @@ -22,9 +25,11 @@ Instead, the value is automatically converted to `minAvailable`, which serves th For example: -* If there are three broker pods and the `maxUnavailable` property is set to `1` in the `Kafka` resource, the `minAvailable` setting is `2`, allowing one pod to be unavailable. +* If there are three broker pods and the `maxUnavailable` property is set to `1` in the `Kafka` resource, the `minAvailable` setting is `2`, allowing one pod to be unavailable. * If there are three broker pods and the `maxUnavailable` property is set to `0` (zero), the `minAvailable` setting is `3`, requiring all three broker pods to be available and allowing zero pods to be unavailable. +The above examples also applies for the `EntityOperator`, `Cruise Control`, `KafkaExporter` deployments. + .Example `PodDisruptionBudget` template configuration [source,yaml,subs=attributes+] ---- @@ -44,8 +49,8 @@ template: .Custom disruption budget behavior -To define custom disruption budget behavior, disable automatic `PodDisruptionBudget` generation. +To define custom disruption budget behavior, disable automatic `PodDisruptionBudget` generation. Set the `STRIMZI_POD_DISRUPTION_BUDGET_GENERATION` environment variable to `false` in the Cluster Operator. -You can then create your own `PodDisruptionBudget` resources. -For example, apply separate budgets to specific node pools using label selectors with `StrimziPodSet` resources. \ No newline at end of file +You can then create your own `PodDisruptionBudget` resources. +For example, apply separate budgets to specific node pools using label selectors with `StrimziPodSet` resources. diff --git a/documentation/modules/appendix_crds.adoc b/documentation/modules/appendix_crds.adoc index ac94c84aea1..7484009a4d1 100644 --- a/documentation/modules/appendix_crds.adoc +++ b/documentation/modules/appendix_crds.adoc @@ -1362,7 +1362,7 @@ Used in: xref:type-CruiseControlTemplate-{context}[`CruiseControlTemplate`], xre [id='type-PodDisruptionBudgetTemplate-{context}'] = `PodDisruptionBudgetTemplate` schema reference -Used in: xref:type-CruiseControlTemplate-{context}[`CruiseControlTemplate`], xref:type-KafkaBridgeTemplate-{context}[`KafkaBridgeTemplate`], xref:type-KafkaClusterTemplate-{context}[`KafkaClusterTemplate`], xref:type-KafkaConnectTemplate-{context}[`KafkaConnectTemplate`], xref:type-ZookeeperClusterTemplate-{context}[`ZookeeperClusterTemplate`] +Used in: xref:type-CruiseControlTemplate-{context}[`CruiseControlTemplate`], xref:type-EntityOperatorTemplate-{context}[`EntityOperatorTemplate`], xref:type-KafkaBridgeTemplate-{context}[`KafkaBridgeTemplate`], xref:type-KafkaClusterTemplate-{context}[`KafkaClusterTemplate`], xref:type-KafkaConnectTemplate-{context}[`KafkaConnectTemplate`], xref:type-KafkaExporterTemplate-{context}[`KafkaExporterTemplate`], xref:type-ZookeeperClusterTemplate-{context}[`ZookeeperClusterTemplate`] xref:type-PodDisruptionBudgetTemplate-schema-{context}[Full list of `PodDisruptionBudgetTemplate` schema properties] @@ -1834,6 +1834,9 @@ Used in: xref:type-EntityOperatorSpec-{context}[`EntityOperatorSpec`] |serviceAccount |xref:type-ResourceTemplate-{context}[`ResourceTemplate`] |Template for the Entity Operator service account. +|podDisruptionBudget +|xref:type-PodDisruptionBudgetTemplate-{context}[`PodDisruptionBudgetTemplate`] +|Template for the Entity Operator Pod Disruption Budget. |entityOperatorRole |xref:type-ResourceTemplate-{context}[`ResourceTemplate`] |Template for the Entity Operator Role. @@ -2276,6 +2279,9 @@ Used in: xref:type-KafkaExporterSpec-{context}[`KafkaExporterSpec`] |serviceAccount |xref:type-ResourceTemplate-{context}[`ResourceTemplate`] |Template for the Kafka Exporter service account. +|podDisruptionBudget +|xref:type-PodDisruptionBudgetTemplate-{context}[`PodDisruptionBudgetTemplate`] +|Template for the Pod Disruption Budget for Kafka Exporter pods. |==== [id='type-KafkaStatus-{context}'] diff --git a/packaging/helm-charts/helm3/strimzi-kafka-operator/crds/040-Crd-kafka.yaml b/packaging/helm-charts/helm3/strimzi-kafka-operator/crds/040-Crd-kafka.yaml index 98ef57d5834..68b77333385 100644 --- a/packaging/helm-charts/helm3/strimzi-kafka-operator/crds/040-Crd-kafka.yaml +++ b/packaging/helm-charts/helm3/strimzi-kafka-operator/crds/040-Crd-kafka.yaml @@ -5122,6 +5122,28 @@ spec: description: Annotations added to the Kubernetes resource. description: Metadata applied to the resource. description: Template for the Entity Operator service account. + podDisruptionBudget: + type: object + properties: + metadata: + type: object + properties: + labels: + additionalProperties: + type: string + type: object + description: Labels added to the Kubernetes resource. + annotations: + additionalProperties: + type: string + type: object + description: Annotations added to the Kubernetes resource. + description: Metadata to apply to the `PodDisruptionBudgetTemplate` resource. + maxUnavailable: + type: integer + minimum: 0 + description: "Maximum number of unavailable pods to allow automatic Pod eviction. A Pod eviction is allowed when the `maxUnavailable` number of pods or fewer are unavailable after the eviction. Setting this value to 0 prevents all voluntary evictions, so the pods must be evicted manually. Defaults to 1." + description: Template for the Entity Operator Pod Disruption Budget. entityOperatorRole: type: object properties: @@ -8525,6 +8547,28 @@ spec: description: Annotations added to the Kubernetes resource. description: Metadata applied to the resource. description: Template for the Kafka Exporter service account. + podDisruptionBudget: + type: object + properties: + metadata: + type: object + properties: + labels: + additionalProperties: + type: string + type: object + description: Labels added to the Kubernetes resource. + annotations: + additionalProperties: + type: string + type: object + description: Annotations added to the Kubernetes resource. + description: Metadata to apply to the `PodDisruptionBudgetTemplate` resource. + maxUnavailable: + type: integer + minimum: 0 + description: "Maximum number of unavailable pods to allow automatic Pod eviction. A Pod eviction is allowed when the `maxUnavailable` number of pods or fewer are unavailable after the eviction. Setting this value to 0 prevents all voluntary evictions, so the pods must be evicted manually. Defaults to 1." + description: Template for the Pod Disruption Budget for Kafka Exporter pods. description: Customization of deployment templates and pods. description: "Configuration of the Kafka Exporter. Kafka Exporter can provide additional metrics, for example lag of consumer group at topic/partition." maintenanceTimeWindows: diff --git a/packaging/install/cluster-operator/040-Crd-kafka.yaml b/packaging/install/cluster-operator/040-Crd-kafka.yaml index a6310a97bbf..52ab32d5fce 100644 --- a/packaging/install/cluster-operator/040-Crd-kafka.yaml +++ b/packaging/install/cluster-operator/040-Crd-kafka.yaml @@ -5121,6 +5121,28 @@ spec: description: Annotations added to the Kubernetes resource. description: Metadata applied to the resource. description: Template for the Entity Operator service account. + podDisruptionBudget: + type: object + properties: + metadata: + type: object + properties: + labels: + additionalProperties: + type: string + type: object + description: Labels added to the Kubernetes resource. + annotations: + additionalProperties: + type: string + type: object + description: Annotations added to the Kubernetes resource. + description: Metadata to apply to the `PodDisruptionBudgetTemplate` resource. + maxUnavailable: + type: integer + minimum: 0 + description: "Maximum number of unavailable pods to allow automatic Pod eviction. A Pod eviction is allowed when the `maxUnavailable` number of pods or fewer are unavailable after the eviction. Setting this value to 0 prevents all voluntary evictions, so the pods must be evicted manually. Defaults to 1." + description: Template for the Entity Operator Pod Disruption Budget. entityOperatorRole: type: object properties: @@ -8524,6 +8546,28 @@ spec: description: Annotations added to the Kubernetes resource. description: Metadata applied to the resource. description: Template for the Kafka Exporter service account. + podDisruptionBudget: + type: object + properties: + metadata: + type: object + properties: + labels: + additionalProperties: + type: string + type: object + description: Labels added to the Kubernetes resource. + annotations: + additionalProperties: + type: string + type: object + description: Annotations added to the Kubernetes resource. + description: Metadata to apply to the `PodDisruptionBudgetTemplate` resource. + maxUnavailable: + type: integer + minimum: 0 + description: "Maximum number of unavailable pods to allow automatic Pod eviction. A Pod eviction is allowed when the `maxUnavailable` number of pods or fewer are unavailable after the eviction. Setting this value to 0 prevents all voluntary evictions, so the pods must be evicted manually. Defaults to 1." + description: Template for the Pod Disruption Budget for Kafka Exporter pods. description: Customization of deployment templates and pods. description: "Configuration of the Kafka Exporter. Kafka Exporter can provide additional metrics, for example lag of consumer group at topic/partition." maintenanceTimeWindows: