Skip to content

Commit f331993

Browse files
authored
Port Windows log collection validation to E2E (#949)
# Description Port manual test to E2E framework. CX-45093 Signed-off-by: Israel Blancas <iblancasa@gmail.com>
1 parent 213ec21 commit f331993

4 files changed

Lines changed: 226 additions & 1 deletion

File tree

otel-integration/k8s-helm/e2e-test/run-all.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ load_test_configs() {
7878
# test|platform|wait label|env vars|values files
7979
TestE2E_Agent|linux|component=agent-collector||./values.yaml ./e2e-test/testdata/values-e2e-test.yaml
8080
TestE2E_Agent|windows|app.kubernetes.io/name=opentelemetry-agent-windows||./values.yaml ./values-windows.yaml ./e2e-test/testdata/values-e2e-windows-test.yaml
81+
TestE2E_WindowsLogCollection|windows|app.kubernetes.io/name=opentelemetry-agent-windows||./values.yaml ./values-windows.yaml ./e2e-test/testdata/values-e2e-windows-test.yaml
8182
TestE2E_ClusterCollector_Metrics|linux|component=agent-collector||./values.yaml ./e2e-test/testdata/values-e2e-test.yaml ./e2e-test/testdata/values-e2e-cluster-collector.yaml
8283
TestE2E_TailSampling_Simple|linux|app.kubernetes.io/instance=otel-integration-agent-e2e|RUN_TAIL_SAMPLING_E2E=1|./values.yaml ./tail-sampling-values.yaml ./e2e-test/testdata/values-e2e-tail-sampling.yaml
8384
TestE2E_TargetAllocator_ServiceMonitorMetrics|linux|component=agent-collector|RUN_TARGET_ALLOCATOR_E2E=1|./values.yaml ./e2e-test/testdata/values-e2e-target-allocator-servicemonitor.yaml

otel-integration/k8s-helm/e2e-test/testdata/values-e2e-windows-test.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
# Override values for Windows E2E: export Windows-agent telemetry to local sinks.
22
global:
3-
logLevel: "info"
3+
logLevel: "debug"
44
hostedEndpoint: "host.docker.internal"
55

66
opentelemetry-agent-windows:
7+
podSecurityContext:
8+
windowsOptions:
9+
runAsUserName: "NT AUTHORITY\\SYSTEM"
10+
securityContext:
11+
windowsOptions:
12+
runAsUserName: "NT AUTHORITY\\SYSTEM"
713
presets:
814
coralogixExporter:
915
enabled: false
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: windows-log-generator
5+
labels:
6+
app: windows-log-generator
7+
spec:
8+
replicas: 1
9+
selector:
10+
matchLabels:
11+
app: windows-log-generator
12+
template:
13+
metadata:
14+
labels:
15+
app: windows-log-generator
16+
spec:
17+
nodeSelector:
18+
kubernetes.io/os: windows
19+
containers:
20+
- name: log-writer
21+
image: mcr.microsoft.com/windows/servercore:ltsc2019
22+
imagePullPolicy: IfNotPresent
23+
command:
24+
- cmd
25+
- /S
26+
- /C
27+
- for /L %i in (1,1,1000000) do @echo WINDOWS-OTEL-LOG-CHECK iteration=%i source=stdout && timeout /t 5 >nul
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package e2e
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"os"
10+
"os/exec"
11+
"path/filepath"
12+
"strings"
13+
"testing"
14+
"time"
15+
16+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/xk8stest"
17+
"github.com/stretchr/testify/require"
18+
apierrors "k8s.io/apimachinery/pkg/api/errors"
19+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
21+
"k8s.io/apimachinery/pkg/runtime/schema"
22+
)
23+
24+
const windowsLogMarker = "WINDOWS-OTEL-LOG-CHECK"
25+
26+
func TestE2E_WindowsLogCollection(t *testing.T) {
27+
if !isWindowsE2EEnvironment() {
28+
t.Skip("windows harness checks require E2E_ENVIRONMENT=windows")
29+
}
30+
31+
k8sClient := newE2EK8sClient(t)
32+
requireWindowsDebugSettings(t, k8sClient)
33+
requireWindowsLogCollection(t, k8sClient)
34+
}
35+
36+
func newE2EK8sClient(t *testing.T) *xk8stest.K8sClient {
37+
t.Helper()
38+
39+
client, err := xk8stest.NewK8sClient(e2eKubeconfigPath())
40+
require.NoError(t, err)
41+
return client
42+
}
43+
44+
func e2eKubeconfigPath() string {
45+
if kubeConfigFromEnv := os.Getenv(kubeConfigEnvVar); kubeConfigFromEnv != "" {
46+
return kubeConfigFromEnv
47+
}
48+
return testKubeConfig
49+
}
50+
51+
func windowsAgentNamespace() string {
52+
if ns := os.Getenv("E2E_WINDOWS_AGENT_NAMESPACE"); ns != "" {
53+
return ns
54+
}
55+
return agentCollectorNamespace()
56+
}
57+
58+
func windowsAgentConfigMapName() string {
59+
if name := os.Getenv("E2E_WINDOWS_AGENT_CONFIGMAP"); name != "" {
60+
return name
61+
}
62+
return "coralogix-opentelemetry-windows-agent"
63+
}
64+
65+
func windowsAgentDaemonSetName() string {
66+
if name := os.Getenv("E2E_WINDOWS_AGENT_DAEMONSET"); name != "" {
67+
return name
68+
}
69+
return "coralogix-opentelemetry-windows-agent"
70+
}
71+
72+
func windowsLogGeneratorNamespace() string {
73+
if ns := os.Getenv("E2E_WINDOWS_LOG_GENERATOR_NAMESPACE"); ns != "" {
74+
return ns
75+
}
76+
return "default"
77+
}
78+
79+
func requireWindowsDebugSettings(t *testing.T, client *xk8stest.K8sClient) {
80+
t.Helper()
81+
82+
configMaps := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"}
83+
configMap, err := client.DynamicClient.Resource(configMaps).
84+
Namespace(windowsAgentNamespace()).
85+
Get(context.Background(), windowsAgentConfigMapName(), metav1.GetOptions{})
86+
require.NoError(t, err)
87+
88+
config := strings.Join(configMapData(configMap), "\n")
89+
require.Contains(t, config, "verbosity: detailed", "debug exporter must use detailed verbosity")
90+
require.Contains(t, config, "- debug", "debug exporter must be enabled in a pipeline")
91+
require.True(t,
92+
strings.Contains(config, "level: debug") || strings.Contains(config, "level: 'debug'") || strings.Contains(config, `level: "debug"`),
93+
"collector log level must be debug",
94+
)
95+
}
96+
97+
func configMapData(configMap *unstructured.Unstructured) []string {
98+
data, _, _ := unstructured.NestedStringMap(configMap.Object, "data")
99+
values := make([]string, 0, len(data))
100+
for _, value := range data {
101+
values = append(values, value)
102+
}
103+
return values
104+
}
105+
106+
func requireWindowsLogCollection(t *testing.T, client *xk8stest.K8sClient) {
107+
t.Helper()
108+
109+
deployment := createWindowsLogGenerator(t, client)
110+
t.Cleanup(func() {
111+
deleteWindowsLogGenerator(t, client, deployment)
112+
})
113+
114+
require.Eventually(t, func() bool {
115+
return kubectlLogsContain(t, "deployment/windows-log-generator", windowsLogGeneratorNamespace(), "--tail=60", windowsLogMarker)
116+
}, 2*time.Minute, 5*time.Second, "Windows log generator did not emit %q", windowsLogMarker)
117+
118+
require.Eventually(t, func() bool {
119+
return kubectlLogsContain(t, fmt.Sprintf("daemonset/%s", windowsAgentDaemonSetName()), windowsAgentNamespace(), "--since=20m", "--tail=4000", "--max-log-requests=20", windowsLogMarker)
120+
}, 5*time.Minute, 10*time.Second, "Windows collector debug exporter did not emit %q", windowsLogMarker)
121+
}
122+
123+
func createWindowsLogGenerator(t *testing.T, client *xk8stest.K8sClient) *unstructured.Unstructured {
124+
t.Helper()
125+
126+
deployments := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}
127+
namespace := windowsLogGeneratorNamespace()
128+
manifestPath := filepath.Join("testdata", "windows-log-generator.yaml")
129+
manifest, err := os.ReadFile(manifestPath)
130+
require.NoError(t, err)
131+
132+
obj := decodeManifestObject(t, manifest, manifestPath)
133+
obj.SetNamespace(namespace)
134+
135+
deleteWindowsLogGenerator(t, client, obj)
136+
137+
created, err := client.DynamicClient.Resource(deployments).Namespace(namespace).
138+
Create(context.Background(), obj, metav1.CreateOptions{})
139+
require.NoError(t, err)
140+
141+
require.Eventually(t, func() bool {
142+
current, err := client.DynamicClient.Resource(deployments).Namespace(namespace).
143+
Get(context.Background(), created.GetName(), metav1.GetOptions{})
144+
if err != nil {
145+
return false
146+
}
147+
readyReplicas, _, _ := unstructured.NestedInt64(current.Object, "status", "readyReplicas")
148+
availableReplicas, _, _ := unstructured.NestedInt64(current.Object, "status", "availableReplicas")
149+
return readyReplicas > 0 || availableReplicas > 0
150+
}, 15*time.Minute, 10*time.Second, "Windows log generator deployment did not become ready")
151+
152+
return created
153+
}
154+
155+
func deleteWindowsLogGenerator(t *testing.T, client *xk8stest.K8sClient, deployment *unstructured.Unstructured) {
156+
t.Helper()
157+
158+
if deployment == nil {
159+
return
160+
}
161+
deployments := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}
162+
deletePolicy := metav1.DeletePropagationForeground
163+
err := client.DynamicClient.Resource(deployments).Namespace(deployment.GetNamespace()).
164+
Delete(context.Background(), deployment.GetName(), metav1.DeleteOptions{PropagationPolicy: &deletePolicy})
165+
if err != nil && !apierrors.IsNotFound(err) {
166+
require.NoError(t, err)
167+
}
168+
}
169+
170+
func kubectlLogsContain(t *testing.T, resource string, namespace string, args ...string) bool {
171+
t.Helper()
172+
173+
commandArgs := []string{"--namespace", namespace, "logs", resource}
174+
commandArgs = append(commandArgs, args[:len(args)-1]...)
175+
needle := args[len(args)-1]
176+
177+
cmd := exec.Command("kubectl", commandArgs...)
178+
if kubeconfig := e2eKubeconfigPath(); kubeconfig != "" {
179+
cmd.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", kubeconfig))
180+
}
181+
output, err := cmd.CombinedOutput()
182+
if err != nil {
183+
t.Logf("kubectl logs %s failed: %v: %s", resource, err, string(output))
184+
return false
185+
}
186+
if strings.Contains(string(output), needle) {
187+
return true
188+
}
189+
t.Logf("kubectl logs %s missing %q", resource, needle)
190+
return false
191+
}

0 commit comments

Comments
 (0)