Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion comp/syntheticstestscheduler/common/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import (
// ConfigRequest represents the type configuration for a network test.
type ConfigRequest interface {
GetSubType() payload.Protocol
// GetNamespace returns the NDM namespace configured on the test, or nil
// when the test does not override the Agent's default namespace.
GetNamespace() *string
}

// NetworkConfigRequest represents the generic part of the network test configuration.
Expand All @@ -25,7 +28,14 @@ type NetworkConfigRequest struct {
ProbeCount *int `json:"probe_count,omitempty"`
TracerouteCount *int `json:"traceroute_count,omitempty"`
MaxTTL *int `json:"max_ttl,omitempty"`
Timeout *int `json:"timeout,omitempty"` // in seconds
Timeout *int `json:"timeout,omitempty"` // in seconds
Namespace *string `json:"namespace,omitempty"` // NDM namespace used to resolve devices; overrides the Agent default when set
}

// GetNamespace returns the namespace override configured on the test, if any.
// It is promoted to every request type embedding NetworkConfigRequest.
func (n NetworkConfigRequest) GetNamespace() *string {
return n.Namespace
}

// UDPConfigRequest represents a udp network test configuration.
Expand Down
2 changes: 2 additions & 0 deletions comp/syntheticstestscheduler/impl/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ go_test(
name = "impl_test",
srcs = [
"assertions_test.go",
"config_test.go",
"syntheticstestscheduler_test.go",
"testpoller_test.go",
"worker_test.go",
],
embed = [":impl"],
gotags = ["test"],
deps = [
"//comp/core/config",
"//comp/core/hostname/hostnameinterface/def",
"//comp/core/log/def",
"//comp/forwarder/eventplatform/def",
Expand Down
4 changes: 4 additions & 0 deletions comp/syntheticstestscheduler/impl/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ type schedulerConfigs struct {
workers int
flushInterval time.Duration
syntheticsSchedulerEnabled bool
// namespace is the default NDM namespace stamped on emitted paths, mirroring
// the network_path integration. Individual tests may override it.
namespace string
}

func newSchedulerConfigs(agentConfig config.Component) *schedulerConfigs {
return &schedulerConfigs{
syntheticsSchedulerEnabled: agentConfig.GetBool("synthetics.collector.enabled"),
workers: agentConfig.GetInt("synthetics.collector.workers"),
flushInterval: agentConfig.GetDuration("synthetics.collector.flush_interval"),
namespace: agentConfig.GetString("network_devices.namespace"),
}
}

Expand Down
43 changes: 43 additions & 0 deletions comp/syntheticstestscheduler/impl/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2025-present Datadog, Inc.

//go:build test

package syntheticstestschedulerimpl

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/DataDog/datadog-agent/comp/core/config"
)

func TestNewSchedulerConfigs_Namespace(t *testing.T) {
tests := []struct {
name string
overrides map[string]interface{}
expected string
}{
{
name: "falls back to network_devices.namespace default",
overrides: map[string]interface{}{},
expected: "default",
},
{
name: "uses configured network_devices.namespace",
overrides: map[string]interface{}{"network_devices.namespace": "ndm-ns"},
expected: "ndm-ns",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := config.NewMockWithOverrides(t, tt.overrides)
configs := newSchedulerConfigs(cfg)
require.Equal(t, tt.expected, configs.namespace)
})
}
}
2 changes: 2 additions & 0 deletions comp/syntheticstestscheduler/impl/scheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type syntheticsTestScheduler struct {
hostNameService hostname.Component
statsdClient ddgostatsd.ClientInterface
testPoller *testPoller
namespace string
}

// newSyntheticsTestScheduler creates a scheduler and initializes its state.
Expand All @@ -63,6 +64,7 @@ func newSyntheticsTestScheduler(configs *schedulerConfigs, forwarder eventplatfo
generateTestResultID: generateRandomStringUInt63,
statsdClient: statsd,
testPoller: poller,
namespace: configs.namespace,
}

// by default, sendResult delegates to the real forwarder-backed implementation
Expand Down
14 changes: 14 additions & 0 deletions comp/syntheticstestscheduler/impl/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,19 @@ func configRequestToResultRequest(req common.ConfigRequest) (common.ResultReques
}
}

// resolveNamespace returns the NDM namespace to stamp on the emitted path. A
// non-empty namespace supplied by the test config takes precedence; otherwise
// the Agent default (network_devices.namespace) is used, mirroring the
// network_path integration.
func (s *syntheticsTestScheduler) resolveNamespace(req common.ConfigRequest) string {
if req != nil {
if ns := req.GetNamespace(); ns != nil && *ns != "" {
return *ns
}
}
return s.namespace
}

// networkPathToTestResult converts a workerResult into the public TestResult structure.
func (s *syntheticsTestScheduler) networkPathToTestResult(w *workerResult) (*common.TestResult, error) {
t := common.Test{
Expand All @@ -394,6 +407,7 @@ func (s *syntheticsTestScheduler) networkPathToTestResult(w *workerResult) (*com
w.tracerouteResult.Source.Name = w.hostname
w.tracerouteResult.Source.DisplayName = w.hostname
w.tracerouteResult.Source.Hostname = w.hostname
w.tracerouteResult.Namespace = s.resolveNamespace(w.testCfg.cfg.Config.Request)
w.tracerouteResult.TestConfigID = w.testCfg.cfg.PublicID
w.tracerouteResult.TestResultID = testResultID
w.tracerouteResult.Origin = payload.PathOriginSynthetics
Expand Down
66 changes: 66 additions & 0 deletions comp/syntheticstestscheduler/impl/worker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,72 @@ func TestNetworkPathToTestResult_UsesBackendResultID(t *testing.T) {
require.Equal(t, "backend-result-id", got.Result.ID)
}

func TestNetworkPathToTestResult_Namespace(t *testing.T) {
override := "prod-namespace"

tests := []struct {
name string
schedulerNS string
requestNamespace *string
expectedNamespace string
}{
{
name: "falls back to agent default namespace",
schedulerNS: "default",
requestNamespace: nil,
expectedNamespace: "default",
},
{
name: "empty test namespace falls back to agent default",
schedulerNS: "default",
requestNamespace: func() *string { s := ""; return &s }(),
expectedNamespace: "default",
},
{
name: "test namespace overrides agent default",
schedulerNS: "default",
requestNamespace: &override,
expectedNamespace: "prod-namespace",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sched := &syntheticsTestScheduler{
namespace: tt.schedulerNS,
generateTestResultID: func(func(rand io.Reader, max *big.Int) (n *big.Int, err error)) (string, error) {
return "generated-id", nil
},
}

worker := workerResult{
testCfg: SyntheticsTestCtx{
cfg: common.SyntheticsTestConfig{
PublicID: "pub-ns",
Type: "network",
Config: struct {
Assertions []common.Assertion `json:"assertions"`
Request common.ConfigRequest `json:"request"`
}{
Request: common.ICMPConfigRequest{
Host: "8.8.8.8",
NetworkConfigRequest: common.NetworkConfigRequest{
Namespace: tt.requestNamespace,
},
},
},
},
},
hostname: "agent-host",
}

got, err := sched.networkPathToTestResult(&worker)
require.NoError(t, err)
require.Equal(t, tt.expectedNamespace, got.Result.Netpath.Namespace)
})
}
}

func TestGenerateRandomStringUInt63(t *testing.T) {
t.Run("success with mocked value", func(t *testing.T) {
randIntFn := func(_ io.Reader, _ *big.Int) (*big.Int, error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Each section from every release note are combined when the
# CHANGELOG.rst is rendered. So the text needs to be worded so that
# it does not depend on any information only available in another
# section. This may mean repeating some details, but each section
# must be readable independently of the other.
#
# Each section note must be formatted as reStructuredText.
---
enhancements:
- |
Network Path Synthetic tests now report an NDM ``namespace`` on emitted
paths. By default the Agent uses its configured ``network_devices.namespace``
(matching the ``network_path`` integration); individual tests can override
it via a ``namespace`` field in their configuration. Previously synthetic
paths were always emitted with an empty namespace.
Loading