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/proxystatetemplate --> auth/v2beta1/computedtrafficpermissions
|
||||
mesh/v2beta1/proxystatetemplate --> catalog/v2beta1/service
|
||||
mesh/v2beta1/proxystatetemplate --> catalog/v2beta1/serviceendpoints
|
||||
mesh/v2beta1/proxystatetemplate --> catalog/v2beta1/workload
|
||||
mesh/v2beta1/proxystatetemplate --> mesh/v2beta1/computedexplicitdestinations
|
||||
mesh/v2beta1/proxystatetemplate --> mesh/v2beta1/computedproxyconfiguration
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/hashicorp/consul/internal/resource"
|
||||
"github.com/hashicorp/consul/proto-public/pbresource"
|
||||
|
@ -66,12 +67,23 @@ func (m *Manager) CalculateDependencies(registrations []resource.Registration) D
|
|||
}
|
||||
|
||||
for _, c := range m.controllers {
|
||||
watches := make([]string, 0, len(c.watches))
|
||||
for _, w := range c.watches {
|
||||
watches = append(watches, typeToString(w.watchedType))
|
||||
watches := map[string]struct{}{}
|
||||
|
||||
// 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
|
||||
|
|
|
@ -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 (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/consul/internal/mesh/internal/controllers/gatewayproxy"
|
||||
"github.com/hashicorp/consul/internal/mesh/internal/controllers/meshconfiguration"
|
||||
|
||||
"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),
|
||||
)
|
||||
|
||||
mgr.Register(gatewayproxy.Controller(cache.New()))
|
||||
|
||||
mgr.Register(routes.Controller())
|
||||
|
||||
mgr.Register(proxyconfiguration.Controller(workloadselectionmapper.New[*pbmesh.ProxyConfiguration](pbmesh.ComputedProxyConfigurationType)))
|
||||
|
|
|
@ -27,4 +27,5 @@ type (
|
|||
DecodedDestinations = resource.DecodedResource[*pbmesh.Destinations]
|
||||
DecodedComputedDestinations = resource.DecodedResource[*pbmesh.ComputedExplicitDestinations]
|
||||
DecodedProxyStateTemplate = resource.DecodedResource[*pbmesh.ProxyStateTemplate]
|
||||
DecodedMeshGateway = resource.DecodedResource[*pbmesh.MeshGateway]
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue