2016-09-08 13:11:39 -04:00
|
|
|
package service
|
|
|
|
|
|
|
|
import (
|
2018-01-03 09:40:55 -05:00
|
|
|
"context"
|
|
|
|
"fmt"
|
2016-09-08 13:11:39 -04:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2017-05-15 08:45:19 -04:00
|
|
|
"github.com/docker/cli/opts"
|
2018-01-03 09:40:55 -05:00
|
|
|
"github.com/docker/docker/api/types"
|
2016-10-13 14:28:32 -04:00
|
|
|
"github.com/docker/docker/api/types/container"
|
2018-01-03 09:40:55 -05:00
|
|
|
"github.com/docker/docker/api/types/swarm"
|
2020-02-22 12:12:14 -05:00
|
|
|
"gotest.tools/v3/assert"
|
|
|
|
is "gotest.tools/v3/assert/cmp"
|
2016-09-08 13:11:39 -04:00
|
|
|
)
|
|
|
|
|
2019-02-02 10:35:26 -05:00
|
|
|
func TestCredentialSpecOpt(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
in string
|
|
|
|
value swarm.CredentialSpec
|
|
|
|
expectedErr string
|
|
|
|
}{
|
|
|
|
{
|
2019-03-27 16:44:32 -04:00
|
|
|
name: "empty",
|
|
|
|
in: "",
|
|
|
|
value: swarm.CredentialSpec{},
|
2019-02-02 10:35:26 -05:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "no-prefix",
|
|
|
|
in: "noprefix",
|
|
|
|
value: swarm.CredentialSpec{},
|
|
|
|
expectedErr: `invalid credential spec: value must be prefixed with "config://", "file://", or "registry://"`,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "config",
|
|
|
|
in: "config://0bt9dmxjvjiqermk6xrop3ekq",
|
|
|
|
value: swarm.CredentialSpec{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "file",
|
|
|
|
in: "file://somefile.json",
|
|
|
|
value: swarm.CredentialSpec{File: "somefile.json"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "registry",
|
|
|
|
in: "registry://testing",
|
|
|
|
value: swarm.CredentialSpec{Registry: "testing"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range tests {
|
|
|
|
tc := tc
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
var cs credentialSpecOpt
|
|
|
|
|
|
|
|
err := cs.Set(tc.in)
|
|
|
|
|
|
|
|
if tc.expectedErr != "" {
|
|
|
|
assert.Error(t, err, tc.expectedErr)
|
|
|
|
} else {
|
|
|
|
assert.NilError(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.Equal(t, cs.String(), tc.in)
|
|
|
|
assert.DeepEqual(t, cs.Value(), &tc.value)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-08 13:11:39 -04:00
|
|
|
func TestMemBytesString(t *testing.T) {
|
2016-12-25 04:11:12 -05:00
|
|
|
var mem opts.MemBytes = 1048576
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.Check(t, is.Equal("1MiB", mem.String()))
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestMemBytesSetAndValue(t *testing.T) {
|
2016-12-25 04:11:12 -05:00
|
|
|
var mem opts.MemBytes
|
2018-03-06 15:13:00 -05:00
|
|
|
assert.NilError(t, mem.Set("5kb"))
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.Check(t, is.Equal(int64(5120), mem.Value()))
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestNanoCPUsString(t *testing.T) {
|
2016-11-01 13:12:29 -04:00
|
|
|
var cpus opts.NanoCPUs = 6100000000
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.Check(t, is.Equal("6.100", cpus.String()))
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestNanoCPUsSetAndValue(t *testing.T) {
|
2016-11-01 13:12:29 -04:00
|
|
|
var cpus opts.NanoCPUs
|
2018-03-06 15:13:00 -05:00
|
|
|
assert.NilError(t, cpus.Set("0.35"))
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.Check(t, is.Equal(int64(350000000), cpus.Value()))
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestUint64OptString(t *testing.T) {
|
|
|
|
value := uint64(2345678)
|
|
|
|
opt := Uint64Opt{value: &value}
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.Check(t, is.Equal("2345678", opt.String()))
|
2016-09-08 13:11:39 -04:00
|
|
|
|
|
|
|
opt = Uint64Opt{}
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.Check(t, is.Equal("", opt.String()))
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestUint64OptSetAndValue(t *testing.T) {
|
|
|
|
var opt Uint64Opt
|
2018-03-06 15:13:00 -05:00
|
|
|
assert.NilError(t, opt.Set("14445"))
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.Check(t, is.Equal(uint64(14445), *opt.Value()))
|
2016-09-08 13:11:39 -04:00
|
|
|
}
|
|
|
|
|
2016-10-13 14:28:32 -04:00
|
|
|
func TestHealthCheckOptionsToHealthConfig(t *testing.T) {
|
|
|
|
dur := time.Second
|
|
|
|
opt := healthCheckOptions{
|
2016-11-29 04:58:47 -05:00
|
|
|
cmd: "curl",
|
2018-04-10 09:55:00 -04:00
|
|
|
interval: opts.PositiveDurationOpt{DurationOpt: *opts.NewDurationOpt(&dur)},
|
|
|
|
timeout: opts.PositiveDurationOpt{DurationOpt: *opts.NewDurationOpt(&dur)},
|
|
|
|
startPeriod: opts.PositiveDurationOpt{DurationOpt: *opts.NewDurationOpt(&dur)},
|
2016-11-29 04:58:47 -05:00
|
|
|
retries: 10,
|
2016-10-13 14:28:32 -04:00
|
|
|
}
|
|
|
|
config, err := opt.toHealthConfig()
|
2018-03-06 14:44:13 -05:00
|
|
|
assert.NilError(t, err)
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.Check(t, is.DeepEqual(&container.HealthConfig{
|
2016-11-29 04:58:47 -05:00
|
|
|
Test: []string{"CMD-SHELL", "curl"},
|
|
|
|
Interval: time.Second,
|
|
|
|
Timeout: time.Second,
|
|
|
|
StartPeriod: time.Second,
|
|
|
|
Retries: 10,
|
2018-03-05 18:53:52 -05:00
|
|
|
}, config))
|
2016-10-13 14:28:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestHealthCheckOptionsToHealthConfigNoHealthcheck(t *testing.T) {
|
|
|
|
opt := healthCheckOptions{
|
|
|
|
noHealthcheck: true,
|
|
|
|
}
|
|
|
|
config, err := opt.toHealthConfig()
|
2018-03-06 14:44:13 -05:00
|
|
|
assert.NilError(t, err)
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.Check(t, is.DeepEqual(&container.HealthConfig{
|
2016-10-13 14:28:32 -04:00
|
|
|
Test: []string{"NONE"},
|
2018-03-05 18:53:52 -05:00
|
|
|
}, config))
|
2016-10-13 14:28:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestHealthCheckOptionsToHealthConfigConflict(t *testing.T) {
|
|
|
|
opt := healthCheckOptions{
|
|
|
|
cmd: "curl",
|
|
|
|
noHealthcheck: true,
|
|
|
|
}
|
|
|
|
_, err := opt.toHealthConfig()
|
2018-03-06 15:54:24 -05:00
|
|
|
assert.Error(t, err, "--no-healthcheck conflicts with --health-* options")
|
2016-10-13 14:28:32 -04:00
|
|
|
}
|
2017-11-08 11:55:16 -05:00
|
|
|
|
|
|
|
func TestResourceOptionsToResourceRequirements(t *testing.T) {
|
|
|
|
incorrectOptions := []resourceOptions{
|
|
|
|
{
|
|
|
|
resGenericResources: []string{"foo=bar", "foo=1"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
resGenericResources: []string{"foo=bar", "foo=baz"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
resGenericResources: []string{"foo=bar"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
resGenericResources: []string{"foo=1", "foo=2"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, opt := range incorrectOptions {
|
|
|
|
_, err := opt.ToResourceRequirements()
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.Check(t, is.ErrorContains(err, ""))
|
2017-11-08 11:55:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
correctOptions := []resourceOptions{
|
|
|
|
{
|
|
|
|
resGenericResources: []string{"foo=1"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
resGenericResources: []string{"foo=1", "bar=2"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, opt := range correctOptions {
|
|
|
|
r, err := opt.ToResourceRequirements()
|
2018-03-06 14:44:13 -05:00
|
|
|
assert.NilError(t, err)
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.Check(t, is.Len(r.Reservations.GenericResources, len(opt.resGenericResources)))
|
2017-11-08 11:55:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2018-01-03 09:40:55 -05:00
|
|
|
|
|
|
|
func TestToServiceNetwork(t *testing.T) {
|
|
|
|
nws := []types.NetworkResource{
|
|
|
|
{Name: "aaa-network", ID: "id555"},
|
|
|
|
{Name: "mmm-network", ID: "id999"},
|
|
|
|
{Name: "zzz-network", ID: "id111"},
|
|
|
|
}
|
|
|
|
|
|
|
|
client := &fakeClient{
|
|
|
|
networkInspectFunc: func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) {
|
|
|
|
for _, network := range nws {
|
|
|
|
if network.ID == networkID || network.Name == networkID {
|
|
|
|
return network, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return types.NetworkResource{}, fmt.Errorf("network not found: %s", networkID)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
nwo := opts.NetworkOpt{}
|
2019-04-02 07:03:49 -04:00
|
|
|
assert.NilError(t, nwo.Set("zzz-network"))
|
|
|
|
assert.NilError(t, nwo.Set("mmm-network"))
|
|
|
|
assert.NilError(t, nwo.Set("aaa-network"))
|
2018-01-03 09:40:55 -05:00
|
|
|
|
|
|
|
o := newServiceOptions()
|
|
|
|
o.mode = "replicated"
|
|
|
|
o.networks = nwo
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
flags := newCreateCommand(nil).Flags()
|
|
|
|
service, err := o.ToService(ctx, client, flags)
|
2018-03-05 18:53:52 -05:00
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id111"}, {Target: "id555"}, {Target: "id999"}}, service.TaskTemplate.Networks))
|
2018-01-03 09:40:55 -05:00
|
|
|
}
|
Fix service rollback options being cross-wired
The "update" and "rollback" configurations were cross-wired, as a result, setting
`--rollback-*` options would override the service's update-options.
Creating a service with both update, and rollback configuration:
docker service create \
--name=test \
--update-failure-action=pause \
--update-max-failure-ratio=0.6 \
--update-monitor=3s \
--update-order=stop-first \
--update-parallelism=3 \
--rollback-failure-action=continue \
--rollback-max-failure-ratio=0.5 \
--rollback-monitor=4s \
--rollback-order=start-first \
--rollback-parallelism=2 \
--tty \
busybox
Before this change:
docker service inspect --format '{{json .Spec.UpdateConfig}}' test \
&& docker service inspect --format '{{json .Spec.RollbackConfig}}' test
Produces:
{"Parallelism":3,"FailureAction":"pause","Monitor":3000000000,"MaxFailureRatio":0.6,"Order":"stop-first"}
{"Parallelism":3,"FailureAction":"pause","Monitor":3000000000,"MaxFailureRatio":0.6,"Order":"stop-first"}
After this change:
{"Parallelism":3,"FailureAction":"pause","Monitor":3000000000,"MaxFailureRatio":0.6,"Order":"stop-first"}
{"Parallelism":2,"FailureAction":"continue","Monitor":4000000000,"MaxFailureRatio":0.5,"Order":"start-first"}
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-05-11 05:47:05 -04:00
|
|
|
|
2020-04-29 11:11:48 -04:00
|
|
|
func TestToServicePidsLimit(t *testing.T) {
|
|
|
|
flags := newCreateCommand(nil).Flags()
|
|
|
|
opt := newServiceOptions()
|
|
|
|
opt.mode = "replicated"
|
|
|
|
opt.resources.limitPids = 100
|
|
|
|
service, err := opt.ToService(context.Background(), &fakeClient{}, flags)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Equal(t, service.TaskTemplate.Resources.Limits.Pids, int64(100))
|
|
|
|
}
|
|
|
|
|
Fix service rollback options being cross-wired
The "update" and "rollback" configurations were cross-wired, as a result, setting
`--rollback-*` options would override the service's update-options.
Creating a service with both update, and rollback configuration:
docker service create \
--name=test \
--update-failure-action=pause \
--update-max-failure-ratio=0.6 \
--update-monitor=3s \
--update-order=stop-first \
--update-parallelism=3 \
--rollback-failure-action=continue \
--rollback-max-failure-ratio=0.5 \
--rollback-monitor=4s \
--rollback-order=start-first \
--rollback-parallelism=2 \
--tty \
busybox
Before this change:
docker service inspect --format '{{json .Spec.UpdateConfig}}' test \
&& docker service inspect --format '{{json .Spec.RollbackConfig}}' test
Produces:
{"Parallelism":3,"FailureAction":"pause","Monitor":3000000000,"MaxFailureRatio":0.6,"Order":"stop-first"}
{"Parallelism":3,"FailureAction":"pause","Monitor":3000000000,"MaxFailureRatio":0.6,"Order":"stop-first"}
After this change:
{"Parallelism":3,"FailureAction":"pause","Monitor":3000000000,"MaxFailureRatio":0.6,"Order":"stop-first"}
{"Parallelism":2,"FailureAction":"continue","Monitor":4000000000,"MaxFailureRatio":0.5,"Order":"start-first"}
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-05-11 05:47:05 -04:00
|
|
|
func TestToServiceUpdateRollback(t *testing.T) {
|
|
|
|
expected := swarm.ServiceSpec{
|
|
|
|
UpdateConfig: &swarm.UpdateConfig{
|
|
|
|
Parallelism: 23,
|
|
|
|
Delay: 34 * time.Second,
|
|
|
|
Monitor: 54321 * time.Nanosecond,
|
|
|
|
FailureAction: "pause",
|
|
|
|
MaxFailureRatio: 0.6,
|
|
|
|
Order: "stop-first",
|
|
|
|
},
|
|
|
|
RollbackConfig: &swarm.UpdateConfig{
|
|
|
|
Parallelism: 12,
|
|
|
|
Delay: 23 * time.Second,
|
|
|
|
Monitor: 12345 * time.Nanosecond,
|
|
|
|
FailureAction: "continue",
|
|
|
|
MaxFailureRatio: 0.5,
|
|
|
|
Order: "start-first",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note: in test-situation, the flags are only used to detect if an option
|
|
|
|
// was set; the actual value itself is read from the serviceOptions below.
|
|
|
|
flags := newCreateCommand(nil).Flags()
|
|
|
|
flags.Set("update-parallelism", "23")
|
|
|
|
flags.Set("update-delay", "34s")
|
|
|
|
flags.Set("update-monitor", "54321ns")
|
|
|
|
flags.Set("update-failure-action", "pause")
|
|
|
|
flags.Set("update-max-failure-ratio", "0.6")
|
|
|
|
flags.Set("update-order", "stop-first")
|
|
|
|
|
|
|
|
flags.Set("rollback-parallelism", "12")
|
|
|
|
flags.Set("rollback-delay", "23s")
|
|
|
|
flags.Set("rollback-monitor", "12345ns")
|
|
|
|
flags.Set("rollback-failure-action", "continue")
|
|
|
|
flags.Set("rollback-max-failure-ratio", "0.5")
|
|
|
|
flags.Set("rollback-order", "start-first")
|
|
|
|
|
|
|
|
o := newServiceOptions()
|
|
|
|
o.mode = "replicated"
|
|
|
|
o.update = updateOptions{
|
|
|
|
parallelism: 23,
|
|
|
|
delay: 34 * time.Second,
|
|
|
|
monitor: 54321 * time.Nanosecond,
|
|
|
|
onFailure: "pause",
|
|
|
|
maxFailureRatio: 0.6,
|
|
|
|
order: "stop-first",
|
|
|
|
}
|
|
|
|
o.rollback = updateOptions{
|
|
|
|
parallelism: 12,
|
|
|
|
delay: 23 * time.Second,
|
|
|
|
monitor: 12345 * time.Nanosecond,
|
|
|
|
onFailure: "continue",
|
|
|
|
maxFailureRatio: 0.5,
|
|
|
|
order: "start-first",
|
|
|
|
}
|
|
|
|
|
|
|
|
service, err := o.ToService(context.Background(), &fakeClient{}, flags)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.DeepEqual(service.UpdateConfig, expected.UpdateConfig))
|
|
|
|
assert.Check(t, is.DeepEqual(service.RollbackConfig, expected.RollbackConfig))
|
|
|
|
}
|
2019-01-09 12:10:35 -05:00
|
|
|
|
2021-01-18 09:33:45 -05:00
|
|
|
func TestToServiceUpdateRollbackOrder(t *testing.T) {
|
|
|
|
flags := newCreateCommand(nil).Flags()
|
|
|
|
flags.Set("update-order", "start-first")
|
|
|
|
flags.Set("rollback-order", "start-first")
|
|
|
|
|
|
|
|
o := newServiceOptions()
|
|
|
|
o.mode = "replicated"
|
|
|
|
o.update = updateOptions{order: "start-first"}
|
|
|
|
o.rollback = updateOptions{order: "start-first"}
|
|
|
|
|
|
|
|
service, err := o.ToService(context.Background(), &fakeClient{}, flags)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.Equal(service.UpdateConfig.Order, o.update.order))
|
|
|
|
assert.Check(t, is.Equal(service.RollbackConfig.Order, o.rollback.order))
|
|
|
|
}
|
|
|
|
|
2019-01-09 12:10:35 -05:00
|
|
|
func TestToServiceMaxReplicasGlobalModeConflict(t *testing.T) {
|
|
|
|
opt := serviceOptions{
|
|
|
|
mode: "global",
|
|
|
|
maxReplicas: 1,
|
|
|
|
}
|
|
|
|
_, err := opt.ToServiceMode()
|
2020-01-09 13:17:43 -05:00
|
|
|
assert.Error(t, err, "replicas-max-per-node can only be used with replicated or replicated-job mode")
|
2019-01-09 12:10:35 -05:00
|
|
|
}
|
2019-02-12 10:07:07 -05:00
|
|
|
|
|
|
|
func TestToServiceSysCtls(t *testing.T) {
|
|
|
|
o := newServiceOptions()
|
|
|
|
o.mode = "replicated"
|
|
|
|
o.sysctls.Set("net.ipv4.ip_forward=1")
|
|
|
|
o.sysctls.Set("kernel.shmmax=123456")
|
|
|
|
|
|
|
|
expected := map[string]string{"net.ipv4.ip_forward": "1", "kernel.shmmax": "123456"}
|
|
|
|
flags := newCreateCommand(nil).Flags()
|
|
|
|
service, err := o.ToService(context.Background(), &fakeClient{}, flags)
|
|
|
|
assert.NilError(t, err)
|
|
|
|
assert.Check(t, is.DeepEqual(service.TaskTemplate.ContainerSpec.Sysctls, expected))
|
|
|
|
}
|