Skip to content

Commit 14ac1b2

Browse files
vitkyrkaclaude
andauthored
autodiscovery: drop our own probe-race re-check, subsumed by #54368
Now that #54368 (autodiscovery: re-validate discovery probe results against live filtering before applying) is the base of this branch, its generic expectedFilteredTemplatesLocked re-check in applyDiscoveredConfigsLocked already re-runs the full FilterTemplates pass -- including filterTemplatesDiscovery's generic-integration namespace/rename checks -- immediately before scheduling a probe result. That fully subsumes our own, narrower conflictingGenericIntegrationLocked re-check and its discoverySuppressedDigest sentinel bookkeeping, so both are removed: - conflictingGenericIntegrationLocked and the block that called it in applyDiscoveredConfigsLocked (configmgr_discovery.go). - The discoverySuppressedDigest sentinel const and the now-dead guard in reconcileService's deletion loop (configmgr.go) -- #54368's mechanism never needs to record a suppressed digest in the first place, since nothing re-enqueues an already-completed probe. - Our three mid-flight regression tests (TestConfigMgr_Discovery_SuppressedBySiblingAlreadyResolved, ...SuppressedBySiblingMetricRename, ...SuppressedByHostWideStaticNamespace), redundant with #54368's own TestConfigMgr_Discovery_StaleProbeResultSuppressedByMidFlightConflict, which already proves the general re-check mechanism triggers correctly. Coverage of our actual namespace/rename-matching logic is untouched, since it lives in comp/core/autodiscovery/listeners (filterTemplatesDiscovery, GenericIntegrationNamespaceRoots) and is tested directly there. StaticConfigIndex and its namespace-root population in processNewConfig/processDelConfigs are unaffected -- filterTemplatesDiscovery (now invoked both synchronously and via #54368's re-check) still depends on them. Environment: Datadog workspace Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent fbd4e3d commit 14ac1b2

3 files changed

Lines changed: 2 additions & 242 deletions

File tree

comp/core/autodiscovery/impl/configmgr.go

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,6 @@ import (
2727
"github.com/DataDog/datadog-agent/pkg/util/log"
2828
)
2929

30-
// discoverySuppressedDigest is a sentinel value recorded in
31-
// serviceResolutions in place of a real resolved-config digest, for a
32-
// discovery template whose result was intentionally not scheduled because a
33-
// generic-integration (openmetrics/prometheus) sibling or host-wide static
34-
// config already claims a conflicting metric namespace (see
35-
// applyDiscoveredConfigsLocked in configmgr_discovery.go). Config.Digest() is
36-
// a hex-formatted hash and can never be empty, so "" is safe to use as a
37-
// sentinel without colliding with a real digest.
38-
//
39-
// Recording this (rather than leaving the digest out of serviceResolutions
40-
// entirely) prevents reconcileService from treating the template as
41-
// "not yet resolved" and re-triggering a fresh discovery probe on every
42-
// unrelated reconcile of the same service. The deletion loop in
43-
// reconcileService must skip this sentinel rather than looking it up in
44-
// scheduledConfigs (which would return a zero-value Config and produce a
45-
// bogus unschedule).
46-
const discoverySuppressedDigest = ""
47-
4830
// configManager implements the logic of handling additions and removals of
4931
// configs (which may or may not be templates) and services, and reconciling
5032
// those together to resolve templates.
@@ -480,9 +462,7 @@ func (cm *reconcilingConfigManager) reconcileService(svcID string) integration.C
480462
// existingResolutions in-place
481463
for templateDigest, resolvedDigest := range existingResolutions {
482464
if _, found = expectedResolutions[templateDigest]; !found {
483-
if resolvedDigest != discoverySuppressedDigest {
484-
changes.UnscheduleConfig(cm.scheduledConfigs[resolvedDigest])
485-
}
465+
changes.UnscheduleConfig(cm.scheduledConfigs[resolvedDigest])
486466
delete(existingResolutions, templateDigest)
487467
// Clear any health issue for this template+service pair
488468
if tpl, ok := cm.activeConfigs[templateDigest]; ok {

comp/core/autodiscovery/impl/configmgr_discovery.go

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ package autodiscoveryimpl
99

1010
import (
1111
"errors"
12-
"fmt"
1312
"strings"
1413

1514
"github.com/DataDog/datadog-agent/comp/core/autodiscovery/configresolver"
@@ -164,31 +163,12 @@ func (cm *reconcilingConfigManager) applyDiscoveredConfigsLocked(svcID, tplDiges
164163
if !ok {
165164
existing = map[string]string{}
166165
}
167-
if prevDigest, hadPrev := existing[tplDigest]; hadPrev && prevDigest != discoverySuppressedDigest {
166+
if prevDigest, hadPrev := existing[tplDigest]; hadPrev {
168167
if old, found := cm.scheduledConfigs[prevDigest]; found {
169168
changes.UnscheduleConfig(old)
170169
}
171170
}
172171

173-
// The discovery probe was enqueued (in resolveTemplateForService) when
174-
// this template first matched the service, at which point a
175-
// generic-integration (openmetrics/prometheus) sibling might not have
176-
// existed yet — filterTemplatesDiscovery only guards against siblings
177-
// present *before* the probe starts. By the time the probe completes
178-
// (at least one discoverer retry cycle later), such a sibling may now be
179-
// scheduled for this same service, or a host-wide static config may now
180-
// claim the namespace. Re-check here before scheduling the result, so a
181-
// slow-arriving discovery probe can't schedule a duplicate on top of a
182-
// sibling that showed up in the meantime.
183-
if conflictName, ok := cm.conflictingGenericIntegrationLocked(svcID, tplDigest, tpl.Name); ok {
184-
msg := fmt.Sprintf("discovery skipped for %s on service %s: %s already covers this metric namespace", tpl.Name, svcID, conflictName)
185-
log.Debugf("autodiscovery: %s", msg)
186-
errorStats.setResolveWarning(tpl.Name, msg)
187-
existing[tplDigest] = discoverySuppressedDigest
188-
cm.serviceResolutions[svcID] = existing
189-
return cm.applyChanges(changes)
190-
}
191-
192172
existing[tplDigest] = decrypted.Digest()
193173
cm.serviceResolutions[svcID] = existing
194174

@@ -197,35 +177,6 @@ func (cm *reconcilingConfigManager) applyDiscoveredConfigsLocked(svcID, tplDiges
197177
return cm.applyChanges(changes)
198178
}
199179

200-
// conflictingGenericIntegrationLocked reports whether a generic-integration
201-
// (openmetrics/prometheus) config already claims a metric namespace whose
202-
// root matches checkName — either a sibling already resolved for the same
203-
// service (svcID, excluding tplDigest itself), or a host-wide static config
204-
// tracked (by namespace root) in cm.staticConfigIndex. On a match, it returns
205-
// the conflicting config's Name (or "a static config" for the host-wide case)
206-
// and true. Must be called with cm.m held.
207-
func (cm *reconcilingConfigManager) conflictingGenericIntegrationLocked(svcID, tplDigest, checkName string) (string, bool) {
208-
if cm.staticConfigIndex.Has(checkName) {
209-
return "a static config", true
210-
}
211-
212-
for otherDigest, otherResolvedDigest := range cm.serviceResolutions[svcID] {
213-
if otherDigest == tplDigest || otherResolvedDigest == discoverySuppressedDigest {
214-
continue
215-
}
216-
otherCfg, found := cm.scheduledConfigs[otherResolvedDigest]
217-
if !found || !listeners.IsGenericIntegrationCheckName(otherCfg.Name) {
218-
continue
219-
}
220-
for _, root := range listeners.GenericIntegrationNamespaceRoots(otherCfg) {
221-
if root == checkName {
222-
return otherCfg.Name, true
223-
}
224-
}
225-
}
226-
return "", false
227-
}
228-
229180
// rewriteSource rewrites a resolved config's file-based Source to encode that
230181
// it was applied via a configuration-discovery probe result, and whether the
231182
// target service is a process or a container. Only the "file" provider is

comp/core/autodiscovery/impl/configmgr_discovery_test.go

Lines changed: 0 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -700,174 +700,3 @@ func TestConfigMgr_Lifecycle_HostMultiNetworkBridge(t *testing.T) {
700700
// fastWorkerMaxAttempts is the retry budget used by the
701701
// EmptyDiscoveryResult lifecycle test — small so the test finishes quickly.
702702
const fastWorkerMaxAttempts = 3
703-
704-
// TestConfigMgr_Discovery_SuppressedBySiblingAlreadyResolved reproduces the
705-
// DSCVR-626 race: the discovery probe for a template is enqueued (in
706-
// resolveTemplateForService) before a generic-integration (openmetrics)
707-
// sibling exists for the same service, and only resolves *after* that
708-
// sibling has already been scheduled with a matching (rooted-in) namespace.
709-
// filterTemplatesDiscovery's pre-probe check can't catch this — the sibling
710-
// wasn't there yet when it ran — so applyDiscoveredConfigsLocked must
711-
// re-check immediately before scheduling the (slow) probe result.
712-
func TestConfigMgr_Discovery_SuppressedBySiblingAlreadyResolved(t *testing.T) {
713-
errorStats = newAcErrorStats() // isolate from other tests' warnings
714-
715-
mockResolver := MockSecretResolver{}
716-
disco := newStubDiscoverer(func(_, _ string) (string, error) {
717-
return `[{"instances":[{"openmetrics_endpoint":"http://%%host%%:9091/metrics"}]}]`, nil
718-
})
719-
cm := newReconcilingConfigManager(&mockResolver, nil, nil, disco, nil).(*reconcilingConfigManager)
720-
cm.start()
721-
t.Cleanup(cm.stop)
722-
723-
svc := &dummyService{
724-
ID: "docker://conflict-1",
725-
ADIdentifiers: []string{"krakend", "docker://conflict-1"},
726-
Hosts: map[string]string{"main": "10.0.0.1"},
727-
}
728-
729-
// The manual openmetrics sibling, matched to this same service via its
730-
// own entity ID, resolves synchronously (it's not a Discovery template)
731-
// as soon as both are known.
732-
sibling := integration.Config{
733-
Name: "openmetrics",
734-
ADIdentifiers: []string{"docker://conflict-1"},
735-
Instances: []integration.Data{[]byte("namespace: krakend.api\nopenmetrics_endpoint: http://10.0.0.1:9091/metrics")},
736-
}
737-
_ = cm.processNewService(svc)
738-
changes, _ := cm.processNewConfig(sibling)
739-
assertConfigsMatch(t, changes.Schedule, matchName("openmetrics"))
740-
require.Len(t, changes.Schedule, 1, "the openmetrics sibling should resolve synchronously")
741-
742-
// Only now does krakend's discovery template arrive and get enqueued —
743-
// by the time the (stubbed) probe "completes", the sibling above is
744-
// already fully resolved and scheduled.
745-
tpl := integration.Config{
746-
Name: "krakend",
747-
ADIdentifiers: []string{"krakend"},
748-
Discovery: &integration.DiscoveryConfig{},
749-
}
750-
_, _ = cm.processNewConfig(tpl)
751-
752-
// No discovered config should ever be delivered for krakend.
753-
select {
754-
case discovered := <-cm.discoveredChanges():
755-
t.Fatalf("krakend discovery should have been suppressed, got: %+v", discovered)
756-
case <-time.After(500 * time.Millisecond):
757-
}
758-
759-
cm.m.Lock()
760-
resolvedDigest, found := cm.serviceResolutions[svc.ID][tpl.Digest()]
761-
cm.m.Unlock()
762-
require.True(t, found, "the suppressed digest should still be recorded so reconcileService doesn't reprobe")
763-
assert.Equal(t, discoverySuppressedDigest, resolvedDigest)
764-
765-
warnings := GetResolveWarnings()
766-
assert.Contains(t, warnings, "krakend")
767-
}
768-
769-
// TestConfigMgr_Discovery_SuppressedBySiblingMetricRename mirrors
770-
// TestConfigMgr_Discovery_SuppressedBySiblingAlreadyResolved, but the sibling
771-
// has no namespace: field at all -- instead it renames a metric directly to
772-
// krakend's own fully-qualified namespace via metrics:, which (per
773-
// https://datadoghq.atlassian.net/browse/DSCVR-626?focusedCommentId=3493570)
774-
// is submitted completely unprefixed and collides just the same.
775-
func TestConfigMgr_Discovery_SuppressedBySiblingMetricRename(t *testing.T) {
776-
errorStats = newAcErrorStats() // isolate from other tests' warnings
777-
778-
mockResolver := MockSecretResolver{}
779-
disco := newStubDiscoverer(func(_, _ string) (string, error) {
780-
return `[{"instances":[{"openmetrics_endpoint":"http://%%host%%:9091/metrics"}]}]`, nil
781-
})
782-
cm := newReconcilingConfigManager(&mockResolver, nil, nil, disco, nil).(*reconcilingConfigManager)
783-
cm.start()
784-
t.Cleanup(cm.stop)
785-
786-
svc := &dummyService{
787-
ID: "docker://conflict-2",
788-
ADIdentifiers: []string{"krakend", "docker://conflict-2"},
789-
Hosts: map[string]string{"main": "10.0.0.1"},
790-
}
791-
792-
sibling := integration.Config{
793-
Name: "openmetrics",
794-
ADIdentifiers: []string{"docker://conflict-2"},
795-
Instances: []integration.Data{[]byte(
796-
"openmetrics_endpoint: http://10.0.0.1:9091/metrics\n" +
797-
"metrics:\n - krakend_requests_total: krakend.api.requests_total",
798-
)},
799-
}
800-
_ = cm.processNewService(svc)
801-
changes, _ := cm.processNewConfig(sibling)
802-
assertConfigsMatch(t, changes.Schedule, matchName("openmetrics"))
803-
require.Len(t, changes.Schedule, 1, "the openmetrics sibling should resolve synchronously")
804-
805-
tpl := integration.Config{
806-
Name: "krakend",
807-
ADIdentifiers: []string{"krakend"},
808-
Discovery: &integration.DiscoveryConfig{},
809-
}
810-
_, _ = cm.processNewConfig(tpl)
811-
812-
select {
813-
case discovered := <-cm.discoveredChanges():
814-
t.Fatalf("krakend discovery should have been suppressed, got: %+v", discovered)
815-
case <-time.After(500 * time.Millisecond):
816-
}
817-
818-
cm.m.Lock()
819-
resolvedDigest, found := cm.serviceResolutions[svc.ID][tpl.Digest()]
820-
cm.m.Unlock()
821-
require.True(t, found, "the suppressed digest should still be recorded so reconcileService doesn't reprobe")
822-
assert.Equal(t, discoverySuppressedDigest, resolvedDigest)
823-
824-
warnings := GetResolveWarnings()
825-
assert.Contains(t, warnings, "krakend")
826-
}
827-
828-
// TestConfigMgr_Discovery_SuppressedByHostWideStaticNamespace mirrors the
829-
// above but for the non-service-scoped case: a static (non-template)
830-
// openmetrics config claiming a matching namespace anywhere on the host,
831-
// tracked (by namespace root) in staticConfigIndex, must also suppress a slow
832-
// discovery probe result at apply time.
833-
func TestConfigMgr_Discovery_SuppressedByHostWideStaticNamespace(t *testing.T) {
834-
errorStats = newAcErrorStats()
835-
836-
mockResolver := MockSecretResolver{}
837-
disco := newStubDiscoverer(func(_, _ string) (string, error) {
838-
return `[{"instances":[{"openmetrics_endpoint":"http://%%host%%:9091/metrics"}]}]`, nil
839-
})
840-
// The config manager would normally add the *root* of a static
841-
// generic-integration config's namespace (see listeners.NamespaceRoot),
842-
// e.g. "krakend" for "krakend.api" — simulate that directly here.
843-
idx := listeners.NewStaticConfigIndex()
844-
idx.Add("krakend")
845-
cm := newReconcilingConfigManager(&mockResolver, nil, idx, disco, nil).(*reconcilingConfigManager)
846-
cm.start()
847-
t.Cleanup(cm.stop)
848-
849-
svc := &dummyService{
850-
ID: "docker://k1",
851-
ADIdentifiers: []string{"krakend"},
852-
Hosts: map[string]string{"main": "10.0.0.1"},
853-
}
854-
tpl := integration.Config{
855-
Name: "krakend",
856-
ADIdentifiers: []string{"krakend"},
857-
Discovery: &integration.DiscoveryConfig{},
858-
}
859-
_, _ = cm.processNewConfig(tpl)
860-
_ = cm.processNewService(svc)
861-
862-
select {
863-
case discovered := <-cm.discoveredChanges():
864-
t.Fatalf("krakend discovery should have been suppressed, got: %+v", discovered)
865-
case <-time.After(500 * time.Millisecond):
866-
}
867-
868-
cm.m.Lock()
869-
resolvedDigest, found := cm.serviceResolutions[svc.ID][tpl.Digest()]
870-
cm.m.Unlock()
871-
require.True(t, found)
872-
assert.Equal(t, discoverySuppressedDigest, resolvedDigest)
873-
}

0 commit comments

Comments
 (0)