2019-03-19 22:56:17 +00:00
package state
import (
"fmt"
2020-04-16 21:00:48 +00:00
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"
2020-04-23 23:16:04 +00:00
"github.com/hashicorp/consul/lib"
2019-03-19 22:56:17 +00:00
memdb "github.com/hashicorp/go-memdb"
)
const (
configTableName = "config-entries"
)
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.
2020-01-24 15:04:58 +00:00
ListRelatedServices ( ) [ ] structs . ServiceID
2019-07-01 20:23:36 +00:00
}
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 {
2020-01-24 15:04:58 +00:00
vals = append ( vals , [ ] byte ( linkedService . String ( ) + "\x00" ) )
2019-07-01 20:23:36 +00:00
}
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" )
}
2020-01-24 15:04:58 +00:00
arg , ok := args [ 0 ] . ( structs . ServiceID )
2019-07-01 20:23:36 +00:00
if ! ok {
2020-01-24 15:04:58 +00:00
return nil , fmt . Errorf ( "argument must be a structs.ServiceID: %#v" , args [ 0 ] )
2019-07-01 20:23:36 +00:00
}
// Add the null character as a terminator
2020-01-24 15:04:58 +00:00
return [ ] byte ( arg . String ( ) + "\x00" ) , nil
2019-07-01 20:23:36 +00:00
}
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 {
2020-07-10 00:56:43 +00:00
return insertConfigEntryWithTxn ( s . tx , c . GetRaftIndex ( ) . ModifyIndex , c )
2019-03-19 22:56:17 +00:00
}
2019-03-20 23:13:13 +00:00
// ConfigEntry is called to get a given config entry.
2020-01-24 15:04:58 +00:00
func ( s * Store ) ConfigEntry ( ws memdb . WatchSet , kind , name string , entMeta * structs . EnterpriseMeta ) ( 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 ( )
2020-07-10 00:56:43 +00:00
return configEntryTxn ( tx , ws , kind , name , entMeta )
2019-06-27 17:37:43 +00:00
}
2019-03-19 22:56:17 +00:00
2020-08-11 20:31:23 +00:00
func configEntryTxn ( tx ReadTxn , ws memdb . WatchSet , kind , name string , entMeta * structs . EnterpriseMeta ) ( uint64 , structs . ConfigEntry , error ) {
2019-03-19 22:56:17 +00:00
// Get the index
idx := maxIndexTxn ( tx , configTableName )
// Get the existing config entry.
2020-07-10 00:56:43 +00:00
watchCh , existing , err := firstWatchConfigEntryWithTxn ( tx , kind , name , entMeta )
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.
2020-01-24 15:04:58 +00:00
func ( s * Store ) ConfigEntries ( ws memdb . WatchSet , entMeta * structs . EnterpriseMeta ) ( uint64 , [ ] structs . ConfigEntry , error ) {
return s . ConfigEntriesByKind ( ws , "" , entMeta )
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.
2020-01-24 15:04:58 +00:00
func ( s * Store ) ConfigEntriesByKind ( ws memdb . WatchSet , kind string , entMeta * structs . EnterpriseMeta ) ( 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 ( )
2020-07-10 00:56:43 +00:00
return configEntriesByKindTxn ( tx , ws , kind , entMeta )
2019-07-02 16:01:17 +00:00
}
2019-03-20 23:13:13 +00:00
2020-08-11 20:31:23 +00:00
func configEntriesByKindTxn ( tx ReadTxn , ws memdb . WatchSet , kind string , entMeta * structs . EnterpriseMeta ) ( 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 != "" {
2020-01-24 15:04:58 +00:00
iter , err = getConfigEntryKindsWithTxn ( tx , kind , entMeta )
2019-03-27 23:52:38 +00:00
} else {
2020-01-24 15:04:58 +00:00
iter , err = getAllConfigEntriesWithTxn ( tx , entMeta )
2019-03-27 23:52:38 +00:00
}
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.
2020-01-24 15:04:58 +00:00
func ( s * Store ) EnsureConfigEntry ( idx uint64 , conf structs . ConfigEntry , entMeta * structs . EnterpriseMeta ) error {
2020-03-19 13:11:20 +00:00
tx := s . db . WriteTxn ( idx )
2019-03-19 22:56:17 +00:00
defer tx . Abort ( )
2020-08-11 20:31:23 +00:00
if err := ensureConfigEntryTxn ( tx , idx , conf , entMeta ) ; err != nil {
2019-03-27 23:52:38 +00:00
return err
}
2020-06-02 20:34:56 +00:00
return tx . Commit ( )
2019-03-27 23:52:38 +00:00
}
// ensureConfigEntryTxn upserts a config entry inside of a transaction.
2020-08-11 20:31:23 +00:00
func ensureConfigEntryTxn ( tx * txn , idx uint64 , conf structs . ConfigEntry , entMeta * structs . EnterpriseMeta ) error {
2019-03-19 22:56:17 +00:00
// Check for existing configuration.
2020-07-10 00:56:43 +00:00
existing , err := firstConfigEntryWithTxn ( tx , conf . GetKind ( ) , conf . GetName ( ) , entMeta )
2019-03-19 22:56:17 +00:00
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
2020-08-11 20:31:23 +00:00
err = validateProposedConfigEntryInGraph ( tx , conf . GetKind ( ) , conf . GetName ( ) , conf , entMeta )
2019-07-01 20:23:36 +00:00
if err != nil {
return err // Err is already sufficiently decorated.
}
2020-07-10 00:56:43 +00:00
if err := validateConfigEntryEnterprise ( tx , conf ) ; err != nil {
2020-05-08 18:24:33 +00:00
return err
2019-03-19 22:56:17 +00:00
}
2020-07-10 00:56:43 +00:00
return insertConfigEntryWithTxn ( tx , idx , conf )
2019-03-19 22:56:17 +00:00
}
2019-03-27 23:52:38 +00:00
// EnsureConfigEntryCAS is called to do a check-and-set upsert of a given config entry.
2020-01-24 15:04:58 +00:00
func ( s * Store ) EnsureConfigEntryCAS ( idx , cidx uint64 , conf structs . ConfigEntry , entMeta * structs . EnterpriseMeta ) ( bool , error ) {
2020-03-19 13:11:20 +00:00
tx := s . db . WriteTxn ( idx )
2019-03-19 22:56:17 +00:00
defer tx . Abort ( )
2019-03-27 23:52:38 +00:00
// Check for existing configuration.
2020-07-10 00:56:43 +00:00
existing , err := firstConfigEntryWithTxn ( tx , conf . GetKind ( ) , conf . GetName ( ) , entMeta )
2019-03-27 23:52:38 +00:00
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
}
2020-08-11 20:31:23 +00:00
if err := ensureConfigEntryTxn ( tx , idx , conf , entMeta ) ; err != nil {
2019-03-27 23:52:38 +00:00
return false , err
}
2020-06-02 20:34:56 +00:00
err = tx . Commit ( )
return err == nil , err
2019-03-27 23:52:38 +00:00
}
2020-01-24 15:04:58 +00:00
func ( s * Store ) DeleteConfigEntry ( idx uint64 , kind , name string , entMeta * structs . EnterpriseMeta ) error {
2020-03-19 13:11:20 +00:00
tx := s . db . WriteTxn ( idx )
2019-03-27 23:52:38 +00:00
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.
2020-07-10 00:56:43 +00:00
existing , err := firstConfigEntryWithTxn ( tx , kind , name , entMeta )
2019-03-19 22:56:17 +00:00
if err != nil {
return fmt . Errorf ( "failed config entry lookup: %s" , err )
}
if existing == nil {
return nil
}
2020-04-16 21:00:48 +00:00
// If the config entry is for terminating or ingress gateways we delete entries from the memdb table
2020-04-08 18:37:24 +00:00
// that associates gateways <-> services.
2020-04-16 21:00:48 +00:00
if kind == structs . TerminatingGateway || kind == structs . IngressGateway {
2020-06-12 14:57:41 +00:00
if _ , err := tx . DeleteAll ( gatewayServicesTableName , "gateway" , structs . NewServiceName ( name , entMeta ) ) ; err != nil {
2020-04-08 18:37:24 +00:00
return fmt . Errorf ( "failed to truncate gateway services table: %v" , err )
}
2020-04-16 21:00:48 +00:00
if err := indexUpdateMaxTxn ( tx , idx , gatewayServicesTableName ) ; err != nil {
return fmt . Errorf ( "failed updating gateway-services index: %v" , err )
2020-04-08 18:37:24 +00:00
}
}
2020-08-11 20:31:23 +00:00
err = validateProposedConfigEntryInGraph ( tx , kind , name , nil , entMeta )
2019-07-01 20:23:36 +00:00
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 )
}
2020-06-02 20:34:56 +00:00
return tx . Commit ( )
2019-03-19 22:56:17 +00:00
}
2019-07-01 20:23:36 +00:00
2020-07-10 00:56:43 +00:00
func insertConfigEntryWithTxn ( tx * txn , idx uint64 , conf structs . ConfigEntry ) error {
2020-05-08 18:24:33 +00:00
if conf == nil {
return fmt . Errorf ( "cannot insert nil config entry" )
}
// If the config entry is for a terminating or ingress gateway we update the memdb table
// that associates gateways <-> services.
if conf . GetKind ( ) == structs . TerminatingGateway || conf . GetKind ( ) == structs . IngressGateway {
2020-07-10 00:56:43 +00:00
err := updateGatewayServices ( tx , idx , conf , conf . GetEnterpriseMeta ( ) )
2020-05-08 18:24:33 +00:00
if err != nil {
return fmt . Errorf ( "failed to associate services to gateway: %v" , err )
}
}
// 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 )
}
if err := indexUpdateMaxTxn ( tx , idx , configTableName ) ; err != nil {
return fmt . Errorf ( "failed updating index: %v" , err )
}
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.
2020-08-11 20:31:23 +00:00
func validateProposedConfigEntryInGraph (
tx ReadTxn ,
2019-07-01 20:23:36 +00:00
kind , name string ,
2019-07-02 16:01:17 +00:00
next structs . ConfigEntry ,
2020-01-24 15:04:58 +00:00
entMeta * structs . EnterpriseMeta ,
2019-07-01 20:23:36 +00:00
) error {
2020-01-24 15:04:58 +00:00
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 :
2020-03-31 16:59:10 +00:00
case structs . IngressGateway :
2020-07-10 00:56:43 +00:00
err := checkGatewayClash ( tx , name , structs . IngressGateway , structs . TerminatingGateway , entMeta )
2020-04-16 21:00:48 +00:00
if err != nil {
return err
}
2020-03-31 19:27:32 +00:00
case structs . TerminatingGateway :
2020-07-10 00:56:43 +00:00
err := checkGatewayClash ( tx , name , structs . TerminatingGateway , structs . IngressGateway , entMeta )
2020-04-16 21:00:48 +00:00
if err != nil {
return err
}
2019-07-01 20:23:36 +00:00
default :
return fmt . Errorf ( "unhandled kind %q during validation of %q" , kind , name )
}
2019-07-02 16:01:17 +00:00
2020-08-11 20:31:23 +00:00
return validateProposedConfigEntryInServiceGraph ( tx , kind , name , next , validateAllChains , entMeta )
2019-07-02 16:01:17 +00:00
}
2020-07-10 00:56:43 +00:00
func checkGatewayClash (
2020-08-11 20:31:23 +00:00
tx ReadTxn ,
2020-04-16 21:00:48 +00:00
name , selfKind , otherKind string ,
entMeta * structs . EnterpriseMeta ,
) error {
2020-07-10 00:56:43 +00:00
_ , entry , err := configEntryTxn ( tx , nil , otherKind , name , entMeta )
2020-04-16 21:00:48 +00:00
if err != nil {
return err
}
if entry != nil {
return fmt . Errorf ( "cannot create a %q config entry with name %q, " +
"a %q config entry with that name already exists" , selfKind , name , otherKind )
}
return nil
}
2019-07-02 16:01:17 +00:00
var serviceGraphKinds = [ ] string {
structs . ServiceRouter ,
structs . ServiceSplitter ,
structs . ServiceResolver ,
2019-07-01 20:23:36 +00:00
}
2020-08-11 20:31:23 +00:00
func validateProposedConfigEntryInServiceGraph (
tx ReadTxn ,
2019-07-01 20:23:36 +00:00
kind , name string ,
2019-07-02 16:01:17 +00:00
next structs . ConfigEntry ,
validateAllChains bool ,
2020-01-24 15:04:58 +00:00
entMeta * structs . EnterpriseMeta ,
2019-07-01 20:23:36 +00:00
) error {
// Collect all of the chains that could be affected by this change
// including our own.
2020-08-12 16:19:20 +00:00
var (
checkChains = make ( map [ structs . ServiceID ] struct { } )
checkIngress [ ] * structs . IngressGatewayConfigEntry
enforceIngressProtocolsMatch bool
)
2019-07-02 16:01:17 +00:00
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 {
2020-07-10 00:56:43 +00:00
_ , entries , err := configEntriesByKindTxn ( tx , nil , kind , structs . WildcardEnterpriseMeta ( ) )
2019-07-02 16:01:17 +00:00
if err != nil {
return err
}
for _ , entry := range entries {
2020-01-24 15:04:58 +00:00
checkChains [ structs . NewServiceID ( entry . GetName ( ) , entry . GetEnterpriseMeta ( ) ) ] = struct { } { }
2019-07-02 16:01:17 +00:00
}
}
2020-08-12 16:19:20 +00:00
_ , entries , err := configEntriesByKindTxn ( tx , nil , structs . IngressGateway , structs . WildcardEnterpriseMeta ( ) )
if err != nil {
return err
}
for _ , entry := range entries {
ingress , ok := entry . ( * structs . IngressGatewayConfigEntry )
if ! ok {
return fmt . Errorf ( "type %T is not an ingress gateway config entry" , entry )
}
checkIngress = append ( checkIngress , ingress )
}
} else if kind == structs . IngressGateway {
// Checking an ingress pointing to multiple chains.
// This is the case for deleting a config entry
if next == nil {
return nil
}
ingress , ok := next . ( * structs . IngressGatewayConfigEntry )
if ! ok {
return fmt . Errorf ( "type %T is not an ingress gateway config entry" , next )
}
checkIngress = append ( checkIngress , ingress )
// When editing an ingress-gateway directly we are stricter about
// validating the protocol equivalence.
enforceIngressProtocolsMatch = true
2019-07-02 16:01:17 +00:00
} else {
// Must be a single chain.
2019-07-01 20:23:36 +00:00
2020-01-24 15:04:58 +00:00
sid := structs . NewServiceID ( name , entMeta )
checkChains [ sid ] = struct { } { }
2019-07-02 16:01:17 +00:00
2020-01-24 15:04:58 +00:00
iter , err := tx . Get ( configTableName , "link" , sid )
2020-06-23 17:18:22 +00:00
if err != nil {
return err
}
2019-07-02 16:01:17 +00:00
for raw := iter . Next ( ) ; raw != nil ; raw = iter . Next ( ) {
entry := raw . ( structs . ConfigEntry )
2020-08-12 16:19:20 +00:00
switch entry . GetKind ( ) {
case structs . ServiceRouter , structs . ServiceSplitter , structs . ServiceResolver :
svcID := structs . NewServiceID ( entry . GetName ( ) , entry . GetEnterpriseMeta ( ) )
checkChains [ svcID ] = struct { } { }
case structs . IngressGateway :
ingress , ok := entry . ( * structs . IngressGatewayConfigEntry )
if ! ok {
return fmt . Errorf ( "type %T is not an ingress gateway config entry" , entry )
}
checkIngress = append ( checkIngress , ingress )
}
}
}
// Ensure if any ingress is affected that we fetch all of the chains needed
// to fully validate that ingress.
for _ , ingress := range checkIngress {
for _ , svcID := range ingress . ListRelatedServices ( ) {
checkChains [ svcID ] = struct { } { }
2019-07-02 16:01:17 +00:00
}
2019-07-01 20:23:36 +00:00
}
overrides := map [ structs . ConfigEntryKindName ] structs . ConfigEntry {
2020-09-02 15:47:19 +00:00
structs . NewConfigEntryKindName ( kind , name , entMeta ) : next ,
2019-07-01 20:23:36 +00:00
}
2020-08-12 16:19:20 +00:00
var (
svcProtocols = make ( map [ structs . ServiceID ] string )
svcTopNodeType = make ( map [ structs . ServiceID ] string )
)
2020-06-16 17:19:31 +00:00
for chain := range checkChains {
2020-08-11 20:31:23 +00:00
protocol , topNode , err := testCompileDiscoveryChain ( tx , chain . ID , overrides , & chain . EnterpriseMeta )
2020-08-12 16:19:20 +00:00
if err != nil {
2019-07-01 20:23:36 +00:00
return err
}
2020-08-12 16:19:20 +00:00
svcProtocols [ chain ] = protocol
svcTopNodeType [ chain ] = topNode . Type
}
// Now validate all of our ingress gateways.
for _ , e := range checkIngress {
for _ , listener := range e . Listeners {
expectedProto := listener . Protocol
for _ , service := range listener . Services {
if service . Name == structs . WildcardSpecifier {
continue
}
svcID := structs . NewServiceID ( service . Name , & service . EnterpriseMeta )
svcProto := svcProtocols [ svcID ]
if svcProto != expectedProto {
// The only time an ingress gateway and its upstreams can
// have differing protocols is when:
//
// 1. ingress is tcp and the target is not-tcp
// AND
// 2. the disco chain has a resolver as the top node
topNodeType := svcTopNodeType [ svcID ]
if enforceIngressProtocolsMatch ||
( expectedProto != "tcp" ) ||
( expectedProto == "tcp" && topNodeType != structs . DiscoveryGraphNodeTypeResolver ) {
return fmt . Errorf (
"service %q has protocol %q, which does not match defined listener protocol %q" ,
svcID . String ( ) ,
svcProto ,
expectedProto ,
)
}
}
}
}
2019-07-01 20:23:36 +00:00
}
return nil
}
2020-08-12 16:19:20 +00:00
// testCompileDiscoveryChain speculatively compiles a discovery chain with
// pending modifications to see if it would be valid. Also returns the computed
// protocol and topmost discovery chain node.
2020-08-11 20:31:23 +00:00
func testCompileDiscoveryChain (
tx ReadTxn ,
2019-07-02 16:01:17 +00:00
chainName string ,
overrides map [ structs . ConfigEntryKindName ] structs . ConfigEntry ,
2020-01-24 15:04:58 +00:00
entMeta * structs . EnterpriseMeta ,
2020-08-12 16:19:20 +00:00
) ( string , * structs . DiscoveryGraphNode , error ) {
2020-08-11 20:31:23 +00:00
_ , speculativeEntries , err := readDiscoveryChainConfigEntriesTxn ( tx , nil , chainName , overrides , entMeta )
2019-07-02 16:01:17 +00:00
if err != nil {
2020-08-12 16:19:20 +00:00
return "" , nil , err
2019-07-02 16:01:17 +00:00
}
// Note we use an arbitrary namespace and datacenter as those would not
// currently affect the graph compilation in ways that matter here.
2019-08-05 18:30:35 +00:00
//
2019-08-19 18:03:03 +00:00
// TODO(rb): we should thread a better value than "dc1" and the throwaway trust domain down here as that is going to sometimes show up in user facing errors
2019-07-02 16:01:17 +00:00
req := discoverychain . CompileRequest {
2019-08-19 18:03:03 +00:00
ServiceName : chainName ,
2020-01-24 15:04:58 +00:00
EvaluateInNamespace : entMeta . NamespaceOrDefault ( ) ,
2019-08-19 18:03:03 +00:00
EvaluateInDatacenter : "dc1" ,
EvaluateInTrustDomain : "b6fc9da3-03d4-4b5a-9134-c045e9b20152.consul" ,
UseInDatacenter : "dc1" ,
Entries : speculativeEntries ,
2019-07-02 16:01:17 +00:00
}
2020-08-12 16:19:20 +00:00
chain , err := discoverychain . Compile ( req )
if err != nil {
return "" , nil , err
}
return chain . Protocol , chain . Nodes [ chain . StartNode ] , nil
2019-07-02 16:01:17 +00:00
}
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 ,
2020-01-24 15:04:58 +00:00
entMeta * structs . EnterpriseMeta ,
2019-07-01 20:23:36 +00:00
) ( uint64 , * structs . DiscoveryChainConfigEntries , error ) {
2020-01-24 15:04:58 +00:00
return s . readDiscoveryChainConfigEntries ( ws , serviceName , nil , entMeta )
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.
//
// 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 ,
2020-01-24 15:04:58 +00:00
entMeta * structs . EnterpriseMeta ,
2019-07-01 20:23:36 +00:00
) ( uint64 , * structs . DiscoveryChainConfigEntries , error ) {
tx := s . db . Txn ( false )
defer tx . Abort ( )
2020-08-11 20:31:23 +00:00
return readDiscoveryChainConfigEntriesTxn ( tx , ws , serviceName , overrides , entMeta )
2019-07-01 20:23:36 +00:00
}
2020-08-11 20:31:23 +00:00
func readDiscoveryChainConfigEntriesTxn (
tx ReadTxn ,
2019-07-01 20:23:36 +00:00
ws memdb . WatchSet ,
serviceName string ,
overrides map [ structs . ConfigEntryKindName ] structs . ConfigEntry ,
2020-01-24 15:04:58 +00:00
entMeta * structs . EnterpriseMeta ,
2019-07-01 20:23:36 +00:00
) ( 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 (
2020-01-24 15:04:58 +00:00
todoSplitters = make ( map [ structs . ServiceID ] struct { } )
todoResolvers = make ( map [ structs . ServiceID ] struct { } )
todoDefaults = make ( map [ structs . ServiceID ] struct { } )
2019-07-01 20:23:36 +00:00
)
2020-01-24 15:04:58 +00:00
sid := structs . NewServiceID ( serviceName , entMeta )
2019-07-02 16:01:17 +00:00
// Grab the proxy defaults if they exist.
2020-08-11 20:31:23 +00:00
idx , proxy , err := getProxyConfigEntryTxn ( tx , ws , structs . ProxyConfigGlobal , overrides , structs . DefaultEnterpriseMeta ( ) )
2019-07-02 16:01:17 +00:00
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.
2020-01-24 15:04:58 +00:00
todoDefaults [ sid ] = struct { } { }
2019-07-01 20:23:36 +00:00
// first fetch the router, of which we only collect 1 per chain eval
2020-08-11 20:31:23 +00:00
_ , router , err := getRouterConfigEntryTxn ( tx , ws , serviceName , overrides , entMeta )
2019-07-01 20:23:36 +00:00
if err != nil {
return 0 , nil , err
} else if router != nil {
2020-01-24 15:04:58 +00:00
res . Routers [ sid ] = router
2019-07-01 20:23:36 +00:00
}
if router != nil {
for _ , svc := range router . ListRelatedServices ( ) {
todoSplitters [ svc ] = struct { } { }
}
} else {
// Next hop in the chain is the splitter.
2020-01-24 15:04:58 +00:00
todoSplitters [ sid ] = struct { } { }
2019-07-01 20:23:36 +00:00
}
for {
2020-01-24 15:04:58 +00:00
splitID , ok := anyKey ( todoSplitters )
2019-07-01 20:23:36 +00:00
if ! ok {
break
}
2020-01-24 15:04:58 +00:00
delete ( todoSplitters , splitID )
2019-07-01 20:23:36 +00:00
2020-01-24 15:04:58 +00:00
if _ , ok := res . Splitters [ splitID ] ; ok {
2019-07-01 20:23:36 +00:00
continue // already fetched
}
// Yes, even for splitters.
2020-01-24 15:04:58 +00:00
todoDefaults [ splitID ] = struct { } { }
2019-07-01 20:23:36 +00:00
2020-08-11 20:31:23 +00:00
_ , splitter , err := getSplitterConfigEntryTxn ( tx , ws , splitID . ID , overrides , & splitID . EnterpriseMeta )
2019-07-01 20:23:36 +00:00
if err != nil {
return 0 , nil , err
}
if splitter == nil {
2020-01-24 15:04:58 +00:00
res . Splitters [ splitID ] = nil
2019-07-01 20:23:36 +00:00
// Next hop in the chain is the resolver.
2020-01-24 15:04:58 +00:00
todoResolvers [ splitID ] = struct { } { }
2019-07-01 20:23:36 +00:00
continue
}
2020-01-24 15:04:58 +00:00
res . Splitters [ splitID ] = splitter
2019-07-01 20:23:36 +00:00
2020-01-24 15:04:58 +00:00
todoResolvers [ splitID ] = struct { } { }
2019-07-01 20:23:36 +00:00
for _ , svc := range splitter . ListRelatedServices ( ) {
// If there is no splitter, this will end up adding a resolver
// after another iteration.
todoSplitters [ svc ] = struct { } { }
}
}
for {
2020-01-24 15:04:58 +00:00
resolverID , ok := anyKey ( todoResolvers )
2019-07-01 20:23:36 +00:00
if ! ok {
break
}
2020-01-24 15:04:58 +00:00
delete ( todoResolvers , resolverID )
2019-07-01 20:23:36 +00:00
2020-01-24 15:04:58 +00:00
if _ , ok := res . Resolvers [ resolverID ] ; ok {
2019-07-01 20:23:36 +00:00
continue // already fetched
}
// And resolvers, too.
2020-01-24 15:04:58 +00:00
todoDefaults [ resolverID ] = struct { } { }
2019-07-01 20:23:36 +00:00
2020-08-11 20:31:23 +00:00
_ , resolver , err := getResolverConfigEntryTxn ( tx , ws , resolverID . ID , overrides , & resolverID . EnterpriseMeta )
2019-07-01 20:23:36 +00:00
if err != nil {
return 0 , nil , err
}
if resolver == nil {
2020-01-24 15:04:58 +00:00
res . Resolvers [ resolverID ] = nil
2019-07-01 20:23:36 +00:00
continue
}
2020-01-24 15:04:58 +00:00
res . Resolvers [ resolverID ] = resolver
2019-07-01 20:23:36 +00:00
for _ , svc := range resolver . ListRelatedServices ( ) {
todoResolvers [ svc ] = struct { } { }
}
}
for {
2020-01-24 15:04:58 +00:00
svcID , ok := anyKey ( todoDefaults )
2019-07-01 20:23:36 +00:00
if ! ok {
break
}
2020-01-24 15:04:58 +00:00
delete ( todoDefaults , svcID )
2019-07-01 20:23:36 +00:00
2020-01-24 15:04:58 +00:00
if _ , ok := res . Services [ svcID ] ; ok {
2019-07-01 20:23:36 +00:00
continue // already fetched
}
2020-08-11 20:31:23 +00:00
_ , entry , err := getServiceConfigEntryTxn ( tx , ws , svcID . ID , overrides , & svcID . EnterpriseMeta )
2019-07-01 20:23:36 +00:00
if err != nil {
return 0 , nil , err
}
if entry == nil {
2020-01-24 15:04:58 +00:00
res . Services [ svcID ] = nil
2019-07-01 20:23:36 +00:00
continue
}
2020-01-24 15:04:58 +00:00
res . Services [ svcID ] = entry
2019-07-01 20:23:36 +00:00
}
// Strip nils now that they are no longer necessary.
2020-01-24 15:04:58 +00:00
for sid , entry := range res . Routers {
2019-07-01 20:23:36 +00:00
if entry == nil {
2020-01-24 15:04:58 +00:00
delete ( res . Routers , sid )
2019-07-01 20:23:36 +00:00
}
}
2020-01-24 15:04:58 +00:00
for sid , entry := range res . Splitters {
2019-07-01 20:23:36 +00:00
if entry == nil {
2020-01-24 15:04:58 +00:00
delete ( res . Splitters , sid )
2019-07-01 20:23:36 +00:00
}
}
2020-01-24 15:04:58 +00:00
for sid , entry := range res . Resolvers {
2019-07-01 20:23:36 +00:00
if entry == nil {
2020-01-24 15:04:58 +00:00
delete ( res . Resolvers , sid )
2019-07-01 20:23:36 +00:00
}
}
2020-01-24 15:04:58 +00:00
for sid , entry := range res . Services {
2019-07-01 20:23:36 +00:00
if entry == nil {
2020-01-24 15:04:58 +00:00
delete ( res . Services , sid )
2019-07-01 20:23:36 +00:00
}
}
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.
2020-01-24 15:04:58 +00:00
func anyKey ( m map [ structs . ServiceID ] struct { } ) ( structs . ServiceID , bool ) {
2019-07-01 20:23:36 +00:00
if len ( m ) == 0 {
2020-01-24 15:04:58 +00:00
return structs . ServiceID { } , false
2019-07-01 20:23:36 +00:00
}
2020-06-16 17:19:31 +00:00
for k := range m {
2019-07-01 20:23:36 +00:00
return k , true
}
2020-01-24 15:04:58 +00:00
return structs . ServiceID { } , false
2019-07-01 20:23:36 +00:00
}
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.
2020-08-11 20:31:23 +00:00
func getProxyConfigEntryTxn (
tx ReadTxn ,
2019-07-02 16:01:17 +00:00
ws memdb . WatchSet ,
name string ,
overrides map [ structs . ConfigEntryKindName ] structs . ConfigEntry ,
2020-01-24 15:04:58 +00:00
entMeta * structs . EnterpriseMeta ,
2019-07-02 16:01:17 +00:00
) ( uint64 , * structs . ProxyConfigEntry , error ) {
2020-07-10 00:56:43 +00:00
idx , entry , err := configEntryWithOverridesTxn ( tx , ws , structs . ProxyDefaults , name , overrides , entMeta )
2019-07-02 16:01:17 +00:00
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.
2020-08-11 20:31:23 +00:00
func getServiceConfigEntryTxn (
tx ReadTxn ,
2019-07-01 20:23:36 +00:00
ws memdb . WatchSet ,
serviceName string ,
overrides map [ structs . ConfigEntryKindName ] structs . ConfigEntry ,
2020-01-24 15:04:58 +00:00
entMeta * structs . EnterpriseMeta ,
2019-07-01 20:23:36 +00:00
) ( uint64 , * structs . ServiceConfigEntry , error ) {
2020-07-10 00:56:43 +00:00
idx , entry , err := configEntryWithOverridesTxn ( tx , ws , structs . ServiceDefaults , serviceName , overrides , entMeta )
2019-07-01 20:23:36 +00:00
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.
2020-08-11 20:31:23 +00:00
func getRouterConfigEntryTxn (
tx ReadTxn ,
2019-07-01 20:23:36 +00:00
ws memdb . WatchSet ,
serviceName string ,
overrides map [ structs . ConfigEntryKindName ] structs . ConfigEntry ,
2020-01-24 15:04:58 +00:00
entMeta * structs . EnterpriseMeta ,
2019-07-01 20:23:36 +00:00
) ( uint64 , * structs . ServiceRouterConfigEntry , error ) {
2020-07-10 00:56:43 +00:00
idx , entry , err := configEntryWithOverridesTxn ( tx , ws , structs . ServiceRouter , serviceName , overrides , entMeta )
2019-07-01 20:23:36 +00:00
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.
2020-08-11 20:31:23 +00:00
func getSplitterConfigEntryTxn (
tx ReadTxn ,
2019-07-01 20:23:36 +00:00
ws memdb . WatchSet ,
serviceName string ,
overrides map [ structs . ConfigEntryKindName ] structs . ConfigEntry ,
2020-01-24 15:04:58 +00:00
entMeta * structs . EnterpriseMeta ,
2019-07-01 20:23:36 +00:00
) ( uint64 , * structs . ServiceSplitterConfigEntry , error ) {
2020-07-10 00:56:43 +00:00
idx , entry , err := configEntryWithOverridesTxn ( tx , ws , structs . ServiceSplitter , serviceName , overrides , entMeta )
2019-07-01 20:23:36 +00:00
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.
2020-08-11 20:31:23 +00:00
func getResolverConfigEntryTxn (
tx ReadTxn ,
2019-07-01 20:23:36 +00:00
ws memdb . WatchSet ,
serviceName string ,
overrides map [ structs . ConfigEntryKindName ] structs . ConfigEntry ,
2020-01-24 15:04:58 +00:00
entMeta * structs . EnterpriseMeta ,
2019-07-01 20:23:36 +00:00
) ( uint64 , * structs . ServiceResolverConfigEntry , error ) {
2020-07-10 00:56:43 +00:00
idx , entry , err := configEntryWithOverridesTxn ( tx , ws , structs . ServiceResolver , serviceName , overrides , entMeta )
2019-07-01 20:23:36 +00:00
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
}
2020-07-10 00:56:43 +00:00
func configEntryWithOverridesTxn (
2020-08-11 20:31:23 +00:00
tx ReadTxn ,
2019-07-01 20:23:36 +00:00
ws memdb . WatchSet ,
kind string ,
name string ,
overrides map [ structs . ConfigEntryKindName ] structs . ConfigEntry ,
2020-01-24 15:04:58 +00:00
entMeta * structs . EnterpriseMeta ,
2019-07-01 20:23:36 +00:00
) ( uint64 , structs . ConfigEntry , error ) {
if len ( overrides ) > 0 {
2020-09-02 15:47:19 +00:00
kn := structs . NewConfigEntryKindName ( kind , name , entMeta )
entry , ok := overrides [ kn ]
2019-07-01 20:23:36 +00:00
if ok {
return 0 , entry , nil // a nil entry implies it should act like it is erased
}
}
2020-07-10 00:56:43 +00:00
return configEntryTxn ( tx , ws , kind , name , entMeta )
2019-07-01 20:23:36 +00:00
}
2020-04-23 23:16:04 +00:00
// protocolForService returns the service graph protocol associated to the
// provided service, checking all relevant config entries.
2020-07-10 00:56:43 +00:00
func protocolForService (
2020-08-11 20:31:23 +00:00
tx ReadTxn ,
2020-04-23 23:16:04 +00:00
ws memdb . WatchSet ,
2020-06-12 14:57:41 +00:00
svc structs . ServiceName ,
2020-04-23 23:16:04 +00:00
) ( uint64 , string , error ) {
// Get the global proxy defaults (for default protocol)
2020-07-10 00:56:43 +00:00
maxIdx , proxyConfig , err := configEntryTxn ( tx , ws , structs . ProxyDefaults , structs . ProxyConfigGlobal , structs . DefaultEnterpriseMeta ( ) )
2020-04-23 23:16:04 +00:00
if err != nil {
return 0 , "" , err
}
2020-07-10 00:56:43 +00:00
idx , serviceDefaults , err := configEntryTxn ( tx , ws , structs . ServiceDefaults , svc . Name , & svc . EnterpriseMeta )
2020-04-23 23:16:04 +00:00
if err != nil {
return 0 , "" , err
}
maxIdx = lib . MaxUint64 ( maxIdx , idx )
entries := structs . NewDiscoveryChainConfigEntries ( )
if proxyConfig != nil {
entries . AddEntries ( proxyConfig )
}
if serviceDefaults != nil {
entries . AddEntries ( serviceDefaults )
}
req := discoverychain . CompileRequest {
2020-06-12 14:57:41 +00:00
ServiceName : svc . Name ,
2020-04-23 23:16:04 +00:00
EvaluateInNamespace : svc . NamespaceOrDefault ( ) ,
EvaluateInDatacenter : "dc1" ,
// Use a dummy trust domain since that won't affect the protocol here.
EvaluateInTrustDomain : "b6fc9da3-03d4-4b5a-9134-c045e9b20152.consul" ,
UseInDatacenter : "dc1" ,
Entries : entries ,
}
chain , err := discoverychain . Compile ( req )
if err != nil {
return 0 , "" , err
}
return maxIdx , chain . Protocol , nil
}