mirror of https://github.com/hashicorp/consul
Updates to Config Entries and Connect for Namespaces (#7116)
parent
47ac0bcd01
commit
c09693e545
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
// +build !consulent
|
||||
|
||||
package cachetype
|
||||
|
||||
func (req *ConnectCALeafRequest) TargetNamespace() string {
|
||||
return "default"
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
// +build !consulent
|
||||
|
||||
package discoverychain
|
||||
|
||||
import "github.com/hashicorp/consul/agent/structs"
|
||||
|
||||
func (c *compiler) GetEnterpriseMeta() *structs.EnterpriseMeta {
|
||||
return structs.DefaultEnterpriseMeta()
|
||||
}
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
// +build !consulent
|
||||
|
||||
package state
|
||||
|
||||
func (s *Store) setupDefaultTestEntMeta() error {
|
||||
return nil
|
||||
}
|
|
@ -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{}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) ||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
// +build !consulent
|
||||
|
||||
package structs
|
||||
|
||||
func (e *ProxyConfigEntry) validateEnterpriseMeta() error {
|
||||
return nil
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
// +build !consulent
|
||||
|
||||
package structs
|
||||
|
||||
func (t *DiscoveryTarget) GetEnterpriseMetadata() *EnterpriseMeta {
|
||||
return DefaultEnterpriseMeta()
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
},
|
||||
"node": {
|
||||
"cluster": "test-proxy",
|
||||
"id": "test-proxy"
|
||||
"id": "test-proxy",
|
||||
"metadata": {
|
||||
"namespace": "default"
|
||||
}
|
||||
},
|
||||
"static_resources": {
|
||||
"clusters": [
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
},
|
||||
"node": {
|
||||
"cluster": "test-proxy",
|
||||
"id": "test-proxy"
|
||||
"id": "test-proxy",
|
||||
"metadata": {
|
||||
"namespace": "default"
|
||||
}
|
||||
},
|
||||
"static_resources": {
|
||||
"clusters": [
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
},
|
||||
"node": {
|
||||
"cluster": "test-proxy",
|
||||
"id": "test-proxy"
|
||||
"id": "test-proxy",
|
||||
"metadata": {
|
||||
"namespace": "default"
|
||||
}
|
||||
},
|
||||
"static_resources": {
|
||||
"clusters": [
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
},
|
||||
"node": {
|
||||
"cluster": "test-proxy",
|
||||
"id": "test-proxy"
|
||||
"id": "test-proxy",
|
||||
"metadata": {
|
||||
"namespace": "default"
|
||||
}
|
||||
},
|
||||
"static_resources": {
|
||||
"clusters": [
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
},
|
||||
"node": {
|
||||
"cluster": "test-proxy",
|
||||
"id": "test-proxy"
|
||||
"id": "test-proxy",
|
||||
"metadata": {
|
||||
"namespace": "default"
|
||||
}
|
||||
},
|
||||
"static_resources": {
|
||||
"clusters": [
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
},
|
||||
"node": {
|
||||
"cluster": "test-proxy",
|
||||
"id": "test-proxy"
|
||||
"id": "test-proxy",
|
||||
"metadata": {
|
||||
"namespace": "default"
|
||||
}
|
||||
},
|
||||
"static_resources": {
|
||||
"clusters": [
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
},
|
||||
"node": {
|
||||
"cluster": "test-proxy",
|
||||
"id": "test-proxy"
|
||||
"id": "test-proxy",
|
||||
"metadata": {
|
||||
"namespace": "default"
|
||||
}
|
||||
},
|
||||
"static_resources": {
|
||||
"clusters": [
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
},
|
||||
"node": {
|
||||
"cluster": "test-proxy",
|
||||
"id": "test-proxy"
|
||||
"id": "test-proxy",
|
||||
"metadata": {
|
||||
"namespace": "default"
|
||||
}
|
||||
},
|
||||
"static_resources": {
|
||||
"clusters": [
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
},
|
||||
"node": {
|
||||
"cluster": "test-proxy",
|
||||
"id": "test-proxy"
|
||||
"id": "test-proxy",
|
||||
"metadata": {
|
||||
"namespace": "default"
|
||||
}
|
||||
},
|
||||
"static_resources": {
|
||||
"clusters": [
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
},
|
||||
"node": {
|
||||
"cluster": "test-proxy",
|
||||
"id": "test-proxy"
|
||||
"id": "test-proxy",
|
||||
"metadata": {
|
||||
"namespace": "default"
|
||||
}
|
||||
},
|
||||
"static_resources": {
|
||||
"clusters": [
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
},
|
||||
"node": {
|
||||
"cluster": "test-proxy",
|
||||
"id": "test-proxy"
|
||||
"id": "test-proxy",
|
||||
"metadata": {
|
||||
"namespace": "default"
|
||||
}
|
||||
},
|
||||
"static_resources": {
|
||||
"clusters": [
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
},
|
||||
"node": {
|
||||
"cluster": "test-proxy",
|
||||
"id": "test-proxy"
|
||||
"id": "test-proxy",
|
||||
"metadata": {
|
||||
"namespace": "default"
|
||||
}
|
||||
},
|
||||
"static_resources": {
|
||||
"clusters": [
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
},
|
||||
"node": {
|
||||
"cluster": "test-proxy",
|
||||
"id": "test-proxy"
|
||||
"id": "test-proxy",
|
||||
"metadata": {
|
||||
"namespace": "default"
|
||||
}
|
||||
},
|
||||
"static_resources": {
|
||||
"clusters": [
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
},
|
||||
"node": {
|
||||
"cluster": "test-proxy",
|
||||
"id": "test-proxy"
|
||||
"id": "test-proxy",
|
||||
"metadata": {
|
||||
"namespace": "default"
|
||||
}
|
||||
},
|
||||
"static_resources": {
|
||||
"clusters": [
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
},
|
||||
"node": {
|
||||
"cluster": "test-proxy",
|
||||
"id": "test-proxy"
|
||||
"id": "test-proxy",
|
||||
"metadata": {
|
||||
"namespace": "default"
|
||||
}
|
||||
},
|
||||
"static_resources": {
|
||||
"clusters": [
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
FROM alpine:latest
|
||||
|
||||
RUN apk add --no-cache tcpdump
|
||||
VOLUME [ "/data" ]
|
||||
|
||||
CMD [ "-w", "/data/all.pcap" ]
|
||||
ENTRYPOINT [ "/usr/sbin/tcpdump" ]
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue