ca: fix a bug that caused a non blocking leaf cert query after a blocking leaf cert query to block (#12820)
Fixes #12048 Fixes #12319 Regression introduced in #11693 Local reproduction steps: 1. `consul agent -dev` 2. `curl -sLiv 'localhost:8500/v1/agent/connect/ca/leaf/web'` 3. make note of the `X-Consul-Index` header returned 4. `curl -sLi 'localhost:8500/v1/agent/connect/ca/leaf/web?index=<VALUE_FROM_STEP_3>'` 5. Kill the above curl when it hangs with Ctrl-C 6. Repeat (2) and it should not hang.
This commit is contained in:
parent
41d9302200
commit
cf0c5110be
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
ca: fix a bug that caused a non blocking leaf cert query after a blocking leaf cert query to block
|
||||||
|
```
|
|
@ -6202,6 +6202,101 @@ func TestAgentConnectCALeafCert_goodNotLocal(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAgentConnectCALeafCert_nonBlockingQuery_after_blockingQuery_shouldNotBlock(t *testing.T) {
|
||||||
|
// see: https://github.com/hashicorp/consul/issues/12048
|
||||||
|
|
||||||
|
runStep := func(t *testing.T, name string, fn func(t *testing.T)) {
|
||||||
|
t.Helper()
|
||||||
|
if !t.Run(name, fn) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("too slow for testing.Short")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
a := NewTestAgent(t, "")
|
||||||
|
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
|
||||||
|
testrpc.WaitForActiveCARoot(t, a.RPC, "dc1", nil)
|
||||||
|
|
||||||
|
{
|
||||||
|
// Register a local service
|
||||||
|
args := &structs.ServiceDefinition{
|
||||||
|
ID: "foo",
|
||||||
|
Name: "test",
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
Port: 8000,
|
||||||
|
Check: structs.CheckType{
|
||||||
|
TTL: 15 * time.Second,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
req := httptest.NewRequest("PUT", "/v1/agent/service/register", jsonReader(args))
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
a.srv.h.ServeHTTP(resp, req)
|
||||||
|
if !assert.Equal(t, 200, resp.Code) {
|
||||||
|
t.Log("Body: ", resp.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
serialNumber string
|
||||||
|
index string
|
||||||
|
issued structs.IssuedCert
|
||||||
|
)
|
||||||
|
runStep(t, "do initial non-blocking query", func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest("GET", "/v1/agent/connect/ca/leaf/test", nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
a.srv.h.ServeHTTP(resp, req)
|
||||||
|
|
||||||
|
dec := json.NewDecoder(resp.Body)
|
||||||
|
require.NoError(t, dec.Decode(&issued))
|
||||||
|
serialNumber = issued.SerialNumber
|
||||||
|
|
||||||
|
require.Equal(t, "MISS", resp.Header().Get("X-Cache"),
|
||||||
|
"for the leaf cert cache type these are always MISS")
|
||||||
|
index = resp.Header().Get("X-Consul-Index")
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
go func() {
|
||||||
|
// launch goroutine for blocking query
|
||||||
|
req := httptest.NewRequest("GET", "/v1/agent/connect/ca/leaf/test?index="+index, nil).Clone(ctx)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
a.srv.h.ServeHTTP(resp, req)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// We just need to ensure that the above blocking query is in-flight before
|
||||||
|
// the next step, so do a little sleep.
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
|
// The initial non-blocking query populated the leaf cert cache entry
|
||||||
|
// implicitly. The agent cache doesn't prune entries very often at all, so
|
||||||
|
// in between both of these steps the data should still be there, causing
|
||||||
|
// this to be a HIT that completes in less than 10m (the default inner leaf
|
||||||
|
// cert blocking query timeout).
|
||||||
|
runStep(t, "do a non-blocking query that should not block", func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest("GET", "/v1/agent/connect/ca/leaf/test", nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
a.srv.h.ServeHTTP(resp, req)
|
||||||
|
|
||||||
|
var issued2 structs.IssuedCert
|
||||||
|
dec := json.NewDecoder(resp.Body)
|
||||||
|
require.NoError(t, dec.Decode(&issued2))
|
||||||
|
|
||||||
|
require.Equal(t, "HIT", resp.Header().Get("X-Cache"))
|
||||||
|
|
||||||
|
// If this is actually returning a cached result, the serial number
|
||||||
|
// should be unchanged.
|
||||||
|
require.Equal(t, serialNumber, issued2.SerialNumber)
|
||||||
|
|
||||||
|
require.Equal(t, issued, issued2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestAgentConnectCALeafCert_Vault_doesNotChurnLeafCertsAtIdle(t *testing.T) {
|
func TestAgentConnectCALeafCert_Vault_doesNotChurnLeafCertsAtIdle(t *testing.T) {
|
||||||
ca.SkipIfVaultNotPresent(t)
|
ca.SkipIfVaultNotPresent(t)
|
||||||
|
|
||||||
|
|
|
@ -376,6 +376,13 @@ func (c *Cache) getEntryLocked(
|
||||||
// Check if re-validate is requested. If so the first time round the
|
// Check if re-validate is requested. If so the first time round the
|
||||||
// loop is not a hit but subsequent ones should be treated normally.
|
// loop is not a hit but subsequent ones should be treated normally.
|
||||||
if !tEntry.Opts.Refresh && info.MustRevalidate {
|
if !tEntry.Opts.Refresh && info.MustRevalidate {
|
||||||
|
if entry.Fetching {
|
||||||
|
// There is an active blocking query for this data, which has not
|
||||||
|
// returned. We can logically deduce that the contents of the cache
|
||||||
|
// are actually current, and we can simply return this while
|
||||||
|
// leaving the blocking query alone.
|
||||||
|
return true, true, entry
|
||||||
|
}
|
||||||
return true, false, entry
|
return true, false, entry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue