// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package resource_test import ( "fmt" "testing" "time" "github.com/oklog/ulid/v2" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" "github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/proto-public/pbresource" ) func TestEqualType(t *testing.T) { t.Run("same pointer", func(t *testing.T) { typ := &pbresource.Type{ Group: "foo", GroupVersion: "v1", Kind: "bar", } require.True(t, resource.EqualType(typ, typ)) }) t.Run("equal", func(t *testing.T) { a := &pbresource.Type{ Group: "foo", GroupVersion: "v1", Kind: "bar", } b := clone(a) require.True(t, resource.EqualType(a, b)) }) t.Run("nil", func(t *testing.T) { a := &pbresource.Type{ Group: "foo", GroupVersion: "v1", Kind: "bar", } require.False(t, resource.EqualType(a, nil)) require.False(t, resource.EqualType(nil, a)) }) t.Run("different Group", func(t *testing.T) { a := &pbresource.Type{ Group: "foo", GroupVersion: "v1", Kind: "bar", } b := clone(a) b.Group = "bar" require.False(t, resource.EqualType(a, b)) }) t.Run("different GroupVersion", func(t *testing.T) { a := &pbresource.Type{ Group: "foo", GroupVersion: "v1", Kind: "bar", } b := clone(a) b.GroupVersion = "v2" require.False(t, resource.EqualType(a, b)) }) t.Run("different Kind", func(t *testing.T) { a := &pbresource.Type{ Group: "foo", GroupVersion: "v1", Kind: "bar", } b := clone(a) b.Kind = "baz" require.False(t, resource.EqualType(a, b)) }) } func TestEqualTenancy(t *testing.T) { t.Run("same pointer", func(t *testing.T) { ten := &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", } require.True(t, resource.EqualTenancy(ten, ten)) }) t.Run("equal", func(t *testing.T) { a := &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", } b := clone(a) require.True(t, resource.EqualTenancy(a, b)) }) t.Run("nil", func(t *testing.T) { a := &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", } require.False(t, resource.EqualTenancy(a, nil)) require.False(t, resource.EqualTenancy(nil, a)) }) t.Run("different Partition", func(t *testing.T) { a := &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", } b := clone(a) b.Partition = "qux" require.False(t, resource.EqualTenancy(a, b)) }) t.Run("different Namespace", func(t *testing.T) { a := &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", } b := clone(a) b.Namespace = "qux" require.False(t, resource.EqualTenancy(a, b)) }) } func TestEqualID(t *testing.T) { t.Run("same pointer", func(t *testing.T) { id := &pbresource.ID{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Uid: ulid.Make().String(), } require.True(t, resource.EqualID(id, id)) }) t.Run("equal", func(t *testing.T) { a := &pbresource.ID{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Uid: ulid.Make().String(), } b := clone(a) require.True(t, resource.EqualID(a, b)) }) t.Run("nil", func(t *testing.T) { a := &pbresource.ID{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Uid: ulid.Make().String(), } require.False(t, resource.EqualID(a, nil)) require.False(t, resource.EqualID(nil, a)) }) t.Run("different type", func(t *testing.T) { a := &pbresource.ID{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Uid: ulid.Make().String(), } b := clone(a) b.Type.Kind = "album" require.False(t, resource.EqualID(a, b)) }) t.Run("different tenancy", func(t *testing.T) { a := &pbresource.ID{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Uid: ulid.Make().String(), } b := clone(a) b.Tenancy.Namespace = "qux" require.False(t, resource.EqualID(a, b)) }) t.Run("different name", func(t *testing.T) { a := &pbresource.ID{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Uid: ulid.Make().String(), } b := clone(a) b.Name = "boom" require.False(t, resource.EqualID(a, b)) }) t.Run("different uid", func(t *testing.T) { a := &pbresource.ID{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Uid: ulid.Make().String(), } b := clone(a) b.Uid = ulid.Make().String() require.False(t, resource.EqualID(a, b)) }) // TODO(peering/v2) Add test for when the peer tenancy of an object differs } func TestEqualReference(t *testing.T) { t.Run("same pointer", func(t *testing.T) { id := &pbresource.Reference{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Section: "blah", } require.True(t, resource.EqualReference(id, id)) }) t.Run("equal", func(t *testing.T) { a := &pbresource.Reference{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Section: "blah", } b := clone(a) require.True(t, resource.EqualReference(a, b)) }) t.Run("nil", func(t *testing.T) { a := &pbresource.Reference{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Section: "blah", } require.False(t, resource.EqualReference(a, nil)) require.False(t, resource.EqualReference(nil, a)) }) t.Run("different type", func(t *testing.T) { a := &pbresource.Reference{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Section: "blah", } b := clone(a) b.Type.Kind = "album" require.False(t, resource.EqualReference(a, b)) }) t.Run("different tenancy", func(t *testing.T) { a := &pbresource.Reference{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Section: "blah", } b := clone(a) b.Tenancy.Namespace = "qux" require.False(t, resource.EqualReference(a, b)) }) t.Run("different name", func(t *testing.T) { a := &pbresource.Reference{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Section: "blah", } b := clone(a) b.Name = "boom" require.False(t, resource.EqualReference(a, b)) }) t.Run("different section", func(t *testing.T) { a := &pbresource.Reference{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Section: "blah", } b := clone(a) b.Section = "not-blah" require.False(t, resource.EqualReference(a, b)) }) // TODO(peering/v2) add test for peer tenancies differing } func TestReferenceOrIDMatch(t *testing.T) { t.Run("equal", func(t *testing.T) { a := &pbresource.Reference{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Section: "blah", } b := &pbresource.ID{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Uid: ulid.Make().String(), } require.True(t, resource.ReferenceOrIDMatch(a, b)) }) t.Run("nil", func(t *testing.T) { a := &pbresource.Reference{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Section: "blah", } b := &pbresource.ID{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Uid: ulid.Make().String(), } require.False(t, resource.ReferenceOrIDMatch(a, nil)) require.False(t, resource.ReferenceOrIDMatch(nil, b)) }) t.Run("different type", func(t *testing.T) { a := &pbresource.Reference{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Section: "blah", } b := &pbresource.ID{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Uid: ulid.Make().String(), } b.Type.Kind = "album" require.False(t, resource.ReferenceOrIDMatch(a, b)) }) t.Run("different tenancy", func(t *testing.T) { a := &pbresource.Reference{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Section: "blah", } b := &pbresource.ID{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Uid: ulid.Make().String(), } b.Tenancy.Namespace = "qux" require.False(t, resource.ReferenceOrIDMatch(a, b)) }) t.Run("different name", func(t *testing.T) { a := &pbresource.Reference{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Section: "blah", } b := &pbresource.ID{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Uid: ulid.Make().String(), } b.Name = "boom" require.False(t, resource.ReferenceOrIDMatch(a, b)) }) // TODO(peering/v2) Add tests for peer tenancy matching } func TestEqualStatus(t *testing.T) { orig := &pbresource.Status{ ObservedGeneration: ulid.Make().String(), Conditions: []*pbresource.Condition{ { Type: "FooType", State: pbresource.Condition_STATE_TRUE, Reason: "FooReason", Message: "Foo is true", Resource: &pbresource.Reference{ Type: &pbresource.Type{ Group: "foo-group", GroupVersion: "foo-group-version", Kind: "foo-kind", }, Tenancy: &pbresource.Tenancy{ Partition: "foo-partition", Namespace: "foo-namespace", }, Name: "foo-name", Section: "foo-section", }, }, }, } // Equal cases. t.Run("same pointer", func(t *testing.T) { require.True(t, resource.EqualStatus(orig, orig, true)) }) t.Run("equal", func(t *testing.T) { require.True(t, resource.EqualStatus(orig, clone(orig), true)) }) // Not equal cases. t.Run("nil", func(t *testing.T) { require.False(t, resource.EqualStatus(orig, nil, true)) require.False(t, resource.EqualStatus(nil, orig, true)) }) testCases := map[string]func(*pbresource.Status){ "different ObservedGeneration": func(s *pbresource.Status) { s.ObservedGeneration = "" }, "different Conditions": func(s *pbresource.Status) { s.Conditions = append(s.Conditions, s.Conditions...) }, "nil Condition": func(s *pbresource.Status) { s.Conditions[0] = nil }, "different Condition.Type": func(s *pbresource.Status) { s.Conditions[0].Type = "BarType" }, "different Condition.State": func(s *pbresource.Status) { s.Conditions[0].State = pbresource.Condition_STATE_FALSE }, "different Condition.Reason": func(s *pbresource.Status) { s.Conditions[0].Reason = "BarReason" }, "different Condition.Message": func(s *pbresource.Status) { s.Conditions[0].Reason = "Bar if false" }, "different Condition.Resource": func(s *pbresource.Status) { s.Conditions[0].Resource = nil }, "different Condition.Resource.Type": func(s *pbresource.Status) { s.Conditions[0].Resource.Type.Group = "bar-group" }, "different Condition.Resource.Tenancy": func(s *pbresource.Status) { s.Conditions[0].Resource.Tenancy.Partition = "bar-partition" }, "different Condition.Resource.Name": func(s *pbresource.Status) { s.Conditions[0].Resource.Name = "bar-name" }, "different Condition.Resource.Section": func(s *pbresource.Status) { s.Conditions[0].Resource.Section = "bar-section" }, // TODO(peering/v2) Add tests for non-local peers in the resource ref } for desc, modFn := range testCases { t.Run(desc, func(t *testing.T) { a, b := clone(orig), clone(orig) modFn(b) require.False(t, resource.EqualStatus(a, b, true)) require.False(t, resource.EqualStatus(b, a, true)) }) } t.Run("compareUpdatedAt = true", func(t *testing.T) { a, b := clone(orig), clone(orig) b.UpdatedAt = timestamppb.New(b.UpdatedAt.AsTime().Add(1 * time.Minute)) require.False(t, resource.EqualStatus(a, b, true)) require.False(t, resource.EqualStatus(b, a, true)) }) t.Run("compareUpdatedAt = false", func(t *testing.T) { a, b := clone(orig), clone(orig) b.UpdatedAt = timestamppb.New(b.UpdatedAt.AsTime().Add(1 * time.Minute)) require.True(t, resource.EqualStatus(a, b, false)) require.True(t, resource.EqualStatus(b, a, false)) }) } func TestEqualStatusMap(t *testing.T) { generation := ulid.Make().String() for idx, tc := range []struct { a, b map[string]*pbresource.Status equal bool }{ {nil, nil, true}, {nil, map[string]*pbresource.Status{}, true}, { map[string]*pbresource.Status{ "consul.io/some-controller": { ObservedGeneration: generation, Conditions: []*pbresource.Condition{ { Type: "Foo", State: pbresource.Condition_STATE_TRUE, Reason: "Bar", Message: "Foo is true because of Bar", }, }, }, }, map[string]*pbresource.Status{ "consul.io/some-controller": { ObservedGeneration: generation, Conditions: []*pbresource.Condition{ { Type: "Foo", State: pbresource.Condition_STATE_TRUE, Reason: "Bar", Message: "Foo is true because of Bar", }, }, }, }, true, }, { map[string]*pbresource.Status{ "consul.io/some-controller": { ObservedGeneration: generation, Conditions: []*pbresource.Condition{ { Type: "Foo", State: pbresource.Condition_STATE_TRUE, Reason: "Bar", Message: "Foo is true because of Bar", }, }, }, }, map[string]*pbresource.Status{ "consul.io/some-controller": { ObservedGeneration: generation, Conditions: []*pbresource.Condition{ { Type: "Foo", State: pbresource.Condition_STATE_FALSE, Reason: "Bar", Message: "Foo is false because of Bar", }, }, }, }, false, }, { map[string]*pbresource.Status{ "consul.io/some-controller": { ObservedGeneration: generation, Conditions: []*pbresource.Condition{ { Type: "Foo", State: pbresource.Condition_STATE_TRUE, Reason: "Bar", Message: "Foo is true because of Bar", }, }, }, }, map[string]*pbresource.Status{ "consul.io/some-controller": { ObservedGeneration: generation, Conditions: []*pbresource.Condition{ { Type: "Foo", State: pbresource.Condition_STATE_TRUE, Reason: "Bar", Message: "Foo is true because of Bar", }, }, }, "consul.io/other-controller": { ObservedGeneration: generation, Conditions: []*pbresource.Condition{ { Type: "Foo", State: pbresource.Condition_STATE_TRUE, Reason: "Bar", Message: "Foo is true because of Bar", }, }, }, }, false, }, } { t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { require.Equal(t, tc.equal, resource.EqualStatusMap(tc.a, tc.b)) require.Equal(t, tc.equal, resource.EqualStatusMap(tc.b, tc.a)) }) } } func BenchmarkEqualType(b *testing.B) { // cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz // BenchmarkEqualType/ours-16 161532109 7.309 ns/op 0 B/op 0 allocs/op // BenchmarkEqualType/reflection-16 1584954 748.4 ns/op 160 B/op 9 allocs/op typeA := &pbresource.Type{ Group: "foo", GroupVersion: "v1", Kind: "bar", } typeB := &pbresource.Type{ Group: "foo", GroupVersion: "v1", Kind: "baz", } b.ResetTimer() b.Run("ours", func(b *testing.B) { for i := 0; i < b.N; i++ { _ = resource.EqualType(typeA, typeB) } }) b.Run("reflection", func(b *testing.B) { for i := 0; i < b.N; i++ { _ = proto.Equal(typeA, typeB) } }) } func BenchmarkEqualTenancy(b *testing.B) { // cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz // BenchmarkEqualTenancy/ours-16 159998534 7.426 ns/op 0 B/op 0 allocs/op // BenchmarkEqualTenancy/reflection-16 2283500 550.3 ns/op 128 B/op 7 allocs/op tenA := &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", } tenB := &pbresource.Tenancy{ Partition: "foo", Namespace: "qux", } b.ResetTimer() b.Run("ours", func(b *testing.B) { for i := 0; i < b.N; i++ { _ = resource.EqualTenancy(tenA, tenB) } }) b.Run("reflection", func(b *testing.B) { for i := 0; i < b.N; i++ { _ = proto.Equal(tenA, tenB) } }) } func BenchmarkEqualID(b *testing.B) { // cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz // BenchmarkEqualID/ours-16 57818125 21.40 ns/op 0 B/op 0 allocs/op // BenchmarkEqualID/reflection-16 3596365 330.1 ns/op 96 B/op 5 allocs/op idA := &pbresource.ID{ Type: &pbresource.Type{ Group: "demo", GroupVersion: "v2", Kind: "artist", }, Tenancy: &pbresource.Tenancy{ Partition: "foo", Namespace: "baz", }, Name: "qux", Uid: ulid.Make().String(), } idB := clone(idA) idB.Uid = ulid.Make().String() b.ResetTimer() b.Run("ours", func(b *testing.B) { for i := 0; i < b.N; i++ { _ = resource.EqualID(idA, idB) } }) b.Run("reflection", func(b *testing.B) { for i := 0; i < b.N; i++ { _ = proto.Equal(idA, idB) } }) } func BenchmarkEqualStatus(b *testing.B) { // cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz // BenchmarkEqualStatus/ours-16 38648232 30.75 ns/op 0 B/op 0 allocs/op // BenchmarkEqualStatus/reflection-16 237694 5267 ns/op 944 B/op 51 allocs/op statusA := &pbresource.Status{ ObservedGeneration: ulid.Make().String(), Conditions: []*pbresource.Condition{ { Type: "FooType", State: pbresource.Condition_STATE_TRUE, Reason: "FooReason", Message: "Foo is true", Resource: &pbresource.Reference{ Type: &pbresource.Type{ Group: "foo-group", GroupVersion: "foo-group-version", Kind: "foo-kind", }, Tenancy: &pbresource.Tenancy{ Partition: "foo-partition", Namespace: "foo-namespace", }, Name: "foo-name", Section: "foo-section", }, }, }, } statusB := clone(statusA) statusB.Conditions[0].Resource.Section = "bar-section" b.ResetTimer() b.Run("ours", func(b *testing.B) { for i := 0; i < b.N; i++ { _ = resource.EqualStatus(statusA, statusB, true) } }) b.Run("reflection", func(b *testing.B) { for i := 0; i < b.N; i++ { _ = proto.Equal(statusA, statusB) } }) } func clone[T proto.Message](v T) T { return proto.Clone(v).(T) }