From f2902e66088b783c2a0814de2f5411176d293eba Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Mon, 13 Mar 2023 16:19:11 -0500 Subject: [PATCH] Add sameness-group configuration entry. (#16608) This commit adds a sameness-group config entry to the API and structs packages. It includes some validation logic and a new memdb index that tracks the default sameness-group for each partition. Sameness groups will simplify the effort of managing failovers / intentions / exports for peers and partitions. Note that this change purely to introduce the configuration entry and does not include the full functionality of sameness-groups. --- agent/consul/state/config_entry.go | 6 ++ .../state/config_entry_sameness_group_oss.go | 29 ++++++++ .../config_entry_sameness_group_oss_test.go | 18 +++++ agent/consul/state/config_entry_schema.go | 14 +++- .../usagemetrics/usagemetrics_oss_test.go | 32 +++++++++ agent/structs/config_entry.go | 4 ++ agent/structs/config_entry_sameness_group.go | 72 +++++++++++++++++++ .../config_entry_sameness_group_oss.go | 10 +++ api/config_entry.go | 3 + api/config_entry_samness_group.go | 25 +++++++ 10 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 agent/consul/state/config_entry_sameness_group_oss.go create mode 100644 agent/consul/state/config_entry_sameness_group_oss_test.go create mode 100644 agent/structs/config_entry_sameness_group.go create mode 100644 agent/structs/config_entry_sameness_group_oss.go create mode 100644 api/config_entry_samness_group.go diff --git a/agent/consul/state/config_entry.go b/agent/consul/state/config_entry.go index 3e4964c5a5..b37098aaf8 100644 --- a/agent/consul/state/config_entry.go +++ b/agent/consul/state/config_entry.go @@ -494,6 +494,11 @@ func insertConfigEntryWithTxn(tx WriteTxn, idx uint64, conf structs.ConfigEntry) return fmt.Errorf("failed to persist service name: %v", err) } } + case structs.SamenessGroup: + err := checkSamenessGroup(tx, conf) + if err != nil { + return err + } } // Insert the config entry and update the index @@ -539,6 +544,7 @@ func validateProposedConfigEntryInGraph( if err != nil { return err } + case structs.SamenessGroup: case structs.ServiceIntentions: case structs.MeshConfig: case structs.ExportedServices: diff --git a/agent/consul/state/config_entry_sameness_group_oss.go b/agent/consul/state/config_entry_sameness_group_oss.go new file mode 100644 index 0000000000..d217061fc9 --- /dev/null +++ b/agent/consul/state/config_entry_sameness_group_oss.go @@ -0,0 +1,29 @@ +//go:build !consulent +// +build !consulent + +package state + +import ( + "fmt" + + "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/go-memdb" +) + +// SamnessGroupDefaultIndex is a placeholder for OSS. Sameness-groups are enterprise only. +type SamenessGroupDefaultIndex struct{} + +var _ memdb.Indexer = (*SamenessGroupDefaultIndex)(nil) +var _ memdb.MultiIndexer = (*SamenessGroupDefaultIndex)(nil) + +func (*SamenessGroupDefaultIndex) FromObject(obj interface{}) (bool, [][]byte, error) { + return false, nil, nil +} + +func (*SamenessGroupDefaultIndex) FromArgs(args ...interface{}) ([]byte, error) { + return nil, nil +} + +func checkSamenessGroup(tx ReadTxn, newConfig structs.ConfigEntry) error { + return fmt.Errorf("sameness-groups are an enterprise-only feature") +} diff --git a/agent/consul/state/config_entry_sameness_group_oss_test.go b/agent/consul/state/config_entry_sameness_group_oss_test.go new file mode 100644 index 0000000000..c6f2471918 --- /dev/null +++ b/agent/consul/state/config_entry_sameness_group_oss_test.go @@ -0,0 +1,18 @@ +//go:build !consulent +// +build !consulent + +package state + +import ( + "github.com/hashicorp/consul/agent/structs" + "github.com/stretchr/testify/require" + "testing" +) + +func TestStore_SamenessGroup_checkSamenessGroup(t *testing.T) { + s := testStateStore(t) + err := s.EnsureConfigEntry(0, &structs.SamenessGroupConfigEntry{ + Name: "sg1", + }) + require.ErrorContains(t, err, "sameness-groups are an enterprise-only feature") +} diff --git a/agent/consul/state/config_entry_schema.go b/agent/consul/state/config_entry_schema.go index e99068d104..7ef560eb1f 100644 --- a/agent/consul/state/config_entry_schema.go +++ b/agent/consul/state/config_entry_schema.go @@ -11,9 +11,10 @@ import ( const ( tableConfigEntries = "config-entries" - indexLink = "link" - indexIntentionLegacyID = "intention-legacy-id" - indexSource = "intention-source" + indexLink = "link" + indexIntentionLegacyID = "intention-legacy-id" + indexSource = "intention-source" + indexSamenessGroupDefault = "sameness-group-default" ) // configTableSchema returns a new table schema used to store global @@ -50,6 +51,12 @@ func configTableSchema() *memdb.TableSchema { Unique: false, Indexer: &ServiceIntentionSourceIndex{}, }, + indexSamenessGroupDefault: { + Name: indexSamenessGroupDefault, + AllowMissing: true, + Unique: true, + Indexer: &SamenessGroupDefaultIndex{}, + }, }, } } @@ -67,6 +74,7 @@ type configEntryIndexable interface { } var _ configEntryIndexable = (*structs.ExportedServicesConfigEntry)(nil) +var _ configEntryIndexable = (*structs.SamenessGroupConfigEntry)(nil) var _ configEntryIndexable = (*structs.IngressGatewayConfigEntry)(nil) var _ configEntryIndexable = (*structs.MeshConfigEntry)(nil) var _ configEntryIndexable = (*structs.ProxyConfigEntry)(nil) diff --git a/agent/consul/usagemetrics/usagemetrics_oss_test.go b/agent/consul/usagemetrics/usagemetrics_oss_test.go index 3789b6660c..44cd1f2936 100644 --- a/agent/consul/usagemetrics/usagemetrics_oss_test.go +++ b/agent/consul/usagemetrics/usagemetrics_oss_test.go @@ -357,6 +357,22 @@ var baseCases = map[string]testCase{ {Name: "kind", Value: "exported-services"}, }, }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=sameness-group": { // Legacy + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "sameness-group"}, + }, + }, + "consul.usage.test.state.config_entries;datacenter=dc1;kind=sameness-group": { + Name: "consul.usage.test.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "sameness-group"}, + }, + }, "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=api-gateway": { // Legacy Name: "consul.usage.test.consul.state.config_entries", Value: 0, @@ -784,6 +800,22 @@ var baseCases = map[string]testCase{ {Name: "kind", Value: "exported-services"}, }, }, + "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=sameness-group": { // Legacy + Name: "consul.usage.test.consul.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "sameness-group"}, + }, + }, + "consul.usage.test.state.config_entries;datacenter=dc1;kind=sameness-group": { + Name: "consul.usage.test.state.config_entries", + Value: 0, + Labels: []metrics.Label{ + {Name: "datacenter", Value: "dc1"}, + {Name: "kind", Value: "sameness-group"}, + }, + }, "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=api-gateway": { // Legacy Name: "consul.usage.test.consul.state.config_entries", Value: 0, diff --git a/agent/structs/config_entry.go b/agent/structs/config_entry.go index 7d8bf1d62f..f330babf70 100644 --- a/agent/structs/config_entry.go +++ b/agent/structs/config_entry.go @@ -34,6 +34,7 @@ const ( ServiceIntentions string = "service-intentions" MeshConfig string = "mesh" ExportedServices string = "exported-services" + SamenessGroup string = "sameness-group" APIGateway string = "api-gateway" BoundAPIGateway string = "bound-api-gateway" InlineCertificate string = "inline-certificate" @@ -59,6 +60,7 @@ var AllConfigEntryKinds = []string{ ServiceIntentions, MeshConfig, ExportedServices, + SamenessGroup, APIGateway, BoundAPIGateway, HTTPRoute, @@ -672,6 +674,8 @@ func MakeConfigEntry(kind, name string) (ConfigEntry, error) { return &MeshConfigEntry{}, nil case ExportedServices: return &ExportedServicesConfigEntry{Name: name}, nil + case SamenessGroup: + return &SamenessGroupConfigEntry{Name: name}, nil case APIGateway: return &APIGatewayConfigEntry{Name: name}, nil case BoundAPIGateway: diff --git a/agent/structs/config_entry_sameness_group.go b/agent/structs/config_entry_sameness_group.go new file mode 100644 index 0000000000..c4d4e3f8a4 --- /dev/null +++ b/agent/structs/config_entry_sameness_group.go @@ -0,0 +1,72 @@ +package structs + +import ( + "encoding/json" + "fmt" + + "github.com/hashicorp/consul/acl" +) + +type SamenessGroupConfigEntry struct { + Name string + IsDefault bool `json:",omitempty" alias:"is_default"` + Members []SamenessGroupMember + Meta map[string]string `json:",omitempty"` + acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"` + RaftIndex +} + +func (s *SamenessGroupConfigEntry) GetKind() string { return SamenessGroup } +func (s *SamenessGroupConfigEntry) GetName() string { return s.Name } +func (s *SamenessGroupConfigEntry) GetMeta() map[string]string { return s.Meta } +func (s *SamenessGroupConfigEntry) GetCreateIndex() uint64 { return s.CreateIndex } +func (s *SamenessGroupConfigEntry) GetModifyIndex() uint64 { return s.ModifyIndex } + +func (s *SamenessGroupConfigEntry) GetRaftIndex() *RaftIndex { + if s == nil { + return &RaftIndex{} + } + return &s.RaftIndex +} + +func (s *SamenessGroupConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta { + if s == nil { + return nil + } + return &s.EnterpriseMeta +} + +func (s *SamenessGroupConfigEntry) Normalize() error { + if s == nil { + return fmt.Errorf("config entry is nil") + } + s.EnterpriseMeta.Normalize() + return nil +} + +func (s *SamenessGroupConfigEntry) CanRead(authz acl.Authorizer) error { + return nil +} + +func (s *SamenessGroupConfigEntry) CanWrite(authz acl.Authorizer) error { + var authzContext acl.AuthorizerContext + s.FillAuthzContext(&authzContext) + return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext) +} + +func (s *SamenessGroupConfigEntry) MarshalJSON() ([]byte, error) { + type Alias SamenessGroupConfigEntry + source := &struct { + Kind string + *Alias + }{ + Kind: SamenessGroup, + Alias: (*Alias)(s), + } + return json.Marshal(source) +} + +type SamenessGroupMember struct { + Partition string + Peer string +} diff --git a/agent/structs/config_entry_sameness_group_oss.go b/agent/structs/config_entry_sameness_group_oss.go new file mode 100644 index 0000000000..21a34b5e1e --- /dev/null +++ b/agent/structs/config_entry_sameness_group_oss.go @@ -0,0 +1,10 @@ +//go:build !consulent +// +build !consulent + +package structs + +import "fmt" + +func (s *SamenessGroupConfigEntry) Validate() error { + return fmt.Errorf("sameness-groups are an enterprise-only feature") +} diff --git a/api/config_entry.go b/api/config_entry.go index 39b7727c89..3a5b7bb36b 100644 --- a/api/config_entry.go +++ b/api/config_entry.go @@ -23,6 +23,7 @@ const ( ServiceIntentions string = "service-intentions" MeshConfig string = "mesh" ExportedServices string = "exported-services" + SamenessGroup string = "sameness-group" ProxyConfigGlobal string = "global" MeshConfigMesh string = "mesh" @@ -355,6 +356,8 @@ func makeConfigEntry(kind, name string) (ConfigEntry, error) { return &MeshConfigEntry{}, nil case ExportedServices: return &ExportedServicesConfigEntry{Name: name}, nil + case SamenessGroup: + return &SamenessGroupConfigEntry{Kind: kind, Name: name}, nil case APIGateway: return &APIGatewayConfigEntry{Kind: kind, Name: name}, nil case TCPRoute: diff --git a/api/config_entry_samness_group.go b/api/config_entry_samness_group.go new file mode 100644 index 0000000000..d910a36686 --- /dev/null +++ b/api/config_entry_samness_group.go @@ -0,0 +1,25 @@ +package api + +type SamenessGroupConfigEntry struct { + Kind string + Name string + Partition string `json:",omitempty"` + IsDefault bool `json:",omitempty" alias:"is_default"` + Members []SamenessGroupMember + Meta map[string]string `json:",omitempty"` + CreateIndex uint64 + ModifyIndex uint64 +} + +type SamenessGroupMember struct { + Partition string + Peer string +} + +func (s *SamenessGroupConfigEntry) GetKind() string { return s.Kind } +func (s *SamenessGroupConfigEntry) GetName() string { return s.Name } +func (s *SamenessGroupConfigEntry) GetPartition() string { return s.Partition } +func (s *SamenessGroupConfigEntry) GetNamespace() string { return "" } +func (s *SamenessGroupConfigEntry) GetCreateIndex() uint64 { return s.CreateIndex } +func (s *SamenessGroupConfigEntry) GetModifyIndex() uint64 { return s.ModifyIndex } +func (s *SamenessGroupConfigEntry) GetMeta() map[string]string { return s.Meta }