This is an automated email from the ASF dual-hosted git repository.

houston pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-operator.git


The following commit(s) were added to refs/heads/main by this push:
     new 7932abb  Add ability to set pod topologySpreadConstraints (#350)
7932abb is described below

commit 7932abb219fc36bd5fb854f8976b436b6960df74
Author: Houston Putman <[email protected]>
AuthorDate: Wed Oct 27 11:45:53 2021 -0400

    Add ability to set pod topologySpreadConstraints (#350)
---
 api/v1beta1/common_types.go                        |  13 +++
 api/v1beta1/zz_generated.deepcopy.go               |   7 ++
 config/crd/bases/solr.apache.org_solrclouds.yaml   |  55 +++++++++++
 .../solr.apache.org_solrprometheusexporters.yaml   |  55 +++++++++++
 controllers/controller_utils_test.go               |  15 +++
 controllers/solrcloud_controller_test.go           |   6 ++
 .../solrprometheusexporter_controller_test.go      |   7 ++
 controllers/util/common.go                         |   6 ++
 controllers/util/prometheus_exporter_util.go       |  13 ++-
 controllers/util/solr_util.go                      |  13 ++-
 helm/solr-operator/Chart.yaml                      |   9 ++
 helm/solr-operator/crds/crds.yaml                  | 110 +++++++++++++++++++++
 helm/solr/README.md                                |   1 +
 helm/solr/templates/_custom_option_helpers.tpl     |   4 +
 helm/solr/values.yaml                              |  14 ++-
 15 files changed, 323 insertions(+), 5 deletions(-)

diff --git a/api/v1beta1/common_types.go b/api/v1beta1/common_types.go
index 13984f4..4d69afd 100644
--- a/api/v1beta1/common_types.go
+++ b/api/v1beta1/common_types.go
@@ -137,6 +137,19 @@ type PodOptions struct {
        // Optional Service Account to run the pod under.
        // +optional
        ServiceAccountName string `json:"serviceAccountName,omitempty"`
+
+       // Optional PodSpreadTopologyConstraints to use when scheduling pods.
+       // More information here: 
https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
+       //
+       // Note: There is no need to provide a "labelSelector", as the operator 
will inject the labels for you if not provided.
+       //
+       // +patchMergeKey=topologyKey
+       // +patchStrategy=merge
+       // +listType=map
+       // +listMapKey=topologyKey
+       // +listMapKey=whenUnsatisfiable
+       // +optional
+       TopologySpreadConstraints []corev1.TopologySpreadConstraint 
`json:"topologySpreadConstraints,omitempty"`
 }
 
 // ServiceOptions defines custom options for services
diff --git a/api/v1beta1/zz_generated.deepcopy.go 
b/api/v1beta1/zz_generated.deepcopy.go
index 9ce6426..01ef438 100644
--- a/api/v1beta1/zz_generated.deepcopy.go
+++ b/api/v1beta1/zz_generated.deepcopy.go
@@ -525,6 +525,13 @@ func (in *PodOptions) DeepCopyInto(out *PodOptions) {
                *out = new(int64)
                **out = **in
        }
+       if in.TopologySpreadConstraints != nil {
+               in, out := &in.TopologySpreadConstraints, 
&out.TopologySpreadConstraints
+               *out = make([]v1.TopologySpreadConstraint, len(*in))
+               for i := range *in {
+                       (*in)[i].DeepCopyInto(&(*out)[i])
+               }
+       }
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new PodOptions.
diff --git a/config/crd/bases/solr.apache.org_solrclouds.yaml 
b/config/crd/bases/solr.apache.org_solrclouds.yaml
index 73d911a..dc363fd 100644
--- a/config/crd/bases/solr.apache.org_solrclouds.yaml
+++ b/config/crd/bases/solr.apache.org_solrclouds.yaml
@@ -3635,6 +3635,61 @@ spec:
                               type: string
                           type: object
                         type: array
+                      topologySpreadConstraints:
+                        description: "Optional PodSpreadTopologyConstraints to 
use when scheduling pods. More information here: 
https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
 \n Note: There is no need to provide a \"labelSelector\", as the operator will 
inject the labels for you if not provided."
+                        items:
+                          description: TopologySpreadConstraint specifies how 
to spread matching pods among the given topology.
+                          properties:
+                            labelSelector:
+                              description: LabelSelector is used to find 
matching pods. Pods that match this label selector are counted to determine the 
number of pods in their corresponding topology domain.
+                              properties:
+                                matchExpressions:
+                                  description: matchExpressions is a list of 
label selector requirements. The requirements are ANDed.
+                                  items:
+                                    description: A label selector requirement 
is a selector that contains values, a key, and an operator that relates the key 
and values.
+                                    properties:
+                                      key:
+                                        description: key is the label key that 
the selector applies to.
+                                        type: string
+                                      operator:
+                                        description: operator represents a 
key's relationship to a set of values. Valid operators are In, NotIn, Exists 
and DoesNotExist.
+                                        type: string
+                                      values:
+                                        description: values is an array of 
string values. If the operator is In or NotIn, the values array must be 
non-empty. If the operator is Exists or DoesNotExist, the values array must be 
empty. This array is replaced during a strategic merge patch.
+                                        items:
+                                          type: string
+                                        type: array
+                                    required:
+                                    - key
+                                    - operator
+                                    type: object
+                                  type: array
+                                matchLabels:
+                                  additionalProperties:
+                                    type: string
+                                  description: matchLabels is a map of 
{key,value} pairs. A single {key,value} in the matchLabels map is equivalent to 
an element of matchExpressions, whose key field is "key", the operator is "In", 
and the values array contains only "value". The requirements are ANDed.
+                                  type: object
+                              type: object
+                            maxSkew:
+                              description: 'MaxSkew describes the degree to 
which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, 
it is the maximum permitted difference between the number of matching pods in 
the target topology and the global minimum. For example, in a 3-zone cluster, 
MaxSkew is set to 1, and pods with the same labelSelector spread as 1/1/0: | 
zone1 | zone2 | zone3 | |   P   |   P   |       | - if MaxSkew is 1, incoming 
pod can only be scheduled [...]
+                              format: int32
+                              type: integer
+                            topologyKey:
+                              description: TopologyKey is the key of node 
labels. Nodes that have a label with this key and identical values are 
considered to be in the same topology. We consider each <key, value> as a 
"bucket", and try to put balanced number of pods into each bucket. It's a 
required field.
+                              type: string
+                            whenUnsatisfiable:
+                              description: 'WhenUnsatisfiable indicates how to 
deal with a pod if it doesn''t satisfy the spread constraint. - DoNotSchedule 
(default) tells the scheduler not to schedule it. - ScheduleAnyway tells the 
scheduler to schedule the pod in any location,   but giving higher precedence 
to topologies that would help reduce the   skew. A constraint is considered 
"Unsatisfiable" for an incoming pod if and only if every possible node 
assigment for that pod would viol [...]
+                              type: string
+                          required:
+                          - maxSkew
+                          - topologyKey
+                          - whenUnsatisfiable
+                          type: object
+                        type: array
+                        x-kubernetes-list-map-keys:
+                        - topologyKey
+                        - whenUnsatisfiable
+                        x-kubernetes-list-type: map
                       volumes:
                         description: Additional non-data volumes to load into 
the default container.
                         items:
diff --git a/config/crd/bases/solr.apache.org_solrprometheusexporters.yaml 
b/config/crd/bases/solr.apache.org_solrprometheusexporters.yaml
index eb1a9e4..8e5b6d4 100644
--- a/config/crd/bases/solr.apache.org_solrprometheusexporters.yaml
+++ b/config/crd/bases/solr.apache.org_solrprometheusexporters.yaml
@@ -2538,6 +2538,61 @@ spec:
                               type: string
                           type: object
                         type: array
+                      topologySpreadConstraints:
+                        description: "Optional PodSpreadTopologyConstraints to 
use when scheduling pods. More information here: 
https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
 \n Note: There is no need to provide a \"labelSelector\", as the operator will 
inject the labels for you if not provided."
+                        items:
+                          description: TopologySpreadConstraint specifies how 
to spread matching pods among the given topology.
+                          properties:
+                            labelSelector:
+                              description: LabelSelector is used to find 
matching pods. Pods that match this label selector are counted to determine the 
number of pods in their corresponding topology domain.
+                              properties:
+                                matchExpressions:
+                                  description: matchExpressions is a list of 
label selector requirements. The requirements are ANDed.
+                                  items:
+                                    description: A label selector requirement 
is a selector that contains values, a key, and an operator that relates the key 
and values.
+                                    properties:
+                                      key:
+                                        description: key is the label key that 
the selector applies to.
+                                        type: string
+                                      operator:
+                                        description: operator represents a 
key's relationship to a set of values. Valid operators are In, NotIn, Exists 
and DoesNotExist.
+                                        type: string
+                                      values:
+                                        description: values is an array of 
string values. If the operator is In or NotIn, the values array must be 
non-empty. If the operator is Exists or DoesNotExist, the values array must be 
empty. This array is replaced during a strategic merge patch.
+                                        items:
+                                          type: string
+                                        type: array
+                                    required:
+                                    - key
+                                    - operator
+                                    type: object
+                                  type: array
+                                matchLabels:
+                                  additionalProperties:
+                                    type: string
+                                  description: matchLabels is a map of 
{key,value} pairs. A single {key,value} in the matchLabels map is equivalent to 
an element of matchExpressions, whose key field is "key", the operator is "In", 
and the values array contains only "value". The requirements are ANDed.
+                                  type: object
+                              type: object
+                            maxSkew:
+                              description: 'MaxSkew describes the degree to 
which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, 
it is the maximum permitted difference between the number of matching pods in 
the target topology and the global minimum. For example, in a 3-zone cluster, 
MaxSkew is set to 1, and pods with the same labelSelector spread as 1/1/0: | 
zone1 | zone2 | zone3 | |   P   |   P   |       | - if MaxSkew is 1, incoming 
pod can only be scheduled [...]
+                              format: int32
+                              type: integer
+                            topologyKey:
+                              description: TopologyKey is the key of node 
labels. Nodes that have a label with this key and identical values are 
considered to be in the same topology. We consider each <key, value> as a 
"bucket", and try to put balanced number of pods into each bucket. It's a 
required field.
+                              type: string
+                            whenUnsatisfiable:
+                              description: 'WhenUnsatisfiable indicates how to 
deal with a pod if it doesn''t satisfy the spread constraint. - DoNotSchedule 
(default) tells the scheduler not to schedule it. - ScheduleAnyway tells the 
scheduler to schedule the pod in any location,   but giving higher precedence 
to topologies that would help reduce the   skew. A constraint is considered 
"Unsatisfiable" for an incoming pod if and only if every possible node 
assigment for that pod would viol [...]
+                              type: string
+                          required:
+                          - maxSkew
+                          - topologyKey
+                          - whenUnsatisfiable
+                          type: object
+                        type: array
+                        x-kubernetes-list-map-keys:
+                        - topologyKey
+                        - whenUnsatisfiable
+                        x-kubernetes-list-type: map
                       volumes:
                         description: Additional non-data volumes to load into 
the default container.
                         items:
diff --git a/controllers/controller_utils_test.go 
b/controllers/controller_utils_test.go
index c0de366..31fc8f0 100644
--- a/controllers/controller_utils_test.go
+++ b/controllers/controller_utils_test.go
@@ -911,4 +911,19 @@ var (
                MinSessionTimeout:    6,
                QuorumListenOnAllIPs: true,
        }
+       testTopologySpreadConstraints = []corev1.TopologySpreadConstraint{
+               {
+                       MaxSkew:           3,
+                       TopologyKey:       "zone",
+                       WhenUnsatisfiable: corev1.DoNotSchedule,
+                       LabelSelector: &metav1.LabelSelector{
+                               MatchLabels: map[string]string{"test": "label"},
+                       },
+               },
+               {
+                       MaxSkew:           3,
+                       TopologyKey:       "region",
+                       WhenUnsatisfiable: corev1.ScheduleAnyway,
+               },
+       }
 )
diff --git a/controllers/solrcloud_controller_test.go 
b/controllers/solrcloud_controller_test.go
index 3f07e22..848e913 100644
--- a/controllers/solrcloud_controller_test.go
+++ b/controllers/solrcloud_controller_test.go
@@ -180,6 +180,7 @@ var _ = FDescribe("SolrCloud controller - General", func() {
                                                ImagePullSecrets:              
testAdditionalImagePullSecrets,
                                                TerminationGracePeriodSeconds: 
&testTerminationGracePeriodSeconds,
                                                ServiceAccountName:            
testServiceAccountName,
+                                               TopologySpreadConstraints:     
testTopologySpreadConstraints,
                                        },
                                        StatefulSetOptions: 
&solrv1beta1.StatefulSetOptions{
                                                Annotations:         
testSSAnnotations,
@@ -244,6 +245,11 @@ var _ = FDescribe("SolrCloud controller - General", func() 
{
                        
Expect(statefulSet.Spec.Template.Spec.ImagePullSecrets).To(ConsistOf(append(testAdditionalImagePullSecrets,
 corev1.LocalObjectReference{Name: testImagePullSecretName})), "Incorrect 
imagePullSecrets")
                        
Expect(statefulSet.Spec.Template.Spec.TerminationGracePeriodSeconds).To(Equal(&testTerminationGracePeriodSeconds),
 "Incorrect terminationGracePeriodSeconds")
                        
Expect(statefulSet.Spec.Template.Spec.ServiceAccountName).To(Equal(testServiceAccountName),
 "Incorrect serviceAccountName")
+                       
Expect(statefulSet.Spec.Template.Spec.TopologySpreadConstraints).To(HaveLen(len(testTopologySpreadConstraints)),
 "Wrong number of topologySpreadConstraints")
+                       
Expect(statefulSet.Spec.Template.Spec.TopologySpreadConstraints[0]).To(Equal(testTopologySpreadConstraints[0]),
 "Wrong first topologySpreadConstraint")
+                       expectedSecondTopologyConstraint := 
testTopologySpreadConstraints[1].DeepCopy()
+                       expectedSecondTopologyConstraint.LabelSelector = 
statefulSet.Spec.Selector
+                       
Expect(statefulSet.Spec.Template.Spec.TopologySpreadConstraints[1]).To(Equal(*expectedSecondTopologyConstraint),
 "Wrong second topologySpreadConstraint")
 
                        // Check the update strategy
                        
Expect(statefulSet.Spec.UpdateStrategy.Type).To(Equal(appsv1.RollingUpdateStatefulSetStrategyType),
 "Incorrect statefulset update strategy")
diff --git a/controllers/solrprometheusexporter_controller_test.go 
b/controllers/solrprometheusexporter_controller_test.go
index ce279cf..7a6ae59 100644
--- a/controllers/solrprometheusexporter_controller_test.go
+++ b/controllers/solrprometheusexporter_controller_test.go
@@ -160,6 +160,7 @@ var _ = FDescribe("SolrPrometheusExporter controller - 
General", func() {
                                                TerminationGracePeriodSeconds: 
&testTerminationGracePeriodSeconds,
                                                ServiceAccountName:            
testServiceAccountName,
                                                Lifecycle:                     
testLifecycle,
+                                               TopologySpreadConstraints:     
testTopologySpreadConstraints,
                                        },
                                        DeploymentOptions: 
&solrv1beta1.DeploymentOptions{
                                                Annotations: 
testDeploymentAnnotations,
@@ -232,6 +233,12 @@ var _ = FDescribe("SolrPrometheusExporter controller - 
General", func() {
                        
Expect(*deployment.Spec.Template.Spec.SecurityContext).To(Equal(testPodSecurityContext),
 "Incorrect Pod securityContext")
                        
Expect(deployment.Spec.Template.Spec.Containers[0].Lifecycle).To(Equal(testLifecycle),
 "Incorrect Container lifecycle")
 
+                       
Expect(deployment.Spec.Template.Spec.TopologySpreadConstraints).To(HaveLen(len(testTopologySpreadConstraints)),
 "Wrong number of topologySpreadConstraints")
+                       
Expect(deployment.Spec.Template.Spec.TopologySpreadConstraints[0]).To(Equal(testTopologySpreadConstraints[0]),
 "Wrong first topologySpreadConstraint")
+                       expectedSecondTopologyConstraint := 
testTopologySpreadConstraints[1].DeepCopy()
+                       expectedSecondTopologyConstraint.LabelSelector = 
deployment.Spec.Selector
+                       
Expect(deployment.Spec.Template.Spec.TopologySpreadConstraints[1]).To(Equal(*expectedSecondTopologyConstraint),
 "Wrong second topologySpreadConstraint")
+
                        // Volumes
                        
Expect(deployment.Spec.Template.Spec.Containers[0].VolumeMounts).To(HaveLen(len(extraVolumes)+1),
 "Container has wrong number of volumeMounts")
                        
Expect(deployment.Spec.Template.Spec.Volumes).To(HaveLen(len(extraVolumes)+1), 
"Pod has wrong number of volumes")
diff --git a/controllers/util/common.go b/controllers/util/common.go
index fdd8ac2..fd81e30 100644
--- a/controllers/util/common.go
+++ b/controllers/util/common.go
@@ -483,6 +483,12 @@ func CopyPodTemplates(from, to *corev1.PodTemplateSpec, 
basePath string, logger
                to.Spec.ServiceAccountName = from.Spec.ServiceAccountName
        }
 
+       if !DeepEqualWithNils(to.Spec.TopologySpreadConstraints, 
from.Spec.TopologySpreadConstraints) {
+               requireUpdate = true
+               logger.Info("Update required because field changed", "field", 
basePath+"Spec.TopologySpreadConstraints", "from", 
to.Spec.TopologySpreadConstraints, "to", from.Spec.TopologySpreadConstraints)
+               to.Spec.TopologySpreadConstraints = 
from.Spec.TopologySpreadConstraints
+       }
+
        return requireUpdate
 }
 
diff --git a/controllers/util/prometheus_exporter_util.go 
b/controllers/util/prometheus_exporter_util.go
index fb92a68..b8722c6 100644
--- a/controllers/util/prometheus_exporter_util.go
+++ b/controllers/util/prometheus_exporter_util.go
@@ -70,7 +70,7 @@ func 
GenerateSolrPrometheusExporterDeployment(solrPrometheusExporter *solr.SolrP
                annotations = customDeploymentOptions.Annotations
        }
 
-       customPodOptions := 
solrPrometheusExporter.Spec.CustomKubeOptions.PodOptions
+       customPodOptions := 
solrPrometheusExporter.Spec.CustomKubeOptions.PodOptions.DeepCopy()
        if nil != customPodOptions {
                podLabels = MergeLabelsOrAnnotations(podLabels, 
customPodOptions.Labels)
                podAnnotations = customPodOptions.Annotations
@@ -323,6 +323,17 @@ func 
GenerateSolrPrometheusExporterDeployment(solrPrometheusExporter *solr.SolrP
                if customPodOptions.Lifecycle != nil {
                        metricsContainer.Lifecycle = customPodOptions.Lifecycle
                }
+
+               if len(customPodOptions.TopologySpreadConstraints) > 0 {
+                       deployment.Spec.Template.Spec.TopologySpreadConstraints 
= customPodOptions.TopologySpreadConstraints
+
+                       // Set the label selector for constraints to the 
statefulSet label selector, if none is provided
+                       for i := range 
deployment.Spec.Template.Spec.TopologySpreadConstraints {
+                               if 
deployment.Spec.Template.Spec.TopologySpreadConstraints[i].LabelSelector == nil 
{
+                                       
deployment.Spec.Template.Spec.TopologySpreadConstraints[i].LabelSelector = 
deployment.Spec.Selector.DeepCopy()
+                               }
+                       }
+               }
        }
 
        // Enrich the deployment definition to allow the exporter to make 
requests to TLS enabled Solr pods
diff --git a/controllers/util/solr_util.go b/controllers/util/solr_util.go
index 34886e8..9f41e22 100644
--- a/controllers/util/solr_util.go
+++ b/controllers/util/solr_util.go
@@ -98,7 +98,7 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud, 
solrCloudStatus *solr.SolrCl
                annotations = MergeLabelsOrAnnotations(annotations, 
customSSOptions.Annotations)
        }
 
-       customPodOptions := solrCloud.Spec.CustomSolrKubeOptions.PodOptions
+       customPodOptions := 
solrCloud.Spec.CustomSolrKubeOptions.PodOptions.DeepCopy()
        var podAnnotations map[string]string
        if nil != customPodOptions {
                podLabels = MergeLabelsOrAnnotations(podLabels, 
customPodOptions.Labels)
@@ -546,6 +546,17 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud, 
solrCloudStatus *solr.SolrCl
                if customPodOptions.PriorityClassName != "" {
                        stateful.Spec.Template.Spec.PriorityClassName = 
customPodOptions.PriorityClassName
                }
+
+               if len(customPodOptions.TopologySpreadConstraints) > 0 {
+                       stateful.Spec.Template.Spec.TopologySpreadConstraints = 
customPodOptions.TopologySpreadConstraints
+
+                       // Set the label selector for constraints to the 
statefulSet label selector, if none is provided
+                       for i := range 
stateful.Spec.Template.Spec.TopologySpreadConstraints {
+                               if 
stateful.Spec.Template.Spec.TopologySpreadConstraints[i].LabelSelector == nil {
+                                       
stateful.Spec.Template.Spec.TopologySpreadConstraints[i].LabelSelector = 
stateful.Spec.Selector.DeepCopy()
+                               }
+                       }
+               }
        }
 
        // Enrich the StatefulSet config to enable TLS on Solr pods if needed
diff --git a/helm/solr-operator/Chart.yaml b/helm/solr-operator/Chart.yaml
index c5ea9af..c9f87cd 100644
--- a/helm/solr-operator/Chart.yaml
+++ b/helm/solr-operator/Chart.yaml
@@ -132,6 +132,15 @@ annotations:
           url: https://github.com/apache/solr-operator/issues/348
         - name: Github PR
           url: https://github.com/apache/solr-operator/pull/349
+    - kind: added
+      description: Ability to use topologySpreadConstraints for SolrCloud and 
SolrPrometheusExporter
+      links:
+        - name: Github Issue
+          url: https://github.com/apache/solr-operator/issues/53
+        - name: Github PR
+          url: https://github.com/apache/solr-operator/pull/350
+        - name: Topology Spread Constraints Documentation
+          url: 
https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
   artifacthub.io/images: |
     - name: solr-operator
       image: apache/solr-operator:v0.5.0-prerelease
diff --git a/helm/solr-operator/crds/crds.yaml 
b/helm/solr-operator/crds/crds.yaml
index fa8e34a..43a9551 100644
--- a/helm/solr-operator/crds/crds.yaml
+++ b/helm/solr-operator/crds/crds.yaml
@@ -4769,6 +4769,61 @@ spec:
                               type: string
                           type: object
                         type: array
+                      topologySpreadConstraints:
+                        description: "Optional PodSpreadTopologyConstraints to 
use when scheduling pods. More information here: 
https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
 \n Note: There is no need to provide a \"labelSelector\", as the operator will 
inject the labels for you if not provided."
+                        items:
+                          description: TopologySpreadConstraint specifies how 
to spread matching pods among the given topology.
+                          properties:
+                            labelSelector:
+                              description: LabelSelector is used to find 
matching pods. Pods that match this label selector are counted to determine the 
number of pods in their corresponding topology domain.
+                              properties:
+                                matchExpressions:
+                                  description: matchExpressions is a list of 
label selector requirements. The requirements are ANDed.
+                                  items:
+                                    description: A label selector requirement 
is a selector that contains values, a key, and an operator that relates the key 
and values.
+                                    properties:
+                                      key:
+                                        description: key is the label key that 
the selector applies to.
+                                        type: string
+                                      operator:
+                                        description: operator represents a 
key's relationship to a set of values. Valid operators are In, NotIn, Exists 
and DoesNotExist.
+                                        type: string
+                                      values:
+                                        description: values is an array of 
string values. If the operator is In or NotIn, the values array must be 
non-empty. If the operator is Exists or DoesNotExist, the values array must be 
empty. This array is replaced during a strategic merge patch.
+                                        items:
+                                          type: string
+                                        type: array
+                                    required:
+                                    - key
+                                    - operator
+                                    type: object
+                                  type: array
+                                matchLabels:
+                                  additionalProperties:
+                                    type: string
+                                  description: matchLabels is a map of 
{key,value} pairs. A single {key,value} in the matchLabels map is equivalent to 
an element of matchExpressions, whose key field is "key", the operator is "In", 
and the values array contains only "value". The requirements are ANDed.
+                                  type: object
+                              type: object
+                            maxSkew:
+                              description: 'MaxSkew describes the degree to 
which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, 
it is the maximum permitted difference between the number of matching pods in 
the target topology and the global minimum. For example, in a 3-zone cluster, 
MaxSkew is set to 1, and pods with the same labelSelector spread as 1/1/0: | 
zone1 | zone2 | zone3 | |   P   |   P   |       | - if MaxSkew is 1, incoming 
pod can only be scheduled [...]
+                              format: int32
+                              type: integer
+                            topologyKey:
+                              description: TopologyKey is the key of node 
labels. Nodes that have a label with this key and identical values are 
considered to be in the same topology. We consider each <key, value> as a 
"bucket", and try to put balanced number of pods into each bucket. It's a 
required field.
+                              type: string
+                            whenUnsatisfiable:
+                              description: 'WhenUnsatisfiable indicates how to 
deal with a pod if it doesn''t satisfy the spread constraint. - DoNotSchedule 
(default) tells the scheduler not to schedule it. - ScheduleAnyway tells the 
scheduler to schedule the pod in any location,   but giving higher precedence 
to topologies that would help reduce the   skew. A constraint is considered 
"Unsatisfiable" for an incoming pod if and only if every possible node 
assigment for that pod would viol [...]
+                              type: string
+                          required:
+                          - maxSkew
+                          - topologyKey
+                          - whenUnsatisfiable
+                          type: object
+                        type: array
+                        x-kubernetes-list-map-keys:
+                        - topologyKey
+                        - whenUnsatisfiable
+                        x-kubernetes-list-type: map
                       volumes:
                         description: Additional non-data volumes to load into 
the default container.
                         items:
@@ -10539,6 +10594,61 @@ spec:
                               type: string
                           type: object
                         type: array
+                      topologySpreadConstraints:
+                        description: "Optional PodSpreadTopologyConstraints to 
use when scheduling pods. More information here: 
https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
 \n Note: There is no need to provide a \"labelSelector\", as the operator will 
inject the labels for you if not provided."
+                        items:
+                          description: TopologySpreadConstraint specifies how 
to spread matching pods among the given topology.
+                          properties:
+                            labelSelector:
+                              description: LabelSelector is used to find 
matching pods. Pods that match this label selector are counted to determine the 
number of pods in their corresponding topology domain.
+                              properties:
+                                matchExpressions:
+                                  description: matchExpressions is a list of 
label selector requirements. The requirements are ANDed.
+                                  items:
+                                    description: A label selector requirement 
is a selector that contains values, a key, and an operator that relates the key 
and values.
+                                    properties:
+                                      key:
+                                        description: key is the label key that 
the selector applies to.
+                                        type: string
+                                      operator:
+                                        description: operator represents a 
key's relationship to a set of values. Valid operators are In, NotIn, Exists 
and DoesNotExist.
+                                        type: string
+                                      values:
+                                        description: values is an array of 
string values. If the operator is In or NotIn, the values array must be 
non-empty. If the operator is Exists or DoesNotExist, the values array must be 
empty. This array is replaced during a strategic merge patch.
+                                        items:
+                                          type: string
+                                        type: array
+                                    required:
+                                    - key
+                                    - operator
+                                    type: object
+                                  type: array
+                                matchLabels:
+                                  additionalProperties:
+                                    type: string
+                                  description: matchLabels is a map of 
{key,value} pairs. A single {key,value} in the matchLabels map is equivalent to 
an element of matchExpressions, whose key field is "key", the operator is "In", 
and the values array contains only "value". The requirements are ANDed.
+                                  type: object
+                              type: object
+                            maxSkew:
+                              description: 'MaxSkew describes the degree to 
which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, 
it is the maximum permitted difference between the number of matching pods in 
the target topology and the global minimum. For example, in a 3-zone cluster, 
MaxSkew is set to 1, and pods with the same labelSelector spread as 1/1/0: | 
zone1 | zone2 | zone3 | |   P   |   P   |       | - if MaxSkew is 1, incoming 
pod can only be scheduled [...]
+                              format: int32
+                              type: integer
+                            topologyKey:
+                              description: TopologyKey is the key of node 
labels. Nodes that have a label with this key and identical values are 
considered to be in the same topology. We consider each <key, value> as a 
"bucket", and try to put balanced number of pods into each bucket. It's a 
required field.
+                              type: string
+                            whenUnsatisfiable:
+                              description: 'WhenUnsatisfiable indicates how to 
deal with a pod if it doesn''t satisfy the spread constraint. - DoNotSchedule 
(default) tells the scheduler not to schedule it. - ScheduleAnyway tells the 
scheduler to schedule the pod in any location,   but giving higher precedence 
to topologies that would help reduce the   skew. A constraint is considered 
"Unsatisfiable" for an incoming pod if and only if every possible node 
assigment for that pod would viol [...]
+                              type: string
+                          required:
+                          - maxSkew
+                          - topologyKey
+                          - whenUnsatisfiable
+                          type: object
+                        type: array
+                        x-kubernetes-list-map-keys:
+                        - topologyKey
+                        - whenUnsatisfiable
+                        x-kubernetes-list-type: map
                       volumes:
                         description: Additional non-data volumes to load into 
the default container.
                         items:
diff --git a/helm/solr/README.md b/helm/solr/README.md
index b001c2b..4647587 100644
--- a/helm/solr/README.md
+++ b/helm/solr/README.md
@@ -248,6 +248,7 @@ Configure Solr to use a separate TLS certificate for client 
auth.
 | podOptions.nodeSelector | map[string]string |  | Add a node selector for the 
Solr pod, to specify where it can be scheduled |
 | podOptions.affinity | object |  | Add Kubernetes affinity information for 
the Solr pod |
 | podOptions.tolerations | []object |  | Specify a list of Kubernetes 
tolerations for the Solr pod |
+| podOptions.topologySpreadConstraints | []object |  | Specify a list of 
Kubernetes topologySpreadConstraints for the Solr pod. No need to provide a 
`labelSelector`, as the Solr Operator will default that for you. More 
information can be found in [the 
documentation](https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/).
 |
 | podOptions.serviceAccountName | string |  | Optional serviceAccount to run 
the Solr pods under |
 | podOptions.priorityClassName | string | | Optional priorityClassName for the 
Solr pod |
 | podOptions.sidecarContainers | []object |  | An optional list of additional 
containers to run along side the Solr in its pod |
diff --git a/helm/solr/templates/_custom_option_helpers.tpl 
b/helm/solr/templates/_custom_option_helpers.tpl
index 0e02556..47cb8c8 100644
--- a/helm/solr/templates/_custom_option_helpers.tpl
+++ b/helm/solr/templates/_custom_option_helpers.tpl
@@ -94,6 +94,10 @@ sidecarContainers:
 initContainers:
   {{- toYaml .Values.podOptions.initContainers | nindent 2 }}
 {{ end }}
+{{- if .Values.podOptions.topologySpreadConstraints -}}
+topologySpreadConstraints:
+  {{- toYaml .Values.podOptions.topologySpreadConstraints | nindent 2 }}
+{{ end }}
 {{- end -}}
 
 {{/*
diff --git a/helm/solr/values.yaml b/helm/solr/values.yaml
index 773a5a5..f422820 100644
--- a/helm/solr/values.yaml
+++ b/helm/solr/values.yaml
@@ -258,15 +258,23 @@ podOptions:
 
   priorityClassName: ""
   envVars: []
-  affinity: {}
-  tolerations: []
-  nodeSelector: {}
   podSecurityContext: {}
   terminationGracePeriodSeconds: null
 
   # Set Solr service account individually instead of the global 
"serviceAccount.name"
   serviceAccountName: ""
 
+  # Manage where the Solr pods are scheduled
+  affinity: {}
+  tolerations: []
+  nodeSelector: {}
+  # Documentation available at 
https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
+  # If a labelSelector is not provided, it will be auto-populated by the Solr 
Operator to match the statefulSet labels
+  topologySpreadConstraints: []
+    # - maxSkew: 1
+    #   topologyKey: zone
+    #   whenUnsatisfiable: DoNotSchedule
+
   # Probes for the Solr pods
   livenessProbe: {}
   readinessProbe: {}

Reply via email to