You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ge...@apache.org on 2022/07/27 14:51:29 UTC

[solr-operator] branch main updated: Add Solr ZK Connection Options (#456)

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

gerlowskija 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 8821dc7  Add Solr ZK Connection Options (#456)
8821dc7 is described below

commit 8821dc700bc9f761f15e066c15efe19ad4614873
Author: Houston Putman <ho...@apache.org>
AuthorDate: Wed Jul 27 10:51:25 2022 -0400

    Add Solr ZK Connection Options (#456)
    
    Co-authored-by: Jason Gerlowski <ge...@apache.org>
---
 api/v1beta1/solrcloud_types.go                     |  5 ++
 config/crd/bases/solr.apache.org_solrclouds.yaml   |  3 +
 controllers/controller_utils_test.go               | 53 ++++++++------
 controllers/solrcloud_controller_zk_test.go        | 81 ++++++++++++++++------
 .../solrprometheusexporter_controller_test.go      | 10 ++-
 controllers/util/solr_util.go                      | 20 +++++-
 docs/solr-cloud/solr-cloud-crd.md                  |  3 +
 helm/solr-operator/Chart.yaml                      |  7 ++
 helm/solr-operator/crds/crds.yaml                  |  3 +
 helm/solr/README.md                                |  3 +-
 helm/solr/templates/solrcloud.yaml                 |  3 +
 helm/solr/values.yaml                              |  1 +
 12 files changed, 143 insertions(+), 49 deletions(-)

diff --git a/api/v1beta1/solrcloud_types.go b/api/v1beta1/solrcloud_types.go
index 789f485..29933f5 100644
--- a/api/v1beta1/solrcloud_types.go
+++ b/api/v1beta1/solrcloud_types.go
@@ -101,6 +101,11 @@ type SolrCloudSpec struct {
 	// +optional
 	SolrOpts string `json:"solrOpts,omitempty"`
 
+	// This will add java system properties for connecting to Zookeeper.
+	// SolrZkOpts is the string interface for these optional settings
+	// +optional
+	SolrZkOpts string `json:"solrZkOpts,omitempty"`
+
 	// Set the Solr Log level, defaults to INFO
 	// +optional
 	SolrLogLevel string `json:"solrLogLevel,omitempty"`
diff --git a/config/crd/bases/solr.apache.org_solrclouds.yaml b/config/crd/bases/solr.apache.org_solrclouds.yaml
index e10f486..71d81b5 100644
--- a/config/crd/bases/solr.apache.org_solrclouds.yaml
+++ b/config/crd/bases/solr.apache.org_solrclouds.yaml
@@ -5160,6 +5160,9 @@ spec:
                     description: Verify client's hostname during SSL handshake Only applies for server configuration
                     type: boolean
                 type: object
+              solrZkOpts:
+                description: This will add java system properties for connecting to Zookeeper. SolrZkOpts is the string interface for these optional settings
+                type: string
               updateStrategy:
                 description: Define how Solr rolling updates are executed.
                 properties:
diff --git a/controllers/controller_utils_test.go b/controllers/controller_utils_test.go
index d4e16ad..c6c3b60 100644
--- a/controllers/controller_utils_test.go
+++ b/controllers/controller_utils_test.go
@@ -20,6 +20,7 @@ package controllers
 import (
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
+	"regexp"
 
 	solrv1beta1 "github.com/apache/solr-operator/api/v1beta1"
 	"github.com/apache/solr-operator/controllers/util"
@@ -506,36 +507,32 @@ func filterVarsByName(envVars []corev1.EnvVar, f func(string) bool) []corev1.Env
 }
 
 func testPodEnvVariables(expectedEnvVars map[string]string, foundEnvVars []corev1.EnvVar, additionalOffset ...int) {
-	testGenericPodEnvVariables(expectedEnvVars, foundEnvVars, "SOLR_OPTS", resolveOffset(additionalOffset))
+	testPodEnvVariablesWithGomega(Default, expectedEnvVars, foundEnvVars, resolveOffset(additionalOffset))
 }
 
 func testPodEnvVariablesWithGomega(g Gomega, expectedEnvVars map[string]string, foundEnvVars []corev1.EnvVar, additionalOffset ...int) {
-	testGenericPodEnvVariablesWithGomega(g, expectedEnvVars, foundEnvVars, "SOLR_OPTS", resolveOffset(additionalOffset))
-}
-
-func testMetricsPodEnvVariables(expectedEnvVars map[string]string, foundEnvVars []corev1.EnvVar, additionalOffset ...int) {
-	testGenericPodEnvVariables(expectedEnvVars, foundEnvVars, "JAVA_OPTS", resolveOffset(additionalOffset))
-}
-
-func testMetricsPodEnvVariablesWithGomega(g Gomega, expectedEnvVars map[string]string, foundEnvVars []corev1.EnvVar, additionalOffset ...int) {
-	testGenericPodEnvVariablesWithGomega(g, expectedEnvVars, foundEnvVars, "JAVA_OPTS", resolveOffset(additionalOffset))
-}
-
-func testGenericPodEnvVariables(expectedEnvVars map[string]string, foundEnvVars []corev1.EnvVar, lastVarName string, additionalOffset ...int) {
-	testGenericPodEnvVariablesWithGomega(Default, expectedEnvVars, foundEnvVars, lastVarName, resolveOffset(additionalOffset))
-}
-
-func testGenericPodEnvVariablesWithGomega(g Gomega, expectedEnvVars map[string]string, foundEnvVars []corev1.EnvVar, lastVarName string, additionalOffset ...int) {
+	envVarRegex := regexp.MustCompile(`\$\([a-zA-Z0-9_]+\)`)
 	offset := resolveOffset(additionalOffset)
 	matchCount := 0
-	for _, envVar := range foundEnvVars {
+	var processedEnvVarNames = make([]string, len(foundEnvVars))
+	for i, envVar := range foundEnvVars {
 		if expectedVal, match := expectedEnvVars[envVar.Name]; match {
 			matchCount += 1
 			g.ExpectWithOffset(offset, envVar.Value).To(Equal(expectedVal), "Wrong value for env variable '%s' in podSpec", envVar.Name)
 		}
+
+		// Check that the current envVar only references other env-vars that have already been defined
+		envVarsReferencedByCurrent := envVarRegex.FindAllString(envVar.Value, -1)
+		for _, referencedVar := range envVarsReferencedByCurrent {
+			referencedVarTrimmed := referencedVar[2 : len(referencedVar)-1] // "$(ENV_VAR_NAME)" -> "ENV_VAR_NAME"
+			g.Expect(processedEnvVarNames).To(ContainElement(referencedVarTrimmed),
+				"Env-var %s with value [%s] must be defined after the env-var it depends on: %s",
+				envVar.Name, envVar.Value, referencedVarTrimmed)
+		}
+
+		processedEnvVarNames[i] = envVar.Name
 	}
 	g.ExpectWithOffset(offset, matchCount).To(Equal(len(expectedEnvVars)), "Not all expected env variables found in podSpec")
-	g.ExpectWithOffset(offset, foundEnvVars[len(foundEnvVars)-1].Name).To(Equal(lastVarName), "%s must be the last envVar set, as it uses other envVars.", lastVarName)
 }
 
 func testMapContainsOther(mapName string, base map[string]string, other map[string]string, additionalOffset ...int) {
@@ -549,13 +546,16 @@ func testMapContainsOtherWithGomega(g Gomega, mapName string, base map[string]st
 	}
 }
 
-func testACLEnvVars(actualEnvVars []corev1.EnvVar, hasReadOnly bool, additionalOffset ...int) {
-	testACLEnvVarsWithGomega(Default, actualEnvVars, hasReadOnly, resolveOffset(additionalOffset))
+func insertExpectedAclEnvVars(dest map[string]string, hasReadOnly bool) {
+	expectedEnvVars := getExpectedAclEnvVars(hasReadOnly)
+	for _, expectedEnvVar := range expectedEnvVars {
+		dest[expectedEnvVar.Name] = expectedEnvVar.Value
+	}
 }
 
-func testACLEnvVarsWithGomega(g Gomega, actualEnvVars []corev1.EnvVar, hasReadOnly bool, additionalOffset ...int) {
+func getExpectedAclEnvVars(hasReadOnly bool) []corev1.EnvVar {
 	/*
-		This test verifies ACL related env vars are set correctly and in the correct order, but expects a very specific config to be used in your test SolrCloud config:
+		Populates ACL related env vars are set correctly and in the correct order, assuming a very specific test SolrCloud config:
 		set hasReadOnly = false if ReadOnlyACL is not provided
 					AllACL: &solrv1beta1.ZookeeperACL{
 						SecretRef:   "secret-name",
@@ -627,6 +627,11 @@ func testACLEnvVarsWithGomega(g Gomega, actualEnvVars []corev1.EnvVar, hasReadOn
 				ValueFrom: nil,
 			})
 	}
+	return zkAclEnvVars
+}
+
+func testACLEnvVarsWithGomega(g Gomega, actualEnvVars []corev1.EnvVar, hasReadOnly bool, additionalOffset ...int) {
+	zkAclEnvVars := getExpectedAclEnvVars(hasReadOnly)
 	g.ExpectWithOffset(resolveOffset(additionalOffset), actualEnvVars).To(Equal(zkAclEnvVars), "ZK ACL Env Vars are not correct")
 }
 
@@ -935,4 +940,6 @@ var (
 		},
 	}
 	testIngressClass = "test-ingress-class"
+	testSolrZKOpts   = "-Dsolr.zk.opts=this"
+	testSolrOpts     = "-Dsolr.opts=this"
 )
diff --git a/controllers/solrcloud_controller_zk_test.go b/controllers/solrcloud_controller_zk_test.go
index 6bf4087..9fd0e29 100644
--- a/controllers/solrcloud_controller_zk_test.go
+++ b/controllers/solrcloud_controller_zk_test.go
@@ -29,6 +29,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
+	"strconv"
 )
 
 var _ = FDescribe("SolrCloud controller - Zookeeper", func() {
@@ -112,12 +113,12 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() {
 				"SOLR_NODE_PORT": "8983",
 				"SOLR_OPTS":      "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts",
 			}
+			insertExpectedAclEnvVars(expectedEnvVars, false)
+			for _, envVar := range extraVars {
+				expectedEnvVars[envVar.Name] = envVar.Value
+			}
 			foundEnv := statefulSet.Spec.Template.Spec.Containers[0].Env
-			testACLEnvVars(foundEnv[len(foundEnv)-6:len(foundEnv)-3], false)
-			Expect(foundEnv[len(foundEnv)-3:len(foundEnv)-1]).To(Equal(extraVars), "Extra Env Vars are not the same as the ones provided in podOptions")
-			// Note that this check changes the variable foundEnv, so the values are no longer valid afterwards.
-			// TODO: Make this not invalidate foundEnv
-			testPodEnvVariables(expectedEnvVars, append(foundEnv[:len(foundEnv)-6], foundEnv[len(foundEnv)-1]))
+			testPodEnvVariables(expectedEnvVars, foundEnv)
 			Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "8983"}), "Incorrect pre-stop command")
 		})
 	})
@@ -177,12 +178,12 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() {
 				"SOLR_NODE_PORT": "8983",
 				"SOLR_OPTS":      "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts",
 			}
+			insertExpectedAclEnvVars(expectedEnvVars, true)
+			for _, envVar := range extraVars {
+				expectedEnvVars[envVar.Name] = envVar.Value
+			}
 			foundEnv := statefulSet.Spec.Template.Spec.Containers[0].Env
-			testACLEnvVars(foundEnv[len(foundEnv)-8:len(foundEnv)-3], true)
-			Expect(foundEnv[len(foundEnv)-3:len(foundEnv)-1]).To(Equal(extraVars), "Extra Env Vars are not the same as the ones provided in podOptions")
-			// Note that this check changes the variable foundEnv, so the values are no longer valid afterwards.
-			// TODO: Make this not invalidate foundEnv
-			testPodEnvVariables(expectedEnvVars, append(foundEnv[:len(foundEnv)-8], foundEnv[len(foundEnv)-1]))
+			testPodEnvVariables(expectedEnvVars, foundEnv)
 			Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "8983"}), "Incorrect pre-stop command")
 
 			By("testing that no ZookeeperCluster is created when using a connection String")
@@ -430,12 +431,12 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() {
 				"ZK_CHROOT":      "/a-ch/root",
 				"SOLR_OPTS":      "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts",
 			}
+			insertExpectedAclEnvVars(expectedEnvVars, false)
+			for _, envVar := range extraVars {
+				expectedEnvVars[envVar.Name] = envVar.Value
+			}
 			foundEnv := statefulSet.Spec.Template.Spec.Containers[0].Env
-			testACLEnvVars(foundEnv[len(foundEnv)-6:len(foundEnv)-3], false)
-			Expect(foundEnv[len(foundEnv)-3:len(foundEnv)-1]).To(Equal(extraVars), "Extra Env Vars are not the same as the ones provided in podOptions")
-			// Note that this check changes the variable foundEnv, so the values are no longer valid afterwards.
-			// TODO: Make this not invalidate foundEnv
-			testPodEnvVariables(expectedEnvVars, append(foundEnv[:len(foundEnv)-6], foundEnv[len(foundEnv)-1]))
+			testPodEnvVariables(expectedEnvVars, foundEnv)
 			Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "8983"}), "Incorrect pre-stop command")
 
 			By("testing the created ZookeeperCluster")
@@ -505,18 +506,58 @@ var _ = FDescribe("SolrCloud controller - Zookeeper", func() {
 				"SOLR_NODE_PORT": "8983",
 				"SOLR_OPTS":      "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_CREDS_AND_ACLS) -Dextra -Dopts",
 			}
+			insertExpectedAclEnvVars(expectedEnvVars, true)
+			for _, envVar := range extraVars {
+				expectedEnvVars[envVar.Name] = envVar.Value
+			}
 			foundEnv := statefulSet.Spec.Template.Spec.Containers[0].Env
-			testACLEnvVars(foundEnv[len(foundEnv)-8:len(foundEnv)-3], true)
-			Expect(foundEnv[len(foundEnv)-3:len(foundEnv)-1]).To(Equal(extraVars), "Extra Env Vars are not the same as the ones provided in podOptions")
-			// Note that this check changes the variable foundEnv, so the values are no longer valid afterwards.
-			// TODO: Make this not invalidate foundEnv
-			testPodEnvVariables(expectedEnvVars, append(foundEnv[:len(foundEnv)-8], foundEnv[len(foundEnv)-1]))
+			testPodEnvVariables(expectedEnvVars, foundEnv)
 			Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PreStop.Exec.Command).To(Equal([]string{"solr", "stop", "-p", "8983"}), "Incorrect pre-stop command")
 
 			By("testing the created ZookeeperCluster")
 			expectZookeeperCluster(ctx, solrCloud, solrCloud.ProvidedZookeeperName())
 		})
 	})
+
+	FContext("Solr Cloud with Solr ZK Connection Options", func() {
+		BeforeEach(func() {
+			solrCloud.Spec = solrv1beta1.SolrCloudSpec{
+				ZookeeperRef: &solrv1beta1.ZookeeperRef{
+					ConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
+						InternalConnectionString: "host:7271",
+						ChRoot:                   "/test",
+					},
+				},
+				SolrZkOpts: testSolrZKOpts,
+				SolrOpts:   testSolrOpts,
+				SolrSecurity: &solrv1beta1.SolrSecurityOptions{
+					AuthenticationType: solrv1beta1.Basic,
+				},
+			}
+		})
+		FIt("has the correct resources", func() {
+			By("testing the Solr StatefulSet")
+			statefulSet := expectStatefulSet(ctx, solrCloud, solrCloud.StatefulSetName())
+			Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr StatefulSet requires a container.")
+			expectedEnvVars := map[string]string{
+				"ZK_HOST":        "host:7271/test",
+				"SOLR_HOST":      "$(POD_HOSTNAME).foo-solrcloud-headless.default",
+				"SOLR_PORT":      "8983",
+				"SOLR_NODE_PORT": "8983",
+				"SOLR_ZK_OPTS":   testSolrZKOpts,
+				"SOLR_OPTS":      "-DhostPort=$(SOLR_NODE_PORT) $(SOLR_ZK_OPTS) " + testSolrOpts,
+				"SOLR_STOP_WAIT": strconv.FormatInt(60-5, 10),
+			}
+			testPodEnvVariables(expectedEnvVars, statefulSet.Spec.Template.Spec.Containers[0].Env)
+
+			expectedInitContainerEnvVars := map[string]string{
+				"SOLR_ZK_OPTS":    testSolrZKOpts,
+				"SOLR_OPTS":       "$(SOLR_ZK_OPTS) " + testSolrOpts,
+				"ZKCLI_JVM_FLAGS": "-Dsolr.zk.opts=this",
+			}
+			testPodEnvVariables(expectedInitContainerEnvVars, statefulSet.Spec.Template.Spec.InitContainers[1].Env)
+		})
+	})
 })
 
 func expectZookeeperCluster(ctx context.Context, parentResource client.Object, zkName string, additionalOffset ...int) *zk_crd.ZookeeperCluster {
diff --git a/controllers/solrprometheusexporter_controller_test.go b/controllers/solrprometheusexporter_controller_test.go
index 49f59c6..e4ed986 100644
--- a/controllers/solrprometheusexporter_controller_test.go
+++ b/controllers/solrprometheusexporter_controller_test.go
@@ -397,11 +397,15 @@ var _ = FDescribe("SolrPrometheusExporter controller - General", func() {
 						ValueFrom: nil,
 					},
 				}
+				for _, zkAclEnvVar := range zkAclEnvVars {
+					expectedEnvVars[zkAclEnvVar.Name] = zkAclEnvVar.Value
+				}
+				for _, extraVar := range extraVars {
+					expectedEnvVars[extraVar.Name] = extraVar.Value
+				}
 				g.Expect(foundEnv[0:5]).To(Equal(zkAclEnvVars), "ZK ACL Env Vars are not correct")
 				g.Expect(foundEnv[5:len(foundEnv)-1]).To(Equal(extraVars), "Extra Env Vars are not the same as the ones provided in podOptions")
-				// Note that this check changes the variable foundEnv, so the values are no longer valid afterwards.
-				// TODO: Make this not invalidate foundEnv
-				testMetricsPodEnvVariablesWithGomega(g, expectedEnvVars, foundEnv[len(foundEnv)-1:])
+				testPodEnvVariablesWithGomega(g, expectedEnvVars, foundEnv)
 			})
 			By("Changing the ZKConnection String of the SolrCloud")
 
diff --git a/controllers/util/solr_util.go b/controllers/util/solr_util.go
index 44851d0..0223bb0 100644
--- a/controllers/util/solr_util.go
+++ b/controllers/util/solr_util.go
@@ -1095,6 +1095,9 @@ func generateZKInteractionInitContainer(solrCloud *solr.SolrCloud, solrCloudStat
 
 	if security != nil && security.SecurityJson != "" {
 		envVars = append(envVars, corev1.EnvVar{Name: "SECURITY_JSON", ValueFrom: security.SecurityJsonSrc})
+		if solrCloud.Spec.SolrZkOpts != "" {
+			envVars = append(envVars, corev1.EnvVar{Name: "ZKCLI_JVM_FLAGS", Value: solrCloud.Spec.SolrZkOpts})
+		}
 		cmd += cmdToPutSecurityJsonInZk()
 	}
 
@@ -1138,16 +1141,29 @@ func createZkConnectionEnvVars(solrCloud *solr.SolrCloud, solrCloudStatus *solr.
 		},
 	}
 
+	solrOpts := make([]string, 0)
+
 	// Add ACL information, if given, through Env Vars
 	allACL, readOnlyACL := solrCloud.Spec.ZookeeperRef.GetACLs()
 	if hasACLs, aclEnvs := AddACLsToEnv(allACL, readOnlyACL); hasACLs {
 		envVars = append(envVars, aclEnvs...)
 
 		// The $SOLR_ZK_CREDS_AND_ACLS parameter does not get picked up when running solr, it must be added to the SOLR_OPTS.
-		solrOpt = "$(SOLR_ZK_CREDS_AND_ACLS)"
+		solrOpts = append(solrOpts, "$(SOLR_ZK_CREDS_AND_ACLS)")
+	}
+
+	// Add ZK Connection System Properties to Solr
+	if solrCloud.Spec.SolrZkOpts != "" {
+		envVars = append(envVars, corev1.EnvVar{
+			Name:  "SOLR_ZK_OPTS",
+			Value: solrCloud.Spec.SolrZkOpts,
+		})
+
+		// The $SOLR_ZK_OPTS parameter does not get picked up when running solr, it must be added to the SOLR_OPTS.
+		solrOpts = append(solrOpts, "$(SOLR_ZK_OPTS)")
 	}
 
-	return envVars, solrOpt, len(zkChroot) > 1
+	return envVars, strings.Join(solrOpts, " "), len(zkChroot) > 1
 }
 
 func setupVolumeMountForUserProvidedConfigMapEntry(reconcileConfigInfo map[string]string, fileKey string, solrVolumes []corev1.Volume, envVar string) (*corev1.VolumeMount, *corev1.EnvVar, *corev1.Volume) {
diff --git a/docs/solr-cloud/solr-cloud-crd.md b/docs/solr-cloud/solr-cloud-crd.md
index 92d0d0a..9255364 100644
--- a/docs/solr-cloud/solr-cloud-crd.md
+++ b/docs/solr-cloud/solr-cloud-crd.md
@@ -164,6 +164,9 @@ Under `spec.zookeeperRef`:
   - **`externalConnectionString`** - The ZK connection string to the external Zookeeper cluster, e.g. `zoo1:2181`
   - **`chroot`** - The chroot to use for the cluster
 
+External ZooKeeper clusters are often configured to use ZooKeeper features (e.g. securePort) which require corresponding configuration on the client side.
+To support these use-cases, users may provide arbitrary system properties under `spec.solrZkOpts` which will be passed down to all ZooKeeper clients (Solr, zkcli.sh, etc.) managed by the operator.
+
 #### ACLs
 _Since v0.2.7_
 
diff --git a/helm/solr-operator/Chart.yaml b/helm/solr-operator/Chart.yaml
index 28cd552..64150f8 100644
--- a/helm/solr-operator/Chart.yaml
+++ b/helm/solr-operator/Chart.yaml
@@ -168,6 +168,13 @@ annotations:
           url: https://apache.github.io/solr-operator/docs/upgrade-notes.html#v060
         - name: Solr Addressability Docs
           url: https://apache.github.io/solr-operator/docs/solr-cloud/solr-cloud-crd.html#addressability
+    - kind: added
+      description: SolrCloud now accepts a `solrZkOpts` option for specifying any Java system properties needed to connect to ZooKeeper.
+      links:
+        - name: Github Issue
+          url: https://github.com/apache/solr-operator/issues/435
+        - name: Github PR
+          url: https://github.com/apache/solr-operator/pull/456
   artifacthub.io/images: |
     - name: solr-operator
       image: apache/solr-operator:v0.6.0-prerelease
diff --git a/helm/solr-operator/crds/crds.yaml b/helm/solr-operator/crds/crds.yaml
index 69f6571..995f81c 100644
--- a/helm/solr-operator/crds/crds.yaml
+++ b/helm/solr-operator/crds/crds.yaml
@@ -5392,6 +5392,9 @@ spec:
                     description: Verify client's hostname during SSL handshake Only applies for server configuration
                     type: boolean
                 type: object
+              solrZkOpts:
+                description: This will add java system properties for connecting to Zookeeper. SolrZkOpts is the string interface for these optional settings
+                type: string
               updateStrategy:
                 description: Define how Solr rolling updates are executed.
                 properties:
diff --git a/helm/solr/README.md b/helm/solr/README.md
index 0f815b7..16baf82 100644
--- a/helm/solr/README.md
+++ b/helm/solr/README.md
@@ -92,7 +92,8 @@ Descriptions on how to use these options can be found in the [SolrCloud document
 | busyBoxImage.pullPolicy | string |  | PullPolicy for the BusyBox image, defaults to the empty Pod behavior |
 | busyBoxImage.imagePullSecret | string |  | PullSecret for the BusyBox image |
 | solrOptions.javaMemory | string | `"-Xms1g -Xmx2g"` | Java memory parameters |
-| solrOptions.javaOpts | string | `""` | Additional java arguments to pass via the command line |
+| solrOptions.javaOpts | string | `""` | Additional java arguments to pass via the command line.  ZooKeeper-connection related properties should be reserved for `solrOptions.zkJavaOpts` (see below). |
+| solrOptions.zkJavaOpts | string | `""` | Additional java arguments required to connect to ZooKeeper to pass via the command line |
 | solrOptions.logLevel | string | `"INFO"` | Log level to run Solr under |
 | solrOptions.gcTune | string | `""` | GC Tuning parameters for Solr |
 | solrOptions.solrModules | []string | | List of packaged Solr Modules to load when running Solr. Note: There is no need to specify solr modules necessary for other parts of the Spec (i.e. `backupRepositories[].gcs`), those will be added automatically. |
diff --git a/helm/solr/templates/solrcloud.yaml b/helm/solr/templates/solrcloud.yaml
index 5f12b71..9ad4aee 100644
--- a/helm/solr/templates/solrcloud.yaml
+++ b/helm/solr/templates/solrcloud.yaml
@@ -67,6 +67,9 @@ spec:
   {{- if .Values.solrOptions.javaOpts }}
   solrOpts: {{ .Values.solrOptions.javaOpts | quote }}
   {{- end }}
+  {{- if .Values.solrOptions.zkJavaOpts }}
+  solrZkOpts: {{ .Values.solrOptions.zkJavaOpts | quote }}
+  {{- end }}
 
   {{- if .Values.solrOptions.security }}
   solrSecurity:
diff --git a/helm/solr/values.yaml b/helm/solr/values.yaml
index 7eaeb40..1c1f1d3 100644
--- a/helm/solr/values.yaml
+++ b/helm/solr/values.yaml
@@ -52,6 +52,7 @@ busyBoxImage: {}
 solrOptions:
   javaMemory: ""
   javaOpts: ""
+  zkJavaOpts: ""
   logLevel: ""
   gcTune: ""
   solrModules: []