feat: make default autobuild poll intervals configurable (#1618)

* feat: make default poll intervals for autobuild and ssh ttl polling configurable
This commit is contained in:
Cian Johnston
2022-05-20 11:57:02 +01:00
committed by GitHub
parent 992b58389b
commit 52230fab56
6 changed files with 104 additions and 20 deletions

View File

@ -15,6 +15,7 @@ import (
"os" "os"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
@ -83,6 +84,23 @@ func BoolVarP(flagset *pflag.FlagSet, ptr *bool, name string, shorthand string,
flagset.BoolVarP(ptr, name, shorthand, valb, fmtUsage(usage, env)) flagset.BoolVarP(ptr, name, shorthand, valb, fmtUsage(usage, env))
} }
// DurationVarP sets a time.Duration flag on the given flag set.
func DurationVarP(flagset *pflag.FlagSet, ptr *time.Duration, name string, shorthand string, env string, def time.Duration, usage string) {
val, ok := os.LookupEnv(env)
if !ok || val == "" {
flagset.DurationVarP(ptr, name, shorthand, def, fmtUsage(usage, env))
return
}
valb, err := time.ParseDuration(val)
if err != nil {
flagset.DurationVarP(ptr, name, shorthand, def, fmtUsage(usage, env))
return
}
flagset.DurationVarP(ptr, name, shorthand, valb, fmtUsage(usage, env))
}
func fmtUsage(u string, env string) string { func fmtUsage(u string, env string) string {
if env == "" { if env == "" {
return fmt.Sprintf("%s.", u) return fmt.Sprintf("%s.", u)

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"testing" "testing"
"time"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -183,6 +184,45 @@ func TestCliflag(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, def, got) require.Equal(t, def, got)
}) })
t.Run("DurationDefault", func(t *testing.T) {
var ptr time.Duration
flagset, name, shorthand, env, usage := randomFlag()
def, _ := cryptorand.Duration()
cliflag.DurationVarP(flagset, &ptr, name, shorthand, env, def, usage)
got, err := flagset.GetDuration(name)
require.NoError(t, err)
require.Equal(t, def, got)
require.Contains(t, flagset.FlagUsages(), usage)
require.Contains(t, flagset.FlagUsages(), fmt.Sprintf(" - consumes $%s", env))
})
t.Run("DurationEnvVar", func(t *testing.T) {
var ptr time.Duration
flagset, name, shorthand, env, usage := randomFlag()
envValue, _ := cryptorand.Duration()
t.Setenv(env, envValue.String())
def, _ := cryptorand.Duration()
cliflag.DurationVarP(flagset, &ptr, name, shorthand, env, def, usage)
got, err := flagset.GetDuration(name)
require.NoError(t, err)
require.Equal(t, envValue, got)
})
t.Run("DurationFailParse", func(t *testing.T) {
var ptr time.Duration
flagset, name, shorthand, env, usage := randomFlag()
envValue, _ := cryptorand.String(10)
t.Setenv(env, envValue)
def, _ := cryptorand.Duration()
cliflag.DurationVarP(flagset, &ptr, name, shorthand, env, def, usage)
got, err := flagset.GetDuration(name)
require.NoError(t, err)
require.Equal(t, def, got)
})
} }
func randomFlag() (*pflag.FlagSet, string, string, string, string) { func randomFlag() (*pflag.FlagSet, string, string, string, string) {

View File

@ -60,17 +60,18 @@ import (
// nolint:gocyclo // nolint:gocyclo
func server() *cobra.Command { func server() *cobra.Command {
var ( var (
accessURL string accessURL string
address string address string
promEnabled bool autobuildPollInterval time.Duration
promAddress string promEnabled bool
pprofEnabled bool promAddress string
pprofAddress string pprofEnabled bool
cacheDir string pprofAddress string
dev bool cacheDir string
devUserEmail string dev bool
devUserPassword string devUserEmail string
postgresURL string devUserPassword string
postgresURL string
// provisionerDaemonCount is a uint8 to ensure a number > 0. // provisionerDaemonCount is a uint8 to ensure a number > 0.
provisionerDaemonCount uint8 provisionerDaemonCount uint8
oauth2GithubClientID string oauth2GithubClientID string
@ -361,10 +362,10 @@ func server() *cobra.Command {
return xerrors.Errorf("notify systemd: %w", err) return xerrors.Errorf("notify systemd: %w", err)
} }
lifecyclePoller := time.NewTicker(time.Minute) autobuildPoller := time.NewTicker(autobuildPollInterval)
defer lifecyclePoller.Stop() defer autobuildPoller.Stop()
lifecycleExecutor := executor.New(cmd.Context(), options.Database, logger, lifecyclePoller.C) autobuildExecutor := executor.New(cmd.Context(), options.Database, logger, autobuildPoller.C)
lifecycleExecutor.Run() autobuildExecutor.Run()
// Because the graceful shutdown includes cleaning up workspaces in dev mode, we're // Because the graceful shutdown includes cleaning up workspaces in dev mode, we're
// going to make it harder to accidentally skip the graceful shutdown by hitting ctrl+c // going to make it harder to accidentally skip the graceful shutdown by hitting ctrl+c
@ -454,6 +455,7 @@ func server() *cobra.Command {
}, },
} }
cliflag.DurationVarP(root.Flags(), &autobuildPollInterval, "autobuild-poll-interval", "", "CODER_AUTOBUILD_POLL_INTERVAL", time.Minute, "Specifies the interval at which to poll for and execute automated workspace build operations.")
cliflag.StringVarP(root.Flags(), &accessURL, "access-url", "", "CODER_ACCESS_URL", "", "Specifies the external URL to access Coder.") cliflag.StringVarP(root.Flags(), &accessURL, "access-url", "", "CODER_ACCESS_URL", "", "Specifies the external URL to access Coder.")
cliflag.StringVarP(root.Flags(), &address, "address", "a", "CODER_ADDRESS", "127.0.0.1:3000", "The address to serve the API and dashboard.") cliflag.StringVarP(root.Flags(), &address, "address", "a", "CODER_ADDRESS", "127.0.0.1:3000", "The address to serve the API and dashboard.")
cliflag.BoolVarP(root.Flags(), &promEnabled, "prometheus-enable", "", "CODER_PROMETHEUS_ENABLE", false, "Enable serving prometheus metrics on the addressdefined by --prometheus-address.") cliflag.BoolVarP(root.Flags(), &promEnabled, "prometheus-enable", "", "CODER_PROMETHEUS_ENABLE", false, "Enable serving prometheus metrics on the addressdefined by --prometheus-address.")

View File

@ -25,13 +25,14 @@ import (
"github.com/coder/coder/cryptorand" "github.com/coder/coder/cryptorand"
) )
var autostopPollInterval = 30 * time.Second var workspacePollInterval = time.Minute
var autostopNotifyCountdown = []time.Duration{30 * time.Minute} var autostopNotifyCountdown = []time.Duration{30 * time.Minute}
func ssh() *cobra.Command { func ssh() *cobra.Command {
var ( var (
stdio bool stdio bool
shuffle bool shuffle bool
wsPollInterval time.Duration
) )
cmd := &cobra.Command{ cmd := &cobra.Command{
Annotations: workspaceCommand, Annotations: workspaceCommand,
@ -155,6 +156,7 @@ func ssh() *cobra.Command {
} }
cliflag.BoolVarP(cmd.Flags(), &stdio, "stdio", "", "CODER_SSH_STDIO", false, "Specifies whether to emit SSH output over stdin/stdout.") cliflag.BoolVarP(cmd.Flags(), &stdio, "stdio", "", "CODER_SSH_STDIO", false, "Specifies whether to emit SSH output over stdin/stdout.")
cliflag.BoolVarP(cmd.Flags(), &shuffle, "shuffle", "", "CODER_SSH_SHUFFLE", false, "Specifies whether to choose a random workspace") cliflag.BoolVarP(cmd.Flags(), &shuffle, "shuffle", "", "CODER_SSH_SHUFFLE", false, "Specifies whether to choose a random workspace")
cliflag.DurationVarP(cmd.Flags(), &wsPollInterval, "workspace-poll-interval", "", "CODER_WORKSPACE_POLL_INTERVAL", workspacePollInterval, "Specifies how often to poll for workspace automated shutdown.")
_ = cmd.Flags().MarkHidden("shuffle") _ = cmd.Flags().MarkHidden("shuffle")
return cmd return cmd
@ -252,14 +254,14 @@ func getWorkspaceAndAgent(cmd *cobra.Command, client *codersdk.Client, orgID uui
func tryPollWorkspaceAutostop(ctx context.Context, client *codersdk.Client, workspace codersdk.Workspace) (stop func()) { func tryPollWorkspaceAutostop(ctx context.Context, client *codersdk.Client, workspace codersdk.Workspace) (stop func()) {
lock := flock.New(filepath.Join(os.TempDir(), "coder-autostop-notify-"+workspace.ID.String())) lock := flock.New(filepath.Join(os.TempDir(), "coder-autostop-notify-"+workspace.ID.String()))
condition := notifyCondition(ctx, client, workspace.ID, lock) condition := notifyCondition(ctx, client, workspace.ID, lock)
return notify.Notify(condition, autostopPollInterval, autostopNotifyCountdown...) return notify.Notify(condition, workspacePollInterval, autostopNotifyCountdown...)
} }
// Notify the user if the workspace is due to shutdown. // Notify the user if the workspace is due to shutdown.
func notifyCondition(ctx context.Context, client *codersdk.Client, workspaceID uuid.UUID, lock *flock.Flock) notify.Condition { func notifyCondition(ctx context.Context, client *codersdk.Client, workspaceID uuid.UUID, lock *flock.Flock) notify.Condition {
return func(now time.Time) (deadline time.Time, callback func()) { return func(now time.Time) (deadline time.Time, callback func()) {
// Keep trying to regain the lock. // Keep trying to regain the lock.
locked, err := lock.TryLockContext(ctx, autostopPollInterval) locked, err := lock.TryLockContext(ctx, workspacePollInterval)
if err != nil || !locked { if err != nil || !locked {
return time.Time{}, nil return time.Time{}, nil
} }

View File

@ -3,6 +3,7 @@ package cryptorand
import ( import (
"crypto/rand" "crypto/rand"
"encoding/binary" "encoding/binary"
"time"
"golang.org/x/xerrors" "golang.org/x/xerrors"
) )
@ -193,3 +194,13 @@ func Bool() (bool, error) {
// True if the least significant bit is 1 // True if the least significant bit is 1
return i&1 == 1, nil return i&1 == 1, nil
} }
// Duration returns a random time.Duration value
func Duration() (time.Duration, error) {
i, err := Int63()
if err != nil {
return time.Duration(0), err
}
return time.Duration(i), nil
}

View File

@ -175,3 +175,14 @@ func TestBool(t *testing.T) {
require.True(t, percentage > 48, "expected more than 48 percent of values to be true") require.True(t, percentage > 48, "expected more than 48 percent of values to be true")
require.True(t, percentage < 52, "expected less than 52 percent of values to be true") require.True(t, percentage < 52, "expected less than 52 percent of values to be true")
} }
func TestDuration(t *testing.T) {
t.Parallel()
for i := 0; i < 20; i++ {
v, err := cryptorand.Duration()
require.NoError(t, err, "unexpected error from Duration")
t.Logf("value: %v <- random?", v)
require.True(t, v >= 0.0, "values must be positive")
}
}