mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
add Healthy method to Container that derives it from status string
This commit is contained in:
@@ -28,6 +28,7 @@ require (
|
|||||||
github.com/siderolabs/discovery-client v0.1.9
|
github.com/siderolabs/discovery-client v0.1.9
|
||||||
github.com/siderolabs/grpc-proxy v0.5.1
|
github.com/siderolabs/grpc-proxy v0.5.1
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/vishvananda/netlink v1.3.0
|
github.com/vishvananda/netlink v1.3.0
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
@@ -63,6 +64,7 @@ require (
|
|||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240919170804-a4978c8e603a // indirect
|
github.com/charmbracelet/x/exp/strings v0.0.0-20240919170804-a4978c8e603a // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.0 // indirect
|
github.com/charmbracelet/x/term v0.2.0 // indirect
|
||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
|
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
|
||||||
@@ -147,6 +149,7 @@ require (
|
|||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/polydawn/refmt v0.89.0 // indirect
|
github.com/polydawn/refmt v0.89.0 // indirect
|
||||||
github.com/posener/complete v1.2.3 // indirect
|
github.com/posener/complete v1.2.3 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
@@ -171,6 +174,7 @@ require (
|
|||||||
golang.org/x/tools v0.26.0 // indirect
|
golang.org/x/tools v0.26.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gotest.tools/v3 v3.5.1 // indirect
|
gotest.tools/v3 v3.5.1 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
|
||||||
lukechampine.com/blake3 v1.3.0 // indirect
|
lukechampine.com/blake3 v1.3.0 // indirect
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import "github.com/docker/docker/api/types"
|
import (
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LabelServiceID = "uncloud.service.id"
|
LabelServiceID = "uncloud.service.id"
|
||||||
@@ -11,4 +14,34 @@ type Container struct {
|
|||||||
types.Container
|
types.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement health related methods.
|
// runningStatusRegex matches the status string of a running container.
|
||||||
|
// - "Up 3 minutes (healthy)" -> groups: ["Up 3 minutes (healthy)", "healthy"]
|
||||||
|
// - "Up 5 seconds" -> groups: ["Up 5 seconds", ""]
|
||||||
|
// - "Up 2 hours (unhealthy)" -> groups: ["Up 2 hours (unhealthy)", "unhealthy"]
|
||||||
|
// - "Up 1 minute (health: starting)" -> groups: ["Up 1 minute (health: starting)", "health: starting"]
|
||||||
|
// - "Restarting (0) 5 seconds ago" -> no match
|
||||||
|
// See https://github.com/moby/moby/blob/c130ce1f5d1e38b98a97044a39557de43bc0d58f/container/state.go#L77-L90
|
||||||
|
// for more details on how the status string for a running container is formatted.
|
||||||
|
var runningStatusRegex = regexp.MustCompile(`^Up [^(]+(?:\(([^)]+)\))?$`)
|
||||||
|
|
||||||
|
// Healthy determines if the container is running and healthy based on its status string.
|
||||||
|
// A running container with no health check configured is considered healthy.
|
||||||
|
func (c *Container) Healthy() bool {
|
||||||
|
if c.State != "running" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := runningStatusRegex.FindStringSubmatch(c.Status)
|
||||||
|
// Not "Up" or invalid format.
|
||||||
|
if matches == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's no health status (no health check configured so no parentheses), container is considered healthy.
|
||||||
|
if matches[1] == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the health status in parentheses is "healthy", the container is considered healthy.
|
||||||
|
return matches[1] == types.Healthy
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContainer_Healthy(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("exited", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
c := &Container{Container: types.Container{
|
||||||
|
State: "exited",
|
||||||
|
Status: "Exited (0) 2 minutes ago",
|
||||||
|
}}
|
||||||
|
assert.False(t, c.Healthy())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("running with no health check", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
c := &Container{Container: types.Container{
|
||||||
|
State: "running",
|
||||||
|
Status: "Up 5 minutes",
|
||||||
|
}}
|
||||||
|
assert.True(t, c.Healthy())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("running and healthy", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
c := &Container{Container: types.Container{
|
||||||
|
State: "running",
|
||||||
|
Status: "Up 3 minutes (healthy)",
|
||||||
|
}}
|
||||||
|
assert.True(t, c.Healthy())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("running but unhealthy", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
c := &Container{Container: types.Container{
|
||||||
|
State: "running",
|
||||||
|
Status: "Up 2 hours (unhealthy)",
|
||||||
|
}}
|
||||||
|
assert.False(t, c.Healthy())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("running with health starting", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
c := &Container{Container: types.Container{
|
||||||
|
State: "running",
|
||||||
|
Status: "Up 1 minute (health: starting)",
|
||||||
|
}}
|
||||||
|
assert.False(t, c.Healthy())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid up format no time", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
c := &Container{Container: types.Container{
|
||||||
|
State: "running",
|
||||||
|
Status: "Up",
|
||||||
|
}}
|
||||||
|
assert.False(t, c.Healthy())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid up format empty parentheses", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
c := &Container{Container: types.Container{
|
||||||
|
State: "running",
|
||||||
|
Status: "Up 5 minutes ()",
|
||||||
|
}}
|
||||||
|
assert.False(t, c.Healthy())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("malformed status", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
c := &Container{Container: types.Container{
|
||||||
|
State: "running",
|
||||||
|
Status: "Invalid status",
|
||||||
|
}}
|
||||||
|
assert.False(t, c.Healthy())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("restarting", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
c := &Container{Container: types.Container{
|
||||||
|
State: "running",
|
||||||
|
Status: "Restarting (0) 5 seconds ago",
|
||||||
|
}}
|
||||||
|
assert.False(t, c.Healthy())
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user