1
0
Fork 0
mirror of https://github.com/prometheus-operator/prometheus-operator.git synced 2025-04-16 01:06:27 +00:00
prometheus-operator/pkg/thanos/statefulset_test.go
Stavros Foteinopoulos d723855170
Add ability for custom DNSConfig and DNSPolicy (#3899)
---------

Signed-off-by: Stavros Foteinopoulos <stafot@gmail.com>
Co-authored-by: Simon Pasquier <spasquie@redhat.com>
2024-10-08 17:00:40 +02:00

1018 lines
30 KiB
Go

// Copyright 2020 The prometheus-operator Authors
//
// Licensed 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 thanos
import (
"fmt"
"strings"
"testing"
"github.com/kylelemons/godebug/pretty"
"github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"github.com/prometheus-operator/prometheus-operator/pkg/k8sutil"
"github.com/prometheus-operator/prometheus-operator/pkg/operator"
)
const (
containerName = "thanos-ruler"
)
var (
defaultTestConfig = Config{
ReloaderConfig: operator.DefaultReloaderTestConfig.ReloaderConfig,
ThanosDefaultBaseImage: operator.DefaultThanosBaseImage,
}
emptyQueryEndpoints = []string{""}
)
func TestStatefulSetLabelingAndAnnotations(t *testing.T) {
labels := map[string]string{
"testlabel": "testlabelvalue",
"managed-by": "prometheus-operator",
}
annotations := map[string]string{
"testannotation": "testannotationvalue",
"kubectl.kubernetes.io/last-applied-configuration": "something",
"kubectl.kubernetes.io/something": "something",
}
// kubectl annotations must not be on the statefulset so kubectl does
// not manage the generated object
expectedAnnotations := map[string]string{
"prometheus-operator-input-hash": "",
"testannotation": "testannotationvalue",
}
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
Annotations: annotations,
},
Spec: monitoringv1.ThanosRulerSpec{QueryEndpoints: emptyQueryEndpoints},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
require.Equal(t, labels, sset.Labels, pretty.Compare(labels, sset.Labels))
require.Equal(t, expectedAnnotations, sset.Annotations, pretty.Compare(expectedAnnotations, sset.Annotations))
}
func TestPodLabelsAnnotations(t *testing.T) {
annotations := map[string]string{
"testannotation": "testvalue",
}
labels := map[string]string{
"testlabel": "testvalue",
}
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
ObjectMeta: metav1.ObjectMeta{},
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
PodMetadata: &monitoringv1.EmbeddedObjectMetadata{
Annotations: annotations,
Labels: labels,
},
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
valLabel := sset.Spec.Template.ObjectMeta.Labels["testlabel"]
require.Equal(t, "testvalue", valLabel)
valAnnotations := sset.Spec.Template.ObjectMeta.Annotations["testannotation"]
require.Equal(t, "testvalue", valAnnotations)
}
func TestThanosDefaultBaseImageFlag(t *testing.T) {
thanosBaseImageConfig := Config{
ReloaderConfig: defaultTestConfig.ReloaderConfig,
ThanosDefaultBaseImage: "nondefaultuseflag/quay.io/thanos/thanos",
}
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
Spec: monitoringv1.ThanosRulerSpec{QueryEndpoints: emptyQueryEndpoints},
}, thanosBaseImageConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
image := sset.Spec.Template.Spec.Containers[0].Image
expected := "nondefaultuseflag/quay.io/thanos/thanos" + ":" + operator.DefaultThanosVersion
require.Equal(t, expected, image)
}
func TestStatefulSetVolumes(t *testing.T) {
expected := &appsv1.StatefulSet{
Spec: appsv1.StatefulSetSpec{
Template: v1.PodTemplateSpec{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
VolumeMounts: []v1.VolumeMount{
{
Name: "tls-assets",
ReadOnly: true,
MountPath: "/etc/thanos/certs",
},
{
Name: "web-config",
ReadOnly: true,
MountPath: "/etc/thanos/web_config/web-config.yaml",
SubPath: "web-config.yaml",
},
{
Name: "thanos-ruler-foo-data",
ReadOnly: false,
MountPath: "/thanos/data",
SubPath: "",
},
{
Name: "rules-configmap-one",
ReadOnly: false,
MountPath: "/etc/thanos/rules/rules-configmap-one",
SubPath: "",
},
{
Name: "additional-volume",
ReadOnly: false,
MountPath: "/thanos/additional-volume",
SubPath: "",
},
},
},
},
Volumes: []v1.Volume{
{
Name: "tls-assets",
VolumeSource: v1.VolumeSource{
Projected: &v1.ProjectedVolumeSource{
Sources: []v1.VolumeProjection{},
},
},
},
{
Name: "web-config",
VolumeSource: v1.VolumeSource{
Secret: &v1.SecretVolumeSource{
SecretName: "thanos-ruler-foo-web-config",
},
},
},
{
Name: "rules-configmap-one",
VolumeSource: v1.VolumeSource{
ConfigMap: &v1.ConfigMapVolumeSource{
LocalObjectReference: v1.LocalObjectReference{
Name: "rules-configmap-one",
},
},
},
},
{
Name: "thanos-ruler-foo-data",
VolumeSource: v1.VolumeSource{
EmptyDir: &v1.EmptyDirVolumeSource{
Medium: "",
},
},
},
{
Name: "additional-volume",
VolumeSource: v1.VolumeSource{
EmptyDir: &v1.EmptyDirVolumeSource{
Medium: "",
},
},
},
},
},
},
},
}
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
Volumes: []v1.Volume{
{
Name: "additional-volume",
VolumeSource: v1.VolumeSource{
EmptyDir: &v1.EmptyDirVolumeSource{
Medium: "",
},
},
},
},
VolumeMounts: []v1.VolumeMount{
{
Name: "additional-volume",
ReadOnly: false,
MountPath: "/thanos/additional-volume",
SubPath: "",
},
},
},
}, defaultTestConfig, []string{"rules-configmap-one"}, "", &operator.ShardedSecret{})
require.NoError(t, err)
require.Equal(t, expected.Spec.Template.Spec.Volumes, sset.Spec.Template.Spec.Volumes)
require.Equal(t, expected.Spec.Template.Spec.Containers[0].VolumeMounts, sset.Spec.Template.Spec.Containers[0].VolumeMounts)
}
func TestTracing(t *testing.T) {
const (
secretName = "thanos-tracing-config-secret"
secretKey = "config.yaml"
volumeName = "tracing-config"
mountPath = "/etc/thanos/config/tracing-config"
fullPath = "/etc/thanos/config/tracing-config/config.yaml"
)
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
ObjectMeta: metav1.ObjectMeta{},
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
TracingConfig: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: secretName,
},
Key: secretKey,
},
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
require.Equal(t, containerName, sset.Spec.Template.Spec.Containers[0].Name)
{
var containsVolume bool
for _, volume := range sset.Spec.Template.Spec.Volumes {
if volume.Name == volumeName {
if volume.Secret.SecretName == secretName && volume.Secret.Items[0].Key == secretKey && volume.Secret.Items[0].Path == secretKey {
containsVolume = true
break
}
}
}
require.True(t, containsVolume)
}
{
var containsVolumeMount bool
for _, volumeMount := range sset.Spec.Template.Spec.Containers[0].VolumeMounts {
if volumeMount.Name == volumeName && volumeMount.MountPath == mountPath {
containsVolumeMount = true
}
}
require.True(t, containsVolumeMount)
}
{
const expectedArg = "--tracing.config-file=" + fullPath
var containsArg bool
for _, arg := range sset.Spec.Template.Spec.Containers[0].Args {
if arg == expectedArg {
containsArg = true
break
}
}
require.True(t, containsArg)
}
}
func TestTracingFile(t *testing.T) {
testPath := "/vault/secret/config.yaml"
testKey := "thanos-tracing-config-secret"
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
ObjectMeta: metav1.ObjectMeta{},
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
TracingConfigFile: testPath,
TracingConfig: &v1.SecretKeySelector{
Key: testKey,
},
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
{
var containsArgConfigFile, containsArgConfig bool
expectedArgConfigFile := "--tracing.config-file=" + testPath
expectedArgConfig := "--tracing.config=$(TRACING_CONFIG)"
for _, container := range sset.Spec.Template.Spec.Containers {
if container.Name == "thanos-ruler" {
for _, arg := range container.Args {
if arg == expectedArgConfigFile {
containsArgConfigFile = true
}
if arg == expectedArgConfig {
containsArgConfig = true
}
}
}
}
require.True(t, containsArgConfigFile)
require.False(t, containsArgConfig)
}
}
func TestObjectStorage(t *testing.T) {
const (
secretName = "thanos-objstore-config-secret"
secretKey = "config.yaml"
volumeName = "objstorage-config"
mountPath = "/etc/thanos/config/objstorage-config"
fullPath = "/etc/thanos/config/objstorage-config/config.yaml"
)
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
ObjectMeta: metav1.ObjectMeta{},
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
ObjectStorageConfig: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: secretName,
},
Key: secretKey,
},
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
require.Equal(t, containerName, sset.Spec.Template.Spec.Containers[0].Name)
{
var containsVolume bool
for _, volume := range sset.Spec.Template.Spec.Volumes {
if volume.Name == volumeName {
if volume.Secret.SecretName == secretName && volume.Secret.Items[0].Key == secretKey && volume.Secret.Items[0].Path == secretKey {
containsVolume = true
break
}
}
}
require.True(t, containsVolume)
}
{
var containsVolumeMount bool
for _, volumeMount := range sset.Spec.Template.Spec.Containers[0].VolumeMounts {
if volumeMount.Name == volumeName && volumeMount.MountPath == mountPath {
containsVolumeMount = true
}
}
require.True(t, containsVolumeMount)
}
{
const expectedArg = "--objstore.config-file=" + fullPath
var containsArg bool
for _, arg := range sset.Spec.Template.Spec.Containers[0].Args {
if arg == expectedArg {
containsArg = true
break
}
}
require.True(t, containsArg)
}
}
func TestObjectStorageFile(t *testing.T) {
testPath := "/vault/secret/config.yaml"
testKey := "thanos-objstore-config-secret"
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
ObjectMeta: metav1.ObjectMeta{},
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
ObjectStorageConfigFile: &testPath,
ObjectStorageConfig: &v1.SecretKeySelector{
Key: testKey,
},
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
{
var containsArgConfigFile, containsArgConfig bool
expectedArgConfigFile := "--objstore.config-file=" + testPath
expectedArgConfig := "--objstore.config=$(OBJSTORE_CONFIG)"
for _, container := range sset.Spec.Template.Spec.Containers {
if container.Name == "thanos-ruler" {
for _, arg := range container.Args {
if arg == expectedArgConfigFile {
containsArgConfigFile = true
}
if arg == expectedArgConfig {
containsArgConfig = true
}
}
}
}
require.True(t, containsArgConfigFile)
require.False(t, containsArgConfig)
}
}
func TestAlertRelabel(t *testing.T) {
const (
secretName = "thanos-alertrelabel-config-secret"
secretKey = "config.yaml"
volumeName = "alertrelabel-config"
mountPath = "/etc/thanos/config/alertrelabel-config"
fullPath = "/etc/thanos/config/alertrelabel-config/config.yaml"
)
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
ObjectMeta: metav1.ObjectMeta{},
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
AlertRelabelConfigs: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: secretName,
},
Key: secretKey,
},
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
require.Equal(t, containerName, sset.Spec.Template.Spec.Containers[0].Name)
{
var containsVolume bool
for _, volume := range sset.Spec.Template.Spec.Volumes {
if volume.Name == volumeName {
if volume.Secret.SecretName == secretName && volume.Secret.Items[0].Key == secretKey && volume.Secret.Items[0].Path == secretKey {
containsVolume = true
break
}
}
}
require.True(t, containsVolume)
}
{
var containsVolumeMount bool
for _, volumeMount := range sset.Spec.Template.Spec.Containers[0].VolumeMounts {
if volumeMount.Name == volumeName && volumeMount.MountPath == mountPath {
containsVolumeMount = true
}
}
require.True(t, containsVolumeMount)
}
{
const expectedArg = "--alert.relabel-config-file=" + fullPath
var containsArg bool
for _, arg := range sset.Spec.Template.Spec.Containers[0].Args {
if arg == expectedArg {
containsArg = true
break
}
}
require.True(t, containsArg)
}
}
func TestAlertRelabelFile(t *testing.T) {
testPath := "/vault/secret/config.yaml"
testKey := "thanos-alertrelabel-config-secret"
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
ObjectMeta: metav1.ObjectMeta{},
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
AlertRelabelConfigFile: &testPath,
AlertRelabelConfigs: &v1.SecretKeySelector{
Key: testKey,
},
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
{
var containsArgConfigFile, containsArgConfigs bool
expectedArgConfigFile := "--alert.relabel-config-file=" + testPath
expectedArgConfigs := "--alert.relabel-config=$(ALERT_RELABEL_CONFIG)"
for _, container := range sset.Spec.Template.Spec.Containers {
if container.Name == "thanos-ruler" {
for _, arg := range container.Args {
if arg == expectedArgConfigFile {
containsArgConfigFile = true
}
if arg == expectedArgConfigs {
containsArgConfigs = true
}
}
}
}
require.True(t, containsArgConfigFile)
require.False(t, containsArgConfigs)
}
}
func TestLabelsAndAlertDropLabels(t *testing.T) {
labelPrefix := "--label="
alertDropLabelPrefix := "--alert.label-drop="
tests := []struct {
Name string
Labels map[string]string
AlertDropLabels []string
ExpectedLabels []string
ExpectedAlertDropLabels []string
}{
{
Name: "thanos_ruler_replica-is-set",
Labels: nil,
AlertDropLabels: nil,
ExpectedLabels: []string{`thanos_ruler_replica="$(POD_NAME)"`},
ExpectedAlertDropLabels: []string{"thanos_ruler_replica"},
},
{
Name: "alert-drop-labels-are-set",
Labels: nil,
AlertDropLabels: []string{"test"},
ExpectedLabels: []string{`thanos_ruler_replica="$(POD_NAME)"`},
ExpectedAlertDropLabels: []string{"thanos_ruler_replica", "test"},
},
{
Name: "labels-are-set",
Labels: map[string]string{
"test": "test",
},
AlertDropLabels: nil,
ExpectedLabels: []string{`thanos_ruler_replica="$(POD_NAME)"`, `test="test"`},
ExpectedAlertDropLabels: []string{"thanos_ruler_replica"},
},
{
Name: "both-alert-drop-labels-and-labels-are-set",
Labels: map[string]string{
"test": "test",
},
AlertDropLabels: []string{"test"},
ExpectedLabels: []string{`thanos_ruler_replica="$(POD_NAME)"`, `test="test"`},
ExpectedAlertDropLabels: []string{"thanos_ruler_replica", "test"},
},
{
Name: "assert-labels-order-is-constant",
Labels: map[string]string{
"thanos_ruler_replica": "$(POD_NAME)",
"test": "test",
"test4": "test4",
"test1": "test1",
"test2": "test2",
"test3": "test3",
"foo": "bar",
"bob": "alice",
},
AlertDropLabels: []string{"test", "aaa", "foo", "bar", "foo1", "foo2", "foo3"},
ExpectedLabels: []string{`thanos_ruler_replica="$(POD_NAME)"`, `bob="alice"`, `foo="bar"`, `test="test"`, `test1="test1"`, `test2="test2"`, `test3="test3"`, `test4="test4"`, `thanos_ruler_replica="$(POD_NAME)"`},
ExpectedAlertDropLabels: []string{"thanos_ruler_replica", "test", "aaa", "foo", "bar", "foo1", "foo2", "foo3"},
},
}
for _, tc := range tests {
t.Run(tc.Name, func(t *testing.T) {
actualLabels := []string{}
actualDropLabels := []string{}
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
ObjectMeta: metav1.ObjectMeta{},
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
Labels: tc.Labels,
AlertDropLabels: tc.AlertDropLabels,
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
ruler := sset.Spec.Template.Spec.Containers[0]
require.Equal(t, "thanos-ruler", ruler.Name)
for _, arg := range ruler.Args {
if strings.HasPrefix(arg, labelPrefix) {
actualLabels = append(actualLabels, strings.TrimPrefix(arg, labelPrefix))
} else if strings.HasPrefix(arg, alertDropLabelPrefix) {
actualDropLabels = append(actualDropLabels, strings.TrimPrefix(arg, alertDropLabelPrefix))
}
}
require.Equal(t, tc.ExpectedLabels, actualLabels)
require.Equal(t, tc.ExpectedAlertDropLabels, actualDropLabels)
})
}
}
func TestAdditionalContainers(t *testing.T) {
// The base to compare everything against
baseSet, err := makeStatefulSet(&monitoringv1.ThanosRuler{
Spec: monitoringv1.ThanosRulerSpec{QueryEndpoints: emptyQueryEndpoints},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
// Add an extra container
addSset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
Containers: []v1.Container{
{
Name: "extra-container",
},
},
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
require.Len(t, addSset.Spec.Template.Spec.Containers, len(baseSet.Spec.Template.Spec.Containers)+1)
// Adding a new container with the same name results in a merge and just one container
const existingContainerName = "thanos-ruler"
const containerImage = "madeUpContainerImage"
modSset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
Containers: []v1.Container{
{
Name: existingContainerName,
Image: containerImage,
},
},
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
require.Equal(t, len(baseSet.Spec.Template.Spec.Containers), len(modSset.Spec.Template.Spec.Containers))
// Check that adding a container with an existing name results in a single patched container.
for _, c := range modSset.Spec.Template.Spec.Containers {
require.False(t, c.Name == existingContainerName && c.Image != containerImage)
}
}
func TestRetention(t *testing.T) {
for _, tc := range []struct {
specRetention monitoringv1.Duration
expectedRetention monitoringv1.Duration
}{
{"1d", "1d"},
} {
t.Run(string(tc.specRetention), func(t *testing.T) {
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
Spec: monitoringv1.ThanosRulerSpec{
Retention: tc.specRetention,
QueryEndpoints: emptyQueryEndpoints,
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
trArgs := sset.Spec.Template.Spec.Containers[0].Args
expectedRetentionArg := fmt.Sprintf("--tsdb.retention=%s", tc.expectedRetention)
found := false
for _, flag := range trArgs {
if flag == expectedRetentionArg {
found = true
break
}
}
require.True(t, found)
})
}
}
func TestPodTemplateConfig(t *testing.T) {
nodeSelector := map[string]string{
"foo": "bar",
}
affinity := v1.Affinity{
NodeAffinity: &v1.NodeAffinity{},
PodAffinity: &v1.PodAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []v1.WeightedPodAffinityTerm{
{
PodAffinityTerm: v1.PodAffinityTerm{
Namespaces: []string{"foo"},
},
Weight: 100,
},
},
},
PodAntiAffinity: &v1.PodAntiAffinity{},
}
tolerations := []v1.Toleration{
{
Key: "key",
},
}
userid := int64(1234)
securityContext := v1.PodSecurityContext{
RunAsUser: &userid,
}
priorityClassName := "foo"
serviceAccountName := "thanos-ruler-sa"
hostAliases := []monitoringv1.HostAlias{
{
Hostnames: []string{"foo.com"},
IP: "1.1.1.1",
},
}
imagePullSecrets := []v1.LocalObjectReference{
{
Name: "registry-secret",
},
}
imagePullPolicy := v1.PullAlways
additionalArgs := []monitoringv1.Argument{
{Name: "additional.arg", Value: "additional-arg-value"},
}
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
ObjectMeta: metav1.ObjectMeta{},
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
NodeSelector: nodeSelector,
Affinity: &affinity,
Tolerations: tolerations,
SecurityContext: &securityContext,
PriorityClassName: priorityClassName,
ServiceAccountName: serviceAccountName,
HostAliases: hostAliases,
ImagePullSecrets: imagePullSecrets,
ImagePullPolicy: imagePullPolicy,
AdditionalArgs: additionalArgs,
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
require.Equal(t, nodeSelector, sset.Spec.Template.Spec.NodeSelector)
require.Equal(t, affinity, *sset.Spec.Template.Spec.Affinity)
require.Equal(t, tolerations, sset.Spec.Template.Spec.Tolerations)
require.Equal(t, securityContext, *sset.Spec.Template.Spec.SecurityContext)
require.Equal(t, priorityClassName, sset.Spec.Template.Spec.PriorityClassName)
require.Equal(t, serviceAccountName, sset.Spec.Template.Spec.ServiceAccountName)
require.Equal(t, len(hostAliases), len(sset.Spec.Template.Spec.HostAliases))
require.Equal(t, imagePullSecrets, sset.Spec.Template.Spec.ImagePullSecrets)
for _, initContainer := range sset.Spec.Template.Spec.InitContainers {
require.Equal(t, imagePullPolicy, initContainer.ImagePullPolicy)
}
for _, container := range sset.Spec.Template.Spec.Containers {
require.Equal(t, imagePullPolicy, container.ImagePullPolicy)
}
require.Contains(t, sset.Spec.Template.Spec.Containers[0].Args[len(sset.Spec.Template.Spec.Containers[0].Args)-1], "--additional.arg=additional-arg-value")
require.Equal(t, "rule", sset.Spec.Template.Spec.Containers[0].Args[0])
}
func TestExternalQueryURL(t *testing.T) {
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
Spec: monitoringv1.ThanosRulerSpec{
AlertQueryURL: "https://example.com/",
QueryEndpoints: emptyQueryEndpoints,
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
require.Equal(t, containerName, sset.Spec.Template.Spec.Containers[0].Name)
const expectedArg = "--alert.query-url=https://example.com/"
for _, arg := range sset.Spec.Template.Spec.Containers[0].Args {
if arg == expectedArg {
return
}
}
require.FailNow(t, "Thanos ruler is missing expected argument: %s", expectedArg)
}
func TestSidecarResources(t *testing.T) {
operator.TestSidecarsResources(t, func(reloaderConfig operator.ContainerConfig) *appsv1.StatefulSet {
testConfig := Config{
ReloaderConfig: reloaderConfig,
ThanosDefaultBaseImage: operator.DefaultThanosBaseImage,
}
tr := &monitoringv1.ThanosRuler{
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
},
}
// thanos-ruler sset will only have a configReloader side car
// if it has to mount a ConfigMap
sset, err := makeStatefulSet(tr, testConfig, []string{"my-configmap"}, "", &operator.ShardedSecret{})
require.NoError(t, err)
return sset
})
}
func TestStatefulSetMinReadySeconds(t *testing.T) {
tr := monitoringv1.ThanosRuler{
Spec: monitoringv1.ThanosRulerSpec{
MinReadySeconds: nil,
QueryEndpoints: emptyQueryEndpoints,
},
}
statefulSet, err := makeStatefulSetSpec(&tr, defaultTestConfig, nil, &operator.ShardedSecret{})
require.NoError(t, err)
require.Equal(t, int32(0), statefulSet.MinReadySeconds)
// assert set correctly if not nil
var expect uint32 = 5
tr.Spec.MinReadySeconds = &expect
statefulSet, err = makeStatefulSetSpec(&tr, defaultTestConfig, nil, &operator.ShardedSecret{})
require.NoError(t, err)
require.Equal(t, int32(expect), statefulSet.MinReadySeconds)
}
func TestStatefulSetServiceName(t *testing.T) {
tr := monitoringv1.ThanosRuler{
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
},
}
// assert set correctly
expect := governingServiceName
spec, err := makeStatefulSetSpec(&tr, defaultTestConfig, nil, &operator.ShardedSecret{})
require.NoError(t, err)
require.Equal(t, expect, spec.ServiceName)
}
func TestStatefulSetPVC(t *testing.T) {
labels := map[string]string{
"testlabel": "testlabelvalue",
}
annotations := map[string]string{
"testannotation": "testannotationvalue",
}
storageClass := "storageclass"
pvc := monitoringv1.EmbeddedPersistentVolumeClaim{
EmbeddedObjectMetadata: monitoringv1.EmbeddedObjectMetadata{
Annotations: annotations,
},
Spec: v1.PersistentVolumeClaimSpec{
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
StorageClassName: &storageClass,
},
}
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
Annotations: annotations,
},
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
Storage: &monitoringv1.StorageSpec{
VolumeClaimTemplate: pvc,
},
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
ssetPvc := sset.Spec.VolumeClaimTemplates[0]
require.Equal(t, *pvc.Spec.StorageClassName, *ssetPvc.Spec.StorageClassName)
}
func TestStatefulEmptyDir(t *testing.T) {
labels := map[string]string{
"testlabel": "testlabelvalue",
}
annotations := map[string]string{
"testannotation": "testannotationvalue",
}
emptyDir := v1.EmptyDirVolumeSource{
Medium: v1.StorageMediumMemory,
}
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
Annotations: annotations,
},
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
Storage: &monitoringv1.StorageSpec{
EmptyDir: &emptyDir,
},
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
ssetVolumes := sset.Spec.Template.Spec.Volumes
require.NotNil(t, ssetVolumes[len(ssetVolumes)-1].VolumeSource.EmptyDir)
require.Equal(t, emptyDir.Medium, ssetVolumes[len(ssetVolumes)-1].VolumeSource.EmptyDir.Medium)
}
func TestStatefulSetEphemeral(t *testing.T) {
labels := map[string]string{
"testlabel": "testlabelvalue",
}
annotations := map[string]string{
"testannotation": "testannotationvalue",
}
storageClass := "storageclass"
ephemeral := v1.EphemeralVolumeSource{
VolumeClaimTemplate: &v1.PersistentVolumeClaimTemplate{
Spec: v1.PersistentVolumeClaimSpec{
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
StorageClassName: &storageClass,
},
},
}
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
Annotations: annotations,
},
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
Storage: &monitoringv1.StorageSpec{
Ephemeral: &ephemeral,
},
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
ssetVolumes := sset.Spec.Template.Spec.Volumes
require.NotNil(t, ssetVolumes[len(ssetVolumes)-1].VolumeSource.Ephemeral)
require.Equal(t, ephemeral.VolumeClaimTemplate.Spec.StorageClassName, ssetVolumes[len(ssetVolumes)-1].VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.StorageClassName)
}
func TestThanosVersion(t *testing.T) {
thanosBaseImage := defaultTestConfig.ThanosDefaultBaseImage
for _, tc := range []struct {
version string
expectedImage string
expectedError bool
}{
{"v0.29.0", thanosBaseImage + ":" + "v0.29.0", false},
{"0.29.0", thanosBaseImage + ":" + "0.29.0", false},
{"", thanosBaseImage + ":" + operator.DefaultThanosVersion, false},
{"0.29.0-0123", "", true},
{"0.29.0.DEV", "", true},
} {
t.Run(tc.version, func(t *testing.T) {
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
Version: tc.version,
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
if tc.expectedError {
require.Error(t, err)
}
if !tc.expectedError {
image := sset.Spec.Template.Spec.Containers[0].Image
require.Equal(t, tc.expectedImage, image)
}
})
}
}
func TestStatefulSetDNSPolicyAndDNSConfig(t *testing.T) {
k8sDNSPolicy := v1.DNSClusterFirst
monitoringDNSConfig := monitoringv1.PodDNSConfig{
Nameservers: []string{"8.8.8.8"},
Searches: []string{"custom.search"},
Options: []monitoringv1.PodDNSConfigOption{
{
Name: "ndots",
Value: ptr.To("5"),
},
},
}
k8sDNSConfig := k8sutil.ConvertToK8sDNSConfig(&monitoringDNSConfig)
monitoringDNSPolicyPtr := ptr.To(monitoringv1.DNSPolicy(k8sDNSPolicy))
sset, err := makeStatefulSet(&monitoringv1.ThanosRuler{
ObjectMeta: metav1.ObjectMeta{},
Spec: monitoringv1.ThanosRulerSpec{
QueryEndpoints: emptyQueryEndpoints,
DNSPolicy: monitoringDNSPolicyPtr,
DNSConfig: &monitoringDNSConfig,
},
}, defaultTestConfig, nil, "", &operator.ShardedSecret{})
require.NoError(t, err)
require.Equal(t, k8sDNSPolicy, sset.Spec.Template.Spec.DNSPolicy, "expected DNS policy to match")
require.Equal(t, k8sDNSConfig, sset.Spec.Template.Spec.DNSConfig, "expected DNS configuration to match")
}