mirror of
https://github.com/status-im/consul.git
synced 2025-02-16 15:47:21 +00:00
Net 7074/decentralized exported services management (#20318)
* Add decentralized management of V1 exported-services config entries using V2 multicluster resources. * cleanup --------- Co-authored-by: Matt Keeler <mjkeeler7@gmail.com>
This commit is contained in:
parent
a392242d23
commit
44bcda8523
@ -36,6 +36,7 @@ import (
|
|||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
"github.com/hashicorp/consul-net-rpc/net/rpc"
|
"github.com/hashicorp/consul-net-rpc/net/rpc"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/agent/blockingquery"
|
"github.com/hashicorp/consul/agent/blockingquery"
|
||||||
"github.com/hashicorp/consul/agent/connect"
|
"github.com/hashicorp/consul/agent/connect"
|
||||||
@ -1005,7 +1006,7 @@ func (s *Server) registerControllers(deps Deps, proxyUpdater ProxyUpdater) error
|
|||||||
|
|
||||||
if s.useV2Resources {
|
if s.useV2Resources {
|
||||||
catalog.RegisterControllers(s.controllerManager)
|
catalog.RegisterControllers(s.controllerManager)
|
||||||
multicluster.RegisterControllers(s.controllerManager, multicluster.DefaultControllerDependencies())
|
multicluster.RegisterControllers(s.controllerManager, multicluster.DefaultControllerDependencies(&V1ServiceExportsShim{s: s}))
|
||||||
defaultAllow, err := s.config.ACLResolverSettings.IsDefaultAllow()
|
defaultAllow, err := s.config.ACLResolverSettings.IsDefaultAllow()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -1043,6 +1044,8 @@ func (s *Server) registerControllers(deps Deps, proxyUpdater ProxyUpdater) error
|
|||||||
auth.RegisterControllers(s.controllerManager, auth.DefaultControllerDependencies())
|
auth.RegisterControllers(s.controllerManager, auth.DefaultControllerDependencies())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
multicluster.RegisterControllers(s.controllerManager, multicluster.DefaultControllerDependencies(&V1ServiceExportsShim{s: s}))
|
||||||
|
|
||||||
reaper.RegisterControllers(s.controllerManager)
|
reaper.RegisterControllers(s.controllerManager)
|
||||||
|
|
||||||
if s.config.DevMode {
|
if s.config.DevMode {
|
||||||
|
69
agent/consul/v2_config_entry_exports_shim.go
Normal file
69
agent/consul/v2_config_entry_exports_shim.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
|
||||||
|
package consul
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/acl"
|
||||||
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type V1ServiceExportsShim struct {
|
||||||
|
s *Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *V1ServiceExportsShim) GetExportedServicesConfigEntry(_ context.Context, name string, entMeta *acl.EnterpriseMeta) (*structs.ExportedServicesConfigEntry, error) {
|
||||||
|
_, entry, err := s.s.fsm.State().ConfigEntry(nil, structs.ExportedServices, name, entMeta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
exp, ok := entry.(*structs.ExportedServicesConfigEntry)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("exported services config entry is the wrong type: expected ExportedServicesConfigEntry, actual: %T", entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return exp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *V1ServiceExportsShim) WriteExportedServicesConfigEntry(_ context.Context, cfg *structs.ExportedServicesConfigEntry) error {
|
||||||
|
if err := cfg.Normalize(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cfg.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &structs.ConfigEntryRequest{
|
||||||
|
Op: structs.ConfigEntryUpsert,
|
||||||
|
Entry: cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := s.s.raftApply(structs.ConfigEntryRequestType, req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *V1ServiceExportsShim) DeleteExportedServicesConfigEntry(_ context.Context, name string, entMeta *acl.EnterpriseMeta) error {
|
||||||
|
req := &structs.ConfigEntryRequest{
|
||||||
|
Op: structs.ConfigEntryDelete,
|
||||||
|
Entry: &structs.ExportedServicesConfigEntry{
|
||||||
|
Name: name,
|
||||||
|
EnterpriseMeta: *entMeta,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := req.Entry.Normalize(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := s.s.raftApply(structs.ConfigEntryRequestType, req)
|
||||||
|
return err
|
||||||
|
}
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/hashicorp/consul/internal/controller"
|
"github.com/hashicorp/consul/internal/controller"
|
||||||
"github.com/hashicorp/consul/internal/multicluster/internal/controllers"
|
"github.com/hashicorp/consul/internal/multicluster/internal/controllers"
|
||||||
exportedServicesSamenessGroupExpander "github.com/hashicorp/consul/internal/multicluster/internal/controllers/exportedservices/expander"
|
exportedServicesSamenessGroupExpander "github.com/hashicorp/consul/internal/multicluster/internal/controllers/exportedservices/expander"
|
||||||
|
"github.com/hashicorp/consul/internal/multicluster/internal/controllers/v1compat"
|
||||||
"github.com/hashicorp/consul/internal/multicluster/internal/types"
|
"github.com/hashicorp/consul/internal/multicluster/internal/types"
|
||||||
"github.com/hashicorp/consul/internal/resource"
|
"github.com/hashicorp/consul/internal/resource"
|
||||||
)
|
)
|
||||||
@ -26,9 +27,10 @@ func RegisterTypes(r resource.Registry) {
|
|||||||
|
|
||||||
type ControllerDependencies = controllers.Dependencies
|
type ControllerDependencies = controllers.Dependencies
|
||||||
|
|
||||||
func DefaultControllerDependencies() ControllerDependencies {
|
func DefaultControllerDependencies(ac v1compat.AggregatedConfig) ControllerDependencies {
|
||||||
return ControllerDependencies{
|
return ControllerDependencies{
|
||||||
ExportedServicesSamenessGroupsExpander: exportedServicesSamenessGroupExpander.New(),
|
ExportedServicesSamenessGroupsExpander: exportedServicesSamenessGroupExpander.New(),
|
||||||
|
ConfigEntryExports: ac,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,12 +6,15 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"github.com/hashicorp/consul/internal/controller"
|
"github.com/hashicorp/consul/internal/controller"
|
||||||
"github.com/hashicorp/consul/internal/multicluster/internal/controllers/exportedservices"
|
"github.com/hashicorp/consul/internal/multicluster/internal/controllers/exportedservices"
|
||||||
|
"github.com/hashicorp/consul/internal/multicluster/internal/controllers/v1compat"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Dependencies struct {
|
type Dependencies struct {
|
||||||
ExportedServicesSamenessGroupsExpander exportedservices.ExportedServicesSamenessGroupExpander
|
ExportedServicesSamenessGroupsExpander exportedservices.ExportedServicesSamenessGroupExpander
|
||||||
|
ConfigEntryExports v1compat.AggregatedConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func Register(mgr *controller.Manager, deps Dependencies) {
|
func Register(mgr *controller.Manager, deps Dependencies) {
|
||||||
mgr.Register(exportedservices.Controller(deps.ExportedServicesSamenessGroupsExpander))
|
mgr.Register(exportedservices.Controller(deps.ExportedServicesSamenessGroupsExpander))
|
||||||
|
mgr.Register(v1compat.Controller(deps.ConfigEntryExports))
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,370 @@
|
|||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
|
||||||
|
package v1compat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/acl"
|
||||||
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
"github.com/hashicorp/consul/internal/controller"
|
||||||
|
"github.com/hashicorp/consul/internal/multicluster/internal/types"
|
||||||
|
"github.com/hashicorp/consul/internal/resource"
|
||||||
|
pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1"
|
||||||
|
pbmulticluster "github.com/hashicorp/consul/proto-public/pbmulticluster/v2beta1"
|
||||||
|
"github.com/hashicorp/consul/proto-public/pbresource"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ControllerName = "consul.io/exported-services-v1-compat"
|
||||||
|
controllerMetaKey = "managed-by-controller"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AggregatedConfig interface {
|
||||||
|
GetExportedServicesConfigEntry(context.Context, string, *acl.EnterpriseMeta) (*structs.ExportedServicesConfigEntry, error)
|
||||||
|
WriteExportedServicesConfigEntry(context.Context, *structs.ExportedServicesConfigEntry) error
|
||||||
|
DeleteExportedServicesConfigEntry(context.Context, string, *acl.EnterpriseMeta) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapExportedServices(_ context.Context, _ controller.Runtime, res *pbresource.Resource) ([]controller.Request, error) {
|
||||||
|
return []controller.Request{
|
||||||
|
{
|
||||||
|
ID: &pbresource.ID{
|
||||||
|
Type: pbmulticluster.ComputedExportedServicesType,
|
||||||
|
Tenancy: &pbresource.Tenancy{
|
||||||
|
Partition: res.Id.Tenancy.Partition,
|
||||||
|
},
|
||||||
|
Name: types.ComputedExportedServicesName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Controller(config AggregatedConfig) *controller.Controller {
|
||||||
|
return controller.NewController(ControllerName, pbmulticluster.ComputedExportedServicesType).
|
||||||
|
WithWatch(pbmulticluster.PartitionExportedServicesType, mapExportedServices).
|
||||||
|
WithWatch(pbmulticluster.NamespaceExportedServicesType, mapExportedServices).
|
||||||
|
WithWatch(pbmulticluster.ExportedServicesType, mapExportedServices).
|
||||||
|
// TODO Add custom watch for exported-services for config entry events to attempt re-reconciliation when that changes
|
||||||
|
WithReconciler(&reconciler{config: config})
|
||||||
|
}
|
||||||
|
|
||||||
|
type reconciler struct {
|
||||||
|
config AggregatedConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconcile will reconcile one ComputedExportedServices in response to some event.
|
||||||
|
func (r *reconciler) Reconcile(ctx context.Context, rt controller.Runtime, req controller.Request) error {
|
||||||
|
rt.Logger = rt.Logger.With("resource-id", req.ID, "controller", ControllerName)
|
||||||
|
|
||||||
|
entMeta := acl.DefaultEnterpriseMeta()
|
||||||
|
entMeta.OverridePartition(req.ID.Tenancy.Partition)
|
||||||
|
existing, err := r.config.GetExportedServicesConfigEntry(ctx, req.ID.Tenancy.Partition, entMeta)
|
||||||
|
if err != nil {
|
||||||
|
rt.Logger.Error("error getting exported service config entry", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existing != nil && existing.Meta["managed-by-controller"] != ControllerName {
|
||||||
|
// in order to not cause an outage we need to ensure that the user has replicated all
|
||||||
|
// of their exports to v2 resources first and then setting the meta key on the existing
|
||||||
|
// config entry to allow this controller to overwrite what the user previously had.
|
||||||
|
rt.Logger.Info("existing exported-services config entry is not managed with v2 resources. Add a metadata value of %q with a value of %q to opt-in to controller management of that config entry", controllerMetaKey, ControllerName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
newCfg := &structs.ExportedServicesConfigEntry{
|
||||||
|
// v1 exported-services config entries must have a Name that is the partitions name
|
||||||
|
Name: req.ID.Tenancy.Partition,
|
||||||
|
Meta: map[string]string{
|
||||||
|
controllerMetaKey: ControllerName,
|
||||||
|
},
|
||||||
|
EnterpriseMeta: *entMeta,
|
||||||
|
}
|
||||||
|
|
||||||
|
partitionExports, err := resource.ListDecodedResource[*pbmulticluster.PartitionExportedServices](ctx, rt.Client, &pbresource.ListRequest{
|
||||||
|
Type: pbmulticluster.PartitionExportedServicesType,
|
||||||
|
Tenancy: req.ID.Tenancy,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
rt.Logger.Error("error retrieving partition exported services", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
namespaceExports, err := resource.ListDecodedResource[*pbmulticluster.NamespaceExportedServices](ctx, rt.Client, &pbresource.ListRequest{
|
||||||
|
Type: pbmulticluster.NamespaceExportedServicesType,
|
||||||
|
Tenancy: req.ID.Tenancy,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
rt.Logger.Error("error retrieving namespace exported service", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceExports, err := resource.ListDecodedResource[*pbmulticluster.ExportedServices](ctx, rt.Client, &pbresource.ListRequest{
|
||||||
|
Type: pbmulticluster.ExportedServicesType,
|
||||||
|
Tenancy: req.ID.Tenancy,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
rt.Logger.Error("error retrieving exported services", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(partitionExports) == 0 && len(namespaceExports) == 0 && len(serviceExports) == 0 {
|
||||||
|
if existing == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.config.DeleteExportedServicesConfigEntry(ctx, req.ID.Tenancy.Partition, entMeta); err != nil {
|
||||||
|
rt.Logger.Error("error deleting exported services config entry", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
exports := newExportTracker()
|
||||||
|
|
||||||
|
for _, partitionExport := range partitionExports {
|
||||||
|
exports.trackPartitionConsumer(partitionExport.Data.Consumers)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, namespaceExport := range namespaceExports {
|
||||||
|
exports.trackNamespaceConsumers(namespaceExport.Id.Tenancy.Namespace, namespaceExport.Data.Consumers)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, serviceExport := range serviceExports {
|
||||||
|
for _, svc := range serviceExport.Data.Services {
|
||||||
|
svcId := &pbresource.ID{
|
||||||
|
Type: pbcatalog.ServiceType,
|
||||||
|
Tenancy: serviceExport.Id.Tenancy,
|
||||||
|
Name: svc,
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.trackExportedServices(svcId, serviceExport.Data.Consumers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newCfg.Services = exports.allExports()
|
||||||
|
|
||||||
|
if existing != nil && configEntryEquivalent(existing, newCfg) {
|
||||||
|
rt.Logger.Trace("managed exported-services config entry is already up to date")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.config.WriteExportedServicesConfigEntry(ctx, newCfg); err != nil {
|
||||||
|
rt.Logger.Error("error writing exported services config entry", "error", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rt.Logger.Debug("Updated exported services config entry")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type exportTracker struct {
|
||||||
|
partitions *exportConsumers
|
||||||
|
namespaces map[string]*exportConsumers
|
||||||
|
services map[resource.ReferenceKey]*exportConsumers
|
||||||
|
}
|
||||||
|
|
||||||
|
type exportConsumers struct {
|
||||||
|
partitions map[string]struct{}
|
||||||
|
peers map[string]struct{}
|
||||||
|
samenessGroups map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExportConsumers() *exportConsumers {
|
||||||
|
return &exportConsumers{
|
||||||
|
partitions: make(map[string]struct{}),
|
||||||
|
peers: make(map[string]struct{}),
|
||||||
|
samenessGroups: make(map[string]struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *exportConsumers) addConsumers(consumers []*pbmulticluster.ExportedServicesConsumer) {
|
||||||
|
for _, consumer := range consumers {
|
||||||
|
switch v := consumer.ConsumerTenancy.(type) {
|
||||||
|
case *pbmulticluster.ExportedServicesConsumer_Peer:
|
||||||
|
c.peers[v.Peer] = struct{}{}
|
||||||
|
case *pbmulticluster.ExportedServicesConsumer_Partition:
|
||||||
|
c.partitions[v.Partition] = struct{}{}
|
||||||
|
case *pbmulticluster.ExportedServicesConsumer_SamenessGroup:
|
||||||
|
c.samenessGroups[v.SamenessGroup] = struct{}{}
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("Unknown exported service consumer type: %T", v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *exportConsumers) configEntryConsumers() []structs.ServiceConsumer {
|
||||||
|
consumers := make([]structs.ServiceConsumer, 0, len(c.partitions)+len(c.peers)+len(c.samenessGroups))
|
||||||
|
|
||||||
|
partitions := keys(c.partitions)
|
||||||
|
sort.Strings(partitions)
|
||||||
|
for _, consumer := range partitions {
|
||||||
|
consumers = append(consumers, structs.ServiceConsumer{
|
||||||
|
Partition: consumer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
peers := keys(c.peers)
|
||||||
|
sort.Strings(peers)
|
||||||
|
for _, consumer := range peers {
|
||||||
|
consumers = append(consumers, structs.ServiceConsumer{
|
||||||
|
Partition: consumer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
samenessGroups := keys(c.samenessGroups)
|
||||||
|
sort.Strings(samenessGroups)
|
||||||
|
for _, consumer := range samenessGroups {
|
||||||
|
consumers = append(consumers, structs.ServiceConsumer{
|
||||||
|
Partition: consumer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return consumers
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExportTracker() *exportTracker {
|
||||||
|
return &exportTracker{
|
||||||
|
partitions: newExportConsumers(),
|
||||||
|
namespaces: make(map[string]*exportConsumers),
|
||||||
|
services: make(map[resource.ReferenceKey]*exportConsumers),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *exportTracker) trackPartitionConsumer(consumers []*pbmulticluster.ExportedServicesConsumer) {
|
||||||
|
t.partitions.addConsumers(consumers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *exportTracker) trackNamespaceConsumers(namespace string, consumers []*pbmulticluster.ExportedServicesConsumer) {
|
||||||
|
c, ok := t.namespaces[namespace]
|
||||||
|
if !ok {
|
||||||
|
c = newExportConsumers()
|
||||||
|
t.namespaces[namespace] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
c.addConsumers(consumers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *exportTracker) trackExportedServices(svcID *pbresource.ID, consumers []*pbmulticluster.ExportedServicesConsumer) {
|
||||||
|
key := resource.NewReferenceKey(svcID)
|
||||||
|
|
||||||
|
c, ok := t.services[key]
|
||||||
|
if !ok {
|
||||||
|
c = newExportConsumers()
|
||||||
|
t.services[key] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
c.addConsumers(consumers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *exportTracker) allExports() []structs.ExportedService {
|
||||||
|
var exports []structs.ExportedService
|
||||||
|
|
||||||
|
partitionConsumers := t.partitions.configEntryConsumers()
|
||||||
|
if len(partitionConsumers) > 0 {
|
||||||
|
exports = append(exports, structs.ExportedService{
|
||||||
|
Name: "*",
|
||||||
|
Namespace: "*",
|
||||||
|
Consumers: partitionConsumers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
namespaces := keys(t.namespaces)
|
||||||
|
sort.Strings(namespaces)
|
||||||
|
for _, ns := range namespaces {
|
||||||
|
exports = append(exports, structs.ExportedService{
|
||||||
|
Name: "*",
|
||||||
|
Namespace: ns,
|
||||||
|
Consumers: t.namespaces[ns].configEntryConsumers(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
services := keys(t.services)
|
||||||
|
sort.Slice(services, func(i, j int) bool {
|
||||||
|
// the partitions must already be equal because we are only
|
||||||
|
// looking at resource exports for a single partition.
|
||||||
|
|
||||||
|
if services[i].Namespace < services[j].Namespace {
|
||||||
|
return false
|
||||||
|
} else if services[i].Namespace > services[j].Namespace {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if services[i].Name < services[j].Name {
|
||||||
|
return false
|
||||||
|
} else if services[i].Name > services[j].Name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
for _, svcKey := range services {
|
||||||
|
exports = append(exports, structs.ExportedService{
|
||||||
|
Name: svcKey.Name,
|
||||||
|
Namespace: svcKey.Namespace,
|
||||||
|
Consumers: t.services[svcKey].configEntryConsumers(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return exports
|
||||||
|
}
|
||||||
|
|
||||||
|
func configEntryEquivalent(a, b *structs.ExportedServicesConfigEntry) bool {
|
||||||
|
if a.Name != b.Name {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(a.Services) != len(b.Services) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(a.Services); i++ {
|
||||||
|
svcA := a.Services[i]
|
||||||
|
svcB := b.Services[i]
|
||||||
|
|
||||||
|
if svcA.Name != svcB.Name {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if svcA.Namespace != svcB.Namespace {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(svcA.Consumers) != len(svcB.Consumers) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := 0; j < len(svcA.Consumers); j++ {
|
||||||
|
consumerA := svcA.Consumers[j]
|
||||||
|
consumerB := svcB.Consumers[j]
|
||||||
|
|
||||||
|
if consumerA.Partition != consumerB.Partition {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if consumerA.Peer != consumerB.Peer {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if consumerA.SamenessGroup != consumerB.SamenessGroup {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func keys[K comparable, V any](m map[K]V) []K {
|
||||||
|
keys := make([]K, 0, len(m))
|
||||||
|
for key := range m {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user