merge master

This commit is contained in:
Kit Patella 2020-11-16 10:46:53 -08:00
commit 374748dafc
224 changed files with 6985 additions and 2616 deletions

3
.changelog/9007.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
server: break up Intention.Apply monolithic method
```

3
.changelog/9098.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
cli: snapshot inspect command provides KV usage breakdown
```

3
.changelog/9101.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
agent: return the default ACL policy to callers as a header
```

3
.changelog/9119.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
chore: update to Go 1.14.11 with mitigation for [golang/go#42138](https://github.com/golang/go/issues/42138)
```

3
.changelog/9142.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
autopilot: Added a new `consul operator autopilot state` command to retrieve and view the Autopilot state from consul.
```

3
.changelog/9151.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
server: remove config entry CAS in legacy intention API bridge code
```

3
.changelog/9156.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
namespace: **(Enterprise Only)** Fixed a bug that could case snapshot restoration to fail when it contained a namespace marked for deletion while still containing other resources in that namespace.
```

3
.changelog/9181.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:deprecation
telemetry: the disable_compat_1.9 config will cover more metrics deprecations in future 1.9 point releases. These metrics will be emitted twice for backwards compatibility - if the flag is true, only the new metric name will be written.
```

18
.changelog/9186.txt Normal file
View File

@ -0,0 +1,18 @@
```release-note:bug
server: skip deleted and deleting namespaces when migrating intentions to config entries
```
```release-note:breaking-change
server: **(Enterprise only)** Pre-existing intentions defined with
non-existent destination namespaces were non-functional and are erased during
the upgrade process. This should not matter as these intentions had nothing to
enforce.
```
```release-note:breaking-change
server: **(OSS only)** Pre-existing intentions defined with either a source or
destination namespace value that is not "default" are rewritten or deleted
during the upgrade process. Wildcards first attempt to downgrade to "default"
unless an intention already exists, otherwise these non-functional intentions
are deleted.
```

