From 6161d2e35a4cdc5aab068cd3be67a33172ce6510 Mon Sep 17 00:00:00 2001 From: rghetia Date: Tue, 23 Apr 2019 15:06:44 -0700 Subject: [PATCH] Add support for Tag metadata (#1125) * Add support for tag metadata. * update ocgrpc and ochttp to use new insert/update/upsert api. : * updated existing method optional metadata option. * make TTLNoPropagation and TTLUnlimitedPropagation a function. * changed ttl api. * add test case for multiple TTL metadata. * add test case and note for update/insert api. --- tag/map.go | 66 ++++++++++---- tag/map_codec.go | 15 ++-- tag/map_codec_test.go | 2 +- tag/map_test.go | 194 +++++++++++++++++++++++++++++++++++++++--- tag/metadata.go | 52 +++++++++++ tag/profile_19.go | 2 +- 6 files changed, 293 insertions(+), 38 deletions(-) create mode 100644 tag/metadata.go diff --git a/tag/map.go b/tag/map.go index 5b72ba6ad..0272ef85a 100644 --- a/tag/map.go +++ b/tag/map.go @@ -28,10 +28,15 @@ type Tag struct { Value string } +type tagContent struct { + value string + m metadatas +} + // Map is a map of tags. Use New to create a context containing // a new Map. type Map struct { - m map[Key]string + m map[Key]tagContent } // Value returns the value for the key if a value for the key exists. @@ -40,7 +45,7 @@ func (m *Map) Value(k Key) (string, bool) { return "", false } v, ok := m.m[k] - return v, ok + return v.value, ok } func (m *Map) String() string { @@ -62,21 +67,21 @@ func (m *Map) String() string { return buffer.String() } -func (m *Map) insert(k Key, v string) { +func (m *Map) insert(k Key, v string, md metadatas) { if _, ok := m.m[k]; ok { return } - m.m[k] = v + m.m[k] = tagContent{value: v, m: md} } -func (m *Map) update(k Key, v string) { +func (m *Map) update(k Key, v string, md metadatas) { if _, ok := m.m[k]; ok { - m.m[k] = v + m.m[k] = tagContent{value: v, m: md} } } -func (m *Map) upsert(k Key, v string) { - m.m[k] = v +func (m *Map) upsert(k Key, v string, md metadatas) { + m.m[k] = tagContent{value: v, m: md} } func (m *Map) delete(k Key) { @@ -84,7 +89,7 @@ func (m *Map) delete(k Key) { } func newMap() *Map { - return &Map{m: make(map[Key]string)} + return &Map{m: make(map[Key]tagContent)} } // Mutator modifies a tag map. @@ -95,13 +100,17 @@ type Mutator interface { // Insert returns a mutator that inserts a // value associated with k. If k already exists in the tag map, // mutator doesn't update the value. -func Insert(k Key, v string) Mutator { +// Metadata applies metadata to the tag. It is optional. +// Metadatas are applied in the order in which it is provided. +// If more than one metadata updates the same attribute then +// the update from the last metadata prevails. +func Insert(k Key, v string, mds ...Metadata) Mutator { return &mutator{ fn: func(m *Map) (*Map, error) { if !checkValue(v) { return nil, errInvalidValue } - m.insert(k, v) + m.insert(k, v, createMetadatas(mds...)) return m, nil }, } @@ -110,13 +119,17 @@ func Insert(k Key, v string) Mutator { // Update returns a mutator that updates the // value of the tag associated with k with v. If k doesn't // exists in the tag map, the mutator doesn't insert the value. -func Update(k Key, v string) Mutator { +// Metadata applies metadata to the tag. It is optional. +// Metadatas are applied in the order in which it is provided. +// If more than one metadata updates the same attribute then +// the update from the last metadata prevails. +func Update(k Key, v string, mds ...Metadata) Mutator { return &mutator{ fn: func(m *Map) (*Map, error) { if !checkValue(v) { return nil, errInvalidValue } - m.update(k, v) + m.update(k, v, createMetadatas(mds...)) return m, nil }, } @@ -126,18 +139,37 @@ func Update(k Key, v string) Mutator { // value of the tag associated with k with v. It inserts the // value if k doesn't exist already. It mutates the value // if k already exists. -func Upsert(k Key, v string) Mutator { +// Metadata applies metadata to the tag. It is optional. +// Metadatas are applied in the order in which it is provided. +// If more than one metadata updates the same attribute then +// the update from the last metadata prevails. +func Upsert(k Key, v string, mds ...Metadata) Mutator { return &mutator{ fn: func(m *Map) (*Map, error) { if !checkValue(v) { return nil, errInvalidValue } - m.upsert(k, v) + m.upsert(k, v, createMetadatas(mds...)) return m, nil }, } } +func createMetadatas(mds ...Metadata) metadatas { + var metas metadatas + if len(mds) > 0 { + for _, md := range mds { + if md != nil { + md(&metas) + } + } + } else { + WithTTL(TTLUnlimitedPropagation)(&metas) + } + return metas + +} + // Delete returns a mutator that deletes // the value associated with k. func Delete(k Key) Mutator { @@ -160,10 +192,10 @@ func New(ctx context.Context, mutator ...Mutator) (context.Context, error) { if !checkKeyName(k.Name()) { return ctx, fmt.Errorf("key:%q: %v", k, errInvalidKeyName) } - if !checkValue(v) { + if !checkValue(v.value) { return ctx, fmt.Errorf("key:%q value:%q: %v", k.Name(), v, errInvalidValue) } - m.insert(k, v) + m.insert(k, v.value, v.m) } } var err error diff --git a/tag/map_codec.go b/tag/map_codec.go index c14c7f6db..f8b582761 100644 --- a/tag/map_codec.go +++ b/tag/map_codec.go @@ -170,9 +170,11 @@ func Encode(m *Map) []byte { } eg.writeByte(byte(tagsVersionID)) for k, v := range m.m { - eg.writeByte(byte(keyTypeString)) - eg.writeStringWithVarintLen(k.name) - eg.writeBytesWithVarintLen([]byte(v)) + if v.m.ttl.ttl == valueTTLUnlimitedPropagation { + eg.writeByte(byte(keyTypeString)) + eg.writeStringWithVarintLen(k.name) + eg.writeBytesWithVarintLen([]byte(v.value)) + } } return eg.bytes() } @@ -190,7 +192,7 @@ func Decode(bytes []byte) (*Map, error) { // DecodeEach decodes the given serialized tag map, calling handler for each // tag key and value decoded. -func DecodeEach(bytes []byte, fn func(key Key, val string)) error { +func DecodeEach(bytes []byte, fn func(key Key, val string, md metadatas)) error { eg := &encoderGRPC{ buf: bytes, } @@ -228,7 +230,10 @@ func DecodeEach(bytes []byte, fn func(key Key, val string)) error { if !checkValue(val) { return errInvalidValue } - fn(key, val) + fn(key, val, createMetadatas(WithTTL(TTLUnlimitedPropagation))) + if err != nil { + return err + } } return nil } diff --git a/tag/map_codec_test.go b/tag/map_codec_test.go index a5605e690..344ab0e87 100644 --- a/tag/map_codec_test.go +++ b/tag/map_codec_test.go @@ -91,7 +91,7 @@ func TestEncodeDecode(t *testing.T) { got := make([]keyValue, 0) for k, v := range decoded.m { - got = append(got, keyValue{k, string(v)}) + got = append(got, keyValue{k, string(v.value)}) } want := tc.pairs diff --git a/tag/map_test.go b/tag/map_test.go index 855b747a4..04471a88e 100644 --- a/tag/map_test.go +++ b/tag/map_test.go @@ -23,6 +23,11 @@ import ( "testing" ) +var ( + ttlUnlimitedPropMd = createMetadatas(WithTTL(TTLUnlimitedPropagation)) + ttlNoPropMd = createMetadatas(WithTTL(TTLNoPropagation)) +) + func TestContext(t *testing.T) { k1, _ := NewKey("k1") k2, _ := NewKey("k2") @@ -34,8 +39,8 @@ func TestContext(t *testing.T) { ) got := FromContext(ctx) want := newMap() - want.insert(k1, "v1") - want.insert(k2, "v2") + want.insert(k1, "v1", ttlUnlimitedPropMd) + want.insert(k2, "v2", ttlUnlimitedPropMd) if !reflect.DeepEqual(got, want) { t.Errorf("Map = %#v; want %#v", got, want) @@ -52,8 +57,8 @@ func TestDo(t *testing.T) { ) got := FromContext(ctx) want := newMap() - want.insert(k1, "v1") - want.insert(k2, "v2") + want.insert(k1, "v1", ttlUnlimitedPropMd) + want.insert(k2, "v2", ttlUnlimitedPropMd) Do(ctx, func(ctx context.Context) { got = FromContext(ctx) }) @@ -168,23 +173,175 @@ func TestNewMap(t *testing.T) { } } +func TestNewMapWithMetadata(t *testing.T) { + k3, _ := NewKey("k3") + k4, _ := NewKey("k4") + k5, _ := NewKey("k5") + + tests := []struct { + name string + initial *Map + mods []Mutator + want *Map + }{ + { + name: "from empty; insert", + initial: nil, + mods: []Mutator{ + Insert(k5, "5", WithTTL(TTLNoPropagation)), + Insert(k4, "4"), + }, + want: makeTestTagMapWithMetadata( + tagContent{"5", ttlNoPropMd}, + tagContent{"4", ttlUnlimitedPropMd}), + }, + { + name: "from existing; insert existing", + initial: makeTestTagMapWithMetadata(tagContent{"5", ttlNoPropMd}), + mods: []Mutator{ + Insert(k5, "5", WithTTL(TTLUnlimitedPropagation)), + }, + want: makeTestTagMapWithMetadata(tagContent{"5", ttlNoPropMd}), + }, + { + name: "from existing; update non-existing", + initial: makeTestTagMapWithMetadata(tagContent{"5", ttlNoPropMd}), + mods: []Mutator{ + Update(k4, "4", WithTTL(TTLUnlimitedPropagation)), + }, + want: makeTestTagMapWithMetadata(tagContent{"5", ttlNoPropMd}), + }, + { + name: "from existing; update existing", + initial: makeTestTagMapWithMetadata( + tagContent{"5", ttlUnlimitedPropMd}, + tagContent{"4", ttlNoPropMd}), + mods: []Mutator{ + Update(k5, "5"), + Update(k4, "4", WithTTL(TTLUnlimitedPropagation)), + }, + want: makeTestTagMapWithMetadata( + tagContent{"5", ttlUnlimitedPropMd}, + tagContent{"4", ttlUnlimitedPropMd}), + }, + { + name: "from existing; upsert existing", + initial: makeTestTagMapWithMetadata( + tagContent{"5", ttlNoPropMd}, + tagContent{"4", ttlNoPropMd}), + mods: []Mutator{ + Upsert(k4, "4", WithTTL(TTLUnlimitedPropagation)), + }, + want: makeTestTagMapWithMetadata( + tagContent{"5", ttlNoPropMd}, + tagContent{"4", ttlUnlimitedPropMd}), + }, + { + name: "from existing; upsert non-existing", + initial: makeTestTagMapWithMetadata( + tagContent{"5", ttlNoPropMd}), + mods: []Mutator{ + Upsert(k4, "4", WithTTL(TTLUnlimitedPropagation)), + Upsert(k3, "3"), + }, + want: makeTestTagMapWithMetadata( + tagContent{"5", ttlNoPropMd}, + tagContent{"4", ttlUnlimitedPropMd}, + tagContent{"3", ttlUnlimitedPropMd}), + }, + { + name: "from existing; delete", + initial: makeTestTagMapWithMetadata( + tagContent{"5", ttlNoPropMd}, + tagContent{"4", ttlNoPropMd}), + mods: []Mutator{ + Delete(k5), + }, + want: makeTestTagMapWithMetadata( + tagContent{"4", ttlNoPropMd}), + }, + { + name: "from non-existing; upsert with multiple-metadata", + initial: nil, + mods: []Mutator{ + Upsert(k4, "4", WithTTL(TTLUnlimitedPropagation), WithTTL(TTLNoPropagation)), + Upsert(k5, "5", WithTTL(TTLNoPropagation), WithTTL(TTLUnlimitedPropagation)), + }, + want: makeTestTagMapWithMetadata( + tagContent{"4", ttlNoPropMd}, + tagContent{"5", ttlUnlimitedPropMd}), + }, + { + name: "from non-existing; insert with multiple-metadata", + initial: nil, + mods: []Mutator{ + Insert(k5, "5", WithTTL(TTLNoPropagation), WithTTL(TTLUnlimitedPropagation)), + }, + want: makeTestTagMapWithMetadata( + tagContent{"5", ttlUnlimitedPropMd}), + }, + { + name: "from existing; update with multiple-metadata", + initial: makeTestTagMapWithMetadata( + tagContent{"5", ttlNoPropMd}), + mods: []Mutator{ + Update(k5, "5", WithTTL(TTLNoPropagation), WithTTL(TTLUnlimitedPropagation)), + }, + want: makeTestTagMapWithMetadata( + tagContent{"5", ttlUnlimitedPropMd}), + }, + { + name: "from empty; update invalid", + initial: nil, + mods: []Mutator{ + Insert(k4, "4\x19", WithTTL(TTLUnlimitedPropagation)), + Upsert(k4, "4\x19", WithTTL(TTLUnlimitedPropagation)), + Update(k4, "4\x19", WithTTL(TTLUnlimitedPropagation)), + }, + want: nil, + }, + { + name: "from empty; insert partial", + initial: nil, + mods: []Mutator{ + Upsert(k3, "3", WithTTL(TTLUnlimitedPropagation)), + Upsert(k4, "4\x19", WithTTL(TTLUnlimitedPropagation)), + }, + want: nil, + }, + } + + // Test api for insert, update, and upsert using metadata. + for _, tt := range tests { + ctx := NewContext(context.Background(), tt.initial) + ctx, err := New(ctx, tt.mods...) + if tt.want != nil && err != nil { + t.Errorf("%v: New = %v", tt.name, err) + } + + if got, want := FromContext(ctx), tt.want; !reflect.DeepEqual(got, want) { + t.Errorf("%v: got %v; want %v", tt.name, got, want) + } + } +} + func TestNewValidation(t *testing.T) { tests := []struct { err string seed *Map }{ // Key name validation in seed - {err: "invalid key", seed: &Map{m: map[Key]string{{name: ""}: "foo"}}}, - {err: "", seed: &Map{m: map[Key]string{{name: "key"}: "foo"}}}, - {err: "", seed: &Map{m: map[Key]string{{name: strings.Repeat("a", 255)}: "census"}}}, - {err: "invalid key", seed: &Map{m: map[Key]string{{name: strings.Repeat("a", 256)}: "census"}}}, - {err: "invalid key", seed: &Map{m: map[Key]string{{name: "Приве́т"}: "census"}}}, + {err: "invalid key", seed: &Map{m: map[Key]tagContent{{name: ""}: {"foo", ttlNoPropMd}}}}, + {err: "", seed: &Map{m: map[Key]tagContent{{name: "key"}: {"foo", ttlNoPropMd}}}}, + {err: "", seed: &Map{m: map[Key]tagContent{{name: strings.Repeat("a", 255)}: {"census", ttlNoPropMd}}}}, + {err: "invalid key", seed: &Map{m: map[Key]tagContent{{name: strings.Repeat("a", 256)}: {"census", ttlNoPropMd}}}}, + {err: "invalid key", seed: &Map{m: map[Key]tagContent{{name: "Приве́т"}: {"census", ttlNoPropMd}}}}, // Value validation - {err: "", seed: &Map{m: map[Key]string{{name: "key"}: ""}}}, - {err: "", seed: &Map{m: map[Key]string{{name: "key"}: strings.Repeat("a", 255)}}}, - {err: "invalid value", seed: &Map{m: map[Key]string{{name: "key"}: "Приве́т"}}}, - {err: "invalid value", seed: &Map{m: map[Key]string{{name: "key"}: strings.Repeat("a", 256)}}}, + {err: "", seed: &Map{m: map[Key]tagContent{{name: "key"}: {"", ttlNoPropMd}}}}, + {err: "", seed: &Map{m: map[Key]tagContent{{name: "key"}: {strings.Repeat("a", 255), ttlNoPropMd}}}}, + {err: "invalid value", seed: &Map{m: map[Key]tagContent{{name: "key"}: {"Приве́т", ttlNoPropMd}}}}, + {err: "invalid value", seed: &Map{m: map[Key]tagContent{{name: "key"}: {strings.Repeat("a", 256), ttlNoPropMd}}}}, } for i, tt := range tests { @@ -216,7 +373,16 @@ func makeTestTagMap(ids ...int) *Map { m := newMap() for _, v := range ids { k, _ := NewKey(fmt.Sprintf("k%d", v)) - m.m[k] = fmt.Sprintf("v%d", v) + m.m[k] = tagContent{fmt.Sprintf("v%d", v), ttlUnlimitedPropMd} + } + return m +} + +func makeTestTagMapWithMetadata(tcs ...tagContent) *Map { + m := newMap() + for _, tc := range tcs { + k, _ := NewKey(fmt.Sprintf("k%s", tc.value)) + m.m[k] = tc } return m } diff --git a/tag/metadata.go b/tag/metadata.go new file mode 100644 index 000000000..6571a583e --- /dev/null +++ b/tag/metadata.go @@ -0,0 +1,52 @@ +// Copyright 2019, OpenCensus 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 tag + +const ( + // valueTTLNoPropagation prevents tag from propagating. + valueTTLNoPropagation = 0 + + // valueTTLUnlimitedPropagation allows tag to propagate without any limits on number of hops. + valueTTLUnlimitedPropagation = -1 +) + +// TTL is metadata that specifies number of hops a tag can propagate. +// Details about TTL metadata is specified at https://github.com/census-instrumentation/opencensus-specs/blob/master/tags/TagMap.md#tagmetadata +type TTL struct { + ttl int +} + +var ( + // TTLUnlimitedPropagation is TTL metadata that allows tag to propagate without any limits on number of hops. + TTLUnlimitedPropagation = TTL{ttl: valueTTLUnlimitedPropagation} + + // TTLNoPropagation is TTL metadata that prevents tag from propagating. + TTLNoPropagation = TTL{ttl: valueTTLNoPropagation} +) + +type metadatas struct { + ttl TTL +} + +// Metadata applies metadatas specified by the function. +type Metadata func(*metadatas) + +// WithTTL applies metadata with provided ttl. +func WithTTL(ttl TTL) Metadata { + return func(m *metadatas) { + m.ttl = ttl + } +} diff --git a/tag/profile_19.go b/tag/profile_19.go index f81cd0b4a..b34d95e34 100644 --- a/tag/profile_19.go +++ b/tag/profile_19.go @@ -25,7 +25,7 @@ func do(ctx context.Context, f func(ctx context.Context)) { m := FromContext(ctx) keyvals := make([]string, 0, 2*len(m.m)) for k, v := range m.m { - keyvals = append(keyvals, k.Name(), v) + keyvals = append(keyvals, k.Name(), v.value) } pprof.Do(ctx, pprof.Labels(keyvals...), f) }