mirror of https://github.com/status-im/consul.git
[NET-6426] Add gateway proxy controller that generates empty proxy state template (#19901)
* NET-6426 Create ProxyStateTemplate when reconciling MeshGateway resource * Add TODO for switching fetch method based on gateway type * Use gateway-kind in workload metadata instead of owner reference * Create ProxyStateTemplate builder for gatewayproxy controller * Update to use new controller interface * Add copyright headers * Set correct name for ProxyStateTemplate identity reference * Generate empty ProxyStateTemplate by fetching MeshGateway This cheats and looks up the MeshGateway directly. In the future, we will need a Workload => xGateway mapper * Specify owner reference when writing ProxyStateTemplate * Update dependency mapper to account for multiple controllers per resource type * Regenerate v2 resource dependencies map * Add helpful trace logs, tag TODOs with ticket identifiers
This commit is contained in:
parent
a19df32fa5
commit
ab60fec15a
|
@ -41,6 +41,7 @@ flowchart TD
|
||||||
mesh/v2beta1/proxyconfiguration
|
mesh/v2beta1/proxyconfiguration
|
||||||
mesh/v2beta1/proxystatetemplate --> auth/v2beta1/computedtrafficpermissions
|
mesh/v2beta1/proxystatetemplate --> auth/v2beta1/computedtrafficpermissions
|
||||||
mesh/v2beta1/proxystatetemplate --> catalog/v2beta1/service
|
mesh/v2beta1/proxystatetemplate --> catalog/v2beta1/service
|
||||||
|
mesh/v2beta1/proxystatetemplate --> catalog/v2beta1/serviceendpoints
|
||||||
mesh/v2beta1/proxystatetemplate --> catalog/v2beta1/workload
|
mesh/v2beta1/proxystatetemplate --> catalog/v2beta1/workload
|
||||||
mesh/v2beta1/proxystatetemplate --> mesh/v2beta1/computedexplicitdestinations
|
mesh/v2beta1/proxystatetemplate --> mesh/v2beta1/computedexplicitdestinations
|
||||||
mesh/v2beta1/proxystatetemplate --> mesh/v2beta1/computedproxyconfiguration
|
mesh/v2beta1/proxystatetemplate --> mesh/v2beta1/computedproxyconfiguration
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/internal/resource"
|
"github.com/hashicorp/consul/internal/resource"
|
||||||
"github.com/hashicorp/consul/proto-public/pbresource"
|
"github.com/hashicorp/consul/proto-public/pbresource"
|
||||||
|
@ -66,12 +67,23 @@ func (m *Manager) CalculateDependencies(registrations []resource.Registration) D
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range m.controllers {
|
for _, c := range m.controllers {
|
||||||
watches := make([]string, 0, len(c.watches))
|
watches := map[string]struct{}{}
|
||||||
for _, w := range c.watches {
|
|
||||||
watches = append(watches, typeToString(w.watchedType))
|
// Extend existing watch list if one is present. This is necessary
|
||||||
|
// because there can be multiple controllers for a given type.
|
||||||
|
// ProxyStateTemplate, for example, is controlled by sidecar proxy and
|
||||||
|
// gateway proxy controllers.
|
||||||
|
if existing, ok := out[typeToString(c.managedTypeWatch.watchedType)]; ok {
|
||||||
|
for _, w := range existing {
|
||||||
|
watches[w] = struct{}{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out[typeToString(c.managedTypeWatch.watchedType)] = watches
|
for _, w := range c.watches {
|
||||||
|
watches[typeToString(w.watchedType)] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
out[typeToString(c.managedTypeWatch.watchedType)] = maps.Keys(watches)
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
|
||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/consul/internal/mesh/internal/types"
|
||||||
|
pbauth "github.com/hashicorp/consul/proto-public/pbauth/v2beta1"
|
||||||
|
meshv2beta1 "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1"
|
||||||
|
"github.com/hashicorp/consul/proto-public/pbmesh/v2beta1/pbproxystate"
|
||||||
|
"github.com/hashicorp/consul/proto-public/pbresource"
|
||||||
|
)
|
||||||
|
|
||||||
|
type proxyStateTemplateBuilder struct {
|
||||||
|
workload *types.DecodedWorkload
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyStateTemplateBuilder(workload *types.DecodedWorkload) *proxyStateTemplateBuilder {
|
||||||
|
return &proxyStateTemplateBuilder{
|
||||||
|
workload: workload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *proxyStateTemplateBuilder) identity() *pbresource.Reference {
|
||||||
|
return &pbresource.Reference{
|
||||||
|
Name: b.workload.Data.Identity,
|
||||||
|
Tenancy: b.workload.Id.Tenancy,
|
||||||
|
Type: pbauth.WorkloadIdentityType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *proxyStateTemplateBuilder) listeners() []*pbproxystate.Listener {
|
||||||
|
// TODO NET-6429
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *proxyStateTemplateBuilder) clusters() map[string]*pbproxystate.Cluster {
|
||||||
|
// TODO NET-6430
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *proxyStateTemplateBuilder) endpoints() map[string]*pbproxystate.Endpoints {
|
||||||
|
// TODO NET-6431
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *proxyStateTemplateBuilder) routes() map[string]*pbproxystate.Route {
|
||||||
|
// TODO NET-6428
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *proxyStateTemplateBuilder) Build() *meshv2beta1.ProxyStateTemplate {
|
||||||
|
return &meshv2beta1.ProxyStateTemplate{
|
||||||
|
ProxyState: &meshv2beta1.ProxyState{
|
||||||
|
Identity: b.identity(),
|
||||||
|
Listeners: b.listeners(),
|
||||||
|
Clusters: b.clusters(),
|
||||||
|
Endpoints: b.endpoints(),
|
||||||
|
Routes: b.routes(),
|
||||||
|
},
|
||||||
|
RequiredEndpoints: make(map[string]*pbproxystate.EndpointRef),
|
||||||
|
RequiredLeafCertificates: make(map[string]*pbproxystate.LeafCertificateRef),
|
||||||
|
RequiredTrustBundles: make(map[string]*pbproxystate.TrustBundleRef),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
|
||||||
|
package gatewayproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
"google.golang.org/protobuf/types/known/anypb"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/internal/controller"
|
||||||
|
"github.com/hashicorp/consul/internal/controller/dependency"
|
||||||
|
"github.com/hashicorp/consul/internal/mesh/internal/controllers/gatewayproxy/builder"
|
||||||
|
"github.com/hashicorp/consul/internal/mesh/internal/controllers/gatewayproxy/fetcher"
|
||||||
|
"github.com/hashicorp/consul/internal/mesh/internal/controllers/sidecarproxy/cache"
|
||||||
|
"github.com/hashicorp/consul/internal/resource"
|
||||||
|
pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1"
|
||||||
|
pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1"
|
||||||
|
"github.com/hashicorp/consul/proto-public/pbresource"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ControllerName is the name for this controller. It's used for logging or status keys.
|
||||||
|
const ControllerName = "consul.io/gateway-proxy-controller"
|
||||||
|
|
||||||
|
// Controller is responsible for triggering reconciler for watched resources
|
||||||
|
func Controller(cache *cache.Cache) *controller.Controller {
|
||||||
|
// TODO NET-7016 Use caching functionality in NewController being implemented at time of writing
|
||||||
|
// TODO NET-7017 Add the host of other types we should watch
|
||||||
|
return controller.NewController(ControllerName, pbmesh.ProxyStateTemplateType).
|
||||||
|
WithWatch(pbcatalog.WorkloadType, dependency.ReplaceType(pbmesh.ProxyStateTemplateType)).
|
||||||
|
WithWatch(pbmesh.ComputedProxyConfigurationType, dependency.ReplaceType(pbmesh.ProxyStateTemplateType)).
|
||||||
|
WithReconciler(&reconciler{
|
||||||
|
cache: cache,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// reconciler is responsible for managing the ProxyStateTemplate for all
|
||||||
|
// gateway types: mesh, api (future) and terminating (future).
|
||||||
|
type reconciler struct {
|
||||||
|
cache *cache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconcile is responsible for creating and updating the pbmesh.ProxyStateTemplate
|
||||||
|
// for all gateway types. Since the ProxyStateTemplates managed here will always have
|
||||||
|
// an owner reference pointing to the corresponding pbmesh.MeshGateway, deletion is
|
||||||
|
// left to the garbage collector.
|
||||||
|
func (r *reconciler) Reconcile(ctx context.Context, rt controller.Runtime, req controller.Request) error {
|
||||||
|
rt.Logger = rt.Logger.With("resource-id", req.ID)
|
||||||
|
rt.Logger.Trace("reconciling proxy state template")
|
||||||
|
|
||||||
|
// Instantiate a data fetcher to fetch all reconciliation data.
|
||||||
|
dataFetcher := fetcher.New(rt.Client, r.cache)
|
||||||
|
|
||||||
|
workloadID := resource.ReplaceType(pbcatalog.WorkloadType, req.ID)
|
||||||
|
workload, err := dataFetcher.FetchWorkload(ctx, workloadID)
|
||||||
|
if err != nil {
|
||||||
|
rt.Logger.Error("error reading the associated workload", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if workload == nil {
|
||||||
|
// If workload has been deleted, then return as ProxyStateTemplate should be cleaned up
|
||||||
|
// by the garbage collector because of the owner reference.
|
||||||
|
rt.Logger.Trace("workload doesn't exist; skipping reconciliation", "workload", workloadID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the workload is not for a xGateway, let the sidecarproxy reconciler handle it
|
||||||
|
if gatewayKind := workload.Metadata["gateway-kind"]; gatewayKind == "" {
|
||||||
|
rt.Logger.Trace("workload is not a gateway; skipping reconciliation", "workload", workloadID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO NET-7014 Determine what gateway controls this workload
|
||||||
|
// For now, we cheat by knowing the MeshGateway's name, type + tenancy ahead of time
|
||||||
|
gatewayID := &pbresource.ID{
|
||||||
|
Name: "mesh-gateway",
|
||||||
|
Type: pbmesh.MeshGatewayType,
|
||||||
|
Tenancy: resource.DefaultPartitionedTenancy(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the gateway exists.
|
||||||
|
gateway, err := dataFetcher.FetchMeshGateway(ctx, gatewayID)
|
||||||
|
if err != nil {
|
||||||
|
rt.Logger.Error("error reading the associated gateway", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if gateway == nil {
|
||||||
|
// If gateway has been deleted, then return as ProxyStateTemplate should be
|
||||||
|
// cleaned up by the garbage collector because of the owner reference.
|
||||||
|
rt.Logger.Trace("gateway doesn't exist; skipping reconciliation", "gateway", gatewayID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyStateTemplate, err := dataFetcher.FetchProxyStateTemplate(ctx, req.ID)
|
||||||
|
if err != nil {
|
||||||
|
rt.Logger.Error("error reading proxy state template", "error", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if proxyStateTemplate == nil {
|
||||||
|
req.ID.Uid = ""
|
||||||
|
rt.Logger.Trace("proxy state template for this gateway doesn't yet exist; generating a new one")
|
||||||
|
}
|
||||||
|
|
||||||
|
newPST := builder.NewProxyStateTemplateBuilder(workload).Build()
|
||||||
|
|
||||||
|
proxyTemplateData, err := anypb.New(newPST)
|
||||||
|
if err != nil {
|
||||||
|
rt.Logger.Error("error creating proxy state template data", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rt.Logger.Trace("updating proxy state template")
|
||||||
|
|
||||||
|
// If we're not creating a new PST and the generated one matches the existing one, nothing to do
|
||||||
|
if proxyStateTemplate != nil && proto.Equal(proxyStateTemplate.Data, newPST) {
|
||||||
|
rt.Logger.Trace("no changes to existing proxy state template")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the created/updated ProxyStateTemplate with MeshGateway owner
|
||||||
|
_, err = rt.Client.Write(ctx, &pbresource.WriteRequest{
|
||||||
|
Resource: &pbresource.Resource{
|
||||||
|
Id: req.ID,
|
||||||
|
Metadata: map[string]string{"gateway-kind": workload.Metadata["gateway-kind"]},
|
||||||
|
Owner: workload.Resource.Id,
|
||||||
|
Data: proxyTemplateData,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
rt.Logger.Error("error writing proxy state template", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
|
||||||
|
package fetcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/internal/mesh/internal/controllers/sidecarproxy/cache"
|
||||||
|
"github.com/hashicorp/consul/internal/mesh/internal/types"
|
||||||
|
"github.com/hashicorp/consul/internal/resource"
|
||||||
|
pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1"
|
||||||
|
pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1"
|
||||||
|
"github.com/hashicorp/consul/proto-public/pbresource"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Fetcher struct {
|
||||||
|
client pbresource.ResourceServiceClient
|
||||||
|
cache *cache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(client pbresource.ResourceServiceClient, cache *cache.Cache) *Fetcher {
|
||||||
|
return &Fetcher{
|
||||||
|
client: client,
|
||||||
|
cache: cache,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fetcher) FetchMeshGateway(ctx context.Context, id *pbresource.ID) (*types.DecodedMeshGateway, error) {
|
||||||
|
dec, err := resource.GetDecodedResource[*pbmesh.MeshGateway](ctx, f.client, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if dec == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return dec, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fetcher) FetchProxyStateTemplate(ctx context.Context, id *pbresource.ID) (*types.DecodedProxyStateTemplate, error) {
|
||||||
|
dec, err := resource.GetDecodedResource[*pbmesh.ProxyStateTemplate](ctx, f.client, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if dec == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return dec, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fetcher) FetchWorkload(ctx context.Context, id *pbresource.ID) (*types.DecodedWorkload, error) {
|
||||||
|
dec, err := resource.GetDecodedResource[*pbcatalog.Workload](ctx, f.client, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if dec == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return dec, err
|
||||||
|
}
|
|
@ -5,6 +5,8 @@ package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/internal/mesh/internal/controllers/gatewayproxy"
|
||||||
"github.com/hashicorp/consul/internal/mesh/internal/controllers/meshconfiguration"
|
"github.com/hashicorp/consul/internal/mesh/internal/controllers/meshconfiguration"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/leafcert"
|
"github.com/hashicorp/consul/agent/leafcert"
|
||||||
|
@ -46,6 +48,8 @@ func Register(mgr *controller.Manager, deps Dependencies) {
|
||||||
sidecarproxy.Controller(cache.New(), deps.TrustDomainFetcher, deps.LocalDatacenter, deps.DefaultAllow),
|
sidecarproxy.Controller(cache.New(), deps.TrustDomainFetcher, deps.LocalDatacenter, deps.DefaultAllow),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
mgr.Register(gatewayproxy.Controller(cache.New()))
|
||||||
|
|
||||||
mgr.Register(routes.Controller())
|
mgr.Register(routes.Controller())
|
||||||
|
|
||||||
mgr.Register(proxyconfiguration.Controller(workloadselectionmapper.New[*pbmesh.ProxyConfiguration](pbmesh.ComputedProxyConfigurationType)))
|
mgr.Register(proxyconfiguration.Controller(workloadselectionmapper.New[*pbmesh.ProxyConfiguration](pbmesh.ComputedProxyConfigurationType)))
|
||||||
|
|
|
@ -27,4 +27,5 @@ type (
|
||||||
DecodedDestinations = resource.DecodedResource[*pbmesh.Destinations]
|
DecodedDestinations = resource.DecodedResource[*pbmesh.Destinations]
|
||||||
DecodedComputedDestinations = resource.DecodedResource[*pbmesh.ComputedExplicitDestinations]
|
DecodedComputedDestinations = resource.DecodedResource[*pbmesh.ComputedExplicitDestinations]
|
||||||
DecodedProxyStateTemplate = resource.DecodedResource[*pbmesh.ProxyStateTemplate]
|
DecodedProxyStateTemplate = resource.DecodedResource[*pbmesh.ProxyStateTemplate]
|
||||||
|
DecodedMeshGateway = resource.DecodedResource[*pbmesh.MeshGateway]
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue