github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/service/opts_test.go (about) 1 package service 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 "time" 8 9 "github.com/docker/cli/opts" 10 "github.com/docker/docker/api/types" 11 "github.com/docker/docker/api/types/container" 12 "github.com/docker/docker/api/types/swarm" 13 "gotest.tools/v3/assert" 14 is "gotest.tools/v3/assert/cmp" 15 ) 16 17 func TestCredentialSpecOpt(t *testing.T) { 18 tests := []struct { 19 name string 20 in string 21 value swarm.CredentialSpec 22 expectedErr string 23 }{ 24 { 25 name: "empty", 26 in: "", 27 value: swarm.CredentialSpec{}, 28 }, 29 { 30 name: "no-prefix", 31 in: "noprefix", 32 value: swarm.CredentialSpec{}, 33 expectedErr: `invalid credential spec: value must be prefixed with "config://", "file://", or "registry://"`, 34 }, 35 { 36 name: "config", 37 in: "config://0bt9dmxjvjiqermk6xrop3ekq", 38 value: swarm.CredentialSpec{Config: "0bt9dmxjvjiqermk6xrop3ekq"}, 39 }, 40 { 41 name: "file", 42 in: "file://somefile.json", 43 value: swarm.CredentialSpec{File: "somefile.json"}, 44 }, 45 { 46 name: "registry", 47 in: "registry://testing", 48 value: swarm.CredentialSpec{Registry: "testing"}, 49 }, 50 } 51 52 for _, tc := range tests { 53 tc := tc 54 t.Run(tc.name, func(t *testing.T) { 55 var cs credentialSpecOpt 56 57 err := cs.Set(tc.in) 58 59 if tc.expectedErr != "" { 60 assert.Error(t, err, tc.expectedErr) 61 } else { 62 assert.NilError(t, err) 63 } 64 65 assert.Equal(t, cs.String(), tc.in) 66 assert.DeepEqual(t, cs.Value(), &tc.value) 67 }) 68 } 69 } 70 71 func TestMemBytesString(t *testing.T) { 72 var mem opts.MemBytes = 1048576 73 assert.Check(t, is.Equal("1MiB", mem.String())) 74 } 75 76 func TestMemBytesSetAndValue(t *testing.T) { 77 var mem opts.MemBytes 78 assert.NilError(t, mem.Set("5kb")) 79 assert.Check(t, is.Equal(int64(5120), mem.Value())) 80 } 81 82 func TestNanoCPUsString(t *testing.T) { 83 var cpus opts.NanoCPUs = 6100000000 84 assert.Check(t, is.Equal("6.100", cpus.String())) 85 } 86 87 func TestNanoCPUsSetAndValue(t *testing.T) { 88 var cpus opts.NanoCPUs 89 assert.NilError(t, cpus.Set("0.35")) 90 assert.Check(t, is.Equal(int64(350000000), cpus.Value())) 91 } 92 93 func TestUint64OptString(t *testing.T) { 94 value := uint64(2345678) 95 opt := Uint64Opt{value: &value} 96 assert.Check(t, is.Equal("2345678", opt.String())) 97 98 opt = Uint64Opt{} 99 assert.Check(t, is.Equal("", opt.String())) 100 } 101 102 func TestUint64OptSetAndValue(t *testing.T) { 103 var opt Uint64Opt 104 assert.NilError(t, opt.Set("14445")) 105 assert.Check(t, is.Equal(uint64(14445), *opt.Value())) 106 } 107 108 func TestHealthCheckOptionsToHealthConfig(t *testing.T) { 109 dur := time.Second 110 opt := healthCheckOptions{ 111 cmd: "curl", 112 interval: opts.PositiveDurationOpt{DurationOpt: *opts.NewDurationOpt(&dur)}, 113 timeout: opts.PositiveDurationOpt{DurationOpt: *opts.NewDurationOpt(&dur)}, 114 startPeriod: opts.PositiveDurationOpt{DurationOpt: *opts.NewDurationOpt(&dur)}, 115 startInterval: opts.PositiveDurationOpt{DurationOpt: *opts.NewDurationOpt(&dur)}, 116 retries: 10, 117 } 118 config, err := opt.toHealthConfig() 119 assert.NilError(t, err) 120 assert.Check(t, is.DeepEqual(&container.HealthConfig{ 121 Test: []string{"CMD-SHELL", "curl"}, 122 Interval: time.Second, 123 Timeout: time.Second, 124 StartPeriod: time.Second, 125 StartInterval: time.Second, 126 Retries: 10, 127 }, config)) 128 } 129 130 func TestHealthCheckOptionsToHealthConfigNoHealthcheck(t *testing.T) { 131 opt := healthCheckOptions{ 132 noHealthcheck: true, 133 } 134 config, err := opt.toHealthConfig() 135 assert.NilError(t, err) 136 assert.Check(t, is.DeepEqual(&container.HealthConfig{ 137 Test: []string{"NONE"}, 138 }, config)) 139 } 140 141 func TestHealthCheckOptionsToHealthConfigConflict(t *testing.T) { 142 opt := healthCheckOptions{ 143 cmd: "curl", 144 noHealthcheck: true, 145 } 146 _, err := opt.toHealthConfig() 147 assert.Error(t, err, "--no-healthcheck conflicts with --health-* options") 148 } 149 150 func TestResourceOptionsToResourceRequirements(t *testing.T) { 151 incorrectOptions := []resourceOptions{ 152 { 153 resGenericResources: []string{"foo=bar", "foo=1"}, 154 }, 155 { 156 resGenericResources: []string{"foo=bar", "foo=baz"}, 157 }, 158 { 159 resGenericResources: []string{"foo=bar"}, 160 }, 161 { 162 resGenericResources: []string{"foo=1", "foo=2"}, 163 }, 164 } 165 166 for _, opt := range incorrectOptions { 167 _, err := opt.ToResourceRequirements() 168 assert.Check(t, is.ErrorContains(err, "")) 169 } 170 171 correctOptions := []resourceOptions{ 172 { 173 resGenericResources: []string{"foo=1"}, 174 }, 175 { 176 resGenericResources: []string{"foo=1", "bar=2"}, 177 }, 178 } 179 180 for _, opt := range correctOptions { 181 r, err := opt.ToResourceRequirements() 182 assert.NilError(t, err) 183 assert.Check(t, is.Len(r.Reservations.GenericResources, len(opt.resGenericResources))) 184 } 185 } 186 187 func TestToServiceNetwork(t *testing.T) { 188 nws := []types.NetworkResource{ 189 {Name: "aaa-network", ID: "id555"}, 190 {Name: "mmm-network", ID: "id999"}, 191 {Name: "zzz-network", ID: "id111"}, 192 } 193 194 client := &fakeClient{ 195 networkInspectFunc: func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) { 196 for _, network := range nws { 197 if network.ID == networkID || network.Name == networkID { 198 return network, nil 199 } 200 } 201 return types.NetworkResource{}, fmt.Errorf("network not found: %s", networkID) 202 }, 203 } 204 205 nwo := opts.NetworkOpt{} 206 assert.NilError(t, nwo.Set("zzz-network")) 207 assert.NilError(t, nwo.Set("mmm-network")) 208 assert.NilError(t, nwo.Set("aaa-network")) 209 210 o := newServiceOptions() 211 o.mode = "replicated" 212 o.networks = nwo 213 214 ctx := context.Background() 215 flags := newCreateCommand(nil).Flags() 216 service, err := o.ToService(ctx, client, flags) 217 assert.NilError(t, err) 218 assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id111"}, {Target: "id555"}, {Target: "id999"}}, service.TaskTemplate.Networks)) 219 } 220 221 func TestToServicePidsLimit(t *testing.T) { 222 flags := newCreateCommand(nil).Flags() 223 opt := newServiceOptions() 224 opt.mode = "replicated" 225 opt.resources.limitPids = 100 226 service, err := opt.ToService(context.Background(), &fakeClient{}, flags) 227 assert.NilError(t, err) 228 assert.Equal(t, service.TaskTemplate.Resources.Limits.Pids, int64(100)) 229 } 230 231 func TestToServiceUpdateRollback(t *testing.T) { 232 expected := swarm.ServiceSpec{ 233 UpdateConfig: &swarm.UpdateConfig{ 234 Parallelism: 23, 235 Delay: 34 * time.Second, 236 Monitor: 54321 * time.Nanosecond, 237 FailureAction: "pause", 238 MaxFailureRatio: 0.6, 239 Order: "stop-first", 240 }, 241 RollbackConfig: &swarm.UpdateConfig{ 242 Parallelism: 12, 243 Delay: 23 * time.Second, 244 Monitor: 12345 * time.Nanosecond, 245 FailureAction: "continue", 246 MaxFailureRatio: 0.5, 247 Order: "start-first", 248 }, 249 } 250 251 // Note: in test-situation, the flags are only used to detect if an option 252 // was set; the actual value itself is read from the serviceOptions below. 253 flags := newCreateCommand(nil).Flags() 254 flags.Set("update-parallelism", "23") 255 flags.Set("update-delay", "34s") 256 flags.Set("update-monitor", "54321ns") 257 flags.Set("update-failure-action", "pause") 258 flags.Set("update-max-failure-ratio", "0.6") 259 flags.Set("update-order", "stop-first") 260 261 flags.Set("rollback-parallelism", "12") 262 flags.Set("rollback-delay", "23s") 263 flags.Set("rollback-monitor", "12345ns") 264 flags.Set("rollback-failure-action", "continue") 265 flags.Set("rollback-max-failure-ratio", "0.5") 266 flags.Set("rollback-order", "start-first") 267 268 o := newServiceOptions() 269 o.mode = "replicated" 270 o.update = updateOptions{ 271 parallelism: 23, 272 delay: 34 * time.Second, 273 monitor: 54321 * time.Nanosecond, 274 onFailure: "pause", 275 maxFailureRatio: 0.6, 276 order: "stop-first", 277 } 278 o.rollback = updateOptions{ 279 parallelism: 12, 280 delay: 23 * time.Second, 281 monitor: 12345 * time.Nanosecond, 282 onFailure: "continue", 283 maxFailureRatio: 0.5, 284 order: "start-first", 285 } 286 287 service, err := o.ToService(context.Background(), &fakeClient{}, flags) 288 assert.NilError(t, err) 289 assert.Check(t, is.DeepEqual(service.UpdateConfig, expected.UpdateConfig)) 290 assert.Check(t, is.DeepEqual(service.RollbackConfig, expected.RollbackConfig)) 291 } 292 293 func TestToServiceUpdateRollbackOrder(t *testing.T) { 294 flags := newCreateCommand(nil).Flags() 295 flags.Set("update-order", "start-first") 296 flags.Set("rollback-order", "start-first") 297 298 o := newServiceOptions() 299 o.mode = "replicated" 300 o.update = updateOptions{order: "start-first"} 301 o.rollback = updateOptions{order: "start-first"} 302 303 service, err := o.ToService(context.Background(), &fakeClient{}, flags) 304 assert.NilError(t, err) 305 assert.Check(t, is.Equal(service.UpdateConfig.Order, o.update.order)) 306 assert.Check(t, is.Equal(service.RollbackConfig.Order, o.rollback.order)) 307 } 308 309 func TestToServiceMaxReplicasGlobalModeConflict(t *testing.T) { 310 opt := serviceOptions{ 311 mode: "global", 312 maxReplicas: 1, 313 } 314 _, err := opt.ToServiceMode() 315 assert.Error(t, err, "replicas-max-per-node can only be used with replicated or replicated-job mode") 316 } 317 318 func TestToServiceSysCtls(t *testing.T) { 319 o := newServiceOptions() 320 o.mode = "replicated" 321 o.sysctls.Set("net.ipv4.ip_forward=1") 322 o.sysctls.Set("kernel.shmmax=123456") 323 324 expected := map[string]string{"net.ipv4.ip_forward": "1", "kernel.shmmax": "123456"} 325 flags := newCreateCommand(nil).Flags() 326 service, err := o.ToService(context.Background(), &fakeClient{}, flags) 327 assert.NilError(t, err) 328 assert.Check(t, is.DeepEqual(service.TaskTemplate.ContainerSpec.Sysctls, expected)) 329 }