Skip to content

Commit 5c726d0

Browse files
committed
add support for propagating telemetry in configmap
1 parent fc1aedf commit 5c726d0

File tree

2 files changed

+259
-0
lines changed

2 files changed

+259
-0
lines changed

cmd/thv-operator/controllers/mcpserver_runconfig.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"os"
1010
"sort"
11+
"strconv"
1112
"strings"
1213

1314
corev1 "k8s.io/api/core/v1"
@@ -289,6 +290,9 @@ func (r *MCPServerReconciler) createRunConfigFromMCPServer(m *mcpv1alpha1.MCPSer
289290
}
290291
}
291292

293+
// Add telemetry configuration if specified
294+
addTelemetryConfigOptions(&options, m.Spec.Telemetry, m.Name)
295+
292296
// Use the RunConfigBuilder for operator context with full builder pattern
293297
return runner.NewOperatorRunConfigBuilder(
294298
context.Background(),
@@ -534,3 +538,75 @@ func convertSecretsFromMCPServer(secs []mcpv1alpha1.SecretRef) []string {
534538
}
535539
return secrets
536540
}
541+
542+
// addTelemetryConfigOptions adds telemetry configuration options to the builder options
543+
func addTelemetryConfigOptions(
544+
options *[]runner.RunConfigBuilderOption,
545+
telemetryConfig *mcpv1alpha1.TelemetryConfig,
546+
mcpServerName string,
547+
) {
548+
if telemetryConfig == nil {
549+
return
550+
}
551+
552+
// Default values
553+
var otelEndpoint string
554+
var otelEnablePrometheusMetricsPath bool
555+
var otelTracingEnabled bool
556+
var otelMetricsEnabled bool
557+
var otelServiceName string
558+
var otelSamplingRate = 0.05 // Default sampling rate
559+
var otelHeaders []string
560+
var otelInsecure bool
561+
var otelEnvironmentVariables []string
562+
563+
// Process OpenTelemetry configuration
564+
if telemetryConfig.OpenTelemetry != nil && telemetryConfig.OpenTelemetry.Enabled {
565+
otel := telemetryConfig.OpenTelemetry
566+
567+
otelEndpoint = otel.Endpoint
568+
otelInsecure = otel.Insecure
569+
otelHeaders = otel.Headers
570+
571+
// Use MCPServer name as service name if not specified
572+
if otel.ServiceName != "" {
573+
otelServiceName = otel.ServiceName
574+
} else {
575+
otelServiceName = mcpServerName
576+
}
577+
578+
// Handle tracing configuration
579+
if otel.Tracing != nil {
580+
otelTracingEnabled = otel.Tracing.Enabled
581+
if otel.Tracing.SamplingRate != "" {
582+
// Parse sampling rate string to float64
583+
if rate, err := strconv.ParseFloat(otel.Tracing.SamplingRate, 64); err == nil {
584+
otelSamplingRate = rate
585+
}
586+
}
587+
}
588+
589+
// Handle metrics configuration
590+
if otel.Metrics != nil {
591+
otelMetricsEnabled = otel.Metrics.Enabled
592+
}
593+
}
594+
595+
// Process Prometheus configuration
596+
if telemetryConfig.Prometheus != nil {
597+
otelEnablePrometheusMetricsPath = telemetryConfig.Prometheus.Enabled
598+
}
599+
600+
// Add telemetry config to options
601+
*options = append(*options, runner.WithTelemetryConfig(
602+
otelEndpoint,
603+
otelEnablePrometheusMetricsPath,
604+
otelTracingEnabled,
605+
otelMetricsEnabled,
606+
otelServiceName,
607+
otelSamplingRate,
608+
otelHeaders,
609+
otelInsecure,
610+
otelEnvironmentVariables,
611+
))
612+
}

cmd/thv-operator/controllers/mcpserver_runconfig_test.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,124 @@ func TestCreateRunConfigFromMCPServer(t *testing.T) {
282282
assert.Len(t, config.Secrets, 0)
283283
},
284284
},
285+
{
286+
name: "with telemetry configuration",
287+
mcpServer: &mcpv1alpha1.MCPServer{
288+
ObjectMeta: metav1.ObjectMeta{
289+
Name: "telemetry-server",
290+
Namespace: "test-ns",
291+
},
292+
Spec: mcpv1alpha1.MCPServerSpec{
293+
Image: testImage,
294+
Transport: stdioTransport,
295+
Port: 8080,
296+
Telemetry: &mcpv1alpha1.TelemetryConfig{
297+
OpenTelemetry: &mcpv1alpha1.OpenTelemetryConfig{
298+
Enabled: true,
299+
Endpoint: "http://otel-collector:4317",
300+
ServiceName: "custom-service-name",
301+
Insecure: true,
302+
Headers: []string{"Authorization=Bearer token123", "X-API-Key=abc"},
303+
Tracing: &mcpv1alpha1.OpenTelemetryTracingConfig{
304+
Enabled: true,
305+
SamplingRate: "0.25",
306+
},
307+
Metrics: &mcpv1alpha1.OpenTelemetryMetricsConfig{
308+
Enabled: true,
309+
},
310+
},
311+
Prometheus: &mcpv1alpha1.PrometheusConfig{
312+
Enabled: true,
313+
},
314+
},
315+
},
316+
},
317+
//nolint:thelper // We want to see the error at the specific line
318+
expected: func(t *testing.T, config *runner.RunConfig) {
319+
assert.Equal(t, "telemetry-server", config.Name)
320+
321+
// Verify telemetry config is set
322+
assert.NotNil(t, config.TelemetryConfig)
323+
324+
// Check OpenTelemetry settings
325+
assert.Equal(t, "http://otel-collector:4317", config.TelemetryConfig.Endpoint)
326+
assert.Equal(t, "custom-service-name", config.TelemetryConfig.ServiceName)
327+
assert.True(t, config.TelemetryConfig.Insecure)
328+
assert.True(t, config.TelemetryConfig.TracingEnabled)
329+
assert.True(t, config.TelemetryConfig.MetricsEnabled)
330+
assert.Equal(t, 0.25, config.TelemetryConfig.SamplingRate)
331+
assert.Equal(t, map[string]string{"Authorization": "Bearer token123", "X-API-Key": "abc"}, config.TelemetryConfig.Headers)
332+
333+
// Check Prometheus settings
334+
assert.True(t, config.TelemetryConfig.EnablePrometheusMetricsPath)
335+
},
336+
},
337+
{
338+
name: "with minimal telemetry configuration",
339+
mcpServer: &mcpv1alpha1.MCPServer{
340+
ObjectMeta: metav1.ObjectMeta{
341+
Name: "minimal-telemetry-server",
342+
Namespace: "test-ns",
343+
},
344+
Spec: mcpv1alpha1.MCPServerSpec{
345+
Image: testImage,
346+
Transport: stdioTransport,
347+
Port: 8080,
348+
Telemetry: &mcpv1alpha1.TelemetryConfig{
349+
OpenTelemetry: &mcpv1alpha1.OpenTelemetryConfig{
350+
Enabled: true,
351+
Endpoint: "https://secure-otel:4318",
352+
// ServiceName not specified - should default to MCPServer name
353+
},
354+
},
355+
},
356+
},
357+
//nolint:thelper // We want to see the error at the specific line
358+
expected: func(t *testing.T, config *runner.RunConfig) {
359+
assert.Equal(t, "minimal-telemetry-server", config.Name)
360+
361+
// Verify telemetry config is set
362+
assert.NotNil(t, config.TelemetryConfig)
363+
364+
// Check that service name defaults to MCPServer name
365+
assert.Equal(t, "minimal-telemetry-server", config.TelemetryConfig.ServiceName)
366+
assert.Equal(t, "https://secure-otel:4318", config.TelemetryConfig.Endpoint)
367+
assert.False(t, config.TelemetryConfig.Insecure) // Default should be false
368+
assert.Equal(t, 0.05, config.TelemetryConfig.SamplingRate) // Default sampling rate
369+
},
370+
},
371+
{
372+
name: "with prometheus only telemetry",
373+
mcpServer: &mcpv1alpha1.MCPServer{
374+
ObjectMeta: metav1.ObjectMeta{
375+
Name: "prometheus-only-server",
376+
Namespace: "test-ns",
377+
},
378+
Spec: mcpv1alpha1.MCPServerSpec{
379+
Image: testImage,
380+
Transport: stdioTransport,
381+
Port: 8080,
382+
Telemetry: &mcpv1alpha1.TelemetryConfig{
383+
Prometheus: &mcpv1alpha1.PrometheusConfig{
384+
Enabled: true,
385+
},
386+
},
387+
},
388+
},
389+
//nolint:thelper // We want to see the error at the specific line
390+
expected: func(t *testing.T, config *runner.RunConfig) {
391+
assert.Equal(t, "prometheus-only-server", config.Name)
392+
393+
// Verify telemetry config is set
394+
assert.NotNil(t, config.TelemetryConfig)
395+
396+
// Only Prometheus should be enabled
397+
assert.True(t, config.TelemetryConfig.EnablePrometheusMetricsPath)
398+
assert.False(t, config.TelemetryConfig.TracingEnabled)
399+
assert.False(t, config.TelemetryConfig.MetricsEnabled)
400+
assert.Equal(t, "", config.TelemetryConfig.Endpoint)
401+
},
402+
},
285403
}
286404

