github.com/panekj/cli@v0.0.0-20230304125325-467dd2f3797e/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 retries: 10, 116 } 117 config, err := opt.toHealthConfig() 118 assert.NilError(t, err) 119 assert.Check(t, is.DeepEqual(&container.HealthConfig{ 120 Test: []string{"CMD-SHELL", "curl"}, 121 Interval: time.Second, 122 Timeout: time.Second, 123 StartPeriod: time.Second, 124 Retries: 10, 125 }, config)) 126 } 127 128 func TestHealthCheckOptionsToHealthConfigNoHealthcheck(t *testing.T) { 129 opt := healthCheckOptions{ 130 noHealthcheck: true, 131 } 132 config, err := opt.toHealthConfig() 133 assert.NilError(t, err) 134 assert.Check(t, is.DeepEqual(&container.HealthConfig{ 135 Test: []string{"NONE"}, 136 }, config)) 137 } 138 139 func TestHealthCheckOptionsToHealthConfigConflict(t *testing.T) { 140 opt := healthCheckOptions{ 141 cmd: "curl", 142 noHealthcheck: true, 143 } 144 _, err := opt.toHealthConfig() 145 assert.Error(t, err, "--no-healthcheck conflicts with --health-* options") 146 } 147 148 func TestResourceOptionsToResourceRequirements(t *testing.T) { 149 incorrectOptions := []resourceOptions{ 150 { 151 resGenericResources: []string{"foo=bar", "foo=1"}, 152 }, 153 { 154 resGenericResources: []string{"foo=bar", "foo=baz"}, 155 }, 156 { 157 resGenericResources: []string{"foo=bar"}, 158 }, 159 { 160 resGenericResources: []string{"foo=1", "foo=2"}, 161 }, 162 } 163 164 for _, opt := range incorrectOptions { 165 _, err := opt.ToResourceRequirements() 166 assert.Check(t, is.ErrorContains(err, "")) 167 } 168 169 correctOptions := []resourceOptions{ 170 { 171 resGenericResources: []string{"foo=1"}, 172 }, 173 { 174 resGenericResources: []string{"foo=1", "bar=2"}, 175 }, 176 } 177 178 for _, opt := range correctOptions { 179 r, err := opt.ToResourceRequirements() 180 assert.NilError(t, err) 181 assert.Check(t, is.Len(r.Reservations.GenericResources, len(opt.resGenericResources))) 182 } 183 } 184 185 func TestToServiceNetwork(t *testing.T) { 186 nws := []types.NetworkResource{ 187 {Name: "aaa-network", ID: "id555"}, 188 {Name: "mmm-network", ID: "id999"}, 189 {Name: "zzz-network", ID: "id111"}, 190 } 191 192 client := &fakeClient{ 193 networkInspectFunc: func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) { 194 for _, network := range nws { 195 if network.ID == networkID || network.Name == networkID { 196 return network, nil 197 } 198 } 199 return types.NetworkResource{}, fmt.Errorf("network not found: %s", networkID) 200 }, 201 } 202 203 nwo := opts.NetworkOpt{} 204 assert.NilError(t, nwo.Set("zzz-network")) 205 assert.NilError(t, nwo.Set("mmm-network")) 206 assert.NilError(t, nwo.Set("aaa-network")) 207 208 o := newServiceOptions() 209 o.mode = "replicated" 210 o.networks = nwo 211 212 ctx := context.Background() 213 flags := newCreateCommand(nil).Flags() 214 service, err := o.ToService(ctx, client, flags) 215 assert.NilError(t, err) 216 assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id111"}, {Target: "id555"}, {Target: "id999"}}, service.TaskTemplate.Networks)) 217 } 218 219 func TestToServicePidsLimit(t *testing.T) { 220 flags := newCreateCommand(nil).Flags() 221 opt := newServiceOptions() 222 opt.mode = "replicated" 223 opt.resources.limitPids = 100 224 service, err := opt.ToService(context.Background(), &fakeClient{}, flags) 225 assert.NilError(t, err) 226 assert.Equal(t, service.TaskTemplate.Resources.Limits.Pids, int64(100)) 227 } 228 229 func TestToServiceUpdateRollback(t *testing.T) { 230 expected := swarm.ServiceSpec{ 231 UpdateConfig: &swarm.UpdateConfig{ 232 Parallelism: 23, 233 Delay: 34 * time.Second, 234 Monitor: 54321 * time.Nanosecond, 235 FailureAction: "pause", 236 MaxFailureRatio: 0.6, 237 Order: "stop-first", 238 }, 239 RollbackConfig: &swarm.UpdateConfig{ 240 Parallelism: 12, 241 Delay: 23 * time.Second, 242 Monitor: 12345 * time.Nanosecond, 243 FailureAction: "continue", 244 MaxFailureRatio: 0.5, 245 Order: "start-first", 246 }, 247 } 248 249 // Note: in test-situation, the flags are only used to detect if an option 250 // was set; the actual value itself is read from the serviceOptions below. 251 flags := newCreateCommand(nil).Flags() 252 flags.Set("update-parallelism", "23") 253 flags.Set("update-delay", "34s") 254 flags.Set("update-monitor", "54321ns") 255 flags.Set("update-failure-action", "pause") 256 flags.Set("update-max-failure-ratio", "0.6") 257 flags.Set("update-order", "stop-first") 258 259 flags.Set("rollback-parallelism", "12") 260 flags.Set("rollback-delay", "23s") 261 flags.Set("rollback-monitor", "12345ns") 262 flags.Set("rollback-failure-action", "continue") 263 flags.Set("rollback-max-failure-ratio", "0.5") 264 flags.Set("rollback-order", "start-first") 265 266 o := newServiceOptions() 267 o.mode = "replicated" 268 o.update = updateOptions{ 269 parallelism: 23, 270 delay: 34 * time.Second, 271 monitor: 54321 * time.Nanosecond, 272 onFailure: "pause", 273 maxFailureRatio: 0.6, 274 order: "stop-first", 275 } 276 o.rollback = updateOptions{ 277 parallelism: 12, 278 delay: 23 * time.Second, 279 monitor: 12345 * time.Nanosecond, 280 onFailure: "continue", 281 maxFailureRatio: 0.5, 282 order: "start-first", 283 } 284 285 service, err := o.ToService(context.Background(), &fakeClient{}, flags) 286 assert.NilError(t, err) 287 assert.Check(t, is.DeepEqual(service.UpdateConfig, expected.UpdateConfig)) 288 assert.Check(t, is.DeepEqual(service.RollbackConfig, expected.RollbackConfig)) 289 } 290 291 func TestToServiceUpdateRollbackOrder(t *testing.T) { 292 flags := newCreateCommand(nil).Flags() 293 flags.Set("update-order", "start-first") 294 flags.Set("rollback-order", "start-first") 295 296 o := newServiceOptions() 297 o.mode = "replicated" 298 o.update = updateOptions{order: "start-first"} 299 o.rollback = updateOptions{order: "start-first"} 300 301 service, err := o.ToService(context.Background(), &fakeClient{}, flags) 302 assert.NilError(t, err) 303 assert.Check(t, is.Equal(service.UpdateConfig.Order, o.update.order)) 304 assert.Check(t, is.Equal(service.RollbackConfig.Order, o.rollback.order)) 305 } 306 307 func TestToServiceMaxReplicasGlobalModeConflict(t *testing.T) { 308 opt := serviceOptions{ 309 mode: "global", 310 maxReplicas: 1, 311 } 312 _, err := opt.ToServiceMode() 313 assert.Error(t, err, "replicas-max-per-node can only be used with replicated or replicated-job mode") 314 } 315 316 func TestToServiceSysCtls(t *testing.T) { 317 o := newServiceOptions() 318 o.mode = "replicated" 319 o.sysctls.Set("net.ipv4.ip_forward=1") 320 o.sysctls.Set("kernel.shmmax=123456") 321 322 expected := map[string]string{"net.ipv4.ip_forward": "1", "kernel.shmmax": "123456"} 323 flags := newCreateCommand(nil).Flags() 324 service, err := o.ToService(context.Background(), &fakeClient{}, flags) 325 assert.NilError(t, err) 326 assert.Check(t, is.DeepEqual(service.TaskTemplate.ContainerSpec.Sysctls, expected)) 327 }