blob: d4664fad4673f638a6aa4de994cdf688fb5969d3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package controllers
import (
"context"
solrv1beta1 "github.com/apache/solr-operator/api/v1beta1"
"github.com/apache/solr-operator/controllers/util"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var _ = FDescribe("SolrCloud controller - Storage", func() {
var (
solrCloud *solrv1beta1.SolrCloud
)
BeforeEach(func() {
solrCloud = &solrv1beta1.SolrCloud{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "default",
},
Spec: solrv1beta1.SolrCloudSpec{
ZookeeperRef: &solrv1beta1.ZookeeperRef{
ConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
InternalConnectionString: "host:7271",
},
},
SolrJavaMem: "-Xmx4G",
SolrOpts: "extra-opts",
CustomSolrKubeOptions: solrv1beta1.CustomSolrKubeOptions{
PodOptions: &solrv1beta1.PodOptions{
EnvVariables: extraVars,
PodSecurityContext: &testPodSecurityContext,
Volumes: extraVolumes,
Affinity: testAffinity,
Resources: testResources,
},
},
},
}
})
JustBeforeEach(func(ctx context.Context) {
By("creating the SolrCloud")
Expect(k8sClient.Create(ctx, solrCloud)).To(Succeed())
By("defaulting the missing SolrCloud values")
expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
g.Expect(found.WithDefaults(logger)).To(BeFalse(), "The SolrCloud spec should not need to be defaulted eventually")
})
})
AfterEach(func(ctx context.Context) {
cleanupTest(ctx, solrCloud)
})
FContext("Persistent Data - Custom name & labels", func() {
BeforeEach(func() {
solrCloud.Spec.StorageOptions = solrv1beta1.SolrDataStorageOptions{
PersistentStorage: &solrv1beta1.SolrPersistentDataStorageOptions{
VolumeReclaimPolicy: solrv1beta1.VolumeReclaimPolicyRetain,
PersistentVolumeClaimTemplate: solrv1beta1.PersistentVolumeClaimTemplate{
ObjectMeta: solrv1beta1.TemplateMeta{
Name: "other-data-1",
Labels: map[string]string{"base": "here"},
},
},
},
}
})
FIt("has the correct resources", func(ctx context.Context) {
By("testing the SolrCloud finalizers")
expectSolrCloudWithConsistentChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when the reclaim policy is Retain")
})
By("testing the Solr StatefulSet PVC Spec")
expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
g.Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(2), "Pod has wrong number of volumes")
g.Expect(found.Spec.VolumeClaimTemplates[0].Name).To(Equal(solrCloud.Spec.StorageOptions.PersistentStorage.PersistentVolumeClaimTemplate.ObjectMeta.Name), "Data volume claim doesn't exist")
g.Expect(found.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCTechnologyLabel]).To(Equal("solr-cloud"), "PVC Technology label doesn't match")
g.Expect(found.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCStorageLabel]).To(Equal("data"), "PVC Storage label doesn't match")
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.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.")
})
})
})
FContext("Persistent Data - PVC Delete on Reclaim", func() {
BeforeEach(func() {
solrCloud.Spec.StorageOptions = solrv1beta1.SolrDataStorageOptions{
PersistentStorage: &solrv1beta1.SolrPersistentDataStorageOptions{
VolumeReclaimPolicy: solrv1beta1.VolumeReclaimPolicyDelete,
PersistentVolumeClaimTemplate: solrv1beta1.PersistentVolumeClaimTemplate{
ObjectMeta: solrv1beta1.TemplateMeta{
Name: "other-data-2",
Labels: map[string]string{"base": "here"},
},
},
},
}
})
FIt("has the correct resources", func(ctx context.Context) {
By("testing the SolrCloud finalizers")
expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
g.Expect(found.GetFinalizers()).To(HaveLen(1), "The solrcloud should have no finalizers when the reclaim policy is Retain")
g.Expect(found.GetFinalizers()[0]).To(Equal(util.SolrStorageFinalizer), "Incorrect finalizer set for deleting persistent storage.")
})
By("testing the Solr StatefulSet PVC Spec")
expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
g.Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(2), "Pod has wrong number of volumes")
g.Expect(found.Spec.VolumeClaimTemplates[0].Name).To(Equal(solrCloud.Spec.StorageOptions.PersistentStorage.PersistentVolumeClaimTemplate.ObjectMeta.Name), "Data volume claim doesn't exist")
g.Expect(found.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCTechnologyLabel]).To(Equal("solr-cloud"), "PVC Technology label doesn't match")
g.Expect(found.Spec.VolumeClaimTemplates[0].Labels[util.SolrPVCStorageLabel]).To(Equal("data"), "PVC Storage label doesn't match")
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.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.")
})
By("ensuring that the PVCs are deleted and the finalizer is removed when the SolrCloud is deleted")
Expect(k8sClient.Delete(ctx, solrCloud)).To(Succeed(), "Could not delete solrCloud")
Eventually(func() error {
return k8sClient.Get(ctx, resourceKey(solrCloud, solrCloud.Name), &solrv1beta1.SolrCloud{})
}).Should(MatchError("solrclouds.solr.apache.org \""+solrCloud.Name+"\" not found"), "Cloud has not been deleted, thus the finalizers have not been removed from the object.")
})
})
FContext("Ephemeral Data - Default", func() {
BeforeEach(func() {
solrCloud.Spec.StorageOptions = solrv1beta1.SolrDataStorageOptions{
EphemeralStorage: &solrv1beta1.SolrEphemeralDataStorageOptions{},
}
})
FIt("has the correct resources", func(ctx context.Context) {
By("testing the SolrCloud finalizers")
expectSolrCloudWithConsistentChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when ephemeral storage is used")
})
By("testing the Solr StatefulSet Spec")
expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
g.Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(3), "Pod has wrong number of volumes")
g.Expect(found.Spec.VolumeClaimTemplates).To(HaveLen(0), "No data volume claims should exist when using ephemeral storage")
dataVolume := found.Spec.Template.Spec.Volumes[1]
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.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.")
})
})
})
FContext("Ephemeral Data - Nil EmptyDir", func() {
BeforeEach(func() {
solrCloud.Spec.StorageOptions = solrv1beta1.SolrDataStorageOptions{
EphemeralStorage: &solrv1beta1.SolrEphemeralDataStorageOptions{
EmptyDir: nil,
},
}
})
FIt("has the correct resources", func(ctx context.Context) {
By("testing the SolrCloud finalizers")
expectSolrCloudWithConsistentChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when ephemeral storage is used")
})
By("testing the Solr StatefulSet Spec")
expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
g.Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(3), "Pod has wrong number of volumes")
g.Expect(found.Spec.VolumeClaimTemplates).To(HaveLen(0), "No data volume claims should exist when using ephemeral storage")
dataVolume := found.Spec.Template.Spec.Volumes[1]
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.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.")
})
})
})
FContext("Ephemeral Data - EmptyDir with Specs", func() {
BeforeEach(func() {
solrCloud.Spec.StorageOptions = solrv1beta1.SolrDataStorageOptions{
EphemeralStorage: &solrv1beta1.SolrEphemeralDataStorageOptions{
EmptyDir: &corev1.EmptyDirVolumeSource{
Medium: corev1.StorageMediumMemory,
SizeLimit: resource.NewQuantity(1028*1028*1028, resource.BinarySI),
},
},
}
})
FIt("has the correct resources", func(ctx context.Context) {
By("testing the SolrCloud finalizers")
expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when ephemeral storage is used")
})
By("testing the Solr StatefulSet Spec")
expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
g.Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(3), "Pod has wrong number of volumes")
g.Expect(found.Spec.VolumeClaimTemplates).To(HaveLen(0), "No data volume claims should exist when using ephemeral storage")
dataVolume := found.Spec.Template.Spec.Volumes[1]
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(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.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.")
})
})
})
FContext("Ephemeral Data - HostPath with Specs", func() {
hostPathType := corev1.HostPathDirectoryOrCreate
BeforeEach(func() {
solrCloud.Spec.StorageOptions = solrv1beta1.SolrDataStorageOptions{
EphemeralStorage: &solrv1beta1.SolrEphemeralDataStorageOptions{
HostPath: &corev1.HostPathVolumeSource{
Path: "/tmp",
Type: &hostPathType,
},
},
}
})
FIt("has the correct resources", func(ctx context.Context) {
By("testing the SolrCloud finalizers")
expectSolrCloudWithConsistentChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when ephemeral storage is used")
})
By("testing the Solr StatefulSet Spec")
expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
g.Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(3), "Pod has wrong number of volumes")
g.Expect(found.Spec.VolumeClaimTemplates).To(HaveLen(0), "No data volume claims should exist when using ephemeral storage")
dataVolume := found.Spec.Template.Spec.Volumes[1]
g.Expect(dataVolume.EmptyDir).To(BeNil(), "The data volume should not be an empty-dir.")
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.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.")
})
})
})
FContext("Ephemeral Data - HostPath and EmptyDir (HostPath should win)", func() {
hostPathType := corev1.HostPathDirectoryOrCreate
BeforeEach(func() {
solrCloud.Spec.StorageOptions = solrv1beta1.SolrDataStorageOptions{
EphemeralStorage: &solrv1beta1.SolrEphemeralDataStorageOptions{
HostPath: &corev1.HostPathVolumeSource{
Path: "/tmp",
Type: &hostPathType,
},
EmptyDir: &corev1.EmptyDirVolumeSource{
Medium: corev1.StorageMediumMemory,
SizeLimit: resource.NewQuantity(1028*1028*1028, resource.BinarySI),
},
},
}
})
FIt("has the correct resources", func(ctx context.Context) {
By("testing the SolrCloud finalizers")
expectSolrCloudWithChecks(ctx, solrCloud, func(g Gomega, found *solrv1beta1.SolrCloud) {
g.Expect(found.GetFinalizers()).To(HaveLen(0), "The solrcloud should have no finalizers when ephemeral storage is used")
})
By("testing the Solr StatefulSet Spec")
expectStatefulSetWithChecks(ctx, solrCloud, solrCloud.StatefulSetName(), func(g Gomega, found *appsv1.StatefulSet) {
g.Expect(found.Spec.Template.Spec.Volumes).To(HaveLen(3), "Pod has wrong number of volumes")
g.Expect(found.Spec.VolumeClaimTemplates).To(HaveLen(0), "No data volume claims should exist when using ephemeral storage")
dataVolume := found.Spec.Template.Spec.Volumes[1]
g.Expect(dataVolume.EmptyDir).To(BeNil(), "The data volume should not be an empty-dir.")
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.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.")
})
})
})
})