287405
for _, tt := range tests {
@@ -533,6 +651,71 @@ func TestEnsureRunConfigConfigMap(t *testing.T) {
533651
assert.NotEmpty(t, cm.Annotations["toolhive.stacklok.dev/content-checksum"])
534652
},
535653
},
654+
{
655+
name: "configmap with telemetry configuration",
656+
mcpServer: &mcpv1alpha1.MCPServer{
657+
ObjectMeta: metav1.ObjectMeta{
658+
Name: "telemetry-test",
659+
Namespace: "toolhive-system",
660+
},
661+
Spec: mcpv1alpha1.MCPServerSpec{
662+
Image: "ghcr.io/example/server:v1.0.0",
663+
Transport: "stdio",
664+
Port: 8080,
665+
Telemetry: &mcpv1alpha1.TelemetryConfig{
666+
OpenTelemetry: &mcpv1alpha1.OpenTelemetryConfig{
667+
Enabled: true,
668+
Endpoint: "http://otel-collector:4317",
669+
ServiceName: "test-service",
670+
Headers: []string{"Authorization=Bearer test-token"},
671+
Insecure: true,
672+
Tracing: &mcpv1alpha1.OpenTelemetryTracingConfig{
673+
Enabled: true,
674+
SamplingRate: "0.1",
675+
},
676+
Metrics: &mcpv1alpha1.OpenTelemetryMetricsConfig{
677+
Enabled: true,
678+
},
679+
},
680+
Prometheus: &mcpv1alpha1.PrometheusConfig{
681+
Enabled: true,
682+
},
683+
},
684+
},
685+
},
686+
existingCM: nil,
687+
expectError: false,
688+
validateContent: func(t *testing.T, cm *corev1.ConfigMap) {
689+
t.Helper()
690+
assert.Equal(t, "telemetry-test-runconfig", cm.Name)
691+
assert.Equal(t, "toolhive-system", cm.Namespace)
692+
assert.Contains(t, cm.Data, "runconfig.json")
693+
694+
// Parse and validate telemetry configuration in runconfig.json
695+
var runConfig runner.RunConfig
696+
err := json.Unmarshal([]byte(cm.Data["runconfig.json"]), &runConfig)
697+
require.NoError(t, err)
698+
699+
// Verify basic fields
700+
assert.Equal(t, "telemetry-test", runConfig.Name)
701+
assert.Equal(t, "ghcr.io/example/server:v1.0.0", runConfig.Image)
702+
703+
// Verify telemetry configuration is properly serialized
704+
assert.NotNil(t, runConfig.TelemetryConfig, "TelemetryConfig should be present in runconfig.json")
705+
706+
// Check OpenTelemetry settings
707+
assert.Equal(t, "http://otel-collector:4317", runConfig.TelemetryConfig.Endpoint)
708+
assert.Equal(t, "test-service", runConfig.TelemetryConfig.ServiceName)
709+
assert.True(t, runConfig.TelemetryConfig.Insecure)
710+
assert.True(t, runConfig.TelemetryConfig.TracingEnabled)
711+
assert.True(t, runConfig.TelemetryConfig.MetricsEnabled)
712+
assert.Equal(t, 0.1, runConfig.TelemetryConfig.SamplingRate)
713+
assert.Equal(t, map[string]string{"Authorization": "Bearer test-token"}, runConfig.TelemetryConfig.Headers)
714+
715+
// Check Prometheus settings
716+
assert.True(t, runConfig.TelemetryConfig.EnablePrometheusMetricsPath)
717+
},
718+
},
536719
}
537720

538721
for _, tt := range tests {

0 commit comments

Comments
 (0)