swirl/biz/docker/service.go
2017-09-26 20:50:09 +08:00

516 lines
15 KiB
Go

package docker
import (
"context"
"errors"
"sort"
"strings"
"time"
"github.com/cuigh/swirl/misc"
"github.com/cuigh/swirl/model"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
)
// ServiceList return service list.
func ServiceList(name string, pageIndex, pageSize int) (infos []*model.ServiceListInfo, totalCount int, err error) {
err = mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
var (
services []swarm.Service
nodes []swarm.Node
activeNodes map[string]struct{}
tasks []swarm.Task
)
// acquire services
opts := types.ServiceListOptions{}
if name != "" {
opts.Filters = filters.NewArgs()
opts.Filters.Add("name", name)
}
services, err = cli.ServiceList(ctx, opts)
if err != nil {
return
}
sort.Slice(services, func(i, j int) bool {
return services[i].Spec.Name < services[j].Spec.Name
})
totalCount = len(services)
start, end := misc.Page(totalCount, pageIndex, pageSize)
services = services[start:end]
// acquire all swarm nodes
nodes, err = cli.NodeList(ctx, types.NodeListOptions{})
if err != nil {
return
}
activeNodes = make(map[string]struct{})
for _, n := range nodes {
if n.Status.State != swarm.NodeStateDown {
activeNodes[n.ID] = struct{}{}
}
}
// acquire all related tasks
taskOpts := types.TaskListOptions{
Filters: filters.NewArgs(),
}
for _, service := range services {
taskOpts.Filters.Add("service", service.ID)
}
tasks, err = cli.TaskList(ctx, taskOpts)
if err != nil {
return
}
// count active tasks
running, tasksNoShutdown := map[string]uint64{}, map[string]uint64{}
for _, task := range tasks {
if task.DesiredState != swarm.TaskStateShutdown {
tasksNoShutdown[task.ServiceID]++
}
if _, nodeActive := activeNodes[task.NodeID]; nodeActive && task.Status.State == swarm.TaskStateRunning {
running[task.ServiceID]++
}
}
infos = make([]*model.ServiceListInfo, len(services))
for i, service := range services {
infos[i] = model.NewServiceListInfo(service, running[service.ID])
if service.Spec.Mode.Global != nil {
infos[i].Replicas = tasksNoShutdown[service.ID]
}
}
return
})
return
}
// ServiceCount return number of services.
func ServiceCount() (count int, err error) {
err = mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
var services []swarm.Service
if services, err = cli.ServiceList(ctx, types.ServiceListOptions{}); err == nil {
count = len(services)
}
return
})
return
}
// ServiceInspect return service raw information.
func ServiceInspect(name string) (service swarm.Service, raw []byte, err error) {
err = mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
service, raw, err = cli.ServiceInspectWithRaw(ctx, name, types.ServiceInspectOptions{})
return
})
return
}
// ServiceUpdate update a service.
func ServiceUpdate(info *model.ServiceInfo) error {
return mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
service, _, err := cli.ServiceInspectWithRaw(ctx, info.Name, types.ServiceInspectOptions{})
if err != nil {
return err
}
spec := service.Spec
// Annotations
spec.Annotations.Labels = info.ServiceLabels.ToMap()
// ContainerSpec
spec.TaskTemplate.ContainerSpec.Image = info.Image
spec.TaskTemplate.ContainerSpec.Dir = info.Dir
spec.TaskTemplate.ContainerSpec.User = info.User
spec.TaskTemplate.ContainerSpec.Labels = info.ContainerLabels.ToMap()
spec.TaskTemplate.ContainerSpec.Command = nil
spec.TaskTemplate.ContainerSpec.Args = nil
spec.TaskTemplate.ContainerSpec.Env = nil
if info.Command != "" {
spec.TaskTemplate.ContainerSpec.Command = strings.Split(info.Command, " ")
}
if info.Args != "" {
spec.TaskTemplate.ContainerSpec.Args = strings.Split(info.Args, " ")
}
if envs := info.Environments.ToMap(); len(envs) > 0 {
for n, v := range envs {
spec.TaskTemplate.ContainerSpec.Env = append(spec.TaskTemplate.ContainerSpec.Env, n+"="+v)
}
}
// Mode
if info.Mode == "replicated" {
if spec.Mode.Replicated == nil {
spec.Mode.Replicated = &swarm.ReplicatedService{Replicas: &info.Replicas}
} else {
spec.Mode.Replicated.Replicas = &info.Replicas
}
} else if info.Mode == "global" && spec.Mode.Global == nil {
spec.Mode.Global = &swarm.GlobalService{}
}
// Network
// todo: only process updated networks
spec.TaskTemplate.Networks = nil
for _, n := range info.Networks {
spec.TaskTemplate.Networks = append(spec.TaskTemplate.Networks, swarm.NetworkAttachmentConfig{Target: n})
}
// Endpoint
spec.EndpointSpec = &swarm.EndpointSpec{Mode: swarm.ResolutionMode(info.Endpoint.Mode)}
for _, p := range info.Endpoint.Ports {
port := swarm.PortConfig{
Protocol: p.Protocol,
TargetPort: p.TargetPort,
PublishedPort: p.PublishedPort,
PublishMode: p.PublishMode,
}
spec.EndpointSpec.Ports = append(spec.EndpointSpec.Ports, port)
}
spec.TaskTemplate.ContainerSpec.Secrets = nil
for _, s := range info.Secrets {
spec.TaskTemplate.ContainerSpec.Secrets = append(spec.TaskTemplate.ContainerSpec.Secrets, s.ToSecret())
}
spec.TaskTemplate.ContainerSpec.Configs = nil
for _, c := range info.Configs {
spec.TaskTemplate.ContainerSpec.Configs = append(spec.TaskTemplate.ContainerSpec.Configs, c.ToConfig())
}
// Mounts
// todo: fix > original options are not reserved.
spec.TaskTemplate.ContainerSpec.Mounts = nil
for _, m := range info.Mounts {
if m.Target != "" {
mnt := mount.Mount{
Type: mount.Type(m.Type),
Source: m.Source,
Target: m.Target,
ReadOnly: m.ReadOnly,
}
if m.Propagation != "" {
mnt.BindOptions = &mount.BindOptions{Propagation: m.Propagation}
}
spec.TaskTemplate.ContainerSpec.Mounts = append(spec.TaskTemplate.ContainerSpec.Mounts, mnt)
}
}
// Placement
if spec.TaskTemplate.Placement == nil {
spec.TaskTemplate.Placement = &swarm.Placement{}
}
spec.TaskTemplate.Placement.Constraints = nil
for _, c := range info.Placement.Constraints {
if cons := c.ToConstraint(); cons != "" {
spec.TaskTemplate.Placement.Constraints = append(spec.TaskTemplate.Placement.Constraints, cons)
}
}
spec.TaskTemplate.Placement.Preferences = nil
for _, p := range info.Placement.Preferences {
if p.Spread != "" {
pref := swarm.PlacementPreference{
Spread: &swarm.SpreadOver{SpreadDescriptor: p.Spread},
}
spec.TaskTemplate.Placement.Preferences = append(spec.TaskTemplate.Placement.Preferences, pref)
}
}
// update policy
spec.UpdateConfig = &swarm.UpdateConfig{
Parallelism: info.UpdatePolicy.Parallelism,
FailureAction: info.UpdatePolicy.FailureAction,
Order: info.UpdatePolicy.Order,
}
if info.UpdatePolicy.Delay != "" {
spec.UpdateConfig.Delay, err = time.ParseDuration(info.UpdatePolicy.Delay)
if err != nil {
return err
}
}
// rollback policy
spec.RollbackConfig = &swarm.UpdateConfig{
Parallelism: info.RollbackPolicy.Parallelism,
FailureAction: info.RollbackPolicy.FailureAction,
Order: info.RollbackPolicy.Order,
}
if info.RollbackPolicy.Delay != "" {
spec.RollbackConfig.Delay, err = time.ParseDuration(info.RollbackPolicy.Delay)
if err != nil {
return err
}
}
// restart policy
var d time.Duration
spec.TaskTemplate.RestartPolicy = &swarm.RestartPolicy{
Condition: info.RestartPolicy.Condition,
MaxAttempts: &info.RestartPolicy.MaxAttempts,
}
if info.RestartPolicy.Delay != "" {
d, err = time.ParseDuration(info.RestartPolicy.Delay)
if err != nil {
return err
}
spec.TaskTemplate.RestartPolicy.Delay = &d
}
if info.RestartPolicy.Window != "" {
d, err = time.ParseDuration(info.RestartPolicy.Window)
if err != nil {
return err
}
spec.TaskTemplate.RestartPolicy.Window = &d
}
// resources
if info.Resource.Limit.IsSet() || info.Resource.Reserve.IsSet() {
spec.TaskTemplate.Resources = &swarm.ResourceRequirements{}
if info.Resource.Limit.IsSet() {
spec.TaskTemplate.Resources.Limits, err = info.Resource.Limit.ToResources()
if err != nil {
return err
}
}
if info.Resource.Limit.IsSet() {
spec.TaskTemplate.Resources.Reservations, err = info.Resource.Reserve.ToResources()
if err != nil {
return err
}
}
}
// log driver
if info.LogDriver.Name != "" {
spec.TaskTemplate.LogDriver = &swarm.Driver{
Name: info.LogDriver.Name,
Options: info.LogDriver.Options.ToMap(),
}
}
options := types.ServiceUpdateOptions{
RegistryAuthFrom: types.RegistryAuthFromSpec,
QueryRegistry: false,
}
resp, err := cli.ServiceUpdate(context.Background(), info.Name, service.Version, spec, options)
if err == nil && len(resp.Warnings) > 0 {
mgr.Logger().Warnf("service %s was updated but got warnings: %v", info.Name, resp.Warnings)
}
return err
})
}
// ServiceScale adjust replicas of a service.
func ServiceScale(name string, count uint64) error {
return mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
service, _, err := cli.ServiceInspectWithRaw(ctx, name, types.ServiceInspectOptions{})
if err != nil {
return err
}
spec := service.Spec
if spec.Mode.Replicated == nil {
return errors.New("the mode of service isn't replicated")
}
spec.Mode.Replicated.Replicas = &count
options := types.ServiceUpdateOptions{
RegistryAuthFrom: types.RegistryAuthFromSpec,
QueryRegistry: false,
}
resp, err := cli.ServiceUpdate(context.Background(), name, service.Version, spec, options)
if err == nil && len(resp.Warnings) > 0 {
mgr.Logger().Warnf("service %s was scaled but got warnings: %v", name, resp.Warnings)
}
return err
})
}
// ServiceCreate create a service.
func ServiceCreate(info *model.ServiceInfo) error {
return mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
service := swarm.ServiceSpec{
Annotations: swarm.Annotations{
Name: info.Name,
Labels: info.ServiceLabels.ToMap(),
},
TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{
Image: info.Image,
Dir: info.Dir,
User: info.User,
Labels: info.ContainerLabels.ToMap(),
},
},
EndpointSpec: &swarm.EndpointSpec{Mode: swarm.ResolutionModeVIP},
}
if info.Command != "" {
service.TaskTemplate.ContainerSpec.Command = strings.Split(info.Command, " ")
}
if info.Args != "" {
service.TaskTemplate.ContainerSpec.Args = strings.Split(info.Args, " ")
}
if info.Mode == "replicated" {
service.Mode.Replicated = &swarm.ReplicatedService{Replicas: &info.Replicas}
} else if info.Mode == "global" {
service.Mode.Global = &swarm.GlobalService{}
}
if envs := info.Environments.ToMap(); len(envs) > 0 {
for n, v := range envs {
service.TaskTemplate.ContainerSpec.Env = append(service.TaskTemplate.ContainerSpec.Env, n+"="+v)
}
}
for _, n := range info.Networks {
service.TaskTemplate.Networks = append(service.TaskTemplate.Networks, swarm.NetworkAttachmentConfig{Target: n})
}
if info.Endpoint.Mode != "" && len(info.Endpoint.Ports) > 0 {
service.EndpointSpec = &swarm.EndpointSpec{Mode: swarm.ResolutionMode(info.Endpoint.Mode)}
for _, p := range info.Endpoint.Ports {
port := swarm.PortConfig{
Protocol: p.Protocol,
TargetPort: p.TargetPort,
PublishedPort: p.PublishedPort,
PublishMode: p.PublishMode,
}
service.EndpointSpec.Ports = append(service.EndpointSpec.Ports, port)
}
}
for _, s := range info.Secrets {
service.TaskTemplate.ContainerSpec.Secrets = append(service.TaskTemplate.ContainerSpec.Secrets, s.ToSecret())
}
for _, c := range info.Configs {
service.TaskTemplate.ContainerSpec.Configs = append(service.TaskTemplate.ContainerSpec.Configs, c.ToConfig())
}
for _, m := range info.Mounts {
if m.Target != "" {
mnt := mount.Mount{
Type: mount.Type(m.Type),
Source: m.Source,
Target: m.Target,
ReadOnly: m.ReadOnly,
}
if m.Propagation != "" {
mnt.BindOptions = &mount.BindOptions{Propagation: m.Propagation}
}
service.TaskTemplate.ContainerSpec.Mounts = append(service.TaskTemplate.ContainerSpec.Mounts, mnt)
}
}
service.TaskTemplate.Placement = &swarm.Placement{}
for _, c := range info.Placement.Constraints {
if cons := c.ToConstraint(); cons != "" {
service.TaskTemplate.Placement.Constraints = append(service.TaskTemplate.Placement.Constraints, cons)
}
}
for _, p := range info.Placement.Preferences {
if p.Spread != "" {
pref := swarm.PlacementPreference{
Spread: &swarm.SpreadOver{SpreadDescriptor: p.Spread},
}
service.TaskTemplate.Placement.Preferences = append(service.TaskTemplate.Placement.Preferences, pref)
}
}
// update policy
service.UpdateConfig = &swarm.UpdateConfig{
Parallelism: info.UpdatePolicy.Parallelism,
FailureAction: info.UpdatePolicy.FailureAction,
Order: info.UpdatePolicy.Order,
}
if info.UpdatePolicy.Delay != "" {
service.UpdateConfig.Delay, err = time.ParseDuration(info.UpdatePolicy.Delay)
if err != nil {
return err
}
}
// rollback policy
service.RollbackConfig = &swarm.UpdateConfig{
Parallelism: info.RollbackPolicy.Parallelism,
FailureAction: info.RollbackPolicy.FailureAction,
Order: info.RollbackPolicy.Order,
}
if info.RollbackPolicy.Delay != "" {
service.RollbackConfig.Delay, err = time.ParseDuration(info.RollbackPolicy.Delay)
if err != nil {
return err
}
}
// restart policy
var d time.Duration
service.TaskTemplate.RestartPolicy = &swarm.RestartPolicy{
Condition: info.RestartPolicy.Condition,
MaxAttempts: &info.RestartPolicy.MaxAttempts,
}
if info.RestartPolicy.Delay != "" {
d, err = time.ParseDuration(info.RestartPolicy.Delay)
if err != nil {
return err
}
service.TaskTemplate.RestartPolicy.Delay = &d
}
if info.RestartPolicy.Window != "" {
d, err = time.ParseDuration(info.RestartPolicy.Window)
if err != nil {
return err
}
service.TaskTemplate.RestartPolicy.Window = &d
}
// resources
if info.Resource.Limit.IsSet() || info.Resource.Reserve.IsSet() {
service.TaskTemplate.Resources = &swarm.ResourceRequirements{}
if info.Resource.Limit.IsSet() {
service.TaskTemplate.Resources.Limits, err = info.Resource.Limit.ToResources()
if err != nil {
return err
}
}
if info.Resource.Limit.IsSet() {
service.TaskTemplate.Resources.Reservations, err = info.Resource.Reserve.ToResources()
if err != nil {
return err
}
}
}
// log driver
if info.LogDriver.Name != "" {
service.TaskTemplate.LogDriver = &swarm.Driver{
Name: info.LogDriver.Name,
Options: info.LogDriver.Options.ToMap(),
}
}
opts := types.ServiceCreateOptions{EncodedRegistryAuth: info.RegistryAuth}
var resp types.ServiceCreateResponse
resp, err = cli.ServiceCreate(ctx, service, opts)
if err == nil && len(resp.Warnings) > 0 {
mgr.Logger().Warnf("service %s was created but got warnings: %v", info.Name, resp.Warnings)
}
return
})
}
// ServiceRemove remove a service.
func ServiceRemove(name string) error {
return mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
return cli.ServiceRemove(ctx, name)
})
}