mirror of
https://github.com/sablierapp/sablier.git
synced 2025-12-27 15:41:41 +01:00
refactor: use common object for describing instances
This commit is contained in:
74
app/instance/instance.go
Normal file
74
app/instance/instance.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package instance
|
||||
|
||||
import log "github.com/sirupsen/logrus"
|
||||
|
||||
var Ready = "ready"
|
||||
var NotReady = "not-ready"
|
||||
var Error = "error"
|
||||
|
||||
type State struct {
|
||||
Name string
|
||||
CurrentReplicas int
|
||||
Status string
|
||||
Error string
|
||||
}
|
||||
|
||||
func (instance State) IsReady() bool {
|
||||
return instance.Status == Ready
|
||||
}
|
||||
|
||||
func (instance State) HasError() bool {
|
||||
return instance.Status == Error
|
||||
}
|
||||
|
||||
func ErrorInstanceState(name string, err error) (State, error) {
|
||||
log.Error(err.Error())
|
||||
return State{
|
||||
Name: name,
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Error: err.Error(),
|
||||
}, err
|
||||
}
|
||||
|
||||
func UnrecoverableInstanceState(name string, err string) (State, error) {
|
||||
log.Warn(err)
|
||||
return State{
|
||||
Name: name,
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Error: err,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ReadyInstanceState(name string) (State, error) {
|
||||
return State{
|
||||
Name: name,
|
||||
CurrentReplicas: 1,
|
||||
Status: Ready,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ReadyInstanceStateOfReplicas(name string, replicas int) (State, error) {
|
||||
return State{
|
||||
Name: name,
|
||||
CurrentReplicas: replicas,
|
||||
Status: Ready,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NotReadyInstanceState(name string) (State, error) {
|
||||
return State{
|
||||
Name: name,
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NotReadyInstanceStateOfReplicas(name string, replicas int) (State, error) {
|
||||
return State{
|
||||
Name: name,
|
||||
CurrentReplicas: replicas,
|
||||
Status: NotReady,
|
||||
}, nil
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/acouvreur/sablier/app/instance"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -24,77 +25,77 @@ func NewDockerClassicProvider() (*DockerClassicProvider, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *DockerClassicProvider) Start(name string) (InstanceState, error) {
|
||||
func (provider *DockerClassicProvider) Start(name string) (instance.State, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
err := provider.Client.ContainerStart(ctx, name, types.ContainerStartOptions{})
|
||||
|
||||
if err != nil {
|
||||
return errorInstanceState(name, err)
|
||||
return instance.ErrorInstanceState(name, err)
|
||||
}
|
||||
|
||||
return InstanceState{
|
||||
return instance.State{
|
||||
Name: name,
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (provider *DockerClassicProvider) Stop(name string) (InstanceState, error) {
|
||||
func (provider *DockerClassicProvider) Stop(name string) (instance.State, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
// TODO: Allow to specify a termination timeout
|
||||
err := provider.Client.ContainerStop(ctx, name, nil)
|
||||
|
||||
if err != nil {
|
||||
return errorInstanceState(name, err)
|
||||
return instance.ErrorInstanceState(name, err)
|
||||
}
|
||||
|
||||
return InstanceState{
|
||||
return instance.State{
|
||||
Name: name,
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *DockerClassicProvider) GetState(name string) (InstanceState, error) {
|
||||
func (provider *DockerClassicProvider) GetState(name string) (instance.State, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
spec, err := provider.Client.ContainerInspect(ctx, name)
|
||||
|
||||
if err != nil {
|
||||
return errorInstanceState(name, err)
|
||||
return instance.ErrorInstanceState(name, err)
|
||||
}
|
||||
|
||||
// "created", "running", "paused", "restarting", "removing", "exited", or "dead"
|
||||
switch spec.State.Status {
|
||||
case "created", "paused", "restarting", "removing":
|
||||
return notReadyInstanceState(name)
|
||||
return instance.NotReadyInstanceState(name)
|
||||
case "running":
|
||||
if spec.State.Health != nil {
|
||||
// // "starting", "healthy" or "unhealthy"
|
||||
if spec.State.Health.Status == "healthy" {
|
||||
return readyInstanceState(name)
|
||||
return instance.ReadyInstanceState(name)
|
||||
} else if spec.State.Health.Status == "unhealthy" {
|
||||
if len(spec.State.Health.Log) >= 1 {
|
||||
lastLog := spec.State.Health.Log[len(spec.State.Health.Log)-1]
|
||||
return unrecoverableInstanceState(name, fmt.Sprintf("container is unhealthy: %s (%d)", lastLog.Output, lastLog.ExitCode))
|
||||
return instance.UnrecoverableInstanceState(name, fmt.Sprintf("container is unhealthy: %s (%d)", lastLog.Output, lastLog.ExitCode))
|
||||
} else {
|
||||
return unrecoverableInstanceState(name, "container is unhealthy: no log available")
|
||||
return instance.UnrecoverableInstanceState(name, "container is unhealthy: no log available")
|
||||
}
|
||||
} else {
|
||||
return notReadyInstanceState(name)
|
||||
return instance.NotReadyInstanceState(name)
|
||||
}
|
||||
}
|
||||
return readyInstanceState(name)
|
||||
return instance.ReadyInstanceState(name)
|
||||
case "exited":
|
||||
if spec.State.ExitCode != 0 {
|
||||
return unrecoverableInstanceState(name, fmt.Sprintf("container exited with code \"%d\"", spec.State.ExitCode))
|
||||
return instance.UnrecoverableInstanceState(name, fmt.Sprintf("container exited with code \"%d\"", spec.State.ExitCode))
|
||||
}
|
||||
return notReadyInstanceState(name)
|
||||
return instance.NotReadyInstanceState(name)
|
||||
case "dead":
|
||||
return unrecoverableInstanceState(name, "container in \"dead\" state cannot be restarted")
|
||||
return instance.UnrecoverableInstanceState(name, "container in \"dead\" state cannot be restarted")
|
||||
default:
|
||||
return unrecoverableInstanceState(name, fmt.Sprintf("container status \"%s\" not handled", spec.State.Status))
|
||||
return instance.UnrecoverableInstanceState(name, fmt.Sprintf("container status \"%s\" not handled", spec.State.Status))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/acouvreur/sablier/app/instance"
|
||||
"github.com/acouvreur/sablier/app/providers/mocks"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -21,7 +22,7 @@ func TestDockerClassicProvider_GetState(t *testing.T) {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want InstanceState
|
||||
want instance.State
|
||||
wantErr bool
|
||||
containerSpec types.ContainerJSON
|
||||
err error
|
||||
@@ -34,10 +35,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "nginx",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
wantErr: false,
|
||||
containerSpec: mocks.CreatedContainerSpec("nginx"),
|
||||
@@ -50,10 +51,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "nginx",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 1,
|
||||
Status: Ready,
|
||||
Status: instance.Ready,
|
||||
},
|
||||
wantErr: false,
|
||||
containerSpec: mocks.RunningWithoutHealthcheckContainerSpec("nginx"),
|
||||
@@ -66,10 +67,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "nginx",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
wantErr: false,
|
||||
containerSpec: mocks.RunningWithHealthcheckContainerSpec("nginx", "starting"),
|
||||
@@ -82,10 +83,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "nginx",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Status: instance.Error,
|
||||
Error: "container is unhealthy: curl http://localhost failed (1)",
|
||||
},
|
||||
wantErr: false,
|
||||
@@ -99,10 +100,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "nginx",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 1,
|
||||
Status: Ready,
|
||||
Status: instance.Ready,
|
||||
},
|
||||
wantErr: false,
|
||||
containerSpec: mocks.RunningWithHealthcheckContainerSpec("nginx", "healthy"),
|
||||
@@ -115,10 +116,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "nginx",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
wantErr: false,
|
||||
containerSpec: mocks.PausedContainerSpec("nginx"),
|
||||
@@ -131,10 +132,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "nginx",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
wantErr: false,
|
||||
containerSpec: mocks.RestartingContainerSpec("nginx"),
|
||||
@@ -147,10 +148,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "nginx",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
wantErr: false,
|
||||
containerSpec: mocks.RemovingContainerSpec("nginx"),
|
||||
@@ -163,10 +164,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "nginx",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
wantErr: false,
|
||||
containerSpec: mocks.ExitedContainerSpec("nginx", 0),
|
||||
@@ -179,10 +180,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "nginx",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Status: instance.Error,
|
||||
Error: "container exited with code \"137\"",
|
||||
},
|
||||
wantErr: false,
|
||||
@@ -196,10 +197,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "nginx",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Status: instance.Error,
|
||||
Error: "container in \"dead\" state cannot be restarted",
|
||||
},
|
||||
wantErr: false,
|
||||
@@ -213,10 +214,10 @@ func TestDockerClassicProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "nginx",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Status: instance.Error,
|
||||
Error: "container with name \"nginx\" was not found",
|
||||
},
|
||||
wantErr: true,
|
||||
@@ -255,7 +256,7 @@ func TestDockerClassicProvider_Stop(t *testing.T) {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want InstanceState
|
||||
want instance.State
|
||||
wantErr bool
|
||||
err error
|
||||
}{
|
||||
@@ -267,10 +268,10 @@ func TestDockerClassicProvider_Stop(t *testing.T) {
|
||||
args: args{
|
||||
name: "nginx",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Status: instance.Error,
|
||||
Error: "container with name \"nginx\" was not found",
|
||||
},
|
||||
wantErr: true,
|
||||
@@ -284,10 +285,10 @@ func TestDockerClassicProvider_Stop(t *testing.T) {
|
||||
args: args{
|
||||
name: "nginx",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
wantErr: false,
|
||||
err: nil,
|
||||
@@ -324,7 +325,7 @@ func TestDockerClassicProvider_Start(t *testing.T) {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want InstanceState
|
||||
want instance.State
|
||||
wantErr bool
|
||||
err error
|
||||
}{
|
||||
@@ -336,10 +337,10 @@ func TestDockerClassicProvider_Start(t *testing.T) {
|
||||
args: args{
|
||||
name: "nginx",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Status: instance.Error,
|
||||
Error: "container with name \"nginx\" was not found",
|
||||
},
|
||||
wantErr: true,
|
||||
@@ -353,10 +354,10 @@ func TestDockerClassicProvider_Start(t *testing.T) {
|
||||
args: args{
|
||||
name: "nginx",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
wantErr: false,
|
||||
err: nil,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/acouvreur/sablier/app/instance"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
@@ -27,26 +28,26 @@ func NewDockerSwarmProvider() (*DockerSwarmProvider, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *DockerSwarmProvider) Start(name string) (InstanceState, error) {
|
||||
func (provider *DockerSwarmProvider) Start(name string) (instance.State, error) {
|
||||
return provider.scale(name, 1)
|
||||
}
|
||||
|
||||
func (provider *DockerSwarmProvider) Stop(name string) (InstanceState, error) {
|
||||
func (provider *DockerSwarmProvider) Stop(name string) (instance.State, error) {
|
||||
return provider.scale(name, 0)
|
||||
}
|
||||
|
||||
func (provider *DockerSwarmProvider) scale(name string, replicas uint64) (InstanceState, error) {
|
||||
func (provider *DockerSwarmProvider) scale(name string, replicas uint64) (instance.State, error) {
|
||||
ctx := context.Background()
|
||||
service, err := provider.getServiceByName(name, ctx)
|
||||
|
||||
if err != nil {
|
||||
return errorInstanceState(name, err)
|
||||
return instance.ErrorInstanceState(name, err)
|
||||
}
|
||||
|
||||
foundName := provider.getInstanceName(name, *service)
|
||||
|
||||
if service.Spec.Mode.Replicated == nil {
|
||||
return unrecoverableInstanceState(foundName, "swarm service is not in \"replicated\" mode")
|
||||
return instance.UnrecoverableInstanceState(foundName, "swarm service is not in \"replicated\" mode")
|
||||
}
|
||||
|
||||
service.Spec.Mode.Replicated.Replicas = &replicas
|
||||
@@ -54,35 +55,35 @@ func (provider *DockerSwarmProvider) scale(name string, replicas uint64) (Instan
|
||||
response, err := provider.Client.ServiceUpdate(ctx, service.ID, service.Meta.Version, service.Spec, types.ServiceUpdateOptions{})
|
||||
|
||||
if err != nil {
|
||||
return errorInstanceState(foundName, err)
|
||||
return instance.ErrorInstanceState(foundName, err)
|
||||
}
|
||||
|
||||
if len(response.Warnings) > 0 {
|
||||
return unrecoverableInstanceState(foundName, strings.Join(response.Warnings, ", "))
|
||||
return instance.UnrecoverableInstanceState(foundName, strings.Join(response.Warnings, ", "))
|
||||
}
|
||||
|
||||
return notReadyInstanceState(foundName)
|
||||
return instance.NotReadyInstanceState(foundName)
|
||||
}
|
||||
|
||||
func (provider *DockerSwarmProvider) GetState(name string) (InstanceState, error) {
|
||||
func (provider *DockerSwarmProvider) GetState(name string) (instance.State, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
service, err := provider.getServiceByName(name, ctx)
|
||||
if err != nil {
|
||||
return errorInstanceState(name, err)
|
||||
return instance.ErrorInstanceState(name, err)
|
||||
}
|
||||
|
||||
foundName := provider.getInstanceName(name, *service)
|
||||
|
||||
if service.Spec.Mode.Replicated == nil {
|
||||
return unrecoverableInstanceState(foundName, "swarm service is not in \"replicated\" mode")
|
||||
return instance.UnrecoverableInstanceState(foundName, "swarm service is not in \"replicated\" mode")
|
||||
}
|
||||
|
||||
if service.ServiceStatus.DesiredTasks != service.ServiceStatus.RunningTasks || service.ServiceStatus.DesiredTasks == 0 {
|
||||
return notReadyInstanceState(foundName)
|
||||
return instance.NotReadyInstanceState(foundName)
|
||||
}
|
||||
|
||||
return readyInstanceState(foundName)
|
||||
return instance.ReadyInstanceState(foundName)
|
||||
}
|
||||
|
||||
func (provider *DockerSwarmProvider) getServiceByName(name string, ctx context.Context) (*swarm.Service, error) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/acouvreur/sablier/app/instance"
|
||||
"github.com/acouvreur/sablier/app/providers/mocks"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
@@ -21,7 +22,7 @@ func TestDockerSwarmProvider_Start(t *testing.T) {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want InstanceState
|
||||
want instance.State
|
||||
serviceList []swarm.Service
|
||||
response types.ServiceUpdateResponse
|
||||
wantService swarm.Service
|
||||
@@ -41,10 +42,10 @@ func TestDockerSwarmProvider_Start(t *testing.T) {
|
||||
response: types.ServiceUpdateResponse{
|
||||
Warnings: []string{},
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
wantService: mocks.ServiceReplicated("nginx", 1),
|
||||
wantErr: false,
|
||||
@@ -64,10 +65,10 @@ func TestDockerSwarmProvider_Start(t *testing.T) {
|
||||
response: types.ServiceUpdateResponse{
|
||||
Warnings: []string{},
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Status: instance.Error,
|
||||
Error: "ambiguous service names found for \"nginx\" (STACK1_nginx, STACK2_nginx)",
|
||||
},
|
||||
wantService: mocks.ServiceReplicated("nginx", 1),
|
||||
@@ -89,10 +90,10 @@ func TestDockerSwarmProvider_Start(t *testing.T) {
|
||||
response: types.ServiceUpdateResponse{
|
||||
Warnings: []string{},
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
wantService: mocks.ServiceReplicated("nginx", 1),
|
||||
wantErr: false,
|
||||
@@ -112,10 +113,10 @@ func TestDockerSwarmProvider_Start(t *testing.T) {
|
||||
response: types.ServiceUpdateResponse{
|
||||
Warnings: []string{},
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx (STACK1_nginx)",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
wantService: mocks.ServiceReplicated("STACK1_nginx", 1),
|
||||
wantErr: false,
|
||||
@@ -134,10 +135,10 @@ func TestDockerSwarmProvider_Start(t *testing.T) {
|
||||
response: types.ServiceUpdateResponse{
|
||||
Warnings: []string{},
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Status: instance.Error,
|
||||
Error: "swarm service is not in \"replicated\" mode",
|
||||
},
|
||||
wantService: mocks.ServiceReplicated("nginx", 1),
|
||||
@@ -176,7 +177,7 @@ func TestDockerSwarmProvider_Stop(t *testing.T) {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want InstanceState
|
||||
want instance.State
|
||||
serviceList []swarm.Service
|
||||
response types.ServiceUpdateResponse
|
||||
wantService swarm.Service
|
||||
@@ -196,10 +197,10 @@ func TestDockerSwarmProvider_Stop(t *testing.T) {
|
||||
response: types.ServiceUpdateResponse{
|
||||
Warnings: []string{},
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
wantService: mocks.ServiceReplicated("nginx", 0),
|
||||
wantErr: false,
|
||||
@@ -219,10 +220,10 @@ func TestDockerSwarmProvider_Stop(t *testing.T) {
|
||||
response: types.ServiceUpdateResponse{
|
||||
Warnings: []string{},
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Status: instance.Error,
|
||||
Error: "ambiguous service names found for \"nginx\" (STACK1_nginx, STACK2_nginx)",
|
||||
},
|
||||
wantService: mocks.ServiceReplicated("nginx", 1),
|
||||
@@ -244,10 +245,10 @@ func TestDockerSwarmProvider_Stop(t *testing.T) {
|
||||
response: types.ServiceUpdateResponse{
|
||||
Warnings: []string{},
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
wantService: mocks.ServiceReplicated("nginx", 0),
|
||||
wantErr: false,
|
||||
@@ -267,10 +268,10 @@ func TestDockerSwarmProvider_Stop(t *testing.T) {
|
||||
response: types.ServiceUpdateResponse{
|
||||
Warnings: []string{},
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx (STACK1_nginx)",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
wantService: mocks.ServiceReplicated("STACK1_nginx", 0),
|
||||
wantErr: false,
|
||||
@@ -289,10 +290,10 @@ func TestDockerSwarmProvider_Stop(t *testing.T) {
|
||||
response: types.ServiceUpdateResponse{
|
||||
Warnings: []string{},
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Status: instance.Error,
|
||||
Error: "swarm service is not in \"replicated\" mode",
|
||||
},
|
||||
wantService: mocks.ServiceReplicated("nginx", 1),
|
||||
@@ -331,7 +332,7 @@ func TestDockerSwarmProvider_GetState(t *testing.T) {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want InstanceState
|
||||
want instance.State
|
||||
serviceList []swarm.Service
|
||||
wantErr bool
|
||||
}{
|
||||
@@ -346,10 +347,10 @@ func TestDockerSwarmProvider_GetState(t *testing.T) {
|
||||
serviceList: []swarm.Service{
|
||||
mocks.ServiceReplicated("nginx", 1),
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 1,
|
||||
Status: Ready,
|
||||
Status: instance.Ready,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
@@ -364,10 +365,10 @@ func TestDockerSwarmProvider_GetState(t *testing.T) {
|
||||
serviceList: []swarm.Service{
|
||||
mocks.ServiceNotReadyReplicated("nginx", 1, 0),
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
@@ -382,10 +383,10 @@ func TestDockerSwarmProvider_GetState(t *testing.T) {
|
||||
serviceList: []swarm.Service{
|
||||
mocks.ServiceNotReadyReplicated("STACK_nginx", 1, 0),
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx (STACK_nginx)",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
@@ -400,10 +401,10 @@ func TestDockerSwarmProvider_GetState(t *testing.T) {
|
||||
serviceList: []swarm.Service{
|
||||
mocks.ServiceGlobal("nginx"),
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "nginx",
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Status: instance.Error,
|
||||
Error: "swarm service is not in \"replicated\" mode",
|
||||
},
|
||||
wantErr: false,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/acouvreur/sablier/app/instance"
|
||||
log "github.com/sirupsen/logrus"
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -68,19 +69,19 @@ func NewKubernetesProvider() *KubernetesProvider {
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *KubernetesProvider) Start(name string) (InstanceState, error) {
|
||||
func (provider *KubernetesProvider) Start(name string) (instance.State, error) {
|
||||
config, err := convertName(name)
|
||||
if err != nil {
|
||||
return unrecoverableInstanceState(name, err.Error())
|
||||
return instance.UnrecoverableInstanceState(name, err.Error())
|
||||
}
|
||||
|
||||
return provider.scale(config)
|
||||
}
|
||||
|
||||
func (provider *KubernetesProvider) Stop(name string) (InstanceState, error) {
|
||||
func (provider *KubernetesProvider) Stop(name string) (instance.State, error) {
|
||||
config, err := convertName(name)
|
||||
if err != nil {
|
||||
return unrecoverableInstanceState(name, err.Error())
|
||||
return instance.UnrecoverableInstanceState(name, err.Error())
|
||||
}
|
||||
|
||||
config.Replicas = 0
|
||||
@@ -89,7 +90,7 @@ func (provider *KubernetesProvider) Stop(name string) (InstanceState, error) {
|
||||
|
||||
}
|
||||
|
||||
func (provider *KubernetesProvider) scale(config *Config) (InstanceState, error) {
|
||||
func (provider *KubernetesProvider) scale(config *Config) (instance.State, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
var workload Workload
|
||||
@@ -100,28 +101,28 @@ func (provider *KubernetesProvider) scale(config *Config) (InstanceState, error)
|
||||
case "statefulset":
|
||||
workload = provider.Client.AppsV1().StatefulSets(config.Namespace)
|
||||
default:
|
||||
return unrecoverableInstanceState(config.OriginalName, fmt.Sprintf("unsupported kind \"%s\" must be one of \"deployment\", \"statefulset\"", config.Kind))
|
||||
return instance.UnrecoverableInstanceState(config.OriginalName, fmt.Sprintf("unsupported kind \"%s\" must be one of \"deployment\", \"statefulset\"", config.Kind))
|
||||
}
|
||||
|
||||
s, err := workload.GetScale(ctx, config.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return errorInstanceState(config.OriginalName, err)
|
||||
return instance.ErrorInstanceState(config.OriginalName, err)
|
||||
}
|
||||
|
||||
s.Spec.Replicas = config.Replicas
|
||||
_, err = workload.UpdateScale(ctx, config.Name, s, metav1.UpdateOptions{})
|
||||
|
||||
if err != nil {
|
||||
return errorInstanceState(config.OriginalName, err)
|
||||
return instance.ErrorInstanceState(config.OriginalName, err)
|
||||
}
|
||||
|
||||
return notReadyInstanceState(config.OriginalName)
|
||||
return instance.NotReadyInstanceState(config.OriginalName)
|
||||
}
|
||||
|
||||
func (provider *KubernetesProvider) GetState(name string) (InstanceState, error) {
|
||||
func (provider *KubernetesProvider) GetState(name string) (instance.State, error) {
|
||||
config, err := convertName(name)
|
||||
if err != nil {
|
||||
return unrecoverableInstanceState(name, err.Error())
|
||||
return instance.UnrecoverableInstanceState(name, err.Error())
|
||||
}
|
||||
|
||||
switch config.Kind {
|
||||
@@ -130,40 +131,40 @@ func (provider *KubernetesProvider) GetState(name string) (InstanceState, error)
|
||||
case "statefulset":
|
||||
return provider.getStatefulsetState(config)
|
||||
default:
|
||||
return unrecoverableInstanceState(config.OriginalName, fmt.Sprintf("unsupported kind \"%s\" must be one of \"deployment\", \"statefulset\"", config.Kind))
|
||||
return instance.UnrecoverableInstanceState(config.OriginalName, fmt.Sprintf("unsupported kind \"%s\" must be one of \"deployment\", \"statefulset\"", config.Kind))
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *KubernetesProvider) getDeploymentState(config *Config) (InstanceState, error) {
|
||||
func (provider *KubernetesProvider) getDeploymentState(config *Config) (instance.State, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
d, err := provider.Client.AppsV1().Deployments(config.Namespace).
|
||||
Get(ctx, config.Name, metav1.GetOptions{})
|
||||
|
||||
if err != nil {
|
||||
return errorInstanceState(config.OriginalName, err)
|
||||
return instance.ErrorInstanceState(config.OriginalName, err)
|
||||
}
|
||||
|
||||
if *d.Spec.Replicas == d.Status.ReadyReplicas {
|
||||
return readyInstanceStateOfReplicas(config.OriginalName, int(d.Status.ReadyReplicas))
|
||||
return instance.ReadyInstanceStateOfReplicas(config.OriginalName, int(d.Status.ReadyReplicas))
|
||||
}
|
||||
|
||||
return notReadyInstanceStateOfReplicas(config.OriginalName, int(d.Status.ReadyReplicas))
|
||||
return instance.NotReadyInstanceStateOfReplicas(config.OriginalName, int(d.Status.ReadyReplicas))
|
||||
}
|
||||
|
||||
func (provider *KubernetesProvider) getStatefulsetState(config *Config) (InstanceState, error) {
|
||||
func (provider *KubernetesProvider) getStatefulsetState(config *Config) (instance.State, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
ss, err := provider.Client.AppsV1().StatefulSets(config.Namespace).
|
||||
Get(ctx, config.Name, metav1.GetOptions{})
|
||||
|
||||
if err != nil {
|
||||
return errorInstanceState(config.OriginalName, err)
|
||||
return instance.ErrorInstanceState(config.OriginalName, err)
|
||||
}
|
||||
|
||||
if *ss.Spec.Replicas == ss.Status.ReadyReplicas {
|
||||
return readyInstanceStateOfReplicas(config.OriginalName, int(ss.Status.ReadyReplicas))
|
||||
return instance.ReadyInstanceStateOfReplicas(config.OriginalName, int(ss.Status.ReadyReplicas))
|
||||
}
|
||||
|
||||
return notReadyInstanceStateOfReplicas(config.OriginalName, int(ss.Status.ReadyReplicas))
|
||||
return instance.NotReadyInstanceStateOfReplicas(config.OriginalName, int(ss.Status.ReadyReplicas))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/acouvreur/sablier/app/instance"
|
||||
"github.com/acouvreur/sablier/app/providers/mocks"
|
||||
"github.com/stretchr/testify/mock"
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
@@ -23,7 +24,7 @@ func TestKubernetesProvider_Start(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want InstanceState
|
||||
want instance.State
|
||||
data data
|
||||
wantErr bool
|
||||
}{
|
||||
@@ -32,10 +33,10 @@ func TestKubernetesProvider_Start(t *testing.T) {
|
||||
args: args{
|
||||
name: "deployment_default_nginx_2",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "deployment_default_nginx_2",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
data: data{
|
||||
name: "nginx",
|
||||
@@ -49,10 +50,10 @@ func TestKubernetesProvider_Start(t *testing.T) {
|
||||
args: args{
|
||||
name: "statefulset_default_nginx_2",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "statefulset_default_nginx_2",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
data: data{
|
||||
name: "nginx",
|
||||
@@ -66,10 +67,10 @@ func TestKubernetesProvider_Start(t *testing.T) {
|
||||
args: args{
|
||||
name: "gateway_default_nginx_2",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "gateway_default_nginx_2",
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Status: instance.Error,
|
||||
Error: "unsupported kind \"gateway\" must be one of \"deployment\", \"statefulset\"",
|
||||
},
|
||||
data: data{
|
||||
@@ -118,7 +119,7 @@ func TestKubernetesProvider_Stop(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want InstanceState
|
||||
want instance.State
|
||||
data data
|
||||
wantErr bool
|
||||
}{
|
||||
@@ -127,10 +128,10 @@ func TestKubernetesProvider_Stop(t *testing.T) {
|
||||
args: args{
|
||||
name: "deployment_default_nginx_2",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "deployment_default_nginx_2",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
data: data{
|
||||
name: "nginx",
|
||||
@@ -144,10 +145,10 @@ func TestKubernetesProvider_Stop(t *testing.T) {
|
||||
args: args{
|
||||
name: "statefulset_default_nginx_2",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "statefulset_default_nginx_2",
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
data: data{
|
||||
name: "nginx",
|
||||
@@ -161,10 +162,10 @@ func TestKubernetesProvider_Stop(t *testing.T) {
|
||||
args: args{
|
||||
name: "gateway_default_nginx_2",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "gateway_default_nginx_2",
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Status: instance.Error,
|
||||
Error: "unsupported kind \"gateway\" must be one of \"deployment\", \"statefulset\"",
|
||||
},
|
||||
data: data{
|
||||
@@ -213,7 +214,7 @@ func TestKubernetesProvider_GetState(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want InstanceState
|
||||
want instance.State
|
||||
data data
|
||||
wantErr bool
|
||||
}{
|
||||
@@ -222,10 +223,10 @@ func TestKubernetesProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "deployment_default_nginx_2",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "deployment_default_nginx_2",
|
||||
CurrentReplicas: 2,
|
||||
Status: Ready,
|
||||
Status: instance.Ready,
|
||||
},
|
||||
data: data{
|
||||
name: "nginx",
|
||||
@@ -238,10 +239,10 @@ func TestKubernetesProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "deployment_default_nginx_2",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "deployment_default_nginx_2",
|
||||
CurrentReplicas: 1,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
data: data{
|
||||
name: "nginx",
|
||||
@@ -254,10 +255,10 @@ func TestKubernetesProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "statefulset_default_nginx_2",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "statefulset_default_nginx_2",
|
||||
CurrentReplicas: 2,
|
||||
Status: Ready,
|
||||
Status: instance.Ready,
|
||||
},
|
||||
data: data{
|
||||
name: "nginx",
|
||||
@@ -270,10 +271,10 @@ func TestKubernetesProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "statefulset_default_nginx_2",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "statefulset_default_nginx_2",
|
||||
CurrentReplicas: 1,
|
||||
Status: NotReady,
|
||||
Status: instance.NotReady,
|
||||
},
|
||||
data: data{
|
||||
name: "nginx",
|
||||
@@ -286,10 +287,10 @@ func TestKubernetesProvider_GetState(t *testing.T) {
|
||||
args: args{
|
||||
name: "gateway_default_nginx_2",
|
||||
},
|
||||
want: InstanceState{
|
||||
want: instance.State{
|
||||
Name: "gateway_default_nginx_2",
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Status: instance.Error,
|
||||
Error: "unsupported kind \"gateway\" must be one of \"deployment\", \"statefulset\"",
|
||||
},
|
||||
data: data{
|
||||
|
||||
@@ -1,80 +1,9 @@
|
||||
package providers
|
||||
|
||||
import log "github.com/sirupsen/logrus"
|
||||
|
||||
type InstanceState struct {
|
||||
Name string
|
||||
CurrentReplicas int
|
||||
Status string
|
||||
Error string
|
||||
}
|
||||
|
||||
var Ready = "ready"
|
||||
var NotReady = "not-ready"
|
||||
var Error = "error"
|
||||
import "github.com/acouvreur/sablier/app/instance"
|
||||
|
||||
type Provider interface {
|
||||
Start(name string) (InstanceState, error)
|
||||
Stop(name string) (InstanceState, error)
|
||||
GetState(name string) (InstanceState, error)
|
||||
}
|
||||
|
||||
func (instance InstanceState) IsReady() bool {
|
||||
return instance.Status == Ready
|
||||
}
|
||||
|
||||
func (instance InstanceState) HasError() bool {
|
||||
return instance.Status == Error
|
||||
}
|
||||
|
||||
func errorInstanceState(name string, err error) (InstanceState, error) {
|
||||
log.Error(err.Error())
|
||||
return InstanceState{
|
||||
Name: name,
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Error: err.Error(),
|
||||
}, err
|
||||
}
|
||||
|
||||
func unrecoverableInstanceState(name string, err string) (InstanceState, error) {
|
||||
log.Warn(err)
|
||||
return InstanceState{
|
||||
Name: name,
|
||||
CurrentReplicas: 0,
|
||||
Status: Error,
|
||||
Error: err,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readyInstanceState(name string) (InstanceState, error) {
|
||||
return InstanceState{
|
||||
Name: name,
|
||||
CurrentReplicas: 1,
|
||||
Status: Ready,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readyInstanceStateOfReplicas(name string, replicas int) (InstanceState, error) {
|
||||
return InstanceState{
|
||||
Name: name,
|
||||
CurrentReplicas: replicas,
|
||||
Status: Ready,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func notReadyInstanceState(name string) (InstanceState, error) {
|
||||
return InstanceState{
|
||||
Name: name,
|
||||
CurrentReplicas: 0,
|
||||
Status: NotReady,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func notReadyInstanceStateOfReplicas(name string, replicas int) (InstanceState, error) {
|
||||
return InstanceState{
|
||||
Name: name,
|
||||
CurrentReplicas: replicas,
|
||||
Status: NotReady,
|
||||
}, nil
|
||||
Start(name string) (instance.State, error)
|
||||
Stop(name string) (instance.State, error)
|
||||
GetState(name string) (instance.State, error)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user