Skip to content

Commit 9aac167

Browse files
committed
Split host traffic Dynamic Tests suites
1 parent 235cc7a commit 9aac167

4 files changed

Lines changed: 391 additions & 342 deletions

File tree

test/new-e2e/tests/netpath/dynamic-tests/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ load("//bazel/rules/go:dd_agent_go_test.bzl", "dd_agent_go_test")
33
dd_agent_go_test(
44
name = "dynamic-tests_test",
55
srcs = [
6+
"baseline_host_traffic_dynamic_path_test.go",
7+
"host_traffic_dynamic_path_common_test.go",
68
"host_traffic_dynamic_path_test.go",
79
"netflow_dynamic_path_test.go",
810
],
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2026-present Datadog, Inc.
5+
6+
package networkpathdynamictests
7+
8+
import (
9+
_ "embed"
10+
"testing"
11+
"time"
12+
13+
"github.com/stretchr/testify/assert"
14+
"github.com/stretchr/testify/require"
15+
16+
"github.com/DataDog/datadog-agent/pkg/networkpath/payload"
17+
"github.com/DataDog/datadog-agent/test/e2e-framework/testing/e2e"
18+
)
19+
20+
//go:embed config/baseline_host_traffic_dynamic_path.yaml
21+
var baselineHostTrafficDynamicPathAgentConfig string
22+
23+
//go:embed config/baseline_host_traffic_system_probe.yaml
24+
var baselineHostTrafficSystemProbeConfig string
25+
26+
type baselineHostTrafficDynamicPathSuite struct {
27+
hostTrafficDynamicPathBaseSuite
28+
}
29+
30+
// TestBaselineHostTrafficDynamicPathSuite verifies baseline tests from packaged Agent configuration through fakeintake.
31+
func TestBaselineHostTrafficDynamicPathSuite(t *testing.T) {
32+
e2e.Run(t, &baselineHostTrafficDynamicPathSuite{}, e2e.WithProvisioner(hostTrafficDynamicPathProvisioner("baselineHostTrafficDynamicPath", baselineHostTrafficDynamicPathAgentConfig, baselineHostTrafficSystemProbeConfig)))
33+
}
34+
35+
func (s *baselineHostTrafficDynamicPathSuite) SetupSuite() {
36+
s.BaseSuite.SetupSuite()
37+
defer s.CleanupOnSetupFailure()
38+
s.setupHostTraffic()
39+
require.NoError(s.T(), s.Env().FakeIntake.Client().FlushServerAndResetAggregators())
40+
}
41+
42+
func (s *baselineHostTrafficDynamicPathSuite) TearDownSuite() {
43+
s.tearDownHostTraffic()
44+
s.BaseSuite.TearDownSuite()
45+
}
46+
47+
func (s *baselineHostTrafficDynamicPathSuite) TestHostTrafficDynamicNetworkPath() {
48+
fakeintake := s.Env().FakeIntake.Client()
49+
s.startHostTrafficGenerator(4 * time.Minute)
50+
51+
s.EventuallyWithT(func(c *assert.CollectT) {
52+
assertMetricPresent(c, fakeintake, "datadog.network_path.collector.baseline.selections")
53+
assertMetricPresent(c, fakeintake, "datadog.network_path.store.baseline_dispatched")
54+
55+
netpaths, err := fakeintake.GetLatestNetpathEvents()
56+
require.NoError(c, err)
57+
require.NotEmpty(c, netpaths, "no network path events")
58+
59+
assertHostTrafficNetworkPath(c, netpaths, payload.DynamicTestProfileBaseline, "baseline")
60+
}, 5*time.Minute, 10*time.Second)
61+
}
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2026-present Datadog, Inc.
5+
6+
package networkpathdynamictests
7+
8+
import (
9+
"embed"
10+
"fmt"
11+
"strings"
12+
"time"
13+
14+
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
15+
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
17+
18+
"github.com/DataDog/datadog-agent/pkg/networkpath/payload"
19+
"github.com/DataDog/datadog-agent/test/e2e-framework/common/utils"
20+
"github.com/DataDog/datadog-agent/test/e2e-framework/components/command"
21+
"github.com/DataDog/datadog-agent/test/e2e-framework/components/datadog/agentparams"
22+
"github.com/DataDog/datadog-agent/test/e2e-framework/components/datadog/apps"
23+
"github.com/DataDog/datadog-agent/test/e2e-framework/components/docker"
24+
"github.com/DataDog/datadog-agent/test/e2e-framework/resources/aws"
25+
"github.com/DataDog/datadog-agent/test/e2e-framework/scenarios/aws/ec2"
26+
"github.com/DataDog/datadog-agent/test/e2e-framework/testing/components"
27+
"github.com/DataDog/datadog-agent/test/e2e-framework/testing/e2e"
28+
"github.com/DataDog/datadog-agent/test/e2e-framework/testing/environments"
29+
"github.com/DataDog/datadog-agent/test/e2e-framework/testing/provisioners"
30+
"github.com/DataDog/datadog-agent/test/fakeintake/aggregator"
31+
)
32+
33+
//go:embed fixtures/host_traffic_dns.py
34+
var hostTrafficDNSFiles embed.FS
35+
36+
const (
37+
hostTrafficRemoteConfigDomain = "httpbin-rc.dynamic-netpath.test"
38+
hostTrafficDNSRemotePath = "/tmp/host_traffic_dns.py"
39+
hostTrafficDNSLogPath = "/tmp/host_traffic_dns.log"
40+
hostTrafficDNSPIDPath = "/tmp/host_traffic_dns.pid"
41+
hostTrafficResolverBackupPath = "/tmp/host_traffic_resolv.conf.backup"
42+
hostTrafficResolverLinkPath = "/tmp/host_traffic_resolv.conf.link"
43+
hostTrafficCurlLogPath = "/tmp/host_traffic_dynamic_path_curl.log"
44+
hostTrafficCurlPIDPath = "/tmp/host_traffic_dynamic_path_curl.pid"
45+
hostTrafficHTTPBinComposeYAML = `version: '3.9'
46+
services:
47+
httpbin:
48+
pid: host
49+
privileged: true
50+
ports:
51+
- 80:8080/tcp
52+
image: ghcr.io/datadog/apps-go-httpbin:{APPS_VERSION}
53+
container_name: httpbin
54+
volumes: []
55+
environment: {}
56+
`
57+
)
58+
59+
type hostTrafficDynamicPathEnv struct {
60+
environments.Host
61+
HTTPBinHost *components.RemoteHost
62+
}
63+
64+
type hostTrafficDynamicPathBaseSuite struct {
65+
e2e.BaseSuite[hostTrafficDynamicPathEnv]
66+
}
67+
68+
func hostTrafficDynamicPathProvisioner(name, agentConfig, systemProbeConfig string) provisioners.Provisioner {
69+
return provisioners.NewTypedPulumiProvisioner[hostTrafficDynamicPathEnv](name, func(ctx *pulumi.Context, env *hostTrafficDynamicPathEnv) error {
70+
awsEnv, err := aws.NewEnvironment(ctx)
71+
if err != nil {
72+
return err
73+
}
74+
75+
params := ec2.GetParams(
76+
ec2.WithName("hosttrafficdynamicpathvm"),
77+
ec2.WithAgentOptions(
78+
agentparams.WithAgentConfig(agentConfig),
79+
agentparams.WithSystemProbeConfig(systemProbeConfig),
80+
),
81+
)
82+
if err := ec2.Run(ctx, awsEnv, env, params); err != nil {
83+
return err
84+
}
85+
86+
httpbinHost, err := ec2.NewVM(awsEnv, "hosttraffichttpbinvm")
87+
if err != nil {
88+
return err
89+
}
90+
if err := httpbinHost.Export(ctx, &env.HTTPBinHost.HostOutput); err != nil {
91+
return err
92+
}
93+
94+
// The Ubuntu e2e AMI installs apache2 (via the php meta-package) which binds to
95+
// port 80 by default. Stop and disable it so the httpbin container below can
96+
// claim the port during docker-compose up. `|| true` keeps this idempotent on
97+
// hosts where apache2 is absent.
98+
stopApache, err := httpbinHost.OS.Runner().Command(
99+
"stop-apache2",
100+
&command.Args{
101+
Create: pulumi.String("systemctl disable --now apache2 || true"),
102+
Sudo: true,
103+
},
104+
)
105+
if err != nil {
106+
return err
107+
}
108+
109+
dockerManager, err := docker.NewAWSManager(&awsEnv, httpbinHost, utils.PulumiDependsOn(stopApache))
110+
if err != nil {
111+
return err
112+
}
113+
114+
_, err = dockerManager.ComposeStrUp("httpbin", []docker.ComposeInlineManifest{hostTrafficHTTPBinCompose()}, pulumi.StringMap{})
115+
return err
116+
}, nil)
117+
}
118+
119+
func hostTrafficHTTPBinCompose() docker.ComposeInlineManifest {
120+
return docker.ComposeInlineManifest{
121+
Name: "httpbin",
122+
Content: pulumi.String(strings.ReplaceAll(hostTrafficHTTPBinComposeYAML, "{APPS_VERSION}", apps.Version)),
123+
}
124+
}
125+
126+
func (s *hostTrafficDynamicPathBaseSuite) setupHostTraffic() {
127+
s.ensureCurlInstalled()
128+
s.startHostTrafficDNSServer()
129+
s.configureAgentResolver()
130+
s.assertHostTrafficDomainResolves()
131+
}
132+
133+
func (s *hostTrafficDynamicPathBaseSuite) tearDownHostTraffic() {
134+
s.stopHostTrafficGenerator()
135+
s.restoreAgentResolver()
136+
s.stopHostTrafficDNSServer()
137+
}
138+
139+
func (s *hostTrafficDynamicPathBaseSuite) AfterTest(suiteName, testName string) {
140+
if s.T().Failed() {
141+
s.logRemoteFile(s.Env().HTTPBinHost, hostTrafficDNSLogPath)
142+
s.logRemoteFile(s.Env().RemoteHost, hostTrafficCurlLogPath)
143+
}
144+
s.BaseSuite.AfterTest(suiteName, testName)
145+
}
146+
147+
func (s *hostTrafficDynamicPathBaseSuite) ensureCurlInstalled() {
148+
s.Env().RemoteHost.MustExecute("if ! command -v curl >/dev/null 2>&1; then sudo apt-get update && sudo apt-get install -y curl; fi")
149+
}
150+
151+
func (s *hostTrafficDynamicPathBaseSuite) startHostTrafficDNSServer() {
152+
httpbinHost := s.Env().HTTPBinHost
153+
httpbinHost.CopyFileFromFS(hostTrafficDNSFiles, "fixtures/host_traffic_dns.py", hostTrafficDNSRemotePath)
154+
httpbinHost.MustExecute("sudo chmod 0755 " + shellQuote(hostTrafficDNSRemotePath))
155+
156+
upstream := strings.TrimSpace(httpbinHost.MustExecute("awk '/^nameserver / && $2 ~ /^[0-9.]+$/ {print $2; exit}' /etc/resolv.conf"))
157+
require.NotEmpty(s.T(), upstream, "could not find an IPv4 DNS upstream on the HTTPBin host")
158+
159+
startCommand := fmt.Sprintf(
160+
"nohup python3 %s %s %s %s %s >%s 2>&1 & echo $! >%s",
161+
shellQuote(hostTrafficDNSRemotePath),
162+
shellQuote(httpbinHost.Address),
163+
shellQuote(hostTrafficRemoteConfigDomain),
164+
shellQuote(httpbinHost.Address),
165+
shellQuote(upstream),
166+
shellQuote(hostTrafficDNSLogPath),
167+
shellQuote(hostTrafficDNSPIDPath),
168+
)
169+
httpbinHost.MustExecute(fmt.Sprintf(`if [ -f %s ]; then sudo kill "$(sudo cat %s)" || true; fi
170+
sudo rm -f %s %s
171+
sudo sh -c %s
172+
sleep 1
173+
sudo kill -0 "$(sudo cat %s)"
174+
`,
175+
shellQuote(hostTrafficDNSPIDPath),
176+
shellQuote(hostTrafficDNSPIDPath),
177+
shellQuote(hostTrafficDNSPIDPath),
178+
shellQuote(hostTrafficDNSLogPath),
179+
shellQuote(startCommand),
180+
shellQuote(hostTrafficDNSPIDPath),
181+
))
182+
}
183+
184+
func (s *hostTrafficDynamicPathBaseSuite) stopHostTrafficDNSServer() {
185+
if s.Env().HTTPBinHost == nil {
186+
return
187+
}
188+
_, err := s.Env().HTTPBinHost.Execute(fmt.Sprintf(`if [ -f %s ]; then sudo kill "$(sudo cat %s)" || true; fi`, shellQuote(hostTrafficDNSPIDPath), shellQuote(hostTrafficDNSPIDPath)))
189+
if err != nil {
190+
s.T().Logf("failed to stop host traffic DNS server: %v", err)
191+
}
192+
}
193+
194+
func (s *hostTrafficDynamicPathBaseSuite) configureAgentResolver() {
195+
dnsIP := s.Env().HTTPBinHost.Address
196+
s.Env().RemoteHost.MustExecute(fmt.Sprintf(`set -eu
197+
if [ ! -e %s ]; then
198+
if [ -L /etc/resolv.conf ]; then readlink /etc/resolv.conf | sudo tee %s >/dev/null; fi
199+
sudo cp -L /etc/resolv.conf %s
200+
fi
201+
sudo rm -f /etc/resolv.conf
202+
printf 'nameserver %s\noptions timeout:1 attempts:2\n' | sudo tee /etc/resolv.conf >/dev/null
203+
`, shellQuote(hostTrafficResolverBackupPath), shellQuote(hostTrafficResolverLinkPath), shellQuote(hostTrafficResolverBackupPath), dnsIP))
204+
}
205+
206+
func (s *hostTrafficDynamicPathBaseSuite) restoreAgentResolver() {
207+
if s.Env().RemoteHost == nil {
208+
return
209+
}
210+
_, err := s.Env().RemoteHost.Execute(fmt.Sprintf(`set +e
211+
if [ -f %s ]; then
212+
if [ -s %s ]; then
213+
target="$(cat %s)"
214+
sudo rm -f /etc/resolv.conf
215+
if [ -e "$target" ]; then
216+
sudo ln -s "$target" /etc/resolv.conf
217+
else
218+
sudo cp %s /etc/resolv.conf
219+
fi
220+
else
221+
sudo cp %s /etc/resolv.conf
222+
fi
223+
fi
224+
`, shellQuote(hostTrafficResolverBackupPath), shellQuote(hostTrafficResolverLinkPath), shellQuote(hostTrafficResolverLinkPath), shellQuote(hostTrafficResolverBackupPath), shellQuote(hostTrafficResolverBackupPath)))
225+
if err != nil {
226+
s.T().Logf("failed to restore resolver: %v", err)
227+
}
228+
}
229+
230+
func (s *hostTrafficDynamicPathBaseSuite) assertHostTrafficDomainResolves() {
231+
output := s.Env().RemoteHost.MustExecute("getent ahostsv4 " + shellQuote(hostTrafficRemoteConfigDomain))
232+
require.Contains(s.T(), output, s.Env().HTTPBinHost.Address)
233+
}
234+
235+
func (s *hostTrafficDynamicPathBaseSuite) startHostTrafficGenerator(duration time.Duration) {
236+
seconds := int(duration.Seconds())
237+
trafficCommand := fmt.Sprintf(
238+
"i=0; while [ \"$i\" -lt %d ]; do curl -4 -fsS --max-time 5 %s >/dev/null || true; sleep 2; i=$((i+2)); done",
239+
seconds,
240+
shellQuote(hostTrafficURL(hostTrafficRemoteConfigDomain)),
241+
)
242+
s.Env().RemoteHost.MustExecute(fmt.Sprintf("nohup sh -c %s >%s 2>&1 & echo $! >%s",
243+
shellQuote(trafficCommand),
244+
shellQuote(hostTrafficCurlLogPath),
245+
shellQuote(hostTrafficCurlPIDPath),
246+
))
247+
}
248+
249+
func (s *hostTrafficDynamicPathBaseSuite) stopHostTrafficGenerator() {
250+
if s.Env().RemoteHost == nil {
251+
return
252+
}
253+
_, err := s.Env().RemoteHost.Execute(fmt.Sprintf(`if [ -f %s ]; then kill "$(cat %s)" || true; fi`, shellQuote(hostTrafficCurlPIDPath), shellQuote(hostTrafficCurlPIDPath)))
254+
if err != nil {
255+
s.T().Logf("failed to stop host traffic generator: %v", err)
256+
}
257+
}
258+
259+
func (s *hostTrafficDynamicPathBaseSuite) logRemoteFile(host *components.RemoteHost, path string) {
260+
if host == nil {
261+
return
262+
}
263+
output, err := host.Execute(fmt.Sprintf("if [ -f %s ]; then sudo tail -n 200 %s; fi", shellQuote(path), shellQuote(path)))
264+
if err != nil {
265+
s.T().Logf("failed to read %s: %v", path, err)
266+
return
267+
}
268+
if strings.TrimSpace(output) != "" {
269+
s.T().Logf("%s:\n%s", path, output)
270+
}
271+
}
272+
273+
func assertHostTrafficNetworkPath(c *assert.CollectT, netpaths []*aggregator.Netpath, expectedProfile payload.DynamicTestProfile, description string) *aggregator.Netpath {
274+
match := findHostTrafficNetworkPath(netpaths, hostTrafficRemoteConfigDomain)
275+
require.NotNil(c, match, "no %s host-traffic network path event matched %s:80", description, hostTrafficRemoteConfigDomain)
276+
277+
assert.Equal(c, payload.PathOriginNetworkTraffic, match.Origin)
278+
assert.Equal(c, payload.SourceProductNetworkPath, match.SourceProduct)
279+
assert.Equal(c, payload.TestRunTypeDynamic, match.TestRunType)
280+
assert.Equal(c, expectedProfile, match.DynamicTestProfile)
281+
assert.Equal(c, payload.CollectorTypeAgent, match.CollectorType)
282+
assert.Equal(c, payload.ProtocolTCP, match.Protocol)
283+
assert.Equal(c, hostTrafficRemoteConfigDomain, match.Destination.Hostname)
284+
assert.Equal(c, uint16(80), match.Destination.Port)
285+
require.NotEmpty(c, match.Traceroute.Runs, "matched network path has no traceroute runs")
286+
assert.True(c, hasTracerouteDestinationIP(match), "matched network path has no traceroute destination IP")
287+
return match
288+
}
289+
290+
func findHostTrafficNetworkPath(netpaths []*aggregator.Netpath, domain string) *aggregator.Netpath {
291+
for _, np := range netpaths {
292+
if np == nil {
293+
continue
294+
}
295+
if np.Origin == payload.PathOriginNetworkTraffic &&
296+
np.Protocol == payload.ProtocolTCP &&
297+
np.Destination.Hostname == domain &&
298+
np.Destination.Port == 80 {
299+
return np
300+
}
301+
}
302+
return nil
303+
}
304+
305+
func hasTracerouteDestinationIP(np *aggregator.Netpath) bool {
306+
for _, run := range np.Traceroute.Runs {
307+
if len(run.Destination.IPAddress) > 0 {
308+
return true
309+
}
310+
}
311+
return false
312+
}
313+
314+
func hostTrafficURL(domain string) string {
315+
return "http://" + domain + "/"
316+
}
317+
318+
func shellQuote(value string) string {
319+
return "'" + strings.ReplaceAll(value, "'", "'\"'\"'") + "'"
320+
}

0 commit comments

Comments
 (0)