3
.changelog/_666.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
namespace: **(Enterprise Only)** Fixed an issue where namespaced services and checks were not being deleted when the containing namespace was deleted.
```

View File

@ -3,7 +3,7 @@ version: 2
references:
images:
go: &GOLANG_IMAGE docker.mirror.hashicorp.services/circleci/golang:1.14.11
go: &GOLANG_IMAGE docker.mirror.hashicorp.services/circleci/golang:1.15.5
ember: &EMBER_IMAGE docker.mirror.hashicorp.services/circleci/node:12-browsers
paths:
@ -243,7 +243,7 @@ jobs:
-tags="$GOTAGS" -p 2 \
-race -gcflags=all=-d=checkptr=0 \
./agent/{ae,cache,cache-types,checks,config,pool,proxycfg,router}/... \
./agent/consul/{authmethod,autopilot,fsm,state,stream}/... \
./agent/consul/{authmethod,fsm,state,stream}/... \
./agent/{grpc,rpc,rpcclient,submatview}/... \
./snapshot
@ -344,7 +344,7 @@ jobs:
<<: *build-distros
environment:
<<: *build-env
XC_OS: "darwin freebsd linux windows"
XC_OS: "freebsd linux windows"
XC_ARCH: "386"
# build all amd64 architecture supported OS binaries
@ -821,7 +821,7 @@ jobs:
# only runs on master: checks latest commit to see if the PR associated has a backport/* or docs* label to cherry-pick
cherry-picker:
docker:
- image: docker.mirror.hashicorp.services/alpine:3.11
- image: docker.mirror.hashicorp.services/alpine:3.12
steps:
- run: apk add --no-cache --no-progress git bash curl ncurses jq openssh-client
- checkout
@ -833,7 +833,7 @@ jobs:
trigger-oss-merge:
docker:
- image: docker.mirror.hashicorp.services/alpine:3.11
- image: docker.mirror.hashicorp.services/alpine:3.12
steps:
- run: apk add --no-cache --no-progress curl jq
- run:
@ -894,6 +894,7 @@ workflows:
branches:
only:
- master
- /release\/\d+\.\d+\.x$/
requires:
- build-static-assets
- dev-build:

View File

@ -133,7 +133,7 @@ pr_number=$(echo "$resp" | jq '.items[].number')
# comment on the PR with the build number to make it easy to re-run the job when
# cherry-pick labels are added in the future
github_message=":cherries: Starting backport cherry picking.\n\nTo cherry-pick post-merge, add backport labels and re-run ${CIRCLE_BUILD_URL}."
github_message=":cherries: If backport labels were added before merging, cherry-picking will start automatically.\n\nTo retroactively trigger a backport after merging, add backport labels and re-run ${CIRCLE_BUILD_URL}."
curl -f -s -H "Authorization: token ${GITHUB_TOKEN}" \
-X POST \
-d "{ \"body\": \"${github_message}\"}" \

View File

@ -1,5 +1,25 @@
## UNRELEASED
## 1.9.0-beta3 (November 10, 2020)
BREAKING CHANGES:
* connect: Switch the default gateway port from 443 to 8443 to avoid assumption of Envoy running as root. [[GH-9113](https://github.com/hashicorp/consul/issues/9113)]
* raft: Raft protocol v2 is no longer supported. If currently using protocol v2 then an intermediate upgrade to a version supporting both v2 and v3 protocols will be necessary (1.0.0 - 1.8.x). Note that the Raft protocol configured with the `raft_protocol` setting and the Consul RPC protocol configured with the `protocol` setting and output by the `consul version` command are distinct and supported Consul RPC protocol versions are not altered. [[GH-9103](https://github.com/hashicorp/consul/issues/9103)]
FEATURES:
* autopilot: A new `/v1/operator/autopilot/state` HTTP API was created to give greater visibility into what autopilot is doing and how it has classified all the servers it is tracking. [[GH-9103](https://github.com/hashicorp/consul/issues/9103)]
IMPROVEMENTS:
* autopilot: **(Enterprise Only)** Autopilot now supports using both Redundancy Zones and Automated Upgrades together. [[GH-9103](https://github.com/hashicorp/consul/issues/9103)]
* chore: update to Go 1.14.11 with mitigation for [golang/go#42138](https://github.com/golang/go/issues/42138) [[GH-9119](https://github.com/hashicorp/consul/issues/9119)]
BUG FIXES:
* autopilot: **(Enterprise Only)** Previously servers in other zones would not be promoted when all servers in a second zone had failed. Now the actual behavior matches the docs and autopilot will promote a healthy non-voter from any zone to replace failure of an entire zone. [[GH-9103](https://github.com/hashicorp/consul/issues/9103)]
## 1.9.0-beta2 (November 07, 2020)
BREAKING CHANGES:

View File

@ -77,8 +77,8 @@ type RuntimeConfig struct {
// ACLDefaultPolicy is used to control the ACL interaction when
// there is no defined policy. This can be "allow" which means
// ACLs are used to black-list, or "deny" which means ACLs are
// white-lists.
// ACLs are used to deny-list, or "deny" which means ACLs are
// allow-lists.
//
// hcl: acl.default_policy = ("allow"|"deny")
ACLDefaultPolicy string

View File

@ -268,8 +268,8 @@ type Config struct {
// ACLDefaultPolicy is used to control the ACL interaction when
// there is no defined policy. This can be "allow" which means
// ACLs are used to black-list, or "deny" which means ACLs are
// white-lists.
// ACLs are used to deny-list, or "deny" which means ACLs are
// allow-lists.
ACLDefaultPolicy string
// ACLDownPolicy controls the behavior of ACLs if the ACLDatacenter

View File

@ -48,10 +48,6 @@ type ConfigEntry struct {
// Apply does an upsert of the given config entry.
func (c *ConfigEntry) Apply(args *structs.ConfigEntryRequest, reply *bool) error {
return c.applyInternal(args, reply, nil)
}
func (c *ConfigEntry) applyInternal(args *structs.ConfigEntryRequest, reply *bool, normalizeAndValidateFn func(structs.ConfigEntry) error) error {
if err := c.srv.validateEnterpriseRequest(args.Entry.GetEnterpriseMeta(), true); err != nil {
return err
}
@ -76,17 +72,11 @@ func (c *ConfigEntry) applyInternal(args *structs.ConfigEntryRequest, reply *boo
}
// Normalize and validate the incoming config entry as if it came from a user.
if normalizeAndValidateFn == nil {
if err := args.Entry.Normalize(); err != nil {
return err
}
if err := args.Entry.Validate(); err != nil {
return err
}
} else {
if err := normalizeAndValidateFn(args.Entry); err != nil {
return err
}
if err := args.Entry.Normalize(); err != nil {
return err
}
if err := args.Entry.Validate(); err != nil {
return err
}
if authz != nil && !args.Entry.CanWrite(authz) {
@ -483,6 +473,11 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
func (c *ConfigEntry) preflightCheck(kind string) error {
switch kind {
case structs.ServiceIntentions:
// Exit early if Connect hasn't been enabled.
if !c.srv.config.ConnectEnabled {
return ErrConnectNotEnabled
}
usingConfigEntries, err := c.srv.fsm.State().AreIntentionsInConfigEntries()
if err != nil {
return fmt.Errorf("system metadata lookup failed: %v", err)

View File

@ -382,6 +382,11 @@ func (c *FSM) applyIntentionOperation(buf []byte, index uint64) interface{} {
[]metrics.Label{{Name: "op", Value: string(req.Op)}})
defer metrics.MeasureSinceWithLabels([]string{"fsm", "intention"}, time.Now(),
[]metrics.Label{{Name: "op", Value: string(req.Op)}})
if req.Mutation != nil {
return c.state.IntentionMutation(index, req.Op, req.Mutation)
}
switch req.Op {
case structs.IntentionOpCreate, structs.IntentionOpUpdate:
//nolint:staticcheck

View File

@ -33,22 +33,11 @@ var (
ErrIntentionNotFound = errors.New("Intention not found")
)
// NewIntentionEndpoint returns a new Intention endpoint.
func NewIntentionEndpoint(srv *Server, logger hclog.Logger) *Intention {
return &Intention{
srv: srv,
logger: logger,
configEntryEndpoint: &ConfigEntry{srv},
}
}
// Intention manages the Connect intentions.
type Intention struct {
// srv is a pointer back to the server.
srv *Server
logger hclog.Logger
configEntryEndpoint *ConfigEntry
}
func (s *Intention) checkIntentionID(id string) (bool, error) {
@ -62,25 +51,139 @@ func (s *Intention) checkIntentionID(id string) (bool, error) {
return true, nil
}
// prepareApplyCreate validates that the requester has permissions to create
// the new intention, generates a new uuid for the intention and generally
// validates that the request is well-formed
//
// Returns an existing service-intentions config entry for this destination if
// one exists.
func (s *Intention) prepareApplyCreate(
ident structs.ACLIdentity,
var ErrIntentionsNotUpgradedYet = errors.New("Intentions are read only while being upgraded to config entries")
// legacyUpgradeCheck fast fails a write request using the legacy intention
// RPCs if the system is known to be mid-upgrade. This is purely a perf
// optimization and the actual real enforcement happens in the FSM. It would be
// wasteful to round trip all the way through raft to have it fail for
// known-up-front reasons, hence why we check it twice.
func (s *Intention) legacyUpgradeCheck() error {
usingConfigEntries, err := s.srv.fsm.State().AreIntentionsInConfigEntries()
if err != nil {
return fmt.Errorf("system metadata lookup failed: %v", err)
}
if !usingConfigEntries {
return ErrIntentionsNotUpgradedYet
}
return nil
}
// Apply creates or updates an intention in the data store.
func (s *Intention) Apply(args *structs.IntentionRequest, reply *string) error {
// Exit early if Connect hasn't been enabled.
if !s.srv.config.ConnectEnabled {
return ErrConnectNotEnabled
}
// Ensure that all service-intentions config entry writes go to the primary
// datacenter. These will then be replicated to all the other datacenters.
args.Datacenter = s.srv.config.PrimaryDatacenter
if done, err := s.srv.ForwardRPC("Intention.Apply", args, args, reply); done {
return err
}
defer metrics.MeasureSince([]string{"consul", "intention", "apply"}, time.Now())
defer metrics.MeasureSince([]string{"intention", "apply"}, time.Now())
if err := s.legacyUpgradeCheck(); err != nil {
return err
}
if args.Mutation != nil {
return fmt.Errorf("Mutation field is internal only and must not be set via RPC")
}
// Always set a non-nil intention to avoid nil-access below
if args.Intention == nil {
args.Intention = &structs.Intention{}
}
// Get the ACL token for the request for the checks below.
var entMeta structs.EnterpriseMeta
ident, authz, err := s.srv.ResolveTokenIdentityAndDefaultMeta(args.Token, &entMeta, nil)
if err != nil {
return err
}
var accessorID string
if ident != nil {
accessorID = ident.ID()
}
var (
mut *structs.IntentionMutation
legacyWrite bool
)
switch args.Op {
case structs.IntentionOpCreate:
legacyWrite = true
mut, err = s.computeApplyChangesLegacyCreate(accessorID, authz, &entMeta, args)
case structs.IntentionOpUpdate:
legacyWrite = true
mut, err = s.computeApplyChangesLegacyUpdate(accessorID, authz, &entMeta, args)
case structs.IntentionOpUpsert:
legacyWrite = false
mut, err = s.computeApplyChangesUpsert(accessorID, authz, &entMeta, args)
case structs.IntentionOpDelete:
if args.Intention.ID == "" {
legacyWrite = false
mut, err = s.computeApplyChangesDelete(accessorID, authz, &entMeta, args)
} else {
legacyWrite = true
mut, err = s.computeApplyChangesLegacyDelete(accessorID, authz, &entMeta, args)
}
case structs.IntentionOpDeleteAll:
// This is an internal operation initiated by the leader and is not
// exposed for general RPC use.
return fmt.Errorf("Invalid Intention operation: %v", args.Op)
default:
return fmt.Errorf("Invalid Intention operation: %v", args.Op)
}
if err != nil {
return err
}
if legacyWrite {
*reply = args.Intention.ID
} else {
*reply = ""
}
// Switch to the config entry manipulating flavor:
args.Mutation = mut
args.Intention = nil
resp, err := s.srv.raftApply(structs.IntentionRequestType, args)
if err != nil {
return err
}
if respErr, ok := resp.(error); ok {
return respErr
}
return nil
}
func (s *Intention) computeApplyChangesLegacyCreate(
accessorID string,
authz acl.Authorizer,
entMeta *structs.EnterpriseMeta,
args *structs.IntentionRequest,
) (*structs.ServiceIntentionsConfigEntry, error) {
) (*structs.IntentionMutation, error) {
// This variant is just for legacy UUID-based intentions.
args.Intention.DefaultNamespaces(entMeta)
if !args.Intention.CanWrite(authz) {
var accessorID string
if ident != nil {
accessorID = ident.ID()
}
sn := args.Intention.SourceServiceName()
dn := args.Intention.DestinationServiceName()
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
s.logger.Warn("Intention creation denied due to ACLs", "intention", args.Intention.ID, "accessorID", accessorID)
s.logger.Warn("Intention creation denied due to ACLs",
"source", sn.String(),
"destination", dn.String(),
"accessorID", accessorID)
return nil, acl.ErrPermissionDenied
}
@ -106,8 +209,6 @@ func (s *Intention) prepareApplyCreate(
args.Intention.SourceType = structs.IntentionSourceConsul
}
args.Intention.DefaultNamespaces(entMeta)
if err := s.validateEnterpriseIntention(args.Intention); err != nil {
return nil, err
}
@ -117,74 +218,59 @@ func (s *Intention) prepareApplyCreate(
return nil, err
}
_, configEntry, err := s.srv.fsm.State().ConfigEntry(nil, structs.ServiceIntentions, args.Intention.DestinationName, args.Intention.DestinationEnterpriseMeta())
if err != nil {
return nil, fmt.Errorf("service-intentions config entry lookup failed: %v", err)
} else if configEntry == nil {
return nil, nil
// NOTE: if the append of this source causes a duplicate source name the
// config entry validation will fail so we don't have to check that
// explicitly here.
mut := &structs.IntentionMutation{
Destination: args.Intention.DestinationServiceName(),
Value: args.Intention.ToSourceIntention(true),
}
return configEntry.(*structs.ServiceIntentionsConfigEntry), nil
// Set the created/updated times. If this is an update instead of an insert
// the UpdateOver() will fix it up appropriately.
now := time.Now().UTC()
mut.Value.LegacyCreateTime = timePointer(now)
mut.Value.LegacyUpdateTime = timePointer(now)
return mut, nil
}
// prepareApplyUpdateLegacy validates that the requester has permissions on both the updated and existing
// intention as well as generally validating that the request is well-formed
//
// Returns an existing service-intentions config entry for this destination if
// one exists.
func (s *Intention) prepareApplyUpdateLegacy(
ident structs.ACLIdentity,
func (s *Intention) computeApplyChangesLegacyUpdate(
accessorID string,
authz acl.Authorizer,
entMeta *structs.EnterpriseMeta,
args *structs.IntentionRequest,
) (*structs.ServiceIntentionsConfigEntry, error) {
if !args.Intention.CanWrite(authz) {
var accessorID string
if ident != nil {
accessorID = ident.ID()
}
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
s.logger.Warn("Update operation on intention denied due to ACLs", "intention", args.Intention.ID, "accessorID", accessorID)
return nil, acl.ErrPermissionDenied
}
) (*structs.IntentionMutation, error) {
// This variant is just for legacy UUID-based intentions.
_, configEntry, ixn, err := s.srv.fsm.State().IntentionGet(nil, args.Intention.ID)
_, _, ixn, err := s.srv.fsm.State().IntentionGet(nil, args.Intention.ID)
if err != nil {
return nil, fmt.Errorf("Intention lookup failed: %v", err)
}
if ixn == nil || configEntry == nil {
if ixn == nil {
return nil, fmt.Errorf("Cannot modify non-existent intention: '%s'", args.Intention.ID)
}
// Perform the ACL check that we have write to the old intention too,
// which must be true to perform any rename. This is the only ACL enforcement
// done for deletions and a secondary enforcement for updates.
if !ixn.CanWrite(authz) {
var accessorID string
if ident != nil {
accessorID = ident.ID()
}
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
s.logger.Warn("Update operation on intention denied due to ACLs", "intention", args.Intention.ID, "accessorID", accessorID)
return nil, acl.ErrPermissionDenied
}
args.Intention.DefaultNamespaces(entMeta)
// Prior to v1.9.0 renames of the destination side of an intention were
// allowed, but that behavior doesn't work anymore.
if ixn.DestinationServiceName() != args.Intention.DestinationServiceName() {
return nil, fmt.Errorf("Cannot modify DestinationNS or DestinationName for an intention once it exists.")
}
// We always update the updatedat field.
args.Intention.UpdatedAt = time.Now().UTC()
// Default source type
if args.Intention.SourceType == "" {
args.Intention.SourceType = structs.IntentionSourceConsul
}
args.Intention.DefaultNamespaces(entMeta)
if err := s.validateEnterpriseIntention(args.Intention); err != nil {
return nil, err
}
@ -196,329 +282,149 @@ func (s *Intention) prepareApplyUpdateLegacy(
return nil, err
}
return configEntry, nil
mut := &structs.IntentionMutation{
ID: args.Intention.ID,
Value: args.Intention.ToSourceIntention(true),
}
// Set the created/updated times. If this is an update instead of an insert
// the UpdateOver() will fix it up appropriately.
now := time.Now().UTC()
mut.Value.LegacyCreateTime = timePointer(now)
mut.Value.LegacyUpdateTime = timePointer(now)
return mut, nil
}
// prepareApplyDeleteLegacy ensures that the intention specified by the ID in the request exists
// and that the requester is authorized to delete it
//
// Returns an existing service-intentions config entry for this destination if
// one exists.
func (s *Intention) prepareApplyDeleteLegacy(
ident structs.ACLIdentity,
func (s *Intention) computeApplyChangesUpsert(
accessorID string,
authz acl.Authorizer,
entMeta *structs.EnterpriseMeta,
args *structs.IntentionRequest,
) (*structs.ServiceIntentionsConfigEntry, error) {
// If this is not a create, then we have to verify the ID.
_, configEntry, ixn, err := s.srv.fsm.State().IntentionGet(nil, args.Intention.ID)
) (*structs.IntentionMutation, error) {
// This variant is just for config-entry based intentions.
if args.Intention.ID != "" {
// This is a new-style only endpoint
return nil, fmt.Errorf("ID must not be specified")
}
args.Intention.DefaultNamespaces(entMeta)
if !args.Intention.CanWrite(authz) {
sn := args.Intention.SourceServiceName()
dn := args.Intention.DestinationServiceName()
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
s.logger.Warn("Intention upsert denied due to ACLs",
"source", sn.String(),
"destination", dn.String(),
"accessorID", accessorID)
return nil, acl.ErrPermissionDenied
}
_, prevEntry, err := s.srv.fsm.State().ConfigEntry(nil, structs.ServiceIntentions, args.Intention.DestinationName, args.Intention.DestinationEnterpriseMeta())
if err != nil {
return nil, fmt.Errorf("Intention lookup failed: %v", err)
}
if ixn == nil || configEntry == nil {
if prevEntry == nil {
// Meta is NOT permitted here, as it would need to be persisted on
// the enclosing config entry.
if len(args.Intention.Meta) > 0 {
return nil, fmt.Errorf("Meta must not be specified")
}
} else {
if len(args.Intention.Meta) > 0 {
// Meta is NOT permitted here, but there is one exception. If
// you are updating a previous record, but that record lives
// within a config entry that itself has Meta, then you may
// incidentally ship the Meta right back to consul.
//
// In that case if Meta is provided, it has to be a perfect
// match for what is already on the enclosing config entry so
// it's safe to discard.
if !equalStringMaps(prevEntry.GetMeta(), args.Intention.Meta) {
return nil, fmt.Errorf("Meta must not be specified, or should be unchanged during an update.")
}
// Now it is safe to discard
args.Intention.Meta = nil
}
}
return &structs.IntentionMutation{
Destination: args.Intention.DestinationServiceName(),
Source: args.Intention.SourceServiceName(),
Value: args.Intention.ToSourceIntention(false),
}, nil
}
func (s *Intention) computeApplyChangesLegacyDelete(
accessorID string,
authz acl.Authorizer,
entMeta *structs.EnterpriseMeta,
args *structs.IntentionRequest,
) (*structs.IntentionMutation, error) {
_, _, ixn, err := s.srv.fsm.State().IntentionGet(nil, args.Intention.ID)
if err != nil {
return nil, fmt.Errorf("Intention lookup failed: %v", err)
}
if ixn == nil {
return nil, fmt.Errorf("Cannot delete non-existent intention: '%s'", args.Intention.ID)
}
// Perform the ACL check that we have write to the old intention. This is
// the only ACL enforcement done for deletions and a secondary enforcement
// for updates.
if !ixn.CanWrite(authz) {
var accessorID string
if ident != nil {
accessorID = ident.ID()
}
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
s.logger.Warn("Deletion operation on intention denied due to ACLs", "intention", args.Intention.ID, "accessorID", accessorID)
return nil, acl.ErrPermissionDenied
}
return configEntry, nil
return &structs.IntentionMutation{
ID: args.Intention.ID,
}, nil
}
var ErrIntentionsNotUpgradedYet = errors.New("Intentions are read only while being upgraded to config entries")
// legacyUpgradeCheck fast fails a write request using the legacy intention
// RPCs if the system is known to be mid-upgrade. This is purely a perf
// optimization and the actual real enforcement happens in the FSM. It would be
// wasteful to round trip all the way through raft to have it fail for
// known-up-front reasons, hence why we check it twice.
func (s *Intention) legacyUpgradeCheck() error {
usingConfigEntries, err := s.srv.fsm.State().AreIntentionsInConfigEntries()
if err != nil {
return fmt.Errorf("system metadata lookup failed: %v", err)
}
if !usingConfigEntries {
return ErrIntentionsNotUpgradedYet
}
return nil
}
// Apply creates or updates an intention in the data store.
func (s *Intention) Apply(
func (s *Intention) computeApplyChangesDelete(
accessorID string,
authz acl.Authorizer,
entMeta *structs.EnterpriseMeta,
args *structs.IntentionRequest,
reply *string) error {
// Ensure that all service-intentions config entry writes go to the primary
// datacenter. These will then be replicated to all the other datacenters.
args.Datacenter = s.srv.config.PrimaryDatacenter
if done, err := s.srv.ForwardRPC("Intention.Apply", args, args, reply); done {
return err
}
defer metrics.MeasureSince([]string{"consul", "intention", "apply"}, time.Now())
defer metrics.MeasureSince([]string{"intention", "apply"}, time.Now())
if err := s.legacyUpgradeCheck(); err != nil {
return err
}
// Always set a non-nil intention to avoid nil-access below
if args.Intention == nil {
args.Intention = &structs.Intention{}
}
// Get the ACL token for the request for the checks below.
var entMeta structs.EnterpriseMeta
ident, authz, err := s.srv.ResolveTokenIdentityAndDefaultMeta(args.Token, &entMeta, nil)
if err != nil {
return err
}
var (
prevEntry *structs.ServiceIntentionsConfigEntry
upsertEntry *structs.ServiceIntentionsConfigEntry
legacyWrite bool
noop bool
)
switch args.Op {
case structs.IntentionOpCreate:
legacyWrite = true
// This variant is just for legacy UUID-based intentions.
prevEntry, err = s.prepareApplyCreate(ident, authz, &entMeta, args)
if err != nil {
return err
}
if prevEntry == nil {
upsertEntry = args.Intention.ToConfigEntry(true)
} else {
upsertEntry = prevEntry.Clone()
upsertEntry.Sources = append(upsertEntry.Sources, args.Intention.ToSourceIntention(true))
}
case structs.IntentionOpUpdate:
// This variant is just for legacy UUID-based intentions.
legacyWrite = true
prevEntry, err = s.prepareApplyUpdateLegacy(ident, authz, &entMeta, args)
if err != nil {
return err
}
upsertEntry = prevEntry.Clone()
for i, src := range upsertEntry.Sources {
if src.LegacyID == args.Intention.ID {
upsertEntry.Sources[i] = args.Intention.ToSourceIntention(true)
break
}
}
case structs.IntentionOpUpsert:
// This variant is just for config-entry based intentions.
legacyWrite = false
if args.Intention.ID != "" {
// This is a new-style only endpoint
return fmt.Errorf("ID must not be specified")
}
args.Intention.DefaultNamespaces(&entMeta)
prevEntry, err = s.getServiceIntentionsConfigEntry(args.Intention.DestinationName, args.Intention.DestinationEnterpriseMeta())
if err != nil {
return err
}
) (*structs.IntentionMutation, error) {
args.Intention.DefaultNamespaces(entMeta)
if !args.Intention.CanWrite(authz) {
sn := args.Intention.SourceServiceName()
// TODO(intentions): have service-intentions validation functions
// return structured errors so that we can rewrite the field prefix
// here so that the validation errors are not misleading.
if prevEntry == nil {
// Meta is NOT permitted here, as it would need to be persisted on
// the enclosing config entry.
if len(args.Intention.Meta) > 0 {
return fmt.Errorf("Meta must not be specified")
}
upsertEntry = args.Intention.ToConfigEntry(false)
} else {
upsertEntry = prevEntry.Clone()
if len(args.Intention.Meta) > 0 {
// Meta is NOT permitted here, but there is one exception. If
// you are updating a previous record, but that record lives
// within a config entry that itself has Meta, then you may
// incidentally ship the Meta right back to consul.
//
// In that case if Meta is provided, it has to be a perfect
// match for what is already on the enclosing config entry so
// it's safe to discard.
if !equalStringMaps(upsertEntry.Meta, args.Intention.Meta) {
return fmt.Errorf("Meta must not be specified, or should be unchanged during an update.")
}
// Now it is safe to discard
args.Intention.Meta = nil
}
found := false
for i, src := range upsertEntry.Sources {
if src.SourceServiceName() == sn {
upsertEntry.Sources[i] = args.Intention.ToSourceIntention(false)
found = true
break
}
}
if !found {
upsertEntry.Sources = append(upsertEntry.Sources, args.Intention.ToSourceIntention(false))
}
}
case structs.IntentionOpDelete:
// There are two ways to get this request:
//
// 1) legacy: the ID field is populated
// 2) config-entry: the ID field is NOT populated
if args.Intention.ID == "" {
// config-entry style: no LegacyID
legacyWrite = false
args.Intention.DefaultNamespaces(&entMeta)
prevEntry, err = s.getServiceIntentionsConfigEntry(args.Intention.DestinationName, args.Intention.DestinationEnterpriseMeta())
if err != nil {
return err
}
// NOTE: validation errors may be misleading!
noop = true
if prevEntry != nil {
sn := args.Intention.SourceServiceName()
upsertEntry = prevEntry.Clone()
for i, src := range upsertEntry.Sources {
if src.SourceServiceName() == sn {
// Delete slice element: https://github.com/golang/go/wiki/SliceTricks#delete
// a = append(a[:i], a[i+1:]...)
upsertEntry.Sources = append(upsertEntry.Sources[:i], upsertEntry.Sources[i+1:]...)
if len(upsertEntry.Sources) == 0 {
upsertEntry.Sources = nil
}
noop = false
break
}
}
}
} else {
// legacy style: LegacyID required
legacyWrite = true
prevEntry, err = s.prepareApplyDeleteLegacy(ident, authz, args)
if err != nil {
return err
}
upsertEntry = prevEntry.Clone()
for i, src := range upsertEntry.Sources {
if src.LegacyID == args.Intention.ID {
// Delete slice element: https://github.com/golang/go/wiki/SliceTricks#delete
// a = append(a[:i], a[i+1:]...)
upsertEntry.Sources = append(upsertEntry.Sources[:i], upsertEntry.Sources[i+1:]...)
if len(upsertEntry.Sources) == 0 {
upsertEntry.Sources = nil
}
break
}
}
}
case structs.IntentionOpDeleteAll:
// This is an internal operation initiated by the leader and is not
// exposed for general RPC use.
fallthrough
default:
return fmt.Errorf("Invalid Intention operation: %v", args.Op)
dn := args.Intention.DestinationServiceName()
// todo(kit) Migrate intention access denial logging over to audit logging when we implement it
s.logger.Warn("Intention delete denied due to ACLs",
"source", sn.String(),
"destination", dn.String(),
"accessorID", accessorID)
return nil, acl.ErrPermissionDenied
}
if !noop && prevEntry != nil && legacyWrite && !prevEntry.LegacyIDFieldsAreAllSet() {
sn := prevEntry.DestinationServiceName()
return fmt.Errorf("cannot use legacy intention API to edit intentions with a destination of %q after editing them via a service-intentions config entry", sn.String())
// Pre-flight to avoid pointless raft operations.
_, _, ixn, err := s.srv.fsm.State().IntentionGetExact(nil, args.Intention.ToExact())
if err != nil {
return nil, fmt.Errorf("Intention lookup failed: %v", err)
}
if ixn == nil {
return nil, nil
}
// setup the reply which will have been filled in by one of the preparedApply* funcs
if legacyWrite {
*reply = args.Intention.ID
} else {
*reply = ""
}
if noop {
return nil
}
// Commit indirectly by invoking the other RPC handler directly.
configReq := &structs.ConfigEntryRequest{
Datacenter: args.Datacenter,
WriteRequest: args.WriteRequest,
}
if upsertEntry == nil || len(upsertEntry.Sources) == 0 {
configReq.Op = structs.ConfigEntryDelete
configReq.Entry = &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: prevEntry.Name,
EnterpriseMeta: prevEntry.EnterpriseMeta,
}
var ignored struct{}
return s.configEntryEndpoint.Delete(configReq, &ignored)
} else {
// Update config entry CAS
configReq.Op = structs.ConfigEntryUpsertCAS
configReq.Entry = upsertEntry
var normalizeAndValidateFn func(raw structs.ConfigEntry) error
if legacyWrite {
normalizeAndValidateFn = func(raw structs.ConfigEntry) error {
entry := raw.(*structs.ServiceIntentionsConfigEntry)
if err := entry.LegacyNormalize(); err != nil {
return err
}
return entry.LegacyValidate()
}
}
var applied bool
err := s.configEntryEndpoint.applyInternal(configReq, &applied, normalizeAndValidateFn)
if err != nil {
return err
}
if !applied {
return fmt.Errorf("config entry failed to persist due to CAS failure: kind=%q, name=%q", upsertEntry.Kind, upsertEntry.Name)
}
return nil
}
return &structs.IntentionMutation{
Destination: args.Intention.DestinationServiceName(),
Source: args.Intention.SourceServiceName(),
}, nil
}
// Get returns a single intention by ID.
func (s *Intention) Get(
args *structs.IntentionQueryRequest,
reply *structs.IndexedIntentions) error {
func (s *Intention) Get(args *structs.IntentionQueryRequest, reply *structs.IndexedIntentions) error {
// Exit early if Connect hasn't been enabled.
if !s.srv.config.ConnectEnabled {
return ErrConnectNotEnabled
}
// Forward if necessary
if done, err := s.srv.ForwardRPC("Intention.Get", args, args, reply); done {
return err
@ -591,9 +497,12 @@ func (s *Intention) Get(
}
// List returns all the intentions.
func (s *Intention) List(
args *structs.IntentionListRequest,
reply *structs.IndexedIntentions) error {
func (s *Intention) List(args *structs.IntentionListRequest, reply *structs.IndexedIntentions) error {
// Exit early if Connect hasn't been enabled.
if !s.srv.config.ConnectEnabled {
return ErrConnectNotEnabled
}
// Forward if necessary
if done, err := s.srv.ForwardRPC("Intention.List", args, args, reply); done {
return err
@ -658,9 +567,12 @@ func (s *Intention) List(
}
// Match returns the set of intentions that match the given source/destination.
func (s *Intention) Match(
args *structs.IntentionQueryRequest,
reply *structs.IndexedIntentionMatches) error {
func (s *Intention) Match(args *structs.IntentionQueryRequest, reply *structs.IndexedIntentionMatches) error {
// Exit early if Connect hasn't been enabled.
if !s.srv.config.ConnectEnabled {
return ErrConnectNotEnabled
}
// Forward if necessary
if done, err := s.srv.ForwardRPC("Intention.Match", args, args, reply); done {
return err
@ -729,9 +641,12 @@ func (s *Intention) Match(
// Note: Whenever the logic for this method is changed, you should take
// a look at the agent authorize endpoint (agent/agent_endpoint.go) since
// the logic there is similar.
func (s *Intention) Check(
args *structs.IntentionQueryRequest,
reply *structs.IntentionQueryCheckResponse) error {
func (s *Intention) Check(args *structs.IntentionQueryRequest, reply *structs.IntentionQueryCheckResponse) error {
// Exit early if Connect hasn't been enabled.
if !s.srv.config.ConnectEnabled {
return ErrConnectNotEnabled
}
// Forward maybe
if done, err := s.srv.ForwardRPC("Intention.Check", args, args, reply); done {
return err
@ -851,23 +766,6 @@ func (s *Intention) validateEnterpriseIntention(ixn *structs.Intention) error {
return nil
}
func (s *Intention) getServiceIntentionsConfigEntry(name string, entMeta *structs.EnterpriseMeta) (*structs.ServiceIntentionsConfigEntry, error) {
_, raw, err := s.srv.fsm.State().ConfigEntry(nil, structs.ServiceIntentions, name, entMeta)
if err != nil {
return nil, fmt.Errorf("Intention lookup failed: %v", err)
}
if raw == nil {
return nil, nil
}
configEntry, ok := raw.(*structs.ServiceIntentionsConfigEntry)
if !ok {
return nil, fmt.Errorf("invalid service config type %T", raw)
}
return configEntry, nil
}
func equalStringMaps(a, b map[string]string) bool {
if len(a) != len(b) {
return false

View File

@ -1033,18 +1033,32 @@ func (s *Server) bootstrapConfigEntries(entries []structs.ConfigEntry) error {
state := s.fsm.State()
// Do a quick preflight check to see if someone is trying to upgrade from
// an older pre-1.9.0 version of consul with intentions AND are trying to
// bootstrap a service-intentions config entry at the same time.
// Do some quick preflight checks to see if someone is doing something
// that's not allowed at this time:
//
// - Trying to upgrade from an older pre-1.9.0 version of consul with
// intentions AND are trying to bootstrap a service-intentions config entry
// at the same time.
//
// - Trying to insert service-intentions config entries when connect is
// disabled.
usingConfigEntries, err := s.fsm.State().AreIntentionsInConfigEntries()
if err != nil {
return fmt.Errorf("Failed to determine if we are migrating intentions yet: %v", err)
}
if !usingConfigEntries {
if !usingConfigEntries || !s.config.ConnectEnabled {
for _, entry := range entries {
if entry.GetKind() == structs.ServiceIntentions {
return fmt.Errorf("Refusing to apply configuration entry %q / %q because intentions are still being migrated to config entries: %v",
entry.GetKind(), entry.GetName(), err)
if !s.config.ConnectEnabled {
return fmt.Errorf("Refusing to apply configuration entry %q / %q because Connect must be enabled to bootstrap intentions",
entry.GetKind(), entry.GetName())
}
if !usingConfigEntries {
return fmt.Errorf("Refusing to apply configuration entry %q / %q because intentions are still being migrated to config entries",
entry.GetKind(), entry.GetName())
}
}
}
}

View File

@ -100,6 +100,11 @@ func (s *Server) legacyIntentionMigration(ctx context.Context) error {
return err
}
entries, err = s.filterMigratedLegacyIntentions(entries)
if err != nil {
return err
}
// Totally cheat and repurpose one part of config entry replication
// here so we automatically get our writes rate limited.
_, err = s.reconcileLocalConfig(ctx, entries, structs.ConfigEntryUpsert)

View File

@ -84,3 +84,7 @@ func migrateIntentionsToConfigEntries(ixns structs.Intentions) []*structs.Servic
return structs.MigrateIntentions(output)
}
func (s *Server) filterMigratedLegacyIntentions(entries []structs.ConfigEntry) ([]structs.ConfigEntry, error) {
return entries, nil
}

View File

@ -11,6 +11,13 @@ import (
"github.com/stretchr/testify/require"
)
func testLeader_LegacyIntentionMigrationHookEnterprise(_ *testing.T, _ *Server, _ bool) {
}
func appendLegacyIntentionsForMigrationTestEnterprise(_ *testing.T, _ *Server, ixns []*structs.Intention) []*structs.Intention {
return ixns
}
func TestMigrateIntentionsToConfigEntries(t *testing.T) {
compare := func(t *testing.T, got structs.Intentions, expect [][]string) {
t.Helper()

View File

@ -425,6 +425,11 @@ func TestLeader_LegacyIntentionMigration(t *testing.T) {
makeIxn("contractor", "*", false),
makeIxn("*", "*", true),
}
ixns = appendLegacyIntentionsForMigrationTestEnterprise(t, s1pre, ixns)
testLeader_LegacyIntentionMigrationHookEnterprise(t, s1pre, true)
var retained []*structs.Intention
for _, ixn := range ixns {
ixn2 := *ixn
resp, err := s1pre.raftApply(structs.IntentionRequestType, &structs.IntentionRequest{
@ -435,6 +440,10 @@ func TestLeader_LegacyIntentionMigration(t *testing.T) {
if respErr, ok := resp.(error); ok {
t.Fatalf("respErr: %v", respErr)
}
if _, present := ixn.Meta["unit-test-discarded"]; !present {
retained = append(retained, ixn)
}
}
mapify := func(ixns []*structs.Intention) map[string]*structs.Intention {
@ -465,7 +474,7 @@ func TestLeader_LegacyIntentionMigration(t *testing.T) {
for k, expectV := range expect {
gotV, ok := gotM[k]
if !ok {
r.Errorf("results are missing key %q", k)
r.Errorf("results are missing key %q: %v", k, expectV)
continue
}
@ -483,8 +492,14 @@ func TestLeader_LegacyIntentionMigration(t *testing.T) {
}
expectM := mapify(ixns)
checkIntentions(t, s1pre, false, expectM)
checkIntentions(t, s1pre, true, expectM)
expectRetainedM := mapify(retained)
require.True(t, t.Run("check initial intentions", func(t *testing.T) {
checkIntentions(t, s1pre, false, expectM)
}))
require.True(t, t.Run("check initial legacy intentions", func(t *testing.T) {
checkIntentions(t, s1pre, true, expectM)
}))
// Shutdown s1pre and restart it to trigger migration.
s1pre.Shutdown()
@ -500,8 +515,7 @@ func TestLeader_LegacyIntentionMigration(t *testing.T) {
testrpc.WaitForLeader(t, s1.RPC, "dc1")
// check that all 7 intentions are present before migration
checkIntentions(t, s1, false, expectM)
testLeader_LegacyIntentionMigrationHookEnterprise(t, s1, false)
// Wait until the migration routine is complete.
retry.Run(t, func(r *retry.R) {
@ -513,9 +527,13 @@ func TestLeader_LegacyIntentionMigration(t *testing.T) {
})
// check that all 7 intentions are present the general way after migration
checkIntentions(t, s1, false, expectM)
// check that no intentions exist in the legacy table
checkIntentions(t, s1, true, map[string]*structs.Intention{})
require.True(t, t.Run("check migrated intentions", func(t *testing.T) {
checkIntentions(t, s1, false, expectRetainedM)
}))
require.True(t, t.Run("check migrated legacy intentions", func(t *testing.T) {
// check that no intentions exist in the legacy table
checkIntentions(t, s1, true, map[string]*structs.Intention{})
}))
mapifyConfigs := func(entries interface{}) map[structs.ConfigEntryKindName]*structs.ServiceIntentionsConfigEntry {
m := make(map[structs.ConfigEntryKindName]*structs.ServiceIntentionsConfigEntry)
@ -541,7 +559,7 @@ func TestLeader_LegacyIntentionMigration(t *testing.T) {
require.NoError(t, err)
gotConfigsM := mapifyConfigs(gotConfigs)
expectConfigs := structs.MigrateIntentions(ixns)
expectConfigs := structs.MigrateIntentions(retained)
for _, entry := range expectConfigs {
require.NoError(t, entry.LegacyNormalize()) // tidy them up the same way the write would
}

View File

@ -12,8 +12,17 @@ import (
type LeaderRoutine func(ctx context.Context) error
type leaderRoutine struct {
running bool
cancel context.CancelFunc
cancel context.CancelFunc
stoppedCh chan struct{} // closed when no longer running
}
func (r *leaderRoutine) running() bool {
select {
case <-r.stoppedCh:
return false
default:
return true
}
}
type LeaderRoutineManager struct {
@ -41,7 +50,7 @@ func (m *LeaderRoutineManager) IsRunning(name string) bool {
defer m.lock.Unlock()
if routine, ok := m.routines[name]; ok {
return routine.running
return routine.running()
}
return false
@ -55,7 +64,7 @@ func (m *LeaderRoutineManager) StartWithContext(parentCtx context.Context, name
m.lock.Lock()
defer m.lock.Unlock()
if instance, ok := m.routines[name]; ok && instance.running {
if instance, ok := m.routines[name]; ok && instance.running() {
return nil
}
@ -65,11 +74,15 @@ func (m *LeaderRoutineManager) StartWithContext(parentCtx context.Context, name
ctx, cancel := context.WithCancel(parentCtx)
instance := &leaderRoutine{
running: true,
cancel: cancel,
cancel: cancel,
stoppedCh: make(chan struct{}),
}
go func() {
defer func() {
close(instance.stoppedCh)
}()
err := routine(ctx)
if err != nil && err != context.DeadlineExceeded && err != context.Canceled {
m.logger.Error("routine exited with error",
@ -79,10 +92,6 @@ func (m *LeaderRoutineManager) StartWithContext(parentCtx context.Context, name
} else {
m.logger.Debug("stopped routine", "routine", name)
}
m.lock.Lock()
instance.running = false
m.lock.Unlock()
}()
m.routines[name] = instance
@ -90,7 +99,19 @@ func (m *LeaderRoutineManager) StartWithContext(parentCtx context.Context, name
return nil
}
func (m *LeaderRoutineManager) Stop(name string) error {
func (m *LeaderRoutineManager) Stop(name string) <-chan struct{} {
instance := m.stopInstance(name)
if instance == nil {
// Fabricate a closed channel so it won't block forever.
ch := make(chan struct{})
close(ch)
return ch
}
return instance.stoppedCh
}
func (m *LeaderRoutineManager) stopInstance(name string) *leaderRoutine {
m.lock.Lock()
defer m.lock.Unlock()
@ -100,15 +121,16 @@ func (m *LeaderRoutineManager) Stop(name string) error {
return nil
}
if !instance.running {
return nil
if !instance.running() {
return instance
}
m.logger.Debug("stopping routine", "routine", name)
instance.cancel()
delete(m.routines, name)
return nil
return instance
}
func (m *LeaderRoutineManager) StopAll() {
@ -116,7 +138,7 @@ func (m *LeaderRoutineManager) StopAll() {
defer m.lock.Unlock()
for name, routine := range m.routines {
if !routine.running {
if !routine.running() {
continue
}
m.logger.Debug("stopping routine", "routine", name)

View File

@ -36,7 +36,9 @@ func TestLeaderRoutineManager(t *testing.T) {
require.Equal(r, uint32(1), atomic.LoadUint32(&runs))
require.Equal(r, uint32(1), atomic.LoadUint32(&running))
})
require.NoError(t, mgr.Stop("run"))
doneCh := mgr.Stop("run")
require.NotNil(t, doneCh)
<-doneCh
// ensure the background go routine was actually cancelled
retry.Run(t, func(r *retry.R) {
@ -51,7 +53,10 @@ func TestLeaderRoutineManager(t *testing.T) {
require.Equal(r, uint32(1), atomic.LoadUint32(&running))
})
require.NoError(t, mgr.Stop("run"))
doneCh = mgr.Stop("run")
require.NotNil(t, doneCh)
<-doneCh
retry.Run(t, func(r *retry.R) {
require.Equal(r, uint32(0), atomic.LoadUint32(&running))
})

View File

@ -1230,63 +1230,133 @@ func TestLeader_ConfigEntryBootstrap(t *testing.T) {
func TestLeader_ConfigEntryBootstrap_Fail(t *testing.T) {
t.Parallel()
pr, pw := io.Pipe()
defer pw.Close()
type testcase struct {
name string
entries []structs.ConfigEntry
serverCB func(c *Config)
expectMessage string
}
ch := make(chan string, 1)
go func() {
defer pr.Close()
scan := bufio.NewScanner(pr)
for scan.Scan() {
line := scan.Text()
if strings.Contains(line, "failed to establish leadership") {
ch <- ""
return
}
if strings.Contains(line, "successfully established leadership") {
ch <- "leadership should not have gotten here if config entries properly failed"
return
}
}
if scan.Err() != nil {
ch <- fmt.Sprintf("ERROR: %v", scan.Err())
} else {
ch <- "should not get here"
}
}()
_, config := testServerConfig(t)
config.Build = "1.6.0"
config.ConfigEntryBootstrap = []structs.ConfigEntry{
&structs.ServiceSplitterConfigEntry{
Kind: structs.ServiceSplitter,
Name: "web",
Splits: []structs.ServiceSplit{
{Weight: 100, Service: "web"},
cases := []testcase{
{
name: "service-splitter without L7 protocol",
entries: []structs.ConfigEntry{
&structs.ServiceSplitterConfigEntry{
Kind: structs.ServiceSplitter,
Name: "web",
Splits: []structs.ServiceSplit{
{Weight: 100, Service: "web"},
},
},
},
expectMessage: `Failed to apply configuration entry "service-splitter" / "web": discovery chain "web" uses a protocol "tcp" that does not permit advanced routing or splitting behavior"`,
},
{
name: "service-intentions without migration",
entries: []structs.ConfigEntry{
&structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "web",
Sources: []*structs.SourceIntention{
{
Name: "debug",
Action: structs.IntentionActionAllow,
},
},
},
},
serverCB: func(c *Config) {
c.OverrideInitialSerfTags = func(tags map[string]string) {
tags["ft_si"] = "0"
}
},
expectMessage: `Refusing to apply configuration entry "service-intentions" / "web" because intentions are still being migrated to config entries`,
},
{
name: "service-intentions without Connect",
entries: []structs.ConfigEntry{
&structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "web",
Sources: []*structs.SourceIntention{
{
Name: "debug",
Action: structs.IntentionActionAllow,
},
},
},
},
serverCB: func(c *Config) {
c.ConnectEnabled = false
},
expectMessage: `Refusing to apply configuration entry "service-intentions" / "web" because Connect must be enabled to bootstrap intentions"`,
},
}
logger := hclog.NewInterceptLogger(&hclog.LoggerOptions{
Name: config.NodeName,
Level: hclog.Debug,
Output: io.MultiWriter(pw, testutil.NewLogBuffer(t)),
})
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
pr, pw := io.Pipe()
defer pw.Close()
deps := newDefaultDeps(t, config)
deps.Logger = logger
var (
ch = make(chan string, 1)
applyErrorLine string
)
go func() {
defer pr.Close()
scan := bufio.NewScanner(pr)
for scan.Scan() {
line := scan.Text()
srv, err := NewServer(config, deps)
require.NoError(t, err)
defer srv.Shutdown()
if strings.Contains(line, "failed to establish leadership") {
applyErrorLine = line
ch <- ""
return
}
if strings.Contains(line, "successfully established leadership") {
ch <- "leadership should not have gotten here if config entries properly failed"
return
}
}
select {
case result := <-ch:
require.Empty(t, result)
case <-time.After(time.Second):
t.Fatal("timeout waiting for a result from tailing logs")
if scan.Err() != nil {
ch <- fmt.Sprintf("ERROR: %v", scan.Err())
} else {
ch <- "should not get here"
}
}()
_, config := testServerConfig(t)
config.Build = "1.6.0"
config.ConfigEntryBootstrap = tc.entries
if tc.serverCB != nil {
tc.serverCB(config)
}
logger := hclog.NewInterceptLogger(&hclog.LoggerOptions{
Name: config.NodeName,
Level: hclog.Debug,
Output: io.MultiWriter(pw, testutil.NewLogBuffer(t)),
})
deps := newDefaultDeps(t, config)
deps.Logger = logger
srv, err := NewServer(config, deps)
require.NoError(t, err)
defer srv.Shutdown()
select {
case result := <-ch:
require.Empty(t, result)
if tc.expectMessage != "" {
require.Contains(t, applyErrorLine, tc.expectMessage)
}
case <-time.After(time.Second):
t.Fatal("timeout waiting for a result from tailing logs")
}
})
}
}

View File

