mirror of
https://github.com/hanzo-ml/kserve.git
synced 2026-10-30 20:53:49 +00:00
Signed-off-by: Bartosz Majsak <[email protected]> Co-authored-by: Sivanantham <[email protected]>
403 lines
13 KiB
Go
403 lines
13 KiB
Go
/*
|
|
Copyright 2025 The KServe 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 otel
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/kserve/kserve/pkg/apis/serving/v1beta1"
|
|
|
|
otelv1beta1 "github.com/open-telemetry/opentelemetry-operator/apis/v1beta1"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
|
)
|
|
|
|
func TestCreateOtelCollector(t *testing.T) {
|
|
// Using assert library instead of gomega
|
|
|
|
testCases := []struct {
|
|
name string
|
|
componentMeta metav1.ObjectMeta
|
|
metricNames []string
|
|
otelConfig v1beta1.OtelCollectorConfig
|
|
expectedConfig map[string]interface{}
|
|
}{
|
|
{
|
|
name: "test with port annotation and single metric",
|
|
componentMeta: metav1.ObjectMeta{
|
|
Name: "test-service",
|
|
Namespace: "default",
|
|
Annotations: map[string]string{
|
|
AnnotationPrometheusPort: "9090",
|
|
},
|
|
},
|
|
metricNames: []string{"request-count"},
|
|
otelConfig: v1beta1.OtelCollectorConfig{
|
|
ScrapeInterval: "15s",
|
|
MetricReceiverEndpoint: "otel-collector:4317",
|
|
},
|
|
expectedConfig: map[string]interface{}{
|
|
KeyJobName: JobNameOtelCollector,
|
|
KeyScrapeInterval: "15s",
|
|
KeyStaticConfigs: []interface{}{
|
|
map[string]interface{}{
|
|
KeyTargets: []interface{}{"localhost:9090"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "test without port annotation and multiple metrics",
|
|
componentMeta: metav1.ObjectMeta{
|
|
Name: "test-service",
|
|
Namespace: "default",
|
|
},
|
|
metricNames: []string{"metric1", "metric2"},
|
|
otelConfig: v1beta1.OtelCollectorConfig{
|
|
ScrapeInterval: "30s",
|
|
},
|
|
expectedConfig: map[string]interface{}{
|
|
KeyJobName: JobNameOtelCollector,
|
|
KeyScrapeInterval: "30s",
|
|
KeyStaticConfigs: []interface{}{
|
|
map[string]interface{}{
|
|
KeyTargets: []interface{}{"localhost:8080"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
collector := createOtelCollector(tc.componentMeta, tc.metricNames, tc.otelConfig)
|
|
|
|
assert.Equal(t, tc.componentMeta.Name, collector.Name)
|
|
assert.Equal(t, tc.componentMeta.Namespace, collector.Namespace)
|
|
assert.Equal(t, otelv1beta1.ModeSidecar, collector.Spec.Mode)
|
|
|
|
// Assert config details
|
|
receivers := collector.Spec.Config.Receivers.Object
|
|
prometheusConfig := receivers[PrometheusReceiver].(map[string]interface{})
|
|
config := prometheusConfig[KeyConfig].(map[string]interface{})
|
|
scrapeConfigs := config[KeyScrapeConfigs].([]interface{})
|
|
scrapeConfig := scrapeConfigs[0].(map[string]interface{})
|
|
|
|
assert.Equal(t, tc.expectedConfig[KeyJobName], scrapeConfig[KeyJobName])
|
|
assert.Equal(t, tc.expectedConfig[KeyScrapeInterval], scrapeConfig[KeyScrapeInterval])
|
|
|
|
staticConfigs := scrapeConfig[KeyStaticConfigs].([]interface{})
|
|
staticConfig := staticConfigs[0].(map[string]interface{})
|
|
targets := staticConfig[KeyTargets].([]interface{})
|
|
|
|
assert.Equal(t, tc.expectedConfig[KeyStaticConfigs].([]interface{})[0].(map[string]interface{})[KeyTargets], targets)
|
|
|
|
// Verify filter processor if metric names exist
|
|
if len(tc.metricNames) > 0 {
|
|
processors := collector.Spec.Config.Processors.Object
|
|
filterMetrics := processors[ProcessorFilterMetrics].(map[string]interface{})
|
|
metrics := filterMetrics[KeyMetrics].(map[string]interface{})
|
|
include := metrics[KeyInclude].(map[string]interface{})
|
|
metricNames := include[KeyMetricNames].([]string)
|
|
assert.ElementsMatch(t, tc.metricNames, metricNames)
|
|
}
|
|
|
|
// Verify processors always include resourcedetection/env and transform
|
|
processors := collector.Spec.Config.Processors.Object
|
|
assert.Contains(t, processors, ProcessorResourcedetectionEnv)
|
|
assert.Contains(t, processors, ProcessorTransform)
|
|
|
|
// Verify pipeline processors
|
|
pipeline := collector.Spec.Config.Service.Pipelines[PipelineMetrics].Processors
|
|
if len(tc.metricNames) > 0 {
|
|
assert.Equal(t, []string{ProcessorResourcedetectionEnv, ProcessorTransform, ProcessorFilterMetrics}, pipeline)
|
|
// Verify filter processor config
|
|
filterMetrics := processors[ProcessorFilterMetrics].(map[string]interface{})
|
|
metrics := filterMetrics[KeyMetrics].(map[string]interface{})
|
|
include := metrics[KeyInclude].(map[string]interface{})
|
|
metricNames := include[KeyMetricNames].([]string)
|
|
assert.ElementsMatch(t, tc.metricNames, metricNames)
|
|
} else {
|
|
assert.Equal(t, []string{ProcessorResourcedetectionEnv, ProcessorTransform}, pipeline)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReconcileCreate(t *testing.T) {
|
|
// Using assert library instead of gomega
|
|
|
|
// Setup test data
|
|
componentMeta := metav1.ObjectMeta{
|
|
Name: "test-service",
|
|
Namespace: "default",
|
|
}
|
|
|
|
otelConfig := v1beta1.OtelCollectorConfig{
|
|
ScrapeInterval: "15s",
|
|
MetricReceiverEndpoint: "otel-collector:4317",
|
|
}
|
|
|
|
// Create the test scheme
|
|
scheme := runtime.NewScheme()
|
|
_ = otelv1beta1.AddToScheme(scheme)
|
|
|
|
// Create fake client
|
|
client := fake.NewClientBuilder().WithScheme(scheme).Build()
|
|
// Create reconciler
|
|
reconciler, err := NewOtelReconciler(client, scheme, componentMeta, []string{}, otelConfig)
|
|
require.NoError(t, err)
|
|
|
|
// Test reconcile - should create a new resource
|
|
err = reconciler.Reconcile(t.Context())
|
|
require.NoError(t, err)
|
|
assert.NoError(t, err)
|
|
// Verify collector was created
|
|
collector := &otelv1beta1.OpenTelemetryCollector{}
|
|
err = client.Get(t.Context(), types.NamespacedName{Name: componentMeta.Name, Namespace: componentMeta.Namespace}, collector)
|
|
require.NoError(t, err)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, componentMeta.Name, collector.Name)
|
|
assert.Equal(t, componentMeta.Namespace, collector.Namespace)
|
|
}
|
|
|
|
func TestReconcileUpdate(t *testing.T) {
|
|
// Using assert library instead of gomega
|
|
|
|
// Setup test data
|
|
componentMeta := metav1.ObjectMeta{
|
|
Name: "test-service",
|
|
Namespace: "default",
|
|
}
|
|
|
|
otelConfig := v1beta1.OtelCollectorConfig{
|
|
ScrapeInterval: "15s",
|
|
MetricReceiverEndpoint: "otel-collector:4317",
|
|
}
|
|
|
|
// Create the test scheme
|
|
scheme := runtime.NewScheme()
|
|
_ = otelv1beta1.AddToScheme(scheme)
|
|
|
|
// Create existing collector with different config
|
|
existingCollector := &otelv1beta1.OpenTelemetryCollector{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: componentMeta.Name,
|
|
Namespace: componentMeta.Namespace,
|
|
},
|
|
Spec: otelv1beta1.OpenTelemetryCollectorSpec{
|
|
Mode: otelv1beta1.ModeSidecar,
|
|
Config: otelv1beta1.Config{
|
|
Receivers: otelv1beta1.AnyConfig{Object: map[string]interface{}{
|
|
PrometheusReceiver: map[string]interface{}{
|
|
KeyConfig: map[string]interface{}{
|
|
KeyScrapeConfigs: []interface{}{
|
|
map[string]interface{}{
|
|
KeyJobName: "old-collector",
|
|
KeyScrapeInterval: "30s",
|
|
KeyStaticConfigs: []interface{}{
|
|
map[string]interface{}{
|
|
KeyTargets: []interface{}{"localhost:8080"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
Service: otelv1beta1.Service{
|
|
Pipelines: map[string]*otelv1beta1.Pipeline{
|
|
PipelineMetrics: {
|
|
Receivers: []string{PrometheusReceiver},
|
|
Processors: []string{},
|
|
Exporters: []string{"otlp"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create fake client with existing collector
|
|
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(existingCollector).Build()
|
|
// Create reconciler
|
|
reconciler, err := NewOtelReconciler(client, scheme, componentMeta, []string{}, otelConfig)
|
|
require.NoError(t, err)
|
|
|
|
// Test reconcile - should update existing resource
|
|
err = reconciler.Reconcile(t.Context())
|
|
require.NoError(t, err)
|
|
assert.NoError(t, err)
|
|
// Verify collector was updated
|
|
updatedCollector := &otelv1beta1.OpenTelemetryCollector{}
|
|
err = client.Get(t.Context(), types.NamespacedName{Name: componentMeta.Name, Namespace: componentMeta.Namespace}, updatedCollector)
|
|
require.NoError(t, err)
|
|
assert.NoError(t, err)
|
|
|
|
// Verify updated config
|
|
receivers := updatedCollector.Spec.Config.Receivers.Object
|
|
prometheusConfig := receivers[PrometheusReceiver].(map[string]interface{})
|
|
config := prometheusConfig[KeyConfig].(map[string]interface{})
|
|
scrapeConfigs := config[KeyScrapeConfigs].([]interface{})
|
|
scrapeConfig := scrapeConfigs[0].(map[string]interface{})
|
|
|
|
assert.Equal(t, JobNameOtelCollector, scrapeConfig[KeyJobName])
|
|
assert.Equal(t, "15s", scrapeConfig[KeyScrapeInterval])
|
|
}
|
|
|
|
func TestSetControllerReferences(t *testing.T) {
|
|
// Using assert library instead of gomega
|
|
|
|
// Setup test data
|
|
componentMeta := metav1.ObjectMeta{
|
|
Name: "test-service",
|
|
Namespace: "default",
|
|
}
|
|
|
|
// Create a proper runtime.Object for owner reference
|
|
owner := &v1beta1.InferenceService{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "owner-resource",
|
|
Namespace: "default",
|
|
UID: types.UID("test-uid"),
|
|
},
|
|
}
|
|
|
|
otelConfig := v1beta1.OtelCollectorConfig{
|
|
ScrapeInterval: "15s",
|
|
MetricReceiverEndpoint: "otel-collector:4317",
|
|
}
|
|
|
|
// Create the test scheme
|
|
scheme := runtime.NewScheme()
|
|
_ = otelv1beta1.AddToScheme(scheme)
|
|
_ = v1beta1.AddToScheme(scheme)
|
|
|
|
// Create fake client
|
|
client := fake.NewClientBuilder().WithScheme(scheme).Build()
|
|
// Create reconciler
|
|
reconciler, err := NewOtelReconciler(client, scheme, componentMeta, []string{}, otelConfig)
|
|
require.NoError(t, err)
|
|
|
|
// Test set controller reference
|
|
err = reconciler.SetControllerReferences(owner, scheme)
|
|
require.NoError(t, err)
|
|
assert.NoError(t, err)
|
|
|
|
// Verify owner reference was set
|
|
ownerRefs := reconciler.OTelCollector.OwnerReferences
|
|
assert.Len(t, ownerRefs, 1)
|
|
assert.Equal(t, owner.Name, ownerRefs[0].Name)
|
|
assert.Equal(t, owner.UID, ownerRefs[0].UID)
|
|
}
|
|
|
|
func TestCreateOtelCollectorWithResources(t *testing.T) {
|
|
componentMeta := metav1.ObjectMeta{
|
|
Name: "test-service",
|
|
Namespace: "default",
|
|
}
|
|
|
|
otelConfig := v1beta1.OtelCollectorConfig{
|
|
ScrapeInterval: "30s",
|
|
MetricReceiverEndpoint: "otel-receiver:4317",
|
|
Resource: v1beta1.ResourceConfig{
|
|
CPULimit: "500m",
|
|
MemoryLimit: "1Gi",
|
|
CPURequest: "100m",
|
|
MemoryRequest: "256Mi",
|
|
},
|
|
}
|
|
|
|
collector := createOtelCollector(componentMeta, []string{}, otelConfig)
|
|
|
|
// Verify resource requirements are set correctly
|
|
resources := collector.Spec.Resources
|
|
|
|
// Check CPU limits and requests
|
|
cpuLimit := resources.Limits["cpu"]
|
|
assert.Equal(t, "500m", cpuLimit.String())
|
|
cpuRequest := resources.Requests["cpu"]
|
|
assert.Equal(t, "100m", cpuRequest.String())
|
|
|
|
// Check memory limits and requests
|
|
memoryLimit := resources.Limits["memory"]
|
|
assert.Equal(t, "1Gi", memoryLimit.String())
|
|
memoryRequest := resources.Requests["memory"]
|
|
assert.Equal(t, "256Mi", memoryRequest.String())
|
|
}
|
|
|
|
func TestCreateOtelCollectorWithPartialResources(t *testing.T) {
|
|
componentMeta := metav1.ObjectMeta{
|
|
Name: "test-service",
|
|
Namespace: "default",
|
|
}
|
|
|
|
otelConfig := v1beta1.OtelCollectorConfig{
|
|
ScrapeInterval: "30s",
|
|
MetricReceiverEndpoint: "otel-receiver:4317",
|
|
Resource: v1beta1.ResourceConfig{
|
|
CPULimit: "500m",
|
|
CPURequest: "100m",
|
|
// Memory limits and requests are not set
|
|
},
|
|
}
|
|
|
|
collector := createOtelCollector(componentMeta, []string{}, otelConfig)
|
|
|
|
// Verify only CPU resource requirements are set
|
|
resources := collector.Spec.Resources
|
|
|
|
// Check CPU limits and requests are set
|
|
cpuLimit := resources.Limits["cpu"]
|
|
assert.Equal(t, "500m", cpuLimit.String())
|
|
cpuRequest := resources.Requests["cpu"]
|
|
assert.Equal(t, "100m", cpuRequest.String())
|
|
|
|
// Check memory limits and requests are not set
|
|
_, hasMemoryLimit := resources.Limits["memory"]
|
|
assert.False(t, hasMemoryLimit)
|
|
_, hasMemoryRequest := resources.Requests["memory"]
|
|
assert.False(t, hasMemoryRequest)
|
|
}
|
|
|
|
func TestCreateOtelCollectorWithNoResources(t *testing.T) {
|
|
componentMeta := metav1.ObjectMeta{
|
|
Name: "test-service",
|
|
Namespace: "default",
|
|
}
|
|
|
|
otelConfig := v1beta1.OtelCollectorConfig{
|
|
ScrapeInterval: "30s",
|
|
MetricReceiverEndpoint: "otel-receiver:4317",
|
|
Resource: v1beta1.ResourceConfig{}, // Empty resource config
|
|
}
|
|
|
|
collector := createOtelCollector(componentMeta, []string{}, otelConfig)
|
|
|
|
// Verify no resource requirements are set
|
|
resources := collector.Spec.Resources
|
|
|
|
// Check that limits and requests maps are either nil or empty
|
|
assert.Empty(t, resources.Limits)
|
|
assert.Empty(t, resources.Requests)
|
|
}
|