diff --git a/cni/Dockerfile b/cni/Dockerfile index e67b453456..471831d678 100644 --- a/cni/Dockerfile +++ b/cni/Dockerfile @@ -22,6 +22,7 @@ RUN GOOS=$OS CGO_ENABLED=0 go build -a -o /go/bin/azure-vnet -trimpath -ldflags RUN GOOS=$OS CGO_ENABLED=0 go build -a -o /go/bin/azure-vnet-telemetry -trimpath -ldflags "-s -w -X main.version="$VERSION" -X "$CNI_AI_PATH"="$CNI_AI_ID"" -gcflags="-dwarflocationlists=true" cni/telemetry/service/telemetrymain.go RUN GOOS=$OS CGO_ENABLED=0 go build -a -o /go/bin/azure-vnet-ipam -trimpath -ldflags "-s -w -X main.version="$VERSION"" -gcflags="-dwarflocationlists=true" cni/ipam/plugin/main.go RUN GOOS=$OS CGO_ENABLED=0 go build -a -o /go/bin/azure-vnet-stateless -trimpath -ldflags "-s -w -X main.version="$VERSION"" -gcflags="-dwarflocationlists=true" cni/network/stateless/main.go +RUN GOOS=$OS CGO_ENABLED=0 go build -a -o /go/bin/azure-cni-telemetry-sidecar -trimpath -ldflags "-X main.version=$VERSION" -gcflags="-dwarflocationlists=true" ./cns/cni-telemetry-sidecar FROM mariner-core AS compressor ARG OS diff --git a/cns/cni-telemetry-sidecar/configmanager.go b/cns/cni-telemetry-sidecar/configmanager.go new file mode 100644 index 0000000000..0edf94de4c --- /dev/null +++ b/cns/cni-telemetry-sidecar/configmanager.go @@ -0,0 +1,183 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/Azure/azure-container-networking/cns/configuration" + "github.com/Azure/azure-container-networking/telemetry" + "go.uber.org/zap" +) + +// ConfigManager handles CNS configuration loading +type ConfigManager struct { + configPath string + logger *zap.Logger +} + +// NewConfigManager creates a new config manager +func NewConfigManager(configPath string) *ConfigManager { + return &ConfigManager{ + configPath: configPath, + } +} + +// SetLogger sets the zap logger for the config manager +func (cm *ConfigManager) SetLogger(logger *zap.Logger) { + cm.logger = logger +} + +// LoadConfig loads the CNS configuration from file +func (cm *ConfigManager) LoadConfig() (*configuration.CNSConfig, error) { + if cm.logger != nil { + cm.logger.Debug("Loading CNS configuration", zap.String("path", cm.configPath)) + } + + // Check if config file exists + if _, err := os.Stat(cm.configPath); os.IsNotExist(err) { + if cm.logger != nil { + cm.logger.Info("CNS config file not found, using default configuration", + zap.String("path", cm.configPath)) + } + return cm.createDefaultConfig(), nil + } + + // Read the config file + data, err := os.ReadFile(cm.configPath) + if err != nil { + if cm.logger != nil { + cm.logger.Error("Failed to read CNS config file", + zap.String("path", cm.configPath), + zap.Error(err)) + } + return nil, fmt.Errorf("failed to read config file %s: %w", cm.configPath, err) + } + + // Parse the JSON configuration + var config configuration.CNSConfig + if err := json.Unmarshal(data, &config); err != nil { + if cm.logger != nil { + cm.logger.Error("Failed to parse CNS config file", + zap.String("path", cm.configPath), + zap.Error(err)) + } + return nil, fmt.Errorf("failed to parse config file: %w", err) + } + + // Apply defaults and environment variable overrides + cm.setConfigDefaults(&config) + + // Check for AppInsights key from all sources (build-time, config, env) + hasAppInsightsKey := cm.hasEffectiveAppInsightsKey(&config.TelemetrySettings) + + if cm.logger != nil { + cm.logger.Info("Successfully loaded CNS configuration", + zap.String("path", cm.configPath), + zap.Bool("telemetryDisabled", config.TelemetrySettings.DisableAll), + zap.Bool("cniTelemetryEnabled", config.TelemetrySettings.EnableCNITelemetry), + zap.String("socketPath", config.TelemetrySettings.CNITelemetrySocketPath), + zap.Bool("hasAppInsightsKey", hasAppInsightsKey)) + } + + return &config, nil +} + +// createDefaultConfig creates a default configuration +func (cm *ConfigManager) createDefaultConfig() *configuration.CNSConfig { + config := &configuration.CNSConfig{ + TelemetrySettings: configuration.TelemetrySettings{ + DisableAll: false, + TelemetryBatchSizeBytes: defaultBatchSizeInBytes, + TelemetryBatchIntervalInSecs: defaultBatchIntervalInSecs, + RefreshIntervalInSecs: defaultRefreshTimeoutInSecs, + DisableMetadataRefreshThread: false, + DebugMode: false, + DisableTrace: false, + DisableMetric: false, + DisableEvent: false, + EnableCNITelemetry: false, // Default to false + CNITelemetrySocketPath: "/var/run/azure-vnet-telemetry.sock", + }, + } + + // Set AppInsights key from environment variables (if any) + cm.setAppInsightsKeyFromEnv(&config.TelemetrySettings) + + return config +} + +// setConfigDefaults applies default values and environment variable overrides +func (cm *ConfigManager) setConfigDefaults(config *configuration.CNSConfig) { + // Set default values for telemetry settings if not specified + if config.TelemetrySettings.TelemetryBatchSizeBytes == 0 { + config.TelemetrySettings.TelemetryBatchSizeBytes = defaultBatchSizeInBytes + } + if config.TelemetrySettings.TelemetryBatchIntervalInSecs == 0 { + config.TelemetrySettings.TelemetryBatchIntervalInSecs = defaultBatchIntervalInSecs + } + if config.TelemetrySettings.RefreshIntervalInSecs == 0 { + config.TelemetrySettings.RefreshIntervalInSecs = defaultRefreshTimeoutInSecs + } + + // Set default CNI telemetry socket path + if config.TelemetrySettings.CNITelemetrySocketPath == "" { + config.TelemetrySettings.CNITelemetrySocketPath = "/var/run/azure-vnet-telemetry.sock" + } + + // Handle AppInsights instrumentation key from environment variables + cm.setAppInsightsKeyFromEnv(&config.TelemetrySettings) +} + +// setAppInsightsKeyFromEnv sets the AppInsights instrumentation key from environment variables +func (cm *ConfigManager) setAppInsightsKeyFromEnv(ts *configuration.TelemetrySettings) { + // Try multiple environment variable names + envKeys := []string{ + "APPINSIGHTS_INSTRUMENTATIONKEY", + "APPLICATIONINSIGHTS_CONNECTION_STRING", + "AI_INSTRUMENTATION_KEY", + } + + // If no key is set in config, try environment variables + if ts.AppInsightsInstrumentationKey == "" { + for _, envKey := range envKeys { + if key := os.Getenv(envKey); key != "" { + ts.AppInsightsInstrumentationKey = key + if cm.logger != nil { + cm.logger.Debug("Found AppInsights key in environment variable", + zap.String("envVar", envKey)) + } + break + } + } + } +} + +// hasEffectiveAppInsightsKey checks if AppInsights key is available from any source +// (build-time aiMetadata, config file, or environment variables) +func (cm *ConfigManager) hasEffectiveAppInsightsKey(ts *configuration.TelemetrySettings) bool { + // Priority 1: Build-time embedded key via telemetry.aiMetadata + if buildTimeKey := telemetry.GetAIMetadata(); buildTimeKey != "" { + return true + } + + // Priority 2: Config file + if ts.AppInsightsInstrumentationKey != "" { + return true + } + + // Priority 3: Environment variables + envKeys := []string{ + "APPINSIGHTS_INSTRUMENTATIONKEY", + "APPLICATIONINSIGHTS_CONNECTION_STRING", + "AI_INSTRUMENTATION_KEY", + } + + for _, envKey := range envKeys { + if key := os.Getenv(envKey); key != "" { + return true + } + } + + return false +} diff --git a/cns/cni-telemetry-sidecar/main.go b/cns/cni-telemetry-sidecar/main.go new file mode 100644 index 0000000000..0e96772ee8 --- /dev/null +++ b/cns/cni-telemetry-sidecar/main.go @@ -0,0 +1,100 @@ +package main + +import ( + "context" + "flag" + "os" + "os/signal" + "syscall" + + "github.com/Azure/azure-container-networking/telemetry" + "go.uber.org/zap" +) + +var ( + configPath = flag.String("config", "/etc/azure-cns/cns_config.json", "Path to CNS configuration file") + logLevel = flag.String("log-level", "info", "Log level (debug, info, warn, error)") + + // This variable is set at build time via ldflags from Makefile + version = "1.0.0" // -X main.version=$(CNI_TELEMETRY_SIDECAR_VERSION) +) + +func main() { + flag.Parse() + + // Initialize logger + logger, err := initializeLogger(*logLevel) + if err != nil { + panic(err) + } + defer logger.Sync() + + // DEBUG: Check if aiMetadata was set at build time via ldflags + currentAIMetadata := telemetry.GetAIMetadata() + logger.Info("Starting Azure CNI Telemetry Sidecar", + zap.String("version", version), + zap.String("configPath", *configPath), + zap.String("logLevel", *logLevel), + zap.Bool("hasBuiltInAIKey", currentAIMetadata != ""), + zap.String("aiKeyPrefix", MaskAIKey(currentAIMetadata))) + + // Create and configure telemetry sidecar + // Pass the configPath to NewTelemetrySidecar (it expects a string parameter) + sidecar := NewTelemetrySidecar(*configPath) + if err := sidecar.SetLogger(logger); err != nil { + logger.Error("Failed to set logger", zap.Error(err)) + os.Exit(1) + } + + // Log which AI key source we're using + if currentAIMetadata != "" { + logger.Info("Using build-time embedded AppInsights key (from Makefile)") + } else { + logger.Info("No build-time AppInsights key found - will check config/environment") + } + + // Create context with cancellation for graceful shutdown + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Handle shutdown signals + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + go func() { + sig := <-sigChan + logger.Info("Received shutdown signal", zap.String("signal", sig.String())) + cancel() + }() + + // Run the sidecar + if err := sidecar.Run(ctx); err != nil && err != context.Canceled { + logger.Error("Sidecar execution failed", zap.Error(err)) + os.Exit(1) + } + + logger.Info("Azure CNI Telemetry Sidecar shutdown complete") +} + +// initializeLogger creates a zap logger with the specified level +func initializeLogger(level string) (*zap.Logger, error) { + var zapLevel zap.AtomicLevel + switch level { + case "debug": + zapLevel = zap.NewAtomicLevelAt(zap.DebugLevel) + case "info": + zapLevel = zap.NewAtomicLevelAt(zap.InfoLevel) + case "warn": + zapLevel = zap.NewAtomicLevelAt(zap.WarnLevel) + case "error": + zapLevel = zap.NewAtomicLevelAt(zap.ErrorLevel) + default: + zapLevel = zap.NewAtomicLevelAt(zap.InfoLevel) + } + + config := zap.NewProductionConfig() + config.Level = zapLevel + config.DisableStacktrace = true + config.DisableCaller = false + + return config.Build() +} diff --git a/cns/cni-telemetry-sidecar/sidecar.go b/cns/cni-telemetry-sidecar/sidecar.go new file mode 100644 index 0000000000..35e89ff5b2 --- /dev/null +++ b/cns/cni-telemetry-sidecar/sidecar.go @@ -0,0 +1,322 @@ +package main + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/Azure/azure-container-networking/aitelemetry" + "github.com/Azure/azure-container-networking/cns/configuration" + "github.com/Azure/azure-container-networking/telemetry" + "go.uber.org/zap" +) + +const ( + // Constants matching telemetrymain.go + defaultReportToHostIntervalInSecs = 30 + defaultRefreshTimeoutInSecs = 15 + defaultBatchSizeInBytes = 16384 + defaultBatchIntervalInSecs = 15 + defaultGetEnvRetryCount = 2 + defaultGetEnvRetryWaitTimeInSecs = 3 + pluginName = "AzureCNI" + cniTelemetryVersion = "1.0.0" +) + +// TelemetrySidecar replaces the azure-vnet-telemetry binary fork process +type TelemetrySidecar struct { + configPath string + configManager *ConfigManager + logger *zap.Logger + telemetryBuffer *telemetry.TelemetryBuffer + builtInAIKey string // AppInsights key embedded at build time (like azure-vnet-telemetry) +} + +// NewTelemetrySidecar creates a new telemetry sidecar instance +func NewTelemetrySidecar(configPath string) *TelemetrySidecar { + return &TelemetrySidecar{ + configPath: configPath, + configManager: NewConfigManager(configPath), + } +} + +// SetLogger sets the zap logger for the sidecar +func (s *TelemetrySidecar) SetLogger(logger *zap.Logger) error { + if logger == nil { + return fmt.Errorf("logger cannot be nil") + } + s.logger = logger + s.configManager.SetLogger(logger) + return nil +} + +// SetBuiltInAIKey sets the build-time embedded AppInsights key +func (s *TelemetrySidecar) SetBuiltInAIKey(key string) { + s.builtInAIKey = key +} + +// Run starts the telemetry sidecar (replaces main() in telemetrymain.go) +func (s *TelemetrySidecar) Run(ctx context.Context) error { + if s.logger == nil { + return fmt.Errorf("logger not initialized - call SetLogger() first") + } + + s.logger.Info("Starting Azure CNI Telemetry Sidecar (replacing azure-vnet-telemetry binary)") + + // Load CNS configuration + cnsConfig, err := s.configManager.LoadConfig() + if err != nil { + return fmt.Errorf("failed to load CNS configuration: %w", err) + } + + // Check if telemetry should run + if !s.shouldRunTelemetry(cnsConfig) { + s.logger.Info("CNI Telemetry disabled, entering sleep mode") + return s.sleepUntilShutdown(ctx) + } + + // Convert CNS config to telemetry config (like telemetrymain.go does) + telemetryConfig := s.convertToTelemetryConfig(cnsConfig) + s.setTelemetryDefaults(&telemetryConfig) + + s.logger.Info("Telemetry configuration", zap.Any("config", telemetryConfig)) + + // Initialize and start telemetry service with both configs (like telemetrymain.go does) + if err := s.startTelemetryService(ctx, telemetryConfig, cnsConfig); err != nil { + return fmt.Errorf("failed to start telemetry service: %w", err) + } + + // Keep running until context is cancelled + <-ctx.Done() + return s.cleanup() +} + +// convertToTelemetryConfig converts CNS config to telemetry config +func (s *TelemetrySidecar) convertToTelemetryConfig(cnsConfig *configuration.CNSConfig) telemetry.TelemetryConfig { + ts := cnsConfig.TelemetrySettings + + return telemetry.TelemetryConfig{ + ReportToHostIntervalInSeconds: time.Duration(defaultReportToHostIntervalInSecs) * time.Second, + DisableAll: ts.DisableAll, + DisableTrace: ts.DisableTrace, + DisableMetric: ts.DisableMetric, + BatchSizeInBytes: ts.TelemetryBatchSizeBytes, + BatchIntervalInSecs: ts.TelemetryBatchIntervalInSecs, + RefreshTimeoutInSecs: ts.RefreshIntervalInSecs, + DisableMetadataThread: ts.DisableMetadataRefreshThread, + DebugMode: ts.DebugMode, + GetEnvRetryCount: defaultGetEnvRetryCount, + GetEnvRetryWaitTimeInSecs: defaultGetEnvRetryWaitTimeInSecs, + } +} + +// setTelemetryDefaults sets default values (same as telemetrymain.go) +func (s *TelemetrySidecar) setTelemetryDefaults(config *telemetry.TelemetryConfig) { + if config.ReportToHostIntervalInSeconds == 0 { + config.ReportToHostIntervalInSeconds = time.Duration(defaultReportToHostIntervalInSecs) * time.Second + } + + if config.RefreshTimeoutInSecs == 0 { + config.RefreshTimeoutInSecs = defaultRefreshTimeoutInSecs + } + + if config.BatchIntervalInSecs == 0 { + config.BatchIntervalInSecs = defaultBatchIntervalInSecs + } + + if config.BatchSizeInBytes == 0 { + config.BatchSizeInBytes = defaultBatchSizeInBytes + } + + if config.GetEnvRetryCount == 0 { + config.GetEnvRetryCount = defaultGetEnvRetryCount + } + + if config.GetEnvRetryWaitTimeInSecs == 0 { + config.GetEnvRetryWaitTimeInSecs = defaultGetEnvRetryWaitTimeInSecs + } +} + +// getAppInsightsKey gets the AppInsights key with priority: build-time > config > env vars +func (s *TelemetrySidecar) getAppInsightsKey(cnsConfig *configuration.CNSConfig) string { + // Priority 1: Build-time embedded key via telemetry.aiMetadata (like azure-vnet-telemetry) + if buildTimeKey := telemetry.GetAIMetadata(); buildTimeKey != "" { + s.logger.Debug("Using build-time embedded AppInsights key") + return buildTimeKey + } + + // Priority 2: CNS configuration + if cnsConfig != nil && cnsConfig.TelemetrySettings.AppInsightsInstrumentationKey != "" { + s.logger.Debug("Using AppInsights key from CNS configuration") + return cnsConfig.TelemetrySettings.AppInsightsInstrumentationKey + } + + // Priority 3: Environment variables (fallback) + envKeys := []string{ + "APPINSIGHTS_INSTRUMENTATIONKEY", + "APPLICATIONINSIGHTS_CONNECTION_STRING", + "AI_INSTRUMENTATION_KEY", + } + + for _, envKey := range envKeys { + if envKey := os.Getenv(envKey); envKey != "" { + s.logger.Debug("Using AppInsights key from environment variable", zap.String("envVar", envKey)) + return envKey + } + } + + // Only log warning if no key found from any source + s.logger.Warn("No AppInsights instrumentation key found from any source (build-time, config, or environment)") + return "" +} + +// startTelemetryService starts the telemetry service (replicates telemetrymain.go logic) +func (s *TelemetrySidecar) startTelemetryService(ctx context.Context, config telemetry.TelemetryConfig, cnsConfig *configuration.CNSConfig) error { + s.logger.Info("Initializing telemetry service") + + // Get AppInsights key with priority order (build-time aiMetadata has highest priority) + aiKey := s.getAppInsightsKey(cnsConfig) + + // DEBUG: Only show detailed debug info in debug mode + if s.logger.Level() == zap.DebugLevel { + currentAIMetadata := telemetry.GetAIMetadata() + s.logger.Debug("AI telemetry status", + zap.String("buildTimeAIMetadata", MaskAIKey(currentAIMetadata)), + zap.String("resolvedAIKey", MaskAIKey(aiKey)), + zap.Bool("aiMetadataSet", currentAIMetadata != "")) + } + + if aiKey != "" { + // Set environment variable for compatibility with other telemetry components + os.Setenv("APPINSIGHTS_INSTRUMENTATIONKEY", aiKey) + + // If aiMetadata wasn't set at build time, set it now for runtime scenarios + if currentAIMetadata := telemetry.GetAIMetadata(); currentAIMetadata == "" { + telemetry.SetAIMetadata(aiKey) + s.logger.Debug("Set aiMetadata at runtime") + } + } + + // Clean up any orphan socket (same as telemetrymain.go) + tbtemp := telemetry.NewTelemetryBuffer(s.logger) + tbtemp.Cleanup(telemetry.FdName) + + // Create telemetry buffer (same as telemetrymain.go) + s.telemetryBuffer = telemetry.NewTelemetryBuffer(s.logger) + + // Start telemetry server (same as telemetrymain.go) + for { + s.logger.Info("Starting telemetry server") + err := s.telemetryBuffer.StartServer() + if err == nil || s.telemetryBuffer.FdExists { + break + } + + s.logger.Error("Telemetry service starting failed", zap.Error(err)) + s.telemetryBuffer.Cleanup(telemetry.FdName) + time.Sleep(time.Millisecond * 200) + } + + // Only create AI telemetry handle if we have an AI key or aiMetadata is set + finalAIMetadata := telemetry.GetAIMetadata() + if finalAIMetadata != "" { + // Configure AI settings (same as telemetrymain.go) + aiConfig := aitelemetry.AIConfig{ + AppName: pluginName, + AppVersion: cniTelemetryVersion, + BatchSize: config.BatchSizeInBytes, + BatchInterval: config.BatchIntervalInSecs, + RefreshTimeout: config.RefreshTimeoutInSecs, + DisableMetadataRefreshThread: config.DisableMetadataThread, + DebugMode: config.DebugMode, + GetEnvRetryCount: config.GetEnvRetryCount, + GetEnvRetryWaitTimeInSecs: config.GetEnvRetryWaitTimeInSecs, + } + + s.logger.Info("Initializing Azure Application Insights telemetry") + + // Create AI telemetry handle (same as telemetrymain.go) + if err := s.telemetryBuffer.CreateAITelemetryHandle(aiConfig, config.DisableAll, config.DisableTrace, config.DisableMetric); err != nil { + s.logger.Error("Failed to initialize Azure Application Insights", zap.Error(err)) + s.logger.Info("Continuing with local telemetry only") + } else { + s.logger.Info("Azure Application Insights telemetry initialized successfully") + } + } else { + s.logger.Info("Running with local telemetry only (no Azure Application Insights key)") + } + + s.logger.Info("Telemetry service started successfully", + zap.Duration("reportInterval", config.ReportToHostIntervalInSeconds), + zap.String("pluginName", pluginName), + zap.String("version", cniTelemetryVersion), + zap.Bool("azureIntegration", finalAIMetadata != "")) + + // Start the data push routine in background (same as telemetrymain.go) + go s.telemetryBuffer.PushData(ctx) + + return nil +} + +// Helper function to mask AI key for logging +func MaskAIKey(aiKey string) string { + if len(aiKey) <= 8 { + return aiKey + } + return aiKey[:8] + "..." +} + +// shouldRunTelemetry determines if CNI telemetry should be enabled +func (s *TelemetrySidecar) shouldRunTelemetry(cnsConfig *configuration.CNSConfig) bool { + // Check if telemetry is disabled globally + if cnsConfig.TelemetrySettings.DisableAll { + s.logger.Info("Telemetry disabled via CNS configuration") + return false + } + + // Check if CNI telemetry is specifically enabled + if !cnsConfig.TelemetrySettings.EnableCNITelemetry { + s.logger.Info("CNI Telemetry disabled via CNS configuration") + return false + } + + // Check if we have an AI key from any source + aiKey := s.getAppInsightsKey(cnsConfig) + hasAIKey := aiKey != "" + + if hasAIKey { + s.logger.Info("CNI Telemetry enabled with AppInsights integration", + zap.Bool("enableCNITelemetry", cnsConfig.TelemetrySettings.EnableCNITelemetry), + zap.Bool("hasAppInsightsKey", true)) + } else { + s.logger.Info("CNI Telemetry enabled with local-only mode", + zap.Bool("enableCNITelemetry", cnsConfig.TelemetrySettings.EnableCNITelemetry), + zap.Bool("hasAppInsightsKey", false)) + } + + return true +} + +// cleanup handles graceful shutdown (like telemetrymain.go) +func (s *TelemetrySidecar) cleanup() error { + s.logger.Info("Shutting down CNI Telemetry service") + + if s.telemetryBuffer != nil { + // Close AI telemetry handle (same as telemetrymain.go) + telemetry.CloseAITelemetryHandle() + + // Cleanup socket and resources + s.telemetryBuffer.Cleanup(telemetry.FdName) + s.logger.Info("Telemetry service cleaned up successfully") + } + + return nil +} + +// sleepUntilShutdown keeps the container running when telemetry is disabled +func (s *TelemetrySidecar) sleepUntilShutdown(ctx context.Context) error { + s.logger.Info("CNI Telemetry sidecar sleeping until shutdown signal received") + <-ctx.Done() + return ctx.Err() +} diff --git a/cns/configuration/configuration.go b/cns/configuration/configuration.go index 9ec5f8664f..b7deb115d8 100644 --- a/cns/configuration/configuration.go +++ b/cns/configuration/configuration.go @@ -88,6 +88,10 @@ type TelemetrySettings struct { ConfigSnapshotIntervalInMins int // AppInsightsInstrumentationKey allows the user to override the default appinsights ikey AppInsightsInstrumentationKey string + // Flag to enable CNI telemetry collection via sidecar + EnableCNITelemetry bool + // Path to the CNI telemetry socket file that azure-vnet CNI connects to + CNITelemetrySocketPath string } type ManagedSettings struct { diff --git a/telemetry/aiwrapper.go b/telemetry/aiwrapper.go index 95622adb69..bafa15b16b 100644 --- a/telemetry/aiwrapper.go +++ b/telemetry/aiwrapper.go @@ -86,3 +86,13 @@ func CloseAITelemetryHandle() { th.Close(waitTimeInSecs) } } + +// GetAIMetadata returns the current aiMetadata value +func GetAIMetadata() string { + return aiMetadata +} + +// SetAIMetadata sets the aiMetadata value (for runtime configuration) +func SetAIMetadata(metadata string) { + aiMetadata = metadata +}