Skip to content

Commit

Permalink
Added option to set "stale" consistency by default for http requests.
Browse files Browse the repository at this point in the history
  • Loading branch information
wojtkiewicz committed Jun 22, 2017
1 parent c9cf7e0 commit 28ceaa6
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 42 deletions.
8 changes: 8 additions & 0 deletions agent/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ type DNSConfig struct {

// HTTPConfig is used to fine tune the Http sub-system.
type HTTPConfig struct {
// AllowStale is used to turn on stale consistency mode by default for HTTP API requests.
// This gives horizontal read scalability since any Consul server can service the query instead of
// only the leader.
AllowStale bool `mapstructure:"allow_stale"`

// ResponseHeaders are used to add HTTP header response fields to the HTTP API responses.
ResponseHeaders map[string]string `mapstructure:"response_headers"`
}
Expand Down Expand Up @@ -1994,6 +1999,9 @@ func MergeConfig(a, b *Config) *Config {
result.HTTPConfig.ResponseHeaders[field] = value
}
}
if b.HTTPConfig.AllowStale {
result.HTTPConfig.AllowStale = true
}
if len(b.Meta) != 0 {
if result.Meta == nil {
result.Meta = make(map[string]string)
Expand Down
13 changes: 8 additions & 5 deletions agent/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,15 +358,17 @@ func parseWait(resp http.ResponseWriter, req *http.Request, b *structs.QueryOpti
}

// parseConsistency is used to parse the ?stale and ?consistent query params.
// allowStale forces stale consistency instead of default one if none was provided in the query.
// Returns true on error
func parseConsistency(resp http.ResponseWriter, req *http.Request, b *structs.QueryOptions) bool {
func parseConsistency(resp http.ResponseWriter, req *http.Request, allowStale bool, b *structs.QueryOptions) bool {
query := req.URL.Query()
if _, ok := query["stale"]; ok {
b.AllowStale = true
}
if _, ok := query["consistent"]; ok {
b.RequireConsistent = true
}
b.AllowStale = !b.RequireConsistent && allowStale
if _, ok := query["stale"]; ok {
b.AllowStale = true
}
if b.AllowStale && b.RequireConsistent {
resp.WriteHeader(http.StatusBadRequest) // 400
fmt.Fprint(resp, "Cannot specify ?stale with ?consistent, conflicting semantics.")
Expand Down Expand Up @@ -433,7 +435,8 @@ func (s *HTTPServer) parseMetaFilter(req *http.Request) map[string]string {
func (s *HTTPServer) parse(resp http.ResponseWriter, req *http.Request, dc *string, b *structs.QueryOptions) bool {
s.parseDC(req, dc)
s.parseToken(req, &b.Token)
if parseConsistency(resp, req, b) {
allowStale := s.agent.config.HTTPConfig.AllowStale
if parseConsistency(resp, req, allowStale, b) {
return true
}
return parseWait(resp, req, b)
Expand Down
51 changes: 28 additions & 23 deletions agent/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,31 +436,36 @@ func TestParseWait_InvalidIndex(t *testing.T) {
func TestParseConsistency(t *testing.T) {
t.Parallel()
resp := httptest.NewRecorder()
var b structs.QueryOptions

req, _ := http.NewRequest("GET", "/v1/catalog/nodes?stale", nil)
if d := parseConsistency(resp, req, &b); d {
t.Fatalf("unexpected done")
}

if !b.AllowStale {
t.Fatalf("Bad: %v", b)
}
if b.RequireConsistent {
t.Fatalf("Bad: %v", b)
}

b = structs.QueryOptions{}
req, _ = http.NewRequest("GET", "/v1/catalog/nodes?consistent", nil)
if d := parseConsistency(resp, req, &b); d {
t.Fatalf("unexpected done")
tests := []struct {
url string
allowStale bool
wantAllowStale bool
wantRequireConsistent bool
}{
{"/v1/catalog/nodes?stale", false, true, false},
{"/v1/catalog/nodes?stale", true, true, false},
{"/v1/catalog/nodes?consistent", false, false, true},
{"/v1/catalog/nodes?consistent", true, false, true},
{"/v1/catalog/nodes", false, false, false},
{"/v1/catalog/nodes", true, true, false},
}

if b.AllowStale {
t.Fatalf("Bad: %v", b)
}
if !b.RequireConsistent {
t.Fatalf("Bad: %v", b)
for _, tt := range tests {
name := fmt.Sprintf("url=%v, HTTP.AllowStale=%v", tt.url, tt.allowStale)
t.Run(name, func(t *testing.T) {
var q structs.QueryOptions
req, _ := http.NewRequest("GET", tt.url, nil)
if d := parseConsistency(resp, req, tt.allowStale, &q); d {
t.Fatalf("Failed to parse consistency.")
}
if got, want := q.AllowStale, tt.wantAllowStale; got != want {
t.Fatalf("got allowStale %v want %v", got, want)
}
if got, want := q.RequireConsistent, tt.wantRequireConsistent; got != want {
t.Fatalf("got requireConsistent %v want %v", got, want)
}
})
}
}

Expand All @@ -470,7 +475,7 @@ func TestParseConsistency_Invalid(t *testing.T) {
var b structs.QueryOptions

req, _ := http.NewRequest("GET", "/v1/catalog/nodes?stale&consistent", nil)
if d := parseConsistency(resp, req, &b); !d {
if d := parseConsistency(resp, req, false, &b); !d {
t.Fatalf("expected done")
}

Expand Down
38 changes: 24 additions & 14 deletions website/source/docs/agent/options.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,30 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
be increasingly uncommon to need to change this value with modern
resolvers).

* <a name="http_config"></a><a href="#http_config">`http_config`</a> This object allows a number
of sub-keys to be set which can tune how HTTP queries are serviced.
<br><br>
The following sub-keys are available:

* <a name="allow_stale"></a><a href="#allow_stale">`allow_stale`</a> - Enables a stale query
for HTTP queries. This allows any Consul server, rather than only the leader, to service
the request. The advantage of this is you get linear read scalability with Consul servers.
This defaults to false.

* <a name="http_api_response_headers"></a><a href="#http_api_response_headers">`http_api_response_headers`</a>
This object allows adding headers to the HTTP API
responses. For example, the following config can be used to enable
[CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) on
the HTTP API endpoints:

```javascript
{
"http_api_response_headers": {
"Access-Control-Allow-Origin": "*"
}
}
```

* <a name="domain"></a><a href="#domain">`domain`</a> Equivalent to the
[`-domain` command-line flag](#_domain).

Expand Down Expand Up @@ -724,20 +748,6 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
PEM-encoded private key. The key is used with the certificate to verify the agent's authenticity.
This must be provided along with [`cert_file`](#cert_file).

* <a name="http_api_response_headers"></a><a href="#http_api_response_headers">`http_api_response_headers`</a>
This object allows adding headers to the HTTP API
responses. For example, the following config can be used to enable
[CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) on
the HTTP API endpoints:

```javascript
{
"http_api_response_headers": {
"Access-Control-Allow-Origin": "*"
}
}
```

* <a name="leave_on_terminate"></a><a href="#leave_on_terminate">`leave_on_terminate`</a> If
enabled, when the agent receives a TERM signal, it will send a `Leave` message to the rest
of the cluster and gracefully leave. The default behavior for this feature varies based on
Expand Down

0 comments on commit 28ceaa6

Please sign in to comment.