core: Record the time a node became active (#10489)

* core: Record the time a node became active

* Update vault/core.go

Co-authored-by: Nick Cabatoff <ncabatoff@hashicorp.com>

* Add omitempty field

* Update vendor

* Added CL entry and fixed test

* Fix test

* Fix command package tests

Co-authored-by: Nick Cabatoff <ncabatoff@hashicorp.com>
This commit is contained in:
Brian Kassouf 2020-12-11 16:50:19 -08:00 committed by GitHub
parent f137c945d7
commit 275ca323e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 89 additions and 46 deletions

View File

@ -1,6 +1,9 @@
package api package api
import "context" import (
"context"
"time"
)
func (c *Sys) Leader() (*LeaderResponse, error) { func (c *Sys) Leader() (*LeaderResponse, error) {
r := c.c.NewRequest("GET", "/v1/sys/leader") r := c.c.NewRequest("GET", "/v1/sys/leader")
@ -21,6 +24,7 @@ func (c *Sys) Leader() (*LeaderResponse, error) {
type LeaderResponse struct { type LeaderResponse struct {
HAEnabled bool `json:"ha_enabled"` HAEnabled bool `json:"ha_enabled"`
IsSelf bool `json:"is_self"` IsSelf bool `json:"is_self"`
ActiveTime time.Time `json:"active_time"`
LeaderAddress string `json:"leader_address"` LeaderAddress string `json:"leader_address"`
LeaderClusterAddress string `json:"leader_cluster_address"` LeaderClusterAddress string `json:"leader_cluster_address"`
PerfStandby bool `json:"performance_standby"` PerfStandby bool `json:"performance_standby"`

3
changelog/10489.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
core: Added active since timestamp to the status output of active nodes.
```

View File

@ -7,6 +7,7 @@ import (
"os" "os"
"sort" "sort"
"strings" "strings"
"time"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
@ -191,6 +192,9 @@ func (t TableFormatter) OutputSealStatusStruct(ui cli.Ui, secret *api.Secret, da
} }
out = append(out, fmt.Sprintf("HA Mode | %s", mode)) out = append(out, fmt.Sprintf("HA Mode | %s", mode))
if status.IsSelf && !status.ActiveTime.IsZero() {
out = append(out, fmt.Sprintf("Active Since | %s", status.ActiveTime.Format(time.RFC3339Nano)))
}
// This is down here just to keep ordering consistent // This is down here just to keep ordering consistent
if showLeaderAddr { if showLeaderAddr {
out = append(out, fmt.Sprintf("Active Node Address | %s", status.LeaderAddress)) out = append(out, fmt.Sprintf("Active Node Address | %s", status.LeaderAddress))
@ -405,6 +409,7 @@ func OutputSealStatus(ui cli.Ui, client *api.Client, status *api.SealStatusRespo
// copy leaderStatus fields into sealStatusOutput for display later // copy leaderStatus fields into sealStatusOutput for display later
sealStatusOutput.HAEnabled = leaderStatus.HAEnabled sealStatusOutput.HAEnabled = leaderStatus.HAEnabled
sealStatusOutput.IsSelf = leaderStatus.IsSelf sealStatusOutput.IsSelf = leaderStatus.IsSelf
sealStatusOutput.ActiveTime = leaderStatus.ActiveTime
sealStatusOutput.LeaderAddress = leaderStatus.LeaderAddress sealStatusOutput.LeaderAddress = leaderStatus.LeaderAddress
sealStatusOutput.LeaderClusterAddress = leaderStatus.LeaderClusterAddress sealStatusOutput.LeaderClusterAddress = leaderStatus.LeaderClusterAddress
sealStatusOutput.PerfStandby = leaderStatus.PerfStandby sealStatusOutput.PerfStandby = leaderStatus.PerfStandby
@ -433,6 +438,7 @@ type SealStatusOutput struct {
api.SealStatusResponse api.SealStatusResponse
HAEnabled bool `json:"ha_enabled"` HAEnabled bool `json:"ha_enabled"`
IsSelf bool `json:"is_self,omitempty""` IsSelf bool `json:"is_self,omitempty""`
ActiveTime time.Time `json:"active_time,omitempty""`
LeaderAddress string `json:"leader_address,omitempty"` LeaderAddress string `json:"leader_address,omitempty"`
LeaderClusterAddress string `json:"leader_cluster_address,omitempty"` LeaderClusterAddress string `json:"leader_cluster_address,omitempty"`
PerfStandby bool `json:"performance_standby,omitempty"` PerfStandby bool `json:"performance_standby,omitempty"`

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"strings" "strings"
"testing" "testing"
"time"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api"
@ -178,6 +179,7 @@ func getMockStatusData(emptyFields bool) SealStatusOutput {
sealStatusResponseMock, sealStatusResponseMock,
true, // HAEnabled true, // HAEnabled
true, // IsSelf true, // IsSelf
time.Time{}.UTC(), // ActiveTime
"leader address", // LeaderAddress "leader address", // LeaderAddress
"leader cluster address", // LeaderClusterAddress "leader cluster address", // LeaderClusterAddress
true, // PerfStandby true, // PerfStandby
@ -208,6 +210,7 @@ func getMockStatusData(emptyFields bool) SealStatusOutput {
sealStatusResponseMock, sealStatusResponseMock,
false, // HAEnabled false, // HAEnabled
false, // IsSelf false, // IsSelf
time.Time{}.UTC(), // ActiveTime
"", // LeaderAddress "", // LeaderAddress
"", // LeaderClusterAddress "", // LeaderClusterAddress
false, // PerfStandby false, // PerfStandby

View File

@ -2,6 +2,7 @@ package http
import ( import (
"net/http" "net/http"
"time"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/vault" "github.com/hashicorp/vault/vault"
@ -36,6 +37,9 @@ func handleSysLeaderGet(core *vault.Core, w http.ResponseWriter, r *http.Request
LeaderClusterAddress: clusterAddr, LeaderClusterAddress: clusterAddr,
PerfStandby: core.PerfStandby(), PerfStandby: core.PerfStandby(),
} }
if isLeader {
resp.ActiveTime = core.ActiveTime()
}
if resp.PerfStandby { if resp.PerfStandby {
resp.PerfStandbyLastRemoteWAL = vault.LastRemoteWAL(core) resp.PerfStandbyLastRemoteWAL = vault.LastRemoteWAL(core)
} else if isLeader || !haEnabled { } else if isLeader || !haEnabled {
@ -50,6 +54,7 @@ func handleSysLeaderGet(core *vault.Core, w http.ResponseWriter, r *http.Request
type LeaderResponse struct { type LeaderResponse struct {
HAEnabled bool `json:"ha_enabled"` HAEnabled bool `json:"ha_enabled"`
IsSelf bool `json:"is_self"` IsSelf bool `json:"is_self"`
ActiveTime time.Time `json:"active_time,omitempty"`
LeaderAddress string `json:"leader_address"` LeaderAddress string `json:"leader_address"`
LeaderClusterAddress string `json:"leader_cluster_address"` LeaderClusterAddress string `json:"leader_cluster_address"`
PerfStandby bool `json:"performance_standby"` PerfStandby bool `json:"performance_standby"`

View File

@ -5,6 +5,7 @@ import (
"net/http" "net/http"
"reflect" "reflect"
"testing" "testing"
"time"
"github.com/hashicorp/vault/vault" "github.com/hashicorp/vault/vault"
) )
@ -27,10 +28,11 @@ func TestSysLeader_get(t *testing.T) {
"leader_cluster_address": "", "leader_cluster_address": "",
"performance_standby": false, "performance_standby": false,
"performance_standby_last_remote_wal": json.Number("0"), "performance_standby_last_remote_wal": json.Number("0"),
"active_time": time.Time{}.UTC().Format(time.RFC3339),
} }
testResponseStatus(t, resp, 200) testResponseStatus(t, resp, 200)
testResponseBody(t, resp, &actual) testResponseBody(t, resp, &actual)
if !reflect.DeepEqual(actual, expected) { if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual) t.Fatalf("bad: %#v \n%#v", actual, expected)
} }
} }

View File

@ -535,6 +535,10 @@ type Core struct {
activityLogConfig ActivityLogCoreConfig activityLogConfig ActivityLogCoreConfig
// activeTime is set on active nodes indicating the time at which this node
// became active.
activeTime time.Time
// KeyRotateGracePeriod is how long we allow an upgrade path // KeyRotateGracePeriod is how long we allow an upgrade path
// for standby instances before we delete the upgrade keys // for standby instances before we delete the upgrade keys
keyRotateGracePeriod *int64 keyRotateGracePeriod *int64
@ -1856,6 +1860,10 @@ func (s standardUnsealStrategy) unseal(ctx context.Context, logger log.Logger, c
c.clearForwardingClients() c.clearForwardingClients()
c.requestForwardingConnectionLock.Unlock() c.requestForwardingConnectionLock.Unlock()
// Mark the active time. We do this first so it can be correlated to the logs
// for the active startup.
c.activeTime = time.Now().UTC()
if err := postUnsealPhysical(c); err != nil { if err := postUnsealPhysical(c); err != nil {
return err return err
} }
@ -2047,6 +2055,7 @@ func (c *Core) preSeal() error {
// Clear any pending funcs // Clear any pending funcs
c.postUnsealFuncs = nil c.postUnsealFuncs = nil
c.activeTime = time.Time{}
// Clear any rekey progress // Clear any rekey progress
c.barrierRekeyConfig = nil c.barrierRekeyConfig = nil

View File

@ -70,6 +70,13 @@ func (c *Core) PerfStandby() bool {
return perfStandby return perfStandby
} }
func (c *Core) ActiveTime() time.Time {
c.stateLock.RLock()
activeTime := c.activeTime
c.stateLock.RUnlock()
return activeTime
}
// StandbyStates is meant as a way to avoid some extra locking on the very // StandbyStates is meant as a way to avoid some extra locking on the very
// common sys/health check. // common sys/health check.
func (c *Core) StandbyStates() (standby, perfStandby bool) { func (c *Core) StandbyStates() (standby, perfStandby bool) {

View File

@ -1,6 +1,9 @@
package api package api
import "context" import (
"context"
"time"
)
func (c *Sys) Leader() (*LeaderResponse, error) { func (c *Sys) Leader() (*LeaderResponse, error) {
r := c.c.NewRequest("GET", "/v1/sys/leader") r := c.c.NewRequest("GET", "/v1/sys/leader")
@ -21,6 +24,7 @@ func (c *Sys) Leader() (*LeaderResponse, error) {
type LeaderResponse struct { type LeaderResponse struct {
HAEnabled bool `json:"ha_enabled"` HAEnabled bool `json:"ha_enabled"`
IsSelf bool `json:"is_self"` IsSelf bool `json:"is_self"`
ActiveTime time.Time `json:"active_time"`
LeaderAddress string `json:"leader_address"` LeaderAddress string `json:"leader_address"`
LeaderClusterAddress string `json:"leader_cluster_address"` LeaderClusterAddress string `json:"leader_cluster_address"`
PerfStandby bool `json:"performance_standby"` PerfStandby bool `json:"performance_standby"`