Files
sablier/pkg/scaler/docker_swarm.go
2022-07-23 12:53:23 -04:00

163 lines
3.8 KiB
Go

package scaler
import (
"context"
"fmt"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
log "github.com/sirupsen/logrus"
)
type DockerSwarmScaler struct {
Client client.ServiceAPIClient
}
func NewDockerSwarmScaler() *DockerSwarmScaler {
return &DockerSwarmScaler{}
}
func (scaler *DockerSwarmScaler) ScaleUp(name string) error {
ctx := context.Background()
service, err := scaler.GetServiceByName(name, ctx)
if err != nil {
log.Error(err.Error())
return err
}
if *service.Spec.Mode.Replicated.Replicas == onereplicas {
log.Infof("%s already scaled up to %d", name, onereplicas)
return nil
}
log.Infof("scaling up %s to %d", name, onereplicas)
service.Spec.Mode.Replicated = &swarm.ReplicatedService{
Replicas: &onereplicas,
}
response, err := scaler.Client.ServiceUpdate(ctx, service.ID, service.Meta.Version, service.Spec, types.ServiceUpdateOptions{})
if err != nil {
log.Error(err.Error())
return err
}
if len(response.Warnings) > 0 {
log.Warnf("received scaling up service %s: %v", name, response.Warnings)
}
return nil
}
func (scaler *DockerSwarmScaler) ScaleDown(name string) error {
ctx := context.Background()
service, err := scaler.GetServiceByName(name, ctx)
if err != nil {
log.Error(err.Error())
return err
}
replicas := uint64(0)
if *service.Spec.Mode.Replicated.Replicas == replicas {
log.Infof("%s already scaled down to %d", name, replicas)
return nil
}
log.Infof("scaling down %s to %d", name, replicas)
service.Spec.Mode.Replicated = &swarm.ReplicatedService{
Replicas: &replicas,
}
response, err := scaler.Client.ServiceUpdate(ctx, service.ID, service.Meta.Version, service.Spec, types.ServiceUpdateOptions{})
if err != nil {
log.Error(err.Error())
return err
}
if len(response.Warnings) > 0 {
log.Warnf("received scaling up service %s: %v", name, response.Warnings)
}
return nil
}
func (scaler *DockerSwarmScaler) IsUp(name string) bool {
ctx := context.Background()
service, err := scaler.GetServiceByName(name, ctx)
if err != nil {
log.Error(err.Error())
return false
}
return scaler.isServiceRunningFor(service, 5*time.Second)
}
func (scaler *DockerSwarmScaler) isServiceRunningFor(service *swarm.Service, duration time.Duration) bool {
if service.ServiceStatus.DesiredTasks == 0 {
return false
}
if service.ServiceStatus.DesiredTasks != service.ServiceStatus.RunningTasks {
return false
}
opts := types.TaskListOptions{
Filters: filters.NewArgs(),
}
opts.Filters.Add("desired-state", "running")
opts.Filters.Add("service", service.Spec.Name)
ctx := context.Background()
tasks, err := scaler.Client.TaskList(ctx, opts)
if err != nil {
log.Error(err.Error())
return false
}
if len(tasks) == 0 {
log.Error("No task found with filter desired-state=running and service=", service.Spec.Name)
return false
}
// Getting 503 first time a workload is woken up https://github.com/acouvreur/traefik-ondemand-service/issues/24
// Let the service be up for a given duration
for _, task := range tasks {
if time.Since(task.Status.Timestamp) < (time.Second * 5) {
return false
}
}
return true
}
func (scaler *DockerSwarmScaler) GetServiceByName(name string, ctx context.Context) (*swarm.Service, error) {
opts := types.ServiceListOptions{
Filters: filters.NewArgs(),
Status: true,
}
opts.Filters.Add("name", name)
services, err := scaler.Client.ServiceList(ctx, opts)
if err != nil {
return nil, err
}
if len(services) == 0 {
return nil, fmt.Errorf(fmt.Sprintf("service with name %s was not found", name))
}
if len(services) > 1 {
return nil, fmt.Errorf("multiple services (%d) with name %s were found: %v", len(services), name, services)
}
return &services[0], nil
}