diff --git a/Gopkg.lock b/Gopkg.lock index 41110711cf..d7cd23bfc8 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -905,6 +905,18 @@ revision = "59d1ce35d30f3c25ba762169da2a37eab6ffa041" version = "v0.22.1" +[[projects]] + digest = "1:8dd0eb34abada570618ce982ec22f197fc97edb6f6d5e6ef76dc2069bd5a30e7" + name = "go.opentelemetry.io/otel" + packages = [ + "api/core", + "api/propagation", + "api/trace", + ] + pruneopts = "NUT" + revision = "857e80c27031b0e05246974a78769e7ea41fca23" + version = "v0.4.2" + [[projects]] digest = "1:cc9d86ec4e6e3bdf87e3a421273bfeed003cf8e21351c0302fe8b0eb7b10efe6" name = "go.uber.org/atomic" @@ -1761,7 +1773,7 @@ [[projects]] branch = "master" - digest = "1:bd47f98237125c93232b63405185952137882ae2621edf541429fd5f8b8380dd" + digest = "1:900a3b678808abc9b2f147ad24c993de6ef7db218b5f8805f2c8ef294bc1d0c1" name = "knative.dev/eventing" packages = [ "pkg/adapter", @@ -1849,6 +1861,8 @@ "pkg/tracing", "pkg/utils", "test", + "test/conformance/helpers", + "test/conformance/helpers/tracing", "test/e2e/helpers", "test/lib", "test/lib/cloudevents", @@ -2148,6 +2162,7 @@ "knative.dev/eventing/pkg/tracing", "knative.dev/eventing/pkg/utils", "knative.dev/eventing/test", + "knative.dev/eventing/test/conformance/helpers", "knative.dev/eventing/test/e2e/helpers", "knative.dev/eventing/test/lib", "knative.dev/eventing/test/lib/resources", @@ -2197,6 +2212,7 @@ "knative.dev/pkg/system", "knative.dev/pkg/system/testing", "knative.dev/pkg/test", + "knative.dev/pkg/test/zipkin", "knative.dev/pkg/tracker", "knative.dev/pkg/webhook", "knative.dev/pkg/webhook/certificates", diff --git a/test/conformance/README.md b/test/conformance/README.md new file mode 100644 index 0000000000..d0b4f5d016 --- /dev/null +++ b/test/conformance/README.md @@ -0,0 +1,18 @@ +# Conformance tests + +Conformance tests verifies kantive eventing implementation for expected behavior +described in +[specification](https://github.com/knative/eventing/tree/master/docs/spec). + +## Running conformance tests + +Run test with e2e tag and optionally select conformance test + +> NOTE: Make sure you have built the +> [test images](https://github.com/knative/eventing/tree/master/test#building-the-test-images)! + +```bash +go test -v -tags=e2e -count=1 ./test/conformance/... + +go test -v -timeout 30s -tags e2e knative.dev/eventing/test/conformance -run ^TestChannelStatus$ -channels=messaging.knative.dev/v1alpha1:NatssChannel,messaging.knative.dev/v1alpha1:KafkaChannel +``` diff --git a/test/conformance/channel_status_test.go b/test/conformance/channel_status_test.go new file mode 100644 index 0000000000..a4e43f8664 --- /dev/null +++ b/test/conformance/channel_status_test.go @@ -0,0 +1,30 @@ +//+build e2e + +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package conformance + +import ( + "testing" + + eventingconformancehelpers "knative.dev/eventing/test/conformance/helpers" + "knative.dev/eventing/test/lib" +) + +func TestChannelStatus(t *testing.T) { + eventingconformancehelpers.ChannelStatusTestHelperWithChannelTestRunner(t, channelTestRunner, lib.SetupClientOptionNoop) +} diff --git a/test/conformance/main_test.go b/test/conformance/main_test.go new file mode 100644 index 0000000000..92cd1c011a --- /dev/null +++ b/test/conformance/main_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package conformance + +import ( + "log" + "os" + "testing" + + "knative.dev/eventing-contrib/test" + eventingTest "knative.dev/eventing/test" + "knative.dev/eventing/test/lib" + "knative.dev/pkg/test/zipkin" +) + +var channelTestRunner lib.ChannelTestRunner + +func TestMain(m *testing.M) { + os.Exit(func() int { + eventingTest.InitializeEventingFlags() + channelTestRunner = lib.ChannelTestRunner{ + ChannelFeatureMap: test.ChannelFeatureMap, + ChannelsToTest: eventingTest.EventingFlags.Channels, + } + + // Any tests may SetupZipkinTracing, it will only actually be done once. This should be the ONLY + // place that cleans it up. If an individual test calls this instead, then it will break other + // tests that need the tracing in place. + defer zipkin.CleanupZipkinTracingSetup(log.Printf) + + return m.Run() + }()) +} diff --git a/test/e2e-tests.sh b/test/e2e-tests.sh index 247ed837b9..d3a5abd33c 100755 --- a/test/e2e-tests.sh +++ b/test/e2e-tests.sh @@ -245,6 +245,8 @@ initialize $@ --skip-istio-addon # https://github.com/knative/eventing-contrib/issues/917 go_test_e2e -timeout=20m -parallel=1 ./test/e2e -channels=messaging.knative.dev/v1alpha1:NatssChannel,messaging.knative.dev/v1alpha1:KafkaChannel || fail_test +go_test_e2e -timeout=5m -parallel=2 ./test/conformance -channels=messaging.knative.dev/v1alpha1:NatssChannel,messaging.knative.dev/v1alpha1:KafkaChannel || fail_test + # If you wish to use this script just as test setup, *without* teardown, just uncomment this line and comment all go_test_e2e commands # trap - SIGINT SIGQUIT SIGTSTP EXIT diff --git a/vendor/go.opentelemetry.io/otel/LICENSE b/vendor/go.opentelemetry.io/otel/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/go.opentelemetry.io/otel/api/core/doc.go b/vendor/go.opentelemetry.io/otel/api/core/doc.go new file mode 100644 index 0000000000..9f47b79bbc --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/core/doc.go @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This package provides basic types used in OpenTelemetry - keys, +// values, numbers and span contexts. +package core // import "go.opentelemetry.io/otel/api/core" diff --git a/vendor/go.opentelemetry.io/otel/api/core/key.go b/vendor/go.opentelemetry.io/otel/api/core/key.go new file mode 100644 index 0000000000..aead1afb0b --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/core/key.go @@ -0,0 +1,339 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +//go:generate stringer -type=ValueType + +import ( + "encoding/json" + "fmt" + "strconv" + "unsafe" +) + +// Key represents the key part in key-value pairs. It's a string. The +// allowed character set in the key depends on the use of the key. +type Key string + +// KeyValue holds a key and value pair. +type KeyValue struct { + Key Key + Value Value +} + +// ValueType describes the type of the data Value holds. +type ValueType int + +// Value represents the value part in key-value pairs. +type Value struct { + vtype ValueType + numeric uint64 + stringly string + // TODO Lazy value type? +} + +const ( + INVALID ValueType = iota // No value. + BOOL // Boolean value, use AsBool() to get it. + INT32 // 32 bit signed integral value, use AsInt32() to get it. + INT64 // 64 bit signed integral value, use AsInt64() to get it. + UINT32 // 32 bit unsigned integral value, use AsUint32() to get it. + UINT64 // 64 bit unsigned integral value, use AsUint64() to get it. + FLOAT32 // 32 bit floating point value, use AsFloat32() to get it. + FLOAT64 // 64 bit floating point value, use AsFloat64() to get it. + STRING // String value, use AsString() to get it. +) + +// Bool creates a BOOL Value. +func Bool(v bool) Value { + return Value{ + vtype: BOOL, + numeric: boolToRaw(v), + } +} + +// Int64 creates an INT64 Value. +func Int64(v int64) Value { + return Value{ + vtype: INT64, + numeric: int64ToRaw(v), + } +} + +// Uint64 creates a UINT64 Value. +func Uint64(v uint64) Value { + return Value{ + vtype: UINT64, + numeric: uint64ToRaw(v), + } +} + +// Float64 creates a FLOAT64 Value. +func Float64(v float64) Value { + return Value{ + vtype: FLOAT64, + numeric: float64ToRaw(v), + } +} + +// Int32 creates an INT32 Value. +func Int32(v int32) Value { + return Value{ + vtype: INT32, + numeric: int32ToRaw(v), + } +} + +// Uint32 creates a UINT32 Value. +func Uint32(v uint32) Value { + return Value{ + vtype: UINT32, + numeric: uint32ToRaw(v), + } +} + +// Float32 creates a FLOAT32 Value. +func Float32(v float32) Value { + return Value{ + vtype: FLOAT32, + numeric: float32ToRaw(v), + } +} + +// String creates a STRING Value. +func String(v string) Value { + return Value{ + vtype: STRING, + stringly: v, + } +} + +// Int creates either an INT32 or an INT64 Value, depending on whether +// the int type is 32 or 64 bits wide. +func Int(v int) Value { + if unsafe.Sizeof(v) == 4 { + return Int32(int32(v)) + } + return Int64(int64(v)) +} + +// Uint creates either a UINT32 or a UINT64 Value, depending on +// whether the uint type is 32 or 64 bits wide. +func Uint(v uint) Value { + if unsafe.Sizeof(v) == 4 { + return Uint32(uint32(v)) + } + return Uint64(uint64(v)) +} + +// Bool creates a KeyValue instance with a BOOL Value. +func (k Key) Bool(v bool) KeyValue { + return KeyValue{ + Key: k, + Value: Bool(v), + } +} + +// Int64 creates a KeyValue instance with an INT64 Value. +func (k Key) Int64(v int64) KeyValue { + return KeyValue{ + Key: k, + Value: Int64(v), + } +} + +// Uint64 creates a KeyValue instance with a UINT64 Value. +func (k Key) Uint64(v uint64) KeyValue { + return KeyValue{ + Key: k, + Value: Uint64(v), + } +} + +// Float64 creates a KeyValue instance with a FLOAT64 Value. +func (k Key) Float64(v float64) KeyValue { + return KeyValue{ + Key: k, + Value: Float64(v), + } +} + +// Int32 creates a KeyValue instance with an INT32 Value. +func (k Key) Int32(v int32) KeyValue { + return KeyValue{ + Key: k, + Value: Int32(v), + } +} + +// Uint32 creates a KeyValue instance with a UINT32 Value. +func (k Key) Uint32(v uint32) KeyValue { + return KeyValue{ + Key: k, + Value: Uint32(v), + } +} + +// Float32 creates a KeyValue instance with a FLOAT32 Value. +func (k Key) Float32(v float32) KeyValue { + return KeyValue{ + Key: k, + Value: Float32(v), + } +} + +// String creates a KeyValue instance with a STRING Value. +func (k Key) String(v string) KeyValue { + return KeyValue{ + Key: k, + Value: String(v), + } +} + +// Int creates a KeyValue instance with either an INT32 or an INT64 +// Value, depending on whether the int type is 32 or 64 bits wide. +func (k Key) Int(v int) KeyValue { + return KeyValue{ + Key: k, + Value: Int(v), + } +} + +// Uint creates a KeyValue instance with either an UINT32 or an UINT64 +// Value, depending on whether the uint type is 32 or 64 bits wide. +func (k Key) Uint(v uint) KeyValue { + return KeyValue{ + Key: k, + Value: Uint(v), + } +} + +// Defined returns true for non-empty keys. +func (k Key) Defined() bool { + return len(k) != 0 +} + +// Type returns a type of the Value. +func (v *Value) Type() ValueType { + return v.vtype +} + +// Bool returns the bool value. Make sure that the Value's type is +// BOOL. +func (v *Value) AsBool() bool { + return rawToBool(v.numeric) +} + +// AsInt32 returns the int32 value. Make sure that the Value's type is +// INT32. +func (v *Value) AsInt32() int32 { + return rawToInt32(v.numeric) +} + +// AsInt64 returns the int64 value. Make sure that the Value's type is +// INT64. +func (v *Value) AsInt64() int64 { + return rawToInt64(v.numeric) +} + +// AsUint32 returns the uint32 value. Make sure that the Value's type +// is UINT32. +func (v *Value) AsUint32() uint32 { + return rawToUint32(v.numeric) +} + +// AsUint64 returns the uint64 value. Make sure that the Value's type is +// UINT64. +func (v *Value) AsUint64() uint64 { + return rawToUint64(v.numeric) +} + +// AsFloat32 returns the float32 value. Make sure that the Value's +// type is FLOAT32. +func (v *Value) AsFloat32() float32 { + return rawToFloat32(v.numeric) +} + +// AsFloat64 returns the float64 value. Make sure that the Value's +// type is FLOAT64. +func (v *Value) AsFloat64() float64 { + return rawToFloat64(v.numeric) +} + +// AsString returns the string value. Make sure that the Value's type +// is STRING. +func (v *Value) AsString() string { + return v.stringly +} + +type unknownValueType struct{} + +// AsInterface returns Value's data as interface{}. +func (v *Value) AsInterface() interface{} { + switch v.Type() { + case BOOL: + return v.AsBool() + case INT32: + return v.AsInt32() + case INT64: + return v.AsInt64() + case UINT32: + return v.AsUint32() + case UINT64: + return v.AsUint64() + case FLOAT32: + return v.AsFloat32() + case FLOAT64: + return v.AsFloat64() + case STRING: + return v.stringly + } + return unknownValueType{} +} + +// Emit returns a string representation of Value's data. +func (v *Value) Emit() string { + switch v.Type() { + case BOOL: + return strconv.FormatBool(v.AsBool()) + case INT32: + return strconv.FormatInt(int64(v.AsInt32()), 10) + case INT64: + return strconv.FormatInt(v.AsInt64(), 10) + case UINT32: + return strconv.FormatUint(uint64(v.AsUint32()), 10) + case UINT64: + return strconv.FormatUint(v.AsUint64(), 10) + case FLOAT32: + return fmt.Sprint(v.AsFloat32()) + case FLOAT64: + return fmt.Sprint(v.AsFloat64()) + case STRING: + return v.stringly + default: + return "unknown" + } +} + +// MarshalJSON returns the JSON encoding of the Value. +func (v *Value) MarshalJSON() ([]byte, error) { + var jsonVal struct { + Type string + Value interface{} + } + jsonVal.Type = v.Type().String() + jsonVal.Value = v.AsInterface() + return json.Marshal(jsonVal) +} diff --git a/vendor/go.opentelemetry.io/otel/api/core/number.go b/vendor/go.opentelemetry.io/otel/api/core/number.go new file mode 100644 index 0000000000..6c67cdc563 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/core/number.go @@ -0,0 +1,646 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +//go:generate stringer -type=NumberKind + +import ( + "fmt" + "math" + "sync/atomic" +) + +// NumberKind describes the data type of the Number. +type NumberKind int8 + +const ( + // Int64NumberKind means that the Number stores int64. + Int64NumberKind NumberKind = iota + // Float64NumberKind means that the Number stores float64. + Float64NumberKind + // Uint64NumberKind means that the Number stores uint64. + Uint64NumberKind +) + +// Zero returns a zero value for a given NumberKind +func (k NumberKind) Zero() Number { + switch k { + case Int64NumberKind: + return NewInt64Number(0) + case Float64NumberKind: + return NewFloat64Number(0.) + case Uint64NumberKind: + return NewUint64Number(0) + default: + return Number(0) + } +} + +// Minimum returns the minimum representable value +// for a given NumberKind +func (k NumberKind) Minimum() Number { + switch k { + case Int64NumberKind: + return NewInt64Number(math.MinInt64) + case Float64NumberKind: + return NewFloat64Number(-1. * math.MaxFloat64) + case Uint64NumberKind: + return NewUint64Number(0) + default: + return Number(0) + } +} + +// Maximum returns the maximum representable value +// for a given NumberKind +func (k NumberKind) Maximum() Number { + switch k { + case Int64NumberKind: + return NewInt64Number(math.MaxInt64) + case Float64NumberKind: + return NewFloat64Number(math.MaxFloat64) + case Uint64NumberKind: + return NewUint64Number(math.MaxUint64) + default: + return Number(0) + } +} + +// Number represents either an integral or a floating point value. It +// needs to be accompanied with a source of NumberKind that describes +// the actual type of the value stored within Number. +type Number uint64 + +// - constructors + +// NewNumberFromRaw creates a new Number from a raw value. +func NewNumberFromRaw(r uint64) Number { + return Number(r) +} + +// NewInt64Number creates an integral Number. +func NewInt64Number(i int64) Number { + return NewNumberFromRaw(int64ToRaw(i)) +} + +// NewFloat64Number creates a floating point Number. +func NewFloat64Number(f float64) Number { + return NewNumberFromRaw(float64ToRaw(f)) +} + +// NewInt64Number creates an integral Number. +func NewUint64Number(u uint64) Number { + return NewNumberFromRaw(uint64ToRaw(u)) +} + +// - as x + +// AsNumber gets the Number. +func (n *Number) AsNumber() Number { + return *n +} + +// AsRaw gets the uninterpreted raw value. Might be useful for some +// atomic operations. +func (n *Number) AsRaw() uint64 { + return uint64(*n) +} + +// AsInt64 assumes that the value contains an int64 and returns it as +// such. +func (n *Number) AsInt64() int64 { + return rawToInt64(n.AsRaw()) +} + +// AsFloat64 assumes that the measurement value contains a float64 and +// returns it as such. +func (n *Number) AsFloat64() float64 { + return rawToFloat64(n.AsRaw()) +} + +// AsUint64 assumes that the value contains an uint64 and returns it +// as such. +func (n *Number) AsUint64() uint64 { + return rawToUint64(n.AsRaw()) +} + +// - as x atomic + +// AsNumberAtomic gets the Number atomically. +func (n *Number) AsNumberAtomic() Number { + return NewNumberFromRaw(n.AsRawAtomic()) +} + +// AsRawAtomic gets the uninterpreted raw value atomically. Might be +// useful for some atomic operations. +func (n *Number) AsRawAtomic() uint64 { + return atomic.LoadUint64(n.AsRawPtr()) +} + +// AsInt64Atomic assumes that the number contains an int64 and returns +// it as such atomically. +func (n *Number) AsInt64Atomic() int64 { + return atomic.LoadInt64(n.AsInt64Ptr()) +} + +// AsFloat64Atomic assumes that the measurement value contains a +// float64 and returns it as such atomically. +func (n *Number) AsFloat64Atomic() float64 { + return rawToFloat64(n.AsRawAtomic()) +} + +// AsUint64Atomic assumes that the number contains a uint64 and +// returns it as such atomically. +func (n *Number) AsUint64Atomic() uint64 { + return atomic.LoadUint64(n.AsUint64Ptr()) +} + +// - as x ptr + +// AsRawPtr gets the pointer to the raw, uninterpreted raw +// value. Might be useful for some atomic operations. +func (n *Number) AsRawPtr() *uint64 { + return (*uint64)(n) +} + +// AsInt64Ptr assumes that the number contains an int64 and returns a +// pointer to it. +func (n *Number) AsInt64Ptr() *int64 { + return rawPtrToInt64Ptr(n.AsRawPtr()) +} + +// AsFloat64Ptr assumes that the number contains a float64 and returns a +// pointer to it. +func (n *Number) AsFloat64Ptr() *float64 { + return rawPtrToFloat64Ptr(n.AsRawPtr()) +} + +// AsUint64Ptr assumes that the number contains a uint64 and returns a +// pointer to it. +func (n *Number) AsUint64Ptr() *uint64 { + return rawPtrToUint64Ptr(n.AsRawPtr()) +} + +// - coerce + +// CoerceToInt64 casts the number to int64. May result in +// data/precision loss. +func (n *Number) CoerceToInt64(kind NumberKind) int64 { + switch kind { + case Int64NumberKind: + return n.AsInt64() + case Float64NumberKind: + return int64(n.AsFloat64()) + case Uint64NumberKind: + return int64(n.AsUint64()) + default: + // you get what you deserve + return 0 + } +} + +// CoerceToFloat64 casts the number to float64. May result in +// data/precision loss. +func (n *Number) CoerceToFloat64(kind NumberKind) float64 { + switch kind { + case Int64NumberKind: + return float64(n.AsInt64()) + case Float64NumberKind: + return n.AsFloat64() + case Uint64NumberKind: + return float64(n.AsUint64()) + default: + // you get what you deserve + return 0 + } +} + +// CoerceToUint64 casts the number to uint64. May result in +// data/precision loss. +func (n *Number) CoerceToUint64(kind NumberKind) uint64 { + switch kind { + case Int64NumberKind: + return uint64(n.AsInt64()) + case Float64NumberKind: + return uint64(n.AsFloat64()) + case Uint64NumberKind: + return n.AsUint64() + default: + // you get what you deserve + return 0 + } +} + +// - set + +// SetNumber sets the number to the passed number. Both should be of +// the same kind. +func (n *Number) SetNumber(nn Number) { + *n.AsRawPtr() = nn.AsRaw() +} + +// SetRaw sets the number to the passed raw value. Both number and the +// raw number should represent the same kind. +func (n *Number) SetRaw(r uint64) { + *n.AsRawPtr() = r +} + +// SetInt64 assumes that the number contains an int64 and sets it to +// the passed value. +func (n *Number) SetInt64(i int64) { + *n.AsInt64Ptr() = i +} + +// SetFloat64 assumes that the number contains a float64 and sets it +// to the passed value. +func (n *Number) SetFloat64(f float64) { + *n.AsFloat64Ptr() = f +} + +// SetUint64 assumes that the number contains a uint64 and sets it to +// the passed value. +func (n *Number) SetUint64(u uint64) { + *n.AsUint64Ptr() = u +} + +// - set atomic + +// SetNumberAtomic sets the number to the passed number +// atomically. Both should be of the same kind. +func (n *Number) SetNumberAtomic(nn Number) { + atomic.StoreUint64(n.AsRawPtr(), nn.AsRaw()) +} + +// SetRawAtomic sets the number to the passed raw value +// atomically. Both number and the raw number should represent the +// same kind. +func (n *Number) SetRawAtomic(r uint64) { + atomic.StoreUint64(n.AsRawPtr(), r) +} + +// SetInt64Atomic assumes that the number contains an int64 and sets +// it to the passed value atomically. +func (n *Number) SetInt64Atomic(i int64) { + atomic.StoreInt64(n.AsInt64Ptr(), i) +} + +// SetFloat64Atomic assumes that the number contains a float64 and +// sets it to the passed value atomically. +func (n *Number) SetFloat64Atomic(f float64) { + atomic.StoreUint64(n.AsRawPtr(), float64ToRaw(f)) +} + +// SetUint64Atomic assumes that the number contains a uint64 and sets +// it to the passed value atomically. +func (n *Number) SetUint64Atomic(u uint64) { + atomic.StoreUint64(n.AsUint64Ptr(), u) +} + +// - swap + +// SwapNumber sets the number to the passed number and returns the old +// number. Both this number and the passed number should be of the +// same kind. +func (n *Number) SwapNumber(nn Number) Number { + old := *n + n.SetNumber(nn) + return old +} + +// SwapRaw sets the number to the passed raw value and returns the old +// raw value. Both number and the raw number should represent the same +// kind. +func (n *Number) SwapRaw(r uint64) uint64 { + old := n.AsRaw() + n.SetRaw(r) + return old +} + +// SwapInt64 assumes that the number contains an int64, sets it to the +// passed value and returns the old int64 value. +func (n *Number) SwapInt64(i int64) int64 { + old := n.AsInt64() + n.SetInt64(i) + return old +} + +// SwapFloat64 assumes that the number contains an float64, sets it to +// the passed value and returns the old float64 value. +func (n *Number) SwapFloat64(f float64) float64 { + old := n.AsFloat64() + n.SetFloat64(f) + return old +} + +// SwapUint64 assumes that the number contains an uint64, sets it to +// the passed value and returns the old uint64 value. +func (n *Number) SwapUint64(u uint64) uint64 { + old := n.AsUint64() + n.SetUint64(u) + return old +} + +// - swap atomic + +// SwapNumberAtomic sets the number to the passed number and returns +// the old number atomically. Both this number and the passed number +// should be of the same kind. +func (n *Number) SwapNumberAtomic(nn Number) Number { + return NewNumberFromRaw(atomic.SwapUint64(n.AsRawPtr(), nn.AsRaw())) +} + +// SwapRawAtomic sets the number to the passed raw value and returns +// the old raw value atomically. Both number and the raw number should +// represent the same kind. +func (n *Number) SwapRawAtomic(r uint64) uint64 { + return atomic.SwapUint64(n.AsRawPtr(), r) +} + +// SwapInt64Atomic assumes that the number contains an int64, sets it +// to the passed value and returns the old int64 value atomically. +func (n *Number) SwapInt64Atomic(i int64) int64 { + return atomic.SwapInt64(n.AsInt64Ptr(), i) +} + +// SwapFloat64Atomic assumes that the number contains an float64, sets +// it to the passed value and returns the old float64 value +// atomically. +func (n *Number) SwapFloat64Atomic(f float64) float64 { + return rawToFloat64(atomic.SwapUint64(n.AsRawPtr(), float64ToRaw(f))) +} + +// SwapUint64Atomic assumes that the number contains an uint64, sets +// it to the passed value and returns the old uint64 value atomically. +func (n *Number) SwapUint64Atomic(u uint64) uint64 { + return atomic.SwapUint64(n.AsUint64Ptr(), u) +} + +// - add + +// AddNumber assumes that this and the passed number are of the passed +// kind and adds the passed number to this number. +func (n *Number) AddNumber(kind NumberKind, nn Number) { + switch kind { + case Int64NumberKind: + n.AddInt64(nn.AsInt64()) + case Float64NumberKind: + n.AddFloat64(nn.AsFloat64()) + case Uint64NumberKind: + n.AddUint64(nn.AsUint64()) + } +} + +// AddRaw assumes that this number and the passed raw value are of the +// passed kind and adds the passed raw value to this number. +func (n *Number) AddRaw(kind NumberKind, r uint64) { + n.AddNumber(kind, NewNumberFromRaw(r)) +} + +// AddInt64 assumes that the number contains an int64 and adds the +// passed int64 to it. +func (n *Number) AddInt64(i int64) { + *n.AsInt64Ptr() += i +} + +// AddFloat64 assumes that the number contains a float64 and adds the +// passed float64 to it. +func (n *Number) AddFloat64(f float64) { + *n.AsFloat64Ptr() += f +} + +// AddUint64 assumes that the number contains a uint64 and adds the +// passed uint64 to it. +func (n *Number) AddUint64(u uint64) { + *n.AsUint64Ptr() += u +} + +// - add atomic + +// AddNumberAtomic assumes that this and the passed number are of the +// passed kind and adds the passed number to this number atomically. +func (n *Number) AddNumberAtomic(kind NumberKind, nn Number) { + switch kind { + case Int64NumberKind: + n.AddInt64Atomic(nn.AsInt64()) + case Float64NumberKind: + n.AddFloat64Atomic(nn.AsFloat64()) + case Uint64NumberKind: + n.AddUint64Atomic(nn.AsUint64()) + } +} + +// AddRawAtomic assumes that this number and the passed raw value are +// of the passed kind and adds the passed raw value to this number +// atomically. +func (n *Number) AddRawAtomic(kind NumberKind, r uint64) { + n.AddNumberAtomic(kind, NewNumberFromRaw(r)) +} + +// AddInt64Atomic assumes that the number contains an int64 and adds +// the passed int64 to it atomically. +func (n *Number) AddInt64Atomic(i int64) { + atomic.AddInt64(n.AsInt64Ptr(), i) +} + +// AddFloat64Atomic assumes that the number contains a float64 and +// adds the passed float64 to it atomically. +func (n *Number) AddFloat64Atomic(f float64) { + for { + o := n.AsFloat64Atomic() + if n.CompareAndSwapFloat64(o, o+f) { + break + } + } +} + +// AddUint64Atomic assumes that the number contains a uint64 and +// atomically adds the passed uint64 to it. +func (n *Number) AddUint64Atomic(u uint64) { + atomic.AddUint64(n.AsUint64Ptr(), u) +} + +// - compare and swap (atomic only) + +// CompareAndSwapNumber does the atomic CAS operation on this +// number. This number and passed old and new numbers should be of the +// same kind. +func (n *Number) CompareAndSwapNumber(on, nn Number) bool { + return atomic.CompareAndSwapUint64(n.AsRawPtr(), on.AsRaw(), nn.AsRaw()) +} + +// CompareAndSwapRaw does the atomic CAS operation on this +// number. This number and passed old and new raw values should be of +// the same kind. +func (n *Number) CompareAndSwapRaw(or, nr uint64) bool { + return atomic.CompareAndSwapUint64(n.AsRawPtr(), or, nr) +} + +// CompareAndSwapInt64 assumes that this number contains an int64 and +// does the atomic CAS operation on it. +func (n *Number) CompareAndSwapInt64(oi, ni int64) bool { + return atomic.CompareAndSwapInt64(n.AsInt64Ptr(), oi, ni) +} + +// CompareAndSwapFloat64 assumes that this number contains a float64 and +// does the atomic CAS operation on it. +func (n *Number) CompareAndSwapFloat64(of, nf float64) bool { + return atomic.CompareAndSwapUint64(n.AsRawPtr(), float64ToRaw(of), float64ToRaw(nf)) +} + +// CompareAndSwapUint64 assumes that this number contains a uint64 and +// does the atomic CAS operation on it. +func (n *Number) CompareAndSwapUint64(ou, nu uint64) bool { + return atomic.CompareAndSwapUint64(n.AsUint64Ptr(), ou, nu) +} + +// - compare + +// CompareNumber compares two Numbers given their kind. Both numbers +// should have the same kind. This returns: +// 0 if the numbers are equal +// -1 if the subject `n` is less than the argument `nn` +// +1 if the subject `n` is greater than the argument `nn` +func (n *Number) CompareNumber(kind NumberKind, nn Number) int { + switch kind { + case Int64NumberKind: + return n.CompareInt64(nn.AsInt64()) + case Float64NumberKind: + return n.CompareFloat64(nn.AsFloat64()) + case Uint64NumberKind: + return n.CompareUint64(nn.AsUint64()) + default: + // you get what you deserve + return 0 + } +} + +// CompareRaw compares two numbers, where one is input as a raw +// uint64, interpreting both values as a `kind` of number. +func (n *Number) CompareRaw(kind NumberKind, r uint64) int { + return n.CompareNumber(kind, NewNumberFromRaw(r)) +} + +// CompareInt64 assumes that the Number contains an int64 and performs +// a comparison between the value and the other value. It returns the +// typical result of the compare function: -1 if the value is less +// than the other, 0 if both are equal, 1 if the value is greater than +// the other. +func (n *Number) CompareInt64(i int64) int { + this := n.AsInt64() + if this < i { + return -1 + } else if this > i { + return 1 + } + return 0 +} + +// CompareFloat64 assumes that the Number contains a float64 and +// performs a comparison between the value and the other value. It +// returns the typical result of the compare function: -1 if the value +// is less than the other, 0 if both are equal, 1 if the value is +// greater than the other. +// +// Do not compare NaN values. +func (n *Number) CompareFloat64(f float64) int { + this := n.AsFloat64() + if this < f { + return -1 + } else if this > f { + return 1 + } + return 0 +} + +// CompareUint64 assumes that the Number contains an uint64 and performs +// a comparison between the value and the other value. It returns the +// typical result of the compare function: -1 if the value is less +// than the other, 0 if both are equal, 1 if the value is greater than +// the other. +func (n *Number) CompareUint64(u uint64) int { + this := n.AsUint64() + if this < u { + return -1 + } else if this > u { + return 1 + } + return 0 +} + +// - relations to zero + +// IsPositive returns true if the actual value is greater than zero. +func (n *Number) IsPositive(kind NumberKind) bool { + return n.compareWithZero(kind) > 0 +} + +// IsNegative returns true if the actual value is less than zero. +func (n *Number) IsNegative(kind NumberKind) bool { + return n.compareWithZero(kind) < 0 +} + +// IsZero returns true if the actual value is equal to zero. +func (n *Number) IsZero(kind NumberKind) bool { + return n.compareWithZero(kind) == 0 +} + +// - misc + +// Emit returns a string representation of the raw value of the +// Number. A %d is used for integral values, %f for floating point +// values. +func (n *Number) Emit(kind NumberKind) string { + switch kind { + case Int64NumberKind: + return fmt.Sprintf("%d", n.AsInt64()) + case Float64NumberKind: + return fmt.Sprintf("%f", n.AsFloat64()) + case Uint64NumberKind: + return fmt.Sprintf("%d", n.AsUint64()) + default: + return "" + } +} + +// AsInterface returns the number as an interface{}, typically used +// for NumberKind-correct JSON conversion. +func (n *Number) AsInterface(kind NumberKind) interface{} { + switch kind { + case Int64NumberKind: + return n.AsInt64() + case Float64NumberKind: + return n.AsFloat64() + case Uint64NumberKind: + return n.AsUint64() + default: + return math.NaN() + } +} + +// - private stuff + +func (n *Number) compareWithZero(kind NumberKind) int { + switch kind { + case Int64NumberKind: + return n.CompareInt64(0) + case Float64NumberKind: + return n.CompareFloat64(0.) + case Uint64NumberKind: + return n.CompareUint64(0) + default: + // you get what you deserve + return 0 + } +} diff --git a/vendor/go.opentelemetry.io/otel/api/core/numberkind_string.go b/vendor/go.opentelemetry.io/otel/api/core/numberkind_string.go new file mode 100644 index 0000000000..be3eea9be3 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/core/numberkind_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -type=NumberKind"; DO NOT EDIT. + +package core + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Int64NumberKind-0] + _ = x[Float64NumberKind-1] + _ = x[Uint64NumberKind-2] +} + +const _NumberKind_name = "Int64NumberKindFloat64NumberKindUint64NumberKind" + +var _NumberKind_index = [...]uint8{0, 15, 32, 48} + +func (i NumberKind) String() string { + if i < 0 || i >= NumberKind(len(_NumberKind_index)-1) { + return "NumberKind(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _NumberKind_name[_NumberKind_index[i]:_NumberKind_index[i+1]] +} diff --git a/vendor/go.opentelemetry.io/otel/api/core/rawhelpers.go b/vendor/go.opentelemetry.io/otel/api/core/rawhelpers.go new file mode 100644 index 0000000000..1e8c7252f8 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/core/rawhelpers.go @@ -0,0 +1,91 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + "math" + "unsafe" +) + +func boolToRaw(b bool) uint64 { + if b { + return 1 + } + return 0 +} + +func rawToBool(r uint64) bool { + return r != 0 +} + +func int64ToRaw(i int64) uint64 { + return uint64(i) +} + +func rawToInt64(r uint64) int64 { + return int64(r) +} + +func uint64ToRaw(u uint64) uint64 { + return u +} + +func rawToUint64(r uint64) uint64 { + return r +} + +func float64ToRaw(f float64) uint64 { + return math.Float64bits(f) +} + +func rawToFloat64(r uint64) float64 { + return math.Float64frombits(r) +} + +func int32ToRaw(i int32) uint64 { + return uint64(i) +} + +func rawToInt32(r uint64) int32 { + return int32(r) +} + +func uint32ToRaw(u uint32) uint64 { + return uint64(u) +} + +func rawToUint32(r uint64) uint32 { + return uint32(r) +} + +func float32ToRaw(f float32) uint64 { + return uint32ToRaw(math.Float32bits(f)) +} + +func rawToFloat32(r uint64) float32 { + return math.Float32frombits(rawToUint32(r)) +} + +func rawPtrToFloat64Ptr(r *uint64) *float64 { + return (*float64)(unsafe.Pointer(r)) +} + +func rawPtrToInt64Ptr(r *uint64) *int64 { + return (*int64)(unsafe.Pointer(r)) +} + +func rawPtrToUint64Ptr(r *uint64) *uint64 { + return r +} diff --git a/vendor/go.opentelemetry.io/otel/api/core/span_context.go b/vendor/go.opentelemetry.io/otel/api/core/span_context.go new file mode 100644 index 0000000000..6f6f2d6942 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/core/span_context.go @@ -0,0 +1,187 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + "bytes" + "encoding/hex" + "encoding/json" +) + +const ( + traceFlagsBitMaskSampled = byte(0x01) + traceFlagsBitMaskUnused = byte(0xFE) + + // TraceFlagsSampled is a byte with sampled bit set. It is a convenient value initializer + // for SpanContext TraceFlags field when a trace is sampled. + TraceFlagsSampled = traceFlagsBitMaskSampled + TraceFlagsUnused = traceFlagsBitMaskUnused + + ErrInvalidHexID errorConst = "trace-id and span-id can only contain [0-9a-f] characters, all lowercase" + + ErrInvalidTraceIDLength errorConst = "hex encoded trace-id must have length equals to 32" + ErrNilTraceID errorConst = "trace-id can't be all zero" + + ErrInvalidSpanIDLength errorConst = "hex encoded span-id must have length equals to 16" + ErrNilSpanID errorConst = "span-id can't be all zero" +) + +type errorConst string + +func (e errorConst) Error() string { + return string(e) +} + +// TraceID is a unique identity of a trace. +type TraceID [16]byte + +var nilTraceID TraceID +var _ json.Marshaler = nilTraceID + +// IsValid checks whether the trace ID is valid. A valid trace ID does +// not consist of zeros only. +func (t TraceID) IsValid() bool { + return !bytes.Equal(t[:], nilTraceID[:]) +} + +// MarshalJSON implements a custom marshal function to encode TraceID +// as a hex string. +func (t TraceID) MarshalJSON() ([]byte, error) { + return json.Marshal(hex.EncodeToString(t[:])) +} + +// SpanID is a unique identify of a span in a trace. +type SpanID [8]byte + +var nilSpanID SpanID +var _ json.Marshaler = nilSpanID + +// IsValid checks whether the span ID is valid. A valid span ID does +// not consist of zeros only. +func (s SpanID) IsValid() bool { + return !bytes.Equal(s[:], nilSpanID[:]) +} + +// MarshalJSON implements a custom marshal function to encode SpanID +// as a hex string. +func (s SpanID) MarshalJSON() ([]byte, error) { + return json.Marshal(hex.EncodeToString(s[:])) +} + +// TraceIDFromHex returns a TraceID from a hex string if it is compliant +// with the w3c trace-context specification. +// See more at https://www.w3.org/TR/trace-context/#trace-id +func TraceIDFromHex(h string) (TraceID, error) { + t := TraceID{} + if len(h) != 32 { + return t, ErrInvalidTraceIDLength + } + + if err := decodeHex(h, t[:]); err != nil { + return t, err + } + + if !t.IsValid() { + return t, ErrNilTraceID + } + return t, nil +} + +// SpanIDFromHex returns a SpanID from a hex string if it is compliant +// with the w3c trace-context specification. +// See more at https://www.w3.org/TR/trace-context/#parent-id +func SpanIDFromHex(h string) (SpanID, error) { + s := SpanID{} + if len(h) != 16 { + return s, ErrInvalidSpanIDLength + } + + if err := decodeHex(h, s[:]); err != nil { + return s, err + } + + if !s.IsValid() { + return s, ErrNilSpanID + } + return s, nil +} + +func decodeHex(h string, b []byte) error { + for _, r := range h { + switch { + case 'a' <= r && r <= 'f': + continue + case '0' <= r && r <= '9': + continue + default: + return ErrInvalidHexID + } + } + + decoded, err := hex.DecodeString(h) + if err != nil { + return err + } + + copy(b, decoded) + return nil +} + +// SpanContext contains basic information about the span - its trace +// ID, span ID and trace flags. +type SpanContext struct { + TraceID TraceID + SpanID SpanID + TraceFlags byte +} + +// EmptySpanContext is meant for internal use to return invalid span +// context during error conditions. +func EmptySpanContext() SpanContext { + return SpanContext{} +} + +// IsValid checks if the span context is valid. A valid span context +// has a valid trace ID and a valid span ID. +func (sc SpanContext) IsValid() bool { + return sc.HasTraceID() && sc.HasSpanID() +} + +// HasTraceID checks if the span context has a valid trace ID. +func (sc SpanContext) HasTraceID() bool { + return sc.TraceID.IsValid() +} + +// HasSpanID checks if the span context has a valid span ID. +func (sc SpanContext) HasSpanID() bool { + return sc.SpanID.IsValid() +} + +// SpanIDString returns a hex string representation of the span ID in +// the span context. +func (sc SpanContext) SpanIDString() string { + return hex.EncodeToString(sc.SpanID[:]) +} + +// TraceIDString returns a hex string representation of the trace ID +// in the span context. +func (sc SpanContext) TraceIDString() string { + return hex.EncodeToString(sc.TraceID[:]) +} + +// IsSampled check if the sampling bit in trace flags is set. +func (sc SpanContext) IsSampled() bool { + return sc.TraceFlags&traceFlagsBitMaskSampled == traceFlagsBitMaskSampled +} diff --git a/vendor/go.opentelemetry.io/otel/api/core/valuetype_string.go b/vendor/go.opentelemetry.io/otel/api/core/valuetype_string.go new file mode 100644 index 0000000000..35d2175cb4 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/core/valuetype_string.go @@ -0,0 +1,31 @@ +// Code generated by "stringer -type=ValueType"; DO NOT EDIT. + +package core + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[INVALID-0] + _ = x[BOOL-1] + _ = x[INT32-2] + _ = x[INT64-3] + _ = x[UINT32-4] + _ = x[UINT64-5] + _ = x[FLOAT32-6] + _ = x[FLOAT64-7] + _ = x[STRING-8] +} + +const _ValueType_name = "INVALIDBOOLINT32INT64UINT32UINT64FLOAT32FLOAT64STRING" + +var _ValueType_index = [...]uint8{0, 7, 11, 16, 21, 27, 33, 40, 47, 53} + +func (i ValueType) String() string { + if i < 0 || i >= ValueType(len(_ValueType_index)-1) { + return "ValueType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _ValueType_name[_ValueType_index[i]:_ValueType_index[i+1]] +} diff --git a/vendor/go.opentelemetry.io/otel/api/propagation/doc.go b/vendor/go.opentelemetry.io/otel/api/propagation/doc.go new file mode 100644 index 0000000000..d2839f5be4 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/propagation/doc.go @@ -0,0 +1,16 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package propagation contains interface definition for HTTP propagators. +package propagation // import "go.opentelemetry.io/otel/api/propagation" diff --git a/vendor/go.opentelemetry.io/otel/api/propagation/propagation.go b/vendor/go.opentelemetry.io/otel/api/propagation/propagation.go new file mode 100644 index 0000000000..8e2d38338b --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/propagation/propagation.go @@ -0,0 +1,143 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package propagation + +import ( + "context" +) + +// HTTPSupplier is an interface that specifies methods to retrieve and +// store a single value for a key to an associated carrier. It is +// implemented by http.Headers. +type HTTPSupplier interface { + // Get method retrieves a single value for a given key. + Get(key string) string + // Set method stores a single value for a given key. Note that + // this should not be appending a value to some array, but + // rather overwrite the old value. + Set(key string, value string) +} + +// HTTPExtractor extracts information from a HTTPSupplier into a +// context. +type HTTPExtractor interface { + // Extract method retrieves encoded information using supplier + // from the associated carrier, decodes it and creates a new + // context containing the decoded information. + // + // Information can be a correlation context or a remote span + // context. In case of span context, the propagator should + // store it in the context using + // trace.ContextWithRemoteSpanContext. In case of correlation + // context, the propagator should use correlation.WithMap to + // store it in the context. + Extract(context.Context, HTTPSupplier) context.Context +} + +// HTTPInjector injects information into a HTTPSupplier. +type HTTPInjector interface { + // Inject method retrieves information from the context, + // encodes it into propagator specific format and then injects + // the encoded information using supplier into an associated + // carrier. + Inject(context.Context, HTTPSupplier) +} + +// Config contains the current set of extractors and injectors. +type Config struct { + httpEx []HTTPExtractor + httpIn []HTTPInjector +} + +// Propagators is the interface to a set of injectors and extractors +// for all supported carrier formats. It can be used to chain multiple +// propagators into a single entity. +type Propagators interface { + // HTTPExtractors returns the configured extractors. + HTTPExtractors() []HTTPExtractor + + // HTTPInjectors returns the configured injectors. + HTTPInjectors() []HTTPInjector +} + +// HTTPPropagator is the interface to inject to and extract from +// HTTPSupplier. +type HTTPPropagator interface { + HTTPInjector + HTTPExtractor + + // GetAllKeys returns the HTTP header names used. + GetAllKeys() []string +} + +// Option support passing configuration parameters to New(). +type Option func(*Config) + +// propagators is the default Propagators implementation. +type propagators struct { + config Config +} + +// New returns a standard Propagators implementation. +func New(options ...Option) Propagators { + config := Config{} + for _, opt := range options { + opt(&config) + } + return &propagators{ + config: config, + } +} + +// WithInjectors appends to the optional injector set. +func WithInjectors(inj ...HTTPInjector) Option { + return func(config *Config) { + config.httpIn = append(config.httpIn, inj...) + } +} + +// WithExtractors appends to the optional extractor set. +func WithExtractors(ext ...HTTPExtractor) Option { + return func(config *Config) { + config.httpEx = append(config.httpEx, ext...) + } +} + +// HTTPExtractors implements Propagators. +func (p *propagators) HTTPExtractors() []HTTPExtractor { + return p.config.httpEx +} + +// HTTPInjectors implements Propagators. +func (p *propagators) HTTPInjectors() []HTTPInjector { + return p.config.httpIn +} + +// ExtractHTTP applies props.HTTPExtractors() to the passed context +// and the supplier and returns the combined result context. +func ExtractHTTP(ctx context.Context, props Propagators, supplier HTTPSupplier) context.Context { + for _, ex := range props.HTTPExtractors() { + ctx = ex.Extract(ctx, supplier) + } + return ctx +} + +// InjectHTTP applies props.HTTPInjectors() to the passed context and +// the supplier. +func InjectHTTP(ctx context.Context, props Propagators, supplier HTTPSupplier) { + for _, in := range props.HTTPInjectors() { + in.Inject(ctx, supplier) + } +} diff --git a/vendor/go.opentelemetry.io/otel/api/trace/always_off_sampler.go b/vendor/go.opentelemetry.io/otel/api/trace/always_off_sampler.go new file mode 100644 index 0000000000..116afaa70e --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/trace/always_off_sampler.go @@ -0,0 +1,55 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "go.opentelemetry.io/otel/api/core" +) + +const ( + alwaysOffSamplerDescription = "AlwaysOffSampler" +) + +var alwaysOffSamplerDecision = Decision{Sampled: false} + +type alwaysOffSampler struct{} + +// ShouldSample implements Sampler interface. +// It always returns a Decision with Sampled value set to false +// and with Attributes set to an empty slice. +func (ns alwaysOffSampler) ShouldSample( + _ core.SpanContext, + _ bool, + _ core.TraceID, + _ core.SpanID, + _ string, + _ SpanKind, + _ []core.KeyValue, + _ []Link, +) Decision { + return alwaysOffSamplerDecision +} + +// Description implements Sampler interface. +// It returns the description of this sampler. +func (ns alwaysOffSampler) Description() string { + return alwaysOffSamplerDescription +} + +var _ Sampler = alwaysOffSampler{} + +func AlwaysOffSampler() Sampler { + return alwaysOffSampler{} +} diff --git a/vendor/go.opentelemetry.io/otel/api/trace/always_on_sampler.go b/vendor/go.opentelemetry.io/otel/api/trace/always_on_sampler.go new file mode 100644 index 0000000000..ff2c1b330d --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/trace/always_on_sampler.go @@ -0,0 +1,55 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "go.opentelemetry.io/otel/api/core" +) + +const ( + alwaysOnSamplerDescription = "AlwaysOnSampler" +) + +var alwaysOnSamplerDecision = Decision{Sampled: true} + +type alwaysOnSampler struct{} + +// ShouldSample implements Sampler interface. +// It always returns a Decision with Sampled value set to true +// and with Attributes set to an empty slice. +func (as alwaysOnSampler) ShouldSample( + _ core.SpanContext, + _ bool, + _ core.TraceID, + _ core.SpanID, + _ string, + _ SpanKind, + _ []core.KeyValue, + _ []Link, +) Decision { + return alwaysOnSamplerDecision +} + +// Description implements Sampler interface. +// It returns the description of this sampler. +func (as alwaysOnSampler) Description() string { + return alwaysOnSamplerDescription +} + +var _ Sampler = alwaysOnSampler{} + +func AlwaysOnSampler() Sampler { + return alwaysOnSampler{} +} diff --git a/vendor/go.opentelemetry.io/otel/api/trace/api.go b/vendor/go.opentelemetry.io/otel/api/trace/api.go new file mode 100644 index 0000000000..9e0031a08d --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/trace/api.go @@ -0,0 +1,259 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "context" + "time" + + "google.golang.org/grpc/codes" + + "go.opentelemetry.io/otel/api/core" +) + +type Provider interface { + // Tracer creates a named tracer that implements Tracer interface. + // If the name is an empty string then provider uses default name. + Tracer(name string) Tracer +} + +type Tracer interface { + // Start a span. + Start(ctx context.Context, spanName string, opts ...StartOption) (context.Context, Span) + + // WithSpan wraps the execution of the fn function with a span. + // It starts a new span, sets it as an active span in the context, + // executes the fn function and closes the span before returning the result of fn. + WithSpan( + ctx context.Context, + spanName string, + fn func(ctx context.Context) error, + opts ...StartOption, + ) error +} + +// EndConfig provides options to set properties of span at the time of ending +// the span. +type EndConfig struct { + EndTime time.Time +} + +// EndOption applies changes to EndConfig that sets options when the span is ended. +type EndOption func(*EndConfig) + +// WithEndTime sets the end time of the span to provided time t, when it is ended. +func WithEndTime(t time.Time) EndOption { + return func(c *EndConfig) { + c.EndTime = t + } +} + +// ErrorConfig provides options to set properties of an error event at the time it is recorded. +type ErrorConfig struct { + Timestamp time.Time + StatusCode codes.Code +} + +// ErrorOption applies changes to ErrorConfig that sets options when an error event is recorded. +type ErrorOption func(*ErrorConfig) + +// WithErrorTime sets the time at which the error event should be recorded. +func WithErrorTime(t time.Time) ErrorOption { + return func(c *ErrorConfig) { + c.Timestamp = t + } +} + +// WithErrorStatus indicates the span status that should be set when recording an error event. +func WithErrorStatus(s codes.Code) ErrorOption { + return func(c *ErrorConfig) { + c.StatusCode = s + } +} + +type Span interface { + // Tracer returns tracer used to create this span. Tracer cannot be nil. + Tracer() Tracer + + // End completes the span. No updates are allowed to span after it + // ends. The only exception is setting status of the span. + End(options ...EndOption) + + // AddEvent adds an event to the span. + AddEvent(ctx context.Context, name string, attrs ...core.KeyValue) + // AddEventWithTimestamp adds an event with a custom timestamp + // to the span. + AddEventWithTimestamp(ctx context.Context, timestamp time.Time, name string, attrs ...core.KeyValue) + + // IsRecording returns true if the span is active and recording events is enabled. + IsRecording() bool + + // RecordError records an error as a span event. + RecordError(ctx context.Context, err error, opts ...ErrorOption) + + // SpanContext returns span context of the span. Returned SpanContext is usable + // even after the span ends. + SpanContext() core.SpanContext + + // SetStatus sets the status of the span in the form of a code + // and a message. SetStatus overrides the value of previous + // calls to SetStatus on the Span. + // + // The default span status is OK, so it is not necessary to + // explicitly set an OK status on successful Spans unless it + // is to add an OK message or to override a previous status on the Span. + SetStatus(codes.Code, string) + + // SetName sets the name of the span. + SetName(name string) + + // Set span attributes + SetAttributes(...core.KeyValue) +} + +// StartOption applies changes to StartConfig that sets options at span start time. +type StartOption func(*StartConfig) + +// StartConfig provides options to set properties of span at the time of starting +// a new span. +type StartConfig struct { + Attributes []core.KeyValue + StartTime time.Time + Links []Link + Record bool + NewRoot bool + SpanKind SpanKind +} + +// Link is used to establish relationship between two spans within the same Trace or +// across different Traces. Few examples of Link usage. +// 1. Batch Processing: A batch of elements may contain elements associated with one +// or more traces/spans. Since there can only be one parent SpanContext, Link is +// used to keep reference to SpanContext of all elements in the batch. +// 2. Public Endpoint: A SpanContext in incoming client request on a public endpoint +// is untrusted from service provider perspective. In such case it is advisable to +// start a new trace with appropriate sampling decision. +// However, it is desirable to associate incoming SpanContext to new trace initiated +// on service provider side so two traces (from Client and from Service Provider) can +// be correlated. +type Link struct { + core.SpanContext + Attributes []core.KeyValue +} + +// SpanKind represents the role of a Span inside a Trace. Often, this defines how a Span +// will be processed and visualized by various backends. +type SpanKind int + +const ( + // As a convenience, these match the proto definition, see + // opentelemetry/proto/trace/v1/trace.proto + // + // The unspecified value is not a valid `SpanKind`. Use + // `ValidateSpanKind()` to coerce a span kind to a valid + // value. + SpanKindUnspecified SpanKind = 0 + SpanKindInternal SpanKind = 1 + SpanKindServer SpanKind = 2 + SpanKindClient SpanKind = 3 + SpanKindProducer SpanKind = 4 + SpanKindConsumer SpanKind = 5 +) + +// ValidateSpanKind returns a valid span kind value. This will coerce +// invalid values into the default value, SpanKindInternal. +func ValidateSpanKind(spanKind SpanKind) SpanKind { + switch spanKind { + case SpanKindInternal, + SpanKindServer, + SpanKindClient, + SpanKindProducer, + SpanKindConsumer: + // valid + return spanKind + default: + return SpanKindInternal + } +} + +// String returns the specified name of the SpanKind in lower-case. +func (sk SpanKind) String() string { + switch sk { + case SpanKindInternal: + return "internal" + case SpanKindServer: + return "server" + case SpanKindClient: + return "client" + case SpanKindProducer: + return "producer" + case SpanKindConsumer: + return "consumer" + default: + return "unspecified" + } +} + +// WithStartTime sets the start time of the span to provided time t, when it is started. +// In absence of this option, wall clock time is used as start time. +// This option is typically used when starting of the span is delayed. +func WithStartTime(t time.Time) StartOption { + return func(c *StartConfig) { + c.StartTime = t + } +} + +// WithAttributes sets attributes to span. These attributes provides additional +// data about the span. +// Multiple `WithAttributes` options appends the attributes preserving the order. +func WithAttributes(attrs ...core.KeyValue) StartOption { + return func(c *StartConfig) { + c.Attributes = append(c.Attributes, attrs...) + } +} + +// WithRecord specifies that the span should be recorded. +// Note that the implementation may still override this preference, +// e.g., if the span is a child in an unsampled trace. +func WithRecord() StartOption { + return func(c *StartConfig) { + c.Record = true + } +} + +// WithNewRoot specifies that the current span or remote span context +// in context passed to `Start` should be ignored when deciding about +// a parent, which effectively means creating a span with new trace +// ID. The current span and the remote span context may be added as +// links to the span by the implementation. +func WithNewRoot() StartOption { + return func(c *StartConfig) { + c.NewRoot = true + } +} + +// LinkedTo allows instantiating a Span with initial Links. +func LinkedTo(sc core.SpanContext, attrs ...core.KeyValue) StartOption { + return func(c *StartConfig) { + c.Links = append(c.Links, Link{sc, attrs}) + } +} + +// WithSpanKind specifies the role a Span on a Trace. +func WithSpanKind(sk SpanKind) StartOption { + return func(c *StartConfig) { + c.SpanKind = sk + } +} diff --git a/vendor/go.opentelemetry.io/otel/api/trace/b3_propagator.go b/vendor/go.opentelemetry.io/otel/api/trace/b3_propagator.go new file mode 100644 index 0000000000..203298e230 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/trace/b3_propagator.go @@ -0,0 +1,207 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "context" + "fmt" + "strings" + + "go.opentelemetry.io/otel/api/core" + "go.opentelemetry.io/otel/api/propagation" +) + +const ( + B3SingleHeader = "X-B3" + B3DebugFlagHeader = "X-B3-Flags" + B3TraceIDHeader = "X-B3-TraceId" + B3SpanIDHeader = "X-B3-SpanId" + B3SampledHeader = "X-B3-Sampled" + B3ParentSpanIDHeader = "X-B3-ParentSpanId" +) + +// B3 propagator serializes core.SpanContext to/from B3 Headers. +// This propagator supports both version of B3 headers, +// 1. Single Header : +// X-B3: {TraceId}-{SpanId}-{SamplingState}-{ParentSpanId} +// 2. Multiple Headers: +// X-B3-TraceId: {TraceId} +// X-B3-ParentSpanId: {ParentSpanId} +// X-B3-SpanId: {SpanId} +// X-B3-Sampled: {SamplingState} +// X-B3-Flags: {DebugFlag} +// +// If SingleHeader is set to true then X-B3 header is used to inject and extract. Otherwise, +// separate headers are used to inject and extract. +type B3 struct { + SingleHeader bool +} + +var _ propagation.HTTPPropagator = B3{} + +func (b3 B3) Inject(ctx context.Context, supplier propagation.HTTPSupplier) { + sc := SpanFromContext(ctx).SpanContext() + if !sc.IsValid() { + return + } + if b3.SingleHeader { + sampled := sc.TraceFlags & core.TraceFlagsSampled + supplier.Set(B3SingleHeader, + fmt.Sprintf("%s-%.16x-%.1d", sc.TraceIDString(), sc.SpanID, sampled)) + } else { + supplier.Set(B3TraceIDHeader, sc.TraceIDString()) + supplier.Set(B3SpanIDHeader, + fmt.Sprintf("%.16x", sc.SpanID)) + + var sampled string + if sc.IsSampled() { + sampled = "1" + } else { + sampled = "0" + } + supplier.Set(B3SampledHeader, sampled) + } +} + +// Extract retrieves B3 Headers from the supplier +func (b3 B3) Extract(ctx context.Context, supplier propagation.HTTPSupplier) context.Context { + var sc core.SpanContext + if b3.SingleHeader { + sc = b3.extractSingleHeader(supplier) + } else { + sc = b3.extract(supplier) + } + return ContextWithRemoteSpanContext(ctx, sc) +} + +func (b3 B3) extract(supplier propagation.HTTPSupplier) core.SpanContext { + tid, err := core.TraceIDFromHex(supplier.Get(B3TraceIDHeader)) + if err != nil { + return core.EmptySpanContext() + } + sid, err := core.SpanIDFromHex(supplier.Get(B3SpanIDHeader)) + if err != nil { + return core.EmptySpanContext() + } + sampled, ok := b3.extractSampledState(supplier.Get(B3SampledHeader)) + if !ok { + return core.EmptySpanContext() + } + + debug, ok := b3.extracDebugFlag(supplier.Get(B3DebugFlagHeader)) + if !ok { + return core.EmptySpanContext() + } + if debug == core.TraceFlagsSampled { + sampled = core.TraceFlagsSampled + } + + sc := core.SpanContext{ + TraceID: tid, + SpanID: sid, + TraceFlags: sampled, + } + + if !sc.IsValid() { + return core.EmptySpanContext() + } + + return sc +} + +func (b3 B3) extractSingleHeader(supplier propagation.HTTPSupplier) core.SpanContext { + h := supplier.Get(B3SingleHeader) + if h == "" || h == "0" { + return core.EmptySpanContext() + } + sc := core.SpanContext{} + parts := strings.Split(h, "-") + l := len(parts) + if l > 4 { + return core.EmptySpanContext() + } + + if l < 2 { + return core.EmptySpanContext() + } + + var err error + sc.TraceID, err = core.TraceIDFromHex(parts[0]) + if err != nil { + return core.EmptySpanContext() + } + + sc.SpanID, err = core.SpanIDFromHex(parts[1]) + if err != nil { + return core.EmptySpanContext() + } + + if l > 2 { + var ok bool + sc.TraceFlags, ok = b3.extractSampledState(parts[2]) + if !ok { + return core.EmptySpanContext() + } + } + if l == 4 { + _, err = core.SpanIDFromHex(parts[3]) + if err != nil { + return core.EmptySpanContext() + } + } + + if !sc.IsValid() { + return core.EmptySpanContext() + } + + return sc +} + +// extractSampledState parses the value of the X-B3-Sampled b3Header. +func (b3 B3) extractSampledState(sampled string) (flag byte, ok bool) { + switch sampled { + case "", "0": + return 0, true + case "1": + return core.TraceFlagsSampled, true + case "true": + if !b3.SingleHeader { + return core.TraceFlagsSampled, true + } + case "d": + if b3.SingleHeader { + return core.TraceFlagsSampled, true + } + } + return 0, false +} + +// extracDebugFlag parses the value of the X-B3-Sampled b3Header. +func (b3 B3) extracDebugFlag(debug string) (flag byte, ok bool) { + switch debug { + case "", "0": + return 0, true + case "1": + return core.TraceFlagsSampled, true + } + return 0, false +} + +func (b3 B3) GetAllKeys() []string { + if b3.SingleHeader { + return []string{B3SingleHeader} + } + return []string{B3TraceIDHeader, B3SpanIDHeader, B3SampledHeader} +} diff --git a/vendor/go.opentelemetry.io/otel/api/trace/context.go b/vendor/go.opentelemetry.io/otel/api/trace/context.go new file mode 100644 index 0000000000..27ca98fddc --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/trace/context.go @@ -0,0 +1,57 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "context" + + "go.opentelemetry.io/otel/api/core" +) + +type traceContextKeyType int + +const ( + currentSpanKey traceContextKeyType = iota + remoteContextKey +) + +// ContextWithSpan creates a new context with a current span set to +// the passed span. +func ContextWithSpan(ctx context.Context, span Span) context.Context { + return context.WithValue(ctx, currentSpanKey, span) +} + +// SpanFromContext returns the current span stored in the context. +func SpanFromContext(ctx context.Context) Span { + if span, has := ctx.Value(currentSpanKey).(Span); has { + return span + } + return NoopSpan{} +} + +// ContextWithRemoteSpanContext creates a new context with a remote +// span context set to the passed span context. +func ContextWithRemoteSpanContext(ctx context.Context, sc core.SpanContext) context.Context { + return context.WithValue(ctx, remoteContextKey, sc) +} + +// RemoteSpanContextFromContext returns the remote span context stored +// in the context. +func RemoteSpanContextFromContext(ctx context.Context) core.SpanContext { + if sc, ok := ctx.Value(remoteContextKey).(core.SpanContext); ok { + return sc + } + return core.EmptySpanContext() +} diff --git a/vendor/go.opentelemetry.io/otel/api/trace/doc.go b/vendor/go.opentelemetry.io/otel/api/trace/doc.go new file mode 100644 index 0000000000..56c13f0494 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/trace/doc.go @@ -0,0 +1,15 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace // import "go.opentelemetry.io/otel/api/trace" diff --git a/vendor/go.opentelemetry.io/otel/api/trace/noop_span.go b/vendor/go.opentelemetry.io/otel/api/trace/noop_span.go new file mode 100644 index 0000000000..03e5e35ec8 --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/trace/noop_span.go @@ -0,0 +1,76 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "context" + "time" + + "google.golang.org/grpc/codes" + + "go.opentelemetry.io/otel/api/core" +) + +type NoopSpan struct { +} + +var _ Span = (*NoopSpan)(nil) + +// SpanContext returns an invalid span context. +func (NoopSpan) SpanContext() core.SpanContext { + return core.EmptySpanContext() +} + +// IsRecording always returns false for NoopSpan. +func (NoopSpan) IsRecording() bool { + return false +} + +// SetStatus does nothing. +func (NoopSpan) SetStatus(status codes.Code, msg string) { +} + +// SetError does nothing. +func (NoopSpan) SetError(v bool) { +} + +// SetAttributes does nothing. +func (NoopSpan) SetAttributes(attributes ...core.KeyValue) { +} + +// End does nothing. +func (NoopSpan) End(options ...EndOption) { +} + +// RecordError does nothing. +func (NoopSpan) RecordError(ctx context.Context, err error, opts ...ErrorOption) { +} + +// Tracer returns noop implementation of Tracer. +func (NoopSpan) Tracer() Tracer { + return NoopTracer{} +} + +// AddEvent does nothing. +func (NoopSpan) AddEvent(ctx context.Context, name string, attrs ...core.KeyValue) { +} + +// AddEventWithTimestamp does nothing. +func (NoopSpan) AddEventWithTimestamp(ctx context.Context, timestamp time.Time, name string, attrs ...core.KeyValue) { +} + +// SetName does nothing. +func (NoopSpan) SetName(name string) { +} diff --git a/vendor/go.opentelemetry.io/otel/api/trace/noop_trace.go b/vendor/go.opentelemetry.io/otel/api/trace/noop_trace.go new file mode 100644 index 0000000000..63efdeaf5e --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/trace/noop_trace.go @@ -0,0 +1,34 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "context" +) + +type NoopTracer struct{} + +var _ Tracer = NoopTracer{} + +// WithSpan wraps around execution of func with noop span. +func (t NoopTracer) WithSpan(ctx context.Context, name string, body func(context.Context) error, opts ...StartOption) error { + return body(ctx) +} + +// Start starts a noop span. +func (NoopTracer) Start(ctx context.Context, name string, opts ...StartOption) (context.Context, Span) { + span := NoopSpan{} + return ContextWithSpan(ctx, span), span +} diff --git a/vendor/go.opentelemetry.io/otel/api/trace/noop_trace_provider.go b/vendor/go.opentelemetry.io/otel/api/trace/noop_trace_provider.go new file mode 100644 index 0000000000..616c23a44a --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/trace/noop_trace_provider.go @@ -0,0 +1,24 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +type NoopProvider struct{} + +var _ Provider = NoopProvider{} + +// Tracer returns noop implementation of Tracer. +func (p NoopProvider) Tracer(name string) Tracer { + return NoopTracer{} +} diff --git a/vendor/go.opentelemetry.io/otel/api/trace/sampler.go b/vendor/go.opentelemetry.io/otel/api/trace/sampler.go new file mode 100644 index 0000000000..aed57cf40d --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/trace/sampler.go @@ -0,0 +1,47 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import "go.opentelemetry.io/otel/api/core" + +type Sampler interface { + // ShouldSample returns a Decision that contains a decision whether to sample + // or not sample the span to be created. Decision is based on a Sampler specific + // algorithm that takes into account one or more input parameters. + ShouldSample( + sc core.SpanContext, + remote bool, + traceID core.TraceID, + spanID core.SpanID, + spanName string, + spanKind SpanKind, + attributes []core.KeyValue, + links []Link, + ) Decision + + // Description returns of the sampler. It contains its name or short description + // and its configured properties. + // For example 'ProbabilitySampler:{0.00001}' + Description() string +} + +type Decision struct { + // Sampled is set true if the span should be sampled. + Sampled bool + + // Attributes provides insight into Sample r's decision process. + // It could be empty slice or nil if no attributes are recorded by the sampler. + Attributes []core.KeyValue +} diff --git a/vendor/go.opentelemetry.io/otel/api/trace/trace_context_propagator.go b/vendor/go.opentelemetry.io/otel/api/trace/trace_context_propagator.go new file mode 100644 index 0000000000..75c2af08aa --- /dev/null +++ b/vendor/go.opentelemetry.io/otel/api/trace/trace_context_propagator.go @@ -0,0 +1,132 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package trace + +import ( + "context" + "encoding/hex" + "fmt" + "regexp" + "strings" + + "go.opentelemetry.io/otel/api/core" + "go.opentelemetry.io/otel/api/propagation" +) + +const ( + supportedVersion = 0 + maxVersion = 254 + traceparentHeader = "Traceparent" +) + +// TraceContext propagates SpanContext in W3C TraceContext format. +//nolint:golint +type TraceContext struct{} + +var _ propagation.HTTPPropagator = TraceContext{} +var traceCtxRegExp = regexp.MustCompile("^[0-9a-f]{2}-[a-f0-9]{32}-[a-f0-9]{16}-[a-f0-9]{2}-?") + +// DefaultHTTPPropagator returns the default trace HTTP propagator. +func DefaultHTTPPropagator() propagation.HTTPPropagator { + return TraceContext{} +} + +func (TraceContext) Inject(ctx context.Context, supplier propagation.HTTPSupplier) { + sc := SpanFromContext(ctx).SpanContext() + if !sc.IsValid() { + return + } + h := fmt.Sprintf("%.2x-%s-%.16x-%.2x", + supportedVersion, + sc.TraceIDString(), + sc.SpanID, + sc.TraceFlags&core.TraceFlagsSampled) + supplier.Set(traceparentHeader, h) +} + +func (tc TraceContext) Extract(ctx context.Context, supplier propagation.HTTPSupplier) context.Context { + return ContextWithRemoteSpanContext(ctx, tc.extract(supplier)) +} + +func (TraceContext) extract(supplier propagation.HTTPSupplier) core.SpanContext { + h := supplier.Get(traceparentHeader) + if h == "" { + return core.EmptySpanContext() + } + + h = strings.Trim(h, "-") + if !traceCtxRegExp.MatchString(h) { + return core.EmptySpanContext() + } + + sections := strings.Split(h, "-") + if len(sections) < 4 { + return core.EmptySpanContext() + } + + if len(sections[0]) != 2 { + return core.EmptySpanContext() + } + ver, err := hex.DecodeString(sections[0]) + if err != nil { + return core.EmptySpanContext() + } + version := int(ver[0]) + if version > maxVersion { + return core.EmptySpanContext() + } + + if version == 0 && len(sections) != 4 { + return core.EmptySpanContext() + } + + if len(sections[1]) != 32 { + return core.EmptySpanContext() + } + + var sc core.SpanContext + + sc.TraceID, err = core.TraceIDFromHex(sections[1][:32]) + if err != nil { + return core.EmptySpanContext() + } + + if len(sections[2]) != 16 { + return core.EmptySpanContext() + } + sc.SpanID, err = core.SpanIDFromHex(sections[2]) + if err != nil { + return core.EmptySpanContext() + } + + if len(sections[3]) != 2 { + return core.EmptySpanContext() + } + opts, err := hex.DecodeString(sections[3]) + if err != nil || len(opts) < 1 || (version == 0 && opts[0] > 2) { + return core.EmptySpanContext() + } + sc.TraceFlags = opts[0] &^ core.TraceFlagsUnused + + if !sc.IsValid() { + return core.EmptySpanContext() + } + + return sc +} + +func (TraceContext) GetAllKeys() []string { + return []string{traceparentHeader} +} diff --git a/vendor/knative.dev/eventing/test/conformance/helpers/broker_tracing_test_helper.go b/vendor/knative.dev/eventing/test/conformance/helpers/broker_tracing_test_helper.go new file mode 100644 index 0000000000..cf9eda5ddc --- /dev/null +++ b/vendor/knative.dev/eventing/test/conformance/helpers/broker_tracing_test_helper.go @@ -0,0 +1,239 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helpers + +import ( + "fmt" + "strings" + "testing" + + ce "github.com/cloudevents/sdk-go/v1" + "github.com/openzipkin/zipkin-go/model" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/uuid" + + "knative.dev/eventing/pkg/apis/eventing" + "knative.dev/eventing/pkg/apis/eventing/v1alpha1" + tracinghelper "knative.dev/eventing/test/conformance/helpers/tracing" + "knative.dev/eventing/test/lib" + "knative.dev/eventing/test/lib/cloudevents" + "knative.dev/eventing/test/lib/resources" +) + +// BrokerTracingTestHelperWithChannelTestRunner runs the Broker tracing tests for all Channels in +// the ChannelTestRunner. +func BrokerTracingTestHelperWithChannelTestRunner( + t *testing.T, + brokerClass string, + channelTestRunner lib.ChannelTestRunner, + setupClient lib.SetupClientOption, +) { + channelTestRunner.RunTests(t, lib.FeatureBasic, func(t *testing.T, channel metav1.TypeMeta) { + BrokerTracingTestHelper(t, brokerClass, channel, setupClient) + }) +} + +// BrokerTracingTestHelper runs the Broker tracing test using the given TypeMeta. +func BrokerTracingTestHelper(t *testing.T, brokerClass string, channel metav1.TypeMeta, setupClient lib.SetupClientOption) { + testCases := map[string]TracingTestCase{ + "includes incoming trace id": { + IncomingTraceId: true, + }, + } + + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + tracingTest(t, setupClient, setupBrokerTracing(brokerClass), channel, tc) + }) + } +} + +// setupBrokerTracing is the general setup for TestBrokerTracing. It creates the following: +// 1. Broker. +// 2. Trigger on 'foo' events -> K8s Service -> transformer Pod (which replies with a 'bar' event). +// 3. Trigger on 'bar' events -> K8s Service -> eventdetails Pod. +// 4. Sender Pod which sends a 'foo' event. +// It returns a string that is expected to be sent by the SendEvents Pod and should be present in +// the LogEvents Pod logs. +func setupBrokerTracing(brokerClass string) SetupInfrastructureFunc { + const ( + etTransformer = "transformer" + etLogger = "logger" + defaultCMPName = "eventing" + ) + return func( + t *testing.T, + channel *metav1.TypeMeta, + client *lib.Client, + loggerPodName string, + tc TracingTestCase, + ) (tracinghelper.TestSpanTree, lib.EventMatchFunc) { + // Create the Broker. + if brokerClass == eventing.ChannelBrokerClassValue { + // create required RBAC resources including ServiceAccounts and ClusterRoleBindings for Brokers + client.CreateConfigMapPropagationOrFail(defaultCMPName) + client.CreateRBACResourcesForBrokers() + } + broker := client.CreateBrokerOrFail( + "br", + resources.WithBrokerClassForBroker(brokerClass), + resources.WithChannelTemplateForBroker(channel), + ) + + // Create a logger (EventRecord) Pod and a K8s Service that points to it. + logPod := resources.EventRecordPod(loggerPodName) + client.CreatePodOrFail(logPod, lib.WithService(loggerPodName)) + + // Create a Trigger that receives events (type=bar) and sends them to the logger Pod. + client.CreateTriggerOrFail( + "logger", + resources.WithBroker(broker.Name), + resources.WithAttributesTriggerFilter(v1alpha1.TriggerAnyFilter, etLogger, map[string]interface{}{}), + resources.WithSubscriberServiceRefForTrigger(loggerPodName), + ) + + // Create a transformer (EventTransfrmer) Pod that replies with the same event as the input, + // except the reply's event's type is changed to bar. + eventTransformerPod := resources.EventTransformationPod("transformer", &cloudevents.CloudEvent{ + EventContextV1: ce.EventContextV1{ + Type: etLogger, + }, + }) + client.CreatePodOrFail(eventTransformerPod, lib.WithService(eventTransformerPod.Name)) + + // Create a Trigger that receives events (type=foo) and sends them to the transformer Pod. + client.CreateTriggerOrFail( + "transformer", + resources.WithBroker(broker.Name), + resources.WithAttributesTriggerFilter(v1alpha1.TriggerAnyFilter, etTransformer, map[string]interface{}{}), + resources.WithSubscriberServiceRefForTrigger(eventTransformerPod.Name), + ) + + // Wait for all test resources to be ready, so that we can start sending events. + client.WaitForAllTestResourcesReadyOrFail() + + // Everything is setup to receive an event. Generate a CloudEvent. + senderName := "sender" + eventID := string(uuid.NewUUID()) + body := fmt.Sprintf("TestBrokerTracing %s", eventID) + event := cloudevents.New( + fmt.Sprintf(`{"msg":%q}`, body), + cloudevents.WithSource(senderName), + cloudevents.WithID(eventID), + cloudevents.WithType(etTransformer), + ) + + // Send the CloudEvent (either with or without tracing inside the SendEvents Pod). + sendEvent := client.SendFakeEventToAddressableOrFail + if tc.IncomingTraceId { + sendEvent = client.SendFakeEventWithTracingToAddressableOrFail + } + sendEvent(senderName, broker.Name, lib.BrokerTypeMeta, event) + + // TODO Actually determine the cluster's domain, similar to knative.dev/pkg/network/domain.go. + domain := "cluster.local" + + // We expect the following spans: + // 1. Send pod sends event to the Broker Ingress (only if the sending pod generates a span). + // 2. Broker Ingress receives the event from the sending pod. + // 3. Broker Filter for the "transformer" trigger sends the event to the transformer pod. + // 4. Transformer pod receives the event from the Broker Filter for the "transformer" trigger. + // 5. Broker Filter for the "logger" trigger sends the event to the logger pod. + // 6. Logger pod receives the event from the Broker Filter for the "logger" trigger. + + // Useful constants we will use below. + loggerSVCHost := k8sServiceHost(domain, client.Namespace, loggerPodName) + transformerSVCHost := k8sServiceHost(domain, client.Namespace, eventTransformerPod.Name) + + // Steps 7-10: Event from TrChannel sent to transformer Trigger and its reply to the InChannel. + transformerEventSentFromTrChannelToTransformer := tracinghelper.TestSpanTree{ + Note: "3. Broker Filter for the 'transformer' trigger sends the event to the transformer pod.", + Span: tracinghelper.MatchHTTPSpanWithReply( + model.Client, + tracinghelper.WithHTTPHostAndPath(transformerSVCHost, "/"), + ), + Children: []tracinghelper.TestSpanTree{ + { + Note: "4. Transformer pod receives the event from the Broker Filter for the 'transformer' trigger.", + Span: tracinghelper.MatchHTTPSpanWithReply( + model.Server, + tracinghelper.WithHTTPHostAndPath(transformerSVCHost, "/"), + tracinghelper.WithLocalEndpointServiceName(eventTransformerPod.Name), + ), + }, + }, + } + + // Step 11-20. Directly steps 11-12. Steps 13-20 are children. + // Steps 11-12 Reply from the 'transformer' is sent by the Broker TrChannel to the Broker + // Ingress. + transformerEventResponseFromTrChannel := tracinghelper.TestSpanTree{ + Note: "5. Broker Filter for the 'logger' trigger sends the event to the logger pod.", + Span: tracinghelper.MatchHTTPSpanNoReply( + model.Client, + tracinghelper.WithHTTPHostAndPath(loggerSVCHost, "/"), + ), + Children: []tracinghelper.TestSpanTree{ + { + Note: "6. Logger pod receives the event from the Broker Filter for the 'logger' trigger.", + Span: tracinghelper.MatchHTTPSpanNoReply( + model.Server, + tracinghelper.WithHTTPHostAndPath(loggerSVCHost, "/"), + tracinghelper.WithLocalEndpointServiceName(loggerPodName), + ), + }, + }, + } + + // Steps 0-22. Directly steps 0-4 (missing 1). + // Steps 0-4 (missing 1, which is optional and added below if present): Event sent to the Broker + // Ingress. + expected := tracinghelper.TestSpanTree{ + Note: "2. Broker Ingress receives the event from the sending pod.", + Span: tracinghelper.MatchHTTPSpanNoReply(model.Server), + Children: []tracinghelper.TestSpanTree{ + // Steps 7-10. + transformerEventSentFromTrChannelToTransformer, + // Steps 11-22 + transformerEventResponseFromTrChannel, + }, + } + + if tc.IncomingTraceId { + expected = tracinghelper.TestSpanTree{ + Note: "1. Send pod sends event to the Broker Ingress (only if the sending pod generates a span).", + Span: tracinghelper.MatchHTTPSpanNoReply( + model.Client, + tracinghelper.WithLocalEndpointServiceName(senderName), + ), + Children: []tracinghelper.TestSpanTree{expected}, + } + } + matchFunc := func(ev ce.Event) bool { + if ev.Source() != senderName { + return false + } + if ev.ID() != eventID { + return false + } + db, _ := ev.DataBytes() + return strings.Contains(string(db), body) + } + + return expected, matchFunc + } +} diff --git a/vendor/knative.dev/eventing/test/conformance/helpers/channel.go b/vendor/knative.dev/eventing/test/conformance/helpers/channel.go new file mode 100644 index 0000000000..554a9bfc9b --- /dev/null +++ b/vendor/knative.dev/eventing/test/conformance/helpers/channel.go @@ -0,0 +1,69 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helpers + +import ( + "github.com/pkg/errors" + "knative.dev/eventing/test/lib" + "knative.dev/eventing/test/lib/duck" + "knative.dev/eventing/test/lib/resources" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + eventingduckv1alpha1 "knative.dev/eventing/pkg/apis/duck/v1alpha1" + eventingduckv1beta1 "knative.dev/eventing/pkg/apis/duck/v1beta1" +) + +func getChannelDuckTypeSupportVersion(channelName string, client *lib.Client, channel *metav1.TypeMeta) (string, error) { + metaResource := resources.NewMetaResource(channelName, client.Namespace, channel) + obj, err := duck.GetGenericObject(client.Dynamic, metaResource, &eventingduckv1beta1.Channelable{}) + if err != nil { + return "", errors.Wrapf(err, "Unable to GET the channel %v", metaResource) + } + channelable, ok := obj.(*eventingduckv1beta1.Channelable) + if !ok { + return "", errors.Wrapf(err, "Unable to cast the channel %v", metaResource) + } + return channelable.ObjectMeta.Annotations[SubscribableAnnotationKey], nil +} + +func getChannelAsV1Beta1Channelable(channelName string, client *lib.Client, channel metav1.TypeMeta) (*eventingduckv1beta1.Channelable, error) { + metaResource := resources.NewMetaResource(channelName, client.Namespace, &channel) + obj, err := duck.GetGenericObject(client.Dynamic, metaResource, &eventingduckv1beta1.Channelable{}) + if err != nil { + return nil, errors.Wrapf(err, "Unable to get the channel as v1beta1 Channel duck type: %q", channel) + } + channelable, ok := obj.(*eventingduckv1beta1.Channelable) + if !ok { + return nil, errors.Errorf("Unable to cast channel %q to v1beta1 duck type", channel) + } + + return channelable, nil +} + +func getChannelAsV1Alpha1Channelable(channelName string, client *lib.Client, channel metav1.TypeMeta) (*eventingduckv1alpha1.Channelable, error) { + metaResource := resources.NewMetaResource(channelName, client.Namespace, &channel) + obj, err := duck.GetGenericObject(client.Dynamic, metaResource, &eventingduckv1alpha1.Channelable{}) + if err != nil { + return nil, errors.Wrapf(err, "Unable to get the channel as v1alpha1 Channel duck type: %q", channel) + } + channelable, ok := obj.(*eventingduckv1alpha1.Channelable) + if !ok { + return nil, errors.Errorf("Unable to cast channel %q to v1alpha1 duck type", channel) + } + + return channelable, nil +} diff --git a/vendor/knative.dev/eventing/test/conformance/helpers/channel_crd_name_test_helper.go b/vendor/knative.dev/eventing/test/conformance/helpers/channel_crd_name_test_helper.go new file mode 100644 index 0000000000..ad7af89c43 --- /dev/null +++ b/vendor/knative.dev/eventing/test/conformance/helpers/channel_crd_name_test_helper.go @@ -0,0 +1,55 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helpers + +import ( + "testing" + + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/eventing/test/lib" +) + +const ( + ChannelNameSuffix = "Channel" +) + +// ChannelCRDNameTestHelperWithChannelTestRunner runs the Channel CRD name tests for all +// Channel resources in the ChannelTestRunner. +func ChannelCRDNameTestHelperWithChannelTestRunner( + t *testing.T, + channelTestRunner lib.ChannelTestRunner, + options ...lib.SetupClientOption, +) { + + channelTestRunner.RunTests(t, lib.FeatureBasic, func(st *testing.T, channel metav1.TypeMeta) { + client := lib.Setup(st, true, options...) + defer lib.TearDown(client) + + t.Run("Channel name has required suffix", func(t *testing.T) { + channelNameHasRequiredSuffix(st, client, channel) + }) + }) +} + +func channelNameHasRequiredSuffix(st *testing.T, client *lib.Client, channel metav1.TypeMeta) { + // From spec: The CRD's Kind SHOULD have the suffix Channel. The name MAY be just Channel. + if !strings.HasSuffix(channel.Kind, ChannelNameSuffix) { + client.T.Fatalf("Kind is not suffixed with %q : %q", ChannelNameSuffix, channel) + } +} diff --git a/vendor/knative.dev/eventing/test/conformance/helpers/channel_header_single_event_helper.go b/vendor/knative.dev/eventing/test/conformance/helpers/channel_header_single_event_helper.go new file mode 100644 index 0000000000..7faab38d77 --- /dev/null +++ b/vendor/knative.dev/eventing/test/conformance/helpers/channel_header_single_event_helper.go @@ -0,0 +1,103 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helpers + +import ( + "fmt" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/uuid" + + "knative.dev/eventing/test/lib" + "knative.dev/eventing/test/lib/cloudevents" + "knative.dev/eventing/test/lib/resources" +) + +/* +singleEvent tests the following scenario: + +EventSource ---> Channel ---> Subscription ---> Service(Logger) + +*/ + +// SingleEventHelperForChannelTestHelper is the helper function for header_test +func SingleEventHelperForChannelTestHelper(t *testing.T, encoding string, + channelTestRunner lib.ChannelTestRunner, + options ...lib.SetupClientOption, +) { + channelName := "conformance-headers-channel-" + encoding + senderName := "conformance-headers-sender-" + encoding + subscriptionName := "conformance-headers-subscription-" + encoding + loggerPodName := "conformance-headers-logger-pod-" + encoding + + channelTestRunner.RunTests(t, lib.FeatureBasic, func(st *testing.T, channel metav1.TypeMeta) { + st.Logf("Running header conformance test with channel %q", channel) + client := lib.Setup(st, true, options...) + defer lib.TearDown(client) + + // create channel + st.Logf("Creating channel") + client.CreateChannelOrFail(channelName, &channel) + + // create logger service as the subscriber + pod := resources.EventDetailsPod(loggerPodName) + client.CreatePodOrFail(pod, lib.WithService(loggerPodName)) + + // create subscription to subscribe the channel, and forward the received events to the logger service + client.CreateSubscriptionOrFail( + subscriptionName, + channelName, + &channel, + resources.WithSubscriberForSubscription(loggerPodName), + ) + + // wait for all test resources to be ready, so that we can start sending events + client.WaitForAllTestResourcesReadyOrFail() + + // send fake CloudEvent to the channel + eventID := fmt.Sprintf("%s", uuid.NewUUID()) + body := fmt.Sprintf("TestSingleHeaderEvent %s", eventID) + event := cloudevents.New( + fmt.Sprintf(`{"msg":%q}`, body), + cloudevents.WithSource(senderName), + cloudevents.WithID(eventID), + cloudevents.WithEncoding(encoding), + ) + st.Logf("Sending event with tracing headers to %s", senderName) + client.SendFakeEventWithTracingToAddressableOrFail(senderName, channelName, &channel, event) + + // verify the logger service receives the event + st.Logf("Logging for event with body %s", body) + + if err := client.CheckLog(loggerPodName, lib.CheckerContains(body)); err != nil { + st.Fatalf("String %q not found in logs of logger pod %q: %v", body, loggerPodName, err) + } + + // verify that required traceparent header is set + requiredHeaderNameList := []string{"Traceparent"} + for _, headerName := range requiredHeaderNameList { + expectedHeaderLog := fmt.Sprintf("Got Header %s:", headerName) + if err := client.CheckLog(loggerPodName, lib.CheckerContains(expectedHeaderLog)); err != nil { + st.Fatalf("String %q not found in logs of logger pod %q: %v", expectedHeaderLog, loggerPodName, err) + } + } + + //TODO report x-custom-header + + }) +} diff --git a/vendor/knative.dev/eventing/test/conformance/helpers/channel_status_test_helper.go b/vendor/knative.dev/eventing/test/conformance/helpers/channel_status_test_helper.go new file mode 100644 index 0000000000..20448cf2bc --- /dev/null +++ b/vendor/knative.dev/eventing/test/conformance/helpers/channel_status_test_helper.go @@ -0,0 +1,102 @@ +/* +Copyright 2020 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helpers + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "knative.dev/eventing/test/lib" +) + +const ( + SubscribableAnnotationKey = "messaging.knative.dev/subscribable" +) + +// ChannelStatusTestHelperWithChannelTestRunner runs the Channel metadata tests for all Channels in +// the ChannelTestRunner. +func ChannelStatusTestHelperWithChannelTestRunner( + t *testing.T, + channelTestRunner lib.ChannelTestRunner, + options ...lib.SetupClientOption, +) { + + channelTestRunner.RunTests(t, lib.FeatureBasic, func(st *testing.T, channel metav1.TypeMeta) { + client := lib.Setup(st, true, options...) + defer lib.TearDown(client) + + t.Run("Channel has required status fields", func(t *testing.T) { + channelHasRequiredStatus(st, client, channel, options...) + }) + }) +} + +func channelHasRequiredStatus(st *testing.T, client *lib.Client, channel metav1.TypeMeta, options ...lib.SetupClientOption) { + st.Logf("Running channel status conformance test with channel %q", channel) + + channelName := "channel-req-labels" + + client.T.Logf("Creating channel %+v-%s", channel, channelName) + client.CreateChannelOrFail(channelName, &channel) + client.WaitForResourceReadyOrFail(channelName, &channel) + + dtsv, err := getChannelDuckTypeSupportVersion(channelName, client, &channel) + if err != nil { + st.Fatalf("Unable to check Channel duck type support version for %q: %q", channel, err) + } + + if dtsv == "" || dtsv == "v1alpha1" { + // treat missing annotation value as v1alpha1, as written in the spec + channelable, err := getChannelAsV1Alpha1Channelable(channelName, client, channel) + if err != nil { + st.Fatalf("Unable to get channel %q to v1alpha1 duck type: %q", channel, err) + } + + // SPEC: Channel CRD MUST have a status subresource which contains address + if channelable.Status.AddressStatus.Address == nil { + st.Fatalf("%q does not have status.address", channel) + } + + // SPEC: When the channel instance is ready to receive events status.address.hostname and + // status.address.url MUST be populated + if channelable.Status.AddressStatus.Address.Hostname == "" { + st.Fatalf("No hostname found for %q", channel) + } + if channelable.Status.AddressStatus.Address.URL.IsEmpty() { + st.Fatalf("No hostname found for %q", channel) + } + } else if dtsv == "v1beta1" { + channelable, err := getChannelAsV1Beta1Channelable(channelName, client, channel) + if err != nil { + st.Fatalf("Unable to get channel %q to v1beta1 duck type: %q", channel, err) + } + + // SPEC: Channel CRD MUST have a status subresource which contains address + if channelable.Status.AddressStatus.Address == nil { + st.Fatalf("%q does not have status.address", channel) + } + + // SPEC: When the channel instance is ready to receive events status.address.hostname and + // status.address.url MUST be populated + if channelable.Status.Address.URL.IsEmpty() { + st.Fatalf("No hostname found for %q", channel) + } + } else { + st.Fatalf("Channel doesn't support v1alpha1 nor v1beta1 Channel duck types: %q", channel) + } +} diff --git a/vendor/knative.dev/eventing/test/conformance/helpers/channel_tracing_test_helper.go b/vendor/knative.dev/eventing/test/conformance/helpers/channel_tracing_test_helper.go new file mode 100644 index 0000000000..78c8d55a1e --- /dev/null +++ b/vendor/knative.dev/eventing/test/conformance/helpers/channel_tracing_test_helper.go @@ -0,0 +1,350 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helpers + +import ( + "context" + "fmt" + "net/http" + "strings" + "testing" + "time" + + ce "github.com/cloudevents/sdk-go/v1" + "github.com/openzipkin/zipkin-go/model" + "go.opentelemetry.io/otel/api/trace" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/uuid" + "knative.dev/pkg/test/zipkin" + + tracinghelper "knative.dev/eventing/test/conformance/helpers/tracing" + "knative.dev/eventing/test/lib" + "knative.dev/eventing/test/lib/cloudevents" + "knative.dev/eventing/test/lib/resources" +) + +// SetupInfrastructureFunc sets up the infrastructure for running tracing tests. It returns the +// expected trace as well as a string that is expected to be in the logger Pod's logs. +type SetupInfrastructureFunc func( + t *testing.T, + channel *metav1.TypeMeta, + client *lib.Client, + loggerPodName string, + tc TracingTestCase, +) (tracinghelper.TestSpanTree, lib.EventMatchFunc) + +// TracingTestCase is the test case information for tracing tests. +type TracingTestCase struct { + // IncomingTraceId controls whether the original request is sent to the Broker/Channel already + // has a trace ID associated with it by the sender. + IncomingTraceId bool + // Istio controls whether the Pods being created for the test (sender, transformer, logger, + // etc.) have Istio sidecars. It does not affect the Channel Pods. + Istio bool +} + +// ChannelTracingTestHelperWithChannelTestRunner runs the Channel tracing tests for all Channels in +// the ChannelTestRunner. +func ChannelTracingTestHelperWithChannelTestRunner( + t *testing.T, + channelTestRunner lib.ChannelTestRunner, + setupClient lib.SetupClientOption, +) { + channelTestRunner.RunTests(t, lib.FeatureBasic, func(t *testing.T, channel metav1.TypeMeta) { + ChannelTracingTestHelper(t, channel, setupClient) + }) +} + +// ChannelTracingTestHelper runs the Channel tracing test using the given TypeMeta. +func ChannelTracingTestHelper(t *testing.T, channel metav1.TypeMeta, setupClient lib.SetupClientOption) { + testCases := map[string]TracingTestCase{ + "includes incoming trace id": { + IncomingTraceId: true, + }, + } + + for n, tc := range testCases { + t.Run(n, func(t *testing.T) { + tracingTest(t, setupClient, setupChannelTracingWithReply, channel, tc) + }) + } +} + +func tracingTest( + t *testing.T, + setupClient lib.SetupClientOption, + setupInfrastructure SetupInfrastructureFunc, + channel metav1.TypeMeta, + tc TracingTestCase, +) { + const ( + loggerPodName = "logger" + ) + + client := lib.Setup(t, true, setupClient) + defer lib.TearDown(client) + + // Do NOT call zipkin.CleanupZipkinTracingSetup. That will be called exactly once in + // TestMain. + tracinghelper.Setup(t, client) + + expected, mustMatch := setupInfrastructure(t, &channel, client, loggerPodName, tc) + matches := assertEventMatch(t, client, loggerPodName, mustMatch) + + traceID := getTraceIDHeader(t, matches) + trace, err := zipkin.JSONTracePred(traceID, 5*time.Minute, func(trace []model.SpanModel) bool { + tree, err := tracinghelper.GetTraceTree(trace) + if err != nil { + return false + } + // Do not pass t to prevent unnecessary log output. + return len(expected.MatchesSubtree(nil, tree)) > 0 + }) + if err != nil { + t.Logf("Unable to get trace %q: %v. Trace so far %+v", traceID, err, tracinghelper.PrettyPrintTrace(trace)) + tree, err := tracinghelper.GetTraceTree(trace) + if err != nil { + t.Fatal(err) + } + if len(expected.MatchesSubtree(t, tree)) == 0 { + t.Fatalf("No matching subtree. want: %v got: %v", expected, tree) + } + } +} + +// assertEventMatch verifies that recorder pod contains at least one event that +// matches mustMatch. It is used to show that the expected event was sent to +// the logger Pod. It returns a list of the matching events. +func assertEventMatch(t *testing.T, client *lib.Client, recorderPodName string, + mustMatch lib.EventMatchFunc) []lib.EventInfo { + targetTracker, err := client.NewEventInfoStore(recorderPodName, t.Logf) + if err != nil { + t.Fatalf("Pod tracker failed: %v", err) + } + defer targetTracker.Cleanup() + matches, err := targetTracker.WaitAtLeastNMatch(lib.ValidEvFunc(mustMatch), 1) + if err != nil { + t.Fatalf("Expected messages not found: %v", err) + } + return matches +} + +// getTraceIDHeader gets the TraceID from the passed in events. It returns the header from the +// first matching event, but registers a fatal error if more than one traceid header is seen +// in that message. +func getTraceIDHeader(t *testing.T, evInfos []lib.EventInfo) string { + for i := range evInfos { + if nil != evInfos[i].HTTPHeaders { + sc := trace.RemoteSpanContextFromContext(trace.DefaultHTTPPropagator().Extract(context.TODO(), http.Header(evInfos[i].HTTPHeaders))) + if sc.HasTraceID() { + return sc.TraceIDString() + } + } + } + t.Fatalf("FAIL: No traceid in %d messages: (%s)", len(evInfos), evInfos) + return "" +} + +// setupChannelTracing is the general setup for TestChannelTracing. It creates the following: +// SendEvents (Pod) -> Channel -> Subscription -> K8s Service -> Mutate (Pod) +// v +// LogEvents (Pod) <- K8s Service <- Subscription <- Channel <- (Reply) Subscription +// It returns the expected trace tree and a match function that is expected to be sent +// by the SendEvents Pod and should be present in the RecordEvents list of events. +func setupChannelTracingWithReply( + t *testing.T, + channel *metav1.TypeMeta, + client *lib.Client, + loggerPodName string, + tc TracingTestCase, +) (tracinghelper.TestSpanTree, lib.EventMatchFunc) { + // Create the Channels. + channelName := "ch" + client.CreateChannelOrFail(channelName, channel) + + replyChannelName := "reply-ch" + client.CreateChannelOrFail(replyChannelName, channel) + + // Create the 'sink', a LogEvents Pod and a K8s Service that points to it. + loggerPod := resources.EventRecordPod(loggerPodName) + client.CreatePodOrFail(loggerPod, lib.WithService(loggerPodName)) + + // Create the subscriber, a Pod that mutates the event. + transformerPod := resources.EventTransformationPod("transformer", &cloudevents.CloudEvent{ + EventContextV1: ce.EventContextV1{ + Type: "mutated", + }, + }) + client.CreatePodOrFail(transformerPod, lib.WithService(transformerPod.Name)) + + // Create the Subscription linking the Channel to the mutator. + client.CreateSubscriptionOrFail( + "sub", + channelName, + channel, + resources.WithSubscriberForSubscription(transformerPod.Name), + resources.WithReplyForSubscription(replyChannelName, channel)) + + // Create the Subscription linking the reply Channel to the LogEvents K8s Service. + client.CreateSubscriptionOrFail( + "reply-sub", + replyChannelName, + channel, + resources.WithSubscriberForSubscription(loggerPodName), + ) + + // Wait for all test resources to be ready, so that we can start sending events. + client.WaitForAllTestResourcesReadyOrFail() + + // Everything is setup to receive an event. Generate a CloudEvent. + senderName := "sender" + eventID := string(uuid.NewUUID()) + body := fmt.Sprintf("TestChannelTracing %s", eventID) + event := cloudevents.New( + fmt.Sprintf(`{"msg":%q}`, body), + cloudevents.WithSource(senderName), + cloudevents.WithID(eventID), + ) + + // Send the CloudEvent (either with or without tracing inside the SendEvents Pod). + sendEvent := client.SendFakeEventToAddressableOrFail + if tc.IncomingTraceId { + sendEvent = client.SendFakeEventWithTracingToAddressableOrFail + } + sendEvent(senderName, channelName, channel, event) + + // We expect the following spans: + // 1. Sending pod sends event to Channel (only if the sending pod generates a span). + // 2. Channel receives event from sending pod. + // 3. Channel sends event to transformer pod. + // 4. Transformer Pod receives event from Channel. + // 5. Channel sends reply from Transformer Pod to the reply Channel. + // 6. Reply Channel receives event from the original Channel's reply. + // 7. Reply Channel sends event to the logging Pod. + // 8. Logging pod receives event from Channel. + expected := tracinghelper.TestSpanTree{ + // 1 is added below if it is needed. + // 2. Channel receives event from sending pod. + Span: tracinghelper.MatchHTTPSpanNoReply( + model.Server, + tracinghelper.WithHTTPHostAndPath( + fmt.Sprintf("%s-kn-channel.%s.svc.cluster.local", channelName, client.Namespace), + "/", + ), + ), + Children: []tracinghelper.TestSpanTree{ + { + // 3. Channel sends event to transformer pod. + Span: tracinghelper.MatchHTTPSpanWithReply( + model.Client, + tracinghelper.WithHTTPHostAndPath( + fmt.Sprintf("%s.%s.svc.cluster.local", transformerPod.Name, client.Namespace), + "/", + ), + ), + Children: []tracinghelper.TestSpanTree{ + { + // 4. Transformer Pod receives event from Channel. + Span: tracinghelper.MatchHTTPSpanWithReply( + model.Server, + tracinghelper.WithHTTPHostAndPath( + fmt.Sprintf("%s.%s.svc.cluster.local", transformerPod.Name, client.Namespace), + "/", + ), + tracinghelper.WithLocalEndpointServiceName(transformerPod.Name), + ), + }, + }, + }, + { + // 5. Channel sends reply from Transformer Pod to the reply Channel. + Span: tracinghelper.MatchHTTPSpanNoReply( + model.Client, + tracinghelper.WithHTTPHostAndPath( + fmt.Sprintf("%s-kn-channel.%s.svc.cluster.local", replyChannelName, client.Namespace), + "", + ), + ), + Children: []tracinghelper.TestSpanTree{ + // 6. Reply Channel receives event from the original Channel's reply. + { + Span: tracinghelper.MatchHTTPSpanNoReply( + model.Server, + tracinghelper.WithHTTPHostAndPath( + fmt.Sprintf("%s-kn-channel.%s.svc.cluster.local", replyChannelName, client.Namespace), + "/", + ), + ), + Children: []tracinghelper.TestSpanTree{ + { + // 7. Reply Channel sends event to the logging Pod. + Span: tracinghelper.MatchHTTPSpanNoReply( + model.Client, + tracinghelper.WithHTTPHostAndPath( + fmt.Sprintf("%s.%s.svc.cluster.local", loggerPod.Name, client.Namespace), + "/", + ), + ), + Children: []tracinghelper.TestSpanTree{ + { + // 8. Logging pod receives event from Channel. + Span: tracinghelper.MatchHTTPSpanNoReply( + model.Server, + tracinghelper.WithHTTPHostAndPath( + fmt.Sprintf("%s.%s.svc.cluster.local", loggerPod.Name, client.Namespace), + "/", + ), + tracinghelper.WithLocalEndpointServiceName(loggerPod.Name), + ), + }, + }, + }, + }, + }, + }, + }, + }, + } + + if tc.IncomingTraceId { + expected = tracinghelper.TestSpanTree{ + // 1. Sending pod sends event to Channel (only if the sending pod generates a span). + Span: tracinghelper.MatchHTTPSpanNoReply( + model.Client, + tracinghelper.WithHTTPHostAndPath( + fmt.Sprintf("%s-kn-channel.%s.svc.cluster.local", channelName, client.Namespace), + "", + ), + tracinghelper.WithLocalEndpointServiceName("sender"), + ), + Children: []tracinghelper.TestSpanTree{expected}, + } + } + + matchFunc := func(ev ce.Event) bool { + if ev.Source() != senderName { + return false + } + if ev.ID() != eventID { + return false + } + db, _ := ev.DataBytes() + return strings.Contains(string(db), body) + } + + return expected, matchFunc +} diff --git a/vendor/knative.dev/eventing/test/conformance/helpers/tracing/traces.go b/vendor/knative.dev/eventing/test/conformance/helpers/tracing/traces.go new file mode 100644 index 0000000000..4718c36092 --- /dev/null +++ b/vendor/knative.dev/eventing/test/conformance/helpers/tracing/traces.go @@ -0,0 +1,268 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tracing + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "strconv" + "testing" + + "github.com/openzipkin/zipkin-go/model" +) + +// PrettyPrintTrace pretty prints a Trace. +func PrettyPrintTrace(trace []model.SpanModel) string { + b, _ := json.Marshal(trace) + return string(b) +} + +// SpanTree is the tree of Spans representation of a Trace. +type SpanTree struct { + Root bool + Span model.SpanModel + Children []SpanTree +} + +func (t SpanTree) String() string { + b, _ := json.MarshalIndent(t, "", " ") + return string(b) +} + +type SpanMatcher struct { + Kind *model.Kind `json:"a_Kind,omitempty"` + LocalEndpointServiceName string `json:"b_Name,omitempty"` + Tags map[string]string `json:"c_Tags,omitempty"` +} + +func (m *SpanMatcher) Cmp(m2 *SpanMatcher) int { + if m == nil { + if m2 == nil { + return 0 + } + return -1 + } + if m2 == nil { + return 1 + } + + if *m.Kind < *m2.Kind { + return -1 + } else if *m.Kind > *m2.Kind { + return 1 + } + + t1 := m.Tags + t2 := m2.Tags + for _, key := range []string{"http.url", "http.host", "http.path"} { + if t1[key] < t2[key] { + return -1 + } else if t1[key] > t2[key] { + return 1 + } + } + return 0 +} + +type SpanMatcherOption func(*SpanMatcher) + +func WithLocalEndpointServiceName(s string) SpanMatcherOption { + return func(m *SpanMatcher) { + m.LocalEndpointServiceName = s + } +} + +func WithHTTPHostAndPath(host, path string) SpanMatcherOption { + return func(m *SpanMatcher) { + if m.Kind != nil { + if *m.Kind == model.Client { + m.Tags["http.url"] = fmt.Sprintf("http://%s%s", host, path) + } else if *m.Kind == model.Server { + m.Tags["http.host"] = host + m.Tags["http.path"] = path + } + } + } +} + +func (m *SpanMatcher) MatchesSpan(span *model.SpanModel) error { + if m == nil { + return nil + } + if m.Kind != nil { + if *m.Kind != span.Kind { + return fmt.Errorf("mismatched kind: got %q, want %q", span.Kind, *m.Kind) + } + } + if m.LocalEndpointServiceName != "" { + if span.LocalEndpoint == nil { + return errors.New("missing local endpoint") + } + if m.LocalEndpointServiceName != span.LocalEndpoint.ServiceName { + return fmt.Errorf("mismatched LocalEndpoint ServiceName: got %q, want %q", span.LocalEndpoint.ServiceName, m.LocalEndpointServiceName) + } + } + for k, v := range m.Tags { + if t := span.Tags[k]; t != v { + return fmt.Errorf("unexpected tag %s: got %q, want %q", k, t, v) + } + } + return nil +} + +func MatchHTTPSpanWithCode(kind model.Kind, statusCode int, opts ...SpanMatcherOption) *SpanMatcher { + m := &SpanMatcher{ + Kind: &kind, + Tags: map[string]string{ + "http.method": http.MethodPost, + "http.status_code": strconv.Itoa(statusCode), + }, + } + for _, opt := range opts { + opt(m) + } + return m +} + +func MatchHTTPSpanNoReply(kind model.Kind, opts ...SpanMatcherOption) *SpanMatcher { + return MatchHTTPSpanWithCode(kind, 202) +} + +func MatchHTTPSpanWithReply(kind model.Kind, opts ...SpanMatcherOption) *SpanMatcher { + return MatchHTTPSpanWithCode(kind, 200, opts...) +} + +// TestSpanTree is the expected version of SpanTree used for assertions in testing. +// +// The JSON names of the fields are weird because we want a specific order when pretty printing +// JSON. The JSON will be printed in alphabetical order, so we are imposing a certain order by +// prefixing the keys with a specific letter. The letter has no mean other than ordering. +type TestSpanTree struct { + Note string `json:"a_Note,omitempty"` + Span *SpanMatcher `json:"c_Span"` + Children []TestSpanTree `json:"z_Children,omitempty"` +} + +func (tt TestSpanTree) String() string { + b, _ := json.MarshalIndent(tt, "", " ") + return string(b) +} + +// GetTraceTree converts a set slice of spans into a SpanTree. +func GetTraceTree(trace []model.SpanModel) (*SpanTree, error) { + var roots []model.SpanModel + parents := map[model.ID][]model.SpanModel{} + for _, span := range trace { + if span.ParentID != nil { + parents[*span.ParentID] = append(parents[*span.ParentID], span) + } else { + roots = append(roots, span) + } + } + + children, err := getChildren(parents, roots) + if err != nil { + return nil, fmt.Errorf("could not create span tree for %v: %v", PrettyPrintTrace(trace), err) + } + + tree := SpanTree{ + Root: true, + Children: children, + } + if len(parents) != 0 { + return nil, fmt.Errorf("left over spans after generating the SpanTree: %v. Original: %v", parents, PrettyPrintTrace(trace)) + } + return &tree, nil +} + +func getChildren(parents map[model.ID][]model.SpanModel, current []model.SpanModel) ([]SpanTree, error) { + var children []SpanTree + for _, span := range current { + grandchildren, err := getChildren(parents, parents[span.ID]) + if err != nil { + return children, err + } + children = append(children, SpanTree{ + Span: span, + Children: grandchildren, + }) + delete(parents, span.ID) + } + + return children, nil +} + +// MatchesSubtree checks to see if this TestSpanTree matches a subtree +// of the actual SpanTree. It is intended to be used for assertions +// while testing. Returns the set of possible subtree matches with the +// corresponding set of unmatched siblings. +func (tt TestSpanTree) MatchesSubtree(t *testing.T, actual *SpanTree) (matches [][]SpanTree) { + if t != nil { + t.Helper() + t.Logf("attempting to match test tree %v against %v", tt, actual) + } + if err := tt.Span.MatchesSpan(&actual.Span); err == nil { + if t != nil { + t.Logf("%v matches span %v, matching children", tt.Span, actual.Span) + } + // Tree roots match; check children. + if err := matchesSubtrees(t, tt.Children, actual.Children); err == nil { + // A matching root leaves no unmatched siblings. + matches = append(matches, nil) + } + } else if t != nil { + t.Logf("%v does not match span %v: %v", tt.Span, actual.Span, err) + } + // Recursively match children. + for i, child := range actual.Children { + for _, childMatch := range tt.MatchesSubtree(t, &child) { + // Append unmatched children to child results. + childMatch = append(childMatch, actual.Children[:i]...) + childMatch = append(childMatch, actual.Children[i+1:]...) + matches = append(matches, childMatch) + } + } + return +} + +// matchesSubtrees checks for a match of each TestSpanTree with a +// subtree of a distrinct actual SpanTree. +func matchesSubtrees(t *testing.T, ts []TestSpanTree, as []SpanTree) error { + if t != nil { + t.Helper() + t.Logf("attempting to match test trees %v against %v", ts, as) + } + if len(ts) == 0 { + return nil + } + tt := ts[0] + for j, a := range as { + // If there is no error, then it matched successfully. + for _, match := range tt.MatchesSubtree(t, &a) { + asNew := make([]SpanTree, 0, len(as)-1+len(match)) + asNew = append(asNew, as[:j]...) + asNew = append(asNew, as[j+1:]...) + asNew = append(asNew, match...) + if err := matchesSubtrees(t, ts[1:], asNew); err == nil { + return nil + } + } + } + return fmt.Errorf("unmatched span trees. want: %s got %s", ts, as) +} diff --git a/vendor/knative.dev/eventing/test/conformance/helpers/tracing/zipkin.go b/vendor/knative.dev/eventing/test/conformance/helpers/tracing/zipkin.go new file mode 100644 index 0000000000..e6c7a5ea74 --- /dev/null +++ b/vendor/knative.dev/eventing/test/conformance/helpers/tracing/zipkin.go @@ -0,0 +1,67 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tracing + +import ( + "sync" + "testing" + "time" + + "knative.dev/pkg/test/zipkin" + + "knative.dev/eventing/test/lib" + + pkgtesting "knative.dev/pkg/configmap/testing" +) + +// Setup sets up port forwarding to Zipkin and sets the knative-eventing tracing config to debug +// mode (everything is sampled). +func Setup(t *testing.T, client *lib.Client) { + // Do NOT call zipkin.CleanupZipkinTracingSetup. That will be called exactly once in + // TestMain. + if !zipkin.SetupZipkinTracing(client.Kube.Kube, t.Logf) { + t.Fatalf("Unable to set up Zipkin for tracking") + } + setTracingConfigToZipkin(t, client) +} + +var setTracingConfigOnce = sync.Once{} + +// setTracingConfigToZipkin sets the tracing configuration to point at the standard Zipkin endpoint +// installed by the e2e test setup scripts. +// Note that this used to set the sampling rate to 100%. We _think_ that overwhelmed the Zipkin +// instance and caused https://github.com/knative/eventing/issues/2040. So now we just ensure that +// the tests that test tracing ensure that the requests are made with the sampled flag set to true. +// TODO Do we need a tear down method to revert the config map to its original state? +func setTracingConfigToZipkin(t *testing.T, client *lib.Client) { + setTracingConfigOnce.Do(func() { + tracingConfig, exampleTracingConfig := pkgtesting.ConfigMapsFromTestFile(t, "config-tracing") + _, backendOk := tracingConfig.Data["backend"] + _, endpointOk := tracingConfig.Data["endpoint"] + var err error + if backendOk && endpointOk { + _, err = client.Kube.GetConfigMap(tracingConfig.GetNamespace()).Update(tracingConfig) + } else { + _, err = client.Kube.GetConfigMap(exampleTracingConfig.GetNamespace()).Update(exampleTracingConfig) + } + if err != nil { + t.Fatalf("Unable to set the ConfigMap: %v", err) + } + // Wait for 5 seconds to let the ConfigMap be synced up. + time.Sleep(5 * time.Second) + }) +} diff --git a/vendor/knative.dev/eventing/test/conformance/helpers/uri.go b/vendor/knative.dev/eventing/test/conformance/helpers/uri.go new file mode 100644 index 0000000000..90dedb3457 --- /dev/null +++ b/vendor/knative.dev/eventing/test/conformance/helpers/uri.go @@ -0,0 +1,47 @@ +/* +Copyright 2019 The Knative Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helpers + +import ( + "fmt" + + "knative.dev/eventing/pkg/apis/eventing/v1alpha1" +) + +func brokerIngressHost(domain string, broker v1alpha1.Broker) string { + return fmt.Sprintf("%s-broker.%s.svc.%s", broker.Name, broker.Namespace, domain) +} + +func channelHost(domain, namespace, chanName string) string { + return fmt.Sprintf("%s-kn-channel.%s.svc.%s", chanName, namespace, domain) +} + +func brokerTriggerChannelHost(domain string, broker v1alpha1.Broker) string { + return channelHost(domain, broker.Namespace, fmt.Sprintf("%s-kne-trigger", broker.Name)) +} + +func brokerFilterHost(domain string, broker v1alpha1.Broker) string { + return fmt.Sprintf("%s-broker-filter.%s.svc.%s", broker.Name, broker.Namespace, domain) +} + +func triggerPath(trigger v1alpha1.Trigger) string { + return fmt.Sprintf("/triggers/%s/%s/%s", trigger.Namespace, trigger.Name, trigger.UID) +} + +func k8sServiceHost(domain, namespace, svcName string) string { + return fmt.Sprintf("%s.%s.svc.%s", svcName, namespace, domain) +}