Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mesh: create new routes-controller to reconcile xRoute types into a ComputedRoutes resource #18460

Merged
merged 49 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
cd28e66
wip: routes controller etc
rboyer Aug 9, 2023
4c180d7
add more tests and fix a few bugs
rboyer Aug 14, 2023
b328d3a
rename troublesome function
rboyer Aug 14, 2023
0e07b43
clarify how null routing works
rboyer Aug 14, 2023
a8ad665
remove debugging string
rboyer Aug 14, 2023
9cb6287
test overlap
rboyer Aug 14, 2023
d7de729
fix more bugs with conflict
rboyer Aug 14, 2023
f4f0230
remove stray comment
rboyer Aug 14, 2023
7d04781
remove old todo
rboyer Aug 14, 2023
d218235
remove stray logs
rboyer Aug 14, 2023
3ad8e8a
update messages for new name
rboyer Aug 14, 2023
edff762
use loggerFor in both places
rboyer Aug 14, 2023
aa58b8b
cleanup naming and export similarly to failover controller
rboyer Aug 14, 2023
3b28e46
remove debuggin
rboyer Aug 14, 2023
cd69033
remove todos
rboyer Aug 14, 2023
091cc12
rename protobuf message types
rboyer Aug 14, 2023
cec0174
extract shared blob
rboyer Aug 14, 2023
9772251
appease the linter
rboyer Aug 14, 2023
e240ec7
remove todo
rboyer Aug 14, 2023
4e0da78
code gen helpers
rboyer Aug 14, 2023
58a692c
fix go.mod
rboyer Aug 15, 2023
fba64fd
fix signatures
rboyer Aug 21, 2023
9570170
remove unnecessary meta fields
rboyer Aug 21, 2023
b189c94
clarify comment
rboyer Aug 21, 2023
0430838
use default printing format
rboyer Aug 21, 2023
82538b4
early return
rboyer Aug 21, 2023
b1f1c91
update commentary
rboyer Aug 21, 2023
5383230
checkpoint
rboyer Aug 22, 2023
2de11ed
adding some generation tests outside of controller logic
rboyer Aug 22, 2023
57e460e
move deduplication into a test and rely upon controller framework to …
rboyer Aug 22, 2023
9dfdde7
stuff
rboyer Aug 22, 2023
bb3dca3
more tests
rboyer Aug 22, 2023
95be76d
change indent
rboyer Aug 22, 2023
fc61ea7
add more coverage to just the generate.go file
rboyer Aug 22, 2023
547ff46
lint
rboyer Aug 23, 2023
2516db6
clean up this plumbing
rboyer Aug 23, 2023
0f7466d
test initial sort
rboyer Aug 23, 2023
c946c95
More sort
rboyer Aug 23, 2023
aef3c44
reorder to appease lint
rboyer Aug 23, 2023
14d345f
update signatures to be friendlier to testing
rboyer Aug 24, 2023
6864c73
fixup for scope
rboyer Sep 11, 2023
cb7e073
fix enumcover lint
rboyer Sep 11, 2023
4ec9640
use generics instead of generated methods
rboyer Sep 11, 2023
5f8c937
switch to resource.ReplaceType
rboyer Sep 11, 2023
4f81494
use existing interface
rboyer Sep 11, 2023
f3cac93
unexport
rboyer Sep 11, 2023
94cb21b
add shared helper
rboyer Sep 11, 2023
f64b243
remove extra suspenders
rboyer Sep 11, 2023
b4e15ab
this was dead code
rboyer Sep 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ require (
github.com/mitchellh/pointerstructure v1.2.1
github.com/mitchellh/reflectwalk v1.0.2
github.com/natefinch/npipe v0.0.0-20160621034901-c1b8fa8bdcce
github.com/oklog/ulid v1.3.1
github.com/oklog/ulid/v2 v2.1.0
github.com/olekukonko/tablewriter v0.0.4
github.com/patrickmn/go-cache v2.1.0+incompatible
Expand Down Expand Up @@ -219,7 +220,6 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/packethost/packngo v0.1.1-0.20180711074735-b9cb5096f54c // indirect
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
Expand Down
34 changes: 34 additions & 0 deletions internal/controller/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package controller

import (
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/proto-public/pbresource"
)

// MakeRequests accepts a list of pbresource.ID and pbresource.Reference items,
// and mirrors them into a slice of []controller.Request items where the Type
// of of the items has replaced by 'typ'.
func MakeRequests[V resource.ReferenceOrID](
typ *pbresource.Type,
refs []V,
) []Request {
if len(refs) == 0 {
return nil
}

out := make([]Request, 0, len(refs))
for _, ref := range refs {
out = append(out, Request{
ID: &pbresource.ID{
Type: typ,
Tenancy: ref.GetTenancy(),
Name: ref.GetName(),
},
})
}

return out
}
77 changes: 77 additions & 0 deletions internal/controller/helper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package controller

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/proto-public/pbresource"
"github.com/hashicorp/consul/proto/private/prototest"
)

