This is an automated email from the ASF dual-hosted git repository.
janhoy 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 4c9b7db Solr xml subpath mount (#874)
4c9b7db is described below
commit 4c9b7db3c0f6c8d48aba6f0ecb305c66b538c7a6
Author: Jan Høydahl <[email protected]>
AuthorDate: Thu Nov 5 23:27:51 2026 +0100
Solr xml subpath mount (#874)
* Mount solr.xml via a subPath volumeMount instead of an initContainer
---
controllers/solrcloud_controller_backup_test.go | 53 ++++++++++++++----
.../solrcloud_controller_basic_auth_test.go | 2 +-
controllers/solrcloud_controller_storage_test.go | 14 ++---
controllers/solrcloud_controller_test.go | 50 ++++++++++++-----
controllers/solrcloud_controller_zk_test.go | 6 +--
controllers/util/solr_util.go | 62 +++++++++++-----------
.../solr-cloud/pages/custom-solr-config.adoc | 6 ++-
.../modules/upgrade-notes/pages/upgrade-notes.adoc | 15 +++++-
helm/solr-operator/Chart.yaml | 5 ++
9 files changed, 145 insertions(+), 68 deletions(-)
diff --git a/controllers/solrcloud_controller_backup_test.go
b/controllers/solrcloud_controller_backup_test.go
index 7f223fc..d7531c7 100644
--- a/controllers/solrcloud_controller_backup_test.go
+++ b/controllers/solrcloud_controller_backup_test.go
@@ -118,8 +118,8 @@ var _ = FDescribe("SolrCloud controller - Backup
Repositories", func() {
// Check Volumes
extraVolumes[0].DefaultContainerMount.Name =
extraVolumes[0].Name
-
Expect(statefulSet.Spec.Template.Spec.Containers[0].VolumeMounts).To(HaveLen(len(extraVolumes)+1),
"Container has wrong number of volumeMounts")
-
Expect(statefulSet.Spec.Template.Spec.Containers[0].VolumeMounts[1]).To(Equal(*extraVolumes[0].DefaultContainerMount),
"Additional Volume from podOptions not mounted into container properly.")
+
Expect(statefulSet.Spec.Template.Spec.Containers[0].VolumeMounts).To(HaveLen(len(extraVolumes)+2),
"Container has wrong number of volumeMounts")
+
Expect(statefulSet.Spec.Template.Spec.Containers[0].VolumeMounts[2]).To(Equal(*extraVolumes[0].DefaultContainerMount),
"Additional Volume from podOptions not mounted into container properly.")
Expect(statefulSet.Spec.Template.Spec.Volumes).To(HaveLen(len(extraVolumes)+3),
"Pod has wrong number of volumes")
Expect(statefulSet.Spec.Template.Spec.Volumes[3].Name).To(Equal(extraVolumes[0].Name),
"Additional Volume from podOptions not loaded into pod properly.")
Expect(statefulSet.Spec.Template.Spec.Volumes[3].VolumeSource).To(Equal(extraVolumes[0].Source),
"Additional Volume from podOptions not loaded into pod properly.")
@@ -182,8 +182,8 @@ var _ = FDescribe("SolrCloud controller - Backup
Repositories", func() {
// Check that no additional volumes have been
added
extraVolumes[0].DefaultContainerMount.Name =
extraVolumes[0].Name
-
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts).To(HaveLen(len(extraVolumes)+1),
"Container has wrong number of volumeMounts")
-
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[1]).To(Equal(*extraVolumes[0].DefaultContainerMount),
"Additional Volume from podOptions not mounted into container properly.")
+
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts).To(HaveLen(len(extraVolumes)+2),
"Container has wrong number of volumeMounts")
+
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[2]).To(Equal(*extraVolumes[0].DefaultContainerMount),
"Additional Volume from podOptions not mounted into container properly.")
g.Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(len(extraVolumes)+3),
"Pod has wrong number of volumes")
g.Expect(found.Spec.Template.Spec.Volumes[3].Name).To(Equal(extraVolumes[0].Name),
"Additional Volume from podOptions not loaded into pod properly.")
g.Expect(found.Spec.Template.Spec.Volumes[3].VolumeSource).To(Equal(extraVolumes[0].Source),
"Additional Volume from podOptions not loaded into pod properly.")
@@ -241,11 +241,11 @@ var _ = FDescribe("SolrCloud controller - Backup
Repositories", func() {
// Check that no additional volumes have been
added
extraVolumes[0].DefaultContainerMount.Name =
extraVolumes[0].Name
-
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts).To(HaveLen(len(extraVolumes)+2),
"Container has wrong number of volumeMounts")
-
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[1].Name).To(Equal("backup-repository-test-repo"),
"S3Credentials file volumeMount has wrong name.")
-
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[1].MountPath).To(Equal("/var/solr/data/backup-restore/test-repo/s3credential"),
"S3Credentials file volumeMount has wrong mount path.")
-
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[1].ReadOnly).To(BeTrue(),
"S3Credentials file volumeMount must be read-only.")
-
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[2]).To(Equal(*extraVolumes[0].DefaultContainerMount),
"Additional Volume from podOptions not mounted into container properly.")
+
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts).To(HaveLen(len(extraVolumes)+3),
"Container has wrong number of volumeMounts")
+
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[2].Name).To(Equal("backup-repository-test-repo"),
"S3Credentials file volumeMount has wrong name.")
+
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[2].MountPath).To(Equal("/var/solr/data/backup-restore/test-repo/s3credential"),
"S3Credentials file volumeMount has wrong mount path.")
+
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[2].ReadOnly).To(BeTrue(),
"S3Credentials file volumeMount must be read-only.")
+
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[3]).To(Equal(*extraVolumes[0].DefaultContainerMount),
"Additional Volume from podOptions not mounted into container properly.")
g.Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(len(extraVolumes)+4),
"Pod has wrong number of volumes")
g.Expect(found.Spec.Template.Spec.Volumes[3].Name).To(Equal("backup-repository-test-repo"),
"S3Credentials file volume has wrong name.")
g.Expect(found.Spec.Template.Spec.Volumes[3].VolumeSource.Secret).To(Not(BeNil()),
"S3Credentials file has to be loaded via a secret volume.")
@@ -278,6 +278,41 @@ var _ = FDescribe("SolrCloud controller - Backup
Repositories", func() {
})
})
+ FContext("Volume Repository", func() {
+ BeforeEach(func() {
+ solrCloud.Spec = solrv1beta1.SolrCloudSpec{
+ ZookeeperRef: &solrv1beta1.ZookeeperRef{
+ ConnectionInfo:
&solrv1beta1.ZookeeperConnectionInfo{
+ InternalConnectionString:
"host:7271",
+ },
+ },
+ BackupRepositories:
[]solrv1beta1.SolrBackupRepository{
+ {
+ Name: "volume-repo",
+ Volume:
&solrv1beta1.VolumeRepository{
+ Source:
corev1.VolumeSource{
+ EmptyDir:
&corev1.EmptyDirVolumeSource{},
+ },
+ },
+ },
+ },
+ }
+ })
+ FIt("has the correct resources", func(ctx context.Context) {
+ By("testing the Solr StatefulSet")
+ statefulSet := expectStatefulSet(ctx, solrCloud,
solrCloud.StatefulSetName())
+
+ By("making sure an initContainer is still created to
fix the permissions of the backup volume")
+
Expect(statefulSet.Spec.Template.Spec.InitContainers).To(HaveLen(1), "A volume
backup repository requires an initContainer to set up the volume permissions")
+ volumePrepContainer :=
statefulSet.Spec.Template.Spec.InitContainers[0]
+
Expect(volumePrepContainer.Name).To(Equal("volume-prep"), "Wrong name for the
volume prep initContainer")
+
Expect(volumePrepContainer.Command[2]).To(Not(ContainSubstring("solr.xml")),
"The volume prep initContainer should no longer copy the solr.xml, it is
mounted directly into the Solr home directory")
+
Expect(volumePrepContainer.Command[2]).To(ContainSubstring(util.VolumeRepoVolumeMountPath(&solrCloud.Spec.BackupRepositories[0])),
"The volume prep initContainer should set up the permissions of the backup
volume")
+ Expect(volumePrepContainer.VolumeMounts).To(HaveLen(1),
"The volume prep initContainer only needs the backup volume mounted")
+
Expect(volumePrepContainer.VolumeMounts[0].Name).To(Equal(util.RepoVolumeName(&solrCloud.Spec.BackupRepositories[0])),
"Wrong volume mounted in the volume prep initContainer")
+ })
+ })
+
FContext("Multiple Repositories - Annotations", func() {
BeforeEach(func() {
solrCloud.Spec = solrv1beta1.SolrCloudSpec{
diff --git a/controllers/solrcloud_controller_basic_auth_test.go
b/controllers/solrcloud_controller_basic_auth_test.go
index 1129560..4b04906 100644
--- a/controllers/solrcloud_controller_basic_auth_test.go
+++ b/controllers/solrcloud_controller_basic_auth_test.go
@@ -311,7 +311,6 @@ func expectBasicAuthConfigOnPodTemplateWithGomega(g Gomega,
solrCloud *solrv1bet
// if no user-provided auth secret, then check that security.json gets
bootstrapped correctly
if solrCloud.Spec.SolrSecurity.BasicAuthSecret == "" ||
solrCloud.Spec.SolrSecurity.BootstrapSecurityJson != nil {
// initContainers
- g.Expect(podTemplate.Spec.InitContainers).To(Not(BeEmpty()),
"The Solr Pod template requires an init container to bootstrap the
security.json")
var expInitContainer *corev1.Container = nil
for _, cnt := range podTemplate.Spec.InitContainers {
if cnt.Name == "setup-zk" {
@@ -321,6 +320,7 @@ func expectBasicAuthConfigOnPodTemplateWithGomega(g Gomega,
solrCloud *solrv1bet
}
if expectBootstrapSecret ||
solrCloud.Spec.SolrSecurity.BootstrapSecurityJson != nil {
+ g.Expect(expInitContainer).To(Not(BeNil()), "The Solr
Pod template requires a setup-zk init container to bootstrap the security.json")
// if the zookeeperRef has ACLs set, verify the env
vars were set correctly for this initContainer
allACL, _ := solrCloud.Spec.ZookeeperRef.GetACLs()
if allACL != nil {
diff --git a/controllers/solrcloud_controller_storage_test.go
b/controllers/solrcloud_controller_storage_test.go
index b60aa30..87a5c35 100644
--- a/controllers/solrcloud_controller_storage_test.go
+++ b/controllers/solrcloud_controller_storage_test.go
@@ -111,7 +111,7 @@ var _ = FDescribe("SolrCloud controller - Storage", func() {
g.Expect(found.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCInstanceLabel]).To(Equal(solrCloud.Name),
"PVC Instance label doesn't match")
g.Expect(found.Spec.VolumeClaimTemplates[0].Labels["base"]).To(Equal("here"),
"Additional PVC label doesn't match")
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name).To(Equal(found.Spec.VolumeClaimTemplates[0].Name),
"Custom PVC name not used in volume mount")
-
g.Expect(found.Spec.Template.Spec.InitContainers[0].VolumeMounts[1].Name).To(Equal(found.Spec.VolumeClaimTemplates[0].Name),
"Custom PVC name not used in volume mount")
+
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[1]).To(Equal(corev1.VolumeMount{Name:
"solr-xml", MountPath: "/var/solr/data/solr.xml", SubPath: util.SolrXmlFile,
ReadOnly: true}), "The solr.xml must be mounted into the Solr home directory
via a subPath")
g.Expect(found.Spec.Template.Spec.ReadinessGates).To(Not(ContainElement(corev1.PodReadinessGate{ConditionType:
util.SolrReplicasNotEvictedReadinessCondition})), "Persistent Data using Pods
should not have a readiness gate to evict replicas.")
g.Expect(found.Spec.Template.Spec.ReadinessGates).To(ContainElement(corev1.PodReadinessGate{ConditionType:
util.SolrIsNotStoppedReadinessCondition}), "All pods should contain the
isNotStopped readinessGate.")
})
@@ -155,7 +155,7 @@ var _ = FDescribe("SolrCloud controller - Storage", func() {
g.Expect(found.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCInstanceLabel]).To(Equal(solrCloud.Name),
"PVC Instance label doesn't match")
g.Expect(found.Spec.VolumeClaimTemplates[0].Labels["base"]).To(Equal("here"),
"Additional PVC label doesn't match")
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name).To(Equal(found.Spec.VolumeClaimTemplates[0].Name),
"Custom PVC name not used in volume mount")
-
g.Expect(found.Spec.Template.Spec.InitContainers[0].VolumeMounts[1].Name).To(Equal(found.Spec.VolumeClaimTemplates[0].Name),
"Custom PVC name not used in volume mount")
+
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[1]).To(Equal(corev1.VolumeMount{Name:
"solr-xml", MountPath: "/var/solr/data/solr.xml", SubPath: util.SolrXmlFile,
ReadOnly: true}), "The solr.xml must be mounted into the Solr home directory
via a subPath")
g.Expect(found.Spec.Template.Spec.ReadinessGates).To(Not(ContainElement(corev1.PodReadinessGate{ConditionType:
util.SolrReplicasNotEvictedReadinessCondition})), "Persistent Data using Pods
should not have a readiness gate to evict replicas.")
g.Expect(found.Spec.Template.Spec.ReadinessGates).To(ContainElement(corev1.PodReadinessGate{ConditionType:
util.SolrIsNotStoppedReadinessCondition}), "All pods should contain the
isNotStopped readinessGate.")
})
@@ -189,7 +189,7 @@ var _ = FDescribe("SolrCloud controller - Storage", func() {
g.Expect(dataVolume.EmptyDir).To(Not(BeNil()),
"The data volume should be an empty-dir.")
g.Expect(dataVolume.HostPath).To(BeNil(), "The
data volume should not be a hostPath volume.")
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name).To(Equal(dataVolume.Name),
"Ephemeral Data volume name not used in volume mount")
-
g.Expect(found.Spec.Template.Spec.InitContainers[0].VolumeMounts[1].Name).To(Equal(dataVolume.Name),
"Ephemeral Data volume name not used in volume mount")
+
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[1]).To(Equal(corev1.VolumeMount{Name:
"solr-xml", MountPath: "/var/solr/data/solr.xml", SubPath: util.SolrXmlFile,
ReadOnly: true}), "The solr.xml must be mounted into the Solr home directory
via a subPath")
g.Expect(found.Spec.Template.Spec.ReadinessGates).To(ContainElement(corev1.PodReadinessGate{ConditionType:
util.SolrReplicasNotEvictedReadinessCondition}), "Ephemeral Data using Pods
should have a readiness gate to evict replicas.")
g.Expect(found.Spec.Template.Spec.ReadinessGates).To(ContainElement(corev1.PodReadinessGate{ConditionType:
util.SolrIsNotStoppedReadinessCondition}), "All pods should contain the
isNotStopped readinessGate.")
})
@@ -218,7 +218,7 @@ var _ = FDescribe("SolrCloud controller - Storage", func() {
g.Expect(dataVolume.EmptyDir).To(Not(BeNil()),
"The data volume should be an empty-dir.")
g.Expect(dataVolume.HostPath).To(BeNil(), "The
data volume should not be a hostPath volume.")
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name).To(Equal(dataVolume.Name),
"Ephemeral Data volume name not used in volume mount")
-
g.Expect(found.Spec.Template.Spec.InitContainers[0].VolumeMounts[1].Name).To(Equal(dataVolume.Name),
"Ephemeral Data volume name not used in volume mount")
+
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[1]).To(Equal(corev1.VolumeMount{Name:
"solr-xml", MountPath: "/var/solr/data/solr.xml", SubPath: util.SolrXmlFile,
ReadOnly: true}), "The solr.xml must be mounted into the Solr home directory
via a subPath")
g.Expect(found.Spec.Template.Spec.ReadinessGates).To(ContainElement(corev1.PodReadinessGate{ConditionType:
util.SolrReplicasNotEvictedReadinessCondition}), "Ephemeral Data using Pods
should have a readiness gate to evict replicas.")
g.Expect(found.Spec.Template.Spec.ReadinessGates).To(ContainElement(corev1.PodReadinessGate{ConditionType:
util.SolrIsNotStoppedReadinessCondition}), "All pods should contain the
isNotStopped readinessGate.")
})
@@ -251,7 +251,7 @@ var _ = FDescribe("SolrCloud controller - Storage", func() {
g.Expect(dataVolume.HostPath).To(BeNil(), "The
data volume should not be a hostPath volume.")
g.Expect(dataVolume.EmptyDir).To(Equal(solrCloud.Spec.StorageOptions.EphemeralStorage.EmptyDir),
"The empty dir settings do not match with what was provided.")
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name).To(Equal(dataVolume.Name),
"Ephemeral Data volume name not used in volume mount")
-
g.Expect(found.Spec.Template.Spec.InitContainers[0].VolumeMounts[1].Name).To(Equal(dataVolume.Name),
"Ephemeral Data volume name not used in volume mount")
+
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[1]).To(Equal(corev1.VolumeMount{Name:
"solr-xml", MountPath: "/var/solr/data/solr.xml", SubPath: util.SolrXmlFile,
ReadOnly: true}), "The solr.xml must be mounted into the Solr home directory
via a subPath")
g.Expect(found.Spec.Template.Spec.ReadinessGates).To(ContainElement(corev1.PodReadinessGate{ConditionType:
util.SolrReplicasNotEvictedReadinessCondition}), "Ephemeral Data using Pods
should have a readiness gate to evict replicas.")
g.Expect(found.Spec.Template.Spec.ReadinessGates).To(ContainElement(corev1.PodReadinessGate{ConditionType:
util.SolrIsNotStoppedReadinessCondition}), "All pods should contain the
isNotStopped readinessGate.")
})
@@ -285,7 +285,7 @@ var _ = FDescribe("SolrCloud controller - Storage", func() {
g.Expect(dataVolume.HostPath).To(Not(BeNil()),
"The data volume should be a hostPath volume.")
g.Expect(dataVolume.HostPath).To(Equal(solrCloud.Spec.StorageOptions.EphemeralStorage.HostPath),
"The hostPath settings do not match with what was provided.")
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name).To(Equal(dataVolume.Name),
"Ephemeral Data volume name not used in volume mount")
-
g.Expect(found.Spec.Template.Spec.InitContainers[0].VolumeMounts[1].Name).To(Equal(dataVolume.Name),
"Ephemeral Data volume name not used in volume mount")
+
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[1]).To(Equal(corev1.VolumeMount{Name:
"solr-xml", MountPath: "/var/solr/data/solr.xml", SubPath: util.SolrXmlFile,
ReadOnly: true}), "The solr.xml must be mounted into the Solr home directory
via a subPath")
g.Expect(found.Spec.Template.Spec.ReadinessGates).To(ContainElement(corev1.PodReadinessGate{ConditionType:
util.SolrReplicasNotEvictedReadinessCondition}), "Ephemeral Data using Pods
should have a readiness gate to evict replicas.")
g.Expect(found.Spec.Template.Spec.ReadinessGates).To(ContainElement(corev1.PodReadinessGate{ConditionType:
util.SolrIsNotStoppedReadinessCondition}), "All pods should contain the
isNotStopped readinessGate.")
})
@@ -323,7 +323,7 @@ var _ = FDescribe("SolrCloud controller - Storage", func() {
g.Expect(dataVolume.HostPath).To(Not(BeNil()),
"The data volume should be a hostPath volume.")
g.Expect(dataVolume.HostPath).To(Equal(solrCloud.Spec.StorageOptions.EphemeralStorage.HostPath),
"The hostPath settings do not match with what was provided.")
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name).To(Equal(dataVolume.Name),
"Ephemeral Data volume name not used in volume mount")
-
g.Expect(found.Spec.Template.Spec.InitContainers[0].VolumeMounts[1].Name).To(Equal(dataVolume.Name),
"Ephemeral Data volume name not used in volume mount")
+
g.Expect(found.Spec.Template.Spec.Containers[0].VolumeMounts[1]).To(Equal(corev1.VolumeMount{Name:
"solr-xml", MountPath: "/var/solr/data/solr.xml", SubPath: util.SolrXmlFile,
ReadOnly: true}), "The solr.xml must be mounted into the Solr home directory
via a subPath")
g.Expect(found.Spec.Template.Spec.ReadinessGates).To(ContainElement(corev1.PodReadinessGate{ConditionType:
util.SolrReplicasNotEvictedReadinessCondition}), "Ephemeral Data using Pods
should have a readiness gate to evict replicas.")
g.Expect(found.Spec.Template.Spec.ReadinessGates).To(ContainElement(corev1.PodReadinessGate{ConditionType:
util.SolrIsNotStoppedReadinessCondition}), "All pods should contain the
isNotStopped readinessGate.")
})
diff --git a/controllers/solrcloud_controller_test.go
b/controllers/solrcloud_controller_test.go
index 42a7d64..abca59a 100644
--- a/controllers/solrcloud_controller_test.go
+++ b/controllers/solrcloud_controller_test.go
@@ -108,8 +108,8 @@ var _ = FDescribe("SolrCloud controller - General", func() {
Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(3), "Solr
StatefulSet requires the solr container plus the desired sidecars.")
Expect(statefulSet.Spec.Template.Spec.Containers[1:]).To(Equal(extraContainers1),
"Solr StatefulSet has incorrect extra containers")
-
Expect(statefulSet.Spec.Template.Spec.InitContainers).To(HaveLen(3), "Solr
StatefulSet requires a default initContainer plus the additional specified.")
-
Expect(statefulSet.Spec.Template.Spec.InitContainers[1:]).To(Equal(extraContainers2),
"Solr StatefulSet has incorrect extra initContainers")
+
Expect(statefulSet.Spec.Template.Spec.InitContainers).To(HaveLen(2), "Solr
StatefulSet should only have the additional initContainers specified, no
default ones are needed.")
+
Expect(statefulSet.Spec.Template.Spec.InitContainers).To(Equal(extraContainers2),
"Solr StatefulSet has incorrect extra initContainers")
// Check the update strategy
Expect(statefulSet.Spec.UpdateStrategy.Type).To(Equal(appsv1.OnDeleteStatefulSetStrategyType),
"Incorrect statefulset update strategy")
@@ -145,8 +145,8 @@ var _ = FDescribe("SolrCloud controller - General", func() {
Expect(statefulSet.Spec.Template.Spec.Containers[0].Resources.Requests).To(Equal(testResources.Requests),
"Resources.Requests is not the same as the one provided in podOptions")
Expect(statefulSet.Spec.Template.Spec.Containers[0].SecurityContext).To(BeNil(),
"SecurityContext is not the expected default value, 'nil'")
extraVolumes[0].DefaultContainerMount.Name =
extraVolumes[0].Name
-
Expect(statefulSet.Spec.Template.Spec.Containers[0].VolumeMounts).To(HaveLen(len(extraVolumes)+1),
"Container has wrong number of volumeMounts")
-
Expect(statefulSet.Spec.Template.Spec.Containers[0].VolumeMounts[1]).To(Equal(*extraVolumes[0].DefaultContainerMount),
"Additional Volume from podOptions not mounted into container properly.")
+
Expect(statefulSet.Spec.Template.Spec.Containers[0].VolumeMounts).To(HaveLen(len(extraVolumes)+2),
"Container has wrong number of volumeMounts")
+
Expect(statefulSet.Spec.Template.Spec.Containers[0].VolumeMounts[2]).To(Equal(*extraVolumes[0].DefaultContainerMount),
"Additional Volume from podOptions not mounted into container properly.")
Expect(statefulSet.Spec.Template.Spec.Volumes).To(HaveLen(len(extraVolumes)+3),
"Pod has wrong number of volumes")
Expect(statefulSet.Spec.Template.Spec.Volumes[3].Name).To(Equal(extraVolumes[0].Name),
"Additional Volume from podOptions not loaded into pod properly.")
Expect(statefulSet.Spec.Template.Spec.Volumes[3].VolumeSource).To(Equal(extraVolumes[0].Source),
"Additional Volume from podOptions not loaded into pod properly.")
@@ -277,6 +277,27 @@ var _ = FDescribe("SolrCloud controller - General", func()
{
Expect(statefulSet.Spec.Template.Annotations).To(HaveKeyWithValue(util.SolrXmlMd5Annotation,
fmt.Sprintf("%x", md5.Sum([]byte(configMap.Data[util.SolrXmlFile])))), "Wrong
solr.xml MD5 annotation in the pod template!")
+ By("testing that the solr.xml is mounted into the Solr
home directory, without an initContainer")
+ for _, initContainer := range
statefulSet.Spec.Template.Spec.InitContainers {
+
Expect(initContainer.Name).To(Not(Or(Equal("cp-solr-xml"),
Equal("volume-prep"))), "No volume prep initContainer is needed, the solr.xml
is mounted directly into the Solr home directory")
+ }
+ var solrXmlVol *corev1.Volume = nil
+ for _, vol := range
statefulSet.Spec.Template.Spec.Volumes {
+ if vol.Name == "solr-xml" {
+ solrXmlVol = &vol
+ break
+ }
+ }
+ Expect(solrXmlVol).To(Not(BeNil()), "Didn't find the
solr-xml volume in the pod template!")
+ Expect(solrXmlVol.ConfigMap).To(Not(BeNil()), "The
solr-xml volume should have a ConfigMap source")
+
Expect(solrXmlVol.ConfigMap.Name).To(Equal(solrCloud.ConfigMapName()), "The
solr-xml volume has the wrong configMap as its source")
+
Expect(statefulSet.Spec.Template.Spec.Containers[0].VolumeMounts).To(ContainElement(corev1.VolumeMount{
+ Name: "solr-xml",
+ MountPath: "/var/solr/data/solr.xml",
+ SubPath: util.SolrXmlFile,
+ ReadOnly: true,
+ }), "The solr.xml must be mounted into the Solr home
directory via a subPath, so that the rest of the directory is untouched")
+
Expect(statefulSet.Spec.Template.Spec.Containers).To(HaveLen(1), "Solr
StatefulSet requires a container.")
expectedEnvVars := map[string]string{
"ZK_HOST": "host:7271/test",
@@ -312,17 +333,12 @@ var _ = FDescribe("SolrCloud controller - General",
func() {
Expect(statefulSet.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities.Add).To(HaveLen(1))
Expect(statefulSet.Spec.Template.Spec.Containers[0].SecurityContext.Capabilities.Add[0]).To(Equal(corev1.Capability("someCapability")))
Expect(statefulSet.Spec.Template.Spec.InitContainers[0].Resources).To(Equal(testResources2),
"Incorrect initContainer[0] resources")
-
Expect(statefulSet.Spec.Template.Spec.InitContainers[1].Resources).To(Equal(testResources2),
"Incorrect initContainer[1] resources")
-
Expect(statefulSet.Spec.Template.Spec.InitContainers[2].Resources).ToNot(Equal(testResources2),
"Incorrect initContainer[2] resources, should not use the default override")
+
Expect(statefulSet.Spec.Template.Spec.InitContainers[1].Resources).ToNot(Equal(testResources2),
"Incorrect initContainer[1] resources, should not use the default override")
Expect(statefulSet.Spec.Template.Spec.InitContainers[0].SecurityContext).To(Not(BeNil()),
"InitContainer[0] should have security context")
Expect(statefulSet.Spec.Template.Spec.InitContainers[0].SecurityContext.RunAsNonRoot).To(PointTo(BeTrue()),
"Incorrect initContainer[0] security context runAsNonRoot")
Expect(statefulSet.Spec.Template.Spec.InitContainers[0].SecurityContext.Capabilities.Add).To(HaveLen(2),
"Incorrect number of capabilities in initContainer[0]")
Expect(statefulSet.Spec.Template.Spec.InitContainers[0].SecurityContext.Capabilities.Add).To(ContainElements(corev1.Capability("CHOWN"),
corev1.Capability("DAC_OVERRIDE")), "Incorrect capabilities in
initContainer[0]")
-
Expect(statefulSet.Spec.Template.Spec.InitContainers[1].SecurityContext).To(Not(BeNil()),
"InitContainer[1] should have security context")
-
Expect(statefulSet.Spec.Template.Spec.InitContainers[1].SecurityContext.RunAsNonRoot).To(PointTo(BeTrue()),
"Incorrect initContainer[1] security context runAsNonRoot")
-
Expect(statefulSet.Spec.Template.Spec.InitContainers[1].SecurityContext.Capabilities.Add).To(HaveLen(2),
"Incorrect number of capabilities in initContainer[1]")
-
Expect(statefulSet.Spec.Template.Spec.InitContainers[1].SecurityContext.Capabilities.Add).To(ContainElements(corev1.Capability("CHOWN"),
corev1.Capability("DAC_OVERRIDE")), "Incorrect capabilities in
initContainer[1]")
-
Expect(statefulSet.Spec.Template.Spec.InitContainers[2].SecurityContext).To(BeNil(),
"InitContainer[2] should not have custom security context since it's
user-provided")
+
Expect(statefulSet.Spec.Template.Spec.InitContainers[1].SecurityContext).To(BeNil(),
"InitContainer[1] should not have custom security context since it's
user-provided")
Expect(statefulSet.Spec.Template.Spec.Tolerations).To(Equal(testTolerations),
"Incorrect Tolerations for Pod")
Expect(statefulSet.Spec.Template.Spec.PriorityClassName).To(Equal(testPriorityClass),
"Incorrect Priority class name for Pod Spec")
Expect(statefulSet.Spec.Template.Spec.ImagePullSecrets).To(ConsistOf(append(testAdditionalImagePullSecrets,
corev1.LocalObjectReference{Name: testImagePullSecretName})), "Incorrect
imagePullSecrets")
@@ -769,13 +785,21 @@ var _ = FDescribe("SolrCloud controller - General",
func() {
g.Expect(customConfigVol.VolumeSource.ConfigMap, "solr-xml Volume should have a
ConfigMap source")
g.Expect(customConfigVol.VolumeSource.ConfigMap.Name).To(Equal(testCustomConfigMap),
"solr-xml has the wrong configMap as its source")
+ // The "solr-xml" volume is mounted twice in
this case, once for the solr.xml (via a subPath)
+ // and once for the directory containing the
log4j2.xml.
var logXmlVolMount *corev1.VolumeMount = nil
+ var solrXmlVolMount *corev1.VolumeMount = nil
for _, mount := range
found.Spec.Template.Spec.Containers[0].VolumeMounts {
if mount.Name == "solr-xml" {
- logXmlVolMount = &mount
- break
+ if mount.SubPath ==
util.SolrXmlFile {
+ solrXmlVolMount = &mount
+ } else {
+ logXmlVolMount = &mount
+ }
}
}
+ g.Expect(solrXmlVolMount).To(Not(BeNil()),
"Didn't find the solr-xml Volume mount")
+
g.Expect(solrXmlVolMount.MountPath).To(Equal("/var/solr/data/solr.xml"),
"solr-xml Volume mount has the wrong path")
g.Expect(logXmlVolMount).To(Not(BeNil()),
"Didn't find the log4j2-xml Volume mount")
expectedMountPath :=
fmt.Sprintf("/var/solr/%s", testCustomConfigMap)
g.Expect(logXmlVolMount.MountPath).To(Equal(expectedMountPath), "log4j2-xml
Volume mount has the wrong path")
diff --git a/controllers/solrcloud_controller_zk_test.go
b/controllers/solrcloud_controller_zk_test.go
index 7193420..1619e76 100644
--- a/controllers/solrcloud_controller_zk_test.go
+++ b/controllers/solrcloud_controller_zk_test.go
@@ -270,8 +270,8 @@ var _ = FDescribe("SolrCloud controller - Zookeeper",
func() {
Expect(statefulSet.Annotations).To(Equal(util.MergeLabelsOrAnnotations(testSSAnnotations,
expectedStatefulSetAnnotations)), "Wrong statefulSet annotations")
Expect(statefulSet.Spec.Template.Spec.Containers[0].Lifecycle.PostStart).To(BeNil(),
"There should be no postStart command, even if there is a chRoot for ZK")
Expect(statefulSet.Spec.Template.Spec.ServiceAccountName).To(BeEmpty(), "No
custom serviceAccountName specified, so the field should be empty.")
-
Expect(statefulSet.Spec.Template.Spec.InitContainers).To(HaveLen(2), "Solr
StatefulSet requires 2 init containers, one for the solr.xml and one for ZK.")
-
Expect(statefulSet.Spec.Template.Spec.InitContainers[1].Command).To(Equal([]string{"sh",
"-c", "solr zk ls ${ZK_CHROOT} -z ${ZK_SERVER} || solr zk mkroot ${ZK_CHROOT}
-z ${ZK_SERVER}; "}), "Wrong command for the ZK init container")
+
Expect(statefulSet.Spec.Template.Spec.InitContainers).To(HaveLen(1), "Solr
StatefulSet requires 1 init container, for ZK.")
+
Expect(statefulSet.Spec.Template.Spec.InitContainers[0].Command).To(Equal([]string{"sh",
"-c", "solr zk ls ${ZK_CHROOT} -z ${ZK_SERVER} || solr zk mkroot ${ZK_CHROOT}
-z ${ZK_SERVER}; "}), "Wrong command for the ZK init container")
// Check the update strategy
Expect(statefulSet.Spec.UpdateStrategy.Type).To(Equal(appsv1.OnDeleteStatefulSetStrategyType),
"Incorrect statefulset update strategy")
@@ -587,7 +587,7 @@ var _ = FDescribe("SolrCloud controller - Zookeeper",
func() {
"SOLR_OPTS": "$(SOLR_ZK_OPTS) " +
testSolrOpts,
"SOLR_TOOL_OPTS": "$(SOLR_ZK_OPTS) " +
testSolrOpts,
}
- testPodEnvVariables(expectedInitContainerEnvVars,
statefulSet.Spec.Template.Spec.InitContainers[1].Env)
+ testPodEnvVariables(expectedInitContainerEnvVars,
statefulSet.Spec.Template.Spec.InitContainers[0].Env)
})
})
})
diff --git a/controllers/util/solr_util.go b/controllers/util/solr_util.go
index 33def13..64c0b92 100644
--- a/controllers/util/solr_util.go
+++ b/controllers/util/solr_util.go
@@ -251,7 +251,13 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud,
solrCloudStatus *solr.SolrCl
})
}
- volumeMounts := []corev1.VolumeMount{{Name: solrDataVolumeName,
MountPath: "/var/solr/data"}}
+ volumeMounts := []corev1.VolumeMount{
+ {Name: solrDataVolumeName, MountPath: "/var/solr/data"},
+ // Only the solr.xml file is mounted into the Solr home
directory, via a subPath, so that
+ // the rest of the directory is untouched. subPath mounts do
not see ConfigMap updates,
+ // instead the solr.xml MD5 pod annotation set below restarts
the pods.
+ {Name: "solr-xml", MountPath: "/var/solr/data/" + SolrXmlFile,
SubPath: SolrXmlFile, ReadOnly: true},
+ }
// Add necessary specs for backupRepos
backupEnvVars := make([]corev1.EnvVar, 0)
@@ -457,7 +463,7 @@ func GenerateStatefulSet(solrCloud *solr.SolrCloud,
solrCloudStatus *solr.SolrCl
Value: strings.Join(allSolrOpts, " "),
})
- initContainers := generateSolrSetupInitContainers(solrCloud,
solrCloudStatus, solrDataVolumeName, security)
+ initContainers := generateSolrSetupInitContainers(solrCloud,
solrCloudStatus, security)
// Add user defined additional init containers
if customPodOptions != nil && len(customPodOptions.InitContainers) > 0 {
@@ -721,19 +727,11 @@ 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) {
- // The setup of the solr.xml will always be necessary
- volumeMounts := []corev1.VolumeMount{
- {
- Name: "solr-xml",
- MountPath: "/tmp",
- },
- {
- Name: solrDataVolumeName,
- MountPath: "/tmp-config",
- },
- }
- setupCommands := []string{"cp /tmp/solr.xml /tmp-config/solr.xml"}
+func generateSolrSetupInitContainers(solrCloud *solr.SolrCloud,
solrCloudStatus *solr.SolrCloudStatus, security *SecurityConfig) (containers
[]corev1.Container) {
+ // The solr.xml is mounted into the Solr home directory, so the only
thing left to prepare is
+ // the permissions of backup repository volumes.
+ var volumeMounts []corev1.VolumeMount
+ var setupCommands []string
// Figure out the solrUser and solrGroup to use
solrUser := DefaultSolrUser
@@ -787,23 +785,25 @@ func generateSolrSetupInitContainers(solrCloud
*solr.SolrCloud, solrCloudStatus
}
}
- volumePrepResources := corev1.ResourceList{
- corev1.ResourceCPU: *DefaultSolrVolumePrepInitContainerCPU,
- corev1.ResourceMemory:
*DefaultSolrVolumePrepInitContainerMemory,
- }
- volumePrepInitContainer := corev1.Container{
- Name: "cp-solr-xml",
- Image: solrCloud.Spec.BusyBoxImage.ToImageName(),
- ImagePullPolicy: solrCloud.Spec.BusyBoxImage.PullPolicy,
- Command: []string{"sh", "-c",
strings.Join(setupCommands, " && ")},
- VolumeMounts: volumeMounts,
- Resources: corev1.ResourceRequirements{
- Requests: volumePrepResources,
- Limits: volumePrepResources,
- },
- }
+ if len(setupCommands) > 0 {
+ volumePrepResources := corev1.ResourceList{
+ corev1.ResourceCPU:
*DefaultSolrVolumePrepInitContainerCPU,
+ corev1.ResourceMemory:
*DefaultSolrVolumePrepInitContainerMemory,
+ }
+ volumePrepInitContainer := corev1.Container{
+ Name: "volume-prep",
+ Image:
solrCloud.Spec.BusyBoxImage.ToImageName(),
+ ImagePullPolicy: solrCloud.Spec.BusyBoxImage.PullPolicy,
+ Command: []string{"sh", "-c",
strings.Join(setupCommands, " && ")},
+ VolumeMounts: volumeMounts,
+ Resources: corev1.ResourceRequirements{
+ Requests: volumePrepResources,
+ Limits: volumePrepResources,
+ },
+ }
- containers = append(containers, volumePrepInitContainer)
+ containers = append(containers, volumePrepInitContainer)
+ }
if hasZKSetupContainer, zkSetupContainer :=
generateZKInteractionInitContainer(solrCloud, solrCloudStatus, security);
hasZKSetupContainer {
containers = append(containers, zkSetupContainer)
diff --git a/docs/modules/solr-cloud/pages/custom-solr-config.adoc
b/docs/modules/solr-cloud/pages/custom-solr-config.adoc
index ca73387..eda4955 100644
--- a/docs/modules/solr-cloud/pages/custom-solr-config.adoc
+++ b/docs/modules/solr-cloud/pages/custom-solr-config.adoc
@@ -36,10 +36,12 @@ spec:
== Custom solr.xml
Solr pods load node-level configuration settings from
`/var/solr/data/solr.xml`.
-This important configuration file gets created by the `cp-solr-xml`
initContainer which bootstraps the `solr.home` directory on each pod before
starting the main container.
-The default `solr.xml` is mounted into the `cp-solr-xml` initContainer from a
ConfigMap named `<INSTANCE>-solrcloud-configmap` (where `<INSTANCE>` is the
name of your SolrCloud instance) created by the Solr operator.
+It is mounted there from a ConfigMap, via a `subPath` volumeMount so that only
this one file is mounted into the `solr.home` directory.
+The default `solr.xml` comes from a ConfigMap named
`<INSTANCE>-solrcloud-configmap` (where `<INSTANCE>` is the name of your
SolrCloud instance) created by the Solr operator.
+The mount only exists in the Solr container, so a sidecar or custom
initContainer that needs the `solr.xml` has to mount the ConfigMap itself.
NOTE: The data in the default ConfigMap is not editable! Any changes to the
`solr.xml` in the default ConfigMap created by the operator will be overwritten
during the next reconcile cycle.
+The `solr.xml` in a running pod is read-only, and ConfigMap changes only reach
pods through the restart described below.
Many of the specific values in `solr.xml` can be set using Java system
properties; for instance, the following setting controls the read timeout for
the HTTP client used by Solr's `HttpShardHandlerFactory`:
diff --git a/docs/modules/upgrade-notes/pages/upgrade-notes.adoc
b/docs/modules/upgrade-notes/pages/upgrade-notes.adoc
index 6a6d5f7..c4caa07 100644
--- a/docs/modules/upgrade-notes/pages/upgrade-notes.adoc
+++ b/docs/modules/upgrade-notes/pages/upgrade-notes.adoc
@@ -137,12 +137,23 @@ _Note that the Helm chart version does not contain a `v`
prefix, which the downl
=== v0.10.0
* The default Solr version has been upgraded from `8.11` to `9.11.0`.
-This only affects new resources, as default versions are hard-written to the
resources immediately.
-Note that Solr `>=9.10` will not start a node older than any Solr node already
running in the same cluster, so a rolling downgrade of Solr requires setting
the `SOLR_CLOUD_DOWNGRADE_ENABLED` envVar to `true` on the pods.
+This only affects newly created resources, since the operator writes the
resolved Solr version into the resource's spec on the first reconcile, so an
operator upgrade never changes the Solr version of a running cluster.
+Note that a Solr `>=9.10` node refuses to start if its `major.minor` version
is lower than the highest version already running in the same Solr cluster, so
a rolling downgrade of Solr requires setting the `SOLR_CLOUD_DOWNGRADE_ENABLED`
envVar to `true` on the pods.
* Operator logs now default to `json` encoding at `info` level.
Set the new helm value `development` to `true` (CLI flag `--zap-devel`) for
the previous `console` encoding at `debug` level.
+* The `cp-solr-xml` initContainer has been removed.
+The `solr.xml` is now mounted read-only into `/var/solr/data/solr.xml` from
the ConfigMap, via a `subPath` volumeMount, instead of being copied there
before Solr starts.
+** This changes the Solr pod spec, so the Solr pods are restarted when the
operator is upgraded.
+SolrClouds using `updateStrategy.method: Manual` are the exception, since
neither the operator nor Kubernetes deletes their pods: they keep running with
the old layout until you delete the pods yourself.
+** The `solr.xml` is now only visible inside the Solr container.
+Sidecars and custom initContainers that mount the data volume no longer get it
from there.
+With persistent or `hostPath` data storage they do still see the `solr.xml`
that `cp-solr-xml` copied onto the volume before the upgrade, but that copy is
stale and will never be updated again.
+Mount the `solr.xml` ConfigMap into such a container if it needs the current
configuration.
+** The permission fixing that `cp-solr-xml` also did for `volume`
`backupRepositories` has moved to a new `volume-prep` initContainer, which is
only created for SolrClouds that have a `volume` `backupRepository`.
+All other Solr pods now start without any volume prep initContainer.
+
=== v0.8.0
* *The minimum supported Solr version is now 8.11*
diff --git a/helm/solr-operator/Chart.yaml b/helm/solr-operator/Chart.yaml
index 1fc22f4..9b5e6ff 100644
--- a/helm/solr-operator/Chart.yaml
+++ b/helm/solr-operator/Chart.yaml
@@ -155,6 +155,11 @@ annotations:
url: https://solr.apache.org/docs/9_11_0/changes/Changes.html
- name: Solr Security News
url: https://solr.apache.org/security-news.html
+ - kind: changed
+ description: The `solr.xml` is now mounted into the Solr home directory
from the ConfigMap, via a subPath volumeMount, instead of being copied there by
the `cp-solr-xml` initContainer. That initContainer is only created for
`volume` backupRepositories now, renamed to `volume-prep`
+ links:
+ - name: Github Issue
+ url: https://github.com/apache/solr-operator/issues/161
- kind: fixed
description: A cluster operation (scale up/down, rolling update, replica
balancing, PVC expansion) that fails, e.g. because Solr is temporarily
unreachable, is now always retried instead of stalling the SolrCloud until
something else triggers a reconcile
links: