Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tracestate: Allow duplicate keys but rewrite them #1183

Merged
merged 4 commits into from
Jan 10, 2022
Merged

tracestate: Allow duplicate keys but rewrite them #1183

merged 4 commits into from
Jan 10, 2022

Conversation

marclop
Copy link
Contributor

@marclop marclop commented Jan 5, 2022

Description

Changes the validation logic to allow duplicate keys to be present in
tracestate entries, modifying the NewTraceState constructor to rewrite
the duplicate es keys according to the new W3C spec, but leaving 3rd
party duplicates untouched.

https://w3c.github.io/trace-context/#combined-header-value

Related issues

Closes #1182

Changes the validation logic to allow duplicate keys to be present in
tracestate entries, modifying the `NewTraceState` constructor to rewrite
the duplicate keys according to the new W3C spec.

Signed-off-by: Marc Lopez Rubio <[email protected]>
@apmmachine
Copy link
Contributor

apmmachine commented Jan 5, 2022

💚 Build Succeeded

the below badges are clickable and redirect to their specific view in the CI or DOCS
Pipeline View Test View Changes Artifacts preview preview

Expand to view the summary

Build stats

  • Start Time: 2022-01-10T06:36:55.249+0000

  • Duration: 34 min 34 sec

  • Commit: 4344152

Test stats 🧪

Test Results
Failed 0
Passed 11991
Skipped 279
Total 12270

🤖 GitHub comments

To re-run your PR in the CI, just comment with:

  • /test : Re-trigger the build.

  • run benchmark tests : Run the benchmark test.

  • run elasticsearch-ci/docs : Re-trigger the docs validation. (use unformatted text in the comment!)

tracecontext.go Outdated
var last *TraceStateEntry
var modified []TraceStateEntry
recorded := make(map[string]uint8)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could also be named seen or observed, I think it works either way but I'm happy to rename it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't make a big difference to me, happy with whichever color you choose for the bikeshed :)

tracecontext.go Outdated Show resolved Hide resolved
tracecontext_test.go Outdated Show resolved Hide resolved
@marclop marclop marked this pull request as ready for review January 5, 2022 08:48
@marclop marclop requested a review from a team January 5, 2022 08:48
Copy link
Contributor

@stuartnelson3 stuartnelson3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couple small comments, but nothing that requires a re-review

tracecontext.go Outdated
var last *TraceStateEntry
var modified []TraceStateEntry
recorded := make(map[string]uint8)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't make a big difference to me, happy with whichever color you choose for the bikeshed :)

tracecontext.go Outdated
}
// After we've ranged over the whole entry slice, any duplicates present
// must be written first in a last to first order, thus we range the slice
// backwards.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah hah, the order is why modified is a slice and not a map?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exactly!

tracecontext.go Outdated
if count > 1 {
for i, m := range modified {
if m.Key == e.Key {
modified[i] = e
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we've found a matching key, we could continue, right? Since we're only wanting to record the latest entry e for a specific key e.Key?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah we could if we kept the implementation as is, I'm going to simplify it and change it to only act on the 'es' vendor key which is the only one we care about, it'll also eliminate allocations :)

tracecontext.go Outdated
recorded[e.Key]++
}
// After we've ranged over the whole entry slice, any duplicates present
// must be written first in a last to first order, thus we range the slice
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: must be written in a last to first order, I think it makes the comment a bit simpler since we're already saying last and first

tracecontext.go Outdated Show resolved Hide resolved
Signed-off-by: Marc Lopez Rubio <[email protected]>
@marclop marclop requested review from axw and stuartnelson3 January 6, 2022 07:05
tracecontext.go Outdated Show resolved Hide resolved
tracecontext_test.go Outdated Show resolved Hide resolved
tracecontext.go Outdated
var last *TraceStateEntry
var recorded uint8
// Range over the entries and find duplicate `es` vendor keys. When 'es'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm finding this method quite difficult to follow now. Would this alternative work?

var haveElastic bool
var last *TraceState
for _, e := range entries {
	if e.Key == elasticTracestateVendorKey {
		if haveElastic {
			// Discard duplicate "es" entries; keep the last entry's value.
			out.head.value = e.value
			continue
		}
		haveElastic = true
		e.next = last
		last = nil // cause elastic to be moved to head
	}
	e := e // copy
	if last == nil {
		out.head = &e
	} else {
		last.next = &e
	}
	last = &e
}
if haveElastic {
	out.parseElasticTracestateError = out.parseElasticTracestate(out.head)
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW we didn't previously move the elastic entry to the head because IIANM that's only expected when mutating tracestate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mostly kept the suggestion intact, but had to make some changes in 9270b6a to not lose any entries that were found previous to the es key.

Signed-off-by: Marc Lopez Rubio <[email protected]>
Copy link
Member

@axw axw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the updates!

tracecontext.go Outdated Show resolved Hide resolved
tracecontext.go Outdated Show resolved Hide resolved
Signed-off-by: Marc Lopez Rubio <[email protected]>
@marclop marclop enabled auto-merge (squash) January 10, 2022 06:53
@marclop marclop merged commit 6a8e564 into elastic:master Jan 10, 2022
@marclop marclop deleted the b/fix-tracestate-duplicate-key-handling branch January 10, 2022 07:10
axw pushed a commit to axw/apm-agent-go that referenced this pull request Jan 10, 2022
Changes the validation logic to allow duplicate keys to be present in
tracestate entries, modifying the `NewTraceState` constructor to rewrite
the duplicate `es` keys according to the new W3C spec, but leaving 3rd
party duplicates untouched. 

Signed-off-by: Marc Lopez Rubio <[email protected]>
axw added a commit that referenced this pull request Jan 10, 2022
* CI: use apm-integration-testing/7.x for v1

Use apm-integration-testing 7.x branch for testing
apm-agent-go v1. We'll update apm-integration-testing's
main branch to test v2.

* deps: Pin `olivere/elastic` dependency (#1178)

To version `v7.0.29` for pre module Go versions.

Signed-off-by: Marc Lopez Rubio <[email protected]>

* tracestate: Allow duplicate keys but rewrite them (#1183)

Changes the validation logic to allow duplicate keys to be present in
tracestate entries, modifying the `NewTraceState` constructor to rewrite
the duplicate `es` keys according to the new W3C spec, but leaving 3rd
party duplicates untouched. 

Signed-off-by: Marc Lopez Rubio <[email protected]>

Co-authored-by: Marc Lopez Rubio <[email protected]>
@simitt simitt added this to the 2.0 milestone Feb 9, 2022
@axw axw self-assigned this Feb 10, 2022
@axw
Copy link
Member

axw commented Feb 10, 2022

Verified using go.elastic.co/apm/v2 v2.0.0-20220209134054-640a916049c9 (current main), along with apmhttp/v2. To get apmhttp/v2 to work, I had to add a replace stanza:

replace go.elastic.co/apm/v2 v2.0.0 => go.elastic.co/apm/v2 v2.0.0-20220209134054-640a916049c9

I compared the v2 behaviour against the latest v1 agent.

package main

import (
        "fmt"
        "io"
        "io/ioutil"
        "net/http"
        "net/http/httptest"

        //"go.elastic.co/apm"
        //"go.elastic.co/apm/module/apmhttp"
        "go.elastic.co/apm/module/apmhttp/v2"
        "go.elastic.co/apm/v2"
)

func main() {
        srv := httptest.NewServer(apmhttp.Wrap(http.HandlerFunc(
                func(w http.ResponseWriter, r *http.Request) {
                        tx := apm.TransactionFromContext(r.Context())
                        fmt.Println(tx.TraceContext().State)
                },
        )))
        req, _ := http.NewRequest("GET", srv.URL, nil)
        req.Header.Set("Tracestate", "foo=bar,foo=bar")
        req.Header.Set("Traceparent", "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01")
        resp, _ := http.DefaultClient.Do(req)

        io.Copy(ioutil.Discard, resp.Body)
        resp.Body.Close()
}
  • With v1, tracestate is cleared out.
  • With v2, tracestate is maintained as-is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Update tracestate parsing rules to comply with latest W3C spec
5 participants