mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
move common api types to api package
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const (
|
||||
LabelManaged = "uncloud.managed"
|
||||
LabelServiceID = "uncloud.service.id"
|
||||
LabelServiceName = "uncloud.service.name"
|
||||
LabelServiceMode = "uncloud.service.mode"
|
||||
)
|
||||
|
||||
type Container struct {
|
||||
types.Container
|
||||
}
|
||||
|
||||
// ServiceID returns the service ID that the container is part of.
|
||||
func (c *Container) ServiceID() string {
|
||||
return c.Labels[LabelServiceID]
|
||||
}
|
||||
|
||||
// ServiceName returns the service name that the container is part of.
|
||||
func (c *Container) ServiceName() string {
|
||||
return c.Labels[LabelServiceName]
|
||||
}
|
||||
|
||||
// ServiceMode returns the replication mode of the service that the container is part of.
|
||||
func (c *Container) ServiceMode() string {
|
||||
return c.Labels[LabelServiceMode]
|
||||
}
|
||||
|
||||
// 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 api
|
||||
|
||||
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())
|
||||
})
|
||||
}
|
||||
+46
-3
@@ -1,5 +1,11 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"uncloud/internal/machine/api/pb"
|
||||
)
|
||||
|
||||
const (
|
||||
ServiceModeReplicated = "replicated"
|
||||
ServiceModeGlobal = "global"
|
||||
@@ -19,7 +25,44 @@ type ContainerSpec struct {
|
||||
Init *bool
|
||||
}
|
||||
|
||||
type MachineContainerID struct {
|
||||
MachineID string
|
||||
ContainerID string
|
||||
type Service struct {
|
||||
ID string
|
||||
Name string
|
||||
Mode string
|
||||
Containers []MachineContainer
|
||||
}
|
||||
|
||||
type MachineContainer struct {
|
||||
MachineID string
|
||||
Container Container
|
||||
}
|
||||
|
||||
func ServiceFromProto(s *pb.Service) (Service, error) {
|
||||
var err error
|
||||
containers := make([]MachineContainer, len(s.Containers))
|
||||
for i, sc := range s.Containers {
|
||||
containers[i], err = machineContainerFromProto(sc)
|
||||
if err != nil {
|
||||
return Service{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return Service{
|
||||
ID: s.Id,
|
||||
Name: s.Name,
|
||||
Mode: s.Mode,
|
||||
Containers: containers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func machineContainerFromProto(sc *pb.Service_Container) (MachineContainer, error) {
|
||||
var c Container
|
||||
if err := json.Unmarshal(sc.Container, &c); err != nil {
|
||||
return MachineContainer{}, fmt.Errorf("unmarshal container: %w", err)
|
||||
}
|
||||
|
||||
return MachineContainer{
|
||||
MachineID: sc.MachineId,
|
||||
Container: c,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user