1
0
mirror of https://github.com/coreos/prometheus-operator.git synced 2026-02-05 06:45:27 +01:00
Files
prometheus-operator/test/e2e/prometheus_test.go
Simon Pasquier 355cd36130 change: add new arguments to manifests
This commit adds the following arguments in jsonnet + default manifests:

* `--watch-referenced-objects-in-all-namespaces=true`
* `--disable-unmanaged-prometheus-configuration=true`

Signed-off-by: Simon Pasquier <spasquie@redhat.com>
2025-11-28 12:43:51 +01:00

5929 lines
175 KiB
Go

// Copyright 2016 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 e2e
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"log"
"maps"
"net/http"
"net/url"
"os"
"reflect"
"slices"
"strconv"
"strings"
"testing"
"time"
"github.com/kylelemons/godebug/pretty"
"github.com/stretchr/testify/require"
"golang.org/x/net/http2"
"google.golang.org/protobuf/proto"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/wait"
certutil "k8s.io/client-go/util/cert"
"k8s.io/utils/ptr"
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"github.com/prometheus-operator/prometheus-operator/pkg/operator"
testFramework "github.com/prometheus-operator/prometheus-operator/test/framework"
)
var (
certsDir = "../../test/e2e/tls_certs/"
)
func createMutualTLSSecret(t *testing.T, secretName, ns string) {
serverCert, err := os.ReadFile(certsDir + "ca.crt")
if err != nil {
t.Fatalf("failed to load %s: %v", "ca.crt", err)
}
scrapingKey, err := os.ReadFile(certsDir + "client.key")
if err != nil {
t.Fatalf("failed to load %s: %v", "client.key", err)
}
scrapingCert, err := os.ReadFile(certsDir + "client.crt")
if err != nil {
t.Fatalf("failed to load %s: %v", "client.crt", err)
}
s := testFramework.MakeSecretWithCert(ns, secretName,
[]string{"key.pem", "cert.pem", "ca.crt"}, [][]byte{scrapingKey, scrapingCert, serverCert})
_, err = framework.KubeClient.CoreV1().Secrets(s.ObjectMeta.Namespace).Create(context.Background(), s, metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}
}
func deployInstrumentedApplicationWithTLS(name, ns string) error {
dep, err := testFramework.MakeDeployment("../../test/framework/resources/basic-auth-app-deployment.yaml")
if err != nil {
return err
}
dep.Spec.Template.Spec.Containers[0].Args = []string{"--cert-path=/etc/certs"}
dep.Spec.Template.Spec.Volumes = []v1.Volume{{
Name: "tls-certs",
VolumeSource: v1.VolumeSource{
Secret: &v1.SecretVolumeSource{
SecretName: testFramework.ServerTLSSecret,
},
},
}}
dep.Spec.Template.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{
{
Name: dep.Spec.Template.Spec.Volumes[0].Name,
MountPath: "/etc/certs",
},
}
if err := framework.CreateDeployment(context.Background(), ns, dep); err != nil {
return fmt.Errorf("failed to create app deployment: %w", err)
}
svc := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: dep.Name,
Labels: map[string]string{
"group": name,
},
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
Ports: []v1.ServicePort{
{
Name: "mtls",
Port: 8081,
},
},
Selector: map[string]string{
"group": name,
},
},
}
if _, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, svc); err != nil {
return fmt.Errorf("failed to create app service: %w", err)
}
sm := framework.MakeBasicServiceMonitor(name)
sm.Spec.Endpoints = []monitoringv1.Endpoint{
{
Port: "mtls",
Interval: "1s",
Scheme: ptr.To(monitoringv1.SchemeHTTPS),
TLSConfig: &monitoringv1.TLSConfig{
SafeTLSConfig: monitoringv1.SafeTLSConfig{
ServerName: ptr.To("caandserver.com"),
CA: monitoringv1.SecretOrConfigMap{
Secret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: testFramework.ScrapingTLSSecret,
},
Key: testFramework.CAKey,
},
},
Cert: monitoringv1.SecretOrConfigMap{
Secret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: testFramework.ScrapingTLSSecret,
},
Key: testFramework.CertKey,
},
},
KeySecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: testFramework.ScrapingTLSSecret,
},
Key: testFramework.PrivateKey,
},
},
},
},
}
if _, err := framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), sm, metav1.CreateOptions{}); err != nil {
return fmt.Errorf("failed to create ServiceMonitor: %w", err)
}
return nil
}
// createRemoteWriteStack creates a pair of Prometheus objects with the first
// instance scraping targets and remote-writing samples to the second one.
// The 1st returned value is the scraping Prometheus service.
// The 2nd returned value is the receiver Prometheus service.
func createRemoteWriteStack(name, ns string, prwtc testFramework.PromRemoteWriteTestConfig) (*v1.Service, *v1.Service, error) {
// Prometheus instance with remote-write receiver enabled.
receiverName := fmt.Sprintf("%s-%s", name, "receiver")
rwReceiver := framework.MakeBasicPrometheus(ns, receiverName, receiverName, 1)
framework.EnableRemoteWriteReceiverWithTLS(rwReceiver)
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, rwReceiver); err != nil {
return nil, nil, err
}
rwReceiverService := framework.MakePrometheusService(receiverName, receiverName, v1.ServiceTypeClusterIP)
if _, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, rwReceiverService); err != nil {
return nil, nil, err
}
// Prometheus instance scraping targets.
prometheus := framework.MakeBasicPrometheus(ns, name, name, 1)
prwtc.AddRemoteWriteWithTLSToPrometheus(prometheus, "https://"+rwReceiverService.Name+":9090/api/v1/write")
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, prometheus); err != nil {
return nil, nil, err
}
prometheusService := framework.MakePrometheusService(name, name, v1.ServiceTypeClusterIP)
if _, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, prometheusService); err != nil {
return nil, nil, err
}
return prometheusService, rwReceiverService, nil
}
func createServiceAccountSecret(t *testing.T, saName, ns string) {
// Create the secret object
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: saName + "-sa-secret",
Namespace: ns,
Annotations: map[string]string{
"kubernetes.io/service-account.name": saName,
},
},
Type: v1.SecretTypeServiceAccountToken,
}
// Create the secret
_, err := framework.KubeClient.CoreV1().Secrets(ns).Create(context.Background(), secret, metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}
if err != nil {
fmt.Printf("Failed to create secret: %v\n", err)
os.Exit(1)
}
}
func testPromRemoteWriteWithTLS(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
rwConfig testFramework.PromRemoteWriteTestConfig
success bool
}{
{
// All TLS materials in one secret.
name: "variant-1",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "client.key",
SecretName: "client-tls-key-cert-ca",
},
ClientCert: testFramework.Cert{
Filename: "client.crt",
ResourceName: "client-tls-key-cert-ca",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "ca.crt",
ResourceName: "client-tls-key-cert-ca",
ResourceType: testFramework.SECRET,
},
InsecureSkipVerify: false,
},
success: true,
},
{
// TLS materials split into individual secrets.
name: "variant-2",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "client.key",
SecretName: "client-tls-key",
},
ClientCert: testFramework.Cert{
Filename: "client.crt",
ResourceName: "client-tls-cert",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "ca.crt",
ResourceName: "client-tls-ca",
ResourceType: testFramework.SECRET,
},
InsecureSkipVerify: false,
},
success: true,
},
{
// client cert/key and CA in different secrets.
name: "variant-3",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "client.key",
SecretName: "client-tls-key-cert",
},
ClientCert: testFramework.Cert{
Filename: "client.crt",
ResourceName: "client-tls-key-cert",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "ca.crt",
ResourceName: "client-tls-ca",
ResourceType: testFramework.SECRET,
},
InsecureSkipVerify: false,
},
success: true,
},
{
// client key and client cert/CA in different secrets.
name: "variant-4",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "client.key",
SecretName: "client-tls-key",
},
ClientCert: testFramework.Cert{
Filename: "client.crt",
ResourceName: "client-tls-cert-ca",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "ca.crt",
ResourceName: "client-tls-cert-ca",
ResourceType: testFramework.SECRET,
},
InsecureSkipVerify: false,
},
success: true,
},
{
// client cert and client key/CA in different secrets.
name: "variant-5",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "client.key",
SecretName: "client-tls-key-ca",
},
ClientCert: testFramework.Cert{
Filename: "client.crt",
ResourceName: "client-tls-cert",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "ca.crt",
ResourceName: "client-tls-key-ca",
ResourceType: testFramework.SECRET,
},
InsecureSkipVerify: false,
},
success: true,
},
{
// client key in secret and client cert/CA in configmap.
name: "variant-6",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "client.key",
SecretName: "client-tls-key",
},
ClientCert: testFramework.Cert{
Filename: "client.crt",
ResourceName: "client-tls-cert-ca",
ResourceType: testFramework.CONFIGMAP,
},
CA: testFramework.Cert{
Filename: "ca.crt",
ResourceName: "client-tls-cert-ca",
ResourceType: testFramework.CONFIGMAP,
},
InsecureSkipVerify: false,
},
success: true,
},
{
// client key in secret and dedicated configmaps for client cert and CA.
name: "variant-7",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "client.key",
SecretName: "client-tls-key",
},
ClientCert: testFramework.Cert{
Filename: "client.crt",
ResourceName: "client-tls-cert",
ResourceType: testFramework.CONFIGMAP,
},
CA: testFramework.Cert{
Filename: "ca.crt",
ResourceName: "client-tls-ca",
ResourceType: testFramework.CONFIGMAP,
},
InsecureSkipVerify: false,
},
success: true,
},
{
// client key/cert in secret and CA in configmap.
name: "variant-8",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "client.key",
SecretName: "client-tls-key-cert",
},
ClientCert: testFramework.Cert{
Filename: "client.crt",
ResourceName: "client-tls-key-cert",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "ca.crt",
ResourceName: "client-tls-ca",
ResourceType: testFramework.CONFIGMAP,
},
InsecureSkipVerify: false,
},
success: true,
},
{
// client key and cert in dedicated secrets and CA in configmap.
name: "variant-9",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "client.key",
SecretName: "client-tls-key",
},
ClientCert: testFramework.Cert{
Filename: "client.crt",
ResourceName: "client-tls-cert",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "ca.crt",
ResourceName: "client-tls-ca",
ResourceType: testFramework.CONFIGMAP,
},
InsecureSkipVerify: false,
},
success: true,
},
{
// client key in secret, cert in configmap and CA in secret.
name: "variant-10",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "client.key",
SecretName: "client-tls-key-ca",
},
ClientCert: testFramework.Cert{
Filename: "client.crt",
ResourceName: "client-tls-cert",
ResourceType: testFramework.CONFIGMAP,
},
CA: testFramework.Cert{
Filename: "ca.crt",
ResourceName: "client-tls-key-ca",
ResourceType: testFramework.SECRET,
},
InsecureSkipVerify: false,
},
success: true,
},
{
// client key in secret, cert in configmap and CA in secret.
name: "variant-11",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "client.key",
SecretName: "client-tls-key",
},
ClientCert: testFramework.Cert{
Filename: "client.crt",
ResourceName: "client-tls-cert",
ResourceType: testFramework.CONFIGMAP,
},
CA: testFramework.Cert{
Filename: "ca.crt",
ResourceName: "client-tls-ca",
ResourceType: testFramework.SECRET,
},
InsecureSkipVerify: false,
},
success: true,
},
{
// client key/cert in secret and no CA.
name: "variant-12",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "client.key",
SecretName: "client-tls-key-cert",
},
ClientCert: testFramework.Cert{
Filename: "client.crt",
ResourceName: "client-tls-key-cert",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "",
ResourceName: "",
ResourceType: testFramework.SECRET,
},
InsecureSkipVerify: true,
},
success: true,
},
// non working configurations
// we will check it only for one configuration for simplicity - only one Secret
{
// Invalid CA.
name: "variant-13",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "client.key",
SecretName: "client-tls-key-cert-ca",
},
ClientCert: testFramework.Cert{
Filename: "client.crt",
ResourceName: "client-tls-key-cert-ca",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "bad_ca.crt",
ResourceName: "client-tls-key-cert-ca",
ResourceType: testFramework.SECRET,
},
InsecureSkipVerify: false,
},
success: false,
},
{
// Missing CA.
name: "variant-14",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "client.key",
SecretName: "client-tls-key-cert",
},
ClientCert: testFramework.Cert{
Filename: "client.crt",
ResourceName: "client-tls-key-cert",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "",
ResourceName: "",
ResourceType: testFramework.SECRET,
},
InsecureSkipVerify: false,
},
success: false,
},
{
// Invalid cert/key + CA.
name: "variant-15",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "bad_client.key",
SecretName: "client-tls-key-cert-ca",
},
ClientCert: testFramework.Cert{
Filename: "bad_client.crt",
ResourceName: "client-tls-key-cert-ca",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "bad_ca.crt",
ResourceName: "client-tls-key-cert-ca",
ResourceType: testFramework.SECRET,
},
InsecureSkipVerify: false,
},
success: false,
},
{
// Invalid cert + missing CA.
name: "variant-16",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "bad_client.key",
SecretName: "client-tls-key-cert",
},
ClientCert: testFramework.Cert{
Filename: "bad_client.crt",
ResourceName: "client-tls-key-cert",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "",
ResourceName: "",
ResourceType: testFramework.SECRET,
},
InsecureSkipVerify: false,
},
success: false,
},
{
// Missing cert/key + invalid CA.
name: "variant-17",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "",
SecretName: "",
},
ClientCert: testFramework.Cert{
Filename: "",
ResourceName: "",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "bad_ca.crt",
ResourceName: "client-tls-ca",
ResourceType: testFramework.SECRET,
},
InsecureSkipVerify: false,
},
success: false,
},
{
// Missing cert/key + CA.
name: "variant-18",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "",
SecretName: "",
},
ClientCert: testFramework.Cert{
Filename: "",
ResourceName: "",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "",
ResourceName: "",
ResourceType: testFramework.SECRET,
},
InsecureSkipVerify: false,
},
success: false,
},
{
// Invalid cert/key.
name: "variant-19",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "bad_client.key",
SecretName: "client-tls-key-cert-ca",
},
ClientCert: testFramework.Cert{
Filename: "bad_client.crt",
ResourceName: "client-tls-key-cert-ca",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "ca.crt",
ResourceName: "client-tls-key-cert-ca",
ResourceType: testFramework.SECRET,
},
InsecureSkipVerify: false,
},
success: false,
},
// Had to change the success flag to True, because prometheus receiver is running in VerifyClientCertIfGiven mode. Details here - https://github.com/prometheus-operator/prometheus-operator/pull/4337#discussion_r735064646
{
// Valid CA without cert/key.
name: "variant-20",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "",
SecretName: "",
},
ClientCert: testFramework.Cert{
Filename: "",
ResourceName: "",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "ca.crt",
ResourceName: "client-tls-ca",
ResourceType: testFramework.SECRET,
},
InsecureSkipVerify: false,
},
success: true,
},
{
// Prometheus Remote Write v2.0.
name: "remote-write-v2.0",
rwConfig: testFramework.PromRemoteWriteTestConfig{
ClientKey: testFramework.Key{
Filename: "client.key",
SecretName: "client-tls-key-cert-ca",
},
ClientCert: testFramework.Cert{
Filename: "client.crt",
ResourceName: "client-tls-key-cert-ca",
ResourceType: testFramework.SECRET,
},
CA: testFramework.Cert{
Filename: "ca.crt",
ResourceName: "client-tls-key-cert-ca",
ResourceType: testFramework.SECRET,
},
RemoteWriteMessageVersion: ptr.To(monitoringv1.RemoteWriteMessageVersion2_0),
},
success: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
// The sub-test deploys the following setup:
//
// [example app] <---scrapes--- [Prometheus] ---remote-writes---> [Prometheus receiver]
//
// When the test expects a success, it should find the samples in the Prometheus receiver.
// Otherwise the samples should always be found in the scraping Prometheus.
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "test"
// Create the secrets/configmaps storing the TLS certificates.
err := framework.CreateCertificateResources(ns, certsDir, tc.rwConfig)
if err != nil {
t.Fatal(err)
}
if err = deployInstrumentedApplicationWithTLS(name, ns); err != nil {
t.Fatal(err)
}
svc, receiverSvc, err := createRemoteWriteStack(name, ns, tc.rwConfig)
if err != nil {
t.Fatal(err)
}
// Wait for the instrumented application to be scraped.
if err := framework.WaitForHealthyTargets(context.Background(), ns, svc.Name, 1); err != nil {
t.Fatal(err)
}
// Query metrics from the scraping Prometheus.
q := "up{container='example-app'} == 1"
response, err := framework.PrometheusQuery(ns, svc.Name, "http", q)
if err != nil {
t.Fatal(err)
}
if len(response) != 1 {
t.Fatalf("Prometheus does not have the instrumented app metrics: %v", response)
}
if !tc.success {
q = "absent(up)"
}
// Query metrics from the remote-write receiver.
response, err = framework.PrometheusQuery(ns, receiverSvc.Name, "https", q)
if err != nil {
t.Fatalf("(%s, %s, %s): query %q failed: %s", tc.rwConfig.ClientKey.Filename, tc.rwConfig.ClientCert.Filename, tc.rwConfig.CA.Filename, q, err.Error())
}
if len(response) != 1 {
t.Fatalf("(%s, %s, %s): query %q failed: %v", tc.rwConfig.ClientKey.Filename, tc.rwConfig.ClientCert.Filename, tc.rwConfig.CA.Filename, q, response)
}
})
}
}
func testPromCreateDeleteCluster(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "test"
prometheusCRD := framework.MakeBasicPrometheus(ns, name, name, 1)
prometheusCRD.Namespace = ns
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, prometheusCRD); err != nil {
t.Fatal(err)
}
if err := framework.DeletePrometheusAndWaitUntilGone(context.Background(), ns, name); err != nil {
t.Fatal(err)
}
}
func testPromScaleUpDownReplicas(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "test"
p, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, framework.MakeBasicPrometheus(ns, name, name, 1))
if err != nil {
t.Fatal(err)
}
p, err = framework.UpdatePrometheusReplicasAndWaitUntilReady(context.Background(), p.Name, ns, *p.Spec.Replicas+1)
if err != nil {
t.Fatal(err)
}
_, err = framework.UpdatePrometheusReplicasAndWaitUntilReady(context.Background(), p.Name, ns, *p.Spec.Replicas-1)
if err != nil {
t.Fatal(err)
}
}
func testPromNoServiceMonitorSelector(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "test"
p := framework.MakeBasicPrometheus(ns, name, name, 1)
p.Spec.ServiceMonitorSelector = nil
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p); err != nil {
t.Fatal(err)
}
}
func testPromVersionMigration(t *testing.T) {
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "test"
startVersion := operator.PrometheusCompatibilityMatrix[0]
compatibilityMatrix := operator.PrometheusCompatibilityMatrix[1:]
p := framework.MakeBasicPrometheus(ns, name, name, 1)
p.Spec.Version = startVersion
p, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p)
if err != nil {
t.Fatal(err)
}
for _, v := range compatibilityMatrix {
t.Run("to "+v, func(t *testing.T) {
p, err = framework.PatchPrometheusAndWaitUntilReady(
context.Background(),
name,
ns,
monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Version: v,
},
},
)
if err != nil {
t.Fatalf("update to version %s: %v", v, err)
}
if err := framework.WaitForPrometheusRunImageAndReady(context.Background(), ns, p); err != nil {
t.Fatalf("update to version %s: %v", v, err)
}
})
}
}
func testPromResourceUpdate(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "test"
p := framework.MakeBasicPrometheus(ns, name, name, 1)
p.Spec.Resources = v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("100Mi"),
},
}
p, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p)
if err != nil {
t.Fatal(err)
}
podSelector := fields.SelectorFromSet(fields.Set(map[string]string{
operator.ApplicationNameLabelKey: "prometheus",
operator.ApplicationInstanceLabelKey: name,
})).String()
pods, err := framework.KubeClient.CoreV1().Pods(ns).List(
context.Background(),
metav1.ListOptions{
LabelSelector: podSelector,
},
)
require.NoError(t, err)
res := pods.Items[0].Spec.Containers[0].Resources
if !reflect.DeepEqual(res, p.Spec.Resources) {
t.Fatalf("resources don't match. Has %#+v, want %#+v", res, p.Spec.Resources)
}
p, err = framework.PatchPrometheusAndWaitUntilReady(
context.Background(),
p.Name,
ns,
monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("200Mi"),
},
},
},
},
)
require.NoError(t, err)
var pollErr error
err = wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 2*time.Minute, false, func(ctx context.Context) (bool, error) {
pods, err := framework.KubeClient.CoreV1().Pods(ns).List(
ctx,
metav1.ListOptions{
LabelSelector: podSelector,
},
)
if err != nil {
pollErr = err
return false, nil
}
if len(pods.Items) != 1 {
pollErr = fmt.Errorf("expected 1 pod, got %d", len(pods.Items))
return false, nil
}
res = pods.Items[0].Spec.Containers[0].Resources
if !reflect.DeepEqual(res, p.Spec.Resources) {
pollErr = fmt.Errorf("resources don't match\ngot: %#+v\nwant: %#+v", res, p.Spec.Resources)
return false, nil
}
return true, nil
})
require.NoError(t, err, fmt.Sprintf("%s: %s", err, pollErr))
}
func testPromStorageLabelsAnnotations(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "test"
p := framework.MakeBasicPrometheus(ns, name, name, 1)
p.Spec.Storage = &monitoringv1.StorageSpec{
VolumeClaimTemplate: monitoringv1.EmbeddedPersistentVolumeClaim{
EmbeddedObjectMetadata: monitoringv1.EmbeddedObjectMetadata{
Labels: map[string]string{
"test-label": "foo",
},
Annotations: map[string]string{
"test-annotation": "bar",
},
},
Spec: v1.PersistentVolumeClaimSpec{
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
Resources: v1.VolumeResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceStorage: resource.MustParse("200Mi"),
},
},
},
},
}
p, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p)
if err != nil {
t.Fatal(err)
}
if val := p.Spec.Storage.VolumeClaimTemplate.Labels["test-label"]; val != "foo" {
t.Errorf("incorrect volume claim label, want: %v, got: %v", "foo", val)
}
if val := p.Spec.Storage.VolumeClaimTemplate.Annotations["test-annotation"]; val != "bar" {
t.Errorf("incorrect volume claim annotation, want: %v, got: %v", "bar", val)
}
err = wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 2*time.Minute, false, func(ctx context.Context) (bool, error) {
sts, err := framework.KubeClient.AppsV1().StatefulSets(ns).List(ctx, metav1.ListOptions{})
if err != nil {
return false, err
}
if len(sts.Items) < 1 {
return false, nil
}
for _, vct := range sts.Items[0].Spec.VolumeClaimTemplates {
if vct.Name == "prometheus-"+name+"-db" {
if val := vct.Labels["test-label"]; val != "foo" {
return false, fmt.Errorf("incorrect volume claim label on sts, want: %v, got: %v", "foo", val)
}
if val := vct.Annotations["test-annotation"]; val != "bar" {
return false, fmt.Errorf("incorrect volume claim annotation on sts, want: %v, got: %v", "bar", val)
}
return true, nil
}
}
return false, nil
})
if err != nil {
t.Fatal(err)
}
}
func testPromStorageUpdate(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
p := framework.MakeBasicPrometheus(ns, "test", "", 1)
p, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p)
if err != nil {
t.Fatal(err)
}
p, err = framework.PatchPrometheusAndWaitUntilReady(
context.Background(),
p.Name,
ns,
monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Storage: &monitoringv1.StorageSpec{
VolumeClaimTemplate: monitoringv1.EmbeddedPersistentVolumeClaim{
EmbeddedObjectMetadata: monitoringv1.EmbeddedObjectMetadata{
Labels: map[string]string{
"test": "testPromStorageUpdate",
},
},
Spec: v1.PersistentVolumeClaimSpec{
AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
Resources: v1.VolumeResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceStorage: resource.MustParse("200Mi"),
},
},
},
},
},
},
},
)
require.NoError(t, err)
err = framework.WaitForBoundPVC(context.Background(), ns, "test=testPromStorageUpdate", 1)
require.NoError(t, err)
// Invalid storageclass e2e test
_, err = framework.PatchPrometheus(
context.Background(),
p.Name,
ns,
monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Storage: &monitoringv1.StorageSpec{
VolumeClaimTemplate: monitoringv1.EmbeddedPersistentVolumeClaim{
EmbeddedObjectMetadata: monitoringv1.EmbeddedObjectMetadata{
Labels: map[string]string{
"test": "testPromStorageUpdate",
},
},
Spec: v1.PersistentVolumeClaimSpec{
StorageClassName: ptr.To("unknown-storage-class"),
Resources: v1.VolumeResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceStorage: resource.MustParse("200Mi"),
},
},
},
},
},
},
},
)
require.NoError(t, err)
var loopError error
err = wait.PollUntilContextTimeout(context.Background(), 5*time.Second, framework.DefaultTimeout, true, func(ctx context.Context) (bool, error) {
current, err := framework.MonClientV1.Prometheuses(ns).Get(ctx, p.Name, metav1.GetOptions{})
if err != nil {
loopError = fmt.Errorf("failed to get object: %w", err)
return false, nil
}
if err := framework.AssertCondition(current.Status.Conditions, monitoringv1.Reconciled, monitoringv1.ConditionFalse); err == nil {
return true, nil
}
return false, nil
})
if err != nil {
t.Fatalf("%v: %v", err, loopError)
}
}
func testPromReloadConfig(t *testing.T) {
for _, tc := range []struct {
reloadStrategy monitoringv1.ReloadStrategyType
}{
{
reloadStrategy: monitoringv1.HTTPReloadStrategyType,
},
{
reloadStrategy: monitoringv1.ProcessSignalReloadStrategyType,
},
} {
t.Run(fmt.Sprintf("%s reload strategy", tc.reloadStrategy), func(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "test"
p := framework.MakeBasicPrometheus(ns, name, name, 1)
p.Spec.ServiceMonitorSelector = nil
p.Spec.PodMonitorSelector = nil
p.Spec.AdditionalScrapeConfigs = &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: fmt.Sprintf("additional-config-%s", name),
},
Key: "config.yaml",
}
p.Spec.ReloadStrategy = ptr.To(tc.reloadStrategy)
svc := framework.MakePrometheusService(p.Name, "not-relevant", v1.ServiceTypeClusterIP)
cfg := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("additional-config-%s", name),
},
Data: map[string][]byte{
"config.yaml": []byte(`
- job_name: testReloadConfig
metrics_path: /metrics
static_configs:
- targets:
- 111.111.111.111:9090
`),
},
}
cfg, err := framework.KubeClient.CoreV1().Secrets(ns).Create(context.Background(), cfg, metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p); err != nil {
t.Fatal(err)
}
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, svc); err != nil {
t.Fatal(err)
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
if err := framework.WaitForActiveTargets(context.Background(), ns, svc.Name, 1); err != nil {
t.Fatal(err)
}
cfg.Data["config.yaml"] = []byte(`
- job_name: testReloadConfig
metrics_path: /metrics
static_configs:
- targets:
- 111.111.111.111:9090
- 111.111.111.112:9090
`)
if _, err := framework.KubeClient.CoreV1().Secrets(ns).Update(context.Background(), cfg, metav1.UpdateOptions{}); err != nil {
t.Fatal(err)
}
if err := framework.WaitForActiveTargets(context.Background(), ns, svc.Name, 2); err != nil {
t.Fatal(err)
}
})
}
}
func testPromAdditionalScrapeConfig(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
prometheusName := "test"
group := "additional-config-test"
svc := framework.MakePrometheusService(prometheusName, group, v1.ServiceTypeClusterIP)
s := framework.MakeBasicServiceMonitor(group)
if _, err := framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), s, metav1.CreateOptions{}); err != nil {
t.Fatal("Creating ServiceMonitor failed: ", err)
}
additionalConfig := `
- job_name: "prometheus"
static_configs:
- targets: ["localhost:9090"]
`
secret := v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "additional-scrape-configs",
},
Data: map[string][]byte{
"prometheus-additional.yaml": []byte(additionalConfig),
},
}
_, err := framework.KubeClient.CoreV1().Secrets(ns).Create(context.Background(), &secret, metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}
p := framework.MakeBasicPrometheus(ns, prometheusName, group, 1)
p.Spec.AdditionalScrapeConfigs = &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "additional-scrape-configs",
},
Key: "prometheus-additional.yaml",
}
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p); err != nil {
t.Fatal(err)
}
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, svc); err != nil {
t.Fatal(fmt.Errorf("creating prometheus service failed: %w", err))
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
// Wait for ServiceMonitor target, as well as additional-config target
if err := framework.WaitForActiveTargets(context.Background(), ns, svc.Name, 2); err != nil {
t.Fatal(err)
}
}
func testPromAdditionalAlertManagerConfig(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
prometheusName := "test"
group := "additional-alert-config-test"
svc := framework.MakePrometheusService(prometheusName, group, v1.ServiceTypeClusterIP)
s := framework.MakeBasicServiceMonitor(group)
if _, err := framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), s, metav1.CreateOptions{}); err != nil {
t.Fatal("Creating ServiceMonitor failed: ", err)
}
additionalConfig := `
- path_prefix: /
scheme: http
static_configs:
- targets: ["localhost:9093"]
`
secret := v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "additional-alert-configs",
},
Data: map[string][]byte{
"prometheus-additional.yaml": []byte(additionalConfig),
},
}
_, err := framework.KubeClient.CoreV1().Secrets(ns).Create(context.Background(), &secret, metav1.CreateOptions{})
if err != nil {
t.Fatal(err)
}
p := framework.MakeBasicPrometheus(ns, prometheusName, group, 1)
p.Spec.AdditionalAlertManagerConfigs = &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "additional-alert-configs",
},
Key: "prometheus-additional.yaml",
}
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p); err != nil {
t.Fatal(err)
}
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, svc); err != nil {
t.Fatal(fmt.Errorf("creating prometheus service failed: %w", err))
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
// Wait for ServiceMonitor target
if err := framework.WaitForActiveTargets(context.Background(), ns, svc.Name, 1); err != nil {
t.Fatal(err)
}
err = wait.PollUntilContextTimeout(context.Background(), time.Second, 5*time.Minute, false, func(ctx context.Context) (done bool, err error) {
response, err := framework.PrometheusSVCGetRequest(ctx, ns, svc.Name, "http", "/api/v1/alertmanagers", map[string]string{})
if err != nil {
return true, err
}
ra := prometheusAlertmanagerAPIResponse{}
if err := json.NewDecoder(bytes.NewBuffer(response)).Decode(&ra); err != nil {
return true, err
}
if ra.Status == "success" && len(ra.Data.ActiveAlertmanagers) == 1 {
return true, nil
}
return false, nil
})
if err != nil {
t.Fatal(fmt.Errorf("validating Prometheus Alertmanager configuration failed: %w", err))
}
}
func testPromReloadRules(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "test"
firstAlertName := "firstAlert"
secondAlertName := "secondAlert"
ruleFile, err := framework.MakeAndCreateFiringRule(context.Background(), ns, name, firstAlertName)
if err != nil {
t.Fatal(err)
}
p := framework.MakeBasicPrometheus(ns, name, name, 1)
p.Spec.EvaluationInterval = "1s"
p, err = framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p)
if err != nil {
t.Fatal(err)
}
pSVC := framework.MakePrometheusService(p.Name, "not-relevant", v1.ServiceTypeClusterIP)
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, pSVC); err != nil {
t.Fatal(fmt.Errorf("creating Prometheus service failed: %w", err))
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
err = framework.WaitForPrometheusFiringAlert(context.Background(), p.Namespace, pSVC.Name, firstAlertName)
if err != nil {
t.Fatal(err)
}
ruleFile.Spec.Groups = []monitoringv1.RuleGroup{
{
Name: "my-alerting-group",
Rules: []monitoringv1.Rule{
{
Alert: secondAlertName,
Expr: intstr.FromString("vector(1)"),
},
},
},
}
_, err = framework.UpdateRule(context.Background(), ns, ruleFile)
if err != nil {
t.Fatal(err)
}
err = framework.WaitForPrometheusFiringAlert(context.Background(), p.Namespace, pSVC.Name, secondAlertName)
if err != nil {
t.Fatal(err)
}
}
func testPromMultiplePrometheusRulesSameNS(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "test"
alertNames := []string{"first-alert", "second-alert"}
for _, alertName := range alertNames {
_, err := framework.MakeAndCreateFiringRule(context.Background(), ns, alertName, alertName)
if err != nil {
t.Fatal(err)
}
}
p := framework.MakeBasicPrometheus(ns, name, name, 1)
p.Spec.EvaluationInterval = "1s"
p, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p)
if err != nil {
t.Fatal(err)
}
pSVC := framework.MakePrometheusService(p.Name, "not-relevant", v1.ServiceTypeClusterIP)
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, pSVC); err != nil {
t.Fatal(fmt.Errorf("creating Prometheus service failed: %w", err))
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
for _, alertName := range alertNames {
err := framework.WaitForPrometheusFiringAlert(context.Background(), p.Namespace, pSVC.Name, alertName)
if err != nil {
t.Fatal(err)
}
}
}
func testPromMultiplePrometheusRulesDifferentNS(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
rootNS := framework.CreateNamespace(context.Background(), t, testCtx)
alertNSOne := framework.CreateNamespace(context.Background(), t, testCtx)
alertNSTwo := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, rootNS)
name := "test"
ruleFiles := []struct {
alertName string
ns string
}{{"first-alert", alertNSOne}, {"second-alert", alertNSTwo}}
ruleFilesNamespaceSelector := map[string]string{"monitored": "true"}
for _, file := range ruleFiles {
err := framework.AddLabelsToNamespace(context.Background(), file.ns, ruleFilesNamespaceSelector)
if err != nil {
t.Fatal(err)
}
}
for _, file := range ruleFiles {
_, err := framework.MakeAndCreateFiringRule(context.Background(), file.ns, file.alertName, file.alertName)
if err != nil {
t.Fatal(err)
}
}
p := framework.MakeBasicPrometheus(rootNS, name, name, 1)
p.Spec.EvaluationInterval = "1s"
p.Spec.RuleNamespaceSelector = &metav1.LabelSelector{
MatchLabels: ruleFilesNamespaceSelector,
}
p, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), rootNS, p)
if err != nil {
t.Fatal(err)
}
pSVC := framework.MakePrometheusService(p.Name, "not-relevant", v1.ServiceTypeClusterIP)
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), rootNS, pSVC); err != nil {
t.Fatal(fmt.Errorf("creating Prometheus service failed: %w", err))
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
for _, file := range ruleFiles {
err := framework.WaitForPrometheusFiringAlert(context.Background(), p.Namespace, pSVC.Name, file.alertName)
if err != nil {
t.Fatal(err)
}
}
// Remove the selecting label from the namespaces holding PrometheusRules
// and wait until the rules are removed from Prometheus.
// See https://github.com/prometheus-operator/prometheus-operator/issues/3847
for _, file := range ruleFiles {
if err := framework.RemoveLabelsFromNamespace(context.Background(), file.ns, "monitored"); err != nil {
t.Fatal(err)
}
}
for _, file := range ruleFiles {
var loopError error
err = wait.PollUntilContextTimeout(context.Background(), time.Second, 5*framework.DefaultTimeout, false, func(ctx context.Context) (bool, error) {
var alerts []map[string]string
alerts, loopError = framework.GetPrometheusFiringAlerts(ctx, file.ns, pSVC.Name, file.alertName)
if len(alerts) > 0 {
loopError = fmt.Errorf("%s: got %d alerts", file.alertName, len(alerts))
return false, nil
}
return true, nil
})
if err != nil {
t.Fatalf("waiting for alert %q in namespace %s to stop firing: %v: %v", file.alertName, file.ns, err, loopError)
}
}
}
func testPromRulesExceedingConfigMapLimit(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
prometheusRules := []*monitoringv1.PrometheusRule{}
rule, err := framework.CreateRule(context.Background(), ns, generateHugePrometheusRule(ns, "a"))
require.NoError(t, err)
prometheusRules = append(prometheusRules, rule)
name := "test"
p := framework.MakeBasicPrometheus(ns, name, name, 1)
p.Spec.EvaluationInterval = "1s"
p, err = framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p)
require.NoError(t, err)
// Record the statefulset's generation.
sts, err := framework.KubeClient.AppsV1().StatefulSets(ns).Get(context.Background(), fmt.Sprintf("prometheus-%s", p.Name), metav1.GetOptions{})
require.NoError(t, err)
generation := sts.Generation
pSVC := framework.MakePrometheusService(p.Name, "not-relevant", v1.ServiceTypeClusterIP)
_, err = framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, pSVC)
require.NoError(t, err)
_, err = framework.WaitForConfigMapExist(context.Background(), ns, "prometheus-"+p.Name+"-rulefiles-0")
require.NoError(t, err)
require.NoError(t, framework.WaitForConfigMapNotExist(context.Background(), ns, "prometheus-"+p.Name+"-rulefiles-1"))
// Check that at least 1 alert of the PrometheusRule object fires.
for _, pr := range prometheusRules {
alertName := pr.Spec.Groups[0].Rules[0].Alert
require.NotEmpty(t, alertName)
require.NoError(t, framework.WaitForPrometheusFiringAlert(context.Background(), ns, pSVC.Name, alertName))
}
// Generate another large PrometheusRule object.
rule, err = framework.CreateRule(context.Background(), ns, generateHugePrometheusRule(ns, "b"))
require.NoError(t, err)
// Verify that 2 configmaps exist.
prometheusRules = append(prometheusRules, rule)
for i := range 2 {
_, err := framework.WaitForConfigMapExist(context.Background(), ns, "prometheus-"+p.Name+"-rulefiles-"+strconv.Itoa(i))
require.NoError(t, err)
}
// Check that at least 1 alert from each PrometheusRule object fires.
for _, pr := range prometheusRules {
alertName := pr.Spec.Groups[0].Rules[0].Alert
require.NotEmpty(t, alertName)
require.NoError(t, framework.WaitForPrometheusFiringAlert(context.Background(), ns, pSVC.Name, alertName))
}
// Verify that the statefulset's generation hasn't changed.
sts, err = framework.KubeClient.AppsV1().StatefulSets(ns).Get(context.Background(), fmt.Sprintf("prometheus-%s", p.Name), metav1.GetOptions{})
require.NoError(t, err)
require.Equal(t, generation, sts.Generation)
}
func testPromRulesMustBeAnnotated(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "admission"
admissionAlert := "admissionAlert"
_, err := framework.MakeAndCreateFiringRule(context.Background(), ns, name, admissionAlert)
if err != nil {
t.Fatal(err)
}
rule, err := framework.GetRule(context.Background(), ns, name)
if err != nil {
t.Fatal(err)
}
val, ok := rule.Annotations["prometheus-operator-validated"]
if !ok {
t.Fatal("Expected prometheusrule to be annotated")
}
if val != "true" {
t.Fatal("Expected prometheusrule annotation to be 'true'")
}
}
func testInvalidRulesAreRejected(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "admission"
admissionAlert := "admissionAlert"
_, err := framework.MakeAndCreateInvalidRule(context.Background(), ns, name, admissionAlert)
if err == nil {
t.Fatal("Expected invalid prometheusrule to be rejected")
}
}
func testPromReconcileStatusWhenInvalidRuleCreated(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
ruleFilesNamespaceSelector := map[string]string{"excludeFromWebhook": "true", "role": "rulefile"}
err := framework.AddLabelsToNamespace(context.Background(), ns, ruleFilesNamespaceSelector)
if err != nil {
t.Fatal(err)
}
ruleName := "invalidrule"
alertName := "invalidalert"
_, err = framework.MakeAndCreateInvalidRule(context.Background(), ns, ruleName, alertName)
if err != nil {
t.Fatalf("expected invalid rule to be created in namespace %v", err)
}
prom := framework.MakeBasicPrometheus(ns, "basic-prometheus", "test-group", 1)
if _, err = framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, prom); err != nil {
t.Fatal("Creating prometheus failed: ", err)
}
}
// generateHugePrometheusRule returns a Prometheus rule object that would fill
// more than half of the space of a Kubernetes ConfigMap.
func generateHugePrometheusRule(ns, identifier string) *monitoringv1.PrometheusRule {
// One rule marshaled as yaml is ~34 bytes long, the max is ~524288 bytes.
rules := make([]monitoringv1.Rule, 0, 12000)
for range 12000 {
rules = append(rules, monitoringv1.Rule{
Alert: "alert-" + identifier,
Expr: intstr.FromString("vector(1)"),
})
}
return framework.MakeBasicRule(ns, "prometheus-rule-"+identifier, []monitoringv1.RuleGroup{
{
Name: "rules-group",
Rules: rules,
},
})
}
// Make sure the Prometheus operator only updates the Prometheus config secret
// and the Prometheus rules configmap on relevant changes.
func testPromOnlyUpdatedOnRelevantChanges(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "test"
prometheus := framework.MakeBasicPrometheus(ns, name, name, 1)
// Adding an annotation to Prometheus lead to high CPU usage in the past
// updating the Prometheus StatefulSet in a loop (See
// https://github.com/prometheus-operator/prometheus-operator/issues/1659). Added here to
// prevent a regression.
prometheus.Annotations["test-annotation"] = "test-value"
ctx, cancel := context.WithCancel(context.Background())
type versionedResource interface {
GetResourceVersion() string
}
resourceDefinitions := []struct {
Name string
Getter func(prometheusName string) (versionedResource, error)
Versions map[string]any
MaxExpectedChanges int
}{
{
Name: "rulesConfigMap",
Getter: func(prometheusName string) (versionedResource, error) {
return framework.
KubeClient.
CoreV1().
ConfigMaps(ns).
Get(context.Background(), "prometheus-"+prometheusName+"-rulefiles-0", metav1.GetOptions{})
},
// The Prometheus Operator first creates the ConfigMap for the
// given Prometheus stateful set and then updates it with the matching
// Prometheus rules.
MaxExpectedChanges: 2,
},
{
Name: "configurationSecret",
Getter: func(prometheusName string) (versionedResource, error) {
return framework.
KubeClient.
CoreV1().
Secrets(ns).
Get(context.Background(), "prometheus-"+prometheusName, metav1.GetOptions{})
},
MaxExpectedChanges: 2,
},
{
Name: "tlsAssetSecret",
Getter: func(prometheusName string) (versionedResource, error) {
return framework.
KubeClient.
CoreV1().
Secrets(ns).
Get(context.Background(), "prometheus-"+prometheusName+"-tls-assets-0", metav1.GetOptions{})
},
MaxExpectedChanges: 2,
},
{
Name: "statefulset",
Getter: func(prometheusName string) (versionedResource, error) {
return framework.
KubeClient.
AppsV1().
StatefulSets(ns).
Get(context.Background(), "prometheus-"+prometheusName, metav1.GetOptions{})
},
// First is the creation of the StatefulSet itself, following is the
// update of e.g. the ReadyReplicas status field
MaxExpectedChanges: 3,
},
{
Name: "service-operated",
Getter: func(_ string) (versionedResource, error) {
return framework.
KubeClient.
CoreV1().
Services(ns).
Get(context.Background(), "prometheus-operated", metav1.GetOptions{})
},
MaxExpectedChanges: 1,
},
{
Name: "serviceMonitor",
Getter: func(prometheusName string) (versionedResource, error) {
return framework.
MonClientV1.
ServiceMonitors(ns).
Get(context.Background(), prometheusName, metav1.GetOptions{})
},
MaxExpectedChanges: 1,
},
}
// Init Versions maps
for i := range resourceDefinitions {
resourceDefinitions[i].Versions = map[string]any{}
}
errc := make(chan error, 1)
go func() {
for {
select {
case <-ctx.Done():
return
default:
time.Sleep(10 * time.Millisecond)
for i, resourceDef := range resourceDefinitions {
resource, err := resourceDef.Getter(prometheus.Name)
if apierrors.IsNotFound(err) {
continue
}
if err != nil {
cancel()
errc <- err
return
}
resourceDefinitions[i].Versions[resource.GetResourceVersion()] = resource
}
}
}
}()
alertName := "my-alert"
if _, err := framework.MakeAndCreateFiringRule(context.Background(), ns, "my-prometheus-rule", alertName); err != nil {
t.Fatal(err)
}
prometheus, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, prometheus)
if err != nil {
t.Fatal(err)
}
pSVC := framework.MakePrometheusService(prometheus.Name, name, v1.ServiceTypeClusterIP)
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, pSVC); err != nil {
t.Fatal(fmt.Errorf("creating Prometheus service failed: %w", err))
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
s := framework.MakeBasicServiceMonitor(name)
if _, err := framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), s, metav1.CreateOptions{}); err != nil {
t.Fatal("Creating ServiceMonitor failed: ", err)
}
err = framework.WaitForPrometheusFiringAlert(context.Background(), prometheus.Namespace, pSVC.Name, alertName)
if err != nil {
t.Fatal(err)
}
err = framework.WaitForDiscoveryWorking(context.Background(), ns, pSVC.Name, prometheus.Name)
if err != nil {
t.Fatal(fmt.Errorf("validating Prometheus target discovery failed: %w", err))
}
if err := framework.DeletePrometheusAndWaitUntilGone(context.Background(), ns, name); err != nil {
t.Fatal(err)
}
cancel()
select {
case err := <-errc:
t.Fatal(err)
default:
}
for _, resource := range resourceDefinitions {
if len(resource.Versions) > resource.MaxExpectedChanges || len(resource.Versions) < 1 {
var previous any
for _, version := range resource.Versions {
if previous == nil {
previous = version
continue
}
t.Log(pretty.Compare(previous, version))
previous = version
}
t.Fatalf(
"expected resource %v to be created/updated %v times, but saw %v instead",
resource.Name,
resource.MaxExpectedChanges,
len(resource.Versions),
)
}
}
}
func testPromPreserveUserAddedMetadata(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "test"
prometheusCRD := framework.MakeBasicPrometheus(ns, name, name, 1)
prometheusCRD.Namespace = ns
prometheusCRD, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, prometheusCRD)
if err != nil {
t.Fatal(err)
}
updatedLabels := map[string]string{
"user-defined-label": "custom-label-value",
}
updatedAnnotations := map[string]string{
"user-defined-annotation": "custom-annotation-val",
}
svcClient := framework.KubeClient.CoreV1().Services(ns)
endpointsClient := framework.KubeClient.CoreV1().Endpoints(ns)
ssetClient := framework.KubeClient.AppsV1().StatefulSets(ns)
secretClient := framework.KubeClient.CoreV1().Secrets(ns)
resourceConfigs := []struct {
name string
get func() (metav1.Object, error)
update func(object metav1.Object) (metav1.Object, error)
}{
{
name: "prometheus-operated service",
get: func() (metav1.Object, error) {
return svcClient.Get(context.Background(), "prometheus-operated", metav1.GetOptions{})
},
update: func(object metav1.Object) (metav1.Object, error) {
return svcClient.Update(context.Background(), asService(t, object), metav1.UpdateOptions{})
},
},
{
name: "prometheus stateful set",
get: func() (metav1.Object, error) {
return ssetClient.Get(context.Background(), "prometheus-test", metav1.GetOptions{})
},
update: func(object metav1.Object) (metav1.Object, error) {
return ssetClient.Update(context.Background(), asStatefulSet(t, object), metav1.UpdateOptions{})
},
},
{
name: "prometheus-operated endpoints",
get: func() (metav1.Object, error) {
return endpointsClient.Get(context.Background(), "prometheus-operated", metav1.GetOptions{})
},
update: func(object metav1.Object) (metav1.Object, error) {
return endpointsClient.Update(context.Background(), asEndpoints(t, object), metav1.UpdateOptions{})
},
},
{
name: "prometheus secret",
get: func() (metav1.Object, error) {
return secretClient.Get(context.Background(), "prometheus-test", metav1.GetOptions{})
},
update: func(object metav1.Object) (metav1.Object, error) {
return secretClient.Update(context.Background(), asSecret(t, object), metav1.UpdateOptions{})
},
},
}
for _, rConf := range resourceConfigs {
res, err := rConf.get()
if err != nil {
t.Fatal(err)
}
updateObjectLabels(res, updatedLabels)
updateObjectAnnotations(res, updatedAnnotations)
_, err = rConf.update(res)
if err != nil {
t.Fatal(err)
}
}
// Ensure resource reconciles
_, err = framework.UpdatePrometheusReplicasAndWaitUntilReady(context.Background(), prometheusCRD.Name, ns, *prometheusCRD.Spec.Replicas+1)
if err != nil {
t.Fatal(err)
}
// Assert labels preserved
for _, rConf := range resourceConfigs {
res, err := rConf.get()
if err != nil {
t.Fatal(err)
}
labels := res.GetLabels()
if !containsValues(labels, updatedLabels) {
t.Errorf("%s: labels do not contain updated labels, found: %q, should contain: %q", rConf.name, labels, updatedLabels)
}
annotations := res.GetAnnotations()
if !containsValues(annotations, updatedAnnotations) {
t.Fatalf("%s: annotations do not contain updated annotations, found: %q, should contain: %q", rConf.name, annotations, updatedAnnotations)
}
}
// Cleanup
if err := framework.DeletePrometheusAndWaitUntilGone(context.Background(), ns, name); err != nil {
t.Fatal(err)
}
}
func asService(t *testing.T, object metav1.Object) *v1.Service {
svc, ok := object.(*v1.Service)
if !ok {
t.Fatalf("expected service got %T", object)
}
return svc
}
func asEndpoints(t *testing.T, object metav1.Object) *v1.Endpoints {
endpoints, ok := object.(*v1.Endpoints)
if !ok {
t.Fatalf("expected endpoints got %T", object)
}
return endpoints
}
func asStatefulSet(t *testing.T, object metav1.Object) *appsv1.StatefulSet {
sset, ok := object.(*appsv1.StatefulSet)
if !ok {
t.Fatalf("expected stateful set got %T", object)
}
return sset
}
func asSecret(t *testing.T, object metav1.Object) *v1.Secret {
sec, ok := object.(*v1.Secret)
if !ok {
t.Fatalf("expected secret set got %T", object)
}
return sec
}
func containsValues(got, expected map[string]string) bool {
for k, v := range expected {
if got[k] != v {
return false
}
}
return true
}
func updateObjectLabels(object metav1.Object, labels map[string]string) {
current := object.GetLabels()
current = mergeMap(current, labels)
object.SetLabels(current)
}
func updateObjectAnnotations(object metav1.Object, annotations map[string]string) {
current := object.GetAnnotations()
current = mergeMap(current, annotations)
object.SetAnnotations(current)
}
func mergeMap(a, b map[string]string) map[string]string {
if a == nil {
a = make(map[string]string, len(b))
}
maps.Copy(a, b)
return a
}
func testPromWhenDeleteCRDCleanUpViaOwnerRef(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "test"
p := framework.MakeBasicPrometheus(ns, name, name, 1)
p, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p)
if err != nil {
t.Fatal(err)
}
configMapName := fmt.Sprintf("prometheus-%v-rulefiles-0", p.Name)
_, err = framework.WaitForConfigMapExist(context.Background(), ns, configMapName)
if err != nil {
t.Fatal(err)
}
// Waits for Prometheus pods to vanish
err = framework.DeletePrometheusAndWaitUntilGone(context.Background(), ns, p.Name)
if err != nil {
t.Fatal(err)
}
err = framework.WaitForConfigMapNotExist(context.Background(), ns, configMapName)
if err != nil {
t.Fatal(err)
}
}
func testPromDiscovery(t *testing.T) {
for _, tc := range []struct {
role *monitoringv1.ServiceDiscoveryRole
}{
{
role: nil,
},
{
role: ptr.To(monitoringv1.EndpointsRole),
},
{
role: ptr.To(monitoringv1.EndpointSliceRole),
},
} {
t.Run(fmt.Sprintf("role=%s", ptr.Deref(tc.role, "<nil>")), func(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
prometheusName := "test"
group := "servicediscovery-test"
svc := framework.MakePrometheusService(prometheusName, group, v1.ServiceTypeClusterIP)
s := framework.MakeBasicServiceMonitor(group)
if _, err := framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), s, metav1.CreateOptions{}); err != nil {
t.Fatal("Creating ServiceMonitor failed: ", err)
}
p := framework.MakeBasicPrometheus(ns, prometheusName, group, 1)
p.Spec.ServiceDiscoveryRole = tc.role
_, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p)
if err != nil {
t.Fatal(err)
}
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, svc); err != nil {
t.Fatal(fmt.Errorf("creating prometheus service failed: %w", err))
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
_, err = framework.KubeClient.CoreV1().Secrets(ns).Get(context.Background(), fmt.Sprintf("prometheus-%s", prometheusName), metav1.GetOptions{})
if err != nil {
t.Fatal("Generated Secret could not be retrieved: ", err)
}
err = framework.WaitForDiscoveryWorking(context.Background(), ns, svc.Name, prometheusName)
if err != nil {
t.Fatal(fmt.Errorf("validating Prometheus target discovery failed: %w", err))
}
})
}
}
func testPromSharedResourcesReconciliation(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
s := framework.MakeBasicServiceMonitor("reconcile-test")
if _, err := framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), s, metav1.CreateOptions{}); err != nil {
t.Fatalf("Creating ServiceMonitor failed: %v", err)
}
// Create 2 Prometheus different Prometheus instances that watch the service monitor created above.
for _, prometheusName := range []string{"test", "test2"} {
p := framework.MakeBasicPrometheus(ns, prometheusName, "reconcile-test", 1)
_, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p)
if err != nil {
t.Fatal(err)
}
svc := framework.MakePrometheusService(prometheusName, fmt.Sprintf("reconcile-%s", prometheusName), v1.ServiceTypeClusterIP)
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, svc); err != nil {
t.Fatal(err)
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
_, err = framework.KubeClient.CoreV1().Secrets(ns).Get(context.Background(), fmt.Sprintf("prometheus-%s", prometheusName), metav1.GetOptions{})
if err != nil {
t.Fatalf("Generated Secret could not be retrieved for %s: %v", prometheusName, err)
}
err = framework.WaitForActiveTargets(context.Background(), ns, svc.Name, 1)
if err != nil {
t.Fatalf("Validating Prometheus active targets failed for %s: %v", prometheusName, err)
}
}
if err := framework.MonClientV1.ServiceMonitors(ns).Delete(context.Background(), "reconcile-test", metav1.DeleteOptions{}); err != nil {
t.Fatalf("Deleting ServiceMonitor failed: %v", err)
}
// Delete the service monitors and check that both Prometheus instances are updated.
for _, prometheusName := range []string{"test", "test2"} {
svc := framework.MakePrometheusService(prometheusName, fmt.Sprintf("reconcile-%s", prometheusName), v1.ServiceTypeClusterIP)
if err := framework.WaitForActiveTargets(context.Background(), ns, svc.Name, 0); err != nil {
t.Fatalf("Validating Prometheus active targets failed for %s: %v", prometheusName, err)
}
}
}
func testShardingProvisioning(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
prometheusName := "test"
group := "servicediscovery-test"
svc := framework.MakePrometheusService(prometheusName, group, v1.ServiceTypeClusterIP)
s := framework.MakeBasicServiceMonitor(group)
if _, err := framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), s, metav1.CreateOptions{}); err != nil {
t.Fatal("Creating ServiceMonitor failed: ", err)
}
p := framework.MakeBasicPrometheus(ns, prometheusName, group, 1)
shards := int32(2)
p.Spec.Shards = &shards
_, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p)
if err != nil {
t.Fatal(err)
}
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, svc); err != nil {
t.Fatal(fmt.Errorf("creating prometheus service failed: %w", err))
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
pods := []struct {
pod string
expectedShardConfigSnippet string
}{
{
pod: "prometheus-test-0",
expectedShardConfigSnippet: `
- source_labels:
- __tmp_hash
- __tmp_disable_sharding
regex: 0;|.+;.+
action: keep`,
}, {
pod: "prometheus-test-shard-1-0",
expectedShardConfigSnippet: `
- source_labels:
- __tmp_hash
- __tmp_disable_sharding
regex: 1;|.+;.+
action: keep`,
},
}
for _, p := range pods {
stdout, _, err := framework.ExecWithOptions(context.Background(), testFramework.ExecOptions{
Command: []string{
"/bin/sh", "-c", "cat /etc/prometheus/config_out/prometheus.env.yaml",
},
Namespace: ns,
PodName: p.pod,
ContainerName: "prometheus",
CaptureStdout: true,
CaptureStderr: true,
Stdin: nil,
})
if err != nil {
t.Fatalf("Failed to read config from pod %q: %v", p.pod, err)
}
if !strings.Contains(stdout, p.expectedShardConfigSnippet) {
t.Fatalf("Expected shard config to be present for %v but not found in config:\n\n%s\n\nexpected to find:\n\n%s", p.pod, stdout, p.expectedShardConfigSnippet)
}
}
}
func testResharding(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
prometheusName := "test"
group := "servicediscovery-test"
svc := framework.MakePrometheusService(prometheusName, group, v1.ServiceTypeClusterIP)
s := framework.MakeBasicServiceMonitor(group)
if _, err := framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), s, metav1.CreateOptions{}); err != nil {
t.Fatal("Creating ServiceMonitor failed: ", err)
}
p := framework.MakeBasicPrometheus(ns, prometheusName, group, 1)
p, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p)
if err != nil {
t.Fatal(err)
}
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, svc); err != nil {
t.Fatal(fmt.Errorf("creating prometheus service failed: %w", err))
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
shards := int32(2)
p, err = framework.PatchPrometheusAndWaitUntilReady(
context.Background(),
p.Name,
ns,
monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Shards: &shards,
},
},
)
if err != nil {
t.Fatal(err)
}
_, err = framework.KubeClient.AppsV1().StatefulSets(ns).Get(context.Background(), fmt.Sprintf("prometheus-%s", p.Name), metav1.GetOptions{})
if err != nil {
t.Fatal(err)
}
_, err = framework.KubeClient.AppsV1().StatefulSets(ns).Get(context.Background(), fmt.Sprintf("prometheus-%s-shard-1", p.Name), metav1.GetOptions{})
if err != nil {
t.Fatal(err)
}
shards = int32(1)
p, err = framework.PatchPrometheusAndWaitUntilReady(
context.Background(),
p.Name,
ns,
monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Shards: &shards,
},
},
)
if err != nil {
t.Fatal(err)
}
_, err = framework.KubeClient.AppsV1().StatefulSets(ns).Get(context.Background(), fmt.Sprintf("prometheus-%s", p.Name), metav1.GetOptions{})
if err != nil {
t.Fatal(err)
}
err = wait.PollUntilContextTimeout(context.Background(), time.Second, 1*time.Minute, false, func(ctx context.Context) (bool, error) {
_, err = framework.KubeClient.AppsV1().StatefulSets(ns).Get(ctx, fmt.Sprintf("prometheus-%s-shard-1", p.Name), metav1.GetOptions{})
if err != nil && !apierrors.IsNotFound(err) {
return false, err
}
if err == nil {
// StatefulSet still exists.
return false, nil
}
// StatefulSet not found.
return true, nil
})
}
func testPromAlertmanagerDiscovery(t *testing.T) {
for _, tc := range []struct {
sdRole monitoringv1.ServiceDiscoveryRole
}{
{
sdRole: monitoringv1.EndpointsRole,
},
{
sdRole: monitoringv1.EndpointSliceRole,
},
} {
t.Run(string(tc.sdRole), func(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
prometheusName := "test"
alertmanagerName := "test"
group := "servicediscovery-test"
svc := framework.MakePrometheusService(prometheusName, group, v1.ServiceTypeClusterIP)
amsvc := framework.MakeAlertmanagerService(alertmanagerName, group, v1.ServiceTypeClusterIP)
p := framework.MakeBasicPrometheus(ns, prometheusName, group, 1)
framework.AddAlertingToPrometheus(p, ns, alertmanagerName)
p.Spec.ServiceDiscoveryRole = ptr.To(tc.sdRole)
_, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p)
if err != nil {
t.Fatal(err)
}
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, svc); err != nil {
t.Fatal(fmt.Errorf("creating Prometheus service failed: %w", err))
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
s := framework.MakeBasicServiceMonitor(group)
if _, err := framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), s, metav1.CreateOptions{}); err != nil {
t.Fatalf("Creating ServiceMonitor failed: %v", err)
}
_, err = framework.KubeClient.CoreV1().Secrets(ns).Get(context.Background(), fmt.Sprintf("prometheus-%s", prometheusName), metav1.GetOptions{})
if err != nil {
t.Fatalf("Generated Secret could not be retrieved: %v", err)
}
if _, err := framework.CreateAlertmanagerAndWaitUntilReady(context.Background(), framework.MakeBasicAlertmanager(ns, alertmanagerName, 3)); err != nil {
t.Fatal(err)
}
if _, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, amsvc); err != nil {
t.Fatal(fmt.Errorf("creating Alertmanager service failed: %w", err))
}
err = wait.PollUntilContextTimeout(context.Background(), time.Second, 5*time.Minute, false, isAlertmanagerDiscoveryWorking(ns, svc.Name, alertmanagerName))
if err != nil {
t.Fatal(fmt.Errorf("validating Prometheus Alertmanager discovery failed: %w", err))
}
})
}
}
func testPromExposingWithKubernetesAPI(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
basicPrometheus := framework.MakeBasicPrometheus(ns, "basic-prometheus", "test-group", 1)
service := framework.MakePrometheusService(basicPrometheus.Name, "test-group", v1.ServiceTypeClusterIP)
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, basicPrometheus); err != nil {
t.Fatal("Creating prometheus failed: ", err)
}
if _, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, service); err != nil {
t.Fatal("Creating prometheus service failed: ", err)
}
ProxyGet := framework.KubeClient.CoreV1().Services(ns).ProxyGet
request := ProxyGet("", service.Name, "web", "/metrics", make(map[string]string))
_, err := request.DoRaw(context.Background())
if err != nil {
t.Fatal(err)
}
}
func testPromDiscoverTargetPort(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
prometheusName := "test"
group := "servicediscovery-test"
svc := framework.MakePrometheusService(prometheusName, group, v1.ServiceTypeClusterIP)
targetPort := intstr.FromInt(9090)
sm := &monitoringv1.ServiceMonitor{
ObjectMeta: metav1.ObjectMeta{
Name: prometheusName,
Labels: map[string]string{
"group": group,
},
},
Spec: monitoringv1.ServiceMonitorSpec{
Selector: metav1.LabelSelector{
MatchLabels: map[string]string{
"group": group,
},
},
Endpoints: []monitoringv1.Endpoint{
{
TargetPort: &targetPort,
Interval: "5s",
},
},
},
}
if _, err := framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), sm, metav1.CreateOptions{}); err != nil {
t.Fatal("Creating ServiceMonitor failed: ", err)
}
p := framework.MakeBasicPrometheus(ns, prometheusName, group, 1)
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p); err != nil {
t.Fatal(err)
}
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, svc); err != nil {
t.Fatal(fmt.Errorf("creating prometheus service failed: %w", err))
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
_, err := framework.KubeClient.CoreV1().Secrets(ns).Get(context.Background(), fmt.Sprintf("prometheus-%s", prometheusName), metav1.GetOptions{})
if err != nil {
t.Fatal("Generated Secret could not be retrieved: ", err)
}
err = framework.WaitForDiscoveryWorking(context.Background(), ns, svc.Name, prometheusName)
if err != nil {
t.Fatal(fmt.Errorf("validating Prometheus target discovery failed: %w", err))
}
}
func testPromOpMatchPromAndServMonInDiffNSs(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
prometheusNSName := framework.CreateNamespace(context.Background(), t, testCtx)
serviceMonitorNSName := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, prometheusNSName)
if err := framework.AddLabelsToNamespace(
context.Background(),
serviceMonitorNSName,
map[string]string{"team": "frontend"},
); err != nil {
t.Fatal(err)
}
group := "sample-app"
prometheusJobName := serviceMonitorNSName + "/" + group
prometheusName := "test"
svc := framework.MakePrometheusService(prometheusName, group, v1.ServiceTypeClusterIP)
s := framework.MakeBasicServiceMonitor(group)
if _, err := framework.MonClientV1.ServiceMonitors(serviceMonitorNSName).Create(context.Background(), s, metav1.CreateOptions{}); err != nil {
t.Fatal("Creating ServiceMonitor failed: ", err)
}
p := framework.MakeBasicPrometheus(prometheusNSName, prometheusName, group, 1)
p.Spec.ServiceMonitorNamespaceSelector = &metav1.LabelSelector{
MatchLabels: map[string]string{
"team": "frontend",
},
}
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), prometheusNSName, p); err != nil {
t.Fatal(err)
}
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), prometheusNSName, svc); err != nil {
t.Fatal(fmt.Errorf("creating prometheus service failed: %w", err))
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
resp, err := framework.PrometheusSVCGetRequest(context.Background(), prometheusNSName, svc.Name, "http", "/api/v1/status/config", map[string]string{})
if err != nil {
t.Fatal(err)
}
if strings.Count(string(resp), prometheusJobName) != 1 {
t.Fatalf("expected Prometheus operator to configure Prometheus in ns '%v' to scrape the service monitor in ns '%v'", prometheusNSName, serviceMonitorNSName)
}
}
// testThanos deploys a Prometheus resource with 2 replicas ans Thanos sidecar
// and verifies that it can be queried by a Thanos Querier.
func testThanos(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
prom := framework.MakeBasicPrometheus(ns, "basic-prometheus", "test-group", 1)
prom.Spec.Replicas = proto.Int32(2)
prom.Spec.Thanos = &monitoringv1.ThanosSpec{
Version: ptr.To(operator.DefaultThanosVersion),
}
prom, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, prom)
require.NoError(t, err)
promSvc := framework.MakePrometheusService(prom.Name, "test-group", v1.ServiceTypeClusterIP)
_, err = framework.KubeClient.CoreV1().Services(ns).Create(context.Background(), promSvc, metav1.CreateOptions{})
require.NoError(t, err)
svcMon := framework.MakeBasicServiceMonitor("test-group")
_, err = framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), svcMon, metav1.CreateOptions{})
require.NoError(t, err)
querier, err := testFramework.MakeThanosQuerier(
fmt.Sprintf("dnssrv+_grpc._tcp.prometheus-operated.%s.svc.cluster.local", ns),
)
require.NoError(t, err)
err = framework.CreateDeployment(context.Background(), ns, querier)
require.NoError(t, err)
qrySvc := framework.MakeThanosQuerierService(querier.Name)
_, err = framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, qrySvc)
require.NoError(t, err)
err = wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 5*time.Minute, false, func(ctx context.Context) (bool, error) {
proxyGet := framework.KubeClient.CoreV1().Services(ns).ProxyGet
request := proxyGet("http", qrySvc.Name, "web", "/api/v1/query", map[string]string{
"query": "prometheus_build_info",
"dedup": "false",
})
b, err := request.DoRaw(ctx)
if err != nil {
t.Logf("Error performing request against Thanos query: %v\n\nretrying...", err)
return false, nil
}
d := struct {
Data struct {
Result []map[string]any `json:"result"`
} `json:"data"`
}{}
err = json.Unmarshal(b, &d)
if err != nil {
return false, err
}
result := len(d.Data.Result)
// We're expecting 4 results as we are requesting the
// `prometheus_build_info` metric, which is collected for both
// Prometheus replicas by both replicas.
expected := 4
if result != expected {
t.Logf("Unexpected number of results from query. Got %d, expected %d. retrying...", result, expected)
return false, nil
}
return true, nil
})
require.NoError(t, err)
}
func testPromGetAuthSecret(t *testing.T) {
t.Parallel()
name := "test"
tests := []struct {
name string
secret *v1.Secret
serviceMonitor func() *monitoringv1.ServiceMonitor
}{
{
name: "basic-auth",
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string][]byte{
"user": []byte("user"),
"password": []byte("pass"),
},
},
serviceMonitor: func() *monitoringv1.ServiceMonitor {
sm := framework.MakeBasicServiceMonitor(name)
sm.Spec.Endpoints[0].BasicAuth = &monitoringv1.BasicAuth{
Username: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "user",
},
Password: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "password",
},
}
return sm
},
},
{
name: "bearer-token",
secret: &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string][]byte{
"bearertoken": []byte("abc"),
},
},
serviceMonitor: func() *monitoringv1.ServiceMonitor {
sm := framework.MakeBasicServiceMonitor(name)
sm.Spec.Endpoints[0].BearerTokenSecret = &v1.SecretKeySelector{ //nolint:staticcheck // Ignore SA1019 this field is marked as deprecated.
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "bearertoken",
}
sm.Spec.Endpoints[0].Path = "/bearer-metrics"
return sm
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBACGlobal(context.Background(), t, testCtx, ns)
matchLabels := map[string]string{
"tc": ns,
}
testNamespace := framework.CreateNamespace(context.Background(), t, testCtx)
err := framework.AddLabelsToNamespace(context.Background(), testNamespace, matchLabels)
if err != nil {
t.Fatal(err)
}
simple, err := testFramework.MakeDeployment("../../test/framework/resources/basic-auth-app-deployment.yaml")
if err != nil {
t.Fatal(err)
}
if err := framework.CreateDeployment(context.Background(), testNamespace, simple); err != nil {
t.Fatal("Creating simple basic auth app failed: ", err)
}
authSecret := test.secret
if _, err := framework.KubeClient.CoreV1().Secrets(testNamespace).Create(context.Background(), authSecret, metav1.CreateOptions{}); err != nil {
t.Fatal(err)
}
svc := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: map[string]string{
"group": name,
},
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
Ports: []v1.ServicePort{
{
Name: "web",
Port: 8080,
},
},
Selector: map[string]string{
"group": name,
},
},
}
sm := test.serviceMonitor()
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), testNamespace, svc); err != nil {
t.Fatal(err)
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
if _, err := framework.MonClientV1.ServiceMonitors(testNamespace).Create(context.Background(), sm, metav1.CreateOptions{}); err != nil {
t.Fatal("Creating ServiceMonitor failed: ", err)
}
prometheusCRD := framework.MakeBasicPrometheus(ns, name, name, 1)
prometheusCRD.Spec.ServiceMonitorNamespaceSelector = &metav1.LabelSelector{
MatchLabels: matchLabels,
}
prometheusCRD.Spec.ScrapeInterval = "1s"
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, prometheusCRD); err != nil {
t.Fatal(err)
}
if err := framework.WaitForHealthyTargets(context.Background(), ns, "prometheus-operated", 1); err != nil {
t.Fatal(err)
}
})
}
}
// testOperatorNSScope tests the multi namespace feature of the Prometheus Operator.
// It checks whether it ignores rules that are not in the watched namespaces of the
// Prometheus Operator. The Prometheus Operator internally treats watching a
// single namespace different than watching multiple namespaces, hence the two
// sub-tests.
func testOperatorNSScope(t *testing.T) {
name := "test"
firtAlertName := "firstAlert"
secondAlertName := "secondAlert"
t.Run("SingleNS", func(t *testing.T) {
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
operatorNS := framework.CreateNamespace(context.Background(), t, testCtx)
mainNS := framework.CreateNamespace(context.Background(), t, testCtx)
arbitraryNS := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, mainNS)
prometheusNamespaceSelector := map[string]string{"prometheus": mainNS}
// Add labels to namespaces for Prometheus RuleNamespaceSelector.
for _, ns := range []string{mainNS, arbitraryNS} {
err := framework.AddLabelsToNamespace(context.Background(), ns, prometheusNamespaceSelector)
if err != nil {
t.Fatal(err)
}
}
// Prometheus Operator only watches single namespace mainNS, not arbitraryNS.
_, err := framework.CreateOrUpdatePrometheusOperator(context.Background(), operatorNS, []string{mainNS}, nil, nil, nil, false, true, true)
if err != nil {
t.Fatal(err)
}
ruleDef := []struct {
NSName string
AlertName string
}{{arbitraryNS, secondAlertName}, {mainNS, firtAlertName}}
for _, r := range ruleDef {
_, err := framework.MakeAndCreateFiringRule(context.Background(), r.NSName, name, r.AlertName)
if err != nil {
t.Fatal(err)
}
}
p := framework.MakeBasicPrometheus(mainNS, name, name, 1)
p.Spec.RuleNamespaceSelector = &metav1.LabelSelector{
MatchLabels: prometheusNamespaceSelector,
}
p.Spec.EvaluationInterval = "1s"
p, err = framework.CreatePrometheusAndWaitUntilReady(context.Background(), mainNS, p)
if err != nil {
t.Fatal(err)
}
pSVC := framework.MakePrometheusService(p.Name, "not-relevant", v1.ServiceTypeClusterIP)
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), mainNS, pSVC); err != nil {
t.Fatal(fmt.Errorf("creating Prometheus service failed: %w", err))
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
err = framework.WaitForPrometheusFiringAlert(context.Background(), p.Namespace, pSVC.Name, firtAlertName)
if err != nil {
t.Fatal(err)
}
alerts, err := framework.GetPrometheusFiringAlerts(context.Background(), p.Namespace, pSVC.Name, secondAlertName)
require.NoError(t, err)
require.Len(t, alerts, 0)
})
t.Run("MultiNS", func(t *testing.T) {
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
operatorNS := framework.CreateNamespace(context.Background(), t, testCtx)
prometheusNS := framework.CreateNamespace(context.Background(), t, testCtx)
ruleNS := framework.CreateNamespace(context.Background(), t, testCtx)
arbitraryNS := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, prometheusNS)
prometheusNamespaceSelector := map[string]string{"prometheus": prometheusNS}
for _, ns := range []string{ruleNS, arbitraryNS} {
err := framework.AddLabelsToNamespace(context.Background(), ns, prometheusNamespaceSelector)
if err != nil {
t.Fatal(err)
}
}
// Prometheus Operator only watches prometheusNS and ruleNS, not arbitraryNS.
_, err := framework.CreateOrUpdatePrometheusOperator(context.Background(), operatorNS, []string{prometheusNS, ruleNS}, nil, nil, nil, false, true, true)
if err != nil {
t.Fatal(err)
}
ruleDef := []struct {
NSName string
AlertName string
}{{arbitraryNS, secondAlertName}, {ruleNS, firtAlertName}}
for _, r := range ruleDef {
_, err := framework.MakeAndCreateFiringRule(context.Background(), r.NSName, name, r.AlertName)
if err != nil {
t.Fatal(err)
}
}
p := framework.MakeBasicPrometheus(prometheusNS, name, name, 1)
p.Spec.RuleNamespaceSelector = &metav1.LabelSelector{
MatchLabels: prometheusNamespaceSelector,
}
p.Spec.EvaluationInterval = "1s"
p, err = framework.CreatePrometheusAndWaitUntilReady(context.Background(), prometheusNS, p)
if err != nil {
t.Fatal(err)
}
pSVC := framework.MakePrometheusService(p.Name, "not-relevant", v1.ServiceTypeClusterIP)
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), prometheusNS, pSVC); err != nil {
t.Fatal(fmt.Errorf("creating Prometheus service failed: %w", err))
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
err = framework.WaitForPrometheusFiringAlert(context.Background(), p.Namespace, pSVC.Name, firtAlertName)
if err != nil {
t.Fatal(err)
}
alerts, err := framework.GetPrometheusFiringAlerts(context.Background(), p.Namespace, pSVC.Name, secondAlertName)
require.NoError(t, err)
require.Len(t, alerts, 0)
})
}
// testPromArbitraryFSAcc tests the
// github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1.PrometheusSpec.ArbitraryFSAccessThroughSMs
// configuration with the service monitor bearer token and tls assets option.
func testPromArbitraryFSAcc(t *testing.T) {
t.Parallel()
name := "test"
tests := []struct {
name string
arbitraryFSAccessThroughSMsConfig monitoringv1.ArbitraryFSAccessThroughSMsConfig
endpoint monitoringv1.Endpoint
expectTargets bool
}{
//
// Bearer tokens:
//
{
name: "allowed-bearer-file",
arbitraryFSAccessThroughSMsConfig: monitoringv1.ArbitraryFSAccessThroughSMsConfig{
Deny: false,
},
endpoint: monitoringv1.Endpoint{
Port: "web",
BearerTokenFile: "/etc/ca-certificates/bearer-token",
},
expectTargets: true,
},
{
name: "denied-bearer-file",
arbitraryFSAccessThroughSMsConfig: monitoringv1.ArbitraryFSAccessThroughSMsConfig{
Deny: true,
},
endpoint: monitoringv1.Endpoint{
Port: "web",
BearerTokenFile: "/etc/ca-certificates/bearer-token",
},
expectTargets: false,
},
{
name: "denied-bearer-secret",
arbitraryFSAccessThroughSMsConfig: monitoringv1.ArbitraryFSAccessThroughSMsConfig{
Deny: true,
},
endpoint: monitoringv1.Endpoint{
Port: "web",
BearerTokenSecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "bearer-token",
},
},
expectTargets: true,
},
//
// TLS assets:
//
{
name: "allowed-tls-file",
arbitraryFSAccessThroughSMsConfig: monitoringv1.ArbitraryFSAccessThroughSMsConfig{
Deny: false,
},
endpoint: monitoringv1.Endpoint{
Port: "web",
TLSConfig: &monitoringv1.TLSConfig{
CAFile: "/etc/ca-certificates/cert.pem",
CertFile: "/etc/ca-certificates/cert.pem",
KeyFile: "/etc/ca-certificates/key.pem",
},
},
expectTargets: true,
},
{
name: "denied-tls-file",
arbitraryFSAccessThroughSMsConfig: monitoringv1.ArbitraryFSAccessThroughSMsConfig{
Deny: true,
},
endpoint: monitoringv1.Endpoint{
Port: "web",
TLSConfig: &monitoringv1.TLSConfig{
CAFile: "/etc/ca-certificates/cert.pem",
CertFile: "/etc/ca-certificates/cert.pem",
KeyFile: "/etc/ca-certificates/key.pem",
},
},
expectTargets: false,
},
{
name: "denied-tls-secret",
arbitraryFSAccessThroughSMsConfig: monitoringv1.ArbitraryFSAccessThroughSMsConfig{
Deny: true,
},
endpoint: monitoringv1.Endpoint{
Port: "web",
TLSConfig: &monitoringv1.TLSConfig{
SafeTLSConfig: monitoringv1.SafeTLSConfig{
InsecureSkipVerify: ptr.To(true),
CA: monitoringv1.SecretOrConfigMap{
Secret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "cert.pem",
},
},
Cert: monitoringv1.SecretOrConfigMap{
Secret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "cert.pem",
},
},
KeySecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "key.pem",
},
},
},
},
expectTargets: true,
},
{
name: "denied-tls-configmap",
arbitraryFSAccessThroughSMsConfig: monitoringv1.ArbitraryFSAccessThroughSMsConfig{
Deny: true,
},
endpoint: monitoringv1.Endpoint{
Port: "web",
TLSConfig: &monitoringv1.TLSConfig{
SafeTLSConfig: monitoringv1.SafeTLSConfig{
InsecureSkipVerify: ptr.To(true),
CA: monitoringv1.SecretOrConfigMap{
ConfigMap: &v1.ConfigMapKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "cert.pem",
},
},
Cert: monitoringv1.SecretOrConfigMap{
ConfigMap: &v1.ConfigMapKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "cert.pem",
},
},
KeySecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "key.pem",
},
},
},
},
expectTargets: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
// Create secret either used by bearer token secret key ref, tls
// asset key ref or tls configmap key ref.
cert, err := os.ReadFile("../../test/instrumented-sample-app/certs/cert.pem")
if err != nil {
t.Fatalf("failed to load cert.pem: %v", err)
}
key, err := os.ReadFile("../../test/instrumented-sample-app/certs/key.pem")
if err != nil {
t.Fatalf("failed to load key.pem: %v", err)
}
tlsCertsSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string][]byte{
"cert.pem": cert,
"key.pem": key,
"bearer-token": []byte("abc"),
},
}
if _, err := framework.KubeClient.CoreV1().Secrets(ns).Create(context.Background(), tlsCertsSecret, metav1.CreateOptions{}); err != nil {
t.Fatal(err)
}
tlsCertsConfigMap := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string]string{
"cert.pem": string(cert),
},
}
if _, err := framework.KubeClient.CoreV1().ConfigMaps(ns).Create(context.Background(), tlsCertsConfigMap, metav1.CreateOptions{}); err != nil {
t.Fatal(err)
}
s := framework.MakeBasicServiceMonitor(name)
s.Spec.Endpoints[0] = test.endpoint
if _, err := framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), s, metav1.CreateOptions{}); err != nil {
t.Fatal("creating ServiceMonitor failed: ", err)
}
prometheusCRD := framework.MakeBasicPrometheus(ns, name, name, 1)
prometheusCRD.Namespace = ns
prometheusCRD.Spec.ArbitraryFSAccessThroughSMs = test.arbitraryFSAccessThroughSMsConfig
if strings.HasSuffix(test.name, "-file") {
mountTLSFiles(prometheusCRD, name)
}
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, prometheusCRD); err != nil {
t.Fatal(err)
}
svc := framework.MakePrometheusService(prometheusCRD.Name, name, v1.ServiceTypeClusterIP)
if _, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, svc); err != nil {
t.Fatal(err)
}
if test.expectTargets {
if err := framework.WaitForActiveTargets(context.Background(), ns, svc.Name, 1); err != nil {
t.Fatal(err)
}
return
}
// Make sure Prometheus has enough time to reload.
time.Sleep(2 * time.Minute)
if err := framework.WaitForActiveTargets(context.Background(), ns, svc.Name, 0); err != nil {
t.Fatal(err)
}
})
}
}
// mountTLSFiles is a helper to manually mount TLS certificate files
// into the prometheus container.
func mountTLSFiles(p *monitoringv1.Prometheus, secretName string) {
volumeName := secretName
p.Spec.Volumes = append(p.Spec.Volumes,
v1.Volume{
Name: volumeName,
VolumeSource: v1.VolumeSource{
Secret: &v1.SecretVolumeSource{
SecretName: secretName,
},
},
})
p.Spec.Containers = []v1.Container{
{
Name: "prometheus",
VolumeMounts: []v1.VolumeMount{
{
Name: volumeName,
MountPath: "/etc/ca-certificates",
},
},
},
}
}
// testPromTLSConfigViaSecret tests the service monitor endpoint option to load
// certificate assets via Kubernetes secrets into the Prometheus container.
func testPromTLSConfigViaSecret(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "test"
//
// Setup sample app.
//
cert, err := os.ReadFile("../../test/instrumented-sample-app/certs/cert.pem")
if err != nil {
t.Fatalf("failed to load cert.pem: %v", err)
}
key, err := os.ReadFile("../../test/instrumented-sample-app/certs/key.pem")
if err != nil {
t.Fatalf("failed to load key.pem: %v", err)
}
tlsCertsSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string][]byte{
"cert.pem": cert,
"key.pem": key,
},
}
if _, err := framework.KubeClient.CoreV1().Secrets(ns).Create(context.Background(), tlsCertsSecret, metav1.CreateOptions{}); err != nil {
t.Fatal(err)
}
simple, err := testFramework.MakeDeployment("../../test/framework/resources/basic-auth-app-deployment.yaml")
if err != nil {
t.Fatal(err)
}
simple.Spec.Template.Spec.Containers[0].Args = []string{"--cert-path=/etc/certs"}
simple.Spec.Template.Spec.Volumes = []v1.Volume{
{
Name: "tls-certs",
VolumeSource: v1.VolumeSource{
Secret: &v1.SecretVolumeSource{
SecretName: tlsCertsSecret.Name,
},
},
},
}
simple.Spec.Template.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{
{
Name: simple.Spec.Template.Spec.Volumes[0].Name,
MountPath: "/etc/certs",
},
}
if err := framework.CreateDeployment(context.Background(), ns, simple); err != nil {
t.Fatal("Creating simple basic auth app failed: ", err)
}
svc := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: map[string]string{
"group": name,
},
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
Ports: []v1.ServicePort{
{
Name: "web",
Port: 8080,
},
{
Name: "mtls",
Port: 8081,
},
},
Selector: map[string]string{
"group": name,
},
},
}
if _, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, svc); err != nil {
t.Fatal(err)
}
//
// Setup monitoring.
//
sm := framework.MakeBasicServiceMonitor(name)
sm.Spec.Endpoints = []monitoringv1.Endpoint{
{
Port: "mtls",
Interval: "30s",
Scheme: ptr.To(monitoringv1.SchemeHTTPS),
TLSConfig: &monitoringv1.TLSConfig{
SafeTLSConfig: monitoringv1.SafeTLSConfig{
InsecureSkipVerify: ptr.To(true),
Cert: monitoringv1.SecretOrConfigMap{
Secret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: tlsCertsSecret.Name,
},
Key: "cert.pem",
},
},
KeySecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: tlsCertsSecret.Name,
},
Key: "key.pem",
},
},
},
},
}
if _, err := framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), sm, metav1.CreateOptions{}); err != nil {
t.Fatal("creating ServiceMonitor failed: ", err)
}
prometheusCRD := framework.MakeBasicPrometheus(ns, name, name, 1)
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, prometheusCRD); err != nil {
t.Fatal(err)
}
promSVC := framework.MakePrometheusService(prometheusCRD.Name, name, v1.ServiceTypeClusterIP)
if _, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, promSVC); err != nil {
t.Fatal(err)
}
//
// Check for proper scraping.
//
if err := framework.WaitForHealthyTargets(context.Background(), ns, promSVC.Name, 1); err != nil {
t.Fatal(err)
}
// TODO: Do a poll instead, should speed up things.
time.Sleep(30 * time.Second)
response, err := framework.PrometheusSVCGetRequest(context.Background(), ns, promSVC.Name, "http", "/api/v1/query", map[string]string{"query": fmt.Sprintf(`up{job="%v",endpoint="%v"}`, name, sm.Spec.Endpoints[0].Port)})
if err != nil {
t.Fatal(err)
}
q := testFramework.PrometheusQueryAPIResponse{}
if err := json.NewDecoder(bytes.NewBuffer(response)).Decode(&q); err != nil {
t.Fatal(err)
}
if q.Status != "success" {
t.Fatalf("expected query status to be 'success' but got %v", q.Status)
}
if q.Data.Result[0].Value[1] != "1" {
t.Fatalf("expected query result to be '1' but got %v", q.Data.Result[0].Value[1])
}
}
func testPromStaticProbe(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
blackboxExporterName := "blackbox-exporter"
if err := framework.CreateBlackBoxExporterAndWaitUntilReady(context.Background(), ns, blackboxExporterName); err != nil {
t.Fatal("Creating blackbox exporter failed: ", err)
}
blackboxSvc := framework.MakeBlackBoxExporterService(ns, blackboxExporterName)
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, blackboxSvc); err != nil {
t.Fatal("creating blackbox exporter service failed ", err)
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
prometheusName := "test"
group := "probe-test"
svc := framework.MakePrometheusService(prometheusName, group, v1.ServiceTypeClusterIP)
proberURL := blackboxExporterName + ":9115"
targets := []string{svc.Name + ":9090"}
probe := framework.MakeBasicStaticProbe(group, proberURL, targets)
if _, err := framework.MonClientV1.Probes(ns).Create(context.Background(), probe, metav1.CreateOptions{}); err != nil {
t.Fatal("Creating Probe failed: ", err)
}
p := framework.MakeBasicPrometheus(ns, prometheusName, group, 1)
p.Spec.ProbeSelector = &metav1.LabelSelector{
MatchLabels: map[string]string{
"group": group,
},
}
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p); err != nil {
t.Fatal(err)
}
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, svc); err != nil {
t.Fatal(fmt.Errorf("creating prometheus service failed: %w", err))
} else {
testCtx.AddFinalizerFn(finalizerFn)
}
expectedURL := url.URL{Host: proberURL, Scheme: "http", Path: "/probe"}
q := expectedURL.Query()
q.Set("module", "http_2xx")
q.Set("target", targets[0])
expectedURL.RawQuery = q.Encode()
if err := wait.PollUntilContextTimeout(context.Background(), time.Second, time.Minute*5, false, func(ctx context.Context) (bool, error) {
activeTargets, err := framework.GetActiveTargets(ctx, ns, svc.Name)
if err != nil {
return false, err
}
if len(activeTargets) != 1 {
return false, nil
}
exp := expectedURL.String()
if activeTargets[0].ScrapeURL != exp {
return false, nil
}
if value, ok := activeTargets[0].Labels["instance"]; !ok || value != targets[0] {
return false, nil
}
return true, nil
}); err != nil {
t.Fatal("waiting for static probe targets timed out.")
}
}
func testPromSecurePodMonitor(t *testing.T) {
t.Parallel()
name := "test"
tests := []struct {
name string
endpoint monitoringv1.PodMetricsEndpoint
}{
//
// Basic auth:
//
{
name: "basic-auth-secret",
endpoint: monitoringv1.PodMetricsEndpoint{
Port: ptr.To("web"),
HTTPConfig: monitoringv1.HTTPConfig{
BasicAuth: &monitoringv1.BasicAuth{
Username: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "user",
},
Password: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "password",
},
},
},
},
},
//
// Bearer tokens:
//
{
name: "bearer-secret",
endpoint: monitoringv1.PodMetricsEndpoint{
Port: ptr.To("web"),
HTTPConfig: monitoringv1.HTTPConfig{
BearerTokenSecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "bearer-token",
},
},
Path: "/bearer-metrics",
},
},
//
// TLS assets:
//
{
name: "tls-secret",
endpoint: monitoringv1.PodMetricsEndpoint{
Port: ptr.To("mtls"),
Scheme: ptr.To(monitoringv1.SchemeHTTPS),
HTTPConfig: monitoringv1.HTTPConfig{
TLSConfig: &monitoringv1.SafeTLSConfig{
InsecureSkipVerify: ptr.To(true),
CA: monitoringv1.SecretOrConfigMap{
Secret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "cert.pem",
},
},
Cert: monitoringv1.SecretOrConfigMap{
Secret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "cert.pem",
},
},
KeySecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "key.pem",
},
},
},
Path: "/",
},
},
{
name: "tls-configmap",
endpoint: monitoringv1.PodMetricsEndpoint{
Port: ptr.To("mtls"),
Scheme: ptr.To(monitoringv1.SchemeHTTPS),
HTTPConfig: monitoringv1.HTTPConfig{
TLSConfig: &monitoringv1.SafeTLSConfig{
InsecureSkipVerify: ptr.To(true),
CA: monitoringv1.SecretOrConfigMap{
ConfigMap: &v1.ConfigMapKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "cert.pem",
},
},
Cert: monitoringv1.SecretOrConfigMap{
ConfigMap: &v1.ConfigMapKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "cert.pem",
},
},
KeySecret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Key: "key.pem",
},
},
},
Path: "/",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
// Create secret either used by bearer token secret key ref, tls
// asset key ref or tls configmap key ref.
cert, err := os.ReadFile("../../test/instrumented-sample-app/certs/cert.pem")
if err != nil {
t.Fatalf("failed to load cert.pem: %v", err)
}
key, err := os.ReadFile("../../test/instrumented-sample-app/certs/key.pem")
if err != nil {
t.Fatalf("failed to load key.pem: %v", err)
}
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string][]byte{
"user": []byte("user"),
"password": []byte("pass"),
"bearer-token": []byte("abc"),
"cert.pem": cert,
"key.pem": key,
},
}
if _, err := framework.KubeClient.CoreV1().Secrets(ns).Create(context.Background(), secret, metav1.CreateOptions{}); err != nil {
t.Fatal(err)
}
tlsCertsConfigMap := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string]string{
"cert.pem": string(cert),
},
}
if _, err := framework.KubeClient.CoreV1().ConfigMaps(ns).Create(context.Background(), tlsCertsConfigMap, metav1.CreateOptions{}); err != nil {
t.Fatal(err)
}
simple, err := testFramework.MakeDeployment("../../test/framework/resources/basic-auth-app-deployment.yaml")
if err != nil {
t.Fatal(err)
}
simple.Spec.Template.Spec.Volumes = []v1.Volume{
{
Name: name,
VolumeSource: v1.VolumeSource{
Secret: &v1.SecretVolumeSource{
SecretName: name,
},
},
},
}
simple.Spec.Template.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{
{
Name: name,
MountPath: "/etc/ca-certificates",
},
}
if *test.endpoint.Port == "mtls" {
simple.Spec.Template.Spec.Containers[0].Args = []string{"--cert-path=/etc/ca-certificates"}
}
if err := framework.CreateDeployment(context.Background(), ns, simple); err != nil {
t.Fatal("failed to create simple basic auth app: ", err)
}
pm := framework.MakeBasicPodMonitor(name)
pm.Spec.PodMetricsEndpoints = []monitoringv1.PodMetricsEndpoint{test.endpoint}
if _, err := framework.MonClientV1.PodMonitors(ns).Create(context.Background(), pm, metav1.CreateOptions{}); err != nil {
t.Fatal("failed to create PodMonitor: ", err)
}
prom := framework.MakeBasicPrometheus(ns, name, name, 1)
prom.Spec.ScrapeInterval = "1s"
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, prom); err != nil {
t.Fatal(err)
}
if err := framework.WaitForHealthyTargets(context.Background(), ns, "prometheus-operated", 1); err != nil {
t.Fatal(err)
}
})
}
}
func testPromWebWithThanosSidecar(t *testing.T) {
t.Parallel()
trueVal := true
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
host := fmt.Sprintf("%s.%s.svc", "basic-prometheus", ns)
certBytes, keyBytes, err := certutil.GenerateSelfSignedCertKey(host, nil, nil)
if err != nil {
t.Fatal(err)
}
kubeClient := framework.KubeClient
if err := framework.CreateOrUpdateSecretWithCert(context.Background(), certBytes, keyBytes, ns, "web-tls"); err != nil {
t.Fatal(err)
}
version := operator.DefaultThanosVersion
prom := framework.MakeBasicPrometheus(ns, "basic-prometheus", "test-group", 1)
prom.Spec.Web = &monitoringv1.PrometheusWebSpec{
WebConfigFileFields: monitoringv1.WebConfigFileFields{
TLSConfig: &monitoringv1.WebTLSConfig{
KeySecret: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "web-tls",
},
Key: "tls.key",
},
Cert: monitoringv1.SecretOrConfigMap{
Secret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "web-tls",
},
Key: "tls.crt",
},
},
},
HTTPConfig: &monitoringv1.WebHTTPConfig{
HTTP2: &trueVal,
Headers: &monitoringv1.WebHTTPHeaders{
ContentSecurityPolicy: "default-src 'self'",
XFrameOptions: "Deny",
XContentTypeOptions: "NoSniff",
XXSSProtection: "1; mode=block",
StrictTransportSecurity: "max-age=31536000; includeSubDomains",
},
},
},
}
prom.Spec.Thanos = &monitoringv1.ThanosSpec{
Version: &version,
}
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, prom); err != nil {
t.Fatalf("Creating prometheus failed: %v", err)
}
var pollErr error
err = wait.PollUntilContextTimeout(context.Background(), time.Second, time.Minute, false, func(ctx context.Context) (bool, error) {
promPods, err := kubeClient.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{})
if err != nil {
pollErr = err
return false, nil
}
if len(promPods.Items) == 0 {
pollErr = fmt.Errorf("No prometheus pods found in namespace %s", ns)
return false, nil
}
cfg := framework.RestConfig
podName := promPods.Items[0].Name
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
closer, err := testFramework.StartPortForward(ctx, cfg, "https", podName, ns, "9090")
if err != nil {
pollErr = fmt.Errorf("failed to start port forwarding: %v", err)
t.Log(pollErr)
return false, nil
}
defer closer()
req, err := http.NewRequestWithContext(ctx, "GET", "https://localhost:9090", nil)
if err != nil {
pollErr = err
return false, nil
}
// The prometheus certificate is issued to <pod>.<namespace>.svc,
// but port-forwarding is done through localhost.
// This is why we use an http client which skips the TLS verification.
// In the test we will verify the TLS certificate manually to make sure
// the prometheus instance is configured properly.
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
err = http2.ConfigureTransport(transport)
if err != nil {
pollErr = err
return false, nil
}
httpClient := http.Client{
Transport: transport,
}
resp, err := httpClient.Do(req)
if err != nil {
pollErr = err
return false, nil
}
if resp.ProtoMajor != 2 {
pollErr = fmt.Errorf("expected ProtoMajor to be 2 but got %d", resp.ProtoMajor)
return false, nil
}
receivedCertBytes, err := certutil.EncodeCertificates(resp.TLS.PeerCertificates...)
if err != nil {
pollErr = err
return false, nil
}
if !bytes.Equal(receivedCertBytes, certBytes) {
pollErr = fmt.Errorf("certificate received from prometheus instance does not match the one which is configured")
return false, nil
}
expectedHeaders := map[string]string{
"Content-Security-Policy": "default-src 'self'",
"X-Frame-Options": "deny",
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
}
for k, v := range expectedHeaders {
rv := resp.Header.Get(k)
if rv != v {
pollErr = fmt.Errorf("expected header %s value to be %s but got %s", k, v, rv)
return false, nil
}
}
reloadSuccessTimestamp, err := framework.GetMetricValueFromPod(context.Background(), "https", ns, podName, "8080", "reloader_last_reload_success_timestamp_seconds")
if err != nil {
pollErr = err
return false, nil
}
if reloadSuccessTimestamp == 0 {
pollErr = fmt.Errorf("config reloader failed to reload once")
return false, nil
}
thanosSidecarPrometheusUp, err := framework.GetMetricValueFromPod(context.Background(), "http", ns, podName, "10902", "thanos_sidecar_prometheus_up")
if err != nil {
pollErr = err
return false, nil
}
if thanosSidecarPrometheusUp == 0 {
pollErr = fmt.Errorf("thanos sidecar failed to connect prometheus")
return false, nil
}
return true, nil
})
if err != nil {
t.Fatalf("poll function execution error: %v: %v", err, pollErr)
}
// Simulate a certificate renewal and check that the new certificate is in place
certBytesNew, keyBytesNew, err := certutil.GenerateSelfSignedCertKey(host, nil, nil)
if err != nil {
t.Fatal(err)
}
if err = framework.CreateOrUpdateSecretWithCert(context.Background(), certBytesNew, keyBytesNew, ns, "web-tls"); err != nil {
t.Fatal(err)
}
err = wait.PollUntilContextTimeout(context.Background(), time.Second, 2*time.Minute, false, func(ctx context.Context) (bool, error) {
promPods, err := kubeClient.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{})
if err != nil {
pollErr = err
return false, nil
}
if len(promPods.Items) == 0 {
pollErr = fmt.Errorf("No prometheus pods found in namespace %s", ns)
return false, nil
}
cfg := framework.RestConfig
podName := promPods.Items[0].Name
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
closer, err := testFramework.StartPortForward(ctx, cfg, "https", podName, ns, "9090")
if err != nil {
pollErr = fmt.Errorf("failed to start port forwarding: %v", err)
t.Log(pollErr)
return false, nil
}
defer closer()
req, err := http.NewRequestWithContext(ctx, "GET", "https://localhost:9090", nil)
if err != nil {
pollErr = err
return false, nil
}
// The prometheus certificate is issued to <pod>.<namespace>.svc,
// but port-forwarding is done through localhost.
// This is why we use an http client which skips the TLS verification.
// In the test we will verify the TLS certificate manually to make sure
// the prometheus instance is configured properly.
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
err = http2.ConfigureTransport(transport)
if err != nil {
pollErr = err
return false, nil
}
httpClient := http.Client{
Transport: transport,
}
resp, err := httpClient.Do(req)
if err != nil {
pollErr = err
return false, nil
}
if resp.ProtoMajor != 2 {
pollErr = fmt.Errorf("expected ProtoMajor to be 2 but got %d", resp.ProtoMajor)
return false, nil
}
receivedCertBytes, err := certutil.EncodeCertificates(resp.TLS.PeerCertificates...)
if err != nil {
pollErr = err
return false, nil
}
if !bytes.Equal(receivedCertBytes, certBytesNew) {
pollErr = fmt.Errorf("certificate received from prometheus instance does not match the one which is configured after certificate renewal")
return false, nil
}
return true, nil
})
if err != nil {
t.Fatalf("poll function execution error: %v: %v", err, pollErr)
}
}
func testPromMinReadySeconds(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
kubeClient := framework.KubeClient
prom := framework.MakeBasicPrometheus(ns, "basic-prometheus", "test-group", 1)
prom.Spec.MinReadySeconds = ptr.To(int32(5))
prom, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, prom)
require.NoError(t, err)
promSS, err := kubeClient.AppsV1().StatefulSets(ns).Get(context.Background(), "prometheus-basic-prometheus", metav1.GetOptions{})
require.NoError(t, err)
require.Equal(t, int32(5), promSS.Spec.MinReadySeconds)
_, err = framework.PatchPrometheusAndWaitUntilReady(
context.Background(),
prom.Name,
ns,
monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
MinReadySeconds: ptr.To(int32(10)),
},
},
)
require.NoError(t, err)
promSS, err = kubeClient.AppsV1().StatefulSets(ns).Get(context.Background(), "prometheus-basic-prometheus", metav1.GetOptions{})
require.NoError(t, err)
require.Equal(t, int32(10), promSS.Spec.MinReadySeconds)
}
// testPromEnforcedNamespaceLabel checks that the enforcedNamespaceLabel field
// is honored even if a user tries to bypass the enforcement.
func testPromEnforcedNamespaceLabel(t *testing.T) {
t.Parallel()
for i, tc := range []struct {
relabelConfigs []monitoringv1.RelabelConfig
metricRelabelConfigs []monitoringv1.RelabelConfig
}{
{
// override label using the labeldrop action.
relabelConfigs: []monitoringv1.RelabelConfig{
{
Regex: "namespace",
Action: "labeldrop",
},
},
metricRelabelConfigs: []monitoringv1.RelabelConfig{
{
Regex: "namespace",
Action: "labeldrop",
},
},
},
{
// override label using the replace action.
relabelConfigs: []monitoringv1.RelabelConfig{
{
TargetLabel: "namespace",
Replacement: ptr.To("ns1"),
},
},
metricRelabelConfigs: []monitoringv1.RelabelConfig{
{
TargetLabel: "namespace",
Replacement: ptr.To("ns1"),
},
},
},
{
// override label using the replace action with empty replacement.
relabelConfigs: []monitoringv1.RelabelConfig{
{
TargetLabel: "namespace",
Replacement: ptr.To(""),
},
},
metricRelabelConfigs: []monitoringv1.RelabelConfig{
{
TargetLabel: "namespace",
Replacement: ptr.To(""),
},
},
},
{
// override label using the labelmap action.
relabelConfigs: []monitoringv1.RelabelConfig{
{
TargetLabel: "temp_namespace",
Replacement: ptr.To("ns1"),
},
},
metricRelabelConfigs: []monitoringv1.RelabelConfig{
{
Action: "labelmap",
Regex: "temp_namespace",
Replacement: ptr.To("namespace"),
},
{
Action: "labeldrop",
Regex: "temp_namespace",
},
},
},
} {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
ctx := framework.NewTestCtx(t)
defer ctx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, ctx)
framework.SetupPrometheusRBAC(context.Background(), t, ctx, ns)
prometheusName := "test"
group := "servicediscovery-test"
svc := framework.MakePrometheusService(prometheusName, group, v1.ServiceTypeClusterIP)
s := framework.MakeBasicServiceMonitor(group)
s.Spec.Endpoints[0].RelabelConfigs = tc.relabelConfigs
s.Spec.Endpoints[0].MetricRelabelConfigs = tc.metricRelabelConfigs
if _, err := framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), s, metav1.CreateOptions{}); err != nil {
t.Fatal("Creating ServiceMonitor failed: ", err)
}
p := framework.MakeBasicPrometheus(ns, prometheusName, group, 1)
p.Spec.EnforcedNamespaceLabel = "namespace"
_, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p)
if err != nil {
t.Fatal(err)
}
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, svc); err != nil {
t.Fatal(fmt.Errorf("creating prometheus service failed: %w", err))
} else {
ctx.AddFinalizerFn(finalizerFn)
}
_, err = framework.KubeClient.CoreV1().Secrets(ns).Get(context.Background(), fmt.Sprintf("prometheus-%s", prometheusName), metav1.GetOptions{})
if err != nil {
t.Fatal("Generated Secret could not be retrieved: ", err)
}
err = framework.WaitForDiscoveryWorking(context.Background(), ns, svc.Name, prometheusName)
if err != nil {
t.Fatal(fmt.Errorf("validating Prometheus target discovery failed: %w", err))
}
// Check that the namespace label is enforced to the correct value.
var (
loopErr error
namespaceLabel string
)
err = wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 1*time.Minute, false, func(_ context.Context) (bool, error) {
loopErr = nil
res, err := framework.PrometheusQuery(ns, svc.Name, "http", "prometheus_build_info")
if err != nil {
loopErr = fmt.Errorf("failed to query Prometheus: %w", err)
return false, nil
}
if len(res) != 1 {
loopErr = fmt.Errorf("expecting 1 item but got %d", len(res))
return false, nil
}
for k, v := range res[0].Metric {
if k == "namespace" {
namespaceLabel = v
return true, nil
}
}
loopErr = fmt.Errorf("expecting to find 'namespace' label in %v", res[0].Metric)
return false, nil
})
if err != nil {
t.Fatalf("%v: %v", err, loopErr)
}
if namespaceLabel != ns {
t.Fatalf("expecting 'namespace' label value to be %q but got %q instead", ns, namespaceLabel)
}
})
}
}
// testPromNamespaceEnforcementExclusion checks that the enforcedNamespaceLabel field
// is not enforced on objects defined in ExcludedFromEnforcement.
func testPromNamespaceEnforcementExclusion(t *testing.T) {
t.Parallel()
for i, tc := range []struct {
relabelConfigs []monitoringv1.RelabelConfig
metricRelabelConfigs []monitoringv1.RelabelConfig
expectedNamespace string
}{
{
// override label using the labeldrop action.
relabelConfigs: []monitoringv1.RelabelConfig{
{
Regex: "namespace",
Action: "labeldrop",
},
},
metricRelabelConfigs: []monitoringv1.RelabelConfig{
{
Regex: "namespace",
Action: "labeldrop",
},
},
expectedNamespace: "",
},
{
// override label using the replace action.
relabelConfigs: []monitoringv1.RelabelConfig{
{
TargetLabel: "namespace",
Replacement: ptr.To("ns1"),
},
},
metricRelabelConfigs: []monitoringv1.RelabelConfig{
{
TargetLabel: "namespace",
Replacement: ptr.To("ns1"),
},
},
expectedNamespace: "ns1",
},
{
// override label using the labelmap action.
relabelConfigs: []monitoringv1.RelabelConfig{
{
TargetLabel: "temp_namespace",
Replacement: ptr.To("ns1"),
},
},
metricRelabelConfigs: []monitoringv1.RelabelConfig{
{
Action: "labelmap",
Regex: "temp_namespace",
Replacement: ptr.To("namespace"),
},
{
Action: "labeldrop",
Regex: "temp_namespace",
},
},
expectedNamespace: "ns1",
},
} {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
ctx := framework.NewTestCtx(t)
defer ctx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, ctx)
framework.SetupPrometheusRBAC(context.Background(), t, ctx, ns)
prometheusName := "test"
group := "servicediscovery-test"
svc := framework.MakePrometheusService(prometheusName, group, v1.ServiceTypeClusterIP)
s := framework.MakeBasicServiceMonitor(group)
s.Spec.Endpoints[0].RelabelConfigs = tc.relabelConfigs
s.Spec.Endpoints[0].MetricRelabelConfigs = tc.metricRelabelConfigs
if _, err := framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), s, metav1.CreateOptions{}); err != nil {
t.Fatal("Creating ServiceMonitor failed: ", err)
}
p := framework.MakeBasicPrometheus(ns, prometheusName, group, 1)
p.Spec.EnforcedNamespaceLabel = "namespace"
p.Spec.ExcludedFromEnforcement = []monitoringv1.ObjectReference{
{
Namespace: ns,
Group: "monitoring.coreos.com",
Resource: monitoringv1.ServiceMonitorName,
},
}
_, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p)
if err != nil {
t.Fatal(err)
}
if finalizerFn, err := framework.CreateOrUpdateServiceAndWaitUntilReady(context.Background(), ns, svc); err != nil {
t.Fatal(fmt.Errorf("creating prometheus service failed: %w", err))
} else {
ctx.AddFinalizerFn(finalizerFn)
}
_, err = framework.KubeClient.CoreV1().Secrets(ns).Get(context.Background(), fmt.Sprintf("prometheus-%s", prometheusName), metav1.GetOptions{})
if err != nil {
t.Fatal("Generated Secret could not be retrieved: ", err)
}
err = framework.WaitForDiscoveryWorking(context.Background(), ns, svc.Name, prometheusName)
if err != nil {
t.Fatal(fmt.Errorf("validating Prometheus target discovery failed: %w", err))
}
// Check that the namespace label isn't enforced.
var (
loopErr error
namespaceLabel string
)
err = wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 1*time.Minute, false, func(_ context.Context) (bool, error) {
loopErr = nil
res, err := framework.PrometheusQuery(ns, svc.Name, "http", "prometheus_build_info")
if err != nil {
loopErr = fmt.Errorf("failed to query Prometheus: %w", err)
return false, nil
}
if len(res) != 1 {
loopErr = fmt.Errorf("expecting 1 item but got %d", len(res))
return false, nil
}
for k, v := range res[0].Metric {
if k == "namespace" {
namespaceLabel = v
break
}
}
return true, nil
})
if err != nil {
t.Fatalf("%v: %v", err, loopErr)
}
if namespaceLabel != tc.expectedNamespace {
t.Fatalf("expecting custom 'namespace' label value %q due to exclusion. but got %q instead", tc.expectedNamespace, namespaceLabel)
}
})
}
}
func testPrometheusCRDValidation(t *testing.T) {
t.Parallel()
name := "test"
replicas := int32(1)
tests := []struct {
name string
prometheusSpec monitoringv1.PrometheusSpec
expectedError bool
}{
//
// RetentionSize Validation:
//
{
name: "zero-size-without-unit",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
},
RetentionSize: "0",
},
},
{
name: "legacy-unit",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
},
RetentionSize: "1.5GB",
},
},
{
name: "iec-unit",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
},
RetentionSize: "100MiB",
},
},
{
name: "legacy-missing-symbol",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
},
RetentionSize: "10M",
},
expectedError: true,
},
{
name: "legacy-missing-unit",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
},
RetentionSize: "1000",
},
expectedError: true,
},
{
name: "iec-missing-symbol",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
},
RetentionSize: "15Gi",
},
expectedError: true,
},
//
// ScrapeInterval validation
{
name: "zero-time-without-unit",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
ScrapeInterval: "0",
},
},
},
{
name: "time-in-seconds-unit",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
ScrapeInterval: "30s",
},
},
},
{
name: "complex-time-unit",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
ScrapeInterval: "1h30m15s",
},
},
},
{
name: "time-missing-symbols",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
ScrapeInterval: "600",
},
},
expectedError: true,
},
{
name: "time-unit-misspelled",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
ScrapeInterval: "60ss",
},
},
expectedError: true,
},
{
name: "max-connections",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
Web: &monitoringv1.PrometheusWebSpec{
MaxConnections: proto.Int32(100),
},
},
},
},
{
name: "max-connections-negative-value",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
Web: &monitoringv1.PrometheusWebSpec{
MaxConnections: proto.Int32(-1),
},
},
},
expectedError: true,
},
{
name: "max-concurrency",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
},
Query: &monitoringv1.QuerySpec{
MaxConcurrency: ptr.To(int32(100)),
},
},
},
{
name: "max-concurrency-zero-value",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
},
Query: &monitoringv1.QuerySpec{
MaxConcurrency: ptr.To(int32(0)),
},
},
expectedError: true,
},
{
name: "valid-dns-policy-and-config",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
DNSPolicy: ptr.To(monitoringv1.DNSPolicy("ClusterFirst")),
DNSConfig: &monitoringv1.PodDNSConfig{
Nameservers: []string{"8.8.8.8"},
Options: []monitoringv1.PodDNSConfigOption{
{
Name: "ndots",
Value: ptr.To("5"),
},
},
},
},
},
expectedError: false,
},
{
name: "invalid-dns-policy",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
DNSPolicy: ptr.To(monitoringv1.DNSPolicy("InvalidPolicy")),
},
},
expectedError: true,
},
//
// Alertmanagers-Endpoints tests
{
name: "no-endpoint-namespace",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
},
Alerting: &monitoringv1.AlertingSpec{
Alertmanagers: []monitoringv1.AlertmanagerEndpoints{
{
Name: "test",
Port: intstr.FromInt(9797),
Scheme: ptr.To(monitoringv1.SchemeHTTPS),
PathPrefix: ptr.To("/alerts"),
BearerTokenFile: "/file",
APIVersion: ptr.To(monitoringv1.AlertmanagerAPIVersion1),
},
},
},
},
expectedError: false,
},
{
name: "endpoint-namespace",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
},
Alerting: &monitoringv1.AlertingSpec{
Alertmanagers: []monitoringv1.AlertmanagerEndpoints{
{
Name: "test",
Namespace: ptr.To("default"),
Port: intstr.FromInt(9797),
Scheme: ptr.To(monitoringv1.SchemeHTTPS),
PathPrefix: ptr.To("/alerts"),
BearerTokenFile: "/file",
APIVersion: ptr.To(monitoringv1.AlertmanagerAPIVersion1),
},
},
},
},
expectedError: false,
},
{
name: "no-endpoint-name",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
Resources: v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: resource.MustParse("400Mi"),
},
},
},
Alerting: &monitoringv1.AlertingSpec{
Alertmanagers: []monitoringv1.AlertmanagerEndpoints{
{
Namespace: ptr.To("default"),
Port: intstr.FromInt(9797),
Scheme: ptr.To(monitoringv1.SchemeHTTPS),
PathPrefix: ptr.To("/alerts"),
BearerTokenFile: "/file",
APIVersion: ptr.To(monitoringv1.AlertmanagerAPIVersion1),
},
},
},
},
expectedError: true,
},
{
name: "valid-remote-write-message-version",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
RemoteWrite: []monitoringv1.RemoteWriteSpec{
{
URL: "http://example.com",
MessageVersion: ptr.To(monitoringv1.RemoteWriteMessageVersion2_0),
},
},
},
},
expectedError: false,
},
{
name: "invalid-remote-write-message-version",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
RemoteWrite: []monitoringv1.RemoteWriteSpec{
{
URL: "http://example.com",
MessageVersion: ptr.To(monitoringv1.RemoteWriteMessageVersion("xx")),
},
},
},
},
expectedError: true,
},
{
name: "invalid-empty-remote-write-url",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
RemoteWrite: []monitoringv1.RemoteWriteSpec{
{
URL: "",
},
},
},
},
expectedError: true,
},
{
name: "valid-remote-write-receiver-message-versions",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
RemoteWriteReceiverMessageVersions: []monitoringv1.RemoteWriteMessageVersion{
monitoringv1.RemoteWriteMessageVersion2_0,
},
},
},
expectedError: false,
},
{
name: "invalid-remote-write-receiver-message-versions",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
RemoteWriteReceiverMessageVersions: []monitoringv1.RemoteWriteMessageVersion{
monitoringv1.RemoteWriteMessageVersion2_0,
monitoringv1.RemoteWriteMessageVersion("xx"),
},
},
},
expectedError: true,
},
{
name: "valid-retain-config",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
},
ShardRetentionPolicy: &monitoringv1.ShardRetentionPolicy{
WhenScaled: &monitoringv1.RetainWhenScaledRetentionType,
Retain: &monitoringv1.RetainConfig{
RetentionPeriod: monitoringv1.Duration("3d"),
},
},
},
},
{
name: "invalid-terminationGracePeriodSeconds",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
TerminationGracePeriodSeconds: ptr.To(int64(-100)),
},
},
expectedError: true,
},
{
name: "valid-terminationGracePeriodSeconds",
prometheusSpec: monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Replicas: &replicas,
Version: operator.DefaultPrometheusVersion,
ServiceAccountName: "prometheus",
TerminationGracePeriodSeconds: ptr.To(int64(100)),
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
prom := &monitoringv1.Prometheus{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: ns,
Annotations: map[string]string{},
},
Spec: test.prometheusSpec,
}
if test.expectedError {
_, err := framework.MonClientV1.Prometheuses(ns).Create(context.Background(), prom, metav1.CreateOptions{})
if err == nil {
t.Fatal("expected error but got nil")
}
if !apierrors.IsInvalid(err) {
t.Fatalf("expected Invalid error but got %v", err)
}
return
}
_, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, prom)
if err != nil {
t.Fatalf("expected no error but got %v", err)
}
})
}
}
func testRelabelConfigCRDValidation(t *testing.T) {
t.Parallel()
name := "test"
tests := []struct {
scenario string
relabelConfigs []monitoringv1.RelabelConfig
expectedError bool
}{
{
scenario: "no-explicit-sep",
relabelConfigs: []monitoringv1.RelabelConfig{
{
SourceLabels: []monitoringv1.LabelName{"__address__"},
Action: "replace",
Regex: "([^:]+)(?::\\d+)?",
Replacement: ptr.To("$1:80"),
TargetLabel: "__address__",
},
},
},
{
scenario: "no-explicit-action",
relabelConfigs: []monitoringv1.RelabelConfig{
{
SourceLabels: []monitoringv1.LabelName{"__address__"},
Separator: ptr.To(","),
Regex: "([^:]+)(?::\\d+)?",
Replacement: ptr.To("$1:80"),
TargetLabel: "__address__",
},
},
},
{
scenario: "empty-separator",
relabelConfigs: []monitoringv1.RelabelConfig{
{
Separator: ptr.To(""),
},
},
},
{
scenario: "invalid-action",
relabelConfigs: []monitoringv1.RelabelConfig{
{
Action: "replacee",
},
},
expectedError: true,
},
{
scenario: "accepts-utf-8-labels",
relabelConfigs: []monitoringv1.RelabelConfig{
{
SourceLabels: []monitoringv1.LabelName{"app.info"},
Action: "replace",
TargetLabel: "app.info",
Replacement: ptr.To("test.app"),
},
},
},
}
for _, test := range tests {
t.Run(test.scenario, func(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
p := framework.MakeBasicPrometheus(ns, name, "", 1)
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p); err != nil {
t.Fatalf("expected no error but got %v", err)
}
s := framework.MakeBasicServiceMonitor(name)
s.Spec.Endpoints[0].RelabelConfigs = test.relabelConfigs
_, err := framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), s, metav1.CreateOptions{})
if err == nil {
if test.expectedError {
t.Fatal("expected error but got nil")
}
return
}
if !apierrors.IsInvalid(err) {
t.Fatalf("expected Invalid error but got %v", err)
}
})
}
}
func testPromQueryLogFile(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
p := framework.MakeBasicPrometheus(ns, "test", "", 1)
p.Spec.QueryLogFile = "query.log"
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p); err != nil {
t.Fatal(err)
}
}
func testPromUnavailableConditionStatus(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
p := framework.MakeBasicPrometheus(ns, "test", "", 2)
// A non-existing service account prevents the creation of the statefulset's pods.
p.Spec.ServiceAccountName = "does-not-exist"
_, err := framework.MonClientV1.Prometheuses(ns).Create(context.Background(), p, metav1.CreateOptions{})
require.NoError(t, err)
var pollErr error
err = wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 5*time.Minute, false, func(ctx context.Context) (bool, error) {
var current *monitoringv1.Prometheus
current, pollErr = framework.MonClientV1.Prometheuses(p.Namespace).Get(ctx, p.Name, metav1.GetOptions{})
if pollErr != nil {
return false, nil
}
for _, cond := range current.Status.Conditions {
if cond.Type != monitoringv1.Available {
continue
}
if cond.Status != monitoringv1.ConditionFalse {
pollErr = fmt.Errorf(
"expected Available condition to be 'False', got %q (reason %s, %q)",
cond.Status,
cond.Reason,
cond.Message,
)
return false, nil
}
if cond.Reason != "NoPodReady" {
pollErr = fmt.Errorf(
"expected Available condition's reason to be 'NoPodReady', got %s (message %q)",
cond.Reason,
cond.Message,
)
return false, nil
}
return true, nil
}
pollErr = fmt.Errorf("failed to find Available condition in status subresource")
return false, nil
})
if err != nil {
t.Fatalf("waiting for Prometheus %v/%v: %v: %v", p.Namespace, p.Name, err, pollErr)
}
}
func testPromDegradedConditionStatus(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
p := framework.MakeBasicPrometheus(ns, "test", "", 2)
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p); err != nil {
t.Fatal(err)
}
// Roll out a new version of the Prometheus object that references a
// non-existing container image which should trigger the
// "Available=Degraded" condition.
p, err := framework.PatchPrometheus(
context.Background(),
p.Name,
ns,
monitoringv1.PrometheusSpec{
CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
Containers: []v1.Container{{
Name: "bad-image",
Image: "quay.io/prometheus-operator/invalid-image",
}},
},
},
)
if err != nil {
t.Fatal(err)
}
var pollErr error
err = wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 5*time.Minute, false, func(ctx context.Context) (bool, error) {
var current *monitoringv1.Prometheus
current, pollErr = framework.MonClientV1.Prometheuses(p.Namespace).Get(ctx, p.Name, metav1.GetOptions{})
if pollErr != nil {
return false, nil
}
for _, cond := range current.Status.Conditions {
if cond.Type != monitoringv1.Available {
continue
}
if cond.Status != monitoringv1.ConditionDegraded {
pollErr = fmt.Errorf(
"expected Available condition to be 'Degraded', got %q (reason %s, %q)",
cond.Status,
cond.Reason,
cond.Message,
)
return false, nil
}
if cond.Reason != "SomePodsNotReady" {
pollErr = fmt.Errorf(
"expected Available condition's reason to be 'SomePodsNotReady', got %s (message %q)",
cond.Reason,
cond.Message,
)
return false, nil
}
if !strings.Contains(cond.Message, "bad-image") {
pollErr = fmt.Errorf(
"expected Available condition's message to contain 'bad-image', got %q",
cond.Message,
)
return false, nil
}
return true, nil
}
pollErr = fmt.Errorf("failed to find Available condition in status subresource")
return false, nil
})
if err != nil {
t.Fatalf("waiting for Prometheus %v/%v: %v: %v", p.Namespace, p.Name, err, pollErr)
}
}
func testPromStrategicMergePatch(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "secret", Namespace: ns},
Type: v1.SecretType("Opaque"),
Data: map[string][]byte{},
}
_, err := framework.KubeClient.CoreV1().Secrets(ns).Create(context.Background(), secret, metav1.CreateOptions{})
if err != nil {
t.Fatalf("failed to create secret: %s", err)
}
configmap := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: "configmap", Namespace: ns},
Data: map[string]string{},
}
_, err = framework.KubeClient.CoreV1().ConfigMaps(ns).Create(context.Background(), configmap, metav1.CreateOptions{})
if err != nil {
t.Fatalf("failed to create configmap: %s", err)
}
p := framework.MakeBasicPrometheus(ns, "test", "", 1)
p.Spec.Secrets = []string{secret.Name}
p.Spec.ConfigMaps = []string{configmap.Name}
p.Spec.Containers = []v1.Container{{
Name: "sidecar",
Image: "nginx",
// Ensure that the sidecar container can mount the additional secret and configmap.
VolumeMounts: []v1.VolumeMount{{
Name: "secret-" + secret.Name,
MountPath: "/tmp/secret",
}, {
Name: "configmap-" + configmap.Name,
MountPath: "/tmp/configmap",
}},
}}
if _, err := framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p); err != nil {
t.Fatal(err)
}
}
func testPrometheusWithStatefulsetCreationFailure(t *testing.T) {
ctx := context.Background()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
p := framework.MakeBasicPrometheus(ns, "test", "", 1)
// Invalid spec which prevents the creation of the statefulset
p.Spec.Web = &monitoringv1.PrometheusWebSpec{
WebConfigFileFields: monitoringv1.WebConfigFileFields{
TLSConfig: &monitoringv1.WebTLSConfig{
Cert: monitoringv1.SecretOrConfigMap{
ConfigMap: &v1.ConfigMapKeySelector{},
Secret: &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "tls-cert",
},
Key: "tls.crt",
},
},
KeySecret: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "tls-cert",
},
Key: "tls.key",
},
},
},
}
_, err := framework.MonClientV1.Prometheuses(p.Namespace).Create(ctx, p, metav1.CreateOptions{})
require.NoError(t, err)
var loopError error
err = wait.PollUntilContextTimeout(ctx, time.Second, framework.DefaultTimeout, true, func(ctx context.Context) (bool, error) {
current, err := framework.MonClientV1.Prometheuses(ns).Get(ctx, "test", metav1.GetOptions{})
if err != nil {
loopError = fmt.Errorf("failed to get object: %w", err)
return false, nil
}
if err := framework.AssertCondition(current.Status.Conditions, monitoringv1.Reconciled, monitoringv1.ConditionFalse); err != nil {
loopError = err
return false, nil
}
if err := framework.AssertCondition(current.Status.Conditions, monitoringv1.Available, monitoringv1.ConditionFalse); err != nil {
loopError = err
return false, nil
}
return true, nil
})
if err != nil {
t.Fatalf("%v: %v", err, loopError)
}
require.NoError(t, framework.DeletePrometheusAndWaitUntilGone(context.Background(), ns, "test"))
}
func testPrometheusStatusScale(t *testing.T) {
t.Parallel()
ctx := context.Background()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(ctx, t, testCtx)
framework.SetupPrometheusRBAC(ctx, t, testCtx, ns)
name := "test"
p := framework.MakeBasicPrometheus(ns, name, name, 1)
p.Spec.CommonPrometheusFields.Shards = proto.Int32(1)
p, err := framework.CreatePrometheusAndWaitUntilReady(ctx, ns, p)
if err != nil {
t.Fatal(err)
}
if p.Status.Shards != 1 {
t.Fatalf("expected scale of 1 shard, got %d", p.Status.Shards)
}
p, err = framework.ScalePrometheusAndWaitUntilReady(ctx, name, ns, 2)
if err != nil {
t.Fatal(err)
}
if p.Status.Shards != 2 {
t.Fatalf("expected scale of 2 shards, got %d", p.Status.Shards)
}
}
func testPrometheusServiceName(t *testing.T) {
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
name := "test-servicename"
svc := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-service", name),
Namespace: ns,
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,
Ports: []v1.ServicePort{
{
Name: "web",
Port: 9090,
},
},
Selector: map[string]string{
"prometheus": name,
"app.kubernetes.io/name": "prometheus",
"app.kubernetes.io/instance": name,
"app.kubernetes.io/managed-by": "prometheus-operator",
},
},
}
_, err := framework.KubeClient.CoreV1().Services(ns).Create(context.Background(), svc, metav1.CreateOptions{})
require.NoError(t, err)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
p := framework.MakeBasicPrometheus(ns, name, name, 1)
p.Spec.ServiceName = &svc.Name
_, err = framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, p)
require.NoError(t, err)
targets, err := framework.GetActiveTargets(context.Background(), ns, svc.Name)
require.NoError(t, err)
require.Empty(t, targets)
// Ensure that the default governing service was not created by the operator.
svcList, err := framework.KubeClient.CoreV1().Services(ns).List(context.Background(), metav1.ListOptions{})
require.NoError(t, err)
require.Len(t, svcList.Items, 1)
require.Equal(t, svcList.Items[0].Name, svc.Name)
}
// testPrometheusRetentionPolicies tests the shard retention policies for Prometheus.
// ShardRetentionPolicy requires the ShardRetention feature gate to be enabled,
// therefore, it runs in the feature-gated test suite.
func testPrometheusRetentionPolicies(t *testing.T) {
t.Parallel()
ctx := context.Background()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(ctx, t, testCtx)
framework.SetupPrometheusRBAC(ctx, t, testCtx, ns)
_, err := framework.CreateOrUpdatePrometheusOperatorWithOpts(
ctx, testFramework.PrometheusOperatorOpts{
Namespace: ns,
AllowedNamespaces: []string{ns},
EnabledFeatureGates: []operator.FeatureGateName{operator.PrometheusShardRetentionPolicyFeature},
},
)
require.NoError(t, err)
testCases := []struct {
name string
whenScaledDown *monitoringv1.WhenScaledRetentionType
expectedRemainingSts int
}{
{
name: "delete",
whenScaledDown: ptr.To(monitoringv1.DeleteWhenScaledRetentionType),
expectedRemainingSts: 1,
},
{
name: "retain",
whenScaledDown: ptr.To(monitoringv1.RetainWhenScaledRetentionType),
expectedRemainingSts: 2,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
p := framework.MakeBasicPrometheus(ns, tc.name, tc.name, 1)
p.Spec.ShardRetentionPolicy = &monitoringv1.ShardRetentionPolicy{
WhenScaled: tc.whenScaledDown,
}
p.Spec.Shards = ptr.To(int32(2))
_, err := framework.CreatePrometheusAndWaitUntilReady(ctx, ns, p)
require.NoError(t, err, "failed to create Prometheus")
p, err = framework.ScalePrometheusAndWaitUntilReady(ctx, tc.name, ns, 1)
require.NoError(t, err, "failed to scale down Prometheus")
require.Equal(t, int32(1), p.Status.Shards, "expected scale of 1 shard")
podList, err := framework.KubeClient.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{LabelSelector: p.Status.Selector})
require.NoError(t, err, "failed to list statefulsets")
require.Len(t, podList.Items, tc.expectedRemainingSts)
})
}
}
// testPrometheusReconciliationOnSecretChanges ensures that the operator
// reconciles the configureation whenever a secret referenced by a service
// monitor gets added/deleted in another namespace than the workload.
func testPrometheusReconciliationOnSecretChanges(t *testing.T) {
t.Parallel()
ctx := context.Background()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(ctx, t, testCtx) // where Prometheus is deployed.
ns2 := framework.CreateNamespace(ctx, t, testCtx) // where the service monitor is deployed.
name := "test-secret-changes"
// Deploy the example application + service.
simple, err := testFramework.MakeDeployment("../../test/framework/resources/basic-auth-app-deployment.yaml")
require.NoError(t, err)
framework.CreateDeployment(context.Background(), ns2, simple)
require.NoError(t, err)
svc := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: map[string]string{
"group": name,
},
},
Spec: v1.ServiceSpec{
Selector: simple.Spec.Template.ObjectMeta.Labels,
Ports: []v1.ServicePort{
{
Name: "web",
Port: 8080,
},
},
},
}
_, err = framework.KubeClient.CoreV1().Services(ns2).Create(ctx, svc, metav1.CreateOptions{})
require.NoError(t, err)
sm := framework.MakeBasicServiceMonitor(name)
sm.Spec.Endpoints[0].Interval = monitoringv1.Duration("1s")
sm.Spec.Endpoints[0].BasicAuth = &monitoringv1.BasicAuth{
Username: v1.SecretKeySelector{
Key: "user",
LocalObjectReference: v1.LocalObjectReference{
Name: "auth",
},
},
Password: v1.SecretKeySelector{
Key: "pass",
LocalObjectReference: v1.LocalObjectReference{
Name: "auth",
},
},
}
sm, err = framework.MonClientV1.ServiceMonitors(ns2).Create(ctx, sm, metav1.CreateOptions{})
require.NoError(t, err)
framework.SetupPrometheusRBACGlobal(ctx, t, testCtx, ns)
require.NoError(t, err)
p := framework.MakeBasicPrometheus(ns, name, name, 1)
p.Spec.ServiceMonitorNamespaceSelector = &metav1.LabelSelector{
MatchLabels: map[string]string{
"kubernetes.io/metadata.name": ns2,
},
}
_, err = framework.CreatePrometheusAndWaitUntilReady(ctx, ns, p)
require.NoError(t, err)
// There should be no target because the service monitor references a
// secret which doesn't exist so it won't be selected.
targets, err := framework.GetActiveTargets(ctx, ns, "prometheus-operated")
require.NoError(t, err)
require.Empty(t, targets)
// Create the secret and wait for the target to be discovered.
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "auth",
Namespace: ns2,
},
StringData: map[string]string{
"user": "user",
"pass": "pass",
},
Type: v1.SecretTypeOpaque,
}
secret, err = framework.KubeClient.CoreV1().Secrets(ns2).Create(ctx, secret, metav1.CreateOptions{})
require.NoError(t, err)
t.Logf("secret %s/%s created", secret.GetNamespace(), secret.GetName())
err = framework.WaitForHealthyTargets(ctx, ns, "prometheus-operated", 1)
require.NoError(t, err)
err = framework.KubeClient.CoreV1().Secrets(ns2).Delete(ctx, secret.Name, metav1.DeleteOptions{})
require.NoError(t, err)
err = framework.WaitForActiveTargets(ctx, ns, "prometheus-operated", 0)
require.NoError(t, err)
}
func testPrometheusUTF8MetricsSupport(t *testing.T) {
if os.Getenv("TEST_PROMETHEUS_V2") == "true" {
t.Skip("UTF-8 metrics support is not available in Prometheus v2")
}
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
// Disable admission webhook for rule since utf8 is not enabled by default and rule contain metric name with utf8 characters.
ruleNamespaceSelector := map[string]string{"excludeFromWebhook": "true"}
err := framework.AddLabelsToNamespace(context.Background(), ns, ruleNamespaceSelector)
require.NoError(t, err)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "prometheus-utf8-test"
// Create deployment for instrumented sample app
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "instrumented-sample-app",
Namespace: ns,
Labels: map[string]string{
"app": "instrumented-sample-app",
},
},
Spec: appsv1.DeploymentSpec{
Replicas: ptr.To(int32(1)),
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app": "instrumented-sample-app"},
},
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "instrumented-sample-app",
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Name: "instrumented-sample-app",
Image: "quay.io/prometheus-operator/instrumented-sample-app:latest",
Ports: []v1.ContainerPort{{
Name: "web",
ContainerPort: 8080,
Protocol: v1.ProtocolTCP,
}},
}},
},
},
},
}
_, err = framework.KubeClient.AppsV1().Deployments(ns).Create(context.Background(), deployment, metav1.CreateOptions{})
require.NoError(t, err)
service := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "utf8-test-service",
Namespace: ns,
Labels: map[string]string{
"app": "instrumented-sample-app",
"group": "test-app",
},
},
Spec: v1.ServiceSpec{
Ports: []v1.ServicePort{{Name: "web", Port: 8080, TargetPort: intstr.FromInt(8080)}},
Selector: map[string]string{"app": "instrumented-sample-app"},
},
}
_, err = framework.KubeClient.CoreV1().Services(ns).Create(context.Background(), service, metav1.CreateOptions{})
require.NoError(t, err)
sm := &monitoringv1.ServiceMonitor{
ObjectMeta: metav1.ObjectMeta{
Name: "utf8-servicemonitor",
Namespace: ns,
Labels: map[string]string{"group": "test-app"},
},
Spec: monitoringv1.ServiceMonitorSpec{
Selector: metav1.LabelSelector{
MatchLabels: map[string]string{"app": "instrumented-sample-app"},
},
Endpoints: []monitoringv1.Endpoint{{
Port: "web",
Interval: "30s",
BasicAuth: &monitoringv1.BasicAuth{
Username: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{Name: "basic-auth"},
Key: "username",
},
Password: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{Name: "basic-auth"},
Key: "password",
},
},
}},
},
}
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "basic-auth",
Namespace: ns,
},
Type: v1.SecretTypeOpaque,
StringData: map[string]string{
"username": "user",
"password": "pass",
},
}
_, err = framework.KubeClient.CoreV1().Secrets(ns).Create(context.Background(), secret, metav1.CreateOptions{})
require.NoError(t, err)
_, err = framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), sm, metav1.CreateOptions{})
require.NoError(t, err)
// Wait for deployment to be ready
err = framework.WaitForDeploymentReady(context.Background(), ns, "instrumented-sample-app", 1)
require.NoError(t, err)
// Create PrometheusRule with UTF-8 metrics.
prometheusRule := &monitoringv1.PrometheusRule{
ObjectMeta: metav1.ObjectMeta{
Name: "utf8-prometheus-rule",
Namespace: ns,
Labels: map[string]string{
"app": "test-app",
"role": "rulefile",
},
Annotations: map[string]string{
"description": "Test rule",
},
},
Spec: monitoringv1.PrometheusRuleSpec{
Groups: []monitoringv1.RuleGroup{{
Name: "utf8.test.rules",
Rules: []monitoringv1.Rule{
{
Alert: "UTF8TestAlert",
Expr: intstr.FromString(`count by("app.version") ({"app.info"})`),
Labels: map[string]string{
"severity": "warning",
"service.name": "web",
},
Annotations: map[string]string{
"summary": "Service is down",
"description.cluster": "The cluster service is not responding",
"runbook": "https://runbook.example.com/cluster",
},
},
{
Record: "cluster.app_info:5m",
Expr: intstr.FromString(`avg_over_time({"app.info"}[5m])`),
Labels: map[string]string{
"service.cluster": "availability",
},
},
},
}},
},
}
_, err = framework.MonClientV1.PrometheusRules(ns).Create(context.Background(), prometheusRule, metav1.CreateOptions{})
require.NoError(t, err)
prom := framework.MakeBasicPrometheus(ns, name, "test-app", 1)
_, err = framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, prom)
require.NoError(t, err)
// Default Prometheus service name is "prometheus-operated".
promSvcName := "prometheus-operated"
// Wait for the instrumented-sample-app target to be discovered
err = framework.WaitForHealthyTargets(context.Background(), ns, promSvcName, 1)
require.NoError(t, err)
// Verify UTF8 metrics work in queries.
err = wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 2*time.Minute, false, func(ctx context.Context) (bool, error) {
// Query for UTF8 metric
results, err := framework.PrometheusQuery(ns, promSvcName, "http", `{"app.info"}`)
if err != nil {
t.Logf("UTF8 query failed: %v", err)
return false, nil
}
if len(results) == 0 {
t.Logf("UTF8 query returned no results")
return false, nil
}
return true, nil
})
require.NoError(t, err, "UTF-8 metrics should work in Prometheus 3.0+ queries")
// Check UTF8 recording rule from PrometheusRule
err = wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 2*time.Minute, false, func(ctx context.Context) (bool, error) {
results, err := framework.PrometheusQuery(ns, promSvcName, "http", `{"cluster.app_info:5m"}`)
if err != nil {
t.Logf("UTF8 PrometheusRule recording query failed: %v", err)
return false, nil
}
if len(results) == 0 {
t.Logf("UTF8 recording query returned no results")
return false, nil
}
return true, nil
})
require.NoError(t, err, "UTF-8 PrometheusRule recording rule should work")
// Verify the alert rule exists in Prometheus
err = wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 2*time.Minute, false, func(ctx context.Context) (bool, error) {
results, err := framework.PrometheusQuery(ns, promSvcName, "http", `ALERTS{alertname="UTF8TestAlert"}`)
if err != nil {
t.Logf("UTF8 alert rule query failed: %v", err)
return false, nil
}
if len(results) == 0 {
t.Logf("UTF8TestAlert rule not found - may not be loaded yet")
return false, nil
}
t.Logf("UTF8TestAlert rule found and loaded")
return true, nil
})
require.NoError(t, err, "UTF-8 alert rule should be queryable")
}
func testPrometheusUTF8LabelSupport(t *testing.T) {
if os.Getenv("TEST_PROMETHEUS_V2") == "true" {
t.Skip("UTF-8 label support is not available in Prometheus v2")
}
t.Parallel()
testCtx := framework.NewTestCtx(t)
defer testCtx.Cleanup(t)
ns := framework.CreateNamespace(context.Background(), t, testCtx)
framework.SetupPrometheusRBAC(context.Background(), t, testCtx, ns)
name := "prometheus-utf8-test"
// Create deployment for instrumented sample app
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "instrumented-sample-app",
Namespace: ns,
Labels: map[string]string{
"app": "instrumented-sample-app",
},
},
Spec: appsv1.DeploymentSpec{
Replicas: ptr.To(int32(1)),
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"app.name": "instrumented-sample-app"},
},
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app.name": "instrumented-sample-app",
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Name: "instrumented-sample-app",
Image: "quay.io/prometheus-operator/instrumented-sample-app:latest",
Ports: []v1.ContainerPort{{
Name: "web",
ContainerPort: 8080,
Protocol: v1.ProtocolTCP,
}},
}},
},
},
},
}
_, err := framework.KubeClient.AppsV1().Deployments(ns).Create(context.Background(), deployment, metav1.CreateOptions{})
require.NoError(t, err)
service := &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "utf8-test-service",
Namespace: ns,
Labels: map[string]string{
"app.name": "instrumented-sample-app",
"group": "test.app",
"cluster": "dev",
},
},
Spec: v1.ServiceSpec{
Ports: []v1.ServicePort{{Name: "web", Port: 8080, TargetPort: intstr.FromInt(8080)}},
Selector: map[string]string{"app.name": "instrumented-sample-app"},
},
}
_, err = framework.KubeClient.CoreV1().Services(ns).Create(context.Background(), service, metav1.CreateOptions{})
require.NoError(t, err)
sm := &monitoringv1.ServiceMonitor{
ObjectMeta: metav1.ObjectMeta{
Name: "utf8-servicemonitor",
Namespace: ns,
Labels: map[string]string{"group": "test.app", "app.name": "instrumented-sample-app"},
},
Spec: monitoringv1.ServiceMonitorSpec{
Selector: metav1.LabelSelector{
MatchLabels: map[string]string{"app.name": "instrumented-sample-app"},
},
Endpoints: []monitoringv1.Endpoint{{
Port: "web",
Interval: "2s",
RelabelConfigs: []monitoringv1.RelabelConfig{{
SourceLabels: []monitoringv1.LabelName{"__meta_kubernetes_service_label_cluster"},
TargetLabel: "service_clustér_label",
Action: "replace",
}},
BasicAuth: &monitoringv1.BasicAuth{
Username: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{Name: "basic-auth"},
Key: "username",
},
Password: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{Name: "basic-auth"},
Key: "password",
},
},
}},
},
}
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "basic-auth",
Namespace: ns,
},
Type: v1.SecretTypeOpaque,
StringData: map[string]string{
"username": "user",
"password": "pass",
},
}
_, err = framework.KubeClient.CoreV1().Secrets(ns).Create(context.Background(), secret, metav1.CreateOptions{})
require.NoError(t, err)
_, err = framework.MonClientV1.ServiceMonitors(ns).Create(context.Background(), sm, metav1.CreateOptions{})
require.NoError(t, err)
err = framework.WaitForDeploymentReady(context.Background(), ns, "instrumented-sample-app", 1)
require.NoError(t, err)
prom := framework.MakeBasicPrometheus(ns, name, "test.app", 1)
_, err = framework.CreatePrometheusAndWaitUntilReady(context.Background(), ns, prom)
require.NoError(t, err)
// Default Prometheus service name is "prometheus-operated".
promSvcName := "prometheus-operated"
// Wait for the instrumented-sample-app target to be discovered
err = framework.WaitForHealthyTargets(context.Background(), ns, promSvcName, 1)
require.NoError(t, err)
// Verify UTF8 labels work in queries.
err = wait.PollUntilContextTimeout(context.Background(), 5*time.Second, 2*time.Minute, false, func(ctx context.Context) (bool, error) {
results, err := framework.PrometheusQuery(ns, promSvcName, "http", `{"service_clustér_label"="dev"}`)
if err != nil {
t.Logf("UTF8 label query failed: %v", err)
return false, nil
}
if len(results) == 0 {
t.Logf("UTF8 label query returned no results")
return false, nil
}
return true, nil
})
require.NoError(t, err, "UTF-8 label queries should work in Prometheus 3.0+ queries")
}
func isAlertmanagerDiscoveryWorking(ns, promSVCName, alertmanagerName string) func(ctx context.Context) (bool, error) {
return func(ctx context.Context) (bool, error) {
pods, err := framework.KubeClient.CoreV1().Pods(ns).List(
ctx,
metav1.ListOptions{
LabelSelector: fields.SelectorFromSet(fields.Set(map[string]string{
operator.ApplicationNameLabelKey: "alertmanager",
operator.ApplicationInstanceLabelKey: alertmanagerName,
})).String(),
},
)
if err != nil {
return false, err
}
if 3 != len(pods.Items) {
return false, nil
}
expectedAlertmanagerTargets := []string{}
for _, p := range pods.Items {
expectedAlertmanagerTargets = append(expectedAlertmanagerTargets, fmt.Sprintf("http://%s:9093/api/v2/alerts", p.Status.PodIP))
}
response, err := framework.PrometheusSVCGetRequest(context.Background(), ns, promSVCName, "http", "/api/v1/alertmanagers", map[string]string{})
if err != nil {
return false, err
}
ra := prometheusAlertmanagerAPIResponse{}
if err := json.NewDecoder(bytes.NewBuffer(response)).Decode(&ra); err != nil {
return false, err
}
if assertExpectedAlertmanagerTargets(ra.Data.ActiveAlertmanagers, expectedAlertmanagerTargets) {
return true, nil
}
return false, nil
}
}
func assertExpectedAlertmanagerTargets(ams []*alertmanagerTarget, expectedTargets []string) bool {
log.Printf("Expected Alertmanager Targets: %#+v\n", expectedTargets)
existingTargets := []string{}
for _, am := range ams {
existingTargets = append(existingTargets, am.URL)
}
slices.Sort(expectedTargets)
slices.Sort(existingTargets)
if !reflect.DeepEqual(expectedTargets, existingTargets) {
log.Printf("Existing Alertmanager Targets: %#+v\n", existingTargets)
return false
}
return true
}
type alertmanagerTarget struct {
URL string `json:"url"`
}
type alertmanagerDiscovery struct {
ActiveAlertmanagers []*alertmanagerTarget `json:"activeAlertmanagers"`
}
type prometheusAlertmanagerAPIResponse struct {
Status string `json:"status"`
Data *alertmanagerDiscovery `json:"data"`
}