-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into query-store-metrics
Signed-off-by: Matej Gera <38492574+matej-g@users.noreply.github.com>
- Loading branch information
Showing
18 changed files
with
651 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
# Auto generated by busybox-updater.sh. DO NOT EDIT | ||
amd64=d8d3654786836cad8c09543704807c7a6d75de53b9e9cd21a1bbd8cb1a607004 | ||
arm64=a3435ee186dbf88238388c112761488ecd2c264dbff8957ab73f804be62a9080 | ||
arm=b063a2176f23a13007de5c447ab3552f8e355162ac54fc2a545b00b612d4c81e | ||
ppc64le=203c3f97bc34c4d5df50bd61beaa397f2a4c7cbd470c84fe7ec3db12409435d3 | ||
s390x=1a6eb305bd08bd1d38cb85a097ad776a78dd72b7c1a35094bb080788a39b174c | ||
amd64=c9f983fc55b0b74723a69c31688cca7d5a2e5b2af7c954780f29a331817982f3 | ||
arm64=1349554b18d6c349a390929c2a4855fadb003b2243aabf2cc71b931068c69279 | ||
arm=be08b36d0e8f90b6fb317d29582c632ce365a00648a81c4022c4ff79df928ad9 | ||
ppc64le=d44f541b0df83608110e695b9a1e71604ab94924954a1b18f6d76c4b5871cadd | ||
s390x=007b2b388c575d00c7234d29227bbb8216786d7ba3f86d82696dc6fe86ac1ec0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
// Copyright (c) The Thanos Authors. | ||
// Licensed under the Apache License 2.0. | ||
|
||
package extkingpin | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"time" | ||
|
||
"github.com/fsnotify/fsnotify" | ||
"github.com/go-kit/log" | ||
"github.com/go-kit/log/level" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
type fileContent interface { | ||
Content() ([]byte, error) | ||
Path() string | ||
} | ||
|
||
// PathContentReloader starts a file watcher that monitors the file indicated by fileContent.Path() and runs | ||
// reloadFunc whenever a change is detected. | ||
// A debounce timer can be configured via opts to handle situations where many "write" events are received together or | ||
// a "create" event is followed up by a "write" event, for example. Files will be effectively reloaded at the latest | ||
// after 2 times the debounce timer. By default the debouncer timer is 1 second. | ||
// To ensure renames and deletes are properly handled, the file watcher is put at the file's parent folder. See | ||
// https://github.com/fsnotify/fsnotify/issues/214 for more details. | ||
func PathContentReloader(ctx context.Context, fileContent fileContent, logger log.Logger, reloadFunc func(), debounceTime time.Duration) error { | ||
filePath, err := filepath.Abs(fileContent.Path()) | ||
if err != nil { | ||
return errors.Wrap(err, "getting absolute file path") | ||
} | ||
|
||
watcher, err := fsnotify.NewWatcher() | ||
if filePath == "" { | ||
level.Debug(logger).Log("msg", "no path detected for config reload") | ||
} | ||
if err != nil { | ||
return errors.Wrap(err, "creating file watcher") | ||
} | ||
go func() { | ||
var reloadTimer *time.Timer | ||
if debounceTime != 0 { | ||
reloadTimer = time.AfterFunc(debounceTime, func() { | ||
reloadFunc() | ||
level.Debug(logger).Log("msg", "configuration reloaded after debouncing") | ||
}) | ||
} | ||
defer watcher.Close() | ||
for { | ||
select { | ||
case <-ctx.Done(): | ||
if reloadTimer != nil { | ||
reloadTimer.Stop() | ||
} | ||
return | ||
case event := <-watcher.Events: | ||
// fsnotify sometimes sends a bunch of events without name or operation. | ||
// It's unclear what they are and why they are sent - filter them out. | ||
if event.Name == "" { | ||
break | ||
} | ||
// We are watching the file's parent folder (more details on this is done can be found below), but are | ||
// only interested in changed to the target file. Discard every other file as quickly as possible. | ||
if event.Name != filePath { | ||
break | ||
} | ||
// We only react to files being written or created. | ||
// On chmod or remove we have nothing to do. | ||
// On rename we have the old file name (not useful). A create event for the new file will come later. | ||
if event.Op&fsnotify.Write == 0 && event.Op&fsnotify.Create == 0 { | ||
break | ||
} | ||
level.Debug(logger).Log("msg", fmt.Sprintf("change detected for %s", filePath), "eventName", event.Name, "eventOp", event.Op) | ||
if reloadTimer != nil { | ||
reloadTimer.Reset(debounceTime) | ||
} | ||
case err := <-watcher.Errors: | ||
level.Error(logger).Log("msg", "watcher error", "error", err) | ||
} | ||
} | ||
}() | ||
// We watch the file's parent folder and not the file itself to better handle DELETE and RENAME events. Check | ||
// https://github.com/fsnotify/fsnotify/issues/214 for more details. | ||
if err := watcher.Add(path.Dir(filePath)); err != nil { | ||
return errors.Wrapf(err, "adding path %s to file watcher", filePath) | ||
} | ||
return nil | ||
} | ||
|
||
type staticPathContent struct { | ||
content []byte | ||
path string | ||
} | ||
|
||
var _ fileContent = (*staticPathContent)(nil) | ||
|
||
// Content returns the cached content. | ||
func (t *staticPathContent) Content() ([]byte, error) { | ||
return t.content, nil | ||
} | ||
|
||
// Path returns the path to the file that contains the content. | ||
func (t *staticPathContent) Path() string { | ||
return t.path | ||
} | ||
|
||
// NewStaticPathContent creates a new content that can be used to serve a static configuration. It copies the | ||
// configuration from `fromPath` into `destPath` to avoid confusion with file watchers. | ||
func NewStaticPathContent(fromPath string) (*staticPathContent, error) { | ||
content, err := os.ReadFile(fromPath) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "could not load test content: %s", fromPath) | ||
} | ||
return &staticPathContent{content, fromPath}, nil | ||
} | ||
|
||
// Rewrite rewrites the file backing this staticPathContent and swaps the local content cache. The file writing | ||
// is needed to trigger the file system monitor. | ||
func (t *staticPathContent) Rewrite(newContent []byte) error { | ||
t.content = newContent | ||
// Write the file to ensure possible file watcher reloaders get triggered. | ||
return os.WriteFile(t.path, newContent, 0666) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// Copyright (c) The Thanos Authors. | ||
// Licensed under the Apache License 2.0. | ||
|
||
package extkingpin | ||
|
||
import ( | ||
"context" | ||
"os" | ||
"path" | ||
"sync" | ||
"testing" | ||
"time" | ||
|
||
"github.com/go-kit/log" | ||
"github.com/thanos-io/thanos/pkg/testutil" | ||
) | ||
|
||
func TestPathContentReloader(t *testing.T) { | ||
type args struct { | ||
runSteps func(t *testing.T, testFile string, pathContent *staticPathContent) | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
wantReloads int | ||
}{ | ||
{ | ||
name: "Many operations, only rewrite triggers one reload", | ||
args: args{ | ||
runSteps: func(t *testing.T, testFile string, pathContent *staticPathContent) { | ||
testutil.Ok(t, os.Chmod(testFile, 0777)) | ||
testutil.Ok(t, os.Remove(testFile)) | ||
testutil.Ok(t, pathContent.Rewrite([]byte("test modified"))) | ||
}, | ||
}, | ||
wantReloads: 1, | ||
}, | ||
{ | ||
name: "Many operations, only rename triggers one reload", | ||
args: args{ | ||
runSteps: func(t *testing.T, testFile string, pathContent *staticPathContent) { | ||
testutil.Ok(t, os.Chmod(testFile, 0777)) | ||
testutil.Ok(t, os.Rename(testFile, testFile+".tmp")) | ||
testutil.Ok(t, os.Rename(testFile+".tmp", testFile)) | ||
}, | ||
}, | ||
wantReloads: 1, | ||
}, | ||
{ | ||
name: "Many operations, two rewrites trigger two reloads", | ||
args: args{ | ||
runSteps: func(t *testing.T, testFile string, pathContent *staticPathContent) { | ||
testutil.Ok(t, os.Chmod(testFile, 0777)) | ||
testutil.Ok(t, os.Remove(testFile)) | ||
testutil.Ok(t, pathContent.Rewrite([]byte("test modified"))) | ||
time.Sleep(2 * time.Second) | ||
testutil.Ok(t, pathContent.Rewrite([]byte("test modified again"))) | ||
}, | ||
}, | ||
wantReloads: 1, | ||
}, | ||
{ | ||
name: "Chmod doesn't trigger reload", | ||
args: args{ | ||
runSteps: func(t *testing.T, testFile string, pathContent *staticPathContent) { | ||
testutil.Ok(t, os.Chmod(testFile, 0777)) | ||
}, | ||
}, | ||
wantReloads: 0, | ||
}, | ||
{ | ||
name: "Remove doesn't trigger reload", | ||
args: args{ | ||
runSteps: func(t *testing.T, testFile string, pathContent *staticPathContent) { | ||
testutil.Ok(t, os.Remove(testFile)) | ||
}, | ||
}, | ||
wantReloads: 0, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
testFile := path.Join(t.TempDir(), "test") | ||
testutil.Ok(t, os.WriteFile(testFile, []byte("test"), 0666)) | ||
pathContent, err := NewStaticPathContent(testFile) | ||
testutil.Ok(t, err) | ||
|
||
wg := &sync.WaitGroup{} | ||
wg.Add(tt.wantReloads) | ||
|
||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
reloadCount := 0 | ||
err = PathContentReloader(ctx, pathContent, log.NewLogfmtLogger(os.Stdout), func() { | ||
reloadCount++ | ||
wg.Done() | ||
}, 100*time.Millisecond) | ||
testutil.Ok(t, err) | ||
|
||
tt.args.runSteps(t, testFile, pathContent) | ||
wg.Wait() | ||
testutil.Equals(t, tt.wantReloads, reloadCount) | ||
}) | ||
} | ||
} |
Oops, something went wrong.