func TestMakeRequests(t *testing.T) {
redType := &pbresource.Type{
Group: "colors",
GroupVersion: "vfake",
Kind: "red",
}
blueType := &pbresource.Type{
Group: "colors",
GroupVersion: "vfake",
Kind: "blue",
}

casparID := &pbresource.ID{
Type: redType,
Tenancy: resource.DefaultNamespacedTenancy(),
Name: "caspar",
Uid: "ignored",
}
babypantsID := &pbresource.ID{
Type: redType,
Tenancy: resource.DefaultNamespacedTenancy(),
Name: "babypants",
Uid: "ignored",
}
zimRef := &pbresource.Reference{
Type: redType,
Tenancy: resource.DefaultNamespacedTenancy(),
Name: "zim",
Section: "ignored",
}
girRef := &pbresource.Reference{
Type: redType,
Tenancy: resource.DefaultNamespacedTenancy(),
Name: "gir",
Section: "ignored",
}

newBlueReq := func(name string) Request {
return Request{
ID: &pbresource.ID{
Type: blueType,
Tenancy: resource.DefaultNamespacedTenancy(),
Name: name,
},
}
}

require.Nil(t, MakeRequests[*pbresource.ID](blueType, nil))
require.Nil(t, MakeRequests[*pbresource.Reference](blueType, nil))

prototest.AssertElementsMatch(t, []Request{
newBlueReq("caspar"), newBlueReq("babypants"),
}, MakeRequests[*pbresource.ID](blueType, []*pbresource.ID{
casparID, babypantsID,
}))

prototest.AssertElementsMatch(t, []Request{
newBlueReq("gir"), newBlueReq("zim"),
}, MakeRequests[*pbresource.Reference](blueType, []*pbresource.Reference{
girRef, zimRef,
}))
}
39 changes: 30 additions & 9 deletions internal/mesh/exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package mesh
import (
"github.com/hashicorp/consul/internal/controller"
"github.com/hashicorp/consul/internal/mesh/internal/controllers"
"github.com/hashicorp/consul/internal/mesh/internal/controllers/routes"
"github.com/hashicorp/consul/internal/mesh/internal/controllers/sidecarproxy"
"github.com/hashicorp/consul/internal/mesh/internal/controllers/sidecarproxy/status"
"github.com/hashicorp/consul/internal/mesh/internal/types"
Expand Down Expand Up @@ -47,15 +48,16 @@ var (

// Resource Types for the latest version.

ProxyConfigurationType = types.ProxyConfigurationType
UpstreamsType = types.UpstreamsType
UpstreamsConfigurationType = types.UpstreamsConfigurationType
ProxyStateTemplateType = types.ProxyStateTemplateType
HTTPRouteType = types.HTTPRouteType
GRPCRouteType = types.GRPCRouteType
TCPRouteType = types.TCPRouteType
DestinationPolicyType = types.DestinationPolicyType
ComputedRoutesType = types.ComputedRoutesType
ProxyConfigurationType = types.ProxyConfigurationType
UpstreamsType = types.UpstreamsType
UpstreamsConfigurationType = types.UpstreamsConfigurationType
ProxyStateTemplateType = types.ProxyStateTemplateType
ProxyStateTemplateConfigurationType = types.ProxyStateTemplateType
HTTPRouteType = types.HTTPRouteType
GRPCRouteType = types.GRPCRouteType
TCPRouteType = types.TCPRouteType
DestinationPolicyType = types.DestinationPolicyType
ComputedRoutesType = types.ComputedRoutesType

// Controller statuses.

Expand All @@ -68,6 +70,25 @@ var (
SidecarProxyStatusReasonDestinationServiceFound = status.StatusReasonDestinationServiceFound
SidecarProxyStatusReasonMeshProtocolDestinationPort = status.StatusReasonMeshProtocolDestinationPort
SidecarProxyStatusReasonNonMeshProtocolDestinationPort = status.StatusReasonNonMeshProtocolDestinationPort

// Routes controller
RoutesStatusKey = routes.StatusKey
RoutesStatusConditionAccepted = routes.StatusConditionAccepted
RoutesStatusConditionAcceptedMissingParentRefReason = routes.MissingParentRefReason
RoutesStatusConditionAcceptedMissingBackendRefReason = routes.MissingBackendRefReason
RoutesStatusConditionAcceptedParentRefOutsideMeshReason = routes.ParentRefOutsideMeshReason
RoutesStatusConditionAcceptedBackendRefOutsideMeshReason = routes.BackendRefOutsideMeshReason
RoutesStatusConditionAcceptedParentRefUsingMeshPortReason = routes.ParentRefUsingMeshPortReason
RoutesStatusConditionAcceptedBackendRefUsingMeshPortReason = routes.BackendRefUsingMeshPortReason
RoutesStatusConditionAcceptedUnknownParentRefPortReason = routes.UnknownParentRefPortReason
RoutesStatusConditionAcceptedUnknownBackendRefPortReason = routes.UnknownBackendRefPortReason
RoutesStatusConditionAcceptedConflictNotBoundToParentRefReason = routes.ConflictNotBoundToParentRefReason
)

const (
// Important constants

NullRouteBackend = types.NullRouteBackend
)

// RegisterTypes adds all resource types within the "mesh" API group
Expand Down
3 changes: 3 additions & 0 deletions internal/mesh/internal/controllers/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/hashicorp/consul/internal/catalog"
"github.com/hashicorp/consul/internal/controller"
"github.com/hashicorp/consul/internal/mesh/internal/cache/sidecarproxycache"
"github.com/hashicorp/consul/internal/mesh/internal/controllers/routes"
"github.com/hashicorp/consul/internal/mesh/internal/controllers/sidecarproxy"
"github.com/hashicorp/consul/internal/mesh/internal/controllers/xds"
"github.com/hashicorp/consul/internal/mesh/internal/mappers/sidecarproxymapper"
Expand All @@ -31,4 +32,6 @@ func Register(mgr *controller.Manager, deps Dependencies) {
mgr.Register(
sidecarproxy.Controller(destinationsCache, proxyCfgCache, m, deps.TrustDomainFetcher, deps.LocalDatacenter),
)

mgr.Register(routes.Controller())
}
178 changes: 178 additions & 0 deletions internal/mesh/internal/controllers/routes/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package routes

import (
"context"

"github.com/hashicorp/go-hclog"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"

"github.com/hashicorp/consul/internal/catalog"
"github.com/hashicorp/consul/internal/controller"
"github.com/hashicorp/consul/internal/mesh/internal/controllers/routes/loader"
"github.com/hashicorp/consul/internal/mesh/internal/controllers/routes/xroutemapper"
"github.com/hashicorp/consul/internal/mesh/internal/types"
"github.com/hashicorp/consul/internal/resource"
pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v1alpha1"
"github.com/hashicorp/consul/proto-public/pbresource"
)

func Controller() controller.Controller {
mapper := xroutemapper.New()

r := &routesReconciler{
mapper: mapper,
}
return controller.ForType(types.ComputedRoutesType).
WithWatch(types.HTTPRouteType, mapper.MapHTTPRoute).
WithWatch(types.GRPCRouteType, mapper.MapGRPCRoute).
WithWatch(types.TCPRouteType, mapper.MapTCPRoute).
WithWatch(types.DestinationPolicyType, mapper.MapDestinationPolicy).
WithWatch(catalog.FailoverPolicyType, mapper.MapFailoverPolicy).
WithWatch(catalog.ServiceType, mapper.MapService).
WithReconciler(r)
}

type routesReconciler struct {
mapper *xroutemapper.Mapper
}

func (r *routesReconciler) Reconcile(ctx context.Context, rt controller.Runtime, req controller.Request) error {
// Notably don't inject the resource-id here into the logger, since we have
// to do a fan-out to multiple resources due to xRoutes having multiple
// parent refs.
rt.Logger = rt.Logger.With("controller", StatusKey)

rt.Logger.Trace("reconciling computed routes")

loggerFor := func(id *pbresource.ID) hclog.Logger {
return rt.Logger.With("resource-id", id)
}
related, err := loader.LoadResourcesForComputedRoutes(ctx, loggerFor, rt.Client, r.mapper, req.ID)
if err != nil {
rt.Logger.Error("error loading relevant resources", "error", err)
return err
}

pending := make(PendingStatuses)

ValidateXRouteReferences(related, pending)

generatedResults := GenerateComputedRoutes(related, pending)

if err := UpdatePendingStatuses(ctx, rt, pending); err != nil {
rt.Logger.Error("error updating statuses for affected relevant resources", "error", err)
return err
}

for _, result := range generatedResults {
computedRoutesID := result.ID

logger := rt.Logger.With("resource-id", computedRoutesID)

prev, err := resource.GetDecodedResource[*pbmesh.ComputedRoutes](ctx, rt.Client, computedRoutesID)
if err != nil {
logger.Error("error loading previous computed routes", "error", err)
return err
}

if err := ensureComputedRoutesIsSynced(ctx, logger, rt.Client, result, prev); err != nil {
return err
}
Comment on lines +76 to +84
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes me wonder if the resource service should have an endpoint to WriteIfDataDiffers (or maybe just some gRPC metadata to opt-in to the behavior of eliding unnecessary writes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the server does it, it would only benefit the removal of some raft writes. If the client does it then you get the benefit of also avoiding sending the data over the network at all.

}

return nil
}

func ensureComputedRoutesIsSynced(
ctx context.Context,
logger hclog.Logger,
client pbresource.ResourceServiceClient,
result *ComputedRoutesResult,
prev *types.DecodedComputedRoutes,
) error {
if result.Data == nil {
return deleteComputedRoutes(ctx, logger, client, prev)
}

// Upsert the resource if changed.
if prev != nil {
if proto.Equal(prev.Data, result.Data) {
return nil // no change
}
result.ID = prev.Resource.Id
}

return upsertComputedRoutes(ctx, logger, client, result.ID, result.OwnerID, result.Data)
}

func upsertComputedRoutes(
ctx context.Context,
logger hclog.Logger,
client pbresource.ResourceServiceClient,
id *pbresource.ID,
ownerID *pbresource.ID,
data *pbmesh.ComputedRoutes,
) error {
mcData, err := anypb.New(data)
if err != nil {
logger.Error("error marshalling new computed routes payload", "error", err)
return err
}

// Now perform the write. The computed routes resource should be owned
// by the service so that it will automatically be deleted upon service
// deletion.

_, err = client.Write(ctx, &pbresource.WriteRequest{
Resource: &pbresource.Resource{
Id: id,
Owner: ownerID,
Data: mcData,
},
})
if err != nil {
logger.Error("error writing computed routes", "error", err)
return err
}

logger.Trace("updated computed routes resource was successfully written")

return nil
}

func deleteComputedRoutes(
ctx context.Context,
logger hclog.Logger,
client pbresource.ResourceServiceClient,
prev *types.DecodedComputedRoutes,
) error {
if prev == nil {
return nil
}

// The service the computed routes controls no longer participates in the
// mesh at all.

logger.Trace("removing previous computed routes")

// This performs a CAS deletion.
_, err := client.Delete(ctx, &pbresource.DeleteRequest{
Id: prev.Resource.Id,
Version: prev.Resource.Version,
})
// Potentially we could look for CAS failures by checking if the gRPC
// status code is Aborted. However its an edge case and there could
// possibly be other reasons why the gRPC status code would be aborted
// besides CAS version mismatches. The simplest thing to do is to just
// propagate the error and retry reconciliation later.
if err != nil {
logger.Error("error deleting previous computed routes resource", "error", err)
return err
}

return nil
}
Loading
Loading