Update assumptions around exported-service config
Given that the exported-services config entry can use wildcards, the precedence for wildcards is handled as with intentions. The most exact match is the match that applies for any given service. We do not take the union of all that apply. Another update that was made was to reflect that only one exported-services config entry applies to any given service in a partition. This is a pre-existing constraint that gets enforced by the Normalize() method on that config entry type.
This commit is contained in:
parent
6ef38eaea7
commit
073c9e3a91
|
@ -603,6 +603,10 @@ func validateProposedConfigEntryInServiceGraph(
|
||||||
wildcardEntMeta := kindName.WithWildcardNamespace()
|
wildcardEntMeta := kindName.WithWildcardNamespace()
|
||||||
|
|
||||||
switch kindName.Kind {
|
switch kindName.Kind {
|
||||||
|
case structs.ExportedServices, structs.MeshConfig:
|
||||||
|
// Exported services and mesh config do not influence discovery chains.
|
||||||
|
return nil
|
||||||
|
|
||||||
case structs.ProxyDefaults:
|
case structs.ProxyDefaults:
|
||||||
// Check anything that has a discovery chain entry. In the future we could
|
// Check anything that has a discovery chain entry. In the future we could
|
||||||
// somehow omit the ones that have a default protocol configured.
|
// somehow omit the ones that have a default protocol configured.
|
||||||
|
@ -1414,52 +1418,6 @@ func configEntryWithOverridesTxn(
|
||||||
return configEntryTxn(tx, ws, kind, name, entMeta)
|
return configEntryTxn(tx, ws, kind, name, entMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getExportedServicesConfigEntriesTxn fetches exported-service config entries and
|
|
||||||
// filters their exported services to only those that match serviceName and entMeta.
|
|
||||||
// Because the resulting config entries may have had their exported services modified,
|
|
||||||
// they *should not* be used in subsequent writes.
|
|
||||||
func getExportedServiceConfigEntriesTxn(
|
|
||||||
tx ReadTxn,
|
|
||||||
ws memdb.WatchSet,
|
|
||||||
serviceName string,
|
|
||||||
entMeta *acl.EnterpriseMeta,
|
|
||||||
) (uint64, []*structs.ExportedServicesConfigEntry, error) {
|
|
||||||
var exportedServicesEntries []*structs.ExportedServicesConfigEntry
|
|
||||||
// slice of names to match config entries against
|
|
||||||
matchCandidates := getExportedServicesMatchServiceNames(serviceName, entMeta)
|
|
||||||
// matcher func generator for currying the matcher func over EnterpriseMeta values
|
|
||||||
// from the associated config entry
|
|
||||||
matchFunc := func(matchMeta *acl.EnterpriseMeta) func(structs.ExportedService) bool {
|
|
||||||
return func(exportedService structs.ExportedService) bool {
|
|
||||||
matchSvcName := structs.NewServiceName(exportedService.Name, matchMeta)
|
|
||||||
for _, candidate := range matchCandidates {
|
|
||||||
if candidate.Matches(matchSvcName) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
idx, entries, err := configEntriesByKindTxn(tx, ws, structs.ExportedServices, entMeta)
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
for _, entry := range entries {
|
|
||||||
esEntry, ok := entry.(*structs.ExportedServicesConfigEntry)
|
|
||||||
if !ok {
|
|
||||||
return 0, nil, fmt.Errorf("type %T is not a %s config entry", esEntry, structs.ExportedServices)
|
|
||||||
}
|
|
||||||
// get a copy of the config entry with Services filtered to match serviceName
|
|
||||||
newEntry := filterExportedServices(esEntry, matchFunc(entry.GetEnterpriseMeta()))
|
|
||||||
// the filter will return a new entry, so checking to see if its services is empty says that there
|
|
||||||
// were matches and that we should include it in the results
|
|
||||||
if len(newEntry.Services) > 0 {
|
|
||||||
exportedServicesEntries = append(exportedServicesEntries, newEntry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return idx, exportedServicesEntries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// protocolForService returns the service graph protocol associated to the
|
// protocolForService returns the service graph protocol associated to the
|
||||||
// provided service, checking all relevant config entries.
|
// provided service, checking all relevant config entries.
|
||||||
func protocolForService(
|
func protocolForService(
|
||||||
|
@ -1502,23 +1460,6 @@ func protocolForService(
|
||||||
return maxIdx, chain.Protocol, nil
|
return maxIdx, chain.Protocol, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterExportedServices returns the slice of ExportedService that matc ffor matching service names
|
|
||||||
// returning a copy of entry with only the services that match one of the
|
|
||||||
// services in candidates.
|
|
||||||
func filterExportedServices(
|
|
||||||
entry *structs.ExportedServicesConfigEntry,
|
|
||||||
testFunc func(structs.ExportedService) bool,
|
|
||||||
) *structs.ExportedServicesConfigEntry {
|
|
||||||
newEntry := *entry
|
|
||||||
newEntry.Services = []structs.ExportedService{}
|
|
||||||
for _, ceSvc := range entry.Services {
|
|
||||||
if testFunc(ceSvc) {
|
|
||||||
newEntry.Services = append(newEntry.Services, ceSvc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &newEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConfigEntryQuery(c structs.ConfigEntry) configentry.KindName {
|
func newConfigEntryQuery(c structs.ConfigEntry) configentry.KindName {
|
||||||
return configentry.NewKindName(c.GetKind(), c.GetName(), c.GetEnterpriseMeta())
|
return configentry.NewKindName(c.GetKind(), c.GetName(), c.GetEnterpriseMeta())
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,120 +40,124 @@ func testIndexerTableConfigEntries() map[string]indexerTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStore_ExportedServices(t *testing.T) {
|
func TestStore_peersForService(t *testing.T) {
|
||||||
|
queryName := "foo"
|
||||||
|
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
name string
|
name string
|
||||||
write []structs.ConfigEntry
|
write structs.ConfigEntry
|
||||||
query string
|
expect []string
|
||||||
expect []*structs.ExportedServicesConfigEntry
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cases := []testCase{
|
cases := []testCase{
|
||||||
{
|
{
|
||||||
name: "empty everything",
|
name: "empty everything",
|
||||||
write: []structs.ConfigEntry{},
|
expect: nil,
|
||||||
query: "foo",
|
|
||||||
expect: []*structs.ExportedServicesConfigEntry{},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no matching exported services",
|
name: "service is not exported",
|
||||||
write: []structs.ConfigEntry{
|
write: &structs.ExportedServicesConfigEntry{
|
||||||
&structs.ProxyConfigEntry{Name: "foo"},
|
Name: "default",
|
||||||
&structs.ProxyConfigEntry{Name: "bar"},
|
Services: []structs.ExportedService{
|
||||||
&structs.ExportedServicesConfigEntry{
|
{
|
||||||
Name: "baz",
|
Name: "not-" + queryName,
|
||||||
Services: []structs.ExportedService{
|
Consumers: []structs.ServiceConsumer{
|
||||||
{Name: "baz"},
|
{
|
||||||
|
PeerName: "zip",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
query: "foo",
|
expect: nil,
|
||||||
expect: []*structs.ExportedServicesConfigEntry{},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "exact match service name",
|
name: "wildcard name matches",
|
||||||
write: []structs.ConfigEntry{
|
write: &structs.ExportedServicesConfigEntry{
|
||||||
&structs.ExportedServicesConfigEntry{
|
Name: "default",
|
||||||
Name: "foo",
|
Services: []structs.ExportedService{
|
||||||
Services: []structs.ExportedService{
|
{
|
||||||
{Name: "foo"},
|
Name: "not-" + queryName,
|
||||||
|
Consumers: []structs.ServiceConsumer{
|
||||||
|
{
|
||||||
|
PeerName: "zip",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
&structs.ExportedServicesConfigEntry{
|
Name: structs.WildcardSpecifier,
|
||||||
Name: "bar",
|
Consumers: []structs.ServiceConsumer{
|
||||||
Services: []structs.ExportedService{
|
{
|
||||||
{Name: "bar"},
|
PeerName: "bar",
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
},
|
PeerName: "baz",
|
||||||
query: "bar",
|
},
|
||||||
expect: []*structs.ExportedServicesConfigEntry{
|
},
|
||||||
{
|
|
||||||
Name: "bar",
|
|
||||||
Services: []structs.ExportedService{
|
|
||||||
{Name: "bar"},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expect: []string{"bar", "baz"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "wildcard match on service name",
|
name: "exact name takes precedence over wildcard",
|
||||||
write: []structs.ConfigEntry{
|
write: &structs.ExportedServicesConfigEntry{
|
||||||
&structs.ExportedServicesConfigEntry{
|
Name: "default",
|
||||||
Name: "foo",
|
Services: []structs.ExportedService{
|
||||||
Services: []structs.ExportedService{
|
{
|
||||||
{Name: "foo"},
|
Name: queryName,
|
||||||
|
Consumers: []structs.ServiceConsumer{
|
||||||
|
{
|
||||||
|
PeerName: "baz",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
&structs.ExportedServicesConfigEntry{
|
Name: structs.WildcardSpecifier,
|
||||||
Name: "wildcard",
|
Consumers: []structs.ServiceConsumer{
|
||||||
Services: []structs.ExportedService{
|
{
|
||||||
{Name: structs.WildcardSpecifier},
|
PeerName: "zip",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
query: "foo",
|
|
||||||
expect: []*structs.ExportedServicesConfigEntry{
|
|
||||||
{
|
|
||||||
Name: "foo",
|
|
||||||
Services: []structs.ExportedService{
|
|
||||||
{Name: "foo"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "wildcard",
|
|
||||||
Services: []structs.ExportedService{
|
|
||||||
{Name: structs.WildcardSpecifier},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
expect: []string{"baz"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
s := testStateStore(t)
|
s := testStateStore(t)
|
||||||
|
var lastIdx uint64
|
||||||
|
|
||||||
// Write the entries.
|
// Write the entry.
|
||||||
for idx, entry := range tc.write {
|
if tc.write != nil {
|
||||||
require.NoError(t, s.EnsureConfigEntry(uint64(idx+1), entry))
|
require.NoError(t, tc.write.Normalize())
|
||||||
|
require.NoError(t, tc.write.Validate())
|
||||||
|
|
||||||
|
lastIdx++
|
||||||
|
require.NoError(t, s.EnsureConfigEntry(lastIdx, tc.write))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the entries back.
|
// Read the entries back.
|
||||||
tx := s.db.ReadTxn()
|
tx := s.db.ReadTxn()
|
||||||
defer tx.Abort()
|
defer tx.Abort()
|
||||||
idx, entries, err := getExportedServiceConfigEntriesTxn(tx, nil, tc.query, acl.DefaultEnterpriseMeta())
|
|
||||||
|
idx, peers, err := peersForServiceTxn(tx, nil, queryName, acl.DefaultEnterpriseMeta())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, uint64(len(tc.write)), idx)
|
|
||||||
|
// This is a little weird, but when there are no results, the index returned should be the max index for the
|
||||||
|
// config entries table so that the caller can watch for changes to it
|
||||||
|
if len(peers) == 0 {
|
||||||
|
require.Equal(t, maxIndexTxn(tx, tableConfigEntries), idx)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, lastIdx, idx)
|
||||||
|
}
|
||||||
|
|
||||||
// Verify the result.
|
// Verify the result.
|
||||||
require.Len(t, entries, len(tc.expect))
|
require.Len(t, peers, len(tc.expect))
|
||||||
for idx, got := range entries {
|
require.Equal(t, tc.expect, peers)
|
||||||
// ignore raft fields
|
|
||||||
got.ModifyIndex = 0
|
|
||||||
got.CreateIndex = 0
|
|
||||||
require.Equal(t, tc.expect[idx], got)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -439,35 +439,37 @@ func (s *Store) exportedServicesForPeerTxn(ws memdb.WatchSet, tx ReadTxn, peerin
|
||||||
// PeeringsForService returns the list of peerings that are associated with the service name provided in the query.
|
// PeeringsForService returns the list of peerings that are associated with the service name provided in the query.
|
||||||
// This is used to configure connect proxies for a given service. The result is generated by querying for exported
|
// This is used to configure connect proxies for a given service. The result is generated by querying for exported
|
||||||
// service config entries and filtering for those that match the given service.
|
// service config entries and filtering for those that match the given service.
|
||||||
|
//
|
||||||
// TODO(peering): this implementation does all of the work on read to materialize this list of peerings, we should explore
|
// TODO(peering): this implementation does all of the work on read to materialize this list of peerings, we should explore
|
||||||
// writing to a separate index that has service peerings prepared ahead of time should this become a performance bottleneck.
|
// writing to a separate index that has service peerings prepared ahead of time should this become a performance bottleneck.
|
||||||
func (s *Store) PeeringsForService(ws memdb.WatchSet, serviceName string, entMeta acl.EnterpriseMeta) (uint64, []*pbpeering.Peering, error) {
|
func (s *Store) PeeringsForService(ws memdb.WatchSet, serviceName string, entMeta acl.EnterpriseMeta) (uint64, []*pbpeering.Peering, error) {
|
||||||
tx := s.db.ReadTxn()
|
tx := s.db.ReadTxn()
|
||||||
defer tx.Abort()
|
defer tx.Abort()
|
||||||
|
|
||||||
// short-circuit if the service does not exist in the context of the query -- this prevents "leaking" services
|
// Short-circuit if the service does not exist in the context of the query -- this prevents "leaking" services
|
||||||
// when there are wildcard rules in place.
|
// when there are wildcard rules in place.
|
||||||
if svcIdx, svcExists, err := serviceExists(tx, ws, serviceName, &entMeta, ""); err != nil {
|
if svcIdx, svcExists, err := serviceExists(tx, ws, serviceName, &entMeta, ""); err != nil {
|
||||||
return 0, nil, fmt.Errorf("failed to check if service exists: %w", err)
|
return 0, nil, fmt.Errorf("failed to check if service exists: %w", err)
|
||||||
|
|
||||||
} else if !svcExists {
|
} else if !svcExists {
|
||||||
// if the service does not exist, return the max index for the services table so caller can watch for changes
|
// If the service does not exist, return the max index for the services table so caller can watch for changes.
|
||||||
return svcIdx, nil, nil
|
return svcIdx, nil, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
// config entries must be defined in the default namespace, so we only need the partition here
|
|
||||||
meta := structs.DefaultEnterpriseMetaInPartition(entMeta.PartitionOrDefault())
|
// Return the idx of the config entry so the caller can watch for changes.
|
||||||
// return the idx of the config entry that was last modified so caller can watch for changes
|
idx, peerNames, err := peersForServiceTxn(tx, ws, serviceName, &entMeta)
|
||||||
idx, peeredServices, err := readPeeredServicesFromConfigEntriesTxn(tx, ws, serviceName, meta)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, fmt.Errorf("failed to read peered services for service name: %w", err)
|
return 0, nil, fmt.Errorf("failed to read peers for service name %q: %w", serviceName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var peerings []*pbpeering.Peering
|
var peerings []*pbpeering.Peering
|
||||||
|
|
||||||
// lookup the peering for each matching peered service
|
// Lookup and return the peering corresponding to each name.
|
||||||
for _, peeredService := range peeredServices {
|
for _, name := range peerNames {
|
||||||
readQuery := Query{
|
readQuery := Query{
|
||||||
Value: peeredService.PeerName,
|
Value: name,
|
||||||
EnterpriseMeta: peeredService.Name.EnterpriseMeta,
|
EnterpriseMeta: *structs.NodeEnterpriseMetaInPartition(entMeta.PartitionOrDefault()),
|
||||||
}
|
}
|
||||||
_, peering, err := peeringReadTxn(tx, ws, readQuery)
|
_, peering, err := peeringReadTxn(tx, ws, readQuery)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -478,7 +480,6 @@ func (s *Store) PeeringsForService(ws memdb.WatchSet, serviceName string, entMet
|
||||||
}
|
}
|
||||||
peerings = append(peerings, peering)
|
peerings = append(peerings, peering)
|
||||||
}
|
}
|
||||||
// see note above about idx
|
|
||||||
return idx, peerings, nil
|
return idx, peerings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -597,50 +598,80 @@ func (r *Restore) PeeringTrustBundle(ptb *pbpeering.PeeringTrustBundle) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readPeeredServicesFromConfigEntriesTxn queries exported-service config entries to return peers for serviceName
|
// peersForServiceTxn returns the names of all peers that a service is exported to.
|
||||||
// in the form of a []structs.PeeredService.
|
func peersForServiceTxn(
|
||||||
func readPeeredServicesFromConfigEntriesTxn(
|
|
||||||
tx ReadTxn,
|
tx ReadTxn,
|
||||||
ws memdb.WatchSet,
|
ws memdb.WatchSet,
|
||||||
serviceName string,
|
serviceName string,
|
||||||
entMeta *acl.EnterpriseMeta,
|
entMeta *acl.EnterpriseMeta,
|
||||||
) (uint64, []structs.PeeredService, error) {
|
) (uint64, []string, error) {
|
||||||
var results []structs.PeeredService
|
// Exported service config entries are scoped to partitions so they are in the default namespace.
|
||||||
|
partitionMeta := structs.DefaultEnterpriseMetaInPartition(entMeta.PartitionOrDefault())
|
||||||
|
|
||||||
// Get all exported-service config entries for that have exports for serviceName. This assumes the result
|
idx, rawEntry, err := configEntryTxn(tx, ws, structs.ExportedServices, partitionMeta.PartitionOrDefault(), partitionMeta)
|
||||||
// has exported services filtered to only those matching serviceName so no futher filtering is needed.
|
|
||||||
idx, exportedServicesEntries, err := getExportedServiceConfigEntriesTxn(tx, ws, serviceName, entMeta)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
if rawEntry == nil {
|
||||||
|
return idx, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// dedupe results by peer name
|
entry, ok := rawEntry.(*structs.ExportedServicesConfigEntry)
|
||||||
resultSet := make(map[string]struct{})
|
if !ok {
|
||||||
// filter entries to only those that have a peer consumer defined
|
return 0, nil, fmt.Errorf("unexpected type %T for pbpeering.Peering index", rawEntry)
|
||||||
for _, entry := range exportedServicesEntries {
|
}
|
||||||
for _, service := range entry.Services {
|
|
||||||
// entries must have consumers
|
|
||||||
if service.Consumers == nil || len(service.Consumers) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, consumer := range service.Consumers {
|
|
||||||
// and consumers must have a peer
|
|
||||||
if consumer.PeerName == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// if we get here, we have a peer consumer, but we should dedupe peer names, so skip if it's already in the set
|
|
||||||
if _, ok := resultSet[consumer.PeerName]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we got here, we can add to the result set
|
var (
|
||||||
resultSet[consumer.PeerName] = struct{}{}
|
wildcardNamespaceIdx = -1
|
||||||
result := structs.PeeredService{
|
wildcardServiceIdx = -1
|
||||||
Name: structs.NewServiceName(serviceName, entry.GetEnterpriseMeta()),
|
exactMatchIdx = -1
|
||||||
PeerName: consumer.PeerName,
|
)
|
||||||
}
|
|
||||||
results = append(results, result)
|
// Ensure the metadata is defaulted since we make assertions against potentially empty values below.
|
||||||
}
|
// In OSS this is a no-op.
|
||||||
|
if entMeta == nil {
|
||||||
|
entMeta = acl.DefaultEnterpriseMeta()
|
||||||
|
}
|
||||||
|
entMeta.Normalize()
|
||||||
|
|
||||||
|
// Services can be exported via wildcards or by their exact name:
|
||||||
|
// Namespace: *, Service: *
|
||||||
|
// Namespace: Exact, Service: *
|
||||||
|
// Namespace: Exact, Service: Exact
|
||||||
|
for i, service := range entry.Services {
|
||||||
|
switch {
|
||||||
|
case service.Namespace == structs.WildcardSpecifier:
|
||||||
|
wildcardNamespaceIdx = i
|
||||||
|
|
||||||
|
case service.Name == structs.WildcardSpecifier && service.Namespace == entMeta.NamespaceOrEmpty():
|
||||||
|
wildcardServiceIdx = i
|
||||||
|
|
||||||
|
case service.Name == serviceName && service.Namespace == entMeta.NamespaceOrEmpty():
|
||||||
|
exactMatchIdx = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var results []string
|
||||||
|
|
||||||
|
// Prefer the exact match over the wildcard match. This matches how we handle intention precedence.
|
||||||
|
var targetIdx int
|
||||||
|
switch {
|
||||||
|
case exactMatchIdx >= 0:
|
||||||
|
targetIdx = exactMatchIdx
|
||||||
|
|
||||||
|
case wildcardServiceIdx >= 0:
|
||||||
|
targetIdx = wildcardServiceIdx
|
||||||
|
|
||||||
|
case wildcardNamespaceIdx >= 0:
|
||||||
|
targetIdx = wildcardNamespaceIdx
|
||||||
|
|
||||||
|
default:
|
||||||
|
return idx, results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range entry.Services[targetIdx].Consumers {
|
||||||
|
if c.PeerName != "" {
|
||||||
|
results = append(results, c.PeerName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return idx, results, nil
|
return idx, results, nil
|
||||||
|
|
|
@ -907,7 +907,7 @@ func TestStateStore_PeeringsForService(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
services []structs.ServiceName
|
services []structs.ServiceName
|
||||||
peerings []*pbpeering.Peering
|
peerings []*pbpeering.Peering
|
||||||
entries []*structs.ExportedServicesConfigEntry
|
entry *structs.ExportedServicesConfigEntry
|
||||||
query []string
|
query []string
|
||||||
expect [][]*pbpeering.Peering
|
expect [][]*pbpeering.Peering
|
||||||
expectIdx uint64
|
expectIdx uint64
|
||||||
|
@ -945,9 +945,10 @@ func TestStateStore_PeeringsForService(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the config entries.
|
// Write the config entries.
|
||||||
for _, entry := range tc.entries {
|
if tc.entry != nil {
|
||||||
lastIdx++
|
lastIdx++
|
||||||
require.NoError(t, s.EnsureConfigEntry(lastIdx, entry))
|
require.NoError(t, tc.entry.Normalize())
|
||||||
|
require.NoError(t, s.EnsureConfigEntry(lastIdx, tc.entry))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query for peers.
|
// Query for peers.
|
||||||
|
@ -976,7 +977,7 @@ func TestStateStore_PeeringsForService(t *testing.T) {
|
||||||
{Name: "foo"},
|
{Name: "foo"},
|
||||||
},
|
},
|
||||||
peerings: []*pbpeering.Peering{},
|
peerings: []*pbpeering.Peering{},
|
||||||
entries: []*structs.ExportedServicesConfigEntry{},
|
entry: nil,
|
||||||
query: []string{"foo"},
|
query: []string{"foo"},
|
||||||
expect: [][]*pbpeering.Peering{{}},
|
expect: [][]*pbpeering.Peering{{}},
|
||||||
},
|
},
|
||||||
|
@ -986,7 +987,7 @@ func TestStateStore_PeeringsForService(t *testing.T) {
|
||||||
{Name: "foo"},
|
{Name: "foo"},
|
||||||
},
|
},
|
||||||
peerings: []*pbpeering.Peering{},
|
peerings: []*pbpeering.Peering{},
|
||||||
entries: []*structs.ExportedServicesConfigEntry{},
|
entry: nil,
|
||||||
query: []string{"bar"},
|
query: []string{"bar"},
|
||||||
expect: [][]*pbpeering.Peering{{}},
|
expect: [][]*pbpeering.Peering{{}},
|
||||||
expectIdx: uint64(2), // catalog services max index
|
expectIdx: uint64(2), // catalog services max index
|
||||||
|
@ -1001,24 +1002,22 @@ func TestStateStore_PeeringsForService(t *testing.T) {
|
||||||
{Name: "peer1", State: pbpeering.PeeringState_INITIAL},
|
{Name: "peer1", State: pbpeering.PeeringState_INITIAL},
|
||||||
{Name: "peer2", State: pbpeering.PeeringState_INITIAL},
|
{Name: "peer2", State: pbpeering.PeeringState_INITIAL},
|
||||||
},
|
},
|
||||||
entries: []*structs.ExportedServicesConfigEntry{
|
entry: &structs.ExportedServicesConfigEntry{
|
||||||
{
|
Name: "default",
|
||||||
Name: "ce1",
|
Services: []structs.ExportedService{
|
||||||
Services: []structs.ExportedService{
|
{
|
||||||
{
|
Name: "foo",
|
||||||
Name: "foo",
|
Consumers: []structs.ServiceConsumer{
|
||||||
Consumers: []structs.ServiceConsumer{
|
{
|
||||||
{
|
PeerName: "peer1",
|
||||||
PeerName: "peer1",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
Name: "bar",
|
{
|
||||||
Consumers: []structs.ServiceConsumer{
|
Name: "bar",
|
||||||
{
|
Consumers: []structs.ServiceConsumer{
|
||||||
PeerName: "peer2",
|
{
|
||||||
},
|
PeerName: "peer2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1046,27 +1045,25 @@ func TestStateStore_PeeringsForService(t *testing.T) {
|
||||||
{Name: "peer2", State: pbpeering.PeeringState_INITIAL},
|
{Name: "peer2", State: pbpeering.PeeringState_INITIAL},
|
||||||
{Name: "peer3", State: pbpeering.PeeringState_INITIAL},
|
{Name: "peer3", State: pbpeering.PeeringState_INITIAL},
|
||||||
},
|
},
|
||||||
entries: []*structs.ExportedServicesConfigEntry{
|
entry: &structs.ExportedServicesConfigEntry{
|
||||||
{
|
Name: "default",
|
||||||
Name: "ce1",
|
Services: []structs.ExportedService{
|
||||||
Services: []structs.ExportedService{
|
{
|
||||||
{
|
Name: "*",
|
||||||
Name: "*",
|
Consumers: []structs.ServiceConsumer{
|
||||||
Consumers: []structs.ServiceConsumer{
|
{
|
||||||
{
|
PeerName: "peer1",
|
||||||
PeerName: "peer1",
|
},
|
||||||
},
|
{
|
||||||
{
|
PeerName: "peer2",
|
||||||
PeerName: "peer2",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
Name: "bar",
|
{
|
||||||
Consumers: []structs.ServiceConsumer{
|
Name: "bar",
|
||||||
{
|
Consumers: []structs.ServiceConsumer{
|
||||||
PeerName: "peer3",
|
{
|
||||||
},
|
PeerName: "peer3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1079,8 +1076,6 @@ func TestStateStore_PeeringsForService(t *testing.T) {
|
||||||
{Name: "peer2", State: pbpeering.PeeringState_INITIAL},
|
{Name: "peer2", State: pbpeering.PeeringState_INITIAL},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
{Name: "peer1", State: pbpeering.PeeringState_INITIAL},
|
|
||||||
{Name: "peer2", State: pbpeering.PeeringState_INITIAL},
|
|
||||||
{Name: "peer3", State: pbpeering.PeeringState_INITIAL},
|
{Name: "peer3", State: pbpeering.PeeringState_INITIAL},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -359,24 +359,22 @@ func TestPeeringService_TrustBundleRead(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPeeringService_TrustBundleListByService(t *testing.T) {
|
func TestPeeringService_TrustBundleListByService(t *testing.T) {
|
||||||
// test executes the following scenario:
|
// Test executes the following scenario:
|
||||||
// 0 - initial setup test server, state store, RPC client, verify empty results
|
// 0 - Initial setup test server, state store, RPC client, verify empty results
|
||||||
// 1 - create a service, verify results still empty
|
// 1 - Create a service, verify results still empty
|
||||||
// 2 - create a peering, verify results still empty
|
// 2 - Create a peering, verify results still empty
|
||||||
// 3 - create a config entry, verify results still empty
|
// 3 - Create a config entry, verify results still empty
|
||||||
// 4 - create trust bundles, verify bundles are returned
|
// 4 - Create trust bundles, verify bundles are returned
|
||||||
// 5 - delete the config entry, verify results empty
|
// 5 - Delete the config entry, verify results empty
|
||||||
// 6 - restore config entry, verify bundles are returned
|
// 6 - Restore config entry, verify bundles are returned
|
||||||
// 7 - add peering, trust bundles, wildcard config entry, verify updated results are present
|
// 7 - Add a second peering that the test service is not exported to
|
||||||
// 8 - delete first config entry, verify bundles are returned
|
// 8 - Export the service to the new peering
|
||||||
// 9 - delete the service, verify results empty
|
// 9 - Delete the service
|
||||||
// Note: these steps are dependent on each other by design so that we can verify that
|
// Note: these steps are dependent on each other by design so that we can verify that
|
||||||
// combinations of services, peerings, trust bundles, and config entries all affect results
|
// combinations of services, peerings, trust bundles, and config entries all affect results
|
||||||
|
|
||||||
// fixed for the test
|
|
||||||
nodeName := "test-node"
|
nodeName := "test-node"
|
||||||
|
|
||||||
// keep track of index across steps
|
|
||||||
var lastIdx uint64
|
var lastIdx uint64
|
||||||
|
|
||||||
// Create test server
|
// Create test server
|
||||||
|
@ -445,6 +443,7 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) {
|
||||||
// Write any config entries
|
// Write any config entries
|
||||||
for _, entry := range deps.entries {
|
for _, entry := range deps.entries {
|
||||||
idx++
|
idx++
|
||||||
|
require.NoError(t, entry.Normalize())
|
||||||
require.NoError(t, store.EnsureConfigEntry(idx, entry))
|
require.NoError(t, store.EnsureConfigEntry(idx, entry))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,6 +459,8 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) {
|
||||||
// TODO(peering): see note on newTestServer, once we have a better server mock,
|
// TODO(peering): see note on newTestServer, once we have a better server mock,
|
||||||
// we should add functionality here to verify errors from backend
|
// we should add functionality here to verify errors from backend
|
||||||
verify := func(t *testing.T, tc *testCase) {
|
verify := func(t *testing.T, tc *testCase) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
t.Cleanup(cancel)
|
t.Cleanup(cancel)
|
||||||
|
|
||||||
|
@ -478,7 +479,7 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) {
|
||||||
// Execute scenario steps
|
// Execute scenario steps
|
||||||
// ----------------------
|
// ----------------------
|
||||||
|
|
||||||
// 0 - initial empty state
|
// 0 - Initial empty state.
|
||||||
// -----------------------
|
// -----------------------
|
||||||
verify(t, &testCase{
|
verify(t, &testCase{
|
||||||
req: &pbpeering.TrustBundleListByServiceRequest{
|
req: &pbpeering.TrustBundleListByServiceRequest{
|
||||||
|
@ -489,7 +490,7 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 1 - create a service, verify results still empty
|
// 1 - Create a service, verify results still empty.
|
||||||
// ------------------------------------------------
|
// ------------------------------------------------
|
||||||
lastIdx = setup(t, lastIdx, testDeps{services: []string{"foo"}})
|
lastIdx = setup(t, lastIdx, testDeps{services: []string{"foo"}})
|
||||||
verify(t, &testCase{
|
verify(t, &testCase{
|
||||||
|
@ -501,7 +502,7 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 2 - create a peering, verify results still empty
|
// 2 - Create a peering, verify results still empty.
|
||||||
// ------------------------------------------------
|
// ------------------------------------------------
|
||||||
lastIdx = setup(t, lastIdx, testDeps{
|
lastIdx = setup(t, lastIdx, testDeps{
|
||||||
peerings: []*pbpeering.Peering{
|
peerings: []*pbpeering.Peering{
|
||||||
|
@ -522,12 +523,12 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 3 - create a config entry, verify results still empty
|
// 3 - Create a config entry, verify results still empty.
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
lastIdx = setup(t, lastIdx, testDeps{
|
lastIdx = setup(t, lastIdx, testDeps{
|
||||||
entries: []*structs.ExportedServicesConfigEntry{
|
entries: []*structs.ExportedServicesConfigEntry{
|
||||||
{
|
{
|
||||||
Name: "export-foo",
|
Name: "default",
|
||||||
Services: []structs.ExportedService{
|
Services: []structs.ExportedService{
|
||||||
{
|
{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
|
@ -550,7 +551,7 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 4 - create trust bundles, verify bundles are returned
|
// 4 - Create trust bundles, verify bundles are returned.
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
lastIdx = setup(t, lastIdx, testDeps{
|
lastIdx = setup(t, lastIdx, testDeps{
|
||||||
bundles: []*pbpeering.PeeringTrustBundle{
|
bundles: []*pbpeering.PeeringTrustBundle{
|
||||||
|
@ -576,10 +577,10 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 5 - delete the config entry, verify results empty
|
// 5 - Delete the config entry, verify results empty.
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
lastIdx++
|
lastIdx++
|
||||||
require.NoError(t, store.DeleteConfigEntry(lastIdx, structs.ExportedServices, "export-foo", nil))
|
require.NoError(t, store.DeleteConfigEntry(lastIdx, structs.ExportedServices, "default", nil))
|
||||||
verify(t, &testCase{
|
verify(t, &testCase{
|
||||||
req: &pbpeering.TrustBundleListByServiceRequest{
|
req: &pbpeering.TrustBundleListByServiceRequest{
|
||||||
ServiceName: "foo",
|
ServiceName: "foo",
|
||||||
|
@ -589,12 +590,12 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 6 - restore config entry, verify bundles are returned
|
// 6 - Restore config entry, verify bundles are returned.
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
lastIdx = setup(t, lastIdx, testDeps{
|
lastIdx = setup(t, lastIdx, testDeps{
|
||||||
entries: []*structs.ExportedServicesConfigEntry{
|
entries: []*structs.ExportedServicesConfigEntry{
|
||||||
{
|
{
|
||||||
Name: "export-foo",
|
Name: "default",
|
||||||
Services: []structs.ExportedService{
|
Services: []structs.ExportedService{
|
||||||
{
|
{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
|
@ -621,10 +622,9 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 7 - add peering, trust bundles, wildcard config entry, verify updated results are present
|
// 7 - Add new peer and trust bundle. It should be ignored because foo is not exported to it.
|
||||||
// -----------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------
|
||||||
lastIdx = setup(t, lastIdx, testDeps{
|
lastIdx = setup(t, lastIdx, testDeps{
|
||||||
services: []string{"bar"},
|
|
||||||
peerings: []*pbpeering.Peering{
|
peerings: []*pbpeering.Peering{
|
||||||
{
|
{
|
||||||
Name: "peer2",
|
Name: "peer2",
|
||||||
|
@ -633,20 +633,6 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) {
|
||||||
PeerServerAddresses: []string{"peer2-addr"},
|
PeerServerAddresses: []string{"peer2-addr"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
entries: []*structs.ExportedServicesConfigEntry{
|
|
||||||
{
|
|
||||||
Name: "export-all",
|
|
||||||
Services: []structs.ExportedService{
|
|
||||||
{
|
|
||||||
Name: structs.WildcardSpecifier,
|
|
||||||
Consumers: []structs.ServiceConsumer{
|
|
||||||
{PeerName: "peer1"},
|
|
||||||
{PeerName: "peer2"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
bundles: []*pbpeering.PeeringTrustBundle{
|
bundles: []*pbpeering.PeeringTrustBundle{
|
||||||
{
|
{
|
||||||
TrustDomain: "peer2.com",
|
TrustDomain: "peer2.com",
|
||||||
|
@ -666,18 +652,28 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) {
|
||||||
PeerName: "peer1",
|
PeerName: "peer1",
|
||||||
RootPEMs: []string{"peer1-root-1"},
|
RootPEMs: []string{"peer1-root-1"},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
TrustDomain: "peer2.com",
|
|
||||||
PeerName: "peer2",
|
|
||||||
RootPEMs: []string{"peer2-root-1"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 8 - delete first config entry, verify bundles are returned
|
// 8 - Replace config entry to export all services to both peers
|
||||||
lastIdx++
|
// -----------------------------------------------------------------------------------------
|
||||||
require.NoError(t, store.DeleteConfigEntry(lastIdx, structs.ExportedServices, "export-foo", nil))
|
lastIdx = setup(t, lastIdx, testDeps{
|
||||||
|
entries: []*structs.ExportedServicesConfigEntry{
|
||||||
|
{
|
||||||
|
Name: "default",
|
||||||
|
Services: []structs.ExportedService{
|
||||||
|
{
|
||||||
|
Name: structs.WildcardSpecifier,
|
||||||
|
Consumers: []structs.ServiceConsumer{
|
||||||
|
{PeerName: "peer1"},
|
||||||
|
{PeerName: "peer2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
verify(t, &testCase{
|
verify(t, &testCase{
|
||||||
req: &pbpeering.TrustBundleListByServiceRequest{
|
req: &pbpeering.TrustBundleListByServiceRequest{
|
||||||
ServiceName: "foo",
|
ServiceName: "foo",
|
||||||
|
|
|
@ -8,12 +8,6 @@ type PeeringToken struct {
|
||||||
PeerID string
|
PeerID string
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeeredService is a service that has been configured with an exported-service config entry to be exported to a peer.
|
|
||||||
type PeeredService struct {
|
|
||||||
Name ServiceName
|
|
||||||
PeerName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: this is not serialized via msgpack so it can be changed without concern.
|
// NOTE: this is not serialized via msgpack so it can be changed without concern.
|
||||||
type ExportedServiceList struct {
|
type ExportedServiceList struct {
|
||||||
// Services is a list of exported services that apply to both standard
|
// Services is a list of exported services that apply to both standard
|
||||||
|
|
Loading…
Reference in New Issue