feat: add resilient container restart controls
This commit is contained in:
1 parent
1bbb14470c
commit
7bb33e528c
13 files changed
+747
-28
No files matched your search
@@ -1,13 +1,26 @@
|
||||
package updateagent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/root-kdw12/hetzner-api-dyndns-records-webinterface/internal/buildinfo"
|
||||
)
|
||||
|
||||
type roundTripFunc func(*http.Request) (*http.Response, error)
|
||||
|
||||
func (f roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||
return f(request)
|
||||
}
|
||||
|
||||
func TestValidateUpdateCompatibilityAllowsDirectMajorJump(t *testing.T) {
|
||||
t.Parallel()
|
||||
current := compatibleImage("v1.4.0", "v1.4.0")
|
||||
@@ -17,6 +30,40 @@ func TestValidateUpdateCompatibilityAllowsDirectMajorJump(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitForDockerAPIRetriesBootTransient(t *testing.T) {
|
||||
t.Parallel()
|
||||
var calls atomic.Int32
|
||||
err := waitForDockerAPI(context.Background(), 100*time.Millisecond, time.Millisecond, func(context.Context) error {
|
||||
if calls.Add(1) < 3 {
|
||||
return errors.New("Docker is still starting")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil || calls.Load() != 3 {
|
||||
t.Fatalf("Docker boot transient was not retried: calls=%d err=%v", calls.Load(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitCurrentHealthyAllowsStartingButRejectsUnhealthy(t *testing.T) {
|
||||
t.Parallel()
|
||||
var calls atomic.Int32
|
||||
client := dockerTestClient(func() string {
|
||||
if calls.Add(1) == 1 {
|
||||
return "starting"
|
||||
}
|
||||
return "healthy"
|
||||
})
|
||||
inspected, err := client.waitCurrentHealthy(context.Background(), "container-id", "sha256:current", 100*time.Millisecond, time.Millisecond)
|
||||
if err != nil || inspected.State.Health == nil || inspected.State.Health.Status != "healthy" {
|
||||
t.Fatalf("starting container did not become eligible: inspect=%#v err=%v", inspected, err)
|
||||
}
|
||||
|
||||
unhealthy := dockerTestClient(func() string { return "unhealthy" })
|
||||
if _, err := unhealthy.waitCurrentHealthy(context.Background(), "container-id", "sha256:current", 100*time.Millisecond, time.Millisecond); err == nil || !strings.Contains(err.Error(), "unhealthy") {
|
||||
t.Fatalf("unhealthy container was not rejected immediately: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveCurrentVersionRequiresRuntimeAndImageMetadataToAgree(t *testing.T) {
|
||||
t.Parallel()
|
||||
config := json.RawMessage(`{"Image":"registry.example/app:v1.4.0","Labels":{"org.opencontainers.image.version":"v1.4.0"}}`)
|
||||
@@ -203,3 +250,11 @@ func contains(values []string, expected string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func dockerTestClient(health func() string) *dockerClient {
|
||||
transport := roundTripFunc(func(*http.Request) (*http.Response, error) {
|
||||
body := `{"Id":"container-id","Image":"sha256:current","Config":{"Labels":{}},"State":{"Running":true,"ExitCode":0,"Health":{"Status":"` + health() + `"}}}`
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(body))}, nil
|
||||
})
|
||||
return &dockerClient{client: &http.Client{Transport: transport}, apiPrefix: "/v1.0"}
|
||||
}
|
||||
Reference in new issue
Block a user