@ -11,7 +11,7 @@ func init() {
registerEndpoint(func(s *Server) interface{} { return &FederationState{s} })
registerEndpoint(func(s *Server) interface{} { return &DiscoveryChain{s} })
registerEndpoint(func(s *Server) interface{} { return &Health{s} })
registerEndpoint(func(s *Server) interface{} { return NewIntentionEndpoint(s, s.loggers.Named(logging.Intentions)) })
registerEndpoint(func(s *Server) interface{} { return &Intention{s, s.loggers.Named(logging.Intentions)} })
registerEndpoint(func(s *Server) interface{} { return &Internal{s, s.loggers.Named(logging.Internal)} })
registerEndpoint(func(s *Server) interface{} { return &KVS{s, s.loggers.Named(logging.KV)} })
registerEndpoint(func(s *Server) interface{} { return &Operator{s, s.loggers.Named(logging.Operator)} })

View File

@ -925,7 +925,7 @@ func TestSession_Apply_BadTTL(t *testing.T) {
if err == nil {
t.Fatal("expected error")
}
if err.Error() != "Session TTL '10z' invalid: time: unknown unit z in duration 10z" {
if err.Error() != `Session TTL '10z' invalid: time: unknown unit "z" in duration "10z"` {
t.Fatalf("incorrect error message: %s", err.Error())
}

View File

@ -263,7 +263,7 @@ func (s *Snapshot) Checks(node string) (memdb.ResultIterator, error) {
// performed within a single transaction to avoid race conditions on state
// updates.
func (s *Restore) Registration(idx uint64, req *structs.RegisterRequest) error {
return s.store.ensureRegistrationTxn(s.tx, idx, true, req)
return s.store.ensureRegistrationTxn(s.tx, idx, true, req, true)
}
// EnsureRegistration is used to make sure a node, service, and check
@ -273,7 +273,7 @@ func (s *Store) EnsureRegistration(idx uint64, req *structs.RegisterRequest) err
tx := s.db.WriteTxn(idx)
defer tx.Abort()
if err := s.ensureRegistrationTxn(tx, idx, false, req); err != nil {
if err := s.ensureRegistrationTxn(tx, idx, false, req, false); err != nil {
return err
}
@ -294,8 +294,8 @@ func (s *Store) ensureCheckIfNodeMatches(tx WriteTxn, idx uint64, preserveIndexe
// ensureRegistrationTxn is used to make sure a node, service, and check
// registration is performed within a single transaction to avoid race
// conditions on state updates.
func (s *Store) ensureRegistrationTxn(tx WriteTxn, idx uint64, preserveIndexes bool, req *structs.RegisterRequest) error {
if _, err := validateRegisterRequestTxn(tx, req); err != nil {
func (s *Store) ensureRegistrationTxn(tx WriteTxn, idx uint64, preserveIndexes bool, req *structs.RegisterRequest, restore bool) error {
if _, err := validateRegisterRequestTxn(tx, req, restore); err != nil {
return err
}

View File

@ -40,7 +40,7 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Name: "service reg, new node",
Mutate: func(s *Store, tx *txn) error {
return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web"))
testServiceRegistration(t, "web"), false)
},
WantEvents: []stream.Event{
testServiceHealthEvent(t, "web"),
@ -51,11 +51,11 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Name: "service reg, existing node",
Setup: func(s *Store, tx *txn) error {
return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db"))
testServiceRegistration(t, "db"), false)
},
Mutate: func(s *Store, tx *txn) error {
return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web"))
testServiceRegistration(t, "web"), false)
},
WantEvents: []stream.Event{
// Should only publish new service
@ -67,11 +67,11 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Name: "service dereg, existing node",
Setup: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil {
testServiceRegistration(t, "db"), false); err != nil {
return err
}
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil {
testServiceRegistration(t, "web"), false); err != nil {
return err
}
return nil
@ -88,10 +88,10 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
{
Name: "node dereg",
Setup: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "db")); err != nil {
if err := s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "db"), false); err != nil {
return err
}
if err := s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web")); err != nil {
if err := s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web"), false); err != nil {
return err
}
return nil
@ -109,7 +109,7 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
{
Name: "connect native reg, new node",
Mutate: func(s *Store, tx *txn) error {
return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web", regConnectNative))
return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web", regConnectNative), false)
},
WantEvents: []stream.Event{
// We should see both a regular service health event as well as a connect
@ -122,10 +122,10 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
{
Name: "connect native reg, existing node",
Setup: func(s *Store, tx *txn) error {
return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "db"))
return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "db"), false)
},
Mutate: func(s *Store, tx *txn) error {
return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web", regConnectNative))
return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web", regConnectNative), false)
},
WantEvents: []stream.Event{
// We should see both a regular service health event as well as a connect
@ -143,11 +143,11 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
{
Name: "connect native dereg, existing node",
Setup: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "db")); err != nil {
if err := s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "db"), false); err != nil {
return err
}
return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web", regConnectNative))
return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web", regConnectNative), false)
},
Mutate: func(s *Store, tx *txn) error {
return s.deleteServiceTxn(tx, tx.Index, "node1", "web", nil)
@ -162,10 +162,10 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
{
Name: "connect sidecar reg, new node",
Mutate: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web")); err != nil {
if err := s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web"), false); err != nil {
return err
}
return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web", regSidecar))
return s.ensureRegistrationTxn(tx, tx.Index, false, testServiceRegistration(t, "web", regSidecar), false)
},
WantEvents: []stream.Event{
// We should see both a regular service health event for the web service
@ -180,15 +180,15 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Name: "connect sidecar reg, existing node",
Setup: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil {
testServiceRegistration(t, "db"), false); err != nil {
return err
}
return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web"))
testServiceRegistration(t, "web"), false)
},
Mutate: func(s *Store, tx *txn) error {
return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar))
testServiceRegistration(t, "web", regSidecar), false)
},
WantEvents: []stream.Event{
// We should see both a regular service health event for the proxy
@ -202,15 +202,15 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Name: "connect sidecar dereg, existing node",
Setup: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil {
testServiceRegistration(t, "db"), false); err != nil {
return err
}
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil {
testServiceRegistration(t, "web"), false); err != nil {
return err
}
return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar))
testServiceRegistration(t, "web", regSidecar), false)
},
Mutate: func(s *Store, tx *txn) error {
// Delete only the sidecar
@ -227,20 +227,20 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Name: "connect sidecar mutate svc",
Setup: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil {
testServiceRegistration(t, "db"), false); err != nil {
return err
}
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil {
testServiceRegistration(t, "web"), false); err != nil {
return err
}
return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar))
testServiceRegistration(t, "web", regSidecar), false)
},
Mutate: func(s *Store, tx *txn) error {
// Change port of the target service instance
return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regMutatePort))
testServiceRegistration(t, "web", regMutatePort), false)
},
WantEvents: []stream.Event{
// We should see the service topic update but not connect since proxy
@ -258,20 +258,20 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Name: "connect sidecar mutate sidecar",
Setup: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil {
testServiceRegistration(t, "db"), false); err != nil {
return err
}
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil {
testServiceRegistration(t, "web"), false); err != nil {
return err
}
return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar))
testServiceRegistration(t, "web", regSidecar), false)
},
Mutate: func(s *Store, tx *txn) error {
// Change port of the sidecar service instance
return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar, regMutatePort))
testServiceRegistration(t, "web", regSidecar, regMutatePort), false)
},
WantEvents: []stream.Event{
// We should see the proxy service topic update and a connect update
@ -295,24 +295,24 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Name: "connect sidecar rename service",
Setup: func(s *Store, tx *txn) error {
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil {
testServiceRegistration(t, "db"), false); err != nil {
return err
}
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil {
testServiceRegistration(t, "web"), false); err != nil {
return err
}
return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar))
testServiceRegistration(t, "web", regSidecar), false)
},
Mutate: func(s *Store, tx *txn) error {
// Change service name but not ID, update proxy too
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regRenameService)); err != nil {
testServiceRegistration(t, "web", regRenameService), false); err != nil {
return err
}
return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar, regRenameService))
testServiceRegistration(t, "web", regSidecar, regRenameService), false)
},
WantEvents: []stream.Event{
// We should see events to deregister the old service instance and the
@ -353,25 +353,25 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Setup: func(s *Store, tx *txn) error {
// Register a web_changed service
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web_changed")); err != nil {
testServiceRegistration(t, "web_changed"), false); err != nil {
return err
}
// Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil {
testServiceRegistration(t, "web"), false); err != nil {
return err
}
// And a sidecar initially for web, will be moved to target web_changed
// in Mutate.
return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar))
testServiceRegistration(t, "web", regSidecar), false)
},
Mutate: func(s *Store, tx *txn) error {
// Change only the destination service of the proxy without a service
// rename or deleting and recreating the proxy. This is far fetched but
// still valid.
return s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar, regRenameService))
testServiceRegistration(t, "web", regSidecar, regRenameService), false)
},
WantEvents: []stream.Event{
// We should only see service health events for the sidecar service
@ -405,17 +405,17 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Setup: func(s *Store, tx *txn) error {
// Register a db service
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil {
testServiceRegistration(t, "db"), false); err != nil {
return err
}
// Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil {
testServiceRegistration(t, "web"), false); err != nil {
return err
}
// With a connect sidecar
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)); err != nil {
testServiceRegistration(t, "web", regSidecar), false); err != nil {
return err
}
return nil
@ -423,7 +423,7 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Mutate: func(s *Store, tx *txn) error {
// Change only the node meta.
return s.ensureRegistrationTxn(tx, tx.Index, false,
testNodeRegistration(t, regNodeMeta))
testNodeRegistration(t, regNodeMeta), false)
},
WantEvents: []stream.Event{
// We should see updates for all services and a connect update for the
@ -463,17 +463,17 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Setup: func(s *Store, tx *txn) error {
// Register a db service
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil {
testServiceRegistration(t, "db"), false); err != nil {
return err
}
// Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil {
testServiceRegistration(t, "web"), false); err != nil {
return err
}
// With a connect sidecar
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)); err != nil {
testServiceRegistration(t, "web", regSidecar), false); err != nil {
return err
}
return nil
@ -485,17 +485,17 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
// services registered afterwards.
// Register a db service
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db", regRenameNode)); err != nil {
testServiceRegistration(t, "db", regRenameNode), false); err != nil {
return err
}
// Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regRenameNode)); err != nil {
testServiceRegistration(t, "web", regRenameNode), false); err != nil {
return err
}
// With a connect sidecar
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar, regRenameNode)); err != nil {
testServiceRegistration(t, "web", regSidecar, regRenameNode), false); err != nil {
return err
}
return nil
@ -540,17 +540,17 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Setup: func(s *Store, tx *txn) error {
// Register a db service
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil {
testServiceRegistration(t, "db"), false); err != nil {
return err
}
// Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil {
testServiceRegistration(t, "web"), false); err != nil {
return err
}
// With a connect sidecar
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)); err != nil {
testServiceRegistration(t, "web", regSidecar), false); err != nil {
return err
}
return nil
@ -558,7 +558,7 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Mutate: func(s *Store, tx *txn) error {
// Change only the node-level check status
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regNodeCheckFail)); err != nil {
testServiceRegistration(t, "web", regNodeCheckFail), false); err != nil {
return err
}
return nil
@ -600,17 +600,17 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Setup: func(s *Store, tx *txn) error {
// Register a db service
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil {
testServiceRegistration(t, "db"), false); err != nil {
return err
}
// Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil {
testServiceRegistration(t, "web"), false); err != nil {
return err
}
// With a connect sidecar
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)); err != nil {
testServiceRegistration(t, "web", regSidecar), false); err != nil {
return err
}
return nil
@ -618,7 +618,7 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Mutate: func(s *Store, tx *txn) error {
// Change the service-level check status
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regServiceCheckFail)); err != nil {
testServiceRegistration(t, "web", regServiceCheckFail), false); err != nil {
return err
}
// Also change the service-level check status for the proxy. This is
@ -626,7 +626,7 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
// - the proxies check would get updated at roughly the same time as the
// target service check updates.
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar, regServiceCheckFail)); err != nil {
testServiceRegistration(t, "web", regSidecar, regServiceCheckFail), false); err != nil {
return err
}
return nil
@ -663,17 +663,17 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Setup: func(s *Store, tx *txn) error {
// Register a db service
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil {
testServiceRegistration(t, "db"), false); err != nil {
return err
}
// Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil {
testServiceRegistration(t, "web"), false); err != nil {
return err
}
// With a connect sidecar
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)); err != nil {
testServiceRegistration(t, "web", regSidecar), false); err != nil {
return err
}
return nil
@ -717,17 +717,17 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
Setup: func(s *Store, tx *txn) error {
// Register a db service
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil {
testServiceRegistration(t, "db"), false); err != nil {
return err
}
// Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil {
testServiceRegistration(t, "web"), false); err != nil {
return err
}
// With a connect sidecar
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)); err != nil {
testServiceRegistration(t, "web", regSidecar), false); err != nil {
return err
}
return nil
@ -777,19 +777,19 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
// Register a db service
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "db")); err != nil {
testServiceRegistration(t, "db"), false); err != nil {
return err
}
// Node2
// Also a web
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regNode2)); err != nil {
testServiceRegistration(t, "web", regNode2), false); err != nil {
return err
}
// With a connect sidecar
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar, regNode2)); err != nil {
testServiceRegistration(t, "web", regSidecar, regNode2), false); err != nil {
return err
}
@ -808,17 +808,17 @@ func TestServiceHealthEventsFromChanges(t *testing.T) {
// Register those on node1
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web")); err != nil {
testServiceRegistration(t, "web"), false); err != nil {
return err
}
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "web", regSidecar)); err != nil {
testServiceRegistration(t, "web", regSidecar), false); err != nil {
return err
}
// And for good measure, add a new connect-native service to node2
if err := s.ensureRegistrationTxn(tx, tx.Index, false,
testServiceRegistration(t, "api", regConnectNative, regNode2)); err != nil {
testServiceRegistration(t, "api", regConnectNative, regNode2), false); err != nil {
return err
}

View File

@ -323,7 +323,7 @@ func catalogChecksForNodeService(tx ReadTxn, node string, service string, entMet
return tx.Get("checks", "node_service", node, service)
}
func validateRegisterRequestTxn(_ ReadTxn, _ *structs.RegisterRequest) (*structs.EnterpriseMeta, error) {
func validateRegisterRequestTxn(_ ReadTxn, _ *structs.RegisterRequest, _ bool) (*structs.EnterpriseMeta, error) {
return nil, nil
}

View File

@ -180,7 +180,7 @@ func (s *Store) EnsureConfigEntry(idx uint64, conf structs.ConfigEntry, entMeta
}
// ensureConfigEntryTxn upserts a config entry inside of a transaction.
func ensureConfigEntryTxn(tx *txn, idx uint64, conf structs.ConfigEntry, entMeta *structs.EnterpriseMeta) error {
func ensureConfigEntryTxn(tx WriteTxn, idx uint64, conf structs.ConfigEntry, entMeta *structs.EnterpriseMeta) error {
// Check for existing configuration.
existing, err := firstConfigEntryWithTxn(tx, conf.GetKind(), conf.GetName(), entMeta)
if err != nil {
@ -254,6 +254,14 @@ func (s *Store) DeleteConfigEntry(idx uint64, kind, name string, entMeta *struct
tx := s.db.WriteTxn(idx)
defer tx.Abort()
if err := deleteConfigEntryTxn(tx, idx, kind, name, entMeta); err != nil {
return err
}
return tx.Commit()
}
func deleteConfigEntryTxn(tx WriteTxn, idx uint64, kind, name string, entMeta *structs.EnterpriseMeta) error {
// Try to retrieve the existing config entry.
existing, err := firstConfigEntryWithTxn(tx, kind, name, entMeta)
if err != nil {
@ -298,10 +306,10 @@ func (s *Store) DeleteConfigEntry(idx uint64, kind, name string, entMeta *struct
return fmt.Errorf("failed updating index: %s", err)
}
return tx.Commit()
return nil
}
func insertConfigEntryWithTxn(tx *txn, idx uint64, conf structs.ConfigEntry) error {
func insertConfigEntryWithTxn(tx WriteTxn, idx uint64, conf structs.ConfigEntry) error {
if conf == nil {
return fmt.Errorf("cannot insert nil config entry")
}

View File

@ -207,6 +207,294 @@ func (s *Store) legacyIntentionsListTxn(tx ReadTxn, ws memdb.WatchSet, entMeta *
var ErrLegacyIntentionsAreDisabled = errors.New("Legacy intention modifications are disabled after the config entry migration.")
func (s *Store) IntentionMutation(idx uint64, op structs.IntentionOp, mut *structs.IntentionMutation) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
usingConfigEntries, err := areIntentionsInConfigEntries(tx, nil)
if err != nil {
return err
}
if !usingConfigEntries {
return errors.New("state: IntentionMutation() is not allowed when intentions are not stored in config entries")
}
switch op {
case structs.IntentionOpCreate:
if err := s.intentionMutationLegacyCreate(tx, idx, mut.Destination, mut.Value); err != nil {
return err
}
case structs.IntentionOpUpdate:
if err := s.intentionMutationLegacyUpdate(tx, idx, mut.ID, mut.Value); err != nil {
return err
}
case structs.IntentionOpDelete:
if mut.ID == "" {
if err := s.intentionMutationDelete(tx, idx, mut.Destination, mut.Source); err != nil {
return err
}
} else {
if err := s.intentionMutationLegacyDelete(tx, idx, mut.ID); err != nil {
return err
}
}
case structs.IntentionOpUpsert:
if err := s.intentionMutationUpsert(tx, idx, mut.Destination, mut.Source, mut.Value); err != nil {
return err
}
case structs.IntentionOpDeleteAll:
// This is an internal operation initiated by the leader and is not
// exposed for general RPC use.
return fmt.Errorf("Invalid Intention mutation operation '%s'", op)
default:
return fmt.Errorf("Invalid Intention mutation operation '%s'", op)
}
return tx.Commit()
}
func (s *Store) intentionMutationLegacyCreate(
tx WriteTxn,
idx uint64,
dest structs.ServiceName,
value *structs.SourceIntention,
) error {
_, configEntry, err := configEntryTxn(tx, nil, structs.ServiceIntentions, dest.Name, &dest.EnterpriseMeta)
if err != nil {
return fmt.Errorf("service-intentions config entry lookup failed: %v", err)
}
var upsertEntry *structs.ServiceIntentionsConfigEntry
if configEntry == nil {
upsertEntry = &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: dest.Name,
EnterpriseMeta: dest.EnterpriseMeta,
Sources: []*structs.SourceIntention{value},
}
} else {
prevEntry := configEntry.(*structs.ServiceIntentionsConfigEntry)
if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
return err
}
upsertEntry = prevEntry.Clone()
upsertEntry.Sources = append(upsertEntry.Sources, value)
}
if err := upsertEntry.LegacyNormalize(); err != nil {
return err
}
if err := upsertEntry.LegacyValidate(); err != nil {
return err
}
if err := ensureConfigEntryTxn(tx, idx, upsertEntry, upsertEntry.GetEnterpriseMeta()); err != nil {
return err
}
return nil
}
func (s *Store) intentionMutationLegacyUpdate(
tx WriteTxn,
idx uint64,
legacyID string,
value *structs.SourceIntention,
) error {
// This variant is just for legacy UUID-based intentions.
_, prevEntry, ixn, err := s.IntentionGet(nil, legacyID)
if err != nil {
return fmt.Errorf("Intention lookup failed: %v", err)
}
if ixn == nil || prevEntry == nil {
return fmt.Errorf("Cannot modify non-existent intention: '%s'", legacyID)
}
if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
return err
}
upsertEntry := prevEntry.Clone()
foundMatch := upsertEntry.UpdateSourceByLegacyID(
legacyID,
value,
)
if !foundMatch {
return fmt.Errorf("Cannot modify non-existent intention: '%s'", legacyID)
}
if err := upsertEntry.LegacyNormalize(); err != nil {
return err
}
if err := upsertEntry.LegacyValidate(); err != nil {
return err
}
if err := ensureConfigEntryTxn(tx, idx, upsertEntry, upsertEntry.GetEnterpriseMeta()); err != nil {
return err
}
return nil
}
func (s *Store) intentionMutationDelete(
tx WriteTxn,
idx uint64,
dest structs.ServiceName,
src structs.ServiceName,
) error {
_, configEntry, err := configEntryTxn(tx, nil, structs.ServiceIntentions, dest.Name, &dest.EnterpriseMeta)
if err != nil {
return fmt.Errorf("service-intentions config entry lookup failed: %v", err)
}
if configEntry == nil {
return nil
}
prevEntry := configEntry.(*structs.ServiceIntentionsConfigEntry)
upsertEntry := prevEntry.Clone()
deleted := upsertEntry.DeleteSourceByName(src)
if !deleted {
return nil
}
if upsertEntry == nil || len(upsertEntry.Sources) == 0 {
return deleteConfigEntryTxn(
tx,
idx,
structs.ServiceIntentions,
dest.Name,
&dest.EnterpriseMeta,
)
}
if err := upsertEntry.Normalize(); err != nil {
return err
}
if err := upsertEntry.Validate(); err != nil {
return err
}
if err := ensureConfigEntryTxn(tx, idx, upsertEntry, upsertEntry.GetEnterpriseMeta()); err != nil {
return err
}
return nil
}
func (s *Store) intentionMutationLegacyDelete(
tx WriteTxn,
idx uint64,
legacyID string,
) error {
_, prevEntry, ixn, err := s.IntentionGet(nil, legacyID)
if err != nil {
return fmt.Errorf("Intention lookup failed: %v", err)
}
if ixn == nil || prevEntry == nil {
return fmt.Errorf("Cannot delete non-existent intention: '%s'", legacyID)
}
if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
return err
}
upsertEntry := prevEntry.Clone()
deleted := upsertEntry.DeleteSourceByLegacyID(legacyID)
if !deleted {
return fmt.Errorf("Cannot delete non-existent intention: '%s'", legacyID)
}
if upsertEntry == nil || len(upsertEntry.Sources) == 0 {
return deleteConfigEntryTxn(
tx,
idx,
structs.ServiceIntentions,
prevEntry.Name,
&prevEntry.EnterpriseMeta,
)
}
if err := upsertEntry.LegacyNormalize(); err != nil {
return err
}
if err := upsertEntry.LegacyValidate(); err != nil {
return err
}
if err := ensureConfigEntryTxn(tx, idx, upsertEntry, upsertEntry.GetEnterpriseMeta()); err != nil {
return err
}
return nil
}
func (s *Store) intentionMutationUpsert(
tx WriteTxn,
idx uint64,
dest structs.ServiceName,
src structs.ServiceName,
value *structs.SourceIntention,
) error {
// This variant is just for config-entry based intentions.
_, configEntry, err := configEntryTxn(tx, nil, structs.ServiceIntentions, dest.Name, &dest.EnterpriseMeta)
if err != nil {
return fmt.Errorf("service-intentions config entry lookup failed: %v", err)
}
var prevEntry *structs.ServiceIntentionsConfigEntry
if configEntry != nil {
prevEntry = configEntry.(*structs.ServiceIntentionsConfigEntry)
}
var upsertEntry *structs.ServiceIntentionsConfigEntry
if prevEntry == nil {
upsertEntry = &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: dest.Name,
EnterpriseMeta: dest.EnterpriseMeta,
Sources: []*structs.SourceIntention{value},
}
} else {
upsertEntry = prevEntry.Clone()
upsertEntry.UpsertSourceByName(src, value)
}
if err := upsertEntry.Normalize(); err != nil {
return err
}
if err := upsertEntry.Validate(); err != nil {
return err
}
if err := ensureConfigEntryTxn(tx, idx, upsertEntry, upsertEntry.GetEnterpriseMeta()); err != nil {
return err
}
return nil
}
func checkLegacyIntentionApplyAllowed(prevEntry *structs.ServiceIntentionsConfigEntry) error {
if prevEntry == nil {
return nil
}
if prevEntry.LegacyIDFieldsAreAllSet() {
return nil
}
sn := prevEntry.DestinationServiceName()
return fmt.Errorf("cannot use legacy intention API to edit intentions with a destination of %q after editing them via a service-intentions config entry", sn.String())
}
// LegacyIntentionSet creates or updates an intention.
//
// Deprecated: Edit service-intentions config entries directly.

View File

@ -13,6 +13,14 @@ import (
"github.com/stretchr/testify/require"
)
var (
testLocation = time.FixedZone("UTC-8", -8*60*60)
testTimeA = time.Date(1955, 11, 5, 6, 15, 0, 0, testLocation)
testTimeB = time.Date(1985, 10, 26, 1, 35, 0, 0, testLocation)
testTimeC = time.Date(2015, 10, 21, 16, 29, 0, 0, testLocation)
)
func testBothIntentionFormats(t *testing.T, f func(t *testing.T, s *Store, legacy bool)) {
t.Helper()
@ -72,6 +80,8 @@ func TestStore_IntentionSetGet_basic(t *testing.T) {
DestinationNS: "default",
DestinationName: "web",
Meta: map[string]string{},
CreatedAt: testTimeA,
UpdatedAt: testTimeA,
}
// Inserting a with empty ID is disallowed.
@ -89,6 +99,8 @@ func TestStore_IntentionSetGet_basic(t *testing.T) {
DestinationNS: "default",
DestinationName: "web",
Meta: map[string]string{},
CreatedAt: testTimeA,
UpdatedAt: testTimeA,
RaftIndex: structs.RaftIndex{
CreateIndex: lastIndex,
ModifyIndex: lastIndex,
@ -103,10 +115,12 @@ func TestStore_IntentionSetGet_basic(t *testing.T) {
Name: "web",
Sources: []*structs.SourceIntention{
{
LegacyID: srcID,
Name: "*",
Action: structs.IntentionActionAllow,
LegacyMeta: map[string]string{},
LegacyID: srcID,
Name: "*",
Action: structs.IntentionActionAllow,
LegacyMeta: map[string]string{},
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
},
},
}
@ -258,6 +272,534 @@ func TestStore_LegacyIntentionDelete_failsAfterUpgrade(t *testing.T) {
testutil.RequireErrorContains(t, err, ErrLegacyIntentionsAreDisabled.Error())
}
func TestStore_IntentionMutation(t *testing.T) {
testBothIntentionFormats(t, func(t *testing.T, s *Store, legacy bool) {
if legacy {
mut := &structs.IntentionMutation{}
err := s.IntentionMutation(1, structs.IntentionOpCreate, mut)
testutil.RequireErrorContains(t, err, "state: IntentionMutation() is not allowed when intentions are not stored in config entries")
} else {
testStore_IntentionMutation(t, s)
}
})
}
func testStore_IntentionMutation(t *testing.T, s *Store) {
lastIndex := uint64(1)
defaultEntMeta := structs.DefaultEnterpriseMeta()
var (
id1 = testUUID()
id2 = testUUID()
id3 = testUUID()
)
eqEntry := func(t *testing.T, expect, got *structs.ServiceIntentionsConfigEntry) {
t.Helper()
// Zero out some fields for comparison.
got = got.Clone()
got.RaftIndex = structs.RaftIndex{}
for _, src := range got.Sources {
src.LegacyCreateTime = nil
src.LegacyUpdateTime = nil
if len(src.LegacyMeta) == 0 {
src.LegacyMeta = nil
}
}
require.Equal(t, expect, got)
}
// Try to create an intention without an ID to prove that LegacyValidate is being called.
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpCreate, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
},
}), `Sources[0].LegacyID must be set`)
// Create intention and create config entry
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpCreate, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
LegacyID: id1,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
},
}))
// Ensure it's there now.
idx, entry, ixn, err := s.IntentionGet(nil, id1)
require.NoError(t, err)
require.NotNil(t, entry)
require.NotNil(t, ixn)
require.Equal(t, lastIndex, idx)
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "api",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
LegacyID: id1,
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
Precedence: 9,
Type: structs.IntentionSourceConsul,
},
},
}, entry)
// only one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Len(t, entries, 1)
lastIndex++
}
// Try to create a duplicate intention.
{
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpCreate, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
LegacyID: id2,
LegacyCreateTime: &testTimeB,
LegacyUpdateTime: &testTimeB,
},
}), `more than once`)
}
// Create intention with existing config entry
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpCreate, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "debug",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
LegacyID: id2,
LegacyCreateTime: &testTimeB,
LegacyUpdateTime: &testTimeB,
},
}))
// Ensure it's there now.
idx, entry, ixn, err := s.IntentionGet(nil, id2)
require.NoError(t, err)
require.NotNil(t, entry)
require.NotNil(t, ixn)
require.Equal(t, lastIndex, idx)
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "api",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
LegacyID: id1,
Precedence: 9,
Type: structs.IntentionSourceConsul,
},
{
Name: "debug",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
LegacyID: id2,
Precedence: 9,
Type: structs.IntentionSourceConsul,
},
},
}, entry)
// only one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Len(t, entries, 1)
lastIndex++
}
// Try to update an intention without specifying an ID
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpdate, &structs.IntentionMutation{
ID: "",
Destination: structs.NewServiceName("api", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
},
}), `failed config entry lookup: index error: UUID must be 36 characters`)
// Try to update a non-existent intention
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpdate, &structs.IntentionMutation{
ID: id3,
Destination: structs.NewServiceName("api", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
},
}), `Cannot modify non-existent intention`)
// Update an existing intention by ID
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpdate, &structs.IntentionMutation{
ID: id2,
Destination: structs.NewServiceName("api", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "debug",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
LegacyID: id2,
LegacyCreateTime: &testTimeB,
LegacyUpdateTime: &testTimeC,
Description: "op update",
},
}))
// Ensure it's there now.
idx, entry, ixn, err := s.IntentionGet(nil, id2)
require.NoError(t, err)
require.NotNil(t, entry)
require.NotNil(t, ixn)
require.Equal(t, lastIndex, idx)
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "api",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
LegacyID: id1,
Precedence: 9,
Type: structs.IntentionSourceConsul,
},
{
Name: "debug",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
LegacyID: id2,
Precedence: 9,
Type: structs.IntentionSourceConsul,
Description: "op update",
},
},
}, entry)
// only one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Len(t, entries, 1)
lastIndex++
}
// Try to delete a non-existent intention
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
ID: id3,
}), `Cannot delete non-existent intention`)
// delete by id
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
ID: id1,
}))
// only one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Len(t, entries, 1)
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "api",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "debug",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
LegacyID: id2,
Precedence: 9,
Type: structs.IntentionSourceConsul,
Description: "op update",
},
},
}, entries[0].(*structs.ServiceIntentionsConfigEntry))
lastIndex++
}
// delete last one by id
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
ID: id2,
}))
// none one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Empty(t, entries)
lastIndex++
}
// upsert intention for first time
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpsert, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
},
}))
// Ensure it's there now.
idx, entry, ixn, err := s.IntentionGetExact(nil, &structs.IntentionQueryExact{
SourceNS: "default",
SourceName: "web",
DestinationNS: "default",
DestinationName: "api",
})
require.NoError(t, err)
require.NotNil(t, entry)
require.NotNil(t, ixn)
require.Equal(t, lastIndex, idx)
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "api",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
Precedence: 9,
Type: structs.IntentionSourceConsul,
},
},
}, entry)
// only one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Len(t, entries, 1)
lastIndex++
}
// upsert over itself (REPLACE)
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpsert, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Source: structs.NewServiceName("web", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
Description: "upserted over",
},
}))
// Ensure it's there now.
idx, entry, ixn, err := s.IntentionGetExact(nil, &structs.IntentionQueryExact{
SourceNS: "default",
SourceName: "web",
DestinationNS: "default",
DestinationName: "api",
})
require.NoError(t, err)
require.NotNil(t, entry)
require.NotNil(t, ixn)
require.Equal(t, lastIndex, idx)
require.Equal(t, "upserted over", ixn.Description)
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "api",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
Precedence: 9,
Type: structs.IntentionSourceConsul,
Description: "upserted over",
},
},
}, entry)
// only one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Len(t, entries, 1)
lastIndex++
}
// upsert into existing config entry (APPEND)
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpUpsert, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Source: structs.NewServiceName("debug", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "debug",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
},
}))
// Ensure it's there now.
idx, entry, ixn, err := s.IntentionGetExact(nil, &structs.IntentionQueryExact{
SourceNS: "default",
SourceName: "debug",
DestinationNS: "default",
DestinationName: "api",
})
require.NoError(t, err)
require.NotNil(t, entry)
require.NotNil(t, ixn)
require.Equal(t, lastIndex, idx)
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "api",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
Precedence: 9,
Type: structs.IntentionSourceConsul,
Description: "upserted over",
},
{
Name: "debug",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
Precedence: 9,
Type: structs.IntentionSourceConsul,
},
},
}, entry)
// only one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Len(t, entries, 1)
lastIndex++
}
// Try to delete a non-existent intention by name
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Source: structs.NewServiceName("blurb", defaultEntMeta),
}))
// delete by name
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Source: structs.NewServiceName("web", defaultEntMeta),
}))
// only one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Len(t, entries, 1)
eqEntry(t, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "api",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "debug",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionDeny,
Precedence: 9,
Type: structs.IntentionSourceConsul,
},
},
}, entries[0].(*structs.ServiceIntentionsConfigEntry))
lastIndex++
}
// delete last one by name
{
require.NoError(t, s.IntentionMutation(lastIndex, structs.IntentionOpDelete, &structs.IntentionMutation{
Destination: structs.NewServiceName("api", defaultEntMeta),
Source: structs.NewServiceName("debug", defaultEntMeta),
}))
// none one
_, entries, err := s.ConfigEntries(nil, nil)
require.NoError(t, err)
require.Empty(t, entries)
lastIndex++
}
// Try to update an intention with an ID on a non-legacy config entry.
{
idFake := testUUID()
require.NoError(t, s.EnsureConfigEntry(lastIndex, &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "new",
EnterpriseMeta: *defaultEntMeta,
Sources: []*structs.SourceIntention{
{
Name: "web",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
Precedence: 9,
Type: structs.IntentionSourceConsul,
},
},
}, nil))
lastIndex++
// ...via create
testutil.RequireErrorContains(t, s.IntentionMutation(lastIndex, structs.IntentionOpCreate, &structs.IntentionMutation{
Destination: structs.NewServiceName("new", defaultEntMeta),
Value: &structs.SourceIntention{
Name: "old",
EnterpriseMeta: *defaultEntMeta,
Action: structs.IntentionActionAllow,
LegacyID: idFake,
},
}), `cannot use legacy intention API to edit intentions with a destination`)
}
}
func TestStore_LegacyIntentionSet_emptyId(t *testing.T) {
// note: irrelevant test for config entries variant
s := testStateStore(t)
@ -282,24 +824,27 @@ func TestStore_IntentionSet_updateCreatedAt(t *testing.T) {
testBothIntentionFormats(t, func(t *testing.T, s *Store, legacy bool) {
// Build a valid intention
var (
id = testUUID()
createTime time.Time
id = testUUID()
)
if legacy {
ixn := structs.Intention{
ID: id,
CreatedAt: time.Now().UTC(),
ID: id,
SourceNS: "default",
SourceName: "*",
DestinationNS: "default",
DestinationName: "web",
Action: structs.IntentionActionAllow,
CreatedAt: testTimeA,
UpdatedAt: testTimeA,
}
// Insert
require.NoError(t, s.LegacyIntentionSet(1, &ixn))
createTime = ixn.CreatedAt
// Change a value and test updating
ixnUpdate := ixn
ixnUpdate.CreatedAt = createTime.Add(10 * time.Second)
ixnUpdate.CreatedAt = testTimeB
require.NoError(t, s.LegacyIntentionSet(2, &ixnUpdate))
id = ixn.ID
@ -310,10 +855,12 @@ func TestStore_IntentionSet_updateCreatedAt(t *testing.T) {
Name: "web",
Sources: []*structs.SourceIntention{
{
LegacyID: id,
Name: "*",
Action: structs.IntentionActionAllow,
LegacyMeta: map[string]string{},
LegacyID: id,
Name: "*",
Action: structs.IntentionActionAllow,
LegacyMeta: map[string]string{},
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
},
},
}
@ -321,15 +868,13 @@ func TestStore_IntentionSet_updateCreatedAt(t *testing.T) {
require.NoError(t, conf.LegacyNormalize())
require.NoError(t, conf.LegacyValidate())
require.NoError(t, s.EnsureConfigEntry(1, conf.Clone(), nil))
createTime = *conf.Sources[0].LegacyCreateTime
}
// Read it back and verify
_, _, actual, err := s.IntentionGet(nil, id)
require.NoError(t, err)
require.NotNil(t, actual)
require.Equal(t, createTime, actual.CreatedAt)
require.Equal(t, testTimeA, actual.CreatedAt)
})
}
@ -339,7 +884,14 @@ func TestStore_IntentionSet_metaNil(t *testing.T) {
if legacy {
// Build a valid intention
ixn := &structs.Intention{
ID: id,
ID: id,
SourceNS: "default",
SourceName: "*",
DestinationNS: "default",
DestinationName: "web",
Action: structs.IntentionActionAllow,
CreatedAt: testTimeA,
UpdatedAt: testTimeA,
}
// Insert
@ -351,9 +903,11 @@ func TestStore_IntentionSet_metaNil(t *testing.T) {
Name: "web",
Sources: []*structs.SourceIntention{
{
LegacyID: id,
Name: "*",
Action: structs.IntentionActionAllow,
LegacyID: id,
Name: "*",
Action: structs.IntentionActionAllow,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
},
},
}
@ -380,8 +934,15 @@ func TestStore_IntentionSet_metaSet(t *testing.T) {
if legacy {
// Build a valid intention
ixn := structs.Intention{
ID: id,
Meta: expectMeta,
ID: id,
SourceNS: "default",
SourceName: "*",
DestinationNS: "default",
DestinationName: "web",
Action: structs.IntentionActionAllow,
CreatedAt: testTimeA,
UpdatedAt: testTimeA,
Meta: expectMeta,
}
// Insert
@ -394,10 +955,12 @@ func TestStore_IntentionSet_metaSet(t *testing.T) {
Name: "web",
Sources: []*structs.SourceIntention{
{
LegacyID: id,
Name: "*",
Action: structs.IntentionActionAllow,
LegacyMeta: expectMeta,
LegacyID: id,
Name: "*",
Action: structs.IntentionActionAllow,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
LegacyMeta: expectMeta,
},
},
}
@ -428,7 +991,14 @@ func TestStore_IntentionDelete(t *testing.T) {
// Create
if legacy {
ixn := &structs.Intention{
ID: id,
ID: id,
SourceNS: "default",
SourceName: "*",
DestinationNS: "default",
DestinationName: "web",
Action: structs.IntentionActionAllow,
CreatedAt: testTimeA,
UpdatedAt: testTimeA,
}
lastIndex++
require.NoError(t, s.LegacyIntentionSet(lastIndex, ixn))
@ -442,9 +1012,11 @@ func TestStore_IntentionDelete(t *testing.T) {
Name: "web",
Sources: []*structs.SourceIntention{
{
LegacyID: id,
Name: "*",
Action: structs.IntentionActionAllow,
LegacyID: id,
Name: "*",
Action: structs.IntentionActionAllow,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
},
},
}
@ -519,6 +1091,8 @@ func TestStore_IntentionsList(t *testing.T) {
SourceType: structs.IntentionSourceConsul,
Action: structs.IntentionActionAllow,
Meta: map[string]string{},
CreatedAt: testTimeA,
UpdatedAt: testTimeA,
}
}
@ -530,22 +1104,17 @@ func TestStore_IntentionsList(t *testing.T) {
id := testUUID()
for _, src := range srcs {
conf.Sources = append(conf.Sources, &structs.SourceIntention{
LegacyID: id,
Name: src,
Action: structs.IntentionActionAllow,
LegacyID: id,
Name: src,
Action: structs.IntentionActionAllow,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
})
}
return conf
}
cmpIntention := func(ixn *structs.Intention, id string) *structs.Intention {
ixn.ID = id
//nolint:staticcheck
ixn.UpdatePrecedence()
return ixn
}
clearIrrelevantFields := func(ixns []*structs.Intention) {
clearIrrelevantFields := func(ixns ...*structs.Intention) {
// Clear fields irrelevant for comparison.
for _, ixn := range ixns {
ixn.Hash = nil
@ -556,6 +1125,15 @@ func TestStore_IntentionsList(t *testing.T) {
}
}
cmpIntention := func(ixn *structs.Intention, id string) *structs.Intention {
ixn2 := ixn.Clone()
ixn2.ID = id
clearIrrelevantFields(ixn2)
//nolint:staticcheck
ixn2.UpdatePrecedence()
return ixn2
}
var (
expectIDs []string
)
@ -610,7 +1188,7 @@ func TestStore_IntentionsList(t *testing.T) {
require.Equal(t, !legacy, fromConfig)
require.Equal(t, lastIndex, idx)
clearIrrelevantFields(actual)
clearIrrelevantFields(actual...)
require.Equal(t, expected, actual)
})
}

View File

@ -69,7 +69,7 @@ func (s *Snapshot) Tombstones() (memdb.ResultIterator, error) {
// KVS is used when restoring from a snapshot. Use KVSSet for general inserts.
func (s *Restore) KVS(entry *structs.DirEntry) error {
if err := insertKVTxn(s.tx, entry, true); err != nil {
if err := insertKVTxn(s.tx, entry, true, true); err != nil {
return fmt.Errorf("failed inserting kvs entry: %s", err)
}
@ -153,7 +153,7 @@ func kvsSetTxn(tx WriteTxn, idx uint64, entry *structs.DirEntry, updateSession b
entry.ModifyIndex = idx
// Store the kv pair in the state store and update the index.
if err := insertKVTxn(tx, entry, false); err != nil {
if err := insertKVTxn(tx, entry, false, false); err != nil {
return fmt.Errorf("failed inserting kvs entry: %s", err)
}

View File

@ -16,7 +16,7 @@ func kvsIndexer() *memdb.StringFieldIndex {
}
}
func insertKVTxn(tx WriteTxn, entry *structs.DirEntry, updateMax bool) error {
func insertKVTxn(tx WriteTxn, entry *structs.DirEntry, updateMax bool, _ bool) error {
if err := tx.Insert("kvs", entry); err != nil {
return err
}

View File

@ -146,7 +146,7 @@ func (s *Snapshot) Sessions() (memdb.ResultIterator, error) {
// Session is used when restoring from a snapshot. For general inserts, use
// SessionCreate.
func (s *Restore) Session(sess *structs.Session) error {
if err := insertSessionTxn(s.tx, sess, sess.ModifyIndex, true); err != nil {
if err := insertSessionTxn(s.tx, sess, sess.ModifyIndex, true, true); err != nil {
return fmt.Errorf("failed inserting session: %s", err)
}
@ -213,7 +213,7 @@ func sessionCreateTxn(tx *txn, idx uint64, sess *structs.Session) error {
}
// Insert the session
if err := insertSessionTxn(tx, sess, idx, false); err != nil {
if err := insertSessionTxn(tx, sess, idx, false, false); err != nil {
return fmt.Errorf("failed inserting session: %s", err)
}

View File

@ -48,7 +48,7 @@ func sessionDeleteWithSession(tx WriteTxn, session *structs.Session, idx uint64)
return nil
}
func insertSessionTxn(tx *txn, session *structs.Session, idx uint64, updateMax bool) error {
func insertSessionTxn(tx *txn, session *structs.Session, idx uint64, updateMax bool, _ bool) error {
if err := tx.Insert("sessions", session); err != nil {
return err
}

View File

@ -61,7 +61,7 @@ func (c *ClientConnPool) ClientConn(datacenter string) (*grpc.ClientConn, error)
grpc.WithInsecure(),
grpc.WithContextDialer(c.dialer),
grpc.WithDisableRetry(),
grpc.WithStatsHandler(newStatsHandler(defaultMetrics)),
grpc.WithStatsHandler(newStatsHandler(defaultMetrics())),
// nolint:staticcheck // there is no other supported alternative to WithBalancerName
grpc.WithBalancerName("pick_first"))
if err != nil {

View File

@ -14,11 +14,12 @@ import (
// The register function will be called with the grpc.Server to register
// gRPC services with the server.
func NewHandler(addr net.Addr, register func(server *grpc.Server)) *Handler {
metrics := defaultMetrics()
// We don't need to pass tls.Config to the server since it's multiplexed
// behind the RPC listener, which already has TLS configured.
srv := grpc.NewServer(
grpc.StatsHandler(newStatsHandler(defaultMetrics)),
grpc.StreamInterceptor((&activeStreamCounter{metrics: defaultMetrics}).Intercept),
grpc.StatsHandler(newStatsHandler(metrics)),
grpc.StreamInterceptor((&activeStreamCounter{metrics: metrics}).Intercept),
)
register(srv)

View File

@ -10,7 +10,6 @@ import (
"google.golang.org/grpc/stats"
)
var defaultMetrics = metrics.Default()
var StatsGauges = []prometheus.GaugeDefinition{
{
Name: []string{"grpc", "server", "connections"},
@ -48,6 +47,8 @@ var StatsCounters = []prometheus.CounterDefinition{
},
}
var defaultMetrics = metrics.Default
// statsHandler is a grpc/stats.StatsHandler which emits connection and
// request metrics to go-metrics.
type statsHandler struct {

View File

@ -113,11 +113,14 @@ func patchGlobalMetrics(t *testing.T) (*fakeMetricsSink, func()) {
FilterDefault: true,
}
var err error
defaultMetrics, err = metrics.New(cfg, sink)
defaultMetrics = func() *metrics.Metrics {
m, _ := metrics.New(cfg, sink)
return m
}
require.NoError(t, err)
reset := func() {
t.Helper()
defaultMetrics, err = metrics.New(cfg, &metrics.BlackholeSink{})
defaultMetrics = metrics.Default
require.NoError(t, err, "failed to reset global metrics")
}
return sink, reset

View File

@ -365,6 +365,7 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc
return func(resp http.ResponseWriter, req *http.Request) {
setHeaders(resp, s.agent.config.HTTPResponseHeaders)
setTranslateAddr(resp, s.agent.config.TranslateWANAddrs)
setACLDefaultPolicy(resp, s.agent.config.ACLDefaultPolicy)
// Obfuscate any tokens from appearing in the logs
formVals, err := url.ParseQuery(req.URL.RawQuery)
@ -705,6 +706,12 @@ func setConsistency(resp http.ResponseWriter, consistency string) {
}
}
func setACLDefaultPolicy(resp http.ResponseWriter, aclDefaultPolicy string) {
if aclDefaultPolicy != "" {
resp.Header().Set("X-Consul-Default-ACL-Policy", aclDefaultPolicy)
}
}
// setLastContact is used to set the last contact header
func setLastContact(resp http.ResponseWriter, last time.Duration) {
if last < 0 {

View File

@ -415,6 +415,54 @@ func TestHTTPAPI_TranslateAddrHeader(t *testing.T) {
}
}
func TestHTTPAPI_DefaultACLPolicy(t *testing.T) {
t.Parallel()
type testcase struct {
name string
hcl string
expect string
}
cases := []testcase{
{
name: "default is allow",
hcl: ``,
expect: "allow",
},
{
name: "explicit allow",
hcl: `acl { default_policy = "allow" }`,
expect: "allow",
},
{
name: "explicit deny",
hcl: `acl { default_policy = "deny" }`,
expect: "deny",
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, tc.hcl)
defer a.Shutdown()
resp := httptest.NewRecorder()
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
return nil, nil
}
req, _ := http.NewRequest("GET", "/v1/agent/self", nil)
a.srv.wrap(handler, []string{"GET"})(resp, req)
require.Equal(t, tc.expect, resp.Header().Get("X-Consul-Default-ACL-Policy"))
})
}
}
func TestHTTPAPIResponseHeaders(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, `

View File

@ -11,6 +11,8 @@ func autopilotToAPIServerEnterprise(_ *autopilot.ServerState, _ *api.AutopilotSe
// noop in oss
}
func autopilotToAPIStateEnterprise(_ *autopilot.State, _ *api.AutopilotState) {
// noop in oss
func autopilotToAPIStateEnterprise(state *autopilot.State, apiState *api.AutopilotState) {
// without the enterprise features there is no different between these two and we don't want to
// alarm anyone by leaving this as the zero value.
apiState.OptimisticFailureTolerance = state.FailureTolerance
}

View File

@ -656,9 +656,10 @@ func TestAutopilotStateToAPIConversion(t *testing.T) {
}
expected := api.AutopilotState{
Healthy: true,
FailureTolerance: 1,
Leader: string(leaderID),
Healthy: true,
FailureTolerance: 1,
OptimisticFailureTolerance: 1,
Leader: string(leaderID),
Voters: []string{
string(leaderID),
string(follower1ID),

View File

@ -519,7 +519,7 @@ START:
// Try to get a conn first
conn, err := p.acquire(dc, nodeName, addr)
if err != nil {
return nil, nil, fmt.Errorf("failed to get conn: %v", err)
return nil, nil, fmt.Errorf("failed to get conn: %w", err)
}
// Get a client
@ -533,7 +533,7 @@ START:
retries++
goto START
}
return nil, nil, fmt.Errorf("failed to start stream: %v", err)
return nil, nil, fmt.Errorf("failed to start stream: %w", err)
}
return conn, client, nil
}
@ -575,14 +575,14 @@ func (p *ConnPool) rpcInsecure(dc string, addr net.Addr, method string, args int
var codec rpc.ClientCodec
conn, _, err := p.dial(dc, addr, 0, RPCTLSInsecure)
if err != nil {
return fmt.Errorf("rpcinsecure error establishing connection: %v", err)
return fmt.Errorf("rpcinsecure error establishing connection: %w", err)
}
codec = msgpackrpc.NewCodecFromHandle(true, true, conn, structs.MsgpackHandle)
// Make the RPC call
err = msgpackrpc.CallWithCodec(codec, method, args, reply)
if err != nil {
return fmt.Errorf("rpcinsecure error making call: %v", err)
return fmt.Errorf("rpcinsecure error making call: %w", err)
}
return nil
@ -594,7 +594,7 @@ func (p *ConnPool) rpc(dc string, nodeName string, addr net.Addr, method string,
// Get a usable client
conn, sc, err := p.getClient(dc, nodeName, addr)
if err != nil {
return fmt.Errorf("rpc error getting client: %v", err)
return fmt.Errorf("rpc error getting client: %w", err)
}
// Make the RPC call
@ -611,7 +611,7 @@ func (p *ConnPool) rpc(dc string, nodeName string, addr net.Addr, method string,
}
p.releaseConn(conn)
return fmt.Errorf("rpc error making call: %v", err)
return fmt.Errorf("rpc error making call: %w", err)
}
// Done with the connection

View File

@ -59,6 +59,59 @@ func (e *ServiceIntentionsConfigEntry) DestinationServiceName() ServiceName {
return NewServiceName(e.Name, &e.EnterpriseMeta)
}
func (e *ServiceIntentionsConfigEntry) UpdateSourceByLegacyID(legacyID string, update *SourceIntention) bool {
for i, src := range e.Sources {
if src.LegacyID == legacyID {
e.Sources[i] = update
return true
}
}
return false
}
func (e *ServiceIntentionsConfigEntry) UpsertSourceByName(sn ServiceName, upsert *SourceIntention) {
for i, src := range e.Sources {
if src.SourceServiceName() == sn {
e.Sources[i] = upsert
return
}
}
e.Sources = append(e.Sources, upsert)
}
func (e *ServiceIntentionsConfigEntry) DeleteSourceByLegacyID(legacyID string) bool {
for i, src := range e.Sources {
if src.LegacyID == legacyID {
// Delete slice element: https://github.com/golang/go/wiki/SliceTricks#delete
// a = append(a[:i], a[i+1:]...)
e.Sources = append(e.Sources[:i], e.Sources[i+1:]...)
if len(e.Sources) == 0 {
e.Sources = nil
}
return true
}
}
return false
}
func (e *ServiceIntentionsConfigEntry) DeleteSourceByName(sn ServiceName) bool {
for i, src := range e.Sources {
if src.SourceServiceName() == sn {
// Delete slice element: https://github.com/golang/go/wiki/SliceTricks#delete
// a = append(a[:i], a[i+1:]...)
e.Sources = append(e.Sources[:i], e.Sources[i+1:]...)
if len(e.Sources) == 0 {
e.Sources = nil
}
return true
}
}
return false
}
func (e *ServiceIntentionsConfigEntry) ToIntention(src *SourceIntention) *Intention {
meta := e.Meta
if src.LegacyID != "" {
@ -352,6 +405,9 @@ func (e *ServiceIntentionsConfigEntry) normalize(legacyWrite bool) error {
return fmt.Errorf("config entry is nil")
}
// NOTE: this function must be deterministic so that the raft log doesn't
// diverge. This means no ID assignments or time.Now() usage!
e.Kind = ServiceIntentions
e.EnterpriseMeta.Normalize()
@ -377,11 +433,6 @@ func (e *ServiceIntentionsConfigEntry) normalize(legacyWrite bool) error {
if src.LegacyMeta == nil {
src.LegacyMeta = make(map[string]string)
}
// Set the created/updated times. If this is an update instead of an insert
// the UpdateOver() will fix it up appropriately.
now := time.Now().UTC()
src.LegacyCreateTime = timePointer(now)
src.LegacyUpdateTime = timePointer(now)
} else {
// Legacy fields are cleared, except LegacyMeta which we leave
// populated so that we can later fail the write in Validate() and
@ -541,11 +592,25 @@ func (e *ServiceIntentionsConfigEntry) validate(legacyWrite bool) error {
)
}
}
if src.LegacyCreateTime == nil {
return fmt.Errorf("Sources[%d].LegacyCreateTime must be set", i)
}
if src.LegacyUpdateTime == nil {
return fmt.Errorf("Sources[%d].LegacyUpdateTime must be set", i)
}
} else {
if len(src.LegacyMeta) > 0 {
return fmt.Errorf("Sources[%d].LegacyMeta must be omitted", i)
}
src.LegacyMeta = nil // ensure it's completely unset
if src.LegacyCreateTime != nil {
return fmt.Errorf("Sources[%d].LegacyCreateTime must be omitted", i)
}
if src.LegacyUpdateTime != nil {
return fmt.Errorf("Sources[%d].LegacyUpdateTime must be omitted", i)
}
}
if legacyWrite {

View File

@ -21,6 +21,14 @@ func generateUUID() (ret string) {
}
func TestServiceIntentionsConfigEntry(t *testing.T) {
var (
testLocation = time.FixedZone("UTC-8", -8*60*60)
testTimeA = time.Date(1955, 11, 5, 6, 15, 0, 0, testLocation)
testTimeB = time.Date(1985, 10, 26, 1, 35, 0, 0, testLocation)
testTimeC = time.Date(2015, 10, 21, 16, 29, 0, 0, testLocation)
)
type testcase struct {
entry *ServiceIntentionsConfigEntry
legacy bool
@ -126,9 +134,11 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Name: "test",
Sources: []*SourceIntention{
{
LegacyID: legacyIDs[0],
Name: "foo",
Action: IntentionActionAllow,
LegacyID: legacyIDs[0],
Name: "foo",
Action: IntentionActionAllow,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
},
},
Meta: map[string]string{
@ -198,10 +208,12 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Name: "test",
Sources: []*SourceIntention{
{
LegacyID: legacyIDs[0],
Name: "foo",
Action: IntentionActionAllow,
Description: strings.Repeat("x", 512),
LegacyID: legacyIDs[0],
Name: "foo",
Action: IntentionActionAllow,
Description: strings.Repeat("x", 512),
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
LegacyMeta: map[string]string{ // stray Meta will be dropped
"old": "data",
},
@ -217,10 +229,12 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Name: "test",
Sources: []*SourceIntention{
{
LegacyID: legacyIDs[0],
Name: "foo",
Action: IntentionActionAllow,
LegacyMeta: makeStringMap(65, 5, 5),
LegacyID: legacyIDs[0],
Name: "foo",
Action: IntentionActionAllow,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
LegacyMeta: makeStringMap(65, 5, 5),
},
},
},
@ -233,10 +247,12 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Name: "test",
Sources: []*SourceIntention{
{
LegacyID: legacyIDs[0],
Name: "foo",
Action: IntentionActionAllow,
LegacyMeta: makeStringMap(64, 129, 5),
LegacyID: legacyIDs[0],
Name: "foo",
Action: IntentionActionAllow,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
LegacyMeta: makeStringMap(64, 129, 5),
},
},
},
@ -249,10 +265,12 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Name: "test",
Sources: []*SourceIntention{
{
LegacyID: legacyIDs[0],
Name: "foo",
Action: IntentionActionAllow,
LegacyMeta: makeStringMap(64, 128, 513),
LegacyID: legacyIDs[0],
Name: "foo",
Action: IntentionActionAllow,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
LegacyMeta: makeStringMap(64, 128, 513),
},
},
},
@ -265,10 +283,12 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Name: "test",
Sources: []*SourceIntention{
{
LegacyID: legacyIDs[0],
Name: "foo",
Action: IntentionActionAllow,
LegacyMeta: makeStringMap(64, 128, 512),
LegacyID: legacyIDs[0],
Name: "foo",
Action: IntentionActionAllow,
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
LegacyMeta: makeStringMap(64, 128, 512),
},
},
},
@ -280,9 +300,11 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Name: "test",
Sources: []*SourceIntention{
{
Name: "foo",
Action: IntentionActionAllow,
Description: strings.Repeat("x", 512),
Name: "foo",
Action: IntentionActionAllow,
Description: strings.Repeat("x", 512),
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
},
},
},
@ -1008,8 +1030,10 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Action: IntentionActionDeny,
},
{
Name: "foo",
Action: IntentionActionAllow,
Name: "foo",
Action: IntentionActionAllow,
LegacyCreateTime: &testTimeA, // stray times will be dropped
LegacyUpdateTime: &testTimeA,
},
{
Name: "bar",
@ -1059,9 +1083,11 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
Name: "test",
Sources: []*SourceIntention{
{
Name: WildcardSpecifier,
Action: IntentionActionDeny,
LegacyID: legacyIDs[0],
Name: WildcardSpecifier,
Action: IntentionActionDeny,
LegacyID: legacyIDs[0],
LegacyCreateTime: &testTimeA,
LegacyUpdateTime: &testTimeA,
},
{
Name: "foo",
@ -1071,23 +1097,27 @@ func TestServiceIntentionsConfigEntry(t *testing.T) {
"key1": "val1",
"key2": "val2",
},
LegacyCreateTime: &testTimeB,
LegacyUpdateTime: &testTimeB,
},
{
Name: "bar",
Action: IntentionActionDeny,
LegacyID: legacyIDs[2],
Name: "bar",
Action: IntentionActionDeny,
LegacyID: legacyIDs[2],
LegacyCreateTime: &testTimeC,
LegacyUpdateTime: &testTimeC,
},
},
},
check: func(t *testing.T, entry *ServiceIntentionsConfigEntry) {
require.Len(t, entry.Sources, 3)
assert.False(t, entry.Sources[0].LegacyCreateTime.IsZero())
assert.False(t, entry.Sources[0].LegacyUpdateTime.IsZero())
assert.False(t, entry.Sources[1].LegacyCreateTime.IsZero())
assert.False(t, entry.Sources[1].LegacyUpdateTime.IsZero())
assert.False(t, entry.Sources[2].LegacyCreateTime.IsZero())
assert.False(t, entry.Sources[2].LegacyUpdateTime.IsZero())
// assert.False(t, entry.Sources[0].LegacyCreateTime.IsZero())
// assert.False(t, entry.Sources[0].LegacyUpdateTime.IsZero())
// assert.False(t, entry.Sources[1].LegacyCreateTime.IsZero())
// assert.False(t, entry.Sources[1].LegacyUpdateTime.IsZero())
// assert.False(t, entry.Sources[2].LegacyCreateTime.IsZero())
// assert.False(t, entry.Sources[2].LegacyUpdateTime.IsZero())
assert.Equal(t, []*SourceIntention{
{
@ -1299,6 +1329,8 @@ func TestMigrateIntentions(t *testing.T) {
LegacyMeta: map[string]string{
"key1": "val1",
},
LegacyCreateTime: &anyTime,
LegacyUpdateTime: &anyTime,
},
},
},
@ -1349,6 +1381,8 @@ func TestMigrateIntentions(t *testing.T) {
LegacyMeta: map[string]string{
"key1": "val1",
},
LegacyCreateTime: &anyTime,
LegacyUpdateTime: &anyTime,
},
{
LegacyID: legacyIDs[1],
@ -1359,6 +1393,8 @@ func TestMigrateIntentions(t *testing.T) {
LegacyMeta: map[string]string{
"key2": "val2",
},
LegacyCreateTime: &anyTime,
LegacyUpdateTime: &anyTime,
},
},
},
@ -1409,6 +1445,8 @@ func TestMigrateIntentions(t *testing.T) {
LegacyMeta: map[string]string{
"key1": "val1",
},
LegacyCreateTime: &anyTime,
LegacyUpdateTime: &anyTime,
},
},
},
@ -1425,6 +1463,8 @@ func TestMigrateIntentions(t *testing.T) {
LegacyMeta: map[string]string{
"key2": "val2",
},
LegacyCreateTime: &anyTime,
LegacyUpdateTime: &anyTime,
},
},
},

View File

@ -440,6 +440,9 @@ func (x *Intention) ToConfigEntry(legacy bool) *ServiceIntentionsConfigEntry {
}
func (x *Intention) ToSourceIntention(legacy bool) *SourceIntention {
ct := x.CreatedAt // copy
ut := x.UpdatedAt
src := &SourceIntention{
Name: x.SourceName,
EnterpriseMeta: *x.SourceEnterpriseMeta(),
@ -450,8 +453,8 @@ func (x *Intention) ToSourceIntention(legacy bool) *SourceIntention {
Type: x.SourceType,
Description: x.Description,
LegacyMeta: x.Meta,
LegacyCreateTime: nil, // Ignore
LegacyUpdateTime: nil, // Ignore
LegacyCreateTime: &ct,
LegacyUpdateTime: &ut,
}
if !legacy {
src.Permissions = x.Permissions
@ -522,13 +525,30 @@ type IntentionRequest struct {
Op IntentionOp
// Intention is the intention.
//
// This is mutually exclusive with the Mutation field.
Intention *Intention
// Mutation is a change to make to an Intention.
//
// This is mutually exclusive with the Intention field.
//
// This field is only set by the leader before writing to the raft log and
// is not settable via the API or an RPC.
Mutation *IntentionMutation
// WriteRequest is a common struct containing ACL tokens and other
// write-related common elements for requests.
WriteRequest
}
type IntentionMutation struct {
ID string
Destination ServiceName
Source ServiceName
Value *SourceIntention
}
// RequestDatacenter returns the datacenter for a given request.
func (q *IntentionRequest) RequestDatacenter() string {
return q.Datacenter

File diff suppressed because one or more lines are too long

View File

@ -158,15 +158,6 @@ const (
// configured to use TLS. Any other value indicates that it was not setup in
// that manner.
MemberTagValueUseTLS = "1"
// MemberTagKeyReadReplica is the key used to indicate that the member is a read
// replica server (will remain a Raft non-voter).
// Read Replicas are a Consul Enterprise feature.
MemberTagKeyReadReplica = "nonvoter"
// MemberTagValueReadReplica is the value of the MemberTagKeyReadReplica key when
// the member is in fact a read-replica. Any other value indicates that it is not.
// Read Replicas are a Consul Enterprise feature.
MemberTagValueReadReplica = "1"
)
type MemberACLMode string

View File

@ -254,6 +254,11 @@ type QueryMeta struct {
// CacheAge is set if request was ?cached and indicates how stale the cached
// response is.
CacheAge time.Duration
// DefaultACLPolicy is used to control the ACL interaction when there is no
// defined policy. This can be "allow" which means ACLs are used to
// deny-list, or "deny" which means ACLs are allow-lists.
DefaultACLPolicy string
}
// WriteMeta is used to return meta data about a write
@ -962,6 +967,12 @@ func parseQueryMeta(resp *http.Response, q *QueryMeta) error {
q.AddressTranslationEnabled = false
}
// Parse X-Consul-Default-ACL-Policy
switch v := header.Get("X-Consul-Default-ACL-Policy"); v {
case "allow", "deny":
q.DefaultACLPolicy = v
}
// Parse Cache info
if cacheStr := header.Get("X-Cache"); cacheStr != "" {
q.CacheHit = strings.EqualFold(cacheStr, "HIT")

View File

@ -840,6 +840,7 @@ func TestAPI_ParseQueryMeta(t *testing.T) {
resp.Header.Set("X-Consul-LastContact", "80")
resp.Header.Set("X-Consul-KnownLeader", "true")
resp.Header.Set("X-Consul-Translate-Addresses", "true")
resp.Header.Set("X-Consul-Default-ACL-Policy", "deny")
qm := &QueryMeta{}
if err := parseQueryMeta(resp, qm); err != nil {
@ -858,6 +859,9 @@ func TestAPI_ParseQueryMeta(t *testing.T) {
if !qm.AddressTranslationEnabled {
t.Fatalf("Bad: %v", qm)
}
if qm.DefaultACLPolicy != "deny" {
t.Fatalf("Bad: %v", qm)
}
}
func TestAPI_UnixSocket(t *testing.T) {

View File

@ -114,7 +114,7 @@ type OperatorHealthReply struct {
type AutopilotState struct {
Healthy bool
FailureTolerance int
OptimisitcFailureTolerance int
OptimisticFailureTolerance int
Servers map[string]AutopilotServer
Leader string
@ -137,7 +137,7 @@ type AutopilotServer struct {
StableSince time.Time
RedundancyZone string `json:",omitempty"`
UpgradeVersion string `json:",omitempty"`
ReadReplica bool `json:",omitempty"`
ReadReplica bool
Status AutopilotServerStatus
Meta map[string]string
NodeType AutopilotServerType

View File

@ -1,4 +1,4 @@
ARG GOLANG_VERSION=1.14.11
ARG GOLANG_VERSION=1.15.5
FROM golang:${GOLANG_VERSION}
ARG GOTOOLS="github.com/elazarl/go-bindata-assetfs/... \

View File

@ -1,6 +1,6 @@
FROM travisci/ci-garnet:packer-1512502276-986baf0
ENV GOLANG_VERSION 1.14.11
ENV GOLANG_VERSION 1.15.5
RUN mkdir -p /home/travis/go && chown -R travis /home/travis/go

View File

@ -85,6 +85,7 @@ import (
operauto "github.com/hashicorp/consul/command/operator/autopilot"
operautoget "github.com/hashicorp/consul/command/operator/autopilot/get"
operautoset "github.com/hashicorp/consul/command/operator/autopilot/set"
operautostate "github.com/hashicorp/consul/command/operator/autopilot/state"
operraft "github.com/hashicorp/consul/command/operator/raft"
operraftlist "github.com/hashicorp/consul/command/operator/raft/listpeers"
operraftremove "github.com/hashicorp/consul/command/operator/raft/removepeer"
@ -202,6 +203,7 @@ func init() {
Register("operator autopilot", func(cli.Ui) (cli.Command, error) { return operauto.New(), nil })
Register("operator autopilot get-config", func(ui cli.Ui) (cli.Command, error) { return operautoget.New(ui), nil })
Register("operator autopilot set-config", func(ui cli.Ui) (cli.Command, error) { return operautoset.New(ui), nil })
Register("operator autopilot state", func(ui cli.Ui) (cli.Command, error) { return operautostate.New(ui), nil })
Register("operator raft", func(cli.Ui) (cli.Command, error) { return operraft.New(), nil })
Register("operator raft list-peers", func(ui cli.Ui) (cli.Command, error) { return operraftlist.New(ui), nil })
Register("operator raft remove-peer", func(ui cli.Ui) (cli.Command, error) { return operraftremove.New(ui), nil })

View File

@ -44,7 +44,7 @@ func TestConfigUtil_Values(t *testing.T) {
{
`{ "duration": "nope" }`,
"",
"invalid duration nope",
`invalid duration "nope"`,
},
{
`{ "string": 123 }`,

View File

@ -0,0 +1,206 @@
package state
import (
"bytes"
"encoding/json"
"fmt"
"sort"
"github.com/hashicorp/consul/api"
)
const (
PrettyFormat string = "pretty"
JSONFormat string = "json"
)
// Formatter defines methods provided by an autopilot state output formatter
type Formatter interface {
FormatState(state *api.AutopilotState) (string, error)
}
// GetSupportedFormats returns supported formats
func GetSupportedFormats() []string {
return []string{PrettyFormat, JSONFormat}
}
// NewFormatter returns Formatter implementation
func NewFormatter(format string) (formatter Formatter, err error) {
switch format {
case PrettyFormat:
formatter = newPrettyFormatter()
case JSONFormat:
formatter = newJSONFormatter()
default:
err = fmt.Errorf("Unknown format: %s", format)
}
return formatter, err
}
func newPrettyFormatter() Formatter {
return &prettyFormatter{}
}
type prettyFormatter struct {
}
func outputStringSlice(buffer *bytes.Buffer, indent string, values []string) {
for _, val := range values {
buffer.WriteString(fmt.Sprintf("%s%s\n", indent, val))
}
}
type mapOutput struct {
key string
value string
}
func formatZone(zoneName string, zone *api.AutopilotZone) string {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf(" %s:\n", zoneName))
buffer.WriteString(fmt.Sprintf(" Failure Tolerance: %d\n", zone.FailureTolerance))
buffer.WriteString(" Voters:\n")
outputStringSlice(&buffer, " ", zone.Voters)
buffer.WriteString(" Servers:\n")
outputStringSlice(&buffer, " ", zone.Servers)
return buffer.String()
}
func formatServer(srv *api.AutopilotServer) string {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf(" %s\n", srv.ID))
buffer.WriteString(fmt.Sprintf(" Name: %s\n", srv.Name))
buffer.WriteString(fmt.Sprintf(" Address: %s\n", srv.Address))
buffer.WriteString(fmt.Sprintf(" Version: %s\n", srv.Version))
buffer.WriteString(fmt.Sprintf(" Status: %s\n", srv.Status))
buffer.WriteString(fmt.Sprintf(" Node Type: %s\n", srv.NodeType))
buffer.WriteString(fmt.Sprintf(" Node Status: %s\n", srv.NodeStatus))
buffer.WriteString(fmt.Sprintf(" Healthy: %t\n", srv.Healthy))
buffer.WriteString(fmt.Sprintf(" Last Contact: %s\n", srv.LastContact.String()))
buffer.WriteString(fmt.Sprintf(" Last Term: %d\n", srv.LastTerm))
buffer.WriteString(fmt.Sprintf(" Last Index: %d\n", srv.LastIndex))
if srv.RedundancyZone != "" {
buffer.WriteString(fmt.Sprintf(" Redundancy Zone: %s\n", srv.RedundancyZone))
}
if srv.UpgradeVersion != "" {
buffer.WriteString(fmt.Sprintf(" Upgrade Version: %s\n", srv.UpgradeVersion))
}
if srv.ReadReplica {
buffer.WriteString(fmt.Sprintf(" Read Replica: %t\n", srv.ReadReplica))
}
if len(srv.Meta) > 0 {
buffer.WriteString(fmt.Sprintf(" Meta\n"))
var outputs []mapOutput
for k, v := range srv.Meta {
outputs = append(outputs, mapOutput{key: k, value: fmt.Sprintf(" %q: %q\n", k, v)})
}
sort.Slice(outputs, func(i, j int) bool {
return outputs[i].key < outputs[j].key
})
for _, output := range outputs {
buffer.WriteString(output.value)
}
}
return buffer.String()
}
func (f *prettyFormatter) FormatState(state *api.AutopilotState) (string, error) {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf("Healthy: %t\n", state.Healthy))
buffer.WriteString(fmt.Sprintf("Failure Tolerance: %d\n", state.FailureTolerance))
buffer.WriteString(fmt.Sprintf("Optimistic Failure Tolerance: %d\n", state.OptimisticFailureTolerance))
buffer.WriteString(fmt.Sprintf("Leader: %s\n", state.Leader))
buffer.WriteString("Voters:\n")
outputStringSlice(&buffer, " ", state.Voters)
if len(state.ReadReplicas) > 0 {
buffer.WriteString("Read Replicas:\n")
outputStringSlice(&buffer, " ", state.ReadReplicas)
}
if len(state.RedundancyZones) > 0 {
var outputs []mapOutput
buffer.WriteString("Redundancy Zones:\n")
for zoneName, zone := range state.RedundancyZones {
outputs = append(outputs, mapOutput{key: zoneName, value: formatZone(zoneName, &zone)})
}
sort.Slice(outputs, func(i, j int) bool {
return outputs[i].key < outputs[j].key
})
for _, output := range outputs {
buffer.WriteString(output.value)
}
}
if state.Upgrade != nil {
u := state.Upgrade
buffer.WriteString("Upgrade:\n")
buffer.WriteString(fmt.Sprintf(" Status: %s\n", u.Status))
buffer.WriteString(fmt.Sprintf(" Target Version: %s\n", u.TargetVersion))
if len(u.TargetVersionVoters) > 0 {
buffer.WriteString(" Target Version Voters:\n")
outputStringSlice(&buffer, " ", u.TargetVersionVoters)
}
if len(u.TargetVersionNonVoters) > 0 {
buffer.WriteString(" Target Version Non-Voters:\n")
outputStringSlice(&buffer, " ", u.TargetVersionNonVoters)
}
if len(u.TargetVersionReadReplicas) > 0 {
buffer.WriteString(" Target Version ReadReplicas:\n")
outputStringSlice(&buffer, " ", u.TargetVersionReadReplicas)
}
if len(u.OtherVersionVoters) > 0 {
buffer.WriteString(" Other Version Voters:\n")
outputStringSlice(&buffer, " ", u.OtherVersionVoters)
}
if len(u.OtherVersionNonVoters) > 0 {
buffer.WriteString(" Other Version Non-Voters:\n")
outputStringSlice(&buffer, " ", u.OtherVersionNonVoters)
}
if len(u.OtherVersionReadReplicas) > 0 {
buffer.WriteString(" Other Version ReadReplicas:\n")
outputStringSlice(&buffer, " ", u.OtherVersionReadReplicas)
}
}
buffer.WriteString("Servers:\n")
var outputs []mapOutput
for id, srv := range state.Servers {
outputs = append(outputs, mapOutput{key: id, value: formatServer(&srv)})
}
sort.Slice(outputs, func(i, j int) bool {
return outputs[i].key < outputs[j].key
})
for _, output := range outputs {
buffer.WriteString(output.value)
}
return buffer.String(), nil
}
func newJSONFormatter() Formatter {
return &jsonFormatter{}
}
type jsonFormatter struct {
}
func (f *jsonFormatter) FormatState(state *api.AutopilotState) (string, error) {
b, err := json.MarshalIndent(state, "", " ")
if err != nil {
return "", fmt.Errorf("Failed to marshal token: %v", err)
}
return string(b), nil
}

View File

@ -0,0 +1,99 @@
package state
import (
"flag"
"fmt"
"strings"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/command/flags"
"github.com/mitchellh/cli"
)
func New(ui cli.Ui) *cmd {
c := &cmd{UI: ui}
c.init()
return c
}
type cmd struct {
UI cli.Ui
flags *flag.FlagSet
http *flags.HTTPFlags
help string
format string
}
func (c *cmd) init() {
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
c.flags.StringVar(
&c.format,
"format",
PrettyFormat,
fmt.Sprintf("Output format {%s}", strings.Join(GetSupportedFormats(), "|")),
)
c.http = &flags.HTTPFlags{}
flags.Merge(c.flags, c.http.ClientFlags())
flags.Merge(c.flags, c.http.ServerFlags())
c.help = flags.Usage(help, c.flags)
}
func (c *cmd) Run(args []string) int {
if err := c.flags.Parse(args); err != nil {
if err == flag.ErrHelp {
return 0
}
c.UI.Error(fmt.Sprintf("Failed to parse args: %v", err))
return 1
}
// Set up a client.
client, err := c.http.APIClient()
if err != nil {
c.UI.Error(fmt.Sprintf("Error initializing client: %s", err))
return 1
}
// Fetch the current configuration.
opts := &api.QueryOptions{
AllowStale: c.http.Stale(),
}
state, err := client.Operator().AutopilotState(opts)
if err != nil {
c.UI.Error(fmt.Sprintf("Error querying Autopilot state: %s", err))
return 1
}
formatter, err := NewFormatter(c.format)
if err != nil {
c.UI.Error(err.Error())
return 1
}
out, err := formatter.FormatState(state)
if err != nil {
c.UI.Error(err.Error())
return 1
}
if out != "" {
c.UI.Info(out)
}
return 0
}
func (c *cmd) Synopsis() string {
return synopsis
}
func (c *cmd) Help() string {
return c.help
}
const synopsis = "Display the current Autopilot configuration"
const help = `
Usage: consul operator autopilot get-config [options]
Displays the current Autopilot configuration.
`

View File

@ -0,0 +1,126 @@
package state
import (
"encoding/json"
"flag"
"io/ioutil"
"path/filepath"
"strings"
"testing"
"github.com/hashicorp/consul/agent"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/testrpc"
"github.com/mitchellh/cli"
"github.com/stretchr/testify/require"
)
// update allows golden files to be updated based on the current output.
var update = flag.Bool("update", false, "update golden files")
// golden reads and optionally writes the expected data to the golden file,
// returning the contents as a string.
func golden(t *testing.T, name, got string) string {
t.Helper()
golden := filepath.Join("testdata", name+".golden")
if *update && got != "" {
err := ioutil.WriteFile(golden, []byte(got), 0644)
require.NoError(t, err)
}
expected, err := ioutil.ReadFile(golden)
require.NoError(t, err)
return string(expected)
}
func TestStateCommand_noTabs(t *testing.T) {
t.Parallel()
if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') {
t.Fatal("help has tabs")
}
}
func TestStateCommand_Pretty(t *testing.T) {
t.Parallel()
a := agent.NewTestAgent(t, `
node_id = "f0427127-7531-455a-b651-f1ea1d8451f0"
`)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
ui := cli.NewMockUi()
cmd := New(ui)
args := []string{
"-http-addr=" + a.HTTPAddr(),
}
code := cmd.Run(args)
require.Empty(t, ui.ErrorWriter.String())
require.Equal(t, code, 0)
output := ui.OutputWriter.String()
// Just a few quick checks to ensure we got output
// the output formatter will be tested in another test.
require.Regexp(t, `^Healthy:`, output)
require.Regexp(t, `(?m)^Leader:`, output)
}
func TestStateCommand_JSON(t *testing.T) {
t.Parallel()
a := agent.NewTestAgent(t, "")
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
ui := cli.NewMockUi()
cmd := New(ui)
args := []string{
"-http-addr=" + a.HTTPAddr(),
"-format=json",
}
code := cmd.Run(args)
require.Empty(t, ui.ErrorWriter.String())
require.Equal(t, code, 0)
output := ui.OutputWriter.String()
var state api.AutopilotState
require.NoError(t, json.Unmarshal([]byte(output), &state))
}
func TestStateCommand_Formatter(t *testing.T) {
cases := []string{
"oss",
"enterprise",
}
for _, name := range cases {
t.Run(name, func(t *testing.T) {
statePath := filepath.Join("testdata", name, "state.json")
input, err := ioutil.ReadFile(statePath)
require.NoError(t, err)
var state api.AutopilotState
require.NoError(t, json.Unmarshal(input, &state))
for _, format := range GetSupportedFormats() {
t.Run(format, func(t *testing.T) {
formatter, err := NewFormatter(format)
require.NoError(t, err)
actual, err := formatter.FormatState(&state)
require.NoError(t, err)
expected := golden(t, filepath.Join(name, format), actual)
require.Equal(t, expected, actual)
})
}
})
}
}

View File

@ -0,0 +1,401 @@
{
"Healthy": true,
"FailureTolerance": 1,
"OptimisticFailureTolerance": 0,
"Servers": {
"1b8044f6-1d25-4a83-9662-acdf404341d2": {
"ID": "1b8044f6-1d25-4a83-9662-acdf404341d2",
"Name": "node6.new",
"Address": "198.18.1.6:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone3",
"UpgradeVersion": "2.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "2.0.0",
"zone": "zone3"
},
"NodeType": "zone-standby"
},
"1baeb453-ad9e-489a-bbfe-53bee097aec8": {
"ID": "1baeb453-ad9e-489a-bbfe-53bee097aec8",
"Name": "node4",
"Address": "198.18.0.4:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone2",
"UpgradeVersion": "1.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "1.0.0",
"zone": "zone2"
},
"NodeType": "zone-standby"
},
"3044d109-f028-4489-b59c-f267afe408f2": {
"ID": "3044d109-f028-4489-b59c-f267afe408f2",
"Name": "node2.new",
"Address": "198.18.1.2:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone1",
"UpgradeVersion": "2.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "2.0.0",
"zone": "zone1"
},
"NodeType": "zone-standby"
},
"4691e516-6989-4a16-8f55-12ff2226a3c9": {
"ID": "4691e516-6989-4a16-8f55-12ff2226a3c9",
"Name": "node4.new",
"Address": "198.18.1.4:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone2",
"UpgradeVersion": "2.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "2.0.0",
"zone": "zone2"
},
"NodeType": "zone-standby"
},
"4c42fe86-321d-4e8f-be0d-c261e6cfc453": {
"ID": "4c42fe86-321d-4e8f-be0d-c261e6cfc453",
"Name": "node5",
"Address": "198.18.0.5:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone3",
"UpgradeVersion": "1.0.0",
"ReadReplica": false,
"Status": "voter",
"Meta": {
"foo": "bar",
"upgrade": "1.0.0",
"zone": "zone3"
},
"NodeType": "zone-voter"
},
"6ca5a41c-6162-41c1-b4eb-09082efe206f": {
"ID": "6ca5a41c-6162-41c1-b4eb-09082efe206f",
"Name": "node2",
"Address": "198.18.0.2:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone1",
"UpgradeVersion": "1.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "1.0.0",
"zone": "zone1"
},
"NodeType": "zone-standby"
},
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d": {
"ID": "6e8a37e5-a1c4-4212-a75e-91487d8cda6d",
"Name": "node3",
"Address": "198.18.0.3:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone2",
"UpgradeVersion": "1.0.0",
"ReadReplica": false,
"Status": "voter",
"Meta": {
"foo": "bar",
"upgrade": "1.0.0",
"zone": "zone2"
},
"NodeType": "zone-voter"
},
"746b8782-ce77-41fd-bce0-cce62cca62b4": {
"ID": "746b8782-ce77-41fd-bce0-cce62cca62b4",
"Name": "node6",
"Address": "198.18.0.6:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone3",
"UpgradeVersion": "1.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "1.0.0",
"zone": "zone3"
},
"NodeType": "zone-standby"
},
"79324811-9588-4311-b208-f272e38aaabf": {
"ID": "79324811-9588-4311-b208-f272e38aaabf",
"Name": "node1",
"Address": "198.18.0.1:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "0s",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone1",
"UpgradeVersion": "1.0.0",
"ReadReplica": false,
"Status": "leader",
"Meta": {
"foo": "bar",
"upgrade": "1.0.0",
"zone": "zone1"
},
"NodeType": "zone-voter"
},
"7b02a615-ccce-4251-bda8-b89e0bd4f7c7": {
"ID": "7b02a615-ccce-4251-bda8-b89e0bd4f7c7",
"Name": "read-replica",
"Address": "198.18.0.7:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "2ms",
"LastTerm": 3,
"LastIndex": 39,
"Healthy": true,
"StableSince": "2020-11-06T14:53:00Z",
"UpgradeVersion": "1.0.0",
"ReadReplica": true,
"Status": "non-voter",
"Meta": {
"baz": "foo",
"version": "1.0.0"
},
"NodeType": "read-replica"
},
"98dfd0fd-504e-4280-8e73-6983a6af1b8c": {
"ID": "98dfd0fd-504e-4280-8e73-6983a6af1b8c",
"Name": "node5.new",
"Address": "198.18.1.5:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone3",
"UpgradeVersion": "2.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"foo": "bar",
"upgrade": "2.0.0",
"zone": "zone3"
},
"NodeType": "zone-standby"
},
"997b0851-37c5-4d65-a477-a8b3a56eea42": {
"ID": "997b0851-37c5-4d65-a477-a8b3a56eea42",
"Name": "node3.new",
"Address": "198.18.1.3:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone2",
"UpgradeVersion": "2.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"foo": "bar",
"upgrade": "2.0.0",
"zone": "zone2"
},
"NodeType": "zone-standby"
},
"de95799e-15a4-4c86-b508-78840554b7cb": {
"ID": "de95799e-15a4-4c86-b508-78840554b7cb",
"Name": "node1.new",
"Address": "198.18.1.1:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "0s",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone1",
"UpgradeVersion": "2.0.0",
"ReadReplica": false,
"Status": "non-voter",
"Meta": {
"foo": "bar",
"upgrade": "2.0.0",
"zone": "zone1"
},
"NodeType": "zone-standby"
}
},
"Leader": "79324811-9588-4311-b208-f272e38aaabf",
"Voters": [
"79324811-9588-4311-b208-f272e38aaabf",
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d",
"4c42fe86-321d-4e8f-be0d-c261e6cfc453"
],
"ReadReplicas": [
"7b02a615-ccce-4251-bda8-b89e0bd4f7c7"
],
"RedundancyZones": {
"zone1": {
"Servers": [
"79324811-9588-4311-b208-f272e38aaabf",
"6ca5a41c-6162-41c1-b4eb-09082efe206f",
"de95799e-15a4-4c86-b508-78840554b7cb",
"3044d109-f028-4489-b59c-f267afe408f2"
],
"Voters": [
"79324811-9588-4311-b208-f272e38aaabf"
],
"FailureTolerance": 3
},
"zone2": {
"Servers": [
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d",
"1baeb453-ad9e-489a-bbfe-53bee097aec8",
"997b0851-37c5-4d65-a477-a8b3a56eea42",
"4691e516-6989-4a16-8f55-12ff2226a3c9"
],
"Voters": [
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d"
],
"FailureTolerance": 3
},
"zone3": {
"Servers": [
"4c42fe86-321d-4e8f-be0d-c261e6cfc453",
"746b8782-ce77-41fd-bce0-cce62cca62b4",
"98dfd0fd-504e-4280-8e73-6983a6af1b8c",
"1b8044f6-1d25-4a83-9662-acdf404341d2"
],
"Voters": [
"4c42fe86-321d-4e8f-be0d-c261e6cfc453"
],
"FailureTolerance": 3
}
},
"Upgrade": {
"Status": "promoting",
"TargetVersion": "2.0.0",
"TargetVersionNonVoters": [
"de95799e-15a4-4c86-b508-78840554b7cb",
"3044d109-f028-4489-b59c-f267afe408f2",
"997b0851-37c5-4d65-a477-a8b3a56eea42",
"4691e516-6989-4a16-8f55-12ff2226a3c9",
"98dfd0fd-504e-4280-8e73-6983a6af1b8c",
"1b8044f6-1d25-4a83-9662-acdf404341d2"
],
"OtherVersionVoters": [
"79324811-9588-4311-b208-f272e38aaabf",
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d",
"4c42fe86-321d-4e8f-be0d-c261e6cfc453"
],
"OtherVersionNonVoters": [
"6ca5a41c-6162-41c1-b4eb-09082efe206f",
"1baeb453-ad9e-489a-bbfe-53bee097aec8",
"746b8782-ce77-41fd-bce0-cce62cca62b4"
],
"OtherVersionReadReplicas": [
"7b02a615-ccce-4251-bda8-b89e0bd4f7c7"
],
"RedundancyZones": {
"zone1": {
"TargetVersionNonVoters": [
"de95799e-15a4-4c86-b508-78840554b7cb",
"3044d109-f028-4489-b59c-f267afe408f2"
],
"OtherVersionVoters": [
"79324811-9588-4311-b208-f272e38aaabf"
],
"OtherVersionNonVoters": [
"6ca5a41c-6162-41c1-b4eb-09082efe206f"
]
},
"zone2": {
"TargetVersionNonVoters": [
"997b0851-37c5-4d65-a477-a8b3a56eea42",
"4691e516-6989-4a16-8f55-12ff2226a3c9"
],
"OtherVersionVoters": [
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d"
],
"OtherVersionNonVoters": [
"1baeb453-ad9e-489a-bbfe-53bee097aec8"
]
},
"zone3": {
"TargetVersionNonVoters": [
"98dfd0fd-504e-4280-8e73-6983a6af1b8c",
"1b8044f6-1d25-4a83-9662-acdf404341d2"
],
"OtherVersionVoters": [
"4c42fe86-321d-4e8f-be0d-c261e6cfc453"
],
"OtherVersionNonVoters": [
"746b8782-ce77-41fd-bce0-cce62cca62b4"
]
}
}
}
}

View File

@ -0,0 +1,279 @@
Healthy: true
Failure Tolerance: 1
Optimistic Failure Tolerance: 0
Leader: 79324811-9588-4311-b208-f272e38aaabf
Voters:
79324811-9588-4311-b208-f272e38aaabf
6e8a37e5-a1c4-4212-a75e-91487d8cda6d
4c42fe86-321d-4e8f-be0d-c261e6cfc453
Read Replicas:
7b02a615-ccce-4251-bda8-b89e0bd4f7c7
Redundancy Zones:
zone1:
Failure Tolerance: 3
Voters:
79324811-9588-4311-b208-f272e38aaabf
Servers:
79324811-9588-4311-b208-f272e38aaabf
6ca5a41c-6162-41c1-b4eb-09082efe206f
de95799e-15a4-4c86-b508-78840554b7cb
3044d109-f028-4489-b59c-f267afe408f2
zone2:
Failure Tolerance: 3
Voters:
6e8a37e5-a1c4-4212-a75e-91487d8cda6d
Servers:
6e8a37e5-a1c4-4212-a75e-91487d8cda6d
1baeb453-ad9e-489a-bbfe-53bee097aec8
997b0851-37c5-4d65-a477-a8b3a56eea42
4691e516-6989-4a16-8f55-12ff2226a3c9
zone3:
Failure Tolerance: 3
Voters:
4c42fe86-321d-4e8f-be0d-c261e6cfc453
Servers:
4c42fe86-321d-4e8f-be0d-c261e6cfc453
746b8782-ce77-41fd-bce0-cce62cca62b4
98dfd0fd-504e-4280-8e73-6983a6af1b8c
1b8044f6-1d25-4a83-9662-acdf404341d2
Upgrade:
Status: promoting
Target Version: 2.0.0
Target Version Non-Voters:
de95799e-15a4-4c86-b508-78840554b7cb
3044d109-f028-4489-b59c-f267afe408f2
997b0851-37c5-4d65-a477-a8b3a56eea42
4691e516-6989-4a16-8f55-12ff2226a3c9
98dfd0fd-504e-4280-8e73-6983a6af1b8c
1b8044f6-1d25-4a83-9662-acdf404341d2
Other Version Voters:
79324811-9588-4311-b208-f272e38aaabf
6e8a37e5-a1c4-4212-a75e-91487d8cda6d
4c42fe86-321d-4e8f-be0d-c261e6cfc453
Other Version Non-Voters:
6ca5a41c-6162-41c1-b4eb-09082efe206f
1baeb453-ad9e-489a-bbfe-53bee097aec8
746b8782-ce77-41fd-bce0-cce62cca62b4
Other Version ReadReplicas:
7b02a615-ccce-4251-bda8-b89e0bd4f7c7
Servers:
1b8044f6-1d25-4a83-9662-acdf404341d2
Name: node6.new
Address: 198.18.1.6:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 41
Redundancy Zone: zone3
Upgrade Version: 2.0.0
Meta
"bar": "baz"
"upgrade": "2.0.0"
"zone": "zone3"
1baeb453-ad9e-489a-bbfe-53bee097aec8
Name: node4
Address: 198.18.0.4:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 41
Redundancy Zone: zone2
Upgrade Version: 1.0.0
Meta
"bar": "baz"
"upgrade": "1.0.0"
"zone": "zone2"
3044d109-f028-4489-b59c-f267afe408f2
Name: node2.new
Address: 198.18.1.2:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 41
Redundancy Zone: zone1
Upgrade Version: 2.0.0
Meta
"bar": "baz"
"upgrade": "2.0.0"
"zone": "zone1"
4691e516-6989-4a16-8f55-12ff2226a3c9
Name: node4.new
Address: 198.18.1.4:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 41
Redundancy Zone: zone2
Upgrade Version: 2.0.0
Meta
"bar": "baz"
"upgrade": "2.0.0"
"zone": "zone2"
4c42fe86-321d-4e8f-be0d-c261e6cfc453
Name: node5
Address: 198.18.0.5:8300
Version: 1.9.0
Status: voter
Node Type: zone-voter
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 42
Redundancy Zone: zone3
Upgrade Version: 1.0.0
Meta
"foo": "bar"
"upgrade": "1.0.0"
"zone": "zone3"
6ca5a41c-6162-41c1-b4eb-09082efe206f
Name: node2
Address: 198.18.0.2:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 41
Redundancy Zone: zone1
Upgrade Version: 1.0.0
Meta
"bar": "baz"
"upgrade": "1.0.0"
"zone": "zone1"
6e8a37e5-a1c4-4212-a75e-91487d8cda6d
Name: node3
Address: 198.18.0.3:8300
Version: 1.9.0
Status: voter
Node Type: zone-voter
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 42
Redundancy Zone: zone2
Upgrade Version: 1.0.0
Meta
"foo": "bar"
"upgrade": "1.0.0"
"zone": "zone2"
746b8782-ce77-41fd-bce0-cce62cca62b4
Name: node6
Address: 198.18.0.6:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 41
Redundancy Zone: zone3
Upgrade Version: 1.0.0
Meta
"bar": "baz"
"upgrade": "1.0.0"
"zone": "zone3"
79324811-9588-4311-b208-f272e38aaabf
Name: node1
Address: 198.18.0.1:8300
Version: 1.9.0
Status: leader
Node Type: zone-voter
Node Status: alive
Healthy: true
Last Contact: 0s
Last Term: 3
Last Index: 42
Redundancy Zone: zone1
Upgrade Version: 1.0.0
Meta
"foo": "bar"
"upgrade": "1.0.0"
"zone": "zone1"
7b02a615-ccce-4251-bda8-b89e0bd4f7c7
Name: read-replica
Address: 198.18.0.7:8300
Version: 1.9.0
Status: non-voter
Node Type: read-replica
Node Status: alive
Healthy: true
Last Contact: 2ms
Last Term: 3
Last Index: 39
Upgrade Version: 1.0.0
Read Replica: true
Meta
"baz": "foo"
"version": "1.0.0"
98dfd0fd-504e-4280-8e73-6983a6af1b8c
Name: node5.new
Address: 198.18.1.5:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 42
Redundancy Zone: zone3
Upgrade Version: 2.0.0
Meta
"foo": "bar"
"upgrade": "2.0.0"
"zone": "zone3"
997b0851-37c5-4d65-a477-a8b3a56eea42
Name: node3.new
Address: 198.18.1.3:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 42
Redundancy Zone: zone2
Upgrade Version: 2.0.0
Meta
"foo": "bar"
"upgrade": "2.0.0"
"zone": "zone2"
de95799e-15a4-4c86-b508-78840554b7cb
Name: node1.new
Address: 198.18.1.1:8300
Version: 1.9.0
Status: non-voter
Node Type: zone-standby
Node Status: alive
Healthy: true
Last Contact: 0s
Last Term: 3
Last Index: 42
Redundancy Zone: zone1
Upgrade Version: 2.0.0
Meta
"foo": "bar"
"upgrade": "2.0.0"
"zone": "zone1"

View File

@ -0,0 +1,389 @@
{
"Healthy": true,
"FailureTolerance": 1,
"OptimisitcFailureTolerance": 10,
"Servers": {
"1b8044f6-1d25-4a83-9662-acdf404341d2": {
"ID": "1b8044f6-1d25-4a83-9662-acdf404341d2",
"Name": "node6.new",
"Address": "198.18.1.6:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone3",
"UpgradeVersion": "2.0.0",
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "2.0.0",
"zone": "zone3"
},
"NodeType": "zone-standby"
},
"1baeb453-ad9e-489a-bbfe-53bee097aec8": {
"ID": "1baeb453-ad9e-489a-bbfe-53bee097aec8",
"Name": "node4",
"Address": "198.18.0.4:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone2",
"UpgradeVersion": "1.0.0",
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "1.0.0",
"zone": "zone2"
},
"NodeType": "zone-standby"
},
"3044d109-f028-4489-b59c-f267afe408f2": {
"ID": "3044d109-f028-4489-b59c-f267afe408f2",
"Name": "node2.new",
"Address": "198.18.1.2:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone1",
"UpgradeVersion": "2.0.0",
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "2.0.0",
"zone": "zone1"
},
"NodeType": "zone-standby"
},
"4691e516-6989-4a16-8f55-12ff2226a3c9": {
"ID": "4691e516-6989-4a16-8f55-12ff2226a3c9",
"Name": "node4.new",
"Address": "198.18.1.4:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone2",
"UpgradeVersion": "2.0.0",
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "2.0.0",
"zone": "zone2"
},
"NodeType": "zone-standby"
},
"4c42fe86-321d-4e8f-be0d-c261e6cfc453": {
"ID": "4c42fe86-321d-4e8f-be0d-c261e6cfc453",
"Name": "node5",
"Address": "198.18.0.5:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone3",
"UpgradeVersion": "1.0.0",
"Status": "voter",
"Meta": {
"foo": "bar",
"upgrade": "1.0.0",
"zone": "zone3"
},
"NodeType": "zone-voter"
},
"6ca5a41c-6162-41c1-b4eb-09082efe206f": {
"ID": "6ca5a41c-6162-41c1-b4eb-09082efe206f",
"Name": "node2",
"Address": "198.18.0.2:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone1",
"UpgradeVersion": "1.0.0",
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "1.0.0",
"zone": "zone1"
},
"NodeType": "zone-standby"
},
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d": {
"ID": "6e8a37e5-a1c4-4212-a75e-91487d8cda6d",
"Name": "node3",
"Address": "198.18.0.3:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone2",
"UpgradeVersion": "1.0.0",
"Status": "voter",
"Meta": {
"foo": "bar",
"upgrade": "1.0.0",
"zone": "zone2"
},
"NodeType": "zone-voter"
},
"746b8782-ce77-41fd-bce0-cce62cca62b4": {
"ID": "746b8782-ce77-41fd-bce0-cce62cca62b4",
"Name": "node6",
"Address": "198.18.0.6:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"RedundancyZone": "zone3",
"UpgradeVersion": "1.0.0",
"Status": "non-voter",
"Meta": {
"bar": "baz",
"upgrade": "1.0.0",
"zone": "zone3"
},
"NodeType": "zone-standby"
},
"79324811-9588-4311-b208-f272e38aaabf": {
"ID": "79324811-9588-4311-b208-f272e38aaabf",
"Name": "node1",
"Address": "198.18.0.1:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "0s",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone1",
"UpgradeVersion": "1.0.0",
"Status": "leader",
"Meta": {
"foo": "bar",
"upgrade": "1.0.0",
"zone": "zone1"
},
"NodeType": "zone-voter"
},
"7b02a615-ccce-4251-bda8-b89e0bd4f7c7": {
"ID": "7b02a615-ccce-4251-bda8-b89e0bd4f7c7",
"Name": "read-replica",
"Address": "198.18.0.7:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "2ms",
"LastTerm": 3,
"LastIndex": 39,
"Healthy": true,
"StableSince": "2020-11-06T14:53:00Z",
"UpgradeVersion": "1.0.0",
"ReadReplica": true,
"Status": "non-voter",
"Meta": {
"baz": "foo",
"version": "1.0.0"
},
"NodeType": "read-replica"
},
"98dfd0fd-504e-4280-8e73-6983a6af1b8c": {
"ID": "98dfd0fd-504e-4280-8e73-6983a6af1b8c",
"Name": "node5.new",
"Address": "198.18.1.5:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone3",
"UpgradeVersion": "2.0.0",
"Status": "non-voter",
"Meta": {
"foo": "bar",
"upgrade": "2.0.0",
"zone": "zone3"
},
"NodeType": "zone-standby"
},
"997b0851-37c5-4d65-a477-a8b3a56eea42": {
"ID": "997b0851-37c5-4d65-a477-a8b3a56eea42",
"Name": "node3.new",
"Address": "198.18.1.3:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone2",
"UpgradeVersion": "2.0.0",
"Status": "non-voter",
"Meta": {
"foo": "bar",
"upgrade": "2.0.0",
"zone": "zone2"
},
"NodeType": "zone-standby"
},
"de95799e-15a4-4c86-b508-78840554b7cb": {
"ID": "de95799e-15a4-4c86-b508-78840554b7cb",
"Name": "node1.new",
"Address": "198.18.1.1:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "0s",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"RedundancyZone": "zone1",
"UpgradeVersion": "2.0.0",
"Status": "non-voter",
"Meta": {
"foo": "bar",
"upgrade": "2.0.0",
"zone": "zone1"
},
"NodeType": "zone-standby"
}
},
"Leader": "79324811-9588-4311-b208-f272e38aaabf",
"Voters": [
"79324811-9588-4311-b208-f272e38aaabf",
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d",
"4c42fe86-321d-4e8f-be0d-c261e6cfc453"
],
"ReadReplicas": [
"7b02a615-ccce-4251-bda8-b89e0bd4f7c7"
],
"RedundancyZones": {
"zone1": {
"Servers": [
"79324811-9588-4311-b208-f272e38aaabf",
"6ca5a41c-6162-41c1-b4eb-09082efe206f",
"de95799e-15a4-4c86-b508-78840554b7cb",
"3044d109-f028-4489-b59c-f267afe408f2"
],
"Voters": [
"79324811-9588-4311-b208-f272e38aaabf"
],
"FailureTolerance": 3
},
"zone2": {
"Servers": [
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d",
"1baeb453-ad9e-489a-bbfe-53bee097aec8",
"997b0851-37c5-4d65-a477-a8b3a56eea42",
"4691e516-6989-4a16-8f55-12ff2226a3c9"
],
"Voters": [
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d"
],
"FailureTolerance": 3
},
"zone3": {
"Servers": [
"4c42fe86-321d-4e8f-be0d-c261e6cfc453",
"746b8782-ce77-41fd-bce0-cce62cca62b4",
"98dfd0fd-504e-4280-8e73-6983a6af1b8c",
"1b8044f6-1d25-4a83-9662-acdf404341d2"
],
"Voters": [
"4c42fe86-321d-4e8f-be0d-c261e6cfc453"
],
"FailureTolerance": 3
}
},
"Upgrade": {
"Status": "promoting",
"TargetVersion": "2.0.0",
"TargetVersionNonVoters": [
"de95799e-15a4-4c86-b508-78840554b7cb",
"3044d109-f028-4489-b59c-f267afe408f2",
"997b0851-37c5-4d65-a477-a8b3a56eea42",
"4691e516-6989-4a16-8f55-12ff2226a3c9",
"98dfd0fd-504e-4280-8e73-6983a6af1b8c",
"1b8044f6-1d25-4a83-9662-acdf404341d2"
],
"OtherVersionVoters": [
"79324811-9588-4311-b208-f272e38aaabf",
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d",
"4c42fe86-321d-4e8f-be0d-c261e6cfc453"
],
"OtherVersionNonVoters": [
"6ca5a41c-6162-41c1-b4eb-09082efe206f",
"1baeb453-ad9e-489a-bbfe-53bee097aec8",
"746b8782-ce77-41fd-bce0-cce62cca62b4"
],
"OtherVersionReadReplicas": [
"7b02a615-ccce-4251-bda8-b89e0bd4f7c7"
],
"RedundancyZones": {
"zone1": {
"TargetVersionNonVoters": [
"de95799e-15a4-4c86-b508-78840554b7cb",
"3044d109-f028-4489-b59c-f267afe408f2"
],
"OtherVersionVoters": [
"79324811-9588-4311-b208-f272e38aaabf"
],
"OtherVersionNonVoters": [
"6ca5a41c-6162-41c1-b4eb-09082efe206f"
]
},
"zone2": {
"TargetVersionNonVoters": [
"997b0851-37c5-4d65-a477-a8b3a56eea42",
"4691e516-6989-4a16-8f55-12ff2226a3c9"
],
"OtherVersionVoters": [
"6e8a37e5-a1c4-4212-a75e-91487d8cda6d"
],
"OtherVersionNonVoters": [
"1baeb453-ad9e-489a-bbfe-53bee097aec8"
]
},
"zone3": {
"TargetVersionNonVoters": [
"98dfd0fd-504e-4280-8e73-6983a6af1b8c",
"1b8044f6-1d25-4a83-9662-acdf404341d2"
],
"OtherVersionVoters": [
"4c42fe86-321d-4e8f-be0d-c261e6cfc453"
],
"OtherVersionNonVoters": [
"746b8782-ce77-41fd-bce0-cce62cca62b4"
]
}
}
}
}

View File

@ -0,0 +1,67 @@
{
"Healthy": true,
"FailureTolerance": 1,
"OptimisticFailureTolerance": 0,
"Servers": {
"79324811-9588-4311-b208-f272e38aaabf": {
"ID": "79324811-9588-4311-b208-f272e38aaabf",
"Name": "node1",
"Address": "198.18.0.1:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "0s",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"ReadReplica": false,
"Status": "leader",
"Meta": {
"foo": "bar"
},
"NodeType": "voter"
},
"ae84aefb-a303-4734-8739-5c102d4ee2d9": {
"ID": "ae84aefb-a303-4734-8739-5c102d4ee2d9",
"Name": "node3",
"Address": "198.18.0.3:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "2ms",
"LastTerm": 3,
"LastIndex": 39,
"Healthy": true,
"StableSince": "2020-11-06T14:53:00Z",
"ReadReplica": false,
"Status": "voter",
"Meta": {
"baz": "foo"
},
"NodeType": "voter"
},
"ef8aee9a-f9d6-4ec4-b383-aac956bdb80f": {
"ID": "ef8aee9a-f9d6-4ec4-b383-aac956bdb80f",
"Name": "node2",
"Address": "198.18.0.2:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"ReadReplica": false,
"Status": "voter",
"Meta": {
"bar": "baz"
},
"NodeType": "voter"
}
},
"Leader": "79324811-9588-4311-b208-f272e38aaabf",
"Voters": [
"79324811-9588-4311-b208-f272e38aaabf",
"ef8aee9a-f9d6-4ec4-b383-aac956bdb80f",
"ae84aefb-a303-4734-8739-5c102d4ee2d9"
]
}

View File

@ -0,0 +1,48 @@
Healthy: true
Failure Tolerance: 1
Optimistic Failure Tolerance: 0
Leader: 79324811-9588-4311-b208-f272e38aaabf
Voters:
79324811-9588-4311-b208-f272e38aaabf
ef8aee9a-f9d6-4ec4-b383-aac956bdb80f
ae84aefb-a303-4734-8739-5c102d4ee2d9
Servers:
79324811-9588-4311-b208-f272e38aaabf
Name: node1
Address: 198.18.0.1:8300
Version: 1.9.0
Status: leader
Node Type: voter
Node Status: alive
Healthy: true
Last Contact: 0s
Last Term: 3
Last Index: 42
Meta
"foo": "bar"
ae84aefb-a303-4734-8739-5c102d4ee2d9
Name: node3
Address: 198.18.0.3:8300
Version: 1.9.0
Status: voter
Node Type: voter
Node Status: alive
Healthy: true
Last Contact: 2ms
Last Term: 3
Last Index: 39
Meta
"baz": "foo"
ef8aee9a-f9d6-4ec4-b383-aac956bdb80f
Name: node2
Address: 198.18.0.2:8300
Version: 1.9.0
Status: voter
Node Type: voter
Node Status: alive
Healthy: true
Last Contact: 1ms
Last Term: 3
Last Index: 41
Meta
"bar": "baz"

View File

@ -0,0 +1,64 @@
{
"Healthy": true,
"FailureTolerance": 1,
"OptimisitcFailureTolerance": 0,
"Servers": {
"79324811-9588-4311-b208-f272e38aaabf": {
"ID": "79324811-9588-4311-b208-f272e38aaabf",
"Name": "node1",
"Address": "198.18.0.1:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "0s",
"LastTerm": 3,
"LastIndex": 42,
"Healthy": true,
"StableSince": "2020-11-06T14:51:00Z",
"Status": "leader",
"Meta": {
"foo": "bar"
},
"NodeType": "voter"
},
"ae84aefb-a303-4734-8739-5c102d4ee2d9": {
"ID": "ae84aefb-a303-4734-8739-5c102d4ee2d9",
"Name": "node3",
"Address": "198.18.0.3:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "2ms",
"LastTerm": 3,
"LastIndex": 39,
"Healthy": true,
"StableSince": "2020-11-06T14:53:00Z",
"Status": "voter",
"Meta": {
"baz": "foo"
},
"NodeType": "voter"
},
"ef8aee9a-f9d6-4ec4-b383-aac956bdb80f": {
"ID": "ef8aee9a-f9d6-4ec4-b383-aac956bdb80f",
"Name": "node2",
"Address": "198.18.0.2:8300",
"NodeStatus": "alive",
"Version": "1.9.0",
"LastContact": "1ms",
"LastTerm": 3,
"LastIndex": 41,
"Healthy": true,
"StableSince": "2020-11-06T14:52:00Z",
"Status": "voter",
"Meta": {
"bar": "baz"
},
"NodeType": "voter"
}
},
"Leader": "79324811-9588-4311-b208-f272e38aaabf",
"Voters": [
"79324811-9588-4311-b208-f272e38aaabf",
"ef8aee9a-f9d6-4ec4-b383-aac956bdb80f",
"ae84aefb-a303-4734-8739-5c102d4ee2d9"
]
}

View File

@ -48,18 +48,31 @@ func (_ *prettyFormatter) Format(info *OutputFormat) (string, error) {
fmt.Fprintf(tw, "\n Term\t%d", info.Meta.Term)
fmt.Fprintf(tw, "\n Version\t%d", info.Meta.Version)
fmt.Fprintf(tw, "\n")
fmt.Fprintln(tw, "\n Type\tCount\tSize\t")
fmt.Fprintf(tw, " %s\t%s\t%s\t", "----", "----", "----")
fmt.Fprintln(tw, "\n Type\tCount\tSize")
fmt.Fprintf(tw, " %s\t%s\t%s", "----", "----", "----")
// For each different type generate new output
for _, s := range info.Stats {
fmt.Fprintf(tw, "\n %s\t%d\t%s\t", s.Name, s.Count, ByteSize(uint64(s.Sum)))
fmt.Fprintf(tw, "\n %s\t%d\t%s", s.Name, s.Count, ByteSize(uint64(s.Sum)))
}
fmt.Fprintf(tw, "\n %s\t%s\t%s", "----", "----", "----")
fmt.Fprintf(tw, "\n Total\t\t%s", ByteSize(uint64(info.TotalSize)))
if info.StatsKV != nil {
fmt.Fprintf(tw, "\n")
fmt.Fprintln(tw, "\n Key Name\tCount\tSize")
fmt.Fprintf(tw, " %s\t%s\t%s", "----", "----", "----")
// For each different type generate new output
for _, s := range info.StatsKV {
fmt.Fprintf(tw, "\n %s\t%d\t%s", s.Name, s.Count, ByteSize(uint64(s.Sum)))
}
fmt.Fprintf(tw, "\n %s\t%s\t%s", "----", "----", "----")
fmt.Fprintf(tw, "\n Total\t\t%s", ByteSize(uint64(info.TotalSizeKV)))
}
fmt.Fprintf(tw, "\n %s\t%s\t%s\t", "----", "----", "----")
fmt.Fprintf(tw, "\n Total\t\t%s\t", ByteSize(uint64(info.TotalSize)))
if err := tw.Flush(); err != nil {
return b.String(), err
}
return b.String(), nil
}

View File

@ -13,6 +13,11 @@ func TestFormat(t *testing.T) {
Sum: 1,
Count: 2,
}}
mkv := []typeStats{{
Name: "msgKV",
Sum: 1,
Count: 2,
}}
info := OutputFormat{
Meta: &MetadataInfo{
ID: "one",
@ -21,8 +26,10 @@ func TestFormat(t *testing.T) {
Term: 4,
Version: 1,
},
Stats: m,
TotalSize: 1,
Stats: m,
StatsKV: mkv,
TotalSize: 1,
TotalSizeKV: 1,
}
formatters := map[string]Formatter{

View File

@ -29,10 +29,21 @@ type cmd struct {
flags *flag.FlagSet
help string
format string
// flags
kvDetails bool
kvDepth int
kvFilter string
}
func (c *cmd) init() {
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
c.flags.BoolVar(&c.kvDetails, "kvdetails", false,
"Provides a detailed KV space usage breakdown for any KV data that's been stored.")
c.flags.IntVar(&c.kvDepth, "kvdepth", 2,
"Can only be used with -kvdetails. The key prefix depth used to breakdown KV store data. Defaults to 2.")
c.flags.StringVar(&c.kvFilter, "kvfilter", "",
"Can only be used with -kvdetails. Limits KV key breakdown using this prefix filter.")
c.flags.StringVar(
&c.format,
"format",
@ -52,12 +63,24 @@ type MetadataInfo struct {
Version raft.SnapshotVersion
}
// SnapshotInfo is used for passing snapshot stat
// information between functions
type SnapshotInfo struct {
Meta MetadataInfo
Stats map[structs.MessageType]typeStats
StatsKV map[string]typeStats
TotalSize int
TotalSizeKV int
}
// OutputFormat is used for passing information
// through the formatter
type OutputFormat struct {
Meta *MetadataInfo
Stats []typeStats
TotalSize int
Meta *MetadataInfo
Stats []typeStats
StatsKV []typeStats
TotalSize int
TotalSizeKV int
}
func (c *cmd) Run(args []string) int {
@ -101,7 +124,7 @@ func (c *cmd) Run(args []string) int {
}
}()
stats, totalSize, err := enhance(readFile)
info, err := c.enhance(readFile)
if err != nil {
c.UI.Error(fmt.Sprintf("Error extracting snapshot data: %s", err))
return 1
@ -122,13 +145,17 @@ func (c *cmd) Run(args []string) int {
}
//Restructures stats given above to be human readable
formattedStats := generatetypeStats(stats)
formattedStats := generateStats(info)
formattedStatsKV := generateKVStats(info)
in := &OutputFormat{
Meta: metaformat,
Stats: formattedStats,
TotalSize: totalSize,
Meta: metaformat,
Stats: formattedStats,
StatsKV: formattedStatsKV,
TotalSize: info.TotalSize,
TotalSizeKV: info.TotalSizeKV,
}
out, err := formatter.Format(in)
if err != nil {
c.UI.Error(err.Error())
@ -145,19 +172,55 @@ type typeStats struct {
Count int
}
func generatetypeStats(info map[structs.MessageType]typeStats) []typeStats {
ss := make([]typeStats, 0, len(info))
// generateStats formats the stats for the output struct
// that's used to produce the printed output the user sees.
func generateStats(info SnapshotInfo) []typeStats {
ss := make([]typeStats, 0, len(info.Stats))
for _, s := range info {
for _, s := range info.Stats {
ss = append(ss, s)
}
// Sort the stat slice
sort.Slice(ss, func(i, j int) bool { return ss[i].Sum > ss[j].Sum })
ss = sortTypeStats(ss)
return ss
}
// generateKVStats reformats the KV stats to work with
// the output struct that's used to produce the printed
// output the user sees.
func generateKVStats(info SnapshotInfo) []typeStats {
kvLen := len(info.StatsKV)
if kvLen > 0 {
ks := make([]typeStats, 0, kvLen)
for _, s := range info.StatsKV {
ks = append(ks, s)
}
ks = sortTypeStats(ks)
return ks
}
return nil
}
// sortTypeStats sorts the stat slice by size and then
// alphabetically in the case the size is identical
func sortTypeStats(stats []typeStats) []typeStats {
sort.Slice(stats, func(i, j int) bool {
// sort alphabetically if size is equal
if stats[i].Sum == stats[j].Sum {
return stats[i].Name < stats[j].Name
}
return stats[i].Sum > stats[j].Sum
})
return stats
}
// countingReader helps keep track of the bytes we have read
// when reading snapshots
type countingReader struct {
@ -175,36 +238,89 @@ func (r *countingReader) Read(p []byte) (n int, err error) {
// enhance utilizes ReadSnapshot to populate the struct with
// all of the snapshot's itemized data
func enhance(file io.Reader) (map[structs.MessageType]typeStats, int, error) {
stats := make(map[structs.MessageType]typeStats)
func (c *cmd) enhance(file io.Reader) (SnapshotInfo, error) {
info := SnapshotInfo{
Stats: make(map[structs.MessageType]typeStats),
StatsKV: make(map[string]typeStats),
TotalSize: 0,
TotalSizeKV: 0,
}
cr := &countingReader{wrappedReader: file}
totalSize := 0
handler := func(header *fsm.SnapshotHeader, msg structs.MessageType, dec *codec.Decoder) error {
name := structs.MessageType.String(msg)
s := stats[msg]
s := info.Stats[msg]
if s.Name == "" {
s.Name = name
}
var val interface{}
err := dec.Decode(&val)
if err != nil {
return fmt.Errorf("failed to decode msg type %v, error %v", name, err)
}
size := cr.read - totalSize
size := cr.read - info.TotalSize
s.Sum += size
s.Count++
totalSize = cr.read
stats[msg] = s
info.TotalSize = cr.read
info.Stats[msg] = s
c.kvEnhance(s.Name, val, size, &info)
return nil
}
if err := fsm.ReadSnapshot(cr, handler); err != nil {
return nil, 0, err
return info, err
}
return stats, totalSize, nil
return info, nil
}
// kvEnhance populates the struct with all of the snapshot's
// size information for KV data stored in it
func (c *cmd) kvEnhance(keyType string, val interface{}, size int, info *SnapshotInfo) {
if c.kvDetails {
if keyType != "KVS" {
return
}
// have to coerce this into a usable type here or this won't work
keyVal := val.(map[string]interface{})
for k, v := range keyVal {
// we only care about the entry on the key specifically
// related to the key name, so skip all others
if k != "Key" {
continue
}
// check for whether a filter is specified. if it is, skip
// any keys that don't match.
if len(c.kvFilter) > 0 && !strings.HasPrefix(v.(string), c.kvFilter) {
break
}
split := strings.Split(v.(string), "/")
// handle the situation where the key is shorter than
// the specified depth.
actualDepth := c.kvDepth
if c.kvDepth > len(split) {
actualDepth = len(split)
}
prefix := strings.Join(split[0:actualDepth], "/")
kvs := info.StatsKV[prefix]
if kvs.Name == "" {
kvs.Name = prefix
}
kvs.Sum += size
kvs.Count++
info.TotalSizeKV += size
info.StatsKV[prefix] = kvs
}
}
}
func (c *cmd) Synopsis() string {
return synopsis
}

View File

@ -95,3 +95,57 @@ func TestSnapshotInspectCommand(t *testing.T) {
want := golden(t, t.Name(), ui.OutputWriter.String())
require.Equal(t, want, ui.OutputWriter.String())
}
func TestSnapshotInspectKVDetailsCommand(t *testing.T) {
filepath := "./testdata/backupWithKV.snap"
// Inspect the snapshot
ui := cli.NewMockUi()
c := New(ui)
args := []string{"-kvdetails", filepath}
code := c.Run(args)
if code != 0 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
}
want := golden(t, t.Name(), ui.OutputWriter.String())
require.Equal(t, want, ui.OutputWriter.String())
}
func TestSnapshotInspectKVDetailsDepthCommand(t *testing.T) {
filepath := "./testdata/backupWithKV.snap"
// Inspect the snapshot
ui := cli.NewMockUi()
c := New(ui)
args := []string{"-kvdetails", "-kvdepth", "3", filepath}
code := c.Run(args)
if code != 0 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
}
want := golden(t, t.Name(), ui.OutputWriter.String())
require.Equal(t, want, ui.OutputWriter.String())
}
func TestSnapshotInspectKVDetailsDepthFilterCommand(t *testing.T) {
filepath := "./testdata/backupWithKV.snap"
// Inspect the snapshot
ui := cli.NewMockUi()
c := New(ui)
args := []string{"-kvdetails", "-kvdepth", "3", "-kvfilter", "vault/logical", filepath}
code := c.Run(args)
if code != 0 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
}
want := golden(t, t.Name(), ui.OutputWriter.String())
require.Equal(t, want, ui.OutputWriter.String())
}

View File

@ -4,16 +4,16 @@
Term 2
Version 1
Type Count Size
---- ---- ----
Register 3 1.7KB
ConnectCA 1 1.2KB
ConnectCAProviderState 1 1.1KB
Index 12 344B
Autopilot 1 199B
ConnectCAConfig 1 197B
FederationState 1 139B
SystemMetadata 1 68B
ChunkingState 1 12B
---- ---- ----
Type Count Size
---- ---- ----
Register 3 1.7KB
ConnectCA 1 1.2KB
ConnectCAProviderState 1 1.1KB
Index 12 344B
Autopilot 1 199B
ConnectCAConfig 1 197B
FederationState 1 139B
SystemMetadata 1 68B
ChunkingState 1 12B
---- ---- ----
Total 5KB

View File

@ -4,15 +4,15 @@
Term 2
Version 1
Type Count Size
---- ---- ----
Register 3 1.8KB
ConnectCA 1 1.2KB
ConnectCAProviderState 1 1.1KB
Index 11 313B
ConnectCAConfig 1 247B
Autopilot 1 199B
SystemMetadata 1 68B
ChunkingState 1 12B
---- ---- ----
Type Count Size
---- ---- ----
Register 3 1.8KB
ConnectCA 1 1.2KB
ConnectCAProviderState 1 1.1KB
Index 11 313B
ConnectCAConfig 1 247B
Autopilot 1 199B
SystemMetadata 1 68B
ChunkingState 1 12B
---- ---- ----
Total 5KB

View File

@ -0,0 +1,27 @@
ID 2-12426-1604593650375
Size 17228
Index 12426
Term 2
Version 1
Type Count Size
---- ---- ----
KVS 27 12.3KB
Register 5 3.4KB
Index 11 285B
Autopilot 1 199B
Session 1 199B
CoordinateBatchUpdate 1 166B
Tombstone 2 146B
FederationState 1 139B
ChunkingState 1 12B
---- ---- ----
Total 16.8KB
Key Name Count Size
---- ---- ----
vault/core 16 5.9KB
vault/sys 7 4.4KB
vault/logical 4 2KB
---- ---- ----
Total 12.3KB

View File

@ -0,0 +1,44 @@
ID 2-12426-1604593650375
Size 17228
Index 12426
Term 2
Version 1
Type Count Size
---- ---- ----
KVS 27 12.3KB
Register 5 3.4KB
Index 11 285B
Autopilot 1 199B
Session 1 199B
CoordinateBatchUpdate 1 166B
Tombstone 2 146B
FederationState 1 139B
ChunkingState 1 12B
---- ---- ----
Total 16.8KB
Key Name Count Size
---- ---- ----
vault/sys/policy 3 3.3KB
vault/logical/0989e79e-06cd-5374-c8c0-4c6d675bc1c9 3 1.8KB
vault/core/leader 1 1.6KB
vault/sys/token 3 1KB
vault/core/mounts 1 675B
vault/core/wrapping 1 633B
vault/core/local-mounts 1 450B
vault/core/auth 1 423B
vault/core/cluster 2 388B
vault/core/keyring 1 320B
vault/core/master 1 237B
vault/core/seal-config 1 211B
vault/logical/5c018b68-3573-41d3-0c33-04bce60cd6b0 1 210B
vault/core/hsm 1 189B
vault/core/local-audit 1 185B
vault/core/local-auth 1 183B
vault/core/audit 1 179B
vault/core/lock 1 170B
vault/core/shamir-kek 1 159B
vault/sys/counters 1 155B
---- ---- ----
Total 12.3KB

View File

@ -0,0 +1,26 @@
ID 2-12426-1604593650375
Size 17228
Index 12426
Term 2
Version 1
Type Count Size
---- ---- ----
KVS 27 12.3KB
Register 5 3.4KB
Index 11 285B
Autopilot 1 199B
Session 1 199B
CoordinateBatchUpdate 1 166B
Tombstone 2 146B
FederationState 1 139B
ChunkingState 1 12B
---- ---- ----
Total 16.8KB
Key Name Count Size
---- ---- ----
vault/logical/0989e79e-06cd-5374-c8c0-4c6d675bc1c9 3 1.8KB
vault/logical/5c018b68-3573-41d3-0c33-04bce60cd6b0 1 210B
---- ---- ----
Total 2KB

BIN
command/snapshot/inspect/testdata/backupWithKV.snap (Stored with Git LFS) vendored Normal file

Binary file not shown.

View File

@ -13,5 +13,13 @@
"Count": 2
}
],
"TotalSize": 1
"StatsKV": [
{
"Name": "msgKV",
"Sum": 1,
"Count": 2
}
],
"TotalSize": 1,
"TotalSizeKV": 1
}

View File

@ -4,8 +4,14 @@
Term 4
Version 1
Type Count Size
---- ---- ----
msg 2 1B
---- ---- ----
Total 1B
Type Count Size
---- ---- ----
msg 2 1B
---- ---- ----
Total 1B
Key Name Count Size
---- ---- ----
msgKV 2 1B
---- ---- ----
Total 1B

View File

@ -1,29 +1,16 @@
-----BEGIN CERTIFICATE-----
MIIFADCCAugCCQCPPTSu2adkQzANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTE3MDQxNDE5MTE0MVoXDTIyMDMxOTE5MTE0MVowPzELMAkG
A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxGzAZBgNVBAoMEkNvbnN1bCBU
ZXN0IENsaWVudDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOyJwuXC
N+8sOnFmWDx6w5Xs+ADd905EKkfrdLc0qIcrndZwdCx9zOeArLhUS1aoh0ctwz5x
wBsmUgwIQwr/V6Q1+sAEDmPmnuAiR/m3lxE+ZPr1CyNhrrTqs7jXkRRDNtZevw6d
mTE0FC4Tho016NVXckVZRRJL8Svfk2GvZbZ+1HAIi1a/Du7VQPbd1HOLKPkpb0JU
AksdAaks/avzKhRUoHQBR/T4S9GK95WrCqJIZG6iOVK2cymfZ9t/qYdm5czdq8ho
kWNqOtVb+yBx4/zs8JZI3/TZ/K4nSFcpZNZmPI7xsBL0/GG7nqWyAFWFsn/+4q0a
FIBVUe+ydONKoXKZI080b9CLkNXIp1P8rJsodjKZO/xr+/JCeo6D7ZAJymFe1QXJ
aGt7ac08s9scU97n0iuCRn6RXLdii9pbiAlArmAE1zXQKA/hcyVtjfUdeq0kA1RE
308gUab552XstQUHH0/+JcoPhn1UN+D6UOw7CxVQrq6FBDeDt7S/WtJNGM3GYrQW
tbvTQhDW0jBp0Tr+BwIDXYN5QLM6rcnB2GR3+aYVuvre6DrvisA6yKO+0F29n2LM
DdcUu8ZGzoEyFOZAgcw943VlU8bSxWkANoO0e3gKub4AvOaRqvVKyeFuFnwzKjSE
jiZTHVWs/1XVTnK51b9H33uK976O0q9qGlERAgMBAAEwDQYJKoZIhvcNAQELBQAD
ggIBAAGb44gmBikP4KJOLDDQWUX+l6kfIEobKzulmUcZ5puNS68fnQrWYUXTk5MK
IKBfuQSrxux/CEx1nBIZS/IcAnxG6GpxIcVdtzrZ/2yhDsmT3ZlgMo0+kNgqPnB2
hVe19HO44KAkjKpbjzqya2YX5CY83WBbB0kLvbRtBraU74LDyPD8Nb2YgCKkKZuC
WHtvMw/3bGQ74FXRIjLBv1moOiWaOvaMpztn4GzChZuU8Ikyps3YiRKES2gSvPIj
tynzyoED/ddNA6Q/NeLYaCqwzIVScuqBsDCNTvFqZztS4J7VJxxnRztXb/og2yE4
xqXezVV/O+6HhRPnxInYNiMRS++C/cyeKt150t/YS718KlebL4Pd+fQ4fDQzMyYl
ixEOVFLZiT07heUBkNA//Y0thBr/VbMvvcnMKmm3wLKsL/VGznI5akZhBa0DRj/0
y+dSNJ2CL4I11SQ5yIaVgggqVEJ6wUWpE5woLQuvg+P+QVgkBtGR7H5dmREMq06L
dPWFoBNBil5MNDLAt2eoOauoVdba1XWWW6HZJP9lR/qgFeE9yWAFUB0BwonkYJYM
0Tm9+Jv87nJAy+a1RDszjAMV/N1RvCOb9+2g4NnHSXNVqH+gP0BsVYP5e2A31GX0
aftVp7tPN1bSFt7nFZNbbsFhRD/fQvOpCOC/7pTnXx8zhl/2
MIICnDCCAkOgAwIBAgIRAOnKNzSoGq53Rq/G5tbm85swCgYIKoZIzj0EAwIwgbkx
CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
bzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcw
FQYDVQQKEw5IYXNoaUNvcnAgSW5jLjFAMD4GA1UEAxM3Q29uc3VsIEFnZW50IENB
IDE4NTU3MTQ5MTMzMTA0NzczNDYwMjQyMDcxODI5NjUzMzQzNTQ0MzAeFw0yMDEw
MjgyMjI3NTZaFw0yMTEwMjgyMjI3NTZaMBwxGjAYBgNVBAMTEWNsaWVudC5kYzEu
Y29uc3VsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMtVdDd8tDZBaOaDFFzWD
0hTxO7soxUuz1dWaO8FGhIS07dfSBjYumEOgfNtfOzAILvkBd4gS8DrQZ2Rbks86
iKOBxzCBxDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsG
AQUFBwMBMAwGA1UdEwEB/wQCMAAwKQYDVR0OBCIEIEJWUjlDw7H2fbRpGG8fpqCq
GEX80iDpQqXOU0wg6fEPMCsGA1UdIwQkMCKAIAu+td60D/Er7Xjtyg0B6XflfKYm
IdXjPfiFy8SGeKS2MC0GA1UdEQQmMCSCEWNsaWVudC5kYzEuY29uc3Vsgglsb2Nh
bGhvc3SHBH8AAAEwCgYIKoZIzj0EAwIDRwAwRAIgYAZTf8VcZ4nQl4lbm579BfXy
6YpYz/DdfkEODUBxUyYCIDXhfmxtL/gTSkIh1E+fV7H7ZmqPKgTDH1XBV2zYnj/C
-----END CERTIFICATE-----

View File

@ -1,27 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIEhDCCAmwCAQAwPzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
GzAZBgNVBAoMEkNvbnN1bCBUZXN0IENsaWVudDCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAOyJwuXCN+8sOnFmWDx6w5Xs+ADd905EKkfrdLc0qIcrndZw
dCx9zOeArLhUS1aoh0ctwz5xwBsmUgwIQwr/V6Q1+sAEDmPmnuAiR/m3lxE+ZPr1
CyNhrrTqs7jXkRRDNtZevw6dmTE0FC4Tho016NVXckVZRRJL8Svfk2GvZbZ+1HAI
i1a/Du7VQPbd1HOLKPkpb0JUAksdAaks/avzKhRUoHQBR/T4S9GK95WrCqJIZG6i
OVK2cymfZ9t/qYdm5czdq8hokWNqOtVb+yBx4/zs8JZI3/TZ/K4nSFcpZNZmPI7x
sBL0/GG7nqWyAFWFsn/+4q0aFIBVUe+ydONKoXKZI080b9CLkNXIp1P8rJsodjKZ
O/xr+/JCeo6D7ZAJymFe1QXJaGt7ac08s9scU97n0iuCRn6RXLdii9pbiAlArmAE
1zXQKA/hcyVtjfUdeq0kA1RE308gUab552XstQUHH0/+JcoPhn1UN+D6UOw7CxVQ
rq6FBDeDt7S/WtJNGM3GYrQWtbvTQhDW0jBp0Tr+BwIDXYN5QLM6rcnB2GR3+aYV
uvre6DrvisA6yKO+0F29n2LMDdcUu8ZGzoEyFOZAgcw943VlU8bSxWkANoO0e3gK
ub4AvOaRqvVKyeFuFnwzKjSEjiZTHVWs/1XVTnK51b9H33uK976O0q9qGlERAgMB
AAGgADANBgkqhkiG9w0BAQsFAAOCAgEARWD26zR/JuyER2SmCq0GYc4i5bTmQOZw
BcToQsYQcfTrdZspzKhb4EM560tVqE8V6Ckkl2V97ZbbD7KIRgY6LEsCcr6VCfU5
rSiyHo1M/PDnK6FqYE0qTqqu3jOGmh97RAZi8REy8gfT/PrE8kDaEVfm0nCB8aaW
8wg6ncmStRLKJB4QP1YDpcQH568MMY/0E7xUixp1KjaA4VwE4QEFNoxmpp/Dwpzw
BB6kjlKM2tsjkbm0EHCPU5creAoqPNVQNCIVTDBUgkwhUrU2ljAjlC24tL66L0FP
RKmyk8yWR2PQkkkWlRwarypGmOAE0GMVMICNh5WTaPaa1XTTpZme3ctpZQZO3LRT
Bao4tXM3pA8vApCbkqExR1ow86HvOHv50Y+LQpD1fk7cj18npCR831f1cr/jQw6V
p4Z6bdftF38ZCsCdjUHXbtc3tA0220I+0e8ng1znAfMPQC+KVrHaapt1OFWGHPFk
KDLyxtZN1IOEOkjtI23KOUlEtG4jnm5UjFG43EXA3qmbUrHNCsHhRpHhMBqu4a0Z
ifPQ5+YFyUCmgVUSGeGmpnyxWalmwOT2Ygd/uhn7TnZ0GAv+x1tLHNe78Q8YKi9a
gw4zZcDFJmCPnVkfQ+lVSSY5GjSPI8DZCfr+IJBIPOiknTGP9g2dEbUX8xVyEfK6
18IjYxoATJE=
-----END CERTIFICATE REQUEST-----

View File

@ -1,51 +1,5 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIDxDVYnUL3LCN7kSKF/ShH1c8HacmeUyU/2qJ/fo+5kDoAoGCCqGSM49
AwEHoUQDQgAEMtVdDd8tDZBaOaDFFzWD0hTxO7soxUuz1dWaO8FGhIS07dfSBjYu
mEOgfNtfOzAILvkBd4gS8DrQZ2Rbks86iA==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEINtFYGWAzcVyRRQKjadE83olH8xAwZYe5sEn4rfPtI8xoAoGCCqGSM49
AwEHoUQDQgAErHueX3t67iU5Bj7Nh53zhggnF4pLwjuDbmTDSYIe/Tbeixc2M2Nb
7cGr9/Bk9cH8exB/o2KzbQ2nxPZ+ftBTAQ==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC7zCCApSgAwIBAgIRAIubxOonau4Z6UJRYv5KBDMwCgYIKoZIzj0EAwIwgbkx
CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
bzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcw
FQYDVQQKEw5IYXNoaUNvcnAgSW5jLjFAMD4GA1UEAxM3Q29uc3VsIEFnZW50IENB
IDE4NTU3MTQ5MTMzMTA0NzczNDYwMjQyMDcxODI5NjUzMzQzNTQ0MzAeFw0yMDEw
MjgyMjI3NTZaFw0yNTEwMjcyMjI3NTZaMIG5MQswCQYDVQQGEwJVUzELMAkGA1UE
CBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xGjAYBgNVBAkTETEwMSBTZWNv
bmQgU3RyZWV0MQ4wDAYDVQQREwU5NDEwNTEXMBUGA1UEChMOSGFzaGlDb3JwIElu
Yy4xQDA+BgNVBAMTN0NvbnN1bCBBZ2VudCBDQSAxODU1NzE0OTEzMzEwNDc3MzQ2
MDI0MjA3MTgyOTY1MzM0MzU0NDMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASs
e55fe3ruJTkGPs2HnfOGCCcXikvCO4NuZMNJgh79Nt6LFzYzY1vtwav38GT1wfx7
EH+jYrNtDafE9n5+0FMBo3sweTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUw
AwEB/zApBgNVHQ4EIgQgC7613rQP8SvteO3KDQHpd+V8piYh1eM9+IXLxIZ4pLYw
KwYDVR0jBCQwIoAgC7613rQP8SvteO3KDQHpd+V8piYh1eM9+IXLxIZ4pLYwCgYI
KoZIzj0EAwIDSQAwRgIhALoE4RO8DHR4AkxmO5ostQxAYMIpiSTC9VZsWva3hHj4
AiEAijGw7bHPearXh9I2ghGE4jGJbGK4R9JHcLOq3+GE2Ng=
-----END CERTIFICATE-----

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIDxDVYnUL3LCN7kSKF/ShH1c8HacmeUyU/2qJ/fo+5kDoAoGCCqGSM49
AwEHoUQDQgAEMtVdDd8tDZBaOaDFFzWD0hTxO7soxUuz1dWaO8FGhIS07dfSBjYu
mEOgfNtfOzAILvkBd4gS8DrQZ2Rbks86iA==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICnDCCAkOgAwIBAgIRAOnKNzSoGq53Rq/G5tbm85swCgYIKoZIzj0EAwIwgbkx
CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
bzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcw
FQYDVQQKEw5IYXNoaUNvcnAgSW5jLjFAMD4GA1UEAxM3Q29uc3VsIEFnZW50IENB
IDE4NTU3MTQ5MTMzMTA0NzczNDYwMjQyMDcxODI5NjUzMzQzNTQ0MzAeFw0yMDEw
MjgyMjI3NTZaFw0yMTEwMjgyMjI3NTZaMBwxGjAYBgNVBAMTEWNsaWVudC5kYzEu
Y29uc3VsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMtVdDd8tDZBaOaDFFzWD
0hTxO7soxUuz1dWaO8FGhIS07dfSBjYumEOgfNtfOzAILvkBd4gS8DrQZ2Rbks86
iKOBxzCBxDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsG
AQUFBwMBMAwGA1UdEwEB/wQCMAAwKQYDVR0OBCIEIEJWUjlDw7H2fbRpGG8fpqCq
GEX80iDpQqXOU0wg6fEPMCsGA1UdIwQkMCKAIAu+td60D/Er7Xjtyg0B6XflfKYm
IdXjPfiFy8SGeKS2MC0GA1UdEQQmMCSCEWNsaWVudC5kYzEuY29uc3Vsgglsb2Nh
bGhvc3SHBH8AAAEwCgYIKoZIzj0EAwIDRwAwRAIgYAZTf8VcZ4nQl4lbm579BfXy
6YpYz/DdfkEODUBxUyYCIDXhfmxtL/gTSkIh1E+fV7H7ZmqPKgTDH1XBV2zYnj/C
-----END CERTIFICATE-----

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICYdaRvHDtbGbReTekgKf9uyKFEnR7kr7VU3kw3uGzAhoAoGCCqGSM49
AwEHoUQDQgAE0etZvg/aUTU+HPwDHtEwZslBuEshwHl7AcERHQeFTuhtfjpwHQw+
uTunFkmQoqNmE+n7P4v7fe771lpxif8VwA==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIICxjCCAmugAwIBAgIRAOKZmO0GuFJUOfJ7Ycf0WOEwCgYIKoZIzj0EAwIwgbkx
CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
bzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcw
FQYDVQQKEw5IYXNoaUNvcnAgSW5jLjFAMD4GA1UEAxM3Q29uc3VsIEFnZW50IENB
IDE4NTU3MTQ5MTMzMTA0NzczNDYwMjQyMDcxODI5NjUzMzQzNTQ0MzAeFw0yMDEw
MjgyMjI3NTZaFw0yMTEwMjgyMjI3NTZaMBwxGjAYBgNVBAMTEXNlcnZlci5kYzEu
Y29uc3VsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0etZvg/aUTU+HPwDHtEw
ZslBuEshwHl7AcERHQeFTuhtfjpwHQw+uTunFkmQoqNmE+n7P4v7fe771lpxif8V
wKOB7zCB7DAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG
AQUFBwMCMAwGA1UdEwEB/wQCMAAwKQYDVR0OBCIEIEA1xxAYluRqg6wFwGu75o/5
8Ty6FWR9RgIYvZzCM2N9MCsGA1UdIwQkMCKAIAu+td60D/Er7Xjtyg0B6XflfKYm
IdXjPfiFy8SGeKS2MFUGA1UdEQROMEyCC2NvbnN1bC50ZXN0ghlzZXJ2ZXIwLnNl
cnZlci5kYzEuY29uc3VsghFzZXJ2ZXIuZGMxLmNvbnN1bIIJbG9jYWxob3N0hwR/
AAABMAoGCCqGSM49BAMCA0kAMEYCIQDz9YnCvKkgGqw5M0HLDI82rqwQsH2SRQUs
kogKi3oGmQIhAPBA5AgF3y1E94PbeYfvoDBJy1JiY3KsckY2Gz+M8Iyc
-----END CERTIFICATE-----

33
test/client_certs/generate.sh Executable file
View File

@ -0,0 +1,33 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")"
if [[ ! -f consul-agent-ca-key.pem ]] || [[ ! -f consul-agent-ca.pem ]]; then
echo "Regenerating CA..."
rm -f consul-agent-ca-key.pem consul-agent-ca.pem
consul tls ca create
fi
rm -f rootca.crt rootca.key path/rootca.crt
cp consul-agent-ca.pem rootca.crt
cp consul-agent-ca-key.pem rootca.key
cp rootca.crt path
if [[ ! -f dc1-server-consul-0.pem ]] || [[ ! -f dc1-server-consul-0-key.pem ]]; then
echo "Regenerating server..."
rm -f dc1-server-consul-0.pem dc1-server-consul-0-key.pem
consul tls cert create -server -node=server0 -additional-dnsname=consul.test
fi
rm -f server.crt server.key
cp dc1-server-consul-0.pem server.crt
cp dc1-server-consul-0-key.pem server.key
if [[ ! -f dc1-client-consul-0.pem ]] || [[ ! -f dc1-client-consul-0-key.pem ]]; then
echo "Regenerating client..."
rm -f dc1-client-consul-0.pem dc1-client-consul-0-key.pem
consul tls cert create -client
fi
rm -f client.crt client.key
cp dc1-client-consul-0.pem client.crt
cp dc1-client-consul-0-key.pem client.key

View File

@ -1,31 +1,18 @@
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----

View File

@ -1,31 +1,18 @@
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----

View File

@ -1,51 +1,5 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEINtFYGWAzcVyRRQKjadE83olH8xAwZYe5sEn4rfPtI8xoAoGCCqGSM49
AwEHoUQDQgAErHueX3t67iU5Bj7Nh53zhggnF4pLwjuDbmTDSYIe/Tbeixc2M2Nb
7cGr9/Bk9cH8exB/o2KzbQ2nxPZ+ftBTAQ==
-----END EC PRIVATE KEY-----

View File

@ -1 +0,0 @@
8F3D34AED9A76443

View File

@ -1,30 +1,17 @@
-----BEGIN CERTIFICATE-----
MIIFHDCCAwQCCQCPPTSu2adkQjANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTE3MDQxNDE5MDkzOFoXDTIyMDMxOTE5MDkzOFowWzELMAkG
A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAwwLY29uc3VsLnRlc3QwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQDI57K1LPlNXpK4ud6XbqhQAM9w6mym5d+5
utGlq+ojNrnKdhaokzDOcEpELLguQlFevDrTGuBJLxInmDvRN8BmYshZAuwW+odw
iGa4IiT9rN0JeQIQr9DvNjRe7Rhzb3v0CIFIwaBWZyRSdEKuZORyDOvSudATI2p9
Ux9HpWJKG9clHEiAjWeseLfEttkAZgojPgdz/7Nq2CT877QtdbbKO8h+IIKevhVM
JLIyWhwGMy5orlVP42om7wgAIonm8LFCHe84JUVlOjuJgMzfUvz/UuKJFbAku7Dt
UXKL+gZsJg5YHhtMMHQO40UDNTfxNEiIRNyOHaQv4ptRUJq7vIbBng+m1uyYFkJp
NvTf4+OApXzsfhb8bjt8QRn5fsCsYU94oUVy+DSw10EW+IR1F7pGJZX5dYZgm6H7
E+cj0pYV1dKbdP/5L3mv6KJUTaGDRanLiNPQFOyhs6y6Qf1Y7a270oge9UpHAQ6D
KI5jvCuwzW2wDE7KEpo5SGRIjoK9KfouyhlYAaIiJNnVyjVXDXGhzJgH+/2cGW/n
TQAqKl4OtmzyNpihgv9Mhm86nro0XLnZHruJZ/SvZgpmSs8luo70CFK990j65JiM
UTvBdKADfjCZB4NxVo3yUC0UVaMsoMg8mbOGJPErY8WaFCqwsBHLPmbvbZvp8BYs
ve3dDhh+OQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQABCbceu3dDQ+NSRN45vEiB
wiyq7NLVHoSw7UWN0RPg7SVOPNt7lOqQ5ZtYNJQ0XAuBdHzNAZ/sfUgfURJbYhs5
MYalkkmMkpNWZQoMYxdj1sLX6KzLFFOtcACUSuZyOSGCBmuEDuDUTsQp1drZjNqb
fOtCWAuvSSbw8SnO6eTjUCwjcbe482kR5vnoaoRetmtTpIFdxwOXnegDXgkBnMMr
JSx+xIRVn2JzxJwaBmtn16P0/ksEMSPh0rKRvzE9zC7Jm2DvNOgEkdK8ae6DHFYY
zSonWjX3SlT+gpvuCwXC4ZmO2nV4CH34JDUUNRPub7o+t5XTGUxPGo0c/tdQYWZc
k0XTRzK6pLAq4X3NAF83nZ8pEi5z1u0Y+Dcx3AqBfhJSc2wg5++GZbELZGAmgiH2
WZa2/9zkCUGPke4GoctYNNhTmquk6ysJjDO+xOtaEQNwk04mqBwvb7j85iRNBkYX
+9PDF4sumEkkuNFB9vRSEJVrn3onQuZ3n2k1IujR6S6Vojhvideo5aT1NVq+1SQ5
bDe5E4IyrsFuxjYNIyA1hZmX2/qXYJ9U05QbyMs170lNhoKkdOf1voCxG3+sRTfz
yhGNJ2ks0MuEEiEjOFh9Za9lnk4/OHUG4yjWWdE5S/WH1xyyfokIM7xbo3Le8XgJ
ddL0E6Ag/Ow1ey1H7gsNOA==
MIICxjCCAmugAwIBAgIRAOKZmO0GuFJUOfJ7Ycf0WOEwCgYIKoZIzj0EAwIwgbkx
CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
bzEaMBgGA1UECRMRMTAxIFNlY29uZCBTdHJlZXQxDjAMBgNVBBETBTk0MTA1MRcw
FQYDVQQKEw5IYXNoaUNvcnAgSW5jLjFAMD4GA1UEAxM3Q29uc3VsIEFnZW50IENB
IDE4NTU3MTQ5MTMzMTA0NzczNDYwMjQyMDcxODI5NjUzMzQzNTQ0MzAeFw0yMDEw
MjgyMjI3NTZaFw0yMTEwMjgyMjI3NTZaMBwxGjAYBgNVBAMTEXNlcnZlci5kYzEu
Y29uc3VsMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0etZvg/aUTU+HPwDHtEw
ZslBuEshwHl7AcERHQeFTuhtfjpwHQw+uTunFkmQoqNmE+n7P4v7fe771lpxif8V
wKOB7zCB7DAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG
AQUFBwMCMAwGA1UdEwEB/wQCMAAwKQYDVR0OBCIEIEA1xxAYluRqg6wFwGu75o/5
8Ty6FWR9RgIYvZzCM2N9MCsGA1UdIwQkMCKAIAu+td60D/Er7Xjtyg0B6XflfKYm
IdXjPfiFy8SGeKS2MFUGA1UdEQROMEyCC2NvbnN1bC50ZXN0ghlzZXJ2ZXIwLnNl
cnZlci5kYzEuY29uc3VsghFzZXJ2ZXIuZGMxLmNvbnN1bIIJbG9jYWxob3N0hwR/
AAABMAoGCCqGSM49BAMCA0kAMEYCIQDz9YnCvKkgGqw5M0HLDI82rqwQsH2SRQUs
kogKi3oGmQIhAPBA5AgF3y1E94PbeYfvoDBJy1JiY3KsckY2Gz+M8Iyc
-----END CERTIFICATE-----

View File

@ -1,27 +0,0 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIEoDCCAogCAQAwWzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAwwLY29u
c3VsLnRlc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDI57K1LPlN
XpK4ud6XbqhQAM9w6mym5d+5utGlq+ojNrnKdhaokzDOcEpELLguQlFevDrTGuBJ
LxInmDvRN8BmYshZAuwW+odwiGa4IiT9rN0JeQIQr9DvNjRe7Rhzb3v0CIFIwaBW
ZyRSdEKuZORyDOvSudATI2p9Ux9HpWJKG9clHEiAjWeseLfEttkAZgojPgdz/7Nq
2CT877QtdbbKO8h+IIKevhVMJLIyWhwGMy5orlVP42om7wgAIonm8LFCHe84JUVl
OjuJgMzfUvz/UuKJFbAku7DtUXKL+gZsJg5YHhtMMHQO40UDNTfxNEiIRNyOHaQv
4ptRUJq7vIbBng+m1uyYFkJpNvTf4+OApXzsfhb8bjt8QRn5fsCsYU94oUVy+DSw
10EW+IR1F7pGJZX5dYZgm6H7E+cj0pYV1dKbdP/5L3mv6KJUTaGDRanLiNPQFOyh
s6y6Qf1Y7a270oge9UpHAQ6DKI5jvCuwzW2wDE7KEpo5SGRIjoK9KfouyhlYAaIi
JNnVyjVXDXGhzJgH+/2cGW/nTQAqKl4OtmzyNpihgv9Mhm86nro0XLnZHruJZ/Sv
ZgpmSs8luo70CFK990j65JiMUTvBdKADfjCZB4NxVo3yUC0UVaMsoMg8mbOGJPEr
Y8WaFCqwsBHLPmbvbZvp8BYsve3dDhh+OQIDAQABoAAwDQYJKoZIhvcNAQELBQAD
ggIBAIq+JNjO/pixXFRwYUISRib68ya39C0VKj2T8L4HsAtpP2K7gkKGEoQ5AFhi
wxe0424myEBBZ4kS6y5BCETtKFb3C9K4c2l1Wp5GIR9QUrj2qrorGnZNJSEBrtHm
G4neTxPyepZ+n9C9K5RWC/S/l8nge4+oWU2F0nWIAFK+4GGGTyXFjbfGKtET8CKd
NQ4jcrabkRd49qPrXhEMTWzPkKdtZ4Gh7eFVAi5IrgJJlJPhb5/XnUQ+6L7NrIWG
D1nHCq8XNDm8Gg9KiMuB4/A7dcqyaoyYofojrnZ2HryGTeLbezpwQmMfVy1dM0ht
vueKQwOS7xjiglsV48G5mmql+0auYoBLes2yYGBJ4gLC+Gwokav+FtRhyyRUVZ3+
lta2a8XpzdMpmESQrwvz+v37fC/eBsplhk8tTy0IQUzUp0KVCRkR40UHeH9YKsu1
cvPwfzk3DPt2wHWjBw7b8RKSXQFyszP9QWODqxnOtE7BoNdHiyxF/W4RaysIVrAG
Vs2iRNVSPexiT30BgnfQN1i1HU4eXC/QuTWilo86vwDOjpHhr9FdjPghLk2TbV+g
iO3TX0zy0t6Un5rLGMt066pAFCIqynT1sf3CTVXbaeWpkHj1wjVmYOoG75xxWMsv
GKmSoKRex/SDko3s2pmfQ+vmkCTKQqabayUH5mh1Ron9B043
-----END CERTIFICATE REQUEST-----

View File

@ -1,51 +1,5 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICYdaRvHDtbGbReTekgKf9uyKFEnR7kr7VU3kw3uGzAhoAoGCCqGSM49
AwEHoUQDQgAE0etZvg/aUTU+HPwDHtEwZslBuEshwHl7AcERHQeFTuhtfjpwHQw+
uTunFkmQoqNmE+n7P4v7fe771lpxif8VwA==
-----END EC PRIVATE KEY-----

View File

@ -920,11 +920,6 @@ func (c *Configurator) wrapALPNTLSClient(dc, nodeName, alpnProto string, conn ne
return nil, err
}
if cs := tlsConn.ConnectionState(); !cs.NegotiatedProtocolIsMutual {
tlsConn.Close()
return nil, fmt.Errorf("could not negotiate ALPN protocol %q with %q", alpnProto, config.ServerName)
}
return tlsConn, nil
}

View File

@ -215,7 +215,6 @@ func TestConfigurator_outgoingWrapperALPN_OK(t *testing.T) {
tlsConn := tlsClient.(*tls.Conn)
cs := tlsConn.ConnectionState()
require.Equal(t, "foo", cs.NegotiatedProtocol)
require.True(t, cs.NegotiatedProtocolIsMutual)
err = <-errc
require.NoError(t, err)

View File

@ -5,8 +5,8 @@ module.exports = {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
legacyDecorators: true
}
legacyDecorators: true,
},
},
plugins: ['ember'],
extends: ['eslint:recommended', 'plugin:ember/recommended'],
@ -17,7 +17,7 @@ module.exports = {
'no-unused-vars': ['error', { args: 'none' }],
'ember/no-new-mixins': ['warn'],
'ember/no-jquery': 'warn',
'ember/no-global-jquery': 'warn'
'ember/no-global-jquery': 'warn',
},
overrides: [
// node files
@ -31,14 +31,14 @@ module.exports = {
'blueprints/*/index.js',
'config/**/*.js',
'lib/*/index.js',
'server/**/*.js'
'server/**/*.js',
],
parserOptions: {
sourceType: 'script'
sourceType: 'script',
},
env: {
browser: false,
node: true
node: true,
},
plugins: ['node'],
rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
@ -46,8 +46,8 @@ module.exports = {
// this can be removed once the following is fixed
// https://github.com/mysticatea/eslint-plugin-node/issues/77
'node/no-unpublished-require': 'off'
})
}
]
'node/no-unpublished-require': 'off',
}),
},
],
};

Some files were not shown because too many files have changed in this diff Show More