diff --git a/internal/manifests/collector/adapters/config_from.go b/internal/manifests/collector/adapters/config_from.go index cfb6d766d..685fdbcab 100644 --- a/internal/manifests/collector/adapters/config_from.go +++ b/internal/manifests/collector/adapters/config_from.go @@ -58,6 +58,7 @@ type Traces struct { type MetricsCollected struct { StatsD *statsD `json:"statsd,omitempty"` CollectD *collectD `json:"collectd,omitempty"` + JMX *jmx `json:"jmx,omitempty"` } type LogMetricsCollected struct { @@ -86,9 +87,13 @@ type AppSignals struct { type emf struct { } + +type jmx struct{} + type kubernetes struct { EnhancedContainerInsights bool `json:"enhanced_container_insights,omitempty"` AcceleratedComputeMetrics bool `json:"accelerated_compute_metrics,omitempty"` + JMXContainerInsights bool `json:"jmx_container_insights,omitempty"` } type xray struct { diff --git a/internal/manifests/collector/ports.go b/internal/manifests/collector/ports.go index 55fed0c2c..929b54384 100644 --- a/internal/manifests/collector/ports.go +++ b/internal/manifests/collector/ports.go @@ -37,12 +37,14 @@ const ( EMFTcp = "emf-tcp" EMFUdp = "emf-udp" CWA = "cwa-" + JmxHttp = "jmx-http" ) var receiverDefaultPortsMap = map[string]int32{ StatsD: 8125, CollectD: 25826, XrayTraces: 2000, + JmxHttp: 4314, OtlpGrpc: 4317, OtlpHttp: 4318, EMF: 25888, @@ -143,6 +145,9 @@ func getMetricsReceiversServicePorts(logger logr.Logger, config *adapters.CwaCon if config.Metrics.MetricsCollected.CollectD != nil { getReceiverServicePort(logger, config.Metrics.MetricsCollected.CollectD.ServiceAddress, CollectD, corev1.ProtocolUDP, servicePortsMap) } + if config.Metrics.MetricsCollected.JMX != nil { + getReceiverServicePort(logger, "", JmxHttp, corev1.ProtocolTCP, servicePortsMap) + } } func getReceiverServicePort(logger logr.Logger, serviceAddress string, receiverName string, protocol corev1.Protocol, servicePortsMap map[int32][]corev1.ServicePort) { @@ -195,6 +200,20 @@ func getLogsReceiversServicePorts(logger logr.Logger, config *adapters.CwaConfig servicePortsMap[receiverDefaultPortsMap[EMF]] = []corev1.ServicePort{tcp, udp} } } + + //JMX Container Insights + if config.Logs != nil && config.Logs.LogMetricsCollected != nil && config.Logs.LogMetricsCollected.Kubernetes != nil && config.Logs.LogMetricsCollected.Kubernetes.JMXContainerInsights { + if _, ok := servicePortsMap[receiverDefaultPortsMap[JmxHttp]]; ok { + logger.Info("Duplicate port has been configured in Agent Config for port", zap.Int32("port", receiverDefaultPortsMap[JmxHttp])) + } else { + tcp := corev1.ServicePort{ + Name: JmxHttp, + Port: receiverDefaultPortsMap[JmxHttp], + Protocol: corev1.ProtocolTCP, + } + servicePortsMap[receiverDefaultPortsMap[JmxHttp]] = []corev1.ServicePort{tcp} + } + } } func getTracesReceiversServicePorts(logger logr.Logger, config *adapters.CwaConfig, servicePortsMap map[int32][]corev1.ServicePort) []corev1.ServicePort { diff --git a/internal/manifests/collector/ports_test.go b/internal/manifests/collector/ports_test.go index bade37cb7..b8cf41292 100644 --- a/internal/manifests/collector/ports_test.go +++ b/internal/manifests/collector/ports_test.go @@ -204,6 +204,24 @@ func TestInvalidConfigGetContainerPorts(t *testing.T) { } +func TestJMXGetContainerPorts(t *testing.T) { + cfg := getJSONStringFromFile("./test-resources/jmxAgentConfig.json") + containerPorts := getContainerPorts(logger, cfg, []corev1.ServicePort{}) + assert.Equal(t, 1, len(containerPorts)) + assert.Equal(t, int32(4314), containerPorts[JmxHttp].ContainerPort) + assert.Equal(t, JmxHttp, containerPorts[JmxHttp].Name) + assert.Equal(t, corev1.ProtocolTCP, containerPorts[JmxHttp].Protocol) +} + +func TestJMXContainerInsightsGetContainerPorts(t *testing.T) { + cfg := getJSONStringFromFile("./test-resources/jmxContainerInsightsConfig.json") + containerPorts := getContainerPorts(logger, cfg, []corev1.ServicePort{}) + assert.Equal(t, 1, len(containerPorts)) + assert.Equal(t, int32(4314), containerPorts[JmxHttp].ContainerPort) + assert.Equal(t, JmxHttp, containerPorts[JmxHttp].Name) + assert.Equal(t, corev1.ProtocolTCP, containerPorts[JmxHttp].Protocol) +} + func getJSONStringFromFile(path string) string { buf, err := os.ReadFile(path) if err != nil { diff --git a/internal/manifests/collector/test-resources/jmxAgentConfig.json b/internal/manifests/collector/test-resources/jmxAgentConfig.json new file mode 100644 index 000000000..70756dc00 --- /dev/null +++ b/internal/manifests/collector/test-resources/jmxAgentConfig.json @@ -0,0 +1,8 @@ +{ + "metrics": { + "metrics_collected": { + "jmx": { + } + } + } +} \ No newline at end of file diff --git a/internal/manifests/collector/test-resources/jmxContainerInsightsConfig.json b/internal/manifests/collector/test-resources/jmxContainerInsightsConfig.json new file mode 100644 index 000000000..9ee4ef445 --- /dev/null +++ b/internal/manifests/collector/test-resources/jmxContainerInsightsConfig.json @@ -0,0 +1,10 @@ +{ + "logs": { + "metrics_collected": { + "kubernetes": { + "cluster_name": "TestCluster", + "jmx_container_insights": true + } + } + } +} \ No newline at end of file diff --git a/pkg/instrumentation/defaultinstrumentation.go b/pkg/instrumentation/defaultinstrumentation.go index a16857280..b5421389b 100644 --- a/pkg/instrumentation/defaultinstrumentation.go +++ b/pkg/instrumentation/defaultinstrumentation.go @@ -8,13 +8,13 @@ import ( "fmt" "os" - "k8s.io/apimachinery/pkg/api/resource" - corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/aws/amazon-cloudwatch-agent-operator/apis/v1alpha1" "github.com/aws/amazon-cloudwatch-agent-operator/internal/manifests/collector/adapters" + "github.com/aws/amazon-cloudwatch-agent-operator/pkg/instrumentation/jmx" ) const ( @@ -50,7 +50,7 @@ func getInstrumentationConfigForResource(langStr string, resourceStr string) cor return instrumentationConfigForResource } -func getDefaultInstrumentation(agentConfig *adapters.CwaConfig, isWindowsPod bool) (*v1alpha1.Instrumentation, error) { +func getDefaultInstrumentation(agentConfig *adapters.CwaConfig, additionalEnvs map[Type]map[string]string, isWindowsPod bool) (*v1alpha1.Instrumentation, error) { javaInstrumentationImage, ok := os.LookupEnv("AUTO_INSTRUMENTATION_JAVA") if !ok { return nil, errors.New("unable to determine java instrumentation image") @@ -77,9 +77,10 @@ func getDefaultInstrumentation(agentConfig *adapters.CwaConfig, isWindowsPod boo // set protocol by checking cloudwatch agent config for tls setting exporterPrefix := http - if agentConfig != nil { - appSignalsConfig := agentConfig.GetApplicationSignalsConfig() - if appSignalsConfig != nil && appSignalsConfig.TLS != nil { + isApplicationSignalsEnabled := agentConfig != nil && agentConfig.GetApplicationSignalsConfig() != nil + + if isApplicationSignalsEnabled { + if agentConfig.GetApplicationSignalsConfig().TLS != nil { exporterPrefix = https } } @@ -103,18 +104,7 @@ func getDefaultInstrumentation(agentConfig *adapters.CwaConfig, isWindowsPod boo }, Java: v1alpha1.Java{ Image: javaInstrumentationImage, - Env: []corev1.EnvVar{ - {Name: "OTEL_AWS_APP_SIGNALS_ENABLED", Value: "true"}, //TODO: remove in favor of new name once safe - {Name: "OTEL_AWS_APPLICATION_SIGNALS_ENABLED", Value: "true"}, - {Name: "OTEL_TRACES_SAMPLER_ARG", Value: fmt.Sprintf("endpoint=%s://%s:2000", http, cloudwatchAgentServiceEndpoint)}, - {Name: "OTEL_TRACES_SAMPLER", Value: "xray"}, - {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, - {Name: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/traces", exporterPrefix, cloudwatchAgentServiceEndpoint)}, - {Name: "OTEL_AWS_APP_SIGNALS_EXPORTER_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/metrics", exporterPrefix, cloudwatchAgentServiceEndpoint)}, //TODO: remove in favor of new name once safe - {Name: "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/metrics", exporterPrefix, cloudwatchAgentServiceEndpoint)}, - {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, - {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, - }, + Env: getJavaEnvs(isApplicationSignalsEnabled, cloudwatchAgentServiceEndpoint, exporterPrefix, additionalEnvs[TypeJava]), Resources: corev1.ResourceRequirements{ Limits: getInstrumentationConfigForResource(java, limit), Requests: getInstrumentationConfigForResource(java, request), @@ -122,20 +112,7 @@ func getDefaultInstrumentation(agentConfig *adapters.CwaConfig, isWindowsPod boo }, Python: v1alpha1.Python{ Image: pythonInstrumentationImage, - Env: []corev1.EnvVar{ - {Name: "OTEL_AWS_APP_SIGNALS_ENABLED", Value: "true"}, //TODO: remove in favor of new name once safe - {Name: "OTEL_AWS_APPLICATION_SIGNALS_ENABLED", Value: "true"}, - {Name: "OTEL_TRACES_SAMPLER_ARG", Value: fmt.Sprintf("endpoint=%s://%s:2000", http, cloudwatchAgentServiceEndpoint)}, - {Name: "OTEL_TRACES_SAMPLER", Value: "xray"}, - {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, - {Name: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/traces", exporterPrefix, cloudwatchAgentServiceEndpoint)}, - {Name: "OTEL_AWS_APP_SIGNALS_EXPORTER_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/metrics", exporterPrefix, cloudwatchAgentServiceEndpoint)}, //TODO: remove in favor of new name once safe - {Name: "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/metrics", exporterPrefix, cloudwatchAgentServiceEndpoint)}, - {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, - {Name: "OTEL_PYTHON_DISTRO", Value: "aws_distro"}, - {Name: "OTEL_PYTHON_CONFIGURATOR", Value: "aws_configurator"}, - {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, - }, + Env: getPythonEnvs(isApplicationSignalsEnabled, cloudwatchAgentServiceEndpoint, exporterPrefix, additionalEnvs[TypePython]), Resources: corev1.ResourceRequirements{ Limits: getInstrumentationConfigForResource(python, limit), Requests: getInstrumentationConfigForResource(python, request), @@ -143,20 +120,7 @@ func getDefaultInstrumentation(agentConfig *adapters.CwaConfig, isWindowsPod boo }, DotNet: v1alpha1.DotNet{ Image: dotNetInstrumentationImage, - Env: []corev1.EnvVar{ - {Name: "OTEL_AWS_APPLICATION_SIGNALS_ENABLED", Value: "true"}, - {Name: "OTEL_TRACES_SAMPLER_ARG", Value: fmt.Sprintf("endpoint=%s://%s:2000", http, cloudwatchAgentServiceEndpoint)}, - {Name: "OTEL_TRACES_SAMPLER", Value: "xray"}, - {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, - {Name: "OTEL_EXPORTER_OTLP_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316", exporterPrefix, cloudwatchAgentServiceEndpoint)}, - {Name: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/traces", exporterPrefix, cloudwatchAgentServiceEndpoint)}, - {Name: "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/metrics", exporterPrefix, cloudwatchAgentServiceEndpoint)}, - {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, - {Name: "OTEL_DOTNET_DISTRO", Value: "aws_distro"}, - {Name: "OTEL_DOTNET_CONFIGURATOR", Value: "aws_configurator"}, - {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, - {Name: "OTEL_DOTNET_AUTO_PLUGINS", Value: "AWS.Distro.OpenTelemetry.AutoInstrumentation.Plugin, AWS.Distro.OpenTelemetry.AutoInstrumentation"}, - }, + Env: getDotNetEnvs(isApplicationSignalsEnabled, cloudwatchAgentServiceEndpoint, exporterPrefix, additionalEnvs[TypeDotNet]), Resources: corev1.ResourceRequirements{ Limits: getInstrumentationConfigForResource(dotNet, limit), Requests: getInstrumentationConfigForResource(dotNet, request), @@ -164,16 +128,7 @@ func getDefaultInstrumentation(agentConfig *adapters.CwaConfig, isWindowsPod boo }, NodeJS: v1alpha1.NodeJS{ Image: nodeJSInstrumentationImage, - Env: []corev1.EnvVar{ - {Name: "OTEL_AWS_APPLICATION_SIGNALS_ENABLED", Value: "true"}, - {Name: "OTEL_TRACES_SAMPLER_ARG", Value: fmt.Sprintf("endpoint=%s://%s:2000", exporterPrefix, cloudwatchAgentServiceEndpoint)}, - {Name: "OTEL_TRACES_SAMPLER", Value: "xray"}, - {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, - {Name: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/traces", exporterPrefix, cloudwatchAgentServiceEndpoint)}, - {Name: "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/metrics", exporterPrefix, cloudwatchAgentServiceEndpoint)}, - {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, - {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, - }, + Env: getNodeJSEnvs(isApplicationSignalsEnabled, cloudwatchAgentServiceEndpoint, exporterPrefix, additionalEnvs[TypeDotNet]), Resources: corev1.ResourceRequirements{ Limits: getInstrumentationConfigForResource(nodeJS, limit), Requests: getInstrumentationConfigForResource(nodeJS, request), @@ -182,3 +137,99 @@ func getDefaultInstrumentation(agentConfig *adapters.CwaConfig, isWindowsPod boo }, }, nil } + +func getJavaEnvs(isAppSignalsEnabled bool, cloudwatchAgentServiceEndpoint, exporterPrefix string, additionalEnvs map[string]string) []corev1.EnvVar { + envs := []corev1.EnvVar{ + {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, + {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, + {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, + } + + if isAppSignalsEnabled { + appSignalsEnvs := []corev1.EnvVar{ + {Name: "OTEL_AWS_APP_SIGNALS_ENABLED", Value: "true"}, //TODO: remove in favor of new name once safe + {Name: "OTEL_AWS_APPLICATION_SIGNALS_ENABLED", Value: "true"}, + {Name: "OTEL_TRACES_SAMPLER_ARG", Value: fmt.Sprintf("endpoint=%s://%s:2000", http, cloudwatchAgentServiceEndpoint)}, + {Name: "OTEL_TRACES_SAMPLER", Value: "xray"}, + {Name: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/traces", exporterPrefix, cloudwatchAgentServiceEndpoint)}, + {Name: "OTEL_AWS_APP_SIGNALS_EXPORTER_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/metrics", exporterPrefix, cloudwatchAgentServiceEndpoint)}, //TODO: remove in favor of new name once safe + {Name: "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/metrics", exporterPrefix, cloudwatchAgentServiceEndpoint)}, + } + envs = append(envs, appSignalsEnvs...) + } else { + envs = append(envs, corev1.EnvVar{ + Name: "OTEL_TRACES_EXPORTER", Value: "none", + }) + } + + var jmxEnvs []corev1.EnvVar + if targetSystems, ok := additionalEnvs[jmx.EnvTargetSystem]; ok { + jmxEnvs = []corev1.EnvVar{ + {Name: "OTEL_AWS_JMX_EXPORTER_METRICS_ENDPOINT", Value: fmt.Sprintf("%s://%s:4314/v1/metrics", http, cloudwatchAgentServiceEndpoint)}, + {Name: "OTEL_JMX_TARGET_SYSTEM", Value: targetSystems}, + } + } + if len(jmxEnvs) != 0 { + envs = append(envs, jmxEnvs...) + } + return envs +} + +func getPythonEnvs(isAppSignalsEnabled bool, cloudwatchAgentServiceEndpoint, exporterPrefix string, additionalEnvs map[string]string) []corev1.EnvVar { + var envs []corev1.EnvVar + if isAppSignalsEnabled { + envs = []corev1.EnvVar{ + {Name: "OTEL_AWS_APP_SIGNALS_ENABLED", Value: "true"}, //TODO: remove in favor of new name once safe + {Name: "OTEL_AWS_APPLICATION_SIGNALS_ENABLED", Value: "true"}, + {Name: "OTEL_TRACES_SAMPLER_ARG", Value: fmt.Sprintf("endpoint=%s://%s:2000", http, cloudwatchAgentServiceEndpoint)}, + {Name: "OTEL_TRACES_SAMPLER", Value: "xray"}, + {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, + {Name: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/traces", exporterPrefix, cloudwatchAgentServiceEndpoint)}, + {Name: "OTEL_AWS_APP_SIGNALS_EXPORTER_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/metrics", exporterPrefix, cloudwatchAgentServiceEndpoint)}, //TODO: remove in favor of new name once safe + {Name: "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/metrics", exporterPrefix, cloudwatchAgentServiceEndpoint)}, + {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, + {Name: "OTEL_PYTHON_DISTRO", Value: "aws_distro"}, + {Name: "OTEL_PYTHON_CONFIGURATOR", Value: "aws_configurator"}, + {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, + } + } + return envs +} + +func getDotNetEnvs(isAppSignalsEnabled bool, cloudwatchAgentServiceEndpoint, exporterPrefix string, additionalEnvs map[string]string) []corev1.EnvVar { + var envs []corev1.EnvVar + if isAppSignalsEnabled { + envs = []corev1.EnvVar{ + {Name: "OTEL_AWS_APPLICATION_SIGNALS_ENABLED", Value: "true"}, + {Name: "OTEL_TRACES_SAMPLER_ARG", Value: fmt.Sprintf("endpoint=%s://%s:2000", http, cloudwatchAgentServiceEndpoint)}, + {Name: "OTEL_TRACES_SAMPLER", Value: "xray"}, + {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, + {Name: "OTEL_EXPORTER_OTLP_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316", exporterPrefix, cloudwatchAgentServiceEndpoint)}, + {Name: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/traces", exporterPrefix, cloudwatchAgentServiceEndpoint)}, + {Name: "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/metrics", exporterPrefix, cloudwatchAgentServiceEndpoint)}, + {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, + {Name: "OTEL_DOTNET_DISTRO", Value: "aws_distro"}, + {Name: "OTEL_DOTNET_CONFIGURATOR", Value: "aws_configurator"}, + {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, + {Name: "OTEL_DOTNET_AUTO_PLUGINS", Value: "AWS.Distro.OpenTelemetry.AutoInstrumentation.Plugin, AWS.Distro.OpenTelemetry.AutoInstrumentation"}, + } + } + return envs +} + +func getNodeJSEnvs(isAppSignalsEnabled bool, cloudwatchAgentServiceEndpoint, exporterPrefix string, additionalEnvs map[string]string) []corev1.EnvVar { + var envs []corev1.EnvVar + if isAppSignalsEnabled { + envs = []corev1.EnvVar{ + {Name: "OTEL_AWS_APPLICATION_SIGNALS_ENABLED", Value: "true"}, + {Name: "OTEL_TRACES_SAMPLER_ARG", Value: fmt.Sprintf("endpoint=%s://%s:2000", exporterPrefix, cloudwatchAgentServiceEndpoint)}, + {Name: "OTEL_TRACES_SAMPLER", Value: "xray"}, + {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, + {Name: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/traces", exporterPrefix, cloudwatchAgentServiceEndpoint)}, + {Name: "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT", Value: fmt.Sprintf("%s://%s:4316/v1/metrics", exporterPrefix, cloudwatchAgentServiceEndpoint)}, + {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, + {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, + } + } + return envs +} diff --git a/pkg/instrumentation/defaultinstrumentation_test.go b/pkg/instrumentation/defaultinstrumentation_test.go index bb1d920ff..2b31e716e 100644 --- a/pkg/instrumentation/defaultinstrumentation_test.go +++ b/pkg/instrumentation/defaultinstrumentation_test.go @@ -8,13 +8,13 @@ import ( "reflect" "testing" - "k8s.io/apimachinery/pkg/api/resource" - corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/aws/amazon-cloudwatch-agent-operator/apis/v1alpha1" "github.com/aws/amazon-cloudwatch-agent-operator/internal/manifests/collector/adapters" + "github.com/aws/amazon-cloudwatch-agent-operator/pkg/instrumentation/jmx" ) func Test_getDefaultInstrumentationLinux(t *testing.T) { @@ -59,16 +59,16 @@ func Test_getDefaultInstrumentationLinux(t *testing.T) { Java: v1alpha1.Java{ Image: defaultJavaInstrumentationImage, Env: []corev1.EnvVar{ + {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, + {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, + {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, {Name: "OTEL_AWS_APP_SIGNALS_ENABLED", Value: "true"}, //TODO: remove in favor of new name once safe {Name: "OTEL_AWS_APPLICATION_SIGNALS_ENABLED", Value: "true"}, {Name: "OTEL_TRACES_SAMPLER_ARG", Value: "endpoint=http://cloudwatch-agent.amazon-cloudwatch:2000"}, {Name: "OTEL_TRACES_SAMPLER", Value: "xray"}, - {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, {Name: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", Value: "http://cloudwatch-agent.amazon-cloudwatch:4316/v1/traces"}, {Name: "OTEL_AWS_APP_SIGNALS_EXPORTER_ENDPOINT", Value: "http://cloudwatch-agent.amazon-cloudwatch:4316/v1/metrics"}, //TODO: remove in favor of new name once safe {Name: "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT", Value: "http://cloudwatch-agent.amazon-cloudwatch:4316/v1/metrics"}, - {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, - {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, }, Resources: corev1.ResourceRequirements{ Limits: corev1.ResourceList{ @@ -180,16 +180,16 @@ func Test_getDefaultInstrumentationLinux(t *testing.T) { Java: v1alpha1.Java{ Image: defaultJavaInstrumentationImage, Env: []corev1.EnvVar{ + {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, + {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, + {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, {Name: "OTEL_AWS_APP_SIGNALS_ENABLED", Value: "true"}, //TODO: remove in favor of new name once safe {Name: "OTEL_AWS_APPLICATION_SIGNALS_ENABLED", Value: "true"}, {Name: "OTEL_TRACES_SAMPLER_ARG", Value: "endpoint=http://cloudwatch-agent.amazon-cloudwatch:2000"}, {Name: "OTEL_TRACES_SAMPLER", Value: "xray"}, - {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, {Name: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", Value: "https://cloudwatch-agent.amazon-cloudwatch:4316/v1/traces"}, {Name: "OTEL_AWS_APP_SIGNALS_EXPORTER_ENDPOINT", Value: "https://cloudwatch-agent.amazon-cloudwatch:4316/v1/metrics"}, //TODO: remove in favor of new name once safe {Name: "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT", Value: "https://cloudwatch-agent.amazon-cloudwatch:4316/v1/metrics"}, - {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, - {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, }, Resources: corev1.ResourceRequirements{ Limits: corev1.ResourceList{ @@ -327,7 +327,7 @@ func Test_getDefaultInstrumentationLinux(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := getDefaultInstrumentation(tt.args.agentConfig, false) + got, err := getDefaultInstrumentation(tt.args.agentConfig, nil, false) if (err != nil) != tt.wantErr { t.Errorf("getDefaultInstrumentation() error = %v, wantErr %v", err, tt.wantErr) return @@ -382,16 +382,16 @@ func Test_getDefaultInstrumentationWindows(t *testing.T) { Java: v1alpha1.Java{ Image: defaultJavaInstrumentationImage, Env: []corev1.EnvVar{ + {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, + {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, + {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, {Name: "OTEL_AWS_APP_SIGNALS_ENABLED", Value: "true"}, //TODO: remove in favor of new name once safe {Name: "OTEL_AWS_APPLICATION_SIGNALS_ENABLED", Value: "true"}, {Name: "OTEL_TRACES_SAMPLER_ARG", Value: "endpoint=http://cloudwatch-agent-windows-headless.amazon-cloudwatch.svc.cluster.local:2000"}, {Name: "OTEL_TRACES_SAMPLER", Value: "xray"}, - {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, {Name: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", Value: "http://cloudwatch-agent-windows-headless.amazon-cloudwatch.svc.cluster.local:4316/v1/traces"}, {Name: "OTEL_AWS_APP_SIGNALS_EXPORTER_ENDPOINT", Value: "http://cloudwatch-agent-windows-headless.amazon-cloudwatch.svc.cluster.local:4316/v1/metrics"}, //TODO: remove in favor of new name once safe {Name: "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT", Value: "http://cloudwatch-agent-windows-headless.amazon-cloudwatch.svc.cluster.local:4316/v1/metrics"}, - {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, - {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, }, Resources: corev1.ResourceRequirements{ Limits: corev1.ResourceList{ @@ -503,16 +503,16 @@ func Test_getDefaultInstrumentationWindows(t *testing.T) { Java: v1alpha1.Java{ Image: defaultJavaInstrumentationImage, Env: []corev1.EnvVar{ + {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, + {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, + {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, {Name: "OTEL_AWS_APP_SIGNALS_ENABLED", Value: "true"}, //TODO: remove in favor of new name once safe {Name: "OTEL_AWS_APPLICATION_SIGNALS_ENABLED", Value: "true"}, {Name: "OTEL_TRACES_SAMPLER_ARG", Value: "endpoint=http://cloudwatch-agent-windows-headless.amazon-cloudwatch.svc.cluster.local:2000"}, {Name: "OTEL_TRACES_SAMPLER", Value: "xray"}, - {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, {Name: "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", Value: "https://cloudwatch-agent-windows-headless.amazon-cloudwatch.svc.cluster.local:4316/v1/traces"}, {Name: "OTEL_AWS_APP_SIGNALS_EXPORTER_ENDPOINT", Value: "https://cloudwatch-agent-windows-headless.amazon-cloudwatch.svc.cluster.local:4316/v1/metrics"}, //TODO: remove in favor of new name once safe {Name: "OTEL_AWS_APPLICATION_SIGNALS_EXPORTER_ENDPOINT", Value: "https://cloudwatch-agent-windows-headless.amazon-cloudwatch.svc.cluster.local:4316/v1/metrics"}, - {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, - {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, }, Resources: corev1.ResourceRequirements{ Limits: corev1.ResourceList{ @@ -650,7 +650,7 @@ func Test_getDefaultInstrumentationWindows(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := getDefaultInstrumentation(tt.args.agentConfig, true) + got, err := getDefaultInstrumentation(tt.args.agentConfig, nil, true) if (err != nil) != tt.wantErr { t.Errorf("getDefaultInstrumentation() error = %v, wantErr %v", err, tt.wantErr) return @@ -661,3 +661,244 @@ func Test_getDefaultInstrumentationWindows(t *testing.T) { }) } } + +func Test_getDefaultInstrumentationLinuxWithApplicationSignalsDisabled(t *testing.T) { + os.Setenv("AUTO_INSTRUMENTATION_JAVA", defaultJavaInstrumentationImage) + os.Setenv("AUTO_INSTRUMENTATION_PYTHON", defaultPythonInstrumentationImage) + os.Setenv("AUTO_INSTRUMENTATION_DOTNET", defaultDotNetInstrumentationImage) + os.Setenv("AUTO_INSTRUMENTATION_NODEJS", defaultNodeJSInstrumentationImage) + os.Setenv("AUTO_INSTRUMENTATION_JAVA_CPU_LIMIT", "500m") + os.Setenv("AUTO_INSTRUMENTATION_JAVA_MEM_LIMIT", "64Mi") + os.Setenv("AUTO_INSTRUMENTATION_JAVA_CPU_REQUEST", "50m") + os.Setenv("AUTO_INSTRUMENTATION_JAVA_MEM_REQUEST", "64Mi") + os.Setenv("AUTO_INSTRUMENTATION_PYTHON_CPU_LIMIT", "500m") + os.Setenv("AUTO_INSTRUMENTATION_PYTHON_MEM_LIMIT", "32Mi") + os.Setenv("AUTO_INSTRUMENTATION_PYTHON_CPU_REQUEST", "50m") + os.Setenv("AUTO_INSTRUMENTATION_PYTHON_MEM_REQUEST", "32Mi") + os.Setenv("AUTO_INSTRUMENTATION_DOTNET_CPU_LIMIT", "500m") + os.Setenv("AUTO_INSTRUMENTATION_DOTNET_MEM_LIMIT", "128Mi") + os.Setenv("AUTO_INSTRUMENTATION_DOTNET_CPU_REQUEST", "50m") + os.Setenv("AUTO_INSTRUMENTATION_DOTNET_MEM_REQUEST", "128Mi") + os.Setenv("AUTO_INSTRUMENTATION_NODEJS_CPU_LIMIT", "500m") + os.Setenv("AUTO_INSTRUMENTATION_NODEJS_MEM_LIMIT", "128Mi") + os.Setenv("AUTO_INSTRUMENTATION_NODEJS_CPU_REQUEST", "50m") + os.Setenv("AUTO_INSTRUMENTATION_NODEJS_MEM_REQUEST", "128Mi") + + httpInst := &v1alpha1.Instrumentation{ + Status: v1alpha1.InstrumentationStatus{}, + TypeMeta: metav1.TypeMeta{ + APIVersion: defaultAPIVersion, + Kind: defaultKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: defaultInstrumentation, + Namespace: defaultNamespace, + }, + Spec: v1alpha1.InstrumentationSpec{ + Propagators: []v1alpha1.Propagator{ + v1alpha1.TraceContext, + v1alpha1.Baggage, + v1alpha1.B3, + v1alpha1.XRay, + }, + Java: v1alpha1.Java{ + Image: defaultJavaInstrumentationImage, + Env: []corev1.EnvVar{ + {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, + {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, + {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, + {Name: "OTEL_TRACES_EXPORTER", Value: "none"}, + }, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("64Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("50m"), + corev1.ResourceMemory: resource.MustParse("64Mi"), + }, + }, + }, + Python: v1alpha1.Python{ + Image: defaultPythonInstrumentationImage, + Env: []corev1.EnvVar{}, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("32Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("50m"), + corev1.ResourceMemory: resource.MustParse("32Mi"), + }, + }, + }, + DotNet: v1alpha1.DotNet{ + Image: defaultDotNetInstrumentationImage, + Env: []corev1.EnvVar{}, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("50m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + }, + NodeJS: v1alpha1.NodeJS{ + Image: defaultNodeJSInstrumentationImage, + Env: []corev1.EnvVar{}, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("50m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + }, + }, + } + + jmxInst := &v1alpha1.Instrumentation{ + Status: v1alpha1.InstrumentationStatus{}, + TypeMeta: metav1.TypeMeta{ + APIVersion: defaultAPIVersion, + Kind: defaultKind, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: defaultInstrumentation, + Namespace: defaultNamespace, + }, + Spec: v1alpha1.InstrumentationSpec{ + Propagators: []v1alpha1.Propagator{ + v1alpha1.TraceContext, + v1alpha1.Baggage, + v1alpha1.B3, + v1alpha1.XRay, + }, + Java: v1alpha1.Java{ + Image: defaultJavaInstrumentationImage, + Env: []corev1.EnvVar{ + {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, + {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, + {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, + {Name: "OTEL_TRACES_EXPORTER", Value: "none"}, + {Name: "OTEL_AWS_JMX_EXPORTER_METRICS_ENDPOINT", Value: "http://cloudwatch-agent.amazon-cloudwatch:4314/v1/metrics"}, + {Name: "OTEL_JMX_TARGET_SYSTEM", Value: "jvm"}, + }, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("64Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("50m"), + corev1.ResourceMemory: resource.MustParse("64Mi"), + }, + }, + }, + Python: v1alpha1.Python{ + Image: defaultPythonInstrumentationImage, + Env: []corev1.EnvVar{}, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("32Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("50m"), + corev1.ResourceMemory: resource.MustParse("32Mi"), + }, + }, + }, + DotNet: v1alpha1.DotNet{ + Image: defaultDotNetInstrumentationImage, + Env: []corev1.EnvVar{}, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("50m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + }, + NodeJS: v1alpha1.NodeJS{ + Image: defaultNodeJSInstrumentationImage, + Env: []corev1.EnvVar{}, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("500m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("50m"), + corev1.ResourceMemory: resource.MustParse("128Mi"), + }, + }, + }, + }, + } + + type args struct { + agentConfig *adapters.CwaConfig + additionalEnvs map[Type]map[string]string + } + tests := []struct { + name string + args args + want *v1alpha1.Instrumentation + wantErr bool + }{ + { + name: "no-application-signals", + args: args{ + agentConfig: &adapters.CwaConfig{ + Logs: &adapters.Logs{ + LogMetricsCollected: &adapters.LogMetricsCollected{}, + }, + }, + }, + want: httpInst, + wantErr: false, + }, + { + name: "no-application-signals-with-additional-vars", + args: args{ + agentConfig: &adapters.CwaConfig{ + Logs: &adapters.Logs{ + LogMetricsCollected: &adapters.LogMetricsCollected{}, + }, + }, + additionalEnvs: map[Type]map[string]string{ + TypeJava: { + jmx.EnvTargetSystem: jmx.TargetJVM, + }, + }, + }, + want: jmxInst, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getDefaultInstrumentation(tt.args.agentConfig, tt.args.additionalEnvs, false) + if (err != nil) != tt.wantErr { + t.Errorf("getDefaultInstrumentation() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got.Spec.Java.Env, tt.want.Spec.Java.Env) { + t.Errorf("getDefaultInstrumentation() Java environment vars got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/instrumentation/jmx/jmx.go b/pkg/instrumentation/jmx/jmx.go new file mode 100644 index 000000000..eb4b2a46a --- /dev/null +++ b/pkg/instrumentation/jmx/jmx.go @@ -0,0 +1,24 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package jmx + +const ( + annotationPrefix = "cloudwatch.aws.amazon.com/inject-jmx-" +) + +const ( + EnvTargetSystem = "OTEL_JMX_TARGET_SYSTEM" + + TargetJVM = "jvm" + TargetTomcat = "tomcat" + TargetKafka = "kafka" + TargetKafkaConsumer = "kafka-consumer" + TargetKafkaProducer = "kafka-producer" +) + +var SupportedTargets = []string{TargetJVM, TargetTomcat, TargetKafka, TargetKafkaConsumer, TargetKafkaProducer} + +func AnnotationKey(target string) string { + return annotationPrefix + target +} diff --git a/pkg/instrumentation/podmutator.go b/pkg/instrumentation/podmutator.go index 2e893a2f2..ba67a55c1 100644 --- a/pkg/instrumentation/podmutator.go +++ b/pkg/instrumentation/podmutator.go @@ -19,6 +19,7 @@ import ( "github.com/aws/amazon-cloudwatch-agent-operator/internal/manifests/collector/adapters" "github.com/aws/amazon-cloudwatch-agent-operator/internal/webhook/podmutation" "github.com/aws/amazon-cloudwatch-agent-operator/pkg/featuregate" + "github.com/aws/amazon-cloudwatch-agent-operator/pkg/instrumentation/jmx" ) const ( @@ -368,8 +369,19 @@ func (pm *instPodMutator) getInstrumentationInstance(ctx context.Context, ns cor return nil, nil } + var additionalEnvs map[Type]map[string]string + if instAnnotation == annotationInjectJava { + additionalEnvs = map[Type]map[string]string{} + targetSystems := getJmxTargetSystems(ns, pod) + if len(targetSystems) != 0 { + additionalEnvs[TypeJava] = map[string]string{ + jmx.EnvTargetSystem: strings.Join(targetSystems, ","), + } + } + } + if strings.EqualFold(instValue, "true") { - return pm.selectInstrumentationInstanceFromNamespace(ctx, ns, isWindowsPod(pod)) + return pm.selectInstrumentationInstanceFromNamespace(ctx, ns, additionalEnvs, isWindowsPod(pod)) } var instNamespacedName types.NamespacedName @@ -388,7 +400,7 @@ func (pm *instPodMutator) getInstrumentationInstance(ctx context.Context, ns cor return otelInst, nil } -func (pm *instPodMutator) selectInstrumentationInstanceFromNamespace(ctx context.Context, ns corev1.Namespace, isWindowsPod bool) (*v1alpha1.Instrumentation, error) { +func (pm *instPodMutator) selectInstrumentationInstanceFromNamespace(ctx context.Context, ns corev1.Namespace, additionalEnvs map[Type]map[string]string, isWindowsPod bool) (*v1alpha1.Instrumentation, error) { var otelInsts v1alpha1.InstrumentationList if err := pm.Client.List(ctx, &otelInsts, client.InNamespace(ns.Name)); err != nil { return nil, err @@ -403,7 +415,7 @@ func (pm *instPodMutator) selectInstrumentationInstanceFromNamespace(ctx context pm.Logger.Error(err, "unable to retrieve cloudwatch agent config for instrumentation") } - return getDefaultInstrumentation(config, isWindowsPod) + return getDefaultInstrumentation(config, additionalEnvs, isWindowsPod) case s > 1: return nil, errMultipleInstancesPossible default: @@ -425,3 +437,14 @@ func GetAmazonCloudWatchAgentResource(ctx context.Context, c client.Client, name func isWindowsPod(pod corev1.Pod) bool { return pod.Spec.NodeSelector["kubernetes.io/os"] == "windows" } + +func getJmxTargetSystems(ns corev1.Namespace, pod corev1.Pod) []string { + var targetSystems []string + for _, target := range jmx.SupportedTargets { + value := annotationValue(ns.ObjectMeta, pod.ObjectMeta, jmx.AnnotationKey(target)) + if strings.EqualFold(value, "true") { + targetSystems = append(targetSystems, target) + } + } + return targetSystems +} diff --git a/pkg/instrumentation/podmutator_test.go b/pkg/instrumentation/podmutator_test.go index cbf66bc2f..e7ad2f061 100644 --- a/pkg/instrumentation/podmutator_test.go +++ b/pkg/instrumentation/podmutator_test.go @@ -21,6 +21,7 @@ import ( "github.com/aws/amazon-cloudwatch-agent-operator/apis/v1alpha1" "github.com/aws/amazon-cloudwatch-agent-operator/internal/manifests/collector/adapters" "github.com/aws/amazon-cloudwatch-agent-operator/pkg/featuregate" + "github.com/aws/amazon-cloudwatch-agent-operator/pkg/instrumentation/jmx" ) const ( @@ -31,7 +32,7 @@ const ( ) func TestGetInstrumentationInstanceFromNameSpaceDefault(t *testing.T) { - defaultInst, _ := getDefaultInstrumentation(&adapters.CwaConfig{}, false) + defaultInst, _ := getDefaultInstrumentation(&adapters.CwaConfig{}, nil, false) namespace := corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: "default-namespace", @@ -45,12 +46,64 @@ func TestGetInstrumentationInstanceFromNameSpaceDefault(t *testing.T) { Client: fake.NewClientBuilder().Build(), Logger: logr.Logger{}, } - instrumentation, err := podMutator.selectInstrumentationInstanceFromNamespace(context.Background(), namespace, false) + instrumentation, err := podMutator.selectInstrumentationInstanceFromNamespace(context.Background(), namespace, nil, false) assert.Nil(t, err) assert.Equal(t, defaultInst, instrumentation) } +func TestGetInstrumentationInstanceJMX(t *testing.T) { + if err := v1alpha1.AddToScheme(testScheme); err != nil { + fmt.Printf("failed to register scheme: %v", err) + os.Exit(1) + } + mutator := instPodMutator{ + Client: fake.NewClientBuilder().Build(), + Logger: logr.Discard(), + } + + tests := []struct { + name string + pod corev1.Pod + ns corev1.Namespace + wantLen int + wantEnv []corev1.EnvVar + }{ + { + name: "enable jvm/tomcat", + pod: corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + annotationInjectJava: "true", + jmx.AnnotationKey(jmx.TargetJVM): "true", + jmx.AnnotationKey(jmx.TargetTomcat): "true", + }, + }, + }, + ns: corev1.Namespace{}, + wantLen: 6, + wantEnv: []corev1.EnvVar{ + {Name: "OTEL_EXPORTER_OTLP_PROTOCOL", Value: "http/protobuf"}, + {Name: "OTEL_METRICS_EXPORTER", Value: "none"}, + {Name: "OTEL_LOGS_EXPORTER", Value: "none"}, + {Name: "OTEL_TRACES_EXPORTER", Value: "none"}, + {Name: "OTEL_AWS_JMX_EXPORTER_METRICS_ENDPOINT", Value: "http://cloudwatch-agent.amazon-cloudwatch:4314/v1/metrics"}, + {Name: "OTEL_JMX_TARGET_SYSTEM", Value: "jvm,tomcat"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + inst, err := mutator.getInstrumentationInstance(context.Background(), tt.ns, tt.pod, annotationInjectJava) + assert.NoError(t, err) + assert.Len(t, inst.Spec.Java.Env, tt.wantLen) + for _, env := range tt.wantEnv { + assert.Containsf(t, inst.Spec.Java.Env, env, "java env does not contain %s:%s", env.Name, env.Value) + } + }) + } +} + func TestMutatePod(t *testing.T) { mutator := NewMutator(logr.Discard(), k8sClient, record.NewFakeRecorder(100)) require.NotNil(t, mutator)