diff --git a/api/v1alpha1/vector_common_types.go b/api/v1alpha1/vector_common_types.go index c1cc3c1..712b021 100644 --- a/api/v1alpha1/vector_common_types.go +++ b/api/v1alpha1/vector_common_types.go @@ -84,6 +84,16 @@ type VectorCommon struct { // Enable internal metrics exporter // +optional InternalMetrics bool `json:"internalMetrics,omitempty"` + // ScrapeInterval defines the interval at which Prometheus should scrape metrics. + // Example values: "30s", "1m", "5m". If not specified, Prometheus default is used. + // +optional + // +kubebuilder:validation:Pattern=`^(0|([0-9]+(\.[0-9]+)?(ms|s|m|h))+)$` + ScrapeInterval string `json:"scrapeInterval,omitempty"` + // ScrapeTimeout defines the timeout for scraping metrics. + // Example values: "10s", "30s". Must be less than ScrapeInterval. If not specified, Prometheus default is used. + // +optional + // +kubebuilder:validation:Pattern=`^(0|([0-9]+(\.[0-9]+)?(ms|s|m|h))+)$` + ScrapeTimeout string `json:"scrapeTimeout,omitempty"` // List of volumes that can be mounted by containers belonging to the pod. // +optional Volumes []v1.Volume `json:"volumes,omitempty"` diff --git a/config/crd/bases/observability.kaasops.io_clustervectoraggregators.yaml b/config/crd/bases/observability.kaasops.io_clustervectoraggregators.yaml index d56d862..ab6fa3d 100644 --- a/config/crd/bases/observability.kaasops.io_clustervectoraggregators.yaml +++ b/config/crd/bases/observability.kaasops.io_clustervectoraggregators.yaml @@ -3147,6 +3147,18 @@ spec: schedulerName: description: SchedulerName - defines kubernetes scheduler name type: string + scrapeInterval: + description: |- + ScrapeInterval defines the interval at which Prometheus should scrape metrics. + Example values: "30s", "1m", "5m". If not specified, Prometheus default is used. + pattern: ^(0|([0-9]+(\.[0-9]+)?(ms|s|m|h))+)$ + type: string + scrapeTimeout: + description: |- + ScrapeTimeout defines the timeout for scraping metrics. + Example values: "10s", "30s". Must be less than ScrapeInterval. If not specified, Prometheus default is used. + pattern: ^(0|([0-9]+(\.[0-9]+)?(ms|s|m|h))+)$ + type: string selector: description: |- Selector defines a filter for the Vector Pipeline and Cluster Vector Pipeline by labels. diff --git a/config/crd/bases/observability.kaasops.io_vectoraggregators.yaml b/config/crd/bases/observability.kaasops.io_vectoraggregators.yaml index 6497664..d795466 100644 --- a/config/crd/bases/observability.kaasops.io_vectoraggregators.yaml +++ b/config/crd/bases/observability.kaasops.io_vectoraggregators.yaml @@ -3141,6 +3141,18 @@ spec: schedulerName: description: SchedulerName - defines kubernetes scheduler name type: string + scrapeInterval: + description: |- + ScrapeInterval defines the interval at which Prometheus should scrape metrics. + Example values: "30s", "1m", "5m". If not specified, Prometheus default is used. + pattern: ^(0|([0-9]+(\.[0-9]+)?(ms|s|m|h))+)$ + type: string + scrapeTimeout: + description: |- + ScrapeTimeout defines the timeout for scraping metrics. + Example values: "10s", "30s". Must be less than ScrapeInterval. If not specified, Prometheus default is used. + pattern: ^(0|([0-9]+(\.[0-9]+)?(ms|s|m|h))+)$ + type: string selector: description: |- Selector defines a filter for the Vector Pipeline and Cluster Vector Pipeline by labels. diff --git a/config/crd/bases/observability.kaasops.io_vectors.yaml b/config/crd/bases/observability.kaasops.io_vectors.yaml index 53577da..ddb3cce 100644 --- a/config/crd/bases/observability.kaasops.io_vectors.yaml +++ b/config/crd/bases/observability.kaasops.io_vectors.yaml @@ -3149,6 +3149,18 @@ spec: schedulerName: description: SchedulerName - defines kubernetes scheduler name type: string + scrapeInterval: + description: |- + ScrapeInterval defines the interval at which Prometheus should scrape metrics. + Example values: "30s", "1m", "5m". If not specified, Prometheus default is used. + pattern: ^(0|([0-9]+(\.[0-9]+)?(ms|s|m|h))+)$ + type: string + scrapeTimeout: + description: |- + ScrapeTimeout defines the timeout for scraping metrics. + Example values: "10s", "30s". Must be less than ScrapeInterval. If not specified, Prometheus default is used. + pattern: ^(0|([0-9]+(\.[0-9]+)?(ms|s|m|h))+)$ + type: string tolerations: description: Tolerations If specified, the pod's tolerations. items: diff --git a/docs/specification.md b/docs/specification.md index 5eb3000..96512ca 100644 --- a/docs/specification.md +++ b/docs/specification.md @@ -10,7 +10,7 @@ # Vector Spec - + @@ -26,6 +26,18 @@ + + + + + + + + + + + + diff --git a/internal/vector/aggregator/podmonitor.go b/internal/vector/aggregator/podmonitor.go index cbf6955..48b2ca8 100644 --- a/internal/vector/aggregator/podmonitor.go +++ b/internal/vector/aggregator/podmonitor.go @@ -22,15 +22,22 @@ func (ctrl *Controller) createVectorAggregatorPodMonitor() *monitorv1.PodMonitor matchLabels := ctrl.matchLabelsForVectorAggregator() annotations := ctrl.annotationsForVectorAggregator() + endpoint := monitorv1.PodMetricsEndpoint{ + Path: "/metrics", + Port: "prom-exporter", + } + + if ctrl.Spec.ScrapeInterval != "" { + endpoint.Interval = monitorv1.Duration(ctrl.Spec.ScrapeInterval) + } + if ctrl.Spec.ScrapeTimeout != "" { + endpoint.ScrapeTimeout = monitorv1.Duration(ctrl.Spec.ScrapeTimeout) + } + podmonitor := &monitorv1.PodMonitor{ ObjectMeta: ctrl.objectMetaVectorAggregator(labels, annotations, ctrl.Namespace), Spec: monitorv1.PodMonitorSpec{ - PodMetricsEndpoints: []monitorv1.PodMetricsEndpoint{ - { - Path: "/metrics", - Port: "prom-exporter", - }, - }, + PodMetricsEndpoints: []monitorv1.PodMetricsEndpoint{endpoint}, Selector: metav1.LabelSelector{ MatchLabels: matchLabels, }, diff --git a/internal/vector/aggregator/podmonitor_test.go b/internal/vector/aggregator/podmonitor_test.go new file mode 100644 index 0000000..b6f44fb --- /dev/null +++ b/internal/vector/aggregator/podmonitor_test.go @@ -0,0 +1,227 @@ +package aggregator + +import ( + "testing" + + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + vectorv1alpha1 "github.com/kaasops/vector-operator/api/v1alpha1" +) + +// Helper function to create a Controller for testing +func createTestController(name, namespace string, spec *vectorv1alpha1.VectorAggregatorCommon, isCluster bool) *Controller { + agg := &vectorv1alpha1.VectorAggregator{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } + + return &Controller{ + Name: name, + Namespace: namespace, + VectorAggregator: agg, + APIVersion: "observability.kaasops.io/v1alpha1", + Kind: "VectorAggregator", + Spec: spec, + isClusterAggregator: isCluster, + } +} + +func TestCreateVectorAggregatorPodMonitor_WithCustomSettings(t *testing.T) { + g := NewWithT(t) + + ctrl := createTestController("test-aggregator", "default", + &vectorv1alpha1.VectorAggregatorCommon{ + VectorCommon: vectorv1alpha1.VectorCommon{ + ScrapeInterval: "60s", + ScrapeTimeout: "20s", + }, + }, false) + + pm := ctrl.createVectorAggregatorPodMonitor() + + // Verify PodMonitor structure + g.Expect(pm).NotTo(BeNil(), "PodMonitor should not be nil") + g.Expect(pm.Spec.PodMetricsEndpoints).To(HaveLen(1), "Should have exactly one endpoint") + + // Verify scrape settings + endpoint := pm.Spec.PodMetricsEndpoints[0] + g.Expect(string(endpoint.Interval)).To(Equal("60s"), "scrapeInterval should be 60s") + g.Expect(string(endpoint.ScrapeTimeout)).To(Equal("20s"), "scrapeTimeout should be 20s") + + // Verify endpoint configuration + g.Expect(endpoint.Port).To(Equal("prom-exporter"), "Port should be prom-exporter") + g.Expect(endpoint.Path).To(Equal("/metrics"), "Path should be /metrics") + + // Verify metadata + g.Expect(pm.ObjectMeta.Namespace).To(Equal("default"), "Namespace should match Aggregator namespace") +} + +func TestCreateVectorAggregatorPodMonitor_WithDefaults(t *testing.T) { + g := NewWithT(t) + + ctrl := createTestController("test-aggregator", "default", + &vectorv1alpha1.VectorAggregatorCommon{ + VectorCommon: vectorv1alpha1.VectorCommon{ + // No scrape settings specified + }, + }, false) + + pm := ctrl.createVectorAggregatorPodMonitor() + + g.Expect(pm).NotTo(BeNil()) + g.Expect(pm.Spec.PodMetricsEndpoints).To(HaveLen(1)) + + endpoint := pm.Spec.PodMetricsEndpoints[0] + // When not specified, fields should be empty (Prometheus will use defaults) + g.Expect(string(endpoint.Interval)).To(BeEmpty(), "Interval should be empty when not specified") + g.Expect(string(endpoint.ScrapeTimeout)).To(BeEmpty(), "ScrapeTimeout should be empty when not specified") + + // Basic endpoint config should still be set + g.Expect(endpoint.Port).To(Equal("prom-exporter")) + g.Expect(endpoint.Path).To(Equal("/metrics")) +} + +func TestCreateVectorAggregatorPodMonitor_LabelSelector(t *testing.T) { + g := NewWithT(t) + + ctrl := createTestController("test-aggregator", "test-namespace", + &vectorv1alpha1.VectorAggregatorCommon{}, false) + + pm := ctrl.createVectorAggregatorPodMonitor() + + // Verify selector has proper labels to target only Aggregator pods + g.Expect(pm.Spec.Selector.MatchLabels).To(HaveKeyWithValue("app.kubernetes.io/component", "Aggregator"), + "Selector should include component=Aggregator label") + g.Expect(pm.Spec.Selector.MatchLabels).To(HaveKeyWithValue("app.kubernetes.io/instance", "test-aggregator"), + "Selector should include instance label matching Aggregator name") +} + +func TestCreateVectorAggregatorPodMonitor_OnlyIntervalSet(t *testing.T) { + g := NewWithT(t) + + ctrl := createTestController("test-aggregator", "default", + &vectorv1alpha1.VectorAggregatorCommon{ + VectorCommon: vectorv1alpha1.VectorCommon{ + ScrapeInterval: "1m", + // ScrapeTimeout not set + }, + }, false) + + pm := ctrl.createVectorAggregatorPodMonitor() + + endpoint := pm.Spec.PodMetricsEndpoints[0] + g.Expect(string(endpoint.Interval)).To(Equal("1m"), "Interval should be set") + g.Expect(string(endpoint.ScrapeTimeout)).To(BeEmpty(), "Timeout should remain empty") +} + +func TestCreateVectorAggregatorPodMonitor_OnlyTimeoutSet(t *testing.T) { + g := NewWithT(t) + + ctrl := createTestController("test-aggregator", "default", + &vectorv1alpha1.VectorAggregatorCommon{ + VectorCommon: vectorv1alpha1.VectorCommon{ + // ScrapeInterval not set + ScrapeTimeout: "30s", + }, + }, false) + + pm := ctrl.createVectorAggregatorPodMonitor() + + endpoint := pm.Spec.PodMetricsEndpoints[0] + g.Expect(string(endpoint.Interval)).To(BeEmpty(), "Interval should remain empty") + g.Expect(string(endpoint.ScrapeTimeout)).To(Equal("30s"), "Timeout should be set") +} + +func TestCreateVectorAggregatorPodMonitor_DurationFormats(t *testing.T) { + testCases := []struct { + name string + interval string + timeout string + expectedInt string + expectedTime string + }{ + { + name: "Seconds format", + interval: "60s", + timeout: "20s", + expectedInt: "60s", + expectedTime: "20s", + }, + { + name: "Minutes format", + interval: "10m", + timeout: "2m", + expectedInt: "10m", + expectedTime: "2m", + }, + { + name: "Mixed format", + interval: "2m30s", + timeout: "45s", + expectedInt: "2m30s", + expectedTime: "45s", + }, + { + name: "Milliseconds format", + interval: "1000ms", + timeout: "500ms", + expectedInt: "1000ms", + expectedTime: "500ms", + }, + { + name: "Hours format", + interval: "2h", + timeout: "1h", + expectedInt: "2h", + expectedTime: "1h", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + ctrl := createTestController("test-aggregator", "default", + &vectorv1alpha1.VectorAggregatorCommon{ + VectorCommon: vectorv1alpha1.VectorCommon{ + ScrapeInterval: tc.interval, + ScrapeTimeout: tc.timeout, + }, + }, false) + + pm := ctrl.createVectorAggregatorPodMonitor() + endpoint := pm.Spec.PodMetricsEndpoints[0] + + g.Expect(string(endpoint.Interval)).To(Equal(tc.expectedInt)) + g.Expect(string(endpoint.ScrapeTimeout)).To(Equal(tc.expectedTime)) + }) + } +} + +func TestCreateVectorAggregatorPodMonitor_ClusterAggregator(t *testing.T) { + g := NewWithT(t) + + ctrl := createTestController("cluster-test-aggregator", "vector-system", + &vectorv1alpha1.VectorAggregatorCommon{ + VectorCommon: vectorv1alpha1.VectorCommon{ + ScrapeInterval: "90s", + ScrapeTimeout: "25s", + }, + }, true) + + pm := ctrl.createVectorAggregatorPodMonitor() + + // Verify ClusterVectorAggregator also gets proper PodMonitor + g.Expect(pm).NotTo(BeNil()) + + endpoint := pm.Spec.PodMetricsEndpoints[0] + g.Expect(string(endpoint.Interval)).To(Equal("90s")) + g.Expect(string(endpoint.ScrapeTimeout)).To(Equal("25s")) + + // Verify selector for ClusterVectorAggregator + g.Expect(pm.Spec.Selector.MatchLabels).To(HaveKeyWithValue("app.kubernetes.io/component", "Aggregator")) + g.Expect(pm.Spec.Selector.MatchLabels).To(HaveKeyWithValue("app.kubernetes.io/instance", "cluster-test-aggregator")) +} diff --git a/internal/vector/vectoragent/vectoragent_podmonitor.go b/internal/vector/vectoragent/vectoragent_podmonitor.go index dd09077..ae44d7e 100644 --- a/internal/vector/vectoragent/vectoragent_podmonitor.go +++ b/internal/vector/vectoragent/vectoragent_podmonitor.go @@ -11,15 +11,22 @@ func (ctrl *Controller) createVectorAgentPodMonitor() *monitorv1.PodMonitor { matchLabels := ctrl.matchLabelsForVectorAgent() annotations := ctrl.annotationsForVectorAgent() + endpoint := monitorv1.PodMetricsEndpoint{ + Path: "/metrics", + Port: "prom-exporter", + } + + if ctrl.Vector.Spec.Agent.ScrapeInterval != "" { + endpoint.Interval = monitorv1.Duration(ctrl.Vector.Spec.Agent.ScrapeInterval) + } + if ctrl.Vector.Spec.Agent.ScrapeTimeout != "" { + endpoint.ScrapeTimeout = monitorv1.Duration(ctrl.Vector.Spec.Agent.ScrapeTimeout) + } + podmonitor := &monitorv1.PodMonitor{ ObjectMeta: ctrl.objectMetaVectorAgent(labels, annotations, ctrl.Vector.Namespace), Spec: monitorv1.PodMonitorSpec{ - PodMetricsEndpoints: []monitorv1.PodMetricsEndpoint{ - { - Path: "/metrics", - Port: "prom-exporter", - }, - }, + PodMetricsEndpoints: []monitorv1.PodMetricsEndpoint{endpoint}, Selector: metav1.LabelSelector{ MatchLabels: matchLabels, }, diff --git a/internal/vector/vectoragent/vectoragent_podmonitor_test.go b/internal/vector/vectoragent/vectoragent_podmonitor_test.go new file mode 100644 index 0000000..741d38d --- /dev/null +++ b/internal/vector/vectoragent/vectoragent_podmonitor_test.go @@ -0,0 +1,236 @@ +package vectoragent + +import ( + "testing" + + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + vectorv1alpha1 "github.com/kaasops/vector-operator/api/v1alpha1" +) + +func TestCreateVectorAgentPodMonitor_WithCustomSettings(t *testing.T) { + g := NewWithT(t) + + ctrl := &Controller{ + Vector: &vectorv1alpha1.Vector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vector", + Namespace: "default", + }, + Spec: vectorv1alpha1.VectorSpec{ + Agent: &vectorv1alpha1.VectorAgent{ + VectorCommon: vectorv1alpha1.VectorCommon{ + ScrapeInterval: "45s", + ScrapeTimeout: "15s", + }, + }, + }, + }, + } + + pm := ctrl.createVectorAgentPodMonitor() + + // Verify PodMonitor structure + g.Expect(pm).NotTo(BeNil(), "PodMonitor should not be nil") + g.Expect(pm.Spec.PodMetricsEndpoints).To(HaveLen(1), "Should have exactly one endpoint") + + // Verify scrape settings + endpoint := pm.Spec.PodMetricsEndpoints[0] + g.Expect(string(endpoint.Interval)).To(Equal("45s"), "scrapeInterval should be 45s") + g.Expect(string(endpoint.ScrapeTimeout)).To(Equal("15s"), "scrapeTimeout should be 15s") + + // Verify endpoint configuration + g.Expect(endpoint.Port).To(Equal("prom-exporter"), "Port should be prom-exporter") + g.Expect(endpoint.Path).To(Equal("/metrics"), "Path should be /metrics") + + // Verify metadata + g.Expect(pm.ObjectMeta.Namespace).To(Equal("default"), "Namespace should match Vector namespace") +} + +func TestCreateVectorAgentPodMonitor_WithDefaults(t *testing.T) { + g := NewWithT(t) + + ctrl := &Controller{ + Vector: &vectorv1alpha1.Vector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vector", + Namespace: "default", + }, + Spec: vectorv1alpha1.VectorSpec{ + Agent: &vectorv1alpha1.VectorAgent{ + VectorCommon: vectorv1alpha1.VectorCommon{ + // No scrape settings specified + }, + }, + }, + }, + } + + pm := ctrl.createVectorAgentPodMonitor() + + g.Expect(pm).NotTo(BeNil()) + g.Expect(pm.Spec.PodMetricsEndpoints).To(HaveLen(1)) + + endpoint := pm.Spec.PodMetricsEndpoints[0] + // When not specified, fields should be empty (Prometheus will use defaults) + g.Expect(string(endpoint.Interval)).To(BeEmpty(), "Interval should be empty when not specified") + g.Expect(string(endpoint.ScrapeTimeout)).To(BeEmpty(), "ScrapeTimeout should be empty when not specified") + + // Basic endpoint config should still be set + g.Expect(endpoint.Port).To(Equal("prom-exporter")) + g.Expect(endpoint.Path).To(Equal("/metrics")) +} + +func TestCreateVectorAgentPodMonitor_LabelSelector(t *testing.T) { + g := NewWithT(t) + + ctrl := &Controller{ + Vector: &vectorv1alpha1.Vector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vector-agent", + Namespace: "test-namespace", + }, + Spec: vectorv1alpha1.VectorSpec{ + Agent: &vectorv1alpha1.VectorAgent{}, + }, + }, + } + + pm := ctrl.createVectorAgentPodMonitor() + + // Verify selector has proper labels to target only Agent pods + g.Expect(pm.Spec.Selector.MatchLabels).To(HaveKeyWithValue("app.kubernetes.io/component", "Agent"), + "Selector should include component=Agent label") + g.Expect(pm.Spec.Selector.MatchLabels).To(HaveKeyWithValue("app.kubernetes.io/instance", "test-vector-agent"), + "Selector should include instance label matching Vector name") +} + +func TestCreateVectorAgentPodMonitor_OnlyIntervalSet(t *testing.T) { + g := NewWithT(t) + + ctrl := &Controller{ + Vector: &vectorv1alpha1.Vector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vector", + Namespace: "default", + }, + Spec: vectorv1alpha1.VectorSpec{ + Agent: &vectorv1alpha1.VectorAgent{ + VectorCommon: vectorv1alpha1.VectorCommon{ + ScrapeInterval: "30s", + // ScrapeTimeout not set + }, + }, + }, + }, + } + + pm := ctrl.createVectorAgentPodMonitor() + + endpoint := pm.Spec.PodMetricsEndpoints[0] + g.Expect(string(endpoint.Interval)).To(Equal("30s"), "Interval should be set") + g.Expect(string(endpoint.ScrapeTimeout)).To(BeEmpty(), "Timeout should remain empty") +} + +func TestCreateVectorAgentPodMonitor_OnlyTimeoutSet(t *testing.T) { + g := NewWithT(t) + + ctrl := &Controller{ + Vector: &vectorv1alpha1.Vector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vector", + Namespace: "default", + }, + Spec: vectorv1alpha1.VectorSpec{ + Agent: &vectorv1alpha1.VectorAgent{ + VectorCommon: vectorv1alpha1.VectorCommon{ + // ScrapeInterval not set + ScrapeTimeout: "10s", + }, + }, + }, + }, + } + + pm := ctrl.createVectorAgentPodMonitor() + + endpoint := pm.Spec.PodMetricsEndpoints[0] + g.Expect(string(endpoint.Interval)).To(BeEmpty(), "Interval should remain empty") + g.Expect(string(endpoint.ScrapeTimeout)).To(Equal("10s"), "Timeout should be set") +} + +func TestCreateVectorAgentPodMonitor_DurationFormats(t *testing.T) { + testCases := []struct { + name string + interval string + timeout string + expectedInt string + expectedTime string + }{ + { + name: "Seconds format", + interval: "30s", + timeout: "10s", + expectedInt: "30s", + expectedTime: "10s", + }, + { + name: "Minutes format", + interval: "5m", + timeout: "1m", + expectedInt: "5m", + expectedTime: "1m", + }, + { + name: "Mixed format", + interval: "1m30s", + timeout: "30s", + expectedInt: "1m30s", + expectedTime: "30s", + }, + { + name: "Milliseconds format", + interval: "500ms", + timeout: "100ms", + expectedInt: "500ms", + expectedTime: "100ms", + }, + { + name: "Hours format", + interval: "1h", + timeout: "30m", + expectedInt: "1h", + expectedTime: "30m", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + ctrl := &Controller{ + Vector: &vectorv1alpha1.Vector{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vector", + Namespace: "default", + }, + Spec: vectorv1alpha1.VectorSpec{ + Agent: &vectorv1alpha1.VectorAgent{ + VectorCommon: vectorv1alpha1.VectorCommon{ + ScrapeInterval: tc.interval, + ScrapeTimeout: tc.timeout, + }, + }, + }, + }, + } + + pm := ctrl.createVectorAgentPodMonitor() + endpoint := pm.Spec.PodMetricsEndpoints[0] + + g.Expect(string(endpoint.Interval)).To(Equal(tc.expectedInt)) + g.Expect(string(endpoint.ScrapeTimeout)).To(Equal(tc.expectedTime)) + }) + } +} diff --git a/test/e2e/framework/framework.go b/test/e2e/framework/framework.go index 32ba098..1b46161 100644 --- a/test/e2e/framework/framework.go +++ b/test/e2e/framework/framework.go @@ -280,19 +280,40 @@ func (f *Framework) ApplyTestDataWithoutNamespaceReplacement(path string) { Expect(err).NotTo(HaveOccurred(), "Failed to apply test data %s", path) } -// replaceNamespace replaces hardcoded namespaces in YAML content +// DeleteTestData loads and deletes a test manifest from testdata directory +// It automatically replaces any hardcoded namespace with the framework's namespace +func (f *Framework) DeleteTestData(path string) { + By(fmt.Sprintf("deleting test data: %s", path)) + + content, err := os.ReadFile(filepath.Join(f.TestDataPath, path)) + Expect(err).NotTo(HaveOccurred(), "Failed to load test data from %s", path) + + // Replace namespace in YAML if present + yamlContent := replaceNamespace(string(content), f.namespace) + + err = f.kubectl.DeleteFromYAML(yamlContent) + Expect(err).NotTo(HaveOccurred(), "Failed to delete test data %s in namespace %s", path, f.namespace) +} + +// replaceNamespace replaces namespace placeholders and fields in YAML content func replaceNamespace(yaml, namespace string) string { - // This is a simple replacement - for production use, proper YAML parsing might be better - // But for tests this is sufficient + // Replace NAMESPACE placeholders throughout the content + // This handles cases like spec.resourceNamespace: NAMESPACE + yaml = replacePlaceholder(yaml, "NAMESPACE", namespace) + + // Also replace explicit namespace fields in metadata sections + // This handles cases like: + // namespace: some-other-namespace lines := []string{} for _, line := range splitLines(yaml) { - // Replace namespace: with namespace: + // Check for " namespace:" (2 spaces + "namespace:" = 12 chars) if len(line) > 12 && line[:12] == " namespace:" { lines = append(lines, fmt.Sprintf(" namespace: %s", namespace)) } else { lines = append(lines, line) } } + return joinLines(lines) } diff --git a/test/e2e/framework/kubectl/client.go b/test/e2e/framework/kubectl/client.go index 6d13ca7..95e2425 100644 --- a/test/e2e/framework/kubectl/client.go +++ b/test/e2e/framework/kubectl/client.go @@ -80,6 +80,28 @@ func (c *Client) ApplyWithoutNamespaceOverride(yamlContent string) error { return err } +// DeleteFromYAML deletes resources from YAML content +func (c *Client) DeleteFromYAML(yamlContent string) error { + // Validate namespace to prevent command injection + if err := ValidateNamespace(c.namespace); err != nil { + return fmt.Errorf("namespace validation failed: %w", err) + } + + // Log command for audit and reproducibility + log.Printf("KUBECTL_CMD: kubectl delete -f - -n %s", c.namespace) + + cmd := exec.Command("kubectl", "delete", "-f", "-", "-n", c.namespace) + cmd.Stdin = strings.NewReader(yamlContent) + output, err := utils.Run(cmd) + + // Log kubectl output for debugging + if len(output) > 0 { + fmt.Printf("kubectl delete: %s\n", string(output)) + } + + return err +} + // Get retrieves a resource by name and type func (c *Client) Get(resourceType, name string) ([]byte, error) { // Validate parameters to prevent command injection diff --git a/test/e2e/podmonitor_e2e_test.go b/test/e2e/podmonitor_e2e_test.go new file mode 100644 index 0000000..839acbf --- /dev/null +++ b/test/e2e/podmonitor_e2e_test.go @@ -0,0 +1,460 @@ +/* +Copyright 2024. + +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 ( + "encoding/base64" + "encoding/json" + "fmt" + "os/exec" + "strings" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/kaasops/vector-operator/test/e2e/framework" + "github.com/kaasops/vector-operator/test/e2e/framework/config" +) + +// PodMonitor tests verify the PodMonitor creation and configuration +// including scrapeInterval and scrapeTimeout settings +var _ = Describe("PodMonitor Configuration", Label(config.LabelSmoke, config.LabelFast), Ordered, func() { + f := framework.NewUniqueFramework("test-podmonitor") + + BeforeAll(func() { + f.Setup() + }) + + AfterAll(func() { + f.Teardown() + f.PrintMetrics() + }) + + Context("Agent PodMonitor with custom scrape settings", func() { + It("should create PodMonitor with scrapeInterval and scrapeTimeout", func() { + By("deploying Vector Agent with custom scrape settings") + f.ApplyTestData("podmonitor/agent-with-scrape-config.yaml") + + // Wait for agent resources to be created + time.Sleep(5 * time.Second) + + By("verifying PodMonitor is created") + Eventually(func() error { + return checkPodMonitorExists(f.Namespace(), "podmonitor-agent-agent") + }, config.ServiceCreateTimeout, config.DefaultPollInterval).Should(Succeed()) + + By("verifying scrapeInterval is set correctly") + interval, err := getPodMonitorScrapeInterval(f.Namespace(), "podmonitor-agent-agent") + Expect(err).NotTo(HaveOccurred()) + Expect(interval).To(Equal("45s"), "scrapeInterval should be 45s") + + By("verifying scrapeTimeout is set correctly") + timeout, err := getPodMonitorScrapeTimeout(f.Namespace(), "podmonitor-agent-agent") + Expect(err).NotTo(HaveOccurred()) + Expect(timeout).To(Equal("15s"), "scrapeTimeout should be 15s") + }) + + It("should create PodMonitor with default settings when not specified", func() { + By("deploying Vector Agent with default settings") + f.ApplyTestData("podmonitor/agent-with-defaults.yaml") + + // Wait for agent resources to be created + time.Sleep(5 * time.Second) + + By("verifying PodMonitor is created") + Eventually(func() error { + return checkPodMonitorExists(f.Namespace(), "podmonitor-agent-defaults-agent") + }, config.ServiceCreateTimeout, config.DefaultPollInterval).Should(Succeed()) + + By("verifying scrapeInterval is not set (uses Prometheus default)") + interval, err := getPodMonitorScrapeInterval(f.Namespace(), "podmonitor-agent-defaults-agent") + Expect(err).NotTo(HaveOccurred()) + Expect(interval).To(BeEmpty(), "scrapeInterval should be empty when not specified") + + By("verifying scrapeTimeout is not set (uses Prometheus default)") + timeout, err := getPodMonitorScrapeTimeout(f.Namespace(), "podmonitor-agent-defaults-agent") + Expect(err).NotTo(HaveOccurred()) + Expect(timeout).To(BeEmpty(), "scrapeTimeout should be empty when not specified") + }) + + It("should NOT create PodMonitor when internalMetrics is disabled", func() { + By("deploying Vector Agent with internalMetrics disabled") + f.ApplyTestData("podmonitor/agent-no-metrics.yaml") + + // Wait for agent resources to be created + time.Sleep(5 * time.Second) + + By("verifying PodMonitor is NOT created") + Consistently(func() error { + return checkPodMonitorExists(f.Namespace(), "podmonitor-agent-no-metrics-agent") + }, 10*time.Second, time.Second).Should(HaveOccurred(), "PodMonitor should not exist when internalMetrics is disabled") + }) + }) + + Context("Aggregator PodMonitor with custom scrape settings", func() { + It("should create PodMonitor with scrapeInterval and scrapeTimeout", func() { + By("deploying VectorAggregator with custom scrape settings") + f.ApplyTestData("podmonitor/aggregator-with-scrape-config.yaml") + f.WaitForDeploymentReady("podmonitor-aggregator-aggregator") + + By("verifying PodMonitor is created") + Eventually(func() error { + return checkPodMonitorExists(f.Namespace(), "podmonitor-aggregator-aggregator") + }, config.ServiceCreateTimeout, config.DefaultPollInterval).Should(Succeed()) + + By("verifying scrapeInterval is set correctly") + interval, err := getPodMonitorScrapeInterval(f.Namespace(), "podmonitor-aggregator-aggregator") + Expect(err).NotTo(HaveOccurred()) + Expect(interval).To(Equal("60s"), "scrapeInterval should be 60s") + + By("verifying scrapeTimeout is set correctly") + timeout, err := getPodMonitorScrapeTimeout(f.Namespace(), "podmonitor-aggregator-aggregator") + Expect(err).NotTo(HaveOccurred()) + Expect(timeout).To(Equal("20s"), "scrapeTimeout should be 20s") + }) + + It("should create PodMonitor with default settings when not specified", func() { + By("deploying VectorAggregator with default settings") + f.ApplyTestData("podmonitor/aggregator-with-defaults.yaml") + f.WaitForDeploymentReady("podmonitor-aggregator-defaults-aggregator") + + By("verifying PodMonitor is created") + Eventually(func() error { + return checkPodMonitorExists(f.Namespace(), "podmonitor-aggregator-defaults-aggregator") + }, config.ServiceCreateTimeout, config.DefaultPollInterval).Should(Succeed()) + + By("verifying scrapeInterval is not set (uses Prometheus default)") + interval, err := getPodMonitorScrapeInterval(f.Namespace(), "podmonitor-aggregator-defaults-aggregator") + Expect(err).NotTo(HaveOccurred()) + Expect(interval).To(BeEmpty(), "scrapeInterval should be empty when not specified") + + By("verifying scrapeTimeout is not set (uses Prometheus default)") + timeout, err := getPodMonitorScrapeTimeout(f.Namespace(), "podmonitor-aggregator-defaults-aggregator") + Expect(err).NotTo(HaveOccurred()) + Expect(timeout).To(BeEmpty(), "scrapeTimeout should be empty when not specified") + }) + }) + + Context("PodMonitor label selectors", func() { + It("should have correct matchLabels to select only related pods", func() { + By("verifying Agent PodMonitor selector") + selector, err := getPodMonitorSelector(f.Namespace(), "podmonitor-agent-agent") + Expect(err).NotTo(HaveOccurred()) + + By("checking selector contains component=Agent") + Expect(selector).To(HaveKeyWithValue("app.kubernetes.io/component", "Agent")) + Expect(selector).To(HaveKeyWithValue("app.kubernetes.io/instance", "podmonitor-agent")) + + By("verifying Aggregator PodMonitor selector") + selectorAgg, err := getPodMonitorSelector(f.Namespace(), "podmonitor-aggregator-aggregator") + Expect(err).NotTo(HaveOccurred()) + + By("checking selector contains component=Aggregator") + Expect(selectorAgg).To(HaveKeyWithValue("app.kubernetes.io/component", "Aggregator")) + Expect(selectorAgg).To(HaveKeyWithValue("app.kubernetes.io/instance", "podmonitor-aggregator")) + }) + }) + + Context("ClusterVectorAggregator PodMonitor with custom scrape settings", func() { + It("should create PodMonitor with scrapeInterval and scrapeTimeout", func() { + By("deploying ClusterVectorAggregator with custom scrape settings") + f.ApplyTestData("podmonitor/cluster-aggregator-with-scrape-config.yaml") + f.WaitForDeploymentReady("podmonitor-cluster-agg-aggregator") + + By("verifying PodMonitor is created") + Eventually(func() error { + return checkPodMonitorExists(f.Namespace(), "podmonitor-cluster-agg-aggregator") + }, config.ServiceCreateTimeout, config.DefaultPollInterval).Should(Succeed()) + + By("verifying scrapeInterval is set correctly") + interval, err := getPodMonitorScrapeInterval(f.Namespace(), "podmonitor-cluster-agg-aggregator") + Expect(err).NotTo(HaveOccurred()) + Expect(interval).To(Equal("90s"), "scrapeInterval should be 90s") + + By("verifying scrapeTimeout is set correctly") + timeout, err := getPodMonitorScrapeTimeout(f.Namespace(), "podmonitor-cluster-agg-aggregator") + Expect(err).NotTo(HaveOccurred()) + Expect(timeout).To(Equal("25s"), "scrapeTimeout should be 25s") + }) + + It("should create PodMonitor with default settings when not specified", func() { + By("deploying ClusterVectorAggregator with default settings") + f.ApplyTestData("podmonitor/cluster-aggregator-with-defaults.yaml") + f.WaitForDeploymentReady("podmonitor-cluster-agg-defaults-aggregator") + + By("verifying PodMonitor is created") + Eventually(func() error { + return checkPodMonitorExists(f.Namespace(), "podmonitor-cluster-agg-defaults-aggregator") + }, config.ServiceCreateTimeout, config.DefaultPollInterval).Should(Succeed()) + + By("verifying scrapeInterval is not set (uses Prometheus default)") + interval, err := getPodMonitorScrapeInterval(f.Namespace(), "podmonitor-cluster-agg-defaults-aggregator") + Expect(err).NotTo(HaveOccurred()) + Expect(interval).To(BeEmpty(), "scrapeInterval should be empty when not specified") + + By("verifying scrapeTimeout is not set (uses Prometheus default)") + timeout, err := getPodMonitorScrapeTimeout(f.Namespace(), "podmonitor-cluster-agg-defaults-aggregator") + Expect(err).NotTo(HaveOccurred()) + Expect(timeout).To(BeEmpty(), "scrapeTimeout should be empty when not specified") + }) + + It("should have correct matchLabels to select only ClusterVectorAggregator pods", func() { + By("verifying ClusterVectorAggregator PodMonitor selector") + selector, err := getPodMonitorSelector(f.Namespace(), "podmonitor-cluster-agg-aggregator") + Expect(err).NotTo(HaveOccurred()) + + By("checking selector contains component=Aggregator") + Expect(selector).To(HaveKeyWithValue("app.kubernetes.io/component", "Aggregator")) + Expect(selector).To(HaveKeyWithValue("app.kubernetes.io/instance", "podmonitor-cluster-agg")) + }) + }) +}) + +// InternalMetrics tests verify the isExporterSinkExists logic +var _ = Describe("Internal Metrics Exporter Logic", Label(config.LabelSmoke, config.LabelFast), Ordered, func() { + f := framework.NewUniqueFramework("test-exporter-logic") + + BeforeAll(func() { + f.Setup() + }) + + AfterAll(func() { + f.Teardown() + f.PrintMetrics() + }) + + Context("Auto-add prometheus_exporter when not present", func() { + It("should add default prometheus_exporter when pipeline has no exporter sink", func() { + By("deploying test pod with app=test label") + f.ApplyTestData("podmonitor/test-pod.yaml") + f.WaitForPodReady("test-app") + + By("deploying Vector Agent with internalMetrics enabled") + f.ApplyTestData("podmonitor/agent-with-defaults.yaml") + time.Sleep(5 * time.Second) + + By("creating pipeline without prometheus_exporter sink") + f.ApplyTestData("podmonitor/pipeline-without-exporter.yaml") + f.WaitForPipelineValid("no-exporter-pipeline") + + By("verifying agent config contains default prometheus_exporter") + Eventually(func() bool { + return checkConfigHasExporter(f.Namespace(), "podmonitor-agent-defaults-agent", "internalMetricsSink") + }, config.ServiceCreateTimeout, config.DefaultPollInterval).Should(BeTrue(), + "Agent config should have auto-added prometheus_exporter") + }) + }) + + Context("Skip adding prometheus_exporter when already present", func() { + It("should NOT add default prometheus_exporter when pipeline already has one", func() { + By("deploying Vector Agent with internalMetrics enabled") + // Using existing agent from previous test + + By("creating pipeline WITH custom prometheus_exporter sink") + f.ApplyTestData("podmonitor/pipeline-with-custom-exporter.yaml") + f.WaitForPipelineValid("custom-exporter-pipeline") + + By("verifying agent config uses custom exporter from pipeline") + Eventually(func() bool { + return checkConfigHasExporter(f.Namespace(), "podmonitor-agent-defaults-agent", "custom_prom_exporter") + }, config.ServiceCreateTimeout, config.DefaultPollInterval).Should(BeTrue(), + "Agent config should have custom prometheus_exporter from pipeline") + + By("verifying default exporter is NOT added when custom exporter exists") + // When user provides custom prometheus_exporter, the default should NOT be added + // because isExporterSinkExists() detects the custom exporter + Consistently(func() bool { + return !checkConfigHasExporter(f.Namespace(), "podmonitor-agent-defaults-agent", "internalMetricsSink") + }, 10*time.Second, 2*time.Second).Should(BeTrue(), + "Default exporter should NOT be added when custom exporter exists") + }) + }) +}) + +// PodMonitor Update tests verify that PodMonitor updates when CRD changes +var _ = Describe("PodMonitor Update Behavior", Label(config.LabelSmoke, config.LabelFast), Ordered, func() { + f := framework.NewUniqueFramework("test-podmonitor-update") + + BeforeAll(func() { + f.Setup() + }) + + AfterAll(func() { + f.Teardown() + f.PrintMetrics() + }) + + Context("Update scrapeInterval and scrapeTimeout", func() { + It("should update PodMonitor when Agent scrapeInterval changes", func() { + By("deploying Vector Agent with initial scrapeInterval=45s") + f.ApplyTestData("podmonitor/agent-with-scrape-config.yaml") + time.Sleep(5 * time.Second) + + By("verifying initial scrapeInterval is 45s") + Eventually(func() string { + interval, _ := getPodMonitorScrapeInterval(f.Namespace(), "podmonitor-agent-agent") + return interval + }, config.ServiceCreateTimeout, config.DefaultPollInterval).Should(Equal("45s"), + "Initial scrapeInterval should be 45s") + + By("updating Vector Agent with new scrapeInterval=90s") + f.ApplyTestData("podmonitor/agent-with-updated-interval.yaml") + + By("verifying PodMonitor scrapeInterval updates to 90s") + Eventually(func() string { + interval, _ := getPodMonitorScrapeInterval(f.Namespace(), "podmonitor-agent-agent") + return interval + }, config.ServiceCreateTimeout, config.DefaultPollInterval).Should(Equal("90s"), + "Updated scrapeInterval should be 90s") + + By("verifying scrapeTimeout also updates to 30s") + Eventually(func() string { + timeout, _ := getPodMonitorScrapeTimeout(f.Namespace(), "podmonitor-agent-agent") + return timeout + }, config.ServiceCreateTimeout, config.DefaultPollInterval).Should(Equal("30s"), + "Updated scrapeTimeout should be 30s") + }) + }) +}) + +// PodMonitor Cleanup tests verify that PodMonitor is deleted when Vector CR is deleted +var _ = Describe("PodMonitor Cleanup Behavior", Label(config.LabelSmoke, config.LabelFast), Ordered, func() { + f := framework.NewUniqueFramework("test-podmonitor-cleanup") + + BeforeAll(func() { + f.Setup() + }) + + AfterAll(func() { + f.Teardown() + f.PrintMetrics() + }) + + Context("Delete Vector CR", func() { + It("should delete PodMonitor when Agent is deleted", func() { + By("deploying Vector Agent with PodMonitor") + f.ApplyTestData("podmonitor/agent-with-scrape-config.yaml") + time.Sleep(5 * time.Second) + + By("verifying PodMonitor exists") + Eventually(func() error { + return checkPodMonitorExists(f.Namespace(), "podmonitor-agent-agent") + }, config.ServiceCreateTimeout, config.DefaultPollInterval).Should(Succeed(), + "PodMonitor should exist after Agent creation") + + By("deleting Vector Agent CR") + f.DeleteTestData("podmonitor/agent-with-scrape-config.yaml") + + By("verifying PodMonitor is cleaned up") + Eventually(func() error { + return checkPodMonitorExists(f.Namespace(), "podmonitor-agent-agent") + }, config.ServiceCreateTimeout, config.DefaultPollInterval).Should(HaveOccurred(), + "PodMonitor should be deleted when Agent is deleted") + }) + + It("should delete PodMonitor when Aggregator is deleted", func() { + By("deploying VectorAggregator with PodMonitor") + f.ApplyTestData("podmonitor/aggregator-with-scrape-config.yaml") + f.WaitForDeploymentReady("podmonitor-aggregator-aggregator") + + By("verifying PodMonitor exists") + Eventually(func() error { + return checkPodMonitorExists(f.Namespace(), "podmonitor-aggregator-aggregator") + }, config.ServiceCreateTimeout, config.DefaultPollInterval).Should(Succeed(), + "PodMonitor should exist after Aggregator creation") + + By("deleting VectorAggregator CR") + f.DeleteTestData("podmonitor/aggregator-with-scrape-config.yaml") + + By("verifying PodMonitor is cleaned up") + Eventually(func() error { + return checkPodMonitorExists(f.Namespace(), "podmonitor-aggregator-aggregator") + }, config.ServiceCreateTimeout, config.DefaultPollInterval).Should(HaveOccurred(), + "PodMonitor should be deleted when Aggregator is deleted") + }) + }) +}) + +// Helper functions for PodMonitor verification + +func checkPodMonitorExists(namespace, name string) error { + cmd := exec.Command("kubectl", "get", "podmonitor", name, "-n", namespace) + _, err := cmd.Output() + return err +} + +func getPodMonitorScrapeInterval(namespace, name string) (string, error) { + cmd := exec.Command("kubectl", "get", "podmonitor", name, "-n", namespace, + "-o", "jsonpath={.spec.podMetricsEndpoints[0].interval}") + output, err := cmd.Output() + if err != nil { + return "", err + } + return strings.TrimSpace(string(output)), nil +} + +func getPodMonitorScrapeTimeout(namespace, name string) (string, error) { + cmd := exec.Command("kubectl", "get", "podmonitor", name, "-n", namespace, + "-o", "jsonpath={.spec.podMetricsEndpoints[0].scrapeTimeout}") + output, err := cmd.Output() + if err != nil { + return "", err + } + return strings.TrimSpace(string(output)), nil +} + +func getPodMonitorSelector(namespace, name string) (map[string]string, error) { + cmd := exec.Command("kubectl", "get", "podmonitor", name, "-n", namespace, "-o", "json") + output, err := cmd.Output() + if err != nil { + return nil, err + } + + var podMonitor struct { + Spec struct { + Selector struct { + MatchLabels map[string]string `json:"matchLabels"` + } `json:"selector"` + } `json:"spec"` + } + + if err := json.Unmarshal(output, &podMonitor); err != nil { + return nil, fmt.Errorf("failed to parse PodMonitor JSON: %w", err) + } + + return podMonitor.Spec.Selector.MatchLabels, nil +} + +func checkConfigHasExporter(namespace, secretName, exporterName string) bool { + // Get the secret containing vector config + cmd := exec.Command("kubectl", "get", "secret", secretName, "-n", namespace, + "-o", "jsonpath={.data['agent\\.json']}") + output, err := cmd.Output() + if err != nil { + return false + } + + // Decode base64 + decoded, err := base64.StdEncoding.DecodeString(string(output)) + if err != nil { + return false + } + + // Check if exporter name is in the config + return strings.Contains(string(decoded), exporterName) +} diff --git a/test/e2e/testdata/podmonitor/agent-no-metrics.yaml b/test/e2e/testdata/podmonitor/agent-no-metrics.yaml new file mode 100644 index 0000000..ab7820e --- /dev/null +++ b/test/e2e/testdata/podmonitor/agent-no-metrics.yaml @@ -0,0 +1,8 @@ +apiVersion: observability.kaasops.io/v1alpha1 +kind: Vector +metadata: + name: podmonitor-agent-no-metrics +spec: + agent: + image: timberio/vector:0.40.0-alpine + internalMetrics: false diff --git a/test/e2e/testdata/podmonitor/agent-with-defaults.yaml b/test/e2e/testdata/podmonitor/agent-with-defaults.yaml new file mode 100644 index 0000000..cf1a2d7 --- /dev/null +++ b/test/e2e/testdata/podmonitor/agent-with-defaults.yaml @@ -0,0 +1,8 @@ +apiVersion: observability.kaasops.io/v1alpha1 +kind: Vector +metadata: + name: podmonitor-agent-defaults +spec: + agent: + image: timberio/vector:0.40.0-alpine + internalMetrics: true diff --git a/test/e2e/testdata/podmonitor/agent-with-scrape-config.yaml b/test/e2e/testdata/podmonitor/agent-with-scrape-config.yaml new file mode 100644 index 0000000..15ad9a4 --- /dev/null +++ b/test/e2e/testdata/podmonitor/agent-with-scrape-config.yaml @@ -0,0 +1,10 @@ +apiVersion: observability.kaasops.io/v1alpha1 +kind: Vector +metadata: + name: podmonitor-agent +spec: + agent: + image: timberio/vector:0.40.0-alpine + internalMetrics: true + scrapeInterval: "45s" + scrapeTimeout: "15s" diff --git a/test/e2e/testdata/podmonitor/agent-with-updated-interval.yaml b/test/e2e/testdata/podmonitor/agent-with-updated-interval.yaml new file mode 100644 index 0000000..b0aed2c --- /dev/null +++ b/test/e2e/testdata/podmonitor/agent-with-updated-interval.yaml @@ -0,0 +1,10 @@ +apiVersion: observability.kaasops.io/v1alpha1 +kind: Vector +metadata: + name: podmonitor-agent +spec: + agent: + image: timberio/vector:0.40.0-alpine + internalMetrics: true + scrapeInterval: "90s" # Updated from 45s to 90s + scrapeTimeout: "30s" # Updated from 15s to 30s diff --git a/test/e2e/testdata/podmonitor/aggregator-with-defaults.yaml b/test/e2e/testdata/podmonitor/aggregator-with-defaults.yaml new file mode 100644 index 0000000..fd23b10 --- /dev/null +++ b/test/e2e/testdata/podmonitor/aggregator-with-defaults.yaml @@ -0,0 +1,11 @@ +apiVersion: observability.kaasops.io/v1alpha1 +kind: VectorAggregator +metadata: + name: podmonitor-aggregator-defaults +spec: + image: timberio/vector:0.40.0-alpine + replicas: 1 + internalMetrics: true + selector: + matchLabels: + app: test diff --git a/test/e2e/testdata/podmonitor/aggregator-with-scrape-config.yaml b/test/e2e/testdata/podmonitor/aggregator-with-scrape-config.yaml new file mode 100644 index 0000000..4890612 --- /dev/null +++ b/test/e2e/testdata/podmonitor/aggregator-with-scrape-config.yaml @@ -0,0 +1,13 @@ +apiVersion: observability.kaasops.io/v1alpha1 +kind: VectorAggregator +metadata: + name: podmonitor-aggregator +spec: + image: timberio/vector:0.40.0-alpine + replicas: 1 + internalMetrics: true + scrapeInterval: "60s" + scrapeTimeout: "20s" + selector: + matchLabels: + app: test diff --git a/test/e2e/testdata/podmonitor/cluster-aggregator-with-defaults.yaml b/test/e2e/testdata/podmonitor/cluster-aggregator-with-defaults.yaml new file mode 100644 index 0000000..21dcb5f --- /dev/null +++ b/test/e2e/testdata/podmonitor/cluster-aggregator-with-defaults.yaml @@ -0,0 +1,12 @@ +apiVersion: observability.kaasops.io/v1alpha1 +kind: ClusterVectorAggregator +metadata: + name: podmonitor-cluster-agg-defaults +spec: + resourceNamespace: NAMESPACE + image: timberio/vector:0.40.0-alpine + replicas: 1 + internalMetrics: true + selector: + matchLabels: + app: cluster-test diff --git a/test/e2e/testdata/podmonitor/cluster-aggregator-with-scrape-config.yaml b/test/e2e/testdata/podmonitor/cluster-aggregator-with-scrape-config.yaml new file mode 100644 index 0000000..4dc9137 --- /dev/null +++ b/test/e2e/testdata/podmonitor/cluster-aggregator-with-scrape-config.yaml @@ -0,0 +1,14 @@ +apiVersion: observability.kaasops.io/v1alpha1 +kind: ClusterVectorAggregator +metadata: + name: podmonitor-cluster-agg +spec: + resourceNamespace: NAMESPACE + image: timberio/vector:0.40.0-alpine + replicas: 1 + internalMetrics: true + scrapeInterval: "90s" + scrapeTimeout: "25s" + selector: + matchLabels: + app: cluster-test diff --git a/test/e2e/testdata/podmonitor/pipeline-aggregator-role.yaml b/test/e2e/testdata/podmonitor/pipeline-aggregator-role.yaml new file mode 100644 index 0000000..9a6fc9b --- /dev/null +++ b/test/e2e/testdata/podmonitor/pipeline-aggregator-role.yaml @@ -0,0 +1,16 @@ +apiVersion: observability.kaasops.io/v1alpha1 +kind: VectorPipeline +metadata: + name: aggregator-test-pipeline + labels: + app: test +spec: + sources: + http_source: + type: http_server + address: "0.0.0.0:8080" + sinks: + blackhole: + type: blackhole + inputs: + - http_source diff --git a/test/e2e/testdata/podmonitor/pipeline-with-custom-exporter.yaml b/test/e2e/testdata/podmonitor/pipeline-with-custom-exporter.yaml new file mode 100644 index 0000000..ed3db6b --- /dev/null +++ b/test/e2e/testdata/podmonitor/pipeline-with-custom-exporter.yaml @@ -0,0 +1,27 @@ +apiVersion: observability.kaasops.io/v1alpha1 +kind: VectorPipeline +metadata: + name: custom-exporter-pipeline + labels: + app: test +spec: + sources: + k8s_logs: + type: kubernetes_logs + extra_label_selector: "app=test" + transforms: + log_to_metric: + type: log_to_metric + inputs: + - k8s_logs + metrics: + - type: counter + field: message + name: log_lines_total + namespace: custom + sinks: + custom_prom_exporter: + type: prometheus_exporter + inputs: + - log_to_metric + address: "0.0.0.0:9599" diff --git a/test/e2e/testdata/podmonitor/pipeline-without-exporter.yaml b/test/e2e/testdata/podmonitor/pipeline-without-exporter.yaml new file mode 100644 index 0000000..f80f9e8 --- /dev/null +++ b/test/e2e/testdata/podmonitor/pipeline-without-exporter.yaml @@ -0,0 +1,16 @@ +apiVersion: observability.kaasops.io/v1alpha1 +kind: VectorPipeline +metadata: + name: no-exporter-pipeline + labels: + app: test +spec: + sources: + k8s_logs: + type: kubernetes_logs + extra_label_selector: "app=test" + sinks: + blackhole: + type: blackhole + inputs: + - k8s_logs diff --git a/test/e2e/testdata/podmonitor/test-pod.yaml b/test/e2e/testdata/podmonitor/test-pod.yaml new file mode 100644 index 0000000..dae0145 --- /dev/null +++ b/test/e2e/testdata/podmonitor/test-pod.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + name: test-app + labels: + app: test +spec: + containers: + - name: nginx + image: nginx:alpine + command: ["/bin/sh", "-c"] + args: + - | + while true; do + echo "Test log message from test-app" + sleep 5 + done
agentagent image Image for Vector agent. timberio/vector:0.48.0-distroless-libc by default
api ApiSpec
internalMetricsEnable internal metrics exporter. When enabled, a PodMonitor resource is created for Prometheus scraping. By default - false
scrapeIntervalInterval at which Prometheus should scrape metrics from the internal metrics exporter. Examples: "30s", "1m", "5m". Only used when internalMetrics is true. If not specified, Prometheus default is used.
scrapeTimeoutTimeout for scraping metrics. Must be less than scrapeInterval. Examples: "10s", "30s". Only used when internalMetrics is true. If not specified, Prometheus default is used.
service Temporary field for enabling service for Vector DaemonSet. By default - false