-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support Agent Caching for Service Discovery Results (#4541)
* Add cache types for catalog/services and health/services and basic test that caching works * Support non-blocking cache types with Cache-Control semantics. * Update API docs to include caching info for every endpoint. * Comment updates per PR feedback. * Add note on caching to the 10,000 foot view on the architecture page to make the new data path more clear. * Document prepared query staleness quirk and force all background requests to AllowStale so we can spread service discovery load across servers.
- Loading branch information
Showing
55 changed files
with
2,183 additions
and
480 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package cachetype | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hashicorp/consul/agent/cache" | ||
"github.com/hashicorp/consul/agent/structs" | ||
) | ||
|
||
// Recommended name for registration. | ||
const CatalogServicesName = "catalog-services" | ||
|
||
// CatalogServices supports fetching discovering service instances via the | ||
// catalog. | ||
type CatalogServices struct { | ||
RPC RPC | ||
} | ||
|
||
func (c *CatalogServices) Fetch(opts cache.FetchOptions, req cache.Request) (cache.FetchResult, error) { | ||
var result cache.FetchResult | ||
|
||
// The request should be a DCSpecificRequest. | ||
reqReal, ok := req.(*structs.ServiceSpecificRequest) | ||
if !ok { | ||
return result, fmt.Errorf( | ||
"Internal cache failure: request wrong type: %T", req) | ||
} | ||
|
||
// Set the minimum query index to our current index so we block | ||
reqReal.QueryOptions.MinQueryIndex = opts.MinIndex | ||
reqReal.QueryOptions.MaxQueryTime = opts.Timeout | ||
|
||
// Allways allow stale - there's no point in hitting leader if the request is | ||
// going to be served from cache and endup arbitrarily stale anyway. This | ||
// allows cached service-discover to automatically read scale across all | ||
// servers too. | ||
reqReal.AllowStale = true | ||
|
||
// Fetch | ||
var reply structs.IndexedServiceNodes | ||
if err := c.RPC.RPC("Catalog.ServiceNodes", reqReal, &reply); err != nil { | ||
return result, err | ||
} | ||
|
||
result.Value = &reply | ||
result.Index = reply.QueryMeta.Index | ||
return result, nil | ||
} | ||
|
||
func (c *CatalogServices) SupportsBlocking() bool { | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package cachetype | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/hashicorp/consul/agent/cache" | ||
"github.com/hashicorp/consul/agent/structs" | ||
"github.com/stretchr/testify/mock" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestCatalogServices(t *testing.T) { | ||
require := require.New(t) | ||
rpc := TestRPC(t) | ||
defer rpc.AssertExpectations(t) | ||
typ := &CatalogServices{RPC: rpc} | ||
|
||
// Expect the proper RPC call. This also sets the expected value | ||
// since that is return-by-pointer in the arguments. | ||
var resp *structs.IndexedServiceNodes | ||
rpc.On("RPC", "Catalog.ServiceNodes", mock.Anything, mock.Anything).Return(nil). | ||
Run(func(args mock.Arguments) { | ||
req := args.Get(1).(*structs.ServiceSpecificRequest) | ||
require.Equal(uint64(24), req.QueryOptions.MinQueryIndex) | ||
require.Equal(1*time.Second, req.QueryOptions.MaxQueryTime) | ||
require.Equal("web", req.ServiceName) | ||
require.Equal("canary", req.ServiceTag) | ||
require.True(req.AllowStale) | ||
|
||
reply := args.Get(2).(*structs.IndexedServiceNodes) | ||
reply.QueryMeta.Index = 48 | ||
resp = reply | ||
}) | ||
|
||
// Fetch | ||
result, err := typ.Fetch(cache.FetchOptions{ | ||
MinIndex: 24, | ||
Timeout: 1 * time.Second, | ||
}, &structs.ServiceSpecificRequest{ | ||
Datacenter: "dc1", | ||
ServiceName: "web", | ||
ServiceTag: "canary", | ||
}) | ||
require.NoError(err) | ||
require.Equal(cache.FetchResult{ | ||
Value: resp, | ||
Index: 48, | ||
}, result) | ||
} | ||
|
||
func TestCatalogServices_badReqType(t *testing.T) { | ||
require := require.New(t) | ||
rpc := TestRPC(t) | ||
defer rpc.AssertExpectations(t) | ||
typ := &CatalogServices{RPC: rpc} | ||
|
||
// Fetch | ||
_, err := typ.Fetch(cache.FetchOptions{}, cache.TestRequest( | ||
t, cache.RequestInfo{Key: "foo", MinIndex: 64})) | ||
require.Error(err) | ||
require.Contains(err.Error(), "wrong type") | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package cachetype | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hashicorp/consul/agent/cache" | ||
"github.com/hashicorp/consul/agent/structs" | ||
) | ||
|
||
// Recommended name for registration. | ||
const HealthServicesName = "health-services" | ||
|
||
// HealthServices supports fetching discovering service instances via the | ||
// catalog. | ||
type HealthServices struct { | ||
RPC RPC | ||
} | ||
|
||
func (c *HealthServices) Fetch(opts cache.FetchOptions, req cache.Request) (cache.FetchResult, error) { | ||
var result cache.FetchResult | ||
|
||
// The request should be a DCSpecificRequest. | ||
reqReal, ok := req.(*structs.ServiceSpecificRequest) | ||
if !ok { | ||
return result, fmt.Errorf( | ||
"Internal cache failure: request wrong type: %T", req) | ||
} | ||
|
||
// Set the minimum query index to our current index so we block | ||
reqReal.QueryOptions.MinQueryIndex = opts.MinIndex | ||
reqReal.QueryOptions.MaxQueryTime = opts.Timeout | ||
|
||
// Allways allow stale - there's no point in hitting leader if the request is | ||
// going to be served from cache and endup arbitrarily stale anyway. This | ||
// allows cached service-discover to automatically read scale across all | ||
// servers too. | ||
reqReal.AllowStale = true | ||
|
||
// Fetch | ||
var reply structs.IndexedCheckServiceNodes | ||
if err := c.RPC.RPC("Health.ServiceNodes", reqReal, &reply); err != nil { | ||
return result, err | ||
} | ||
|
||
result.Value = &reply | ||
result.Index = reply.QueryMeta.Index | ||
return result, nil | ||
} | ||
|
||
func (c *HealthServices) SupportsBlocking() bool { | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package cachetype | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/hashicorp/consul/agent/cache" | ||
"github.com/hashicorp/consul/agent/structs" | ||
"github.com/stretchr/testify/mock" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestHealthServices(t *testing.T) { | ||
require := require.New(t) | ||
rpc := TestRPC(t) | ||
defer rpc.AssertExpectations(t) | ||
typ := &HealthServices{RPC: rpc} | ||
|
||
// Expect the proper RPC call. This also sets the expected value | ||
// since that is return-by-pointer in the arguments. | ||
var resp *structs.IndexedCheckServiceNodes | ||
rpc.On("RPC", "Health.ServiceNodes", mock.Anything, mock.Anything).Return(nil). | ||
Run(func(args mock.Arguments) { | ||
req := args.Get(1).(*structs.ServiceSpecificRequest) | ||
require.Equal(uint64(24), req.QueryOptions.MinQueryIndex) | ||
require.Equal(1*time.Second, req.QueryOptions.MaxQueryTime) | ||
require.Equal("web", req.ServiceName) | ||
require.Equal("canary", req.ServiceTag) | ||
require.True(req.AllowStale) | ||
|
||
reply := args.Get(2).(*structs.IndexedCheckServiceNodes) | ||
reply.QueryMeta.Index = 48 | ||
resp = reply | ||
}) | ||
|
||
// Fetch | ||
result, err := typ.Fetch(cache.FetchOptions{ | ||
MinIndex: 24, | ||
Timeout: 1 * time.Second, | ||
}, &structs.ServiceSpecificRequest{ | ||
Datacenter: "dc1", | ||
ServiceName: "web", | ||
ServiceTag: "canary", | ||
}) | ||
require.NoError(err) | ||
require.Equal(cache.FetchResult{ | ||
Value: resp, | ||
Index: 48, | ||
}, result) | ||
} | ||
|
||
func TestHealthServices_badReqType(t *testing.T) { | ||
require := require.New(t) | ||
rpc := TestRPC(t) | ||
defer rpc.AssertExpectations(t) | ||
typ := &HealthServices{RPC: rpc} | ||
|
||
// Fetch | ||
_, err := typ.Fetch(cache.FetchOptions{}, cache.TestRequest( | ||
t, cache.RequestInfo{Key: "foo", MinIndex: 64})) | ||
require.Error(err) | ||
require.Contains(err.Error(), "wrong type") | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package cachetype | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hashicorp/consul/agent/cache" | ||
"github.com/hashicorp/consul/agent/structs" | ||
) | ||
|
||
// Recommended name for registration. | ||
const PreparedQueryName = "prepared-query" | ||
|
||
// PreparedQuery supports fetching discovering service instances via prepared | ||
// queries. | ||
type PreparedQuery struct { | ||
RPC RPC | ||
} | ||
|
||
func (c *PreparedQuery) Fetch(opts cache.FetchOptions, req cache.Request) (cache.FetchResult, error) { | ||
var result cache.FetchResult | ||
|
||
// The request should be a PreparedQueryExecuteRequest. | ||
reqReal, ok := req.(*structs.PreparedQueryExecuteRequest) | ||
if !ok { | ||
return result, fmt.Errorf( | ||
"Internal cache failure: request wrong type: %T", req) | ||
} | ||
|
||
// Allways allow stale - there's no point in hitting leader if the request is | ||
// going to be served from cache and endup arbitrarily stale anyway. This | ||
// allows cached service-discover to automatically read scale across all | ||
// servers too. | ||
reqReal.AllowStale = true | ||
|
||
// Fetch | ||
var reply structs.PreparedQueryExecuteResponse | ||
if err := c.RPC.RPC("PreparedQuery.Execute", reqReal, &reply); err != nil { | ||
return result, err | ||
} | ||
|
||
result.Value = &reply | ||
result.Index = reply.QueryMeta.Index | ||
|
||
return result, nil | ||
} | ||
|
||
func (c *PreparedQuery) SupportsBlocking() bool { | ||
// Prepared queries don't support blocking. | ||
return false | ||
} |
Oops, something went wrong.