From d9a23bb2fa5c6981bb904a63d2433e54557d9f43 Mon Sep 17 00:00:00 2001
From: Pierre Souchay
Date: Tue, 17 Apr 2018 10:17:16 +0200
Subject: [PATCH 01/54] Track calls blocked by ACLs using metrics
---
agent/local/state.go | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/agent/local/state.go b/agent/local/state.go
index f19e88a766..cacbd6607b 100644
--- a/agent/local/state.go
+++ b/agent/local/state.go
@@ -10,6 +10,7 @@ import (
"sync/atomic"
"time"
+ metrics "github.com/armon/go-metrics"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/agent/token"
@@ -842,6 +843,7 @@ func (l *State) deleteService(id string) error {
// todo(fs): some backoff strategy might be a better solution
l.services[id].InSync = true
l.logger.Printf("[WARN] agent: Service %q deregistration blocked by ACLs", id)
+ metrics.IncrCounter([]string{"consul", "acl", "blocked", "service", "deregistration"}, 1)
return nil
default:
@@ -879,6 +881,7 @@ func (l *State) deleteCheck(id types.CheckID) error {
// todo(fs): some backoff strategy might be a better solution
l.checks[id].InSync = true
l.logger.Printf("[WARN] agent: Check %q deregistration blocked by ACLs", id)
+ metrics.IncrCounter([]string{"consul", "acl", "blocked", "check", "deregistration"}, 1)
return nil
default:
@@ -949,6 +952,7 @@ func (l *State) syncService(id string) error {
l.checks[check.CheckID].InSync = true
}
l.logger.Printf("[WARN] agent: Service %q registration blocked by ACLs", id)
+ metrics.IncrCounter([]string{"consul", "acl", "blocked", "service", "registration"}, 1)
return nil
default:
@@ -994,6 +998,7 @@ func (l *State) syncCheck(id types.CheckID) error {
// todo(fs): some backoff strategy might be a better solution
l.checks[id].InSync = true
l.logger.Printf("[WARN] agent: Check %q registration blocked by ACLs", id)
+ metrics.IncrCounter([]string{"consul", "acl", "blocked", "service", "registration"}, 1)
return nil
default:
@@ -1025,6 +1030,7 @@ func (l *State) syncNodeInfo() error {
// todo(fs): some backoff strategy might be a better solution
l.nodeInfoInSync = true
l.logger.Printf("[WARN] agent: Node info update blocked by ACLs")
+ metrics.IncrCounter([]string{"consul", "acl", "blocked", "node", "registration"}, 1)
return nil
default:
From f13aa5ba9bbbaa2a3ba8ad539358bbfe3daba40c Mon Sep 17 00:00:00 2001
From: Pierre Souchay
Date: Wed, 18 Apr 2018 16:51:22 +0200
Subject: [PATCH 02/54] Added labels to improve new metric
---
agent/local/state.go | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/agent/local/state.go b/agent/local/state.go
index cacbd6607b..0e9b513e6c 100644
--- a/agent/local/state.go
+++ b/agent/local/state.go
@@ -843,7 +843,7 @@ func (l *State) deleteService(id string) error {
// todo(fs): some backoff strategy might be a better solution
l.services[id].InSync = true
l.logger.Printf("[WARN] agent: Service %q deregistration blocked by ACLs", id)
- metrics.IncrCounter([]string{"consul", "acl", "blocked", "service", "deregistration"}, 1)
+ metrics.IncrCounterWithLabels([]string{"consul", "acl", "blocked", "service", "deregistration"}, 1, []metrics.Label{{Name: "id", Value: id}})
return nil
default:
@@ -881,7 +881,7 @@ func (l *State) deleteCheck(id types.CheckID) error {
// todo(fs): some backoff strategy might be a better solution
l.checks[id].InSync = true
l.logger.Printf("[WARN] agent: Check %q deregistration blocked by ACLs", id)
- metrics.IncrCounter([]string{"consul", "acl", "blocked", "check", "deregistration"}, 1)
+ metrics.IncrCounterWithLabels([]string{"consul", "acl", "blocked", "check", "deregistration"}, 1, []metrics.Label{{Name: "id", Value: string(id)}})
return nil
default:
@@ -952,7 +952,7 @@ func (l *State) syncService(id string) error {
l.checks[check.CheckID].InSync = true
}
l.logger.Printf("[WARN] agent: Service %q registration blocked by ACLs", id)
- metrics.IncrCounter([]string{"consul", "acl", "blocked", "service", "registration"}, 1)
+ metrics.IncrCounterWithLabels([]string{"consul", "acl", "blocked", "service", "registration"}, 1, []metrics.Label{{Name: "id", Value: id}})
return nil
default:
@@ -998,7 +998,7 @@ func (l *State) syncCheck(id types.CheckID) error {
// todo(fs): some backoff strategy might be a better solution
l.checks[id].InSync = true
l.logger.Printf("[WARN] agent: Check %q registration blocked by ACLs", id)
- metrics.IncrCounter([]string{"consul", "acl", "blocked", "service", "registration"}, 1)
+ metrics.IncrCounterWithLabels([]string{"consul", "acl", "blocked", "check", "registration"}, 1, []metrics.Label{{Name: "check", Value: string(id)}})
return nil
default:
@@ -1030,7 +1030,7 @@ func (l *State) syncNodeInfo() error {
// todo(fs): some backoff strategy might be a better solution
l.nodeInfoInSync = true
l.logger.Printf("[WARN] agent: Node info update blocked by ACLs")
- metrics.IncrCounter([]string{"consul", "acl", "blocked", "node", "registration"}, 1)
+ metrics.IncrCounterWithLabels([]string{"consul", "acl", "blocked", "node", "registration"}, 1, []metrics.Label{{Name: "id", Value: string(l.config.NodeID)}, {Name: "name", Value: l.config.NodeName}})
return nil
default:
From 65d3a2b26e1fca8a54d88ec6390b48bd22d04dd7 Mon Sep 17 00:00:00 2001
From: Pierre Souchay
Date: Wed, 18 Apr 2018 17:09:25 +0200
Subject: [PATCH 03/54] Fixed import
---
agent/local/state.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/agent/local/state.go b/agent/local/state.go
index 0e9b513e6c..c8505b2e27 100644
--- a/agent/local/state.go
+++ b/agent/local/state.go
@@ -10,7 +10,7 @@ import (
"sync/atomic"
"time"
- metrics "github.com/armon/go-metrics"
+ "github.com/armon/go-metrics"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/agent/token"
From 064f8ad1704943505a2d1f372555d25c0053c15b Mon Sep 17 00:00:00 2001
From: Pierre Souchay
Date: Fri, 8 Jun 2018 11:51:50 +0200
Subject: [PATCH 04/54] Removed consul prefix from metrics as requested by
@kyhavlov
---
agent/local/state.go | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/agent/local/state.go b/agent/local/state.go
index c8505b2e27..da0b354505 100644
--- a/agent/local/state.go
+++ b/agent/local/state.go
@@ -843,7 +843,7 @@ func (l *State) deleteService(id string) error {
// todo(fs): some backoff strategy might be a better solution
l.services[id].InSync = true
l.logger.Printf("[WARN] agent: Service %q deregistration blocked by ACLs", id)
- metrics.IncrCounterWithLabels([]string{"consul", "acl", "blocked", "service", "deregistration"}, 1, []metrics.Label{{Name: "id", Value: id}})
+ metrics.IncrCounterWithLabels([]string{"acl", "blocked", "service", "deregistration"}, 1, []metrics.Label{{Name: "id", Value: id}})
return nil
default:
@@ -881,7 +881,7 @@ func (l *State) deleteCheck(id types.CheckID) error {
// todo(fs): some backoff strategy might be a better solution
l.checks[id].InSync = true
l.logger.Printf("[WARN] agent: Check %q deregistration blocked by ACLs", id)
- metrics.IncrCounterWithLabels([]string{"consul", "acl", "blocked", "check", "deregistration"}, 1, []metrics.Label{{Name: "id", Value: string(id)}})
+ metrics.IncrCounterWithLabels([]string{"acl", "blocked", "check", "deregistration"}, 1, []metrics.Label{{Name: "id", Value: string(id)}})
return nil
default:
@@ -952,7 +952,7 @@ func (l *State) syncService(id string) error {
l.checks[check.CheckID].InSync = true
}
l.logger.Printf("[WARN] agent: Service %q registration blocked by ACLs", id)
- metrics.IncrCounterWithLabels([]string{"consul", "acl", "blocked", "service", "registration"}, 1, []metrics.Label{{Name: "id", Value: id}})
+ metrics.IncrCounterWithLabels([]string{"acl", "blocked", "service", "registration"}, 1, []metrics.Label{{Name: "id", Value: id}})
return nil
default:
@@ -998,7 +998,7 @@ func (l *State) syncCheck(id types.CheckID) error {
// todo(fs): some backoff strategy might be a better solution
l.checks[id].InSync = true
l.logger.Printf("[WARN] agent: Check %q registration blocked by ACLs", id)
- metrics.IncrCounterWithLabels([]string{"consul", "acl", "blocked", "check", "registration"}, 1, []metrics.Label{{Name: "check", Value: string(id)}})
+ metrics.IncrCounterWithLabels([]string{"acl", "blocked", "check", "registration"}, 1, []metrics.Label{{Name: "check", Value: string(id)}})
return nil
default:
@@ -1030,7 +1030,7 @@ func (l *State) syncNodeInfo() error {
// todo(fs): some backoff strategy might be a better solution
l.nodeInfoInSync = true
l.logger.Printf("[WARN] agent: Node info update blocked by ACLs")
- metrics.IncrCounterWithLabels([]string{"consul", "acl", "blocked", "node", "registration"}, 1, []metrics.Label{{Name: "id", Value: string(l.config.NodeID)}, {Name: "name", Value: l.config.NodeName}})
+ metrics.IncrCounterWithLabels([]string{"acl", "blocked", "node", "registration"}, 1, []metrics.Label{{Name: "id", Value: string(l.config.NodeID)}, {Name: "name", Value: l.config.NodeName}})
return nil
default:
From c83124a94cbf22664fa467008ed98f04658fd069 Mon Sep 17 00:00:00 2001
From: Pierre Souchay
Date: Fri, 8 Jun 2018 11:56:46 +0200
Subject: [PATCH 05/54] Removed labels from new ACL denied metrics
---
agent/local/state.go | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/agent/local/state.go b/agent/local/state.go
index da0b354505..ba56636cda 100644
--- a/agent/local/state.go
+++ b/agent/local/state.go
@@ -843,7 +843,7 @@ func (l *State) deleteService(id string) error {
// todo(fs): some backoff strategy might be a better solution
l.services[id].InSync = true
l.logger.Printf("[WARN] agent: Service %q deregistration blocked by ACLs", id)
- metrics.IncrCounterWithLabels([]string{"acl", "blocked", "service", "deregistration"}, 1, []metrics.Label{{Name: "id", Value: id}})
+ metrics.IncrCounter([]string{"acl", "blocked", "service", "deregistration"}, 1)
return nil
default:
@@ -881,7 +881,7 @@ func (l *State) deleteCheck(id types.CheckID) error {
// todo(fs): some backoff strategy might be a better solution
l.checks[id].InSync = true
l.logger.Printf("[WARN] agent: Check %q deregistration blocked by ACLs", id)
- metrics.IncrCounterWithLabels([]string{"acl", "blocked", "check", "deregistration"}, 1, []metrics.Label{{Name: "id", Value: string(id)}})
+ metrics.IncrCounter([]string{"acl", "blocked", "check", "deregistration"}, 1)
return nil
default:
@@ -952,7 +952,7 @@ func (l *State) syncService(id string) error {
l.checks[check.CheckID].InSync = true
}
l.logger.Printf("[WARN] agent: Service %q registration blocked by ACLs", id)
- metrics.IncrCounterWithLabels([]string{"acl", "blocked", "service", "registration"}, 1, []metrics.Label{{Name: "id", Value: id}})
+ metrics.IncrCounter([]string{"acl", "blocked", "service", "registration"}, 1)
return nil
default:
@@ -998,7 +998,7 @@ func (l *State) syncCheck(id types.CheckID) error {
// todo(fs): some backoff strategy might be a better solution
l.checks[id].InSync = true
l.logger.Printf("[WARN] agent: Check %q registration blocked by ACLs", id)
- metrics.IncrCounterWithLabels([]string{"acl", "blocked", "check", "registration"}, 1, []metrics.Label{{Name: "check", Value: string(id)}})
+ metrics.IncrCounter([]string{"acl", "blocked", "check", "registration"}, 1)
return nil
default:
@@ -1030,7 +1030,7 @@ func (l *State) syncNodeInfo() error {
// todo(fs): some backoff strategy might be a better solution
l.nodeInfoInSync = true
l.logger.Printf("[WARN] agent: Node info update blocked by ACLs")
- metrics.IncrCounterWithLabels([]string{"acl", "blocked", "node", "registration"}, 1, []metrics.Label{{Name: "id", Value: string(l.config.NodeID)}, {Name: "name", Value: l.config.NodeName}})
+ metrics.IncrCounter([]string{"acl", "blocked", "node", "registration"}, 1)
return nil
default:
From 44deab454d3974542b2fda4aa2e7649fb7b2a4f5 Mon Sep 17 00:00:00 2001
From: John Cowen
Date: Mon, 25 Jun 2018 12:11:01 +0100
Subject: [PATCH 06/54] Reset `` after every test, back to its
original static value
---
ui-v2/tests/acceptance/startup.feature | 1 -
ui-v2/tests/helpers/yadda-annotations.js | 15 +++++++++++++--
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/ui-v2/tests/acceptance/startup.feature b/ui-v2/tests/acceptance/startup.feature
index 70e54705ca..a847fef3d6 100644
--- a/ui-v2/tests/acceptance/startup.feature
+++ b/ui-v2/tests/acceptance/startup.feature
@@ -3,7 +3,6 @@ Feature: startup
In order to give users an indication as early as possible that they are at the right place
As a user
I should be able to see a startup logo
-@ignore
Scenario: When loading the index.html file into a browser
Given 1 datacenter model with the value "dc-1"
Then the url should be ''
diff --git a/ui-v2/tests/helpers/yadda-annotations.js b/ui-v2/tests/helpers/yadda-annotations.js
index 7c972601e4..43cc67853b 100644
--- a/ui-v2/tests/helpers/yadda-annotations.js
+++ b/ui-v2/tests/helpers/yadda-annotations.js
@@ -3,6 +3,18 @@ import { skip } from 'qunit';
import { setupApplicationTest, setupRenderingTest, setupTest } from 'ember-qunit';
import api from 'consul-ui/tests/helpers/api';
+const staticClassList = [...document.documentElement.classList];
+function reset() {
+ window.localStorage.clear();
+ api.server.reset();
+ const list = document.documentElement.classList;
+ while (list.length > 0) {
+ list.remove(list.item(0));
+ }
+ staticClassList.forEach(function(item) {
+ list.add(item);
+ });
+}
// this logic could be anything, but in this case...
// if @ignore, then return skip (for backwards compatibility)
// if have annotations in config, then only run those that have a matching annotation
@@ -64,8 +76,7 @@ function setupScenario(featureAnnotations, scenarioAnnotations) {
}
return function(model) {
model.afterEach(function() {
- window.localStorage.clear();
- api.server.reset();
+ reset();
});
};
// return setupFn;
From 8e7e224f3fdb69497e000c25c5c109b6aa1d6b1f Mon Sep 17 00:00:00 2001
From: John Cowen
Date: Tue, 26 Jun 2018 10:48:26 +0100
Subject: [PATCH 07/54] Encode all the hexcodes
---
ui-v2/app/styles/components/icons.scss | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/ui-v2/app/styles/components/icons.scss b/ui-v2/app/styles/components/icons.scss
index 5b4c139302..e12d1fae52 100644
--- a/ui-v2/app/styles/components/icons.scss
+++ b/ui-v2/app/styles/components/icons.scss
@@ -152,18 +152,18 @@
}
%with-right-arrow-grey {
@extend %pseudo-icon;
- background-image: url('data:image/svg+xml;charset=UTF-8,');
+ background-image: url('data:image/svg+xml;charset=UTF-8,');
}
%with-deny-icon {
@extend %pseudo-icon;
- background-image: url('data:image/svg+xml;charset=UTF-8,');
+ background-image: url('data:image/svg+xml;charset=UTF-8,');
width: 16px;
height: 16px;
background-color: transparent;
}
%with-deny-icon-grey {
@extend %pseudo-icon;
- background-image: url('data:image/svg+xml;charset=UTF-8,');
+ background-image: url('data:image/svg+xml;charset=UTF-8,');
}
%with-deny::before {
@extend %with-deny-icon;
From 9406ca1c959e591299351dfaf26fbf0c26dd71c0 Mon Sep 17 00:00:00 2001
From: Pierre Souchay
Date: Thu, 28 Jun 2018 09:06:14 +0200
Subject: [PATCH 08/54] Only send one single ACL cache refresh across network
when TTL is over
It will allow the following:
* when connectivity is limited (saturated linnks between DCs), only one
single request to refresh an ACL will be sent to ACL master DC instead
of statcking ACL refresh queries
* when extend-cache is used for ACL, do not wait for result, but refresh
the ACL asynchronously, so no delay is not impacting slave DC
* When extend-cache is not used, keep the existing blocking mechanism,
but only send a single refresh request.
This will fix https://github.com/hashicorp/consul/issues/3524
---
agent/consul/acl.go | 79 +++++++++++++++++++++++++++++++++++++++++----
1 file changed, 72 insertions(+), 7 deletions(-)
diff --git a/agent/consul/acl.go b/agent/consul/acl.go
index ce3282b40e..0bf1dbb4cd 100644
--- a/agent/consul/acl.go
+++ b/agent/consul/acl.go
@@ -4,6 +4,7 @@ import (
"fmt"
"log"
"os"
+ "sync"
"time"
"github.com/armon/go-metrics"
@@ -116,6 +117,9 @@ type aclCache struct {
// local is a function used to look for an ACL locally if replication is
// enabled. This will be nil if replication isn't enabled.
local acl.FaultFunc
+
+ fetchMutex sync.RWMutex
+ fetchMap map[string][]chan (RemoteACLResult)
}
// newACLCache returns a new non-authoritative cache for ACLs. This is used for
@@ -146,6 +150,11 @@ func newACLCache(conf *Config, logger *log.Logger, rpc rpcFn, local acl.FaultFun
return cache, nil
}
+type RemoteACLResult struct {
+ result acl.ACL
+ err error
+}
+
// lookupACL is used when we are non-authoritative, and need to resolve an ACL.
func (c *aclCache) lookupACL(id, authDC string) (acl.ACL, error) {
// Check the cache for the ACL.
@@ -161,8 +170,22 @@ func (c *aclCache) lookupACL(id, authDC string) (acl.ACL, error) {
return cached.ACL, nil
}
metrics.IncrCounter([]string{"acl", "cache_miss"}, 1)
+ res := c.lookupACLRemote(id, authDC, cached)
+ return res.result, res.err
+}
- // Attempt to refresh the policy from the ACL datacenter via an RPC.
+func (c *aclCache) fireResult(id string, theACL acl.ACL, err error) {
+ c.fetchMutex.Lock()
+ channels := c.fetchMap[id]
+ delete(c.fetchMap, id)
+ c.fetchMutex.Unlock()
+ for _, cx := range channels {
+ cx <- RemoteACLResult{theACL, err}
+ close(cx)
+ }
+}
+
+func (c *aclCache) loadACLInChan(id, authDC string, cached *aclCacheEntry) {
args := structs.ACLPolicyRequest{
Datacenter: authDC,
ACL: id,
@@ -173,13 +196,21 @@ func (c *aclCache) lookupACL(id, authDC string) (acl.ACL, error) {
var reply structs.ACLPolicy
err := c.rpc("ACL.GetPolicy", &args, &reply)
if err == nil {
- return c.useACLPolicy(id, authDC, cached, &reply)
+ theACL, theError := c.useACLPolicy(id, authDC, cached, &reply)
+ if cached != nil && theACL != nil {
+ cached.ACL = theACL
+ cached.ETag = reply.ETag
+ cached.Expires = time.Now().Add(c.config.ACLTTL)
+ }
+ c.fireResult(id, theACL, theError)
+ return
}
// Check for not-found, which will cause us to bail immediately. For any
// other error we report it in the logs but can continue.
if acl.IsErrNotFound(err) {
- return nil, acl.ErrNotFound
+ c.fireResult(id, nil, acl.ErrNotFound)
+ return
}
c.logger.Printf("[ERR] consul.acl: Failed to get policy from ACL datacenter: %v", err)
@@ -227,24 +258,58 @@ func (c *aclCache) lookupACL(id, authDC string) (acl.ACL, error) {
reply.TTL = c.config.ACLTTL
reply.Parent = parent
reply.Policy = policy
- return c.useACLPolicy(id, authDC, cached, &reply)
+ theACL, theError := c.useACLPolicy(id, authDC, cached, &reply)
+ if cached != nil && theACL != nil {
+ cached.ACL = theACL
+ cached.ETag = reply.ETag
+ cached.Expires = time.Now().Add(c.config.ACLTTL)
+ }
+ c.fireResult(id, theACL, theError)
+ return
}
ACL_DOWN:
// Unable to refresh, apply the down policy.
switch c.config.ACLDownPolicy {
case "allow":
- return acl.AllowAll(), nil
+ c.fireResult(id, acl.AllowAll(), nil)
+ return
case "extend-cache":
if cached != nil {
- return cached.ACL, nil
+ c.fireResult(id, cached.ACL, nil)
+ return
}
fallthrough
default:
- return acl.DenyAll(), nil
+ c.fireResult(id, acl.DenyAll(), nil)
+ return
}
}
+func (c *aclCache) lookupACLRemote(id, authDC string, cached *aclCacheEntry) RemoteACLResult {
+ // Attempt to refresh the policy from the ACL datacenter via an RPC.
+ myChan := make(chan RemoteACLResult)
+ mustWaitForResult := cached == nil || c.config.ACLDownPolicy != "extend-cache"
+ c.fetchMutex.Lock()
+ clients, ok := c.fetchMap[id]
+ if !ok {
+ clients = make([]chan RemoteACLResult, 16)
+ }
+ if mustWaitForResult {
+ c.fetchMap[id] = append(clients, myChan)
+ }
+ c.fetchMutex.Unlock()
+
+ if !ok {
+ go c.loadACLInChan(id, authDC, cached)
+ }
+ if !mustWaitForResult {
+ return RemoteACLResult{cached.ACL, nil}
+ }
+ res := <-myChan
+ return res
+}
+
// useACLPolicy handles an ACLPolicy response
func (c *aclCache) useACLPolicy(id, authDC string, cached *aclCacheEntry, p *structs.ACLPolicy) (acl.ACL, error) {
// Check if we can used the cached policy
From bfc83ce045ee6c2ef56079e0e5ecb4fda931ed05 Mon Sep 17 00:00:00 2001
From: Pierre Souchay
Date: Fri, 29 Jun 2018 10:59:36 +0200
Subject: [PATCH 09/54] Updated ACL guide
---
website/source/docs/guides/acl.html.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/website/source/docs/guides/acl.html.md b/website/source/docs/guides/acl.html.md
index a78f60b55e..f6aad52bce 100644
--- a/website/source/docs/guides/acl.html.md
+++ b/website/source/docs/guides/acl.html.md
@@ -1061,6 +1061,10 @@ and the [`acl_down_policy`](/docs/agent/options.html#acl_down_policy)
is set to "extend-cache", tokens will be resolved during the outage using the
replicated set of ACLs. An [ACL replication status](/api/acl.html#acl_replication_status)
endpoint is available to monitor the health of the replication process.
+Also note that in recent versions of Consul (greater than 1.2.0), using
+`acl_down_policy = "extend-cache"` refreshes token asynchronously when an ACL is
+already cached and is expired. It allows to avoid having issues when connectivity with
+the authoritative is not completely broken, but very slow.
Locally-resolved ACLs will be cached using the [`acl_ttl`](/docs/agent/options.html#acl_ttl)
setting of the non-authoritative datacenter, so these entries may persist in the
From abde81a3e759b99aefab232d78fb871d9688f532 Mon Sep 17 00:00:00 2001
From: Pierre Souchay
Date: Sun, 1 Jul 2018 12:50:53 +0200
Subject: [PATCH 10/54] Added async-cache with similar behaviour as
extend-cache but asynchronously
---
agent/acl.go | 1 +
agent/acl_test.go | 133 ++++++++++++++++++++-------------------
agent/config/runtime.go | 4 +-
agent/consul/acl.go | 14 +++--
agent/consul/acl_test.go | 132 +++++++++++++++++++-------------------
agent/consul/config.go | 6 +-
6 files changed, 153 insertions(+), 137 deletions(-)
diff --git a/agent/acl.go b/agent/acl.go
index e266feafa8..1143da97cc 100644
--- a/agent/acl.go
+++ b/agent/acl.go
@@ -104,6 +104,7 @@ func newACLManager(config *config.RuntimeConfig) (*aclManager, error) {
case "deny":
down = acl.DenyAll()
case "extend-cache":
+ case "async-cache":
// Leave the down policy as nil to signal this.
default:
return nil, fmt.Errorf("invalid ACL down policy %q", config.ACLDownPolicy)
diff --git a/agent/acl_test.go b/agent/acl_test.go
index 8b16269981..0932dc5254 100644
--- a/agent/acl_test.go
+++ b/agent/acl_test.go
@@ -274,79 +274,82 @@ func TestACL_Down_Allow(t *testing.T) {
func TestACL_Down_Extend(t *testing.T) {
t.Parallel()
- a := NewTestAgent(t.Name(), TestACLConfig()+`
- acl_down_policy = "extend-cache"
+ aclExtendPolicies := []string{"extend-cache", "async-cache"}
+ for _, aclDownPolicy := range aclExtendPolicies {
+ a := NewTestAgent(t.Name(), TestACLConfig()+`
+ acl_down_policy = "`+aclDownPolicy+`"
acl_enforce_version_8 = true
`)
- defer a.Shutdown()
+ defer a.Shutdown()
- m := MockServer{
- // Populate the cache for one of the tokens.
- getPolicyFn: func(req *structs.ACLPolicyRequest, reply *structs.ACLPolicy) error {
- *reply = structs.ACLPolicy{
- Parent: "allow",
- Policy: &rawacl.Policy{
- Agents: []*rawacl.AgentPolicy{
- &rawacl.AgentPolicy{
- Node: a.config.NodeName,
- Policy: "read",
+ m := MockServer{
+ // Populate the cache for one of the tokens.
+ getPolicyFn: func(req *structs.ACLPolicyRequest, reply *structs.ACLPolicy) error {
+ *reply = structs.ACLPolicy{
+ Parent: "allow",
+ Policy: &rawacl.Policy{
+ Agents: []*rawacl.AgentPolicy{
+ &rawacl.AgentPolicy{
+ Node: a.config.NodeName,
+ Policy: "read",
+ },
},
},
- },
- }
- return nil
- },
- }
- if err := a.registerEndpoint("ACL", &m); err != nil {
- t.Fatalf("err: %v", err)
- }
+ }
+ return nil
+ },
+ }
+ if err := a.registerEndpoint("ACL", &m); err != nil {
+ t.Fatalf("err: %v", err)
+ }
- acl, err := a.resolveToken("yep")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if acl == nil {
- t.Fatalf("should not be nil")
- }
- if !acl.AgentRead(a.config.NodeName) {
- t.Fatalf("should allow")
- }
- if acl.AgentWrite(a.config.NodeName) {
- t.Fatalf("should deny")
- }
+ acl, err := a.resolveToken("yep")
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ if acl == nil {
+ t.Fatalf("should not be nil")
+ }
+ if !acl.AgentRead(a.config.NodeName) {
+ t.Fatalf("should allow")
+ }
+ if acl.AgentWrite(a.config.NodeName) {
+ t.Fatalf("should deny")
+ }
- // Now take down ACLs and make sure a new token fails to resolve.
- m.getPolicyFn = func(*structs.ACLPolicyRequest, *structs.ACLPolicy) error {
- return fmt.Errorf("ACLs are broken")
- }
- acl, err = a.resolveToken("nope")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if acl == nil {
- t.Fatalf("should not be nil")
- }
- if acl.AgentRead(a.config.NodeName) {
- t.Fatalf("should deny")
- }
- if acl.AgentWrite(a.config.NodeName) {
- t.Fatalf("should deny")
- }
+ // Now take down ACLs and make sure a new token fails to resolve.
+ m.getPolicyFn = func(*structs.ACLPolicyRequest, *structs.ACLPolicy) error {
+ return fmt.Errorf("ACLs are broken")
+ }
+ acl, err = a.resolveToken("nope")
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ if acl == nil {
+ t.Fatalf("should not be nil")
+ }
+ if acl.AgentRead(a.config.NodeName) {
+ t.Fatalf("should deny")
+ }
+ if acl.AgentWrite(a.config.NodeName) {
+ t.Fatalf("should deny")
+ }
- // Read the token from the cache while ACLs are broken, which should
- // extend.
- acl, err = a.resolveToken("yep")
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if acl == nil {
- t.Fatalf("should not be nil")
- }
- if !acl.AgentRead(a.config.NodeName) {
- t.Fatalf("should allow")
- }
- if acl.AgentWrite(a.config.NodeName) {
- t.Fatalf("should deny")
+ // Read the token from the cache while ACLs are broken, which should
+ // extend.
+ acl, err = a.resolveToken("yep")
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ if acl == nil {
+ t.Fatalf("should not be nil")
+ }
+ if !acl.AgentRead(a.config.NodeName) {
+ t.Fatalf("should allow")
+ }
+ if acl.AgentWrite(a.config.NodeName) {
+ t.Fatalf("should deny")
+ }
}
}
diff --git a/agent/config/runtime.go b/agent/config/runtime.go
index 9674d14d5f..05ad8796ca 100644
--- a/agent/config/runtime.go
+++ b/agent/config/runtime.go
@@ -94,8 +94,10 @@ type RuntimeConfig struct {
// ACL's to be used to service requests. This
// is the default. If the ACL is not in the cache,
// this acts like deny.
+ // * async-cache - Same behaviour as extend-cache, but perform ACL
+ // Lookups asynchronously when cache TTL is expired.
//
- // hcl: acl_down_policy = ("allow"|"deny"|"extend-cache")
+ // hcl: acl_down_policy = ("allow"|"deny"|"extend-cache"|"async-cache")
ACLDownPolicy string
// ACLEnforceVersion8 is used to gate a set of ACL policy features that
diff --git a/agent/consul/acl.go b/agent/consul/acl.go
index 0bf1dbb4cd..23bdbff356 100644
--- a/agent/consul/acl.go
+++ b/agent/consul/acl.go
@@ -146,10 +146,12 @@ func newACLCache(conf *Config, logger *log.Logger, rpc rpcFn, local acl.FaultFun
if err != nil {
return nil, fmt.Errorf("Failed to create ACL policy cache: %v", err)
}
+ cache.fetchMap = make(map[string][]chan (RemoteACLResult))
return cache, nil
}
+// Result Type returned when fetching Remote ACLs asynchronously
type RemoteACLResult struct {
result acl.ACL
err error
@@ -179,8 +181,9 @@ func (c *aclCache) fireResult(id string, theACL acl.ACL, err error) {
channels := c.fetchMap[id]
delete(c.fetchMap, id)
c.fetchMutex.Unlock()
+ aclResult := RemoteACLResult{theACL, err}
for _, cx := range channels {
- cx <- RemoteACLResult{theACL, err}
+ cx <- aclResult
close(cx)
}
}
@@ -231,7 +234,7 @@ func (c *aclCache) loadACLInChan(id, authDC string, cached *aclCacheEntry) {
// local ACL fault function is registered to query replicated ACL data,
// and the user's policy allows it, we will try locally before we give
// up.
- if c.local != nil && c.config.ACLDownPolicy == "extend-cache" {
+ if c.local != nil && (c.config.ACLDownPolicy == "extend-cache" || c.config.ACLDownPolicy == "async-cache") {
parent, rules, err := c.local(id)
if err != nil {
// We don't make an exception here for ACLs that aren't
@@ -274,6 +277,7 @@ ACL_DOWN:
case "allow":
c.fireResult(id, acl.AllowAll(), nil)
return
+ case "async-cache":
case "extend-cache":
if cached != nil {
c.fireResult(id, cached.ACL, nil)
@@ -289,11 +293,11 @@ ACL_DOWN:
func (c *aclCache) lookupACLRemote(id, authDC string, cached *aclCacheEntry) RemoteACLResult {
// Attempt to refresh the policy from the ACL datacenter via an RPC.
myChan := make(chan RemoteACLResult)
- mustWaitForResult := cached == nil || c.config.ACLDownPolicy != "extend-cache"
+ mustWaitForResult := cached == nil || c.config.ACLDownPolicy != "async-cache"
c.fetchMutex.Lock()
clients, ok := c.fetchMap[id]
- if !ok {
- clients = make([]chan RemoteACLResult, 16)
+ if !ok || clients == nil {
+ clients = make([]chan RemoteACLResult, 0)
}
if mustWaitForResult {
c.fetchMap[id] = append(clients, myChan)
diff --git a/agent/consul/acl_test.go b/agent/consul/acl_test.go
index ace1284a86..fd08b54ab8 100644
--- a/agent/consul/acl_test.go
+++ b/agent/consul/acl_test.go
@@ -508,78 +508,82 @@ func TestACL_DownPolicy_Allow(t *testing.T) {
func TestACL_DownPolicy_ExtendCache(t *testing.T) {
t.Parallel()
- dir1, s1 := testServerWithConfig(t, func(c *Config) {
- c.ACLDatacenter = "dc1"
- c.ACLTTL = 0
- c.ACLDownPolicy = "extend-cache"
- c.ACLMasterToken = "root"
- })
- defer os.RemoveAll(dir1)
- defer s1.Shutdown()
- client := rpcClient(t, s1)
- defer client.Close()
+ aclExtendPolicies := []string{"extend-cache", "async-cache"} //"async-cache"
- dir2, s2 := testServerWithConfig(t, func(c *Config) {
- c.ACLDatacenter = "dc1" // Enable ACLs!
- c.ACLTTL = 0
- c.ACLDownPolicy = "extend-cache"
- c.Bootstrap = false // Disable bootstrap
- })
- defer os.RemoveAll(dir2)
- defer s2.Shutdown()
+ for _, aclDownPolicy := range aclExtendPolicies {
+ dir1, s1 := testServerWithConfig(t, func(c *Config) {
+ c.ACLDatacenter = "dc1"
+ c.ACLTTL = 0
+ c.ACLDownPolicy = aclDownPolicy
+ c.ACLMasterToken = "root"
+ })
+ defer os.RemoveAll(dir1)
+ defer s1.Shutdown()
+ client := rpcClient(t, s1)
+ defer client.Close()
- // Try to join
- joinLAN(t, s2, s1)
- retry.Run(t, func(r *retry.R) { r.Check(wantRaft([]*Server{s1, s2})) })
+ dir2, s2 := testServerWithConfig(t, func(c *Config) {
+ c.ACLDatacenter = "dc1" // Enable ACLs!
+ c.ACLTTL = 0
+ c.ACLDownPolicy = aclDownPolicy
+ c.Bootstrap = false // Disable bootstrap
+ })
+ defer os.RemoveAll(dir2)
+ defer s2.Shutdown()
- testrpc.WaitForLeader(t, s1.RPC, "dc1")
+ // Try to join
+ joinLAN(t, s2, s1)
+ retry.Run(t, func(r *retry.R) { r.Check(wantRaft([]*Server{s1, s2})) })
- // Create a new token
- arg := structs.ACLRequest{
- Datacenter: "dc1",
- Op: structs.ACLSet,
- ACL: structs.ACL{
- Name: "User token",
- Type: structs.ACLTypeClient,
- Rules: testACLPolicy,
- },
- WriteRequest: structs.WriteRequest{Token: "root"},
- }
- var id string
- if err := s1.RPC("ACL.Apply", &arg, &id); err != nil {
- t.Fatalf("err: %v", err)
- }
+ testrpc.WaitForLeader(t, s1.RPC, "dc1")
- // find the non-authoritative server
- var nonAuth *Server
- var auth *Server
- if !s1.IsLeader() {
- nonAuth = s1
- auth = s2
- } else {
- nonAuth = s2
- auth = s1
- }
+ // Create a new token
+ arg := structs.ACLRequest{
+ Datacenter: "dc1",
+ Op: structs.ACLSet,
+ ACL: structs.ACL{
+ Name: "User token",
+ Type: structs.ACLTypeClient,
+ Rules: testACLPolicy,
+ },
+ WriteRequest: structs.WriteRequest{Token: "root"},
+ }
+ var id string
+ if err := s1.RPC("ACL.Apply", &arg, &id); err != nil {
+ t.Fatalf("err: %v", err)
+ }
- // Warm the caches
- aclR, err := nonAuth.resolveToken(id)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if aclR == nil {
- t.Fatalf("bad acl: %#v", aclR)
- }
+ // find the non-authoritative server
+ var nonAuth *Server
+ var auth *Server
+ if !s1.IsLeader() {
+ nonAuth = s1
+ auth = s2
+ } else {
+ nonAuth = s2
+ auth = s1
+ }
- // Kill the authoritative server
- auth.Shutdown()
+ // Warm the caches
+ aclR, err := nonAuth.resolveToken(id)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ if aclR == nil {
+ t.Fatalf("bad acl: %#v", aclR)
+ }
- // Token should resolve into cached copy
- aclR2, err := nonAuth.resolveToken(id)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if aclR2 != aclR {
- t.Fatalf("bad acl: %#v", aclR)
+ // Kill the authoritative server
+ auth.Shutdown()
+
+ // Token should resolve into cached copy
+ aclR2, err := nonAuth.resolveToken(id)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ if aclR2 != aclR {
+ t.Fatalf("bad acl: %#v", aclR)
+ }
}
}
diff --git a/agent/consul/config.go b/agent/consul/config.go
index 29a5245319..570a07c163 100644
--- a/agent/consul/config.go
+++ b/agent/consul/config.go
@@ -235,8 +235,9 @@ type Config struct {
// ACLDownPolicy controls the behavior of ACLs if the ACLDatacenter
// cannot be contacted. It can be either "deny" to deny all requests,
- // or "extend-cache" which ignores the ACLCacheInterval and uses
- // cached policies. If a policy is not in the cache, it acts like deny.
+ // "extend-cache" or "async-cache" which ignores the ACLCacheInterval and
+ // uses cached policies.
+ // If a policy is not in the cache, it acts like deny.
// "allow" can be used to allow all requests. This is not recommended.
ACLDownPolicy string
@@ -378,6 +379,7 @@ func (c *Config) CheckACL() error {
switch c.ACLDownPolicy {
case "allow":
case "deny":
+ case "async-cache":
case "extend-cache":
default:
return fmt.Errorf("Unsupported down ACL policy: %s", c.ACLDownPolicy)
From 1e7665c0d58111ee7f4d1fcd4d6fbd7b26c0ec82 Mon Sep 17 00:00:00 2001
From: Pierre Souchay
Date: Sun, 1 Jul 2018 20:00:20 +0200
Subject: [PATCH 11/54] Updated documentation and adding more test case for
async-cache
---
agent/consul/acl_test.go | 198 +++++++++++-----------
website/source/docs/agent/options.html.md | 6 +-
website/source/docs/guides/acl.html.md | 7 +-
3 files changed, 109 insertions(+), 102 deletions(-)
diff --git a/agent/consul/acl_test.go b/agent/consul/acl_test.go
index fd08b54ab8..45048f80a1 100644
--- a/agent/consul/acl_test.go
+++ b/agent/consul/acl_test.go
@@ -589,116 +589,120 @@ func TestACL_DownPolicy_ExtendCache(t *testing.T) {
func TestACL_Replication(t *testing.T) {
t.Parallel()
- dir1, s1 := testServerWithConfig(t, func(c *Config) {
- c.ACLDatacenter = "dc1"
- c.ACLMasterToken = "root"
- })
- defer os.RemoveAll(dir1)
- defer s1.Shutdown()
- client := rpcClient(t, s1)
- defer client.Close()
+ aclExtendPolicies := []string{"extend-cache", "async-cache"} //"async-cache"
- dir2, s2 := testServerWithConfig(t, func(c *Config) {
- c.Datacenter = "dc2"
- c.ACLDatacenter = "dc1"
- c.ACLDefaultPolicy = "deny"
- c.ACLDownPolicy = "extend-cache"
- c.EnableACLReplication = true
- c.ACLReplicationInterval = 10 * time.Millisecond
- c.ACLReplicationApplyLimit = 1000000
- })
- s2.tokens.UpdateACLReplicationToken("root")
- defer os.RemoveAll(dir2)
- defer s2.Shutdown()
+ for _, aclDownPolicy := range aclExtendPolicies {
+ dir1, s1 := testServerWithConfig(t, func(c *Config) {
+ c.ACLDatacenter = "dc1"
+ c.ACLMasterToken = "root"
+ })
+ defer os.RemoveAll(dir1)
+ defer s1.Shutdown()
+ client := rpcClient(t, s1)
+ defer client.Close()
- dir3, s3 := testServerWithConfig(t, func(c *Config) {
- c.Datacenter = "dc3"
- c.ACLDatacenter = "dc1"
- c.ACLDownPolicy = "deny"
- c.EnableACLReplication = true
- c.ACLReplicationInterval = 10 * time.Millisecond
- c.ACLReplicationApplyLimit = 1000000
- })
- s3.tokens.UpdateACLReplicationToken("root")
- defer os.RemoveAll(dir3)
- defer s3.Shutdown()
+ dir2, s2 := testServerWithConfig(t, func(c *Config) {
+ c.Datacenter = "dc2"
+ c.ACLDatacenter = "dc1"
+ c.ACLDefaultPolicy = "deny"
+ c.ACLDownPolicy = aclDownPolicy
+ c.EnableACLReplication = true
+ c.ACLReplicationInterval = 10 * time.Millisecond
+ c.ACLReplicationApplyLimit = 1000000
+ })
+ s2.tokens.UpdateACLReplicationToken("root")
+ defer os.RemoveAll(dir2)
+ defer s2.Shutdown()
- // Try to join.
- joinWAN(t, s2, s1)
- joinWAN(t, s3, s1)
- testrpc.WaitForLeader(t, s1.RPC, "dc1")
- testrpc.WaitForLeader(t, s1.RPC, "dc2")
- testrpc.WaitForLeader(t, s1.RPC, "dc3")
+ dir3, s3 := testServerWithConfig(t, func(c *Config) {
+ c.Datacenter = "dc3"
+ c.ACLDatacenter = "dc1"
+ c.ACLDownPolicy = "deny"
+ c.EnableACLReplication = true
+ c.ACLReplicationInterval = 10 * time.Millisecond
+ c.ACLReplicationApplyLimit = 1000000
+ })
+ s3.tokens.UpdateACLReplicationToken("root")
+ defer os.RemoveAll(dir3)
+ defer s3.Shutdown()
- // Create a new token.
- arg := structs.ACLRequest{
- Datacenter: "dc1",
- Op: structs.ACLSet,
- ACL: structs.ACL{
- Name: "User token",
- Type: structs.ACLTypeClient,
- Rules: testACLPolicy,
- },
- WriteRequest: structs.WriteRequest{Token: "root"},
- }
- var id string
- if err := s1.RPC("ACL.Apply", &arg, &id); err != nil {
- t.Fatalf("err: %v", err)
- }
- // Wait for replication to occur.
- retry.Run(t, func(r *retry.R) {
- _, acl, err := s2.fsm.State().ACLGet(nil, id)
+ // Try to join.
+ joinWAN(t, s2, s1)
+ joinWAN(t, s3, s1)
+ testrpc.WaitForLeader(t, s1.RPC, "dc1")
+ testrpc.WaitForLeader(t, s1.RPC, "dc2")
+ testrpc.WaitForLeader(t, s1.RPC, "dc3")
+
+ // Create a new token.
+ arg := structs.ACLRequest{
+ Datacenter: "dc1",
+ Op: structs.ACLSet,
+ ACL: structs.ACL{
+ Name: "User token",
+ Type: structs.ACLTypeClient,
+ Rules: testACLPolicy,
+ },
+ WriteRequest: structs.WriteRequest{Token: "root"},
+ }
+ var id string
+ if err := s1.RPC("ACL.Apply", &arg, &id); err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ // Wait for replication to occur.
+ retry.Run(t, func(r *retry.R) {
+ _, acl, err := s2.fsm.State().ACLGet(nil, id)
+ if err != nil {
+ r.Fatal(err)
+ }
+ if acl == nil {
+ r.Fatal(nil)
+ }
+ _, acl, err = s3.fsm.State().ACLGet(nil, id)
+ if err != nil {
+ r.Fatal(err)
+ }
+ if acl == nil {
+ r.Fatal(nil)
+ }
+ })
+
+ // Kill the ACL datacenter.
+ s1.Shutdown()
+
+ // Token should resolve on s2, which has replication + extend-cache.
+ acl, err := s2.resolveToken(id)
if err != nil {
- r.Fatal(err)
+ t.Fatalf("err: %v", err)
}
if acl == nil {
- r.Fatal(nil)
+ t.Fatalf("missing acl")
}
- _, acl, err = s3.fsm.State().ACLGet(nil, id)
+
+ // Check the policy
+ if acl.KeyRead("bar") {
+ t.Fatalf("unexpected read")
+ }
+ if !acl.KeyRead("foo/test") {
+ t.Fatalf("unexpected failed read")
+ }
+
+ // Although s3 has replication, and we verified that the ACL is there,
+ // it can not be used because of the down policy.
+ acl, err = s3.resolveToken(id)
if err != nil {
- r.Fatal(err)
+ t.Fatalf("err: %v", err)
}
if acl == nil {
- r.Fatal(nil)
+ t.Fatalf("missing acl")
}
- })
- // Kill the ACL datacenter.
- s1.Shutdown()
-
- // Token should resolve on s2, which has replication + extend-cache.
- acl, err := s2.resolveToken(id)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if acl == nil {
- t.Fatalf("missing acl")
- }
-
- // Check the policy
- if acl.KeyRead("bar") {
- t.Fatalf("unexpected read")
- }
- if !acl.KeyRead("foo/test") {
- t.Fatalf("unexpected failed read")
- }
-
- // Although s3 has replication, and we verified that the ACL is there,
- // it can not be used because of the down policy.
- acl, err = s3.resolveToken(id)
- if err != nil {
- t.Fatalf("err: %v", err)
- }
- if acl == nil {
- t.Fatalf("missing acl")
- }
-
- // Check the policy.
- if acl.KeyRead("bar") {
- t.Fatalf("unexpected read")
- }
- if acl.KeyRead("foo/test") {
- t.Fatalf("unexpected read")
+ // Check the policy.
+ if acl.KeyRead("bar") {
+ t.Fatalf("unexpected read")
+ }
+ if acl.KeyRead("foo/test") {
+ t.Fatalf("unexpected read")
+ }
}
}
diff --git a/website/source/docs/agent/options.html.md b/website/source/docs/agent/options.html.md
index 4e2ecb3033..65402c2caf 100644
--- a/website/source/docs/agent/options.html.md
+++ b/website/source/docs/agent/options.html.md
@@ -496,11 +496,13 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
to enable ACL support.
* `acl_down_policy` - Either
- "allow", "deny" or "extend-cache"; "extend-cache" is the default. In the case that the
+ "allow", "deny", "extend-cache" or "async-cache"; "extend-cache" is the default. In the case that the
policy for a token cannot be read from the [`acl_datacenter`](#acl_datacenter) or leader
node, the down policy is applied. In "allow" mode, all actions are permitted, "deny" restricts
all operations, and "extend-cache" allows any cached ACLs to be used, ignoring their TTL
- values. If a non-cached ACL is used, "extend-cache" acts like "deny".
+ values. If a non-cached ACL is used, "extend-cache" acts like "deny". "async-cache" acts the same
+ way as "extend-cache" but performs updates asynchronously when ACL is present but its TTL is
+ expired.
* `acl_agent_master_token` -
Used to access agent endpoints that require agent read
diff --git a/website/source/docs/guides/acl.html.md b/website/source/docs/guides/acl.html.md
index f6aad52bce..c25666ab94 100644
--- a/website/source/docs/guides/acl.html.md
+++ b/website/source/docs/guides/acl.html.md
@@ -1062,9 +1062,10 @@ is set to "extend-cache", tokens will be resolved during the outage using the
replicated set of ACLs. An [ACL replication status](/api/acl.html#acl_replication_status)
endpoint is available to monitor the health of the replication process.
Also note that in recent versions of Consul (greater than 1.2.0), using
-`acl_down_policy = "extend-cache"` refreshes token asynchronously when an ACL is
-already cached and is expired. It allows to avoid having issues when connectivity with
-the authoritative is not completely broken, but very slow.
+`acl_down_policy = "async-cache"` refreshes token asynchronously when an ACL is
+already cached and is expired while similar semantics than "extend-cache".
+It allows to avoid having issues when connectivity with the authoritative is not completely
+broken, but very slow.
Locally-resolved ACLs will be cached using the [`acl_ttl`](/docs/agent/options.html#acl_ttl)
setting of the non-authoritative datacenter, so these entries may persist in the
From 0f1735634d1f9d522be336bc302a4684728dab67 Mon Sep 17 00:00:00 2001
From: Pierre Souchay
Date: Sun, 1 Jul 2018 23:37:40 +0200
Subject: [PATCH 12/54] Improve doc for async-cache
---
website/source/docs/agent/options.html.md | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/website/source/docs/agent/options.html.md b/website/source/docs/agent/options.html.md
index 65402c2caf..06f071dd04 100644
--- a/website/source/docs/agent/options.html.md
+++ b/website/source/docs/agent/options.html.md
@@ -500,9 +500,10 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
policy for a token cannot be read from the [`acl_datacenter`](#acl_datacenter) or leader
node, the down policy is applied. In "allow" mode, all actions are permitted, "deny" restricts
all operations, and "extend-cache" allows any cached ACLs to be used, ignoring their TTL
- values. If a non-cached ACL is used, "extend-cache" acts like "deny". "async-cache" acts the same
- way as "extend-cache" but performs updates asynchronously when ACL is present but its TTL is
- expired.
+ values. If a non-cached ACL is used, "extend-cache" acts like "deny".
+ The value "async-cache" acts the same way as "extend-cache" but performs updates
+ asynchronously when ACL is present but its TTL is expired, thus, if latency is bad between
+ ACL authoritative and other datacenters, latency of operations is not impacted.
* `acl_agent_master_token` -
Used to access agent endpoints that require agent read
From 684674b757b4f9fb50f2600513f5c05fc7950b55 Mon Sep 17 00:00:00 2001
From: Pierre Souchay
Date: Mon, 2 Jul 2018 00:24:37 +0200
Subject: [PATCH 13/54] Trying to fix Travis tests
---
GNUmakefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/GNUmakefile b/GNUmakefile
index 5df83416b9..d510c0fe6a 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -158,7 +158,7 @@ test: other-consul dev-build vet
@# hide it from travis as it exceeds their log limits and causes job to be
@# terminated (over 4MB and over 10k lines in the UI). We need to output
@# _something_ to stop them terminating us due to inactivity...
- { go test $(GOTEST_FLAGS) -tags '$(GOTAGS)' -timeout 5m $(GOTEST_PKGS) 2>&1 ; echo $$? > exit-code ; } | tee test.log | egrep '^(ok|FAIL)\s*github.com/hashicorp/consul'
+ { go test $(GOTEST_FLAGS) -tags '$(GOTAGS)' -timeout 7m $(GOTEST_PKGS) 2>&1 ; echo $$? > exit-code ; } | tee test.log | egrep '^(ok|FAIL)\s*github.com/hashicorp/consul'
@echo "Exit code: $$(cat exit-code)" >> test.log
@# This prints all the race report between ====== lines
@awk '/^WARNING: DATA RACE/ {do_print=1; print "=================="} do_print==1 {print} /^={10,}/ {do_print=0}' test.log || true
From bd023f352e466b47a773241c34249ad822bc4c91 Mon Sep 17 00:00:00 2001
From: Pierre Souchay
Date: Mon, 2 Jul 2018 17:39:34 +0200
Subject: [PATCH 14/54] Updated swith case to use same branch for async-cache
and extend-cache
---
agent/acl.go | 3 +--
agent/consul/acl.go | 3 +--
agent/consul/config.go | 3 +--
3 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/agent/acl.go b/agent/acl.go
index 1143da97cc..5c4deb7cc8 100644
--- a/agent/acl.go
+++ b/agent/acl.go
@@ -103,8 +103,7 @@ func newACLManager(config *config.RuntimeConfig) (*aclManager, error) {
down = acl.AllowAll()
case "deny":
down = acl.DenyAll()
- case "extend-cache":
- case "async-cache":
+ case "async-cache", "extend-cache":
// Leave the down policy as nil to signal this.
default:
return nil, fmt.Errorf("invalid ACL down policy %q", config.ACLDownPolicy)
diff --git a/agent/consul/acl.go b/agent/consul/acl.go
index 23bdbff356..4dbfe2b39e 100644
--- a/agent/consul/acl.go
+++ b/agent/consul/acl.go
@@ -277,8 +277,7 @@ ACL_DOWN:
case "allow":
c.fireResult(id, acl.AllowAll(), nil)
return
- case "async-cache":
- case "extend-cache":
+ case "async-cache", "extend-cache":
if cached != nil {
c.fireResult(id, cached.ACL, nil)
return
diff --git a/agent/consul/config.go b/agent/consul/config.go
index 570a07c163..b878d9a99d 100644
--- a/agent/consul/config.go
+++ b/agent/consul/config.go
@@ -379,8 +379,7 @@ func (c *Config) CheckACL() error {
switch c.ACLDownPolicy {
case "allow":
case "deny":
- case "async-cache":
- case "extend-cache":
+ case "async-cache", "extend-cache":
default:
return fmt.Errorf("Unsupported down ACL policy: %s", c.ACLDownPolicy)
}
From 1492243e0aaec34af642eb235fb424422c3dcd38 Mon Sep 17 00:00:00 2001
From: Kyle Havlovitz
Date: Thu, 21 Jun 2018 15:42:28 -0700
Subject: [PATCH 15/54] connect/ca: add logic for pruning old stale RootCA
entries
---
agent/consul/connect_ca_endpoint.go | 2 +
agent/consul/leader.go | 107 +++++++++++++++++++++++++++-
agent/consul/leader_test.go | 63 ++++++++++++++++
agent/consul/server.go | 6 ++
agent/structs/connect_ca.go | 5 ++
5 files changed, 182 insertions(+), 1 deletion(-)
diff --git a/agent/consul/connect_ca_endpoint.go b/agent/consul/connect_ca_endpoint.go
index 47672ee559..1e04f6733e 100644
--- a/agent/consul/connect_ca_endpoint.go
+++ b/agent/consul/connect_ca_endpoint.go
@@ -5,6 +5,7 @@ import (
"fmt"
"reflect"
"strings"
+ "time"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/connect"
@@ -177,6 +178,7 @@ func (s *ConnectCA) ConfigurationSet(
newRoot := *r
if newRoot.Active {
newRoot.Active = false
+ newRoot.RotateOutAt = time.Now().Add(caRootExpireDuration)
}
newRoots = append(newRoots, &newRoot)
}
diff --git a/agent/consul/leader.go b/agent/consul/leader.go
index 5fe4eebece..2b0497dc38 100644
--- a/agent/consul/leader.go
+++ b/agent/consul/leader.go
@@ -28,7 +28,18 @@ const (
barrierWriteTimeout = 2 * time.Minute
)
-var minAutopilotVersion = version.Must(version.NewVersion("0.8.0"))
+var (
+ // caRootPruneInterval is how often we check for stale CARoots to remove.
+ caRootPruneInterval = time.Hour
+
+ // caRootExpireDuration is the duration after which an inactive root is considered
+ // "expired".
+ caRootExpireDuration = 7 * 24 * time.Hour
+
+ // minAutopilotVersion is the minimum Consul version in which Autopilot features
+ // are supported.
+ minAutopilotVersion = version.Must(version.NewVersion("0.8.0"))
+)
// monitorLeadership is used to monitor if we acquire or lose our role
// as the leader in the Raft cluster. There is some work the leader is
@@ -220,6 +231,8 @@ func (s *Server) establishLeadership() error {
return err
}
+ s.startCARootPruning()
+
s.setConsistentReadReady()
return nil
}
@@ -236,6 +249,8 @@ func (s *Server) revokeLeadership() error {
return err
}
+ s.stopCARootPruning()
+
s.setCAProvider(nil, nil)
s.resetConsistentReadReady()
@@ -550,6 +565,96 @@ func (s *Server) setCAProvider(newProvider ca.Provider, root *structs.CARoot) {
s.caProviderRoot = root
}
+// startCARootPruning starts a goroutine that looks for stale CARoots
+// and removes them from the state store.
+func (s *Server) startCARootPruning() {
+ if !s.config.ConnectEnabled {
+ return
+ }
+
+ s.caPruningLock.Lock()
+ defer s.caPruningLock.Unlock()
+
+ if s.caPruningEnabled {
+ return
+ }
+
+ s.caPruningCh = make(chan struct{})
+
+ go func() {
+ ticker := time.NewTicker(caRootPruneInterval)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case <-s.caPruningCh:
+ return
+ case <-ticker.C:
+ if err := s.pruneCARoots(); err != nil {
+ s.logger.Printf("[ERR] connect: error pruning CA roots: %v", err)
+ }
+ }
+ }
+ }()
+
+ s.caPruningEnabled = true
+}
+
+// pruneCARoots looks for any CARoots that have been rotated out and expired.
+func (s *Server) pruneCARoots() error {
+ idx, roots, err := s.fsm.State().CARoots(nil)
+ if err != nil {
+ return err
+ }
+
+ var newRoots structs.CARoots
+ for _, r := range roots {
+ if !r.Active && !r.RotateOutAt.IsZero() && r.RotateOutAt.Before(time.Now()) {
+ s.logger.Printf("[INFO] connect: pruning old unused root CA (ID: %s)", r.ID)
+ continue
+ }
+ newRoot := *r
+ newRoots = append(newRoots, &newRoot)
+ }
+
+ // Return early if there's nothing to remove.
+ if len(newRoots) == len(roots) {
+ return nil
+ }
+
+ // Commit the new root state.
+ var args structs.CARequest
+ args.Op = structs.CAOpSetRoots
+ args.Index = idx
+ args.Roots = newRoots
+ resp, err := s.raftApply(structs.ConnectCARequestType, args)
+ if err != nil {
+ return err
+ }
+ if respErr, ok := resp.(error); ok {
+ return respErr
+ }
+
+ return nil
+}
+
+// stopCARootPruning stops the CARoot pruning process.
+func (s *Server) stopCARootPruning() {
+ if !s.config.ConnectEnabled {
+ return
+ }
+
+ s.caPruningLock.Lock()
+ defer s.caPruningLock.Unlock()
+
+ if !s.caPruningEnabled {
+ return
+ }
+
+ close(s.caPruningCh)
+ s.caPruningEnabled = false
+}
+
// reconcileReaped is used to reconcile nodes that have failed and been reaped
// from Serf but remain in the catalog. This is done by looking for unknown nodes with serfHealth checks registered.
// We generate a "reap" event to cause the node to be cleaned up.
diff --git a/agent/consul/leader_test.go b/agent/consul/leader_test.go
index da2092fdab..a685223cb3 100644
--- a/agent/consul/leader_test.go
+++ b/agent/consul/leader_test.go
@@ -5,12 +5,14 @@ import (
"testing"
"time"
+ "github.com/hashicorp/consul/agent/connect"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/testrpc"
"github.com/hashicorp/consul/testutil/retry"
"github.com/hashicorp/net-rpc-msgpackrpc"
"github.com/hashicorp/serf/serf"
+ "github.com/stretchr/testify/require"
)
func TestLeader_RegisterMember(t *testing.T) {
@@ -1001,3 +1003,64 @@ func TestLeader_ACL_Initialization(t *testing.T) {
})
}
}
+
+func TestLeader_CARootPruning(t *testing.T) {
+ t.Parallel()
+
+ caRootExpireDuration = 500 * time.Millisecond
+ caRootPruneInterval = 200 * time.Millisecond
+
+ require := require.New(t)
+ dir1, s1 := testServer(t)
+ defer os.RemoveAll(dir1)
+ defer s1.Shutdown()
+ codec := rpcClient(t, s1)
+ defer codec.Close()
+
+ testrpc.WaitForLeader(t, s1.RPC, "dc1")
+
+ // Get the current root
+ rootReq := &structs.DCSpecificRequest{
+ Datacenter: "dc1",
+ }
+ var rootList structs.IndexedCARoots
+ require.Nil(msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", rootReq, &rootList))
+ require.Len(rootList.Roots, 1)
+ oldRoot := rootList.Roots[0]
+
+ // Update the provider config to use a new private key, which should
+ // cause a rotation.
+ _, newKey, err := connect.GeneratePrivateKey()
+ require.NoError(err)
+ newConfig := &structs.CAConfiguration{
+ Provider: "consul",
+ Config: map[string]interface{}{
+ "PrivateKey": newKey,
+ "RootCert": "",
+ "RotationPeriod": 90 * 24 * time.Hour,
+ },
+ }
+ {
+ args := &structs.CARequest{
+ Datacenter: "dc1",
+ Config: newConfig,
+ }
+ var reply interface{}
+
+ require.NoError(msgpackrpc.CallWithCodec(codec, "ConnectCA.ConfigurationSet", args, &reply))
+ }
+
+ // Should have 2 roots now.
+ _, roots, err := s1.fsm.State().CARoots(nil)
+ require.NoError(err)
+ require.Len(roots, 2)
+
+ time.Sleep(caRootExpireDuration * 2)
+
+ // Now the old root should be pruned.
+ _, roots, err = s1.fsm.State().CARoots(nil)
+ require.NoError(err)
+ require.Len(roots, 1)
+ require.True(roots[0].Active)
+ require.NotEqual(roots[0].ID, oldRoot.ID)
+}
diff --git a/agent/consul/server.go b/agent/consul/server.go
index ca363e7a1b..4520b11114 100644
--- a/agent/consul/server.go
+++ b/agent/consul/server.go
@@ -107,6 +107,12 @@ type Server struct {
caProviderRoot *structs.CARoot
caProviderLock sync.RWMutex
+ // caPruningCh is used to shut down the CA root pruning goroutine when we
+ // lose leadership.
+ caPruningCh chan struct{}
+ caPruningLock sync.RWMutex
+ caPruningEnabled bool
+
// Consul configuration
config *Config
diff --git a/agent/structs/connect_ca.go b/agent/structs/connect_ca.go
index 2577a99a69..52b9c16f15 100644
--- a/agent/structs/connect_ca.go
+++ b/agent/structs/connect_ca.go
@@ -73,6 +73,11 @@ type CARoot struct {
// cannot be active.
Active bool
+ // RotateOutAt is the time at which this CA can be removed from the state.
+ // This will only be set on roots that have been rotated out from being the
+ // active one.
+ RotateOutAt time.Time `json:"-"`
+
RaftIndex
}
From df8506e20043c38c55cb1fda275baf6a10a4cc50 Mon Sep 17 00:00:00 2001
From: John Cowen
Date: Mon, 2 Jul 2018 19:02:16 +0100
Subject: [PATCH 16/54] Move testing doubles to use data embedded in the HTML
vs HTTP/fetch
Previously `api-double` usage in ember would require a bunch of `fetch`
requests to pull in the 'api double', this had a number of disadvantages.
1. The doubles needed to be available via HTTP, which meant a short term
solution of rsyncing the double files over to `public` in order to be served
over HTTP. An alternative to that would have been figuring out how to serve
something straight from `node_modules`, which would have been preferable.
2. ember/testem would not serve dot files (so anything starting with a
., like `.config`. To solve this via ember/testem would have involved
digging in to understand how to enable the serving of dot files.
3. ember/testem automatically rewrote urls for non-existant files to
folders, i.e. adding a slash for you, so `/v1/connect/intentions` would
be rewritten to `/v1/connect/intentions/`. This is undesirable, and
solving this via ember/testem would have involved digging deep to
disable that.
Serving the files via HTTP has now changed. The double files are now
embedded into the HTML has 'embedded templates' that can be found by
using the url of the file and a simple `querySelector`. This of course
only happens during testing and means I can fully control the 'serving'
of the doubles now, so I can say goodbye to the need to move files
around, worry about the need to serve dotfiles and the undesirable
trailing slashes rewriting. Winner!
Find the files and embedding them is done using a straightforward
recursive-readdir-sync (the `content-for` functionality is a synchronous
api) as oppose to getting stuck into `broccoli`.
---
ui-v2/.ember-cli | 3 ++-
ui-v2/GNUmakefile | 21 ++++++++++++---------
ui-v2/config/environment.js | 20 ++++++++++++--------
ui-v2/package.json | 13 ++++++-------
ui-v2/tests/helpers/api.js | 12 +++++++++++-
ui-v2/yarn.lock | 25 +++++++++++++++----------
6 files changed, 58 insertions(+), 36 deletions(-)
diff --git a/ui-v2/.ember-cli b/ui-v2/.ember-cli
index ee64cfed2a..cdbed3326d 100644
--- a/ui-v2/.ember-cli
+++ b/ui-v2/.ember-cli
@@ -5,5 +5,6 @@
Setting `disableAnalytics` to true will prevent any data from being sent.
*/
- "disableAnalytics": false
+ "disableAnalytics": false,
+ "proxy": "http://localhost:3000"
}
diff --git a/ui-v2/GNUmakefile b/ui-v2/GNUmakefile
index f703de4f4f..e87521b992 100644
--- a/ui-v2/GNUmakefile
+++ b/ui-v2/GNUmakefile
@@ -1,28 +1,31 @@
ROOT:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
all: build
-
+
deps: node_modules
-
+
build: deps
yarn run build
-
+
start: deps
yarn run start
-
+
+start-api: deps
+ yarn run start:api
+
test: deps
yarn run test
-
+
test-view: deps
yarn run test:view
-
+
lint: deps
yarn run lint:js
-
+
format: deps
yarn run format:js
-
+
node_modules: yarn.lock package.json
yarn install
-
+
.PHONY: all deps build start test test-view lint format
diff --git a/ui-v2/config/environment.js b/ui-v2/config/environment.js
index b72e7b00de..6ee649a70e 100644
--- a/ui-v2/config/environment.js
+++ b/ui-v2/config/environment.js
@@ -30,9 +30,9 @@ module.exports = function(environment) {
ENV = Object.assign({}, ENV, {
CONSUL_GIT_SHA: (function() {
if (process.env.CONSUL_GIT_SHA) {
- return process.env.CONSUL_GIT_SHA
+ return process.env.CONSUL_GIT_SHA;
}
-
+
return require('child_process')
.execSync('git rev-parse --short HEAD')
.toString()
@@ -40,7 +40,7 @@ module.exports = function(environment) {
})(),
CONSUL_VERSION: (function() {
if (process.env.CONSUL_VERSION) {
- return process.env.CONSUL_VERSION
+ return process.env.CONSUL_VERSION;
}
// see /scripts/dist.sh:8
const version_go = `${path.dirname(path.dirname(__dirname))}/version/version.go`;
@@ -53,13 +53,13 @@ module.exports = function(environment) {
.trim()
.split('"')[1];
})(),
- CONSUL_BINARY_TYPE: (function() {
+ CONSUL_BINARY_TYPE: function() {
if (process.env.CONSUL_BINARY_TYPE) {
- return process.env.CONSUL_BINARY_TYPE
+ return process.env.CONSUL_BINARY_TYPE;
}
-
- return "oss"
- }),
+
+ return 'oss';
+ },
CONSUL_DOCUMENTATION_URL: 'https://www.consul.io/docs',
CONSUL_COPYRIGHT_URL: 'https://www.hashicorp.com',
CONSUL_COPYRIGHT_YEAR: '2018',
@@ -86,6 +86,10 @@ module.exports = function(environment) {
ENV.APP.rootElement = '#ember-testing';
ENV.APP.autoboot = false;
+ ENV['ember-cli-api-double'] = {
+ reader: 'html',
+ endpoints: ['/node_modules/@hashicorp/consul-api-double/v1'],
+ };
}
if (environment === 'production') {
diff --git a/ui-v2/package.json b/ui-v2/package.json
index 1f89ef82bf..5dd0dee53b 100644
--- a/ui-v2/package.json
+++ b/ui-v2/package.json
@@ -1,6 +1,6 @@
{
"name": "consul-ui",
- "version": "2.0.0",
+ "version": "2.2.0",
"private": true,
"description": "The web ui for Consul, by HashiCorp.",
"directories": {
@@ -14,9 +14,9 @@
"lint:js": "eslint -c .eslintrc.js --fix ./*.js ./.*.js app config lib server tests",
"format:js": "prettier --write \"{app,config,lib,server,tests}/**/*.js\" ./*.js ./.*.js",
"start": "ember serve",
- "test:sync": "rsync -aq ./node_modules/@hashicorp/consul-api-double/ ./public/consul-api-double/",
- "test": "yarn run test:sync;ember test",
- "test:view": "yarn run test:sync;ember test --server",
+ "start:api": "api-double --dir ./node_modules/@hashicorp/consul-api-double",
+ "test": "ember test",
+ "test:view": "ember test --server",
"precommit": "lint-staged"
},
"lint-staged": {
@@ -29,10 +29,9 @@
"git add"
]
},
- "dependencies": {},
"devDependencies": {
- "@hashicorp/consul-api-double": "^1.0.0",
- "@hashicorp/ember-cli-api-double": "^1.0.2",
+ "@hashicorp/consul-api-double": "^1.2.0",
+ "@hashicorp/ember-cli-api-double": "^1.3.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"base64-js": "^1.3.0",
"broccoli-asset-rev": "^2.4.5",
diff --git a/ui-v2/tests/helpers/api.js b/ui-v2/tests/helpers/api.js
index 800672103e..75acba0027 100644
--- a/ui-v2/tests/helpers/api.js
+++ b/ui-v2/tests/helpers/api.js
@@ -1,4 +1,14 @@
import getAPI from '@hashicorp/ember-cli-api-double';
import setCookies from 'consul-ui/tests/helpers/set-cookies';
import typeToURL from 'consul-ui/tests/helpers/type-to-url';
-export default getAPI('/consul-api-double', setCookies, typeToURL);
+import config from 'consul-ui/config/environment';
+const apiConfig = config['ember-cli-api-double'];
+let path = '/consul-api-double';
+let reader;
+if (apiConfig) {
+ const temp = apiConfig.endpoints[0].split('/');
+ reader = apiConfig.reader;
+ temp.pop();
+ path = temp.join('/');
+}
+export default getAPI(path, setCookies, typeToURL, reader);
diff --git a/ui-v2/yarn.lock b/ui-v2/yarn.lock
index c604847c41..50303e742d 100644
--- a/ui-v2/yarn.lock
+++ b/ui-v2/yarn.lock
@@ -69,9 +69,9 @@
dependencies:
"@glimmer/di" "^0.2.0"
-"@hashicorp/api-double@^1.1.0":
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/@hashicorp/api-double/-/api-double-1.2.0.tgz#d2846f79d086ac009673ae755da15301e0f2f7c3"
+"@hashicorp/api-double@^1.3.0":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@hashicorp/api-double/-/api-double-1.3.1.tgz#fd9d706674b934857a638459c2bb52d2f2809455"
dependencies:
"@gardenhq/o" "^8.0.1"
"@gardenhq/tick-control" "^2.0.0"
@@ -81,20 +81,21 @@
faker "^4.1.0"
js-yaml "^3.10.0"
-"@hashicorp/consul-api-double@^1.0.0":
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-1.1.0.tgz#658f9e89208fa23f251ca66c66aeb7241a13f23f"
-
-"@hashicorp/ember-cli-api-double@^1.0.2":
+"@hashicorp/consul-api-double@^1.2.0":
version "1.2.0"
- resolved "https://registry.yarnpkg.com/@hashicorp/ember-cli-api-double/-/ember-cli-api-double-1.2.0.tgz#aed3a9659abb3f3c56d77e400abc7fcbdcf2b78b"
+ resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-1.2.0.tgz#2cd2a991818e13e7b97803af3d62ec6c9cb83b28"
+
+"@hashicorp/ember-cli-api-double@^1.3.0":
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/@hashicorp/ember-cli-api-double/-/ember-cli-api-double-1.3.0.tgz#d07b5b11701cd55d6b01cb8a47ce17c4bac21fed"
dependencies:
- "@hashicorp/api-double" "^1.1.0"
+ "@hashicorp/api-double" "^1.3.0"
array-range "^1.0.1"
ember-cli-babel "^6.6.0"
js-yaml "^3.11.0"
merge-options "^1.0.1"
pretender "^2.0.0"
+ recursive-readdir-sync "^1.0.6"
"@sinonjs/formatio@^2.0.0":
version "2.0.0"
@@ -7735,6 +7736,10 @@ recast@^0.11.17, recast@^0.11.3:
private "~0.1.5"
source-map "~0.5.0"
+recursive-readdir-sync@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/recursive-readdir-sync/-/recursive-readdir-sync-1.0.6.tgz#1dbf6d32f3c5bb8d3cde97a6c588d547a9e13d56"
+
redent@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
From 77fe08b7c9bdb81b842c66c3baa1aafe5e6d2bff Mon Sep 17 00:00:00 2001
From: Siva
Date: Thu, 28 Jun 2018 22:09:15 -0400
Subject: [PATCH 17/54] Website: Added more telemetry metrics
---
website/source/docs/agent/telemetry.html.md | 92 +++++++++++++++++++++
1 file changed, 92 insertions(+)
diff --git a/website/source/docs/agent/telemetry.html.md b/website/source/docs/agent/telemetry.html.md
index a0a9685c36..b7356cc179 100644
--- a/website/source/docs/agent/telemetry.html.md
+++ b/website/source/docs/agent/telemetry.html.md
@@ -355,6 +355,62 @@ These metrics are used to monitor the health of the Consul servers.
Unit
Type
+
+
`consul.raft.fsm.snapshot`
+
This metric measures the time taken by the FSM to record the current state for the snapshot.
+
ms
+
timer
+
+
+
+
`consul.raft.fsm.apply`
+
This metric gives the number of logs committed since the last interval.
+
commit logs / interval
+
counter
+
+
+
+
`consul.raft.fsm.restore`
+
This metric measures the time taken by the FSM to restore its state from a snapshot.
+
ms
+
timer
+
+
+
`consul.raft.snapshot.create`
+
This metric measures the time taken to initialize the snapshot process.
+
ms
+
timer
+
+
+
`consul.raft.snapshot.persist`
+
This metric measures the time taken to dump the current snapshot taken by the Consul agent to the disk.
+
ms
+
timer
+
+
+
`consul.raft.snapshot.takeSnapshot`
+
This metric measures the total time involved in taking the current snapshot (creating one and persisting it) by the Consul agent.
+
ms
+
timer
+
+
+
`consul.raft.replication.heartbeat`
+
This metric measures the time taken to invoke appendEntries on a peer, so that it doesn’t timeout on a periodic basis.
+
ms
+
timer
+
+
+
`consul.serf.snapshot.appendLine`
+
This metric measures the time taken by the Consul agent to append an entry into the existing log.
+
ms
+
timer
+
+
+
`consul.serf.snapshot.compact`
+
This metric measures the time by the Consul agent to compact a log. This operation occurs only when the snapshot becomes too large enough to justify the compaction .
+
ms
+
timer
+
`consul.raft.state.leader`
This increments whenever a Consul server becomes a leader. If there are frequent leadership changes this may be indication that the servers are overloaded and aren't meeting the soft real-time requirements for Raft, or that there are networking problems between the servers.
@@ -655,6 +711,42 @@ These metrics give insight into the health of the cluster as a whole.
suspect messages received / interval
counter
+
+
`consul.memberlist.gossip`
+
This metric gives the number of gossips (messages) broadcasted to a set of randomly selected nodes.
+
messages / Interval
+
counter
+
+
+
`consul.memberlist.msg_alive`
+
This metric counts the number of alive agents, that the agent has mapped out so far, based on the message information given by the network layer.
+
nodes / Interval
+
counter
+
+
+
`consul.memberlist.msg_dead`
+
This metric gives the number of dead agents, that the agent has mapped out so far, based on the message information given by the network layer.
+
nodes / Interval
+
counter
+
+
+
`consul.memberlist.msg_suspect`
+
This metric gives the number of suspect nodes, that the agent has mapped out so far, based on the message information given by the network layer.
+
nodes / Interval
+
counter
+
+
+
`consul.memberlist.probeNode`
+
This metric measures the time taken to perform a single round of failure detection on a select agent.
+
nodes / Interval
+
counter
+
+
+
`consul.memberlist.pushPullNode`
+
This metric measures the number of agents that have exchanged state with this agent.
+
nodes / Interval
+
counter
+
`consul.serf.member.flap`
Available in Consul 0.7 and later, this increments when an agent is marked dead and then recovers within a short time period. This can be an indicator of overloaded agents, network problems, or configuration errors where agents can not connect to each other on the [required ports](/docs/agent/options.html#ports).
From a47a0b617f7fc174bf2682c02d6a18d875116c23 Mon Sep 17 00:00:00 2001
From: John Cowen
Date: Tue, 3 Jul 2018 13:23:45 +0100
Subject: [PATCH 18/54] Hedge for when consul sends nodes with an empty ID
---
ui-v2/app/adapters/node.js | 3 ++
.../acceptance/dc/nodes/empty-ids.feature | 33 +++++++++++++++++++
.../steps/dc/nodes/empty-ids-steps.js | 10 ++++++
3 files changed, 46 insertions(+)
create mode 100644 ui-v2/tests/acceptance/dc/nodes/empty-ids.feature
create mode 100644 ui-v2/tests/acceptance/steps/dc/nodes/empty-ids-steps.js
diff --git a/ui-v2/app/adapters/node.js b/ui-v2/app/adapters/node.js
index 0890a8aa1a..22460875ad 100644
--- a/ui-v2/app/adapters/node.js
+++ b/ui-v2/app/adapters/node.js
@@ -23,6 +23,9 @@ export default Adapter.extend({
break;
default:
response = response.map((item, i, arr) => {
+ if (item[SLUG_KEY] === '') {
+ item[SLUG_KEY] = item['Node'];
+ }
return {
...item,
...{
diff --git a/ui-v2/tests/acceptance/dc/nodes/empty-ids.feature b/ui-v2/tests/acceptance/dc/nodes/empty-ids.feature
new file mode 100644
index 0000000000..434766775f
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/nodes/empty-ids.feature
@@ -0,0 +1,33 @@
+@setupApplicationTest
+Feature: Hedge for if nodes come in over the API with no ID
+ Scenario: A node list with some missing IDs
+ Given 1 datacenter model with the value "dc-1"
+ And 5 node models from yaml
+ ---
+ - ID: id-1
+ Node: name-1
+ - ID: ""
+ Node: name-2
+ - ID: ""
+ Node: name-3
+ - ID: ""
+ Node: name-4
+ - ID: ""
+ Node: name-5
+ ---
+ When I visit the nodes page for yaml
+ ---
+ dc: dc-1
+ ---
+ Then the url should be /dc-1/nodes
+ Then I see name on the nodes like yaml
+ ---
+ - name-1
+ - name-2
+ - name-3
+ - name-4
+ - name-5
+
+@ignore
+ Scenario: Visually comparing
+ Then the ".unhealthy" element should look like the "/node_modules/@hashicorp/consul-testing-extras/fixtures/dc/nodes/empty-ids.png" image
diff --git a/ui-v2/tests/acceptance/steps/dc/nodes/empty-ids-steps.js b/ui-v2/tests/acceptance/steps/dc/nodes/empty-ids-steps.js
new file mode 100644
index 0000000000..a7eff3228b
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/nodes/empty-ids-steps.js
@@ -0,0 +1,10 @@
+import steps from '../../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
From 6398fb02c78bb87a01c16da139a217e58b344e8e Mon Sep 17 00:00:00 2001
From: John Cowen
Date: Tue, 3 Jul 2018 14:48:04 +0100
Subject: [PATCH 19/54] Ensure we catch empty ID's for single nodes also
I don't think this would have a large effect on the UI whichever but
best to make sure
---
ui-v2/app/adapters/node.js | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/ui-v2/app/adapters/node.js b/ui-v2/app/adapters/node.js
index 22460875ad..902687ff40 100644
--- a/ui-v2/app/adapters/node.js
+++ b/ui-v2/app/adapters/node.js
@@ -1,6 +1,14 @@
import Adapter from './application';
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/node';
import { OK as HTTP_OK } from 'consul-ui/utils/http/status';
+// TODO: Looks like ID just isn't used at all
+// consider just using .Node for the SLUG_KEY
+const fillSlug = function(item) {
+ if (item[SLUG_KEY] === '') {
+ item[SLUG_KEY] = item['Node'];
+ }
+ return item;
+};
export default Adapter.extend({
urlForQuery: function(query, modelName) {
return this.appendURL('internal/ui/nodes', [], this.cleanQuery(query));
@@ -14,6 +22,7 @@ export default Adapter.extend({
const url = this.parseURL(requestData.url);
switch (true) {
case this.isQueryRecord(url):
+ response = fillSlug(response);
response = {
...response,
...{
@@ -23,9 +32,7 @@ export default Adapter.extend({
break;
default:
response = response.map((item, i, arr) => {
- if (item[SLUG_KEY] === '') {
- item[SLUG_KEY] = item['Node'];
- }
+ item = fillSlug(item);
return {
...item,
...{
From 7f35ca33f4a2753f6d6e7a7b635e1594f977c38d Mon Sep 17 00:00:00 2001
From: Paul Banks
Date: Tue, 3 Jul 2018 14:54:44 +0100
Subject: [PATCH 20/54] Remove the TODO latency measurement.
---
website/source/docs/connect/proxies.html.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/website/source/docs/connect/proxies.html.md b/website/source/docs/connect/proxies.html.md
index 32fbe2603a..2d60c92f45 100644
--- a/website/source/docs/connect/proxies.html.md
+++ b/website/source/docs/connect/proxies.html.md
@@ -41,7 +41,7 @@ The default managed proxy is a basic proxy built-in to Consul and written
in Go. Having a basic built-in proxy allows Consul to have a sane default
with performance that is good enough for most workloads. In some basic
benchmarks, the service-to-service communication over the built-in proxy
-could sustain 5 Gbps with a per-hop latency of less than X microseconds. Therefore,
+could sustain 5 Gbps with sub-millisecond latency. Therefore,
the performance impact of even the basic built-in proxy is minimal.
Consul will be
From 9579ba4e12bf8a0d5366812117252f2e405f05b9 Mon Sep 17 00:00:00 2001
From: Siva
Date: Tue, 3 Jul 2018 10:27:01 -0400
Subject: [PATCH 21/54] Website: Added more telemetry details for raft and
memberlist.
---
website/source/docs/agent/telemetry.html.md | 43 +++++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/website/source/docs/agent/telemetry.html.md b/website/source/docs/agent/telemetry.html.md
index b7356cc179..df9b843e10 100644
--- a/website/source/docs/agent/telemetry.html.md
+++ b/website/source/docs/agent/telemetry.html.md
@@ -429,6 +429,25 @@ These metrics are used to monitor the health of the Consul servers.
raft transactions / interval
counter
+
+
`consul.raft.barrier`
+
This metric counts the number of times the agent has started the barrier i.e the number of times it has
+ issued a blocking call, to ensure that the agent has all the pending operations that were queued, to be applied to the agent's FSM.
+
blocks / interval
+
counter
+
+
+
`consul.raft.verify_leader`
+
This metric counts the number of times an agent checks whether it is still the leader or not
+
checks / interval
+
Counter
+
+
+
`consul.raft.restore`
+
This metric counts the number of times the restore operation has been performed by the agent. Here, restore refers to the action of raft consuming an external snapshot to restore its state.
+
operation invoked / interval
+
counter
+
`consul.raft.commitTime`
This measures the time it takes to commit a new entry to the Raft log on the leader.
@@ -705,6 +724,30 @@ These metrics give insight into the health of the cluster as a whole.
Unit
Type
+
+
`consul.memberlist.degraded.probe`
+
This metric counts the number of times the agent has performed failure detection on an other agent at a slower probe rate. The agent uses its own health metric as an indicator to perform this action. (If its health score is low, means that the node is healthy, and vice versa.)
+
probes / interval
+
counter
+
+
+
`consul.memberlist.degraded.timeout`
+
This metric counts the number of times an agent was marked as a dead node, whilst not getting enough confirmations from a randomly selected list of agent nodes in an agent's membership.
+
occurrence / interval
+
counter
+
+
+
`consul.memberlist.msg.dead`
+
This metric counts the number of times an agent has marked another agent to be a dead node.
+
messages / interval
+
counter
+
+
+
`consul.memberlist.health.score`
+
This metric emits the agent's updated health score. This score is updated whenever the agent notices any changes in the response, from a set of randomly probed agents. This value ranges from 0-8, the lowest indicating the agent is healthy and vice versa.
+
score
+
gauge
+
`consul.memberlist.msg.suspect`
This increments when an agent suspects another as failed when executing random probes as part of the gossip protocol. These can be an indicator of overloaded agents, network problems, or configuration errors where agents can not connect to each other on the [required ports](/docs/agent/options.html#ports).
From b445df39bbce32528fa065021fc119dd7f74f869 Mon Sep 17 00:00:00 2001
From: John Cowen
Date: Tue, 3 Jul 2018 15:40:15 +0100
Subject: [PATCH 22/54] Use html5 oninput instead of onkeyup for native textual
inputs
This enables people to enter things using the mouse to paste for
example, plus possible other things.
As an aside it also answers my query regarding `fillIn` for testing,
nothing needs to be actually _typed_ anymore! Doh
---
.../templates/components/freetext-filter.hbs | 2 +-
ui-v2/app/templates/dc/kv/-form.hbs | 6 +++---
.../acceptance/components/acl-filter.feature | 4 ++--
.../components/catalog-filter.feature | 4 ++--
.../acceptance/components/kv-filter.feature | 2 +-
.../acceptance/components/text-input.feature | 21 +++++++++++++++++++
ui-v2/tests/acceptance/dc/acls/update.feature | 2 +-
ui-v2/tests/acceptance/dc/kvs/update.feature | 2 +-
.../steps/components/text-input-steps.js | 10 +++++++++
ui-v2/tests/acceptance/token-header.feature | 2 +-
ui-v2/tests/pages/dc/acls/edit.js | 3 ++-
ui-v2/tests/pages/dc/kv/edit.js | 3 ++-
ui-v2/tests/steps.js | 2 +-
13 files changed, 48 insertions(+), 15 deletions(-)
create mode 100644 ui-v2/tests/acceptance/components/text-input.feature
create mode 100644 ui-v2/tests/acceptance/steps/components/text-input-steps.js
diff --git a/ui-v2/app/templates/components/freetext-filter.hbs b/ui-v2/app/templates/components/freetext-filter.hbs
index 590726e08c..417a3787d2 100644
--- a/ui-v2/app/templates/components/freetext-filter.hbs
+++ b/ui-v2/app/templates/components/freetext-filter.hbs
@@ -1,6 +1,6 @@
{{!}}
\ No newline at end of file
diff --git a/ui-v2/app/templates/dc/kv/-form.hbs b/ui-v2/app/templates/dc/kv/-form.hbs
index 6303969045..9b859c5a19 100644
--- a/ui-v2/app/templates/dc/kv/-form.hbs
+++ b/ui-v2/app/templates/dc/kv/-form.hbs
@@ -3,14 +3,14 @@
{{#if create }}
{{/if}}
{{#if (or (eq (left-trim item.Key parent.Key) '') (not-eq (last item.Key) '/')) }}
diff --git a/ui-v2/tests/acceptance/components/acl-filter.feature b/ui-v2/tests/acceptance/components/acl-filter.feature
index 0d88655225..15ffad0a2c 100644
--- a/ui-v2/tests/acceptance/components/acl-filter.feature
+++ b/ui-v2/tests/acceptance/components/acl-filter.feature
@@ -25,12 +25,12 @@ Feature: dc / components /acl filter: Acl Filter
When I click all on the filter
Then I see allIsSelected on the filter
- Then I type with yaml
+ Then I fill in with yaml
---
s: Anonymous Token
---
And I see 1 [Model] model with the name "Anonymous Token"
- Then I type with yaml
+ Then I fill in with yaml
---
s: secret
---
diff --git a/ui-v2/tests/acceptance/components/catalog-filter.feature b/ui-v2/tests/acceptance/components/catalog-filter.feature
index 2635228002..9db4e0ee7c 100644
--- a/ui-v2/tests/acceptance/components/catalog-filter.feature
+++ b/ui-v2/tests/acceptance/components/catalog-filter.feature
@@ -47,7 +47,7 @@ Feature: components / catalog-filter
When I click all on the filter
And I see allIsSelected on the filter
- Then I type with yaml
+ Then I fill in with yaml
---
s: [Model]-0
---
@@ -75,7 +75,7 @@ Feature: components / catalog-filter
When I click services on the tabs
And I see servicesIsSelected on the tabs
- Then I type with yaml
+ Then I fill in with yaml
---
s: 65535
---
diff --git a/ui-v2/tests/acceptance/components/kv-filter.feature b/ui-v2/tests/acceptance/components/kv-filter.feature
index ebe740c466..f2cedbddbd 100644
--- a/ui-v2/tests/acceptance/components/kv-filter.feature
+++ b/ui-v2/tests/acceptance/components/kv-filter.feature
@@ -12,7 +12,7 @@ Feature: components / kv-filter
dc: dc-1
---
Then the url should be [Url]
- Then I type with yaml
+ Then I fill in with yaml
---
s: [Text]
---
diff --git a/ui-v2/tests/acceptance/components/text-input.feature b/ui-v2/tests/acceptance/components/text-input.feature
new file mode 100644
index 0000000000..02bc044138
--- /dev/null
+++ b/ui-v2/tests/acceptance/components/text-input.feature
@@ -0,0 +1,21 @@
+@setupApplicationTest
+Feature: Text input
+ Background:
+ Given 1 datacenter model with the value "dc-1"
+ Scenario:
+ When I visit the [Page] page for yaml
+ ---
+ dc: dc-1
+ ---
+ Then the url should be [Url]
+ Then I fill in with json
+ ---
+ [Data]
+ ---
+ Then I see submitIsEnabled
+ Where:
+ --------------------------------------------------------------------------------
+ | Page | Url | Data |
+ | kv | /dc-1/kv/create | {"additional": "hi", "value": "there"} |
+ | acl | /dc-1/acls/create | {"name": "hi"} |
+ --------------------------------------------------------------------------------
diff --git a/ui-v2/tests/acceptance/dc/acls/update.feature b/ui-v2/tests/acceptance/dc/acls/update.feature
index 77c6417fd7..f79d637c70 100644
--- a/ui-v2/tests/acceptance/dc/acls/update.feature
+++ b/ui-v2/tests/acceptance/dc/acls/update.feature
@@ -12,7 +12,7 @@ Feature: dc / acls / update: ACL Update
acl: key
---
Then the url should be /datacenter/acls/key
- Then I type with yaml
+ Then I fill in with yaml
---
name: [Name]
---
diff --git a/ui-v2/tests/acceptance/dc/kvs/update.feature b/ui-v2/tests/acceptance/dc/kvs/update.feature
index b6a880c8dc..1f00f7c257 100644
--- a/ui-v2/tests/acceptance/dc/kvs/update.feature
+++ b/ui-v2/tests/acceptance/dc/kvs/update.feature
@@ -12,7 +12,7 @@ Feature: dc / kvs / update: KV Update
kv: [Name]
---
Then the url should be /datacenter/kv/[Name]/edit
- Then I type with yaml
+ Then I fill in with yaml
---
value: [Value]
---
diff --git a/ui-v2/tests/acceptance/steps/components/text-input-steps.js b/ui-v2/tests/acceptance/steps/components/text-input-steps.js
new file mode 100644
index 0000000000..960cdf533d
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/components/text-input-steps.js
@@ -0,0 +1,10 @@
+import steps from '../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/acceptance/token-header.feature b/ui-v2/tests/acceptance/token-header.feature
index 750a3fe745..6ca14431c8 100644
--- a/ui-v2/tests/acceptance/token-header.feature
+++ b/ui-v2/tests/acceptance/token-header.feature
@@ -16,7 +16,7 @@ Feature: token headers
Given 1 datacenter model with the value "datacenter"
When I visit the settings page
Then the url should be /settings
- Then I type with yaml
+ Then I fill in with yaml
---
token: [Token]
---
diff --git a/ui-v2/tests/pages/dc/acls/edit.js b/ui-v2/tests/pages/dc/acls/edit.js
index d15301ef46..7056072201 100644
--- a/ui-v2/tests/pages/dc/acls/edit.js
+++ b/ui-v2/tests/pages/dc/acls/edit.js
@@ -1,4 +1,4 @@
-import { create, clickable, triggerable } from 'ember-cli-page-object';
+import { create, clickable, triggerable, is } from 'ember-cli-page-object';
import { visitable } from 'consul-ui/tests/lib/page-object/visitable';
export default create({
@@ -7,4 +7,5 @@ export default create({
// fillIn: fillable('input, textarea, [contenteditable]'),
name: triggerable('keypress', '[name="name"]'),
submit: clickable('[type=submit]'),
+ submitIsEnabled: is(':not(:disabled)', '[type=submit]'),
});
diff --git a/ui-v2/tests/pages/dc/kv/edit.js b/ui-v2/tests/pages/dc/kv/edit.js
index c8af7bae0f..df07d0df5b 100644
--- a/ui-v2/tests/pages/dc/kv/edit.js
+++ b/ui-v2/tests/pages/dc/kv/edit.js
@@ -1,4 +1,4 @@
-import { create, clickable } from 'ember-cli-page-object';
+import { create, clickable, is } from 'ember-cli-page-object';
import { visitable } from 'consul-ui/tests/lib/page-object/visitable';
export default create({
@@ -7,4 +7,5 @@ export default create({
// fillIn: fillable('input, textarea, [contenteditable]'),
// name: triggerable('keypress', '[name="additional"]'),
submit: clickable('[type=submit]'),
+ submitIsEnabled: is(':not(:disabled)', '[type=submit]'),
});
diff --git a/ui-v2/tests/steps.js b/ui-v2/tests/steps.js
index 383dc4bf7c..901843bd12 100644
--- a/ui-v2/tests/steps.js
+++ b/ui-v2/tests/steps.js
@@ -335,7 +335,7 @@ export default function(assert) {
`Expected to not see ${property} on ${component}`
);
})
- .then(['I see $property'], function(property, component) {
+ .then(['I see $property'], function(property) {
assert.ok(currentPage[property], `Expected to see ${property}`);
})
.then(['I see the text "$text" in "$selector"'], function(text, selector) {
From 5d8bf053e0742f623290dabea13bdc8933db6c7f Mon Sep 17 00:00:00 2001
From: Siva
Date: Tue, 3 Jul 2018 10:59:31 -0400
Subject: [PATCH 23/54] Website/Telemetry: Errata for snapshot.compact and
reworded memberlist.health.score
---
website/source/docs/agent/telemetry.html.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/website/source/docs/agent/telemetry.html.md b/website/source/docs/agent/telemetry.html.md
index df9b843e10..4028ed9d10 100644
--- a/website/source/docs/agent/telemetry.html.md
+++ b/website/source/docs/agent/telemetry.html.md
@@ -407,7 +407,7 @@ These metrics are used to monitor the health of the Consul servers.
`consul.serf.snapshot.compact`
-
This metric measures the time by the Consul agent to compact a log. This operation occurs only when the snapshot becomes too large enough to justify the compaction .
+
This metric measures the time taken by the Consul agent to compact a log. This operation occurs only when the snapshot becomes large enough to justify the compaction .
ms
timer
@@ -744,7 +744,7 @@ These metrics give insight into the health of the cluster as a whole.
`consul.memberlist.health.score`
-
This metric emits the agent's updated health score. This score is updated whenever the agent notices any changes in the response, from a set of randomly probed agents. This value ranges from 0-8, the lowest indicating the agent is healthy and vice versa.
+
This metric describes a node's perception of its own health based on how well it is meeting the soft real-time requirements of the protocol. This metric ranges from 0 to 8, where 0 indicates "totally healthy". This health score is used to scale the time between outgoing probes, and higher scores translate into longer probing intervals. For more details see section IV of the Lifeguard paper: https://arxiv.org/pdf/1707.00788.pdf
score
gauge
From a1a62e3b914c0a0a06ceee9281247ebe273bb2ef Mon Sep 17 00:00:00 2001
From: Siva
Date: Tue, 3 Jul 2018 17:37:29 -0400
Subject: [PATCH 24/54] Website/Docs/Telemetry: Added more raft and memberlist
items.
---
website/source/docs/agent/telemetry.html.md | 90 +++++++++++++++++++++
1 file changed, 90 insertions(+)
diff --git a/website/source/docs/agent/telemetry.html.md b/website/source/docs/agent/telemetry.html.md
index 4028ed9d10..f78ba9118b 100644
--- a/website/source/docs/agent/telemetry.html.md
+++ b/website/source/docs/agent/telemetry.html.md
@@ -466,6 +466,72 @@ These metrics are used to monitor the health of the Consul servers.
ms
timer
+
+
`consul.raft.state.follower`
+
This metric counts the number of times an agent has entered the follower mode. This happens when a new agent joins the cluster or after the end of a leader election.
+
follower state entered / interval
+
counter
+
+
+
`consul.raft.transistion.heartbeat_timeout`
+
This metric gives the number of times an agent has transistioned to the Candidate state, after receive no heartbeat messages from the last known leader.
+
timeouts / interval
+
counter
+
+
+
`consul.raft.restoreUserSnapshot`
+
This metric measures the time taken by the agent to restore the FSM state from a user's snapshot
+
ms
+
timer
+
+
+
`consul.raft.rpc.processHeartBeat`
+
This metric measures the time taken to process a heartbeat request.
+
ms
+
timer
+
+
+
`consul.raft.rpc.appendEntries`
+
This metric measures the time taken to process an append entries RPC call from an agent.
+
ms
+
timer
+
+
+
`consul.raft.rpc.appendEntries.storeLogs`
+
This metric measures the time taken to add any outstanding logs for an agent, since the last appendEntries was invoked
+
ms
+
timer
+
+
+
`consul.raft.rpc.appendEntries.processLogs`
+
This metric measures the time taken to process the outstanding log entries of an agent.
+
ms
+
timer
+
+
+
+
`consul.raft.rpc.requestVote`
+
This metric measures the time taken to process the request vote RPC call.
+
ms
+
timer
+
+
+
`consul.raft.rpc.installSnapshot`
+
This metric measures the time taken to process the installSnapshot RPC call. This metric should only be seen on agents which are currently in the follower state.
+
ms
+
timer
+
+
`consul.raft.replication.appendEntries.rpc`
+
This metric measures the time taken by the append entries RFC, to replicate the log entries of a leader agent onto its follower agent(s)
+
ms
+
timer
+
+
+
`consul.raft.replication.appendEntries.logs`
+
This metric measures the number of logs replicated to an agent, to bring it upto speed with the leader's logs.
+
logs appended/ interval
+
counter
+
`consul.raft.leader.lastContact`
This will only be emitted by the Raft leader and measures the time since the leader was last able to contact the follower nodes when checking its leader lease. It can be used as a measure for how stable the Raft timing is and how close the leader is to timing out its lease.
The lease timeout is 500 ms times the [`raft_multiplier` configuration](/docs/agent/options.html#raft_multiplier), so this telemetry value should not be getting close to that configured value, otherwise the Raft timing is marginal and might need to be tuned, or more powerful servers might be needed. See the [Server Performance](/docs/guides/performance.html) guide for more details.
@@ -754,6 +820,30 @@ These metrics give insight into the health of the cluster as a whole.
suspect messages received / interval
counter
+
+
`consul.memberlist.tcp.accept`
+
This metric counts the number of times an agent has accepted an incoming TCP stream connection.
+
connections accepted / interval
+
counter
+
+
+
`consul.memberlist.udp.sent/received`
+
This metric measures the total number of bytes sent/received by an agent through the UDP protocol.
+
bytes sent or bytes received / interval
+
counter
+
+
+
`consul.memberlist.tcp.connect`
+
This metric counts the number of times an agent has initiated a push/pull sync with an other agent.
+
push/pull initiated / interval
+
counter
+
+
+
`consul.memberlist.tcp.sent`
+
This metric measures the total number of bytes sent by an agent through the TCP protocol
+
bytes sent / interval
+
counter
+
`consul.memberlist.gossip`
This metric gives the number of gossips (messages) broadcasted to a set of randomly selected nodes.
From bc9c5927b78d896341f254e6b3548f7ca0c19f92 Mon Sep 17 00:00:00 2001
From: Siva Prasad
Date: Tue, 3 Jul 2018 18:18:57 -0400
Subject: [PATCH 25/54] Website/Docs/Telemetry : Errata
---
website/source/docs/agent/telemetry.html.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/website/source/docs/agent/telemetry.html.md b/website/source/docs/agent/telemetry.html.md
index f78ba9118b..fc93d61fa1 100644
--- a/website/source/docs/agent/telemetry.html.md
+++ b/website/source/docs/agent/telemetry.html.md
@@ -474,7 +474,7 @@ These metrics are used to monitor the health of the Consul servers.
`consul.raft.transistion.heartbeat_timeout`
-
This metric gives the number of times an agent has transistioned to the Candidate state, after receive no heartbeat messages from the last known leader.
+
This metric gives the number of times an agent has transitioned to the Candidate state, after receive no heartbeat messages from the last known leader.
timeouts / interval
counter
From ed286585e92d9813f451a6999623d8fa75b2ded9 Mon Sep 17 00:00:00 2001
From: John Cowen
Date: Wed, 4 Jul 2018 13:21:30 +0100
Subject: [PATCH 26/54] Add some low hanging intention tests, basically add
intentions to others
1. There are various things tests that can just have intentions added
into them, like filters and such like, add intentions to these
2. Start thinking about being able to negate steps easily, which will
lead on to a cleanup of the steps
---
ui-v2/app/templates/dc/intentions/index.hbs | 6 +-
.../acceptance/components/acl-filter.feature | 2 +-
.../components/intention-filter.feature | 55 +++++++++++++++++++
.../acceptance/components/text-input.feature | 2 +-
.../tests/acceptance/page-navigation.feature | 32 ++++++-----
.../components/intention-filter-steps.js | 10 ++++
ui-v2/tests/acceptance/submit-blank.feature | 11 ++--
ui-v2/tests/pages.js | 2 +
.../pages/components/intention-filter.js | 9 +++
ui-v2/tests/pages/components/page.js | 2 +-
ui-v2/tests/pages/dc/intentions/index.js | 16 ++++++
ui-v2/tests/steps.js | 6 +-
12 files changed, 126 insertions(+), 27 deletions(-)
create mode 100644 ui-v2/tests/acceptance/components/intention-filter.feature
create mode 100644 ui-v2/tests/acceptance/steps/components/intention-filter-steps.js
create mode 100644 ui-v2/tests/pages/components/intention-filter.js
create mode 100644 ui-v2/tests/pages/dc/intentions/index.js
diff --git a/ui-v2/app/templates/dc/intentions/index.hbs b/ui-v2/app/templates/dc/intentions/index.hbs
index 60ed96a2fe..946524d05f 100644
--- a/ui-v2/app/templates/dc/intentions/index.hbs
+++ b/ui-v2/app/templates/dc/intentions/index.hbs
@@ -27,7 +27,7 @@
{{/block-slot}}
{{#block-slot 'row'}}
{{#if (eq item.DestinationName '*') }}
All Services (*)
{{else}}
diff --git a/ui-v2/tests/acceptance/components/acl-filter.feature b/ui-v2/tests/acceptance/components/acl-filter.feature
index 15ffad0a2c..6d90a68f78 100644
--- a/ui-v2/tests/acceptance/components/acl-filter.feature
+++ b/ui-v2/tests/acceptance/components/acl-filter.feature
@@ -1,5 +1,5 @@
@setupApplicationTest
-Feature: dc / components /acl filter: Acl Filter
+Feature: components / acl filter: Acl Filter
In order to find the acl token I'm looking for easier
As a user
I should be able to filter by type and freetext search tokens by name and token
diff --git a/ui-v2/tests/acceptance/components/intention-filter.feature b/ui-v2/tests/acceptance/components/intention-filter.feature
new file mode 100644
index 0000000000..c797dee40f
--- /dev/null
+++ b/ui-v2/tests/acceptance/components/intention-filter.feature
@@ -0,0 +1,55 @@
+@setupApplicationTest
+Feature: components / intention filter: Intention Filter
+ In order to find the intention I'm looking for easier
+ As a user
+ I should be able to filter by 'policy' (allow/deny) and freetext search tokens by source and destination
+ Scenario: Filtering [Model]
+ Given 1 datacenter model with the value "dc-1"
+ And 2 [Model] models
+ When I visit the [Page] page for yaml
+ ---
+ dc: dc-1
+ ---
+ Then the url should be [Url]
+
+ Then I see 2 [Model] models
+ And I see allIsSelected on the filter
+
+ When I click allow on the filter
+ Then I see allowIsSelected on the filter
+ And I see 1 [Model] model
+ And I see 1 [Model] model with the action "allow"
+
+ When I click deny on the filter
+ Then I see denyIsSelected on the filter
+ And I see 1 [Model] model
+ And I see 1 [Model] model with the action "deny"
+
+ When I click all on the filter
+ Then I see 2 [Model] models
+ Then I see allIsSelected on the filter
+ Then I fill in with yaml
+ ---
+ s: alarm
+ ---
+ And I see 1 [Model] model
+ And I see 1 [Model] model with the source "alarm"
+ Then I fill in with yaml
+ ---
+ s: feed
+ ---
+ And I see 1 [Model] model
+ And I see 1 [Model] model with the destination "feed"
+ Then I fill in with yaml
+ ---
+ s: transmitter
+ ---
+ And I see 2 [Model] models
+ And I see 1 [Model] model with the source "transmitter"
+ And I see 1 [Model] model with the destination "transmitter"
+
+ Where:
+ ---------------------------------------------
+ | Model | Page | Url |
+ | intention | intentions | /dc-1/intentions |
+ ---------------------------------------------
diff --git a/ui-v2/tests/acceptance/components/text-input.feature b/ui-v2/tests/acceptance/components/text-input.feature
index 02bc044138..e8d531b221 100644
--- a/ui-v2/tests/acceptance/components/text-input.feature
+++ b/ui-v2/tests/acceptance/components/text-input.feature
@@ -1,5 +1,5 @@
@setupApplicationTest
-Feature: Text input
+Feature: components / text-input: Text input
Background:
Given 1 datacenter model with the value "dc-1"
Scenario:
diff --git a/ui-v2/tests/acceptance/page-navigation.feature b/ui-v2/tests/acceptance/page-navigation.feature
index 360b910757..73d83beb18 100644
--- a/ui-v2/tests/acceptance/page-navigation.feature
+++ b/ui-v2/tests/acceptance/page-navigation.feature
@@ -16,13 +16,14 @@ Feature: Page Navigation
When I click [Link] on the navigation
Then the url should be [Url]
Where:
- --------------------------------------
- | Link | Url |
- | nodes | /dc-1/nodes |
- | kvs | /dc-1/kv |
- | acls | /dc-1/acls |
- | settings | /settings |
- --------------------------------------
+ ----------------------------------------
+ | Link | Url |
+ | nodes | /dc-1/nodes |
+ | kvs | /dc-1/kv |
+ | acls | /dc-1/acls |
+ | intentions | /dc-1/intentions |
+ | settings | /settings |
+ ----------------------------------------
Scenario: Clicking a [Item] in the [Model] listing
When I visit the [Model] page for yaml
---
@@ -31,13 +32,14 @@ Feature: Page Navigation
When I click [Item] on the [Model]
Then the url should be [Url]
Where:
- --------------------------------------------------------
- | Item | Model | Url |
- | service | services | /dc-1/services/service-0 |
- | node | nodes | /dc-1/nodes/node-0 |
- | kv | kvs | /dc-1/kv/necessitatibus-0/edit |
- | acl | acls | /dc-1/acls/anonymous |
- --------------------------------------------------------
+ -------------------------------------------------------------------------------------
+ | Item | Model | Url |
+ | service | services | /dc-1/services/service-0 |
+ | node | nodes | /dc-1/nodes/node-0 |
+ | kv | kvs | /dc-1/kv/necessitatibus-0/edit |
+ | intention | intentions | /dc-1/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca |
+ | acl | acls | /dc-1/acls/anonymous |
+ -------------------------------------------------------------------------------------
@ignore
- Scenario: Clicking a kv in the kvs listing, without depending on the salt ^
+ Scenario: Clicking items in the listings, without depending on the salt ^
Then ok
diff --git a/ui-v2/tests/acceptance/steps/components/intention-filter-steps.js b/ui-v2/tests/acceptance/steps/components/intention-filter-steps.js
new file mode 100644
index 0000000000..960cdf533d
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/components/intention-filter-steps.js
@@ -0,0 +1,10 @@
+import steps from '../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/acceptance/submit-blank.feature b/ui-v2/tests/acceptance/submit-blank.feature
index a8024a384d..444c497f3e 100644
--- a/ui-v2/tests/acceptance/submit-blank.feature
+++ b/ui-v2/tests/acceptance/submit-blank.feature
@@ -13,11 +13,12 @@ Feature: submit blank
And I submit
Then the url should be /datacenter/[Slug]/create
Where:
- ------------------
- | Model | Slug |
- | kv | kv |
- | acl | acls |
- ------------------
+ --------------------------
+ | Model | Slug |
+ | kv | kv |
+ | acl | acls |
+ | intention | intentions |
+ --------------------------
@ignore
Scenario: The button is disabled
Then ok
diff --git a/ui-v2/tests/pages.js b/ui-v2/tests/pages.js
index 599da15a38..99366a40c6 100644
--- a/ui-v2/tests/pages.js
+++ b/ui-v2/tests/pages.js
@@ -9,6 +9,7 @@ import kvs from 'consul-ui/tests/pages/dc/kv/index';
import kv from 'consul-ui/tests/pages/dc/kv/edit';
import acls from 'consul-ui/tests/pages/dc/acls/index';
import acl from 'consul-ui/tests/pages/dc/acls/edit';
+import intentions from 'consul-ui/tests/pages/dc/intentions/index';
import intention from 'consul-ui/tests/pages/dc/intentions/edit';
export default {
@@ -23,5 +24,6 @@ export default {
kv,
acls,
acl,
+ intentions,
intention,
};
diff --git a/ui-v2/tests/pages/components/intention-filter.js b/ui-v2/tests/pages/components/intention-filter.js
new file mode 100644
index 0000000000..39a835e6d4
--- /dev/null
+++ b/ui-v2/tests/pages/components/intention-filter.js
@@ -0,0 +1,9 @@
+import { triggerable } from 'ember-cli-page-object';
+import radiogroup from 'consul-ui/tests/lib/page-object/radiogroup';
+export default {
+ ...radiogroup('action', ['', 'allow', 'deny']),
+ ...{
+ scope: '[data-test-intention-filter]',
+ search: triggerable('keypress', '[name="s"]'),
+ },
+};
diff --git a/ui-v2/tests/pages/components/page.js b/ui-v2/tests/pages/components/page.js
index 507dcaca96..aa634c7b1b 100644
--- a/ui-v2/tests/pages/components/page.js
+++ b/ui-v2/tests/pages/components/page.js
@@ -1,6 +1,6 @@
import { clickable } from 'ember-cli-page-object';
export default {
- navigation: ['services', 'nodes', 'kvs', 'acls', 'docs', 'settings'].reduce(
+ navigation: ['services', 'nodes', 'kvs', 'acls', 'intentions', 'docs', 'settings'].reduce(
function(prev, item, i, arr) {
const key = item;
return Object.assign({}, prev, {
diff --git a/ui-v2/tests/pages/dc/intentions/index.js b/ui-v2/tests/pages/dc/intentions/index.js
new file mode 100644
index 0000000000..b5d2a93722
--- /dev/null
+++ b/ui-v2/tests/pages/dc/intentions/index.js
@@ -0,0 +1,16 @@
+import { create, visitable, collection, attribute, clickable } from 'ember-cli-page-object';
+
+import filter from 'consul-ui/tests/pages/components/intention-filter';
+export default create({
+ visit: visitable('/:dc/intentions'),
+ intentions: collection('[data-test-tabular-row]', {
+ source: attribute('data-test-intention-source', '[data-test-intention-source]'),
+ destination: attribute('data-test-intention-destination', '[data-test-intention-destination]'),
+ action: attribute('data-test-intention-action', '[data-test-intention-action]'),
+ intention: clickable('a'),
+ actions: clickable('label'),
+ delete: clickable('[data-test-delete]'),
+ confirmDelete: clickable('button.type-delete'),
+ }),
+ filter: filter,
+});
diff --git a/ui-v2/tests/steps.js b/ui-v2/tests/steps.js
index 901843bd12..2a61c74555 100644
--- a/ui-v2/tests/steps.js
+++ b/ui-v2/tests/steps.js
@@ -5,6 +5,8 @@ import getDictionary from '@hashicorp/ember-cli-api-double/dictionary';
import pages from 'consul-ui/tests/pages';
import api from 'consul-ui/tests/helpers/api';
+const dont = `( don't| shouldn't| can't)?`;
+
const create = function(number, name, value) {
// don't return a promise here as
// I don't need it to wait
@@ -240,7 +242,9 @@ export default function(assert) {
assert.equal(len, num, `Expected ${num} ${model}s, saw ${len}`);
})
- .then(['I see $num $model model with the $property "$value"'], function(
+ // TODO: I${ dont } see
+ .then([`I see $num $model model[s]? with the $property "$value"`], function(
+ // negate,
num,
model,
property,
From e3ce2a8bebef9021a0ccab87bea7e0676bc05119 Mon Sep 17 00:00:00 2001
From: John Cowen
Date: Wed, 4 Jul 2018 13:41:44 +0100
Subject: [PATCH 27/54] Lock Session invalidation acceptance test
---
ui-v2/app/templates/dc/nodes/-sessions.hbs | 2 +-
.../dc/nodes/sessions/invalidate.feature | 26 +++++++++++++++++++
.../acceptance/dc/nodes/sessions/list.feature | 2 +-
.../dc/nodes/sessions/invalidate-steps.js | 10 +++++++
ui-v2/tests/pages/dc/nodes/show.js | 4 ++-
ui-v2/tests/steps.js | 2 +-
6 files changed, 42 insertions(+), 4 deletions(-)
create mode 100644 ui-v2/tests/acceptance/dc/nodes/sessions/invalidate.feature
create mode 100644 ui-v2/tests/acceptance/steps/dc/nodes/sessions/invalidate-steps.js
diff --git a/ui-v2/app/templates/dc/nodes/-sessions.hbs b/ui-v2/app/templates/dc/nodes/-sessions.hbs
index 8e3d43ddbc..5c0cfda925 100644
--- a/ui-v2/app/templates/dc/nodes/-sessions.hbs
+++ b/ui-v2/app/templates/dc/nodes/-sessions.hbs
@@ -37,7 +37,7 @@
{{#confirmation-dialog message='Are you sure you want to invalidate this session?'}}
{{#block-slot 'action' as |confirm|}}
-
+
{{/block-slot}}
{{#block-slot 'dialog' as |execute cancel message|}}
diff --git a/ui-v2/tests/acceptance/dc/nodes/sessions/invalidate.feature b/ui-v2/tests/acceptance/dc/nodes/sessions/invalidate.feature
new file mode 100644
index 0000000000..786b9e4fd7
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/nodes/sessions/invalidate.feature
@@ -0,0 +1,26 @@
+@setupApplicationTest
+Feature: dc / nodes / sessions / invalidate: Invalidate Lock Sessions
+ In order to invalidate a lock session
+ As a user
+ I should be able to invalidate a lock session by clicking a button and confirming
+ Scenario: Given 2 lock sessions
+ Given 1 datacenter model with the value "dc1"
+ And 1 node model from yaml
+ ---
+ - ID: node-0
+ ---
+ And 2 session models from yaml
+ ---
+ - ID: 7bbbd8bb-fff3-4292-b6e3-cfedd788546a
+ - ID: 7ccd0bd7-a5e0-41ae-a33e-ed3793d803b2
+ ---
+ When I visit the node page for yaml
+ ---
+ dc: dc1
+ node: node-0
+ ---
+ And I click lockSessions on the tabs
+ Then I see lockSessionsIsSelected on the tabs
+ And I click delete on the sessions
+ And I click confirmDelete on the sessions
+ Then a PUT request is made to "/v1/session/destroy/7bbbd8bb-fff3-4292-b6e3-cfedd788546a?dc=dc1"
diff --git a/ui-v2/tests/acceptance/dc/nodes/sessions/list.feature b/ui-v2/tests/acceptance/dc/nodes/sessions/list.feature
index 41055dc745..4c7f17beda 100644
--- a/ui-v2/tests/acceptance/dc/nodes/sessions/list.feature
+++ b/ui-v2/tests/acceptance/dc/nodes/sessions/list.feature
@@ -1,5 +1,5 @@
@setupApplicationTest
-Feature: dc / nodes / sessions /list: List Lock Sessions
+Feature: dc / nodes / sessions / list: List Lock Sessions
In order to get information regarding lock sessions
As a user
I should be able to see a listing of lock sessions with necessary information under the lock sessions tab for a node
diff --git a/ui-v2/tests/acceptance/steps/dc/nodes/sessions/invalidate-steps.js b/ui-v2/tests/acceptance/steps/dc/nodes/sessions/invalidate-steps.js
new file mode 100644
index 0000000000..9bfbe9ac9b
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/nodes/sessions/invalidate-steps.js
@@ -0,0 +1,10 @@
+import steps from '../../../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/pages/dc/nodes/show.js b/ui-v2/tests/pages/dc/nodes/show.js
index d51bda6cf5..298c51f543 100644
--- a/ui-v2/tests/pages/dc/nodes/show.js
+++ b/ui-v2/tests/pages/dc/nodes/show.js
@@ -1,4 +1,4 @@
-import { create, visitable, collection, attribute } from 'ember-cli-page-object';
+import { create, visitable, collection, attribute, clickable } from 'ember-cli-page-object';
import radiogroup from 'consul-ui/tests/lib/page-object/radiogroup';
export default create({
@@ -11,6 +11,8 @@ export default create({
port: attribute('data-test-service-port', '.port'),
}),
sessions: collection('#lock-sessions [data-test-tabular-row]', {
+ delete: clickable('[data-test-delete]'),
+ confirmDelete: clickable('button.type-delete'),
TTL: attribute('data-test-session-ttl', '[data-test-session-ttl]'),
}),
});
diff --git a/ui-v2/tests/steps.js b/ui-v2/tests/steps.js
index 2a61c74555..a3d9365ab5 100644
--- a/ui-v2/tests/steps.js
+++ b/ui-v2/tests/steps.js
@@ -5,7 +5,7 @@ import getDictionary from '@hashicorp/ember-cli-api-double/dictionary';
import pages from 'consul-ui/tests/pages';
import api from 'consul-ui/tests/helpers/api';
-const dont = `( don't| shouldn't| can't)?`;
+// const dont = `( don't| shouldn't| can't)?`;
const create = function(number, name, value) {
// don't return a promise here as
From e0f7cdb1287fcf0d58c1515824f21a26b4fd177f Mon Sep 17 00:00:00 2001
From: John Cowen
Date: Wed, 4 Jul 2018 15:06:20 +0100
Subject: [PATCH 28/54] Start purifying page objects
---
ui-v2/app/templates/dc/acls/-form.hbs | 2 +-
ui-v2/tests/acceptance/dc/acls/delete.feature | 17 +++++++-
.../tests/lib/page-object/createDeletable.js | 11 +++++
.../tests/lib/page-object/createSubmitable.js | 11 +++++
ui-v2/tests/pages.js | 41 +++++++++++++------
ui-v2/tests/pages/dc.js | 21 ++++++----
ui-v2/tests/pages/dc/acls/edit.js | 18 ++++----
ui-v2/tests/pages/dc/acls/index.js | 28 ++++++-------
ui-v2/tests/pages/dc/intentions/edit.js | 15 ++++---
ui-v2/tests/pages/dc/intentions/index.js | 35 ++++++++--------
ui-v2/tests/pages/dc/kv/edit.js | 18 ++++----
ui-v2/tests/pages/dc/kv/index.js | 25 +++++------
ui-v2/tests/pages/dc/nodes/index.js | 21 +++++-----
ui-v2/tests/pages/dc/nodes/show.js | 36 ++++++++--------
ui-v2/tests/pages/dc/services/index.js | 28 ++++++-------
ui-v2/tests/pages/dc/services/show.js | 35 ++++++++--------
ui-v2/tests/pages/index.js | 12 +++---
ui-v2/tests/pages/settings.js | 11 +++--
ui-v2/tests/steps.js | 9 ++++
19 files changed, 223 insertions(+), 171 deletions(-)
create mode 100644 ui-v2/tests/lib/page-object/createDeletable.js
create mode 100644 ui-v2/tests/lib/page-object/createSubmitable.js
diff --git a/ui-v2/app/templates/dc/acls/-form.hbs b/ui-v2/app/templates/dc/acls/-form.hbs
index 2d0ef5edf1..803444e5a2 100644
--- a/ui-v2/app/templates/dc/acls/-form.hbs
+++ b/ui-v2/app/templates/dc/acls/-form.hbs
@@ -35,7 +35,7 @@
{{# if (and (not create) (not-eq item.ID 'anonymous')) }}
{{#confirmation-dialog message='Are you sure you want to delete this ACL token?'}}
{{#block-slot 'action' as |confirm|}}
-
+
{{/block-slot}}
{{#block-slot 'dialog' as |execute cancel message|}}
{{/action-group}}
diff --git a/ui-v2/app/templates/dc/kv/-form.hbs b/ui-v2/app/templates/dc/kv/-form.hbs
index 9b859c5a19..fa188f9d4a 100644
--- a/ui-v2/app/templates/dc/kv/-form.hbs
+++ b/ui-v2/app/templates/dc/kv/-form.hbs
@@ -33,7 +33,7 @@
{{#confirmation-dialog message='Are you sure you want to delete this key?'}}
{{#block-slot 'action' as |confirm|}}
-
+
{{/block-slot}}
{{#block-slot 'dialog' as |execute cancel message|}}
diff --git a/ui-v2/package.json b/ui-v2/package.json
index 5dd0dee53b..70be6565be 100644
--- a/ui-v2/package.json
+++ b/ui-v2/package.json
@@ -2,7 +2,7 @@
"name": "consul-ui",
"version": "2.2.0",
"private": true,
- "description": "The web ui for Consul, by HashiCorp.",
+ "description": "The web UI for Consul, by HashiCorp.",
"directories": {
"doc": "doc",
"test": "tests"
diff --git a/ui-v2/tests/acceptance/dc/acls/delete.feature b/ui-v2/tests/acceptance/dc/acls/delete.feature
deleted file mode 100644
index 4842b2c712..0000000000
--- a/ui-v2/tests/acceptance/dc/acls/delete.feature
+++ /dev/null
@@ -1,32 +0,0 @@
-@setupApplicationTest
-Feature: dc / acls / delete: ACL Delete
- Scenario: Deleting an ACL from the ACL listing page
- Given 1 datacenter model with the value "datacenter"
- And 1 acl model from yaml
- ---
- Name: something
- ID: key
- ---
- When I visit the acls page for yaml
- ---
- dc: datacenter
- ---
- And I click actions on the acls
- And I click delete on the acls
- And I click confirmDelete on the acls
- Then a PUT request is made to "/v1/acl/destroy/key?dc=datacenter"
- Scenario: Deleting an ACL from the ACL detail page
- Given 1 datacenter model with the value "datacenter"
- And 1 acl model from yaml
- ---
- Name: something
- ID: key
- ---
- When I visit the acl page for yaml
- ---
- dc: datacenter
- acl: something
- ---
- And I click delete
- And I click confirmDelete
- Then a PUT request is made to "/v1/acl/destroy/something?dc=datacenter"
diff --git a/ui-v2/tests/acceptance/dc/kvs/delete.feature b/ui-v2/tests/acceptance/dc/kvs/delete.feature
deleted file mode 100644
index 324240b9c9..0000000000
--- a/ui-v2/tests/acceptance/dc/kvs/delete.feature
+++ /dev/null
@@ -1,16 +0,0 @@
-@setupApplicationTest
-Feature: dc / kvs / delete: KV Delete
- Scenario: Delete ACL
- Given 1 datacenter model with the value "datacenter"
- And 1 kv model from yaml
- ---
- - key-name
- ---
- When I visit the kvs page for yaml
- ---
- dc: datacenter
- ---
- And I click actions on the kvs
- And I click delete on the kvs
- And I click confirmDelete on the kvs
- Then a DELETE request is made to "/v1/kv/key-name?dc=datacenter"
diff --git a/ui-v2/tests/acceptance/deleting.feature b/ui-v2/tests/acceptance/deleting.feature
new file mode 100644
index 0000000000..30808f77cd
--- /dev/null
+++ b/ui-v2/tests/acceptance/deleting.feature
@@ -0,0 +1,34 @@
+@setupApplicationTest
+Feature: deleting: Deleting form the listing and the detail page with confirmation
+ Scenario: Deleting a [Model] from the [Model] listing page
+ Given 1 datacenter model with the value "datacenter"
+ And 1 [Model] model from json
+ ---
+ [Data]
+ ---
+ When I visit the [Model]s page for yaml
+ ---
+ dc: datacenter
+ ---
+ And I click actions on the [Model]s
+ And I click delete on the [Model]s
+ And I click confirmDelete on the [Model]s
+ Then a [Method] request is made to "[URL]"
+ When I visit the [Model] page for yaml
+ ---
+ dc: datacenter
+ [Slug]
+ ---
+ And I click delete
+ And I click confirmDelete
+ Then a [Method] request is made to "[URL]"
+ Where:
+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ | Model | Method | URL | Data | Slug |
+ | acl | PUT | /v1/acl/destroy/something?dc=datacenter | {"Name": "something", "ID": "something"} | acl: something |
+ | kv | DELETE | /v1/kv/key-name?dc=datacenter | ["key-name"] | kv: key-name |
+ | intention | DELETE | /v1/connect/intentions/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=datacenter | {"SourceName": "name", "ID": "ee52203d-989f-4f7a-ab5a-2bef004164ca"} | intention: ee52203d-989f-4f7a-ab5a-2bef004164ca |
+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+@ignore
+ Scenario: Sort out the wide tables ^
+ Then ok
diff --git a/ui-v2/tests/acceptance/steps/dc/kvs/delete-steps.js b/ui-v2/tests/acceptance/steps/dc/kvs/delete-steps.js
deleted file mode 100644
index a7eff3228b..0000000000
--- a/ui-v2/tests/acceptance/steps/dc/kvs/delete-steps.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import steps from '../../steps';
-
-// step definitions that are shared between features should be moved to the
-// tests/acceptance/steps/steps.js file
-
-export default function(assert) {
- return steps(assert).then('I should find a file', function() {
- assert.ok(true, this.step);
- });
-}
diff --git a/ui-v2/tests/acceptance/steps/dc/acls/delete-steps.js b/ui-v2/tests/acceptance/steps/deleting-steps.js
similarity index 50%
rename from ui-v2/tests/acceptance/steps/dc/acls/delete-steps.js
rename to ui-v2/tests/acceptance/steps/deleting-steps.js
index a7eff3228b..6e459ed1f0 100644
--- a/ui-v2/tests/acceptance/steps/dc/acls/delete-steps.js
+++ b/ui-v2/tests/acceptance/steps/deleting-steps.js
@@ -1,10 +1,11 @@
-import steps from '../../steps';
+import steps from './steps';
// step definitions that are shared between features should be moved to the
// tests/acceptance/steps/steps.js file
export default function(assert) {
- return steps(assert).then('I should find a file', function() {
- assert.ok(true, this.step);
- });
+ return steps(assert)
+ .then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
}
From 45bb8bce709d6199c94cf3ed2c9acebfa80dcdfd Mon Sep 17 00:00:00 2001
From: M S Vishwanath Bhat
Date: Wed, 4 Jul 2018 17:07:24 +0200
Subject: [PATCH 30/54] Trivial spell correction in connect configuration doc
Signed-off-by: M S Vishwanath Bhat
---
website/source/docs/connect/configuration.html.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/website/source/docs/connect/configuration.html.md b/website/source/docs/connect/configuration.html.md
index a7f9926705..3a08348810 100644
--- a/website/source/docs/connect/configuration.html.md
+++ b/website/source/docs/connect/configuration.html.md
@@ -90,7 +90,7 @@ described here, the rest of the service definition is shown for context and is
* `bind_port` - The
port the proxy will bind it's _public_ mTLS listener to. If not provided, the
- agent will attempt to assing one from its [configured proxy port
+ agent will attempt to assign one from its [configured proxy port
range](/docs/agent/options.html#proxy_min_port) if available. By default the
range is [20000, 20255] and the port is selected at random from that range.
From 649d7777145bcef397e6e01db7be2843b84ea202 Mon Sep 17 00:00:00 2001
From: John Cowen
Date: Wed, 4 Jul 2018 16:37:41 +0100
Subject: [PATCH 31/54] Acceptance test for using tokens from listing and
detail pages
---
ui-v2/app/templates/dc/acls/edit.hbs | 2 +-
ui-v2/tests/acceptance/dc/acls/use.feature | 40 +++++++++++++++++++
.../tests/acceptance/settings/update.feature | 4 ++
.../acceptance/steps/dc/acls/use-steps.js | 10 +++++
ui-v2/tests/pages.js | 2 +-
ui-v2/tests/pages/dc/acls/edit.js | 4 +-
ui-v2/tests/pages/dc/acls/index.js | 2 +
7 files changed, 61 insertions(+), 3 deletions(-)
create mode 100644 ui-v2/tests/acceptance/dc/acls/use.feature
create mode 100644 ui-v2/tests/acceptance/steps/dc/acls/use-steps.js
diff --git a/ui-v2/app/templates/dc/acls/edit.hbs b/ui-v2/app/templates/dc/acls/edit.hbs
index 9ab41201bd..e63de26726 100644
--- a/ui-v2/app/templates/dc/acls/edit.hbs
+++ b/ui-v2/app/templates/dc/acls/edit.hbs
@@ -35,7 +35,7 @@
{{#confirmation-dialog message='Are you sure you want to use this ACL token?'}}
{{#block-slot 'action' as |confirm|}}
-
+
{{/block-slot}}
{{#block-slot 'dialog' as |execute cancel message|}}
diff --git a/ui-v2/tests/acceptance/dc/acls/use.feature b/ui-v2/tests/acceptance/dc/acls/use.feature
new file mode 100644
index 0000000000..6ad7224872
--- /dev/null
+++ b/ui-v2/tests/acceptance/dc/acls/use.feature
@@ -0,0 +1,40 @@
+@setupApplicationTest
+Feature: dc / acls / use: Using an ACL token
+ Background:
+ Given 1 datacenter model with the value "datacenter"
+ And 1 acl model from yaml
+ ---
+ ID: token
+ ---
+ Scenario: Using an ACL token from the listing page
+ When I visit the acls page for yaml
+ ---
+ dc: datacenter
+ ---
+ Then I have settings like yaml
+ ---
+ token: ~
+ ---
+ And I click actions on the acls
+ And I click use on the acls
+ And I click confirmUse on the acls
+ Then I have settings like yaml
+ ---
+ token: token
+ ---
+ Scenario: Using an ACL token from the detail page
+ When I visit the acl page for yaml
+ ---
+ dc: datacenter
+ acl: token
+ ---
+ Then I have settings like yaml
+ ---
+ token: ~
+ ---
+ And I click use
+ And I click confirmUse
+ Then I have settings like yaml
+ ---
+ token: token
+ ---
diff --git a/ui-v2/tests/acceptance/settings/update.feature b/ui-v2/tests/acceptance/settings/update.feature
index 98ac584d0b..300a9123a9 100644
--- a/ui-v2/tests/acceptance/settings/update.feature
+++ b/ui-v2/tests/acceptance/settings/update.feature
@@ -7,6 +7,10 @@ Feature: settings / update: Update Settings
Given 1 datacenter model with the value "datacenter"
When I visit the settings page
Then the url should be /settings
+ Then I have settings like yaml
+ ---
+ token: ~
+ ---
And I submit
Then I have settings like yaml
---
diff --git a/ui-v2/tests/acceptance/steps/dc/acls/use-steps.js b/ui-v2/tests/acceptance/steps/dc/acls/use-steps.js
new file mode 100644
index 0000000000..a7eff3228b
--- /dev/null
+++ b/ui-v2/tests/acceptance/steps/dc/acls/use-steps.js
@@ -0,0 +1,10 @@
+import steps from '../../steps';
+
+// step definitions that are shared between features should be moved to the
+// tests/acceptance/steps/steps.js file
+
+export default function(assert) {
+ return steps(assert).then('I should find a file', function() {
+ assert.ok(true, this.step);
+ });
+}
diff --git a/ui-v2/tests/pages.js b/ui-v2/tests/pages.js
index 175a4dd1c7..f72772b223 100644
--- a/ui-v2/tests/pages.js
+++ b/ui-v2/tests/pages.js
@@ -35,7 +35,7 @@ export default {
kvs: create(kvs(visitable, deletable, clickable, attribute, collection)),
kv: create(kv(visitable, submitable, deletable)),
acls: create(acls(visitable, deletable, clickable, attribute, collection, aclFilter)),
- acl: create(acl(visitable, submitable, deletable)),
+ acl: create(acl(visitable, submitable, deletable, clickable)),
intentions: create(
intentions(visitable, deletable, clickable, attribute, collection, intentionFilter)
),
diff --git a/ui-v2/tests/pages/dc/acls/edit.js b/ui-v2/tests/pages/dc/acls/edit.js
index 1164d4c0ce..53ede15259 100644
--- a/ui-v2/tests/pages/dc/acls/edit.js
+++ b/ui-v2/tests/pages/dc/acls/edit.js
@@ -1,7 +1,9 @@
-export default function(visitable, submitable, deletable, triggerable) {
+export default function(visitable, submitable, deletable, clickable) {
return submitable(
deletable({
visit: visitable(['/:dc/acls/:acl', '/:dc/acls/create']),
+ use: clickable('[data-test-use]'),
+ confirmUse: clickable('button.type-delete'),
})
);
}
diff --git a/ui-v2/tests/pages/dc/acls/index.js b/ui-v2/tests/pages/dc/acls/index.js
index 7c567840e1..d96d935478 100644
--- a/ui-v2/tests/pages/dc/acls/index.js
+++ b/ui-v2/tests/pages/dc/acls/index.js
@@ -7,6 +7,8 @@ export default function(visitable, deletable, clickable, attribute, collection,
name: attribute('data-test-acl', '[data-test-acl]'),
acl: clickable('a'),
actions: clickable('label'),
+ use: clickable('[data-test-use]'),
+ confirmUse: clickable('button.type-delete'),
})
),
filter: filter,
From f85369c375d527f4b24a6efe618c84f3c74e09f7 Mon Sep 17 00:00:00 2001
From: John Cowen
Date: Wed, 4 Jul 2018 17:23:33 +0100
Subject: [PATCH 32/54] Add some navigation testing for back buttons and create
buttons
---
ui-v2/app/templates/dc/acls/edit.hbs | 2 +-
ui-v2/app/templates/dc/acls/index.hbs | 2 +-
ui-v2/app/templates/dc/intentions/edit.hbs | 2 +-
ui-v2/app/templates/dc/intentions/index.hbs | 2 +-
ui-v2/app/templates/dc/kv/edit.hbs | 2 +-
ui-v2/app/templates/dc/kv/index.hbs | 4 +-
ui-v2/app/templates/dc/nodes/show.hbs | 2 +-
ui-v2/app/templates/dc/services/show.hbs | 2 +-
.../tests/acceptance/page-navigation.feature | 46 +++++++++++++------
.../tests/lib/page-object/createCreatable.js | 11 +++++
ui-v2/tests/pages.js | 8 ++--
ui-v2/tests/pages/dc/acls/index.js | 6 +--
ui-v2/tests/pages/dc/intentions/index.js | 6 +--
ui-v2/tests/pages/dc/kv/index.js | 6 +--
14 files changed, 67 insertions(+), 34 deletions(-)
create mode 100644 ui-v2/tests/lib/page-object/createCreatable.js
diff --git a/ui-v2/app/templates/dc/acls/edit.hbs b/ui-v2/app/templates/dc/acls/edit.hbs
index e63de26726..4a6f703016 100644
--- a/ui-v2/app/templates/dc/acls/edit.hbs
+++ b/ui-v2/app/templates/dc/acls/edit.hbs
@@ -1,7 +1,7 @@
{{#app-view class="acl edit" loading=isLoading}}
{{#block-slot 'breadcrumbs'}}
-
This increments whenever a registration fails for an entity (check, node or service) is blocked by an ACL
+
requests
+
counter
+
`consul.client.rpc`
This increments whenever a Consul agent in client mode makes an RPC request to a Consul server. This gives a measure of how much a given agent is loading the Consul servers. Currently, this is only generated by agents in client mode, not Consul servers.
From 0241cda6454e7b46cd4046521ddb2f00a0adc422 Mon Sep 17 00:00:00 2001
From: Paul Banks
Date: Mon, 9 Jul 2018 14:39:20 +0100
Subject: [PATCH 46/54] Fix formatting issue in config docs.
The floating paragraph seems to need additional indentation to work correctly on the markdown parser middle man uses - GitHub got it right before in the preview but the website broke dumping the new config option inline.
---
website/source/docs/agent/options.html.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/website/source/docs/agent/options.html.md b/website/source/docs/agent/options.html.md
index 4e2ecb3033..89917768b4 100644
--- a/website/source/docs/agent/options.html.md
+++ b/website/source/docs/agent/options.html.md
@@ -822,7 +822,7 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
matching hosts, shuffle the list randomly, and then limit the number of
answers to `a_record_limit` (default: no limit). This limit does not apply to SRV records.
- In environments where [RFC 3484 Section 6](https://tools.ietf.org/html/rfc3484#section-6) Rule 9
+ In environments where [RFC 3484 Section 6](https://tools.ietf.org/html/rfc3484#section-6) Rule 9
is implemented and enforced (i.e. DNS answers are always sorted and
therefore never random), clients may need to set this value to `1` to
preserve the expected randomized distribution behavior (note:
@@ -831,7 +831,7 @@ 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).
- * `enable_additional_node_meta_txt` -
+ * `enable_additional_node_meta_txt` -
When set to true, Consul will add TXT records for Node metadata into the Additional section of the DNS responses for several
query types such as SRV queries. When set to false those records are emitted. This does not impact the behavior of those
same TXT records when they would be added to the Answer section of the response like when querying with type TXT or ANY. This
From d508a6ba7c00ee940c1b965114f7cde4fd6718e2 Mon Sep 17 00:00:00 2001
From: Leonid Stryzhevskyi
Date: Mon, 9 Jul 2018 17:28:03 +0300
Subject: [PATCH 47/54] oatpp-consul integration added to Libraries & SDKs page
---
website/source/api/libraries-and-sdks.html.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/website/source/api/libraries-and-sdks.html.md b/website/source/api/libraries-and-sdks.html.md
index 4e38239f53..3bf8f732ab 100644
--- a/website/source/api/libraries-and-sdks.html.md
+++ b/website/source/api/libraries-and-sdks.html.md
@@ -79,4 +79,7 @@ the community.
ConsulSwift - Swift client for the Consul HTTP API