mirror of https://github.com/hashicorp/consul
Browse Source
* agent: configure server lastseen timestamp Signed-off-by: Dan Bond <danbond@protonmail.com> * use correct config Signed-off-by: Dan Bond <danbond@protonmail.com> * add comments Signed-off-by: Dan Bond <danbond@protonmail.com> * use default age in test golden data Signed-off-by: Dan Bond <danbond@protonmail.com> * add changelog Signed-off-by: Dan Bond <danbond@protonmail.com> * fix runtime test Signed-off-by: Dan Bond <danbond@protonmail.com> * agent: add server_metadata Signed-off-by: Dan Bond <danbond@protonmail.com> * update comments Signed-off-by: Dan Bond <danbond@protonmail.com> * correctly check if metadata file does not exist Signed-off-by: Dan Bond <danbond@protonmail.com> * follow instructions for adding new config Signed-off-by: Dan Bond <danbond@protonmail.com> * add comments Signed-off-by: Dan Bond <danbond@protonmail.com> * update comments Signed-off-by: Dan Bond <danbond@protonmail.com> * Update agent/agent.go Co-authored-by: Dan Upton <daniel@floppy.co> * agent/config: add validation for duration with min Signed-off-by: Dan Bond <danbond@protonmail.com> * docs: add new server_rejoin_age_max config definition Signed-off-by: Dan Bond <danbond@protonmail.com> * agent: add unit test for checking server last seen Signed-off-by: Dan Bond <danbond@protonmail.com> * agent: log continually for 60s before erroring Signed-off-by: Dan Bond <danbond@protonmail.com> * pr comments Signed-off-by: Dan Bond <danbond@protonmail.com> * remove unneeded todo * agent: fix error message Signed-off-by: Dan Bond <danbond@protonmail.com> --------- Signed-off-by: Dan Bond <danbond@protonmail.com> Co-authored-by: Dan Upton <daniel@floppy.co>pull/17215/head
Dan Bond
2 years ago
committed by
GitHub
16 changed files with 385 additions and 46 deletions
@ -0,0 +1,3 @@
|
||||
```release-note:improvement |
||||
agent: add a configurable maximimum age (default: 7 days) to prevent servers re-joining a cluster with stale data |
||||
``` |
@ -0,0 +1,71 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package consul |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"io" |
||||
"os" |
||||
"time" |
||||
) |
||||
|
||||
// ServerMetadataFile is the name of the file on disk that server metadata
|
||||
// should be written to.
|
||||
const ServerMetadataFile = "server_metadata.json" |
||||
|
||||
// ServerMetadata represents specific metadata about a running server.
|
||||
type ServerMetadata struct { |
||||
// LastSeenUnix is the timestamp a server was last seen, in Unix format.
|
||||
LastSeenUnix int64 `json:"last_seen_unix"` |
||||
} |
||||
|
||||
// IsLastSeenStale checks whether the last seen timestamp is older than a given duration.
|
||||
func (md *ServerMetadata) IsLastSeenStale(d time.Duration) bool { |
||||
lastSeen := time.Unix(md.LastSeenUnix, 0) |
||||
maxAge := time.Now().Add(-d) |
||||
|
||||
return lastSeen.Before(maxAge) |
||||
} |
||||
|
||||
// OpenServerMetadata is a helper function for opening the server metadata file
|
||||
// with the correct permissions.
|
||||
func OpenServerMetadata(filename string) (io.WriteCloser, error) { |
||||
return os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) |
||||
} |
||||
|
||||
type ServerMetadataReadFunc func(filename string) (*ServerMetadata, error) |
||||
|
||||
// ReadServerMetadata is a helper function for reading the contents of a server
|
||||
// metadata file and unmarshaling the data from JSON.
|
||||
func ReadServerMetadata(filename string) (*ServerMetadata, error) { |
||||
b, err := os.ReadFile(filename) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
var md ServerMetadata |
||||
if err := json.Unmarshal(b, &md); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return &md, nil |
||||
} |
||||
|
||||
// WriteServerMetadata writes server metadata to a file in JSON format.
|
||||
func WriteServerMetadata(w io.Writer) error { |
||||
md := &ServerMetadata{ |
||||
LastSeenUnix: time.Now().Unix(), |
||||
} |
||||
|
||||
b, err := json.Marshal(md) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if _, err := w.Write(b); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -0,0 +1,68 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package consul |
||||
|
||||
import ( |
||||
"bytes" |
||||
"errors" |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
type mockServerMetadataWriter struct { |
||||
writeErr error |
||||
} |
||||
|
||||
func (m *mockServerMetadataWriter) Write(p []byte) (n int, err error) { |
||||
if m.writeErr != nil { |
||||
return 0, m.writeErr |
||||
} |
||||
|
||||
return 1, nil |
||||
} |
||||
|
||||
func TestServerMetadata(t *testing.T) { |
||||
now := time.Now() |
||||
|
||||
t.Run("TestIsLastSeenStaleTrue", func(t *testing.T) { |
||||
// Create a server that is 48 hours old.
|
||||
md := &ServerMetadata{ |
||||
LastSeenUnix: now.Add(-48 * time.Hour).Unix(), |
||||
} |
||||
|
||||
stale := md.IsLastSeenStale(24 * time.Hour) |
||||
assert.True(t, stale) |
||||
}) |
||||
|
||||
t.Run("TestIsLastSeenStaleFalse", func(t *testing.T) { |
||||
// Create a server that is 1 hour old.
|
||||
md := &ServerMetadata{ |
||||
LastSeenUnix: now.Add(-1 * time.Hour).Unix(), |
||||
} |
||||
|
||||
stale := md.IsLastSeenStale(24 * time.Hour) |
||||
assert.False(t, stale) |
||||
}) |
||||
} |
||||
|
||||
func TestWriteServerMetadata(t *testing.T) { |
||||
t.Run("TestWriteError", func(t *testing.T) { |
||||
m := &mockServerMetadataWriter{ |
||||
writeErr: errors.New("write error"), |
||||
} |
||||
|
||||
err := WriteServerMetadata(m) |
||||
assert.Error(t, err) |
||||
}) |
||||
|
||||
t.Run("TestOK", func(t *testing.T) { |
||||
b := new(bytes.Buffer) |
||||
|
||||
err := WriteServerMetadata(b) |
||||
assert.NoError(t, err) |
||||
assert.True(t, b.Len() > 0) |
||||
}) |
||||
} |
Loading…
Reference in new issue