From f1f3c29e1df890f92e248636317e7fff53605d6d Mon Sep 17 00:00:00 2001 From: "R.B. Boyer" Date: Tue, 22 Aug 2023 16:32:53 -0500 Subject: [PATCH] add more coverage to just the generate.go file --- .../internal/controllers/routes/generate.go | 76 +- .../controllers/routes/generate_test.go | 1005 ++++++++++++++++- 2 files changed, 1016 insertions(+), 65 deletions(-) diff --git a/internal/mesh/internal/controllers/routes/generate.go b/internal/mesh/internal/controllers/routes/generate.go index cbfb7912d4f0..3eaf85a2e1ef 100644 --- a/internal/mesh/internal/controllers/routes/generate.go +++ b/internal/mesh/internal/controllers/routes/generate.go @@ -4,6 +4,8 @@ package routes import ( + "fmt" + "github.com/hashicorp/consul/internal/catalog" "github.com/hashicorp/consul/internal/mesh/internal/controllers/routes/loader" "github.com/hashicorp/consul/internal/mesh/internal/types" @@ -102,11 +104,13 @@ func compile( ) for _, ref := range xroute.GetParentRefs() { if resource.ReferenceOrIDMatch(ref.Ref, parentServiceRef) { - ports = append(ports, ref.Port) if ref.Port == "" { wildcardedPort = true break } + if _, ok := allowedPortProtocols[ref.Port]; ok { + ports = append(ports, ref.Port) + } } } @@ -127,37 +131,16 @@ func compile( panic("impossible to have an empty port here") } - // Check if the user provided port is actually valid. - nullRouteTraffic := (parentServiceDec == nil) - if _, ok := allowedPortProtocols[port]; !ok { - nullRouteTraffic = true - } - var node *inputRouteNode switch route := xroute.(type) { case *pbmesh.HTTPRoute: - if nullRouteTraffic { - node = newInputRouteNode(port) - setupDefaultHTTPRouteNode(node, types.NullRouteBackend) - } else { - node = compileHTTPRouteNode(port, res, route, related) - } + node = compileHTTPRouteNode(port, res, route, related) case *pbmesh.GRPCRoute: - if nullRouteTraffic { - node = newInputRouteNode(port) - setupDefaultGRPCRouteNode(node, types.NullRouteBackend) - } else { - node = compileGRPCRouteNode(port, res, route, related) - } + node = compileGRPCRouteNode(port, res, route, related) case *pbmesh.TCPRoute: - if nullRouteTraffic { - node = newInputRouteNode(port) - setupDefaultTCPRouteNode(node, types.NullRouteBackend) - } else { - node = compileTCPRouteNode(port, res, route, related) - } + node = compileTCPRouteNode(port, res, route, related) default: - return // unknown xroute type (impossible) + panic(fmt.Sprintf("unexpected xroute type: %T", xroute)) } routeNodesByPort[node.ParentPort] = append(routeNodesByPort[node.ParentPort], node) @@ -168,6 +151,8 @@ func compile( for port, protocol := range allowedPortProtocols { if _, ok := routeNodesByPort[port]; !ok { var typ *pbresource.Type + + // enumcover:pbcatalog.Protocol switch protocol { case pbcatalog.Protocol_PROTOCOL_HTTP2: typ = types.HTTPRouteType @@ -177,8 +162,10 @@ func compile( typ = types.GRPCRouteType case pbcatalog.Protocol_PROTOCOL_TCP: typ = types.TCPRouteType + case pbcatalog.Protocol_PROTOCOL_MESH: + fallthrough // to default default: - continue // unknown protocol (impossible through validation) + continue // not possible } routeNode := createDefaultRouteNode(parentServiceRef, port, typ) @@ -271,7 +258,7 @@ func compile( svc := related.GetService(svcRef) failoverPolicy := related.GetFailoverPolicyForService(svcRef) - destConfig := related.GetDestinationPolicy(svcRef) + destConfig := related.GetDestinationPolicyForService(svcRef) if svc == nil { panic("impossible at this point; should already have been handled before getting here") @@ -535,13 +522,16 @@ func createDefaultRouteNode( }) switch { case resource.EqualType(types.HTTPRouteType, typ): - setupDefaultHTTPRouteNode(routeNode, defaultBackendTarget) + routeNode.RouteType = types.HTTPRouteType + appendDefaultHTTPRouteRule(routeNode, defaultBackendTarget) case resource.EqualType(types.GRPCRouteType, typ): - setupDefaultGRPCRouteNode(routeNode, defaultBackendTarget) + routeNode.RouteType = types.GRPCRouteType + appendDefaultGRPCRouteRule(routeNode, defaultBackendTarget) case resource.EqualType(types.TCPRouteType, typ): fallthrough default: - setupDefaultTCPRouteNode(routeNode, defaultBackendTarget) + routeNode.RouteType = types.TCPRouteType + appendDefaultTCPRouteRule(routeNode, defaultBackendTarget) } routeNode.Default = true @@ -564,14 +554,6 @@ func appendDefaultRouteNode( } } -func setupDefaultHTTPRouteNode( - routeNode *inputRouteNode, - defaultBackendTarget string, -) { - routeNode.RouteType = types.HTTPRouteType - appendDefaultHTTPRouteRule(routeNode, defaultBackendTarget) -} - func appendDefaultHTTPRouteRule( routeNode *inputRouteNode, backendTarget string, @@ -584,14 +566,6 @@ func appendDefaultHTTPRouteRule( }) } -func setupDefaultGRPCRouteNode( - routeNode *inputRouteNode, - defaultBackendTarget string, -) { - routeNode.RouteType = types.GRPCRouteType - appendDefaultGRPCRouteRule(routeNode, defaultBackendTarget) -} - func appendDefaultGRPCRouteRule( routeNode *inputRouteNode, backendTarget string, @@ -604,14 +578,6 @@ func appendDefaultGRPCRouteRule( }) } -func setupDefaultTCPRouteNode( - routeNode *inputRouteNode, - defaultBackendTarget string, -) { - routeNode.RouteType = types.TCPRouteType - appendDefaultTCPRouteRule(routeNode, defaultBackendTarget) -} - func appendDefaultTCPRouteRule( routeNode *inputRouteNode, backendTarget string, diff --git a/internal/mesh/internal/controllers/routes/generate_test.go b/internal/mesh/internal/controllers/routes/generate_test.go index 6826481ff99a..ecf83da7bfd4 100644 --- a/internal/mesh/internal/controllers/routes/generate_test.go +++ b/internal/mesh/internal/controllers/routes/generate_test.go @@ -6,8 +6,11 @@ package routes import ( "fmt" "testing" + "time" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/durationpb" "github.com/hashicorp/consul/internal/catalog" "github.com/hashicorp/consul/internal/mesh/internal/controllers/routes/loader" @@ -16,6 +19,7 @@ import ( rtest "github.com/hashicorp/consul/internal/resource/resourcetest" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v1alpha1" pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1" + "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/proto/private/prototest" ) @@ -24,15 +28,32 @@ func TestGenerateComputedRoutes(t *testing.T) { types.Register(registry) catalog.RegisterTypes(registry) - run := func(t *testing.T, related *loader.RelatedResources, expect []*ComputedRoutesResult, expectPending int) { + run := func( + t *testing.T, + related *loader.RelatedResources, + expect []*ComputedRoutesResult, + expectPending PendingStatuses, + ) { pending := make(PendingStatuses) got := GenerateComputedRoutes(related, pending) - require.Len(t, pending, expectPending) prototest.AssertElementsMatch[*ComputedRoutesResult]( t, expect, got, ) + + require.Len(t, pending, len(expectPending)) + if len(expectPending) > 0 { + for rk, expectItem := range expectPending { + gotItem, ok := pending[rk] + require.True(t, ok, "missing expected pending status for %v", rk) + prototest.AssertDeepEqual(t, expectItem, gotItem) + } + for rk, _ := range pending { + _, ok := expectPending[rk] + require.True(t, ok, "extra pending status for %v", rk) + } + } } newService := func(name string, data *pbcatalog.Service) *types.DecodedService { @@ -61,6 +82,20 @@ func TestGenerateComputedRoutes(t *testing.T) { return rtest.MustDecode[*pbmesh.TCPRoute](t, svc) } + newDestPolicy := func(name string, data *pbmesh.DestinationPolicy) *types.DecodedDestinationPolicy { + policy := rtest.Resource(types.DestinationPolicyType, name). + WithData(t, data).Build() + rtest.ValidateAndNormalize(t, registry, policy) + return rtest.MustDecode[*pbmesh.DestinationPolicy](t, policy) + } + + newFailPolicy := func(name string, data *pbcatalog.FailoverPolicy) *types.DecodedFailoverPolicy { + policy := rtest.Resource(catalog.FailoverPolicyType, name). + WithData(t, data).Build() + rtest.ValidateAndNormalize(t, registry, policy) + return rtest.MustDecode[*pbcatalog.FailoverPolicy](t, policy) + } + backendName := func(name, port string) string { return fmt.Sprintf("catalog.v1alpha1.Service/default.local.default/%s?port=%s", name, port) } @@ -72,10 +107,13 @@ func TestGenerateComputedRoutes(t *testing.T) { fooServiceID = rtest.Resource(catalog.ServiceType, "foo").ID() fooServiceRef = resource.Reference(fooServiceID, "") + + barServiceID = rtest.Resource(catalog.ServiceType, "bar").ID() + barServiceRef = resource.Reference(barServiceID, "") ) t.Run("none", func(t *testing.T) { - run(t, loader.NewRelatedResources(), nil, 0) + run(t, loader.NewRelatedResources(), nil, nil) }) t.Run("no aligned service", func(t *testing.T) { @@ -87,7 +125,7 @@ func TestGenerateComputedRoutes(t *testing.T) { Data: nil, }, } - run(t, related, expect, 0) + run(t, related, expect, nil) }) t.Run("aligned service not in mesh", func(t *testing.T) { @@ -105,7 +143,7 @@ func TestGenerateComputedRoutes(t *testing.T) { ID: apiComputedRoutesID, Data: nil, }} - run(t, related, expect, 0) + run(t, related, expect, nil) }) t.Run("tcp service with default route", func(t *testing.T) { @@ -150,7 +188,7 @@ func TestGenerateComputedRoutes(t *testing.T) { }, }}, } - run(t, related, expect, 0) + run(t, related, expect, nil) }) for protoName, protocol := range map[string]pbcatalog.Protocol{ @@ -205,7 +243,7 @@ func TestGenerateComputedRoutes(t *testing.T) { }, }, }} - run(t, related, expect, 0) + run(t, related, expect, nil) }) } @@ -252,7 +290,7 @@ func TestGenerateComputedRoutes(t *testing.T) { }, }, }} - run(t, related, expect, 0) + run(t, related, expect, nil) }) t.Run("all ports with a mix of routes", func(t *testing.T) { @@ -433,7 +471,7 @@ func TestGenerateComputedRoutes(t *testing.T) { }, }, }} - run(t, related, expect, 0) + run(t, related, expect, nil) }) t.Run("all ports with a wildcard route only bypassing the protocol", func(t *testing.T) { @@ -524,6 +562,953 @@ func TestGenerateComputedRoutes(t *testing.T) { }, }, }} - run(t, related, expect, 0) + run(t, related, expect, nil) + }) + + t.Run("stale-weird: stale mapper causes visit of irrelevant xRoute", func(t *testing.T) { + apiServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"api-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + fooServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"foo-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + httpRoute1 := &pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + newParentRef(newRef(catalog.ServiceType, "api"), ""), + }, + Rules: []*pbmesh.HTTPRouteRule{{ + BackendRefs: []*pbmesh.HTTPBackendRef{{ + BackendRef: newBackendRef(fooServiceRef, "", ""), + }}, + }}, + } + + apiSvc := newService("api", apiServiceData) + fooSvc := newService("foo", fooServiceData) + apiHTTPRoute1 := newHTTPRoute("api-http-route1", httpRoute1) + + related := loader.NewRelatedResources(). + AddComputedRoutesIDs(apiComputedRoutesID). // deliberately skip adding 'foo' here to exercise the bug + AddResources(apiSvc, fooSvc, apiHTTPRoute1) + + // Update this after the fact, but don't update the inner indexing in + // the 'related' struct. + { + httpRoute1.ParentRefs[0] = newParentRef(newRef(catalog.ServiceType, "foo"), "") + apiHTTPRoute1.Data = httpRoute1 + + anyData, err := anypb.New(httpRoute1) + require.NoError(t, err) + apiHTTPRoute1.Resource.Data = anyData + } + + expect := []*ComputedRoutesResult{{ + ID: apiComputedRoutesID, + OwnerID: apiServiceID, + Data: &pbmesh.ComputedRoutes{ + PortedConfigs: map[string]*pbmesh.ComputedPortRoutes{ + "http": { + Config: &pbmesh.ComputedPortRoutes_Http{ + Http: &pbmesh.ComputedHTTPRoute{ + ParentRef: newParentRef(apiServiceRef, "http"), + Rules: []*pbmesh.ComputedHTTPRouteRule{{ + Matches: []*pbmesh.HTTPRouteMatch{{ + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_PREFIX, + Value: "/", + }, + }}, + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: backendName("api", "http"), + }}, + }}, + }, + }, + UsingDefaultConfig: true, + Targets: map[string]*pbmesh.BackendTargetDetails{ + backendName("api", "http"): { + BackendRef: newBackendRef(apiServiceRef, "http", ""), + Service: apiServiceData, + }, + }, + }, + }, + }, + }} + run(t, related, expect, nil) + }) + + t.Run("stale-weird: parent ref uses invalid port", func(t *testing.T) { + apiServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"api-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + fooServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"foo-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + httpRoute1 := &pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + // Using bad parent port (www). + newParentRef(newRef(catalog.ServiceType, "api"), "www"), + }, + Rules: []*pbmesh.HTTPRouteRule{{ + BackendRefs: []*pbmesh.HTTPBackendRef{{ + BackendRef: newBackendRef(fooServiceRef, "http", ""), + }}, + }}, + } + + related := loader.NewRelatedResources(). + AddComputedRoutesIDs(apiComputedRoutesID). + AddResources( + newService("api", apiServiceData), + newService("foo", fooServiceData), + newHTTPRoute("api-http-route1", httpRoute1), + ) + + expect := []*ComputedRoutesResult{{ + ID: apiComputedRoutesID, + OwnerID: apiServiceID, + Data: &pbmesh.ComputedRoutes{ + PortedConfigs: map[string]*pbmesh.ComputedPortRoutes{ + "http": { + Config: &pbmesh.ComputedPortRoutes_Http{ + Http: &pbmesh.ComputedHTTPRoute{ + ParentRef: newParentRef(apiServiceRef, "http"), + Rules: []*pbmesh.ComputedHTTPRouteRule{{ + Matches: []*pbmesh.HTTPRouteMatch{{ + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_PREFIX, + Value: "/", + }, + }}, + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: backendName("api", "http"), + }}, + }}, + }, + }, + UsingDefaultConfig: true, + Targets: map[string]*pbmesh.BackendTargetDetails{ + backendName("api", "http"): { + BackendRef: newBackendRef(apiServiceRef, "http", ""), + Service: apiServiceData, + }, + }, + }, + }, + }, + }} + run(t, related, expect, nil) + }) + + t.Run("overlapping xRoutes", func(t *testing.T) { + apiServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"api-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + fooServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"foo-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + httpRouteData := &pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + // Using bad parent port (www). + newParentRef(newRef(catalog.ServiceType, "api"), "http"), + }, + Rules: []*pbmesh.HTTPRouteRule{{ + BackendRefs: []*pbmesh.HTTPBackendRef{{ + BackendRef: newBackendRef(fooServiceRef, "http", ""), + }}, + }}, + } + httpRoute := newHTTPRoute("api-http-route", httpRouteData) + + tcpRouteData := &pbmesh.TCPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + // Using bad parent port (www). + newParentRef(newRef(catalog.ServiceType, "api"), "http"), + }, + Rules: []*pbmesh.TCPRouteRule{{ + BackendRefs: []*pbmesh.TCPBackendRef{{ + BackendRef: newBackendRef(fooServiceRef, "http", ""), + }}, + }}, + } + tcpRoute := newTCPRoute("api-tcp-route", tcpRouteData) + // Force them to have the same generation, so that we fall back on + // lexicographic sort on the names as tiebreaker. + // + // api-http-route < api-tcp-route + tcpRoute.Resource.Generation = httpRoute.Resource.Generation + + related := loader.NewRelatedResources(). + AddComputedRoutesIDs(apiComputedRoutesID). + AddResources( + newService("api", apiServiceData), + newService("foo", fooServiceData), + httpRoute, + tcpRoute, + ) + + expect := []*ComputedRoutesResult{{ + ID: apiComputedRoutesID, + OwnerID: apiServiceID, + Data: &pbmesh.ComputedRoutes{ + PortedConfigs: map[string]*pbmesh.ComputedPortRoutes{ + "http": { + Config: &pbmesh.ComputedPortRoutes_Http{ + Http: &pbmesh.ComputedHTTPRoute{ + ParentRef: newParentRef(apiServiceRef, "http"), + Rules: []*pbmesh.ComputedHTTPRouteRule{ + { + Matches: defaultHTTPRouteMatches(), + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: backendName("foo", "http"), + }}, + }, + { + Matches: defaultHTTPRouteMatches(), + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: types.NullRouteBackend, + }}, + }, + }, + }, + }, + Targets: map[string]*pbmesh.BackendTargetDetails{ + backendName("foo", "http"): { + BackendRef: newBackendRef(fooServiceRef, "http", ""), + Service: fooServiceData, + }, + }, + }, + }, + }, + }} + + expectPending := make(PendingStatuses) + expectPending[resource.NewReferenceKey(tcpRoute.Resource.Id)] = &PendingResourceStatusUpdate{ + ID: tcpRoute.Resource.Id, + Generation: tcpRoute.Resource.Generation, + NewConditions: []*pbresource.Condition{ + ConditionConflictNotBoundToParentRef( + apiServiceRef, + "http", + types.HTTPRouteType, + ), + }, + } + + run(t, related, expect, expectPending) + }) + + t.Run("two http routes", func(t *testing.T) { + apiServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"api-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + fooServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"foo-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + barServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"bar-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + httpRoute1 := &pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + newParentRef(newRef(catalog.ServiceType, "api"), "http"), + }, + Rules: []*pbmesh.HTTPRouteRule{{ + Matches: []*pbmesh.HTTPRouteMatch{{ + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_PREFIX, + Value: "/gir", + }, + }}, + BackendRefs: []*pbmesh.HTTPBackendRef{{ + BackendRef: newBackendRef(fooServiceRef, "", ""), + }}, + }}, + } + + httpRoute2 := &pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + newParentRef(newRef(catalog.ServiceType, "api"), "http"), + }, + Rules: []*pbmesh.HTTPRouteRule{{ + Matches: []*pbmesh.HTTPRouteMatch{{ + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_PREFIX, + Value: "/zim", + }, + }}, + BackendRefs: []*pbmesh.HTTPBackendRef{{ + BackendRef: newBackendRef(barServiceRef, "", ""), + }}, + }}, + } + + related := loader.NewRelatedResources(). + AddComputedRoutesIDs(apiComputedRoutesID). + AddResources( + newService("api", apiServiceData), + newService("foo", fooServiceData), + newService("bar", barServiceData), + newHTTPRoute("api-http-route1", httpRoute1), + newHTTPRoute("api-http-route2", httpRoute2), + ) + + expect := []*ComputedRoutesResult{{ + ID: apiComputedRoutesID, + OwnerID: apiServiceID, + Data: &pbmesh.ComputedRoutes{ + PortedConfigs: map[string]*pbmesh.ComputedPortRoutes{ + "http": { + Config: &pbmesh.ComputedPortRoutes_Http{ + Http: &pbmesh.ComputedHTTPRoute{ + ParentRef: newParentRef(apiServiceRef, "http"), + Rules: []*pbmesh.ComputedHTTPRouteRule{ + { + Matches: []*pbmesh.HTTPRouteMatch{{ + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_PREFIX, + Value: "/gir", + }, + }}, + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: backendName("foo", "http"), + }}, + }, + { + Matches: []*pbmesh.HTTPRouteMatch{{ + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_PREFIX, + Value: "/zim", + }, + }}, + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: backendName("bar", "http"), + }}, + }, + { + Matches: defaultHTTPRouteMatches(), + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: types.NullRouteBackend, + }}, + }, + }, + }, + }, + Targets: map[string]*pbmesh.BackendTargetDetails{ + backendName("foo", "http"): { + BackendRef: newBackendRef(fooServiceRef, "http", ""), + Service: fooServiceData, + }, + backendName("bar", "http"): { + BackendRef: newBackendRef(barServiceRef, "http", ""), + Service: barServiceData, + }, + }, + }, + }, + }, + }} + run(t, related, expect, nil) + }) + + t.Run("http route with empty match path", func(t *testing.T) { + apiServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"api-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + fooServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"foo-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + httpRoute1 := &pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + newParentRef(newRef(catalog.ServiceType, "api"), "http"), + }, + Rules: []*pbmesh.HTTPRouteRule{{ + Matches: []*pbmesh.HTTPRouteMatch{{ + Path: nil, + }}, + BackendRefs: []*pbmesh.HTTPBackendRef{{ + BackendRef: newBackendRef(fooServiceRef, "", ""), + }}, + }}, + } + + related := loader.NewRelatedResources(). + AddComputedRoutesIDs(apiComputedRoutesID). + AddResources( + newService("api", apiServiceData), + newService("foo", fooServiceData), + newHTTPRoute("api-http-route1", httpRoute1), + ) + + expect := []*ComputedRoutesResult{{ + ID: apiComputedRoutesID, + OwnerID: apiServiceID, + Data: &pbmesh.ComputedRoutes{ + PortedConfigs: map[string]*pbmesh.ComputedPortRoutes{ + "http": { + Config: &pbmesh.ComputedPortRoutes_Http{ + Http: &pbmesh.ComputedHTTPRoute{ + ParentRef: newParentRef(apiServiceRef, "http"), + Rules: []*pbmesh.ComputedHTTPRouteRule{ + { + Matches: defaultHTTPRouteMatches(), + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: backendName("foo", "http"), + }}, + }, + { + Matches: defaultHTTPRouteMatches(), + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: types.NullRouteBackend, + }}, + }, + }, + }, + }, + Targets: map[string]*pbmesh.BackendTargetDetails{ + backendName("foo", "http"): { + BackendRef: newBackendRef(fooServiceRef, "http", ""), + Service: fooServiceData, + }, + }, + }, + }, + }, + }} + run(t, related, expect, nil) + }) + + t.Run("stale-weird: destination with no service", func(t *testing.T) { + t.Run("http", func(t *testing.T) { + apiServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"api-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + httpRoute1 := &pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + newParentRef(newRef(catalog.ServiceType, "api"), "http"), + }, + Rules: []*pbmesh.HTTPRouteRule{{ + BackendRefs: []*pbmesh.HTTPBackendRef{{ + BackendRef: newBackendRef(fooServiceRef, "", ""), + }}, + }}, + } + + related := loader.NewRelatedResources(). + AddComputedRoutesIDs(apiComputedRoutesID). + AddResources( + newService("api", apiServiceData), + newHTTPRoute("api-http-route1", httpRoute1), + ) + + expect := []*ComputedRoutesResult{{ + ID: apiComputedRoutesID, + OwnerID: apiServiceID, + Data: &pbmesh.ComputedRoutes{ + PortedConfigs: map[string]*pbmesh.ComputedPortRoutes{ + "http": { + Config: &pbmesh.ComputedPortRoutes_Http{ + Http: &pbmesh.ComputedHTTPRoute{ + ParentRef: newParentRef(apiServiceRef, "http"), + Rules: []*pbmesh.ComputedHTTPRouteRule{ + { + Matches: defaultHTTPRouteMatches(), + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: types.NullRouteBackend, + }}, + }, + { + Matches: defaultHTTPRouteMatches(), + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: types.NullRouteBackend, + }}, + }, + }, + }, + }, + }, + }, + }, + }} + run(t, related, expect, nil) + }) + t.Run("grpc", func(t *testing.T) { + apiServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"api-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "grpc", Protocol: pbcatalog.Protocol_PROTOCOL_GRPC}, + }, + } + + grpcRoute1 := &pbmesh.GRPCRoute{ + ParentRefs: []*pbmesh.ParentReference{ + newParentRef(newRef(catalog.ServiceType, "api"), "grpc"), + }, + Rules: []*pbmesh.GRPCRouteRule{{ + BackendRefs: []*pbmesh.GRPCBackendRef{{ + BackendRef: newBackendRef(fooServiceRef, "", ""), + }}, + }}, + } + + related := loader.NewRelatedResources(). + AddComputedRoutesIDs(apiComputedRoutesID). + AddResources( + newService("api", apiServiceData), + newGRPCRoute("api-grpc-route1", grpcRoute1), + ) + + expect := []*ComputedRoutesResult{{ + ID: apiComputedRoutesID, + OwnerID: apiServiceID, + Data: &pbmesh.ComputedRoutes{ + PortedConfigs: map[string]*pbmesh.ComputedPortRoutes{ + "grpc": { + Config: &pbmesh.ComputedPortRoutes_Grpc{ + Grpc: &pbmesh.ComputedGRPCRoute{ + ParentRef: newParentRef(apiServiceRef, "grpc"), + Rules: []*pbmesh.ComputedGRPCRouteRule{ + { + Matches: defaultGRPCRouteMatches(), + BackendRefs: []*pbmesh.ComputedGRPCBackendRef{{ + BackendTarget: types.NullRouteBackend, + }}, + }, + { + Matches: defaultGRPCRouteMatches(), + BackendRefs: []*pbmesh.ComputedGRPCBackendRef{{ + BackendTarget: types.NullRouteBackend, + }}, + }, + }, + }, + }, + }, + }, + }, + }} + run(t, related, expect, nil) + }) + t.Run("tcp", func(t *testing.T) { + apiServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"api-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "tcp", Protocol: pbcatalog.Protocol_PROTOCOL_TCP}, + }, + } + + tcpRoute1 := &pbmesh.TCPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + newParentRef(newRef(catalog.ServiceType, "api"), "tcp"), + }, + Rules: []*pbmesh.TCPRouteRule{{ + BackendRefs: []*pbmesh.TCPBackendRef{{ + BackendRef: newBackendRef(fooServiceRef, "", ""), + }}, + }}, + } + + related := loader.NewRelatedResources(). + AddComputedRoutesIDs(apiComputedRoutesID). + AddResources( + newService("api", apiServiceData), + newTCPRoute("api-tcp-route1", tcpRoute1), + ) + + expect := []*ComputedRoutesResult{{ + ID: apiComputedRoutesID, + OwnerID: apiServiceID, + Data: &pbmesh.ComputedRoutes{ + PortedConfigs: map[string]*pbmesh.ComputedPortRoutes{ + "tcp": { + Config: &pbmesh.ComputedPortRoutes_Tcp{ + Tcp: &pbmesh.ComputedTCPRoute{ + ParentRef: newParentRef(apiServiceRef, "tcp"), + Rules: []*pbmesh.ComputedTCPRouteRule{ + { + BackendRefs: []*pbmesh.ComputedTCPBackendRef{{ + BackendTarget: types.NullRouteBackend, + }}, + }, + }, + }, + }, + }, + }, + }, + }} + run(t, related, expect, nil) + }) + }) + + t.Run("stale-weird: http destination with service not in mesh", func(t *testing.T) { + apiServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"api-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + fooServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"foo-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + httpRoute1 := &pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + newParentRef(newRef(catalog.ServiceType, "api"), "http"), + }, + Rules: []*pbmesh.HTTPRouteRule{{ + BackendRefs: []*pbmesh.HTTPBackendRef{{ + BackendRef: newBackendRef(fooServiceRef, "", ""), + }}, + }}, + } + + related := loader.NewRelatedResources(). + AddComputedRoutesIDs(apiComputedRoutesID). + AddResources( + newService("api", apiServiceData), + newService("foo", fooServiceData), + newHTTPRoute("api-http-route1", httpRoute1), + ) + + expect := []*ComputedRoutesResult{{ + ID: apiComputedRoutesID, + OwnerID: apiServiceID, + Data: &pbmesh.ComputedRoutes{ + PortedConfigs: map[string]*pbmesh.ComputedPortRoutes{ + "http": { + Config: &pbmesh.ComputedPortRoutes_Http{ + Http: &pbmesh.ComputedHTTPRoute{ + ParentRef: newParentRef(apiServiceRef, "http"), + Rules: []*pbmesh.ComputedHTTPRouteRule{ + { + Matches: defaultHTTPRouteMatches(), + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: types.NullRouteBackend, + }}, + }, + { + Matches: defaultHTTPRouteMatches(), + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: types.NullRouteBackend, + }}, + }, + }, + }, + }, + }, + }, + }, + }} + run(t, related, expect, nil) + }) + + t.Run("http route with dest policy", func(t *testing.T) { + apiServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"api-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + fooServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"foo-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + httpRoute1 := &pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + newParentRef(newRef(catalog.ServiceType, "api"), "http"), + }, + Rules: []*pbmesh.HTTPRouteRule{{ + Matches: []*pbmesh.HTTPRouteMatch{{ + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_PREFIX, + Value: "/", + }, + }}, + BackendRefs: []*pbmesh.HTTPBackendRef{{ + BackendRef: newBackendRef(fooServiceRef, "", ""), + }}, + }}, + } + + destPolicy := &pbmesh.DestinationPolicy{ + PortConfigs: map[string]*pbmesh.DestinationConfig{ + "http": { + ConnectTimeout: durationpb.New(55 * time.Second), + }, + }, + } + + related := loader.NewRelatedResources(). + AddComputedRoutesIDs(apiComputedRoutesID). + AddResources( + newService("api", apiServiceData), + newService("foo", fooServiceData), + newHTTPRoute("api-http-route1", httpRoute1), + newDestPolicy("foo", destPolicy), + ) + + expect := []*ComputedRoutesResult{{ + ID: apiComputedRoutesID, + OwnerID: apiServiceID, + Data: &pbmesh.ComputedRoutes{ + PortedConfigs: map[string]*pbmesh.ComputedPortRoutes{ + "http": { + Config: &pbmesh.ComputedPortRoutes_Http{ + Http: &pbmesh.ComputedHTTPRoute{ + ParentRef: newParentRef(apiServiceRef, "http"), + Rules: []*pbmesh.ComputedHTTPRouteRule{ + { + Matches: defaultHTTPRouteMatches(), + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: backendName("foo", "http"), + }}, + }, + { + Matches: defaultHTTPRouteMatches(), + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: types.NullRouteBackend, + }}, + }, + }, + }, + }, + Targets: map[string]*pbmesh.BackendTargetDetails{ + backendName("foo", "http"): { + BackendRef: newBackendRef(fooServiceRef, "http", ""), + Service: fooServiceData, + DestinationPolicy: destPolicy, + }, + }, + }, + }, + }, + }} + run(t, related, expect, nil) + }) + + t.Run("http route with failover policy", func(t *testing.T) { + apiServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"api-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + fooServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"foo-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + barServiceData := &pbcatalog.Service{ + Workloads: &pbcatalog.WorkloadSelector{ + Prefixes: []string{"bar-"}, + }, + Ports: []*pbcatalog.ServicePort{ + {TargetPort: "mesh", Protocol: pbcatalog.Protocol_PROTOCOL_MESH}, + {TargetPort: "http", Protocol: pbcatalog.Protocol_PROTOCOL_HTTP}, + }, + } + + httpRoute1 := &pbmesh.HTTPRoute{ + ParentRefs: []*pbmesh.ParentReference{ + newParentRef(newRef(catalog.ServiceType, "api"), "http"), + }, + Rules: []*pbmesh.HTTPRouteRule{{ + Matches: []*pbmesh.HTTPRouteMatch{{ + Path: &pbmesh.HTTPPathMatch{ + Type: pbmesh.PathMatchType_PATH_MATCH_TYPE_PREFIX, + Value: "/", + }, + }}, + BackendRefs: []*pbmesh.HTTPBackendRef{{ + BackendRef: newBackendRef(fooServiceRef, "", ""), + }}, + }}, + } + + failoverPolicy := &pbcatalog.FailoverPolicy{ + Config: &pbcatalog.FailoverConfig{ + Destinations: []*pbcatalog.FailoverDestination{{ + Ref: barServiceRef, + }}, + }, + } + simplifiedFailoverPolicy := &pbcatalog.FailoverPolicy{ + PortConfigs: map[string]*pbcatalog.FailoverConfig{ + "http": &pbcatalog.FailoverConfig{ + Destinations: []*pbcatalog.FailoverDestination{{ + Ref: barServiceRef, + Port: "http", + }}, + }, + }, + } + + related := loader.NewRelatedResources(). + AddComputedRoutesIDs(apiComputedRoutesID). + AddResources( + newService("api", apiServiceData), + newService("foo", fooServiceData), + newService("bar", barServiceData), + newHTTPRoute("api-http-route1", httpRoute1), + newFailPolicy("foo", failoverPolicy), + ) + + expect := []*ComputedRoutesResult{{ + ID: apiComputedRoutesID, + OwnerID: apiServiceID, + Data: &pbmesh.ComputedRoutes{ + PortedConfigs: map[string]*pbmesh.ComputedPortRoutes{ + "http": { + Config: &pbmesh.ComputedPortRoutes_Http{ + Http: &pbmesh.ComputedHTTPRoute{ + ParentRef: newParentRef(apiServiceRef, "http"), + Rules: []*pbmesh.ComputedHTTPRouteRule{ + { + Matches: defaultHTTPRouteMatches(), + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: backendName("foo", "http"), + }}, + }, + { + Matches: defaultHTTPRouteMatches(), + BackendRefs: []*pbmesh.ComputedHTTPBackendRef{{ + BackendTarget: types.NullRouteBackend, + }}, + }, + }, + }, + }, + Targets: map[string]*pbmesh.BackendTargetDetails{ + backendName("foo", "http"): { + BackendRef: newBackendRef(fooServiceRef, "http", ""), + Service: fooServiceData, + FailoverPolicy: simplifiedFailoverPolicy, + }, + }, + }, + }, + }, + }} + run(t, related, expect, nil) }) }