2019-03-19 22:56:17 +00:00
|
|
|
package state
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
|
2019-07-01 20:23:36 +00:00
|
|
|
"github.com/hashicorp/consul/agent/consul/discoverychain"
|
2019-03-19 22:56:17 +00:00
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
|
|
memdb "github.com/hashicorp/go-memdb"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
configTableName = "config-entries"
|
|
|
|
)
|
|
|
|
|
2019-03-27 23:52:38 +00:00
|
|
|
// configTableSchema returns a new table schema used to store global
|
|
|
|
// config entries.
|
2019-03-19 22:56:17 +00:00
|
|
|
func configTableSchema() *memdb.TableSchema {
|
|
|
|
return &memdb.TableSchema{
|
|
|
|
Name: configTableName,
|
|
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
|
|
"id": &memdb.IndexSchema{
|
|
|
|
Name: "id",
|
|
|
|
AllowMissing: false,
|
|
|
|
Unique: true,
|
|
|
|
Indexer: &memdb.CompoundIndex{
|
|
|
|
Indexes: []memdb.Indexer{
|
|
|
|
&memdb.StringFieldIndex{
|
|
|
|
Field: "Kind",
|
|
|
|
Lowercase: true,
|
|
|
|
},
|
|
|
|
&memdb.StringFieldIndex{
|
|
|
|
Field: "Name",
|
|
|
|
Lowercase: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2019-03-27 23:52:38 +00:00
|
|
|
"kind": &memdb.IndexSchema{
|
|
|
|
Name: "kind",
|
|
|
|
AllowMissing: false,
|
2019-04-07 06:38:08 +00:00
|
|
|
Unique: false,
|
2019-03-27 23:52:38 +00:00
|
|
|
Indexer: &memdb.StringFieldIndex{
|
|
|
|
Field: "Kind",
|
|
|
|
Lowercase: true,
|
|
|
|
},
|
|
|
|
},
|
2019-07-01 20:23:36 +00:00
|
|
|
"link": &memdb.IndexSchema{
|
|
|
|
Name: "link",
|
|
|
|
AllowMissing: true,
|
|
|
|
Unique: false,
|
|
|
|
Indexer: &ConfigEntryLinkIndex{},
|
|
|
|
},
|
2019-03-19 22:56:17 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-01 20:23:36 +00:00
|
|
|
type ConfigEntryLinkIndex struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
type discoveryChainConfigEntry interface {
|
|
|
|
structs.ConfigEntry
|
|
|
|
// ListRelatedServices returns a list of other names of services referenced
|
|
|
|
// in this config entry.
|
|
|
|
ListRelatedServices() []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ConfigEntryLinkIndex) FromObject(obj interface{}) (bool, [][]byte, error) {
|
|
|
|
entry, ok := obj.(structs.ConfigEntry)
|
|
|
|
if !ok {
|
|
|
|
return false, nil, fmt.Errorf("object is not a ConfigEntry")
|
|
|
|
}
|
|
|
|
|
|
|
|
dcEntry, ok := entry.(discoveryChainConfigEntry)
|
|
|
|
if !ok {
|
|
|
|
return false, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
linkedServices := dcEntry.ListRelatedServices()
|
|
|
|
|
|
|
|
numLinks := len(linkedServices)
|
|
|
|
if numLinks == 0 {
|
|
|
|
return false, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
vals := make([][]byte, 0, numLinks)
|
|
|
|
for _, linkedService := range linkedServices {
|
|
|
|
vals = append(vals, []byte(linkedService+"\x00"))
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, vals, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ConfigEntryLinkIndex) FromArgs(args ...interface{}) ([]byte, error) {
|
|
|
|
if len(args) != 1 {
|
|
|
|
return nil, fmt.Errorf("must provide only a single argument")
|
|
|
|
}
|
|
|
|
arg, ok := args[0].(string)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("argument must be a string: %#v", args[0])
|
|
|
|
}
|
|
|
|
// Add the null character as a terminator
|
|
|
|
arg += "\x00"
|
|
|
|
return []byte(arg), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ConfigEntryLinkIndex) PrefixFromArgs(args ...interface{}) ([]byte, error) {
|
|
|
|
val, err := s.FromArgs(args...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Strip the null terminator, the rest is a prefix
|
|
|
|
n := len(val)
|
|
|
|
if n > 0 {
|
|
|
|
return val[:n-1], nil
|
|
|
|
}
|
|
|
|
return val, nil
|
|
|
|
}
|
|
|
|
|
2019-03-19 22:56:17 +00:00
|
|
|
func init() {
|
|
|
|
registerSchema(configTableSchema)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ConfigEntries is used to pull all the config entries for the snapshot.
|
|
|
|
func (s *Snapshot) ConfigEntries() ([]structs.ConfigEntry, error) {
|
2019-03-27 23:52:38 +00:00
|
|
|
entries, err := s.tx.Get(configTableName, "id")
|
2019-03-19 22:56:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var ret []structs.ConfigEntry
|
2019-03-27 23:52:38 +00:00
|
|
|
for wrapped := entries.Next(); wrapped != nil; wrapped = entries.Next() {
|
2019-03-19 22:56:17 +00:00
|
|
|
ret = append(ret, wrapped.(structs.ConfigEntry))
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
2019-03-20 23:13:13 +00:00
|
|
|
// ConfigEntry is used when restoring from a snapshot.
|
2019-03-19 22:56:17 +00:00
|
|
|
func (s *Restore) ConfigEntry(c structs.ConfigEntry) error {
|
|
|
|
// Insert
|
|
|
|
if err := s.tx.Insert(configTableName, c); err != nil {
|
|
|
|
return fmt.Errorf("failed restoring config entry object: %s", err)
|
|
|
|
}
|
|
|
|
if err := indexUpdateMaxTxn(s.tx, c.GetRaftIndex().ModifyIndex, configTableName); err != nil {
|
|
|
|
return fmt.Errorf("failed updating index: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-03-20 23:13:13 +00:00
|
|
|
// ConfigEntry is called to get a given config entry.
|
2019-04-07 06:38:08 +00:00
|
|
|
func (s *Store) ConfigEntry(ws memdb.WatchSet, kind, name string) (uint64, structs.ConfigEntry, error) {
|
2019-03-27 23:52:38 +00:00
|
|
|
tx := s.db.Txn(false)
|
2019-03-19 22:56:17 +00:00
|
|
|
defer tx.Abort()
|
2019-06-27 17:37:43 +00:00
|
|
|
return s.configEntryTxn(tx, ws, kind, name)
|
|
|
|
}
|
2019-03-19 22:56:17 +00:00
|
|
|
|
2019-06-27 17:37:43 +00:00
|
|
|
func (s *Store) configEntryTxn(tx *memdb.Txn, ws memdb.WatchSet, kind, name string) (uint64, structs.ConfigEntry, error) {
|
2019-03-19 22:56:17 +00:00
|
|
|
// Get the index
|
|
|
|
idx := maxIndexTxn(tx, configTableName)
|
|
|
|
|
|
|
|
// Get the existing config entry.
|
2019-04-07 06:38:08 +00:00
|
|
|
watchCh, existing, err := tx.FirstWatch(configTableName, "id", kind, name)
|
2019-03-19 22:56:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, nil, fmt.Errorf("failed config entry lookup: %s", err)
|
|
|
|
}
|
2019-05-02 19:25:29 +00:00
|
|
|
ws.Add(watchCh)
|
2019-03-19 22:56:17 +00:00
|
|
|
if existing == nil {
|
2019-03-27 23:52:38 +00:00
|
|
|
return idx, nil, nil
|
2019-03-19 22:56:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
conf, ok := existing.(structs.ConfigEntry)
|
|
|
|
if !ok {
|
|
|
|
return 0, nil, fmt.Errorf("config entry %q (%s) is an invalid type: %T", name, kind, conf)
|
|
|
|
}
|
|
|
|
|
|
|
|
return idx, conf, nil
|
|
|
|
}
|
|
|
|
|
2019-03-20 23:13:13 +00:00
|
|
|
// ConfigEntries is called to get all config entry objects.
|
2019-04-07 06:38:08 +00:00
|
|
|
func (s *Store) ConfigEntries(ws memdb.WatchSet) (uint64, []structs.ConfigEntry, error) {
|
2019-04-26 17:38:39 +00:00
|
|
|
return s.ConfigEntriesByKind(ws, "")
|
2019-03-27 23:52:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ConfigEntriesByKind is called to get all config entry objects with the given kind.
|
|
|
|
// If kind is empty, all config entries will be returned.
|
2019-04-07 06:38:08 +00:00
|
|
|
func (s *Store) ConfigEntriesByKind(ws memdb.WatchSet, kind string) (uint64, []structs.ConfigEntry, error) {
|
2019-03-27 23:52:38 +00:00
|
|
|
tx := s.db.Txn(false)
|
2019-03-20 23:13:13 +00:00
|
|
|
defer tx.Abort()
|
2019-07-02 16:01:17 +00:00
|
|
|
return s.configEntriesByKindTxn(tx, ws, kind)
|
|
|
|
}
|
2019-03-20 23:13:13 +00:00
|
|
|
|
2019-07-02 16:01:17 +00:00
|
|
|
func (s *Store) configEntriesByKindTxn(tx *memdb.Txn, ws memdb.WatchSet, kind string) (uint64, []structs.ConfigEntry, error) {
|
2019-03-20 23:13:13 +00:00
|
|
|
// Get the index
|
|
|
|
idx := maxIndexTxn(tx, configTableName)
|
|
|
|
|
2019-03-27 23:52:38 +00:00
|
|
|
// Lookup by kind, or all if kind is empty
|
|
|
|
var iter memdb.ResultIterator
|
|
|
|
var err error
|
|
|
|
if kind != "" {
|
|
|
|
iter, err = tx.Get(configTableName, "kind", kind)
|
|
|
|
} else {
|
|
|
|
iter, err = tx.Get(configTableName, "id")
|
|
|
|
}
|
2019-03-20 23:13:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, nil, fmt.Errorf("failed config entry lookup: %s", err)
|
|
|
|
}
|
2019-04-07 06:38:08 +00:00
|
|
|
ws.Add(iter.WatchCh())
|
2019-03-20 23:13:13 +00:00
|
|
|
|
|
|
|
var results []structs.ConfigEntry
|
|
|
|
for v := iter.Next(); v != nil; v = iter.Next() {
|
|
|
|
results = append(results, v.(structs.ConfigEntry))
|
|
|
|
}
|
|
|
|
return idx, results, nil
|
|
|
|
}
|
|
|
|
|
2019-03-27 23:52:38 +00:00
|
|
|
// EnsureConfigEntry is called to do an upsert of a given config entry.
|
2019-03-19 22:56:17 +00:00
|
|
|
func (s *Store) EnsureConfigEntry(idx uint64, conf structs.ConfigEntry) error {
|
|
|
|
tx := s.db.Txn(true)
|
|
|
|
defer tx.Abort()
|
|
|
|
|
2019-03-27 23:52:38 +00:00
|
|
|
if err := s.ensureConfigEntryTxn(tx, idx, conf); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
tx.Commit()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ensureConfigEntryTxn upserts a config entry inside of a transaction.
|
|
|
|
func (s *Store) ensureConfigEntryTxn(tx *memdb.Txn, idx uint64, conf structs.ConfigEntry) error {
|
2019-03-19 22:56:17 +00:00
|
|
|
// Check for existing configuration.
|
|
|
|
existing, err := tx.First(configTableName, "id", conf.GetKind(), conf.GetName())
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed configuration lookup: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
raftIndex := conf.GetRaftIndex()
|
|
|
|
if existing != nil {
|
|
|
|
existingIdx := existing.(structs.ConfigEntry).GetRaftIndex()
|
|
|
|
raftIndex.CreateIndex = existingIdx.CreateIndex
|
|
|
|
raftIndex.ModifyIndex = existingIdx.ModifyIndex
|
|
|
|
} else {
|
|
|
|
raftIndex.CreateIndex = idx
|
|
|
|
}
|
|
|
|
raftIndex.ModifyIndex = idx
|
|
|
|
|
2019-07-01 20:23:36 +00:00
|
|
|
err = s.validateProposedConfigEntryInGraph(
|
|
|
|
tx,
|
|
|
|
idx,
|
|
|
|
conf.GetKind(),
|
|
|
|
conf.GetName(),
|
|
|
|
conf,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err // Err is already sufficiently decorated.
|
|
|
|
}
|
|
|
|
|
2019-03-19 22:56:17 +00:00
|
|
|
// Insert the config entry and update the index
|
|
|
|
if err := tx.Insert(configTableName, conf); err != nil {
|
|
|
|
return fmt.Errorf("failed inserting config entry: %s", err)
|
|
|
|
}
|
2019-03-27 23:52:38 +00:00
|
|
|
if err := indexUpdateMaxTxn(tx, idx, configTableName); err != nil {
|
|
|
|
return fmt.Errorf("failed updating index: %v", err)
|
2019-03-19 22:56:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-03-27 23:52:38 +00:00
|
|
|
// EnsureConfigEntryCAS is called to do a check-and-set upsert of a given config entry.
|
|
|
|
func (s *Store) EnsureConfigEntryCAS(idx, cidx uint64, conf structs.ConfigEntry) (bool, error) {
|
2019-03-19 22:56:17 +00:00
|
|
|
tx := s.db.Txn(true)
|
|
|
|
defer tx.Abort()
|
|
|
|
|
2019-03-27 23:52:38 +00:00
|
|
|
// Check for existing configuration.
|
|
|
|
existing, err := tx.First(configTableName, "id", conf.GetKind(), conf.GetName())
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("failed configuration lookup: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the we should do the set. A ModifyIndex of 0 means that
|
|
|
|
// we are doing a set-if-not-exists.
|
|
|
|
var existingIdx structs.RaftIndex
|
|
|
|
if existing != nil {
|
|
|
|
existingIdx = *existing.(structs.ConfigEntry).GetRaftIndex()
|
|
|
|
}
|
|
|
|
if cidx == 0 && existing != nil {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
if cidx != 0 && existing == nil {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
if existing != nil && cidx != 0 && cidx != existingIdx.ModifyIndex {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.ensureConfigEntryTxn(tx, idx, conf); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
tx.Commit()
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Store) DeleteConfigEntry(idx uint64, kind, name string) error {
|
|
|
|
tx := s.db.Txn(true)
|
|
|
|
defer tx.Abort()
|
2019-03-19 22:56:17 +00:00
|
|
|
|
2019-04-07 06:38:08 +00:00
|
|
|
// Try to retrieve the existing config entry.
|
2019-03-19 22:56:17 +00:00
|
|
|
existing, err := tx.First(configTableName, "id", kind, name)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed config entry lookup: %s", err)
|
|
|
|
}
|
|
|
|
if existing == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-01 20:23:36 +00:00
|
|
|
err = s.validateProposedConfigEntryInGraph(
|
|
|
|
tx,
|
|
|
|
idx,
|
|
|
|
kind,
|
|
|
|
name,
|
|
|
|
nil,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err // Err is already sufficiently decorated.
|
|
|
|
}
|
|
|
|
|
2019-03-19 22:56:17 +00:00
|
|
|
// Delete the config entry from the DB and update the index.
|
|
|
|
if err := tx.Delete(configTableName, existing); err != nil {
|
|
|
|
return fmt.Errorf("failed removing check: %s", err)
|
|
|
|
}
|
|
|
|
if err := tx.Insert("index", &IndexEntry{configTableName, idx}); err != nil {
|
|
|
|
return fmt.Errorf("failed updating index: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tx.Commit()
|
|
|
|
return nil
|
|
|
|
}
|
2019-07-01 20:23:36 +00:00
|
|
|
|
|
|
|
// validateProposedConfigEntryInGraph can be used to verify graph integrity for
|
|
|
|
// a proposed graph create/update/delete.
|
|
|
|
//
|
|
|
|
// This must be called before any mutations occur on the config entries table!
|
|
|
|
//
|
|
|
|
// May return *ConfigEntryGraphValidationError if there is a concern to surface
|
|
|
|
// to the caller that they can correct.
|
|
|
|
func (s *Store) validateProposedConfigEntryInGraph(
|
|
|
|
tx *memdb.Txn,
|
|
|
|
idx uint64,
|
|
|
|
kind, name string,
|
2019-07-02 16:01:17 +00:00
|
|
|
next structs.ConfigEntry,
|
2019-07-01 20:23:36 +00:00
|
|
|
) error {
|
2019-07-02 16:01:17 +00:00
|
|
|
validateAllChains := false
|
|
|
|
|
2019-07-01 20:23:36 +00:00
|
|
|
switch kind {
|
|
|
|
case structs.ProxyDefaults:
|
2019-07-02 16:01:17 +00:00
|
|
|
if name != structs.ProxyConfigGlobal {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
validateAllChains = true
|
2019-07-01 20:23:36 +00:00
|
|
|
case structs.ServiceDefaults:
|
|
|
|
case structs.ServiceRouter:
|
|
|
|
case structs.ServiceSplitter:
|
|
|
|
case structs.ServiceResolver:
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("unhandled kind %q during validation of %q", kind, name)
|
|
|
|
}
|
2019-07-02 16:01:17 +00:00
|
|
|
|
|
|
|
return s.validateProposedConfigEntryInServiceGraph(tx, idx, kind, name, next, validateAllChains)
|
|
|
|
}
|
|
|
|
|
|
|
|
var serviceGraphKinds = []string{
|
|
|
|
structs.ServiceRouter,
|
|
|
|
structs.ServiceSplitter,
|
|
|
|
structs.ServiceResolver,
|
2019-07-01 20:23:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Store) validateProposedConfigEntryInServiceGraph(
|
|
|
|
tx *memdb.Txn,
|
|
|
|
idx uint64,
|
|
|
|
kind, name string,
|
2019-07-02 16:01:17 +00:00
|
|
|
next structs.ConfigEntry,
|
|
|
|
validateAllChains bool,
|
2019-07-01 20:23:36 +00:00
|
|
|
) error {
|
|
|
|
// Collect all of the chains that could be affected by this change
|
|
|
|
// including our own.
|
2019-07-02 16:01:17 +00:00
|
|
|
checkChains := make(map[string]struct{})
|
|
|
|
|
|
|
|
if validateAllChains {
|
|
|
|
// Must be proxy-defaults/global.
|
|
|
|
|
|
|
|
// Check anything that has a discovery chain entry. In the future we could
|
|
|
|
// somehow omit the ones that have a default protocol configured.
|
|
|
|
|
|
|
|
for _, kind := range serviceGraphKinds {
|
|
|
|
_, entries, err := s.configEntriesByKindTxn(tx, nil, kind)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
|
|
checkChains[entry.GetName()] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Must be a single chain.
|
2019-07-01 20:23:36 +00:00
|
|
|
|
2019-07-02 16:01:17 +00:00
|
|
|
checkChains[name] = struct{}{}
|
|
|
|
|
|
|
|
iter, err := tx.Get(configTableName, "link", name)
|
|
|
|
for raw := iter.Next(); raw != nil; raw = iter.Next() {
|
|
|
|
entry := raw.(structs.ConfigEntry)
|
|
|
|
checkChains[entry.GetName()] = struct{}{}
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-07-01 20:23:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
overrides := map[structs.ConfigEntryKindName]structs.ConfigEntry{
|
|
|
|
{Kind: kind, Name: name}: next,
|
|
|
|
}
|
|
|
|
|
|
|
|
for chainName, _ := range checkChains {
|
2019-07-02 16:01:17 +00:00
|
|
|
if err := s.testCompileDiscoveryChain(tx, nil, chainName, overrides); err != nil {
|
2019-07-01 20:23:36 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-02 16:01:17 +00:00
|
|
|
func (s *Store) testCompileDiscoveryChain(
|
|
|
|
tx *memdb.Txn,
|
|
|
|
ws memdb.WatchSet,
|
|
|
|
chainName string,
|
|
|
|
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
|
|
|
|
) error {
|
|
|
|
_, speculativeEntries, err := s.readDiscoveryChainConfigEntriesTxn(tx, nil, chainName, overrides)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(rb): is this ok that we execute the compiler in the state store?
|
|
|
|
|
|
|
|
// Note we use an arbitrary namespace and datacenter as those would not
|
|
|
|
// currently affect the graph compilation in ways that matter here.
|
|
|
|
req := discoverychain.CompileRequest{
|
|
|
|
ServiceName: chainName,
|
|
|
|
CurrentNamespace: "default",
|
|
|
|
CurrentDatacenter: "dc1",
|
|
|
|
Entries: speculativeEntries,
|
|
|
|
}
|
|
|
|
_, err = discoverychain.Compile(req)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-07-01 20:23:36 +00:00
|
|
|
// ReadDiscoveryChainConfigEntries will query for the full discovery chain for
|
|
|
|
// the provided service name. All relevant config entries will be recursively
|
|
|
|
// fetched and included in the result.
|
|
|
|
//
|
|
|
|
// Once returned, the caller still needs to assemble these into a useful graph
|
|
|
|
// structure.
|
|
|
|
func (s *Store) ReadDiscoveryChainConfigEntries(
|
|
|
|
ws memdb.WatchSet,
|
|
|
|
serviceName string,
|
|
|
|
) (uint64, *structs.DiscoveryChainConfigEntries, error) {
|
|
|
|
return s.readDiscoveryChainConfigEntries(ws, serviceName, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// readDiscoveryChainConfigEntries will query for the full discovery chain for
|
|
|
|
// the provided service name. All relevant config entries will be recursively
|
|
|
|
// fetched and included in the result.
|
|
|
|
//
|
|
|
|
// If 'overrides' is provided then it will use entries in that map instead of
|
|
|
|
// the database to simulate the entries that go into a modified discovery chain
|
|
|
|
// without actually modifying it yet. Nil values are tombstones to simulate
|
|
|
|
// deleting an entry.
|
|
|
|
//
|
|
|
|
// Overrides is not mutated.
|
|
|
|
func (s *Store) readDiscoveryChainConfigEntries(
|
|
|
|
ws memdb.WatchSet,
|
|
|
|
serviceName string,
|
|
|
|
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
|
|
|
|
) (uint64, *structs.DiscoveryChainConfigEntries, error) {
|
|
|
|
tx := s.db.Txn(false)
|
|
|
|
defer tx.Abort()
|
|
|
|
return s.readDiscoveryChainConfigEntriesTxn(tx, ws, serviceName, overrides)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Store) readDiscoveryChainConfigEntriesTxn(
|
|
|
|
tx *memdb.Txn,
|
|
|
|
ws memdb.WatchSet,
|
|
|
|
serviceName string,
|
|
|
|
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
|
|
|
|
) (uint64, *structs.DiscoveryChainConfigEntries, error) {
|
2019-07-12 19:16:21 +00:00
|
|
|
res := structs.NewDiscoveryChainConfigEntries()
|
2019-07-01 20:23:36 +00:00
|
|
|
|
|
|
|
// Note that below we always look up splitters and resolvers in pairs, even
|
|
|
|
// in some circumstances where both are not strictly necessary.
|
|
|
|
//
|
|
|
|
// For now we'll just eat the cost of fetching pairs of splitter/resolver
|
|
|
|
// config entries even though we may not always need both. In the common
|
|
|
|
// case we will need the pair so there's not a big drive to optimize this
|
|
|
|
// here at this time.
|
|
|
|
|
|
|
|
// Both Splitters and Resolvers maps will contain placeholder nils until
|
|
|
|
// the end of this function to indicate "no such entry".
|
|
|
|
|
|
|
|
var (
|
|
|
|
todoSplitters = make(map[string]struct{})
|
|
|
|
todoResolvers = make(map[string]struct{})
|
|
|
|
todoDefaults = make(map[string]struct{})
|
|
|
|
)
|
|
|
|
|
2019-07-02 16:01:17 +00:00
|
|
|
// Grab the proxy defaults if they exist.
|
|
|
|
idx, proxy, err := s.getProxyConfigEntryTxn(tx, ws, structs.ProxyConfigGlobal, overrides)
|
|
|
|
if err != nil {
|
|
|
|
return 0, nil, err
|
|
|
|
} else if proxy != nil {
|
|
|
|
res.GlobalProxy = proxy
|
|
|
|
}
|
|
|
|
|
2019-07-01 20:23:36 +00:00
|
|
|
// At every step we'll need service defaults.
|
|
|
|
todoDefaults[serviceName] = struct{}{}
|
|
|
|
|
|
|
|
// first fetch the router, of which we only collect 1 per chain eval
|
|
|
|
_, router, err := s.getRouterConfigEntryTxn(tx, ws, serviceName, overrides)
|
|
|
|
if err != nil {
|
|
|
|
return 0, nil, err
|
|
|
|
} else if router != nil {
|
|
|
|
res.Routers[serviceName] = router
|
|
|
|
}
|
|
|
|
|
|
|
|
if router != nil {
|
|
|
|
for _, svc := range router.ListRelatedServices() {
|
|
|
|
todoSplitters[svc] = struct{}{}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Next hop in the chain is the splitter.
|
|
|
|
todoSplitters[serviceName] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
name, ok := anyKey(todoSplitters)
|
|
|
|
if !ok {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
delete(todoSplitters, name)
|
|
|
|
|
|
|
|
if _, ok := res.Splitters[name]; ok {
|
|
|
|
continue // already fetched
|
|
|
|
}
|
|
|
|
|
|
|
|
// Yes, even for splitters.
|
|
|
|
todoDefaults[name] = struct{}{}
|
|
|
|
|
|
|
|
_, splitter, err := s.getSplitterConfigEntryTxn(tx, ws, name, overrides)
|
|
|
|
if err != nil {
|
|
|
|
return 0, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if splitter == nil {
|
|
|
|
res.Splitters[name] = nil
|
|
|
|
|
|
|
|
// Next hop in the chain is the resolver.
|
|
|
|
todoResolvers[name] = struct{}{}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
res.Splitters[name] = splitter
|
|
|
|
|
|
|
|
todoResolvers[name] = struct{}{}
|
|
|
|
for _, svc := range splitter.ListRelatedServices() {
|
|
|
|
// If there is no splitter, this will end up adding a resolver
|
|
|
|
// after another iteration.
|
|
|
|
todoSplitters[svc] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
name, ok := anyKey(todoResolvers)
|
|
|
|
if !ok {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
delete(todoResolvers, name)
|
|
|
|
|
|
|
|
if _, ok := res.Resolvers[name]; ok {
|
|
|
|
continue // already fetched
|
|
|
|
}
|
|
|
|
|
|
|
|
// And resolvers, too.
|
|
|
|
todoDefaults[name] = struct{}{}
|
|
|
|
|
|
|
|
_, resolver, err := s.getResolverConfigEntryTxn(tx, ws, name, overrides)
|
|
|
|
if err != nil {
|
|
|
|
return 0, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if resolver == nil {
|
|
|
|
res.Resolvers[name] = nil
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
res.Resolvers[name] = resolver
|
|
|
|
|
|
|
|
for _, svc := range resolver.ListRelatedServices() {
|
|
|
|
todoResolvers[svc] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
|
|
|
name, ok := anyKey(todoDefaults)
|
|
|
|
if !ok {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
delete(todoDefaults, name)
|
|
|
|
|
|
|
|
if _, ok := res.Services[name]; ok {
|
|
|
|
continue // already fetched
|
|
|
|
}
|
|
|
|
|
|
|
|
_, entry, err := s.getServiceConfigEntryTxn(tx, ws, name, overrides)
|
|
|
|
if err != nil {
|
|
|
|
return 0, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if entry == nil {
|
|
|
|
res.Services[name] = nil
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
res.Services[name] = entry
|
|
|
|
}
|
|
|
|
|
|
|
|
// Strip nils now that they are no longer necessary.
|
|
|
|
for name, entry := range res.Routers {
|
|
|
|
if entry == nil {
|
|
|
|
delete(res.Routers, name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for name, entry := range res.Splitters {
|
|
|
|
if entry == nil {
|
|
|
|
delete(res.Splitters, name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for name, entry := range res.Resolvers {
|
|
|
|
if entry == nil {
|
|
|
|
delete(res.Resolvers, name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for name, entry := range res.Services {
|
|
|
|
if entry == nil {
|
|
|
|
delete(res.Services, name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return idx, res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// anyKey returns any key from the provided map if any exist. Useful for using
|
|
|
|
// a map as a simple work queue of sorts.
|
|
|
|
func anyKey(m map[string]struct{}) (string, bool) {
|
|
|
|
if len(m) == 0 {
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
for k, _ := range m {
|
|
|
|
return k, true
|
|
|
|
}
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
2019-07-02 16:01:17 +00:00
|
|
|
// getProxyConfigEntryTxn is a convenience method for fetching a
|
|
|
|
// proxy-defaults kind of config entry.
|
|
|
|
//
|
|
|
|
// If an override is returned the index returned will be 0.
|
|
|
|
func (s *Store) getProxyConfigEntryTxn(
|
|
|
|
tx *memdb.Txn,
|
|
|
|
ws memdb.WatchSet,
|
|
|
|
name string,
|
|
|
|
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
|
|
|
|
) (uint64, *structs.ProxyConfigEntry, error) {
|
|
|
|
idx, entry, err := s.configEntryWithOverridesTxn(tx, ws, structs.ProxyDefaults, name, overrides)
|
|
|
|
if err != nil {
|
|
|
|
return 0, nil, err
|
|
|
|
} else if entry == nil {
|
|
|
|
return idx, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
proxy, ok := entry.(*structs.ProxyConfigEntry)
|
|
|
|
if !ok {
|
|
|
|
return 0, nil, fmt.Errorf("invalid service config type %T", entry)
|
|
|
|
}
|
|
|
|
return idx, proxy, nil
|
|
|
|
}
|
|
|
|
|
2019-07-01 20:23:36 +00:00
|
|
|
// getServiceConfigEntryTxn is a convenience method for fetching a
|
|
|
|
// service-defaults kind of config entry.
|
|
|
|
//
|
|
|
|
// If an override is returned the index returned will be 0.
|
|
|
|
func (s *Store) getServiceConfigEntryTxn(
|
|
|
|
tx *memdb.Txn,
|
|
|
|
ws memdb.WatchSet,
|
|
|
|
serviceName string,
|
|
|
|
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
|
|
|
|
) (uint64, *structs.ServiceConfigEntry, error) {
|
|
|
|
idx, entry, err := s.configEntryWithOverridesTxn(tx, ws, structs.ServiceDefaults, serviceName, overrides)
|
|
|
|
if err != nil {
|
|
|
|
return 0, nil, err
|
|
|
|
} else if entry == nil {
|
|
|
|
return idx, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
service, ok := entry.(*structs.ServiceConfigEntry)
|
|
|
|
if !ok {
|
|
|
|
return 0, nil, fmt.Errorf("invalid service config type %T", entry)
|
|
|
|
}
|
|
|
|
return idx, service, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getRouterConfigEntryTxn is a convenience method for fetching a
|
|
|
|
// service-router kind of config entry.
|
|
|
|
//
|
|
|
|
// If an override is returned the index returned will be 0.
|
|
|
|
func (s *Store) getRouterConfigEntryTxn(
|
|
|
|
tx *memdb.Txn,
|
|
|
|
ws memdb.WatchSet,
|
|
|
|
serviceName string,
|
|
|
|
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
|
|
|
|
) (uint64, *structs.ServiceRouterConfigEntry, error) {
|
|
|
|
idx, entry, err := s.configEntryWithOverridesTxn(tx, ws, structs.ServiceRouter, serviceName, overrides)
|
|
|
|
if err != nil {
|
|
|
|
return 0, nil, err
|
|
|
|
} else if entry == nil {
|
|
|
|
return idx, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
router, ok := entry.(*structs.ServiceRouterConfigEntry)
|
|
|
|
if !ok {
|
|
|
|
return 0, nil, fmt.Errorf("invalid service config type %T", entry)
|
|
|
|
}
|
|
|
|
return idx, router, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getSplitterConfigEntryTxn is a convenience method for fetching a
|
|
|
|
// service-splitter kind of config entry.
|
|
|
|
//
|
|
|
|
// If an override is returned the index returned will be 0.
|
|
|
|
func (s *Store) getSplitterConfigEntryTxn(
|
|
|
|
tx *memdb.Txn,
|
|
|
|
ws memdb.WatchSet,
|
|
|
|
serviceName string,
|
|
|
|
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
|
|
|
|
) (uint64, *structs.ServiceSplitterConfigEntry, error) {
|
|
|
|
idx, entry, err := s.configEntryWithOverridesTxn(tx, ws, structs.ServiceSplitter, serviceName, overrides)
|
|
|
|
if err != nil {
|
|
|
|
return 0, nil, err
|
|
|
|
} else if entry == nil {
|
|
|
|
return idx, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
splitter, ok := entry.(*structs.ServiceSplitterConfigEntry)
|
|
|
|
if !ok {
|
|
|
|
return 0, nil, fmt.Errorf("invalid service config type %T", entry)
|
|
|
|
}
|
|
|
|
return idx, splitter, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getResolverConfigEntryTxn is a convenience method for fetching a
|
|
|
|
// service-resolver kind of config entry.
|
|
|
|
//
|
|
|
|
// If an override is returned the index returned will be 0.
|
|
|
|
func (s *Store) getResolverConfigEntryTxn(
|
|
|
|
tx *memdb.Txn,
|
|
|
|
ws memdb.WatchSet,
|
|
|
|
serviceName string,
|
|
|
|
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
|
|
|
|
) (uint64, *structs.ServiceResolverConfigEntry, error) {
|
|
|
|
idx, entry, err := s.configEntryWithOverridesTxn(tx, ws, structs.ServiceResolver, serviceName, overrides)
|
|
|
|
if err != nil {
|
|
|
|
return 0, nil, err
|
|
|
|
} else if entry == nil {
|
|
|
|
return idx, nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
resolver, ok := entry.(*structs.ServiceResolverConfigEntry)
|
|
|
|
if !ok {
|
|
|
|
return 0, nil, fmt.Errorf("invalid service config type %T", entry)
|
|
|
|
}
|
|
|
|
return idx, resolver, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Store) configEntryWithOverridesTxn(
|
|
|
|
tx *memdb.Txn,
|
|
|
|
ws memdb.WatchSet,
|
|
|
|
kind string,
|
|
|
|
name string,
|
|
|
|
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
|
|
|
|
) (uint64, structs.ConfigEntry, error) {
|
|
|
|
if len(overrides) > 0 {
|
|
|
|
entry, ok := overrides[structs.ConfigEntryKindName{
|
|
|
|
Kind: kind, Name: name,
|
|
|
|
}]
|
|
|
|
if ok {
|
|
|
|
return 0, entry, nil // a nil entry implies it should act like it is erased
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return s.configEntryTxn(tx, ws, kind, name)
|
|
|
|
}
|