Updates to Config Entries and Connect for Namespaces (#7116)

pull/7126/head
Matt Keeler 2020-01-24 10:04:58 -05:00 committed by GitHub
parent 47ac0bcd01
commit c09693e545
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
92 changed files with 1893 additions and 825 deletions

View File

@ -4024,6 +4024,14 @@ func (a *Agent) registerCache() {
RefreshTimeout: 10 * time.Minute,
})
a.cache.RegisterType(cachetype.CatalogServiceListName, &cachetype.CatalogServiceList{
RPC: a,
}, &cache.RegisterOptions{
Refresh: true,
RefreshTimer: 0 * time.Second,
RefreshTimeout: 10 * time.Minute,
})
a.cache.RegisterType(cachetype.CatalogDatacentersName, &cachetype.CatalogDatacenters{
RPC: a,
}, &cache.RegisterOptions{

View File

@ -292,7 +292,7 @@ func (s *HTTPServer) AgentService(resp http.ResponseWriter, req *http.Request) (
svcState := s.agent.State.ServiceState(sid)
if svcState == nil {
resp.WriteHeader(http.StatusNotFound)
fmt.Fprintf(resp, "unknown service ID: %s", id)
fmt.Fprintf(resp, "unknown service ID: %s", sid.String())
return "", nil, nil
}

View File

@ -0,0 +1,55 @@
package cachetype
import (
"fmt"
"github.com/hashicorp/consul/agent/cache"
"github.com/hashicorp/consul/agent/structs"
)
// Recommended name for registration.
const CatalogServiceListName = "catalog-services-list"
// CatalogServiceList supports fetching service names via the catalog.
type CatalogServiceList struct {
RPC RPC
}
func (c *CatalogServiceList) Fetch(opts cache.FetchOptions, req cache.Request) (cache.FetchResult, error) {
var result cache.FetchResult
// The request should be a DCSpecificRequest.
reqReal, ok := req.(*structs.DCSpecificRequest)
if !ok {
return result, fmt.Errorf(
"Internal cache failure: request wrong type: %T", req)
}
// Lightweight copy this object so that manipulating QueryOptions doesn't race.
dup := *reqReal
reqReal = &dup
// Set the minimum query index to our current index so we block
reqReal.QueryOptions.MinQueryIndex = opts.MinIndex
reqReal.QueryOptions.MaxQueryTime = opts.Timeout
// Always allow stale - there's no point in hitting leader if the request is
// going to be served from cache and end up arbitrarily stale anyway. This
// allows cached service-discover to automatically read scale across all
// servers too.
reqReal.AllowStale = true
// Fetch
var reply structs.IndexedServiceList
if err := c.RPC.RPC("Catalog.ServiceList", reqReal, &reply); err != nil {
return result, err
}
result.Value = &reply
result.Index = reply.QueryMeta.Index
return result, nil
}
func (c *CatalogServiceList) SupportsBlocking() bool {
return true
}

View File

@ -0,0 +1,66 @@
package cachetype
import (
"testing"
"time"
"github.com/hashicorp/consul/agent/cache"
"github.com/hashicorp/consul/agent/structs"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
func TestCatalogServiceList(t *testing.T) {
rpc := TestRPC(t)
typ := &CatalogServiceList{RPC: rpc}
// Expect the proper RPC call. This also sets the expected value
// since that is return-by-pointer in the arguments.
var resp *structs.IndexedServiceList
rpc.On("RPC", "Catalog.ServiceList", mock.Anything, mock.Anything).Return(nil).
Run(func(args mock.Arguments) {
req := args.Get(1).(*structs.DCSpecificRequest)
require.Equal(t, uint64(24), req.QueryOptions.MinQueryIndex)
require.Equal(t, 1*time.Second, req.QueryOptions.MaxQueryTime)
require.True(t, req.AllowStale)
reply := args.Get(2).(*structs.IndexedServiceList)
reply.Services = structs.ServiceList{
structs.ServiceInfo{
Name: "foo",
},
structs.ServiceInfo{
Name: "bar",
},
}
reply.QueryMeta.Index = 48
resp = reply
})
// Fetch
resultA, err := typ.Fetch(cache.FetchOptions{
MinIndex: 24,
Timeout: 1 * time.Second,
}, &structs.DCSpecificRequest{
Datacenter: "dc1",
})
require.NoError(t, err)
require.Equal(t, cache.FetchResult{
Value: resp,
Index: 48,
}, resultA)
rpc.AssertExpectations(t)
}
func TestCatalogServiceList_badReqType(t *testing.T) {
rpc := TestRPC(t)
typ := &CatalogServiceList{RPC: rpc}
// Fetch
_, err := typ.Fetch(cache.FetchOptions{}, cache.TestRequest(
t, cache.RequestInfo{Key: "foo", MinIndex: 64}))
require.Error(t, err)
require.Contains(t, err.Error(), "wrong type")
rpc.AssertExpectations(t)
}

View File

@ -515,17 +515,17 @@ func (c *ConnectCALeaf) generateNewLeaf(req *ConnectCALeafRequest,
id = &connect.SpiffeIDService{
Host: roots.TrustDomain,
Datacenter: req.Datacenter,
Namespace: "default",
Namespace: req.TargetNamespace(),
Service: req.Service,
}
commonName = connect.ServiceCN(req.Service, roots.TrustDomain)
commonName = connect.ServiceCN(req.Service, req.TargetNamespace(), roots.TrustDomain)
} else if req.Agent != "" {
id = &connect.SpiffeIDAgent{
Host: roots.TrustDomain,
Datacenter: req.Datacenter,
Agent: req.Agent,
}
commonName = connect.ServiceCN(req.Agent, roots.TrustDomain)
commonName = connect.AgentCN(req.Agent, roots.TrustDomain)
dnsNames = append([]string{"localhost"}, req.DNSSAN...)
ipAddresses = append([]net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("::")}, req.IPSAN...)
} else {
@ -645,6 +645,8 @@ type ConnectCALeafRequest struct {
IPSAN []net.IP
MinQueryIndex uint64
MaxQueryTime time.Duration
structs.EnterpriseMeta
}
func (r *ConnectCALeafRequest) Key() string {

View File

@ -0,0 +1,7 @@
// +build !consulent
package cachetype
func (req *ConnectCALeafRequest) TargetNamespace() string {
return "default"
}

View File

@ -2,6 +2,7 @@ package cachetype
import (
"fmt"
"strconv"
"time"
"github.com/hashicorp/consul/agent/cache"
@ -55,8 +56,8 @@ func (c *ServiceHTTPChecks) Fetch(opts cache.FetchOptions, req cache.Request) (c
hash, resp, err := c.Agent.LocalBlockingQuery(true, lastHash, reqReal.MaxQueryTime,
func(ws memdb.WatchSet) (string, interface{}, error) {
// TODO (namespaces) update with the real ent meta once thats plumbed through
svcState := c.Agent.LocalState().ServiceState(structs.NewServiceID(reqReal.ServiceID, nil))
sid := structs.NewServiceID(reqReal.ServiceID, &reqReal.EnterpriseMeta)
svcState := c.Agent.LocalState().ServiceState(sid)
if svcState == nil {
return "", result, fmt.Errorf("Internal cache failure: service '%s' not in agent state", reqReal.ServiceID)
}
@ -64,8 +65,7 @@ func (c *ServiceHTTPChecks) Fetch(opts cache.FetchOptions, req cache.Request) (c
// WatchCh will receive updates on service (de)registrations and check (de)registrations
ws.Add(svcState.WatchCh)
// TODO (namespaces) update with a real entMeta
reply := c.Agent.ServiceHTTPBasedChecks(structs.NewServiceID(reqReal.ServiceID, nil))
reply := c.Agent.ServiceHTTPBasedChecks(sid)
hash, err := hashChecks(reply)
if err != nil {
@ -103,16 +103,28 @@ type ServiceHTTPChecksRequest struct {
ServiceID string
MinQueryIndex uint64
MaxQueryTime time.Duration
structs.EnterpriseMeta
}
func (s *ServiceHTTPChecksRequest) CacheInfo() cache.RequestInfo {
return cache.RequestInfo{
info := cache.RequestInfo{
Token: "",
Key: ServiceHTTPChecksName + ":" + s.ServiceID,
Datacenter: "",
MinIndex: s.MinQueryIndex,
Timeout: s.MaxQueryTime,
}
v, err := hashstructure.Hash([]interface{}{
s.ServiceID,
s.EnterpriseMeta,
}, nil)
if err == nil {
// If there is an error, we don't set the key. A blank key forces
// no cache for this request.
info.Key = strconv.FormatUint(v, 10)
}
return info
}
func hashChecks(checks []structs.CheckType) (string, error) {

View File

@ -32,6 +32,14 @@ func (s *HTTPServer) configGet(resp http.ResponseWriter, req *http.Request) (int
}
pathArgs := strings.SplitN(strings.TrimPrefix(req.URL.Path, "/v1/config/"), "/", 2)
if len(pathArgs) == 2 {
if err := s.parseEntMetaNoWildcard(req, &args.EnterpriseMeta); err != nil {
return nil, err
}
} else if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
return nil, err
}
switch len(pathArgs) {
case 2:
// Both kind/name provided.
@ -85,6 +93,11 @@ func (s *HTTPServer) configDelete(resp http.ResponseWriter, req *http.Request) (
return nil, nil
}
args.Entry = entry
// Parse enterprise meta.
meta := args.Entry.GetEnterpriseMeta()
if err := s.parseEntMetaNoWildcard(req, meta); err != nil {
return nil, err
}
var reply struct{}
if err := s.agent.RPC("ConfigEntry.Delete", &args, &reply); err != nil {
@ -113,6 +126,13 @@ func (s *HTTPServer) ConfigApply(resp http.ResponseWriter, req *http.Request) (i
return nil, BadRequestError{Reason: fmt.Sprintf("Request decoding failed: %v", err)}
}
// Parse enterprise meta.
var meta structs.EnterpriseMeta
if err := s.parseEntMetaNoWildcard(req, &meta); err != nil {
return nil, err
}
args.Entry.GetEnterpriseMeta().Merge(&meta)
// Check for cas value
if casStr := req.URL.Query().Get("cas"); casStr != "" {
casVal, err := strconv.ParseUint(casStr, 10, 64)

View File

@ -174,7 +174,7 @@ func TestConsulCAProvider_SignLeaf(t *testing.T) {
parsed, err := connect.ParseCert(cert)
require.NoError(err)
require.Equal(spiffeService.URI(), parsed.URIs[0])
require.Equal(connect.ServiceCN("foo", connect.TestClusterID), parsed.Subject.CommonName)
require.Equal(connect.ServiceCN("foo", "default", connect.TestClusterID), parsed.Subject.CommonName)
require.Equal(uint64(2), parsed.SerialNumber.Uint64())
subjectKeyID, err := connect.KeyId(csr.PublicKey)
require.NoError(err)
@ -203,7 +203,7 @@ func TestConsulCAProvider_SignLeaf(t *testing.T) {
parsed, err := connect.ParseCert(cert)
require.NoError(err)
require.Equal(spiffeService.URI(), parsed.URIs[0])
require.Equal(connect.ServiceCN("bar", connect.TestClusterID), parsed.Subject.CommonName)
require.Equal(connect.ServiceCN("bar", "default", connect.TestClusterID), parsed.Subject.CommonName)
require.Equal(parsed.SerialNumber.Uint64(), uint64(2))
requireNotEncoded(t, parsed.SubjectKeyId)
requireNotEncoded(t, parsed.AuthorityKeyId)

View File

@ -11,6 +11,56 @@ import (
var invalidDNSNameChars = regexp.MustCompile(`[^a-z0-9]`)
const (
// 64 = max length of a certificate common name
// 21 = 7 bytes for ".consul", 9 bytes for .<trust domain> and 5 bytes for ".svc."
// This ends up being 43 bytes
maxServiceAndNamespaceLen = 64 - 21
minServiceNameLen = 15
minNamespaceNameLen = 15
)
// trucateServiceAndNamespace will take a service name and namespace name and truncate
// them appropriately so that they would fit within the space alloted for them in the
// Common Name field of the x509 certificate. That field is capped at 64 characters
// in length and there is other data that must be a part of the name too. This function
// takes all of that into account.
func truncateServiceAndNamespace(serviceName, namespace string) (string, string) {
svcLen := len(serviceName)
nsLen := len(namespace)
totalLen := svcLen + nsLen
// quick exit when the entirety of both can fit
if totalLen <= maxServiceAndNamespaceLen {
return serviceName, namespace
}
toRemove := totalLen - maxServiceAndNamespaceLen
// now we must figure out how to truncate each one, we need to ensure we don't remove all of either one.
if svcLen <= minServiceNameLen {
// only remove bytes from the namespace
return serviceName, truncateTo(namespace, nsLen-toRemove)
} else if nsLen <= minNamespaceNameLen {
// only remove bytes from the service name
return truncateTo(serviceName, svcLen-toRemove), namespace
} else {
// we can remove an "equal" amount from each. If the number of bytes to remove is odd we give it to the namespace
svcTruncate := svcLen - (toRemove / 2) - (toRemove % 2)
nsTruncate := nsLen - (toRemove / 2)
// checks to ensure we don't reduce one side too much when they are not roughly balanced in length.
if svcTruncate <= minServiceNameLen {
svcTruncate = minServiceNameLen
nsTruncate = maxServiceAndNamespaceLen - minServiceNameLen
} else if nsTruncate <= minNamespaceNameLen {
svcTruncate = maxServiceAndNamespaceLen - minNamespaceNameLen
nsTruncate = minNamespaceNameLen
}
return truncateTo(serviceName, svcTruncate), truncateTo(namespace, nsTruncate)
}
}
// ServiceCN returns the common name for a service's certificate. We can't use
// SPIFFE URIs because some CAs require valid FQDN format. We can't use SNI
// values because they are often too long than the 64 bytes allowed by
@ -31,11 +81,12 @@ var invalidDNSNameChars = regexp.MustCompile(`[^a-z0-9]`)
// total at 64.
//
// trust domain is truncated to keep the whole name short
func ServiceCN(serviceName, trustDomain string) string {
func ServiceCN(serviceName, namespace, trustDomain string) string {
svc := invalidDNSNameChars.ReplaceAllString(strings.ToLower(serviceName), "")
// 20 = 7 bytes for ".consul", 8 bytes for trust domain, 5 bytes for ".svc."
return fmt.Sprintf("%s.svc.%s.consul",
truncateTo(svc, 64-20), truncateTo(trustDomain, 8))
svc, namespace = truncateServiceAndNamespace(svc, namespace)
return fmt.Sprintf("%s.svc.%s.%s.consul",
svc, namespace, truncateTo(trustDomain, 8))
}
// AgentCN returns the common name for an agent certificate. See ServiceCN for
@ -122,7 +173,7 @@ func CNForCertURI(uri CertURI) (string, error) {
// didn't include the Common Name in the CSR.
switch id := uri.(type) {
case *SpiffeIDService:
return ServiceCN(id.Service, id.Host), nil
return ServiceCN(id.Service, id.Namespace, id.Host), nil
case *SpiffeIDAgent:
return AgentCN(id.Agent, id.Host), nil
case *SpiffeIDSigning:

View File

@ -0,0 +1,115 @@
package connect
import (
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func TestServiceAndNamespaceTruncation(t *testing.T) {
type tcase struct {
service string
namespace string
// if left as "" then its expected to not be truncated
expectedService string
// if left as "" then its expected to not be truncated
expectedNamespace string
}
cases := map[string]tcase{
"short-no-truncation": tcase{
service: "foo",
namespace: "bar",
},
"long-service-no-truncation": tcase{
// -3 because thats the length of the namespace
service: strings.Repeat("a", maxServiceAndNamespaceLen-3),
namespace: "bar",
},
"long-namespace-no-truncation": tcase{
service: "foo",
// -3 because thats the length of the service name
namespace: strings.Repeat("b", maxServiceAndNamespaceLen-3),
},
"truncate-service-only": tcase{
// this should force the service name to be truncated
service: strings.Repeat("a", maxServiceAndNamespaceLen-minNamespaceNameLen+5),
expectedService: strings.Repeat("a", maxServiceAndNamespaceLen-minNamespaceNameLen),
// this is the maximum length that will never be truncated for a namespace
namespace: strings.Repeat("b", minNamespaceNameLen),
},
"truncate-namespace-only": tcase{
// this is the maximum length that will never be truncated for a service name
service: strings.Repeat("a", minServiceNameLen),
// this should force the namespace name to be truncated
namespace: strings.Repeat("b", maxServiceAndNamespaceLen-minServiceNameLen+5),
expectedNamespace: strings.Repeat("b", maxServiceAndNamespaceLen-minServiceNameLen),
},
"truncate-both-even": tcase{
// this test would need to be update if the maxServiceAndNamespaceLen variable is updated
// I could put some more complex logic into here to prevent that but it would be mostly
// duplicating the logic in the function itself and thus not really be testing anything
//
// The original lengths of 50 / 51 were chose when maxServiceAndNamespaceLen would be 43
// Therefore a total of 58 characters must be removed. This was chose so that the value
// could be evenly split between the two strings.
service: strings.Repeat("a", 50),
expectedService: strings.Repeat("a", 21),
namespace: strings.Repeat("b", 51),
expectedNamespace: strings.Repeat("b", 22),
},
"truncate-both-odd": tcase{
// this test would need to be update if the maxServiceAndNamespaceLen variable is updated
// I could put some more complex logic into here to prevent that but it would be mostly
// duplicating the logic in the function itself and thus not really be testing anything
//
// The original lengths of 50 / 57 were chose when maxServiceAndNamespaceLen would be 43
// Therefore a total of 57 characters must be removed. This was chose so that the value
// could not be evenly removed from both so the namespace should be truncated to a length
// 1 character more than the service.
service: strings.Repeat("a", 50),
expectedService: strings.Repeat("a", 21),
namespace: strings.Repeat("b", 50),
expectedNamespace: strings.Repeat("b", 22),
},
"truncate-both-min-svc": tcase{
service: strings.Repeat("a", minServiceNameLen+1),
expectedService: strings.Repeat("a", minServiceNameLen),
namespace: strings.Repeat("b", maxServiceAndNamespaceLen),
expectedNamespace: strings.Repeat("b", maxServiceAndNamespaceLen-minServiceNameLen),
},
"truncate-both-min-ns": tcase{
service: strings.Repeat("a", maxServiceAndNamespaceLen),
expectedService: strings.Repeat("a", maxServiceAndNamespaceLen-minNamespaceNameLen),
namespace: strings.Repeat("b", minNamespaceNameLen+1),
expectedNamespace: strings.Repeat("b", minNamespaceNameLen),
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
actualSvc, actualNamespace := truncateServiceAndNamespace(tc.service, tc.namespace)
expectedLen := len(tc.service) + len(tc.namespace)
if tc.expectedService != "" || tc.expectedNamespace != "" {
expectedLen = maxServiceAndNamespaceLen
}
actualLen := len(actualSvc) + len(actualNamespace)
require.Equal(t, expectedLen, actualLen, "Combined length of %d (svc: %d, ns: %d) does not match expected value of %d", actualLen, len(actualSvc), len(actualNamespace), expectedLen)
if tc.expectedService != "" {
require.Equal(t, tc.expectedService, actualSvc)
} else {
require.Equal(t, tc.service, actualSvc)
}
if tc.expectedNamespace != "" {
require.Equal(t, tc.expectedNamespace, actualNamespace)
} else {
require.Equal(t, tc.namespace, actualNamespace)
}
})
}
}

View File

@ -211,7 +211,7 @@ func testLeaf(t testing.T, service string, root *structs.CARoot, keyType string,
// Cert template for generation
template := x509.Certificate{
SerialNumber: sn,
Subject: pkix.Name{CommonName: ServiceCN(service, TestClusterID)},
Subject: pkix.Name{CommonName: ServiceCN(service, "default", TestClusterID)},
URIs: []*url.URL{spiffeId.URI()},
SignatureAlgorithm: SigAlgoForKeyType(rootKeyType),
BasicConstraintsValid: true,

View File

@ -1647,6 +1647,25 @@ func (f *aclFilter) filterAuthMethods(methods *structs.ACLAuthMethods) {
*methods = ret
}
func (f *aclFilter) filterServiceList(services *structs.ServiceList) {
ret := make(structs.ServiceList, 0, len(*services))
for _, svc := range *services {
var authzContext acl.AuthorizerContext
svc.FillAuthzContext(&authzContext)
if f.authorizer.ServiceRead(svc.Name, &authzContext) != acl.Allow {
sid := structs.NewServiceID(svc.Name, &svc.EnterpriseMeta)
f.logger.Printf("[DEBUG] consul: dropping service %q from result due to ACLs", sid.String())
continue
}
ret = append(ret, svc)
}
*services = ret
}
func (r *ACLResolver) filterACLWithAuthorizer(authorizer acl.Authorizer, subj interface{}) error {
if authorizer == nil {
return nil
@ -1726,6 +1745,8 @@ func (r *ACLResolver) filterACLWithAuthorizer(authorizer acl.Authorizer, subj in
case **structs.ACLAuthMethod:
filt.filterAuthMethod(v)
case *structs.IndexedServiceList:
filt.filterServiceList(&v.Services)
default:
panic(fmt.Errorf("Unhandled type passed to ACL filter: %T %#v", subj, subj))
}

View File

@ -821,17 +821,26 @@ func (a *ACL) TokenList(args *structs.ACLTokenListRequest, reply *structs.ACLTok
}
var authzContext acl.AuthorizerContext
authz, err := a.srv.ResolveTokenAndDefaultMeta(args.Token, &args.EnterpriseMeta, &authzContext)
var requestMeta structs.EnterpriseMeta
authz, err := a.srv.ResolveTokenAndDefaultMeta(args.Token, &requestMeta, &authzContext)
if err != nil {
return err
} else if authz == nil || authz.ACLRead(&authzContext) != acl.Allow {
}
// merge the token default meta into the requests meta
args.EnterpriseMeta.Merge(&requestMeta)
args.EnterpriseMeta.FillAuthzContext(&authzContext)
if authz == nil || authz.ACLRead(&authzContext) != acl.Allow {
return acl.ErrPermissionDenied
}
var methodMeta *structs.EnterpriseMeta
if args.AuthMethod != "" {
methodMeta = args.ACLAuthMethodEnterpriseMeta.ToEnterpriseMeta()
methodMeta.Merge(&args.EnterpriseMeta)
// attempt to merge in the overall meta, wildcards will not be merged
methodMeta.MergeNoWildcard(&args.EnterpriseMeta)
// in the event that the meta above didn't merge due to being a wildcard
// we ensure that proper token based meta inference occurs
methodMeta.Merge(&requestMeta)
}
return a.srv.blockingQuery(&args.QueryOptions, &reply.QueryMeta,

View File

@ -320,6 +320,34 @@ func (c *Catalog) ListServices(args *structs.DCSpecificRequest, reply *structs.I
})
}
func (c *Catalog) ServiceList(args *structs.DCSpecificRequest, reply *structs.IndexedServiceList) error {
if done, err := c.srv.forward("Catalog.ServiceList", args, args, reply); done {
return err
}
authz, err := c.srv.ResolveTokenAndDefaultMeta(args.Token, &args.EnterpriseMeta, nil)
if err != nil {
return err
}
if err := c.srv.validateEnterpriseRequest(&args.EnterpriseMeta, false); err != nil {
return err
}
return c.srv.blockingQuery(
&args.QueryOptions,
&reply.QueryMeta,
func(ws memdb.WatchSet, state *state.Store) error {
index, services, err := state.ServiceList(ws, &args.EnterpriseMeta)
if err != nil {
return err
}
reply.Index, reply.Services = index, services
return c.srv.filterACLWithAuthorizer(authz, reply)
})
}
// ServiceNodes returns all the nodes registered as part of a service
func (c *Catalog) ServiceNodes(args *structs.ServiceSpecificRequest, reply *structs.IndexedServiceNodes) error {
if done, err := c.srv.forward("Catalog.ServiceNodes", args, args, reply); done {

View File

@ -349,6 +349,9 @@ type Config struct {
// a Consul server is now up and known about.
ServerUp func()
// Shutdown callback is used to trigger a full Consul shutdown
Shutdown func()
// UserEventHandler callback can be used to handle incoming
// user events. This function should not block.
UserEventHandler func(serf.UserEvent)

View File

@ -19,6 +19,10 @@ type ConfigEntry struct {
// Apply does an upsert of the given config entry.
func (c *ConfigEntry) Apply(args *structs.ConfigEntryRequest, reply *bool) error {
if err := c.srv.validateEnterpriseRequest(args.Entry.GetEnterpriseMeta(), true); err != nil {
return err
}
// Ensure that all config entry writes go to the primary datacenter. These will then
// be replicated to all the other datacenters.
args.Datacenter = c.srv.config.PrimaryDatacenter
@ -28,6 +32,12 @@ func (c *ConfigEntry) Apply(args *structs.ConfigEntryRequest, reply *bool) error
}
defer metrics.MeasureSince([]string{"config_entry", "apply"}, time.Now())
entMeta := args.Entry.GetEnterpriseMeta()
authz, err := c.srv.ResolveTokenAndDefaultMeta(args.Token, entMeta, nil)
if err != nil {
return err
}
// Normalize and validate the incoming config entry.
if err := args.Entry.Normalize(); err != nil {
return err
@ -36,12 +46,7 @@ func (c *ConfigEntry) Apply(args *structs.ConfigEntryRequest, reply *bool) error
return err
}
// Fetch the ACL token, if any.
rule, err := c.srv.ResolveToken(args.Token)
if err != nil {
return err
}
if rule != nil && !args.Entry.CanWrite(rule) {
if authz != nil && !args.Entry.CanWrite(authz) {
return acl.ErrPermissionDenied
}
@ -64,13 +69,16 @@ func (c *ConfigEntry) Apply(args *structs.ConfigEntryRequest, reply *bool) error
// Get returns a single config entry by Kind/Name.
func (c *ConfigEntry) Get(args *structs.ConfigEntryQuery, reply *structs.ConfigEntryResponse) error {
if err := c.srv.validateEnterpriseRequest(&args.EnterpriseMeta, false); err != nil {
return err
}
if done, err := c.srv.forward("ConfigEntry.Get", args, args, reply); done {
return err
}
defer metrics.MeasureSince([]string{"config_entry", "get"}, time.Now())
// Fetch the ACL token, if any.
rule, err := c.srv.ResolveToken(args.Token)
authz, err := c.srv.ResolveTokenAndDefaultMeta(args.Token, &args.EnterpriseMeta, nil)
if err != nil {
return err
}
@ -80,7 +88,7 @@ func (c *ConfigEntry) Get(args *structs.ConfigEntryQuery, reply *structs.ConfigE
if err != nil {
return err
}
if rule != nil && !lookupEntry.CanRead(rule) {
if authz != nil && !lookupEntry.CanRead(authz) {
return acl.ErrPermissionDenied
}
@ -88,7 +96,7 @@ func (c *ConfigEntry) Get(args *structs.ConfigEntryQuery, reply *structs.ConfigE
&args.QueryOptions,
&reply.QueryMeta,
func(ws memdb.WatchSet, state *state.Store) error {
index, entry, err := state.ConfigEntry(ws, args.Kind, args.Name)
index, entry, err := state.ConfigEntry(ws, args.Kind, args.Name, &args.EnterpriseMeta)
if err != nil {
return err
}
@ -106,13 +114,16 @@ func (c *ConfigEntry) Get(args *structs.ConfigEntryQuery, reply *structs.ConfigE
// List returns all the config entries of the given kind. If Kind is blank,
// all existing config entries will be returned.
func (c *ConfigEntry) List(args *structs.ConfigEntryQuery, reply *structs.IndexedConfigEntries) error {
if err := c.srv.validateEnterpriseRequest(&args.EnterpriseMeta, false); err != nil {
return err
}
if done, err := c.srv.forward("ConfigEntry.List", args, args, reply); done {
return err
}
defer metrics.MeasureSince([]string{"config_entry", "list"}, time.Now())
// Fetch the ACL token, if any.
rule, err := c.srv.ResolveToken(args.Token)
authz, err := c.srv.ResolveTokenAndDefaultMeta(args.Token, &args.EnterpriseMeta, nil)
if err != nil {
return err
}
@ -125,7 +136,7 @@ func (c *ConfigEntry) List(args *structs.ConfigEntryQuery, reply *structs.Indexe
&args.QueryOptions,
&reply.QueryMeta,
func(ws memdb.WatchSet, state *state.Store) error {
index, entries, err := state.ConfigEntriesByKind(ws, args.Kind)
index, entries, err := state.ConfigEntriesByKind(ws, args.Kind, &args.EnterpriseMeta)
if err != nil {
return err
}
@ -133,7 +144,7 @@ func (c *ConfigEntry) List(args *structs.ConfigEntryQuery, reply *structs.Indexe
// Filter the entries returned by ACL permissions.
filteredEntries := make([]structs.ConfigEntry, 0, len(entries))
for _, entry := range entries {
if rule != nil && !entry.CanRead(rule) {
if authz != nil && !entry.CanRead(authz) {
continue
}
filteredEntries = append(filteredEntries, entry)
@ -148,13 +159,16 @@ func (c *ConfigEntry) List(args *structs.ConfigEntryQuery, reply *structs.Indexe
// ListAll returns all the known configuration entries
func (c *ConfigEntry) ListAll(args *structs.DCSpecificRequest, reply *structs.IndexedGenericConfigEntries) error {
if err := c.srv.validateEnterpriseRequest(&args.EnterpriseMeta, false); err != nil {
return err
}
if done, err := c.srv.forward("ConfigEntry.ListAll", args, args, reply); done {
return err
}
defer metrics.MeasureSince([]string{"config_entry", "listAll"}, time.Now())
// Fetch the ACL token, if any.
rule, err := c.srv.ResolveToken(args.Token)
authz, err := c.srv.ResolveTokenAndDefaultMeta(args.Token, &args.EnterpriseMeta, nil)
if err != nil {
return err
}
@ -163,7 +177,7 @@ func (c *ConfigEntry) ListAll(args *structs.DCSpecificRequest, reply *structs.In
&args.QueryOptions,
&reply.QueryMeta,
func(ws memdb.WatchSet, state *state.Store) error {
index, entries, err := state.ConfigEntries(ws)
index, entries, err := state.ConfigEntries(ws, &args.EnterpriseMeta)
if err != nil {
return err
}
@ -171,7 +185,7 @@ func (c *ConfigEntry) ListAll(args *structs.DCSpecificRequest, reply *structs.In
// Filter the entries returned by ACL permissions.
filteredEntries := make([]structs.ConfigEntry, 0, len(entries))
for _, entry := range entries {
if rule != nil && !entry.CanRead(rule) {
if authz != nil && !entry.CanRead(authz) {
continue
}
filteredEntries = append(filteredEntries, entry)
@ -185,6 +199,10 @@ func (c *ConfigEntry) ListAll(args *structs.DCSpecificRequest, reply *structs.In
// Delete deletes a config entry.
func (c *ConfigEntry) Delete(args *structs.ConfigEntryRequest, reply *struct{}) error {
if err := c.srv.validateEnterpriseRequest(args.Entry.GetEnterpriseMeta(), true); err != nil {
return err
}
// Ensure that all config entry writes go to the primary datacenter. These will then
// be replicated to all the other datacenters.
args.Datacenter = c.srv.config.PrimaryDatacenter
@ -194,17 +212,17 @@ func (c *ConfigEntry) Delete(args *structs.ConfigEntryRequest, reply *struct{})
}
defer metrics.MeasureSince([]string{"config_entry", "delete"}, time.Now())
authz, err := c.srv.ResolveTokenAndDefaultMeta(args.Token, args.Entry.GetEnterpriseMeta(), nil)
if err != nil {
return err
}
// Normalize the incoming entry.
if err := args.Entry.Normalize(); err != nil {
return err
}
// Fetch the ACL token, if any.
rule, err := c.srv.ResolveToken(args.Token)
if err != nil {
return err
}
if rule != nil && !args.Entry.CanWrite(rule) {
if authz != nil && !args.Entry.CanWrite(authz) {
return acl.ErrPermissionDenied
}
@ -221,18 +239,21 @@ func (c *ConfigEntry) Delete(args *structs.ConfigEntryRequest, reply *struct{})
// ResolveServiceConfig
func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, reply *structs.ServiceConfigResponse) error {
if err := c.srv.validateEnterpriseRequest(&args.EnterpriseMeta, false); err != nil {
return err
}
if done, err := c.srv.forward("ConfigEntry.ResolveServiceConfig", args, args, reply); done {
return err
}
defer metrics.MeasureSince([]string{"config_entry", "resolve_service_config"}, time.Now())
// Fetch the ACL token, if any.
rule, err := c.srv.ResolveToken(args.Token)
var authzContext acl.AuthorizerContext
authz, err := c.srv.ResolveTokenAndDefaultMeta(args.Token, &args.EnterpriseMeta, &authzContext)
if err != nil {
return err
}
// TODO (namespaces) use actual ent authz context
if rule != nil && rule.ServiceRead(args.Name, nil) != acl.Allow {
if authz != nil && authz.ServiceRead(args.Name, &authzContext) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -246,7 +267,7 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
// Pass the WatchSet to both the service and proxy config lookups. If either is updated
// during the blocking query, this function will be rerun and these state store lookups
// will both be current.
index, serviceEntry, err := state.ConfigEntry(ws, structs.ServiceDefaults, args.Name)
index, serviceEntry, err := state.ConfigEntry(ws, structs.ServiceDefaults, args.Name, &args.EnterpriseMeta)
if err != nil {
return err
}
@ -259,7 +280,9 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
}
}
_, proxyEntry, err := state.ConfigEntry(ws, structs.ProxyDefaults, structs.ProxyConfigGlobal)
// Use the default enterprise meta to look up the global proxy defaults. In the future we may allow per-namespace proxy-defaults
// but not yet.
_, proxyEntry, err := state.ConfigEntry(ws, structs.ProxyDefaults, structs.ProxyConfigGlobal, structs.DefaultEnterpriseMeta())
if err != nil {
return err
}
@ -305,9 +328,24 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
proxyConfGlobalProtocol = proxyConf.Config["protocol"]
}
// Apply the upstream protocols to the upstream configs
// map the legacy request structure using only service names
// to the new ServiceID type.
upstreamIDs := args.UpstreamIDs
legacyUpstreams := false
if len(upstreamIDs) == 0 {
legacyUpstreams = true
upstreamIDs = make([]structs.ServiceID, 0)
for _, upstream := range args.Upstreams {
_, upstreamEntry, err := state.ConfigEntry(ws, structs.ServiceDefaults, upstream)
upstreamIDs = append(upstreamIDs, structs.NewServiceID(upstream, &args.EnterpriseMeta))
}
}
usConfigs := make(map[structs.ServiceID]map[string]interface{})
for _, upstream := range upstreamIDs {
_, upstreamEntry, err := state.ConfigEntry(ws, structs.ServiceDefaults, upstream.ID, &upstream.EnterpriseMeta)
if err != nil {
return err
}
@ -336,11 +374,30 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
continue
}
usConfigs[upstream] = map[string]interface{}{
"protocol": protocol,
}
}
// don't allocate the slices just to not fill them
if len(usConfigs) == 0 {
return nil
}
if legacyUpstreams {
if reply.UpstreamConfigs == nil {
reply.UpstreamConfigs = make(map[string]map[string]interface{})
}
reply.UpstreamConfigs[upstream] = map[string]interface{}{
"protocol": protocol,
for us, conf := range usConfigs {
reply.UpstreamConfigs[us.ID] = conf
}
} else {
if reply.UpstreamIDConfigs == nil {
reply.UpstreamIDConfigs = make(structs.UpstreamConfigs, 0, len(usConfigs))
}
for us, conf := range usConfigs {
reply.UpstreamIDConfigs = append(reply.UpstreamIDConfigs, structs.UpstreamConfig{Upstream: us, Config: conf})
}
}

View File

@ -53,7 +53,7 @@ func TestConfigEntry_Apply(t *testing.T) {
// the previous RPC should not return until the primary has been updated but will return
// before the secondary has the data.
state := s1.fsm.State()
_, entry, err := state.ConfigEntry(nil, structs.ServiceDefaults, "foo")
_, entry, err := state.ConfigEntry(nil, structs.ServiceDefaults, "foo", nil)
require.NoError(t, err)
serviceConf, ok := entry.(*structs.ServiceConfigEntry)
@ -64,7 +64,7 @@ func TestConfigEntry_Apply(t *testing.T) {
retry.Run(t, func(r *retry.R) {
// wait for replication to happen
state := s2.fsm.State()
_, entry, err := state.ConfigEntry(nil, structs.ServiceDefaults, "foo")
_, entry, err := state.ConfigEntry(nil, structs.ServiceDefaults, "foo", nil)
require.NoError(r, err)
require.NotNil(r, entry)
// this test is not testing that the config entries that are replicated are correct as thats done elsewhere.
@ -91,7 +91,7 @@ func TestConfigEntry_Apply(t *testing.T) {
require.True(t, out)
state = s1.fsm.State()
_, entry, err = state.ConfigEntry(nil, structs.ServiceDefaults, "foo")
_, entry, err = state.ConfigEntry(nil, structs.ServiceDefaults, "foo", nil)
require.NoError(t, err)
serviceConf, ok = entry.(*structs.ServiceConfigEntry)
@ -124,7 +124,7 @@ func TestConfigEntry_ProxyDefaultsMeshGateway(t *testing.T) {
require.True(t, out)
state := s1.fsm.State()
_, entry, err := state.ConfigEntry(nil, structs.ProxyDefaults, "global")
_, entry, err := state.ConfigEntry(nil, structs.ProxyDefaults, "global", nil)
require.NoError(t, err)
proxyConf, ok := entry.(*structs.ProxyConfigEntry)
@ -192,7 +192,7 @@ operator = "write"
require.NoError(err)
state := s1.fsm.State()
_, entry, err := state.ConfigEntry(nil, structs.ServiceDefaults, "foo")
_, entry, err := state.ConfigEntry(nil, structs.ServiceDefaults, "foo", nil)
require.NoError(err)
serviceConf, ok := entry.(*structs.ServiceConfigEntry)
@ -237,7 +237,7 @@ func TestConfigEntry_Get(t *testing.T) {
Name: "foo",
}
state := s1.fsm.State()
require.NoError(state.EnsureConfigEntry(1, entry))
require.NoError(state.EnsureConfigEntry(1, entry, nil))
args := structs.ConfigEntryQuery{
Kind: structs.ServiceDefaults,
@ -296,11 +296,11 @@ operator = "read"
require.NoError(state.EnsureConfigEntry(1, &structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
}))
}, nil))
require.NoError(state.EnsureConfigEntry(2, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "foo",
}))
}, nil))
// This should fail since we don't have write perms for the "db" service.
args := structs.ConfigEntryQuery{
@ -350,8 +350,8 @@ func TestConfigEntry_List(t *testing.T) {
},
},
}
require.NoError(state.EnsureConfigEntry(1, expected.Entries[0]))
require.NoError(state.EnsureConfigEntry(2, expected.Entries[1]))
require.NoError(state.EnsureConfigEntry(1, expected.Entries[0], nil))
require.NoError(state.EnsureConfigEntry(2, expected.Entries[1], nil))
args := structs.ConfigEntryQuery{
Kind: structs.ServiceDefaults,
@ -394,9 +394,9 @@ func TestConfigEntry_ListAll(t *testing.T) {
},
},
}
require.NoError(state.EnsureConfigEntry(1, expected.Entries[0]))
require.NoError(state.EnsureConfigEntry(2, expected.Entries[1]))
require.NoError(state.EnsureConfigEntry(3, expected.Entries[2]))
require.NoError(state.EnsureConfigEntry(1, expected.Entries[0], nil))
require.NoError(state.EnsureConfigEntry(2, expected.Entries[1], nil))
require.NoError(state.EnsureConfigEntry(3, expected.Entries[2], nil))
args := structs.DCSpecificRequest{
Datacenter: "dc1",
@ -451,15 +451,15 @@ operator = "read"
require.NoError(state.EnsureConfigEntry(1, &structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
}))
}, nil))
require.NoError(state.EnsureConfigEntry(2, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "foo",
}))
}, nil))
require.NoError(state.EnsureConfigEntry(3, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "db",
}))
}, nil))
// This should filter out the "db" service since we don't have permissions for it.
args := structs.ConfigEntryQuery{
@ -532,15 +532,15 @@ operator = "read"
require.NoError(state.EnsureConfigEntry(1, &structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
}))
}, nil))
require.NoError(state.EnsureConfigEntry(2, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "foo",
}))
}, nil))
require.NoError(state.EnsureConfigEntry(3, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "db",
}))
}, nil))
// This should filter out the "db" service since we don't have permissions for it.
args := structs.ConfigEntryQuery{
@ -600,10 +600,10 @@ func TestConfigEntry_Delete(t *testing.T) {
Name: "foo",
}
state := s1.fsm.State()
require.NoError(t, state.EnsureConfigEntry(1, entry))
require.NoError(t, state.EnsureConfigEntry(1, entry, nil))
// Verify it's there.
_, existing, err := state.ConfigEntry(nil, structs.ServiceDefaults, "foo")
_, existing, err := state.ConfigEntry(nil, structs.ServiceDefaults, "foo", nil)
require.NoError(t, err)
serviceConf, ok := existing.(*structs.ServiceConfigEntry)
@ -613,7 +613,7 @@ func TestConfigEntry_Delete(t *testing.T) {
retry.Run(t, func(r *retry.R) {
// wait for it to be replicated into the secondary dc
_, existing, err := s2.fsm.State().ConfigEntry(nil, structs.ServiceDefaults, "foo")
_, existing, err := s2.fsm.State().ConfigEntry(nil, structs.ServiceDefaults, "foo", nil)
require.NoError(r, err)
require.NotNil(r, existing)
})
@ -627,13 +627,13 @@ func TestConfigEntry_Delete(t *testing.T) {
require.NoError(t, msgpackrpc.CallWithCodec(codec2, "ConfigEntry.Delete", &args, &out))
// Verify the entry was deleted.
_, existing, err = s1.fsm.State().ConfigEntry(nil, structs.ServiceDefaults, "foo")
_, existing, err = s1.fsm.State().ConfigEntry(nil, structs.ServiceDefaults, "foo", nil)
require.NoError(t, err)
require.Nil(t, existing)
// verify it gets deleted from the secondary too
retry.Run(t, func(r *retry.R) {
_, existing, err := s2.fsm.State().ConfigEntry(nil, structs.ServiceDefaults, "foo")
_, existing, err := s2.fsm.State().ConfigEntry(nil, structs.ServiceDefaults, "foo", nil)
require.NoError(r, err)
require.Nil(r, existing)
})
@ -682,11 +682,11 @@ operator = "write"
require.NoError(state.EnsureConfigEntry(1, &structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
}))
}, nil))
require.NoError(state.EnsureConfigEntry(2, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "foo",
}))
}, nil))
// This should fail since we don't have write perms for the "db" service.
args := structs.ConfigEntryRequest{
@ -709,7 +709,7 @@ operator = "write"
require.NoError(msgpackrpc.CallWithCodec(codec, "ConfigEntry.Delete", &args, &out))
// Verify the entry was deleted.
_, existing, err := state.ConfigEntry(nil, structs.ServiceDefaults, "foo")
_, existing, err := state.ConfigEntry(nil, structs.ServiceDefaults, "foo", nil)
require.NoError(err)
require.Nil(existing)
@ -729,7 +729,7 @@ operator = "write"
args.WriteRequest.Token = id
require.NoError(msgpackrpc.CallWithCodec(codec, "ConfigEntry.Delete", &args, &out))
_, existing, err = state.ConfigEntry(nil, structs.ServiceDefaults, "foo")
_, existing, err = state.ConfigEntry(nil, structs.ServiceDefaults, "foo", nil)
require.NoError(err)
require.Nil(existing)
}
@ -753,17 +753,17 @@ func TestConfigEntry_ResolveServiceConfig(t *testing.T) {
Config: map[string]interface{}{
"foo": 1,
},
}))
}, nil))
require.NoError(state.EnsureConfigEntry(2, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "foo",
Protocol: "http",
}))
}, nil))
require.NoError(state.EnsureConfigEntry(2, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "bar",
Protocol: "grpc",
}))
}, nil))
args := structs.ServiceConfigRequest{
Name: "foo",
@ -788,7 +788,7 @@ func TestConfigEntry_ResolveServiceConfig(t *testing.T) {
}
require.Equal(expected, out)
_, entry, err := s1.fsm.State().ConfigEntry(nil, structs.ProxyDefaults, structs.ProxyConfigGlobal)
_, entry, err := s1.fsm.State().ConfigEntry(nil, structs.ProxyDefaults, structs.ProxyConfigGlobal, nil)
require.NoError(err)
require.NotNil(entry)
proxyConf, ok := entry.(*structs.ProxyConfigEntry)
@ -819,17 +819,17 @@ func TestConfigEntry_ResolveServiceConfig_Blocking(t *testing.T) {
Config: map[string]interface{}{
"global": 1,
},
}))
}, nil))
require.NoError(state.EnsureConfigEntry(2, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "foo",
Protocol: "grpc",
}))
}, nil))
require.NoError(state.EnsureConfigEntry(3, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "bar",
Protocol: "http",
}))
}, nil))
var index uint64
@ -863,6 +863,7 @@ func TestConfigEntry_ResolveServiceConfig_Blocking(t *testing.T) {
require.NoError(state.DeleteConfigEntry(index+1,
structs.ServiceDefaults,
"foo",
nil,
))
}()
@ -927,6 +928,7 @@ func TestConfigEntry_ResolveServiceConfig_Blocking(t *testing.T) {
require.NoError(state.DeleteConfigEntry(index+1,
structs.ProxyDefaults,
structs.ProxyConfigGlobal,
nil,
))
}()
@ -979,24 +981,24 @@ func TestConfigEntry_ResolveServiceConfig_UpstreamProxyDefaultsProtocol(t *testi
Config: map[string]interface{}{
"protocol": "http",
},
}))
}, nil))
require.NoError(state.EnsureConfigEntry(2, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "foo",
}))
}, nil))
require.NoError(state.EnsureConfigEntry(2, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "bar",
}))
}, nil))
require.NoError(state.EnsureConfigEntry(2, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "other",
}))
}, nil))
require.NoError(state.EnsureConfigEntry(2, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "alreadyprotocol",
Protocol: "grpc",
}))
}, nil))
args := structs.ServiceConfigRequest{
Name: "foo",
@ -1100,15 +1102,15 @@ operator = "write"
require.NoError(state.EnsureConfigEntry(1, &structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal,
}))
}, nil))
require.NoError(state.EnsureConfigEntry(2, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "foo",
}))
}, nil))
require.NoError(state.EnsureConfigEntry(3, &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "db",
}))
}, nil))
// This should fail since we don't have write perms for the "db" service.
args := structs.ServiceConfigRequest{
@ -1163,7 +1165,7 @@ func TestConfigEntry_ProxyDefaultsExposeConfig(t *testing.T) {
require.True(t, out)
state := s1.fsm.State()
_, entry, err := state.ConfigEntry(nil, structs.ProxyDefaults, "global")
_, entry, err := state.ConfigEntry(nil, structs.ProxyDefaults, "global", nil)
require.NoError(t, err)
proxyConf, ok := entry.(*structs.ProxyConfigEntry)

View File

@ -105,6 +105,7 @@ func (s *Server) fetchConfigEntries(lastRemoteIndex uint64) (*structs.IndexedGen
MinQueryIndex: lastRemoteIndex,
Token: s.tokens.ReplicationToken(),
},
EnterpriseMeta: *s.replicationEnterpriseMeta(),
}
var response structs.IndexedGenericConfigEntries
@ -138,7 +139,7 @@ func (s *Server) replicateConfig(ctx context.Context, lastRemoteIndex uint64) (u
// replication process is.
defer metrics.MeasureSince([]string{"leader", "replication", "config", "apply"}, time.Now())
_, local, err := s.fsm.State().ConfigEntries(nil)
_, local, err := s.fsm.State().ConfigEntries(nil, s.replicationEnterpriseMeta())
if err != nil {
return 0, false, fmt.Errorf("failed to retrieve local config entries: %v", err)
}

View File

@ -74,9 +74,9 @@ func TestReplication_ConfigEntries(t *testing.T) {
entries = append(entries, arg.Entry)
checkSame := func(t *retry.R) error {
_, remote, err := s1.fsm.State().ConfigEntries(nil)
_, remote, err := s1.fsm.State().ConfigEntries(nil, structs.ReplicationEnterpriseMeta())
require.NoError(t, err)
_, local, err := s2.fsm.State().ConfigEntries(nil)
_, local, err := s2.fsm.State().ConfigEntries(nil, structs.ReplicationEnterpriseMeta())
require.NoError(t, err)
require.Len(t, local, len(remote))

View File

@ -30,12 +30,13 @@ func (c *DiscoveryChain) Get(args *structs.DiscoveryChainRequest, reply *structs
defer metrics.MeasureSince([]string{"discovery_chain", "get"}, time.Now())
// Fetch the ACL token, if any.
rule, err := c.srv.ResolveToken(args.Token)
entMeta := args.GetEnterpriseMeta()
var authzContext acl.AuthorizerContext
rule, err := c.srv.ResolveTokenAndDefaultMeta(args.Token, entMeta, &authzContext)
if err != nil {
return err
}
// TODO (namespaces) use actual ent authz context
if rule != nil && rule.ServiceRead(args.Name, nil) != acl.Allow {
if rule != nil && rule.ServiceRead(args.Name, &authzContext) != acl.Allow {
return acl.ErrPermissionDenied
}
@ -48,17 +49,11 @@ func (c *DiscoveryChain) Get(args *structs.DiscoveryChainRequest, reply *structs
evalDC = c.srv.config.Datacenter
}
evalNS := args.EvaluateInNamespace
if evalNS == "" {
// TODO(namespaces) pull from something else?
evalNS = "default"
}
return c.srv.blockingQuery(
&args.QueryOptions,
&reply.QueryMeta,
func(ws memdb.WatchSet, state *state.Store) error {
index, entries, err := state.ReadDiscoveryChainConfigEntries(ws, args.Name)
index, entries, err := state.ReadDiscoveryChainConfigEntries(ws, args.Name, entMeta)
if err != nil {
return err
}
@ -82,7 +77,7 @@ func (c *DiscoveryChain) Get(args *structs.DiscoveryChainRequest, reply *structs
// Then we compile it into something useful.
chain, err := discoverychain.Compile(discoverychain.CompileRequest{
ServiceName: args.Name,
EvaluateInNamespace: evalNS,
EvaluateInNamespace: entMeta.NamespaceOrDefault(),
EvaluateInDatacenter: evalDC,
EvaluateInTrustDomain: currentTrustDomain,
UseInDatacenter: c.srv.config.Datacenter,

View File

@ -92,7 +92,7 @@ func Compile(req CompileRequest) (*structs.CompiledDiscoveryChain, error) {
overrideConnectTimeout: req.OverrideConnectTimeout,
entries: entries,
resolvers: make(map[string]*structs.ServiceResolverConfigEntry),
resolvers: make(map[structs.ServiceID]*structs.ServiceResolverConfigEntry),
splitterNodes: make(map[string]*structs.DiscoveryGraphNode),
resolveNodes: make(map[string]*structs.DiscoveryGraphNode),
@ -134,7 +134,7 @@ type compiler struct {
// resolvers is initially seeded by copying the provided entries.Resolvers
// map and default resolvers are added as they are needed.
resolvers map[string]*structs.ServiceResolverConfigEntry
resolvers map[structs.ServiceID]*structs.ServiceResolverConfigEntry
// cached nodes
splitterNodes map[string]*structs.DiscoveryGraphNode
@ -180,6 +180,13 @@ type customizationMarkers struct {
ConnectTimeout bool
}
// serviceIDString deviates from the standard formatting you would get with
// the String() method on the type itself. It is this way to be more
// consistent with other string ids within the discovery chain.
func serviceIDString(sid structs.ServiceID) string {
return fmt.Sprintf("%s.%s", sid.ID, sid.NamespaceOrDefault())
}
func (m *customizationMarkers) IsZero() bool {
return !m.MeshGateway && !m.Protocol && !m.ConnectTimeout
}
@ -201,19 +208,19 @@ func (c *compiler) recordNode(node *structs.DiscoveryGraphNode) {
c.nodes[node.MapKey()] = node
}
func (c *compiler) recordServiceProtocol(serviceName string) error {
if serviceDefault := c.entries.GetService(serviceName); serviceDefault != nil {
return c.recordProtocol(serviceName, serviceDefault.Protocol)
func (c *compiler) recordServiceProtocol(sid structs.ServiceID) error {
if serviceDefault := c.entries.GetService(sid); serviceDefault != nil {
return c.recordProtocol(sid, serviceDefault.Protocol)
}
if c.entries.GlobalProxy != nil {
var cfg proxyConfig
// Ignore errors and fallback on defaults if it does happen.
_ = mapstructure.WeakDecode(c.entries.GlobalProxy.Config, &cfg)
if cfg.Protocol != "" {
return c.recordProtocol(serviceName, cfg.Protocol)
return c.recordProtocol(sid, cfg.Protocol)
}
}
return c.recordProtocol(serviceName, "")
return c.recordProtocol(sid, "")
}
// proxyConfig is a snippet from agent/xds/config.go:ProxyConfig
@ -221,7 +228,7 @@ type proxyConfig struct {
Protocol string `mapstructure:"protocol"`
}
func (c *compiler) recordProtocol(fromService, protocol string) error {
func (c *compiler) recordProtocol(fromService structs.ServiceID, protocol string) error {
if protocol == "" {
protocol = "tcp"
} else {
@ -234,7 +241,7 @@ func (c *compiler) recordProtocol(fromService, protocol string) error {
return &structs.ConfigEntryGraphError{
Message: fmt.Sprintf(
"discovery chain %q uses inconsistent protocols; service %q has %q which is not %q",
c.serviceName, fromService, protocol, c.protocol,
c.serviceName, fromService.String(), protocol, c.protocol,
),
}
}
@ -494,15 +501,17 @@ func (c *compiler) assembleChain() error {
return fmt.Errorf("assembleChain should only be called once")
}
sid := structs.NewServiceID(c.serviceName, c.GetEnterpriseMeta())
// Check for short circuit path.
if len(c.resolvers) == 0 && c.entries.IsChainEmpty() {
// Materialize defaults and cache.
c.resolvers[c.serviceName] = newDefaultServiceResolver(c.serviceName)
c.resolvers[sid] = newDefaultServiceResolver(sid)
}
// The only router we consult is the one for the service name at the top of
// the chain.
router := c.entries.GetRouter(c.serviceName)
router := c.entries.GetRouter(sid)
if router != nil && c.disableAdvancedRoutingFeatures {
router = nil
c.customizedBy.Protocol = true
@ -520,13 +529,15 @@ func (c *compiler) assembleChain() error {
return nil
}
routerID := structs.NewServiceID(router.Name, router.GetEnterpriseMeta())
routeNode := &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeRouter,
Name: router.Name,
Name: serviceIDString(routerID),
Routes: make([]*structs.DiscoveryRoute, 0, len(router.Routes)+1),
}
c.usesAdvancedRoutingFeatures = true
if err := c.recordServiceProtocol(router.Name); err != nil {
if err := c.recordServiceProtocol(routerID); err != nil {
return err
}
@ -543,19 +554,20 @@ func (c *compiler) assembleChain() error {
dest := route.Destination
svc := defaultIfEmpty(dest.Service, c.serviceName)
destNamespace := defaultIfEmpty(dest.Namespace, c.evaluateInNamespace)
// Check to see if the destination is eligible for splitting.
var (
node *structs.DiscoveryGraphNode
err error
)
if dest.ServiceSubset == "" && dest.Namespace == "" {
if dest.ServiceSubset == "" {
node, err = c.getSplitterOrResolverNode(
c.newTarget(svc, dest.ServiceSubset, dest.Namespace, ""),
c.newTarget(svc, "", destNamespace, ""),
)
} else {
node, err = c.getResolverNode(
c.newTarget(svc, dest.ServiceSubset, dest.Namespace, ""),
c.newTarget(svc, dest.ServiceSubset, destNamespace, ""),
false,
)
}
@ -573,7 +585,7 @@ func (c *compiler) assembleChain() error {
}
defaultRoute := &structs.DiscoveryRoute{
Definition: newDefaultServiceRoute(c.serviceName),
Definition: newDefaultServiceRoute(c.serviceName, c.evaluateInNamespace),
NextNode: defaultDestinationNode.MapKey(),
}
routeNode.Routes = append(routeNode.Routes, defaultRoute)
@ -584,7 +596,7 @@ func (c *compiler) assembleChain() error {
return nil
}
func newDefaultServiceRoute(serviceName string) *structs.ServiceRoute {
func newDefaultServiceRoute(serviceName string, namespace string) *structs.ServiceRoute {
return &structs.ServiceRoute{
Match: &structs.ServiceRouteMatch{
HTTP: &structs.ServiceRouteHTTPMatch{
@ -593,6 +605,7 @@ func newDefaultServiceRoute(serviceName string) *structs.ServiceRoute {
},
Destination: &structs.ServiceRouteDestination{
Service: serviceName,
Namespace: namespace,
},
}
}
@ -652,7 +665,7 @@ func (c *compiler) rewriteTarget(t *structs.DiscoveryTarget, service, serviceSub
}
func (c *compiler) getSplitterOrResolverNode(target *structs.DiscoveryTarget) (*structs.DiscoveryGraphNode, error) {
nextNode, err := c.getSplitterNode(target.Service)
nextNode, err := c.getSplitterNode(target.ServiceID())
if err != nil {
return nil, err
} else if nextNode != nil {
@ -661,14 +674,15 @@ func (c *compiler) getSplitterOrResolverNode(target *structs.DiscoveryTarget) (*
return c.getResolverNode(target, false)
}
func (c *compiler) getSplitterNode(name string) (*structs.DiscoveryGraphNode, error) {
func (c *compiler) getSplitterNode(sid structs.ServiceID) (*structs.DiscoveryGraphNode, error) {
name := serviceIDString(sid)
// Do we already have the node?
if prev, ok := c.splitterNodes[name]; ok {
return prev, nil
}
// Fetch the config entry.
splitter := c.entries.GetSplitter(name)
splitter := c.entries.GetSplitter(sid)
if splitter != nil && c.disableAdvancedRoutingFeatures {
splitter = nil
c.customizedBy.Protocol = true
@ -694,10 +708,15 @@ func (c *compiler) getSplitterNode(name string) (*structs.DiscoveryGraphNode, er
}
splitNode.Splits = append(splitNode.Splits, compiledSplit)
svc := defaultIfEmpty(split.Service, name)
svc := defaultIfEmpty(split.Service, sid.ID)
splitID := structs.ServiceID{
ID: svc,
EnterpriseMeta: *split.GetEnterpriseMeta(&sid.EnterpriseMeta),
}
// Check to see if the split is eligible for additional splitting.
if svc != name && split.ServiceSubset == "" && split.Namespace == "" {
nextNode, err := c.getSplitterNode(svc)
if !splitID.Matches(&sid) && split.ServiceSubset == "" {
nextNode, err := c.getSplitterNode(splitID)
if err != nil {
return nil, err
} else if nextNode != nil {
@ -708,7 +727,7 @@ func (c *compiler) getSplitterNode(name string) (*structs.DiscoveryGraphNode, er
}
node, err := c.getResolverNode(
c.newTarget(svc, split.ServiceSubset, split.Namespace, ""),
c.newTarget(splitID.ID, split.ServiceSubset, splitID.NamespaceOrDefault(), ""),
false,
)
if err != nil {
@ -738,16 +757,18 @@ RESOLVE_AGAIN:
return prev, nil
}
if err := c.recordServiceProtocol(target.Service); err != nil {
targetID := target.ServiceID()
if err := c.recordServiceProtocol(targetID); err != nil {
return nil, err
}
// Fetch the config entry.
resolver, ok := c.resolvers[target.Service]
resolver, ok := c.resolvers[targetID]
if !ok {
// Materialize defaults and cache.
resolver = newDefaultServiceResolver(target.Service)
c.resolvers[target.Service] = resolver
resolver = newDefaultServiceResolver(targetID)
c.resolvers[targetID] = resolver
}
if _, ok := redirectHistory[target.ID]; ok {
@ -829,7 +850,7 @@ RESOLVE_AGAIN:
target.Subset = resolver.Subsets[target.ServiceSubset]
if serviceDefault := c.entries.GetService(target.Service); serviceDefault != nil && serviceDefault.ExternalSNI != "" {
if serviceDefault := c.entries.GetService(targetID); serviceDefault != nil && serviceDefault.ExternalSNI != "" {
// Override the default SNI value.
target.SNI = serviceDefault.ExternalSNI
target.External = true
@ -873,7 +894,7 @@ RESOLVE_AGAIN:
} else {
// Default mesh gateway settings
if serviceDefault := c.entries.GetService(target.Service); serviceDefault != nil {
if serviceDefault := c.entries.GetService(targetID); serviceDefault != nil {
target.MeshGateway = serviceDefault.MeshGateway
}
@ -967,10 +988,11 @@ RESOLVE_AGAIN:
return node, nil
}
func newDefaultServiceResolver(serviceName string) *structs.ServiceResolverConfigEntry {
func newDefaultServiceResolver(sid structs.ServiceID) *structs.ServiceResolverConfigEntry {
return &structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: serviceName,
Name: sid.ID,
EnterpriseMeta: sid.EnterpriseMeta,
}
}

View File

@ -0,0 +1,9 @@
// +build !consulent
package discoverychain
import "github.com/hashicorp/consul/agent/structs"
func (c *compiler) GetEnterpriseMeta() *structs.EnterpriseMeta {
return structs.DefaultEnterpriseMeta()
}

View File

@ -154,14 +154,14 @@ func testcase_JustRouterWithDefaults() compileTestCase {
expect := &structs.CompiledDiscoveryChain{
Protocol: "http",
StartNode: "router:main",
StartNode: "router:main.default",
Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main": &structs.DiscoveryGraphNode{
"router:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main",
Name: "main.default",
Routes: []*structs.DiscoveryRoute{
{
Definition: newDefaultServiceRoute("main"),
Definition: newDefaultServiceRoute("main", "default"),
NextNode: "resolver:main.default.dc1",
},
},
@ -204,14 +204,14 @@ func testcase_RouterWithDefaults_NoSplit_WithResolver() compileTestCase {
expect := &structs.CompiledDiscoveryChain{
Protocol: "http",
StartNode: "router:main",
StartNode: "router:main.default",
Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main": &structs.DiscoveryGraphNode{
"router:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main",
Name: "main.default",
Routes: []*structs.DiscoveryRoute{
{
Definition: newDefaultServiceRoute("main"),
Definition: newDefaultServiceRoute("main", "default"),
NextNode: "resolver:main.default.dc1",
},
},
@ -255,21 +255,21 @@ func testcase_RouterWithDefaults_WithNoopSplit_DefaultResolver() compileTestCase
expect := &structs.CompiledDiscoveryChain{
Protocol: "http",
StartNode: "router:main",
StartNode: "router:main.default",
Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main": &structs.DiscoveryGraphNode{
"router:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main",
Name: "main.default",
Routes: []*structs.DiscoveryRoute{
{
Definition: newDefaultServiceRoute("main"),
NextNode: "splitter:main",
Definition: newDefaultServiceRoute("main", "default"),
NextNode: "splitter:main.default",
},
},
},
"splitter:main": &structs.DiscoveryGraphNode{
"splitter:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main",
Name: "main.default",
Splits: []*structs.DiscoverySplit{
{
Weight: 100,
@ -317,21 +317,21 @@ func testcase_NoopSplit_DefaultResolver_ProtocolFromProxyDefaults() compileTestC
expect := &structs.CompiledDiscoveryChain{
Protocol: "http",
StartNode: "router:main",
StartNode: "router:main.default",
Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main": &structs.DiscoveryGraphNode{
"router:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main",
Name: "main.default",
Routes: []*structs.DiscoveryRoute{
{
Definition: newDefaultServiceRoute("main"),
NextNode: "splitter:main",
Definition: newDefaultServiceRoute("main", "default"),
NextNode: "splitter:main.default",
},
},
},
"splitter:main": &structs.DiscoveryGraphNode{
"splitter:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main",
Name: "main.default",
Splits: []*structs.DiscoverySplit{
{
Weight: 100,
@ -386,21 +386,21 @@ func testcase_RouterWithDefaults_WithNoopSplit_WithResolver() compileTestCase {
expect := &structs.CompiledDiscoveryChain{
Protocol: "http",
StartNode: "router:main",
StartNode: "router:main.default",
Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main": &structs.DiscoveryGraphNode{
"router:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main",
Name: "main.default",
Routes: []*structs.DiscoveryRoute{
{
Definition: newDefaultServiceRoute("main"),
NextNode: "splitter:main",
Definition: newDefaultServiceRoute("main", "default"),
NextNode: "splitter:main.default",
},
},
},
"splitter:main": &structs.DiscoveryGraphNode{
"splitter:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main",
Name: "main.default",
Splits: []*structs.DiscoverySplit{
{
Weight: 100,
@ -463,22 +463,22 @@ func testcase_RouteBypassesSplit() compileTestCase {
},
)
router := entries.GetRouter("main")
router := entries.GetRouter(structs.NewServiceID("main", nil))
expect := &structs.CompiledDiscoveryChain{
Protocol: "http",
StartNode: "router:main",
StartNode: "router:main.default",
Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main": &structs.DiscoveryGraphNode{
"router:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main",
Name: "main.default",
Routes: []*structs.DiscoveryRoute{
{
Definition: &router.Routes[0],
NextNode: "resolver:bypass.other.default.dc1",
},
{
Definition: newDefaultServiceRoute("main"),
Definition: newDefaultServiceRoute("main", "default"),
NextNode: "resolver:main.default.dc1",
},
},
@ -530,11 +530,11 @@ func testcase_NoopSplit_DefaultResolver() compileTestCase {
expect := &structs.CompiledDiscoveryChain{
Protocol: "http",
StartNode: "splitter:main",
StartNode: "splitter:main.default",
Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main": &structs.DiscoveryGraphNode{
"splitter:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main",
Name: "main.default",
Splits: []*structs.DiscoverySplit{
{
Weight: 100,
@ -583,11 +583,11 @@ func testcase_NoopSplit_WithResolver() compileTestCase {
expect := &structs.CompiledDiscoveryChain{
Protocol: "http",
StartNode: "splitter:main",
StartNode: "splitter:main.default",
Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main": &structs.DiscoveryGraphNode{
"splitter:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main",
Name: "main.default",
Splits: []*structs.DiscoverySplit{
{
Weight: 100,
@ -643,11 +643,11 @@ func testcase_SubsetSplit() compileTestCase {
expect := &structs.CompiledDiscoveryChain{
Protocol: "http",
StartNode: "splitter:main",
StartNode: "splitter:main.default",
Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main": &structs.DiscoveryGraphNode{
"splitter:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main",
Name: "main.default",
Splits: []*structs.DiscoverySplit{
{
Weight: 60,
@ -712,11 +712,11 @@ func testcase_ServiceSplit() compileTestCase {
expect := &structs.CompiledDiscoveryChain{
Protocol: "http",
StartNode: "splitter:main",
StartNode: "splitter:main.default",
Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main": &structs.DiscoveryGraphNode{
"splitter:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main",
Name: "main.default",
Splits: []*structs.DiscoverySplit{
{
Weight: 60,
@ -801,11 +801,11 @@ func testcase_SplitBypassesSplit() compileTestCase {
expect := &structs.CompiledDiscoveryChain{
Protocol: "http",
StartNode: "splitter:main",
StartNode: "splitter:main.default",
Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main": &structs.DiscoveryGraphNode{
"splitter:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main",
Name: "main.default",
Splits: []*structs.DiscoverySplit{
{
Weight: 100,
@ -1278,11 +1278,11 @@ func testcase_NoopSplit_WithDefaultSubset() compileTestCase {
expect := &structs.CompiledDiscoveryChain{
Protocol: "http",
StartNode: "splitter:main",
StartNode: "splitter:main.default",
Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main": &structs.DiscoveryGraphNode{
"splitter:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main",
Name: "main.default",
Splits: []*structs.DiscoverySplit{
{
Weight: 100,
@ -1586,11 +1586,11 @@ func testcase_MultiDatacenterCanary() compileTestCase {
expect := &structs.CompiledDiscoveryChain{
Protocol: "http",
StartNode: "splitter:main",
StartNode: "splitter:main.default",
Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main": &structs.DiscoveryGraphNode{
"splitter:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main",
Name: "main.default",
Splits: []*structs.DiscoverySplit{
{
Weight: 60,
@ -1712,16 +1712,16 @@ func testcase_AllBellsAndWhistles() compileTestCase {
)
var (
router = entries.GetRouter("main")
router = entries.GetRouter(structs.NewServiceID("main", nil))
)
expect := &structs.CompiledDiscoveryChain{
Protocol: "http",
StartNode: "router:main",
StartNode: "router:main.default",
Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main": &structs.DiscoveryGraphNode{
"router:main.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main",
Name: "main.default",
Routes: []*structs.DiscoveryRoute{
{
Definition: &router.Routes[0],
@ -1729,17 +1729,17 @@ func testcase_AllBellsAndWhistles() compileTestCase {
},
{
Definition: &router.Routes[1],
NextNode: "splitter:svc-split",
NextNode: "splitter:svc-split.default",
},
{
Definition: newDefaultServiceRoute("main"),
Definition: newDefaultServiceRoute("main", "default"),
NextNode: "resolver:default-subset.main.default.dc1",
},
},
},
"splitter:svc-split": &structs.DiscoveryGraphNode{
"splitter:svc-split.default": &structs.DiscoveryGraphNode{
Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "svc-split",
Name: "svc-split.default",
Splits: []*structs.DiscoverySplit{
{
Weight: 60,
@ -2191,9 +2191,9 @@ func setServiceProtocol(entries *structs.DiscoveryChainConfigEntries, name, prot
func newEntries() *structs.DiscoveryChainConfigEntries {
return &structs.DiscoveryChainConfigEntries{
Routers: make(map[string]*structs.ServiceRouterConfigEntry),
Splitters: make(map[string]*structs.ServiceSplitterConfigEntry),
Resolvers: make(map[string]*structs.ServiceResolverConfigEntry),
Routers: make(map[structs.ServiceID]*structs.ServiceRouterConfigEntry),
Splitters: make(map[structs.ServiceID]*structs.ServiceSplitterConfigEntry),
Resolvers: make(map[structs.ServiceID]*structs.ServiceResolverConfigEntry),
}
}

View File

@ -456,7 +456,7 @@ func (c *FSM) applyConfigEntryOperation(buf []byte, index uint64) interface{} {
case structs.ConfigEntryUpsertCAS:
defer metrics.MeasureSinceWithLabels([]string{"fsm", "config_entry", req.Entry.GetKind()}, time.Now(),
[]metrics.Label{{Name: "op", Value: "upsert"}})
updated, err := c.state.EnsureConfigEntryCAS(index, req.Entry.GetRaftIndex().ModifyIndex, req.Entry)
updated, err := c.state.EnsureConfigEntryCAS(index, req.Entry.GetRaftIndex().ModifyIndex, req.Entry, req.Entry.GetEnterpriseMeta())
if err != nil {
return err
}
@ -464,14 +464,14 @@ func (c *FSM) applyConfigEntryOperation(buf []byte, index uint64) interface{} {
case structs.ConfigEntryUpsert:
defer metrics.MeasureSinceWithLabels([]string{"fsm", "config_entry", req.Entry.GetKind()}, time.Now(),
[]metrics.Label{{Name: "op", Value: "upsert"}})
if err := c.state.EnsureConfigEntry(index, req.Entry); err != nil {
if err := c.state.EnsureConfigEntry(index, req.Entry, req.Entry.GetEnterpriseMeta()); err != nil {
return err
}
return true
case structs.ConfigEntryDelete:
defer metrics.MeasureSinceWithLabels([]string{"fsm", "config_entry", req.Entry.GetKind()}, time.Now(),
[]metrics.Label{{Name: "op", Value: "delete"}})
return c.state.DeleteConfigEntry(index, req.Entry.GetKind(), req.Entry.GetName())
return c.state.DeleteConfigEntry(index, req.Entry.GetKind(), req.Entry.GetName(), req.Entry.GetEnterpriseMeta())
default:
return fmt.Errorf("invalid config entry operation type: %v", req.Op)
}

View File

@ -1422,6 +1422,7 @@ func TestFSM_ConfigEntry(t *testing.T) {
Config: map[string]interface{}{
"foo": "bar",
},
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
}
// Create a new request.
@ -1441,7 +1442,7 @@ func TestFSM_ConfigEntry(t *testing.T) {
// Verify it's in the state store.
{
_, config, err := fsm.state.ConfigEntry(nil, structs.ProxyDefaults, "global")
_, config, err := fsm.state.ConfigEntry(nil, structs.ProxyDefaults, "global", nil)
require.NoError(err)
entry.RaftIndex.CreateIndex = 1
entry.RaftIndex.ModifyIndex = 1

View File

@ -240,8 +240,8 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
Kind: structs.ProxyDefaults,
Name: "global",
}
require.NoError(fsm.state.EnsureConfigEntry(18, serviceConfig))
require.NoError(fsm.state.EnsureConfigEntry(19, proxyConfig))
require.NoError(fsm.state.EnsureConfigEntry(18, serviceConfig, structs.DefaultEnterpriseMeta()))
require.NoError(fsm.state.EnsureConfigEntry(19, proxyConfig, structs.DefaultEnterpriseMeta()))
chunkState := &raftchunking.State{
ChunkMap: make(raftchunking.ChunkMap),
@ -479,11 +479,11 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
assert.Equal(caConfig, caConf)
// Verify config entries are restored
_, serviceConfEntry, err := fsm2.state.ConfigEntry(nil, structs.ServiceDefaults, "foo")
_, serviceConfEntry, err := fsm2.state.ConfigEntry(nil, structs.ServiceDefaults, "foo", structs.DefaultEnterpriseMeta())
require.NoError(err)
assert.Equal(serviceConfig, serviceConfEntry)
_, proxyConfEntry, err := fsm2.state.ConfigEntry(nil, structs.ProxyDefaults, "global")
_, proxyConfEntry, err := fsm2.state.ConfigEntry(nil, structs.ProxyDefaults, "global", structs.DefaultEnterpriseMeta())
require.NoError(err)
assert.Equal(proxyConfig, proxyConfEntry)

View File

@ -1105,6 +1105,8 @@ service "foo" {
Op: structs.IntentionOpCreate,
Intention: structs.TestIntention(t),
}
ixn.Intention.SourceNS = "default"
ixn.Intention.DestinationNS = "default"
ixn.Intention.DestinationName = name
ixn.WriteRequest.Token = "root"
@ -1230,13 +1232,7 @@ func TestIntentionMatch_good(t *testing.T) {
func TestIntentionMatch_acl(t *testing.T) {
t.Parallel()
assert := assert.New(t)
dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.ACLDatacenter = "dc1"
c.ACLsEnabled = true
c.ACLMasterToken = "root"
c.ACLDefaultPolicy = "deny"
})
dir1, s1 := testACLServerWithConfig(t, nil, false)
defer os.RemoveAll(dir1)
defer s1.Shutdown()
codec := rpcClient(t, s1)
@ -1244,37 +1240,15 @@ func TestIntentionMatch_acl(t *testing.T) {
testrpc.WaitForLeader(t, s1.RPC, "dc1")
// Create an ACL with service write permissions. This will grant
// intentions read.
var token string
{
var rules = `
service "bar" {
policy = "write"
}`
req := structs.ACLRequest{
Datacenter: "dc1",
Op: structs.ACLSet,
ACL: structs.ACL{
Name: "User token",
Type: structs.ACLTokenTypeClient,
Rules: rules,
},
WriteRequest: structs.WriteRequest{Token: "root"},
}
assert.Nil(msgpackrpc.CallWithCodec(codec, "ACL.Apply", &req, &token))
}
token, err := upsertTestTokenWithPolicyRules(codec, TestDefaultMasterToken, "dc1", `service "bar" { policy = "write" }`)
require.NoError(t, err)
// Create some records
{
insert := [][]string{
{"foo", "*"},
{"foo", "bar"},
{"foo", "baz"}, // shouldn't match
{"bar", "bar"}, // shouldn't match
{"bar", "*"}, // shouldn't match
{"*", "*"},
insert := []string{
"*",
"bar",
"baz",
}
for _, v := range insert {
@ -1283,13 +1257,12 @@ service "bar" {
Op: structs.IntentionOpCreate,
Intention: structs.TestIntention(t),
}
ixn.Intention.DestinationNS = v[0]
ixn.Intention.DestinationName = v[1]
ixn.WriteRequest.Token = "root"
ixn.Intention.DestinationName = v
ixn.WriteRequest.Token = TestDefaultMasterToken
// Create
var reply string
assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
require.Nil(t, msgpackrpc.CallWithCodec(codec, "Intention.Apply", &ixn, &reply))
}
}
@ -1301,7 +1274,7 @@ service "bar" {
Type: structs.IntentionMatchDestination,
Entries: []structs.IntentionMatchEntry{
{
Namespace: "foo",
Namespace: "default",
Name: "bar",
},
},
@ -1309,8 +1282,8 @@ service "bar" {
}
var resp structs.IndexedIntentionMatches
err := msgpackrpc.CallWithCodec(codec, "Intention.Match", req, &resp)
assert.True(acl.IsErrPermissionDenied(err))
assert.Len(resp.Matches, 0)
require.True(t, acl.IsErrPermissionDenied(err))
require.Len(t, resp.Matches, 0)
}
// Test with proper token
@ -1321,24 +1294,24 @@ service "bar" {
Type: structs.IntentionMatchDestination,
Entries: []structs.IntentionMatchEntry{
{
Namespace: "foo",
Namespace: "default",
Name: "bar",
},
},
},
QueryOptions: structs.QueryOptions{Token: token},
QueryOptions: structs.QueryOptions{Token: token.SecretID},
}
var resp structs.IndexedIntentionMatches
assert.Nil(msgpackrpc.CallWithCodec(codec, "Intention.Match", req, &resp))
assert.Len(resp.Matches, 1)
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.Match", req, &resp))
require.Len(t, resp.Matches, 1)
expected := [][]string{{"foo", "bar"}, {"foo", "*"}, {"*", "*"}}
var actual [][]string
expected := []string{"bar", "*"}
var actual []string
for _, ixn := range resp.Matches[0] {
actual = append(actual, []string{ixn.DestinationNS, ixn.DestinationName})
actual = append(actual, ixn.DestinationName)
}
assert.Equal(expected, actual)
require.ElementsMatch(t, expected, actual)
}
}

View File

@ -957,7 +957,7 @@ func (s *Server) bootstrapConfigEntries(entries []structs.ConfigEntry) error {
state := s.fsm.State()
for _, entry := range entries {
// avoid a round trip through Raft if we know the CAS is going to fail
_, existing, err := state.ConfigEntry(nil, entry.GetKind(), entry.GetName())
_, existing, err := state.ConfigEntry(nil, entry.GetKind(), entry.GetName(), entry.GetEnterpriseMeta())
if err != nil {
return fmt.Errorf("Failed to determine whether the configuration for %q / %q already exists: %v", entry.GetKind(), entry.GetName(), err)
}

View File

@ -1153,7 +1153,7 @@ func TestLeader_ConfigEntryBootstrap(t *testing.T) {
testrpc.WaitForTestAgent(t, s1.RPC, "dc1")
retry.Run(t, func(t *retry.R) {
_, entry, err := s1.fsm.State().ConfigEntry(nil, structs.ProxyDefaults, structs.ProxyConfigGlobal)
_, entry, err := s1.fsm.State().ConfigEntry(nil, structs.ProxyDefaults, structs.ProxyConfigGlobal, structs.DefaultEnterpriseMeta())
require.NoError(t, err)
require.NotNil(t, entry)
global, ok := entry.(*structs.ProxyConfigEntry)

View File

@ -1135,7 +1135,7 @@ func TestServer_Reload(t *testing.T) {
s.ReloadConfig(s.config)
_, entry, err := s.fsm.State().ConfigEntry(nil, structs.ProxyDefaults, structs.ProxyConfigGlobal)
_, entry, err := s.fsm.State().ConfigEntry(nil, structs.ProxyDefaults, structs.ProxyConfigGlobal, structs.DefaultEnterpriseMeta())
require.NoError(t, err)
require.NotNil(t, entry)
global, ok := entry.(*structs.ProxyConfigEntry)

View File

@ -748,6 +748,32 @@ func (s *Store) Services(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (ui
return idx, results, nil
}
func (s *Store) ServiceList(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.ServiceList, error) {
tx := s.db.Txn(false)
defer tx.Abort()
idx := s.catalogServicesMaxIndex(tx, entMeta)
services, err := s.catalogServiceList(tx, entMeta, true)
if err != nil {
return 0, nil, fmt.Errorf("failed querying services: %s", err)
}
ws.Add(services.WatchCh())
unique := make(map[structs.ServiceID]struct{})
for service := services.Next(); service != nil; service = services.Next() {
svc := service.(*structs.ServiceNode)
unique[svc.CompoundServiceName()] = struct{}{}
}
results := make(structs.ServiceList, 0, len(unique))
for sid, _ := range unique {
results = append(results, structs.ServiceInfo{Name: sid.ID, EnterpriseMeta: sid.EnterpriseMeta})
}
return idx, results, nil
}
// ServicesByNodeMeta returns all services, filtered by the given node metadata.
func (s *Store) ServicesByNodeMeta(ws memdb.WatchSet, filters map[string]string, entMeta *structs.EnterpriseMeta) (uint64, structs.Services, error) {
tx := s.db.Txn(false)

View File

@ -12,48 +12,6 @@ const (
configTableName = "config-entries"
)
// configTableSchema returns a new table schema used to store global
// config entries.
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,
},
},
},
},
"kind": &memdb.IndexSchema{
Name: "kind",
AllowMissing: false,
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "Kind",
Lowercase: true,
},
},
"link": &memdb.IndexSchema{
Name: "link",
AllowMissing: true,
Unique: false,
Indexer: &ConfigEntryLinkIndex{},
},
},
}
}
type ConfigEntryLinkIndex struct {
}
@ -61,7 +19,7 @@ type discoveryChainConfigEntry interface {
structs.ConfigEntry
// ListRelatedServices returns a list of other names of services referenced
// in this config entry.
ListRelatedServices() []string
ListRelatedServices() []structs.ServiceID
}
func (s *ConfigEntryLinkIndex) FromObject(obj interface{}) (bool, [][]byte, error) {
@ -84,7 +42,7 @@ func (s *ConfigEntryLinkIndex) FromObject(obj interface{}) (bool, [][]byte, erro
vals := make([][]byte, 0, numLinks)
for _, linkedService := range linkedServices {
vals = append(vals, []byte(linkedService+"\x00"))
vals = append(vals, []byte(linkedService.String()+"\x00"))
}
return true, vals, nil
@ -94,13 +52,12 @@ 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)
arg, ok := args[0].(structs.ServiceID)
if !ok {
return nil, fmt.Errorf("argument must be a string: %#v", args[0])
return nil, fmt.Errorf("argument must be a structs.ServiceID: %#v", args[0])
}
// Add the null character as a terminator
arg += "\x00"
return []byte(arg), nil
return []byte(arg.String() + "\x00"), nil
}
func (s *ConfigEntryLinkIndex) PrefixFromArgs(args ...interface{}) ([]byte, error) {
@ -150,18 +107,18 @@ func (s *Restore) ConfigEntry(c structs.ConfigEntry) error {
}
// ConfigEntry is called to get a given config entry.
func (s *Store) ConfigEntry(ws memdb.WatchSet, kind, name string) (uint64, structs.ConfigEntry, error) {
func (s *Store) ConfigEntry(ws memdb.WatchSet, kind, name string, entMeta *structs.EnterpriseMeta) (uint64, structs.ConfigEntry, error) {
tx := s.db.Txn(false)
defer tx.Abort()
return s.configEntryTxn(tx, ws, kind, name)
return s.configEntryTxn(tx, ws, kind, name, entMeta)
}
func (s *Store) configEntryTxn(tx *memdb.Txn, ws memdb.WatchSet, kind, name string) (uint64, structs.ConfigEntry, error) {
func (s *Store) configEntryTxn(tx *memdb.Txn, ws memdb.WatchSet, kind, name string, entMeta *structs.EnterpriseMeta) (uint64, structs.ConfigEntry, error) {
// Get the index
idx := maxIndexTxn(tx, configTableName)
// Get the existing config entry.
watchCh, existing, err := tx.FirstWatch(configTableName, "id", kind, name)
watchCh, existing, err := s.firstWatchConfigEntryWithTxn(tx, kind, name, entMeta)
if err != nil {
return 0, nil, fmt.Errorf("failed config entry lookup: %s", err)
}
@ -179,19 +136,19 @@ func (s *Store) configEntryTxn(tx *memdb.Txn, ws memdb.WatchSet, kind, name stri
}
// ConfigEntries is called to get all config entry objects.
func (s *Store) ConfigEntries(ws memdb.WatchSet) (uint64, []structs.ConfigEntry, error) {
return s.ConfigEntriesByKind(ws, "")
func (s *Store) ConfigEntries(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, []structs.ConfigEntry, error) {
return s.ConfigEntriesByKind(ws, "", entMeta)
}
// ConfigEntriesByKind is called to get all config entry objects with the given kind.
// If kind is empty, all config entries will be returned.
func (s *Store) ConfigEntriesByKind(ws memdb.WatchSet, kind string) (uint64, []structs.ConfigEntry, error) {
func (s *Store) ConfigEntriesByKind(ws memdb.WatchSet, kind string, entMeta *structs.EnterpriseMeta) (uint64, []structs.ConfigEntry, error) {
tx := s.db.Txn(false)
defer tx.Abort()
return s.configEntriesByKindTxn(tx, ws, kind)
return s.configEntriesByKindTxn(tx, ws, kind, entMeta)
}
func (s *Store) configEntriesByKindTxn(tx *memdb.Txn, ws memdb.WatchSet, kind string) (uint64, []structs.ConfigEntry, error) {
func (s *Store) configEntriesByKindTxn(tx *memdb.Txn, ws memdb.WatchSet, kind string, entMeta *structs.EnterpriseMeta) (uint64, []structs.ConfigEntry, error) {
// Get the index
idx := maxIndexTxn(tx, configTableName)
@ -199,9 +156,9 @@ func (s *Store) configEntriesByKindTxn(tx *memdb.Txn, ws memdb.WatchSet, kind st
var iter memdb.ResultIterator
var err error
if kind != "" {
iter, err = tx.Get(configTableName, "kind", kind)
iter, err = getConfigEntryKindsWithTxn(tx, kind, entMeta)
} else {
iter, err = tx.Get(configTableName, "id")
iter, err = getAllConfigEntriesWithTxn(tx, entMeta)
}
if err != nil {
return 0, nil, fmt.Errorf("failed config entry lookup: %s", err)
@ -216,11 +173,11 @@ func (s *Store) configEntriesByKindTxn(tx *memdb.Txn, ws memdb.WatchSet, kind st
}
// EnsureConfigEntry is called to do an upsert of a given config entry.
func (s *Store) EnsureConfigEntry(idx uint64, conf structs.ConfigEntry) error {
func (s *Store) EnsureConfigEntry(idx uint64, conf structs.ConfigEntry, entMeta *structs.EnterpriseMeta) error {
tx := s.db.Txn(true)
defer tx.Abort()
if err := s.ensureConfigEntryTxn(tx, idx, conf); err != nil {
if err := s.ensureConfigEntryTxn(tx, idx, conf, entMeta); err != nil {
return err
}
@ -229,9 +186,9 @@ func (s *Store) EnsureConfigEntry(idx uint64, conf structs.ConfigEntry) error {
}
// ensureConfigEntryTxn upserts a config entry inside of a transaction.
func (s *Store) ensureConfigEntryTxn(tx *memdb.Txn, idx uint64, conf structs.ConfigEntry) error {
func (s *Store) ensureConfigEntryTxn(tx *memdb.Txn, idx uint64, conf structs.ConfigEntry, entMeta *structs.EnterpriseMeta) error {
// Check for existing configuration.
existing, err := tx.First(configTableName, "id", conf.GetKind(), conf.GetName())
existing, err := s.firstConfigEntryWithTxn(tx, conf.GetKind(), conf.GetName(), entMeta)
if err != nil {
return fmt.Errorf("failed configuration lookup: %s", err)
}
@ -252,13 +209,14 @@ func (s *Store) ensureConfigEntryTxn(tx *memdb.Txn, idx uint64, conf structs.Con
conf.GetKind(),
conf.GetName(),
conf,
entMeta,
)
if err != nil {
return err // Err is already sufficiently decorated.
}
// Insert the config entry and update the index
if err := tx.Insert(configTableName, conf); err != nil {
if err := s.insertConfigEntryWithTxn(tx, conf); err != nil {
return fmt.Errorf("failed inserting config entry: %s", err)
}
if err := indexUpdateMaxTxn(tx, idx, configTableName); err != nil {
@ -269,12 +227,12 @@ func (s *Store) ensureConfigEntryTxn(tx *memdb.Txn, idx uint64, conf structs.Con
}
// 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) {
func (s *Store) EnsureConfigEntryCAS(idx, cidx uint64, conf structs.ConfigEntry, entMeta *structs.EnterpriseMeta) (bool, error) {
tx := s.db.Txn(true)
defer tx.Abort()
// Check for existing configuration.
existing, err := tx.First(configTableName, "id", conf.GetKind(), conf.GetName())
existing, err := s.firstConfigEntryWithTxn(tx, conf.GetKind(), conf.GetName(), entMeta)
if err != nil {
return false, fmt.Errorf("failed configuration lookup: %s", err)
}
@ -295,7 +253,7 @@ func (s *Store) EnsureConfigEntryCAS(idx, cidx uint64, conf structs.ConfigEntry)
return false, nil
}
if err := s.ensureConfigEntryTxn(tx, idx, conf); err != nil {
if err := s.ensureConfigEntryTxn(tx, idx, conf, entMeta); err != nil {
return false, err
}
@ -303,12 +261,12 @@ func (s *Store) EnsureConfigEntryCAS(idx, cidx uint64, conf structs.ConfigEntry)
return true, nil
}
func (s *Store) DeleteConfigEntry(idx uint64, kind, name string) error {
func (s *Store) DeleteConfigEntry(idx uint64, kind, name string, entMeta *structs.EnterpriseMeta) error {
tx := s.db.Txn(true)
defer tx.Abort()
// Try to retrieve the existing config entry.
existing, err := tx.First(configTableName, "id", kind, name)
existing, err := s.firstConfigEntryWithTxn(tx, kind, name, entMeta)
if err != nil {
return fmt.Errorf("failed config entry lookup: %s", err)
}
@ -322,6 +280,7 @@ func (s *Store) DeleteConfigEntry(idx uint64, kind, name string) error {
kind,
name,
nil,
entMeta,
)
if err != nil {
return err // Err is already sufficiently decorated.
@ -351,7 +310,9 @@ func (s *Store) validateProposedConfigEntryInGraph(
idx uint64,
kind, name string,
next structs.ConfigEntry,
entMeta *structs.EnterpriseMeta,
) error {
validateAllChains := false
switch kind {
@ -368,7 +329,7 @@ func (s *Store) validateProposedConfigEntryInGraph(
return fmt.Errorf("unhandled kind %q during validation of %q", kind, name)
}
return s.validateProposedConfigEntryInServiceGraph(tx, idx, kind, name, next, validateAllChains)
return s.validateProposedConfigEntryInServiceGraph(tx, idx, kind, name, next, validateAllChains, entMeta)
}
var serviceGraphKinds = []string{
@ -383,10 +344,11 @@ func (s *Store) validateProposedConfigEntryInServiceGraph(
kind, name string,
next structs.ConfigEntry,
validateAllChains bool,
entMeta *structs.EnterpriseMeta,
) error {
// Collect all of the chains that could be affected by this change
// including our own.
checkChains := make(map[string]struct{})
checkChains := make(map[structs.ServiceID]struct{})
if validateAllChains {
// Must be proxy-defaults/global.
@ -395,23 +357,24 @@ func (s *Store) validateProposedConfigEntryInServiceGraph(
// somehow omit the ones that have a default protocol configured.
for _, kind := range serviceGraphKinds {
_, entries, err := s.configEntriesByKindTxn(tx, nil, kind)
_, entries, err := s.configEntriesByKindTxn(tx, nil, kind, entMeta)
if err != nil {
return err
}
for _, entry := range entries {
checkChains[entry.GetName()] = struct{}{}
checkChains[structs.NewServiceID(entry.GetName(), entry.GetEnterpriseMeta())] = struct{}{}
}
}
} else {
// Must be a single chain.
checkChains[name] = struct{}{}
sid := structs.NewServiceID(name, entMeta)
checkChains[sid] = struct{}{}
iter, err := tx.Get(configTableName, "link", name)
iter, err := tx.Get(configTableName, "link", sid)
for raw := iter.Next(); raw != nil; raw = iter.Next() {
entry := raw.(structs.ConfigEntry)
checkChains[entry.GetName()] = struct{}{}
checkChains[structs.NewServiceID(entry.GetName(), entry.GetEnterpriseMeta())] = struct{}{}
}
if err != nil {
return err
@ -422,8 +385,8 @@ func (s *Store) validateProposedConfigEntryInServiceGraph(
{Kind: kind, Name: name}: next,
}
for chainName, _ := range checkChains {
if err := s.testCompileDiscoveryChain(tx, nil, chainName, overrides); err != nil {
for chain, _ := range checkChains {
if err := s.testCompileDiscoveryChain(tx, nil, chain.ID, overrides, &chain.EnterpriseMeta); err != nil {
return err
}
}
@ -436,8 +399,9 @@ func (s *Store) testCompileDiscoveryChain(
ws memdb.WatchSet,
chainName string,
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
entMeta *structs.EnterpriseMeta,
) error {
_, speculativeEntries, err := s.readDiscoveryChainConfigEntriesTxn(tx, nil, chainName, overrides)
_, speculativeEntries, err := s.readDiscoveryChainConfigEntriesTxn(tx, nil, chainName, overrides, entMeta)
if err != nil {
return err
}
@ -448,7 +412,7 @@ func (s *Store) testCompileDiscoveryChain(
// 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
req := discoverychain.CompileRequest{
ServiceName: chainName,
EvaluateInNamespace: "default",
EvaluateInNamespace: entMeta.NamespaceOrDefault(),
EvaluateInDatacenter: "dc1",
EvaluateInTrustDomain: "b6fc9da3-03d4-4b5a-9134-c045e9b20152.consul",
UseInDatacenter: "dc1",
@ -467,8 +431,9 @@ func (s *Store) testCompileDiscoveryChain(
func (s *Store) ReadDiscoveryChainConfigEntries(
ws memdb.WatchSet,
serviceName string,
entMeta *structs.EnterpriseMeta,
) (uint64, *structs.DiscoveryChainConfigEntries, error) {
return s.readDiscoveryChainConfigEntries(ws, serviceName, nil)
return s.readDiscoveryChainConfigEntries(ws, serviceName, nil, entMeta)
}
// readDiscoveryChainConfigEntries will query for the full discovery chain for
@ -485,10 +450,11 @@ func (s *Store) readDiscoveryChainConfigEntries(
ws memdb.WatchSet,
serviceName string,
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
entMeta *structs.EnterpriseMeta,
) (uint64, *structs.DiscoveryChainConfigEntries, error) {
tx := s.db.Txn(false)
defer tx.Abort()
return s.readDiscoveryChainConfigEntriesTxn(tx, ws, serviceName, overrides)
return s.readDiscoveryChainConfigEntriesTxn(tx, ws, serviceName, overrides, entMeta)
}
func (s *Store) readDiscoveryChainConfigEntriesTxn(
@ -496,6 +462,7 @@ func (s *Store) readDiscoveryChainConfigEntriesTxn(
ws memdb.WatchSet,
serviceName string,
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
entMeta *structs.EnterpriseMeta,
) (uint64, *structs.DiscoveryChainConfigEntries, error) {
res := structs.NewDiscoveryChainConfigEntries()
@ -511,13 +478,15 @@ func (s *Store) readDiscoveryChainConfigEntriesTxn(
// 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{})
todoSplitters = make(map[structs.ServiceID]struct{})
todoResolvers = make(map[structs.ServiceID]struct{})
todoDefaults = make(map[structs.ServiceID]struct{})
)
sid := structs.NewServiceID(serviceName, entMeta)
// Grab the proxy defaults if they exist.
idx, proxy, err := s.getProxyConfigEntryTxn(tx, ws, structs.ProxyConfigGlobal, overrides)
idx, proxy, err := s.getProxyConfigEntryTxn(tx, ws, structs.ProxyConfigGlobal, overrides, structs.DefaultEnterpriseMeta())
if err != nil {
return 0, nil, err
} else if proxy != nil {
@ -525,14 +494,14 @@ func (s *Store) readDiscoveryChainConfigEntriesTxn(
}
// At every step we'll need service defaults.
todoDefaults[serviceName] = struct{}{}
todoDefaults[sid] = struct{}{}
// first fetch the router, of which we only collect 1 per chain eval
_, router, err := s.getRouterConfigEntryTxn(tx, ws, serviceName, overrides)
_, router, err := s.getRouterConfigEntryTxn(tx, ws, serviceName, overrides, entMeta)
if err != nil {
return 0, nil, err
} else if router != nil {
res.Routers[serviceName] = router
res.Routers[sid] = router
}
if router != nil {
@ -541,39 +510,39 @@ func (s *Store) readDiscoveryChainConfigEntriesTxn(
}
} else {
// Next hop in the chain is the splitter.
todoSplitters[serviceName] = struct{}{}
todoSplitters[sid] = struct{}{}
}
for {
name, ok := anyKey(todoSplitters)
splitID, ok := anyKey(todoSplitters)
if !ok {
break
}
delete(todoSplitters, name)
delete(todoSplitters, splitID)
if _, ok := res.Splitters[name]; ok {
if _, ok := res.Splitters[splitID]; ok {
continue // already fetched
}
// Yes, even for splitters.
todoDefaults[name] = struct{}{}
todoDefaults[splitID] = struct{}{}
_, splitter, err := s.getSplitterConfigEntryTxn(tx, ws, name, overrides)
_, splitter, err := s.getSplitterConfigEntryTxn(tx, ws, splitID.ID, overrides, &splitID.EnterpriseMeta)
if err != nil {
return 0, nil, err
}
if splitter == nil {
res.Splitters[name] = nil
res.Splitters[splitID] = nil
// Next hop in the chain is the resolver.
todoResolvers[name] = struct{}{}
todoResolvers[splitID] = struct{}{}
continue
}
res.Splitters[name] = splitter
res.Splitters[splitID] = splitter
todoResolvers[name] = struct{}{}
todoResolvers[splitID] = struct{}{}
for _, svc := range splitter.ListRelatedServices() {
// If there is no splitter, this will end up adding a resolver
// after another iteration.
@ -582,30 +551,30 @@ func (s *Store) readDiscoveryChainConfigEntriesTxn(
}
for {
name, ok := anyKey(todoResolvers)
resolverID, ok := anyKey(todoResolvers)
if !ok {
break
}
delete(todoResolvers, name)
delete(todoResolvers, resolverID)
if _, ok := res.Resolvers[name]; ok {
if _, ok := res.Resolvers[resolverID]; ok {
continue // already fetched
}
// And resolvers, too.
todoDefaults[name] = struct{}{}
todoDefaults[resolverID] = struct{}{}
_, resolver, err := s.getResolverConfigEntryTxn(tx, ws, name, overrides)
_, resolver, err := s.getResolverConfigEntryTxn(tx, ws, resolverID.ID, overrides, &resolverID.EnterpriseMeta)
if err != nil {
return 0, nil, err
}
if resolver == nil {
res.Resolvers[name] = nil
res.Resolvers[resolverID] = nil
continue
}
res.Resolvers[name] = resolver
res.Resolvers[resolverID] = resolver
for _, svc := range resolver.ListRelatedServices() {
todoResolvers[svc] = struct{}{}
@ -613,48 +582,48 @@ func (s *Store) readDiscoveryChainConfigEntriesTxn(
}
for {
name, ok := anyKey(todoDefaults)
svcID, ok := anyKey(todoDefaults)
if !ok {
break
}
delete(todoDefaults, name)
delete(todoDefaults, svcID)
if _, ok := res.Services[name]; ok {
if _, ok := res.Services[svcID]; ok {
continue // already fetched
}
_, entry, err := s.getServiceConfigEntryTxn(tx, ws, name, overrides)
_, entry, err := s.getServiceConfigEntryTxn(tx, ws, svcID.ID, overrides, &svcID.EnterpriseMeta)
if err != nil {
return 0, nil, err
}
if entry == nil {
res.Services[name] = nil
res.Services[svcID] = nil
continue
}
res.Services[name] = entry
res.Services[svcID] = entry
}
// Strip nils now that they are no longer necessary.
for name, entry := range res.Routers {
for sid, entry := range res.Routers {
if entry == nil {
delete(res.Routers, name)
delete(res.Routers, sid)
}
}
for name, entry := range res.Splitters {
for sid, entry := range res.Splitters {
if entry == nil {
delete(res.Splitters, name)
delete(res.Splitters, sid)
}
}
for name, entry := range res.Resolvers {
for sid, entry := range res.Resolvers {
if entry == nil {
delete(res.Resolvers, name)
delete(res.Resolvers, sid)
}
}
for name, entry := range res.Services {
for sid, entry := range res.Services {
if entry == nil {
delete(res.Services, name)
delete(res.Services, sid)
}
}
@ -663,14 +632,14 @@ func (s *Store) readDiscoveryChainConfigEntriesTxn(
// 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) {
func anyKey(m map[structs.ServiceID]struct{}) (structs.ServiceID, bool) {
if len(m) == 0 {
return "", false
return structs.ServiceID{}, false
}
for k, _ := range m {
return k, true
}
return "", false
return structs.ServiceID{}, false
}
// getProxyConfigEntryTxn is a convenience method for fetching a
@ -682,8 +651,9 @@ func (s *Store) getProxyConfigEntryTxn(
ws memdb.WatchSet,
name string,
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
entMeta *structs.EnterpriseMeta,
) (uint64, *structs.ProxyConfigEntry, error) {
idx, entry, err := s.configEntryWithOverridesTxn(tx, ws, structs.ProxyDefaults, name, overrides)
idx, entry, err := s.configEntryWithOverridesTxn(tx, ws, structs.ProxyDefaults, name, overrides, entMeta)
if err != nil {
return 0, nil, err
} else if entry == nil {
@ -706,8 +676,9 @@ func (s *Store) getServiceConfigEntryTxn(
ws memdb.WatchSet,
serviceName string,
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
entMeta *structs.EnterpriseMeta,
) (uint64, *structs.ServiceConfigEntry, error) {
idx, entry, err := s.configEntryWithOverridesTxn(tx, ws, structs.ServiceDefaults, serviceName, overrides)
idx, entry, err := s.configEntryWithOverridesTxn(tx, ws, structs.ServiceDefaults, serviceName, overrides, entMeta)
if err != nil {
return 0, nil, err
} else if entry == nil {
@ -730,8 +701,9 @@ func (s *Store) getRouterConfigEntryTxn(
ws memdb.WatchSet,
serviceName string,
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
entMeta *structs.EnterpriseMeta,
) (uint64, *structs.ServiceRouterConfigEntry, error) {
idx, entry, err := s.configEntryWithOverridesTxn(tx, ws, structs.ServiceRouter, serviceName, overrides)
idx, entry, err := s.configEntryWithOverridesTxn(tx, ws, structs.ServiceRouter, serviceName, overrides, entMeta)
if err != nil {
return 0, nil, err
} else if entry == nil {
@ -754,8 +726,9 @@ func (s *Store) getSplitterConfigEntryTxn(
ws memdb.WatchSet,
serviceName string,
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
entMeta *structs.EnterpriseMeta,
) (uint64, *structs.ServiceSplitterConfigEntry, error) {
idx, entry, err := s.configEntryWithOverridesTxn(tx, ws, structs.ServiceSplitter, serviceName, overrides)
idx, entry, err := s.configEntryWithOverridesTxn(tx, ws, structs.ServiceSplitter, serviceName, overrides, entMeta)
if err != nil {
return 0, nil, err
} else if entry == nil {
@ -778,8 +751,9 @@ func (s *Store) getResolverConfigEntryTxn(
ws memdb.WatchSet,
serviceName string,
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
entMeta *structs.EnterpriseMeta,
) (uint64, *structs.ServiceResolverConfigEntry, error) {
idx, entry, err := s.configEntryWithOverridesTxn(tx, ws, structs.ServiceResolver, serviceName, overrides)
idx, entry, err := s.configEntryWithOverridesTxn(tx, ws, structs.ServiceResolver, serviceName, overrides, entMeta)
if err != nil {
return 0, nil, err
} else if entry == nil {
@ -799,6 +773,7 @@ func (s *Store) configEntryWithOverridesTxn(
kind string,
name string,
overrides map[structs.ConfigEntryKindName]structs.ConfigEntry,
entMeta *structs.EnterpriseMeta,
) (uint64, structs.ConfigEntry, error) {
if len(overrides) > 0 {
entry, ok := overrides[structs.ConfigEntryKindName{
@ -809,5 +784,5 @@ func (s *Store) configEntryWithOverridesTxn(
}
}
return s.configEntryTxn(tx, ws, kind, name)
return s.configEntryTxn(tx, ws, kind, name, entMeta)
}

View File

@ -0,0 +1,73 @@
// +build !consulent
package state
import (
"github.com/hashicorp/consul/agent/structs"
memdb "github.com/hashicorp/go-memdb"
)
// configTableSchema returns a new table schema used to store global
// config entries.
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,
},
},
},
},
"kind": &memdb.IndexSchema{
Name: "kind",
AllowMissing: false,
Unique: false,
Indexer: &memdb.StringFieldIndex{
Field: "Kind",
Lowercase: true,
},
},
"link": &memdb.IndexSchema{
Name: "link",
AllowMissing: true,
Unique: false,
Indexer: &ConfigEntryLinkIndex{},
},
},
}
}
func (s *Store) firstConfigEntryWithTxn(tx *memdb.Txn,
kind, name string, entMeta *structs.EnterpriseMeta) (interface{}, error) {
return tx.First(configTableName, "id", kind, name)
}
func (s *Store) firstWatchConfigEntryWithTxn(tx *memdb.Txn,
kind, name string, entMeta *structs.EnterpriseMeta) (<-chan struct{}, interface{}, error) {
return tx.FirstWatch(configTableName, "id", kind, name)
}
func (s *Store) insertConfigEntryWithTxn(tx *memdb.Txn, conf structs.ConfigEntry) error {
return tx.Insert(configTableName, conf)
}
func getAllConfigEntriesWithTxn(tx *memdb.Txn, entMeta *structs.EnterpriseMeta) (memdb.ResultIterator, error) {
return tx.Get(configTableName, "id")
}
func getConfigEntryKindsWithTxn(tx *memdb.Txn,
kind string, entMeta *structs.EnterpriseMeta) (memdb.ResultIterator, error) {
return tx.Get(configTableName, "kind", kind)
}

View File

@ -22,9 +22,9 @@ func TestStore_ConfigEntry(t *testing.T) {
}
// Create
require.NoError(s.EnsureConfigEntry(0, expected))
require.NoError(s.EnsureConfigEntry(0, expected, nil))
idx, config, err := s.ConfigEntry(nil, structs.ProxyDefaults, "global")
idx, config, err := s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil)
require.NoError(err)
require.Equal(uint64(0), idx)
require.Equal(expected, config)
@ -37,17 +37,17 @@ func TestStore_ConfigEntry(t *testing.T) {
"DestinationServiceName": "bar",
},
}
require.NoError(s.EnsureConfigEntry(1, updated))
require.NoError(s.EnsureConfigEntry(1, updated, nil))
idx, config, err = s.ConfigEntry(nil, structs.ProxyDefaults, "global")
idx, config, err = s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil)
require.NoError(err)
require.Equal(uint64(1), idx)
require.Equal(updated, config)
// Delete
require.NoError(s.DeleteConfigEntry(2, structs.ProxyDefaults, "global"))
require.NoError(s.DeleteConfigEntry(2, structs.ProxyDefaults, "global", nil))
idx, config, err = s.ConfigEntry(nil, structs.ProxyDefaults, "global")
idx, config, err = s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil)
require.NoError(err)
require.Equal(uint64(2), idx)
require.Nil(config)
@ -57,19 +57,19 @@ func TestStore_ConfigEntry(t *testing.T) {
Kind: structs.ServiceDefaults,
Name: "foo",
}
require.NoError(s.EnsureConfigEntry(3, serviceConf))
require.NoError(s.EnsureConfigEntry(3, serviceConf, nil))
ws := memdb.NewWatchSet()
_, _, err = s.ConfigEntry(ws, structs.ServiceDefaults, "foo")
_, _, err = s.ConfigEntry(ws, structs.ServiceDefaults, "foo", nil)
require.NoError(err)
// Make an unrelated modification and make sure the watch doesn't fire.
require.NoError(s.EnsureConfigEntry(4, updated))
require.NoError(s.EnsureConfigEntry(4, updated, nil))
require.False(watchFired(ws))
// Update the watched config and make sure it fires.
serviceConf.Protocol = "http"
require.NoError(s.EnsureConfigEntry(5, serviceConf))
require.NoError(s.EnsureConfigEntry(5, serviceConf, nil))
require.True(watchFired(ws))
}
@ -86,9 +86,9 @@ func TestStore_ConfigEntryCAS(t *testing.T) {
}
// Create
require.NoError(s.EnsureConfigEntry(1, expected))
require.NoError(s.EnsureConfigEntry(1, expected, nil))
idx, config, err := s.ConfigEntry(nil, structs.ProxyDefaults, "global")
idx, config, err := s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil)
require.NoError(err)
require.Equal(uint64(1), idx)
require.Equal(expected, config)
@ -101,23 +101,23 @@ func TestStore_ConfigEntryCAS(t *testing.T) {
"DestinationServiceName": "bar",
},
}
ok, err := s.EnsureConfigEntryCAS(2, 99, updated)
ok, err := s.EnsureConfigEntryCAS(2, 99, updated, nil)
require.False(ok)
require.NoError(err)
// Entry should not be changed
idx, config, err = s.ConfigEntry(nil, structs.ProxyDefaults, "global")
idx, config, err = s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil)
require.NoError(err)
require.Equal(uint64(1), idx)
require.Equal(expected, config)
// Update with a valid index
ok, err = s.EnsureConfigEntryCAS(2, 1, updated)
ok, err = s.EnsureConfigEntryCAS(2, 1, updated, nil)
require.True(ok)
require.NoError(err)
// Entry should be updated
idx, config, err = s.ConfigEntry(nil, structs.ProxyDefaults, "global")
idx, config, err = s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil)
require.NoError(err)
require.Equal(uint64(2), idx)
require.Equal(updated, config)
@ -141,25 +141,25 @@ func TestStore_ConfigEntries(t *testing.T) {
Name: "test3",
}
require.NoError(s.EnsureConfigEntry(0, entry1))
require.NoError(s.EnsureConfigEntry(1, entry2))
require.NoError(s.EnsureConfigEntry(2, entry3))
require.NoError(s.EnsureConfigEntry(0, entry1, nil))
require.NoError(s.EnsureConfigEntry(1, entry2, nil))
require.NoError(s.EnsureConfigEntry(2, entry3, nil))
// Get all entries
idx, entries, err := s.ConfigEntries(nil)
idx, entries, err := s.ConfigEntries(nil, nil)
require.NoError(err)
require.Equal(uint64(2), idx)
require.Equal([]structs.ConfigEntry{entry1, entry2, entry3}, entries)
// Get all proxy entries
idx, entries, err = s.ConfigEntriesByKind(nil, structs.ProxyDefaults)
idx, entries, err = s.ConfigEntriesByKind(nil, structs.ProxyDefaults, nil)
require.NoError(err)
require.Equal(uint64(2), idx)
require.Equal([]structs.ConfigEntry{entry1}, entries)
// Get all service entries
ws := memdb.NewWatchSet()
idx, entries, err = s.ConfigEntriesByKind(ws, structs.ServiceDefaults)
idx, entries, err = s.ConfigEntriesByKind(ws, structs.ServiceDefaults, nil)
require.NoError(err)
require.Equal(uint64(2), idx)
require.Equal([]structs.ConfigEntry{entry2, entry3}, entries)
@ -172,20 +172,19 @@ func TestStore_ConfigEntries(t *testing.T) {
Kind: structs.ServiceDefaults,
Name: "test2",
Protocol: "tcp",
}))
}, nil))
require.True(watchFired(ws))
}
func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
for _, tc := range []struct {
name string
type tcase struct {
entries []structs.ConfigEntry
op func(t *testing.T, s *Store) error
expectErr string
expectGraphErr bool
}{
{
name: "splitter fails without default protocol",
}
cases := map[string]tcase{
"splitter fails without default protocol": tcase{
entries: []structs.ConfigEntry{},
op: func(t *testing.T, s *Store) error {
entry := &structs.ServiceSplitterConfigEntry{
@ -196,13 +195,12 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
{Weight: 10, Namespace: "v2"},
},
}
return s.EnsureConfigEntry(0, entry)
return s.EnsureConfigEntry(0, entry, nil)
},
expectErr: "does not permit advanced routing or splitting behavior",
expectGraphErr: true,
},
{
name: "splitter fails with tcp protocol",
"splitter fails with tcp protocol": tcase{
entries: []structs.ConfigEntry{
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
@ -219,13 +217,12 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
{Weight: 10, Namespace: "v2"},
},
}
return s.EnsureConfigEntry(0, entry)
return s.EnsureConfigEntry(0, entry, nil)
},
expectErr: "does not permit advanced routing or splitting behavior",
expectGraphErr: true,
},
{
name: "splitter works with http protocol",
"splitter works with http protocol": tcase{
entries: []structs.ConfigEntry{
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
@ -238,6 +235,19 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
Kind: structs.ServiceDefaults,
Name: "main",
Protocol: "http",
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "main",
Subsets: map[string]structs.ServiceResolverSubset{
"v1": structs.ServiceResolverSubset{
Filter: "Service.Meta.version == v1",
},
"v2": structs.ServiceResolverSubset{
Filter: "Service.Meta.version == v2",
},
},
},
},
op: func(t *testing.T, s *Store) error {
@ -245,15 +255,15 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
Kind: structs.ServiceSplitter,
Name: "main",
Splits: []structs.ServiceSplit{
{Weight: 90, Namespace: "v1"},
{Weight: 10, Namespace: "v2"},
{Weight: 90, ServiceSubset: "v1"},
{Weight: 10, ServiceSubset: "v2"},
},
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
}
return s.EnsureConfigEntry(0, entry)
return s.EnsureConfigEntry(0, entry, nil)
},
},
{
name: "splitter works with http protocol (from proxy-defaults)",
"splitter works with http protocol (from proxy-defaults)": tcase{
entries: []structs.ConfigEntry{
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
@ -272,11 +282,10 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
{Weight: 10, Namespace: "v2"},
},
}
return s.EnsureConfigEntry(0, entry)
return s.EnsureConfigEntry(0, entry, nil)
},
},
{
name: "router fails with tcp protocol",
"router fails with tcp protocol": tcase{
entries: []structs.ConfigEntry{
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
@ -301,13 +310,12 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
},
},
}
return s.EnsureConfigEntry(0, entry)
return s.EnsureConfigEntry(0, entry, nil)
},
expectErr: "does not permit advanced routing or splitting behavior",
expectGraphErr: true,
},
{
name: "router fails without default protocol",
"router fails without default protocol": tcase{
entries: []structs.ConfigEntry{},
op: func(t *testing.T, s *Store) error {
entry := &structs.ServiceRouterConfigEntry{
@ -326,37 +334,47 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
},
},
}
return s.EnsureConfigEntry(0, entry)
return s.EnsureConfigEntry(0, entry, nil)
},
expectErr: "does not permit advanced routing or splitting behavior",
expectGraphErr: true,
},
/////////////////////////////////////////////////
{
name: "cannot remove default protocol after splitter created",
"cannot remove default protocol after splitter created": tcase{
entries: []structs.ConfigEntry{
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "main",
Protocol: "http",
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "main",
Subsets: map[string]structs.ServiceResolverSubset{
"v1": structs.ServiceResolverSubset{
Filter: "Service.Meta.version == v1",
},
"v2": structs.ServiceResolverSubset{
Filter: "Service.Meta.version == v2",
},
},
},
&structs.ServiceSplitterConfigEntry{
Kind: structs.ServiceSplitter,
Name: "main",
Splits: []structs.ServiceSplit{
{Weight: 90, Namespace: "v1"},
{Weight: 10, Namespace: "v2"},
{Weight: 90, ServiceSubset: "v1"},
{Weight: 10, ServiceSubset: "v2"},
},
},
},
op: func(t *testing.T, s *Store) error {
return s.DeleteConfigEntry(0, structs.ServiceDefaults, "main")
return s.DeleteConfigEntry(0, structs.ServiceDefaults, "main", nil)
},
expectErr: "does not permit advanced routing or splitting behavior",
expectGraphErr: true,
},
{
name: "cannot remove global default protocol after splitter created",
"cannot remove global default protocol after splitter created": tcase{
entries: []structs.ConfigEntry{
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
@ -375,13 +393,12 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
},
},
op: func(t *testing.T, s *Store) error {
return s.DeleteConfigEntry(0, structs.ProxyDefaults, structs.ProxyConfigGlobal)
return s.DeleteConfigEntry(0, structs.ProxyDefaults, structs.ProxyConfigGlobal, nil)
},
expectErr: "does not permit advanced routing or splitting behavior",
expectGraphErr: true,
},
{
name: "can remove global default protocol after splitter created if service default overrides it",
"can remove global default protocol after splitter created if service default overrides it": tcase{
entries: []structs.ConfigEntry{
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
@ -395,33 +412,56 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
Name: "main",
Protocol: "http",
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "main",
Subsets: map[string]structs.ServiceResolverSubset{
"v1": structs.ServiceResolverSubset{
Filter: "Service.Meta.version == v1",
},
"v2": structs.ServiceResolverSubset{
Filter: "Service.Meta.version == v2",
},
},
},
&structs.ServiceSplitterConfigEntry{
Kind: structs.ServiceSplitter,
Name: "main",
Splits: []structs.ServiceSplit{
{Weight: 90, Namespace: "v1"},
{Weight: 10, Namespace: "v2"},
{Weight: 90, ServiceSubset: "v1"},
{Weight: 10, ServiceSubset: "v2"},
},
},
},
op: func(t *testing.T, s *Store) error {
return s.DeleteConfigEntry(0, structs.ProxyDefaults, structs.ProxyConfigGlobal)
return s.DeleteConfigEntry(0, structs.ProxyDefaults, structs.ProxyConfigGlobal, nil)
},
},
{
name: "cannot change to tcp protocol after splitter created",
"cannot change to tcp protocol after splitter created": tcase{
entries: []structs.ConfigEntry{
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "main",
Protocol: "http",
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "main",
Subsets: map[string]structs.ServiceResolverSubset{
"v1": structs.ServiceResolverSubset{
Filter: "Service.Meta.version == v1",
},
"v2": structs.ServiceResolverSubset{
Filter: "Service.Meta.version == v2",
},
},
},
&structs.ServiceSplitterConfigEntry{
Kind: structs.ServiceSplitter,
Name: "main",
Splits: []structs.ServiceSplit{
{Weight: 90, Namespace: "v1"},
{Weight: 10, Namespace: "v2"},
{Weight: 90, ServiceSubset: "v1"},
{Weight: 10, ServiceSubset: "v2"},
},
},
},
@ -431,19 +471,27 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
Name: "main",
Protocol: "tcp",
}
return s.EnsureConfigEntry(0, entry)
return s.EnsureConfigEntry(0, entry, nil)
},
expectErr: "does not permit advanced routing or splitting behavior",
expectGraphErr: true,
},
{
name: "cannot remove default protocol after router created",
"cannot remove default protocol after router created": tcase{
entries: []structs.ConfigEntry{
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "main",
Protocol: "http",
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "main",
Subsets: map[string]structs.ServiceResolverSubset{
"other": structs.ServiceResolverSubset{
Filter: "Service.Meta.version == other",
},
},
},
&structs.ServiceRouterConfigEntry{
Kind: structs.ServiceRouter,
Name: "main",
@ -455,26 +503,34 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
},
},
Destination: &structs.ServiceRouteDestination{
Namespace: "other",
ServiceSubset: "other",
},
},
},
},
},
op: func(t *testing.T, s *Store) error {
return s.DeleteConfigEntry(0, structs.ServiceDefaults, "main")
return s.DeleteConfigEntry(0, structs.ServiceDefaults, "main", nil)
},
expectErr: "does not permit advanced routing or splitting behavior",
expectGraphErr: true,
},
{
name: "cannot change to tcp protocol after router created",
"cannot change to tcp protocol after router created": tcase{
entries: []structs.ConfigEntry{
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "main",
Protocol: "http",
},
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "main",
Subsets: map[string]structs.ServiceResolverSubset{
"other": structs.ServiceResolverSubset{
Filter: "Service.Meta.version == other",
},
},
},
&structs.ServiceRouterConfigEntry{
Kind: structs.ServiceRouter,
Name: "main",
@ -486,7 +542,7 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
},
},
Destination: &structs.ServiceRouteDestination{
Namespace: "other",
ServiceSubset: "other",
},
},
},
@ -498,14 +554,13 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
Name: "main",
Protocol: "tcp",
}
return s.EnsureConfigEntry(0, entry)
return s.EnsureConfigEntry(0, entry, nil)
},
expectErr: "does not permit advanced routing or splitting behavior",
expectGraphErr: true,
},
/////////////////////////////////////////////////
{
name: "cannot split to a service using tcp",
"cannot split to a service using tcp": tcase{
entries: []structs.ConfigEntry{
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
@ -527,13 +582,12 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
{Weight: 10, Service: "other"},
},
}
return s.EnsureConfigEntry(0, entry)
return s.EnsureConfigEntry(0, entry, nil)
},
expectErr: "uses inconsistent protocols",
expectGraphErr: true,
},
{
name: "cannot route to a service using tcp",
"cannot route to a service using tcp": tcase{
entries: []structs.ConfigEntry{
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
@ -563,14 +617,13 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
},
},
}
return s.EnsureConfigEntry(0, entry)
return s.EnsureConfigEntry(0, entry, nil)
},
expectErr: "uses inconsistent protocols",
expectGraphErr: true,
},
/////////////////////////////////////////////////
{
name: "cannot failover to a service using a different protocol",
"cannot failover to a service using a different protocol": tcase{
entries: []structs.ConfigEntry{
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
@ -598,13 +651,12 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
},
},
}
return s.EnsureConfigEntry(0, entry)
return s.EnsureConfigEntry(0, entry, nil)
},
expectErr: "uses inconsistent protocols",
expectGraphErr: true,
},
{
name: "cannot redirect to a service using a different protocol",
"cannot redirect to a service using a different protocol": tcase{
entries: []structs.ConfigEntry{
&structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
@ -630,14 +682,13 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
Service: "other",
},
}
return s.EnsureConfigEntry(0, entry)
return s.EnsureConfigEntry(0, entry, nil)
},
expectErr: "uses inconsistent protocols",
expectGraphErr: true,
},
/////////////////////////////////////////////////
{
name: "redirect to a subset that does exist is fine",
"redirect to a subset that does exist is fine": tcase{
entries: []structs.ConfigEntry{
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
@ -659,11 +710,10 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
ServiceSubset: "v1",
},
}
return s.EnsureConfigEntry(0, entry)
return s.EnsureConfigEntry(0, entry, nil)
},
},
{
name: "cannot redirect to a subset that does not exist",
"cannot redirect to a subset that does not exist": tcase{
entries: []structs.ConfigEntry{
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
@ -680,14 +730,13 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
ServiceSubset: "v1",
},
}
return s.EnsureConfigEntry(0, entry)
return s.EnsureConfigEntry(0, entry, nil)
},
expectErr: `does not have a subset named "v1"`,
expectGraphErr: true,
},
/////////////////////////////////////////////////
{
name: "cannot introduce circular resolver redirect",
"cannot introduce circular resolver redirect": tcase{
entries: []structs.ConfigEntry{
&structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
@ -705,13 +754,12 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
Service: "other",
},
}
return s.EnsureConfigEntry(0, entry)
return s.EnsureConfigEntry(0, entry, nil)
},
expectErr: `detected circular resolver redirect`,
expectGraphErr: true,
},
{
name: "cannot introduce circular split",
"cannot introduce circular split": tcase{
entries: []structs.ConfigEntry{
&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
@ -736,18 +784,21 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) {
{Weight: 100, Service: "other"},
},
}
return s.EnsureConfigEntry(0, entry)
return s.EnsureConfigEntry(0, entry, nil)
},
expectErr: `detected circular reference`,
expectGraphErr: true,
},
} {
}
for name, tc := range cases {
name := name
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Run(name, func(t *testing.T) {
s := testStateStore(t)
for _, entry := range tc.entries {
require.NoError(t, s.EnsureConfigEntry(0, entry))
require.NoError(t, s.EnsureConfigEntry(0, entry, nil))
}
err := tc.op(t, s)
@ -819,7 +870,7 @@ func TestStore_ReadDiscoveryChainConfigEntries_Overrides(t *testing.T) {
{Kind: structs.ServiceDefaults, Name: "main"},
},
checkAfter: func(t *testing.T, entrySet *structs.DiscoveryChainConfigEntries) {
defaults := entrySet.GetService("main")
defaults := entrySet.GetService(structs.NewServiceID("main", nil))
require.NotNil(t, defaults)
require.Equal(t, "grpc", defaults.Protocol)
},
@ -912,7 +963,7 @@ func TestStore_ReadDiscoveryChainConfigEntries_Overrides(t *testing.T) {
{Kind: structs.ServiceRouter, Name: "main"},
},
checkAfter: func(t *testing.T, entrySet *structs.DiscoveryChainConfigEntries) {
router := entrySet.GetRouter("main")
router := entrySet.GetRouter(structs.NewServiceID("main", nil))
require.NotNil(t, router)
require.Len(t, router.Routes, 1)
@ -992,7 +1043,7 @@ func TestStore_ReadDiscoveryChainConfigEntries_Overrides(t *testing.T) {
{Kind: structs.ServiceSplitter, Name: "main"},
},
checkAfter: func(t *testing.T, entrySet *structs.DiscoveryChainConfigEntries) {
splitter := entrySet.GetSplitter("main")
splitter := entrySet.GetSplitter(structs.NewServiceID("main", nil))
require.NotNil(t, splitter)
require.Len(t, splitter.Splits, 2)
@ -1044,7 +1095,7 @@ func TestStore_ReadDiscoveryChainConfigEntries_Overrides(t *testing.T) {
{Kind: structs.ServiceResolver, Name: "main"},
},
checkAfter: func(t *testing.T, entrySet *structs.DiscoveryChainConfigEntries) {
resolver := entrySet.GetResolver("main")
resolver := entrySet.GetResolver(structs.NewServiceID("main", nil))
require.NotNil(t, resolver)
require.Equal(t, 33*time.Second, resolver.ConnectTimeout)
},
@ -1055,18 +1106,18 @@ func TestStore_ReadDiscoveryChainConfigEntries_Overrides(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
s := testStateStore(t)
for _, entry := range tc.entries {
require.NoError(t, s.EnsureConfigEntry(0, entry))
require.NoError(t, s.EnsureConfigEntry(0, entry, nil))
}
t.Run("without override", func(t *testing.T) {
_, entrySet, err := s.readDiscoveryChainConfigEntries(nil, "main", nil)
_, entrySet, err := s.readDiscoveryChainConfigEntries(nil, "main", nil, nil)
require.NoError(t, err)
got := entrySetToKindNames(entrySet)
require.ElementsMatch(t, tc.expectBefore, got)
})
t.Run("with override", func(t *testing.T) {
_, entrySet, err := s.readDiscoveryChainConfigEntries(nil, "main", tc.overrides)
_, entrySet, err := s.readDiscoveryChainConfigEntries(nil, "main", tc.overrides, nil)
if tc.expectAfterErr != "" {
require.Error(t, err)
@ -1146,10 +1197,10 @@ func TestStore_ReadDiscoveryChainConfigEntries_SubsetSplit(t *testing.T) {
}
for _, entry := range entries {
require.NoError(t, s.EnsureConfigEntry(0, entry))
require.NoError(t, s.EnsureConfigEntry(0, entry, nil))
}
_, entrySet, err := s.ReadDiscoveryChainConfigEntries(nil, "main")
_, entrySet, err := s.ReadDiscoveryChainConfigEntries(nil, "main", nil)
require.NoError(t, err)
require.Len(t, entrySet.Routers, 0)

View File

@ -0,0 +1,7 @@
// +build !consulent
package state
func (s *Store) setupDefaultTestEntMeta() error {
return nil
}

View File

@ -24,7 +24,11 @@ func (s *HTTPServer) DiscoveryChainRead(resp http.ResponseWriter, req *http.Requ
}
args.EvaluateInDatacenter = req.URL.Query().Get("compile-dc")
// TODO(namespaces): args.EvaluateInNamespace = req.URL.Query().Get("compile-namespace")
var entMeta structs.EnterpriseMeta
if err := s.parseEntMetaNoWildcard(req, &entMeta); err != nil {
return nil, err
}
args.WithEnterpriseMeta(&entMeta)
if req.Method == "POST" {
var raw map[string]interface{}

View File

@ -45,8 +45,8 @@ type Manager struct {
mu sync.Mutex
started bool
proxies map[string]*state
watchers map[string]map[uint64]chan *ConfigSnapshot
proxies map[structs.ServiceID]*state
watchers map[structs.ServiceID]map[uint64]chan *ConfigSnapshot
}
// ManagerConfig holds the required external dependencies for a Manager
@ -79,8 +79,8 @@ func NewManager(cfg ManagerConfig) (*Manager, error) {
// Single item buffer is enough since there is no data transferred so this
// is "level triggering" and we can't miss actual data.
stateCh: make(chan struct{}, 1),
proxies: make(map[string]*state),
watchers: make(map[string]map[uint64]chan *ConfigSnapshot),
proxies: make(map[structs.ServiceID]*state),
watchers: make(map[structs.ServiceID]map[uint64]chan *ConfigSnapshot),
}
return m, nil
}
@ -130,7 +130,7 @@ func (m *Manager) syncState() {
// Traverse the local state and ensure all proxy services are registered
services := m.State.Services(structs.WildcardEnterpriseMeta())
for _, svc := range services {
for sid, svc := range services {
if svc.Kind != structs.ServiceKindConnectProxy && svc.Kind != structs.ServiceKindMeshGateway {
continue
}
@ -141,20 +141,16 @@ func (m *Manager) syncState() {
// know that so we'd need to set it here if not during registration of the
// proxy service. Sidecar Service in the interim can do that, but we should
// validate more generally that that is always true.
err := m.ensureProxyServiceLocked(svc, m.State.ServiceToken(svc.CompoundServiceID()))
err := m.ensureProxyServiceLocked(svc, m.State.ServiceToken(sid))
if err != nil {
m.Logger.Printf("[ERR] failed to watch proxy service %s: %s", svc.ID,
m.Logger.Printf("[ERR] failed to watch proxy service %s: %s", sid.String(),
err)
}
}
// Now see if any proxies were removed
for proxyID := range m.proxies {
var key structs.ServiceID
// TODO (namespaces) pass through some real enterprise meta that probably needs to come from the proxy tracking
key.Init(proxyID, nil)
if _, ok := services[key]; !ok {
if _, ok := services[proxyID]; !ok {
// Remove them
m.removeProxyServiceLocked(proxyID)
}
@ -163,7 +159,8 @@ func (m *Manager) syncState() {
// ensureProxyServiceLocked adds or changes the proxy to our state.
func (m *Manager) ensureProxyServiceLocked(ns *structs.NodeService, token string) error {
state, ok := m.proxies[ns.ID]
sid := ns.CompoundServiceID()
state, ok := m.proxies[sid]
if ok {
if !state.Changed(ns, token) {
@ -190,7 +187,7 @@ func (m *Manager) ensureProxyServiceLocked(ns *structs.NodeService, token string
if err != nil {
return err
}
m.proxies[ns.ID] = state
m.proxies[sid] = state
// Start a goroutine that will wait for changes and broadcast them to watchers.
go func(ch <-chan ConfigSnapshot) {
@ -205,7 +202,7 @@ func (m *Manager) ensureProxyServiceLocked(ns *structs.NodeService, token string
// removeProxyService is called when a service deregisters and frees all
// resources for that service.
func (m *Manager) removeProxyServiceLocked(proxyID string) {
func (m *Manager) removeProxyServiceLocked(proxyID structs.ServiceID) {
state, ok := m.proxies[proxyID]
if !ok {
return
@ -269,7 +266,7 @@ OUTER:
// This should not be possible since we should be the only sender, enforced
// by m.mu but error and drop the update rather than panic.
m.Logger.Printf("[ERR] proxycfg: failed to deliver ConfigSnapshot to %q",
snap.ProxyID)
snap.ProxyID.String())
}
}
@ -277,7 +274,7 @@ OUTER:
// will not fail, but no updates will be delivered until the proxy is
// registered. If there is already a valid snapshot in memory, it will be
// delivered immediately.
func (m *Manager) Watch(proxyID string) (<-chan *ConfigSnapshot, CancelFunc) {
func (m *Manager) Watch(proxyID structs.ServiceID) (<-chan *ConfigSnapshot, CancelFunc) {
m.mu.Lock()
defer m.mu.Unlock()
@ -311,7 +308,7 @@ func (m *Manager) Watch(proxyID string) (<-chan *ConfigSnapshot, CancelFunc) {
// closeWatchLocked cleans up state related to a single watcher. It assumes the
// lock is held.
func (m *Manager) closeWatchLocked(proxyID string, watchIdx uint64) {
func (m *Manager) closeWatchLocked(proxyID structs.ServiceID, watchIdx uint64) {
if watchers, ok := m.watchers[proxyID]; ok {
if ch, ok := watchers[watchIdx]; ok {
delete(watchers, watchIdx)

View File

@ -138,7 +138,7 @@ func TestManager_BasicLifecycle(t *testing.T) {
dbChainCacheKey := testGenCacheKey(&structs.DiscoveryChainRequest{
Name: "db",
EvaluateInDatacenter: "dc1",
EvaluateInNamespace: "default",
EvaluateInNamespace: "",
// This is because structs.TestUpstreams uses an opaque config
// to override connect timeouts.
OverrideConnectTimeout: 1 * time.Second,
@ -151,6 +151,7 @@ func TestManager_BasicLifecycle(t *testing.T) {
QueryOptions: structs.QueryOptions{Token: "my-token", Filter: ""},
ServiceName: "db",
Connect: true,
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
})
db_v1_HealthCacheKey := testGenCacheKey(&structs.ServiceSpecificRequest{
Datacenter: "dc1",
@ -159,6 +160,7 @@ func TestManager_BasicLifecycle(t *testing.T) {
},
ServiceName: "db",
Connect: true,
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
})
db_v2_HealthCacheKey := testGenCacheKey(&structs.ServiceSpecificRequest{
Datacenter: "dc1",
@ -167,6 +169,7 @@ func TestManager_BasicLifecycle(t *testing.T) {
},
ServiceName: "db",
Connect: true,
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
})
// Create test cases using some of the common data above.
@ -185,7 +188,7 @@ func TestManager_BasicLifecycle(t *testing.T) {
expectSnap: &ConfigSnapshot{
Kind: structs.ServiceKindConnectProxy,
Service: webProxy.Service,
ProxyID: webProxy.ID,
ProxyID: webProxy.CompoundServiceID(),
Address: webProxy.Address,
Port: webProxy.Port,
Proxy: webProxy.Proxy,
@ -206,8 +209,8 @@ func TestManager_BasicLifecycle(t *testing.T) {
WatchedGatewayEndpoints: map[string]map[string]structs.CheckServiceNodes{
"db": {},
},
UpstreamEndpoints: map[string]structs.CheckServiceNodes{},
WatchedServiceChecks: map[string][]structs.CheckType{},
PreparedQueryEndpoints: map[string]structs.CheckServiceNodes{},
WatchedServiceChecks: map[structs.ServiceID][]structs.CheckType{},
},
Datacenter: "dc1",
},
@ -229,7 +232,7 @@ func TestManager_BasicLifecycle(t *testing.T) {
expectSnap: &ConfigSnapshot{
Kind: structs.ServiceKindConnectProxy,
Service: webProxy.Service,
ProxyID: webProxy.ID,
ProxyID: webProxy.CompoundServiceID(),
Address: webProxy.Address,
Port: webProxy.Port,
Proxy: webProxy.Proxy,
@ -251,8 +254,8 @@ func TestManager_BasicLifecycle(t *testing.T) {
WatchedGatewayEndpoints: map[string]map[string]structs.CheckServiceNodes{
"db": {},
},
UpstreamEndpoints: map[string]structs.CheckServiceNodes{},
WatchedServiceChecks: map[string][]structs.CheckType{},
PreparedQueryEndpoints: map[string]structs.CheckServiceNodes{},
WatchedServiceChecks: map[structs.ServiceID][]structs.CheckType{},
},
Datacenter: "dc1",
},
@ -331,7 +334,7 @@ func testManager_BasicLifecycle(
}()
// BEFORE we register, we should be able to get a watch channel
wCh, cancel := m.Watch(webProxy.ID)
wCh, cancel := m.Watch(webProxy.CompoundServiceID())
defer cancel()
// And it should block with nothing sent on it yet
@ -355,7 +358,7 @@ func testManager_BasicLifecycle(
assertWatchChanRecvs(t, wCh, expectSnap)
// Register a second watcher
wCh2, cancel2 := m.Watch(webProxy.ID)
wCh2, cancel2 := m.Watch(webProxy.CompoundServiceID())
defer cancel2()
// New watcher should immediately receive the current state
@ -463,11 +466,11 @@ func TestManager_deliverLatest(t *testing.T) {
require.NoError(err)
snap1 := &ConfigSnapshot{
ProxyID: "test-proxy",
ProxyID: structs.NewServiceID("test-proxy", nil),
Port: 1111,
}
snap2 := &ConfigSnapshot{
ProxyID: "test-proxy",
ProxyID: structs.NewServiceID("test-proxy", nil),
Port: 2222,
}

View File

@ -14,9 +14,9 @@ type configSnapshotConnectProxy struct {
WatchedUpstreamEndpoints map[string]map[string]structs.CheckServiceNodes
WatchedGateways map[string]map[string]context.CancelFunc
WatchedGatewayEndpoints map[string]map[string]structs.CheckServiceNodes
WatchedServiceChecks map[string][]structs.CheckType // TODO: missing garbage collection
WatchedServiceChecks map[structs.ServiceID][]structs.CheckType // TODO: missing garbage collection
UpstreamEndpoints map[string]structs.CheckServiceNodes // DEPRECATED:see:WatchedUpstreamEndpoints
PreparedQueryEndpoints map[string]structs.CheckServiceNodes // DEPRECATED:see:WatchedUpstreamEndpoints
}
func (c *configSnapshotConnectProxy) IsEmpty() bool {
@ -30,15 +30,15 @@ func (c *configSnapshotConnectProxy) IsEmpty() bool {
len(c.WatchedGateways) == 0 &&
len(c.WatchedGatewayEndpoints) == 0 &&
len(c.WatchedServiceChecks) == 0 &&
len(c.UpstreamEndpoints) == 0
len(c.PreparedQueryEndpoints) == 0
}
type configSnapshotMeshGateway struct {
WatchedServices map[string]context.CancelFunc
WatchedServices map[structs.ServiceID]context.CancelFunc
WatchedServicesSet bool
WatchedDatacenters map[string]context.CancelFunc
ServiceGroups map[string]structs.CheckServiceNodes
ServiceResolvers map[string]*structs.ServiceResolverConfigEntry
ServiceGroups map[structs.ServiceID]structs.CheckServiceNodes
ServiceResolvers map[structs.ServiceID]*structs.ServiceResolverConfigEntry
GatewayGroups map[string]structs.CheckServiceNodes
}
@ -60,12 +60,13 @@ func (c *configSnapshotMeshGateway) IsEmpty() bool {
type ConfigSnapshot struct {
Kind structs.ServiceKind
Service string
ProxyID string
ProxyID structs.ServiceID
Address string
Port int
TaggedAddresses map[string]structs.ServiceAddress
Proxy structs.ConnectProxyConfig
Datacenter string
Roots *structs.IndexedCARoots
// connect-proxy specific

View File

@ -50,7 +50,7 @@ type state struct {
kind structs.ServiceKind
service string
proxyID string
proxyID structs.ServiceID
address string
port int
taggedAddresses map[string]structs.ServiceAddress
@ -92,7 +92,7 @@ func newState(ns *structs.NodeService, token string) (*state, error) {
return &state{
kind: ns.Kind,
service: ns.Service,
proxyID: ns.ID,
proxyID: ns.CompoundServiceID(),
address: ns.Address,
port: ns.Port,
taggedAddresses: taggedAddresses,
@ -156,10 +156,14 @@ func (s *state) watchMeshGateway(ctx context.Context, dc string, upstreamID stri
ServiceKind: structs.ServiceKindMeshGateway,
UseServiceKind: true,
Source: *s.source,
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
}, "mesh-gateway:"+dc+":"+upstreamID, s.ch)
}
func (s *state) watchConnectProxyService(ctx context.Context, correlationId string, service string, dc string, filter string) error {
func (s *state) watchConnectProxyService(ctx context.Context, correlationId string, service string, dc string, filter string, entMeta *structs.EnterpriseMeta) error {
var finalMeta structs.EnterpriseMeta
finalMeta.Merge(entMeta)
return s.cache.Notify(ctx, cachetype.HealthServicesName, &structs.ServiceSpecificRequest{
Datacenter: dc,
QueryOptions: structs.QueryOptions{
@ -172,6 +176,7 @@ func (s *state) watchConnectProxyService(ctx context.Context, correlationId stri
// the default and makes metrics and other things much cleaner. It's
// simpler for us if we have the type to make things unambiguous.
Source: *s.source,
EnterpriseMeta: finalMeta,
}, correlationId, s.ch)
}
@ -193,6 +198,7 @@ func (s *state) initWatchesConnectProxy() error {
Datacenter: s.source.Datacenter,
Token: s.token,
Service: s.proxyCfg.DestinationServiceName,
EnterpriseMeta: s.proxyID.EnterpriseMeta,
}, leafWatchID, s.ch)
if err != nil {
return err
@ -206,7 +212,7 @@ func (s *state) initWatchesConnectProxy() error {
Type: structs.IntentionMatchDestination,
Entries: []structs.IntentionMatchEntry{
{
Namespace: structs.IntentionDefaultNamespace,
Namespace: s.proxyID.NamespaceOrDefault(),
Name: s.proxyCfg.DestinationServiceName,
},
},
@ -219,13 +225,14 @@ func (s *state) initWatchesConnectProxy() error {
// Watch for service check updates
err = s.cache.Notify(s.ctx, cachetype.ServiceHTTPChecksName, &cachetype.ServiceHTTPChecksRequest{
ServiceID: s.proxyCfg.DestinationServiceID,
}, svcChecksWatchIDPrefix+s.proxyCfg.DestinationServiceID, s.ch)
EnterpriseMeta: s.proxyID.EnterpriseMeta,
}, svcChecksWatchIDPrefix+structs.ServiceIDString(s.proxyCfg.DestinationServiceID, &s.proxyID.EnterpriseMeta), s.ch)
if err != nil {
return err
}
// TODO(namespaces): pull this from something like s.source.Namespace?
currentNamespace := "default"
// let namespace inference happen server side
currentNamespace := ""
// Watch for updates to service endpoints for all upstreams
for _, u := range s.proxyCfg.Upstreams {
@ -317,10 +324,11 @@ func (s *state) initWatchesMeshGateway() error {
}
// Watch for all services
err = s.cache.Notify(s.ctx, cachetype.CatalogListServicesName, &structs.DCSpecificRequest{
err = s.cache.Notify(s.ctx, cachetype.CatalogServiceListName, &structs.DCSpecificRequest{
Datacenter: s.source.Datacenter,
QueryOptions: structs.QueryOptions{Token: s.token},
Source: *s.source,
EnterpriseMeta: *structs.WildcardEnterpriseMeta(),
}, serviceListWatchID, s.ch)
if err != nil {
@ -345,6 +353,7 @@ func (s *state) initWatchesMeshGateway() error {
Datacenter: s.source.Datacenter,
QueryOptions: structs.QueryOptions{Token: s.token},
Kind: structs.ServiceResolver,
EnterpriseMeta: *structs.WildcardEnterpriseMeta(),
}, serviceResolversWatchID, s.ch)
if err != nil {
@ -374,14 +383,15 @@ func (s *state) initialConfigSnapshot() ConfigSnapshot {
snap.ConnectProxy.WatchedUpstreamEndpoints = make(map[string]map[string]structs.CheckServiceNodes)
snap.ConnectProxy.WatchedGateways = make(map[string]map[string]context.CancelFunc)
snap.ConnectProxy.WatchedGatewayEndpoints = make(map[string]map[string]structs.CheckServiceNodes)
snap.ConnectProxy.WatchedServiceChecks = make(map[string][]structs.CheckType)
snap.ConnectProxy.WatchedServiceChecks = make(map[structs.ServiceID][]structs.CheckType)
snap.ConnectProxy.UpstreamEndpoints = make(map[string]structs.CheckServiceNodes) // TODO(rb): deprecated
snap.ConnectProxy.PreparedQueryEndpoints = make(map[string]structs.CheckServiceNodes) // TODO(rb): deprecated
case structs.ServiceKindMeshGateway:
snap.MeshGateway.WatchedServices = make(map[string]context.CancelFunc)
snap.MeshGateway.WatchedServices = make(map[structs.ServiceID]context.CancelFunc)
snap.MeshGateway.WatchedDatacenters = make(map[string]context.CancelFunc)
snap.MeshGateway.ServiceGroups = make(map[string]structs.CheckServiceNodes)
snap.MeshGateway.ServiceGroups = make(map[structs.ServiceID]structs.CheckServiceNodes)
snap.MeshGateway.GatewayGroups = make(map[string]structs.CheckServiceNodes)
snap.MeshGateway.ServiceResolvers = make(map[structs.ServiceID]*structs.ServiceResolverConfigEntry)
// there is no need to initialize the map of service resolvers as we
// fully rebuild it every time we get updates
}
@ -551,28 +561,20 @@ func (s *state) handleUpdateConnectProxy(u cache.UpdateEvent, snap *ConfigSnapsh
}
snap.ConnectProxy.WatchedGatewayEndpoints[svc][dc] = resp.Nodes
case strings.HasPrefix(u.CorrelationID, "upstream:"+serviceIDPrefix):
resp, ok := u.Result.(*structs.IndexedCheckServiceNodes)
if !ok {
return fmt.Errorf("invalid type for response: %T", u.Result)
}
svc := strings.TrimPrefix(u.CorrelationID, "upstream:"+serviceIDPrefix)
snap.ConnectProxy.UpstreamEndpoints[svc] = resp.Nodes
case strings.HasPrefix(u.CorrelationID, "upstream:"+preparedQueryIDPrefix):
resp, ok := u.Result.(*structs.PreparedQueryExecuteResponse)
if !ok {
return fmt.Errorf("invalid type for response: %T", u.Result)
}
pq := strings.TrimPrefix(u.CorrelationID, "upstream:")
snap.ConnectProxy.UpstreamEndpoints[pq] = resp.Nodes
snap.ConnectProxy.PreparedQueryEndpoints[pq] = resp.Nodes
case strings.HasPrefix(u.CorrelationID, svcChecksWatchIDPrefix):
resp, ok := u.Result.([]structs.CheckType)
if !ok {
return fmt.Errorf("invalid type for service checks response: %T, want: []structs.CheckType", u.Result)
}
svcID := strings.TrimPrefix(u.CorrelationID, svcChecksWatchIDPrefix)
svcID := structs.ServiceIDFromString(strings.TrimPrefix(u.CorrelationID, svcChecksWatchIDPrefix))
snap.ConnectProxy.WatchedServiceChecks[svcID] = resp
default:
@ -644,6 +646,7 @@ func (s *state) resetWatchesFromChain(
target.Service,
target.Datacenter,
target.Subset.Filter,
target.GetEnterpriseMetadata(),
)
if err != nil {
cancel()
@ -696,33 +699,37 @@ func (s *state) handleUpdateMeshGateway(u cache.UpdateEvent, snap *ConfigSnapsho
}
snap.Roots = roots
case serviceListWatchID:
services, ok := u.Result.(*structs.IndexedServices)
services, ok := u.Result.(*structs.IndexedServiceList)
if !ok {
return fmt.Errorf("invalid type for response: %T", u.Result)
}
for svcName := range services.Services {
if _, ok := snap.MeshGateway.WatchedServices[svcName]; !ok {
svcMap := make(map[structs.ServiceID]struct{})
for _, svc := range services.Services {
sid := svc.ToServiceID()
if _, ok := snap.MeshGateway.WatchedServices[sid]; !ok {
ctx, cancel := context.WithCancel(s.ctx)
err := s.cache.Notify(ctx, cachetype.HealthServicesName, &structs.ServiceSpecificRequest{
Datacenter: s.source.Datacenter,
QueryOptions: structs.QueryOptions{Token: s.token},
ServiceName: svcName,
ServiceName: svc.Name,
Connect: true,
}, fmt.Sprintf("connect-service:%s", svcName), s.ch)
EnterpriseMeta: sid.EnterpriseMeta,
}, fmt.Sprintf("connect-service:%s", sid.String()), s.ch)
if err != nil {
s.logger.Printf("[ERR] mesh-gateway: failed to register watch for connect-service:%s", svcName)
s.logger.Printf("[ERR] mesh-gateway: failed to register watch for connect-service:%s", sid.String())
cancel()
return err
}
snap.MeshGateway.WatchedServices[svcName] = cancel
snap.MeshGateway.WatchedServices[sid] = cancel
svcMap[sid] = struct{}{}
}
}
for svcName, cancelFn := range snap.MeshGateway.WatchedServices {
if _, ok := services.Services[svcName]; !ok {
delete(snap.MeshGateway.WatchedServices, svcName)
for sid, cancelFn := range snap.MeshGateway.WatchedServices {
if _, ok := svcMap[sid]; !ok {
delete(snap.MeshGateway.WatchedServices, sid)
cancelFn()
}
}
@ -752,6 +759,7 @@ func (s *state) handleUpdateMeshGateway(u cache.UpdateEvent, snap *ConfigSnapsho
ServiceKind: structs.ServiceKindMeshGateway,
UseServiceKind: true,
Source: *s.source,
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
}, fmt.Sprintf("mesh-gateway:%s", dc), s.ch)
if err != nil {
@ -784,10 +792,10 @@ func (s *state) handleUpdateMeshGateway(u cache.UpdateEvent, snap *ConfigSnapsho
return fmt.Errorf("invalid type for response: %T", u.Result)
}
resolvers := make(map[string]*structs.ServiceResolverConfigEntry)
resolvers := make(map[structs.ServiceID]*structs.ServiceResolverConfigEntry)
for _, entry := range configEntries.Entries {
if resolver, ok := entry.(*structs.ServiceResolverConfigEntry); ok {
resolvers[resolver.Name] = resolver
resolvers[structs.NewServiceID(resolver.Name, &resolver.EnterpriseMeta)] = resolver
}
}
snap.MeshGateway.ServiceResolvers = resolvers
@ -799,12 +807,12 @@ func (s *state) handleUpdateMeshGateway(u cache.UpdateEvent, snap *ConfigSnapsho
return fmt.Errorf("invalid type for response: %T", u.Result)
}
svc := strings.TrimPrefix(u.CorrelationID, "connect-service:")
sid := structs.ServiceIDFromString(strings.TrimPrefix(u.CorrelationID, "connect-service:"))
if len(resp.Nodes) > 0 {
snap.MeshGateway.ServiceGroups[svc] = resp.Nodes
} else if _, ok := snap.MeshGateway.ServiceGroups[svc]; ok {
delete(snap.MeshGateway.ServiceGroups, svc)
snap.MeshGateway.ServiceGroups[sid] = resp.Nodes
} else if _, ok := snap.MeshGateway.ServiceGroups[sid]; ok {
delete(snap.MeshGateway.ServiceGroups, sid)
}
case strings.HasPrefix(u.CorrelationID, "mesh-gateway:"):
resp, ok := u.Result.(*structs.IndexedCheckServiceNodes)
@ -845,7 +853,7 @@ func (s *state) Changed(ns *structs.NodeService, token string) bool {
return true
}
return ns.Kind != s.kind ||
s.proxyID != ns.ID ||
s.proxyID != ns.CompoundServiceID() ||
s.address != ns.Address ||
s.port != ns.Port ||
!reflect.DeepEqual(s.proxyCfg, ns.Proxy) ||

View File

@ -185,7 +185,7 @@ func genVerifyRootsWatch(expectedDatacenter string) verifyWatchRequest {
}
func genVerifyListServicesWatch(expectedDatacenter string) verifyWatchRequest {
return genVerifyDCSpecificWatch(cachetype.CatalogListServicesName, expectedDatacenter)
return genVerifyDCSpecificWatch(cachetype.CatalogServiceListName, expectedDatacenter)
}
func verifyDatacentersWatch(t testing.TB, cacheType string, request cache.Request) {
@ -383,7 +383,7 @@ func TestState_WatchesAndUpdates(t *testing.T) {
"discovery-chain:api": genVerifyDiscoveryChainWatch(&structs.DiscoveryChainRequest{
Name: "api",
EvaluateInDatacenter: "dc1",
EvaluateInNamespace: "default",
EvaluateInNamespace: "",
Datacenter: "dc1",
OverrideMeshGateway: structs.MeshGatewayConfig{
Mode: meshGatewayProxyConfigValue,
@ -392,7 +392,7 @@ func TestState_WatchesAndUpdates(t *testing.T) {
"discovery-chain:api-failover-remote?dc=dc2": genVerifyDiscoveryChainWatch(&structs.DiscoveryChainRequest{
Name: "api-failover-remote",
EvaluateInDatacenter: "dc2",
EvaluateInNamespace: "default",
EvaluateInNamespace: "",
Datacenter: "dc1",
OverrideMeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeRemote,
@ -401,7 +401,7 @@ func TestState_WatchesAndUpdates(t *testing.T) {
"discovery-chain:api-failover-local?dc=dc2": genVerifyDiscoveryChainWatch(&structs.DiscoveryChainRequest{
Name: "api-failover-local",
EvaluateInDatacenter: "dc2",
EvaluateInNamespace: "default",
EvaluateInNamespace: "",
Datacenter: "dc1",
OverrideMeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeLocal,
@ -410,7 +410,7 @@ func TestState_WatchesAndUpdates(t *testing.T) {
"discovery-chain:api-failover-direct?dc=dc2": genVerifyDiscoveryChainWatch(&structs.DiscoveryChainRequest{
Name: "api-failover-direct",
EvaluateInDatacenter: "dc2",
EvaluateInNamespace: "default",
EvaluateInNamespace: "",
Datacenter: "dc1",
OverrideMeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeNone,
@ -419,7 +419,7 @@ func TestState_WatchesAndUpdates(t *testing.T) {
"discovery-chain:api-dc2": genVerifyDiscoveryChainWatch(&structs.DiscoveryChainRequest{
Name: "api-dc2",
EvaluateInDatacenter: "dc1",
EvaluateInNamespace: "default",
EvaluateInNamespace: "",
Datacenter: "dc1",
OverrideMeshGateway: structs.MeshGatewayConfig{
Mode: meshGatewayProxyConfigValue,
@ -506,7 +506,7 @@ func TestState_WatchesAndUpdates(t *testing.T) {
require.Len(t, snap.ConnectProxy.WatchedGatewayEndpoints, 5, "%+v", snap.ConnectProxy.WatchedGatewayEndpoints)
require.Len(t, snap.ConnectProxy.WatchedServiceChecks, 0, "%+v", snap.ConnectProxy.WatchedServiceChecks)
require.Len(t, snap.ConnectProxy.UpstreamEndpoints, 0, "%+v", snap.ConnectProxy.UpstreamEndpoints)
require.Len(t, snap.ConnectProxy.PreparedQueryEndpoints, 0, "%+v", snap.ConnectProxy.PreparedQueryEndpoints)
},
}
@ -532,7 +532,7 @@ func TestState_WatchesAndUpdates(t *testing.T) {
require.Len(t, snap.ConnectProxy.WatchedGatewayEndpoints, 5, "%+v", snap.ConnectProxy.WatchedGatewayEndpoints)
require.Len(t, snap.ConnectProxy.WatchedServiceChecks, 0, "%+v", snap.ConnectProxy.WatchedServiceChecks)
require.Len(t, snap.ConnectProxy.UpstreamEndpoints, 0, "%+v", snap.ConnectProxy.UpstreamEndpoints)
require.Len(t, snap.ConnectProxy.PreparedQueryEndpoints, 0, "%+v", snap.ConnectProxy.PreparedQueryEndpoints)
},
}
@ -589,8 +589,8 @@ func TestState_WatchesAndUpdates(t *testing.T) {
events: []cache.UpdateEvent{
cache.UpdateEvent{
CorrelationID: serviceListWatchID,
Result: &structs.IndexedServices{
Services: make(structs.Services),
Result: &structs.IndexedServiceList{
Services: make(structs.ServiceList, 0),
},
Err: nil,
},

View File

@ -571,7 +571,7 @@ func TestConfigSnapshot(t testing.T) *ConfigSnapshot {
return &ConfigSnapshot{
Kind: structs.ServiceKindConnectProxy,
Service: "web-sidecar-proxy",
ProxyID: "web-sidecar-proxy",
ProxyID: structs.NewServiceID("web-sidecar-proxy", nil),
Address: "0.0.0.0",
Port: 9999,
Proxy: structs.ConnectProxyConfig{
@ -590,7 +590,7 @@ func TestConfigSnapshot(t testing.T) *ConfigSnapshot {
DiscoveryChain: map[string]*structs.CompiledDiscoveryChain{
"db": dbChain,
},
UpstreamEndpoints: map[string]structs.CheckServiceNodes{
PreparedQueryEndpoints: map[string]structs.CheckServiceNodes{
"prepared_query:geo-cache": TestUpstreamNodes(t),
},
WatchedUpstreamEndpoints: map[string]map[string]structs.CheckServiceNodes{
@ -866,7 +866,7 @@ func testConfigSnapshotDiscoveryChain(t testing.T, variation string, additionalE
snap := &ConfigSnapshot{
Kind: structs.ServiceKindConnectProxy,
Service: "web-sidecar-proxy",
ProxyID: "web-sidecar-proxy",
ProxyID: structs.NewServiceID("web-sidecar-proxy", nil),
Address: "0.0.0.0",
Port: 9999,
Proxy: structs.ConnectProxyConfig{
@ -979,7 +979,7 @@ func testConfigSnapshotMeshGateway(t testing.T, populateServices bool) *ConfigSn
snap := &ConfigSnapshot{
Kind: structs.ServiceKindMeshGateway,
Service: "mesh-gateway",
ProxyID: "mesh-gateway",
ProxyID: structs.NewServiceID("mesh-gateway", nil),
Address: "1.2.3.4",
Port: 8443,
Proxy: structs.ConnectProxyConfig{
@ -1004,17 +1004,17 @@ func testConfigSnapshotMeshGateway(t testing.T, populateServices bool) *ConfigSn
if populateServices {
snap.MeshGateway = configSnapshotMeshGateway{
WatchedServices: map[string]context.CancelFunc{
"foo": nil,
"bar": nil,
WatchedServices: map[structs.ServiceID]context.CancelFunc{
structs.NewServiceID("foo", nil): nil,
structs.NewServiceID("bar", nil): nil,
},
WatchedServicesSet: true,
WatchedDatacenters: map[string]context.CancelFunc{
"dc2": nil,
},
ServiceGroups: map[string]structs.CheckServiceNodes{
"foo": TestGatewayServiceGroupFooDC1(t),
"bar": TestGatewayServiceGroupBarDC1(t),
ServiceGroups: map[structs.ServiceID]structs.CheckServiceNodes{
structs.NewServiceID("foo", nil): TestGatewayServiceGroupFooDC1(t),
structs.NewServiceID("bar", nil): TestGatewayServiceGroupBarDC1(t),
},
GatewayGroups: map[string]structs.CheckServiceNodes{
"dc2": TestGatewayNodesDC2(t),
@ -1029,7 +1029,7 @@ func TestConfigSnapshotExposeConfig(t testing.T) *ConfigSnapshot {
return &ConfigSnapshot{
Kind: structs.ServiceKindConnectProxy,
Service: "web-proxy",
ProxyID: "web-proxy",
ProxyID: structs.NewServiceID("web-proxy", nil),
Address: "1.2.3.4",
Port: 8080,
Proxy: structs.ConnectProxyConfig{

View File

@ -452,7 +452,7 @@ type asyncRegisterRequest struct {
func makeConfigRequest(agent *Agent, registration *serviceRegistration) *structs.ServiceConfigRequest {
ns := registration.service
name := ns.Service
var upstreams []string
var upstreams []structs.ServiceID
// Note that only sidecar proxies should even make it here for now although
// later that will change to add the condition.
@ -465,7 +465,9 @@ func makeConfigRequest(agent *Agent, registration *serviceRegistration) *structs
// learn about their configs.
for _, us := range ns.Proxy.Upstreams {
if us.DestinationType == "" || us.DestinationType == structs.UpstreamDestTypeService {
upstreams = append(upstreams, us.DestinationName)
sid := us.DestinationID()
sid.EnterpriseMeta.Merge(&ns.EnterpriseMeta)
upstreams = append(upstreams, sid)
}
}
}
@ -474,7 +476,8 @@ func makeConfigRequest(agent *Agent, registration *serviceRegistration) *structs
Name: name,
Datacenter: agent.config.Datacenter,
QueryOptions: structs.QueryOptions{Token: agent.tokens.AgentToken()},
Upstreams: upstreams,
UpstreamIDs: upstreams,
EnterpriseMeta: ns.EnterpriseMeta,
}
if registration.token != "" {
req.QueryOptions.Token = registration.token
@ -527,7 +530,7 @@ func (w *serviceConfigWatch) mergeServiceConfig() (*structs.NodeService, error)
us.MeshGateway.Mode = ns.Proxy.MeshGateway.Mode
}
usCfg, ok := w.defaults.UpstreamConfigs[us.DestinationName]
usCfg, ok := w.defaults.UpstreamIDConfigs.GetUpstreamConfig(us.DestinationID())
if !ok {
// No config defaults to merge
continue

View File

@ -338,12 +338,15 @@ func TestServiceManager_PersistService_API(t *testing.T) {
"foo": 1,
"protocol": "http",
},
UpstreamConfigs: map[string]map[string]interface{}{
"redis": map[string]interface{}{
UpstreamIDConfigs: structs.UpstreamConfigs{
structs.UpstreamConfig{
Upstream: structs.NewServiceID("redis", nil),
Config: map[string]interface{}{
"protocol": "tcp",
},
},
},
},
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
}
expectJSONFile(t, configFile, pcfg, resetDefaultsQueryMeta)
@ -376,12 +379,15 @@ func TestServiceManager_PersistService_API(t *testing.T) {
"foo": 1,
"protocol": "http",
},
UpstreamConfigs: map[string]map[string]interface{}{
"redis": map[string]interface{}{
UpstreamIDConfigs: structs.UpstreamConfigs{
structs.UpstreamConfig{
Upstream: structs.NewServiceID("redis", nil),
Config: map[string]interface{}{
"protocol": "tcp",
},
},
},
},
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
}
expectJSONFile(t, configFile, pcfg, resetDefaultsQueryMeta)
@ -549,12 +555,15 @@ func TestServiceManager_PersistService_ConfigFiles(t *testing.T) {
"foo": 1,
"protocol": "http",
},
UpstreamConfigs: map[string]map[string]interface{}{
"redis": map[string]interface{}{
UpstreamIDConfigs: structs.UpstreamConfigs{
structs.UpstreamConfig{
Upstream: structs.NewServiceID("redis", nil),
Config: map[string]interface{}{
"protocol": "tcp",
},
},
},
},
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
}, resetDefaultsQueryMeta)

View File

@ -41,6 +41,7 @@ type ConfigEntry interface {
CanRead(acl.Authorizer) bool
CanWrite(acl.Authorizer) bool
GetEnterpriseMeta() *EnterpriseMeta
GetRaftIndex() *RaftIndex
}
@ -61,6 +62,7 @@ type ServiceConfigEntry struct {
//
// Connect ConnectConfiguration
EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
RaftIndex
}
@ -84,6 +86,8 @@ func (e *ServiceConfigEntry) Normalize() error {
e.Kind = ServiceDefaults
e.Protocol = strings.ToLower(e.Protocol)
e.EnterpriseMeta.Normalize()
return nil
}
@ -91,12 +95,16 @@ func (e *ServiceConfigEntry) Validate() error {
return nil
}
func (e *ServiceConfigEntry) CanRead(rule acl.Authorizer) bool {
return rule.ServiceRead(e.Name, nil) == acl.Allow
func (e *ServiceConfigEntry) CanRead(authz acl.Authorizer) bool {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ServiceRead(e.Name, &authzContext) == acl.Allow
}
func (e *ServiceConfigEntry) CanWrite(rule acl.Authorizer) bool {
return rule.ServiceWrite(e.Name, nil) == acl.Allow
func (e *ServiceConfigEntry) CanWrite(authz acl.Authorizer) bool {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ServiceWrite(e.Name, &authzContext) == acl.Allow
}
func (e *ServiceConfigEntry) GetRaftIndex() *RaftIndex {
@ -107,6 +115,14 @@ func (e *ServiceConfigEntry) GetRaftIndex() *RaftIndex {
return &e.RaftIndex
}
func (e *ServiceConfigEntry) GetEnterpriseMeta() *EnterpriseMeta {
if e == nil {
return nil
}
return &e.EnterpriseMeta
}
type ConnectConfiguration struct {
SidecarProxy bool
}
@ -119,6 +135,7 @@ type ProxyConfigEntry struct {
MeshGateway MeshGatewayConfig `json:",omitempty"`
Expose ExposeConfig `json:",omitempty"`
EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
RaftIndex
}
@ -142,6 +159,8 @@ func (e *ProxyConfigEntry) Normalize() error {
e.Kind = ProxyDefaults
e.Name = ProxyConfigGlobal
e.EnterpriseMeta.Normalize()
return nil
}
@ -154,15 +173,17 @@ func (e *ProxyConfigEntry) Validate() error {
return fmt.Errorf("invalid name (%q), only %q is supported", e.Name, ProxyConfigGlobal)
}
return nil
return e.validateEnterpriseMeta()
}
func (e *ProxyConfigEntry) CanRead(rule acl.Authorizer) bool {
func (e *ProxyConfigEntry) CanRead(authz acl.Authorizer) bool {
return true
}
func (e *ProxyConfigEntry) CanWrite(rule acl.Authorizer) bool {
return rule.OperatorWrite(nil) == acl.Allow
func (e *ProxyConfigEntry) CanWrite(authz acl.Authorizer) bool {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.OperatorWrite(&authzContext) == acl.Allow
}
func (e *ProxyConfigEntry) GetRaftIndex() *RaftIndex {
@ -173,6 +194,14 @@ func (e *ProxyConfigEntry) GetRaftIndex() *RaftIndex {
return &e.RaftIndex
}
func (e *ProxyConfigEntry) GetEnterpriseMeta() *EnterpriseMeta {
if e == nil {
return nil
}
return &e.EnterpriseMeta
}
func (e *ProxyConfigEntry) MarshalBinary() (data []byte, err error) {
// We mainly want to implement the BinaryMarshaller interface so that
// we can fixup some msgpack types to coerce them into JSON compatible
@ -459,6 +488,7 @@ type ConfigEntryQuery struct {
Name string
Datacenter string
EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
QueryOptions
}
@ -496,7 +526,12 @@ func (r *ConfigEntryQuery) CacheInfo() cache.RequestInfo {
type ServiceConfigRequest struct {
Name string
Datacenter string
// DEPRECATED
// Upstreams is a list of upstream service names to use for resolving the service config
// UpstreamIDs should be used instead which can encode more than just the name to
// uniquely identify a service.
Upstreams []string
UpstreamIDs []ServiceID
EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
QueryOptions
@ -538,9 +573,27 @@ func (r *ServiceConfigRequest) CacheInfo() cache.RequestInfo {
return info
}
type UpstreamConfig struct {
Upstream ServiceID
Config map[string]interface{}
}
type UpstreamConfigs []UpstreamConfig
func (configs UpstreamConfigs) GetUpstreamConfig(sid ServiceID) (config map[string]interface{}, found bool) {
for _, usconf := range configs {
if usconf.Upstream.Matches(&sid) {
return usconf.Config, true
}
}
return nil, false
}
type ServiceConfigResponse struct {
ProxyConfig map[string]interface{}
UpstreamConfigs map[string]map[string]interface{}
UpstreamIDConfigs UpstreamConfigs
MeshGateway MeshGatewayConfig `json:",omitempty"`
Expose ExposeConfig `json:",omitempty"`
QueryMeta
@ -597,6 +650,13 @@ func (r *ServiceConfigResponse) UnmarshalBinary(data []byte) error {
return err
}
}
for k := range r.UpstreamIDConfigs {
r.UpstreamIDConfigs[k].Config, err = lib.MapWalk(r.UpstreamIDConfigs[k].Config)
if err != nil {
return err
}
}
return nil
}

View File

@ -38,6 +38,7 @@ type ServiceRouterConfigEntry struct {
// the default service.
Routes []ServiceRoute
EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
RaftIndex
}
@ -60,6 +61,8 @@ func (e *ServiceRouterConfigEntry) Normalize() error {
e.Kind = ServiceRouter
e.EnterpriseMeta.Normalize()
for _, route := range e.Routes {
if route.Match == nil || route.Match.HTTP == nil {
continue
@ -73,6 +76,9 @@ func (e *ServiceRouterConfigEntry) Normalize() error {
for j := 0; j < len(httpMatch.Methods); j++ {
httpMatch.Methods[j] = strings.ToUpper(httpMatch.Methods[j])
}
if route.Destination != nil && route.Destination.Namespace == "" {
route.Destination.Namespace = e.EnterpriseMeta.NamespaceOrDefault()
}
}
return nil
@ -194,15 +200,15 @@ func (e *ServiceRouterConfigEntry) GetRaftIndex() *RaftIndex {
return &e.RaftIndex
}
func (e *ServiceRouterConfigEntry) ListRelatedServices() []string {
found := make(map[string]struct{})
func (e *ServiceRouterConfigEntry) ListRelatedServices() []ServiceID {
found := make(map[ServiceID]struct{})
// We always inject a default catch-all route to the same service as the router.
found[e.Name] = struct{}{}
found[NewServiceID(e.Name, &e.EnterpriseMeta)] = struct{}{}
for _, route := range e.Routes {
if route.Destination != nil && route.Destination.Service != "" {
found[route.Destination.Service] = struct{}{}
found[NewServiceID(route.Destination.Service, route.Destination.GetEnterpriseMeta(&e.EnterpriseMeta))] = struct{}{}
}
}
@ -210,14 +216,25 @@ func (e *ServiceRouterConfigEntry) ListRelatedServices() []string {
return nil
}
out := make([]string, 0, len(found))
out := make([]ServiceID, 0, len(found))
for svc, _ := range found {
out = append(out, svc)
}
sort.Strings(out)
sort.Slice(out, func(i, j int) bool {
return out[i].EnterpriseMeta.LessThan(&out[j].EnterpriseMeta) ||
out[i].ID < out[j].ID
})
return out
}
func (e *ServiceRouterConfigEntry) GetEnterpriseMeta() *EnterpriseMeta {
if e == nil {
return nil
}
return &e.EnterpriseMeta
}
// ServiceRoute is a single routing rule that routes traffic to the destination
// when the match criteria applies.
type ServiceRoute struct {
@ -386,6 +403,7 @@ type ServiceSplitterConfigEntry struct {
// to the FIRST split.
Splits []ServiceSplit
EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
RaftIndex
}
@ -411,8 +429,13 @@ func (e *ServiceSplitterConfigEntry) Normalize() error {
// This slightly massages inputs to enforce that the smallest representable
// weight is 1/10000 or .01%
e.EnterpriseMeta.Normalize()
if len(e.Splits) > 0 {
for i, split := range e.Splits {
if split.Namespace == "" {
split.Namespace = e.EnterpriseMeta.NamespaceOrDefault()
}
e.Splits[i].Weight = NormalizeServiceSplitWeight(split.Weight)
}
}
@ -493,12 +516,20 @@ func (e *ServiceSplitterConfigEntry) GetRaftIndex() *RaftIndex {
return &e.RaftIndex
}
func (e *ServiceSplitterConfigEntry) ListRelatedServices() []string {
found := make(map[string]struct{})
func (e *ServiceSplitterConfigEntry) GetEnterpriseMeta() *EnterpriseMeta {
if e == nil {
return nil
}
return &e.EnterpriseMeta
}
func (e *ServiceSplitterConfigEntry) ListRelatedServices() []ServiceID {
found := make(map[ServiceID]struct{})
for _, split := range e.Splits {
if split.Service != "" {
found[split.Service] = struct{}{}
found[NewServiceID(split.Service, split.GetEnterpriseMeta(&e.EnterpriseMeta))] = struct{}{}
}
}
@ -506,11 +537,14 @@ func (e *ServiceSplitterConfigEntry) ListRelatedServices() []string {
return nil
}
out := make([]string, 0, len(found))
out := make([]ServiceID, 0, len(found))
for svc, _ := range found {
out = append(out, svc)
}
sort.Strings(out)
sort.Slice(out, func(i, j int) bool {
return out[i].EnterpriseMeta.LessThan(&out[j].EnterpriseMeta) ||
out[i].ID < out[j].ID
})
return out
}
@ -598,6 +632,7 @@ type ServiceResolverConfigEntry struct {
// to this service.
ConnectTimeout time.Duration `json:",omitempty"`
EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
RaftIndex
}
@ -675,6 +710,8 @@ func (e *ServiceResolverConfigEntry) Normalize() error {
e.Kind = ServiceResolver
e.EnterpriseMeta.Normalize()
return nil
}
@ -782,19 +819,27 @@ func (e *ServiceResolverConfigEntry) GetRaftIndex() *RaftIndex {
return &e.RaftIndex
}
func (e *ServiceResolverConfigEntry) ListRelatedServices() []string {
found := make(map[string]struct{})
func (e *ServiceResolverConfigEntry) GetEnterpriseMeta() *EnterpriseMeta {
if e == nil {
return nil
}
return &e.EnterpriseMeta
}
func (e *ServiceResolverConfigEntry) ListRelatedServices() []ServiceID {
found := make(map[ServiceID]struct{})
if e.Redirect != nil {
if e.Redirect.Service != "" {
found[e.Redirect.Service] = struct{}{}
found[NewServiceID(e.Redirect.Service, e.Redirect.GetEnterpriseMeta(&e.EnterpriseMeta))] = struct{}{}
}
}
if len(e.Failover) > 0 {
for _, failover := range e.Failover {
if failover.Service != "" {
found[failover.Service] = struct{}{}
found[NewServiceID(failover.Service, failover.GetEnterpriseMeta(&e.EnterpriseMeta))] = struct{}{}
}
}
}
@ -803,11 +848,14 @@ func (e *ServiceResolverConfigEntry) ListRelatedServices() []string {
return nil
}
out := make([]string, 0, len(found))
out := make([]ServiceID, 0, len(found))
for svc, _ := range found {
out = append(out, svc)
}
sort.Strings(out)
sort.Slice(out, func(i, j int) bool {
return out[i].EnterpriseMeta.LessThan(&out[j].EnterpriseMeta) ||
out[i].ID < out[j].ID
})
return out
}
@ -889,28 +937,34 @@ type discoveryChainConfigEntry interface {
ConfigEntry
// ListRelatedServices returns a list of other names of services referenced
// in this config entry.
ListRelatedServices() []string
ListRelatedServices() []ServiceID
}
func canReadDiscoveryChain(entry discoveryChainConfigEntry, rule acl.Authorizer) bool {
return rule.ServiceRead(entry.GetName(), nil) == acl.Allow
func canReadDiscoveryChain(entry discoveryChainConfigEntry, authz acl.Authorizer) bool {
var authzContext acl.AuthorizerContext
entry.GetEnterpriseMeta().FillAuthzContext(&authzContext)
return authz.ServiceRead(entry.GetName(), &authzContext) == acl.Allow
}
func canWriteDiscoveryChain(entry discoveryChainConfigEntry, rule acl.Authorizer) bool {
var authzContext acl.AuthorizerContext
entry.GetEnterpriseMeta().FillAuthzContext(&authzContext)
name := entry.GetName()
if rule.ServiceWrite(name, nil) != acl.Allow {
if rule.ServiceWrite(name, &authzContext) != acl.Allow {
return false
}
for _, svc := range entry.ListRelatedServices() {
if svc == name {
if svc.ID == name {
continue
}
svc.FillAuthzContext(&authzContext)
// You only need read on related services to redirect traffic flow for
// your own service.
if rule.ServiceRead(svc, nil) != acl.Allow {
if rule.ServiceRead(svc.ID, &authzContext) != acl.Allow {
return false
}
}
@ -920,46 +974,46 @@ func canWriteDiscoveryChain(entry discoveryChainConfigEntry, rule acl.Authorizer
// DiscoveryChainConfigEntries wraps just the raw cross-referenced config
// entries. None of these are defaulted.
type DiscoveryChainConfigEntries struct {
Routers map[string]*ServiceRouterConfigEntry
Splitters map[string]*ServiceSplitterConfigEntry
Resolvers map[string]*ServiceResolverConfigEntry
Services map[string]*ServiceConfigEntry
Routers map[ServiceID]*ServiceRouterConfigEntry
Splitters map[ServiceID]*ServiceSplitterConfigEntry
Resolvers map[ServiceID]*ServiceResolverConfigEntry
Services map[ServiceID]*ServiceConfigEntry
GlobalProxy *ProxyConfigEntry
}
func NewDiscoveryChainConfigEntries() *DiscoveryChainConfigEntries {
return &DiscoveryChainConfigEntries{
Routers: make(map[string]*ServiceRouterConfigEntry),
Splitters: make(map[string]*ServiceSplitterConfigEntry),
Resolvers: make(map[string]*ServiceResolverConfigEntry),
Services: make(map[string]*ServiceConfigEntry),
Routers: make(map[ServiceID]*ServiceRouterConfigEntry),
Splitters: make(map[ServiceID]*ServiceSplitterConfigEntry),
Resolvers: make(map[ServiceID]*ServiceResolverConfigEntry),
Services: make(map[ServiceID]*ServiceConfigEntry),
}
}
func (e *DiscoveryChainConfigEntries) GetRouter(name string) *ServiceRouterConfigEntry {
func (e *DiscoveryChainConfigEntries) GetRouter(sid ServiceID) *ServiceRouterConfigEntry {
if e.Routers != nil {
return e.Routers[name]
return e.Routers[sid]
}
return nil
}
func (e *DiscoveryChainConfigEntries) GetSplitter(name string) *ServiceSplitterConfigEntry {
func (e *DiscoveryChainConfigEntries) GetSplitter(sid ServiceID) *ServiceSplitterConfigEntry {
if e.Splitters != nil {
return e.Splitters[name]
return e.Splitters[sid]
}
return nil
}
func (e *DiscoveryChainConfigEntries) GetResolver(name string) *ServiceResolverConfigEntry {
func (e *DiscoveryChainConfigEntries) GetResolver(sid ServiceID) *ServiceResolverConfigEntry {
if e.Resolvers != nil {
return e.Resolvers[name]
return e.Resolvers[sid]
}
return nil
}
func (e *DiscoveryChainConfigEntries) GetService(name string) *ServiceConfigEntry {
func (e *DiscoveryChainConfigEntries) GetService(sid ServiceID) *ServiceConfigEntry {
if e.Services != nil {
return e.Services[name]
return e.Services[sid]
}
return nil
}
@ -967,40 +1021,40 @@ func (e *DiscoveryChainConfigEntries) GetService(name string) *ServiceConfigEntr
// AddRouters adds router configs. Convenience function for testing.
func (e *DiscoveryChainConfigEntries) AddRouters(entries ...*ServiceRouterConfigEntry) {
if e.Routers == nil {
e.Routers = make(map[string]*ServiceRouterConfigEntry)
e.Routers = make(map[ServiceID]*ServiceRouterConfigEntry)
}
for _, entry := range entries {
e.Routers[entry.Name] = entry
e.Routers[NewServiceID(entry.Name, &entry.EnterpriseMeta)] = entry
}
}
// AddSplitters adds splitter configs. Convenience function for testing.
func (e *DiscoveryChainConfigEntries) AddSplitters(entries ...*ServiceSplitterConfigEntry) {
if e.Splitters == nil {
e.Splitters = make(map[string]*ServiceSplitterConfigEntry)
e.Splitters = make(map[ServiceID]*ServiceSplitterConfigEntry)
}
for _, entry := range entries {
e.Splitters[entry.Name] = entry
e.Splitters[NewServiceID(entry.Name, entry.GetEnterpriseMeta())] = entry
}
}
// AddResolvers adds resolver configs. Convenience function for testing.
func (e *DiscoveryChainConfigEntries) AddResolvers(entries ...*ServiceResolverConfigEntry) {
if e.Resolvers == nil {
e.Resolvers = make(map[string]*ServiceResolverConfigEntry)
e.Resolvers = make(map[ServiceID]*ServiceResolverConfigEntry)
}
for _, entry := range entries {
e.Resolvers[entry.Name] = entry
e.Resolvers[NewServiceID(entry.Name, entry.GetEnterpriseMeta())] = entry
}
}
// AddServices adds service configs. Convenience function for testing.
func (e *DiscoveryChainConfigEntries) AddServices(entries ...*ServiceConfigEntry) {
if e.Services == nil {
e.Services = make(map[string]*ServiceConfigEntry)
e.Services = make(map[ServiceID]*ServiceConfigEntry)
}
for _, entry := range entries {
e.Services[entry.Name] = entry
e.Services[NewServiceID(entry.Name, entry.GetEnterpriseMeta())] = entry
}
}

View File

@ -0,0 +1,39 @@
// +build !consulent
package structs
// GetEnterpriseMeta is used to synthesize the EnterpriseMeta struct from
// fields in the ServiceRouteDestination
func (dest *ServiceRouteDestination) GetEnterpriseMeta(_ *EnterpriseMeta) *EnterpriseMeta {
return DefaultEnterpriseMeta()
}
// GetEnterpriseMeta is used to synthesize the EnterpriseMeta struct from
// fields in the ServiceSplit
func (split *ServiceSplit) GetEnterpriseMeta(_ *EnterpriseMeta) *EnterpriseMeta {
return DefaultEnterpriseMeta()
}
// GetEnterpriseMeta is used to synthesize the EnterpriseMeta struct from
// fields in the ServiceResolverRedirect
func (redir *ServiceResolverRedirect) GetEnterpriseMeta(_ *EnterpriseMeta) *EnterpriseMeta {
return DefaultEnterpriseMeta()
}
// GetEnterpriseMeta is used to synthesize the EnterpriseMeta struct from
// fields in the ServiceResolverFailover
func (failover *ServiceResolverFailover) GetEnterpriseMeta(_ *EnterpriseMeta) *EnterpriseMeta {
return DefaultEnterpriseMeta()
}
// GetEnterpriseMeta is used to synthesize the EnterpriseMeta struct from
// fields in the DiscoveryChainRequest
func (req *DiscoveryChainRequest) GetEnterpriseMeta() *EnterpriseMeta {
return DefaultEnterpriseMeta()
}
// WithEnterpriseMeta will populate the corresponding fields in the
// DiscoveryChainRequest from the EnterpriseMeta struct
func (req *DiscoveryChainRequest) WithEnterpriseMeta(_ *EnterpriseMeta) {
// do nothing
}

View File

@ -67,7 +67,7 @@ func TestConfigEntries_ListRelatedServices_AndACLs(t *testing.T) {
for _, tc := range []struct {
name string
entry discoveryChainConfigEntry
expectServices []string
expectServices []ServiceID
expectACLs []testACL
}{
{
@ -92,7 +92,7 @@ func TestConfigEntries_ListRelatedServices_AndACLs(t *testing.T) {
Service: "other",
},
},
expectServices: []string{"other"},
expectServices: []ServiceID{NewServiceID("other", nil)},
expectACLs: []testACL{
defaultDenyCase,
readTestCase,
@ -123,7 +123,7 @@ func TestConfigEntries_ListRelatedServices_AndACLs(t *testing.T) {
},
},
},
expectServices: []string{"other1", "other2"},
expectServices: []ServiceID{NewServiceID("other1", nil), NewServiceID("other2", nil)},
expectACLs: []testACL{
defaultDenyCase,
readTestCase,
@ -163,7 +163,7 @@ func TestConfigEntries_ListRelatedServices_AndACLs(t *testing.T) {
{Weight: 50, Service: "c"},
},
},
expectServices: []string{"a", "b", "c"},
expectServices: []ServiceID{NewServiceID("a", nil), NewServiceID("b", nil), NewServiceID("c", nil)},
expectACLs: []testACL{
defaultDenyCase,
readTestCase,
@ -182,7 +182,7 @@ func TestConfigEntries_ListRelatedServices_AndACLs(t *testing.T) {
Kind: ServiceRouter,
Name: "test",
},
expectServices: []string{"test"},
expectServices: []ServiceID{NewServiceID("test", nil)},
expectACLs: []testACL{
defaultDenyCase,
readTestCase,
@ -213,7 +213,7 @@ func TestConfigEntries_ListRelatedServices_AndACLs(t *testing.T) {
},
},
},
expectServices: []string{"bar", "foo", "test"},
expectServices: []ServiceID{NewServiceID("bar", nil), NewServiceID("foo", nil), NewServiceID("test", nil)},
expectACLs: []testACL{
defaultDenyCase,
readTestCase,

View File

@ -0,0 +1,7 @@
// +build !consulent
package structs
func (e *ProxyConfigEntry) validateEnterpriseMeta() error {
return nil
}

View File

@ -0,0 +1,13 @@
// +build !consulent
package structs
func (us *Upstream) GetEnterpriseMeta() *EnterpriseMeta {
return DefaultEnterpriseMeta()
}
func (us *Upstream) DestinationID() ServiceID {
return ServiceID{
ID: us.DestinationName,
}
}

View File

@ -238,3 +238,7 @@ func (t *DiscoveryTarget) setID() {
func (t *DiscoveryTarget) String() string {
return t.ID
}
func (t *DiscoveryTarget) ServiceID() ServiceID {
return NewServiceID(t.Service, t.GetEnterpriseMetadata())
}

View File

@ -0,0 +1,7 @@
// +build !consulent
package structs
func (t *DiscoveryTarget) GetEnterpriseMetadata() *EnterpriseMeta {
return DefaultEnterpriseMeta()
}

View File

@ -803,13 +803,16 @@ func (s *ServiceNode) ToNodeService() *NodeService {
}
}
func (s *ServiceNode) CompoundServiceID() ServiceID {
id := s.ServiceID
if id == "" {
id = s.ServiceName
func (sn *ServiceNode) compoundID(preferName bool) ServiceID {
var id string
if sn.ServiceID == "" || (preferName && sn.ServiceName != "") {
id = sn.ServiceName
} else {
id = sn.ServiceID
}
entMeta := s.EnterpriseMeta
// copy the ent meta and normalize it
entMeta := sn.EnterpriseMeta
entMeta.Normalize()
return ServiceID{
@ -818,6 +821,14 @@ func (s *ServiceNode) CompoundServiceID() ServiceID {
}
}
func (sn *ServiceNode) CompoundServiceID() ServiceID {
return sn.compoundID(false)
}
func (sn *ServiceNode) CompoundServiceName() ServiceID {
return sn.compoundID(true)
}
// Weights represent the weight used by DNS for a given status
type Weights struct {
Passing int
@ -938,11 +949,12 @@ func (ns *NodeService) BestAddress(wan bool) (string, int) {
return addr, port
}
func (ns *NodeService) CompoundServiceID() ServiceID {
id := ns.ID
if id == "" {
func (ns *NodeService) compoundID(preferName bool) ServiceID {
var id string
if ns.ID == "" || (preferName && ns.Service != "") {
id = ns.Service
} else {
id = ns.ID
}
// copy the ent meta and normalize it
@ -955,6 +967,14 @@ func (ns *NodeService) CompoundServiceID() ServiceID {
}
}
func (ns *NodeService) CompoundServiceID() ServiceID {
return ns.compoundID(false)
}
func (ns *NodeService) CompoundServiceName() ServiceID {
return ns.compoundID(true)
}
// ServiceConnect are the shared Connect settings between all service
// definitions from the agent to the state store.
type ServiceConnect struct {
@ -1645,6 +1665,22 @@ type IndexedServices struct {
QueryMeta
}
type ServiceInfo struct {
Name string
EnterpriseMeta
}
func (si *ServiceInfo) ToServiceID() ServiceID {
return ServiceID{ID: si.Name, EnterpriseMeta: si.EnterpriseMeta}
}
type ServiceList []ServiceInfo
type IndexedServiceList struct {
Services ServiceList
QueryMeta
}
type IndexedServiceNodes struct {
ServiceNodes ServiceNodes
QueryMeta

View File

@ -26,6 +26,10 @@ func (m *EnterpriseMeta) Merge(_ *EnterpriseMeta) {
// do nothing
}
func (m *EnterpriseMeta) MergeNoWildcard(_ *EnterpriseMeta) {
// do nothing
}
func (m *EnterpriseMeta) Matches(_ *EnterpriseMeta) bool {
return true
}
@ -38,6 +42,10 @@ func (m *EnterpriseMeta) LessThan(_ *EnterpriseMeta) bool {
return false
}
func (m *EnterpriseMeta) NamespaceOrDefault() string {
return "default"
}
// ReplicationEnterpriseMeta stub
func ReplicationEnterpriseMeta() *EnterpriseMeta {
return &emptyEnterpriseMeta
@ -81,10 +89,19 @@ func ServiceIDString(id string, _ *EnterpriseMeta) string {
return id
}
func ParseServiceIDString(input string) (string, *EnterpriseMeta) {
return input, DefaultEnterpriseMeta()
}
func (sid *ServiceID) String() string {
return sid.ID
}
func ServiceIDFromString(input string) ServiceID {
id, _ := ParseServiceIDString(input)
return ServiceID{ID: id}
}
func (cid *CheckID) String() string {
return string(cid.ID)
}

View File

@ -7,9 +7,9 @@ import (
// TestIntention returns a valid, uninserted (no ID set) intention.
func TestIntention(t testing.T) *Intention {
return &Intention{
SourceNS: "eng",
SourceNS: IntentionDefaultNamespace,
SourceName: "api",
DestinationNS: "eng",
DestinationNS: IntentionDefaultNamespace,
DestinationName: "db",
Action: IntentionActionAllow,
SourceType: IntentionSourceConsul,

View File

@ -79,9 +79,7 @@ func (s *Server) clustersFromSnapshotConnectProxy(cfgSnap *proxycfg.ConfigSnapsh
// Add service health checks to the list of paths to create clusters for if needed
if cfgSnap.Proxy.Expose.Checks {
// TODO (namespaces) update with real entmeta
var psid structs.ServiceID
psid.Init(cfgSnap.Proxy.DestinationServiceID, structs.DefaultEnterpriseMeta())
psid := structs.NewServiceID(cfgSnap.Proxy.DestinationServiceID, &cfgSnap.ProxyID.EnterpriseMeta)
for _, check := range s.CheckFetcher.ServiceHTTPBasedChecks(psid) {
p, err := parseCheckPath(check)
if err != nil {
@ -131,7 +129,7 @@ func (s *Server) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsho
// generate the per-service clusters
for svc, _ := range cfgSnap.MeshGateway.ServiceGroups {
clusterName := connect.ServiceSNI(svc, "", "default", cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain)
clusterName := connect.ServiceSNI(svc.ID, "", svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain)
cluster, err := s.makeMeshGatewayCluster(clusterName, cfgSnap)
if err != nil {
@ -143,7 +141,7 @@ func (s *Server) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsho
// generate the service subset clusters
for svc, resolver := range cfgSnap.MeshGateway.ServiceResolvers {
for subsetName, _ := range resolver.Subsets {
clusterName := connect.ServiceSNI(svc, subsetName, "default", cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain)
clusterName := connect.ServiceSNI(svc.ID, subsetName, svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain)
cluster, err := s.makeMeshGatewayCluster(clusterName, cfgSnap)
if err != nil {

View File

@ -259,8 +259,8 @@ func TestClustersFromSnapshot(t *testing.T) {
name: "mesh-gateway-service-subsets",
create: proxycfg.TestConfigSnapshotMeshGateway,
setup: func(snap *proxycfg.ConfigSnapshot) {
snap.MeshGateway.ServiceResolvers = map[string]*structs.ServiceResolverConfigEntry{
"bar": &structs.ServiceResolverConfigEntry{
snap.MeshGateway.ServiceResolvers = map[structs.ServiceID]*structs.ServiceResolverConfigEntry{
structs.NewServiceID("bar", nil): &structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "bar",
Subsets: map[string]structs.ServiceResolverSubset{

View File

@ -37,7 +37,7 @@ func (s *Server) endpointsFromSnapshot(cfgSnap *proxycfg.ConfigSnapshot, token s
// (upstream instances) in the snapshot.
func (s *Server) endpointsFromSnapshotConnectProxy(cfgSnap *proxycfg.ConfigSnapshot, token string) ([]proto.Message, error) {
resources := make([]proto.Message, 0,
len(cfgSnap.ConnectProxy.UpstreamEndpoints)+len(cfgSnap.ConnectProxy.WatchedUpstreamEndpoints))
len(cfgSnap.ConnectProxy.PreparedQueryEndpoints)+len(cfgSnap.ConnectProxy.WatchedUpstreamEndpoints))
for _, u := range cfgSnap.Proxy.Upstreams {
id := u.Identifier()
@ -56,7 +56,7 @@ func (s *Server) endpointsFromSnapshotConnectProxy(cfgSnap *proxycfg.ConfigSnaps
}
clusterName := connect.UpstreamSNI(&u, "", dc, cfgSnap.Roots.TrustDomain)
endpoints, ok := cfgSnap.ConnectProxy.UpstreamEndpoints[id]
endpoints, ok := cfgSnap.ConnectProxy.PreparedQueryEndpoints[id]
if ok {
la := makeLoadAssignment(
clusterName,
@ -167,7 +167,7 @@ func (s *Server) endpointsFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsh
// generate the endpoints for the local service groups
for svc, endpoints := range cfgSnap.MeshGateway.ServiceGroups {
clusterName := connect.ServiceSNI(svc, "", "default", cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain)
clusterName := connect.ServiceSNI(svc.ID, "", svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain)
la := makeLoadAssignment(
clusterName,
[]loadAssignmentEndpointGroup{
@ -181,7 +181,7 @@ func (s *Server) endpointsFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapsh
// generate the endpoints for the service subsets
for svc, resolver := range cfgSnap.MeshGateway.ServiceResolvers {
for subsetName, subset := range resolver.Subsets {
clusterName := connect.ServiceSNI(svc, subsetName, "default", cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain)
clusterName := connect.ServiceSNI(svc.ID, subsetName, svc.NamespaceOrDefault(), cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain)
endpoints := cfgSnap.MeshGateway.ServiceGroups[svc]

View File

@ -311,8 +311,8 @@ func Test_endpointsFromSnapshot(t *testing.T) {
name: "mesh-gateway-service-subsets",
create: proxycfg.TestConfigSnapshotMeshGateway,
setup: func(snap *proxycfg.ConfigSnapshot) {
snap.MeshGateway.ServiceResolvers = map[string]*structs.ServiceResolverConfigEntry{
"bar": &structs.ServiceResolverConfigEntry{
snap.MeshGateway.ServiceResolvers = map[structs.ServiceID]*structs.ServiceResolverConfigEntry{
structs.NewServiceID("bar", nil): &structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "bar",
Subsets: map[string]structs.ServiceResolverSubset{
@ -325,7 +325,7 @@ func Test_endpointsFromSnapshot(t *testing.T) {
},
},
},
"foo": &structs.ServiceResolverConfigEntry{
structs.NewServiceID("foo", nil): &structs.ServiceResolverConfigEntry{
Kind: structs.ServiceResolver,
Name: "foo",
Subsets: map[string]structs.ServiceResolverSubset{

View File

@ -81,9 +81,7 @@ func (s *Server) listenersFromSnapshotConnectProxy(cfgSnap *proxycfg.ConfigSnaps
// Add service health checks to the list of paths to create listeners for if needed
if cfgSnap.Proxy.Expose.Checks {
// TODO (namespaces) update with real ent meta
var psid structs.ServiceID
psid.Init(cfgSnap.Proxy.DestinationServiceID, structs.DefaultEnterpriseMeta())
psid := structs.NewServiceID(cfgSnap.Proxy.DestinationServiceID, &cfgSnap.ProxyID.EnterpriseMeta)
for _, check := range s.CheckFetcher.ServiceHTTPBasedChecks(psid) {
p, err := parseCheckPath(check)
if err != nil {

View File

@ -115,7 +115,7 @@ type ConfigFetcher interface {
// easier testing without several layers of mocked cache, local state and
// proxycfg.Manager.
type ConfigManager interface {
Watch(proxyID string) (<-chan *proxycfg.ConfigSnapshot, proxycfg.CancelFunc)
Watch(proxyID structs.ServiceID) (<-chan *proxycfg.ConfigSnapshot, proxycfg.CancelFunc)
}
// Server represents a gRPC server that can handle both XDS and ext_authz
@ -200,7 +200,7 @@ func (s *Server) process(stream ADSStream, reqCh <-chan *envoy.DiscoveryRequest)
var ok bool
var stateCh <-chan *proxycfg.ConfigSnapshot
var watchCancel func()
var proxyID string
var proxyID structs.ServiceID
// need to run a small state machine to get through initial authentication.
var state = stateInit
@ -254,15 +254,16 @@ func (s *Server) process(stream ADSStream, reqCh <-chan *envoy.DiscoveryRequest)
return err
}
var authzContext acl.AuthorizerContext
switch cfgSnap.Kind {
case structs.ServiceKindConnectProxy:
// TODO (namespaces) - pass through a real ent authz ctx
if rule != nil && rule.ServiceWrite(cfgSnap.Proxy.DestinationServiceName, nil) != acl.Allow {
cfgSnap.ProxyID.EnterpriseMeta.FillAuthzContext(&authzContext)
if rule != nil && rule.ServiceWrite(cfgSnap.Proxy.DestinationServiceName, &authzContext) != acl.Allow {
return status.Errorf(codes.PermissionDenied, "permission denied")
}
case structs.ServiceKindMeshGateway:
// TODO (namespaces) - pass through a real ent authz ctx
if rule != nil && rule.ServiceWrite(cfgSnap.Service, nil) != acl.Allow {
cfgSnap.ProxyID.EnterpriseMeta.FillAuthzContext(&authzContext)
if rule != nil && rule.ServiceWrite(cfgSnap.Service, &authzContext) != acl.Allow {
return status.Errorf(codes.PermissionDenied, "permission denied")
}
default:
@ -310,7 +311,7 @@ func (s *Server) process(stream ADSStream, reqCh <-chan *envoy.DiscoveryRequest)
continue
}
// Start authentication process, we need the proxyID
proxyID = req.Node.Id
proxyID = structs.NewServiceID(req.Node.Id, parseEnterpriseMeta(req.Node))
// Start watching config for that proxy
stateCh, watchCancel = s.CfgMgr.Watch(proxyID)

12
agent/xds/server_oss.go Normal file
View File

@ -0,0 +1,12 @@
// +build !consulent
package xds
import (
envoycore "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
"github.com/hashicorp/consul/agent/structs"
)
func parseEnterpriseMeta(node *envoycore.Node) *structs.EnterpriseMeta {
return structs.DefaultEnterpriseMeta()
}

View File

@ -27,8 +27,8 @@ import (
// testing. It also implements ConnectAuthz to allow control over authorization.
type testManager struct {
sync.Mutex
chans map[string]chan *proxycfg.ConfigSnapshot
cancels chan string
chans map[structs.ServiceID]chan *proxycfg.ConfigSnapshot
cancels chan structs.ServiceID
authz map[string]connectAuthzResult
}
@ -41,21 +41,21 @@ type connectAuthzResult struct {
func newTestManager(t *testing.T) *testManager {
return &testManager{
chans: map[string]chan *proxycfg.ConfigSnapshot{},
cancels: make(chan string, 10),
chans: map[structs.ServiceID]chan *proxycfg.ConfigSnapshot{},
cancels: make(chan structs.ServiceID, 10),
authz: make(map[string]connectAuthzResult),
}
}
// RegisterProxy simulates a proxy registration
func (m *testManager) RegisterProxy(t *testing.T, proxyID string) {
func (m *testManager) RegisterProxy(t *testing.T, proxyID structs.ServiceID) {
m.Lock()
defer m.Unlock()
m.chans[proxyID] = make(chan *proxycfg.ConfigSnapshot, 1)
}
// Deliver simulates a proxy registration
func (m *testManager) DeliverConfig(t *testing.T, proxyID string, cfg *proxycfg.ConfigSnapshot) {
func (m *testManager) DeliverConfig(t *testing.T, proxyID structs.ServiceID, cfg *proxycfg.ConfigSnapshot) {
t.Helper()
m.Lock()
defer m.Unlock()
@ -67,7 +67,7 @@ func (m *testManager) DeliverConfig(t *testing.T, proxyID string, cfg *proxycfg.
}
// Watch implements ConfigManager
func (m *testManager) Watch(proxyID string) (<-chan *proxycfg.ConfigSnapshot, proxycfg.CancelFunc) {
func (m *testManager) Watch(proxyID structs.ServiceID) (<-chan *proxycfg.ConfigSnapshot, proxycfg.CancelFunc) {
m.Lock()
defer m.Unlock()
// ch might be nil but then it will just block forever
@ -81,7 +81,7 @@ func (m *testManager) Watch(proxyID string) (<-chan *proxycfg.ConfigSnapshot, pr
// probably won't work if you are running multiple Watches in parallel on
// multiple proxyIDS due to timing/ordering issues but I don't think we need to
// do that.
func (m *testManager) AssertWatchCancelled(t *testing.T, proxyID string) {
func (m *testManager) AssertWatchCancelled(t *testing.T, proxyID structs.ServiceID) {
t.Helper()
select {
case got := <-m.cancels:
@ -120,13 +120,15 @@ func TestServer_StreamAggregatedResources_BasicProtocol(t *testing.T) {
}
s.Initialize()
sid := structs.NewServiceID("web-sidecar-proxy", nil)
go func() {
err := s.StreamAggregatedResources(envoy.stream)
require.NoError(t, err)
}()
// Register the proxy to create state needed to Watch() on
mgr.RegisterProxy(t, "web-sidecar-proxy")
mgr.RegisterProxy(t, sid)
// Send initial cluster discover
envoy.SendReq(t, ClusterType, 0, 0)
@ -136,7 +138,7 @@ func TestServer_StreamAggregatedResources_BasicProtocol(t *testing.T) {
// Deliver a new snapshot
snap := proxycfg.TestConfigSnapshot(t)
mgr.DeliverConfig(t, "web-sidecar-proxy", snap)
mgr.DeliverConfig(t, sid, snap)
assertResponseSent(t, envoy.stream.sendCh, expectClustersJSON(t, snap, "", 1, 1))
@ -179,7 +181,7 @@ func TestServer_StreamAggregatedResources_BasicProtocol(t *testing.T) {
// doesn't know _what_ changed. We could do something trivial but let's
// simulate a leaf cert expiring and being rotated.
snap.ConnectProxy.Leaf = proxycfg.TestLeafForCA(t, snap.Roots.Roots[0])
mgr.DeliverConfig(t, "web-sidecar-proxy", snap)
mgr.DeliverConfig(t, sid, snap)
// All 3 response that have something to return should return with new version
// note that the ordering is not deterministic in general. Trying to make this
@ -223,7 +225,7 @@ func TestServer_StreamAggregatedResources_BasicProtocol(t *testing.T) {
// Change config again and make sure it's delivered to everyone!
snap.ConnectProxy.Leaf = proxycfg.TestLeafForCA(t, snap.Roots.Roots[0])
mgr.DeliverConfig(t, "web-sidecar-proxy", snap)
mgr.DeliverConfig(t, sid, snap)
assertResponseSent(t, envoy.stream.sendCh, expectClustersJSON(t, snap, "", 3, 7))
assertResponseSent(t, envoy.stream.sendCh, expectEndpointsJSON(t, snap, "", 3, 8))
@ -468,12 +470,13 @@ func TestServer_StreamAggregatedResources_ACLEnforcement(t *testing.T) {
errCh <- s.StreamAggregatedResources(envoy.stream)
}()
sid := structs.NewServiceID("web-sidecar-proxy", nil)
// Register the proxy to create state needed to Watch() on
mgr.RegisterProxy(t, "web-sidecar-proxy")
mgr.RegisterProxy(t, sid)
// Deliver a new snapshot
snap := proxycfg.TestConfigSnapshot(t)
mgr.DeliverConfig(t, "web-sidecar-proxy", snap)
mgr.DeliverConfig(t, sid, snap)
// Send initial listener discover, in real life Envoy always sends cluster
// first but it doesn't really matter and listener has a response that
@ -493,7 +496,7 @@ func TestServer_StreamAggregatedResources_ACLEnforcement(t *testing.T) {
if tt.wantDenied {
require.Error(t, err)
require.Contains(t, err.Error(), "permission denied")
mgr.AssertWatchCancelled(t, "web-sidecar-proxy")
mgr.AssertWatchCancelled(t, sid)
} else {
require.NoError(t, err)
}
@ -549,8 +552,9 @@ func TestServer_StreamAggregatedResources_ACLTokenDeleted_StreamTerminatedDuring
}
}
sid := structs.NewServiceID("web-sidecar-proxy", nil)
// Register the proxy to create state needed to Watch() on
mgr.RegisterProxy(t, "web-sidecar-proxy")
mgr.RegisterProxy(t, sid)
// Send initial cluster discover (OK)
envoy.SendReq(t, ClusterType, 0, 0)
@ -570,7 +574,7 @@ func TestServer_StreamAggregatedResources_ACLTokenDeleted_StreamTerminatedDuring
// Deliver a new snapshot
snap := proxycfg.TestConfigSnapshot(t)
mgr.DeliverConfig(t, "web-sidecar-proxy", snap)
mgr.DeliverConfig(t, sid, snap)
assertResponseSent(t, envoy.stream.sendCh, expectClustersJSON(t, snap, token, 1, 1))
@ -589,7 +593,7 @@ func TestServer_StreamAggregatedResources_ACLTokenDeleted_StreamTerminatedDuring
require.Equal(t, codes.Unauthenticated, gerr.Code())
require.Equal(t, "unauthenticated: ACL not found", gerr.Message())
mgr.AssertWatchCancelled(t, "web-sidecar-proxy")
mgr.AssertWatchCancelled(t, sid)
case <-time.After(50 * time.Millisecond):
t.Fatalf("timed out waiting for handler to finish")
}
@ -640,8 +644,9 @@ func TestServer_StreamAggregatedResources_ACLTokenDeleted_StreamTerminatedInBack
}
}
sid := structs.NewServiceID("web-sidecar-proxy", nil)
// Register the proxy to create state needed to Watch() on
mgr.RegisterProxy(t, "web-sidecar-proxy")
mgr.RegisterProxy(t, sid)
// Send initial cluster discover (OK)
envoy.SendReq(t, ClusterType, 0, 0)
@ -661,7 +666,7 @@ func TestServer_StreamAggregatedResources_ACLTokenDeleted_StreamTerminatedInBack
// Deliver a new snapshot
snap := proxycfg.TestConfigSnapshot(t)
mgr.DeliverConfig(t, "web-sidecar-proxy", snap)
mgr.DeliverConfig(t, sid, snap)
assertResponseSent(t, envoy.stream.sendCh, expectClustersJSON(t, snap, token, 1, 1))
@ -688,7 +693,7 @@ func TestServer_StreamAggregatedResources_ACLTokenDeleted_StreamTerminatedInBack
require.Equal(t, codes.Unauthenticated, gerr.Code())
require.Equal(t, "unauthenticated: ACL not found", gerr.Message())
mgr.AssertWatchCancelled(t, "web-sidecar-proxy")
mgr.AssertWatchCancelled(t, sid)
case <-time.After(200 * time.Millisecond):
t.Fatalf("timed out waiting for handler to finish")
}

View File

@ -88,6 +88,7 @@ type ExposePath struct {
type ServiceConfigEntry struct {
Kind string
Name string
Namespace string `json:",omitempty"`
Protocol string `json:",omitempty"`
MeshGateway MeshGatewayConfig `json:",omitempty"`
Expose ExposeConfig `json:",omitempty"`
@ -115,6 +116,7 @@ func (s *ServiceConfigEntry) GetModifyIndex() uint64 {
type ProxyConfigEntry struct {
Kind string
Name string
Namespace string `json:",omitempty"`
Config map[string]interface{} `json:",omitempty"`
MeshGateway MeshGatewayConfig `json:",omitempty"`
Expose ExposeConfig `json:",omitempty"`

View File

@ -8,6 +8,7 @@ import (
type ServiceRouterConfigEntry struct {
Kind string
Name string
Namespace string `json:",omitempty"`
Routes []ServiceRoute `json:",omitempty"`
@ -106,6 +107,7 @@ func (e *ServiceRouteDestination) UnmarshalJSON(data []byte) error {
type ServiceSplitterConfigEntry struct {
Kind string
Name string
Namespace string `json:",omitempty"`
Splits []ServiceSplit `json:",omitempty"`
@ -128,6 +130,7 @@ type ServiceSplit struct {
type ServiceResolverConfigEntry struct {
Kind string
Name string
Namespace string `json:",omitempty"`
DefaultSubset string `json:",omitempty"`
Subsets map[string]ServiceResolverSubset `json:",omitempty"`

View File

@ -130,6 +130,7 @@ func TestAPI_ConfigEntry_DiscoveryChain(t *testing.T) {
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test-failover",
Namespace: defaultNamespace,
DefaultSubset: "v1",
Subsets: map[string]ServiceResolverSubset{
"v1": ServiceResolverSubset{
@ -145,6 +146,7 @@ func TestAPI_ConfigEntry_DiscoveryChain(t *testing.T) {
},
"v1": ServiceResolverFailover{
Service: "alternate",
Namespace: defaultNamespace,
},
},
ConnectTimeout: 5 * time.Second,
@ -156,10 +158,11 @@ func TestAPI_ConfigEntry_DiscoveryChain(t *testing.T) {
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test-redirect",
Namespace: defaultNamespace,
Redirect: &ServiceResolverRedirect{
Service: "test-failover",
ServiceSubset: "v2",
Namespace: "c",
Namespace: defaultNamespace,
Datacenter: "d",
},
},
@ -170,17 +173,18 @@ func TestAPI_ConfigEntry_DiscoveryChain(t *testing.T) {
entry: &ServiceSplitterConfigEntry{
Kind: ServiceSplitter,
Name: "test-split",
Namespace: defaultNamespace,
Splits: []ServiceSplit{
{
Weight: 90,
Service: "test-failover",
ServiceSubset: "v1",
Namespace: "c",
Namespace: defaultNamespace,
},
{
Weight: 10,
Service: "test-redirect",
Namespace: "z",
Namespace: defaultNamespace,
},
},
},
@ -191,6 +195,7 @@ func TestAPI_ConfigEntry_DiscoveryChain(t *testing.T) {
entry: &ServiceRouterConfigEntry{
Kind: ServiceRouter,
Name: "test-route",
Namespace: defaultNamespace,
Routes: []ServiceRoute{
{
Match: &ServiceRouteMatch{
@ -207,7 +212,7 @@ func TestAPI_ConfigEntry_DiscoveryChain(t *testing.T) {
Destination: &ServiceRouteDestination{
Service: "test-failover",
ServiceSubset: "v2",
Namespace: "sec",
Namespace: defaultNamespace,
PrefixRewrite: "/",
RequestTimeout: 5 * time.Second,
NumRetries: 5,

View File

@ -31,6 +31,7 @@ func (c *cmd) init() {
c.http = &flags.HTTPFlags{}
flags.Merge(c.flags, c.http.ClientFlags())
flags.Merge(c.flags, c.http.ServerFlags())
flags.Merge(c.flags, c.http.NamespaceFlags())
c.help = flags.Usage(help, c.flags)
}

View File

@ -29,6 +29,7 @@ func (c *cmd) init() {
c.http = &flags.HTTPFlags{}
flags.Merge(c.flags, c.http.ClientFlags())
flags.Merge(c.flags, c.http.ServerFlags())
flags.Merge(c.flags, c.http.NamespaceFlags())
c.help = flags.Usage(help, c.flags)
}

View File

@ -34,6 +34,7 @@ func (c *cmd) init() {
c.http = &flags.HTTPFlags{}
flags.Merge(c.flags, c.http.ClientFlags())
flags.Merge(c.flags, c.http.ServerFlags())
flags.Merge(c.flags, c.http.NamespaceFlags())
c.help = flags.Usage(help, c.flags)
}

View File

@ -44,6 +44,7 @@ func (c *cmd) init() {
"This is used in combination with the -cas flag.")
flags.Merge(c.flags, c.http.ClientFlags())
flags.Merge(c.flags, c.http.ServerFlags())
flags.Merge(c.flags, c.http.NamespaceFlags())
c.help = flags.Usage(help, c.flags)
}

View File

@ -92,6 +92,10 @@ type BootstrapTplArgs struct {
// the bootstrap config. It's format may vary based on Envoy version used.
// See https://www.envoyproxy.io/docs/envoy/v1.9.0/api-v2/config/trace/v2/trace.proto.
TracingConfigJSON string
// Namespace is the Consul Enterprise Namespace of the proxy service instance as
// registered with the Consul agent.
Namespace string
}
const bootstrapTemplate = `{
@ -106,7 +110,10 @@ const bootstrapTemplate = `{
},
"node": {
"cluster": "{{ .ProxyCluster }}",
"id": "{{ .ProxyID }}"
"id": "{{ .ProxyID }}",
"metadata": {
"namespace": "{{if ne .Namespace ""}}{{ .Namespace }}{{else}}default{{end}}"
}
},
"static_resources": {
"clusters": [

View File

@ -131,6 +131,7 @@ func (c *cmd) init() {
c.http = &flags.HTTPFlags{}
flags.Merge(c.flags, c.http.ClientFlags())
flags.Merge(c.flags, c.http.NamespaceFlags())
c.help = flags.Usage(help, c.flags)
}
@ -517,6 +518,7 @@ func (c *cmd) templateArgs() (*BootstrapTplArgs, error) {
AdminBindPort: adminPort,
Token: httpCfg.Token,
LocalAgentClusterName: xds.LocalAgentClusterName,
Namespace: httpCfg.Namespace,
}, nil
}

View File

@ -10,7 +10,10 @@
},
"node": {
"cluster": "test-proxy",
"id": "test-proxy"
"id": "test-proxy",
"metadata": {
"namespace": "default"
}
},
"static_resources": {
"clusters": [

View File

@ -10,7 +10,10 @@
},
"node": {
"cluster": "test-proxy",
"id": "test-proxy"
"id": "test-proxy",
"metadata": {
"namespace": "default"
}
},
"static_resources": {
"clusters": [

View File

@ -10,7 +10,10 @@
},
"node": {
"cluster": "test-proxy",
"id": "test-proxy"
"id": "test-proxy",
"metadata": {
"namespace": "default"
}
},
"static_resources": {
"clusters": [

View File

@ -10,7 +10,10 @@
},
"node": {
"cluster": "test-proxy",
"id": "test-proxy"
"id": "test-proxy",
"metadata": {
"namespace": "default"
}
},
"static_resources": {
"clusters": [

View File

@ -10,7 +10,10 @@
},
"node": {
"cluster": "test-proxy",
"id": "test-proxy"
"id": "test-proxy",
"metadata": {
"namespace": "default"
}
},
"static_resources": {
"clusters": [

View File

@ -10,7 +10,10 @@
},
"node": {
"cluster": "test-proxy",
"id": "test-proxy"
"id": "test-proxy",
"metadata": {
"namespace": "default"
}
},
"static_resources": {
"clusters": [

View File

@ -10,7 +10,10 @@
},
"node": {
"cluster": "test-proxy",
"id": "test-proxy"
"id": "test-proxy",
"metadata": {
"namespace": "default"
}
},
"static_resources": {
"clusters": [

View File

@ -10,7 +10,10 @@
},
"node": {
"cluster": "test-proxy",
"id": "test-proxy"
"id": "test-proxy",
"metadata": {
"namespace": "default"
}
},
"static_resources": {
"clusters": [

View File

@ -10,7 +10,10 @@
},
"node": {
"cluster": "test-proxy",
"id": "test-proxy"
"id": "test-proxy",
"metadata": {
"namespace": "default"
}
},
"static_resources": {
"clusters": [

View File

@ -10,7 +10,10 @@
},
"node": {
"cluster": "test-proxy",
"id": "test-proxy"
"id": "test-proxy",
"metadata": {
"namespace": "default"
}
},
"static_resources": {
"clusters": [

View File

@ -10,7 +10,10 @@
},
"node": {
"cluster": "test-proxy",
"id": "test-proxy"
"id": "test-proxy",
"metadata": {
"namespace": "default"
}
},
"static_resources": {
"clusters": [

View File

@ -10,7 +10,10 @@
},
"node": {
"cluster": "test-proxy",
"id": "test-proxy"
"id": "test-proxy",
"metadata": {
"namespace": "default"
}
},
"static_resources": {
"clusters": [

View File

@ -10,7 +10,10 @@
},
"node": {
"cluster": "test-proxy",
"id": "test-proxy"
"id": "test-proxy",
"metadata": {
"namespace": "default"
}
},
"static_resources": {
"clusters": [

View File

@ -10,7 +10,10 @@
},
"node": {
"cluster": "test-proxy",
"id": "test-proxy"
"id": "test-proxy",
"metadata": {
"namespace": "default"
}
},
"static_resources": {
"clusters": [

View File

@ -10,7 +10,10 @@
},
"node": {
"cluster": "test-proxy",
"id": "test-proxy"
"id": "test-proxy",
"metadata": {
"namespace": "default"
}
},
"static_resources": {
"clusters": [

View File

@ -0,0 +1,7 @@
FROM alpine:latest
RUN apk add --no-cache tcpdump
VOLUME [ "/data" ]
CMD [ "-w", "/data/all.pcap" ]
ENTRYPOINT [ "/usr/sbin/tcpdump" ]

View File

@ -156,6 +156,22 @@ services:
- "disabled"
network_mode: service:consul-primary
s3-alt:
depends_on:
- consul-primary
image: "fortio/fortio"
environment:
- "FORTIO_NAME=s3-alt"
command:
- "server"
- "-http-port"
- ":8286"
- "-grpc-port"
- ":8280"
- "-redirect-port"
- "disabled"
network_mode: service:consul-primary
s1-sidecar-proxy:
depends_on:
- consul-primary
@ -303,6 +319,27 @@ services:
- *workdir-volume
network_mode: service:consul-primary
s3-alt-sidecar-proxy:
depends_on:
- consul-primary
image: "envoyproxy/envoy:v${ENVOY_VERSION:-1.8.0}"
command:
- "envoy"
- "-c"
- "/workdir/primary/envoy/s3-alt-bootstrap.json"
- "-l"
- "debug"
# Hot restart breaks since both envoys seem to interact with each other
# despite separate containers that don't share IPC namespace. Not quite
# sure how this happens but may be due to unix socket being in some shared
# location?
- "--disable-hot-restart"
- "--drain-time-s"
- "1"
volumes:
- *workdir-volume
network_mode: service:consul-primary
verify-primary:
depends_on:
- consul-primary
@ -557,3 +594,35 @@ services:
- *workdir-volume
network_mode: service:consul-secondary
pid: host
tcpdump-primary:
depends_on:
- consul-primary
build:
context: .
dockerfile: Dockerfile-tcpdump
# we cant do this in circle but its only here to temporarily enable.
volumes:
- type: bind
source: ./${LOG_DIR}
target: /data
command: -v -i any -w /data/primary.pcap
network_mode: service:consul-primary
tcpdump-secondary:
depends_on:
- consul-secondary
build:
context: .
dockerfile: Dockerfile-tcpdump
# we cant do this in circle but its only here to temporarily enable.
volumes:
- type: bind
source: ./${LOG_DIR}
target: /data
command: -v -i any -w /data/secondary.pcap
network_mode: service:consul-secondary

View File

@ -19,7 +19,7 @@ function retry {
for ((i=1;i<=$max;i++))
do
if $@
if "$@"
then
if test $errtrace -eq 1
then
@ -42,13 +42,13 @@ function retry {
function retry_default {
set +E
ret=0
retry 5 1 $@ || ret=1
retry 5 1 "$@" || ret=1
set -E
return $ret
}
function retry_long {
retry 30 1 $@
retry 30 1 "$@"
}
function echored {
@ -108,15 +108,16 @@ function assert_proxy_presents_cert_uri {
local HOSTPORT=$1
local SERVICENAME=$2
local DC=${3:-primary}
local NS=${4:-default}
CERT=$(retry_default get_cert $HOSTPORT)
echo "WANT SERVICE: $SERVICENAME"
echo "WANT SERVICE: ${NS}/${SERVICENAME}"
echo "GOT CERT:"
echo "$CERT"
echo "$CERT" | grep -Eo "URI:spiffe://([a-zA-Z0-9-]+).consul/ns/default/dc/${DC}/svc/$SERVICENAME"
echo "$CERT" | grep -Eo "URI:spiffe://([a-zA-Z0-9-]+).consul/ns/${NS}/dc/${DC}/svc/$SERVICENAME"
}
function assert_envoy_version {
@ -290,10 +291,41 @@ function assert_envoy_metric_at_least {
fi
}
function assert_envoy_aggregate_metric_at_least {
set -eEuo pipefail
local HOSTPORT=$1
local METRIC=$2
local EXPECT_COUNT=$3
METRICS=$(get_envoy_metrics $HOSTPORT "$METRIC")
if [ -z "${METRICS}" ]
then
echo "Metric not found" 1>&2
return 1
fi
GOT_COUNT=$(awk '{ sum += $2 } END { print sum }' <<< "$METRICS")
if [ -z "$GOT_COUNT" ]
then
echo "Couldn't parse metric count" 1>&2
return 1
fi
if [ $EXPECT_COUNT -gt $GOT_COUNT ]
then
echo "$METRIC - expected >= count: $EXPECT_COUNT, actual count: $GOT_COUNT" 1>&2
return 1
fi
}
function get_healthy_service_count {
local SERVICE_NAME=$1
local DC=$2
run retry_default curl -s -f "127.0.0.1:8500/v1/health/connect/${SERVICE_NAME}?dc=${DC}&passing"
local NS=$3
run retry_default curl -s -f ${HEADERS} "127.0.0.1:8500/v1/health/connect/${SERVICE_NAME}?dc=${DC}&passing&ns=${NS}"
[ "$status" -eq 0 ]
echo "$output" | jq --raw-output '. | length'
}
@ -302,8 +334,9 @@ function assert_service_has_healthy_instances_once {
local SERVICE_NAME=$1
local EXPECT_COUNT=$2
local DC=${3:-primary}
local NS=$4
GOT_COUNT=$(get_healthy_service_count $SERVICE_NAME $DC)
GOT_COUNT=$(get_healthy_service_count "$SERVICE_NAME" "$DC" "$NS")
[ "$GOT_COUNT" -eq $EXPECT_COUNT ]
}
@ -312,8 +345,9 @@ function assert_service_has_healthy_instances {
local SERVICE_NAME=$1
local EXPECT_COUNT=$2
local DC=${3:-primary}
local NS=$4
run retry_long assert_service_has_healthy_instances_once $SERVICE_NAME $EXPECT_COUNT $DC
run retry_long assert_service_has_healthy_instances_once "$SERVICE_NAME" "$EXPECT_COUNT" "$DC" "$NS"
[ "$status" -eq 0 ]
}
@ -335,6 +369,20 @@ function docker_curl {
docker run -ti --rm --network container:envoy_consul-${DC}_1 --entrypoint curl consul-dev "$@"
}
function docker_exec {
if ! docker exec -i "$@"
then
echo "Failed to execute: docker exec -i $@" 1>&2
return 1
fi
}
function docker_consul_exec {
local DC=$1
shift 1
docker_exec envoy_consul-${DC}_1 "$@"
}
function get_envoy_pid {
local BOOTSTRAP_NAME=$1
local DC=${2:-primary}
@ -417,6 +465,7 @@ function gen_envoy_bootstrap {
ADMIN_PORT=$2
DC=${3:-primary}
IS_MGW=${4:-0}
EXTRA_ENVOY_BS_ARGS="${5-}"
PROXY_ID="$SERVICE"
if ! is_set "$IS_MGW"
@ -426,7 +475,7 @@ function gen_envoy_bootstrap {
if output=$(docker_consul "$DC" connect envoy -bootstrap \
-proxy-id $PROXY_ID \
-admin-bind 0.0.0.0:$ADMIN_PORT 2>&1); then
-admin-bind 0.0.0.0:$ADMIN_PORT ${EXTRA_ENVOY_BS_ARGS} 2>&1); then
# All OK, write config to file
echo "$output" > workdir/${DC}/envoy/$SERVICE-bootstrap.json

View File

@ -54,11 +54,11 @@ function cleanup {
trap cleanup EXIT
function command_error {
echo "ERR: command exited with status $1"
echo " command: $2"
echo " line: $3"
echo " function: $4"
echo " called at: $5"
echo "ERR: command exited with status $1" 1>&2
echo " command: $2" 1>&2
echo " line: $3" 1>&2
echo " function: $4" 1>&2
echo " called at: $5" 1>&2
# printf '%s\n' "${FUNCNAME[@]}"
# printf '%s\n' "${BASH_SOURCE[@]}"
# printf '%s\n' "${BASH_LINENO[@]}"
@ -84,7 +84,7 @@ function init_workdir {
# don't wipe logs between runs as they are already split and we need them to
# upload as artifacts later.
rm -rf workdir/${DC}
mkdir -p workdir/${DC}/{consul,envoy,bats,statsd}
mkdir -p workdir/${DC}/{consul,envoy,bats,statsd,data}
# Reload consul config from defaults
cp consul-base-cfg/* workdir/${DC}/consul/
@ -104,6 +104,11 @@ function init_workdir {
find ${CASE_DIR}/${DC} -type f -name '*.bats' -exec cp -f {} workdir/${DC}/bats \;
fi
if test -d "${CASE_DIR}/data"
then
cp -r ${CASE_DIR}/data/* workdir/${DC}/data
fi
return 0
}
@ -243,23 +248,28 @@ function runTest {
fi
fi
echo "Setting up the primary datacenter"
pre_service_setup primary
if [ $? -ne 0 ]
then
echo "Setting up the primary datacenter failed"
capture_logs
return 1
fi
if is_set $REQUIRE_SECONDARY
then
echo "Setting up the secondary datacenter"
pre_service_setup secondary
if [ $? -ne 0 ]
then
echo "Setting up the secondary datacenter failed"
capture_logs
return 1
fi
fi
echo "Starting services"
start_services
if [ $? -ne 0 ]
then