Skip to content

Commit 9596f7e

Browse files
authored
.NET Functional Tests (#1276)
1 parent 5612a7f commit 9596f7e

File tree

12 files changed

+1282
-5
lines changed

12 files changed

+1282
-5
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: dotnet-test
5+
spec:
6+
replicas: 1
7+
selector:
8+
matchLabels:
9+
app: dotnet-test
10+
template:
11+
metadata:
12+
name: dotnet-test
13+
labels:
14+
app: dotnet-test
15+
spec:
16+
containers:
17+
- image: quay.io/splunko11ytest/dotnet_test:latest
18+
name: dotnet-test
19+
imagePullPolicy: IfNotPresent

examples/enable-operator-and-auto-instrumentation/otel-demo-dotnet.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ kubectl create namespace dotnet-demo
1616
```
1717

1818
```bash
19-
curl https://raw.githubusercontent.com/signalfx/splunk-otel-collector-chart/main/functional_tests/testdata/dotnet/deployment.yaml | kubectl apply -n dotnet-demo -f -
19+
curl https://raw.githubusercontent.com/signalfx/splunk-otel-collector-chart/main/examples/enable-operator-and-auto-instrumentation/dotnet/deployment.yaml | kubectl apply -n dotnet-demo -f -
2020
```
2121

2222
### 2. Complete the steps outlined in [Getting started with auto-instrumentation](../../docs/auto-instrumentation-install.md#steps-for-setting-up-auto-instrumentation)

examples/enable-operator-and-auto-instrumentation/rendered_manifests/operator/instrumentation.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ spec:
3939
image: ghcr.io/signalfx/splunk-otel-dotnet/splunk-otel-dotnet:v1.5.0
4040
env:
4141
- name: OTEL_DOTNET_AUTO_PLUGINS
42-
value: "Splunk.OpenTelemetry.AutoInstrumentation.Plugin, Splunk.OpenTelemetry.AutoInstrumentation"
42+
value: "Splunk.OpenTelemetry.AutoInstrumentation.Plugin,Splunk.OpenTelemetry.AutoInstrumentation"
4343
- name: OTEL_RESOURCE_ATTRIBUTES
4444
value: splunk.zc.method=splunk-otel-dotnet:v1.5.0
4545
- name: SPLUNK_OTEL_AGENT

functional_tests/functional_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const (
5757
signalFxReceiverPort = 9443
5858
signalFxReceiverK8sClusterReceiverPort = 19443
5959
otlpReceiverPort = 4317
60+
otlpHTTPReceiverPort = 4318
6061
apiPort = 8881
6162
kindTestKubeEnv = "kind"
6263
eksTestKubeEnv = "eks"
@@ -209,6 +210,7 @@ func deployChartsAndApps(t *testing.T) {
209210
deployments := clientset.AppsV1().Deployments("default")
210211

211212
decode := scheme.Codecs.UniversalDeserializer().Decode
213+
// NodeJS test app
212214
stream, err := os.ReadFile(filepath.Join(testDir, "nodejs", "deployment.yaml"))
213215
require.NoError(t, err)
214216
deployment, _, err := decode(stream, nil, nil)
@@ -234,6 +236,19 @@ func deployChartsAndApps(t *testing.T) {
234236
require.NoError(t, err)
235237
}
236238
}
239+
// .NET test app
240+
stream, err = os.ReadFile(filepath.Join(testDir, "dotnet", "deployment.yaml"))
241+
require.NoError(t, err)
242+
deployment, _, err = decode(stream, nil, nil)
243+
require.NoError(t, err)
244+
_, err = deployments.Create(context.Background(), deployment.(*appsv1.Deployment), metav1.CreateOptions{})
245+
if err != nil {
246+
_, err2 := deployments.Update(context.Background(), deployment.(*appsv1.Deployment), metav1.UpdateOptions{})
247+
assert.NoError(t, err2)
248+
if err2 != nil {
249+
require.NoError(t, err)
250+
}
251+
}
237252
jobstream, err := os.ReadFile(filepath.Join(testDir, manifestsDir, "test_jobs.yaml"))
238253
require.NoError(t, err)
239254
var namespaces []*corev1.Namespace
@@ -315,6 +330,9 @@ func teardown(t *testing.T) {
315330
_ = deployments.Delete(context.Background(), "java-test", metav1.DeleteOptions{
316331
GracePeriodSeconds: &waitTime,
317332
})
333+
_ = deployments.Delete(context.Background(), "dotnet-test", metav1.DeleteOptions{
334+
GracePeriodSeconds: &waitTime,
335+
})
318336
jobstream, err := os.ReadFile(filepath.Join(testDir, manifestsDir, "test_jobs.yaml"))
319337
require.NoError(t, err)
320338
var namespaces []*corev1.Namespace
@@ -388,6 +406,7 @@ func Test_Functions(t *testing.T) {
388406

389407
t.Run("node.js traces captured", testNodeJSTraces)
390408
t.Run("java traces captured", testJavaTraces)
409+
t.Run(".NET traces captured", testDotNetTraces)
391410
t.Run("kubernetes cluster metrics", testK8sClusterReceiverMetrics)
392411
t.Run("agent logs", testAgentLogs)
393412
t.Run("test HEC metrics", testHECMetrics)
@@ -512,6 +531,70 @@ func testJavaTraces(t *testing.T) {
512531
require.NoError(t, err)
513532
}
514533

534+
func testDotNetTraces(t *testing.T) {
535+
tracesConsumer := setupOnce(t).tracesConsumer
536+
537+
var expectedTraces ptrace.Traces
538+
expectedTracesFile := filepath.Join(testDir, expectedValuesDir, "expected_dotnet_traces.yaml")
539+
expectedTraces, err := golden.ReadTraces(expectedTracesFile)
540+
require.NoError(t, err)
541+
542+
waitForTraces(t, 30, tracesConsumer)
543+
var selectedTrace *ptrace.Traces
544+
545+
require.Eventually(t, func() bool {
546+
for i := len(tracesConsumer.AllTraces()) - 1; i > 0; i-- {
547+
trace := tracesConsumer.AllTraces()[i]
548+
if val, ok := trace.ResourceSpans().At(0).Resource().Attributes().Get("telemetry.sdk.language"); ok && strings.Contains(val.Str(), "dotnet") {
549+
if expectedTraces.SpanCount() == trace.SpanCount() {
550+
selectedTrace = &trace
551+
break
552+
}
553+
selectedTrace = &trace
554+
break
555+
}
556+
}
557+
return selectedTrace != nil
558+
}, 3*time.Minute, 5*time.Second)
559+
560+
require.NotNil(t, selectedTrace)
561+
562+
maskScopeVersion(*selectedTrace)
563+
maskScopeVersion(expectedTraces)
564+
maskSpanParentID(*selectedTrace)
565+
maskSpanParentID(expectedTraces)
566+
567+
err = ptracetest.CompareTraces(expectedTraces, *selectedTrace,
568+
ptracetest.IgnoreResourceAttributeValue("os.description"),
569+
ptracetest.IgnoreResourceAttributeValue("process.pid"),
570+
ptracetest.IgnoreResourceAttributeValue("container.id"),
571+
ptracetest.IgnoreResourceAttributeValue("k8s.deployment.name"),
572+
ptracetest.IgnoreResourceAttributeValue("k8s.pod.ip"),
573+
ptracetest.IgnoreResourceAttributeValue("k8s.pod.name"),
574+
ptracetest.IgnoreResourceAttributeValue("k8s.pod.uid"),
575+
ptracetest.IgnoreResourceAttributeValue("k8s.replicaset.name"),
576+
ptracetest.IgnoreResourceAttributeValue("os.version"),
577+
ptracetest.IgnoreResourceAttributeValue("host.arch"),
578+
ptracetest.IgnoreResourceAttributeValue("telemetry.distro.version"),
579+
ptracetest.IgnoreResourceAttributeValue("telemetry.sdk.version"),
580+
ptracetest.IgnoreResourceAttributeValue("telemetry.auto.version"),
581+
ptracetest.IgnoreResourceAttributeValue("splunk.distro.version"),
582+
ptracetest.IgnoreResourceAttributeValue("splunk.zc.method"),
583+
ptracetest.IgnoreSpanAttributeValue("net.sock.peer.port"),
584+
ptracetest.IgnoreSpanAttributeValue("thread.id"),
585+
ptracetest.IgnoreSpanAttributeValue("thread.name"),
586+
ptracetest.IgnoreSpanAttributeValue("os.version"),
587+
ptracetest.IgnoreTraceID(),
588+
ptracetest.IgnoreSpanID(),
589+
ptracetest.IgnoreStartTimestamp(),
590+
ptracetest.IgnoreEndTimestamp(),
591+
ptracetest.IgnoreResourceSpansOrder(),
592+
ptracetest.IgnoreScopeSpansOrder(),
593+
)
594+
595+
require.NoError(t, err)
596+
}
597+
515598
func shortenNames(value string) string {
516599
if strings.HasPrefix(value, "kube-proxy") {
517600
return "kube-proxy"
@@ -1094,6 +1177,7 @@ func setupTraces(t *testing.T) *consumertest.TracesSink {
10941177
f := otlpreceiver.NewFactory()
10951178
cfg := f.CreateDefaultConfig().(*otlpreceiver.Config)
10961179
cfg.Protocols.GRPC.NetAddr.Endpoint = fmt.Sprintf("0.0.0.0:%d", otlpReceiverPort)
1180+
cfg.Protocols.HTTP.Endpoint = fmt.Sprintf("0.0.0.0:%d", otlpHTTPReceiverPort)
10971181

10981182
rcvr, err := f.CreateTracesReceiver(context.Background(), receivertest.NewNopCreateSettings(), cfg, tc)
10991183
require.NoError(t, err)
@@ -1262,3 +1346,17 @@ func maskScopeVersion(traces ptrace.Traces) {
12621346
}
12631347
}
12641348
}
1349+
1350+
func maskSpanParentID(traces ptrace.Traces) {
1351+
rss := traces.ResourceSpans()
1352+
for i := 0; i < rss.Len(); i++ {
1353+
rs := rss.At(i)
1354+
for j := 0; j < rs.ScopeSpans().Len(); j++ {
1355+
ss := rs.ScopeSpans().At(j)
1356+
for k := 0; k < ss.Spans().Len(); k++ {
1357+
span := ss.Spans().At(k)
1358+
span.SetParentSpanID(pcommon.NewSpanIDEmpty())
1359+
}
1360+
}
1361+
}
1362+
}

functional_tests/testdata/dotnet/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Default image repository
22
IMAGE_REPO ?= quay.io/splunko11ytest/dotnet_test
33

4-
# This .NET application cannot run on arm machines due to .NET runtime limitations
4+
# This .NET application cannot currently run on arm machines due to .NET runtime environment limitations
55
# Was not able to use docker buildx with .NET
66
.PHONY: push
77
push:

functional_tests/testdata/dotnet/README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,43 @@ Running this container inside a Kubernetes cluster under observation of the oper
1313
## Develop
1414

1515
Login to quay.io and push with `make push`
16+
Make sure for new image repositories you make the repository public
17+
- Arm based machines can have issues running docker/dockerx build commands that use QEMU virtualization and have .NET support, see: https://github.com/dotnet/dotnet-docker/issues/3832
18+
- On an arm M2 Mac running Docker Desktop 25.0.0, was able to get the docker/dockerx build for 1 arch images but not multi arch images.
19+
- New versions of the OS with Docker may fix this issue.
20+
- Dockerx related command: `docker buildx create --use --name multi-arch-builder`
21+
22+
### Debugging .NET
23+
24+
These env vars can be set to help debug .NET instrumentation
25+
26+
```yaml
27+
env:
28+
- name: OTEL_LOG_LEVEL
29+
value: DEBUG
30+
- name: OTEL_DOTNET_AUTO_TRACES_CONSOLE_EXPORTER_ENABLED
31+
value: "true"
32+
```
33+
34+
### Current issues and workarounds
35+
36+
#### Rule Engine Failure - OTL-2843
37+
38+
An env var may be needed to bypass an error thrown by auto-instrumentation.
39+
40+
Error:
41+
42+
```
43+
[Error] Error in StartupHook initialization: LoaderFolderLocation: /otel-auto-instrumentation-dotnet/net
44+
Exception: Rule Engine Failure: One or more rules failed validation. Automatic Instrumentation won't be loaded.
45+
System.Exception: Rule Engine Failure: One or more rules failed validation. Automatic Instrumentation won't be loaded.
46+
at StartupHook.Initialize() in /_/src/OpenTelemetry.AutoInstrumentation.StartupHook/StartupHook.cs:line 34
47+
```
48+
49+
Env var:
50+
51+
```yaml
52+
env:
53+
- name: OTEL_DOTNET_AUTO_RULE_ENGINE_ENABLED
54+
value: "false"
55+
```

functional_tests/testdata/dotnet/deployment.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ spec:
1414
app: dotnet-test
1515
annotations:
1616
instrumentation.opentelemetry.io/inject-dotnet: "true"
17-
instrumentation.opentelemetry.io/otel-dotnet-auto-runtime: "linux-x64"
17+
instrumentation.opentelemetry.io/otel-dotnet-auto-runtime: "linux-musl-x64"
1818
spec:
1919
containers:
2020
- image: quay.io/splunko11ytest/dotnet_test:latest

functional_tests/testdata/expected_eks_values/expected_cluster_receiver.yaml

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,30 @@ resourceMetrics:
100100
value:
101101
stringValue: k8scluster
102102
timeUnixNano: "1000000"
103+
- asInt: "2"
104+
attributes:
105+
- key: k8s.cluster.name
106+
value:
107+
stringValue: rotel-eks
108+
- key: k8s.namespace.name
109+
value:
110+
stringValue: default
111+
- key: k8s.node.name
112+
value:
113+
stringValue: kind-control-plane
114+
- key: k8s.pod.name
115+
value:
116+
stringValue: dotnet-test-57564b7dc9-pf2bk
117+
- key: k8s.pod.uid
118+
value:
119+
stringValue: b660efd2-e961-488a-af7a-2161f27565bb
120+
- key: metric_source
121+
value:
122+
stringValue: kubernetes
123+
- key: receiver
124+
value:
125+
stringValue: k8scluster
126+
timeUnixNano: "1000000"
103127
- asInt: "2"
104128
attributes:
105129
- key: k8s.cluster.name
@@ -1150,6 +1174,42 @@ resourceMetrics:
11501174
value:
11511175
stringValue: k8scluster
11521176
timeUnixNano: "1000000"
1177+
- asInt: "1"
1178+
attributes:
1179+
- key: container.id
1180+
value:
1181+
stringValue: 38809b5e5212eddddeec2d4d387e2f27ff754e0c3f67cc9a9ffb89db72601480
1182+
- key: container.image.name
1183+
value:
1184+
stringValue: quay.io/splunko11ytest/dotnet_test
1185+
- key: container.image.tag
1186+
value:
1187+
stringValue: latest
1188+
- key: k8s.cluster.name
1189+
value:
1190+
stringValue: rotel-eks
1191+
- key: k8s.container.name
1192+
value:
1193+
stringValue: dotnet-test
1194+
- key: k8s.namespace.name
1195+
value:
1196+
stringValue: default
1197+
- key: k8s.node.name
1198+
value:
1199+
stringValue: kind-control-plane
1200+
- key: k8s.pod.name
1201+
value:
1202+
stringValue: dotnet-test-57564b7dc9-pf2bk
1203+
- key: k8s.pod.uid
1204+
value:
1205+
stringValue: b660efd2-e961-488a-af7a-2161f27565bb
1206+
- key: metric_source
1207+
value:
1208+
stringValue: kubernetes
1209+
- key: receiver
1210+
value:
1211+
stringValue: k8scluster
1212+
timeUnixNano: "1000000"
11531213
- asInt: "1"
11541214
attributes:
11551215
- key: container.id
@@ -3916,6 +3976,27 @@ resourceMetrics:
39163976
value:
39173977
stringValue: k8scluster
39183978
timeUnixNano: "1000000"
3979+
- asInt: "1"
3980+
attributes:
3981+
- key: k8s.cluster.name
3982+
value:
3983+
stringValue: rotel-eks
3984+
- key: k8s.namespace.name
3985+
value:
3986+
stringValue: default
3987+
- key: k8s.replicaset.name
3988+
value:
3989+
stringValue: dotnet-test-57564b7dc9
3990+
- key: k8s.replicaset.uid
3991+
value:
3992+
stringValue: fc4c748c-a646-4813-9abf-f0688a15d022
3993+
- key: metric_source
3994+
value:
3995+
stringValue: kubernetes
3996+
- key: receiver
3997+
value:
3998+
stringValue: k8scluster
3999+
timeUnixNano: "1000000"
39194000
- asInt: "1"
39204001
attributes:
39214002
- key: k8s.cluster.name
@@ -4087,6 +4168,27 @@ resourceMetrics:
40874168
value:
40884169
stringValue: k8scluster
40894170
timeUnixNano: "1000000"
4171+
- asInt: "1"
4172+
attributes:
4173+
- key: k8s.cluster.name
4174+
value:
4175+
stringValue: rotel-eks
4176+
- key: k8s.namespace.name
4177+
value:
4178+
stringValue: default
4179+
- key: k8s.replicaset.name
4180+
value:
4181+
stringValue: dotnet-test-57564b7dc9
4182+
- key: k8s.replicaset.uid
4183+
value:
4184+
stringValue: fc4c748c-a646-4813-9abf-f0688a15d022
4185+
- key: metric_source
4186+
value:
4187+
stringValue: kubernetes
4188+
- key: receiver
4189+
value:
4190+
stringValue: k8scluster
4191+
timeUnixNano: "1000000"
40904192
- asInt: "1"
40914193
attributes:
40924194
- key: k8s.cluster.name

0 commit comments

Comments
 (0)