mirror of
https://github.com/status-im/consul.git
synced 2025-01-09 13:26:07 +00:00
5fb9df1640
* Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
419 lines
11 KiB
Go
419 lines
11 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package config
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
|
|
"github.com/hashicorp/consul/sdk/testutil"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const defaultTimeout = 500 * time.Millisecond
|
|
|
|
func TestNewWatcher(t *testing.T) {
|
|
w, err := NewFileWatcher([]string{}, hclog.New(&hclog.LoggerOptions{}))
|
|
require.NoError(t, err)
|
|
require.NotNil(t, w)
|
|
}
|
|
|
|
func TestWatcherRenameEvent(t *testing.T) {
|
|
|
|
fileTmp := createTempConfigFile(t, "temp_config3")
|
|
filepaths := []string{createTempConfigFile(t, "temp_config1"), createTempConfigFile(t, "temp_config2")}
|
|
wi, err := NewFileWatcher(filepaths, hclog.New(&hclog.LoggerOptions{}))
|
|
w := wi.(*fileWatcher)
|
|
|
|
require.NoError(t, err)
|
|
w.Start(context.Background())
|
|
defer func() {
|
|
_ = w.Stop()
|
|
}()
|
|
|
|
require.NoError(t, err)
|
|
err = os.Rename(fileTmp, filepaths[0])
|
|
time.Sleep(w.reconcileTimeout + 50*time.Millisecond)
|
|
require.NoError(t, err)
|
|
require.NoError(t, assertEvent(filepaths[0], w.eventsCh, defaultTimeout))
|
|
// make sure we consume all events
|
|
_ = assertEvent(filepaths[0], w.eventsCh, defaultTimeout)
|
|
}
|
|
|
|
func TestWatcherAddRemove(t *testing.T) {
|
|
var filepaths []string
|
|
wi, err := NewFileWatcher(filepaths, hclog.New(&hclog.LoggerOptions{}))
|
|
w := wi.(*fileWatcher)
|
|
require.NoError(t, err)
|
|
file1 := createTempConfigFile(t, "temp_config1")
|
|
err = w.Add(file1)
|
|
require.NoError(t, err)
|
|
file2 := createTempConfigFile(t, "temp_config2")
|
|
err = w.Add(file2)
|
|
require.NoError(t, err)
|
|
w.Remove(file2)
|
|
_, ok := w.configFiles[file1]
|
|
require.True(t, ok)
|
|
_, ok = w.configFiles[file2]
|
|
require.False(t, ok)
|
|
|
|
}
|
|
|
|
func TestWatcherReplace(t *testing.T) {
|
|
var filepaths []string
|
|
wi, err := NewFileWatcher(filepaths, hclog.New(&hclog.LoggerOptions{}))
|
|
w := wi.(*fileWatcher)
|
|
require.NoError(t, err)
|
|
file1 := createTempConfigFile(t, "temp_config1")
|
|
err = w.Add(file1)
|
|
require.NoError(t, err)
|
|
file2 := createTempConfigFile(t, "temp_config2")
|
|
err = w.Replace(file1, file2)
|
|
require.NoError(t, err)
|
|
_, ok := w.configFiles[file1]
|
|
require.False(t, ok)
|
|
_, ok = w.configFiles[file2]
|
|
require.True(t, ok)
|
|
}
|
|
|
|
func TestWatcherAddWhileRunning(t *testing.T) {
|
|
var filepaths []string
|
|
wi, err := NewFileWatcher(filepaths, hclog.New(&hclog.LoggerOptions{}))
|
|
w := wi.(*fileWatcher)
|
|
require.NoError(t, err)
|
|
w.Start(context.Background())
|
|
defer func() {
|
|
_ = w.Stop()
|
|
}()
|
|
file1 := createTempConfigFile(t, "temp_config1")
|
|
err = w.Add(file1)
|
|
require.NoError(t, err)
|
|
file2 := createTempConfigFile(t, "temp_config2")
|
|
err = w.Add(file2)
|
|
require.NoError(t, err)
|
|
w.Remove(file2)
|
|
require.Len(t, w.configFiles, 1)
|
|
_, ok := w.configFiles[file1]
|
|
require.True(t, ok)
|
|
_, ok = w.configFiles[file2]
|
|
require.False(t, ok)
|
|
}
|
|
|
|
func TestWatcherRemoveNotFound(t *testing.T) {
|
|
var filepaths []string
|
|
w, err := NewFileWatcher(filepaths, hclog.New(&hclog.LoggerOptions{}))
|
|
require.NoError(t, err)
|
|
w.Start(context.Background())
|
|
defer func() {
|
|
_ = w.Stop()
|
|
}()
|
|
|
|
file := createTempConfigFile(t, "temp_config2")
|
|
w.Remove(file)
|
|
}
|
|
|
|
func TestWatcherAddNotExist(t *testing.T) {
|
|
|
|
file := testutil.TempFile(t, "temp_config")
|
|
filename := file.Name() + randomStr(16)
|
|
w, err := NewFileWatcher([]string{filename}, hclog.New(&hclog.LoggerOptions{}))
|
|
require.Error(t, err, "no such file or directory")
|
|
require.Nil(t, w)
|
|
}
|
|
|
|
func TestEventWatcherWrite(t *testing.T) {
|
|
|
|
file := testutil.TempFile(t, "temp_config")
|
|
_, err := file.WriteString("test config")
|
|
require.NoError(t, err)
|
|
err = file.Sync()
|
|
require.NoError(t, err)
|
|
w, err := NewFileWatcher([]string{file.Name()}, hclog.New(&hclog.LoggerOptions{}))
|
|
require.NoError(t, err)
|
|
w.Start(context.Background())
|
|
defer func() {
|
|
_ = w.Stop()
|
|
}()
|
|
|
|
_, err = file.WriteString("test config 2")
|
|
require.NoError(t, err)
|
|
err = file.Sync()
|
|
require.NoError(t, err)
|
|
require.NoError(t, assertEvent(file.Name(), w.EventsCh(), defaultTimeout))
|
|
}
|
|
|
|
func TestEventWatcherRead(t *testing.T) {
|
|
|
|
filepath := createTempConfigFile(t, "temp_config1")
|
|
w, err := NewFileWatcher([]string{filepath}, hclog.New(&hclog.LoggerOptions{}))
|
|
require.NoError(t, err)
|
|
w.Start(context.Background())
|
|
defer func() {
|
|
_ = w.Stop()
|
|
}()
|
|
|
|
_, err = os.ReadFile(filepath)
|
|
require.NoError(t, err)
|
|
require.Error(t, assertEvent(filepath, w.EventsCh(), defaultTimeout), "timedout waiting for event")
|
|
}
|
|
|
|
func TestEventWatcherChmod(t *testing.T) {
|
|
file := testutil.TempFile(t, "temp_config")
|
|
defer func() {
|
|
err := file.Close()
|
|
require.NoError(t, err)
|
|
}()
|
|
_, err := file.WriteString("test config")
|
|
require.NoError(t, err)
|
|
err = file.Sync()
|
|
require.NoError(t, err)
|
|
|
|
w, err := NewFileWatcher([]string{file.Name()}, hclog.New(&hclog.LoggerOptions{}))
|
|
require.NoError(t, err)
|
|
w.Start(context.Background())
|
|
defer func() {
|
|
_ = w.Stop()
|
|
}()
|
|
|
|
err = file.Chmod(0777)
|
|
require.NoError(t, err)
|
|
require.Error(t, assertEvent(file.Name(), w.EventsCh(), defaultTimeout), "timedout waiting for event")
|
|
}
|
|
|
|
func TestEventWatcherRemoveCreate(t *testing.T) {
|
|
|
|
filepath := createTempConfigFile(t, "temp_config1")
|
|
w, err := NewFileWatcher([]string{filepath}, hclog.New(&hclog.LoggerOptions{}))
|
|
require.NoError(t, err)
|
|
w.Start(context.Background())
|
|
defer func() {
|
|
_ = w.Stop()
|
|
}()
|
|
|
|
require.NoError(t, err)
|
|
err = os.Remove(filepath)
|
|
require.NoError(t, err)
|
|
recreated, err := os.Create(filepath)
|
|
require.NoError(t, err)
|
|
_, err = recreated.WriteString("config 2")
|
|
require.NoError(t, err)
|
|
err = recreated.Sync()
|
|
require.NoError(t, err)
|
|
// this an event coming from the reconcile loop
|
|
require.NoError(t, assertEvent(filepath, w.EventsCh(), defaultTimeout))
|
|
}
|
|
|
|
func TestEventWatcherMove(t *testing.T) {
|
|
|
|
filepath := createTempConfigFile(t, "temp_config1")
|
|
|
|
w, err := NewFileWatcher([]string{filepath}, hclog.New(&hclog.LoggerOptions{}))
|
|
require.NoError(t, err)
|
|
w.Start(context.Background())
|
|
defer func() {
|
|
_ = w.Stop()
|
|
}()
|
|
|
|
for i := 0; i < 10; i++ {
|
|
filepath2 := createTempConfigFile(t, "temp_config2")
|
|
err = os.Rename(filepath2, filepath)
|
|
time.Sleep(timeoutDuration + 50*time.Millisecond)
|
|
require.NoError(t, err)
|
|
require.NoError(t, assertEvent(filepath, w.EventsCh(), defaultTimeout))
|
|
}
|
|
}
|
|
|
|
func TestEventReconcileMove(t *testing.T) {
|
|
filepath := createTempConfigFile(t, "temp_config1")
|
|
filepath2 := createTempConfigFile(t, "temp_config2")
|
|
err := os.Chtimes(filepath, time.Now(), time.Now().Add(-1*time.Second))
|
|
require.NoError(t, err)
|
|
wi, err := NewFileWatcher([]string{filepath}, hclog.New(&hclog.LoggerOptions{}))
|
|
w := wi.(*fileWatcher)
|
|
require.NoError(t, err)
|
|
w.Start(context.Background())
|
|
defer func() {
|
|
_ = w.Stop()
|
|
}()
|
|
|
|
// remove the file from the internal watcher to only trigger the reconcile
|
|
err = w.watcher.Remove(filepath)
|
|
require.NoError(t, err)
|
|
|
|
err = os.Rename(filepath2, filepath)
|
|
time.Sleep(timeoutDuration + 50*time.Millisecond)
|
|
require.NoError(t, err)
|
|
require.NoError(t, assertEvent(filepath, w.EventsCh(), 2000*time.Millisecond))
|
|
}
|
|
|
|
func TestEventWatcherDirCreateRemove(t *testing.T) {
|
|
filepath := testutil.TempDir(t, "temp_config1")
|
|
w, err := NewFileWatcher([]string{filepath}, hclog.New(&hclog.LoggerOptions{}))
|
|
require.NoError(t, err)
|
|
w.Start(context.Background())
|
|
defer func() {
|
|
_ = w.Stop()
|
|
}()
|
|
for i := 0; i < 1; i++ {
|
|
name := filepath + "/" + randomStr(20)
|
|
file, err := os.Create(name)
|
|
require.NoError(t, err)
|
|
err = file.Close()
|
|
require.NoError(t, err)
|
|
require.NoError(t, assertEvent(filepath, w.EventsCh(), defaultTimeout))
|
|
|
|
err = os.Remove(name)
|
|
require.NoError(t, err)
|
|
require.NoError(t, assertEvent(filepath, w.EventsCh(), defaultTimeout))
|
|
}
|
|
}
|
|
|
|
func TestEventWatcherDirMove(t *testing.T) {
|
|
filepath := testutil.TempDir(t, "temp_config1")
|
|
|
|
name := filepath + "/" + randomStr(20)
|
|
file, err := os.Create(name)
|
|
require.NoError(t, err)
|
|
err = file.Close()
|
|
require.NoError(t, err)
|
|
w, err := NewFileWatcher([]string{filepath}, hclog.New(&hclog.LoggerOptions{}))
|
|
require.NoError(t, err)
|
|
w.Start(context.Background())
|
|
defer func() {
|
|
_ = w.Stop()
|
|
}()
|
|
|
|
for i := 0; i < 100; i++ {
|
|
filepathTmp := createTempConfigFile(t, "temp_config2")
|
|
err = os.Rename(filepathTmp, name)
|
|
require.NoError(t, err)
|
|
require.NoError(t, assertEvent(filepath, w.EventsCh(), defaultTimeout))
|
|
}
|
|
}
|
|
|
|
func TestEventWatcherDirMoveTrim(t *testing.T) {
|
|
filepath := testutil.TempDir(t, "temp_config1")
|
|
|
|
name := filepath + "/" + randomStr(20)
|
|
file, err := os.Create(name)
|
|
require.NoError(t, err)
|
|
err = file.Close()
|
|
require.NoError(t, err)
|
|
w, err := NewFileWatcher([]string{filepath + "/"}, hclog.New(&hclog.LoggerOptions{}))
|
|
require.NoError(t, err)
|
|
w.Start(context.Background())
|
|
defer func() {
|
|
_ = w.Stop()
|
|
}()
|
|
|
|
for i := 0; i < 100; i++ {
|
|
filepathTmp := createTempConfigFile(t, "temp_config2")
|
|
err = os.Rename(filepathTmp, name)
|
|
require.NoError(t, err)
|
|
require.NoError(t, assertEvent(filepath, w.EventsCh(), defaultTimeout))
|
|
}
|
|
}
|
|
|
|
// Consul do not support configuration in sub-directories
|
|
func TestEventWatcherSubDirMove(t *testing.T) {
|
|
filepath := testutil.TempDir(t, "temp_config1")
|
|
err := os.Mkdir(filepath+"/temp", 0777)
|
|
require.NoError(t, err)
|
|
name := filepath + "/temp/" + randomStr(20)
|
|
file, err := os.Create(name)
|
|
require.NoError(t, err)
|
|
err = file.Close()
|
|
require.NoError(t, err)
|
|
w, err := NewFileWatcher([]string{filepath}, hclog.New(&hclog.LoggerOptions{}))
|
|
require.NoError(t, err)
|
|
w.Start(context.Background())
|
|
defer func() {
|
|
_ = w.Stop()
|
|
}()
|
|
|
|
for i := 0; i < 2; i++ {
|
|
filepathTmp := createTempConfigFile(t, "temp_config2")
|
|
err = os.Rename(filepathTmp, name)
|
|
require.NoError(t, err)
|
|
require.Error(t, assertEvent(filepath, w.EventsCh(), defaultTimeout), "timedout waiting for event")
|
|
}
|
|
}
|
|
|
|
func TestEventWatcherDirRead(t *testing.T) {
|
|
filepath := testutil.TempDir(t, "temp_config1")
|
|
|
|
name := filepath + "/" + randomStr(20)
|
|
file, err := os.Create(name)
|
|
require.NoError(t, err)
|
|
err = file.Close()
|
|
require.NoError(t, err)
|
|
w, err := NewFileWatcher([]string{filepath}, hclog.New(&hclog.LoggerOptions{}))
|
|
require.NoError(t, err)
|
|
w.Start(context.Background())
|
|
t.Cleanup(func() {
|
|
_ = w.Stop()
|
|
})
|
|
|
|
_, err = os.ReadFile(name)
|
|
require.NoError(t, err)
|
|
require.Error(t, assertEvent(filepath, w.EventsCh(), defaultTimeout), "timedout waiting for event")
|
|
}
|
|
|
|
func TestEventWatcherMoveSoftLink(t *testing.T) {
|
|
|
|
filepath := createTempConfigFile(t, "temp_config1")
|
|
tempDir := testutil.TempDir(t, "temp_dir")
|
|
name := tempDir + "/" + randomStr(20)
|
|
err := os.Symlink(filepath, name)
|
|
require.NoError(t, err)
|
|
|
|
w, err := NewFileWatcher([]string{name}, hclog.New(&hclog.LoggerOptions{}))
|
|
require.NoError(t, err)
|
|
require.NotNil(t, w)
|
|
|
|
}
|
|
|
|
func assertEvent(name string, watcherCh chan *FileWatcherEvent, timeout time.Duration) error {
|
|
select {
|
|
case ev := <-watcherCh:
|
|
if ev.Filenames[0] != name && !strings.Contains(ev.Filenames[0], name) {
|
|
return fmt.Errorf("filename do not match %s %s", ev.Filenames[0], name)
|
|
}
|
|
return nil
|
|
case <-time.After(timeout):
|
|
return fmt.Errorf("timedout waiting for event")
|
|
}
|
|
}
|
|
|
|
func createTempConfigFile(t *testing.T, filename string) string {
|
|
file := testutil.TempFile(t, filename)
|
|
|
|
_, err1 := file.WriteString("test config")
|
|
err2 := file.Close()
|
|
|
|
require.NoError(t, err1)
|
|
require.NoError(t, err2)
|
|
|
|
return file.Name()
|
|
}
|
|
|
|
func randomStr(length int) string {
|
|
const charset = "abcdefghijklmnopqrstuvwxyz" +
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
var seededRand *rand.Rand = rand.New(
|
|
rand.NewSource(time.Now().UnixNano()))
|
|
b := make([]byte, length)
|
|
for i := range b {
|
|
b[i] = charset[seededRand.Intn(len(charset))]
|
|
}
|
|
return string(b)
|
|
}
|