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.
This commit is contained in:
Derek Menteer 2023-03-13 16:19:11 -05:00 committed by GitHub
parent 9872eeaffe
commit 5d17b2c90b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 210 additions and 3 deletions

View File

@ -494,6 +494,11 @@ func insertConfigEntryWithTxn(tx WriteTxn, idx uint64, conf structs.ConfigEntry)
return fmt.Errorf("failed to persist service name: %v", err) 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 // Insert the config entry and update the index
@ -539,6 +544,7 @@ func validateProposedConfigEntryInGraph(
if err != nil { if err != nil {
return err return err
} }
case structs.SamenessGroup:
case structs.ServiceIntentions: case structs.ServiceIntentions:
case structs.MeshConfig: case structs.MeshConfig:
case structs.ExportedServices: case structs.ExportedServices:

View File

@ -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")
}

View File

@ -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")
}

View File

@ -11,9 +11,10 @@ import (
const ( const (
tableConfigEntries = "config-entries" tableConfigEntries = "config-entries"
indexLink = "link" indexLink = "link"
indexIntentionLegacyID = "intention-legacy-id" indexIntentionLegacyID = "intention-legacy-id"
indexSource = "intention-source" indexSource = "intention-source"
indexSamenessGroupDefault = "sameness-group-default"
) )
// configTableSchema returns a new table schema used to store global // configTableSchema returns a new table schema used to store global
@ -50,6 +51,12 @@ func configTableSchema() *memdb.TableSchema {
Unique: false, Unique: false,
Indexer: &ServiceIntentionSourceIndex{}, 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.ExportedServicesConfigEntry)(nil)
var _ configEntryIndexable = (*structs.SamenessGroupConfigEntry)(nil)
var _ configEntryIndexable = (*structs.IngressGatewayConfigEntry)(nil) var _ configEntryIndexable = (*structs.IngressGatewayConfigEntry)(nil)
var _ configEntryIndexable = (*structs.MeshConfigEntry)(nil) var _ configEntryIndexable = (*structs.MeshConfigEntry)(nil)
var _ configEntryIndexable = (*structs.ProxyConfigEntry)(nil) var _ configEntryIndexable = (*structs.ProxyConfigEntry)(nil)

View File

@ -357,6 +357,22 @@ var baseCases = map[string]testCase{
{Name: "kind", Value: "exported-services"}, {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 "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=api-gateway": { // Legacy
Name: "consul.usage.test.consul.state.config_entries", Name: "consul.usage.test.consul.state.config_entries",
Value: 0, Value: 0,
@ -784,6 +800,22 @@ var baseCases = map[string]testCase{
{Name: "kind", Value: "exported-services"}, {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 "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=api-gateway": { // Legacy
Name: "consul.usage.test.consul.state.config_entries", Name: "consul.usage.test.consul.state.config_entries",
Value: 0, Value: 0,

View File

@ -34,6 +34,7 @@ const (
ServiceIntentions string = "service-intentions" ServiceIntentions string = "service-intentions"
MeshConfig string = "mesh" MeshConfig string = "mesh"
ExportedServices string = "exported-services" ExportedServices string = "exported-services"
SamenessGroup string = "sameness-group"
APIGateway string = "api-gateway" APIGateway string = "api-gateway"
BoundAPIGateway string = "bound-api-gateway" BoundAPIGateway string = "bound-api-gateway"
InlineCertificate string = "inline-certificate" InlineCertificate string = "inline-certificate"
@ -59,6 +60,7 @@ var AllConfigEntryKinds = []string{
ServiceIntentions, ServiceIntentions,
MeshConfig, MeshConfig,
ExportedServices, ExportedServices,
SamenessGroup,
APIGateway, APIGateway,
BoundAPIGateway, BoundAPIGateway,
HTTPRoute, HTTPRoute,
@ -672,6 +674,8 @@ func MakeConfigEntry(kind, name string) (ConfigEntry, error) {
return &MeshConfigEntry{}, nil return &MeshConfigEntry{}, nil
case ExportedServices: case ExportedServices:
return &ExportedServicesConfigEntry{Name: name}, nil return &ExportedServicesConfigEntry{Name: name}, nil
case SamenessGroup:
return &SamenessGroupConfigEntry{Name: name}, nil
case APIGateway: case APIGateway:
return &APIGatewayConfigEntry{Name: name}, nil return &APIGatewayConfigEntry{Name: name}, nil
case BoundAPIGateway: case BoundAPIGateway:

View File

@ -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
}

View File

@ -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")
}

View File

@ -23,6 +23,7 @@ const (
ServiceIntentions string = "service-intentions" ServiceIntentions string = "service-intentions"
MeshConfig string = "mesh" MeshConfig string = "mesh"
ExportedServices string = "exported-services" ExportedServices string = "exported-services"
SamenessGroup string = "sameness-group"
ProxyConfigGlobal string = "global" ProxyConfigGlobal string = "global"
MeshConfigMesh string = "mesh" MeshConfigMesh string = "mesh"
@ -355,6 +356,8 @@ func makeConfigEntry(kind, name string) (ConfigEntry, error) {
return &MeshConfigEntry{}, nil return &MeshConfigEntry{}, nil
case ExportedServices: case ExportedServices:
return &ExportedServicesConfigEntry{Name: name}, nil return &ExportedServicesConfigEntry{Name: name}, nil
case SamenessGroup:
return &SamenessGroupConfigEntry{Kind: kind, Name: name}, nil
case APIGateway: case APIGateway:
return &APIGatewayConfigEntry{Kind: kind, Name: name}, nil return &APIGatewayConfigEntry{Kind: kind, Name: name}, nil
case TCPRoute: case TCPRoute:

View File

@ -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 }