Skip to content

Commit d38d1f9

Browse files
fix(processor/schema): fetch target schema URL on upgrade path (#47632)
#### Description When upgrading a signal (incoming version older than the configured target), the manager was fetching the incoming signal's schema URL to retrieve the schema file. Real OTel schema files only contain history up to their own version — an incoming `1.21.0` schema file has no knowledge of changes made in `1.22.0`–`1.27.0`. The translator found no matching revisions and silently returned `NoChange`, meaning no attributes were ever renamed on the upgrade path. The fix: for upgrades, fetch the **target** schema URL instead. The target schema file contains the complete forward migration history. Downgrades correctly continue to fetch the incoming signal's (newer) URL, since that file contains the history needed to reverse changes. #### Link to tracking issue Fixes #47427 #### Testing Added `TestRequestTranslationUpgrade` in `manager_test.go`. It uses a URL-aware HTTP server that serves a stub schema (no forward history) for old version URLs and the full schema only at the target URL. This proves: 1. The fix correctly fetches the target URL on the upgrade path 2. The attribute rename defined in the target schema (`db.cassandra.keyspace` → `db.name`) is applied 3. Without the fix, the test fails because the stub schema has no revisions to apply #### Documentation No documentation changes required — this is a bug fix for behaviour that was never working correctly.
1 parent 4682913 commit d38d1f9

3 files changed

Lines changed: 87 additions & 2 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
change_type: bug_fix
2+
component: processor/schema
3+
note: Fix upgrade path silently applying no changes due to fetching the incoming signal's schema file instead of the target schema file
4+
issues: [47427, 47632]
5+
subtext: |
6+
When upgrading a signal to a newer target version, the processor was fetching the schema
7+
file at the incoming signal's URL. Because OTel schema files only contain history up to
8+
their own version, the translator found no applicable revisions and silently passed the
9+
signal through unchanged. The processor now fetches the target schema URL on the upgrade
10+
path, which contains the complete forward migration history.
11+
change_logs: [user]

processor/schemaprocessor/internal/translation/manager.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,20 @@ func (m *manager) RequestTranslation(ctx context.Context, schemaURL string) (Tra
9595
return t, nil
9696
}
9797

98+
// Always fetch the schema file for the higher version, since it contains the complete
99+
// migration history. For upgrades (signal older than target), fetch the target URL.
100+
// For downgrades (signal newer than target), fetch the signal URL.
101+
fetchURL := schemaURL
102+
if version.Compare(targetTranslation) == Update {
103+
fetchURL = joinSchemaFamilyAndVersion(family, targetTranslation)
104+
}
105+
98106
for _, p := range m.providers {
99-
content, err := p.Retrieve(ctx, schemaURL)
107+
content, err := p.Retrieve(ctx, fetchURL)
100108
if err != nil {
101109
m.log.Error("Failed to lookup schemaURL",
102110
zap.Error(err),
103-
zap.String("schemaURL", schemaURL),
111+
zap.String("schemaURL", fetchURL),
104112
)
105113
// If we fail to retrieve the schema, we should
106114
// try the next provider

processor/schemaprocessor/internal/translation/manager_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import (
1010
"fmt"
1111
"net/http"
1212
"net/http/httptest"
13+
"strings"
1314
"testing"
1415

1516
"github.com/stretchr/testify/assert"
1617
"github.com/stretchr/testify/require"
18+
"go.opentelemetry.io/collector/pdata/ptrace"
1719
"go.uber.org/zap/zaptest"
1820
)
1921

@@ -66,6 +68,70 @@ func TestRequestTranslation(t *testing.T) {
6668
}
6769
}
6870

71+
// TestRequestTranslationUpgrade verifies that the upgrade path fetches the target schema
72+
// URL (not the incoming signal's URL). Real OTel schema files only contain history up to
73+
// their own version, so an incoming 1.0.0 schema file has no knowledge of changes made
74+
// in 1.1.0–1.9.0. The processor must fetch the target schema file to get the full history.
75+
func TestRequestTranslationUpgrade(t *testing.T) {
76+
t.Parallel()
77+
78+
// fullSchema contains history for 1.0.0–1.9.0, including a rename in 1.8.0.
79+
// stubSchema simulates what a real 1.0.0 schema file looks like: it only knows
80+
// about itself and has no forward history.
81+
fullSchema := LoadTranslationVersion(t, TranslationVersion190)
82+
stubSchema := `file_format: 1.0.0
83+
schema_url: https://example.com/1.0.0
84+
versions:
85+
1.0.0:
86+
`
87+
88+
// URL-aware server: serve the full schema only at the target URL.
89+
// All other URLs (simulating older signal schema files) return the stub.
90+
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
91+
var content string
92+
if strings.HasSuffix(r.URL.Path, TranslationVersion190) {
93+
content = fullSchema
94+
} else {
95+
content = stubSchema
96+
}
97+
_, err := w.Write([]byte(content))
98+
assert.NoError(t, err)
99+
}))
100+
t.Cleanup(s.Close)
101+
102+
targetURL := fmt.Sprintf("%s/%s", s.URL, TranslationVersion190)
103+
signalURL := fmt.Sprintf("%s/1.0.0", s.URL)
104+
105+
m, err := NewManager(
106+
[]string{targetURL},
107+
zaptest.NewLogger(t),
108+
NewHTTPProvider(s.Client()),
109+
)
110+
require.NoError(t, err)
111+
112+
tr, err := m.RequestTranslation(t.Context(), signalURL)
113+
require.NoError(t, err, "Must not error on upgrade path")
114+
require.NotNil(t, tr)
115+
116+
// The translator must know about 1.0.0 (loaded from the target schema file).
117+
assert.True(t, tr.SupportedVersion(&Version{1, 0, 0}), "Must support the incoming signal version")
118+
119+
// Apply the upgrade: 1.8.0 in the target schema renames db.cassandra.keyspace → db.name.
120+
scopeSpans := ptrace.NewScopeSpans()
121+
scopeSpans.SetSchemaUrl(signalURL)
122+
span := scopeSpans.Spans().AppendEmpty()
123+
span.Attributes().PutStr("db.cassandra.keyspace", "my_keyspace")
124+
125+
require.NoError(t, tr.ApplyScopeSpanChanges(scopeSpans, signalURL))
126+
127+
val, ok := span.Attributes().Get("db.name")
128+
require.True(t, ok, "db.name must exist after upgrade — rename was not applied")
129+
assert.Equal(t, "my_keyspace", val.Str())
130+
131+
_, oldExists := span.Attributes().Get("db.cassandra.keyspace")
132+
assert.False(t, oldExists, "old attribute must be removed after upgrade")
133+
}
134+
69135
type errorProvider struct{}
70136

71137
func (*errorProvider) Retrieve(_ context.Context, _ string) (string, error) {

0 commit comments

Comments
 (0)