From 46816071df2edb0e05cf527c8cedf3bdd61bda6b Mon Sep 17 00:00:00 2001 From: Semir Patel Date: Mon, 24 Apr 2023 08:14:51 -0500 Subject: [PATCH 01/92] De-scope tenenacy requirements to OSS only for now. (#17087) Partition and namespace must be "default" Peername must be "local" --- .../services/resource/delete_test.go | 12 ++--- .../services/resource/read_test.go | 12 ++--- .../grpc-external/services/resource/server.go | 16 +++--- .../grpc-external/services/resource/write.go | 5 +- .../services/resource/write_test.go | 51 +++++-------------- 5 files changed, 34 insertions(+), 62 deletions(-) diff --git a/agent/grpc-external/services/resource/delete_test.go b/agent/grpc-external/services/resource/delete_test.go index ddbefbd1166d..3147fb5b3000 100644 --- a/agent/grpc-external/services/resource/delete_test.go +++ b/agent/grpc-external/services/resource/delete_test.go @@ -30,17 +30,17 @@ func TestDelete_InputValidation(t *testing.T) { "no tenancy": func(req *pbresource.DeleteRequest) { req.Id.Tenancy = nil }, "no name": func(req *pbresource.DeleteRequest) { req.Id.Name = "" }, // clone necessary to not pollute DefaultTenancy - "tenancy partition wildcard": func(req *pbresource.DeleteRequest) { + "tenancy partition not default": func(req *pbresource.DeleteRequest) { req.Id.Tenancy = clone(req.Id.Tenancy) - req.Id.Tenancy.Partition = storage.Wildcard + req.Id.Tenancy.Partition = "" }, - "tenancy namespace wildcard": func(req *pbresource.DeleteRequest) { + "tenancy namespace not default": func(req *pbresource.DeleteRequest) { req.Id.Tenancy = clone(req.Id.Tenancy) - req.Id.Tenancy.Namespace = storage.Wildcard + req.Id.Tenancy.Namespace = "" }, - "tenancy peername wildcard": func(req *pbresource.DeleteRequest) { + "tenancy peername not local": func(req *pbresource.DeleteRequest) { req.Id.Tenancy = clone(req.Id.Tenancy) - req.Id.Tenancy.PeerName = storage.Wildcard + req.Id.Tenancy.PeerName = "" }, } for desc, modFn := range testCases { diff --git a/agent/grpc-external/services/resource/read_test.go b/agent/grpc-external/services/resource/read_test.go index 6f7f80a090ab..e7265043f3ce 100644 --- a/agent/grpc-external/services/resource/read_test.go +++ b/agent/grpc-external/services/resource/read_test.go @@ -33,17 +33,17 @@ func TestRead_InputValidation(t *testing.T) { "no tenancy": func(req *pbresource.ReadRequest) { req.Id.Tenancy = nil }, "no name": func(req *pbresource.ReadRequest) { req.Id.Name = "" }, // clone necessary to not pollute DefaultTenancy - "tenancy partition wildcard": func(req *pbresource.ReadRequest) { + "tenancy partition not default": func(req *pbresource.ReadRequest) { req.Id.Tenancy = clone(req.Id.Tenancy) - req.Id.Tenancy.Partition = storage.Wildcard + req.Id.Tenancy.Partition = "" }, - "tenancy namespace wildcard": func(req *pbresource.ReadRequest) { + "tenancy namespace not default": func(req *pbresource.ReadRequest) { req.Id.Tenancy = clone(req.Id.Tenancy) - req.Id.Tenancy.Namespace = storage.Wildcard + req.Id.Tenancy.Namespace = "" }, - "tenancy peername wildcard": func(req *pbresource.ReadRequest) { + "tenancy peername not local": func(req *pbresource.ReadRequest) { req.Id.Tenancy = clone(req.Id.Tenancy) - req.Id.Tenancy.PeerName = storage.Wildcard + req.Id.Tenancy.PeerName = "" }, } for desc, modFn := range testCases { diff --git a/agent/grpc-external/services/resource/server.go b/agent/grpc-external/services/resource/server.go index 46bbbef17e22..51bb4610d527 100644 --- a/agent/grpc-external/services/resource/server.go +++ b/agent/grpc-external/services/resource/server.go @@ -131,17 +131,19 @@ func validateId(id *pbresource.ID, errorPrefix string) error { return status.Errorf(codes.InvalidArgument, "%s.%s is required", errorPrefix, field) } + // Revisit defaulting and non-namespaced resources post-1.16 + var expected string switch { - case id.Tenancy.Namespace == storage.Wildcard: - field = "tenancy.namespace" - case id.Tenancy.Partition == storage.Wildcard: - field = "tenancy.partition" - case id.Tenancy.PeerName == storage.Wildcard: - field = "tenancy.peername" + case id.Tenancy.Partition != "default": + field, expected = "partition", "default" + case id.Tenancy.Namespace != "default": + field, expected = "namespace", "default" + case id.Tenancy.PeerName != "local": + field, expected = "peername", "local" } if field != "" { - return status.Errorf(codes.InvalidArgument, "%s.%s cannot be a wildcard", errorPrefix, field) + return status.Errorf(codes.InvalidArgument, "%s.tenancy.%s must be %s", errorPrefix, field, expected) } return nil } diff --git a/agent/grpc-external/services/resource/write.go b/agent/grpc-external/services/resource/write.go index e5c4f3db73a3..121c7ce39ccc 100644 --- a/agent/grpc-external/services/resource/write.go +++ b/agent/grpc-external/services/resource/write.go @@ -119,10 +119,7 @@ func (s *Server) Write(ctx context.Context, req *pbresource.WriteRequest) (*pbre return errUseWriteStatus } - // Enforce same tenancy for owner - if input.Owner != nil && !proto.Equal(input.Id.Tenancy, input.Owner.Tenancy) { - return status.Errorf(codes.InvalidArgument, "owner and resource tenancy must be the same") - } + // TODO(spatel): Revisit owner<->resource tenancy rules post-1.16 // Update path. case err == nil: diff --git a/agent/grpc-external/services/resource/write_test.go b/agent/grpc-external/services/resource/write_test.go index 6d0645d1fe33..666937c12ee5 100644 --- a/agent/grpc-external/services/resource/write_test.go +++ b/agent/grpc-external/services/resource/write_test.go @@ -36,17 +36,17 @@ func TestWrite_InputValidation(t *testing.T) { "no name": func(req *pbresource.WriteRequest) { req.Resource.Id.Name = "" }, "no data": func(req *pbresource.WriteRequest) { req.Resource.Data = nil }, // clone necessary to not pollute DefaultTenancy - "tenancy partition wildcard": func(req *pbresource.WriteRequest) { + "tenancy partition not default": func(req *pbresource.WriteRequest) { req.Resource.Id.Tenancy = clone(req.Resource.Id.Tenancy) - req.Resource.Id.Tenancy.Partition = storage.Wildcard + req.Resource.Id.Tenancy.Partition = "" }, - "tenancy namespace wildcard": func(req *pbresource.WriteRequest) { + "tenancy namespace not default": func(req *pbresource.WriteRequest) { req.Resource.Id.Tenancy = clone(req.Resource.Id.Tenancy) - req.Resource.Id.Tenancy.Namespace = storage.Wildcard + req.Resource.Id.Tenancy.Namespace = "" }, - "tenancy peername wildcard": func(req *pbresource.WriteRequest) { + "tenancy peername not local": func(req *pbresource.WriteRequest) { req.Resource.Id.Tenancy = clone(req.Resource.Id.Tenancy) - req.Resource.Id.Tenancy.PeerName = storage.Wildcard + req.Resource.Id.Tenancy.PeerName = "" }, "wrong data type": func(req *pbresource.WriteRequest) { var err error @@ -99,24 +99,24 @@ func TestWrite_OwnerValidation(t *testing.T) { errorContains: "resource.owner.name", }, // clone necessary to not pollute DefaultTenancy - "owner tenancy partition wildcard": { + "owner tenancy partition not default": { modReqFn: func(req *pbresource.WriteRequest) { req.Resource.Owner.Tenancy = clone(req.Resource.Owner.Tenancy) - req.Resource.Owner.Tenancy.Partition = storage.Wildcard + req.Resource.Owner.Tenancy.Partition = "" }, errorContains: "resource.owner.tenancy.partition", }, - "owner tenancy namespace wildcard": { + "owner tenancy namespace not default": { modReqFn: func(req *pbresource.WriteRequest) { req.Resource.Owner.Tenancy = clone(req.Resource.Owner.Tenancy) - req.Resource.Owner.Tenancy.Namespace = storage.Wildcard + req.Resource.Owner.Tenancy.Namespace = "" }, errorContains: "resource.owner.tenancy.namespace", }, - "owner tenancy peername wildcard": { + "owner tenancy peername not local": { modReqFn: func(req *pbresource.WriteRequest) { req.Resource.Owner.Tenancy = clone(req.Resource.Owner.Tenancy) - req.Resource.Owner.Tenancy.PeerName = storage.Wildcard + req.Resource.Owner.Tenancy.PeerName = "" }, errorContains: "resource.owner.tenancy.peername", }, @@ -491,33 +491,6 @@ func TestWrite_Owner_Immutable(t *testing.T) { require.ErrorContains(t, err, "owner cannot be changed") } -func TestWrite_Owner_RequireSameTenancy(t *testing.T) { - server := testServer(t) - client := testClient(t, server) - - demo.Register(server.Registry) - - artist, err := demo.GenerateV2Artist() - require.NoError(t, err) - rsp1, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: artist}) - require.NoError(t, err) - - // change album tenancy to be different from artist tenancy - album, err := demo.GenerateV2Album(rsp1.Resource.Id) - require.NoError(t, err) - album.Owner.Tenancy = &pbresource.Tenancy{ - Partition: "some", - Namespace: "other", - PeerName: "tenancy", - } - - // verify create fails - _, err = client.Write(testContext(t), &pbresource.WriteRequest{Resource: album}) - require.Error(t, err) - require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.ErrorContains(t, err, "tenancy must be the same") -} - type blockOnceBackend struct { storage.Backend From a33b224a55fb570f4775bb8944eb0e39a4b680c7 Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Mon, 24 Apr 2023 12:03:26 -0500 Subject: [PATCH 02/92] Fix virtual services being included in intention topology as downstreams. (#17099) --- agent/consul/state/intention.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/agent/consul/state/intention.go b/agent/consul/state/intention.go index 3212b2038982..3b03b91007e2 100644 --- a/agent/consul/state/intention.go +++ b/agent/consul/state/intention.go @@ -1079,16 +1079,21 @@ func (s *Store) intentionTopologyTxn( } addSvcs(tempServices) - // Query the virtual ip table as well to include virtual services that don't have a registered instance yet. - vipIndex, vipServices, err := servicesVirtualIPsTxn(tx) - if err != nil { - return index, nil, fmt.Errorf("failed to list service virtual IPs: %v", err) - } - for _, svc := range vipServices { - services[svc.Service.ServiceName] = struct{}{} - } - if vipIndex > index { - index = vipIndex + if !downstreams { + // Query the virtual ip table as well to include virtual services that don't have a registered instance yet. + // We only need to do this for upstreams currently, so that tproxy can find which discovery chains should be + // contacted for failover scenarios. Virtual services technically don't need to be considered as downstreams, + // because they will take on the identity of the calling service, rather than the chain itself. + vipIndex, vipServices, err := servicesVirtualIPsTxn(tx) + if err != nil { + return index, nil, fmt.Errorf("failed to list service virtual IPs: %v", err) + } + for _, svc := range vipServices { + services[svc.Service.ServiceName] = struct{}{} + } + if vipIndex > index { + index = vipIndex + } } } else { // destinations can only ever be upstream, since they are only allowed as intention destination. From d4cacc7232808f85de67a7bfc11d14abd2ce1d8e Mon Sep 17 00:00:00 2001 From: Anita Akaeze Date: Mon, 24 Apr 2023 14:23:24 -0400 Subject: [PATCH 03/92] Merge pull request #5200 from hashicorp/NET-3758 (#17102) * Merge pull request #5200 from hashicorp/NET-3758 NET-3758: connect: update supported envoy versions to 1.26.0 * lint --- .changelog/5200.txt | 3 + .github/workflows/test-integrations.yml | 4 +- .../xdscommon/envoy_versioning_test.go | 104 ++++++++++-------- envoyextensions/xdscommon/proxysupport.go | 2 +- 4 files changed, 64 insertions(+), 49 deletions(-) create mode 100644 .changelog/5200.txt diff --git a/.changelog/5200.txt b/.changelog/5200.txt new file mode 100644 index 000000000000..289c222159e9 --- /dev/null +++ b/.changelog/5200.txt @@ -0,0 +1,3 @@ +```release-note:improvement +connect: update supported envoy versions to 1.23.8, 1.24.6, 1.25.4, 1.26.0 +``` \ No newline at end of file diff --git a/.github/workflows/test-integrations.yml b/.github/workflows/test-integrations.yml index 731045d8cced..3c44198ed757 100644 --- a/.github/workflows/test-integrations.yml +++ b/.github/workflows/test-integrations.yml @@ -153,7 +153,7 @@ jobs: # this is further going to multiplied in envoy-integration tests by the # other dimensions in the matrix. Currently TOTAL_RUNNERS would be # multiplied by 8 based on these values: - # envoy-version: ["1.22.11", "1.23.8", "1.24.6", "1.25.4"] + # envoy-version: ["1.23.8", "1.24.6", "1.25.4", "1.26.0"] # xds-target: ["server", "client"] TOTAL_RUNNERS: 4 JQ_SLICER: '[ inputs ] | [_nwise(length / $runnercount | floor)]' @@ -186,7 +186,7 @@ jobs: strategy: fail-fast: false matrix: - envoy-version: ["1.22.11", "1.23.8", "1.24.6", "1.25.4"] + envoy-version: ["1.23.8", "1.24.6", "1.25.4", "1.26.0"] xds-target: ["server", "client"] test-cases: ${{ fromJSON(needs.generate-envoy-job-matrices.outputs.envoy-matrix) }} env: diff --git a/envoyextensions/xdscommon/envoy_versioning_test.go b/envoyextensions/xdscommon/envoy_versioning_test.go index 96f6f305d4fa..ff9318239348 100644 --- a/envoyextensions/xdscommon/envoy_versioning_test.go +++ b/envoyextensions/xdscommon/envoy_versioning_test.go @@ -81,50 +81,62 @@ func TestDetermineSupportedProxyFeaturesFromString(t *testing.T) { // Just the bad versions cases := map[string]testcase{ - "1.9.0": {expectErr: "Envoy 1.9.0 " + errTooOld}, - "1.10.0": {expectErr: "Envoy 1.10.0 " + errTooOld}, - "1.11.0": {expectErr: "Envoy 1.11.0 " + errTooOld}, - "1.12.0": {expectErr: "Envoy 1.12.0 " + errTooOld}, - "1.12.1": {expectErr: "Envoy 1.12.1 " + errTooOld}, - "1.12.2": {expectErr: "Envoy 1.12.2 " + errTooOld}, - "1.12.3": {expectErr: "Envoy 1.12.3 " + errTooOld}, - "1.12.4": {expectErr: "Envoy 1.12.4 " + errTooOld}, - "1.12.5": {expectErr: "Envoy 1.12.5 " + errTooOld}, - "1.12.6": {expectErr: "Envoy 1.12.6 " + errTooOld}, - "1.12.7": {expectErr: "Envoy 1.12.7 " + errTooOld}, - "1.13.0": {expectErr: "Envoy 1.13.0 " + errTooOld}, - "1.13.1": {expectErr: "Envoy 1.13.1 " + errTooOld}, - "1.13.2": {expectErr: "Envoy 1.13.2 " + errTooOld}, - "1.13.3": {expectErr: "Envoy 1.13.3 " + errTooOld}, - "1.13.4": {expectErr: "Envoy 1.13.4 " + errTooOld}, - "1.13.5": {expectErr: "Envoy 1.13.5 " + errTooOld}, - "1.13.6": {expectErr: "Envoy 1.13.6 " + errTooOld}, - "1.13.7": {expectErr: "Envoy 1.13.7 " + errTooOld}, - "1.14.0": {expectErr: "Envoy 1.14.0 " + errTooOld}, - "1.14.1": {expectErr: "Envoy 1.14.1 " + errTooOld}, - "1.14.2": {expectErr: "Envoy 1.14.2 " + errTooOld}, - "1.14.3": {expectErr: "Envoy 1.14.3 " + errTooOld}, - "1.14.4": {expectErr: "Envoy 1.14.4 " + errTooOld}, - "1.14.5": {expectErr: "Envoy 1.14.5 " + errTooOld}, - "1.14.6": {expectErr: "Envoy 1.14.6 " + errTooOld}, - "1.14.7": {expectErr: "Envoy 1.14.7 " + errTooOld}, - "1.15.0": {expectErr: "Envoy 1.15.0 " + errTooOld}, - "1.15.1": {expectErr: "Envoy 1.15.1 " + errTooOld}, - "1.15.2": {expectErr: "Envoy 1.15.2 " + errTooOld}, - "1.15.3": {expectErr: "Envoy 1.15.3 " + errTooOld}, - "1.15.4": {expectErr: "Envoy 1.15.4 " + errTooOld}, - "1.15.5": {expectErr: "Envoy 1.15.5 " + errTooOld}, - "1.16.1": {expectErr: "Envoy 1.16.1 " + errTooOld}, - "1.16.2": {expectErr: "Envoy 1.16.2 " + errTooOld}, - "1.16.3": {expectErr: "Envoy 1.16.3 " + errTooOld}, - "1.16.4": {expectErr: "Envoy 1.16.4 " + errTooOld}, - "1.16.5": {expectErr: "Envoy 1.16.5 " + errTooOld}, - "1.16.6": {expectErr: "Envoy 1.16.6 " + errTooOld}, - "1.17.4": {expectErr: "Envoy 1.17.4 " + errTooOld}, - "1.18.6": {expectErr: "Envoy 1.18.6 " + errTooOld}, - "1.19.5": {expectErr: "Envoy 1.19.5 " + errTooOld}, - "1.20.7": {expectErr: "Envoy 1.20.7 " + errTooOld}, - "1.21.5": {expectErr: "Envoy 1.21.5 " + errTooOld}, + "1.9.0": {expectErr: "Envoy 1.9.0 " + errTooOld}, + "1.10.0": {expectErr: "Envoy 1.10.0 " + errTooOld}, + "1.11.0": {expectErr: "Envoy 1.11.0 " + errTooOld}, + "1.12.0": {expectErr: "Envoy 1.12.0 " + errTooOld}, + "1.12.1": {expectErr: "Envoy 1.12.1 " + errTooOld}, + "1.12.2": {expectErr: "Envoy 1.12.2 " + errTooOld}, + "1.12.3": {expectErr: "Envoy 1.12.3 " + errTooOld}, + "1.12.4": {expectErr: "Envoy 1.12.4 " + errTooOld}, + "1.12.5": {expectErr: "Envoy 1.12.5 " + errTooOld}, + "1.12.6": {expectErr: "Envoy 1.12.6 " + errTooOld}, + "1.12.7": {expectErr: "Envoy 1.12.7 " + errTooOld}, + "1.13.0": {expectErr: "Envoy 1.13.0 " + errTooOld}, + "1.13.1": {expectErr: "Envoy 1.13.1 " + errTooOld}, + "1.13.2": {expectErr: "Envoy 1.13.2 " + errTooOld}, + "1.13.3": {expectErr: "Envoy 1.13.3 " + errTooOld}, + "1.13.4": {expectErr: "Envoy 1.13.4 " + errTooOld}, + "1.13.5": {expectErr: "Envoy 1.13.5 " + errTooOld}, + "1.13.6": {expectErr: "Envoy 1.13.6 " + errTooOld}, + "1.13.7": {expectErr: "Envoy 1.13.7 " + errTooOld}, + "1.14.0": {expectErr: "Envoy 1.14.0 " + errTooOld}, + "1.14.1": {expectErr: "Envoy 1.14.1 " + errTooOld}, + "1.14.2": {expectErr: "Envoy 1.14.2 " + errTooOld}, + "1.14.3": {expectErr: "Envoy 1.14.3 " + errTooOld}, + "1.14.4": {expectErr: "Envoy 1.14.4 " + errTooOld}, + "1.14.5": {expectErr: "Envoy 1.14.5 " + errTooOld}, + "1.14.6": {expectErr: "Envoy 1.14.6 " + errTooOld}, + "1.14.7": {expectErr: "Envoy 1.14.7 " + errTooOld}, + "1.15.0": {expectErr: "Envoy 1.15.0 " + errTooOld}, + "1.15.1": {expectErr: "Envoy 1.15.1 " + errTooOld}, + "1.15.2": {expectErr: "Envoy 1.15.2 " + errTooOld}, + "1.15.3": {expectErr: "Envoy 1.15.3 " + errTooOld}, + "1.15.4": {expectErr: "Envoy 1.15.4 " + errTooOld}, + "1.15.5": {expectErr: "Envoy 1.15.5 " + errTooOld}, + "1.16.1": {expectErr: "Envoy 1.16.1 " + errTooOld}, + "1.16.2": {expectErr: "Envoy 1.16.2 " + errTooOld}, + "1.16.3": {expectErr: "Envoy 1.16.3 " + errTooOld}, + "1.16.4": {expectErr: "Envoy 1.16.4 " + errTooOld}, + "1.16.5": {expectErr: "Envoy 1.16.5 " + errTooOld}, + "1.16.6": {expectErr: "Envoy 1.16.6 " + errTooOld}, + "1.17.4": {expectErr: "Envoy 1.17.4 " + errTooOld}, + "1.18.6": {expectErr: "Envoy 1.18.6 " + errTooOld}, + "1.19.5": {expectErr: "Envoy 1.19.5 " + errTooOld}, + "1.20.7": {expectErr: "Envoy 1.20.7 " + errTooOld}, + "1.21.5": {expectErr: "Envoy 1.21.5 " + errTooOld}, + "1.22.0": {expectErr: "Envoy 1.22.0 " + errTooOld}, + "1.22.1": {expectErr: "Envoy 1.22.1 " + errTooOld}, + "1.22.2": {expectErr: "Envoy 1.22.2 " + errTooOld}, + "1.22.3": {expectErr: "Envoy 1.22.3 " + errTooOld}, + "1.22.4": {expectErr: "Envoy 1.22.4 " + errTooOld}, + "1.22.5": {expectErr: "Envoy 1.22.5 " + errTooOld}, + "1.22.6": {expectErr: "Envoy 1.22.6 " + errTooOld}, + "1.22.7": {expectErr: "Envoy 1.22.7 " + errTooOld}, + "1.22.8": {expectErr: "Envoy 1.22.8 " + errTooOld}, + "1.22.9": {expectErr: "Envoy 1.22.9 " + errTooOld}, + "1.22.10": {expectErr: "Envoy 1.22.10 " + errTooOld}, + "1.22.11": {expectErr: "Envoy 1.22.11 " + errTooOld}, } // Insert a bunch of valid versions. @@ -139,10 +151,9 @@ func TestDetermineSupportedProxyFeaturesFromString(t *testing.T) { } */ for _, v := range []string{ - "1.22.0", "1.22.1", "1.22.2", "1.22.3", "1.22.4", "1.22.5", "1.22.6", "1.22.7", "1.22.8", "1.22.9", "1.22.10", "1.22.11", "1.23.0", "1.23.1", "1.23.2", "1.23.3", "1.23.4", "1.23.5", "1.23.6", "1.23.7", "1.23.8", "1.24.0", "1.24.1", "1.24.2", "1.24.3", "1.24.4", "1.24.5", "1.24.6", - "1.25.0", "1.25.1", "1.25.2", "1.25.3", "1.25.4", + "1.25.0", "1.25.1", "1.25.2", "1.25.3", "1.25.4", "1.26.0", } { cases[v] = testcase{expect: SupportedProxyFeatures{}} } @@ -152,6 +163,7 @@ func TestDetermineSupportedProxyFeaturesFromString(t *testing.T) { t.Run(name, func(t *testing.T) { sf, err := DetermineSupportedProxyFeaturesFromString(name) if tc.expectErr == "" { + require.NoError(t, err) require.Equal(t, tc.expect, sf) } else { testutil.RequireErrorContains(t, err, tc.expectErr) diff --git a/envoyextensions/xdscommon/proxysupport.go b/envoyextensions/xdscommon/proxysupport.go index c4a96e818154..ff4b79d7354c 100644 --- a/envoyextensions/xdscommon/proxysupport.go +++ b/envoyextensions/xdscommon/proxysupport.go @@ -12,10 +12,10 @@ import "strings" // // see: https://www.consul.io/docs/connect/proxies/envoy#supported-versions var EnvoyVersions = []string{ + "1.26.0", "1.25.4", "1.24.6", "1.23.8", - "1.22.11", } // UnsupportedEnvoyVersions lists any unsupported Envoy versions (mainly minor versions) that fall From 9ce50aefbbb58e1f276682396de61317db712214 Mon Sep 17 00:00:00 2001 From: Dan Bond Date: Mon, 24 Apr 2023 11:34:53 -0700 Subject: [PATCH 04/92] CI: remove uneeded AWS creds from test-integrations (#17104) * Update test-integrations.yml * removing permission lies now that vault is not used in this job. --------- Co-authored-by: John Murret --- .github/workflows/test-integrations.yml | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/.github/workflows/test-integrations.yml b/.github/workflows/test-integrations.yml index 3c44198ed757..720360ee801f 100644 --- a/.github/workflows/test-integrations.yml +++ b/.github/workflows/test-integrations.yml @@ -176,9 +176,6 @@ jobs: envoy-integration-test: runs-on: ${{ fromJSON(needs.setup.outputs.compute-xl) }} - permissions: - id-token: write # NOTE: this permission is explicitly required for Vault auth. - contents: read needs: - setup - generate-envoy-job-matrices @@ -194,24 +191,6 @@ jobs: XDS_TARGET: ${{ matrix.xds-target }} AWS_LAMBDA_REGION: us-west-2 steps: - # NOTE: ENT specific step as we store secrets in Vault. - - name: Authenticate to Vault - if: ${{ endsWith(github.repository, '-enterprise') }} - id: vault-auth - run: vault-auth - - # NOTE: ENT specific step as we store secrets in Vault. - - name: Fetch Secrets - if: ${{ endsWith(github.repository, '-enterprise') }} - id: secrets - uses: hashicorp/vault-action@v2.5.0 - with: - url: ${{ steps.vault-auth.outputs.addr }} - caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} - token: ${{ steps.vault-auth.outputs.token }} - secrets: | - kv/data/github/${{ github.repository }}/aws arn | AWS_ROLE_ARN ; - - uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 - uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0 with: From 001d540afc9694600e059c19b39c053e9a90e738 Mon Sep 17 00:00:00 2001 From: Michael Wilkerson <62034708+wilkermichael@users.noreply.github.com> Date: Mon, 24 Apr 2023 13:21:28 -0700 Subject: [PATCH 05/92] Add sameness group field to prepared queries (#17089) * added method for converting SamenessGroupConfigEntry - added new method `ToQueryFailoverTargets` for converting a SamenessGroupConfigEntry's members to a list of QueryFailoverTargets - renamed `ToFailoverTargets` ToServiceResolverFailoverTargets to distinguish it from `ToQueryFailoverTargets` * Added SamenessGroup to PreparedQuery - exposed Service.Partition to API when defining a prepared query - added a method for determining if a QueryFailoverOptions is empty - This will be useful for validation - added unit tests * added method for retrieving a SamenessGroup to state store * added logic for using PQ with SamenessGroup - added branching path for SamenessGroup handling in execute. It will be handled separate from the normal PQ case - added a new interface so that the `GetSamenessGroupFailoverTargets` can be properly tested - separated the execute logic into a `targetSelector` function so that it can be used for both failover and sameness group PQs - split OSS only methods into new PQ OSS files - added validation that `samenessGroup` is an enterprise only feature * added documentation for PQ SamenessGroup --- agent/consul/discoverychain/compile.go | 2 +- agent/consul/prepared_query/walk_test.go | 1 + agent/consul/prepared_query_endpoint.go | 287 ++++++++++-------- agent/consul/prepared_query_endpoint_oss.go | 38 +++ .../prepared_query_endpoint_oss_test.go | 52 ++++ agent/consul/prepared_query_endpoint_test.go | 43 ++- .../state/config_entry_sameness_group.go | 19 ++ .../config_entry_sameness_group_oss.go | 13 +- agent/structs/prepared_query.go | 13 + agent/structs/prepared_query_test.go | 53 ++++ api/prepared_query.go | 10 +- website/content/api-docs/query.mdx | 17 +- 12 files changed, 417 insertions(+), 131 deletions(-) create mode 100644 agent/consul/prepared_query_endpoint_oss.go create mode 100644 agent/consul/prepared_query_endpoint_oss_test.go create mode 100644 agent/consul/state/config_entry_sameness_group.go diff --git a/agent/consul/discoverychain/compile.go b/agent/consul/discoverychain/compile.go index b355ff9dd6d3..3be5a4aaa490 100644 --- a/agent/consul/discoverychain/compile.go +++ b/agent/consul/discoverychain/compile.go @@ -1196,7 +1196,7 @@ func (c *compiler) makeSamenessGroupFailover(target *structs.DiscoveryTarget, op } var failoverTargets []*structs.DiscoveryTarget - for _, t := range samenessGroup.ToFailoverTargets() { + for _, t := range samenessGroup.ToServiceResolverFailoverTargets() { // Rewrite the target as per the failover policy. targetOpts := structs.MergeDiscoveryTargetOpts(opts, t.ToDiscoveryTargetOpts()) failoverTarget := c.rewriteTarget(target, targetOpts) diff --git a/agent/consul/prepared_query/walk_test.go b/agent/consul/prepared_query/walk_test.go index a151d71c8e10..9ff380248bd2 100644 --- a/agent/consul/prepared_query/walk_test.go +++ b/agent/consul/prepared_query/walk_test.go @@ -46,6 +46,7 @@ func TestWalk_ServiceQuery(t *testing.T) { ".Tags[1]:tag2", ".Tags[2]:tag3", ".Peer:", + ".SamenessGroup:", } expected = append(expected, entMetaWalkFields...) sort.Strings(expected) diff --git a/agent/consul/prepared_query_endpoint.go b/agent/consul/prepared_query_endpoint.go index 9c8c26335ac3..df4ffdc1c647 100644 --- a/agent/consul/prepared_query_endpoint.go +++ b/agent/consul/prepared_query_endpoint.go @@ -141,7 +141,7 @@ func (p *PreparedQuery) Apply(args *structs.PreparedQueryRequest, reply *string) } // parseQuery makes sure the entries of a query are valid for a create or -// update operation. Some of the fields are not checked or are partially +// update operation. Some fields are not checked or are partially // checked, as noted in the comments below. This also updates all the parsed // fields of the query. func parseQuery(query *structs.PreparedQuery) error { @@ -205,6 +205,10 @@ func parseService(svc *structs.ServiceQuery) error { return err } + if err := parseSameness(svc); err != nil { + return err + } + // We skip a few fields: // - There's no validation for Datacenters; we skip any unknown entries // at execution time. @@ -371,108 +375,117 @@ func (p *PreparedQuery) Execute(args *structs.PreparedQueryExecuteRequest, return structs.ErrQueryNotFound } - // Execute the query for the local DC. - if err := p.execute(query, reply, args.Connect); err != nil { - return err - } + // If we have a sameness group, it controls the initial query and + // subsequent failover if required (Enterprise Only) + if query.Service.SamenessGroup != "" { + wrapper := newQueryServerWrapper(p.srv, p.ExecuteRemote) + if err := querySameness(wrapper, *query, args, reply); err != nil { + return err + } + } else { + // Execute the query for the local DC. + if err := p.execute(query, reply, args.Connect); err != nil { + return err + } - // If they supplied a token with the query, use that, otherwise use the - // token passed in with the request. - token := args.QueryOptions.Token - if query.Token != "" { - token = query.Token - } - if err := p.srv.filterACL(token, reply); err != nil { - return err - } + // If they supplied a token with the query, use that, otherwise use the + // token passed in with the request. + token := args.QueryOptions.Token + if query.Token != "" { + token = query.Token + } + if err := p.srv.filterACL(token, reply); err != nil { + return err + } - // TODO (slackpad) We could add a special case here that will avoid the - // fail over if we filtered everything due to ACLs. This seems like it - // might not be worth the code complexity and behavior differences, - // though, since this is essentially a misconfiguration. + // TODO (slackpad) We could add a special case here that will avoid the + // fail over if we filtered everything due to ACLs. This seems like it + // might not be worth the code complexity and behavior differences, + // though, since this is essentially a misconfiguration. - // We have to do this ourselves since we are not doing a blocking RPC. - p.srv.setQueryMeta(&reply.QueryMeta, token) + // We have to do this ourselves since we are not doing a blocking RPC. + p.srv.setQueryMeta(&reply.QueryMeta, token) - // Shuffle the results in case coordinates are not available if they - // requested an RTT sort. - reply.Nodes.Shuffle() + // Shuffle the results in case coordinates are not available if they + // requested an RTT sort. + reply.Nodes.Shuffle() - // Build the query source. This can be provided by the client, or by - // the prepared query. Client-specified takes priority. - qs := args.Source - if qs.Datacenter == "" { - qs.Datacenter = args.Agent.Datacenter - } - if query.Service.Near != "" && qs.Node == "" { - qs.Node = query.Service.Near - } + // Build the query source. This can be provided by the client, or by + // the prepared query. Client-specified takes priority. + qs := args.Source + if qs.Datacenter == "" { + qs.Datacenter = args.Agent.Datacenter + } + if query.Service.Near != "" && qs.Node == "" { + qs.Node = query.Service.Near + } - // Respect the magic "_agent" flag. - if qs.Node == "_agent" { - qs.Node = args.Agent.Node - } else if qs.Node == "_ip" { - if args.Source.Ip != "" { - _, nodes, err := state.Nodes(nil, structs.NodeEnterpriseMetaInDefaultPartition(), structs.TODOPeerKeyword) - if err != nil { - return err - } + // Respect the magic "_agent" flag. + if qs.Node == "_agent" { + qs.Node = args.Agent.Node + } else if qs.Node == "_ip" { + if args.Source.Ip != "" { + _, nodes, err := state.Nodes(nil, structs.NodeEnterpriseMetaInDefaultPartition(), structs.TODOPeerKeyword) + if err != nil { + return err + } - for _, node := range nodes { - if args.Source.Ip == node.Address { - qs.Node = node.Node - break + for _, node := range nodes { + if args.Source.Ip == node.Address { + qs.Node = node.Node + break + } } + } else { + p.logger.Warn("Prepared Query using near=_ip requires " + + "the source IP to be set but none was provided. No distance " + + "sorting will be done.") + } - } else { - p.logger.Warn("Prepared Query using near=_ip requires " + - "the source IP to be set but none was provided. No distance " + - "sorting will be done.") + // Either a source IP was given, but we couldn't find the associated node + // or no source ip was given. In both cases we should wipe the Node value + if qs.Node == "_ip" { + qs.Node = "" + } } - // Either a source IP was given but we couldnt find the associated node - // or no source ip was given. In both cases we should wipe the Node value - if qs.Node == "_ip" { - qs.Node = "" + // Perform the distance sort + err = p.srv.sortNodesByDistanceFrom(qs, reply.Nodes) + if err != nil { + return err } - } - - // Perform the distance sort - err = p.srv.sortNodesByDistanceFrom(qs, reply.Nodes) - if err != nil { - return err - } - // If we applied a distance sort, make sure that the node queried for is in - // position 0, provided the results are from the same datacenter. - if qs.Node != "" && reply.Datacenter == qs.Datacenter { - for i, node := range reply.Nodes { - if strings.EqualFold(node.Node.Node, qs.Node) { - reply.Nodes[0], reply.Nodes[i] = reply.Nodes[i], reply.Nodes[0] - break - } + // If we applied a distance sort, make sure that the node queried for is in + // position 0, provided the results are from the same datacenter. + if qs.Node != "" && reply.Datacenter == qs.Datacenter { + for i, node := range reply.Nodes { + if strings.EqualFold(node.Node.Node, qs.Node) { + reply.Nodes[0], reply.Nodes[i] = reply.Nodes[i], reply.Nodes[0] + break + } - // Put a cap on the depth of the search. The local agent should - // never be further in than this if distance sorting was applied. - if i == 9 { - break + // Put a cap on the depth of the search. The local agent should + // never be further in than this if distance sorting was applied. + if i == 9 { + break + } } } - } - // Apply the limit if given. - if args.Limit > 0 && len(reply.Nodes) > args.Limit { - reply.Nodes = reply.Nodes[:args.Limit] - } + // Apply the limit if given. + if args.Limit > 0 && len(reply.Nodes) > args.Limit { + reply.Nodes = reply.Nodes[:args.Limit] + } - // In the happy path where we found some healthy nodes we go with that - // and bail out. Otherwise, we fail over and try remote DCs, as allowed - // by the query setup. - if len(reply.Nodes) == 0 { - wrapper := &queryServerWrapper{srv: p.srv, executeRemote: p.ExecuteRemote} - if err := queryFailover(wrapper, *query, args, reply); err != nil { - return err + // In the happy path where we found some healthy nodes we go with that + // and bail out. Otherwise, we fail over and try remote DCs, as allowed + // by the query setup. + if len(reply.Nodes) == 0 { + wrapper := newQueryServerWrapper(p.srv, p.ExecuteRemote) + if err := queryFailover(wrapper, *query, args, reply); err != nil { + return err + } } } @@ -660,20 +673,38 @@ func serviceMetaFilter(filters map[string]string, nodes structs.CheckServiceNode return filtered } +type stateLookuper interface { + samenessGroupLookup(name string, entMeta acl.EnterpriseMeta) (uint64, *structs.SamenessGroupConfigEntry, error) +} + +type stateLookup struct { + srv *Server +} + // queryServer is a wrapper that makes it easier to test the failover logic. type queryServer interface { GetLogger() hclog.Logger GetOtherDatacentersByDistance() ([]string, error) GetLocalDC() string ExecuteRemote(args *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error + GetSamenessGroupFailoverTargets(name string, entMeta acl.EnterpriseMeta) ([]structs.QueryFailoverTarget, error) } // queryServerWrapper applies the queryServer interface to a Server. type queryServerWrapper struct { srv *Server + sl stateLookuper executeRemote func(args *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error } +func newQueryServerWrapper(srv *Server, executeRemote func(args *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error) *queryServerWrapper { + return &queryServerWrapper{ + srv: srv, + executeRemote: executeRemote, + sl: stateLookup{srv}, + } +} + // GetLocalDC returns the name of the local datacenter. func (q *queryServerWrapper) GetLocalDC() string { return q.srv.config.Datacenter @@ -771,43 +802,8 @@ func queryFailover(q queryServer, query structs.PreparedQuery, // This keeps track of how many iterations we actually run. failovers++ - // Be super paranoid and set the nodes slice to nil since it's - // the same slice we used before. We know there's nothing in - // there, but the underlying msgpack library has a policy of - // updating the slice when it's non-nil, and that feels dirty. - // Let's just set it to nil so there's no way to communicate - // through this slice across successive RPC calls. - reply.Nodes = nil - - // Reset Peer, because it may have been set by a previous failover - // target. - query.Service.Peer = target.Peer - query.Service.EnterpriseMeta = target.EnterpriseMeta - dc := target.Datacenter - if target.Peer != "" { - dc = q.GetLocalDC() - } - - // Note that we pass along the limit since may be applied - // remotely to save bandwidth. We also pass along the consistency - // mode information and token we were given, so that applies to - // the remote query as well. - remote := &structs.PreparedQueryExecuteRemoteRequest{ - Datacenter: dc, - Query: query, - Limit: args.Limit, - QueryOptions: args.QueryOptions, - Connect: args.Connect, - } - - if err = q.ExecuteRemote(remote, reply); err != nil { - q.GetLogger().Warn("Failed querying for service in datacenter", - "service", query.Service.Service, - "peerName", query.Service.Peer, - "datacenter", dc, - "enterpriseMeta", query.Service.EnterpriseMeta, - "error", err, - ) + err = targetSelector(q, query, args, target, reply) + if err != nil { continue } @@ -823,3 +819,52 @@ func queryFailover(q queryServer, query structs.PreparedQuery, return nil } + +func targetSelector(q queryServer, + query structs.PreparedQuery, + args *structs.PreparedQueryExecuteRequest, + target structs.QueryFailoverTarget, + reply *structs.PreparedQueryExecuteResponse) error { + // Be super paranoid and set the nodes slice to nil since it's + // the same slice we used before. We know there's nothing in + // there, but the underlying msgpack library has a policy of + // updating the slice when it's non-nil, and that feels dirty. + // Let's just set it to nil so there's no way to communicate + // through this slice across successive RPC calls. + reply.Nodes = nil + + // Reset Peer, because it may have been set by a previous failover + // target. + query.Service.Peer = target.Peer + query.Service.EnterpriseMeta = target.EnterpriseMeta + dc := target.Datacenter + if target.Peer != "" { + dc = q.GetLocalDC() + } + + // Note that we pass along the limit since may be applied + // remotely to save bandwidth. We also pass along the consistency + // mode information and token we were given, so that applies to + // the remote query as well. + remote := &structs.PreparedQueryExecuteRemoteRequest{ + Datacenter: dc, + Query: query, + Limit: args.Limit, + QueryOptions: args.QueryOptions, + Connect: args.Connect, + } + + var err error + if err = q.ExecuteRemote(remote, reply); err != nil { + q.GetLogger().Warn("Failed querying for service in datacenter", + "service", query.Service.Service, + "peerName", query.Service.Peer, + "datacenter", dc, + "enterpriseMeta", query.Service.EnterpriseMeta, + "error", err, + ) + return err + } + + return nil +} diff --git a/agent/consul/prepared_query_endpoint_oss.go b/agent/consul/prepared_query_endpoint_oss.go new file mode 100644 index 000000000000..612b81b2e688 --- /dev/null +++ b/agent/consul/prepared_query_endpoint_oss.go @@ -0,0 +1,38 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build !consulent +// +build !consulent + +package consul + +import ( + "fmt" + + "github.com/hashicorp/consul/acl" + "github.com/hashicorp/consul/agent/structs" +) + +func parseSameness(svc *structs.ServiceQuery) error { + if svc.SamenessGroup != "" { + return fmt.Errorf("sameness-groups are an enterprise-only feature") + } + return nil +} + +func (sl stateLookup) samenessGroupLookup(_ string, _ acl.EnterpriseMeta) (uint64, *structs.SamenessGroupConfigEntry, error) { + return 0, nil, nil +} + +// GetSamenessGroupFailoverTargets supports Sameness Groups an enterprise only feature. This satisfies the queryServer interface +func (q *queryServerWrapper) GetSamenessGroupFailoverTargets(_ string, _ acl.EnterpriseMeta) ([]structs.QueryFailoverTarget, error) { + return []structs.QueryFailoverTarget{}, nil +} + +func querySameness(_ queryServer, + _ structs.PreparedQuery, + _ *structs.PreparedQueryExecuteRequest, + _ *structs.PreparedQueryExecuteResponse) error { + + return nil +} diff --git a/agent/consul/prepared_query_endpoint_oss_test.go b/agent/consul/prepared_query_endpoint_oss_test.go new file mode 100644 index 000000000000..ee96a7b2671a --- /dev/null +++ b/agent/consul/prepared_query_endpoint_oss_test.go @@ -0,0 +1,52 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build !consulent +// +build !consulent + +package consul + +import ( + "os" + "testing" + + msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" + "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/testrpc" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPreparedQuery_OSS_Apply(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + dir1, s1 := testServerWithConfig(t) + defer os.RemoveAll(dir1) + defer s1.Shutdown() + codec := rpcClient(t, s1) + defer codec.Close() + + testrpc.WaitForLeader(t, s1.RPC, "dc1") + + // Set up a bare bones query. + query := structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "redis", + }, + }, + } + var reply string + + // Fix that and ensure Targets and Datacenters cannot be set at the same time. + query.Query.Service.SamenessGroup = "sg" + err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply) + require.Error(t, err) + assert.Contains(t, err.Error(), "enterprise") +} diff --git a/agent/consul/prepared_query_endpoint_test.go b/agent/consul/prepared_query_endpoint_test.go index 0e365a30d48c..63277adfe27c 100644 --- a/agent/consul/prepared_query_endpoint_test.go +++ b/agent/consul/prepared_query_endpoint_test.go @@ -6,6 +6,7 @@ package consul import ( "bytes" "context" + "errors" "fmt" "os" "reflect" @@ -37,6 +38,8 @@ import ( "github.com/hashicorp/consul/types" ) +const localTestDC = "dc1" + func TestPreparedQuery_Apply(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") @@ -2814,13 +2817,17 @@ func TestPreparedQuery_Wrapper(t *testing.T) { }) } +var _ queryServer = (*mockQueryServer)(nil) + type mockQueryServer struct { + queryServerWrapper Datacenters []string DatacentersError error QueryLog []string QueryFn func(args *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error Logger hclog.Logger LogBuffer *bytes.Buffer + SamenessGroup map[string]*structs.SamenessGroupConfigEntry } func (m *mockQueryServer) JoinQueryLog() string { @@ -2841,7 +2848,7 @@ func (m *mockQueryServer) GetLogger() hclog.Logger { } func (m *mockQueryServer) GetLocalDC() string { - return "dc1" + return localTestDC } func (m *mockQueryServer) GetOtherDatacentersByDistance() ([]string, error) { @@ -2850,14 +2857,21 @@ func (m *mockQueryServer) GetOtherDatacentersByDistance() ([]string, error) { func (m *mockQueryServer) ExecuteRemote(args *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error { peerName := args.Query.Service.Peer + partitionName := args.Query.Service.PartitionOrEmpty() + namespaceName := args.Query.Service.NamespaceOrEmpty() dc := args.Datacenter if peerName != "" { m.QueryLog = append(m.QueryLog, fmt.Sprintf("peer:%s", peerName)) + } else if partitionName != "" { + m.QueryLog = append(m.QueryLog, fmt.Sprintf("partition:%s", partitionName)) + } else if namespaceName != "" { + m.QueryLog = append(m.QueryLog, fmt.Sprintf("namespace:%s", namespaceName)) } else { m.QueryLog = append(m.QueryLog, fmt.Sprintf("%s:%s", dc, "PreparedQuery.ExecuteRemote")) } reply.PeerName = peerName reply.Datacenter = dc + reply.EnterpriseMeta = acl.NewEnterpriseMetaWithPartition(partitionName, namespaceName) if m.QueryFn != nil { return m.QueryFn(args, reply) @@ -2865,6 +2879,33 @@ func (m *mockQueryServer) ExecuteRemote(args *structs.PreparedQueryExecuteRemote return nil } +type mockStateLookup struct { + SamenessGroup map[string]*structs.SamenessGroupConfigEntry +} + +func (sl mockStateLookup) samenessGroupLookup(name string, entMeta acl.EnterpriseMeta) (uint64, *structs.SamenessGroupConfigEntry, error) { + lookup := name + if ap := entMeta.PartitionOrEmpty(); ap != "" { + lookup = fmt.Sprintf("%s-%s", lookup, ap) + } else if ns := entMeta.NamespaceOrEmpty(); ns != "" { + lookup = fmt.Sprintf("%s-%s", lookup, ns) + } + + sg, ok := sl.SamenessGroup[lookup] + if !ok { + return 0, nil, errors.New("unable to find sameness group") + } + + return 0, sg, nil +} + +func (m *mockQueryServer) GetSamenessGroupFailoverTargets(name string, entMeta acl.EnterpriseMeta) ([]structs.QueryFailoverTarget, error) { + m.sl = mockStateLookup{ + SamenessGroup: m.SamenessGroup, + } + return m.queryServerWrapper.GetSamenessGroupFailoverTargets(name, entMeta) +} + func TestPreparedQuery_queryFailover(t *testing.T) { t.Parallel() query := structs.PreparedQuery{ diff --git a/agent/consul/state/config_entry_sameness_group.go b/agent/consul/state/config_entry_sameness_group.go new file mode 100644 index 000000000000..7f02787b6553 --- /dev/null +++ b/agent/consul/state/config_entry_sameness_group.go @@ -0,0 +1,19 @@ +package state + +import ( + "github.com/hashicorp/consul/agent/configentry" + "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/go-memdb" +) + +// GetSamenessGroup returns a SamenessGroupConfigEntry from the state +// store using the provided parameters. +func (s *Store) GetSamenessGroup(ws memdb.WatchSet, + name string, + overrides map[configentry.KindName]structs.ConfigEntry, + partition string) (uint64, *structs.SamenessGroupConfigEntry, error) { + tx := s.db.ReadTxn() + defer tx.Abort() + + return getSamenessGroupConfigEntryTxn(tx, ws, name, overrides, partition) +} diff --git a/agent/structs/config_entry_sameness_group_oss.go b/agent/structs/config_entry_sameness_group_oss.go index ee23feb3908b..0693ed0ee850 100644 --- a/agent/structs/config_entry_sameness_group_oss.go +++ b/agent/structs/config_entry_sameness_group_oss.go @@ -8,20 +8,27 @@ package structs import "fmt" +// Validate assures that the sameness-groups are an enterprise only feature func (s *SamenessGroupConfigEntry) Validate() error { return fmt.Errorf("sameness-groups are an enterprise-only feature") } -// RelatedPeers returns all peers that are members of a sameness group config entry. +// RelatedPeers is an OSS placeholder noop func (s *SamenessGroupConfigEntry) RelatedPeers() []string { return nil } -// AllMembers adds the local partition to Members when it is set. +// AllMembers is an OSS placeholder noop func (s *SamenessGroupConfigEntry) AllMembers() []SamenessGroupMember { return nil } -func (s *SamenessGroupConfigEntry) ToFailoverTargets() []ServiceResolverFailoverTarget { +// ToServiceResolverFailoverTargets is an OSS placeholder noop +func (s *SamenessGroupConfigEntry) ToServiceResolverFailoverTargets() []ServiceResolverFailoverTarget { + return nil +} + +// ToQueryFailoverTargets is an OSS placeholder noop +func (s *SamenessGroupConfigEntry) ToQueryFailoverTargets(namespace string) []QueryFailoverTarget { return nil } diff --git a/agent/structs/prepared_query.go b/agent/structs/prepared_query.go index c5b75a052978..71b0eba81bc3 100644 --- a/agent/structs/prepared_query.go +++ b/agent/structs/prepared_query.go @@ -44,6 +44,14 @@ func (f *QueryFailoverOptions) AsTargets() []QueryFailoverTarget { return f.Targets } +// IsEmpty returns true if the QueryFailoverOptions are empty (not set), false otherwise +func (f *QueryFailoverOptions) IsEmpty() bool { + if f == nil || (f.NearestN == 0 && len(f.Datacenters) == 0 && len(f.Targets) == 0) { + return true + } + return false +} + type QueryFailoverTarget struct { // Peer specifies a peer to try during failover. Peer string @@ -66,6 +74,11 @@ type ServiceQuery struct { // Service is the service to query. Service string + // SamenessGroup specifies a sameness group to query. The first member of the Sameness Group will + // be targeted first on PQ execution and subsequent members will be targeted during failover scenarios. + // This field is mutually exclusive with Failover. + SamenessGroup string + // Failover controls what we do if there are no healthy nodes in the // local datacenter. Failover QueryFailoverOptions diff --git a/agent/structs/prepared_query_test.go b/agent/structs/prepared_query_test.go index b74504ed58e1..a6d6e64849af 100644 --- a/agent/structs/prepared_query_test.go +++ b/agent/structs/prepared_query_test.go @@ -5,6 +5,8 @@ package structs import ( "testing" + + "github.com/stretchr/testify/assert" ) func TestStructs_PreparedQuery_GetACLPrefix(t *testing.T) { @@ -36,3 +38,54 @@ func TestPreparedQueryExecuteRequest_CacheInfoKey(t *testing.T) { ignored := []string{"Agent", "QueryOptions"} assertCacheInfoKeyIsComplete(t, &PreparedQueryExecuteRequest{}, ignored...) } + +func TestQueryFailoverOptions_IsEmpty(t *testing.T) { + tests := []struct { + name string + query QueryFailoverOptions + isExpectedEmpty bool + }{ + { + name: "expect empty", + query: QueryFailoverOptions{}, + isExpectedEmpty: true, + }, + { + name: "expect not empty NearestN", + query: QueryFailoverOptions{ + NearestN: 1, + }, + isExpectedEmpty: false, + }, + { + name: "expect not empty NearestN negative", + query: QueryFailoverOptions{ + NearestN: -1, + }, + isExpectedEmpty: false, + }, + { + name: "expect not empty datacenters", + query: QueryFailoverOptions{ + Datacenters: []string{"dc"}, + }, + isExpectedEmpty: false, + }, + { + name: "expect not empty targets", + query: QueryFailoverOptions{ + Targets: []QueryFailoverTarget{ + { + Peer: "peer", + }, + }, + }, + isExpectedEmpty: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.isExpectedEmpty, tt.query.IsEmpty()) + }) + } +} diff --git a/api/prepared_query.go b/api/prepared_query.go index b79c9ae41281..bb40e6a7fddd 100644 --- a/api/prepared_query.go +++ b/api/prepared_query.go @@ -51,9 +51,17 @@ type ServiceQuery struct { // Service is the service to query. Service string + // SamenessGroup specifies a sameness group to query. The first member of the Sameness Group will + // be targeted first on PQ execution and subsequent members will be targeted during failover scenarios. + // This field is mutually exclusive with Failover. + SamenessGroup string `json:",omitempty"` + // Namespace of the service to query Namespace string `json:",omitempty"` + // Partition of the service to query + Partition string `json:",omitempty"` + // Near allows baking in the name of a node to automatically distance- // sort from. The magic "_agent" value is supported, which sorts near // the agent which initiated the request by default. @@ -61,7 +69,7 @@ type ServiceQuery struct { // Failover controls what we do if there are no healthy nodes in the // local datacenter. - Failover QueryFailoverOptions + Failover QueryFailoverOptions `json:",omitempty"` // IgnoreCheckIDs is an optional list of health check IDs to ignore when // considering which nodes are healthy. It is useful as an emergency measure diff --git a/website/content/api-docs/query.mdx b/website/content/api-docs/query.mdx index 81c6ed0e4ac9..3a46b383d42d 100644 --- a/website/content/api-docs/query.mdx +++ b/website/content/api-docs/query.mdx @@ -177,13 +177,22 @@ The table below shows this endpoint's support for - `Service` `(string: )` - Specifies the name of the service to query. + - `SamenessGroup` `(string: "")` - Specifies a Sameness group to forward the + query to. The `SamenessGroup` will forward to its members in the order defined, returning on the first + healthy query. `SamenessGroup` is mutually exclusive with `Failover` as `SamenessGroup` members will be used + in place of a defined list of failovers. + - `Namespace` `(string: "")` - Specifies the Consul namespace - to query. If not provided the query will use Consul default namespace for resolution. + to query. If not provided the query will use Consul default namespace for resolution. When combined with + `SamenessGroup` this will specify the namespaces in which the `SamenessGroup` will resolve all members listed. + + - `Partition` `(string: "")` - Specifies the Consul partition + to query. If not provided the query will use Consul's default partition for resolution. When combined with + `SamenessGroup`, this will specify the partition where the `SamenessGroup` exists. - - `Failover` contains two fields, both of which are optional, and determine - what happens if no healthy nodes are available in the local datacenter when + - `Failover` Determines what happens if no healthy nodes are available in the local datacenter when the query is executed. It allows the use of nodes in other datacenters with - very little configuration. + very little configuration. This field is mutually exclusive with `SamenessGroup`. - `NearestN` `(int: 0)` - Specifies that the query will be forwarded to up to `NearestN` other datacenters based on their estimated network round From e47f3216e51b7938681b0ad923f86dc8b408918f Mon Sep 17 00:00:00 2001 From: John Maguire Date: Mon, 24 Apr 2023 16:22:55 -0400 Subject: [PATCH 06/92] APIGW Normalize Status Conditions (#16994) * normalize status conditions for gateways and routes * Added tests for checking condition status and panic conditions for validating combinations, added dummy code for fsm store * get rid of unneeded gateway condition generator struct * Remove unused file * run go mod tidy * Update tests, add conflicted gateway status * put back removed status for test * Fix linting violation, remove custom conflicted status * Update fsm commands oss * Fix incorrect combination of type/condition/status * cleaning up from PR review * Change "invalidCertificate" to be of accepted status * Move status condition enums into api package * Update gateways controller and generated code * Update conditions in fsm oss tests * run go mod tidy on consul-container module to fix linting * Fix type for gateway endpoint test * go mod tidy from changes to api * go mod tidy on troubleshoot * Fix route conflicted reason * fix route conflict reason rename * Fix text for gateway conflicted status * Add valid certificate ref condition setting * Revert change to resolved refs to be handled in future PR --- agent/consul/fsm/commands_oss_test.go | 21 +- agent/consul/fsm_data_store.go | 80 ----- agent/consul/gateways/controller_gateways.go | 269 ++++++++--------- .../gateways/controller_gateways_test.go | 266 +++++++++-------- agent/structs/config_entry_status.go | 89 ++++-- api/config_entry_status.go | 282 +++++++++++++++++- api/config_entry_status_test.go | 187 ++++++++++++ api/go.mod | 6 +- api/go.sum | 12 +- envoyextensions/go.mod | 3 +- envoyextensions/go.sum | 8 +- go.mod | 4 +- go.sum | 8 +- test/integration/consul-container/go.mod | 2 +- test/integration/consul-container/go.sum | 4 +- .../test/gateways/gateway_endpoint_test.go | 2 +- troubleshoot/go.mod | 2 +- troubleshoot/go.sum | 3 +- 18 files changed, 826 insertions(+), 422 deletions(-) delete mode 100644 agent/consul/fsm_data_store.go create mode 100644 api/config_entry_status_test.go diff --git a/agent/consul/fsm/commands_oss_test.go b/agent/consul/fsm/commands_oss_test.go index 981de232a6ba..cea6f05f54f3 100644 --- a/agent/consul/fsm/commands_oss_test.go +++ b/agent/consul/fsm/commands_oss_test.go @@ -13,11 +13,6 @@ import ( "testing" "time" - "github.com/hashicorp/go-raftchunking" - raftchunkingtypes "github.com/hashicorp/go-raftchunking/types" - "github.com/hashicorp/go-uuid" - "github.com/hashicorp/raft" - "github.com/hashicorp/serf/coordinate" "github.com/mitchellh/mapstructure" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -34,6 +29,11 @@ import ( "github.com/hashicorp/consul/proto/private/prototest" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/types" + "github.com/hashicorp/go-raftchunking" + raftchunkingtypes "github.com/hashicorp/go-raftchunking/types" + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/raft" + "github.com/hashicorp/serf/coordinate" ) func generateUUID() (ret string) { @@ -1372,9 +1372,10 @@ func TestFSM_ConfigEntry_StatusCAS(t *testing.T) { EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(), Status: structs.Status{ Conditions: []structs.Condition{{ - Status: "Foo", + Status: string(api.ConditionStatusTrue), }}, - }} + }, + } // Create a new request. req := &structs.ConfigEntryRequest{ @@ -1403,7 +1404,7 @@ func TestFSM_ConfigEntry_StatusCAS(t *testing.T) { // do a status update entry.Status = structs.Status{ Conditions: []structs.Condition{{ - Status: "Foo", + Status: string(api.ConditionStatusTrue), }}, } req = &structs.ConfigEntryRequest{ @@ -1427,7 +1428,7 @@ func TestFSM_ConfigEntry_StatusCAS(t *testing.T) { entry.RaftIndex.ModifyIndex = 2 conditions := config.(*structs.APIGatewayConfigEntry).Status.Conditions require.Len(t, conditions, 1) - require.Equal(t, "Foo", conditions[0].Status) + require.Equal(t, string(api.ConditionStatusTrue), conditions[0].Status) } // attempt to change the status with a regular update and make sure it's ignored @@ -1456,7 +1457,7 @@ func TestFSM_ConfigEntry_StatusCAS(t *testing.T) { require.NoError(t, err) conditions := config.(*structs.APIGatewayConfigEntry).Status.Conditions require.Len(t, conditions, 1) - require.Equal(t, "Foo", conditions[0].Status) + require.Equal(t, string(api.ConditionStatusTrue), conditions[0].Status) } } diff --git a/agent/consul/fsm_data_store.go b/agent/consul/fsm_data_store.go deleted file mode 100644 index 46c3ca2f0f4e..000000000000 --- a/agent/consul/fsm_data_store.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package consul - -import ( - "github.com/hashicorp/consul/acl" - "github.com/hashicorp/consul/agent/consul/fsm" - "github.com/hashicorp/consul/agent/structs" -) - -// FSMDataStore implements the DataStore interface using the Consul server and finite state manager. -type FSMDataStore struct { - server *Server - fsm *fsm.FSM -} - -func NewFSMDataStore(server *Server, fsm *fsm.FSM) *FSMDataStore { - return &FSMDataStore{ - server: server, - fsm: fsm, - } -} - -// GetConfigEntry takes in a kind, name, and meta and returns a configentry and an error from the FSM state -func (f *FSMDataStore) GetConfigEntry(kind string, name string, meta *acl.EnterpriseMeta) (structs.ConfigEntry, error) { - store := f.fsm.State() - - _, entry, err := store.ConfigEntry(nil, kind, name, meta) - if err != nil { - return nil, err - } - return entry, nil -} - -// GetConfigEntriesByKind takes in a kind and returns all instances of that kind of config entry from the FSM state -func (f *FSMDataStore) GetConfigEntriesByKind(kind string) ([]structs.ConfigEntry, error) { - store := f.fsm.State() - - _, entries, err := store.ConfigEntriesByKind(nil, kind, acl.WildcardEnterpriseMeta()) - if err != nil { - return nil, err - } - return entries, nil -} - -// Update takes a config entry and upserts it in the FSM state -func (f *FSMDataStore) Update(entry structs.ConfigEntry) error { - _, err := f.server.leaderRaftApply("ConfigEntry.Apply", structs.ConfigEntryRequestType, &structs.ConfigEntryRequest{ - Op: structs.ConfigEntryUpsertCAS, - Entry: entry, - }) - return err -} - -// UpdateStatus takes a config entry, an error, and updates the status field as needed in the FSM state -func (f *FSMDataStore) UpdateStatus(entry structs.ControlledConfigEntry, err error) error { - if err == nil { - //TODO additional status messages for success? - return nil - } - status := structs.Status{ - Conditions: []structs.Condition{{ - - Status: err.Error() + ": Accepted == false", - }, - }, - } - entry.SetStatus(status) - return f.Update(entry) -} - -// Delete takes a config entry and deletes it from the FSM state -func (f *FSMDataStore) Delete(entry structs.ConfigEntry) error { - _, err := f.server.leaderRaftApply("ConfigEntry.Delete", structs.ConfigEntryRequestType, &structs.ConfigEntryRequest{ - Op: structs.ConfigEntryDelete, - Entry: entry, - }) - return err -} diff --git a/agent/consul/gateways/controller_gateways.go b/agent/consul/gateways/controller_gateways.go index afb687fe0971..e399503f355f 100644 --- a/agent/consul/gateways/controller_gateways.go +++ b/agent/consul/gateways/controller_gateways.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "sync" - "time" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-memdb" @@ -20,6 +19,7 @@ import ( "github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/consul/stream" "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/api" ) var ( @@ -211,8 +211,6 @@ func (r *apiGatewayReconciler) cleanupGateway(_ context.Context, req controller. // referenced this gateway. It then persists any status updates for the gateway, // the modified routes, and updates the bound gateway. func (r *apiGatewayReconciler) reconcileGateway(_ context.Context, req controller.Request, store *state.Store, gateway *structs.APIGatewayConfigEntry) error { - conditions := newGatewayConditionGenerator() - logger := gatewayRequestLogger(r.logger, req) logger.Trace("started reconciling gateway") @@ -244,13 +242,13 @@ func (r *apiGatewayReconciler) reconcileGateway(_ context.Context, req controlle } for ref, err := range certificateErrors { - updater.SetCondition(conditions.invalidCertificate(ref, err)) + updater.SetCondition(invalidCertificate(ref, err)) } if len(certificateErrors) > 0 { - updater.SetCondition(conditions.invalidCertificates()) + updater.SetCondition(invalidCertificates()) } else { - updater.SetCondition(conditions.gatewayAccepted()) + updater.SetCondition(gatewayAccepted()) } // now we bind all of the routes we can @@ -262,19 +260,19 @@ func (r *apiGatewayReconciler) reconcileGateway(_ context.Context, req controlle // unset the old gateway binding in case it's stale for _, parent := range route.GetParents() { if parent.Kind == gateway.Kind && parent.Name == gateway.Name && parent.EnterpriseMeta.IsSame(&gateway.EnterpriseMeta) { - routeUpdater.RemoveCondition(conditions.routeBound(parent)) + routeUpdater.RemoveCondition(routeBound(parent)) } } // set the status for parents that have bound successfully for _, ref := range boundRefs { - routeUpdater.SetCondition(conditions.routeBound(ref)) + routeUpdater.SetCondition(routeBound(ref)) } // set the status for any parents that have errored trying to // bind for ref, err := range bindErrors { - routeUpdater.SetCondition(conditions.routeUnbound(ref, err)) + routeUpdater.SetCondition(routeUnbound(ref, err)) } // if we've updated any statuses, then store them as needing @@ -354,8 +352,6 @@ func (r *apiGatewayReconciler) cleanupRoute(_ context.Context, req controller.Re // gateways that now have route conflicts, and updates all statuses and states // as necessary. func (r *apiGatewayReconciler) reconcileRoute(_ context.Context, req controller.Request, store *state.Store, route structs.BoundRoute) error { - conditions := newGatewayConditionGenerator() - logger := routeRequestLogger(r.logger, req) logger.Trace("reconciling route") @@ -435,23 +431,23 @@ func (r *apiGatewayReconciler) reconcileRoute(_ context.Context, req controller. Entries: chainSet, }) if err != nil { - updater.SetCondition(conditions.routeInvalidDiscoveryChain(err)) + updater.SetCondition(routeInvalidDiscoveryChain(err)) continue } if chain.Protocol != string(route.GetProtocol()) { - updater.SetCondition(conditions.routeInvalidDiscoveryChain(errInvalidProtocol)) + updater.SetCondition(routeInvalidDiscoveryChain(errInvalidProtocol)) continue } - updater.SetCondition(conditions.routeAccepted()) + updater.SetCondition(routeAccepted()) } // if we have no upstream targets, then set the route as invalid // this should already happen in the validation check on write, but // we'll do it here too just in case if len(route.GetServiceNames()) == 0 { - updater.SetCondition(conditions.routeNoUpstreams()) + updater.SetCondition(routeNoUpstreams()) } // the route is valid, attempt to bind it to all gateways @@ -460,12 +456,12 @@ func (r *apiGatewayReconciler) reconcileRoute(_ context.Context, req controller. // set the status of the references that are bound for _, ref := range boundRefs { - updater.SetCondition(conditions.routeBound(ref)) + updater.SetCondition(routeBound(ref)) } // set any binding errors for ref, err := range bindErrors { - updater.SetCondition(conditions.routeUnbound(ref, err)) + updater.SetCondition(routeUnbound(ref, err)) } // set any refs that haven't been bound or explicitly errored @@ -479,7 +475,7 @@ PARENT_LOOP: if _, ok := bindErrors[ref]; ok { continue PARENT_LOOP } - updater.SetCondition(conditions.gatewayNotFound(ref)) + updater.SetCondition(gatewayNotFound(ref)) } return finalize(modifiedGateways) @@ -549,8 +545,6 @@ type gatewayMeta struct { // the map values are pointers so that we can update them directly // and have the changes propagate back to the container gateways. boundListeners map[string]*structs.BoundAPIGatewayListener - - generator *gatewayConditionGenerator } // getAllGatewayMeta returns a pre-constructed list of all valid gateway and state @@ -678,13 +672,13 @@ func (g *gatewayMeta) bindRoute(listener *structs.APIGatewayListener, bound *str } // check to make sure we're not binding to an invalid gateway - if !g.Gateway.Status.MatchesConditionStatus(g.generator.gatewayAccepted()) { + if !g.Gateway.Status.MatchesConditionStatus(gatewayAccepted()) { return false, fmt.Errorf("failed to bind route to gateway %s: gateway has not been accepted", g.Gateway.Name) } // check to make sure we're not binding to an invalid route status := route.GetStatus() - if !status.MatchesConditionStatus(g.generator.routeAccepted()) { + if !status.MatchesConditionStatus(routeAccepted()) { return false, fmt.Errorf("failed to bind route to gateway %s: route has not been accepted", g.Gateway.Name) } @@ -755,7 +749,6 @@ func (g *gatewayMeta) checkCertificates(store *state.Store) (map[structs.Resourc } return nil }) - if err != nil { return nil, err } @@ -773,8 +766,6 @@ func (g *gatewayMeta) checkConflicts() (structs.ControlledConfigEntry, bool) { // setConflicts ensures that no TCP listener has more than the one allowed route and // assigns an appropriate status func (g *gatewayMeta) setConflicts(updater *structs.StatusUpdater) { - conditions := newGatewayConditionGenerator() - g.eachListener(func(listener *structs.APIGatewayListener, bound *structs.BoundAPIGatewayListener) error { ref := structs.ResourceReference{ Kind: structs.APIGateway, @@ -785,19 +776,17 @@ func (g *gatewayMeta) setConflicts(updater *structs.StatusUpdater) { switch listener.Protocol { case structs.ListenerProtocolTCP: if len(bound.Routes) > 1 { - updater.SetCondition(conditions.gatewayListenerConflicts(ref)) + updater.SetCondition(gatewayListenerConflicts(ref)) return nil } } - updater.SetCondition(conditions.gatewayListenerNoConflicts(ref)) + updater.SetCondition(gatewayListenerNoConflicts(ref)) return nil }) } // initialize sets up the listener maps that we use for quickly indexing the listeners in our binding logic func (g *gatewayMeta) initialize() *gatewayMeta { - g.generator = newGatewayConditionGenerator() - // set up the maps for fast access g.boundListeners = make(map[string]*structs.BoundAPIGatewayListener, len(g.BoundGateway.Listeners)) for i, listener := range g.BoundGateway.Listeners { @@ -840,151 +829,132 @@ func newGatewayMeta(gateway *structs.APIGatewayConfigEntry, bound structs.Config }).initialize() } -// gatewayConditionGenerator is a simple struct used for isolating -// the status conditions that we generate for our components -type gatewayConditionGenerator struct { - now *time.Time -} - -// newGatewayConditionGenerator initializes a status conditions generator -func newGatewayConditionGenerator() *gatewayConditionGenerator { - return &gatewayConditionGenerator{ - now: pointerTo(time.Now().UTC()), - } +// gatewayAccepted marks the APIGateway as valid. +func gatewayAccepted() structs.Condition { + return structs.NewGatewayCondition( + api.GatewayConditionAccepted, + api.ConditionStatusTrue, + api.GatewayReasonAccepted, + "gateway is valid", + structs.ResourceReference{}, + ) } // invalidCertificate returns a condition used when a gateway references a // certificate that does not exist. It takes a ref used to scope the condition // to a given APIGateway listener. -func (g *gatewayConditionGenerator) invalidCertificate(ref structs.ResourceReference, err error) structs.Condition { - return structs.Condition{ - Type: "Accepted", - Status: "False", - Reason: "InvalidCertificate", - Message: err.Error(), - Resource: pointerTo(ref), - LastTransitionTime: g.now, - } +func invalidCertificate(ref structs.ResourceReference, err error) structs.Condition { + return structs.NewGatewayCondition( + api.GatewayConditionAccepted, + api.ConditionStatusFalse, + api.GatewayListenerReasonInvalidCertificateRef, + err.Error(), + ref, + ) } // invalidCertificates is used to set the overall condition of the APIGateway // to invalid due to missing certificates that it references. -func (g *gatewayConditionGenerator) invalidCertificates() structs.Condition { - return structs.Condition{ - Type: "Accepted", - Status: "False", - Reason: "InvalidCertificates", - Message: "gateway references invalid certificates", - LastTransitionTime: g.now, - } +func invalidCertificates() structs.Condition { + return structs.NewGatewayCondition( + api.GatewayConditionAccepted, + api.ConditionStatusFalse, + api.GatewayReasonInvalidCertificates, + "gateway references invalid certificates", + structs.ResourceReference{}, + ) } -// gatewayAccepted marks the APIGateway as valid. -func (g *gatewayConditionGenerator) gatewayAccepted() structs.Condition { - return structs.Condition{ - Type: "Accepted", - Status: "True", - Reason: "Accepted", - Message: "gateway is valid", - LastTransitionTime: g.now, - } +// gatewayListenerNoConflicts marks an APIGateway listener as having no conflicts within its +// bound routes +func gatewayListenerNoConflicts(ref structs.ResourceReference) structs.Condition { + return structs.NewGatewayCondition( + api.GatewayConditionConflicted, + api.ConditionStatusFalse, + api.GatewayReasonNoConflict, + "listener has no route conflicts", + ref, + ) +} + +// gatewayListenerConflicts marks an APIGateway listener as having bound routes that conflict with each other +// and make the listener, therefore invalid +func gatewayListenerConflicts(ref structs.ResourceReference) structs.Condition { + return structs.NewGatewayCondition( + api.GatewayConditionConflicted, + api.ConditionStatusTrue, + api.GatewayReasonRouteConflict, + "TCP-based listeners currently only support binding a single route", + ref, + ) } // routeBound marks a Route as bound to the referenced APIGateway -func (g *gatewayConditionGenerator) routeBound(ref structs.ResourceReference) structs.Condition { - return structs.Condition{ - Type: "Bound", - Status: "True", - Reason: "Bound", - Resource: pointerTo(ref), - Message: "successfully bound route", - LastTransitionTime: g.now, - } +func routeBound(ref structs.ResourceReference) structs.Condition { + return structs.NewRouteCondition( + api.RouteConditionBound, + api.ConditionStatusTrue, + api.RouteReasonBound, + "successfully bound route", + ref, + ) } -// routeAccepted marks the Route as valid -func (g *gatewayConditionGenerator) routeAccepted() structs.Condition { - return structs.Condition{ - Type: "Accepted", - Status: "True", - Reason: "Accepted", - Message: "route is valid", - LastTransitionTime: g.now, - } +// gatewayNotFound marks a Route as having failed to bind to a referenced APIGateway due to +// the Gateway not existing (or having not been reconciled yet) +func gatewayNotFound(ref structs.ResourceReference) structs.Condition { + return structs.NewRouteCondition( + api.RouteConditionBound, + api.ConditionStatusFalse, + api.RouteReasonGatewayNotFound, + "gateway was not found", + ref, + ) } // routeUnbound marks the route as having failed to bind to the referenced APIGateway -func (g *gatewayConditionGenerator) routeUnbound(ref structs.ResourceReference, err error) structs.Condition { - return structs.Condition{ - Type: "Bound", - Status: "False", - Reason: "FailedToBind", - Resource: pointerTo(ref), - Message: err.Error(), - LastTransitionTime: g.now, - } +func routeUnbound(ref structs.ResourceReference, err error) structs.Condition { + return structs.NewRouteCondition( + api.RouteConditionBound, + api.ConditionStatusFalse, + api.RouteReasonFailedToBind, + err.Error(), + ref, + ) +} + +// routeAccepted marks the Route as valid +func routeAccepted() structs.Condition { + return structs.NewRouteCondition( + api.RouteConditionAccepted, + api.ConditionStatusTrue, + api.RouteReasonAccepted, + "route is valid", + structs.ResourceReference{}, + ) } // routeInvalidDiscoveryChain marks the route as invalid due to an error while validating its referenced // discovery chian -func (g *gatewayConditionGenerator) routeInvalidDiscoveryChain(err error) structs.Condition { - return structs.Condition{ - Type: "Accepted", - Status: "False", - Reason: "InvalidDiscoveryChain", - Message: err.Error(), - LastTransitionTime: g.now, - } +func routeInvalidDiscoveryChain(err error) structs.Condition { + return structs.NewRouteCondition( + api.RouteConditionAccepted, + api.ConditionStatusFalse, + api.RouteReasonInvalidDiscoveryChain, + err.Error(), + structs.ResourceReference{}, + ) } // routeNoUpstreams marks the route as invalid because it has no upstreams that it targets -func (g *gatewayConditionGenerator) routeNoUpstreams() structs.Condition { - return structs.Condition{ - Type: "Accepted", - Status: "False", - Reason: "NoUpstreamServicesTargeted", - Message: "route must target at least one upstream service", - LastTransitionTime: g.now, - } -} - -// gatewayListenerConflicts marks an APIGateway listener as having bound routes that conflict with each other -// and make the listener, therefore invalid -func (g *gatewayConditionGenerator) gatewayListenerConflicts(ref structs.ResourceReference) structs.Condition { - return structs.Condition{ - Type: "Conflicted", - Status: "True", - Reason: "RouteConflict", - Resource: pointerTo(ref), - Message: "TCP-based listeners currently only support binding a single route", - LastTransitionTime: g.now, - } -} - -// gatewayListenerNoConflicts marks an APIGateway listener as having no conflicts within its -// bound routes -func (g *gatewayConditionGenerator) gatewayListenerNoConflicts(ref structs.ResourceReference) structs.Condition { - return structs.Condition{ - Type: "Conflicted", - Status: "False", - Reason: "NoConflict", - Resource: pointerTo(ref), - Message: "listener has no route conflicts", - LastTransitionTime: g.now, - } -} - -// gatewayNotFound marks a Route as having failed to bind to a referenced APIGateway due to -// the Gateway not existing (or having not been reconciled yet) -func (g *gatewayConditionGenerator) gatewayNotFound(ref structs.ResourceReference) structs.Condition { - return structs.Condition{ - Type: "Bound", - Status: "False", - Reason: "GatewayNotFound", - Resource: pointerTo(ref), - Message: "gateway was not found", - LastTransitionTime: g.now, - } +func routeNoUpstreams() structs.Condition { + return structs.NewRouteCondition( + api.RouteConditionAccepted, + api.ConditionStatusFalse, + api.RouteReasonNoUpstreamServicesTargeted, + "route must target at least one upstream service", + structs.ResourceReference{}, + ) } // bindRoutesToGateways takes a route variadic number of gateways. @@ -1024,7 +994,6 @@ func bindRoutesToGateways(route structs.BoundRoute, gateways ...*gatewayMeta) ([ // removeGateway sets the route's status appropriately when the gateway that it's // attempting to bind to does not exist func removeGateway(gateway structs.ResourceReference, entries ...structs.BoundRoute) []structs.ControlledConfigEntry { - conditions := newGatewayConditionGenerator() modified := []structs.ControlledConfigEntry{} for _, route := range entries { @@ -1032,7 +1001,7 @@ func removeGateway(gateway structs.ResourceReference, entries ...structs.BoundRo for _, parent := range route.GetParents() { if parent.Kind == gateway.Kind && parent.Name == gateway.Name && parent.EnterpriseMeta.IsSame(&gateway.EnterpriseMeta) { - updater.SetCondition(conditions.gatewayNotFound(parent)) + updater.SetCondition(gatewayNotFound(parent)) } } diff --git a/agent/consul/gateways/controller_gateways_test.go b/agent/consul/gateways/controller_gateways_test.go index 3049dd60c1ec..2b304989e48f 100644 --- a/agent/consul/gateways/controller_gateways_test.go +++ b/agent/consul/gateways/controller_gateways_test.go @@ -11,9 +11,10 @@ import ( "testing" "time" - "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/consul/controller" "github.com/hashicorp/consul/agent/consul/fsm" @@ -55,7 +56,7 @@ func TestBoundAPIGatewayBindRoute(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -72,7 +73,7 @@ func TestBoundAPIGatewayBindRoute(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -132,7 +133,7 @@ func TestBoundAPIGatewayBindRoute(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -148,7 +149,7 @@ func TestBoundAPIGatewayBindRoute(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -443,7 +444,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -462,7 +463,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -557,7 +558,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -582,7 +583,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -606,7 +607,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -675,7 +676,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -694,7 +695,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -752,7 +753,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -771,7 +772,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -841,7 +842,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -860,7 +861,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -924,7 +925,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -957,7 +958,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -981,7 +982,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -1064,7 +1065,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -1083,7 +1084,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -1133,7 +1134,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -1158,7 +1159,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -1177,7 +1178,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -1193,7 +1194,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -1254,7 +1255,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -1273,7 +1274,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -1317,7 +1318,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -1336,14 +1337,13 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, }, expectedBoundAPIGateways: []*structs.BoundAPIGatewayConfigEntry{ { - Name: "Gateway", Listeners: []structs.BoundAPIGatewayListener{ { @@ -1435,7 +1435,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -1453,7 +1453,7 @@ func TestBindRoutesToGateways(t *testing.T) { }, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -1493,7 +1493,6 @@ func TestBindRoutesToGateways(t *testing.T) { } func TestAPIGatewayController(t *testing.T) { - conditions := newGatewayConditionGenerator() defaultMeta := acl.DefaultEnterpriseMeta() for name, tc := range map[string]struct { requests []controller.Request @@ -1521,7 +1520,7 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -1552,7 +1551,7 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeNoUpstreams(), + routeNoUpstreams(), }, }, }, @@ -1578,7 +1577,7 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeNoUpstreams(), + routeNoUpstreams(), }, }, }, @@ -1628,8 +1627,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeUnbound(structs.ResourceReference{ + routeAccepted(), + routeUnbound(structs.ResourceReference{ Name: "api-gateway", EnterpriseMeta: *defaultMeta, }, errors.New("failed to bind route to gateway api-gateway: gateway has not been accepted")), @@ -1646,7 +1645,7 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "api-gateway", SectionName: "listener", @@ -1694,7 +1693,7 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeInvalidDiscoveryChain(errInvalidProtocol), + routeInvalidDiscoveryChain(errInvalidProtocol), }, }, }, @@ -1733,8 +1732,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.gatewayNotFound(structs.ResourceReference{ + routeAccepted(), + gatewayNotFound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -1787,8 +1786,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.gatewayNotFound(structs.ResourceReference{ + routeAccepted(), + gatewayNotFound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -1851,8 +1850,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeUnbound(structs.ResourceReference{ + routeAccepted(), + routeUnbound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -1920,8 +1919,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.gatewayNotFound(structs.ResourceReference{ + routeAccepted(), + gatewayNotFound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -1956,7 +1955,7 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -1976,7 +1975,7 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().gatewayAccepted(), + gatewayAccepted(), }, }, }, @@ -2006,8 +2005,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.gatewayAccepted(), - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + gatewayAccepted(), + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2022,8 +2021,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeBound(structs.ResourceReference{ + routeAccepted(), + routeBound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2058,7 +2057,7 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -2098,8 +2097,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.gatewayAccepted(), - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + gatewayAccepted(), + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2114,8 +2113,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeUnbound(structs.ResourceReference{ + routeAccepted(), + routeUnbound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2154,7 +2153,7 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -2172,7 +2171,7 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -2221,8 +2220,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.gatewayAccepted(), - conditions.gatewayListenerConflicts(structs.ResourceReference{ + gatewayAccepted(), + gatewayListenerConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2237,8 +2236,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeBound(structs.ResourceReference{ + routeAccepted(), + routeBound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2252,8 +2251,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeBound(structs.ResourceReference{ + routeAccepted(), + routeBound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2294,7 +2293,7 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -2314,7 +2313,7 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -2364,8 +2363,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.gatewayAccepted(), - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + gatewayAccepted(), + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2380,8 +2379,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeBound(structs.ResourceReference{ + routeAccepted(), + routeBound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2395,8 +2394,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeBound(structs.ResourceReference{ + routeAccepted(), + routeBound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2505,8 +2504,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.gatewayAccepted(), - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + gatewayAccepted(), + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2521,8 +2520,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeBound(structs.ResourceReference{ + routeAccepted(), + routeBound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2536,8 +2535,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeUnbound(structs.ResourceReference{ + routeAccepted(), + routeUnbound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2578,7 +2577,7 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -2596,7 +2595,7 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - newGatewayConditionGenerator().routeAccepted(), + routeAccepted(), }, }, }, @@ -2659,13 +2658,13 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.gatewayAccepted(), - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + gatewayAccepted(), + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", SectionName: "http-listener", }), - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", SectionName: "tcp-listener", @@ -2679,8 +2678,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeBound(structs.ResourceReference{ + routeAccepted(), + routeBound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2694,8 +2693,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeBound(structs.ResourceReference{ + routeAccepted(), + routeBound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2721,8 +2720,8 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeBound(structs.ResourceReference{ + routeAccepted(), + routeBound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2776,7 +2775,7 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", SectionName: "tcp-listener", @@ -2791,7 +2790,7 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), + routeAccepted(), }, }, }, @@ -2810,8 +2809,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeBound(structs.ResourceReference{ + routeAccepted(), + routeBound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2865,7 +2864,7 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", SectionName: "tcp-listener", @@ -2880,7 +2879,7 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeNoUpstreams(), + routeNoUpstreams(), }, }, }, @@ -2908,8 +2907,8 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeBound(structs.ResourceReference{ + routeAccepted(), + routeBound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -2934,8 +2933,8 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeUnbound(structs.ResourceReference{ + routeAccepted(), + routeUnbound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", }, errors.New("foo")), @@ -2965,8 +2964,8 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.gatewayAccepted(), - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + gatewayAccepted(), + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", SectionName: "tcp-listener", @@ -3008,8 +3007,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.gatewayAccepted(), - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + gatewayAccepted(), + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", SectionName: "http-listener", @@ -3023,8 +3022,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeUnbound(structs.ResourceReference{ + routeAccepted(), + routeUnbound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", }, errors.New("failed to bind route tcp-route to gateway gateway with listener ''")), @@ -3037,8 +3036,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeBound(structs.ResourceReference{ + routeAccepted(), + routeBound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", }), @@ -3073,8 +3072,8 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeBound(structs.ResourceReference{ + routeAccepted(), + routeBound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", EnterpriseMeta: *defaultMeta, @@ -3109,8 +3108,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.gatewayNotFound(structs.ResourceReference{ + routeAccepted(), + gatewayNotFound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", }), @@ -3150,8 +3149,8 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.gatewayAccepted(), - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + gatewayAccepted(), + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", SectionName: "tcp-listener", @@ -3176,8 +3175,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.gatewayAccepted(), - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + gatewayAccepted(), + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", SectionName: "tcp-listener", @@ -3221,8 +3220,8 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.routeBound(structs.ResourceReference{ + routeAccepted(), + routeBound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", }), @@ -3237,8 +3236,8 @@ func TestAPIGatewayController(t *testing.T) { EnterpriseMeta: *defaultMeta, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.routeAccepted(), - conditions.gatewayNotFound(structs.ResourceReference{ + routeAccepted(), + gatewayNotFound(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", }), @@ -3287,12 +3286,12 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.invalidCertificate(structs.ResourceReference{ + invalidCertificate(structs.ResourceReference{ Kind: structs.InlineCertificate, Name: "certificate", }, errors.New("certificate not found")), - conditions.invalidCertificates(), - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + invalidCertificates(), + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", SectionName: "http-listener", @@ -3355,8 +3354,8 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.gatewayAccepted(), - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + gatewayAccepted(), + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", SectionName: "http-listener", @@ -3402,12 +3401,12 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.invalidCertificate(structs.ResourceReference{ + invalidCertificate(structs.ResourceReference{ Kind: structs.InlineCertificate, Name: "certificate", }, errors.New("certificate not found")), - conditions.invalidCertificates(), - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + invalidCertificates(), + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", SectionName: "http-listener", @@ -3438,8 +3437,8 @@ func TestAPIGatewayController(t *testing.T) { }}, Status: structs.Status{ Conditions: []structs.Condition{ - conditions.gatewayAccepted(), - conditions.gatewayListenerNoConflicts(structs.ResourceReference{ + gatewayAccepted(), + gatewayListenerNoConflicts(structs.ResourceReference{ Kind: structs.APIGateway, Name: "gateway", SectionName: "http-listener", @@ -3705,12 +3704,15 @@ func (n *noopController) WithWorkers(i int) controller.Controller func (n *noopController) WithQueueFactory(fn func(ctx context.Context, baseBackoff time.Duration, maxBackoff time.Duration) controller.WorkQueue) controller.Controller { return n } + func (n *noopController) AddTrigger(request controller.Request, trigger func(ctx context.Context) error) { n.triggers[request] = struct{}{} } + func (n *noopController) RemoveTrigger(request controller.Request) { delete(n.triggers, request) } + func (n *noopController) Enqueue(requests ...controller.Request) { n.enqueued = append(n.enqueued, requests...) } diff --git a/agent/structs/config_entry_status.go b/agent/structs/config_entry_status.go index 264efb1fa67c..fb91a0add374 100644 --- a/agent/structs/config_entry_status.go +++ b/agent/structs/config_entry_status.go @@ -9,6 +9,7 @@ import ( "time" "github.com/hashicorp/consul/acl" + "github.com/hashicorp/consul/api" ) // ResourceReference is a reference to a ConfigEntry @@ -67,30 +68,6 @@ func (s Status) SameConditions(other Status) bool { if len(s.Conditions) != len(other.Conditions) { return false } - lessResource := func(one, two *ResourceReference) bool { - if one == nil && two == nil { - return false - } - if one == nil { - return true - } - if two == nil { - return false - } - if one.Kind < two.Kind { - return true - } - if one.Kind > two.Kind { - return false - } - if one.Name < two.Name { - return true - } - if one.Name > two.Name { - return false - } - return one.SectionName < two.SectionName - } sortConditions := func(conditions []Condition) []Condition { sort.SliceStable(conditions, func(i, j int) bool { if conditions[i].Type < conditions[j].Type { @@ -114,6 +91,31 @@ func (s Status) SameConditions(other Status) bool { return true } +func lessResource(one, two *ResourceReference) bool { + if one == nil && two == nil { + return false + } + if one == nil { + return true + } + if two == nil { + return false + } + if one.Kind < two.Kind { + return true + } + if one.Kind > two.Kind { + return false + } + if one.Name < two.Name { + return true + } + if one.Name > two.Name { + return false + } + return one.SectionName < two.SectionName +} + // Condition is used for a single message and state associated // with an object. For example, a ConfigEntry that references // multiple other resources may have different statuses with @@ -195,3 +197,42 @@ func (u *StatusUpdater) UpdateEntry() (ControlledConfigEntry, bool) { u.entry.SetStatus(u.status) return u.entry, true } + +func NewGatewayCondition(name api.GatewayConditionType, status api.ConditionStatus, reason api.GatewayConditionReason, message string, resource ResourceReference) Condition { + if err := api.ValidateGatewayConditionReason(name, status, reason); err != nil { + // note we panic here because an invalid combination is a programmer error + // this should never actually be hit + panic(err) + } + + return Condition{ + Type: string(name), + Status: string(status), + Reason: string(reason), + Message: message, + Resource: ptrTo(resource), + LastTransitionTime: ptrTo(time.Now().UTC()), + } +} + +// NewRouteCondition is a helper to build allowable Conditions for a Route config entry +func NewRouteCondition(name api.RouteConditionType, status api.ConditionStatus, reason api.RouteConditionReason, message string, ref ResourceReference) Condition { + if err := api.ValidateRouteConditionReason(name, status, reason); err != nil { + // note we panic here because an invalid combination is a programmer error + // this should never actually be hit + panic(err) + } + + return Condition{ + Type: string(name), + Status: string(status), + Reason: string(reason), + Message: message, + Resource: ptrTo(ref), + LastTransitionTime: ptrTo(time.Now().UTC()), + } +} + +func ptrTo[T any](val T) *T { + return &val +} diff --git a/api/config_entry_status.go b/api/config_entry_status.go index 45197dcc6a5e..dfb97eb4c9bc 100644 --- a/api/config_entry_status.go +++ b/api/config_entry_status.go @@ -4,7 +4,10 @@ package api import ( + "fmt" "time" + + "golang.org/x/exp/slices" ) // ResourceReference is a reference to a ConfigEntry @@ -46,7 +49,7 @@ type Condition struct { // Type is a value from a bounded set of types that an object might have Type string // Status is a value from a bounded set of statuses that an object might have - Status string + Status ConditionStatus // Reason is a value from a bounded set of reasons for a given status Reason string // Message is a message that gives more detailed information about @@ -58,3 +61,280 @@ type Condition struct { // LastTransitionTime is the time at which this Condition was created LastTransitionTime *time.Time } + +type ( + ConditionStatus string +) + +const ( + ConditionStatusTrue ConditionStatus = "True" + ConditionStatusFalse ConditionStatus = "False" + ConditionStatusUnknown ConditionStatus = "Unknown" +) + +// GatewayConditionType is a type of condition associated with a +// Gateway. This type should be used with the GatewayStatus.Conditions +// field. +type GatewayConditionType string + +// GatewayConditionReason defines the set of reasons that explain why a +// particular Gateway condition type has been raised. +type GatewayConditionReason string + +// the following are directly from the k8s spec +const ( + // This condition is true when the controller managing the Gateway is + // syntactically and semantically valid enough to produce some configuration + // in the underlying data plane. This does not indicate whether or not the + // configuration has been propagated to the data plane. + // + // Possible reasons for this condition to be True are: + // + // * "Accepted" + // + // Possible reasons for this condition to be False are: + // + // * InvalidCertificates + // + GatewayConditionAccepted GatewayConditionType = "Accepted" + + // This reason is used with the "Accepted" condition when the condition is + // True. + GatewayReasonAccepted GatewayConditionReason = "Accepted" + + // This reason is used with the "Accepted" condition when the gateway has multiple invalid + // certificates and cannot bind to any routes + GatewayReasonInvalidCertificates GatewayConditionReason = "InvalidCertificates" + + // This condition indicates that the gateway was unable to resolve + // conflicting specification requirements for this Listener. If a + // Listener is conflicted, its network port should not be configured + // on any network elements. + // + // Possible reasons for this condition to be true are: + // + // * "RouteConflict" + // + // Possible reasons for this condition to be False are: + // + // * "NoConflict" + // + // Controllers may raise this condition with other reasons, + // but should prefer to use the reasons listed above to improve + // interoperability. + GatewayConditionConflicted GatewayConditionType = "Conflicted" + // This reason is used with the "Conflicted" condition when the condition + // is False. + GatewayReasonNoConflict GatewayConditionReason = "NoConflict" + // This reason is used with the "Conflicted" condition when the route is + // in a conflicted state, such as when a TCPListener attempts to bind to two routes + GatewayReasonRouteConflict GatewayConditionReason = "RouteConflict" + + // This condition indicates whether the controller was able to + // resolve all the object references for the Gateway. When setting this + // condition to False, a ResourceReference to the misconfigured Listener should + // be provided. + // + // Possible reasons for this condition to be true are: + // + // * "ResolvedRefs" + // + // Possible reasons for this condition to be False are: + // + // * "InvalidCertificateRef" + // * "InvalidRouteKinds" + // * "RefNotPermitted" + // + GatewayConditionResolvedRefs GatewayConditionType = "ResolvedRefs" + + // This reason is used with the "ResolvedRefs" condition when the condition + // is true. + GatewayReasonResolvedRefs GatewayConditionReason = "ResolvedRefs" + + // This reason is used with the "ResolvedRefs" condition when a + // Listener has a TLS configuration with at least one TLS CertificateRef + // that is invalid or does not exist. + // A CertificateRef is considered invalid when it refers to a nonexistent + // or unsupported resource or kind, or when the data within that resource + // is malformed. + // This reason must be used only when the reference is allowed, either by + // referencing an object in the same namespace as the Gateway, or when + // a cross-namespace reference has been explicitly allowed by a ReferenceGrant. + // If the reference is not allowed, the reason RefNotPermitted must be used + // instead. + GatewayListenerReasonInvalidCertificateRef GatewayConditionReason = "InvalidCertificateRef" +) + +var validGatewayConditionReasonsMapping = map[GatewayConditionType]map[ConditionStatus][]GatewayConditionReason{ + GatewayConditionAccepted: { + ConditionStatusTrue: { + GatewayReasonAccepted, + }, + ConditionStatusFalse: { + GatewayListenerReasonInvalidCertificateRef, // TODO: remove this in follow up PR + GatewayReasonInvalidCertificates, + }, + ConditionStatusUnknown: {}, + }, + GatewayConditionConflicted: { + ConditionStatusTrue: { + GatewayReasonRouteConflict, + }, + ConditionStatusFalse: { + GatewayReasonNoConflict, + }, + ConditionStatusUnknown: {}, + }, + GatewayConditionResolvedRefs: { + ConditionStatusTrue: { + GatewayReasonResolvedRefs, + }, + ConditionStatusFalse: { + GatewayListenerReasonInvalidCertificateRef, + }, + ConditionStatusUnknown: {}, + }, +} + +func ValidateGatewayConditionReason(name GatewayConditionType, status ConditionStatus, reason GatewayConditionReason) error { + if err := checkConditionStatus(status); err != nil { + return err + } + + reasons, ok := validGatewayConditionReasonsMapping[name] + if !ok { + return fmt.Errorf("unrecognized GatewayConditionType %q", name) + } + + reasonsForStatus, ok := reasons[status] + if !ok { + return fmt.Errorf("unrecognized ConditionStatus %q", status) + } + + if !slices.Contains(reasonsForStatus, reason) { + return fmt.Errorf("gateway condition reason %q not allowed for gateway condition type %q with status %q", reason, name, status) + } + return nil +} + +// RouteConditionType is a type of condition for a route. +type RouteConditionType string + +// RouteConditionReason is a reason for a route condition. +type RouteConditionReason string + +// The following statuses are taken from the K8's Spec +// With the exception of: "RouteReasonInvalidDiscoveryChain" and "NoUpstreamServicesTargeted" +const ( + // This condition indicates whether the route has been accepted or rejected + // by a Gateway, and why. + // + // Possible reasons for this condition to be true are: + // + // * "Accepted" + // + // Possible reasons for this condition to be False are: + // + // * "InvalidDiscoveryChain" + // * "NoUpstreamServicesTargeted" + // + // + // Controllers may raise this condition with other reasons, + // but should prefer to use the reasons listed above to improve + // interoperability. + RouteConditionAccepted RouteConditionType = "Accepted" + + // This reason is used with the "Accepted" condition when the Route has been + // accepted by the Gateway. + RouteReasonAccepted RouteConditionReason = "Accepted" + + // This reason is used with the "Accepted" condition when the route has an + // invalid discovery chain, this includes conditions like the protocol being invalid + // or the discovery chain failing to compile + RouteReasonInvalidDiscoveryChain RouteConditionReason = "InvalidDiscoveryChain" + + // This reason is used with the "Accepted" condition when the route + RouteReasonNoUpstreamServicesTargeted RouteConditionReason = "NoUpstreamServicesTargeted" +) + +// the following statuses are custom to Consul +const ( + // This condition indicates whether the route was able to successfully bind the + // Listener on the gateway + // Possible reasons for this condition to be true are: + // + // * "Bound" + // + // Possible reasons for this condition to be false are: + // + // * "FailedToBind" + // * "GatewayNotFound" + // + RouteConditionBound RouteConditionType = "Bound" + + // This reason is used with the "Bound" condition when the condition + // is true + RouteReasonBound RouteConditionReason = "Bound" + + // This reason is used with the "Bound" condition when the route failed + // to bind to the gateway + RouteReasonFailedToBind RouteConditionReason = "FailedToBind" + + // This reason is used with the "Bound" condition when the route fails + // to find the gateway + RouteReasonGatewayNotFound RouteConditionReason = "GatewayNotFound" +) + +var validRouteConditionReasonsMapping = map[RouteConditionType]map[ConditionStatus][]RouteConditionReason{ + RouteConditionAccepted: { + ConditionStatusTrue: { + RouteReasonAccepted, + }, + ConditionStatusFalse: { + RouteReasonInvalidDiscoveryChain, + RouteReasonNoUpstreamServicesTargeted, + }, + ConditionStatusUnknown: {}, + }, + RouteConditionBound: { + ConditionStatusTrue: { + RouteReasonBound, + }, + ConditionStatusFalse: { + RouteReasonGatewayNotFound, + RouteReasonFailedToBind, + }, + ConditionStatusUnknown: {}, + }, +} + +func ValidateRouteConditionReason(name RouteConditionType, status ConditionStatus, reason RouteConditionReason) error { + if err := checkConditionStatus(status); err != nil { + return err + } + + reasons, ok := validRouteConditionReasonsMapping[name] + if !ok { + return fmt.Errorf("unrecognized RouteConditionType %s", name) + } + + reasonsForStatus, ok := reasons[status] + if !ok { + return fmt.Errorf("unrecognized ConditionStatus %s", name) + } + + if !slices.Contains(reasonsForStatus, reason) { + return fmt.Errorf("route condition reason %s not allowed for route condition type %s with status %s", reason, name, status) + } + + return nil +} + +func checkConditionStatus(status ConditionStatus) error { + switch status { + case ConditionStatusTrue, ConditionStatusFalse, ConditionStatusUnknown: + return nil + default: + return fmt.Errorf("unrecognized condition status: %q", status) + } +} diff --git a/api/config_entry_status_test.go b/api/config_entry_status_test.go new file mode 100644 index 000000000000..9c6eaf034c32 --- /dev/null +++ b/api/config_entry_status_test.go @@ -0,0 +1,187 @@ +package api + +import "testing" + +func TestValidateGatewayConditionReasonWithValidCombinations(t *testing.T) { + testCases := map[string]struct { + status ConditionStatus + reason GatewayConditionReason + condType GatewayConditionType + }{ + "accepted": { + status: ConditionStatusTrue, + reason: GatewayReasonAccepted, + condType: GatewayConditionAccepted, + }, + "accepted invalid certificates": { + status: ConditionStatusFalse, + reason: GatewayReasonInvalidCertificates, + condType: GatewayConditionAccepted, + }, + "conflicted": { + status: ConditionStatusTrue, + reason: GatewayReasonRouteConflict, + condType: GatewayConditionConflicted, + }, + "conflicted no conflicts": { + status: ConditionStatusFalse, + reason: GatewayReasonNoConflict, + condType: GatewayConditionConflicted, + }, + + "resolved refs": { + status: ConditionStatusTrue, + reason: GatewayReasonResolvedRefs, + condType: GatewayConditionResolvedRefs, + }, + "resolved refs invalid certificate ref": { + status: ConditionStatusFalse, + reason: GatewayListenerReasonInvalidCertificateRef, + condType: GatewayConditionResolvedRefs, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + err := ValidateGatewayConditionReason(tc.condType, tc.status, tc.reason) + if err != nil { + t.Error("Expected gateway condition reason to be valid but it was not") + } + }) + } +} + +func TestValidateGatewayConditionReasonWithInvalidCombinationsReturnsError(t *testing.T) { + // This is not an exhaustive list of all invalid combinations, just a few to confirm + testCases := map[string]struct { + status ConditionStatus + reason GatewayConditionReason + condType GatewayConditionType + }{ + "reason and condition type are valid but status is not": { + status: ConditionStatusTrue, + reason: GatewayReasonNoConflict, + condType: GatewayConditionConflicted, + }, + "reason and status are valid but condition type is not": { + status: ConditionStatusFalse, + reason: GatewayReasonNoConflict, + condType: GatewayConditionResolvedRefs, + }, + "condition type and status are valid but status is not": { + status: ConditionStatusTrue, + reason: GatewayReasonNoConflict, + condType: GatewayConditionAccepted, + }, + "all are invalid": { + status: ConditionStatusUnknown, + reason: GatewayReasonAccepted, + condType: GatewayConditionResolvedRefs, + }, + "pass something other than a condition status": { + status: ConditionStatus("hello"), + reason: GatewayReasonAccepted, + condType: GatewayConditionResolvedRefs, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + err := ValidateGatewayConditionReason(tc.condType, tc.status, tc.reason) + if err == nil { + t.Error("Expected route condition reason to be invalid, but it was valid") + } + }) + } +} + +func TestValidateRouteConfigReasonWithValidCombinations(t *testing.T) { + testCases := map[string]struct { + status ConditionStatus + reason RouteConditionReason + condType RouteConditionType + }{ + "accepted all around": { + status: ConditionStatusTrue, + reason: RouteReasonAccepted, + condType: RouteConditionAccepted, + }, + "accepted invalid discovery chain": { + status: ConditionStatusFalse, + reason: RouteReasonInvalidDiscoveryChain, + condType: RouteConditionAccepted, + }, + "accepted no upstream services targeted": { + status: ConditionStatusFalse, + reason: RouteReasonNoUpstreamServicesTargeted, + condType: RouteConditionAccepted, + }, + "route bound": { + status: ConditionStatusTrue, + reason: RouteReasonBound, + condType: RouteConditionBound, + }, + "route bound gateway not found": { + status: ConditionStatusFalse, + reason: RouteReasonGatewayNotFound, + condType: RouteConditionBound, + }, + "route bound failed to bind": { + status: ConditionStatusFalse, + reason: RouteReasonFailedToBind, + condType: RouteConditionBound, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + err := ValidateRouteConditionReason(tc.condType, tc.status, tc.reason) + if err != nil { + t.Errorf("Expected route condition reason to be valid, it was not") + } + }) + } +} + +func TestValidateRouteConditionReasonInvalidCombinationsCausePanic(t *testing.T) { + // This is not an exhaustive list of all invalid combinations, just a few to confirm + testCases := map[string]struct { + status ConditionStatus + reason RouteConditionReason + condType RouteConditionType + }{ + "reason and condition type are valid but status is not": { + status: ConditionStatusTrue, + reason: RouteReasonNoUpstreamServicesTargeted, + condType: RouteConditionAccepted, + }, + "reason and status are valid but condition type is not": { + status: ConditionStatusFalse, + reason: RouteReasonInvalidDiscoveryChain, + condType: RouteConditionBound, + }, + "condition type and status are valid but status is not": { + status: ConditionStatusUnknown, + reason: RouteReasonBound, + condType: RouteConditionBound, + }, + "all are invalid": { + status: ConditionStatusUnknown, + reason: RouteReasonGatewayNotFound, + condType: RouteConditionBound, + }, + "pass something other than a condition status": { + status: ConditionStatus("hello"), + reason: RouteReasonAccepted, + condType: RouteConditionAccepted, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + err := ValidateRouteConditionReason(tc.condType, tc.status, tc.reason) + if err == nil { + t.Error("Expected route condition reason to be invalid, it was valid") + } + }) + } +} diff --git a/api/go.mod b/api/go.mod index e53e4908c43c..bd9b2c6780f9 100644 --- a/api/go.mod +++ b/api/go.mod @@ -5,7 +5,7 @@ go 1.19 replace github.com/hashicorp/consul/sdk => ../sdk require ( - github.com/google/go-cmp v0.5.7 + github.com/google/go-cmp v0.5.8 github.com/hashicorp/consul/sdk v0.13.1 github.com/hashicorp/go-cleanhttp v0.5.1 github.com/hashicorp/go-hclog v0.12.0 @@ -14,6 +14,7 @@ require ( github.com/hashicorp/serf v0.10.1 github.com/mitchellh/mapstructure v1.4.1 github.com/stretchr/testify v1.7.0 + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 ) require ( @@ -40,7 +41,6 @@ require ( github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/stretchr/objx v0.1.0 // indirect golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + golang.org/x/sys v0.1.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/api/go.sum b/api/go.sum index 043db4cd1efc..8e56a6b16f45 100644 --- a/api/go.sum +++ b/api/go.sum @@ -13,8 +13,8 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= @@ -96,6 +96,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -118,8 +120,9 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -128,9 +131,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/envoyextensions/go.mod b/envoyextensions/go.mod index 6dc8e782b880..225ed86d5817 100644 --- a/envoyextensions/go.mod +++ b/envoyextensions/go.mod @@ -36,6 +36,7 @@ require ( github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect + golang.org/x/sys v0.1.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/envoyextensions/go.sum b/envoyextensions/go.sum index 28b2402b3ec8..c0b34dd3c70a 100644 --- a/envoyextensions/go.sum +++ b/envoyextensions/go.sum @@ -55,7 +55,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/sdk v0.13.1 h1:EygWVWWMczTzXGpO93awkHFzfUka6hLYJ0qhETd+6lY= @@ -143,6 +143,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -185,8 +187,9 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -200,7 +203,6 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/go.mod b/go.mod index ad16988f8529..d78707f8c9a6 100644 --- a/go.mod +++ b/go.mod @@ -227,11 +227,11 @@ require ( go.opencensus.io v0.23.0 // indirect go.opentelemetry.io/proto/otlp v0.7.0 // indirect go.uber.org/atomic v1.9.0 // indirect - golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/term v0.5.0 // indirect golang.org/x/text v0.7.0 // indirect - golang.org/x/tools v0.1.12 // indirect + golang.org/x/tools v0.2.0 // indirect google.golang.org/api v0.57.0 // indirect google.golang.org/appengine v1.6.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 4abe9164b498..964f00fa91e0 100644 --- a/go.sum +++ b/go.sum @@ -1115,8 +1115,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw= -golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1426,8 +1426,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/test/integration/consul-container/go.mod b/test/integration/consul-container/go.mod index 1a3a8510487b..59c6d252eea9 100644 --- a/test/integration/consul-container/go.mod +++ b/test/integration/consul-container/go.mod @@ -77,7 +77,7 @@ require ( github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/sirupsen/logrus v1.8.1 // indirect go.opencensus.io v0.23.0 // indirect - golang.org/x/exp v0.0.0-20230303215020-44a13b063f3e // indirect + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect golang.org/x/net v0.8.0 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect diff --git a/test/integration/consul-container/go.sum b/test/integration/consul-container/go.sum index 9b87f20ebf08..0f6c95e286ec 100644 --- a/test/integration/consul-container/go.sum +++ b/test/integration/consul-container/go.sum @@ -874,8 +874,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230303215020-44a13b063f3e h1:S8xf0d0OEmWrClvbMiUSp+7cGD00txONylwExlf9wR0= -golang.org/x/exp v0.0.0-20230303215020-44a13b063f3e/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/test/integration/consul-container/test/gateways/gateway_endpoint_test.go b/test/integration/consul-container/test/gateways/gateway_endpoint_test.go index b08bb4715026..309538e89ce6 100644 --- a/test/integration/consul-container/test/gateways/gateway_endpoint_test.go +++ b/test/integration/consul-container/test/gateways/gateway_endpoint_test.go @@ -137,7 +137,7 @@ func isBound(conditions []api.Condition) bool { func conditionStatusIsValue(typeName string, statusValue string, conditions []api.Condition) bool { for _, c := range conditions { - if c.Type == typeName && c.Status == statusValue { + if c.Type == typeName && string(c.Status) == statusValue { return true } } diff --git a/troubleshoot/go.mod b/troubleshoot/go.mod index 54838b51d79a..48529793be0a 100644 --- a/troubleshoot/go.mod +++ b/troubleshoot/go.mod @@ -28,7 +28,6 @@ require ( github.com/fatih/color v1.13.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/btree v1.0.0 // indirect - github.com/google/go-cmp v0.5.8 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.2.1 // indirect @@ -47,6 +46,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.2.0 // indirect go.opentelemetry.io/proto/otlp v0.7.0 // indirect + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect golang.org/x/net v0.4.0 // indirect golang.org/x/sys v0.3.0 // indirect golang.org/x/text v0.5.0 // indirect diff --git a/troubleshoot/go.sum b/troubleshoot/go.sum index b8177b292c84..7629cbde9e5b 100644 --- a/troubleshoot/go.sum +++ b/troubleshoot/go.sum @@ -79,7 +79,6 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -211,6 +210,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= From f56b25472d04149d9b59b6831ea50af5542e6dda Mon Sep 17 00:00:00 2001 From: John Murret Date: Mon, 24 Apr 2023 14:25:57 -0600 Subject: [PATCH 07/92] ci: fix runner calculation to exclude the top level directory as part of the calculation (#17090) * fix runner calculation to exclude the top level directory as part of the calculation * fix the logic for generating the directories/functions * De-scope tenenacy requirements to OSS only for now. (#17087) Partition and namespace must be "default" Peername must be "local" * Fix virtual services being included in intention topology as downstreams. (#17099) * Merge pull request #5200 from hashicorp/NET-3758 (#17102) * Merge pull request #5200 from hashicorp/NET-3758 NET-3758: connect: update supported envoy versions to 1.26.0 * lint * CI: remove uneeded AWS creds from test-integrations (#17104) * Update test-integrations.yml * removing permission lies now that vault is not used in this job. --------- Co-authored-by: John Murret * update based on feedback --------- Co-authored-by: Semir Patel Co-authored-by: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Co-authored-by: Anita Akaeze Co-authored-by: Dan Bond --- .github/workflows/test-integrations.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-integrations.yml b/.github/workflows/test-integrations.yml index 720360ee801f..515774acca8a 100644 --- a/.github/workflows/test-integrations.yml +++ b/.github/workflows/test-integrations.yml @@ -159,10 +159,11 @@ jobs: JQ_SLICER: '[ inputs ] | [_nwise(length / $runnercount | floor)]' run: | NUM_RUNNERS=$TOTAL_RUNNERS - NUM_DIRS=$(find ./test/integration/connect/envoy -maxdepth 1 -type d | wc -l) + NUM_DIRS=$(find ./test/integration/connect/envoy -mindepth 1 -maxdepth 1 -type d | wc -l) + if [ "$NUM_DIRS" -lt "$NUM_RUNNERS" ]; then echo "TOTAL_RUNNERS is larger than the number of tests/packages to split." - NUM_RUNNERS=$NUM_DIRS + NUM_RUNNERS=$((NUM_DIRS-1)) fi # fix issue where test splitting calculation generates 1 more split than TOTAL_RUNNERS. NUM_RUNNERS=$((NUM_RUNNERS-1)) @@ -252,10 +253,11 @@ jobs: run: | cd ./test/integration/consul-container NUM_RUNNERS=$TOTAL_RUNNERS - NUM_DIRS=$(find ./test -maxdepth 2 -type d | wc -l) + NUM_DIRS=$(find ./test -mindepth 1 -maxdepth 2 -type d | wc -l) + if [ "$NUM_DIRS" -lt "$NUM_RUNNERS" ]; then echo "TOTAL_RUNNERS is larger than the number of tests/packages to split." - NUM_RUNNERS=$NUM_DIRS + NUM_RUNNERS=$((NUM_DIRS-1)) fi # fix issue where test splitting calculation generates 1 more split than TOTAL_RUNNERS. NUM_RUNNERS=$((NUM_RUNNERS-1)) @@ -358,9 +360,10 @@ jobs: cd ./test/integration/consul-container/test/upgrade NUM_RUNNERS=$TOTAL_RUNNERS NUM_DIRS=$(go test ./... -list=. -json | jq -r '.Output | select (. !=null) | select(. | startswith("Test")) | gsub("[\\n\\t]"; "")' | wc -l) + if [ "$NUM_DIRS" -lt "$NUM_RUNNERS" ]; then echo "TOTAL_RUNNERS is larger than the number of tests/packages to split." - NUM_RUNNERS=$NUM_DIRS + NUM_RUNNERS=$((NUM_DIRS-1)) fi # fix issue where test splitting calculation generates 1 more split than TOTAL_RUNNERS. NUM_RUNNERS=$((NUM_RUNNERS-1)) From 2d3038874f2d054eb71fb557070ca83a0315e23c Mon Sep 17 00:00:00 2001 From: malizz Date: Mon, 24 Apr 2023 15:30:00 -0700 Subject: [PATCH 08/92] remove envoy endpoint flag from k8s docs (#17105) --- website/content/docs/k8s/k8s-cli.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/website/content/docs/k8s/k8s-cli.mdx b/website/content/docs/k8s/k8s-cli.mdx index bb45986e11e1..d0ad98c49a86 100644 --- a/website/content/docs/k8s/k8s-cli.mdx +++ b/website/content/docs/k8s/k8s-cli.mdx @@ -794,7 +794,6 @@ $ consul-k8s troubleshoot upstreams -pod | Flag | Description | Default | | ------------------------------------ | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | `-namespace`, `-n` | `String` The Kubernetes namespace to list proxies in. | Current [kubeconfig](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) namespace. | -| `-envoy-admin-endpoint` | `String` Envoy sidecar address and port | `127.0.0.1:19000` | #### Example Commands @@ -839,7 +838,6 @@ $ consul-k8s troubleshoot proxy -pod -upstream-envoy-id