feat(tasks): implement atomic claims and leases
This commit is contained in:
@@ -15,10 +15,16 @@ const (
|
||||
AssetDirectoryEnvironment = "CMROUBAO_ASSET_DIR"
|
||||
TLSCertificateEnvironment = "CMROUBAO_TLS_CERT_FILE"
|
||||
TLSPrivateKeyEnvironment = "CMROUBAO_TLS_KEY_FILE"
|
||||
ClaimLeaseEnvironment = "CMROUBAO_CLAIM_LEASE"
|
||||
RunningLeaseEnvironment = "CMROUBAO_RUNNING_LEASE"
|
||||
ReadinessTTLEnvironment = "CMROUBAO_READINESS_TTL"
|
||||
|
||||
defaultHTTPAddress = "127.0.0.1:8080"
|
||||
defaultDatabasePath = "var/cmroubao.db"
|
||||
defaultAssetDirectory = "var/assets"
|
||||
defaultClaimLease = 10 * time.Minute
|
||||
defaultRunningLease = 90 * time.Second
|
||||
defaultReadinessTTL = 2 * time.Minute
|
||||
)
|
||||
|
||||
type LookupEnvironment func(string) (string, bool)
|
||||
@@ -35,6 +41,9 @@ type Config struct {
|
||||
IdleTimeout time.Duration
|
||||
ShutdownTimeout time.Duration
|
||||
MaxHeaderBytes int
|
||||
ClaimLease time.Duration
|
||||
RunningLease time.Duration
|
||||
ReadinessTTL time.Duration
|
||||
}
|
||||
|
||||
func Load(lookup LookupEnvironment) (Config, error) {
|
||||
@@ -98,6 +107,36 @@ func Load(lookup LookupEnvironment) (Config, error) {
|
||||
" must use loopback unless TLS is configured",
|
||||
)
|
||||
}
|
||||
claimLease, err := durationEnvironment(
|
||||
lookup,
|
||||
ClaimLeaseEnvironment,
|
||||
defaultClaimLease,
|
||||
time.Minute,
|
||||
30*time.Minute,
|
||||
)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
runningLease, err := durationEnvironment(
|
||||
lookup,
|
||||
RunningLeaseEnvironment,
|
||||
defaultRunningLease,
|
||||
30*time.Second,
|
||||
10*time.Minute,
|
||||
)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
readinessTTL, err := durationEnvironment(
|
||||
lookup,
|
||||
ReadinessTTLEnvironment,
|
||||
defaultReadinessTTL,
|
||||
30*time.Second,
|
||||
10*time.Minute,
|
||||
)
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
|
||||
return Config{
|
||||
HTTPAddress: httpAddress,
|
||||
@@ -111,9 +150,34 @@ func Load(lookup LookupEnvironment) (Config, error) {
|
||||
IdleTimeout: 60 * time.Second,
|
||||
ShutdownTimeout: 10 * time.Second,
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
ClaimLease: claimLease,
|
||||
RunningLease: runningLease,
|
||||
ReadinessTTL: readinessTTL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func durationEnvironment(
|
||||
lookup LookupEnvironment,
|
||||
name string,
|
||||
defaultValue time.Duration,
|
||||
minimum time.Duration,
|
||||
maximum time.Duration,
|
||||
) (time.Duration, error) {
|
||||
value, exists := lookup(name)
|
||||
if !exists {
|
||||
return defaultValue, nil
|
||||
}
|
||||
value = strings.TrimSpace(value)
|
||||
duration, err := time.ParseDuration(value)
|
||||
if err != nil || duration < minimum || duration > maximum {
|
||||
return 0, errors.New(
|
||||
name + " must be a duration between " +
|
||||
minimum.String() + " and " + maximum.String(),
|
||||
)
|
||||
}
|
||||
return duration, nil
|
||||
}
|
||||
|
||||
func cleanOptionalPath(value string) string {
|
||||
if value == "" {
|
||||
return ""
|
||||
|
||||
@@ -29,6 +29,16 @@ func TestLoadUsesSafeDefaults(t *testing.T) {
|
||||
cfg.MaxHeaderBytes <= 0 {
|
||||
t.Fatal("server safety limits must all be positive")
|
||||
}
|
||||
if cfg.ClaimLease != 10*time.Minute ||
|
||||
cfg.RunningLease != 90*time.Second ||
|
||||
cfg.ReadinessTTL != 2*time.Minute {
|
||||
t.Fatalf(
|
||||
"lifecycle durations = %s / %s / %s",
|
||||
cfg.ClaimLease,
|
||||
cfg.RunningLease,
|
||||
cfg.ReadinessTTL,
|
||||
)
|
||||
}
|
||||
if cfg.ShutdownTimeout > 30*time.Second {
|
||||
t.Fatalf("ShutdownTimeout = %s", cfg.ShutdownTimeout)
|
||||
}
|
||||
@@ -41,6 +51,9 @@ func TestLoadAcceptsExplicitConfiguration(t *testing.T) {
|
||||
AssetDirectoryEnvironment: "tmp/assets",
|
||||
TLSCertificateEnvironment: "tmp/server.crt",
|
||||
TLSPrivateKeyEnvironment: "tmp/server.key",
|
||||
ClaimLeaseEnvironment: "15m",
|
||||
RunningLeaseEnvironment: "2m",
|
||||
ReadinessTTLEnvironment: "3m",
|
||||
}
|
||||
|
||||
cfg, err := Load(mapEnvironment(values))
|
||||
@@ -65,6 +78,16 @@ func TestLoadAcceptsExplicitConfiguration(t *testing.T) {
|
||||
cfg.TLSPrivateKey,
|
||||
)
|
||||
}
|
||||
if cfg.ClaimLease != 15*time.Minute ||
|
||||
cfg.RunningLease != 2*time.Minute ||
|
||||
cfg.ReadinessTTL != 3*time.Minute {
|
||||
t.Fatalf(
|
||||
"lifecycle durations = %s / %s / %s",
|
||||
cfg.ClaimLease,
|
||||
cfg.RunningLease,
|
||||
cfg.ReadinessTTL,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadRejectsUnsafeOrInvalidValues(t *testing.T) {
|
||||
@@ -145,6 +168,24 @@ func TestLoadRejectsUnsafeOrInvalidValues(t *testing.T) {
|
||||
TLSPrivateKeyEnvironment: " ",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "claim lease below minimum",
|
||||
values: map[string]string{
|
||||
ClaimLeaseEnvironment: "59s",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "running lease above maximum",
|
||||
values: map[string]string{
|
||||
RunningLeaseEnvironment: "11m",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid readiness TTL",
|
||||
values: map[string]string{
|
||||
ReadinessTTLEnvironment: "soon",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
Reference in New Issue
Block a user