From b0ead4ec9dc43e73016ead1ebe4f01bcaa6eb5ca Mon Sep 17 00:00:00 2001 From: Houston Putman Date: Mon, 17 Mar 2025 18:12:59 -0500 Subject: [PATCH 1/5] Add default permission for replica balancing --- controllers/util/solr_security_util.go | 1 + docs/solr-cloud/solr-cloud-crd.md | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/controllers/util/solr_security_util.go b/controllers/util/solr_security_util.go index eab99d2e..ec889099 100644 --- a/controllers/util/solr_security_util.go +++ b/controllers/util/solr_security_util.go @@ -393,6 +393,7 @@ func generateSecurityJson(solrCloud *solr.SolrCloud) map[string][]byte { { "name": "k8s-metrics", "role":"k8s", "collection": null, "path":"/admin/metrics" }, { "name": "k8s-zk", "role":"k8s", "collection": null, "path":"/admin/zookeeper/status" }, { "name": "k8s-ping", "role":"k8s", "collection": "*", "path":"/admin/ping" }, + { "name": "k8s-replica-balancing", "role":"k8s", "collection": null, "path":"/api/cluster/replicas/balance" }, { "name": "read", "role":["admin","users"] }, { "name": "update", "role":["admin"] }, { "name": "security-read", "role": ["admin"] }, diff --git a/docs/solr-cloud/solr-cloud-crd.md b/docs/solr-cloud/solr-cloud-crd.md index 5eaf6e9d..3ef52a22 100644 --- a/docs/solr-cloud/solr-cloud-crd.md +++ b/docs/solr-cloud/solr-cloud-crd.md @@ -1032,6 +1032,12 @@ Take a moment to review these authorization rules so that you're aware of the ro "collection": "*", "path": "/admin/ping" }, + { + "name": "k8s-replica-balancing", + "role": "k8s", + "collection": null, + "path": "/api/cluster/replicas/balance" + }, { "name": "read", "role": [ "admin", "users" ] @@ -1165,6 +1171,7 @@ Users need to ensure their `security.json` contains the user supplied in the `ba /admin/metrics /admin/ping (for collection="*") /admin/zookeeper/status +/api/cluster/replicas/balance ``` _Tip: see the authorization rules defined by the default `security.json` as a guide for configuring access for the operator user_ From 21a6762fbf5d8743ba19582bb7bef82bd2c992e4 Mon Sep 17 00:00:00 2001 From: Houston Putman Date: Tue, 18 Mar 2025 14:30:24 -0500 Subject: [PATCH 2/5] Add test, make it actually work --- controllers/util/solr_security_util.go | 7 ++- controllers/util/solr_util.go | 28 ++++++----- tests/e2e/solrcloud_security_json_test.go | 59 ++++++++++++++++++++--- tests/e2e/suite_test.go | 35 +++++++++++++- tests/e2e/test_utils_test.go | 40 +++++++++------ tests/scripts/manage_e2e_tests.sh | 2 +- 6 files changed, 133 insertions(+), 38 deletions(-) diff --git a/controllers/util/solr_security_util.go b/controllers/util/solr_security_util.go index ec889099..cbdb748d 100644 --- a/controllers/util/solr_security_util.go +++ b/controllers/util/solr_security_util.go @@ -117,6 +117,9 @@ func reconcileForBasicAuthWithBootstrappedSecurityJson(ctx context.Context, clie // supply the bootstrap security.json to the initContainer via a simple BASE64 encoding env var security.SecurityJson = string(bootstrapSecret.Data[SecurityJsonFile]) + security.SecurityJsonSrc = &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{Name: bootstrapSecret.Name}, Key: SecurityJsonFile}} basicAuthSecret = authSecret } @@ -394,7 +397,9 @@ func generateSecurityJson(solrCloud *solr.SolrCloud) map[string][]byte { { "name": "k8s-zk", "role":"k8s", "collection": null, "path":"/admin/zookeeper/status" }, { "name": "k8s-ping", "role":"k8s", "collection": "*", "path":"/admin/ping" }, { "name": "k8s-replica-balancing", "role":"k8s", "collection": null, "path":"/api/cluster/replicas/balance" }, - { "name": "read", "role":["admin","users"] }, + { "name": "k8s-replica-balancing", "role":"k8s", "collection": null, "path":"/____v2/cluster/replicas/balance" }, + { "name": "admin", "role":["admin","k8s"] }, + { "name": "read", "role":["admin","users","k8s"] }, { "name": "update", "role":["admin"] }, { "name": "security-read", "role": ["admin"] }, { "name": "security-edit", "role": ["admin"] }, diff --git a/controllers/util/solr_util.go b/controllers/util/solr_util.go index 478145a5..fefbe89f 100644 --- a/controllers/util/solr_util.go +++ b/controllers/util/solr_util.go @@ -402,16 +402,6 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCl envVars = append(envVars, backupEnvVars...) } - // Only have a postStart command to create the chRoot, if it is not '/' (which does not need to be created) - var postStart *corev1.LifecycleHandler - if hasChroot { - postStart = &corev1.LifecycleHandler{ - Exec: &corev1.ExecAction{ - Command: []string{"sh", "-c", "solr zk ls ${ZK_CHROOT} -z ${ZK_SERVER} || solr zk mkroot ${ZK_CHROOT} -z ${ZK_SERVER}"}, - }, - } - } - // Default preStop hook preStop := &corev1.LifecycleHandler{ Exec: &corev1.ExecAction{ @@ -456,7 +446,18 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCl Value: strings.Join(allSolrOpts, " "), }) - initContainers := generateSolrSetupInitContainers(solrCloud, solrCloudStatus, solrDataVolumeName, security) + var postStart *corev1.LifecycleHandler + initContainers, doesInitZk := generateSolrSetupInitContainers(solrCloud, solrCloudStatus, solrDataVolumeName, security) + if !doesInitZk && hasChroot { + // Only have a postStart command to create the chRoot, if: + // - it is not '/' (which does not need to be created) + // - there is not an initContainer that already does this + postStart = &corev1.LifecycleHandler{ + Exec: &corev1.ExecAction{ + Command: []string{"sh", "-c", "solr zk ls ${ZK_CHROOT} -z ${ZK_SERVER} || solr zk mkroot ${ZK_CHROOT} -z ${ZK_SERVER}"}, + }, + } + } // Add user defined additional init containers if customPodOptions != nil && len(customPodOptions.InitContainers) > 0 { @@ -704,7 +705,7 @@ func MaintainPreservedStatefulSetFields(expected, found *appsv1.StatefulSet) { expected.Spec.Replicas = found.Spec.Replicas } -func generateSolrSetupInitContainers(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCloudStatus, solrDataVolumeName string, security *SecurityConfig) (containers []corev1.Container) { +func generateSolrSetupInitContainers(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCloudStatus, solrDataVolumeName string, security *SecurityConfig) (containers []corev1.Container, doesInitZk bool) { // The setup of the solr.xml will always be necessary volumeMounts := []corev1.VolumeMount{ { @@ -790,6 +791,7 @@ func generateSolrSetupInitContainers(solrCloud *solr.SolrCloud, solrCloudStatus if hasZKSetupContainer, zkSetupContainer := generateZKInteractionInitContainer(solrCloud, solrCloudStatus, security); hasZKSetupContainer { containers = append(containers, zkSetupContainer) + doesInitZk = true } // If the user has provided custom resources for the default init containers, use them @@ -803,7 +805,7 @@ func generateSolrSetupInitContainers(solrCloud *solr.SolrCloud, solrCloudStatus } } - return containers + return containers, doesInitZk } func createDefaultProbeHandlerForPath(probeScheme corev1.URIScheme, solrPodPort int, path string) corev1.ProbeHandler { diff --git a/tests/e2e/solrcloud_security_json_test.go b/tests/e2e/solrcloud_security_json_test.go index 1ceef5e7..d5476d4a 100644 --- a/tests/e2e/solrcloud_security_json_test.go +++ b/tests/e2e/solrcloud_security_json_test.go @@ -20,9 +20,14 @@ package e2e import ( "context" solrv1beta1 "github.com/apache/solr-operator/api/v1beta1" + "github.com/apache/solr-operator/controllers" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" + "time" ) var _ = FDescribe("E2E - SolrCloud - Security JSON", func() { @@ -35,10 +40,6 @@ var _ = FDescribe("E2E - SolrCloud - Security JSON", func() { }) JustBeforeEach(func(ctx context.Context) { - By("generating the security.json secret and basic auth secret") - generateSolrSecuritySecret(ctx, solrCloud) - generateSolrBasicAuthSecret(ctx, solrCloud) - By("creating the SolrCloud") Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed()) @@ -50,20 +51,64 @@ var _ = FDescribe("E2E - SolrCloud - Security JSON", func() { solrCloud = expectSolrCloudToBeReady(ctx, solrCloud) By("creating a first Solr Collection") - createAndQueryCollection(ctx, solrCloud, "basic", 1, 1) + createAndQueryCollection(ctx, solrCloud, "basic", 2, 1) }) - FContext("Provided Zookeeper", func() { - BeforeEach(func() { + FContext("Provided Security JSON", func() { + BeforeEach(func(ctx context.Context) { solrCloud.Spec.ZookeeperRef = &solrv1beta1.ZookeeperRef{ ProvidedZookeeper: &solrv1beta1.ZookeeperSpec{ Replicas: pointer.Int32(1), Ephemeral: &solrv1beta1.ZKEphemeral{}, }, } + + solrCloud.Spec.SolrSecurity = &solrv1beta1.SolrSecurityOptions{ + AuthenticationType: "Basic", + BasicAuthSecret: solrCloud.Name + "-basic-auth-secret", + BootstrapSecurityJson: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: solrCloud.Name + "-security-secret", + }, + Key: "security.json", + }, + } + + By("generating the security.json secret and basic auth secret") + generateSolrSecuritySecret(ctx, solrCloud) + generateSolrBasicAuthSecret(ctx, solrCloud) }) // All testing will be done in the "JustBeforeEach" logic, no additional tests required here FIt("Starts correctly", func(ctx context.Context) {}) }) + + FContext("Bootstrapped Security", func() { + + BeforeEach(func() { + solrCloud.Spec.SolrSecurity = &solrv1beta1.SolrSecurityOptions{ + AuthenticationType: "Basic", + } + }) + + FIt("Scales up with replica migration", func(ctx context.Context) { + originalSolrCloud := solrCloud.DeepCopy() + solrCloud.Spec.Replicas = pointer.Int32(int32(2)) + By("triggering a scale up via solrCloud replicas") + Expect(k8sClient.Patch(ctx, solrCloud, client.MergeFrom(originalSolrCloud))).To(Succeed(), "Could not patch SolrCloud replicas to initiate scale down") + + By("make sure scaleDown happens without a clusterLock and eventually the replicas are removed") + // Once the scale down actually occurs, the statefulSet annotations should be removed very soon + expectStatefulSetWithChecksAndTimeout(ctx, solrCloud, solrCloud.StatefulSetName(), time.Second*30, time.Millisecond*500, func(g Gomega, found *appsv1.StatefulSet) { + g.Expect(found.Spec.Replicas).To(HaveValue(BeEquivalentTo(2)), "StatefulSet should eventually have 2 pods.") + clusterOp, err := controllers.GetCurrentClusterOp(found) + g.Expect(err).ToNot(HaveOccurred(), "Error occurred while finding clusterLock for SolrCloud") + g.Expect(clusterOp).To(BeNil(), "StatefulSet should have a ScaleDown lock after scaling is complete.") + }) + + queryCollection(ctx, solrCloud, "basic", 0) + + // TODO: When balancing is in all Operator supported Solr versions, add a test to make sure balancing occurred + }) + }) }) diff --git a/tests/e2e/suite_test.go b/tests/e2e/suite_test.go index 1c2aec54..c7464430 100644 --- a/tests/e2e/suite_test.go +++ b/tests/e2e/suite_test.go @@ -106,6 +106,7 @@ var _ = SynchronizedBeforeSuite(func(ctx context.Context) { var err error k8sConfig, err = config.GetConfig() Expect(err).NotTo(HaveOccurred(), "Could not load in default kubernetes config") + k8sConfig.Timeout = time.Minute Expect(zkApi.AddToScheme(scheme.Scheme)).To(Succeed()) Expect(certManagerApi.AddToScheme(scheme.Scheme)).To(Succeed()) k8sClient, err = client.New(k8sConfig, client.Options{Scheme: scheme.Scheme}) @@ -338,7 +339,7 @@ func writeAllSolrInfoToFiles(ctx context.Context, directory string, namespace st } foundServices := &corev1.ServiceList{} - Expect(k8sClient.List(ctx, foundServices, listOps)).To(Succeed(), "Could not fetch Solr pods") + Expect(k8sClient.List(ctx, foundServices, listOps)).To(Succeed(), "Could not fetch Solr services") Expect(foundServices).ToNot(BeNil(), "No Solr services could be found") for _, service := range foundServices.Items { writeAllServiceInfoToFiles( @@ -346,6 +347,25 @@ func writeAllSolrInfoToFiles(ctx context.Context, directory string, namespace st &service, ) } + + // Unfortunately the secrets don't have a technology label + req, err = labels.NewRequirement("solr-cloud", selection.Exists, make([]string, 0)) + Expect(err).ToNot(HaveOccurred()) + + labelSelector = labels.Everything().Add(*req) + listOps = &client.ListOptions{ + Namespace: namespace, + LabelSelector: labelSelector, + } + + foundSecrets := &corev1.SecretList{} + Expect(k8sClient.List(ctx, foundSecrets, listOps)).To(Succeed(), "Could not fetch Solr secrets") + for _, secret := range foundSecrets.Items { + writeAllSecretInfoToFiles( + directory+secret.Name+".secret", + &secret, + ) + } } // writeSolrClusterStatusInfoToFile writes the following each to a separate file with the given base name & directory. @@ -401,6 +421,19 @@ func writeAllServiceInfoToFiles(baseFilename string, service *corev1.Service) { Expect(writeErr).ToNot(HaveOccurred(), "Could not write service json to file") } +// writeAllSecretInfoToFiles writes the following each to a separate file with the given base name & directory. +// - Service +func writeAllSecretInfoToFiles(baseFilename string, secret *corev1.Secret) { + // Write service to a file + statusFile, err := os.Create(baseFilename + ".json") + defer statusFile.Close() + Expect(err).ToNot(HaveOccurred(), "Could not open file to save secret status: %s", baseFilename+".json") + jsonBytes, marshErr := json.MarshalIndent(secret, "", "\t") + Expect(marshErr).ToNot(HaveOccurred(), "Could not serialize secret json") + _, writeErr := statusFile.Write(jsonBytes) + Expect(writeErr).ToNot(HaveOccurred(), "Could not write secret json to file") +} + // writeAllPodInfoToFile writes the following each to a separate file with the given base name & directory. // - Pod Spec/Status // - Pod Events diff --git a/tests/e2e/test_utils_test.go b/tests/e2e/test_utils_test.go index 203b93e3..240ee4ce 100644 --- a/tests/e2e/test_utils_test.go +++ b/tests/e2e/test_utils_test.go @@ -25,6 +25,7 @@ import ( "fmt" "io" "os" + "regexp" "strconv" "strings" "time" @@ -233,7 +234,7 @@ func createAndQueryCollectionWithGomega(ctx context.Context, solrCloud *solrv1be } additionalOffset += 1 - g.EventuallyWithOffset(additionalOffset, func(innerG Gomega) { + g.EventuallyWithOffset(additionalOffset, func(innerG Gomega, ctx context.Context) { response, err := callSolrApiInPod( ctx, solrCloud, @@ -246,7 +247,7 @@ func createAndQueryCollectionWithGomega(ctx context.Context, solrCloud *solrv1be }).Within(time.Second*10).WithContext(ctx).Should(Succeed(), "Collection creation command start was not successful") // Only wait 5 seconds when trying to create the asyncCommand - g.EventuallyWithOffset(additionalOffset, func(innerG Gomega) { + g.EventuallyWithOffset(additionalOffset, func(innerG Gomega, ctx context.Context) { response, err := callSolrApiInPod( ctx, solrCloud, @@ -271,7 +272,7 @@ func createAndQueryCollectionWithGomega(ctx context.Context, solrCloud *solrv1be innerG.Expect(response).To(ContainSubstring("\"state\":\"completed\""), "Did not finish creating Solr Collection in time") }).Within(time.Second*40).WithContext(ctx).Should(Succeed(), "Collection creation was not successful") - g.EventuallyWithOffset(additionalOffset, func(innerG Gomega) { + g.EventuallyWithOffset(additionalOffset, func(innerG Gomega, ctx context.Context) { response, err := callSolrApiInPod( ctx, solrCloud, @@ -296,7 +297,7 @@ func queryCollection(ctx context.Context, solrCloud *solrv1beta1.SolrCloud, coll } func queryCollectionWithGomega(ctx context.Context, solrCloud *solrv1beta1.SolrCloud, collection string, docCount int, g Gomega, additionalOffset ...int) { - g.EventuallyWithOffset(resolveOffset(additionalOffset), func(innerG Gomega) { + g.EventuallyWithOffset(resolveOffset(additionalOffset), func(innerG Gomega, ctx context.Context) { response, err := callSolrApiInPod( ctx, solrCloud, @@ -476,6 +477,20 @@ func callSolrApiInPod(ctx context.Context, solrCloud *solrv1beta1.SolrCloud, htt queryParamsString = "?" + queryParamsString } + toolOpts := "" + if solrCloud.Spec.SolrSecurity != nil && solrCloud.Spec.SolrSecurity.AuthenticationType == solrv1beta1.Basic { + basicAuthSecretName := solrCloud.BasicAuthSecretName() + basicAuthSecret := &corev1.Secret{} + if err = k8sClient.Get(ctx, resourceKey(solrCloud, basicAuthSecretName), basicAuthSecret); err != nil { + return "", err + } + toolOpts = + "JAVA_TOOL_OPTIONS=\"-Dbasicauth=" + + string(basicAuthSecret.Data[corev1.BasicAuthUsernameKey]) + ":" + string(basicAuthSecret.Data[corev1.BasicAuthPasswordKey]) + + " -Dsolr.httpclient.builder.factory=org.apache.solr.client.solrj.impl.PreemptiveBasicAuthClientBuilderFactory\"" + } + GinkgoLogr.Info(toolOpts) + command := []string{ "solr", "api", @@ -489,6 +504,12 @@ func callSolrApiInPod(ctx context.Context, solrCloud *solrv1beta1.SolrCloud, htt apiPath, queryParamsString), } + if toolOpts != "" { + commandString := fmt.Sprintf("%s %s", toolOpts, strings.Join(command, " ")) + commandString = regexp.MustCompile(`\s+`).ReplaceAllString(strings.TrimSpace(commandString), " ") + + command = []string{"sh", "-c", fmt.Sprintf("%q", commandString)} + } return runExecForContainer(ctx, util.SolrNodeContainer, solrCloud.GetRandomSolrPodName(), solrCloud.Namespace, command) } @@ -655,17 +676,6 @@ func generateBaseSolrCloudWithSecurityJSON(replicas int) *solrv1beta1.SolrCloud solrCloud.Spec.SolrSecurity = &solrv1beta1.SolrSecurityOptions{} } - solrCloud.Spec.SolrSecurity.BootstrapSecurityJson = &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: solrCloud.Name + "-security-secret", - }, - Key: "security.json", - } - - solrCloud.Spec.SolrSecurity.AuthenticationType = "Basic" - - solrCloud.Spec.SolrSecurity.BasicAuthSecret = solrCloud.Name + "-basic-auth-secret" - return solrCloud } diff --git a/tests/scripts/manage_e2e_tests.sh b/tests/scripts/manage_e2e_tests.sh index 9af0c5bd..2da32c46 100755 --- a/tests/scripts/manage_e2e_tests.sh +++ b/tests/scripts/manage_e2e_tests.sh @@ -76,7 +76,7 @@ if [[ -z "${KUBERNETES_VERSION:-}" ]]; then KUBERNETES_VERSION="v1.26.6" fi if [[ -z "${SOLR_IMAGE:-}" ]]; then - SOLR_IMAGE="${SOLR_VERSION:-9.4.0}" + SOLR_IMAGE="${SOLR_VERSION:-9.8.1}" fi if [[ "${SOLR_IMAGE}" != *":"* ]]; then SOLR_IMAGE="solr:${SOLR_IMAGE}" From 0fb35032c8bd6c5ebf0c5f5814c123fe12a552e5 Mon Sep 17 00:00:00 2001 From: Houston Putman Date: Wed, 19 Mar 2025 10:47:50 -0500 Subject: [PATCH 3/5] Add both versions of permission for future and back compat --- controllers/util/solr_security_util.go | 3 +-- tests/e2e/solrcloud_security_json_test.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/controllers/util/solr_security_util.go b/controllers/util/solr_security_util.go index cbdb748d..417d6b9c 100644 --- a/controllers/util/solr_security_util.go +++ b/controllers/util/solr_security_util.go @@ -396,9 +396,8 @@ func generateSecurityJson(solrCloud *solr.SolrCloud) map[string][]byte { { "name": "k8s-metrics", "role":"k8s", "collection": null, "path":"/admin/metrics" }, { "name": "k8s-zk", "role":"k8s", "collection": null, "path":"/admin/zookeeper/status" }, { "name": "k8s-ping", "role":"k8s", "collection": "*", "path":"/admin/ping" }, - { "name": "k8s-replica-balancing", "role":"k8s", "collection": null, "path":"/api/cluster/replicas/balance" }, { "name": "k8s-replica-balancing", "role":"k8s", "collection": null, "path":"/____v2/cluster/replicas/balance" }, - { "name": "admin", "role":["admin","k8s"] }, + { "name": "collection-admin-edit", "role":"k8s" }, { "name": "read", "role":["admin","users","k8s"] }, { "name": "update", "role":["admin"] }, { "name": "security-read", "role": ["admin"] }, diff --git a/tests/e2e/solrcloud_security_json_test.go b/tests/e2e/solrcloud_security_json_test.go index d5476d4a..8bc86953 100644 --- a/tests/e2e/solrcloud_security_json_test.go +++ b/tests/e2e/solrcloud_security_json_test.go @@ -99,7 +99,7 @@ var _ = FDescribe("E2E - SolrCloud - Security JSON", func() { By("make sure scaleDown happens without a clusterLock and eventually the replicas are removed") // Once the scale down actually occurs, the statefulSet annotations should be removed very soon - expectStatefulSetWithChecksAndTimeout(ctx, solrCloud, solrCloud.StatefulSetName(), time.Second*30, time.Millisecond*500, func(g Gomega, found *appsv1.StatefulSet) { + expectStatefulSetWithChecksAndTimeout(ctx, solrCloud, solrCloud.StatefulSetName(), time.Minute*2, time.Millisecond*500, func(g Gomega, found *appsv1.StatefulSet) { g.Expect(found.Spec.Replicas).To(HaveValue(BeEquivalentTo(2)), "StatefulSet should eventually have 2 pods.") clusterOp, err := controllers.GetCurrentClusterOp(found) g.Expect(err).ToNot(HaveOccurred(), "Error occurred while finding clusterLock for SolrCloud") From 39188cb6b508b3b4fb30e39d67abb90b8454dbf2 Mon Sep 17 00:00:00 2001 From: Houston Putman Date: Wed, 19 Mar 2025 10:49:00 -0500 Subject: [PATCH 4/5] Undo unrelated zk init container stuff --- controllers/util/solr_util.go | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/controllers/util/solr_util.go b/controllers/util/solr_util.go index fefbe89f..478145a5 100644 --- a/controllers/util/solr_util.go +++ b/controllers/util/solr_util.go @@ -402,6 +402,16 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCl envVars = append(envVars, backupEnvVars...) } + // Only have a postStart command to create the chRoot, if it is not '/' (which does not need to be created) + var postStart *corev1.LifecycleHandler + if hasChroot { + postStart = &corev1.LifecycleHandler{ + Exec: &corev1.ExecAction{ + Command: []string{"sh", "-c", "solr zk ls ${ZK_CHROOT} -z ${ZK_SERVER} || solr zk mkroot ${ZK_CHROOT} -z ${ZK_SERVER}"}, + }, + } + } + // Default preStop hook preStop := &corev1.LifecycleHandler{ Exec: &corev1.ExecAction{ @@ -446,18 +456,7 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCl Value: strings.Join(allSolrOpts, " "), }) - var postStart *corev1.LifecycleHandler - initContainers, doesInitZk := generateSolrSetupInitContainers(solrCloud, solrCloudStatus, solrDataVolumeName, security) - if !doesInitZk && hasChroot { - // Only have a postStart command to create the chRoot, if: - // - it is not '/' (which does not need to be created) - // - there is not an initContainer that already does this - postStart = &corev1.LifecycleHandler{ - Exec: &corev1.ExecAction{ - Command: []string{"sh", "-c", "solr zk ls ${ZK_CHROOT} -z ${ZK_SERVER} || solr zk mkroot ${ZK_CHROOT} -z ${ZK_SERVER}"}, - }, - } - } + initContainers := generateSolrSetupInitContainers(solrCloud, solrCloudStatus, solrDataVolumeName, security) // Add user defined additional init containers if customPodOptions != nil && len(customPodOptions.InitContainers) > 0 { @@ -705,7 +704,7 @@ func MaintainPreservedStatefulSetFields(expected, found *appsv1.StatefulSet) { expected.Spec.Replicas = found.Spec.Replicas } -func generateSolrSetupInitContainers(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCloudStatus, solrDataVolumeName string, security *SecurityConfig) (containers []corev1.Container, doesInitZk bool) { +func generateSolrSetupInitContainers(solrCloud *solr.SolrCloud, solrCloudStatus *solr.SolrCloudStatus, solrDataVolumeName string, security *SecurityConfig) (containers []corev1.Container) { // The setup of the solr.xml will always be necessary volumeMounts := []corev1.VolumeMount{ { @@ -791,7 +790,6 @@ func generateSolrSetupInitContainers(solrCloud *solr.SolrCloud, solrCloudStatus if hasZKSetupContainer, zkSetupContainer := generateZKInteractionInitContainer(solrCloud, solrCloudStatus, security); hasZKSetupContainer { containers = append(containers, zkSetupContainer) - doesInitZk = true } // If the user has provided custom resources for the default init containers, use them @@ -805,7 +803,7 @@ func generateSolrSetupInitContainers(solrCloud *solr.SolrCloud, solrCloudStatus } } - return containers, doesInitZk + return containers } func createDefaultProbeHandlerForPath(probeScheme corev1.URIScheme, solrPodPort int, path string) corev1.ProbeHandler { From 3d8bc010f13e2c928b015ac6616dac1afc76e736 Mon Sep 17 00:00:00 2001 From: Houston Putman Date: Wed, 19 Mar 2025 11:49:13 -0500 Subject: [PATCH 5/5] Fix docs --- docs/solr-cloud/solr-cloud-crd.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/solr-cloud/solr-cloud-crd.md b/docs/solr-cloud/solr-cloud-crd.md index 3ef52a22..52027f01 100644 --- a/docs/solr-cloud/solr-cloud-crd.md +++ b/docs/solr-cloud/solr-cloud-crd.md @@ -1036,7 +1036,11 @@ Take a moment to review these authorization rules so that you're aware of the ro "name": "k8s-replica-balancing", "role": "k8s", "collection": null, - "path": "/api/cluster/replicas/balance" + "path": "/____v2/cluster/replicas/balance" + }, + { + "name": "collection-admin-edit", + "role": "k8s" }, { "name": "read", @@ -1171,7 +1175,12 @@ Users need to ensure their `security.json` contains the user supplied in the `ba /admin/metrics /admin/ping (for collection="*") /admin/zookeeper/status -/api/cluster/replicas/balance +/____v2/cluster/replicas/balance +``` + +And the following named permissions: +```aiignore +collection-admin-edit ``` _Tip: see the authorization rules defined by the default `security.json` as a guide for configuring access for the operator user_