mirror of
https://github.com/sablierapp/sablier.git
synced 2026-01-03 11:34:58 +01:00
feat: add blocking refresh frequency (#558)
* feat: add blocking refresh frequency The refresh frequency when using the blocking strategy was set to 5 seconds. This is now configurable with a default value of 5 seconds. * fail ci if codecov fail * always upload codecov even if ci fails * remove useless panic * use fork * add -short - revert later * remove short * publish test results
This commit is contained in:
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
@@ -17,6 +17,8 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write # OIDC with Codecov
|
||||
issues: write
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -33,15 +35,26 @@ jobs:
|
||||
run: go build -v ./cmd/sablier
|
||||
|
||||
- name: Test
|
||||
run: go test -v -json -race -covermode atomic -coverprofile coverage.txt ./... 2>&1 | go tool go-junit-report -parser gojson > junit.xml
|
||||
run: go test -v -json -race -covermode atomic -coverprofile coverage.txt ./... 2>&1 | go tool go-junit-report -parser gojson -set-exit-code > junit.xml
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
if: ${{ !cancelled() }}
|
||||
uses: acouvreur/codecov-action@main
|
||||
with:
|
||||
use_oidc: true
|
||||
fail_ci_if_error: true
|
||||
|
||||
- name: Upload test results to Codecov
|
||||
if: ${{ !cancelled() }}
|
||||
uses: codecov/test-results-action@v1
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
use_oidc: true
|
||||
fail_ci_if_error: true
|
||||
|
||||
- name: Run Basic Test Results Action
|
||||
if: ${{ !cancelled() }}
|
||||
uses: codecov/basic-test-results@v1
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: junit.xml
|
||||
disable-search: true
|
||||
2
Makefile
2
Makefile
@@ -27,7 +27,7 @@ build:
|
||||
go build -v ./cmd/sablier
|
||||
|
||||
test:
|
||||
go test -v ./...
|
||||
go test ./...
|
||||
|
||||
plugins: build-plugin-traefik test-plugin-traefik build-plugin-caddy test-plugin-caddy
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sablierapp/sablier/cmd/healthcheck"
|
||||
"github.com/sablierapp/sablier/cmd/version"
|
||||
@@ -43,7 +44,7 @@ It provides an integrations with multiple reverse proxies and different loading
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "configFile", "", "Config file path. If not defined, looks for sablier.(yml|yaml|toml) in /etc/sablier/ > $XDG_CONFIG_HOME > $HOME/.config/ and current directory")
|
||||
|
||||
startCmd := NewCmd()
|
||||
startCmd := newStartCommand()
|
||||
// Provider flags
|
||||
startCmd.Flags().StringVar(&conf.Provider.Name, "provider.name", "docker", fmt.Sprintf("Provider to use to manage containers %v", config.GetProviders()))
|
||||
viper.BindPFlag("provider.name", startCmd.Flags().Lookup("provider.name"))
|
||||
@@ -84,6 +85,8 @@ It provides an integrations with multiple reverse proxies and different loading
|
||||
viper.BindPFlag("strategy.dynamic.default-refresh-frequency", startCmd.Flags().Lookup("strategy.dynamic.default-refresh-frequency"))
|
||||
startCmd.Flags().DurationVar(&conf.Strategy.Blocking.DefaultTimeout, "strategy.blocking.default-timeout", 1*time.Minute, "Default timeout used for blocking strategy")
|
||||
viper.BindPFlag("strategy.blocking.default-timeout", startCmd.Flags().Lookup("strategy.blocking.default-timeout"))
|
||||
startCmd.Flags().DurationVar(&conf.Strategy.Blocking.DefaultRefreshFrequency, "strategy.blocking.default-refresh-frequency", 5*time.Second, "Default refresh frequency at which the instances status are checked for blocking strategy")
|
||||
viper.BindPFlag("strategy.blocking.default-refresh-frequency", startCmd.Flags().Lookup("strategy.blocking.default-refresh-frequency"))
|
||||
|
||||
rootCmd.AddCommand(startCmd)
|
||||
rootCmd.AddCommand(version.NewCmd())
|
||||
@@ -115,10 +118,8 @@ func initializeConfig(cmd *cobra.Command) error {
|
||||
// if we cannot parse the config file.
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
// It's okay if there isn't a config file
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
||||
return err
|
||||
} else if cfgFile != "" {
|
||||
// But if we explicitely defined the config file it should return the error
|
||||
var configFileNotFoundError viper.ConfigFileNotFoundError
|
||||
if !errors.As(err, &configFileNotFoundError) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -149,7 +150,7 @@ func bindFlags(cmd *cobra.Command, v *viper.Viper) {
|
||||
})
|
||||
}
|
||||
|
||||
func NewCmd() *cobra.Command {
|
||||
var newStartCommand = func() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "Start the Sablier server",
|
||||
|
||||
186
cmd/sablier/cmd_test.go
Normal file
186
cmd/sablier/cmd_test.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/sablierapp/sablier/pkg/config"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestDefault(t *testing.T) {
|
||||
testDir, err := os.Getwd()
|
||||
require.NoError(t, err, "error getting the current working directory")
|
||||
|
||||
wantConfig, err := os.ReadFile(filepath.Join(testDir, "testdata", "config_default.json"))
|
||||
require.NoError(t, err, "error reading test config file")
|
||||
|
||||
// CHANGE `startCmd` behavior to only print the config, this is for testing purposes only
|
||||
newStartCommand = mockStartCommand
|
||||
|
||||
t.Run("config file", func(t *testing.T) {
|
||||
conf = config.NewConfig()
|
||||
cmd := NewRootCommand()
|
||||
output := &bytes.Buffer{}
|
||||
cmd.SetOut(output)
|
||||
cmd.SetArgs([]string{
|
||||
"start",
|
||||
})
|
||||
cmd.Execute()
|
||||
|
||||
gotOutput := output.String()
|
||||
|
||||
assert.Equal(t, string(wantConfig), gotOutput)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPrecedence(t *testing.T) {
|
||||
testDir, err := os.Getwd()
|
||||
require.NoError(t, err, "error getting the current working directory")
|
||||
|
||||
// CHANGE `startCmd` behavior to only print the config, this is for testing purposes only
|
||||
newStartCommand = mockStartCommand
|
||||
|
||||
t.Run("config file", func(t *testing.T) {
|
||||
wantConfig, err := os.ReadFile(filepath.Join(testDir, "testdata", "config_yaml_wanted.json"))
|
||||
require.NoError(t, err, "error reading test config file")
|
||||
|
||||
conf = config.NewConfig()
|
||||
cmd := NewRootCommand()
|
||||
output := &bytes.Buffer{}
|
||||
cmd.SetOut(output)
|
||||
cmd.SetArgs([]string{
|
||||
"--configFile", filepath.Join(testDir, "testdata", "config.yml"),
|
||||
"start",
|
||||
})
|
||||
cmd.Execute()
|
||||
|
||||
gotOutput := output.String()
|
||||
|
||||
assert.Equal(t, string(wantConfig), gotOutput)
|
||||
})
|
||||
|
||||
t.Run("env var", func(t *testing.T) {
|
||||
setEnvsFromFile(filepath.Join(testDir, "testdata", "config.env"))
|
||||
defer unsetEnvsFromFile(filepath.Join(testDir, "testdata", "config.env"))
|
||||
|
||||
wantConfig, err := os.ReadFile(filepath.Join(testDir, "testdata", "config_env_wanted.json"))
|
||||
require.NoError(t, err, "error reading test config file")
|
||||
|
||||
conf = config.NewConfig()
|
||||
cmd := NewRootCommand()
|
||||
output := &bytes.Buffer{}
|
||||
cmd.SetOut(output)
|
||||
cmd.SetArgs([]string{
|
||||
"--configFile", filepath.Join(testDir, "testdata", "config.yml"),
|
||||
"start",
|
||||
})
|
||||
cmd.Execute()
|
||||
|
||||
gotOutput := output.String()
|
||||
|
||||
assert.Equal(t, string(wantConfig), gotOutput)
|
||||
})
|
||||
|
||||
t.Run("flag", func(t *testing.T) {
|
||||
setEnvsFromFile(filepath.Join(testDir, "testdata", "config.env"))
|
||||
defer unsetEnvsFromFile(filepath.Join(testDir, "testdata", "config.env"))
|
||||
|
||||
wantConfig, err := os.ReadFile(filepath.Join(testDir, "testdata", "config_cli_wanted.json"))
|
||||
require.NoError(t, err, "error reading test config file")
|
||||
|
||||
cmd := NewRootCommand()
|
||||
output := &bytes.Buffer{}
|
||||
conf = config.NewConfig()
|
||||
cmd.SetOut(output)
|
||||
cmd.SetArgs([]string{
|
||||
"--configFile", filepath.Join(testDir, "testdata", "config.yml"),
|
||||
"start",
|
||||
"--provider.name", "cli",
|
||||
"--provider.kubernetes.qps", "256",
|
||||
"--provider.kubernetes.burst", "512",
|
||||
"--provider.kubernetes.delimiter", "_",
|
||||
"--server.port", "3333",
|
||||
"--server.base-path", "/cli/",
|
||||
"--storage.file", "/tmp/cli.json",
|
||||
"--sessions.default-duration", "3h",
|
||||
"--sessions.expiration-interval", "3h",
|
||||
"--logging.level", "info",
|
||||
"--strategy.dynamic.custom-themes-path", "/tmp/cli/themes",
|
||||
// Must use `=` see https://github.com/spf13/cobra/issues/613
|
||||
"--strategy.dynamic.show-details-by-default=false",
|
||||
"--strategy.dynamic.default-theme", "cli",
|
||||
"--strategy.dynamic.default-refresh-frequency", "3h",
|
||||
"--strategy.blocking.default-timeout", "3h",
|
||||
"--strategy.blocking.default-refresh-frequency", "3h",
|
||||
})
|
||||
cmd.Execute()
|
||||
|
||||
gotOutput := output.String()
|
||||
|
||||
assert.Equal(t, string(wantConfig), gotOutput)
|
||||
})
|
||||
}
|
||||
|
||||
func setEnvsFromFile(path string) {
|
||||
readFile, err := os.Open(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer readFile.Close()
|
||||
|
||||
fileScanner := bufio.NewScanner(readFile)
|
||||
|
||||
fileScanner.Split(bufio.ScanLines)
|
||||
|
||||
for fileScanner.Scan() {
|
||||
split := strings.Split(fileScanner.Text(), "=")
|
||||
os.Setenv(split[0], split[1])
|
||||
}
|
||||
}
|
||||
|
||||
func unsetEnvsFromFile(path string) {
|
||||
readFile, err := os.Open(path)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer readFile.Close()
|
||||
|
||||
fileScanner := bufio.NewScanner(readFile)
|
||||
|
||||
fileScanner.Split(bufio.ScanLines)
|
||||
|
||||
for fileScanner.Scan() {
|
||||
split := strings.Split(fileScanner.Text(), "=")
|
||||
os.Unsetenv(split[0])
|
||||
}
|
||||
}
|
||||
|
||||
func mockStartCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "InstanceStart the Sablier server",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
viper.Unmarshal(&conf)
|
||||
|
||||
out := cmd.OutOrStdout()
|
||||
|
||||
encoder := json.NewEncoder(out)
|
||||
|
||||
encoder.SetIndent("", " ")
|
||||
encoder.Encode(conf)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
@@ -36,6 +36,7 @@ func Start(ctx context.Context, conf config.Config) error {
|
||||
}
|
||||
|
||||
s := sablier.New(logger, store, provider)
|
||||
s.BlockingRefreshFrequency = conf.Strategy.Blocking.DefaultRefreshFrequency
|
||||
|
||||
groups, err := provider.InstanceGroups(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -13,4 +13,5 @@ STRATEGY_DYNAMIC_CUSTOM_THEMES_PATH=/tmp/envvar/themes
|
||||
STRATEGY_SHOW_DETAILS_BY_DEFAULT=false
|
||||
STRATEGY_DYNAMIC_DEFAULT_THEME=envvar
|
||||
STRATEGY_DYNAMIC_DEFAULT_REFRESH_FREQUENCY=2h
|
||||
STRATEGY_BLOCKING_DEFAULT_TIMEOUT=2h
|
||||
STRATEGY_BLOCKING_DEFAULT_TIMEOUT=2h
|
||||
STRATEGY_BLOCKING_DEFAULT_REFRESH_FREQUENCY=2h
|
||||
@@ -22,4 +22,5 @@ strategy:
|
||||
default-theme: configfile
|
||||
default-refresh-frequency: 1h
|
||||
blocking:
|
||||
default-timeout: 1h
|
||||
default-timeout: 1h
|
||||
default-refresh-frequency: 1h
|
||||
@@ -30,7 +30,8 @@
|
||||
"DefaultRefreshFrequency": 10800000000000
|
||||
},
|
||||
"Blocking": {
|
||||
"DefaultTimeout": 10800000000000
|
||||
"DefaultTimeout": 10800000000000,
|
||||
"DefaultRefreshFrequency": 10800000000000
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,8 @@
|
||||
"DefaultRefreshFrequency": 5000000000
|
||||
},
|
||||
"Blocking": {
|
||||
"DefaultTimeout": 60000000000
|
||||
"DefaultTimeout": 60000000000,
|
||||
"DefaultRefreshFrequency": 5000000000
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,8 @@
|
||||
"DefaultRefreshFrequency": 7200000000000
|
||||
},
|
||||
"Blocking": {
|
||||
"DefaultTimeout": 7200000000000
|
||||
"DefaultTimeout": 7200000000000,
|
||||
"DefaultRefreshFrequency": 7200000000000
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,8 @@
|
||||
"DefaultRefreshFrequency": 3600000000000
|
||||
},
|
||||
"Blocking": {
|
||||
"DefaultTimeout": 3600000000000
|
||||
"DefaultTimeout": 3600000000000,
|
||||
"DefaultRefreshFrequency": 3600000000000
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,26 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
config2 "github.com/sablierapp/sablier/pkg/config"
|
||||
"context"
|
||||
"github.com/sablierapp/sablier/pkg/config"
|
||||
"github.com/sablierapp/sablier/pkg/sablier"
|
||||
"github.com/sablierapp/sablier/pkg/theme"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:generate go tool mockgen -package apitest -source=api.go -destination=apitest/mocks_sablier.go *
|
||||
|
||||
type Sablier interface {
|
||||
RequestSession(ctx context.Context, names []string, duration time.Duration) (*sablier.SessionState, error)
|
||||
RequestSessionGroup(ctx context.Context, group string, duration time.Duration) (*sablier.SessionState, error)
|
||||
RequestReadySession(ctx context.Context, names []string, duration time.Duration, timeout time.Duration) (*sablier.SessionState, error)
|
||||
RequestReadySessionGroup(ctx context.Context, group string, duration time.Duration, timeout time.Duration) (*sablier.SessionState, error)
|
||||
}
|
||||
|
||||
type ServeStrategy struct {
|
||||
Theme *theme.Themes
|
||||
|
||||
Sablier sablier.Sablier
|
||||
StrategyConfig config2.Strategy
|
||||
SessionsConfig config2.Sessions
|
||||
Sablier Sablier
|
||||
StrategyConfig config.Strategy
|
||||
SessionsConfig config.Sessions
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package api
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/neilotoole/slogt"
|
||||
"github.com/sablierapp/sablier/internal/api/apitest"
|
||||
config2 "github.com/sablierapp/sablier/pkg/config"
|
||||
"github.com/sablierapp/sablier/pkg/sablier/sabliertest"
|
||||
"github.com/sablierapp/sablier/pkg/theme"
|
||||
"go.uber.org/mock/gomock"
|
||||
"gotest.tools/v3/assert"
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func NewApiTest(t *testing.T) (app *gin.Engine, router *gin.RouterGroup, strategy *ServeStrategy, mock *sabliertest.MockSablier) {
|
||||
func NewApiTest(t *testing.T) (app *gin.Engine, router *gin.RouterGroup, strategy *ServeStrategy, mock *apitest.MockSablier) {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
ctrl := gomock.NewController(t)
|
||||
@@ -22,7 +22,7 @@ func NewApiTest(t *testing.T) (app *gin.Engine, router *gin.RouterGroup, strateg
|
||||
|
||||
app = gin.New()
|
||||
router = app.Group("/api")
|
||||
mock = sabliertest.NewMockSablier(ctrl)
|
||||
mock = apitest.NewMockSablier(ctrl)
|
||||
strategy = &ServeStrategy{
|
||||
Theme: th,
|
||||
Sablier: mock,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: sablier.go
|
||||
// Source: api.go
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package sabliertest -source=sablier.go -destination=sabliertest/mocks_sablier.go *
|
||||
// mockgen -package apitest -source=api.go -destination=apitest/mocks_sablier.go *
|
||||
//
|
||||
|
||||
// Package sabliertest is a generated GoMock package.
|
||||
package sabliertest
|
||||
// Package apitest is a generated GoMock package.
|
||||
package apitest
|
||||
|
||||
import (
|
||||
context "context"
|
||||
@@ -42,32 +42,6 @@ func (m *MockSablier) EXPECT() *MockSablierMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// GroupWatch mocks base method.
|
||||
func (m *MockSablier) GroupWatch(ctx context.Context) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "GroupWatch", ctx)
|
||||
}
|
||||
|
||||
// GroupWatch indicates an expected call of GroupWatch.
|
||||
func (mr *MockSablierMockRecorder) GroupWatch(ctx any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GroupWatch", reflect.TypeOf((*MockSablier)(nil).GroupWatch), ctx)
|
||||
}
|
||||
|
||||
// RemoveInstance mocks base method.
|
||||
func (m *MockSablier) RemoveInstance(ctx context.Context, name string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RemoveInstance", ctx, name)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// RemoveInstance indicates an expected call of RemoveInstance.
|
||||
func (mr *MockSablierMockRecorder) RemoveInstance(ctx, name any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveInstance", reflect.TypeOf((*MockSablier)(nil).RemoveInstance), ctx, name)
|
||||
}
|
||||
|
||||
// RequestReadySession mocks base method.
|
||||
func (m *MockSablier) RequestReadySession(ctx context.Context, names []string, duration, timeout time.Duration) (*sablier.SessionState, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -127,29 +101,3 @@ func (mr *MockSablierMockRecorder) RequestSessionGroup(ctx, group, duration any)
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestSessionGroup", reflect.TypeOf((*MockSablier)(nil).RequestSessionGroup), ctx, group, duration)
|
||||
}
|
||||
|
||||
// SetGroups mocks base method.
|
||||
func (m *MockSablier) SetGroups(groups map[string][]string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetGroups", groups)
|
||||
}
|
||||
|
||||
// SetGroups indicates an expected call of SetGroups.
|
||||
func (mr *MockSablierMockRecorder) SetGroups(groups any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetGroups", reflect.TypeOf((*MockSablier)(nil).SetGroups), groups)
|
||||
}
|
||||
|
||||
// StopAllUnregisteredInstances mocks base method.
|
||||
func (m *MockSablier) StopAllUnregisteredInstances(ctx context.Context) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "StopAllUnregisteredInstances", ctx)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// StopAllUnregisteredInstances indicates an expected call of StopAllUnregisteredInstances.
|
||||
func (mr *MockSablierMockRecorder) StopAllUnregisteredInstances(ctx any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StopAllUnregisteredInstances", reflect.TypeOf((*MockSablier)(nil).StopAllUnregisteredInstances), ctx)
|
||||
}
|
||||
@@ -10,7 +10,8 @@ type DynamicStrategy struct {
|
||||
}
|
||||
|
||||
type BlockingStrategy struct {
|
||||
DefaultTimeout time.Duration `mapstructure:"DEFAULT_TIMEOUT" yaml:"defaultTimeout" default:"1m"`
|
||||
DefaultTimeout time.Duration `mapstructure:"DEFAULT_TIMEOUT" yaml:"defaultTimeout" default:"1m"`
|
||||
DefaultRefreshFrequency time.Duration `mapstructure:"DEFAULT_REFRESH_FREQUENCY" yaml:"defaultRefreshFrequency" default:"5s"`
|
||||
}
|
||||
|
||||
type Strategy struct {
|
||||
@@ -35,6 +36,7 @@ func newDynamicStrategy() DynamicStrategy {
|
||||
|
||||
func newBlockingStrategy() BlockingStrategy {
|
||||
return BlockingStrategy{
|
||||
DefaultTimeout: 1 * time.Minute,
|
||||
DefaultTimeout: 1 * time.Minute,
|
||||
DefaultRefreshFrequency: 5 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
// as running instances by Sablier.
|
||||
// By default, Sablier does not stop all already running instances. Meaning that you need to make an
|
||||
// initial request in order to trigger the scaling to zero.
|
||||
func (s *sablier) StopAllUnregisteredInstances(ctx context.Context) error {
|
||||
func (s *Sablier) StopAllUnregisteredInstances(ctx context.Context) error {
|
||||
instances, err := s.provider.InstanceList(ctx, provider.InstanceListOptions{
|
||||
All: false, // Only running instances
|
||||
})
|
||||
@@ -40,7 +40,7 @@ func (s *sablier) StopAllUnregisteredInstances(ctx context.Context) error {
|
||||
return waitGroup.Wait()
|
||||
}
|
||||
|
||||
func (s *sablier) stopFunc(ctx context.Context, name string) func() error {
|
||||
func (s *Sablier) stopFunc(ctx context.Context, name string) func() error {
|
||||
return func() error {
|
||||
err := s.provider.InstanceStop(ctx, name)
|
||||
if err != nil {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s *sablier) GroupWatch(ctx context.Context) {
|
||||
func (s *Sablier) GroupWatch(ctx context.Context) {
|
||||
// This should be changed to event based instead of polling.
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
for {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s *sablier) InstanceRequest(ctx context.Context, name string, duration time.Duration) (InstanceInfo, error) {
|
||||
func (s *Sablier) InstanceRequest(ctx context.Context, name string, duration time.Duration) (InstanceInfo, error) {
|
||||
if name == "" {
|
||||
return InstanceInfo{}, errors.New("instance name cannot be empty")
|
||||
}
|
||||
|
||||
@@ -8,41 +8,32 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:generate go tool mockgen -package sabliertest -source=sablier.go -destination=sabliertest/mocks_sablier.go *
|
||||
|
||||
type Sablier interface {
|
||||
RequestSession(ctx context.Context, names []string, duration time.Duration) (*SessionState, error)
|
||||
RequestSessionGroup(ctx context.Context, group string, duration time.Duration) (*SessionState, error)
|
||||
RequestReadySession(ctx context.Context, names []string, duration time.Duration, timeout time.Duration) (*SessionState, error)
|
||||
RequestReadySessionGroup(ctx context.Context, group string, duration time.Duration, timeout time.Duration) (*SessionState, error)
|
||||
|
||||
RemoveInstance(ctx context.Context, name string) error
|
||||
SetGroups(groups map[string][]string)
|
||||
StopAllUnregisteredInstances(ctx context.Context) error
|
||||
GroupWatch(ctx context.Context)
|
||||
}
|
||||
|
||||
type sablier struct {
|
||||
type Sablier struct {
|
||||
provider Provider
|
||||
sessions Store
|
||||
|
||||
groupsMu sync.RWMutex
|
||||
groups map[string][]string
|
||||
|
||||
// BlockingRefreshFrequency is the frequency at which the instances are checked
|
||||
// against the provider. Defaults to 5 seconds.
|
||||
BlockingRefreshFrequency time.Duration
|
||||
|
||||
l *slog.Logger
|
||||
}
|
||||
|
||||
func New(logger *slog.Logger, store Store, provider Provider) Sablier {
|
||||
return &sablier{
|
||||
provider: provider,
|
||||
sessions: store,
|
||||
groupsMu: sync.RWMutex{},
|
||||
groups: map[string][]string{},
|
||||
l: logger,
|
||||
func New(logger *slog.Logger, store Store, provider Provider) *Sablier {
|
||||
return &Sablier{
|
||||
provider: provider,
|
||||
sessions: store,
|
||||
groupsMu: sync.RWMutex{},
|
||||
groups: map[string][]string{},
|
||||
l: logger,
|
||||
BlockingRefreshFrequency: 5 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sablier) SetGroups(groups map[string][]string) {
|
||||
func (s *Sablier) SetGroups(groups map[string][]string) {
|
||||
s.groupsMu.Lock()
|
||||
defer s.groupsMu.Unlock()
|
||||
if groups == nil {
|
||||
@@ -55,6 +46,6 @@ func (s *sablier) SetGroups(groups map[string][]string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sablier) RemoveInstance(ctx context.Context, name string) error {
|
||||
func (s *Sablier) RemoveInstance(ctx context.Context, name string) error {
|
||||
return s.sessions.Delete(ctx, name)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func setupSablier(t *testing.T) (sablier.Sablier, *storetest.MockStore, *providertest.MockProvider) {
|
||||
func setupSablier(t *testing.T) (*sablier.Sablier, *storetest.MockStore, *providertest.MockProvider) {
|
||||
t.Helper()
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ type InstanceInfoWithError struct {
|
||||
Error error `json:"error"`
|
||||
}
|
||||
|
||||
func (s *sablier) RequestSession(ctx context.Context, names []string, duration time.Duration) (sessionState *SessionState, err error) {
|
||||
func (s *Sablier) RequestSession(ctx context.Context, names []string, duration time.Duration) (sessionState *SessionState, err error) {
|
||||
if len(names) == 0 {
|
||||
return nil, fmt.Errorf("names cannot be empty")
|
||||
}
|
||||
@@ -47,7 +47,7 @@ func (s *sablier) RequestSession(ctx context.Context, names []string, duration t
|
||||
return sessionState, nil
|
||||
}
|
||||
|
||||
func (s *sablier) RequestSessionGroup(ctx context.Context, group string, duration time.Duration) (sessionState *SessionState, err error) {
|
||||
func (s *Sablier) RequestSessionGroup(ctx context.Context, group string, duration time.Duration) (sessionState *SessionState, err error) {
|
||||
if len(group) == 0 {
|
||||
return nil, fmt.Errorf("group is mandatory")
|
||||
}
|
||||
@@ -67,7 +67,7 @@ func (s *sablier) RequestSessionGroup(ctx context.Context, group string, duratio
|
||||
return s.RequestSession(ctx, names, duration)
|
||||
}
|
||||
|
||||
func (s *sablier) RequestReadySession(ctx context.Context, names []string, duration time.Duration, timeout time.Duration) (*SessionState, error) {
|
||||
func (s *Sablier) RequestReadySession(ctx context.Context, names []string, duration time.Duration, timeout time.Duration) (*SessionState, error) {
|
||||
session, err := s.RequestSession(ctx, names, duration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -77,7 +77,7 @@ func (s *sablier) RequestReadySession(ctx context.Context, names []string, durat
|
||||
return session, nil
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
ticker := time.NewTicker(s.BlockingRefreshFrequency)
|
||||
readiness := make(chan *SessionState)
|
||||
errch := make(chan error)
|
||||
quit := make(chan struct{})
|
||||
@@ -121,7 +121,7 @@ func (s *sablier) RequestReadySession(ctx context.Context, names []string, durat
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sablier) RequestReadySessionGroup(ctx context.Context, group string, duration time.Duration, timeout time.Duration) (sessionState *SessionState, err error) {
|
||||
func (s *Sablier) RequestReadySessionGroup(ctx context.Context, group string, duration time.Duration, timeout time.Duration) (sessionState *SessionState, err error) {
|
||||
|
||||
if len(group) == 0 {
|
||||
return nil, fmt.Errorf("group is mandatory")
|
||||
|
||||
Reference in New Issue
Block a user