mirror of https://github.com/status-im/consul.git
Add Proxy Upstreams to Service Definition (#4639)
* Refactor Service Definition ProxyDestination. This includes: - Refactoring all internal structs used - Updated tests for both deprecated and new input for: - Agent Services endpoint response - Agent Service endpoint response - Agent Register endpoint - Unmanaged deprecated field - Unmanaged new fields - Managed deprecated upstreams - Managed new - Catalog Register - Unmanaged deprecated field - Unmanaged new fields - Managed deprecated upstreams - Managed new - Catalog Services endpoint response - Catalog Node endpoint response - Catalog Service endpoint response - Updated API tests for all of the above too (both deprecated and new forms of register) TODO: - config package changes for on-disk service definitions - proxy config endpoint - built-in proxy support for new fields * Agent proxy config endpoint updated with upstreams * Config file changes for upstreams. * Add upstream opaque config and update all tests to ensure it works everywhere. * Built in proxy working with new Upstreams config * Command fixes and deprecations * Fix key translation, upstream type defaults and a spate of other subtele bugs found with ned to end test scripts... TODO: tests still failing on one case that needs a fix. I think it's key translation for upstreams nested in Managed proxy struct. * Fix translated keys in API registration. ≈ * Fixes from docs - omit some empty undocumented fields in API - Bring back ServiceProxyDestination in Catalog responses to not break backwards compat - this was removed assuming it was only used internally. * Documentation updates for Upstreams in service definition * Fixes for tests broken by many refactors. * Enable travis on f-connect branch in this branch too. * Add consistent Deprecation comments to ProxyDestination uses * Update version number on deprecation notices, and correct upstream datacenter field with explanation in docs
This commit is contained in:
parent
b06ddc9187
commit
b83bbf248c
|
@ -190,24 +190,39 @@ func (s *HTTPServer) AgentServices(resp http.ResponseWriter, req *http.Request)
|
|||
EnableTagOverride: s.EnableTagOverride,
|
||||
CreateIndex: s.CreateIndex,
|
||||
ModifyIndex: s.ModifyIndex,
|
||||
ProxyDestination: s.ProxyDestination,
|
||||
Weights: weights,
|
||||
}
|
||||
|
||||
if as.Tags == nil {
|
||||
as.Tags = []string{}
|
||||
}
|
||||
if as.Meta == nil {
|
||||
as.Meta = map[string]string{}
|
||||
}
|
||||
// Attach Connect configs if the exist
|
||||
// Attach Unmanaged Proxy config if exists
|
||||
if s.Kind == structs.ServiceKindConnectProxy {
|
||||
as.Proxy = s.Proxy.ToAPI()
|
||||
// DEPRECATED (ProxyDestination) - remove this when removing ProxyDestination
|
||||
// Also set the deprecated ProxyDestination
|
||||
as.ProxyDestination = as.Proxy.DestinationServiceName
|
||||
}
|
||||
|
||||
// Attach Connect configs if the exist. We use the actual proxy state since
|
||||
// that may have had defaults filled in compared to the config that was
|
||||
// provided with the service as stored in the NodeService here.
|
||||
if proxy, ok := proxies[id+"-proxy"]; ok {
|
||||
as.Connect = &api.AgentServiceConnect{
|
||||
Proxy: &api.AgentServiceConnectProxy{
|
||||
ExecMode: api.ProxyExecMode(proxy.Proxy.ExecMode.String()),
|
||||
Command: proxy.Proxy.Command,
|
||||
Config: proxy.Proxy.Config,
|
||||
ExecMode: api.ProxyExecMode(proxy.Proxy.ExecMode.String()),
|
||||
Command: proxy.Proxy.Command,
|
||||
Config: proxy.Proxy.Config,
|
||||
Upstreams: proxy.Proxy.Upstreams.ToAPI(),
|
||||
},
|
||||
}
|
||||
} else if s.Connect.Native {
|
||||
as.Connect = &api.AgentServiceConnect{
|
||||
Native: true,
|
||||
}
|
||||
}
|
||||
agentSvcs[id] = as
|
||||
}
|
||||
|
@ -546,6 +561,29 @@ func (s *HTTPServer) AgentRegisterService(resp http.ResponseWriter, req *http.Re
|
|||
"enable_tag_override": "EnableTagOverride",
|
||||
})
|
||||
|
||||
// Translate upstream keys - we have the same upstream format in two
|
||||
// possible places.
|
||||
translateUpstreams := func(rawMap map[string]interface{}) {
|
||||
var upstreams []interface{}
|
||||
if us, ok := rawMap["upstreams"].([]interface{}); ok {
|
||||
upstreams = us
|
||||
}
|
||||
if us, ok := rawMap["Upstreams"].([]interface{}); ok {
|
||||
upstreams = us
|
||||
}
|
||||
for _, u := range upstreams {
|
||||
if uMap, ok := u.(map[string]interface{}); ok {
|
||||
config.TranslateKeys(uMap, map[string]string{
|
||||
"destination_name": "DestinationName",
|
||||
"destination_type": "DestinationType",
|
||||
"destination_namespace": "DestinationNamespace",
|
||||
"local_bind_port": "LocalBindPort",
|
||||
"local_bind_address": "LocalBindAddress",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range rawMap {
|
||||
switch strings.ToLower(k) {
|
||||
case "check":
|
||||
|
@ -562,6 +600,32 @@ func (s *HTTPServer) AgentRegisterService(resp http.ResponseWriter, req *http.Re
|
|||
return err
|
||||
}
|
||||
}
|
||||
case "proxy":
|
||||
if valMap, ok := v.(map[string]interface{}); ok {
|
||||
config.TranslateKeys(valMap, map[string]string{
|
||||
"destination_service_name": "DestinationServiceName",
|
||||
"destination_service_id": "DestinationServiceID",
|
||||
"local_service_port": "LocalServicePort",
|
||||
"local_service_address": "LocalServiceAddress",
|
||||
})
|
||||
translateUpstreams(valMap)
|
||||
}
|
||||
case "connect":
|
||||
if connectMap, ok := v.(map[string]interface{}); ok {
|
||||
var proxyMap map[string]interface{}
|
||||
if pMap, ok := connectMap["Proxy"].(map[string]interface{}); ok {
|
||||
proxyMap = pMap
|
||||
}
|
||||
if pMap, ok := connectMap["proxy"].(map[string]interface{}); ok {
|
||||
proxyMap = pMap
|
||||
}
|
||||
if proxyMap != nil {
|
||||
config.TranslateKeys(proxyMap, map[string]string{
|
||||
"exec_mode": "ExecMode",
|
||||
})
|
||||
translateUpstreams(proxyMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -1109,6 +1173,7 @@ func (s *HTTPServer) AgentConnectProxyConfig(resp http.ResponseWriter, req *http
|
|||
ExecMode: api.ProxyExecMode(proxy.Proxy.ExecMode.String()),
|
||||
Command: proxy.Proxy.Command,
|
||||
Config: config,
|
||||
Upstreams: proxy.Proxy.Upstreams.ToAPI(),
|
||||
}
|
||||
return contentHash, reply, nil
|
||||
})
|
||||
|
|
|
@ -28,7 +28,6 @@ import (
|
|||
"github.com/hashicorp/consul/types"
|
||||
"github.com/hashicorp/serf/serf"
|
||||
"github.com/mitchellh/copystructure"
|
||||
"github.com/pascaldekloe/goe/verify"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -75,6 +74,7 @@ func TestAgent_Services(t *testing.T) {
|
|||
"foo": "bar",
|
||||
},
|
||||
TargetServiceID: "mysql",
|
||||
Upstreams: structs.TestUpstreams(t),
|
||||
}
|
||||
_, err := a.State.AddProxy(prxy1, "", "")
|
||||
require.NoError(t, err)
|
||||
|
@ -88,11 +88,12 @@ func TestAgent_Services(t *testing.T) {
|
|||
assert.Lenf(t, val, 1, "bad services: %v", obj)
|
||||
assert.Equal(t, 5000, val["mysql"].Port)
|
||||
assert.Equal(t, srv1.Meta, val["mysql"].Meta)
|
||||
assert.NotNil(t, val["mysql"].Connect)
|
||||
assert.NotNil(t, val["mysql"].Connect.Proxy)
|
||||
require.NotNil(t, val["mysql"].Connect)
|
||||
require.NotNil(t, val["mysql"].Connect.Proxy)
|
||||
assert.Equal(t, prxy1.ExecMode.String(), string(val["mysql"].Connect.Proxy.ExecMode))
|
||||
assert.Equal(t, prxy1.Command, val["mysql"].Connect.Proxy.Command)
|
||||
assert.Equal(t, prxy1.Config, val["mysql"].Connect.Proxy.Config)
|
||||
assert.Equal(t, prxy1.Upstreams.ToAPI(), val["mysql"].Connect.Proxy.Upstreams)
|
||||
}
|
||||
|
||||
// This tests that the agent services endpoint (/v1/agent/services) returns
|
||||
|
@ -106,11 +107,14 @@ func TestAgent_Services_ExternalConnectProxy(t *testing.T) {
|
|||
|
||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||
srv1 := &structs.NodeService{
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
ID: "db-proxy",
|
||||
Service: "db-proxy",
|
||||
Port: 5000,
|
||||
ProxyDestination: "db",
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
ID: "db-proxy",
|
||||
Service: "db-proxy",
|
||||
Port: 5000,
|
||||
Proxy: structs.ConnectProxyConfig{
|
||||
DestinationServiceName: "db",
|
||||
Upstreams: structs.TestUpstreams(t),
|
||||
},
|
||||
}
|
||||
a.State.AddService(srv1, "")
|
||||
|
||||
|
@ -121,7 +125,12 @@ func TestAgent_Services_ExternalConnectProxy(t *testing.T) {
|
|||
assert.Len(val, 1)
|
||||
actual := val["db-proxy"]
|
||||
assert.Equal(api.ServiceKindConnectProxy, actual.Kind)
|
||||
assert.Equal("db", actual.ProxyDestination)
|
||||
assert.Equal(srv1.Proxy.ToAPI(), actual.Proxy)
|
||||
|
||||
// DEPRECATED (ProxyDestination) - remove the next comment and assertion
|
||||
// Should still have deprecated ProxyDestination filled in until we remove it
|
||||
// completely at a major version bump.
|
||||
assert.Equal(srv1.Proxy.DestinationServiceName, actual.ProxyDestination)
|
||||
}
|
||||
|
||||
func TestAgent_Services_ACLFilter(t *testing.T) {
|
||||
|
@ -1376,20 +1385,66 @@ func TestAgent_RegisterService(t *testing.T) {
|
|||
|
||||
func TestAgent_RegisterService_TranslateKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
a := NewTestAgent(t.Name(), "")
|
||||
a := NewTestAgent(t.Name(), `
|
||||
connect {
|
||||
proxy {
|
||||
allow_managed_api_registration = true
|
||||
}
|
||||
}
|
||||
`)
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||
|
||||
json := `{"name":"test", "port":8000, "enable_tag_override": true, "meta": {"some": "meta"}, "weights":{"passing": 16}}`
|
||||
json := `
|
||||
{
|
||||
"name":"test",
|
||||
"port":8000,
|
||||
"enable_tag_override": true,
|
||||
"meta": {
|
||||
"some": "meta"
|
||||
},
|
||||
"kind": "connect-proxy",
|
||||
"proxy": {
|
||||
"destination_service_name": "web",
|
||||
"destination_service_id": "web",
|
||||
"local_service_port": 1234,
|
||||
"local_service_address": "127.0.0.1",
|
||||
"upstreams": [
|
||||
{
|
||||
"destination_type": "service",
|
||||
"destination_namespace": "default",
|
||||
"destination_name": "db",
|
||||
"local_bind_address": "127.0.0.1",
|
||||
"local_bind_port": 1234
|
||||
}
|
||||
]
|
||||
},
|
||||
"connect": {
|
||||
"proxy": {
|
||||
"exec_mode": "script",
|
||||
"upstreams": [
|
||||
{
|
||||
"destination_type": "service",
|
||||
"destination_namespace": "default",
|
||||
"destination_name": "db",
|
||||
"local_bind_address": "127.0.0.1",
|
||||
"local_bind_port": 1234
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"weights":{
|
||||
"passing": 16
|
||||
}
|
||||
}`
|
||||
req, _ := http.NewRequest("PUT", "/v1/agent/service/register", strings.NewReader(json))
|
||||
|
||||
obj, err := a.srv.AgentRegisterService(nil, req)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if obj != nil {
|
||||
t.Fatalf("bad: %v", obj)
|
||||
}
|
||||
rr := httptest.NewRecorder()
|
||||
obj, err := a.srv.AgentRegisterService(rr, req)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, obj)
|
||||
require.Equal(t, 200, rr.Code, "body: %s", rr.Body)
|
||||
|
||||
svc := &structs.NodeService{
|
||||
ID: "test",
|
||||
Service: "test",
|
||||
|
@ -1397,11 +1452,40 @@ func TestAgent_RegisterService_TranslateKeys(t *testing.T) {
|
|||
Port: 8000,
|
||||
EnableTagOverride: true,
|
||||
Weights: &structs.Weights{Passing: 16, Warning: 0},
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
Proxy: structs.ConnectProxyConfig{
|
||||
DestinationServiceName: "web",
|
||||
DestinationServiceID: "web",
|
||||
LocalServiceAddress: "127.0.0.1",
|
||||
LocalServicePort: 1234,
|
||||
Upstreams: structs.Upstreams{
|
||||
{
|
||||
DestinationType: structs.UpstreamDestTypeService,
|
||||
DestinationName: "db",
|
||||
DestinationNamespace: "default",
|
||||
LocalBindAddress: "127.0.0.1",
|
||||
LocalBindPort: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
Connect: structs.ServiceConnect{
|
||||
Proxy: &structs.ServiceDefinitionConnectProxy{
|
||||
ExecMode: "script",
|
||||
Upstreams: structs.Upstreams{
|
||||
{
|
||||
DestinationType: structs.UpstreamDestTypeService,
|
||||
DestinationName: "db",
|
||||
DestinationNamespace: "default",
|
||||
LocalBindAddress: "127.0.0.1",
|
||||
LocalBindPort: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if got, want := a.State.Service("test"), svc; !verify.Values(t, "", got, want) {
|
||||
t.Fail()
|
||||
}
|
||||
got := a.State.Service("test")
|
||||
require.Equal(t, svc, got)
|
||||
}
|
||||
|
||||
func TestAgent_RegisterService_ACLDeny(t *testing.T) {
|
||||
|
@ -1500,6 +1584,8 @@ func TestAgent_RegisterService_ManagedConnectProxy(t *testing.T) {
|
|||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
// Includes an upstream with missing defaulted type
|
||||
Upstreams: structs.TestUpstreams(t).ToAPI(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1520,7 +1606,7 @@ func TestAgent_RegisterService_ManagedConnectProxy(t *testing.T) {
|
|||
proxySvc, ok := a.State.Services()["web-proxy"]
|
||||
require.True(ok, "has proxy service")
|
||||
assert.Equal(structs.ServiceKindConnectProxy, proxySvc.Kind)
|
||||
assert.Equal("web", proxySvc.ProxyDestination)
|
||||
assert.Equal("web", proxySvc.Proxy.DestinationServiceName)
|
||||
assert.NotEmpty(proxySvc.Port, "a port should have been assigned")
|
||||
|
||||
// Ensure proxy itself was registered
|
||||
|
@ -1529,6 +1615,108 @@ func TestAgent_RegisterService_ManagedConnectProxy(t *testing.T) {
|
|||
assert.Equal(structs.ProxyExecModeScript, proxy.Proxy.ExecMode)
|
||||
assert.Equal([]string{"proxy.sh"}, proxy.Proxy.Command)
|
||||
assert.Equal(args.Connect.Proxy.Config, proxy.Proxy.Config)
|
||||
// Unsure the defaulted type is explicitly filled
|
||||
args.Connect.Proxy.Upstreams[0].DestinationType = api.UpstreamDestTypeService
|
||||
assert.Equal(args.Connect.Proxy.Upstreams,
|
||||
proxy.Proxy.Upstreams.ToAPI())
|
||||
|
||||
// Ensure the token was configured
|
||||
assert.Equal("abc123", a.State.ServiceToken("web"))
|
||||
assert.Equal("abc123", a.State.ServiceToken("web-proxy"))
|
||||
}
|
||||
|
||||
// This tests local agent service registration with a managed proxy using
|
||||
// original deprecated upstreams syntax.
|
||||
func TestAgent_RegisterService_ManagedConnectProxyDeprecated(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
a := NewTestAgent(t.Name(), `
|
||||
connect {
|
||||
proxy {
|
||||
allow_managed_api_registration = true
|
||||
}
|
||||
}
|
||||
`)
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
||||
// Register a proxy. Note that the destination doesn't exist here on
|
||||
// this agent or in the catalog at all. This is intended and part
|
||||
// of the design.
|
||||
args := &api.AgentServiceRegistration{
|
||||
Name: "web",
|
||||
Port: 8000,
|
||||
Connect: &api.AgentServiceConnect{
|
||||
Proxy: &api.AgentServiceConnectProxy{
|
||||
ExecMode: "script",
|
||||
Command: []string{"proxy.sh"},
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"upstreams": []interface{}{
|
||||
map[string]interface{}{
|
||||
"destination_name": "db",
|
||||
"local_bind_port": 1234,
|
||||
// this was a field for old upstreams we don't support any more.
|
||||
// It should be copied into Upstreams' Config.
|
||||
"connect_timeout_ms": 1000,
|
||||
},
|
||||
map[string]interface{}{
|
||||
"destination_name": "geo-cache",
|
||||
"destination_type": "prepared_query",
|
||||
"local_bind_port": 1235,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("PUT", "/v1/agent/service/register?token=abc123", jsonReader(args))
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.AgentRegisterService(resp, req)
|
||||
assert.NoError(err)
|
||||
assert.Nil(obj)
|
||||
require.Equal(200, resp.Code, "request failed with body: %s",
|
||||
resp.Body.String())
|
||||
|
||||
// Ensure the target service
|
||||
_, ok := a.State.Services()["web"]
|
||||
assert.True(ok, "has service")
|
||||
|
||||
// Ensure the proxy service was registered
|
||||
proxySvc, ok := a.State.Services()["web-proxy"]
|
||||
require.True(ok, "has proxy service")
|
||||
assert.Equal(structs.ServiceKindConnectProxy, proxySvc.Kind)
|
||||
assert.Equal("web", proxySvc.Proxy.DestinationServiceName)
|
||||
assert.NotEmpty(proxySvc.Port, "a port should have been assigned")
|
||||
|
||||
// Ensure proxy itself was registered
|
||||
proxy := a.State.Proxy("web-proxy")
|
||||
require.NotNil(proxy)
|
||||
assert.Equal(structs.ProxyExecModeScript, proxy.Proxy.ExecMode)
|
||||
assert.Equal([]string{"proxy.sh"}, proxy.Proxy.Command)
|
||||
// Remove the upstreams from the args - we expect them not to show up in
|
||||
// response now since that moved.
|
||||
delete(args.Connect.Proxy.Config, "upstreams")
|
||||
assert.Equal(args.Connect.Proxy.Config, proxy.Proxy.Config)
|
||||
expectUpstreams := structs.Upstreams{
|
||||
{
|
||||
DestinationType: structs.UpstreamDestTypeService,
|
||||
DestinationName: "db",
|
||||
LocalBindPort: 1234,
|
||||
Config: map[string]interface{}{
|
||||
"connect_timeout_ms": float64(1000),
|
||||
},
|
||||
},
|
||||
{
|
||||
DestinationType: structs.UpstreamDestTypePreparedQuery,
|
||||
DestinationName: "geo-cache",
|
||||
LocalBindPort: 1235,
|
||||
},
|
||||
}
|
||||
assert.Equal(expectUpstreams, proxy.Proxy.Upstreams)
|
||||
|
||||
// Ensure the token was configured
|
||||
assert.Equal("abc123", a.State.ServiceToken("web"))
|
||||
|
@ -1584,30 +1772,44 @@ func TestAgent_RegisterService_UnmanagedConnectProxy(t *testing.T) {
|
|||
defer a.Shutdown()
|
||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||
|
||||
// Register a proxy. Note that the destination doesn't exist here on
|
||||
// this agent or in the catalog at all. This is intended and part
|
||||
// of the design.
|
||||
args := &structs.ServiceDefinition{
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
Name: "connect-proxy",
|
||||
Port: 8000,
|
||||
ProxyDestination: "db",
|
||||
Check: structs.CheckType{
|
||||
TTL: 15 * time.Second,
|
||||
// Register a proxy. Note that the destination doesn't exist here on this
|
||||
// agent or in the catalog at all. This is intended and part of the design.
|
||||
args := &api.AgentServiceRegistration{
|
||||
Kind: api.ServiceKindConnectProxy,
|
||||
Name: "connect-proxy",
|
||||
Port: 8000,
|
||||
// DEPRECATED (ProxyDestination) - remove this when removing ProxyDestination
|
||||
ProxyDestination: "bad_destination", // Deprecated, check it's overridden
|
||||
Proxy: &api.AgentServiceConnectProxyConfig{
|
||||
DestinationServiceName: "web",
|
||||
Upstreams: []api.Upstream{
|
||||
{
|
||||
// No type to force default
|
||||
DestinationName: "db",
|
||||
LocalBindPort: 1234,
|
||||
},
|
||||
{
|
||||
DestinationType: "prepared_query",
|
||||
DestinationName: "geo-cache",
|
||||
LocalBindPort: 1235,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest("PUT", "/v1/agent/service/register?token=abc123", jsonReader(args))
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.AgentRegisterService(resp, req)
|
||||
assert.Nil(err)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(obj)
|
||||
|
||||
// Ensure the service
|
||||
svc, ok := a.State.Services()["connect-proxy"]
|
||||
assert.True(ok, "has service")
|
||||
assert.Equal(structs.ServiceKindConnectProxy, svc.Kind)
|
||||
assert.Equal("db", svc.ProxyDestination)
|
||||
// Registration must set that default type
|
||||
args.Proxy.Upstreams[0].DestinationType = api.UpstreamDestTypeService
|
||||
assert.Equal(args.Proxy, svc.Proxy.ToAPI())
|
||||
|
||||
// Ensure the token was configured
|
||||
assert.Equal("abc123", a.State.ServiceToken("connect-proxy"))
|
||||
|
@ -1625,9 +1827,11 @@ func TestAgent_RegisterService_UnmanagedConnectProxyInvalid(t *testing.T) {
|
|||
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||
|
||||
args := &structs.ServiceDefinition{
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
Name: "connect-proxy",
|
||||
ProxyDestination: "db",
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
Name: "connect-proxy",
|
||||
Proxy: &structs.ConnectProxyConfig{
|
||||
DestinationServiceName: "db",
|
||||
},
|
||||
Check: structs.CheckType{
|
||||
TTL: 15 * time.Second,
|
||||
},
|
||||
|
@ -2982,10 +3186,12 @@ func TestAgentConnectProxyConfig_Blocking(t *testing.T) {
|
|||
Config: map[string]interface{}{
|
||||
"bind_port": 1234,
|
||||
"connect_timeout_ms": 500,
|
||||
// Specify upstreams in deprecated nested config way here. We test the
|
||||
// new way in the update case below.
|
||||
"upstreams": []map[string]interface{}{
|
||||
{
|
||||
"destination_name": "db",
|
||||
"local_port": 3131,
|
||||
"local_bind_port": 3131,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2997,34 +3203,36 @@ func TestAgentConnectProxyConfig_Blocking(t *testing.T) {
|
|||
ProxyServiceID: "test-proxy",
|
||||
TargetServiceID: "test",
|
||||
TargetServiceName: "test",
|
||||
ContentHash: "4662e51e78609569",
|
||||
ContentHash: "a7c93585b6d70445",
|
||||
ExecMode: "daemon",
|
||||
Command: []string{"tubes.sh"},
|
||||
Config: map[string]interface{}{
|
||||
"upstreams": []interface{}{
|
||||
map[string]interface{}{
|
||||
"destination_name": "db",
|
||||
"local_port": float64(3131),
|
||||
},
|
||||
},
|
||||
"bind_address": "127.0.0.1",
|
||||
"local_service_address": "127.0.0.1:8000",
|
||||
"bind_port": int(1234),
|
||||
"connect_timeout_ms": float64(500),
|
||||
},
|
||||
Upstreams: []api.Upstream{
|
||||
{
|
||||
DestinationType: "service",
|
||||
DestinationName: "db",
|
||||
LocalBindPort: 3131,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ur, err := copystructure.Copy(expectedResponse)
|
||||
require.NoError(t, err)
|
||||
updatedResponse := ur.(*api.ConnectProxyConfig)
|
||||
updatedResponse.ContentHash = "23b5b6b3767601e1"
|
||||
upstreams := updatedResponse.Config["upstreams"].([]interface{})
|
||||
upstreams = append(upstreams,
|
||||
map[string]interface{}{
|
||||
"destination_name": "cache",
|
||||
"local_port": float64(4242),
|
||||
})
|
||||
updatedResponse.Config["upstreams"] = upstreams
|
||||
updatedResponse.ContentHash = "aedc0ca0f3f7794e"
|
||||
updatedResponse.Upstreams = append(updatedResponse.Upstreams, api.Upstream{
|
||||
DestinationType: "service",
|
||||
DestinationName: "cache",
|
||||
LocalBindPort: 4242,
|
||||
Config: map[string]interface{}{
|
||||
"connect_timeout_ms": float64(1000),
|
||||
},
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -3066,7 +3274,7 @@ func TestAgentConnectProxyConfig_Blocking(t *testing.T) {
|
|||
r2, err := copystructure.Copy(reg)
|
||||
require.NoError(t, err)
|
||||
reg2 := r2.(*structs.ServiceDefinition)
|
||||
reg2.Connect.Proxy.Config = updatedResponse.Config
|
||||
reg2.Connect.Proxy.Upstreams = structs.UpstreamsFromAPI(updatedResponse.Upstreams)
|
||||
req, _ := http.NewRequest("PUT", "/v1/agent/service/register", jsonReader(r2))
|
||||
resp := httptest.NewRecorder()
|
||||
_, err = a.srv.AgentRegisterService(resp, req)
|
||||
|
@ -3385,7 +3593,7 @@ func TestAgentConnectProxyConfig_ConfigHandling(t *testing.T) {
|
|||
TTL: 15 * time.Second,
|
||||
},
|
||||
Connect: &structs.ServiceConnect{
|
||||
// Proxy is populated with the definition in the table below.
|
||||
// Proxy is populated with the definition in the table below.
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -273,7 +273,7 @@ func TestCatalogNodes_Blocking(t *testing.T) {
|
|||
// an error channel instead.
|
||||
errch := make(chan error, 2)
|
||||
go func() {
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||
start := time.Now()
|
||||
|
||||
// register a service after the blocking call
|
||||
|
@ -846,6 +846,9 @@ func TestCatalogServiceNodes_ConnectProxy(t *testing.T) {
|
|||
nodes := obj.(structs.ServiceNodes)
|
||||
assert.Len(nodes, 1)
|
||||
assert.Equal(structs.ServiceKindConnectProxy, nodes[0].ServiceKind)
|
||||
assert.Equal(args.Service.Proxy, nodes[0].ServiceProxy)
|
||||
// DEPRECATED (ProxyDestination) - remove this when removing ProxyDestination
|
||||
assert.Equal(args.Service.Proxy.DestinationServiceName, nodes[0].ServiceProxyDestination)
|
||||
}
|
||||
|
||||
// Test that the Connect-compatible endpoints can be queried for a
|
||||
|
@ -864,7 +867,7 @@ func TestCatalogConnectServiceNodes_good(t *testing.T) {
|
|||
assert.Nil(a.RPC("Catalog.Register", args, &out))
|
||||
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf(
|
||||
"/v1/catalog/connect/%s", args.Service.ProxyDestination), nil)
|
||||
"/v1/catalog/connect/%s", args.Service.Proxy.DestinationServiceName), nil)
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.CatalogConnectServiceNodes(resp, req)
|
||||
assert.Nil(err)
|
||||
|
@ -874,6 +877,7 @@ func TestCatalogConnectServiceNodes_good(t *testing.T) {
|
|||
assert.Len(nodes, 1)
|
||||
assert.Equal(structs.ServiceKindConnectProxy, nodes[0].ServiceKind)
|
||||
assert.Equal(args.Service.Address, nodes[0].ServiceAddress)
|
||||
assert.Equal(args.Service.Proxy, nodes[0].ServiceProxy)
|
||||
}
|
||||
|
||||
func TestCatalogNodeServices(t *testing.T) {
|
||||
|
@ -882,7 +886,7 @@ func TestCatalogNodeServices(t *testing.T) {
|
|||
defer a.Shutdown()
|
||||
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||
|
||||
// Register node
|
||||
// Register node with a regular service and connect proxy
|
||||
args := &structs.RegisterRequest{
|
||||
Datacenter: "dc1",
|
||||
Node: "foo",
|
||||
|
@ -898,6 +902,10 @@ func TestCatalogNodeServices(t *testing.T) {
|
|||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Register a connect proxy
|
||||
args.Service = structs.TestNodeServiceProxy(t)
|
||||
require.NoError(t, a.RPC("Catalog.Register", args, &out))
|
||||
|
||||
req, _ := http.NewRequest("GET", "/v1/catalog/node/foo?dc=dc1", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.CatalogNodeServices(resp, req)
|
||||
|
@ -907,9 +915,12 @@ func TestCatalogNodeServices(t *testing.T) {
|
|||
assertIndex(t, resp)
|
||||
|
||||
services := obj.(*structs.NodeServices)
|
||||
if len(services.Services) != 1 {
|
||||
if len(services.Services) != 2 {
|
||||
t.Fatalf("bad: %v", obj)
|
||||
}
|
||||
|
||||
// Proxy service should have it's config intact
|
||||
require.Equal(t, args.Service.Proxy, services.Services["web-proxy"].Proxy)
|
||||
}
|
||||
|
||||
// Test that the services on a node contain all the Connect proxies on
|
||||
|
|
|
@ -949,7 +949,7 @@ func (b *Builder) Validate(rt RuntimeConfig) error {
|
|||
|
||||
// Validate the given Connect CA provider config
|
||||
validCAProviders := map[string]bool{
|
||||
"": true,
|
||||
"": true,
|
||||
structs.ConsulCAProvider: true,
|
||||
structs.VaultCAProvider: true,
|
||||
}
|
||||
|
@ -1042,27 +1042,27 @@ func (b *Builder) checkVal(v *CheckDefinition) *structs.CheckDefinition {
|
|||
id := types.CheckID(b.stringVal(v.ID))
|
||||
|
||||
return &structs.CheckDefinition{
|
||||
ID: id,
|
||||
Name: b.stringVal(v.Name),
|
||||
Notes: b.stringVal(v.Notes),
|
||||
ServiceID: b.stringVal(v.ServiceID),
|
||||
Token: b.stringVal(v.Token),
|
||||
Status: b.stringVal(v.Status),
|
||||
ScriptArgs: v.ScriptArgs,
|
||||
HTTP: b.stringVal(v.HTTP),
|
||||
Header: v.Header,
|
||||
Method: b.stringVal(v.Method),
|
||||
TCP: b.stringVal(v.TCP),
|
||||
Interval: b.durationVal(fmt.Sprintf("check[%s].interval", id), v.Interval),
|
||||
DockerContainerID: b.stringVal(v.DockerContainerID),
|
||||
Shell: b.stringVal(v.Shell),
|
||||
GRPC: b.stringVal(v.GRPC),
|
||||
GRPCUseTLS: b.boolVal(v.GRPCUseTLS),
|
||||
TLSSkipVerify: b.boolVal(v.TLSSkipVerify),
|
||||
AliasNode: b.stringVal(v.AliasNode),
|
||||
AliasService: b.stringVal(v.AliasService),
|
||||
Timeout: b.durationVal(fmt.Sprintf("check[%s].timeout", id), v.Timeout),
|
||||
TTL: b.durationVal(fmt.Sprintf("check[%s].ttl", id), v.TTL),
|
||||
ID: id,
|
||||
Name: b.stringVal(v.Name),
|
||||
Notes: b.stringVal(v.Notes),
|
||||
ServiceID: b.stringVal(v.ServiceID),
|
||||
Token: b.stringVal(v.Token),
|
||||
Status: b.stringVal(v.Status),
|
||||
ScriptArgs: v.ScriptArgs,
|
||||
HTTP: b.stringVal(v.HTTP),
|
||||
Header: v.Header,
|
||||
Method: b.stringVal(v.Method),
|
||||
TCP: b.stringVal(v.TCP),
|
||||
Interval: b.durationVal(fmt.Sprintf("check[%s].interval", id), v.Interval),
|
||||
DockerContainerID: b.stringVal(v.DockerContainerID),
|
||||
Shell: b.stringVal(v.Shell),
|
||||
GRPC: b.stringVal(v.GRPC),
|
||||
GRPCUseTLS: b.boolVal(v.GRPCUseTLS),
|
||||
TLSSkipVerify: b.boolVal(v.TLSSkipVerify),
|
||||
AliasNode: b.stringVal(v.AliasNode),
|
||||
AliasService: b.stringVal(v.AliasService),
|
||||
Timeout: b.durationVal(fmt.Sprintf("check[%s].timeout", id), v.Timeout),
|
||||
TTL: b.durationVal(fmt.Sprintf("check[%s].ttl", id), v.TTL),
|
||||
DeregisterCriticalServiceAfter: b.durationVal(fmt.Sprintf("check[%s].deregister_critical_service_after", id), v.DeregisterCriticalServiceAfter),
|
||||
}
|
||||
}
|
||||
|
@ -1111,8 +1111,10 @@ func (b *Builder) serviceVal(v *ServiceDefinition) *structs.ServiceDefinition {
|
|||
EnableTagOverride: b.boolVal(v.EnableTagOverride),
|
||||
Weights: serviceWeights,
|
||||
Checks: checks,
|
||||
ProxyDestination: b.stringVal(v.ProxyDestination),
|
||||
Connect: b.serviceConnectVal(v.Connect),
|
||||
// DEPRECATED (ProxyDestination) - don't populate deprecated field, just use
|
||||
// it as a default below on read. Remove that when remofing ProxyDestination
|
||||
Proxy: b.serviceProxyVal(v.Proxy, v.ProxyDestination),
|
||||
Connect: b.serviceConnectVal(v.Connect),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1128,6 +1130,45 @@ func (b *Builder) serviceKindVal(v *string) structs.ServiceKind {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *Builder) serviceProxyVal(v *ServiceProxy, deprecatedDest *string) *structs.ConnectProxyConfig {
|
||||
if v == nil {
|
||||
if deprecatedDest != nil {
|
||||
return &structs.ConnectProxyConfig{
|
||||
DestinationServiceName: b.stringVal(deprecatedDest),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return &structs.ConnectProxyConfig{
|
||||
DestinationServiceName: b.stringVal(v.DestinationServiceName),
|
||||
DestinationServiceID: b.stringVal(v.DestinationServiceID),
|
||||
LocalServiceAddress: b.stringVal(v.LocalServiceAddress),
|
||||
LocalServicePort: b.intVal(v.LocalServicePort),
|
||||
Config: v.Config,
|
||||
Upstreams: b.upstreamsVal(v.Upstreams),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) upstreamsVal(v []Upstream) structs.Upstreams {
|
||||
ups := make(structs.Upstreams, len(v))
|
||||
for i, u := range v {
|
||||
ups[i] = structs.Upstream{
|
||||
DestinationType: b.stringVal(u.DestinationType),
|
||||
DestinationNamespace: b.stringVal(u.DestinationNamespace),
|
||||
DestinationName: b.stringVal(u.DestinationName),
|
||||
Datacenter: b.stringVal(u.Datacenter),
|
||||
LocalBindAddress: b.stringVal(u.LocalBindAddress),
|
||||
LocalBindPort: b.intVal(u.LocalBindPort),
|
||||
Config: u.Config,
|
||||
}
|
||||
if ups[i].DestinationType == "" {
|
||||
ups[i].DestinationType = structs.UpstreamDestTypeService
|
||||
}
|
||||
}
|
||||
return ups
|
||||
}
|
||||
|
||||
func (b *Builder) serviceConnectVal(v *ServiceConnect) *structs.ServiceConnect {
|
||||
if v == nil {
|
||||
return nil
|
||||
|
@ -1136,9 +1177,10 @@ func (b *Builder) serviceConnectVal(v *ServiceConnect) *structs.ServiceConnect {
|
|||
var proxy *structs.ServiceDefinitionConnectProxy
|
||||
if v.Proxy != nil {
|
||||
proxy = &structs.ServiceDefinitionConnectProxy{
|
||||
ExecMode: b.stringVal(v.Proxy.ExecMode),
|
||||
Command: v.Proxy.Command,
|
||||
Config: v.Proxy.Config,
|
||||
ExecMode: b.stringVal(v.Proxy.ExecMode),
|
||||
Command: v.Proxy.Command,
|
||||
Config: v.Proxy.Config,
|
||||
Upstreams: b.upstreamsVal(v.Proxy.Upstreams),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -84,8 +84,12 @@ func Parse(data string, format string) (c Config, err error) {
|
|||
"services",
|
||||
"services.checks",
|
||||
"watches",
|
||||
"service.connect.proxy.config.upstreams",
|
||||
"services.connect.proxy.config.upstreams",
|
||||
"service.connect.proxy.config.upstreams", // Deprecated
|
||||
"services.connect.proxy.config.upstreams", // Deprecated
|
||||
"service.connect.proxy.upstreams",
|
||||
"services.connect.proxy.upstreams",
|
||||
"service.proxy.upstreams",
|
||||
"services.proxy.upstreams",
|
||||
})
|
||||
|
||||
// There is a difference of representation of some fields depending on
|
||||
|
@ -341,8 +345,10 @@ type ServiceDefinition struct {
|
|||
Token *string `json:"token,omitempty" hcl:"token" mapstructure:"token"`
|
||||
Weights *ServiceWeights `json:"weights,omitempty" hcl:"weights" mapstructure:"weights"`
|
||||
EnableTagOverride *bool `json:"enable_tag_override,omitempty" hcl:"enable_tag_override" mapstructure:"enable_tag_override"`
|
||||
ProxyDestination *string `json:"proxy_destination,omitempty" hcl:"proxy_destination" mapstructure:"proxy_destination"`
|
||||
Connect *ServiceConnect `json:"connect,omitempty" hcl:"connect" mapstructure:"connect"`
|
||||
// DEPRECATED (ProxyDestination) - remove this when removing ProxyDestination
|
||||
ProxyDestination *string `json:"proxy_destination,omitempty" hcl:"proxy_destination" mapstructure:"proxy_destination"`
|
||||
Proxy *ServiceProxy `json:"proxy,omitempty" hcl:"proxy" mapstructure:"proxy"`
|
||||
Connect *ServiceConnect `json:"connect,omitempty" hcl:"connect" mapstructure:"connect"`
|
||||
}
|
||||
|
||||
type CheckDefinition struct {
|
||||
|
@ -380,9 +386,81 @@ type ServiceConnect struct {
|
|||
}
|
||||
|
||||
type ServiceConnectProxy struct {
|
||||
Command []string `json:"command,omitempty" hcl:"command" mapstructure:"command"`
|
||||
ExecMode *string `json:"exec_mode,omitempty" hcl:"exec_mode" mapstructure:"exec_mode"`
|
||||
Config map[string]interface{} `json:"config,omitempty" hcl:"config" mapstructure:"config"`
|
||||
Command []string `json:"command,omitempty" hcl:"command" mapstructure:"command"`
|
||||
ExecMode *string `json:"exec_mode,omitempty" hcl:"exec_mode" mapstructure:"exec_mode"`
|
||||
Config map[string]interface{} `json:"config,omitempty" hcl:"config" mapstructure:"config"`
|
||||
Upstreams []Upstream `json:"upstreams,omitempty" hcl:"upstreams" mapstructure:"upstreams"`
|
||||
}
|
||||
|
||||
// ServiceProxy is the additional config needed for a Kind = connect-proxy
|
||||
// registration.
|
||||
type ServiceProxy struct {
|
||||
// DestinationServiceName is required and is the name of the service to accept
|
||||
// traffic for.
|
||||
DestinationServiceName *string `json:"destination_service_name,omitempty" hcl:"destination_service_name" mapstructure:"destination_service_name"`
|
||||
|
||||
// DestinationServiceID is optional and should only be specified for
|
||||
// "side-car" style proxies where the proxy is in front of just a single
|
||||
// instance of the service. It should be set to the service ID of the instance
|
||||
// being represented which must be registered to the same agent. It's valid to
|
||||
// provide a service ID that does not yet exist to avoid timing issues when
|
||||
// bootstrapping a service with a proxy.
|
||||
DestinationServiceID *string `json:"destination_service_id,omitempty" hcl:"destination_service_id" mapstructure:"destination_service_id"`
|
||||
|
||||
// LocalServiceAddress is the address of the local service instance. It is
|
||||
// optional and should only be specified for "side-car" style proxies. It will
|
||||
// default to 127.0.0.1 if the proxy is a "side-car" (DestinationServiceID is
|
||||
// set) but otherwise will be ignored.
|
||||
LocalServiceAddress *string `json:"local_service_address,omitempty" hcl:"local_service_address" mapstructure:"local_service_address"`
|
||||
|
||||
// LocalServicePort is the port of the local service instance. It is optional
|
||||
// and should only be specified for "side-car" style proxies. It will default
|
||||
// to the registered port for the instance if the proxy is a "side-car"
|
||||
// (DestinationServiceID is set) but otherwise will be ignored.
|
||||
LocalServicePort *int `json:"local_service_port,omitempty" hcl:"local_service_port" mapstructure:"local_service_port"`
|
||||
|
||||
// Config is the arbitrary configuration data provided with the proxy
|
||||
// registration.
|
||||
Config map[string]interface{} `json:"config,omitempty" hcl:"config" mapstructure:"config"`
|
||||
|
||||
// Upstreams describes any upstream dependencies the proxy instance should
|
||||
// setup.
|
||||
Upstreams []Upstream `json:"upstreams,omitempty" hcl:"upstreams" mapstructure:"upstreams"`
|
||||
}
|
||||
|
||||
// Upstream represents a single upstream dependency for a service or proxy. It
|
||||
// describes the mechanism used to discover instances to communicate with (the
|
||||
// Target) as well as any potential client configuration that may be useful such
|
||||
// as load balancer options, timeouts etc.
|
||||
type Upstream struct {
|
||||
// Destination fields are the required ones for determining what this upstream
|
||||
// points to. Depending on DestinationType some other fields below might
|
||||
// further restrict the set of instances allowable.
|
||||
//
|
||||
// DestinationType would be better as an int constant but even with custom
|
||||
// JSON marshallers it causes havoc with all the mapstructure mangling we do
|
||||
// on service definitions in various places.
|
||||
DestinationType *string `json:"destination_type,omitempty" hcl:"destination_type" mapstructure:"destination_type"`
|
||||
DestinationNamespace *string `json:"destination_namespace,omitempty" hcl:"destination_namespace" mapstructure:"destination_namespace"`
|
||||
DestinationName *string `json:"destination_name,omitempty" hcl:"destination_name" mapstructure:"destination_name"`
|
||||
|
||||
// Datacenter that the service discovery request should be run against. Note
|
||||
// for prepared queries, the actual results might be from a different
|
||||
// datacenter.
|
||||
Datacenter *string `json:"datacenter,omitempty" hcl:"datacenter" mapstructure:"datacenter"`
|
||||
|
||||
// LocalBindAddress is the ip address a side-car proxy should listen on for
|
||||
// traffic destined for this upstream service. Default if empty is 127.0.0.1.
|
||||
LocalBindAddress *string `json:"local_bind_address,omitempty" hcl:"local_bind_address" mapstructure:"local_bind_address"`
|
||||
|
||||
// LocalBindPort is the ip address a side-car proxy should listen on for traffic
|
||||
// destined for this upstream service. Required.
|
||||
LocalBindPort *int `json:"local_bind_port,omitempty" hcl:"local_bind_port" mapstructure:"local_bind_port"`
|
||||
|
||||
// Config is an opaque config that is specific to the proxy process being run.
|
||||
// It can be used to pass abritrary configuration for this specific upstream
|
||||
// to the proxy.
|
||||
Config map[string]interface{} `json:"config,omitempty" hcl:"config" mapstructure:"config"`
|
||||
}
|
||||
|
||||
// Connect is the agent-global connect configuration.
|
||||
|
|
|
@ -2140,53 +2140,7 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
|
|||
},
|
||||
|
||||
{
|
||||
desc: "HCL service managed proxy 'upstreams'",
|
||||
args: []string{
|
||||
`-data-dir=` + dataDir,
|
||||
},
|
||||
hcl: []string{
|
||||
`service {
|
||||
name = "web"
|
||||
port = 8080
|
||||
connect {
|
||||
proxy {
|
||||
config {
|
||||
upstreams {
|
||||
local_bind_port = 1234
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
skipformat: true, // skipping JSON cause we get slightly diff types (okay)
|
||||
patch: func(rt *RuntimeConfig) {
|
||||
rt.DataDir = dataDir
|
||||
rt.Services = []*structs.ServiceDefinition{
|
||||
&structs.ServiceDefinition{
|
||||
Name: "web",
|
||||
Port: 8080,
|
||||
Connect: &structs.ServiceConnect{
|
||||
Proxy: &structs.ServiceDefinitionConnectProxy{
|
||||
Config: map[string]interface{}{
|
||||
"upstreams": []map[string]interface{}{
|
||||
map[string]interface{}{
|
||||
"local_bind_port": 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Weights: &structs.Weights{
|
||||
Passing: 1,
|
||||
Warning: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "JSON service managed proxy 'upstreams'",
|
||||
desc: "Service managed proxy 'upstreams'",
|
||||
args: []string{
|
||||
`-data-dir=` + dataDir,
|
||||
},
|
||||
|
@ -2197,17 +2151,29 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
|
|||
"port": 8080,
|
||||
"connect": {
|
||||
"proxy": {
|
||||
"config": {
|
||||
"upstreams": [{
|
||||
"local_bind_port": 1234
|
||||
}]
|
||||
}
|
||||
"upstreams": [{
|
||||
"destination_name": "db",
|
||||
"local_bind_port": 1234
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
skipformat: true, // skipping HCL cause we get slightly diff types (okay)
|
||||
hcl: []string{
|
||||
`service {
|
||||
name = "web"
|
||||
port = 8080
|
||||
connect {
|
||||
proxy {
|
||||
upstreams {
|
||||
destination_name = "db"
|
||||
local_bind_port = 1234
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
patch: func(rt *RuntimeConfig) {
|
||||
rt.DataDir = dataDir
|
||||
rt.Services = []*structs.ServiceDefinition{
|
||||
|
@ -2216,11 +2182,11 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
|
|||
Port: 8080,
|
||||
Connect: &structs.ServiceConnect{
|
||||
Proxy: &structs.ServiceDefinitionConnectProxy{
|
||||
Config: map[string]interface{}{
|
||||
"upstreams": []interface{}{
|
||||
map[string]interface{}{
|
||||
"local_bind_port": float64(1234),
|
||||
},
|
||||
Upstreams: structs.Upstreams{
|
||||
{
|
||||
DestinationName: "db",
|
||||
DestinationType: structs.UpstreamDestTypeService,
|
||||
LocalBindPort: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2235,35 +2201,49 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
|
|||
},
|
||||
|
||||
{
|
||||
desc: "JSON multiple services managed proxy 'upstreams'",
|
||||
desc: "Multiple service managed proxy 'upstreams'",
|
||||
args: []string{
|
||||
`-data-dir=` + dataDir,
|
||||
},
|
||||
json: []string{
|
||||
`{
|
||||
"services": [{
|
||||
"service": {
|
||||
"name": "web",
|
||||
"port": 8080,
|
||||
"connect": {
|
||||
"proxy": {
|
||||
"config": {
|
||||
"upstreams": [{
|
||||
"local_bind_port": 1234
|
||||
}, {
|
||||
"local_bind_port": 2345
|
||||
}]
|
||||
}
|
||||
"upstreams": [{
|
||||
"destination_name": "db",
|
||||
"local_bind_port": 1234
|
||||
}, {
|
||||
"destination_name": "cache",
|
||||
"local_bind_port": 2345
|
||||
}]
|
||||
}
|
||||
}
|
||||
},{
|
||||
"name": "service-A2",
|
||||
"port": 81,
|
||||
"tags": [],
|
||||
"checks": []
|
||||
}]
|
||||
}
|
||||
}`,
|
||||
},
|
||||
skipformat: true, // skipping HCL cause we get slightly diff types (okay)
|
||||
hcl: []string{
|
||||
`service {
|
||||
name = "web"
|
||||
port = 8080
|
||||
connect {
|
||||
proxy {
|
||||
upstreams = [
|
||||
{
|
||||
destination_name = "db"
|
||||
local_bind_port = 1234
|
||||
},
|
||||
{
|
||||
destination_name = "cache"
|
||||
local_bind_port = 2345
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
patch: func(rt *RuntimeConfig) {
|
||||
rt.DataDir = dataDir
|
||||
rt.Services = []*structs.ServiceDefinition{
|
||||
|
@ -2272,14 +2252,16 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
|
|||
Port: 8080,
|
||||
Connect: &structs.ServiceConnect{
|
||||
Proxy: &structs.ServiceDefinitionConnectProxy{
|
||||
Config: map[string]interface{}{
|
||||
"upstreams": []interface{}{
|
||||
map[string]interface{}{
|
||||
"local_bind_port": float64(1234),
|
||||
},
|
||||
map[string]interface{}{
|
||||
"local_bind_port": float64(2345),
|
||||
},
|
||||
Upstreams: structs.Upstreams{
|
||||
{
|
||||
DestinationName: "db",
|
||||
DestinationType: structs.UpstreamDestTypeService,
|
||||
LocalBindPort: 1234,
|
||||
},
|
||||
{
|
||||
DestinationName: "cache",
|
||||
DestinationType: structs.UpstreamDestTypeService,
|
||||
LocalBindPort: 2345,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2289,14 +2271,6 @@ func TestConfigFlagsAndEdgecases(t *testing.T) {
|
|||
Warning: 1,
|
||||
},
|
||||
},
|
||||
&structs.ServiceDefinition{
|
||||
Name: "service-A2",
|
||||
Port: 81,
|
||||
Weights: &structs.Weights{
|
||||
Passing: 1,
|
||||
Warning: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -2946,10 +2920,34 @@ func TestFullConfig(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"id": "Kh81CPF6",
|
||||
"kind": "connect-proxy",
|
||||
"name": "Kh81CPF6-proxy",
|
||||
"port": 31471,
|
||||
"kind": "connect-proxy",
|
||||
"proxy_destination": "6L6BVfgH"
|
||||
"proxy": {
|
||||
"config": {
|
||||
"cedGGtZf": "pWrUNiWw"
|
||||
},
|
||||
"destination_service_id": "6L6BVfgH-id",
|
||||
"destination_service_name": "6L6BVfgH",
|
||||
"local_service_address": "127.0.0.2",
|
||||
"local_service_port": 23759,
|
||||
"upstreams": [
|
||||
{
|
||||
"destination_name": "KPtAj2cb",
|
||||
"local_bind_port": 4051,
|
||||
"config": {
|
||||
"kzRnZOyd": "nUNKoL8H"
|
||||
}
|
||||
},
|
||||
{
|
||||
"destination_name": "KSd8HsRl",
|
||||
"destination_namespace": "9nakw0td",
|
||||
"destination_type": "prepared_query",
|
||||
"local_bind_address": "127.24.88.0",
|
||||
"local_bind_port": 11884
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"session_ttl_min": "26627s",
|
||||
|
@ -3448,7 +3446,31 @@ func TestFullConfig(t *testing.T) {
|
|||
name = "Kh81CPF6-proxy"
|
||||
port = 31471
|
||||
kind = "connect-proxy"
|
||||
proxy_destination = "6L6BVfgH"
|
||||
proxy {
|
||||
destination_service_name = "6L6BVfgH"
|
||||
destination_service_id = "6L6BVfgH-id"
|
||||
local_service_address = "127.0.0.2"
|
||||
local_service_port = 23759
|
||||
config {
|
||||
cedGGtZf = "pWrUNiWw"
|
||||
}
|
||||
upstreams = [
|
||||
{
|
||||
destination_name = "KPtAj2cb"
|
||||
local_bind_port = 4051
|
||||
config {
|
||||
kzRnZOyd = "nUNKoL8H"
|
||||
}
|
||||
},
|
||||
{
|
||||
destination_type = "prepared_query"
|
||||
destination_namespace = "9nakw0td"
|
||||
destination_name = "KSd8HsRl"
|
||||
local_bind_port = 11884
|
||||
local_bind_address = "127.24.88.0"
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
session_ttl_min = "26627s"
|
||||
|
@ -3672,14 +3694,14 @@ func TestFullConfig(t *testing.T) {
|
|||
"ZBfTin3L": []string{"1sDbEqYG", "lJGASsWK"},
|
||||
"Ui0nU99X": []string{"LMccm3Qe", "k5H5RggQ"},
|
||||
},
|
||||
Method: "aldrIQ4l",
|
||||
TCP: "RJQND605",
|
||||
Interval: 22164 * time.Second,
|
||||
DockerContainerID: "ipgdFtjd",
|
||||
Shell: "qAeOYy0M",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 1813 * time.Second,
|
||||
TTL: 21743 * time.Second,
|
||||
Method: "aldrIQ4l",
|
||||
TCP: "RJQND605",
|
||||
Interval: 22164 * time.Second,
|
||||
DockerContainerID: "ipgdFtjd",
|
||||
Shell: "qAeOYy0M",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 1813 * time.Second,
|
||||
TTL: 21743 * time.Second,
|
||||
DeregisterCriticalServiceAfter: 14232 * time.Second,
|
||||
},
|
||||
&structs.CheckDefinition{
|
||||
|
@ -3695,14 +3717,14 @@ func TestFullConfig(t *testing.T) {
|
|||
"zcqwA8dO": []string{"qb1zx0DL", "sXCxPFsD"},
|
||||
"qxvdnSE9": []string{"6wBPUYdF", "YYh8wtSZ"},
|
||||
},
|
||||
Method: "gLrztrNw",
|
||||
TCP: "4jG5casb",
|
||||
Interval: 28767 * time.Second,
|
||||
DockerContainerID: "THW6u7rL",
|
||||
Shell: "C1Zt3Zwh",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 18506 * time.Second,
|
||||
TTL: 31006 * time.Second,
|
||||
Method: "gLrztrNw",
|
||||
TCP: "4jG5casb",
|
||||
Interval: 28767 * time.Second,
|
||||
DockerContainerID: "THW6u7rL",
|
||||
Shell: "C1Zt3Zwh",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 18506 * time.Second,
|
||||
TTL: 31006 * time.Second,
|
||||
DeregisterCriticalServiceAfter: 2366 * time.Second,
|
||||
},
|
||||
&structs.CheckDefinition{
|
||||
|
@ -3718,14 +3740,14 @@ func TestFullConfig(t *testing.T) {
|
|||
"hBq0zn1q": {"2a9o9ZKP", "vKwA5lR6"},
|
||||
"f3r6xFtM": {"RyuIdDWv", "QbxEcIUM"},
|
||||
},
|
||||
Method: "Dou0nGT5",
|
||||
TCP: "JY6fTTcw",
|
||||
Interval: 18714 * time.Second,
|
||||
DockerContainerID: "qF66POS9",
|
||||
Shell: "sOnDy228",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 5954 * time.Second,
|
||||
TTL: 30044 * time.Second,
|
||||
Method: "Dou0nGT5",
|
||||
TCP: "JY6fTTcw",
|
||||
Interval: 18714 * time.Second,
|
||||
DockerContainerID: "qF66POS9",
|
||||
Shell: "sOnDy228",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 5954 * time.Second,
|
||||
TTL: 30044 * time.Second,
|
||||
DeregisterCriticalServiceAfter: 13209 * time.Second,
|
||||
},
|
||||
},
|
||||
|
@ -3863,14 +3885,14 @@ func TestFullConfig(t *testing.T) {
|
|||
"UkpmZ3a3": {"2dfzXuxZ"},
|
||||
"cVFpko4u": {"gGqdEB6k", "9LsRo22u"},
|
||||
},
|
||||
Method: "X5DrovFc",
|
||||
TCP: "ICbxkpSF",
|
||||
Interval: 24392 * time.Second,
|
||||
DockerContainerID: "ZKXr68Yb",
|
||||
Shell: "CEfzx0Fo",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 38333 * time.Second,
|
||||
TTL: 57201 * time.Second,
|
||||
Method: "X5DrovFc",
|
||||
TCP: "ICbxkpSF",
|
||||
Interval: 24392 * time.Second,
|
||||
DockerContainerID: "ZKXr68Yb",
|
||||
Shell: "CEfzx0Fo",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 38333 * time.Second,
|
||||
TTL: 57201 * time.Second,
|
||||
DeregisterCriticalServiceAfter: 44214 * time.Second,
|
||||
},
|
||||
},
|
||||
|
@ -3899,14 +3921,14 @@ func TestFullConfig(t *testing.T) {
|
|||
"MUlReo8L": {"AUZG7wHG", "gsN0Dc2N"},
|
||||
"1UJXjVrT": {"OJgxzTfk", "xZZrFsq7"},
|
||||
},
|
||||
Method: "5wkAxCUE",
|
||||
TCP: "MN3oA9D2",
|
||||
Interval: 32718 * time.Second,
|
||||
DockerContainerID: "cU15LMet",
|
||||
Shell: "nEz9qz2l",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 34738 * time.Second,
|
||||
TTL: 22773 * time.Second,
|
||||
Method: "5wkAxCUE",
|
||||
TCP: "MN3oA9D2",
|
||||
Interval: 32718 * time.Second,
|
||||
DockerContainerID: "cU15LMet",
|
||||
Shell: "nEz9qz2l",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 34738 * time.Second,
|
||||
TTL: 22773 * time.Second,
|
||||
DeregisterCriticalServiceAfter: 84282 * time.Second,
|
||||
},
|
||||
&structs.CheckType{
|
||||
|
@ -3920,14 +3942,14 @@ func TestFullConfig(t *testing.T) {
|
|||
"cXPmnv1M": {"imDqfaBx", "NFxZ1bQe"},
|
||||
"vr7wY7CS": {"EtCoNPPL", "9vAarJ5s"},
|
||||
},
|
||||
Method: "wzByP903",
|
||||
TCP: "2exjZIGE",
|
||||
Interval: 5656 * time.Second,
|
||||
DockerContainerID: "5tDBWpfA",
|
||||
Shell: "rlTpLM8s",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 4868 * time.Second,
|
||||
TTL: 11222 * time.Second,
|
||||
Method: "wzByP903",
|
||||
TCP: "2exjZIGE",
|
||||
Interval: 5656 * time.Second,
|
||||
DockerContainerID: "5tDBWpfA",
|
||||
Shell: "rlTpLM8s",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 4868 * time.Second,
|
||||
TTL: 11222 * time.Second,
|
||||
DeregisterCriticalServiceAfter: 68482 * time.Second,
|
||||
},
|
||||
},
|
||||
|
@ -3942,11 +3964,36 @@ func TestFullConfig(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
ID: "Kh81CPF6",
|
||||
Name: "Kh81CPF6-proxy",
|
||||
Port: 31471,
|
||||
Kind: "connect-proxy",
|
||||
ProxyDestination: "6L6BVfgH",
|
||||
ID: "Kh81CPF6",
|
||||
Name: "Kh81CPF6-proxy",
|
||||
Port: 31471,
|
||||
Kind: "connect-proxy",
|
||||
Proxy: &structs.ConnectProxyConfig{
|
||||
DestinationServiceName: "6L6BVfgH",
|
||||
DestinationServiceID: "6L6BVfgH-id",
|
||||
LocalServiceAddress: "127.0.0.2",
|
||||
LocalServicePort: 23759,
|
||||
Config: map[string]interface{}{
|
||||
"cedGGtZf": "pWrUNiWw",
|
||||
},
|
||||
Upstreams: structs.Upstreams{
|
||||
{
|
||||
DestinationType: "service", // Default should be explicitly filled
|
||||
DestinationName: "KPtAj2cb",
|
||||
LocalBindPort: 4051,
|
||||
Config: map[string]interface{}{
|
||||
"kzRnZOyd": "nUNKoL8H",
|
||||
},
|
||||
},
|
||||
{
|
||||
DestinationType: "prepared_query",
|
||||
DestinationNamespace: "9nakw0td",
|
||||
DestinationName: "KSd8HsRl",
|
||||
LocalBindPort: 11884,
|
||||
LocalBindAddress: "127.24.88.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
Weights: &structs.Weights{
|
||||
Passing: 1,
|
||||
Warning: 1,
|
||||
|
@ -3980,14 +4027,14 @@ func TestFullConfig(t *testing.T) {
|
|||
"gv5qefTz": {"5Olo2pMG", "PvvKWQU5"},
|
||||
"SHOVq1Vv": {"jntFhyym", "GYJh32pp"},
|
||||
},
|
||||
Method: "T66MFBfR",
|
||||
TCP: "bNnNfx2A",
|
||||
Interval: 22224 * time.Second,
|
||||
DockerContainerID: "ipgdFtjd",
|
||||
Shell: "omVZq7Sz",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 18913 * time.Second,
|
||||
TTL: 44743 * time.Second,
|
||||
Method: "T66MFBfR",
|
||||
TCP: "bNnNfx2A",
|
||||
Interval: 22224 * time.Second,
|
||||
DockerContainerID: "ipgdFtjd",
|
||||
Shell: "omVZq7Sz",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 18913 * time.Second,
|
||||
TTL: 44743 * time.Second,
|
||||
DeregisterCriticalServiceAfter: 8482 * time.Second,
|
||||
},
|
||||
&structs.CheckType{
|
||||
|
@ -4001,14 +4048,14 @@ func TestFullConfig(t *testing.T) {
|
|||
"4ebP5vL4": {"G20SrL5Q", "DwPKlMbo"},
|
||||
"p2UI34Qz": {"UsG1D0Qh", "NHhRiB6s"},
|
||||
},
|
||||
Method: "ciYHWors",
|
||||
TCP: "FfvCwlqH",
|
||||
Interval: 12356 * time.Second,
|
||||
DockerContainerID: "HBndBU6R",
|
||||
Shell: "hVI33JjA",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 38282 * time.Second,
|
||||
TTL: 1181 * time.Second,
|
||||
Method: "ciYHWors",
|
||||
TCP: "FfvCwlqH",
|
||||
Interval: 12356 * time.Second,
|
||||
DockerContainerID: "HBndBU6R",
|
||||
Shell: "hVI33JjA",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 38282 * time.Second,
|
||||
TTL: 1181 * time.Second,
|
||||
DeregisterCriticalServiceAfter: 4992 * time.Second,
|
||||
},
|
||||
&structs.CheckType{
|
||||
|
@ -4022,14 +4069,14 @@ func TestFullConfig(t *testing.T) {
|
|||
"rjm4DEd3": {"2m3m2Fls"},
|
||||
"l4HwQ112": {"fk56MNlo", "dhLK56aZ"},
|
||||
},
|
||||
Method: "9afLm3Mj",
|
||||
TCP: "fjiLFqVd",
|
||||
Interval: 23926 * time.Second,
|
||||
DockerContainerID: "dO5TtRHk",
|
||||
Shell: "e6q2ttES",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 38483 * time.Second,
|
||||
TTL: 10943 * time.Second,
|
||||
Method: "9afLm3Mj",
|
||||
TCP: "fjiLFqVd",
|
||||
Interval: 23926 * time.Second,
|
||||
DockerContainerID: "dO5TtRHk",
|
||||
Shell: "e6q2ttES",
|
||||
TLSSkipVerify: true,
|
||||
Timeout: 38483 * time.Second,
|
||||
TTL: 10943 * time.Second,
|
||||
DeregisterCriticalServiceAfter: 68787 * time.Second,
|
||||
},
|
||||
},
|
||||
|
@ -4612,6 +4659,7 @@ func TestSanitize(t *testing.T) {
|
|||
"Meta": {},
|
||||
"Name": "foo",
|
||||
"Port": 0,
|
||||
"Proxy": null,
|
||||
"ProxyDestination": "",
|
||||
"Tags": [],
|
||||
"Token": "hidden",
|
||||
|
|
|
@ -83,7 +83,7 @@ func (c *Catalog) Register(args *structs.RegisterRequest, reply *struct{}) error
|
|||
|
||||
// Proxies must have write permission on their destination
|
||||
if args.Service.Kind == structs.ServiceKindConnectProxy {
|
||||
if rule != nil && !rule.ServiceWrite(args.Service.ProxyDestination, nil) {
|
||||
if rule != nil && !rule.ServiceWrite(args.Service.Proxy.DestinationServiceName, nil) {
|
||||
return acl.ErrPermissionDenied
|
||||
}
|
||||
}
|
||||
|
|
|
@ -359,7 +359,40 @@ func TestCatalog_Register_ConnectProxy(t *testing.T) {
|
|||
assert.Len(resp.ServiceNodes, 1)
|
||||
v := resp.ServiceNodes[0]
|
||||
assert.Equal(structs.ServiceKindConnectProxy, v.ServiceKind)
|
||||
assert.Equal(args.Service.ProxyDestination, v.ServiceProxyDestination)
|
||||
assert.Equal(args.Service.Proxy.DestinationServiceName, v.ServiceProxy.DestinationServiceName)
|
||||
}
|
||||
|
||||
// DEPRECATED (ProxyDestination) - remove this whole test case when removing
|
||||
// ProxyDestination
|
||||
func TestCatalog_Register_DeprecatedConnectProxy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := assert.New(t)
|
||||
dir1, s1 := testServer(t)
|
||||
defer os.RemoveAll(dir1)
|
||||
defer s1.Shutdown()
|
||||
codec := rpcClient(t, s1)
|
||||
defer codec.Close()
|
||||
|
||||
args := structs.TestRegisterRequestProxy(t)
|
||||
args.Service.ProxyDestination = "legacy"
|
||||
args.Service.Proxy = structs.ConnectProxyConfig{}
|
||||
|
||||
// Register
|
||||
var out struct{}
|
||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Catalog.Register", &args, &out))
|
||||
|
||||
// List
|
||||
req := structs.ServiceSpecificRequest{
|
||||
Datacenter: "dc1",
|
||||
ServiceName: args.Service.Service,
|
||||
}
|
||||
var resp structs.IndexedServiceNodes
|
||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Catalog.ServiceNodes", &req, &resp))
|
||||
assert.Len(resp.ServiceNodes, 1)
|
||||
v := resp.ServiceNodes[0]
|
||||
assert.Equal(structs.ServiceKindConnectProxy, v.ServiceKind)
|
||||
assert.Equal(args.Service.ProxyDestination, v.ServiceProxy.DestinationServiceName)
|
||||
}
|
||||
|
||||
// Test an invalid ConnectProxy. We don't need to exhaustively test because
|
||||
|
@ -375,13 +408,13 @@ func TestCatalog_Register_ConnectProxy_invalid(t *testing.T) {
|
|||
defer codec.Close()
|
||||
|
||||
args := structs.TestRegisterRequestProxy(t)
|
||||
args.Service.ProxyDestination = ""
|
||||
args.Service.Proxy.DestinationServiceName = ""
|
||||
|
||||
// Register
|
||||
var out struct{}
|
||||
err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &args, &out)
|
||||
assert.NotNil(err)
|
||||
assert.Contains(err.Error(), "ProxyDestination")
|
||||
assert.Contains(err.Error(), "DestinationServiceName")
|
||||
}
|
||||
|
||||
// Test that write is required for the proxy destination to register a proxy.
|
||||
|
@ -422,7 +455,7 @@ service "foo" {
|
|||
// Register should fail because we don't have permission on the destination
|
||||
args := structs.TestRegisterRequestProxy(t)
|
||||
args.Service.Service = "foo"
|
||||
args.Service.ProxyDestination = "bar"
|
||||
args.Service.Proxy.DestinationServiceName = "bar"
|
||||
args.WriteRequest.Token = token
|
||||
var out struct{}
|
||||
err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", &args, &out)
|
||||
|
@ -431,7 +464,7 @@ service "foo" {
|
|||
// Register should fail with the right destination but wrong name
|
||||
args = structs.TestRegisterRequestProxy(t)
|
||||
args.Service.Service = "bar"
|
||||
args.Service.ProxyDestination = "foo"
|
||||
args.Service.Proxy.DestinationServiceName = "foo"
|
||||
args.WriteRequest.Token = token
|
||||
err = msgpackrpc.CallWithCodec(codec, "Catalog.Register", &args, &out)
|
||||
assert.True(acl.IsErrPermissionDenied(err))
|
||||
|
@ -439,7 +472,7 @@ service "foo" {
|
|||
// Register should work with the right destination
|
||||
args = structs.TestRegisterRequestProxy(t)
|
||||
args.Service.Service = "foo"
|
||||
args.Service.ProxyDestination = "foo"
|
||||
args.Service.Proxy.DestinationServiceName = "foo"
|
||||
args.WriteRequest.Token = token
|
||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Catalog.Register", &args, &out))
|
||||
}
|
||||
|
@ -1812,7 +1845,7 @@ func TestCatalog_ListServiceNodes_ConnectProxy(t *testing.T) {
|
|||
assert.Len(resp.ServiceNodes, 1)
|
||||
v := resp.ServiceNodes[0]
|
||||
assert.Equal(structs.ServiceKindConnectProxy, v.ServiceKind)
|
||||
assert.Equal(args.Service.ProxyDestination, v.ServiceProxyDestination)
|
||||
assert.Equal(args.Service.Proxy.DestinationServiceName, v.ServiceProxy.DestinationServiceName)
|
||||
}
|
||||
|
||||
func TestCatalog_ListServiceNodes_ConnectDestination(t *testing.T) {
|
||||
|
@ -1834,7 +1867,7 @@ func TestCatalog_ListServiceNodes_ConnectDestination(t *testing.T) {
|
|||
|
||||
// Register the service
|
||||
{
|
||||
dst := args.Service.ProxyDestination
|
||||
dst := args.Service.Proxy.DestinationServiceName
|
||||
args := structs.TestRegisterRequest(t)
|
||||
args.Service.Service = dst
|
||||
var out struct{}
|
||||
|
@ -1845,25 +1878,25 @@ func TestCatalog_ListServiceNodes_ConnectDestination(t *testing.T) {
|
|||
req := structs.ServiceSpecificRequest{
|
||||
Connect: true,
|
||||
Datacenter: "dc1",
|
||||
ServiceName: args.Service.ProxyDestination,
|
||||
ServiceName: args.Service.Proxy.DestinationServiceName,
|
||||
}
|
||||
var resp structs.IndexedServiceNodes
|
||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Catalog.ServiceNodes", &req, &resp))
|
||||
assert.Len(resp.ServiceNodes, 1)
|
||||
v := resp.ServiceNodes[0]
|
||||
assert.Equal(structs.ServiceKindConnectProxy, v.ServiceKind)
|
||||
assert.Equal(args.Service.ProxyDestination, v.ServiceProxyDestination)
|
||||
assert.Equal(args.Service.Proxy.DestinationServiceName, v.ServiceProxy.DestinationServiceName)
|
||||
|
||||
// List by non-Connect
|
||||
req = structs.ServiceSpecificRequest{
|
||||
Datacenter: "dc1",
|
||||
ServiceName: args.Service.ProxyDestination,
|
||||
ServiceName: args.Service.Proxy.DestinationServiceName,
|
||||
}
|
||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Catalog.ServiceNodes", &req, &resp))
|
||||
assert.Len(resp.ServiceNodes, 1)
|
||||
v = resp.ServiceNodes[0]
|
||||
assert.Equal(args.Service.ProxyDestination, v.ServiceName)
|
||||
assert.Equal("", v.ServiceProxyDestination)
|
||||
assert.Equal(args.Service.Proxy.DestinationServiceName, v.ServiceName)
|
||||
assert.Equal("", v.ServiceProxy.DestinationServiceName)
|
||||
}
|
||||
|
||||
// Test that calling ServiceNodes with Connect: true will return
|
||||
|
@ -1947,7 +1980,7 @@ service "foo" {
|
|||
// Register a proxy
|
||||
args := structs.TestRegisterRequestProxy(t)
|
||||
args.Service.Service = "foo-proxy"
|
||||
args.Service.ProxyDestination = "bar"
|
||||
args.Service.Proxy.DestinationServiceName = "bar"
|
||||
args.WriteRequest.Token = "root"
|
||||
var out struct{}
|
||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Catalog.Register", &args, &out))
|
||||
|
@ -1955,14 +1988,14 @@ service "foo" {
|
|||
// Register a proxy
|
||||
args = structs.TestRegisterRequestProxy(t)
|
||||
args.Service.Service = "foo-proxy"
|
||||
args.Service.ProxyDestination = "foo"
|
||||
args.Service.Proxy.DestinationServiceName = "foo"
|
||||
args.WriteRequest.Token = "root"
|
||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Catalog.Register", &args, &out))
|
||||
|
||||
// Register a proxy
|
||||
args = structs.TestRegisterRequestProxy(t)
|
||||
args.Service.Service = "another-proxy"
|
||||
args.Service.ProxyDestination = "foo"
|
||||
args.Service.Proxy.DestinationServiceName = "foo"
|
||||
args.WriteRequest.Token = "root"
|
||||
assert.Nil(msgpackrpc.CallWithCodec(codec, "Catalog.Register", &args, &out))
|
||||
}
|
||||
|
@ -2102,7 +2135,7 @@ func TestCatalog_NodeServices_ConnectProxy(t *testing.T) {
|
|||
assert.Len(resp.NodeServices.Services, 1)
|
||||
v := resp.NodeServices.Services[args.Service.Service]
|
||||
assert.Equal(structs.ServiceKindConnectProxy, v.Kind)
|
||||
assert.Equal(args.Service.ProxyDestination, v.ProxyDestination)
|
||||
assert.Equal(args.Service.Proxy.DestinationServiceName, v.Proxy.DestinationServiceName)
|
||||
}
|
||||
|
||||
func TestCatalog_NodeServices_ConnectNative(t *testing.T) {
|
||||
|
|
|
@ -865,7 +865,7 @@ service "foo" {
|
|||
args.WriteRequest.Token = "root"
|
||||
args.Service.ID = "foo-proxy-0"
|
||||
args.Service.Service = "foo-proxy"
|
||||
args.Service.ProxyDestination = "bar"
|
||||
args.Service.Proxy.DestinationServiceName = "bar"
|
||||
args.Check = &structs.HealthCheck{
|
||||
Name: "proxy",
|
||||
Status: api.HealthPassing,
|
||||
|
@ -877,7 +877,7 @@ service "foo" {
|
|||
args = structs.TestRegisterRequestProxy(t)
|
||||
args.WriteRequest.Token = "root"
|
||||
args.Service.Service = "foo-proxy"
|
||||
args.Service.ProxyDestination = "foo"
|
||||
args.Service.Proxy.DestinationServiceName = "foo"
|
||||
args.Check = &structs.HealthCheck{
|
||||
Name: "proxy",
|
||||
Status: api.HealthPassing,
|
||||
|
@ -889,7 +889,7 @@ service "foo" {
|
|||
args = structs.TestRegisterRequestProxy(t)
|
||||
args.WriteRequest.Token = "root"
|
||||
args.Service.Service = "another-proxy"
|
||||
args.Service.ProxyDestination = "foo"
|
||||
args.Service.Proxy.DestinationServiceName = "foo"
|
||||
args.Check = &structs.HealthCheck{
|
||||
Name: "proxy",
|
||||
Status: api.HealthPassing,
|
||||
|
|
|
@ -2652,7 +2652,7 @@ func TestPreparedQuery_Execute_ConnectExact(t *testing.T) {
|
|||
case 2:
|
||||
// Connect proxy
|
||||
req.Service.Kind = structs.ServiceKindConnectProxy
|
||||
req.Service.ProxyDestination = req.Service.Service
|
||||
req.Service.Proxy.DestinationServiceName = req.Service.Service
|
||||
req.Service.Service = "proxy"
|
||||
}
|
||||
|
||||
|
@ -2727,7 +2727,7 @@ func TestPreparedQuery_Execute_ConnectExact(t *testing.T) {
|
|||
require.Equal(reply.Service, reply.Nodes[0].Service.Service)
|
||||
|
||||
require.Equal(structs.ServiceKindConnectProxy, reply.Nodes[1].Service.Kind)
|
||||
require.Equal(reply.Service, reply.Nodes[1].Service.ProxyDestination)
|
||||
require.Equal(reply.Service, reply.Nodes[1].Service.Proxy.DestinationServiceName)
|
||||
}
|
||||
|
||||
// Update the query
|
||||
|
@ -2762,7 +2762,7 @@ func TestPreparedQuery_Execute_ConnectExact(t *testing.T) {
|
|||
require.Equal(reply.Service, reply.Nodes[0].Service.Service)
|
||||
|
||||
require.Equal(structs.ServiceKindConnectProxy, reply.Nodes[1].Service.Kind)
|
||||
require.Equal(reply.Service, reply.Nodes[1].Service.ProxyDestination)
|
||||
require.Equal(reply.Service, reply.Nodes[1].Service.Proxy.DestinationServiceName)
|
||||
}
|
||||
|
||||
// Unset the query
|
||||
|
|
|
@ -1428,16 +1428,16 @@ func TestStateStore_EnsureService_connectProxy(t *testing.T) {
|
|||
|
||||
// Create the service registration.
|
||||
ns1 := &structs.NodeService{
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
ID: "connect-proxy",
|
||||
Service: "connect-proxy",
|
||||
Address: "1.1.1.1",
|
||||
Port: 1111,
|
||||
ProxyDestination: "foo",
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
ID: "connect-proxy",
|
||||
Service: "connect-proxy",
|
||||
Address: "1.1.1.1",
|
||||
Port: 1111,
|
||||
Weights: &structs.Weights{
|
||||
Passing: 1,
|
||||
Warning: 1,
|
||||
},
|
||||
Proxy: structs.ConnectProxyConfig{DestinationServiceName: "foo"},
|
||||
}
|
||||
|
||||
// Service successfully registers into the state store.
|
||||
|
@ -2032,8 +2032,8 @@ func TestStateStore_ConnectServiceNodes(t *testing.T) {
|
|||
assert.Nil(s.EnsureNode(11, &structs.Node{Node: "bar", Address: "127.0.0.2"}))
|
||||
assert.Nil(s.EnsureService(12, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: nil, Address: "", Port: 5000}))
|
||||
assert.Nil(s.EnsureService(13, "bar", &structs.NodeService{ID: "api", Service: "api", Tags: nil, Address: "", Port: 5000}))
|
||||
assert.Nil(s.EnsureService(14, "foo", &structs.NodeService{Kind: structs.ServiceKindConnectProxy, ID: "proxy", Service: "proxy", ProxyDestination: "db", Port: 8000}))
|
||||
assert.Nil(s.EnsureService(15, "bar", &structs.NodeService{Kind: structs.ServiceKindConnectProxy, ID: "proxy", Service: "proxy", ProxyDestination: "db", Port: 8000}))
|
||||
assert.Nil(s.EnsureService(14, "foo", &structs.NodeService{Kind: structs.ServiceKindConnectProxy, ID: "proxy", Service: "proxy", Proxy: structs.ConnectProxyConfig{DestinationServiceName: "db"}, Port: 8000}))
|
||||
assert.Nil(s.EnsureService(15, "bar", &structs.NodeService{Kind: structs.ServiceKindConnectProxy, ID: "proxy", Service: "proxy", Proxy: structs.ConnectProxyConfig{DestinationServiceName: "db"}, Port: 8000}))
|
||||
assert.Nil(s.EnsureService(16, "bar", &structs.NodeService{ID: "native-db", Service: "db", Connect: structs.ServiceConnect{Native: true}}))
|
||||
assert.Nil(s.EnsureService(17, "bar", &structs.NodeService{ID: "db2", Service: "db", Tags: []string{"slave"}, Address: "", Port: 8001}))
|
||||
assert.True(watchFired(ws))
|
||||
|
@ -2994,8 +2994,8 @@ func TestStateStore_CheckConnectServiceNodes(t *testing.T) {
|
|||
assert.Nil(s.EnsureNode(11, &structs.Node{Node: "bar", Address: "127.0.0.2"}))
|
||||
assert.Nil(s.EnsureService(12, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: nil, Address: "", Port: 5000}))
|
||||
assert.Nil(s.EnsureService(13, "bar", &structs.NodeService{ID: "api", Service: "api", Tags: nil, Address: "", Port: 5000}))
|
||||
assert.Nil(s.EnsureService(14, "foo", &structs.NodeService{Kind: structs.ServiceKindConnectProxy, ID: "proxy", Service: "proxy", ProxyDestination: "db", Port: 8000}))
|
||||
assert.Nil(s.EnsureService(15, "bar", &structs.NodeService{Kind: structs.ServiceKindConnectProxy, ID: "proxy", Service: "proxy", ProxyDestination: "db", Port: 8000}))
|
||||
assert.Nil(s.EnsureService(14, "foo", &structs.NodeService{Kind: structs.ServiceKindConnectProxy, ID: "proxy", Service: "proxy", Proxy: structs.ConnectProxyConfig{DestinationServiceName: "db"}, Port: 8000}))
|
||||
assert.Nil(s.EnsureService(15, "bar", &structs.NodeService{Kind: structs.ServiceKindConnectProxy, ID: "proxy", Service: "proxy", Proxy: structs.ConnectProxyConfig{DestinationServiceName: "db"}, Port: 8000}))
|
||||
assert.Nil(s.EnsureService(16, "bar", &structs.NodeService{ID: "db2", Service: "db", Tags: []string{"slave"}, Address: "", Port: 8001}))
|
||||
assert.True(watchFired(ws))
|
||||
|
||||
|
@ -3016,7 +3016,7 @@ func TestStateStore_CheckConnectServiceNodes(t *testing.T) {
|
|||
|
||||
for _, n := range nodes {
|
||||
assert.Equal(structs.ServiceKindConnectProxy, n.Service.Kind)
|
||||
assert.Equal("db", n.Service.ProxyDestination)
|
||||
assert.Equal("db", n.Service.Proxy.DestinationServiceName)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ func (idx *IndexConnectService) FromObject(obj interface{}) (bool, []byte, error
|
|||
switch {
|
||||
case sn.ServiceKind == structs.ServiceKindConnectProxy:
|
||||
// For proxies, this service supports Connect for the destination
|
||||
result = []byte(strings.ToLower(sn.ServiceProxyDestination))
|
||||
result = []byte(strings.ToLower(sn.ServiceProxy.DestinationServiceName))
|
||||
|
||||
case sn.ServiceConnect.Native:
|
||||
// For native, this service supports Connect directly
|
||||
|
|
|
@ -47,9 +47,9 @@ func TestIndexConnectService_FromObject(t *testing.T) {
|
|||
{
|
||||
"proxy service",
|
||||
&structs.ServiceNode{
|
||||
ServiceKind: structs.ServiceKindConnectProxy,
|
||||
ServiceName: "db",
|
||||
ServiceProxyDestination: "fOo",
|
||||
ServiceKind: structs.ServiceKindConnectProxy,
|
||||
ServiceName: "db",
|
||||
ServiceProxy: structs.ConnectProxyConfig{DestinationServiceName: "fOo"},
|
||||
},
|
||||
true,
|
||||
[]byte("foo\x00"),
|
||||
|
|
|
@ -1544,7 +1544,7 @@ func TestDNS_ConnectServiceLookup(t *testing.T) {
|
|||
{
|
||||
args := structs.TestRegisterRequestProxy(t)
|
||||
args.Address = "127.0.0.55"
|
||||
args.Service.ProxyDestination = "db"
|
||||
args.Service.Proxy.DestinationServiceName = "db"
|
||||
args.Service.Address = ""
|
||||
args.Service.Port = 12345
|
||||
var out struct{}
|
||||
|
|
|
@ -856,7 +856,7 @@ func TestHealthConnectServiceNodes(t *testing.T) {
|
|||
|
||||
// Request
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf(
|
||||
"/v1/health/connect/%s?dc=dc1", args.Service.ProxyDestination), nil)
|
||||
"/v1/health/connect/%s?dc=dc1", args.Service.Proxy.DestinationServiceName), nil)
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.HealthConnectServiceNodes(resp, req)
|
||||
assert.Nil(err)
|
||||
|
@ -888,7 +888,7 @@ func TestHealthConnectServiceNodes_PassingFilter(t *testing.T) {
|
|||
t.Run("bc_no_query_value", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf(
|
||||
"/v1/health/connect/%s?passing", args.Service.ProxyDestination), nil)
|
||||
"/v1/health/connect/%s?passing", args.Service.Proxy.DestinationServiceName), nil)
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.HealthConnectServiceNodes(resp, req)
|
||||
assert.Nil(err)
|
||||
|
@ -902,7 +902,7 @@ func TestHealthConnectServiceNodes_PassingFilter(t *testing.T) {
|
|||
t.Run("passing_true", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf(
|
||||
"/v1/health/connect/%s?passing=true", args.Service.ProxyDestination), nil)
|
||||
"/v1/health/connect/%s?passing=true", args.Service.Proxy.DestinationServiceName), nil)
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.HealthConnectServiceNodes(resp, req)
|
||||
assert.Nil(err)
|
||||
|
@ -916,7 +916,7 @@ func TestHealthConnectServiceNodes_PassingFilter(t *testing.T) {
|
|||
t.Run("passing_false", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf(
|
||||
"/v1/health/connect/%s?passing=false", args.Service.ProxyDestination), nil)
|
||||
"/v1/health/connect/%s?passing=false", args.Service.Proxy.DestinationServiceName), nil)
|
||||
resp := httptest.NewRecorder()
|
||||
obj, err := a.srv.HealthConnectServiceNodes(resp, req)
|
||||
assert.Nil(err)
|
||||
|
@ -930,7 +930,7 @@ func TestHealthConnectServiceNodes_PassingFilter(t *testing.T) {
|
|||
t.Run("passing_bad", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
req, _ := http.NewRequest("GET", fmt.Sprintf(
|
||||
"/v1/health/connect/%s?passing=nope-nope", args.Service.ProxyDestination), nil)
|
||||
"/v1/health/connect/%s?passing=nope-nope", args.Service.Proxy.DestinationServiceName), nil)
|
||||
resp := httptest.NewRecorder()
|
||||
a.srv.HealthConnectServiceNodes(resp, req)
|
||||
assert.Equal(400, resp.Code)
|
||||
|
|
|
@ -659,12 +659,14 @@ func (l *State) AddProxy(proxy *structs.ConnectManagedProxy, token,
|
|||
// Construct almost all of the NodeService that needs to be registered by the
|
||||
// caller outside of the lock.
|
||||
svc := &structs.NodeService{
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
ID: target.ID + "-proxy",
|
||||
Service: target.Service + "-proxy",
|
||||
ProxyDestination: target.Service,
|
||||
Address: cfg.BindAddress,
|
||||
Port: cfg.BindPort,
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
ID: target.ID + "-proxy",
|
||||
Service: target.Service + "-proxy",
|
||||
Proxy: structs.ConnectProxyConfig{
|
||||
DestinationServiceName: target.Service,
|
||||
},
|
||||
Address: cfg.BindAddress,
|
||||
Port: cfg.BindPort,
|
||||
}
|
||||
|
||||
// Lock now. We can't lock earlier as l.Service would deadlock and shouldn't
|
||||
|
|
|
@ -276,11 +276,11 @@ func TestAgentAntiEntropy_Services_ConnectProxy(t *testing.T) {
|
|||
|
||||
// Exists both same (noop)
|
||||
srv1 := &structs.NodeService{
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
ID: "mysql-proxy",
|
||||
Service: "mysql-proxy",
|
||||
Port: 5000,
|
||||
ProxyDestination: "db",
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
ID: "mysql-proxy",
|
||||
Service: "mysql-proxy",
|
||||
Port: 5000,
|
||||
Proxy: structs.ConnectProxyConfig{DestinationServiceName: "db"},
|
||||
Weights: &structs.Weights{
|
||||
Passing: 1,
|
||||
Warning: 1,
|
||||
|
@ -292,11 +292,11 @@ func TestAgentAntiEntropy_Services_ConnectProxy(t *testing.T) {
|
|||
|
||||
// Exists both, different (update)
|
||||
srv2 := &structs.NodeService{
|
||||
ID: "redis-proxy",
|
||||
Service: "redis-proxy",
|
||||
Port: 8000,
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
ProxyDestination: "redis",
|
||||
ID: "redis-proxy",
|
||||
Service: "redis-proxy",
|
||||
Port: 8000,
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
Proxy: structs.ConnectProxyConfig{DestinationServiceName: "redis"},
|
||||
Weights: &structs.Weights{
|
||||
Passing: 1,
|
||||
Warning: 0,
|
||||
|
@ -312,11 +312,11 @@ func TestAgentAntiEntropy_Services_ConnectProxy(t *testing.T) {
|
|||
|
||||
// Exists local (create)
|
||||
srv3 := &structs.NodeService{
|
||||
ID: "web-proxy",
|
||||
Service: "web-proxy",
|
||||
Port: 80,
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
ProxyDestination: "web",
|
||||
ID: "web-proxy",
|
||||
Service: "web-proxy",
|
||||
Port: 80,
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
Proxy: structs.ConnectProxyConfig{DestinationServiceName: "web"},
|
||||
Weights: &structs.Weights{
|
||||
Passing: 1,
|
||||
Warning: 1,
|
||||
|
@ -326,11 +326,11 @@ func TestAgentAntiEntropy_Services_ConnectProxy(t *testing.T) {
|
|||
|
||||
// Exists remote (delete)
|
||||
srv4 := &structs.NodeService{
|
||||
ID: "lb-proxy",
|
||||
Service: "lb-proxy",
|
||||
Port: 443,
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
ProxyDestination: "lb",
|
||||
ID: "lb-proxy",
|
||||
Service: "lb-proxy",
|
||||
Port: 443,
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
Proxy: structs.ConnectProxyConfig{DestinationServiceName: "lb"},
|
||||
Weights: &structs.Weights{
|
||||
Passing: 1,
|
||||
Warning: 0,
|
||||
|
@ -341,11 +341,11 @@ func TestAgentAntiEntropy_Services_ConnectProxy(t *testing.T) {
|
|||
|
||||
// Exists local, in sync, remote missing (create)
|
||||
srv5 := &structs.NodeService{
|
||||
ID: "cache-proxy",
|
||||
Service: "cache-proxy",
|
||||
Port: 11211,
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
ProxyDestination: "cache-proxy",
|
||||
ID: "cache-proxy",
|
||||
Service: "cache-proxy",
|
||||
Port: 11211,
|
||||
Kind: structs.ServiceKindConnectProxy,
|
||||
Proxy: structs.ConnectProxyConfig{DestinationServiceName: "cache-proxy"},
|
||||
Weights: &structs.Weights{
|
||||
Passing: 1,
|
||||
Warning: 1,
|
||||
|
@ -1862,7 +1862,7 @@ func TestStateProxyManagement(t *testing.T) {
|
|||
assert.Equal("web-proxy", svc.ID)
|
||||
assert.Equal("web-proxy", svc.Service)
|
||||
assert.Equal(structs.ServiceKindConnectProxy, svc.Kind)
|
||||
assert.Equal("web", svc.ProxyDestination)
|
||||
assert.Equal("web", svc.Proxy.DestinationServiceName)
|
||||
assert.Equal("", svc.Address, "should have empty address by default")
|
||||
// Port is non-deterministic but could be either of 20000 or 20001
|
||||
assert.Contains([]int{20000, 20001}, svc.Port)
|
||||
|
@ -1878,7 +1878,7 @@ func TestStateProxyManagement(t *testing.T) {
|
|||
assert.Equal("web-proxy", svcDup.ID)
|
||||
assert.Equal("web-proxy", svcDup.Service)
|
||||
assert.Equal(structs.ServiceKindConnectProxy, svcDup.Kind)
|
||||
assert.Equal("web", svcDup.ProxyDestination)
|
||||
assert.Equal("web", svcDup.Proxy.DestinationServiceName)
|
||||
assert.Equal("", svcDup.Address, "should have empty address by default")
|
||||
// Port must be same as before
|
||||
assert.Equal(svc.Port, svcDup.Port)
|
||||
|
|
|
@ -92,6 +92,9 @@ type ConnectManagedProxy struct {
|
|||
// Config is the arbitrary configuration data provided with the registration.
|
||||
Config map[string]interface{}
|
||||
|
||||
// Upstreams are the dependencies the proxy should setup outgoing listners for.
|
||||
Upstreams Upstreams
|
||||
|
||||
// ProxyService is a pointer to the local proxy's service record for
|
||||
// convenience. The proxies ID and name etc. can be read from there. It may be
|
||||
// nil if the agent is starting up and hasn't registered the service yet. We
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
package structs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
)
|
||||
|
||||
// ConnectProxyConfig describes the configuration needed for any proxy managed
|
||||
// or unmanaged. It describes a single logical service's listener and optionally
|
||||
// upstreams and sidecar-related config for a single instance. To describe a
|
||||
// centralised proxy that routed traffic for multiple services, a different one
|
||||
// of these would be needed for each, sharing the same LogicalProxyID.
|
||||
type ConnectProxyConfig struct {
|
||||
// DestinationServiceName is required and is the name of the service to accept
|
||||
// traffic for.
|
||||
DestinationServiceName string
|
||||
|
||||
// DestinationServiceID is optional and should only be specified for
|
||||
// "side-car" style proxies where the proxy is in front of just a single
|
||||
// instance of the service. It should be set to the service ID of the instance
|
||||
// being represented which must be registered to the same agent. It's valid to
|
||||
// provide a service ID that does not yet exist to avoid timing issues when
|
||||
// bootstrapping a service with a proxy.
|
||||
DestinationServiceID string
|
||||
|
||||
// LocalServiceAddress is the address of the local service instance. It is
|
||||
// optional and should only be specified for "side-car" style proxies. It will
|
||||
// default to 127.0.0.1 if the proxy is a "side-car" (DestinationServiceID is
|
||||
// set) but otherwise will be ignored.
|
||||
LocalServiceAddress string
|
||||
|
||||
// LocalServicePort is the port of the local service instance. It is optional
|
||||
// and should only be specified for "side-car" style proxies. It will default
|
||||
// to the registered port for the instance if the proxy is a "side-car"
|
||||
// (DestinationServiceID is set) but otherwise will be ignored.
|
||||
LocalServicePort int
|
||||
|
||||
// Config is the arbitrary configuration data provided with the proxy
|
||||
// registration.
|
||||
Config map[string]interface{}
|
||||
|
||||
// Upstreams describes any upstream dependencies the proxy instance should
|
||||
// setup.
|
||||
Upstreams Upstreams
|
||||
}
|
||||
|
||||
// ToAPI returns the api struct with the same fields. We have duplicates to
|
||||
// avoid the api package depending on this one which imports a ton of Consul's
|
||||
// core which you don't want if you are just trying to use our client in your
|
||||
// app.
|
||||
func (c *ConnectProxyConfig) ToAPI() *api.AgentServiceConnectProxyConfig {
|
||||
return &api.AgentServiceConnectProxyConfig{
|
||||
DestinationServiceName: c.DestinationServiceName,
|
||||
DestinationServiceID: c.DestinationServiceID,
|
||||
LocalServiceAddress: c.LocalServiceAddress,
|
||||
LocalServicePort: c.LocalServicePort,
|
||||
Config: c.Config,
|
||||
Upstreams: c.Upstreams.ToAPI(),
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
UpstreamDestTypeService = "service"
|
||||
UpstreamDestTypePreparedQuery = "prepared_query"
|
||||
)
|
||||
|
||||
// Upstreams is a list of upstreams. Aliased to allow ToAPI method.
|
||||
type Upstreams []Upstream
|
||||
|
||||
// ToAPI returns the api structs with the same fields. We have duplicates to
|
||||
// avoid the api package depending on this one which imports a ton of Consul's
|
||||
// core which you don't want if you are just trying to use our client in your
|
||||
// app.
|
||||
func (us Upstreams) ToAPI() []api.Upstream {
|
||||
a := make([]api.Upstream, len(us))
|
||||
for i, u := range us {
|
||||
a[i] = u.ToAPI()
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// UpstreamsFromAPI is a helper for converting api.Upstream to Upstream.
|
||||
func UpstreamsFromAPI(us []api.Upstream) Upstreams {
|
||||
a := make([]Upstream, len(us))
|
||||
for i, u := range us {
|
||||
a[i] = UpstreamFromAPI(u)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Upstream represents a single upstream dependency for a service or proxy. It
|
||||
// describes the mechanism used to discover instances to communicate with (the
|
||||
// Target) as well as any potential client configuration that may be useful such
|
||||
// as load balancer options, timeouts etc.
|
||||
type Upstream struct {
|
||||
// Destination fields are the required ones for determining what this upstream
|
||||
// points to. Depending on DestinationType some other fields below might
|
||||
// further restrict the set of instances allowable.
|
||||
//
|
||||
// DestinationType would be better as an int constant but even with custom
|
||||
// JSON marshallers it causes havoc with all the mapstructure mangling we do
|
||||
// on service definitions in various places.
|
||||
DestinationType string
|
||||
DestinationNamespace string `json:",omitempty"`
|
||||
DestinationName string
|
||||
|
||||
// Datacenter that the service discovery request should be run against. Note
|
||||
// for prepared queries, the actual results might be from a different
|
||||
// datacenter.
|
||||
Datacenter string
|
||||
|
||||
// LocalBindAddress is the ip address a side-car proxy should listen on for
|
||||
// traffic destined for this upstream service. Default if empty is 127.0.0.1.
|
||||
LocalBindAddress string `json:",omitempty"`
|
||||
|
||||
// LocalBindPort is the ip address a side-car proxy should listen on for traffic
|
||||
// destined for this upstream service. Required.
|
||||
LocalBindPort int
|
||||
|
||||
// Config is an opaque config that is specific to the proxy process being run.
|
||||
// It can be used to pass abritrary configuration for this specific upstream
|
||||
// to the proxy.
|
||||
Config map[string]interface{}
|
||||
}
|
||||
|
||||
// Validate sanity checks the struct is valid
|
||||
func (u *Upstream) Validate() error {
|
||||
if u.DestinationType != UpstreamDestTypeService &&
|
||||
u.DestinationType != UpstreamDestTypePreparedQuery {
|
||||
return fmt.Errorf("unknown upstream destination type")
|
||||
}
|
||||
|
||||
if u.DestinationName == "" {
|
||||
return fmt.Errorf("upstream destination name cannot be empty")
|
||||
}
|
||||
|
||||
if u.LocalBindPort == 0 {
|
||||
return fmt.Errorf("upstream local bind port cannot be zero")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToAPI returns the api structs with the same fields. We have duplicates to
|
||||
// avoid the api package depending on this one which imports a ton of Consul's
|
||||
// core which you don't want if you are just trying to use our client in your
|
||||
// app.
|
||||
func (u *Upstream) ToAPI() api.Upstream {
|
||||
return api.Upstream{
|
||||
DestinationType: api.UpstreamDestType(u.DestinationType),
|
||||
DestinationNamespace: u.DestinationNamespace,
|
||||
DestinationName: u.DestinationName,
|
||||
Datacenter: u.Datacenter,
|
||||
LocalBindAddress: u.LocalBindAddress,
|
||||
LocalBindPort: u.LocalBindPort,
|
||||
Config: u.Config,
|
||||
}
|
||||
}
|
||||
|
||||
// UpstreamFromAPI is a helper for converting api.Upstream to Upstream.
|
||||
func UpstreamFromAPI(u api.Upstream) Upstream {
|
||||
return Upstream{
|
||||
DestinationType: string(u.DestinationType),
|
||||
DestinationNamespace: u.DestinationNamespace,
|
||||
DestinationName: u.DestinationName,
|
||||
Datacenter: u.Datacenter,
|
||||
LocalBindAddress: u.LocalBindAddress,
|
||||
LocalBindPort: u.LocalBindPort,
|
||||
Config: u.Config,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
package structs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConnectProxyConfig_ToAPI(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in ConnectProxyConfig
|
||||
want *api.AgentServiceConnectProxyConfig
|
||||
}{
|
||||
{
|
||||
name: "service",
|
||||
in: ConnectProxyConfig{
|
||||
DestinationServiceName: "web",
|
||||
DestinationServiceID: "web1",
|
||||
LocalServiceAddress: "127.0.0.2",
|
||||
LocalServicePort: 5555,
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
Upstreams: Upstreams{
|
||||
{
|
||||
DestinationType: UpstreamDestTypeService,
|
||||
DestinationName: "foo",
|
||||
Datacenter: "dc1",
|
||||
LocalBindPort: 1234,
|
||||
},
|
||||
{
|
||||
DestinationType: UpstreamDestTypePreparedQuery,
|
||||
DestinationName: "foo",
|
||||
Datacenter: "dc1",
|
||||
LocalBindPort: 2345,
|
||||
LocalBindAddress: "127.10.10.10",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &api.AgentServiceConnectProxyConfig{
|
||||
DestinationServiceName: "web",
|
||||
DestinationServiceID: "web1",
|
||||
LocalServiceAddress: "127.0.0.2",
|
||||
LocalServicePort: 5555,
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
Upstreams: []api.Upstream{
|
||||
{
|
||||
DestinationType: UpstreamDestTypeService,
|
||||
DestinationName: "foo",
|
||||
Datacenter: "dc1",
|
||||
LocalBindPort: 1234,
|
||||
},
|
||||
{
|
||||
DestinationType: UpstreamDestTypePreparedQuery,
|
||||
DestinationName: "foo",
|
||||
Datacenter: "dc1",
|
||||
LocalBindPort: 2345,
|
||||
LocalBindAddress: "127.10.10.10",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, tt.in.ToAPI())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpstream_MarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in Upstream
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "service",
|
||||
in: Upstream{
|
||||
DestinationType: UpstreamDestTypeService,
|
||||
DestinationName: "foo",
|
||||
Datacenter: "dc1",
|
||||
LocalBindPort: 1234,
|
||||
},
|
||||
want: `{
|
||||
"DestinationType": "service",
|
||||
"DestinationName": "foo",
|
||||
"Datacenter": "dc1",
|
||||
"LocalBindPort": 1234,
|
||||
"Config": null
|
||||
}`,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "pq",
|
||||
in: Upstream{
|
||||
DestinationType: UpstreamDestTypePreparedQuery,
|
||||
DestinationName: "foo",
|
||||
Datacenter: "dc1",
|
||||
LocalBindPort: 1234,
|
||||
},
|
||||
want: `{
|
||||
"DestinationType": "prepared_query",
|
||||
"DestinationName": "foo",
|
||||
"Datacenter": "dc1",
|
||||
"LocalBindPort": 1234,
|
||||
"Config": null
|
||||
}`,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
got, err := json.Marshal(tt.in)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
require.JSONEq(tt.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpstream_UnmarshalJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
json string
|
||||
want Upstream
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "service",
|
||||
json: `{
|
||||
"DestinationType": "service",
|
||||
"DestinationName": "foo",
|
||||
"Datacenter": "dc1"
|
||||
}`,
|
||||
want: Upstream{
|
||||
DestinationType: UpstreamDestTypeService,
|
||||
DestinationName: "foo",
|
||||
Datacenter: "dc1",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "pq",
|
||||
json: `{
|
||||
"DestinationType": "prepared_query",
|
||||
"DestinationName": "foo",
|
||||
"Datacenter": "dc1"
|
||||
}`,
|
||||
want: Upstream{
|
||||
DestinationType: UpstreamDestTypePreparedQuery,
|
||||
DestinationName: "foo",
|
||||
Datacenter: "dc1",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
var got Upstream
|
||||
err := json.Unmarshal([]byte(tt.json), &got)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
require.Equal(tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -26,8 +26,23 @@ type ServiceDefinition struct {
|
|||
Weights *Weights
|
||||
Token string
|
||||
EnableTagOverride bool
|
||||
ProxyDestination string
|
||||
Connect *ServiceConnect
|
||||
// DEPRECATED (ProxyDestination) - remove this when removing ProxyDestination
|
||||
// ProxyDestination is deprecated in favour of Proxy.DestinationServiceName
|
||||
ProxyDestination string `json:",omitempty"`
|
||||
|
||||
// Proxy is the configuration set for Kind = connect-proxy. It is mandatory in
|
||||
// that case and an error to be set for any other kind. This config is part of
|
||||
// a proxy service definition and is distinct from but shares some fields with
|
||||
// the Connect.Proxy which configures a manageged proxy as part of the actual
|
||||
// service's definition. This duplication is ugly but seemed better than the
|
||||
// alternative which was to re-use the same struct fields for both cases even
|
||||
// though the semantics are different and the non-shared fields make no sense
|
||||
// in the other case. ProxyConfig may be a more natural name here, but it's
|
||||
// confusing for the UX because one of the fields in ConnectProxyConfig is
|
||||
// also called just "Config"
|
||||
Proxy *ConnectProxyConfig
|
||||
|
||||
Connect *ServiceConnect
|
||||
}
|
||||
|
||||
func (s *ServiceDefinition) NodeService() *NodeService {
|
||||
|
@ -41,11 +56,23 @@ func (s *ServiceDefinition) NodeService() *NodeService {
|
|||
Port: s.Port,
|
||||
Weights: s.Weights,
|
||||
EnableTagOverride: s.EnableTagOverride,
|
||||
ProxyDestination: s.ProxyDestination,
|
||||
}
|
||||
if s.Connect != nil {
|
||||
ns.Connect = *s.Connect
|
||||
}
|
||||
if s.Proxy != nil {
|
||||
ns.Proxy = *s.Proxy
|
||||
// Ensure the Upstream type is defaulted
|
||||
for i := range ns.Proxy.Upstreams {
|
||||
if ns.Proxy.Upstreams[i].DestinationType == "" {
|
||||
ns.Proxy.Upstreams[i].DestinationType = UpstreamDestTypeService
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// DEPRECATED (ProxyDestination) - remove this when removing ProxyDestination
|
||||
// Legacy convert ProxyDestination into a Proxy config
|
||||
ns.Proxy.DestinationServiceName = s.ProxyDestination
|
||||
}
|
||||
if ns.ID == "" && ns.Service != "" {
|
||||
ns.ID = ns.Service
|
||||
}
|
||||
|
@ -72,18 +99,100 @@ func (s *ServiceDefinition) ConnectManagedProxy() (*ConnectManagedProxy, error)
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// If upstreams were set in the config and NOT in the actual Upstreams field,
|
||||
// extract them out to the new explicit Upstreams and unset in config to make
|
||||
// transition smooth.
|
||||
if deprecatedUpstreams, ok := s.Connect.Proxy.Config["upstreams"]; ok {
|
||||
if len(s.Connect.Proxy.Upstreams) == 0 {
|
||||
if slice, ok := deprecatedUpstreams.([]interface{}); ok {
|
||||
for _, raw := range slice {
|
||||
var oldU deprecatedBuiltInProxyUpstreamConfig
|
||||
var decMeta mapstructure.Metadata
|
||||
decCfg := &mapstructure.DecoderConfig{
|
||||
Metadata: &decMeta,
|
||||
Result: &oldU,
|
||||
}
|
||||
dec, err := mapstructure.NewDecoder(decCfg)
|
||||
if err != nil {
|
||||
// Just skip it - we never used to parse this so never failed
|
||||
// invalid stuff till it hit the proxy. This is a best-effort
|
||||
// attempt to not break existing service definitions so it's not the
|
||||
// end of the world if we don't have exactly the same failure mode
|
||||
// for invalid input.
|
||||
continue
|
||||
}
|
||||
err = dec.Decode(raw)
|
||||
if err != nil {
|
||||
// same logic as above
|
||||
continue
|
||||
}
|
||||
|
||||
newT := UpstreamDestTypeService
|
||||
if oldU.DestinationType == "prepared_query" {
|
||||
newT = UpstreamDestTypePreparedQuery
|
||||
}
|
||||
u := Upstream{
|
||||
DestinationType: newT,
|
||||
DestinationName: oldU.DestinationName,
|
||||
DestinationNamespace: oldU.DestinationNamespace,
|
||||
Datacenter: oldU.DestinationDatacenter,
|
||||
LocalBindAddress: oldU.LocalBindAddress,
|
||||
LocalBindPort: oldU.LocalBindPort,
|
||||
}
|
||||
// Any unrecognized keys should be copied into the config map
|
||||
if len(decMeta.Unused) > 0 {
|
||||
u.Config = make(map[string]interface{})
|
||||
// Paranoid type assertion - mapstructure would have errored if this
|
||||
// wasn't safe but panics are bad...
|
||||
if rawMap, ok := raw.(map[string]interface{}); ok {
|
||||
for _, k := range decMeta.Unused {
|
||||
u.Config[k] = rawMap[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
s.Connect.Proxy.Upstreams = append(s.Connect.Proxy.Upstreams, u)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove upstreams even if we didn't add them for consistency.
|
||||
delete(s.Connect.Proxy.Config, "upstreams")
|
||||
}
|
||||
|
||||
p := &ConnectManagedProxy{
|
||||
ExecMode: execMode,
|
||||
Command: s.Connect.Proxy.Command,
|
||||
Config: s.Connect.Proxy.Config,
|
||||
ExecMode: execMode,
|
||||
Command: s.Connect.Proxy.Command,
|
||||
Config: s.Connect.Proxy.Config,
|
||||
Upstreams: s.Connect.Proxy.Upstreams,
|
||||
// ProxyService will be setup when the agent registers the configured
|
||||
// proxies and starts them etc.
|
||||
TargetServiceID: ns.ID,
|
||||
}
|
||||
|
||||
// Ensure the Upstream type is defaulted
|
||||
for i := range p.Upstreams {
|
||||
if p.Upstreams[i].DestinationType == "" {
|
||||
p.Upstreams[i].DestinationType = UpstreamDestTypeService
|
||||
}
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// deprecatedBuiltInProxyUpstreamConfig is a struct for extracting old
|
||||
// connect/proxy.UpstreamConfiguration syntax upstreams from existing managed
|
||||
// proxy configs to convert them to new first-class Upstreams.
|
||||
type deprecatedBuiltInProxyUpstreamConfig struct {
|
||||
LocalBindAddress string `json:"local_bind_address" hcl:"local_bind_address,attr" mapstructure:"local_bind_address"`
|
||||
LocalBindPort int `json:"local_bind_port" hcl:"local_bind_port,attr" mapstructure:"local_bind_port"`
|
||||
DestinationName string `json:"destination_name" hcl:"destination_name,attr" mapstructure:"destination_name"`
|
||||
DestinationNamespace string `json:"destination_namespace" hcl:"destination_namespace,attr" mapstructure:"destination_namespace"`
|
||||
DestinationType string `json:"destination_type" hcl:"destination_type,attr" mapstructure:"destination_type"`
|
||||
DestinationDatacenter string `json:"destination_datacenter" hcl:"destination_datacenter,attr" mapstructure:"destination_datacenter"`
|
||||
// ConnectTimeoutMs is removed explicitly because any additional config we
|
||||
// find including this field should be put into the opaque Config map in
|
||||
// Upstream.
|
||||
}
|
||||
|
||||
// Validate validates the service definition. This also calls the underlying
|
||||
// Validate method on the NodeService.
|
||||
//
|
||||
|
@ -137,9 +246,10 @@ func (s *ServiceDefinition) CheckTypes() (checks CheckTypes, err error) {
|
|||
// registration. Note this is duplicated in config.ServiceConnectProxy and needs
|
||||
// to be kept in sync.
|
||||
type ServiceDefinitionConnectProxy struct {
|
||||
Command []string `json:",omitempty"`
|
||||
ExecMode string `json:",omitempty"`
|
||||
Config map[string]interface{} `json:",omitempty"`
|
||||
Command []string `json:",omitempty"`
|
||||
ExecMode string `json:",omitempty"`
|
||||
Config map[string]interface{} `json:",omitempty"`
|
||||
Upstreams []Upstream `json:",omitempty"`
|
||||
}
|
||||
|
||||
func (p *ServiceDefinitionConnectProxy) MarshalJSON() ([]byte, error) {
|
||||
|
|
|
@ -84,6 +84,7 @@ func TestServiceDefinitionValidate(t *testing.T) {
|
|||
{
|
||||
"managed proxy with no port set",
|
||||
func(x *ServiceDefinition) {
|
||||
x.Port = 0 // Explicitly unset this as the test default sets it sanely
|
||||
x.Connect = &ServiceConnect{
|
||||
Proxy: &ServiceDefinitionConnectProxy{},
|
||||
}
|
||||
|
@ -111,13 +112,12 @@ func TestServiceDefinitionValidate(t *testing.T) {
|
|||
tc.Modify(service)
|
||||
|
||||
err := service.Validate()
|
||||
t.Logf("error: %s", err)
|
||||
require.Equal(err != nil, tc.Err != "")
|
||||
if err == nil {
|
||||
return
|
||||
if tc.Err == "" {
|
||||
require.NoError(err)
|
||||
} else {
|
||||
require.Error(err)
|
||||
require.Contains(strings.ToLower(err.Error()), strings.ToLower(tc.Err))
|
||||
}
|
||||
|
||||
require.Contains(strings.ToLower(err.Error()), strings.ToLower(tc.Err))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -524,8 +524,10 @@ type ServiceNode struct {
|
|||
ServiceMeta map[string]string
|
||||
ServicePort int
|
||||
ServiceEnableTagOverride bool
|
||||
ServiceProxyDestination string
|
||||
ServiceConnect ServiceConnect
|
||||
// DEPRECATED (ProxyDestination) - remove this when removing ProxyDestination
|
||||
ServiceProxyDestination string
|
||||
ServiceProxy ConnectProxyConfig
|
||||
ServiceConnect ServiceConnect
|
||||
|
||||
RaftIndex
|
||||
}
|
||||
|
@ -554,8 +556,10 @@ func (s *ServiceNode) PartialClone() *ServiceNode {
|
|||
ServiceMeta: nsmeta,
|
||||
ServiceWeights: s.ServiceWeights,
|
||||
ServiceEnableTagOverride: s.ServiceEnableTagOverride,
|
||||
ServiceProxyDestination: s.ServiceProxyDestination,
|
||||
ServiceConnect: s.ServiceConnect,
|
||||
// DEPRECATED (ProxyDestination) - remove this when removing ProxyDestination
|
||||
ServiceProxyDestination: s.ServiceProxyDestination,
|
||||
ServiceProxy: s.ServiceProxy,
|
||||
ServiceConnect: s.ServiceConnect,
|
||||
RaftIndex: RaftIndex{
|
||||
CreateIndex: s.CreateIndex,
|
||||
ModifyIndex: s.ModifyIndex,
|
||||
|
@ -575,7 +579,7 @@ func (s *ServiceNode) ToNodeService() *NodeService {
|
|||
Meta: s.ServiceMeta,
|
||||
Weights: &s.ServiceWeights,
|
||||
EnableTagOverride: s.ServiceEnableTagOverride,
|
||||
ProxyDestination: s.ServiceProxyDestination,
|
||||
Proxy: s.ServiceProxy,
|
||||
Connect: s.ServiceConnect,
|
||||
RaftIndex: RaftIndex{
|
||||
CreateIndex: s.CreateIndex,
|
||||
|
@ -624,13 +628,32 @@ type NodeService struct {
|
|||
Weights *Weights
|
||||
EnableTagOverride bool
|
||||
|
||||
// ProxyDestination is the name of the service that this service is
|
||||
// a Connect proxy for. This is only valid if Kind is "connect-proxy".
|
||||
// The destination may be a service that isn't present in the catalog.
|
||||
// This is expected and allowed to allow for proxies to come up
|
||||
// earlier than their target services.
|
||||
// ProxyDestination is DEPRECATED in favor of Proxy.DestinationServiceName.
|
||||
// It's retained since this struct is used to parse input for
|
||||
// /catalog/register but nothing else internal should use it - once
|
||||
// request/config definitinos are passes all internal uses of NodeService
|
||||
// should have this empty and use the Proxy.DestinationServiceNames field
|
||||
// below.
|
||||
//
|
||||
// It used to store the name of the service that this service is a Connect
|
||||
// proxy for. This is only valid if Kind is "connect-proxy". The destination
|
||||
// may be a service that isn't present in the catalog. This is expected and
|
||||
// allowed to allow for proxies to come up earlier than their target services.
|
||||
// DEPRECATED (ProxyDestination) - remove this when removing ProxyDestination
|
||||
ProxyDestination string
|
||||
|
||||
// Proxy is the configuration set for Kind = connect-proxy. It is mandatory in
|
||||
// that case and an error to be set for any other kind. This config is part of
|
||||
// a proxy service definition and is distinct from but shares some fields with
|
||||
// the Connect.Proxy which configures a manageged proxy as part of the actual
|
||||
// service's definition. This duplication is ugly but seemed better than the
|
||||
// alternative which was to re-use the same struct fields for both cases even
|
||||
// though the semantics are different and the non-shred fields make no sense
|
||||
// in the other case. ProxyConfig may be a more natural name here, but it's
|
||||
// confusing for the UX because one of the fields in ConnectProxyConfig is
|
||||
// also called just "Config"
|
||||
Proxy ConnectProxyConfig
|
||||
|
||||
// Connect are the Connect settings for a service. This is purposely NOT
|
||||
// a pointer so that we never have to nil-check this.
|
||||
Connect ServiceConnect
|
||||
|
@ -661,9 +684,17 @@ func (s *NodeService) Validate() error {
|
|||
|
||||
// ConnectProxy validation
|
||||
if s.Kind == ServiceKindConnectProxy {
|
||||
if strings.TrimSpace(s.ProxyDestination) == "" {
|
||||
// DEPRECATED (ProxyDestination) - remove this when removing ProxyDestination
|
||||
// Fixup legacy requests that specify the ProxyDestination still
|
||||
if s.ProxyDestination != "" && s.Proxy.DestinationServiceName == "" {
|
||||
s.Proxy.DestinationServiceName = s.ProxyDestination
|
||||
s.ProxyDestination = ""
|
||||
}
|
||||
|
||||
if strings.TrimSpace(s.Proxy.DestinationServiceName) == "" {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"ProxyDestination must be non-empty for Connect proxy services"))
|
||||
"Proxy.DestinationServiceName must be non-empty for Connect proxy "+
|
||||
"services"))
|
||||
}
|
||||
|
||||
if s.Port == 0 {
|
||||
|
@ -673,7 +704,7 @@ func (s *NodeService) Validate() error {
|
|||
|
||||
if s.Connect.Native {
|
||||
result = multierror.Append(result, fmt.Errorf(
|
||||
"A Proxy cannot also be ConnectNative, only typical services"))
|
||||
"A Proxy cannot also be Connect Native, only typical services"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -694,7 +725,7 @@ func (s *NodeService) IsSame(other *NodeService) bool {
|
|||
!reflect.DeepEqual(s.Meta, other.Meta) ||
|
||||
s.EnableTagOverride != other.EnableTagOverride ||
|
||||
s.Kind != other.Kind ||
|
||||
s.ProxyDestination != other.ProxyDestination ||
|
||||
!reflect.DeepEqual(s.Proxy, other.Proxy) ||
|
||||
s.Connect != other.Connect {
|
||||
return false
|
||||
}
|
||||
|
@ -713,6 +744,11 @@ func (s *NodeService) ToServiceNode(node string) *ServiceNode {
|
|||
theWeights = *s.Weights
|
||||
}
|
||||
}
|
||||
// DEPRECATED (ProxyDestination) - remove this when removing ProxyDestination
|
||||
legacyProxyDest := s.Proxy.DestinationServiceName
|
||||
if legacyProxyDest == "" {
|
||||
legacyProxyDest = s.ProxyDestination
|
||||
}
|
||||
return &ServiceNode{
|
||||
// Skip ID, see ServiceNode definition.
|
||||
Node: node,
|
||||
|
@ -727,7 +763,8 @@ func (s *NodeService) ToServiceNode(node string) *ServiceNode {
|
|||
ServiceMeta: s.Meta,
|
||||
ServiceWeights: theWeights,
|
||||
ServiceEnableTagOverride: s.EnableTagOverride,
|
||||
ServiceProxyDestination: s.ProxyDestination,
|
||||
ServiceProxy: s.Proxy,
|
||||
ServiceProxyDestination: legacyProxyDest,
|
||||
ServiceConnect: s.Connect,
|
||||
RaftIndex: RaftIndex{
|
||||
CreateIndex: s.CreateIndex,
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEncodeDecode(t *testing.T) {
|
||||
|
@ -122,7 +123,7 @@ func TestStructs_RegisterRequest_ChangesNode(t *testing.T) {
|
|||
}
|
||||
|
||||
// testServiceNode gives a fully filled out ServiceNode instance.
|
||||
func testServiceNode() *ServiceNode {
|
||||
func testServiceNode(t *testing.T) *ServiceNode {
|
||||
return &ServiceNode{
|
||||
ID: types.NodeID("40e4a748-2192-161a-0510-9bf59fe950b5"),
|
||||
Node: "node1",
|
||||
|
@ -144,16 +145,25 @@ func testServiceNode() *ServiceNode {
|
|||
"service": "metadata",
|
||||
},
|
||||
ServiceEnableTagOverride: true,
|
||||
ServiceProxyDestination: "cats",
|
||||
RaftIndex: RaftIndex{
|
||||
CreateIndex: 1,
|
||||
ModifyIndex: 2,
|
||||
},
|
||||
ServiceProxy: TestConnectProxyConfig(t),
|
||||
// DEPRECATED (ProxyDestination) - remove this when removing ProxyDestination
|
||||
// ServiceProxyDestination is deprecated bit must be set consistently with
|
||||
// the value of ServiceProxy.DestinationServiceName otherwise a round-trip
|
||||
// through ServiceNode -> NodeService and back will not match and fail
|
||||
// tests.
|
||||
ServiceProxyDestination: "web",
|
||||
ServiceConnect: ServiceConnect{
|
||||
Native: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestStructs_ServiceNode_PartialClone(t *testing.T) {
|
||||
sn := testServiceNode()
|
||||
sn := testServiceNode(t)
|
||||
|
||||
clone := sn.PartialClone()
|
||||
|
||||
|
@ -173,9 +183,7 @@ func TestStructs_ServiceNode_PartialClone(t *testing.T) {
|
|||
sn.Datacenter = ""
|
||||
sn.TaggedAddresses = nil
|
||||
sn.NodeMeta = nil
|
||||
if !reflect.DeepEqual(sn, clone) {
|
||||
t.Fatalf("bad: %v", clone)
|
||||
}
|
||||
require.Equal(t, sn, clone)
|
||||
|
||||
sn.ServiceTags = append(sn.ServiceTags, "hello")
|
||||
if reflect.DeepEqual(sn, clone) {
|
||||
|
@ -201,7 +209,7 @@ func TestStructs_ServiceNode_PartialClone(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestStructs_ServiceNode_Conversions(t *testing.T) {
|
||||
sn := testServiceNode()
|
||||
sn := testServiceNode(t)
|
||||
|
||||
sn2 := sn.ToNodeService().ToServiceNode("node1")
|
||||
|
||||
|
@ -213,9 +221,7 @@ func TestStructs_ServiceNode_Conversions(t *testing.T) {
|
|||
sn.TaggedAddresses = nil
|
||||
sn.NodeMeta = nil
|
||||
sn.ServiceWeights = Weights{Passing: 1, Warning: 1}
|
||||
if !reflect.DeepEqual(sn, sn2) {
|
||||
t.Fatalf("bad: %#v, but expected %#v", sn2, sn)
|
||||
}
|
||||
require.Equal(t, sn, sn2)
|
||||
}
|
||||
|
||||
func TestStructs_NodeService_ValidateConnectProxy(t *testing.T) {
|
||||
|
@ -232,19 +238,19 @@ func TestStructs_NodeService_ValidateConnectProxy(t *testing.T) {
|
|||
|
||||
{
|
||||
"connect-proxy: no ProxyDestination",
|
||||
func(x *NodeService) { x.ProxyDestination = "" },
|
||||
"ProxyDestination must be",
|
||||
func(x *NodeService) { x.Proxy.DestinationServiceName = "" },
|
||||
"Proxy.DestinationServiceName must be",
|
||||
},
|
||||
|
||||
{
|
||||
"connect-proxy: whitespace ProxyDestination",
|
||||
func(x *NodeService) { x.ProxyDestination = " " },
|
||||
"ProxyDestination must be",
|
||||
func(x *NodeService) { x.Proxy.DestinationServiceName = " " },
|
||||
"Proxy.DestinationServiceName must be",
|
||||
},
|
||||
|
||||
{
|
||||
"connect-proxy: valid ProxyDestination",
|
||||
func(x *NodeService) { x.ProxyDestination = "hello" },
|
||||
func(x *NodeService) { x.Proxy.DestinationServiceName = "hello" },
|
||||
"",
|
||||
},
|
||||
|
||||
|
@ -290,7 +296,12 @@ func TestStructs_NodeService_IsSame(t *testing.T) {
|
|||
},
|
||||
Port: 1234,
|
||||
EnableTagOverride: true,
|
||||
ProxyDestination: "db",
|
||||
Proxy: ConnectProxyConfig{
|
||||
DestinationServiceName: "db",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
if !ns.IsSame(ns) {
|
||||
t.Fatalf("should be equal to itself")
|
||||
|
@ -308,7 +319,12 @@ func TestStructs_NodeService_IsSame(t *testing.T) {
|
|||
"meta2": "value2",
|
||||
"meta1": "value1",
|
||||
},
|
||||
ProxyDestination: "db",
|
||||
Proxy: ConnectProxyConfig{
|
||||
DestinationServiceName: "db",
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
RaftIndex: RaftIndex{
|
||||
CreateIndex: 1,
|
||||
ModifyIndex: 2,
|
||||
|
@ -319,6 +335,7 @@ func TestStructs_NodeService_IsSame(t *testing.T) {
|
|||
}
|
||||
|
||||
check := func(twiddle, restore func()) {
|
||||
t.Helper()
|
||||
if !ns.IsSame(other) || !other.IsSame(ns) {
|
||||
t.Fatalf("should be the same")
|
||||
}
|
||||
|
@ -330,7 +347,7 @@ func TestStructs_NodeService_IsSame(t *testing.T) {
|
|||
|
||||
restore()
|
||||
if !ns.IsSame(other) || !other.IsSame(ns) {
|
||||
t.Fatalf("should be the same")
|
||||
t.Fatalf("should be the same again")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,7 +360,11 @@ func TestStructs_NodeService_IsSame(t *testing.T) {
|
|||
check(func() { other.Meta["meta2"] = "wrongValue" }, func() { other.Meta["meta2"] = "value2" })
|
||||
check(func() { other.EnableTagOverride = false }, func() { other.EnableTagOverride = true })
|
||||
check(func() { other.Kind = ServiceKindConnectProxy }, func() { other.Kind = "" })
|
||||
check(func() { other.ProxyDestination = "" }, func() { other.ProxyDestination = "db" })
|
||||
check(func() { other.Proxy.DestinationServiceName = "" }, func() { other.Proxy.DestinationServiceName = "db" })
|
||||
check(func() { other.Proxy.DestinationServiceID = "XXX" }, func() { other.Proxy.DestinationServiceID = "" })
|
||||
check(func() { other.Proxy.LocalServiceAddress = "XXX" }, func() { other.Proxy.LocalServiceAddress = "" })
|
||||
check(func() { other.Proxy.LocalServicePort = 9999 }, func() { other.Proxy.LocalServicePort = 0 })
|
||||
check(func() { other.Proxy.Config["baz"] = "XXX" }, func() { delete(other.Proxy.Config, "baz") })
|
||||
check(func() { other.Connect.Native = true }, func() { other.Connect.Native = false })
|
||||
}
|
||||
|
||||
|
|
|
@ -41,10 +41,10 @@ func TestNodeService(t testing.T) *NodeService {
|
|||
// Connect proxy.
|
||||
func TestNodeServiceProxy(t testing.T) *NodeService {
|
||||
return &NodeService{
|
||||
Kind: ServiceKindConnectProxy,
|
||||
Service: "connect-proxy",
|
||||
Address: "127.0.0.2",
|
||||
Port: 2222,
|
||||
ProxyDestination: "web",
|
||||
Kind: ServiceKindConnectProxy,
|
||||
Service: "web-proxy",
|
||||
Address: "127.0.0.2",
|
||||
Port: 2222,
|
||||
Proxy: TestConnectProxyConfig(t),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package structs
|
||||
|
||||
import "github.com/mitchellh/go-testing-interface"
|
||||
|
||||
// TestConnectProxyConfig returns a ConnectProxyConfig representing a valid
|
||||
// Connect proxy.
|
||||
func TestConnectProxyConfig(t testing.T) ConnectProxyConfig {
|
||||
return ConnectProxyConfig{
|
||||
DestinationServiceName: "web",
|
||||
Upstreams: TestUpstreams(t),
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpstreams returns a set of upstreams to be used in tests exercising most
|
||||
// important configuration patterns.
|
||||
func TestUpstreams(t testing.T) Upstreams {
|
||||
return Upstreams{
|
||||
{
|
||||
// We rely on this one having default type in a few tests...
|
||||
DestinationName: "db",
|
||||
LocalBindPort: 9191,
|
||||
Config: map[string]interface{}{
|
||||
// Float because this is how it is decoded by JSON decoder so this
|
||||
// enables the value returned to be compared directly to a decoded JSON
|
||||
// response without spurios type loss.
|
||||
"connect_timeout_ms": float64(1000),
|
||||
},
|
||||
},
|
||||
{
|
||||
DestinationType: UpstreamDestTypePreparedQuery,
|
||||
DestinationName: "geo-cache",
|
||||
LocalBindPort: 8181,
|
||||
LocalBindAddress: "127.10.10.10",
|
||||
},
|
||||
}
|
||||
}
|
|
@ -8,13 +8,18 @@ import (
|
|||
func TestServiceDefinition(t testing.T) *ServiceDefinition {
|
||||
return &ServiceDefinition{
|
||||
Name: "db",
|
||||
Port: 1234,
|
||||
}
|
||||
}
|
||||
|
||||
// TestServiceDefinitionProxy returns a ServiceDefinition for a proxy.
|
||||
func TestServiceDefinitionProxy(t testing.T) *ServiceDefinition {
|
||||
return &ServiceDefinition{
|
||||
Kind: ServiceKindConnectProxy,
|
||||
ProxyDestination: "db",
|
||||
Kind: ServiceKindConnectProxy,
|
||||
Name: "foo-proxy",
|
||||
Port: 1234,
|
||||
Proxy: &ConnectProxyConfig{
|
||||
DestinationServiceName: "db",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
64
api/agent.go
64
api/agent.go
|
@ -38,6 +38,18 @@ const (
|
|||
ProxyExecModeScript ProxyExecMode = "script"
|
||||
)
|
||||
|
||||
// UpstreamDestType is the type of upstream discovery mechanism.
|
||||
type UpstreamDestType string
|
||||
|
||||
const (
|
||||
// UpstreamDestTypeService discovers instances via healthy service lookup.
|
||||
UpstreamDestTypeService UpstreamDestType = "service"
|
||||
|
||||
// UpstreamDestTypePreparedQuery discovers instances via prepared query
|
||||
// execution.
|
||||
UpstreamDestTypePreparedQuery UpstreamDestType = "prepared_query"
|
||||
)
|
||||
|
||||
// AgentCheck represents a check known to the agent
|
||||
type AgentCheck struct {
|
||||
Node string
|
||||
|
@ -59,7 +71,7 @@ type AgentWeights struct {
|
|||
|
||||
// AgentService represents a service known to the agent
|
||||
type AgentService struct {
|
||||
Kind ServiceKind
|
||||
Kind ServiceKind `json:",omitempty"`
|
||||
ID string
|
||||
Service string
|
||||
Tags []string
|
||||
|
@ -68,24 +80,38 @@ type AgentService struct {
|
|||
Address string
|
||||
Weights AgentWeights
|
||||
EnableTagOverride bool
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
ProxyDestination string
|
||||
Connect *AgentServiceConnect
|
||||
CreateIndex uint64 `json:",omitempty"`
|
||||
ModifyIndex uint64 `json:",omitempty"`
|
||||
// DEPRECATED (ProxyDestination) - remove this field
|
||||
ProxyDestination string `json:",omitempty"`
|
||||
Proxy *AgentServiceConnectProxyConfig `json:",omitempty"`
|
||||
Connect *AgentServiceConnect `json:",omitempty"`
|
||||
}
|
||||
|
||||
// AgentServiceConnect represents the Connect configuration of a service.
|
||||
type AgentServiceConnect struct {
|
||||
Native bool
|
||||
Proxy *AgentServiceConnectProxy
|
||||
Native bool `json:",omitempty"`
|
||||
Proxy *AgentServiceConnectProxy `json:",omitempty"`
|
||||
}
|
||||
|
||||
// AgentServiceConnectProxy represents the Connect Proxy configuration of a
|
||||
// service.
|
||||
type AgentServiceConnectProxy struct {
|
||||
ExecMode ProxyExecMode
|
||||
Command []string
|
||||
Config map[string]interface{}
|
||||
ExecMode ProxyExecMode `json:",omitempty"`
|
||||
Command []string `json:",omitempty"`
|
||||
Config map[string]interface{} `json:",omitempty"`
|
||||
Upstreams []Upstream `json:",omitempty"`
|
||||
}
|
||||
|
||||
// AgentServiceConnectProxyConfig is the proxy configuration in a connect-proxy
|
||||
// ServiceDefinition or response.
|
||||
type AgentServiceConnectProxyConfig struct {
|
||||
DestinationServiceName string
|
||||
DestinationServiceID string `json:",omitempty"`
|
||||
LocalServiceAddress string `json:",omitempty"`
|
||||
LocalServicePort int `json:",omitempty"`
|
||||
Config map[string]interface{} `json:",omitempty"`
|
||||
Upstreams []Upstream
|
||||
}
|
||||
|
||||
// AgentMember represents a cluster member known to the agent
|
||||
|
@ -129,8 +155,10 @@ type AgentServiceRegistration struct {
|
|||
Weights *AgentWeights `json:",omitempty"`
|
||||
Check *AgentServiceCheck
|
||||
Checks AgentServiceChecks
|
||||
ProxyDestination string `json:",omitempty"`
|
||||
Connect *AgentServiceConnect `json:",omitempty"`
|
||||
// DEPRECATED (ProxyDestination) - remove this field
|
||||
ProxyDestination string `json:",omitempty"`
|
||||
Proxy *AgentServiceConnectProxyConfig `json:",omitempty"`
|
||||
Connect *AgentServiceConnect `json:",omitempty"`
|
||||
}
|
||||
|
||||
// AgentCheckRegistration is used to register a new check
|
||||
|
@ -236,6 +264,18 @@ type ConnectProxyConfig struct {
|
|||
ExecMode ProxyExecMode
|
||||
Command []string
|
||||
Config map[string]interface{}
|
||||
Upstreams []Upstream
|
||||
}
|
||||
|
||||
// Upstream is the response structure for a proxy upstream configuration.
|
||||
type Upstream struct {
|
||||
DestinationType UpstreamDestType `json:",omitempty"`
|
||||
DestinationNamespace string `json:",omitempty"`
|
||||
DestinationName string
|
||||
Datacenter string `json:",omitempty"`
|
||||
LocalBindAddress string `json:",omitempty"`
|
||||
LocalBindPort int `json:",omitempty"`
|
||||
Config map[string]interface{} `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Agent can be used to query the Agent endpoints
|
||||
|
|
|
@ -207,6 +207,11 @@ func TestAPI_AgentServices_ManagedConnectProxy(t *testing.T) {
|
|||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
Upstreams: []Upstream{{
|
||||
DestinationType: "prepared_query",
|
||||
DestinationName: "bar",
|
||||
LocalBindPort: 9191,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -235,7 +240,82 @@ func TestAPI_AgentServices_ManagedConnectProxy(t *testing.T) {
|
|||
t.Fatalf("Bad: %#v", chk)
|
||||
}
|
||||
|
||||
// Proxy config should be present in response
|
||||
// Proxy config should be correct
|
||||
require.Equal(t, reg.Connect, services["foo"].Connect)
|
||||
|
||||
if err := agent.ServiceDeregister("foo"); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPI_AgentServices_ManagedConnectProxyDeprecatedUpstreams(t *testing.T) {
|
||||
t.Parallel()
|
||||
c, s := makeClient(t)
|
||||
defer s.Stop()
|
||||
|
||||
agent := c.Agent()
|
||||
|
||||
reg := &AgentServiceRegistration{
|
||||
Name: "foo",
|
||||
Tags: []string{"bar", "baz"},
|
||||
Port: 8000,
|
||||
Check: &AgentServiceCheck{
|
||||
TTL: "15s",
|
||||
},
|
||||
Connect: &AgentServiceConnect{
|
||||
Proxy: &AgentServiceConnectProxy{
|
||||
ExecMode: ProxyExecModeScript,
|
||||
Command: []string{"foo.rb"},
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"upstreams": []interface{}{
|
||||
map[string]interface{}{
|
||||
"destination_type": "prepared_query",
|
||||
"destination_name": "bar",
|
||||
"local_bind_port": 9191,
|
||||
"connect_timeout_ms": 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := agent.ServiceRegister(reg); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
services, err := agent.Services()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if _, ok := services["foo"]; !ok {
|
||||
t.Fatalf("missing service: %v", services)
|
||||
}
|
||||
checks, err := agent.Checks()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
chk, ok := checks["service:foo"]
|
||||
if !ok {
|
||||
t.Fatalf("missing check: %v", checks)
|
||||
}
|
||||
|
||||
// Checks should default to critical
|
||||
if chk.Status != HealthCritical {
|
||||
t.Fatalf("Bad: %#v", chk)
|
||||
}
|
||||
|
||||
// Proxy config should be present in response, minus the upstreams
|
||||
delete(reg.Connect.Proxy.Config, "upstreams")
|
||||
// Upstreams should be translated into proper field
|
||||
reg.Connect.Proxy.Upstreams = []Upstream{{
|
||||
DestinationType: "prepared_query",
|
||||
DestinationName: "bar",
|
||||
LocalBindPort: 9191,
|
||||
Config: map[string]interface{}{
|
||||
"connect_timeout_ms": float64(1000),
|
||||
},
|
||||
}}
|
||||
require.Equal(t, reg.Connect, services["foo"].Connect)
|
||||
|
||||
if err := agent.ServiceDeregister("foo"); err != nil {
|
||||
|
@ -260,10 +340,12 @@ func TestAPI_AgentServices_ExternalConnectProxy(t *testing.T) {
|
|||
}
|
||||
// Register proxy
|
||||
reg = &AgentServiceRegistration{
|
||||
Kind: ServiceKindConnectProxy,
|
||||
Name: "foo-proxy",
|
||||
Port: 8001,
|
||||
ProxyDestination: "foo",
|
||||
Kind: ServiceKindConnectProxy,
|
||||
Name: "foo-proxy",
|
||||
Port: 8001,
|
||||
Proxy: &AgentServiceConnectProxyConfig{
|
||||
DestinationServiceName: "foo",
|
||||
},
|
||||
}
|
||||
if err := agent.ServiceRegister(reg); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
|
@ -1139,6 +1221,7 @@ func TestAPI_AgentConnectProxyConfig(t *testing.T) {
|
|||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
Upstreams: testUpstreams(t),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1152,15 +1235,16 @@ func TestAPI_AgentConnectProxyConfig(t *testing.T) {
|
|||
ProxyServiceID: "foo-proxy",
|
||||
TargetServiceID: "foo",
|
||||
TargetServiceName: "foo",
|
||||
ContentHash: "2a29f8237db69d0e",
|
||||
ContentHash: "acdf5eb6f5794a14",
|
||||
ExecMode: "daemon",
|
||||
Command: []string{"consul", "connect", "proxy"},
|
||||
Config: map[string]interface{}{
|
||||
"bind_address": "127.0.0.1",
|
||||
"bind_port": float64(20000),
|
||||
"foo": "bar",
|
||||
"bind_address": "127.0.0.1",
|
||||
"bind_port": float64(20000),
|
||||
"foo": "bar",
|
||||
"local_service_address": "127.0.0.1:8000",
|
||||
},
|
||||
Upstreams: testExpectUpstreamsWithDefaults(t, reg.Connect.Proxy.Upstreams),
|
||||
}
|
||||
require.Equal(t, expectConfig, config)
|
||||
require.Equal(t, expectConfig.ContentHash, qm.LastContentHash)
|
||||
|
|
|
@ -31,8 +31,11 @@ type CatalogService struct {
|
|||
ServicePort int
|
||||
ServiceWeights Weights
|
||||
ServiceEnableTagOverride bool
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
// DEPRECATED (ProxyDestination) - remove the next comment!
|
||||
// We forgot to ever add ServiceProxyDestination here so no need to deprecate!
|
||||
ServiceProxy *AgentServiceConnectProxyConfig
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
}
|
||||
|
||||
type CatalogNode struct {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -218,6 +219,43 @@ func TestAPI_CatalogService(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAPI_CatalogServiceUnmanagedProxy(t *testing.T) {
|
||||
t.Parallel()
|
||||
c, s := makeClient(t)
|
||||
defer s.Stop()
|
||||
|
||||
catalog := c.Catalog()
|
||||
|
||||
proxyReg := testUnmanagedProxyRegistration(t)
|
||||
|
||||
retry.Run(t, func(r *retry.R) {
|
||||
_, err := catalog.Register(proxyReg, nil)
|
||||
r.Check(err)
|
||||
|
||||
services, meta, err := catalog.Service("web-proxy", "", nil)
|
||||
if err != nil {
|
||||
r.Fatal(err)
|
||||
}
|
||||
|
||||
if meta.LastIndex == 0 {
|
||||
r.Fatalf("Bad: %v", meta)
|
||||
}
|
||||
|
||||
if len(services) == 0 {
|
||||
r.Fatalf("Bad: %v", services)
|
||||
}
|
||||
|
||||
if services[0].Datacenter != "dc1" {
|
||||
r.Fatalf("Bad datacenter: %v", services[0])
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(services[0].ServiceProxy, proxyReg.Service.Proxy) {
|
||||
r.Fatalf("bad proxy.\nwant: %v\n got: %v", proxyReg.Service.Proxy,
|
||||
services[0].ServiceProxy)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPI_CatalogServiceCached(t *testing.T) {
|
||||
t.Parallel()
|
||||
c, s := makeClient(t)
|
||||
|
@ -286,6 +324,64 @@ func TestAPI_CatalogService_NodeMetaFilter(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func testUpstreams(t *testing.T) []Upstream {
|
||||
return []Upstream{
|
||||
{
|
||||
DestinationName: "db",
|
||||
LocalBindPort: 9191,
|
||||
Config: map[string]interface{}{
|
||||
"connect_timeout_ms": float64(1000),
|
||||
},
|
||||
},
|
||||
{
|
||||
DestinationType: UpstreamDestTypePreparedQuery,
|
||||
DestinationName: "geo-cache",
|
||||
LocalBindPort: 8181,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func testExpectUpstreamsWithDefaults(t *testing.T, upstreams []Upstream) []Upstream {
|
||||
ups := make([]Upstream, len(upstreams))
|
||||
for i := range upstreams {
|
||||
ups[i] = upstreams[i]
|
||||
// Fill in default fields we expect to have back explicitly in a response
|
||||
if ups[i].DestinationType == "" {
|
||||
ups[i].DestinationType = UpstreamDestTypeService
|
||||
}
|
||||
}
|
||||
return ups
|
||||
}
|
||||
|
||||
// testUnmanagedProxy returns a fully configured external proxy service suitable
|
||||
// for checking that all the config fields make it back in a response intact.
|
||||
func testUnmanagedProxy(t *testing.T) *AgentService {
|
||||
return &AgentService{
|
||||
Kind: ServiceKindConnectProxy,
|
||||
Proxy: &AgentServiceConnectProxyConfig{
|
||||
DestinationServiceName: "web",
|
||||
DestinationServiceID: "web1",
|
||||
LocalServiceAddress: "127.0.0.2",
|
||||
LocalServicePort: 8080,
|
||||
Upstreams: testUpstreams(t),
|
||||
},
|
||||
ID: "web-proxy1",
|
||||
Service: "web-proxy",
|
||||
Port: 8001,
|
||||
}
|
||||
}
|
||||
|
||||
// testUnmanagedProxyRegistration returns a *CatalogRegistration for a fully
|
||||
// configured external proxy.
|
||||
func testUnmanagedProxyRegistration(t *testing.T) *CatalogRegistration {
|
||||
return &CatalogRegistration{
|
||||
Datacenter: "dc1",
|
||||
Node: "foobar",
|
||||
Address: "192.168.10.10",
|
||||
Service: testUnmanagedProxy(t),
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPI_CatalogConnect(t *testing.T) {
|
||||
t.Parallel()
|
||||
c, s := makeClient(t)
|
||||
|
@ -294,25 +390,27 @@ func TestAPI_CatalogConnect(t *testing.T) {
|
|||
catalog := c.Catalog()
|
||||
|
||||
// Register service and proxy instances to test against.
|
||||
proxyReg := testUnmanagedProxyRegistration(t)
|
||||
|
||||
proxy := proxyReg.Service
|
||||
|
||||
// DEPRECATED (ProxyDestination) - remove this case when the field is removed
|
||||
deprecatedProxyReg := testUnmanagedProxyRegistration(t)
|
||||
deprecatedProxyReg.Service.ProxyDestination = deprecatedProxyReg.Service.Proxy.DestinationServiceName
|
||||
deprecatedProxyReg.Service.Proxy = nil
|
||||
|
||||
service := &AgentService{
|
||||
ID: "redis1",
|
||||
Service: "redis",
|
||||
ID: proxyReg.Service.Proxy.DestinationServiceID,
|
||||
Service: proxyReg.Service.Proxy.DestinationServiceName,
|
||||
Port: 8000,
|
||||
}
|
||||
proxy := &AgentService{
|
||||
Kind: ServiceKindConnectProxy,
|
||||
ProxyDestination: "redis",
|
||||
ID: "redis-proxy1",
|
||||
Service: "redis-proxy",
|
||||
Port: 8001,
|
||||
}
|
||||
check := &AgentCheck{
|
||||
Node: "foobar",
|
||||
CheckID: "service:redis1",
|
||||
CheckID: "service:" + service.ID,
|
||||
Name: "Redis health check",
|
||||
Notes: "Script based health check",
|
||||
Status: HealthPassing,
|
||||
ServiceID: "redis1",
|
||||
ServiceID: service.ID,
|
||||
}
|
||||
|
||||
reg := &CatalogRegistration{
|
||||
|
@ -322,22 +420,20 @@ func TestAPI_CatalogConnect(t *testing.T) {
|
|||
Service: service,
|
||||
Check: check,
|
||||
}
|
||||
proxyReg := &CatalogRegistration{
|
||||
Datacenter: "dc1",
|
||||
Node: "foobar",
|
||||
Address: "192.168.10.10",
|
||||
Service: proxy,
|
||||
}
|
||||
|
||||
retry.Run(t, func(r *retry.R) {
|
||||
if _, err := catalog.Register(reg, nil); err != nil {
|
||||
r.Fatal(err)
|
||||
}
|
||||
// First try to register deprecated proxy, shouldn't error
|
||||
if _, err := catalog.Register(deprecatedProxyReg, nil); err != nil {
|
||||
r.Fatal(err)
|
||||
}
|
||||
if _, err := catalog.Register(proxyReg, nil); err != nil {
|
||||
r.Fatal(err)
|
||||
}
|
||||
|
||||
services, meta, err := catalog.Connect("redis", "", nil)
|
||||
services, meta, err := catalog.Connect(proxyReg.Service.Proxy.DestinationServiceName, "", nil)
|
||||
if err != nil {
|
||||
r.Fatal(err)
|
||||
}
|
||||
|
@ -357,8 +453,12 @@ func TestAPI_CatalogConnect(t *testing.T) {
|
|||
if services[0].ServicePort != proxy.Port {
|
||||
r.Fatalf("Returned port should be for proxy: %v", services[0])
|
||||
}
|
||||
})
|
||||
|
||||
if !reflect.DeepEqual(services[0].ServiceProxy, proxy.Proxy) {
|
||||
r.Fatalf("Returned proxy config should match:\nWant: %v\n Got: %v",
|
||||
proxy.Proxy, services[0].ServiceProxy)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPI_CatalogConnectNative(t *testing.T) {
|
||||
|
@ -426,8 +526,19 @@ func TestAPI_CatalogNode(t *testing.T) {
|
|||
defer s.Stop()
|
||||
|
||||
catalog := c.Catalog()
|
||||
name, _ := c.Agent().NodeName()
|
||||
|
||||
name, err := c.Agent().NodeName()
|
||||
require.NoError(t, err)
|
||||
|
||||
proxyReg := testUnmanagedProxyRegistration(t)
|
||||
proxyReg.Node = name
|
||||
proxyReg.SkipNodeUpdate = true
|
||||
|
||||
retry.Run(t, func(r *retry.R) {
|
||||
// Register a connect proxy to ensure all it's config fields are returned
|
||||
_, err := catalog.Register(proxyReg, nil)
|
||||
r.Check(err)
|
||||
|
||||
info, meta, err := catalog.Node(name, nil)
|
||||
if err != nil {
|
||||
r.Fatal(err)
|
||||
|
@ -437,17 +548,26 @@ func TestAPI_CatalogNode(t *testing.T) {
|
|||
r.Fatalf("Bad: %v", meta)
|
||||
}
|
||||
|
||||
if len(info.Services) == 0 {
|
||||
r.Fatalf("Bad: %v", info)
|
||||
if len(info.Services) != 2 {
|
||||
r.Fatalf("Bad: %v (len %d)", info, len(info.Services))
|
||||
}
|
||||
|
||||
if _, ok := info.Node.TaggedAddresses["wan"]; !ok {
|
||||
r.Fatalf("Bad: %v", info)
|
||||
r.Fatalf("Bad: %v", info.Node.TaggedAddresses)
|
||||
}
|
||||
|
||||
if info.Node.Datacenter != "dc1" {
|
||||
r.Fatalf("Bad datacenter: %v", info)
|
||||
}
|
||||
|
||||
if _, ok := info.Services["web-proxy1"]; !ok {
|
||||
r.Fatalf("Missing proxy service: %v", info.Services)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(proxyReg.Service.Proxy, info.Services["web-proxy1"].Proxy) {
|
||||
r.Fatalf("Bad proxy config:\nwant %v\n got: %v", proxyReg.Service.Proxy,
|
||||
info.Services["web-proxy"].Proxy)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -484,11 +604,13 @@ func TestAPI_CatalogRegistration(t *testing.T) {
|
|||
}
|
||||
// Register a connect proxy for that service too
|
||||
proxy := &AgentService{
|
||||
ID: "redis-proxy1",
|
||||
Service: "redis-proxy",
|
||||
Port: 8001,
|
||||
Kind: ServiceKindConnectProxy,
|
||||
ProxyDestination: service.ID,
|
||||
ID: "redis-proxy1",
|
||||
Service: "redis-proxy",
|
||||
Port: 8001,
|
||||
Kind: ServiceKindConnectProxy,
|
||||
Proxy: &AgentServiceConnectProxyConfig{
|
||||
DestinationServiceName: service.Service,
|
||||
},
|
||||
}
|
||||
proxyReg := &CatalogRegistration{
|
||||
Datacenter: "dc1",
|
||||
|
|
|
@ -302,10 +302,12 @@ func TestAPI_HealthConnect(t *testing.T) {
|
|||
|
||||
// Register the proxy
|
||||
proxyReg := &AgentServiceRegistration{
|
||||
Name: "foo-proxy",
|
||||
Port: 8001,
|
||||
Kind: ServiceKindConnectProxy,
|
||||
ProxyDestination: "foo",
|
||||
Name: "foo-proxy",
|
||||
Port: 8001,
|
||||
Kind: ServiceKindConnectProxy,
|
||||
Proxy: &AgentServiceConnectProxyConfig{
|
||||
DestinationServiceName: "foo",
|
||||
},
|
||||
}
|
||||
err = agent.ServiceRegister(proxyReg)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/connect/proxy"
|
||||
)
|
||||
|
||||
|
@ -64,7 +65,7 @@ func (f *FlagUpstreams) Set(value string) error {
|
|||
LocalBindAddress: addr,
|
||||
LocalBindPort: int(port),
|
||||
DestinationName: name,
|
||||
DestinationType: destinationType,
|
||||
DestinationType: api.UpstreamDestType(destinationType),
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -66,15 +66,6 @@ type cmd struct {
|
|||
func (c *cmd) init() {
|
||||
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
|
||||
|
||||
c.flags.StringVar(&c.cfgFile, "dev-config", "",
|
||||
"If set, proxy config is read on startup from this file (in HCL or JSON"+
|
||||
"format). If a config file is given, the proxy will use that instead of "+
|
||||
"querying the local agent for it's configuration. It will not reload it "+
|
||||
"except on startup. In this mode the proxy WILL NOT authorize incoming "+
|
||||
"connections with the local agent which is totally insecure. This is "+
|
||||
"ONLY for internal development and testing and will probably be removed "+
|
||||
"once proxy implementation is more complete..")
|
||||
|
||||
c.flags.StringVar(&c.proxyID, "proxy-id", "",
|
||||
"The proxy's ID on the local agent.")
|
||||
|
||||
|
@ -210,17 +201,6 @@ func (c *cmd) Run(args []string) int {
|
|||
}
|
||||
|
||||
func (c *cmd) configWatcher(client *api.Client) (proxyImpl.ConfigWatcher, error) {
|
||||
// Manual configuration file is specified.
|
||||
if c.cfgFile != "" {
|
||||
cfg, err := proxyImpl.ParseConfigFile(c.cfgFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.UI.Info("Configuration mode: File")
|
||||
return proxyImpl.NewStaticConfigWatcher(cfg), nil
|
||||
}
|
||||
|
||||
// Use the configured proxy ID
|
||||
if c.proxyID != "" {
|
||||
c.UI.Info("Configuration mode: Agent API")
|
||||
|
|
|
@ -196,12 +196,14 @@ func (r *RegisterMonitor) register() {
|
|||
|
||||
// If we're here, then we're registering the service.
|
||||
err = r.Client.Agent().ServiceRegister(&api.AgentServiceRegistration{
|
||||
Kind: api.ServiceKindConnectProxy,
|
||||
ProxyDestination: r.Service,
|
||||
ID: serviceID,
|
||||
Name: serviceName,
|
||||
Address: r.LocalAddress,
|
||||
Port: r.LocalPort,
|
||||
Kind: api.ServiceKindConnectProxy,
|
||||
ID: serviceID,
|
||||
Name: serviceName,
|
||||
Address: r.LocalAddress,
|
||||
Port: r.LocalPort,
|
||||
Proxy: &api.AgentServiceConnectProxyConfig{
|
||||
DestinationServiceName: r.Service,
|
||||
},
|
||||
Check: &api.AgentServiceCheck{
|
||||
CheckID: r.checkID(),
|
||||
Name: "proxy heartbeat",
|
||||
|
|
|
@ -24,7 +24,7 @@ func TestRegisterMonitor_good(t *testing.T) {
|
|||
|
||||
// Verify the settings
|
||||
require.Equal(api.ServiceKindConnectProxy, service.Kind)
|
||||
require.Equal("foo", service.ProxyDestination)
|
||||
require.Equal("foo", service.Proxy.DestinationServiceName)
|
||||
require.Equal("127.0.0.1", service.Address)
|
||||
require.Equal(1234, service.Port)
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ package proxy
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/hashicorp/consul/connect"
|
||||
"github.com/hashicorp/consul/lib"
|
||||
"github.com/hashicorp/consul/watch"
|
||||
"github.com/hashicorp/hcl"
|
||||
)
|
||||
|
||||
// Config is the publicly configurable state for an entire proxy instance. It's
|
||||
|
@ -85,43 +84,21 @@ func (plc *PublicListenerConfig) applyDefaults() {
|
|||
}
|
||||
}
|
||||
|
||||
// UpstreamConfig configures an upstream (outgoing) listener.
|
||||
type UpstreamConfig struct {
|
||||
// LocalAddress is the host/ip to listen on for local app connections. Defaults to 127.0.0.1.
|
||||
LocalBindAddress string `json:"local_bind_address" hcl:"local_bind_address,attr" mapstructure:"local_bind_address"`
|
||||
// UpstreamConfig is an alias for api.Upstream so we can parse in a compatible
|
||||
// way but define custom methods for accessing the opaque config metadata.
|
||||
type UpstreamConfig api.Upstream
|
||||
|
||||
LocalBindPort int `json:"local_bind_port" hcl:"local_bind_port,attr" mapstructure:"local_bind_port"`
|
||||
|
||||
// DestinationName is the service name of the destination.
|
||||
DestinationName string `json:"destination_name" hcl:"destination_name,attr" mapstructure:"destination_name"`
|
||||
|
||||
// DestinationNamespace is the namespace of the destination.
|
||||
DestinationNamespace string `json:"destination_namespace" hcl:"destination_namespace,attr" mapstructure:"destination_namespace"`
|
||||
|
||||
// DestinationType determines which service discovery method is used to find a
|
||||
// candidate instance to connect to.
|
||||
DestinationType string `json:"destination_type" hcl:"destination_type,attr" mapstructure:"destination_type"`
|
||||
|
||||
// DestinationDatacenter is the datacenter the destination is in. If empty,
|
||||
// defaults to discovery within the same datacenter.
|
||||
DestinationDatacenter string `json:"destination_datacenter" hcl:"destination_datacenter,attr" mapstructure:"destination_datacenter"`
|
||||
|
||||
// ConnectTimeout is the timeout for establishing connections with the remote
|
||||
// service instance. Defaults to 10,000 (10s).
|
||||
ConnectTimeoutMs int `json:"connect_timeout_ms" hcl:"connect_timeout_ms,attr" mapstructure:"connect_timeout_ms"`
|
||||
|
||||
// resolver is used to plug in the service discover mechanism. It can be used
|
||||
// in tests to bypass discovery. In real usage it is used to inject the
|
||||
// api.Client dependency from the remainder of the config struct parsed from
|
||||
// the user JSON using the UpstreamResolverFromClient helper.
|
||||
resolver connect.Resolver
|
||||
// ConnectTimeout returns the connect timeout field of the nested config struct
|
||||
// or the default value.
|
||||
func (uc *UpstreamConfig) ConnectTimeout() time.Duration {
|
||||
if ms, ok := uc.Config["connect_timeout_ms"].(int); ok {
|
||||
return time.Duration(ms) * time.Millisecond
|
||||
}
|
||||
return 10000 * time.Millisecond
|
||||
}
|
||||
|
||||
// applyDefaults sets zero-valued params to a sane default.
|
||||
func (uc *UpstreamConfig) applyDefaults() {
|
||||
if uc.ConnectTimeoutMs == 0 {
|
||||
uc.ConnectTimeoutMs = 10000
|
||||
}
|
||||
if uc.DestinationType == "" {
|
||||
uc.DestinationType = "service"
|
||||
}
|
||||
|
@ -140,25 +117,26 @@ func (uc *UpstreamConfig) String() string {
|
|||
uc.DestinationType, uc.DestinationNamespace, uc.DestinationName)
|
||||
}
|
||||
|
||||
// UpstreamResolverFromClient returns a ConsulResolver that can resolve the
|
||||
// given UpstreamConfig using the provided api.Client dependency.
|
||||
func UpstreamResolverFromClient(client *api.Client,
|
||||
cfg UpstreamConfig) connect.Resolver {
|
||||
|
||||
// For now default to service as it has the most natural meaning and the error
|
||||
// that the service doesn't exist is probably reasonable if misconfigured. We
|
||||
// should probably handle actual configs that have invalid types at a higher
|
||||
// level anyway (like when parsing).
|
||||
typ := connect.ConsulResolverTypeService
|
||||
if cfg.DestinationType == "prepared_query" {
|
||||
typ = connect.ConsulResolverTypePreparedQuery
|
||||
}
|
||||
return &connect.ConsulResolver{
|
||||
Client: client,
|
||||
Namespace: cfg.DestinationNamespace,
|
||||
Name: cfg.DestinationName,
|
||||
Type: typ,
|
||||
Datacenter: cfg.DestinationDatacenter,
|
||||
// UpstreamResolverFuncFromClient returns a closure that captures a consul
|
||||
// client and when called provides aConsulResolver that can resolve the given
|
||||
// UpstreamConfig using the provided api.Client dependency.
|
||||
func UpstreamResolverFuncFromClient(client *api.Client) func(cfg UpstreamConfig) (connect.Resolver, error) {
|
||||
return func(cfg UpstreamConfig) (connect.Resolver, error) {
|
||||
// For now default to service as it has the most natural meaning and the error
|
||||
// that the service doesn't exist is probably reasonable if misconfigured. We
|
||||
// should probably handle actual configs that have invalid types at a higher
|
||||
// level anyway (like when parsing).
|
||||
typ := connect.ConsulResolverTypeService
|
||||
if cfg.DestinationType == "prepared_query" {
|
||||
typ = connect.ConsulResolverTypePreparedQuery
|
||||
}
|
||||
return &connect.ConsulResolver{
|
||||
Client: client,
|
||||
Namespace: cfg.DestinationNamespace,
|
||||
Name: cfg.DestinationName,
|
||||
Type: typ,
|
||||
Datacenter: cfg.Datacenter,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -195,28 +173,6 @@ func (sc *StaticConfigWatcher) Watch() <-chan *Config {
|
|||
return sc.ch
|
||||
}
|
||||
|
||||
// ParseConfigFile parses proxy configuration from a file for local dev.
|
||||
func ParseConfigFile(filename string) (*Config, error) {
|
||||
bs, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
|
||||
err = hcl.Unmarshal(bs, &cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg.PublicListener.applyDefaults()
|
||||
for idx := range cfg.Upstreams {
|
||||
cfg.Upstreams[idx].applyDefaults()
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// AgentConfigWatcher watches the local Consul agent for proxy config changes.
|
||||
type AgentConfigWatcher struct {
|
||||
client *api.Client
|
||||
|
@ -282,14 +238,10 @@ func (w *AgentConfigWatcher) handler(blockVal watch.BlockingParamVal,
|
|||
}
|
||||
cfg.PublicListener.applyDefaults()
|
||||
|
||||
err = mapstructure.Decode(resp.Config["upstreams"], &cfg.Upstreams)
|
||||
if err != nil {
|
||||
w.logger.Printf("[ERR] proxy config watch upstream listener config "+
|
||||
"couldn't be parsed: %s", err)
|
||||
return
|
||||
}
|
||||
for i := range cfg.Upstreams {
|
||||
cfg.Upstreams[i].applyDefaults()
|
||||
for _, u := range resp.Upstreams {
|
||||
uc := UpstreamConfig(u)
|
||||
uc.applyDefaults()
|
||||
cfg.Upstreams = append(cfg.Upstreams, uc)
|
||||
}
|
||||
|
||||
// Parsed config OK, deliver it!
|
||||
|
|
|
@ -14,45 +14,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseConfigFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg, err := ParseConfigFile("testdata/config-kitchensink.hcl")
|
||||
require.Nil(t, err)
|
||||
|
||||
expect := &Config{
|
||||
Token: "11111111-2222-3333-4444-555555555555",
|
||||
ProxiedServiceName: "web",
|
||||
ProxiedServiceNamespace: "default",
|
||||
PublicListener: PublicListenerConfig{
|
||||
BindAddress: "127.0.0.1",
|
||||
BindPort: 9999,
|
||||
LocalServiceAddress: "127.0.0.1:5000",
|
||||
LocalConnectTimeoutMs: 1000,
|
||||
HandshakeTimeoutMs: 10000, // From defaults
|
||||
},
|
||||
Upstreams: []UpstreamConfig{
|
||||
{
|
||||
LocalBindAddress: "127.0.0.1:6000",
|
||||
DestinationName: "db",
|
||||
DestinationNamespace: "default",
|
||||
DestinationType: "service",
|
||||
ConnectTimeoutMs: 10000,
|
||||
},
|
||||
{
|
||||
LocalBindAddress: "127.0.0.1:6001",
|
||||
DestinationName: "geo-cache",
|
||||
DestinationNamespace: "default",
|
||||
DestinationType: "prepared_query",
|
||||
ConnectTimeoutMs: 10000,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
require.Equal(t, expect, cfg)
|
||||
}
|
||||
|
||||
func TestUpstreamResolverFromClient(t *testing.T) {
|
||||
func TestUpstreamResolverFuncFromClient(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
|
@ -63,10 +25,10 @@ func TestUpstreamResolverFromClient(t *testing.T) {
|
|||
{
|
||||
name: "service",
|
||||
cfg: UpstreamConfig{
|
||||
DestinationNamespace: "foo",
|
||||
DestinationName: "web",
|
||||
DestinationDatacenter: "ny1",
|
||||
DestinationType: "service",
|
||||
DestinationNamespace: "foo",
|
||||
DestinationName: "web",
|
||||
Datacenter: "ny1",
|
||||
DestinationType: "service",
|
||||
},
|
||||
want: &connect.ConsulResolver{
|
||||
Namespace: "foo",
|
||||
|
@ -78,10 +40,10 @@ func TestUpstreamResolverFromClient(t *testing.T) {
|
|||
{
|
||||
name: "prepared_query",
|
||||
cfg: UpstreamConfig{
|
||||
DestinationNamespace: "foo",
|
||||
DestinationName: "web",
|
||||
DestinationDatacenter: "ny1",
|
||||
DestinationType: "prepared_query",
|
||||
DestinationNamespace: "foo",
|
||||
DestinationName: "web",
|
||||
Datacenter: "ny1",
|
||||
DestinationType: "prepared_query",
|
||||
},
|
||||
want: &connect.ConsulResolver{
|
||||
Namespace: "foo",
|
||||
|
@ -93,10 +55,10 @@ func TestUpstreamResolverFromClient(t *testing.T) {
|
|||
{
|
||||
name: "unknown behaves like service",
|
||||
cfg: UpstreamConfig{
|
||||
DestinationNamespace: "foo",
|
||||
DestinationName: "web",
|
||||
DestinationDatacenter: "ny1",
|
||||
DestinationType: "junk",
|
||||
DestinationNamespace: "foo",
|
||||
DestinationName: "web",
|
||||
Datacenter: "ny1",
|
||||
DestinationType: "junk",
|
||||
},
|
||||
want: &connect.ConsulResolver{
|
||||
Namespace: "foo",
|
||||
|
@ -109,7 +71,9 @@ func TestUpstreamResolverFromClient(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Client doesn't really matter as long as it's passed through.
|
||||
got := UpstreamResolverFromClient(nil, tt.cfg)
|
||||
gotFn := UpstreamResolverFuncFromClient(nil)
|
||||
got, err := gotFn(tt.cfg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
|
@ -143,11 +107,11 @@ func TestAgentConfigWatcher(t *testing.T) {
|
|||
"bind_port": 1010,
|
||||
"local_service_address": "127.0.0.1:5000",
|
||||
"handshake_timeout_ms": 999,
|
||||
"upstreams": []interface{}{
|
||||
map[string]interface{}{
|
||||
"destination_name": "db",
|
||||
"local_bind_port": 9191,
|
||||
},
|
||||
},
|
||||
Upstreams: []api.Upstream{
|
||||
{
|
||||
DestinationName: "db",
|
||||
LocalBindPort: 9191,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -179,7 +143,6 @@ func TestAgentConfigWatcher(t *testing.T) {
|
|||
DestinationType: "service",
|
||||
LocalBindPort: 9191,
|
||||
LocalBindAddress: "127.0.0.1",
|
||||
ConnectTimeoutMs: 10000, // from applyDefaults
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -190,13 +153,12 @@ func TestAgentConfigWatcher(t *testing.T) {
|
|||
go func() {
|
||||
// Wait for watcher to be watching
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
upstreams := reg.Connect.Proxy.Config["upstreams"].([]interface{})
|
||||
upstreams = append(upstreams, map[string]interface{}{
|
||||
"destination_name": "cache",
|
||||
"local_bind_port": 9292,
|
||||
"local_bind_address": "127.10.10.10",
|
||||
})
|
||||
reg.Connect.Proxy.Config["upstreams"] = upstreams
|
||||
reg.Connect.Proxy.Upstreams = append(reg.Connect.Proxy.Upstreams,
|
||||
api.Upstream{
|
||||
DestinationName: "cache",
|
||||
LocalBindPort: 9292,
|
||||
LocalBindAddress: "127.10.10.10",
|
||||
})
|
||||
reg.Connect.Proxy.Config["local_connect_timeout_ms"] = 444
|
||||
err := agent.ServiceRegister(reg)
|
||||
require.NoError(t, err)
|
||||
|
@ -208,7 +170,6 @@ func TestAgentConfigWatcher(t *testing.T) {
|
|||
DestinationName: "cache",
|
||||
DestinationNamespace: "default",
|
||||
DestinationType: "service",
|
||||
ConnectTimeoutMs: 10000, // from applyDefaults
|
||||
LocalBindPort: 9292,
|
||||
LocalBindAddress: "127.10.10.10",
|
||||
})
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"time"
|
||||
|
||||
metrics "github.com/armon/go-metrics"
|
||||
"github.com/hashicorp/consul/api"
|
||||
"github.com/hashicorp/consul/connect"
|
||||
)
|
||||
|
||||
|
@ -27,7 +28,7 @@ type Listener struct {
|
|||
// Service is the connect service instance to use.
|
||||
Service *connect.Service
|
||||
|
||||
// listenFunc, dialFunc and bindAddr are set by type-specific constructors
|
||||
// listenFunc, dialFunc, and bindAddr are set by type-specific constructors.
|
||||
listenFunc func() (net.Listener, error)
|
||||
dialFunc func() (net.Conn, error)
|
||||
bindAddr string
|
||||
|
@ -86,7 +87,14 @@ func NewPublicListener(svc *connect.Service, cfg PublicListenerConfig,
|
|||
|
||||
// NewUpstreamListener returns a Listener setup to listen locally for TCP
|
||||
// connections that are proxied to a discovered Connect service instance.
|
||||
func NewUpstreamListener(svc *connect.Service, cfg UpstreamConfig,
|
||||
func NewUpstreamListener(svc *connect.Service, client *api.Client,
|
||||
cfg UpstreamConfig, logger *log.Logger) *Listener {
|
||||
return newUpstreamListenerWithResolver(svc, cfg,
|
||||
UpstreamResolverFuncFromClient(client), logger)
|
||||
}
|
||||
|
||||
func newUpstreamListenerWithResolver(svc *connect.Service, cfg UpstreamConfig,
|
||||
resolverFunc func(UpstreamConfig) (connect.Resolver, error),
|
||||
logger *log.Logger) *Listener {
|
||||
bindAddr := fmt.Sprintf("%s:%d", cfg.LocalBindAddress, cfg.LocalBindPort)
|
||||
return &Listener{
|
||||
|
@ -95,13 +103,14 @@ func NewUpstreamListener(svc *connect.Service, cfg UpstreamConfig,
|
|||
return net.Listen("tcp", bindAddr)
|
||||
},
|
||||
dialFunc: func() (net.Conn, error) {
|
||||
if cfg.resolver == nil {
|
||||
return nil, errors.New("no resolver provided")
|
||||
rf, err := resolverFunc(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(),
|
||||
time.Duration(cfg.ConnectTimeoutMs)*time.Millisecond)
|
||||
cfg.ConnectTimeout())
|
||||
defer cancel()
|
||||
return svc.Dial(ctx, cfg.resolver)
|
||||
return svc.Dial(ctx, rf)
|
||||
},
|
||||
bindAddr: bindAddr,
|
||||
stopChan: make(chan struct{}),
|
||||
|
@ -111,7 +120,7 @@ func NewUpstreamListener(svc *connect.Service, cfg UpstreamConfig,
|
|||
metricLabels: []metrics.Label{
|
||||
{Name: "src", Value: svc.Name()},
|
||||
// TODO(banks): namespace support
|
||||
{Name: "dst_type", Value: cfg.DestinationType},
|
||||
{Name: "dst_type", Value: string(cfg.DestinationType)},
|
||||
{Name: "dst", Value: cfg.DestinationName},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -176,13 +176,9 @@ func TestUpstreamListener(t *testing.T) {
|
|||
DestinationType: "service",
|
||||
DestinationNamespace: "default",
|
||||
DestinationName: "db",
|
||||
ConnectTimeoutMs: 100,
|
||||
Config: map[string]interface{}{"connect_timeout_ms": 100},
|
||||
LocalBindAddress: "localhost",
|
||||
LocalBindPort: ports[0],
|
||||
resolver: &connect.StaticResolver{
|
||||
Addr: testSvr.Addr,
|
||||
CertURI: agConnect.TestSpiffeIDService(t, "db"),
|
||||
},
|
||||
}
|
||||
|
||||
// Setup metrics to test they are recorded
|
||||
|
@ -190,7 +186,12 @@ func TestUpstreamListener(t *testing.T) {
|
|||
|
||||
svc := connect.TestService(t, "web", ca)
|
||||
|
||||
l := NewUpstreamListener(svc, cfg, log.New(os.Stderr, "", log.LstdFlags))
|
||||
// Setup with a statuc resolver instead
|
||||
rf := TestStaticUpstreamResolverFunc(&connect.StaticResolver{
|
||||
Addr: testSvr.Addr,
|
||||
CertURI: agConnect.TestSpiffeIDService(t, "db"),
|
||||
})
|
||||
l := newUpstreamListenerWithResolver(svc, cfg, rf, log.New(os.Stderr, "", log.LstdFlags))
|
||||
|
||||
// Run proxy
|
||||
go func() {
|
||||
|
|
|
@ -96,7 +96,6 @@ func (p *Proxy) Serve() error {
|
|||
// start one of each and stop/modify if changes occur.
|
||||
for _, uc := range newCfg.Upstreams {
|
||||
uc.applyDefaults()
|
||||
uc.resolver = UpstreamResolverFromClient(p.client, uc)
|
||||
|
||||
if uc.LocalBindPort < 1 {
|
||||
p.logger.Printf("[ERR] upstream %s has no local_bind_port. "+
|
||||
|
@ -104,7 +103,7 @@ func (p *Proxy) Serve() error {
|
|||
continue
|
||||
}
|
||||
|
||||
l := NewUpstreamListener(p.service, uc, p.logger)
|
||||
l := NewUpstreamListener(p.service, p.client, uc, p.logger)
|
||||
err := p.startListener(uc.String(), l)
|
||||
if err != nil {
|
||||
p.logger.Printf("[ERR] failed to start upstream %s: %s", uc.String(),
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
# Example proxy config with everything specified
|
||||
|
||||
token = "11111111-2222-3333-4444-555555555555"
|
||||
|
||||
proxied_service_name = "web"
|
||||
proxied_service_namespace = "default"
|
||||
|
||||
public_listener {
|
||||
bind_address = "127.0.0.1"
|
||||
bind_port= "9999"
|
||||
local_service_address = "127.0.0.1:5000"
|
||||
}
|
||||
|
||||
upstreams = [
|
||||
{
|
||||
local_bind_address = "127.0.0.1:6000"
|
||||
destination_name = "db"
|
||||
destination_namespace = "default"
|
||||
destination_type = "service"
|
||||
},
|
||||
{
|
||||
local_bind_address = "127.0.0.1:6001"
|
||||
destination_name = "geo-cache"
|
||||
destination_namespace = "default"
|
||||
destination_type = "prepared_query"
|
||||
}
|
||||
]
|
|
@ -8,6 +8,7 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/consul/connect"
|
||||
"github.com/hashicorp/consul/lib/freeport"
|
||||
"github.com/mitchellh/go-testing-interface"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -110,3 +111,11 @@ func TestEchoConn(t testing.T, conn net.Conn, prefix string) {
|
|||
// see PR #4498
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
// TestStaticUpstreamResolverFunc returns a function that will return a static
|
||||
// resolver for testing UpstreamListener.
|
||||
func TestStaticUpstreamResolverFunc(r connect.Resolver) func(UpstreamConfig) (connect.Resolver, error) {
|
||||
return func(UpstreamConfig) (connect.Resolver, error) {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -170,7 +170,7 @@ func (cr *ConsulResolver) resolveServiceEntry(entry *api.ServiceEntry) (string,
|
|||
}
|
||||
port := entry.Service.Port
|
||||
|
||||
service := entry.Service.ProxyDestination
|
||||
service := entry.Service.Proxy.DestinationServiceName
|
||||
if entry.Service.Connect != nil && entry.Service.Connect.Native {
|
||||
service = entry.Service.Service
|
||||
}
|
||||
|
|
|
@ -59,10 +59,12 @@ func TestConsulResolver_Resolve(t *testing.T) {
|
|||
require.Nil(t, err)
|
||||
|
||||
regProxy := &api.AgentServiceRegistration{
|
||||
Kind: "connect-proxy",
|
||||
Name: "web-proxy",
|
||||
Port: 9090,
|
||||
ProxyDestination: "web",
|
||||
Kind: "connect-proxy",
|
||||
Name: "web-proxy",
|
||||
Port: 9090,
|
||||
Proxy: &api.AgentServiceConnectProxyConfig{
|
||||
DestinationServiceName: "web",
|
||||
},
|
||||
}
|
||||
err = client.Agent().ServiceRegister(regProxy)
|
||||
require.Nil(t, err)
|
||||
|
|
|
@ -275,7 +275,22 @@ $ curl \
|
|||
"bind_address": "127.0.0.1",
|
||||
"bind_port": 20199,
|
||||
"local_service_address": "127.0.0.1:8181"
|
||||
}
|
||||
},
|
||||
"Upstreams": [
|
||||
{
|
||||
"DestinationType": "service",
|
||||
"DestinationName": "db",
|
||||
"LocalBindPort": 1234,
|
||||
"Config": {
|
||||
"connect_timeout_ms": 1000
|
||||
}
|
||||
},
|
||||
{
|
||||
"DestinationType": "prepared_query",
|
||||
"DestinationName": "geo-cache",
|
||||
"LocalBindPort": 1235
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -295,3 +310,7 @@ $ curl \
|
|||
- `Config` `(map<string|any>)` - The configuration for the managed proxy. This
|
||||
is a map of primitive values (including arrays and maps) that is set by the
|
||||
user.
|
||||
|
||||
- `Upstreams` `(array<Upstream>)` - The configured upstreams for the proxy. See
|
||||
[Upstream Configuration Reference](/docs/connect/proxies.html#upstream-configuration-reference)
|
||||
for more details on the format.
|
||||
|
|
|
@ -50,14 +50,15 @@ $ curl \
|
|||
```json
|
||||
{
|
||||
"redis": {
|
||||
"ID": "redis",
|
||||
"Service": "redis",
|
||||
"Tags": [],
|
||||
"Address": "",
|
||||
"Meta": {
|
||||
"redis_version": "4.0"
|
||||
},
|
||||
"Port": 8000
|
||||
"ID": "redis",
|
||||
"Service": "redis",
|
||||
"Tags": [],
|
||||
"Meta": {
|
||||
"redis_version": "4.0"
|
||||
},
|
||||
"Port": 8000,
|
||||
"Address": "",
|
||||
"EnableTagOverride": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -72,7 +73,7 @@ sending updates about its local services to the servers to keep the global
|
|||
catalog in sync.
|
||||
|
||||
For "connect-proxy" kind services, the `service:write` ACL for the
|
||||
`ProxyDestination` value is also required to register the service.
|
||||
`Proxy.DestinationServiceName` value is also required to register the service.
|
||||
|
||||
| Method | Path | Produces |
|
||||
| ------ | ---------------------------- | -------------------------- |
|
||||
|
@ -90,6 +91,9 @@ The table below shows this endpoint's support for
|
|||
|
||||
### Parameters
|
||||
|
||||
Note that this endpoint, unlike most also [supports `snake_case`](/docs/agent/services.html#service-definition-parameter-case)
|
||||
service definition keys for compatibility with the config file format.
|
||||
|
||||
- `Name` `(string: <required>)` - Specifies the logical name of the service.
|
||||
Many service instances may share the same logical service name.
|
||||
|
||||
|
@ -114,14 +118,20 @@ The table below shows this endpoint's support for
|
|||
services that are [Connect-capable](/docs/connect/index.html)
|
||||
proxies representing another service.
|
||||
|
||||
- `ProxyDestination` `(string: "")` - For "connect-proxy" `Kind` services,
|
||||
this must be set to the name of the service that the proxy represents. This
|
||||
service doesn't need to be registered, but the caller must have an ACL token
|
||||
with permissions for this service.
|
||||
- `ProxyDestination` `(string: "")` - **Deprecated** From 1.2.0 to 1.2.3 this
|
||||
was used for "connect-proxy" `Kind` services however the equivalent field is
|
||||
now in `Proxy.DestinationServiceName`. Registrations using this field will
|
||||
continue to work until some later major version where this will be removed
|
||||
entirely. It's strongly recommended to switch to using the new field.
|
||||
|
||||
- `Connect` `(Connect: nil)` - Specifies the configuration for
|
||||
[Connect](/docs/connect/index.html). See the [Connect structure](#connect-structure)
|
||||
section for supported fields.
|
||||
- `Proxy` `(Proxy: nil)` - From 1.2.3 on, specifies the configuration for a
|
||||
Connect proxy instance. This is only valid if `Kind == "connect-proxy"`. See
|
||||
the [Unmanaged Proxy](/docs/connect/proxies.html#unmanaged-proxies)
|
||||
documentation for full details.
|
||||
|
||||
- `Connect` `(Connect: nil)` - Specifies the
|
||||
[configuration for Connect](/docs/connect/configuration.html). See the
|
||||
[Connect Structure](#connect-structure) section below for supported fields.
|
||||
|
||||
- `Check` `(Check: nil)` - Specifies a check. Please see the
|
||||
[check documentation](/api/agent/check.html) for more information about the
|
||||
|
@ -168,6 +178,10 @@ For the `Connect` field, the parameters are:
|
|||
the [Connect](/docs/connect/index.html) protocol [natively](/docs/connect/native.html).
|
||||
If this is true, then Connect proxies, DNS queries, etc. will be able to
|
||||
service discover this service.
|
||||
- `Proxy` `(Proxy: nil)` - Specifies that a managed Connect proxy should be
|
||||
started for this service instance, and optionally provides configuration for
|
||||
the proxy. The format is as documented in
|
||||
[Managed Proxies](/docs/connect/proxies.html#managed-proxies) .
|
||||
|
||||
### Sample Payload
|
||||
|
||||
|
|
|
@ -446,7 +446,20 @@ $ curl \
|
|||
},
|
||||
"ServiceTags": [
|
||||
"tacos"
|
||||
]
|
||||
],
|
||||
"ServiceProxyDestination": "",
|
||||
"ServiceProxy": {
|
||||
"DestinationServiceName": "",
|
||||
"DestinationServiceID": "",
|
||||
"LocalServiceAddress": "",
|
||||
"LocalServicePort": 0,
|
||||
"Config": null,
|
||||
"Upstreams": null
|
||||
},
|
||||
"ServiceConnect": {
|
||||
"Native": false,
|
||||
"Proxy": null
|
||||
},
|
||||
}
|
||||
]
|
||||
```
|
||||
|
@ -488,11 +501,16 @@ $ curl \
|
|||
- `ServiceKind` is the kind of service, usually "". See the Agent
|
||||
registration API for more information.
|
||||
|
||||
- `ServiceProxyDestination` is the name of the service that is being proxied,
|
||||
for "connect-proxy" type services.
|
||||
- `ServiceProxyDestination` **Deprecated** this field duplicates
|
||||
`ServiceProxy.DestinationServiceName` for backwards compatibility. It will be
|
||||
removed in a future major version release.
|
||||
|
||||
- `ServiceProxy` is the proxy config as specified in
|
||||
[Unmanaged Proxies](/docs/connect/proxies.html#unmanaged-proxies).
|
||||
|
||||
- `ServiceConnect` are the [Connect](/docs/connect/index.html) settings. The
|
||||
value of this struct is equivalent to the `Connect` field for service registration.
|
||||
value of this struct is equivalent to the `Connect` field for service
|
||||
registration.
|
||||
|
||||
## List Nodes for Connect-capable Service
|
||||
|
||||
|
|
|
@ -17,10 +17,10 @@ or added at runtime over the HTTP interface.
|
|||
|
||||
## Service Definition
|
||||
|
||||
To configure a service, either provide the service definition as a `-config-file` option to
|
||||
the agent or place it inside the `-config-dir` of the agent. The file
|
||||
must end in the `.json` or `.hcl` extension to be loaded by Consul. Check
|
||||
definitions can be updated by sending a `SIGHUP` to the agent.
|
||||
To configure a service, either provide the service definition as a
|
||||
`-config-file` option to the agent or place it inside the `-config-dir` of the
|
||||
agent. The file must end in the `.json` or `.hcl` extension to be loaded by
|
||||
Consul. Check definitions can be updated by sending a `SIGHUP` to the agent.
|
||||
Alternatively, the service can be registered dynamically using the [HTTP
|
||||
API](/api/index.html).
|
||||
|
||||
|
@ -46,6 +46,14 @@ example shows all possible fields, but note that only a few are required.
|
|||
],
|
||||
"kind": "connect-proxy",
|
||||
"proxy_destination": "redis",
|
||||
"proxy": {
|
||||
"destination_service_name": "redis",
|
||||
"destination_service_id": "redis1",
|
||||
"local_service_name": "127.0.0.1",
|
||||
"local_service_port": 9090,
|
||||
"config": {},
|
||||
"upstreams": []
|
||||
}
|
||||
"connect": {
|
||||
"native": false,
|
||||
"proxy": {
|
||||
|
@ -88,9 +96,13 @@ and all the instances of a given service have their own copy of it.
|
|||
The `kind` field is used to optionally identify the service as an [unmanaged
|
||||
Connect proxy](/docs/connect/proxies.html#unmanaged-proxies) instance with the
|
||||
value `connect-proxy`. For typical non-proxy instances the `kind` field must be
|
||||
omitted. The `proxy_destination` field is also required for unmanaged proxy
|
||||
registrations and is only valid if `kind` is `connect-proxy`. It's value must be
|
||||
the _name_ of the service that the proxy is handling traffic for.
|
||||
omitted. The `proxy` field is also required for unmanaged proxy registrations
|
||||
and is only valid if `kind` is `connect-proxy`. The only required `proxy` field
|
||||
is `destination_service_name`. From version 1.2.0 to 1.3.0 this was specified
|
||||
using `proxy_destination` which still works but is now deprecated. See the
|
||||
[unmanaged proxy
|
||||
configuration](/docs/connect/proxies.html#complete-configuration-example)
|
||||
documentation for full details.
|
||||
|
||||
The `connect` field can be specified to configure
|
||||
[Connect](/docs/connect/index.html) for a service. This field is available in
|
||||
|
@ -220,3 +232,11 @@ recommended to always use DNS-compliant service and tag names.
|
|||
DNS-compliant service and tag names may contain any alpha-numeric characters, as
|
||||
well as dashes. Dots are not supported because Consul internally uses them to
|
||||
delimit service tags.
|
||||
|
||||
## Service Definition Parameter Case
|
||||
|
||||
For historical reasons Consul's API uses `CamelCased` parameter names in
|
||||
responses, however it's configuration file syntax borrowed from HCL uses
|
||||
`snake_case`. For this reason the registration APIs accept both cases for
|
||||
service definition parameters although APIs will return the listings using
|
||||
`CamelCase`.
|
|
@ -48,15 +48,15 @@ needed for a secure deployment.
|
|||
## Built-In Proxy Options
|
||||
|
||||
This is a complete example of all the configuration options available for the
|
||||
built-in proxy. Note that only the `service.connect.proxy.config` map is being
|
||||
described here, the rest of the service definition is shown for context and is
|
||||
[described elsewhere](/docs/connect/proxies.html#managed-proxies).
|
||||
built-in proxy. Note that only the `service.connect.proxy.config` and
|
||||
`service.connect.proxy.upsteams[].config` maps are being described here, the
|
||||
rest of the service definition is shown for context but is [described
|
||||
elsewhere](/docs/connect/proxies.html#managed-proxies).
|
||||
|
||||
```javascript
|
||||
{
|
||||
"service": {
|
||||
"name": "web",
|
||||
"port": 8080,
|
||||
...
|
||||
"connect": {
|
||||
"proxy": {
|
||||
"config": {
|
||||
|
@ -67,24 +67,23 @@ described here, the rest of the service definition is shown for context and is
|
|||
"local_service_address": "127.0.0.1:1234",
|
||||
"local_connect_timeout_ms": 1000,
|
||||
"handshake_timeout_ms": 10000,
|
||||
"upstreams": [
|
||||
{
|
||||
"destination_type": "service",
|
||||
"destination_name": "redis",
|
||||
"destination_datacenter": "dc1",
|
||||
"local_bind_address": "127.0.0.1",
|
||||
"local_bind_port": 1234,
|
||||
"connect_timeout_ms": 10000
|
||||
},
|
||||
]
|
||||
}
|
||||
"upstreams": [...]
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
...
|
||||
"config": {
|
||||
"connect_timeout_ms": 1000
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Configuration Key Reference
|
||||
#### Proxy Config Key Reference
|
||||
|
||||
All fields are optional with a sane default.
|
||||
|
||||
|
@ -131,25 +130,19 @@ All fields are optional with a sane default.
|
|||
number of milliseconds the proxy will wait for _incoming_ mTLS connections to
|
||||
complete the TLS handshake. Defaults to `10000` or 10 seconds.
|
||||
|
||||
* <a name="upstreams"></a><a href="#upstreams">`upstreams`</a> - An array of
|
||||
upstream definitions for remote services that the proxied
|
||||
application needs to make outgoing connections to. Each definition has the
|
||||
following fields:
|
||||
* <a name="destination_name"></a><a href="#destination_name">`destination_name`</a> -
|
||||
[required] The name of the service or prepared query to route connect to.
|
||||
* <a name="local_bind_port"></a><a href="#local_bind_port">`local_bind_port`</a> -
|
||||
[required] The port to bind a local listener to for the application to
|
||||
make outbound connections to this upstream.
|
||||
* <a name="local_bind_address"></a><a href="#local_bind_address">`local_bind_address`</a> -
|
||||
The address to bind a local listener to for the application to make
|
||||
outbound connections to this upstream.
|
||||
* <a name="destination_type"></a><a href="#destination_type">`destination_type`</a> -
|
||||
Either `service` or `upstream`. The type of discovery query to use to find
|
||||
an instance to connect to. Defaults to `service`.
|
||||
* <a name="destination_datacenter"></a><a href="#destination_datacenter">`destination_datacenter`</a> -
|
||||
The datacenter to issue the discovery query too. Defaults to the local datacenter.
|
||||
* <a name="connect_timeout_ms"></a><a href="#connect_timeout_ms">`connect_timeout_ms`</a> -
|
||||
The number of milliseconds the proxy will wait to establish a connection to
|
||||
and complete TLS handshake with the _remote_ application or proxy. Defaults
|
||||
to `10000` or 10 seconds.
|
||||
* <a name="upstreams"></a><a href="#upstreams">`upstreams`</a> - **Deprecated**
|
||||
Upstreams are now specified in the `connect.proxy` definition. Upstreams
|
||||
specified in the opaque config map here will continue to work for
|
||||
compatibility but it's strongly recommended that you move to using the higher
|
||||
level [upstream
|
||||
configuration](http://localhost:4567/docs/connect/proxies.html#upstream-configuration).
|
||||
|
||||
#### Proxy Upstream Config Key Reference
|
||||
|
||||
All fields are optional with a sane default.
|
||||
|
||||
* <a name="connect_timeout_ms"></a><a
|
||||
href="#connect_timeout_ms">`connect_timeout_ms`</a> - The number of
|
||||
milliseconds the proxy will wait to establish a TLS connection to the
|
||||
discovered upstream instance before giving up. Defaults to `10000` or 10
|
||||
seconds.
|
|
@ -111,12 +111,10 @@ proxy configuration:
|
|||
"port": 8080,
|
||||
"connect": {
|
||||
"proxy": {
|
||||
"config": {
|
||||
"upstreams": [{
|
||||
"destination_name": "redis",
|
||||
"local_bind_port": 1234
|
||||
}]
|
||||
}
|
||||
"upstreams": [{
|
||||
"destination_name": "redis",
|
||||
"local_bind_port": 1234
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,8 +132,17 @@ static port will be able to masquerade as the source service ("web" in the
|
|||
example above). You must either trust any loopback access on that port or
|
||||
use namespacing techniques provided by your operating system.
|
||||
|
||||
For full details of the configurable options available see the [built-in proxy
|
||||
configuration reference](/docs/connect/configuration.html#built-in-proxy-options).
|
||||
-> **Deprecation Note:** versions 1.2.0 to 1.3.0 required specifying `upstreams`
|
||||
as part of the opaque `config` that is passed to the proxy. However, since
|
||||
1.3.0, the `upstreams` configuration is now specified directily under the
|
||||
`proxy` key. Old service definitions using the nested config will continue to
|
||||
work and have the values copied into the new location. This allows the upstreams
|
||||
to be registered centrally rather than being part of the local-only config
|
||||
passed to the proxy instance.
|
||||
|
||||
For full details of the additional configurable options available when using the
|
||||
built-in proxy see the [built-in proxy configuration
|
||||
reference](/docs/connect/configuration.html#built-in-proxy-options).
|
||||
|
||||
### Prepared Query Upstreams
|
||||
|
||||
|
@ -157,13 +164,11 @@ service.
|
|||
"port": 8080,
|
||||
"connect": {
|
||||
"proxy": {
|
||||
"config": {
|
||||
"upstreams": [{
|
||||
"destination_name": "redis",
|
||||
"destination_type": "prepared_query",
|
||||
"local_bind_port": 1234
|
||||
}]
|
||||
}
|
||||
"upstreams": [{
|
||||
"destination_name": "redis",
|
||||
"destination_type": "prepared_query",
|
||||
"local_bind_port": 1234
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -176,8 +181,9 @@ only be used to discover services within a single datacenter. See
|
|||
[Multi-Datacenter Connect](/docs/connect/index.html#multi-datacenter) for
|
||||
more information.
|
||||
|
||||
For full details of the configurable options available see the [built-in proxy
|
||||
configuration reference](/docs/connect/configuration.html#built-in-proxy-options).
|
||||
For full details of the additional configurable options available when using the
|
||||
built-in proxy see the [built-in proxy configuration
|
||||
reference](/docs/connect/configuration.html#built-in-proxy-options).
|
||||
|
||||
### Dynamic Upstreams
|
||||
|
||||
|
@ -187,6 +193,54 @@ with Connect. After natively integrating, the HTTP API or
|
|||
[DNS interface](/docs/agent/dns.html#connect-capable-service-lookups)
|
||||
can be used.
|
||||
|
||||
### Upstream Configuration Reference
|
||||
|
||||
The following example shows all possible upstream configuration parameters.
|
||||
|
||||
Note that in versions 1.2.0 to 1.3.0, managed proxy upstreams were specified
|
||||
inside the opaque `connect.proxy.config` map. The format is almost unchanged
|
||||
however managed proxy upstreams are now defined a level up in the
|
||||
`connect.proxy.upstreams`. The old location is deprecated and will be
|
||||
automatically converted into the new for an interim period before support is
|
||||
dropped in a future major release. The only difference in format between the
|
||||
upstream defintions is that the field `destination_datacenter` has been renamed
|
||||
to `datacenter` to reflect that it's the discovery target and not necessarily
|
||||
the same as the instance that will be returned in the case of a prepared query
|
||||
that fails over to another datacenter.
|
||||
|
||||
Note that `snake_case` is used here as it works in both [config file and API
|
||||
registrations](/docs/agent/services.html#service-definition-parameter-case).
|
||||
|
||||
```json
|
||||
{
|
||||
"destination_type": "service",
|
||||
"destination_name": "redis",
|
||||
"datacenter": "dc1",
|
||||
"local_bind_address": "127.0.0.1",
|
||||
"local_bind_port": 1234,
|
||||
"config": {}
|
||||
},
|
||||
```
|
||||
|
||||
* `destination_name` `string: <required>` - Specifies the name of the service or
|
||||
prepared query to route connect to.
|
||||
* `local_bind_port` `int: <required>` - Specifies the port to bind a local
|
||||
listener to for the application to make outbound connections to this upstream.
|
||||
* `local_bind_address` `string: <optional>` - Specifies the address to bind a
|
||||
local listener to for the application to make outbound connections to this
|
||||
upstream. Defaults to `127.0.0.1`.
|
||||
* `destination_type` `string: <optional>` - Speficied the type of discovery
|
||||
query to use to find an instance to connect to. Valid values are `service` or
|
||||
`prepared_query`. Defaults to `service`.
|
||||
* `datacenter` `string: <optional>` - Specifies the datacenter to issue the
|
||||
discovery query too. Defaults to the local datacenter.
|
||||
* `config` `object: <optional>` - Specifies opaque configuration options that
|
||||
will be provided to the proxy instance for this specific upstream. Can contain
|
||||
any valid JSON object. This might be used to configure proxy-specific features
|
||||
like timeouts or retries for the given upstream. See the
|
||||
[built-in proxy configuration reference](/docs/connect/configuration.html#built-in-proxy-options)
|
||||
for options available when using the built-in proxy.
|
||||
|
||||
### Custom Managed Proxy
|
||||
|
||||
[Custom proxies](/docs/connect/proxies/integrate.html) can also be
|
||||
|
@ -201,7 +255,10 @@ an alternate command to execute for the proxy:
|
|||
"connect": {
|
||||
"proxy": {
|
||||
"exec_mode": "daemon",
|
||||
"command": ["/usr/bin/my-proxy", "-flag-example"]
|
||||
"command": ["/usr/bin/my-proxy", "-flag-example"],
|
||||
"config": {
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -230,6 +287,12 @@ connect {
|
|||
With this configuration, all services registered without an explicit
|
||||
proxy command will use `my-proxy` instead of the default built-in proxy.
|
||||
|
||||
The `config` key is an optional opaque JSON object which will be passed through
|
||||
to the proxy via the proxy configuration endpoint to allow any configuration
|
||||
options the proxy needs to be specified. See the [built-in proxy
|
||||
configuration reference](/docs/connect/configuration.html#built-in-proxy-options)
|
||||
for details of config options that can be passed when using the built-in proxy.
|
||||
|
||||
### Managed Proxy Logs
|
||||
|
||||
Managed proxies have both stdout and stderr captured in log files in the agent's
|
||||
|
@ -254,28 +317,84 @@ be started, configured, and stopped manually by some external process such
|
|||
as an operator or scheduler.
|
||||
|
||||
To declare a service as a proxy, the service definition must contain
|
||||
at least two additional fields:
|
||||
the following fields:
|
||||
|
||||
* `Kind` (string) must be set to `connect-proxy`. This declares that the
|
||||
* `kind` (string) must be set to `connect-proxy`. This declares that the
|
||||
service is a proxy type.
|
||||
|
||||
* `ProxyDestination` (string) must be set to the service that this proxy
|
||||
is representing.
|
||||
* `proxy.destination_service_name` (string) must be set to the service that this
|
||||
proxy is representing. Note that this replaces `proxy_destination` in
|
||||
versions 1.2.0 to 1.3.0.
|
||||
|
||||
* `Port` must be set so that other Connect services can discover the exact
|
||||
address for connections. `Address` is optional if the service is being
|
||||
* `port` must be set so that other Connect services can discover the exact
|
||||
address for connections. `address` is optional if the service is being
|
||||
registered against an agent, since it'll inherit the node address.
|
||||
|
||||
Example:
|
||||
Minimal Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"Name": "redis-proxy",
|
||||
"Kind": "connect-proxy",
|
||||
"ProxyDestination": "redis",
|
||||
"Port": 8181
|
||||
"name": "redis-proxy",
|
||||
"kind": "connect-proxy",
|
||||
"proxy": {
|
||||
"destination_service_name": "redis"
|
||||
},
|
||||
"port": 8181
|
||||
}
|
||||
```
|
||||
|
||||
With this service registered, any Connect proxies searching for a
|
||||
Connect-capable endpoint for "redis" will find this proxy.
|
||||
|
||||
### Complete Configuration Example
|
||||
|
||||
The following is a complete example showing all the options available when
|
||||
registering an unmanaged proxy instance.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "redis-proxy",
|
||||
"kind": "connect-proxy",
|
||||
"proxy": {
|
||||
"destination_service_name": "redis",
|
||||
"destination_service_id": "redis1",
|
||||
"local_service_address": "127.0.0.1",
|
||||
"local_service_port": 9090,
|
||||
"config": {},
|
||||
"upstreams": []
|
||||
},
|
||||
"port": 8181
|
||||
}
|
||||
```
|
||||
|
||||
#### Proxy Parameters
|
||||
|
||||
- `destination_service_name` `string: <required>` - Specifies the _name_ of the
|
||||
service this instance is proxying. Both side-car and centralized
|
||||
load-balancing proxies must specify this. It is used during service
|
||||
discovery to find the correct proxy instances to route to for a given service
|
||||
name.
|
||||
|
||||
- `destination_service_id` `string: <optional>` - Specifies the _ID_ of a single
|
||||
specific service instance that this proxy is representing. This is only valid
|
||||
for side-car style proxies that run on the same node. It is assumed that the
|
||||
service instance is registered via the same Consul agent so the ID is unique
|
||||
and has no node qualifier. This is useful to show in tooling which proxy
|
||||
instance is a side-car for which application instance and will enable
|
||||
fine-grained analysis of the metrics coming from the proxy.
|
||||
|
||||
- `local_service_address` `string: <optional>` - Specifies the address a side-car
|
||||
proxy should attempt to connect to the local application instance on.
|
||||
Defaults to 127.0.0.1.
|
||||
|
||||
- `local_service_port` `int: <optional>` - Specifies the port a side-car
|
||||
proxy should attempt to connect to the local application instance on.
|
||||
Defaults to the port advertised by the service instance identified by
|
||||
`destination_service_id` if it exists otherwise it may be empty in responses.
|
||||
|
||||
- `config` `object: <optional>` - Specifies opaque config JSON that will be
|
||||
stored and returned along with the service instance from future API calls.
|
||||
|
||||
- `upstreams` `array<Upstream>: <optional>` - Specifies the upstream services
|
||||
this proxy should create listeners for. The format is defined in
|
||||
[Upstream Configuration Reference](#upstream-configuration-reference).
|
Loading…
Reference in New Issue