github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/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 186 func TestToServiceNetwork(t *testing.T) { 187 nws := []types.NetworkResource{ 188 {Name: "aaa-network", ID: "id555"}, 189 {Name: "mmm-network", ID: "id999"}, 190 {Name: "zzz-network", ID: "id111"}, 191 } 192 193 client := &fakeClient{ 194 networkInspectFunc: func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) { 195 for _, network := range nws { 196 if network.ID == networkID || network.Name == networkID { 197 return network, nil 198 } 199 } 200 return types.NetworkResource{}, fmt.Errorf("network not found: %s", networkID) 201 }, 202 } 203 204 nwo := opts.NetworkOpt{} 205 assert.NilError(t, nwo.Set("zzz-network")) 206 assert.NilError(t, nwo.Set("mmm-network")) 207 assert.NilError(t, nwo.Set("aaa-network")) 208 209 o := newServiceOptions() 210 o.mode = "replicated" 211 o.networks = nwo 212 213 ctx := context.Background() 214 flags := newCreateCommand(nil).Flags() 215 service, err := o.ToService(ctx, client, flags) 216 assert.NilError(t, err) 217 assert.Check(t, is.DeepEqual([]swarm.NetworkAttachmentConfig{{Target: "id111"}, {Target: "id555"}, {Target: "id999"}}, service.TaskTemplate.Networks)) 218 } 219 220 func TestToServicePidsLimit(t *testing.T) { 221 flags := newCreateCommand(nil).Flags() 222 opt := newServiceOptions() 223 opt.mode = "replicated" 224 opt.resources.limitPids = 100 225 service, err := opt.ToService(context.Background(), &fakeClient{}, flags) 226 assert.NilError(t, err) 227 assert.Equal(t, service.TaskTemplate.Resources.Limits.Pids, int64(100)) 228 } 229 230 func TestToServiceUpdateRollback(t *testing.T) { 231 expected := swarm.ServiceSpec{ 232 UpdateConfig: &swarm.UpdateConfig{ 233 Parallelism: 23, 234 Delay: 34 * time.Second, 235 Monitor: 54321 * time.Nanosecond, 236 FailureAction: "pause", 237 MaxFailureRatio: 0.6, 238 Order: "stop-first", 239 }, 240 RollbackConfig: &swarm.UpdateConfig{ 241 Parallelism: 12, 242 Delay: 23 * time.Second, 243 Monitor: 12345 * time.Nanosecond, 244 FailureAction: "continue", 245 MaxFailureRatio: 0.5, 246 Order: "start-first", 247 }, 248 } 249 250 // Note: in test-situation, the flags are only used to detect if an option 251 // was set; the actual value itself is read from the serviceOptions below. 252 flags := newCreateCommand(nil).Flags() 253 flags.Set("update-parallelism", "23") 254 flags.Set("update-delay", "34s") 255 flags.Set("update-monitor", "54321ns") 256 flags.Set("update-failure-action", "pause") 257 flags.Set("update-max-failure-ratio", "0.6") 258 flags.Set("update-order", "stop-first") 259 260 flags.Set("rollback-parallelism", "12") 261 flags.Set("rollback-delay", "23s") 262 flags.Set("rollback-monitor", "12345ns") 263 flags.Set("rollback-failure-action", "continue") 264 flags.Set("rollback-max-failure-ratio", "0.5") 265 flags.Set("rollback-order", "start-first") 266 267 o := newServiceOptions() 268 o.mode = "replicated" 269 o.update = updateOptions{ 270 parallelism: 23, 271 delay: 34 * time.Second, 272 monitor: 54321 * time.Nanosecond, 273 onFailure: "pause", 274 maxFailureRatio: 0.6, 275 order: "stop-first", 276 } 277 o.rollback = updateOptions{ 278 parallelism: 12, 279 delay: 23 * time.Second, 280 monitor: 12345 * time.Nanosecond, 281 onFailure: "continue", 282 maxFailureRatio: 0.5, 283 order: "start-first", 284 } 285 286 service, err := o.ToService(context.Background(), &fakeClient{}, flags) 287 assert.NilError(t, err) 288 assert.Check(t, is.DeepEqual(service.UpdateConfig, expected.UpdateConfig)) 289 assert.Check(t, is.DeepEqual(service.RollbackConfig, expected.RollbackConfig)) 290 } 291 292 func TestToServiceMaxReplicasGlobalModeConflict(t *testing.T) { 293 opt := serviceOptions{ 294 mode: "global", 295 maxReplicas: 1, 296 } 297 _, err := opt.ToServiceMode() 298 assert.Error(t, err, "replicas-max-per-node can only be used with replicated or replicated-job mode") 299 } 300 301 func TestToServiceSysCtls(t *testing.T) { 302 o := newServiceOptions() 303 o.mode = "replicated" 304 o.sysctls.Set("net.ipv4.ip_forward=1") 305 o.sysctls.Set("kernel.shmmax=123456") 306 307 expected := map[string]string{"net.ipv4.ip_forward": "1", "kernel.shmmax": "123456"} 308 flags := newCreateCommand(nil).Flags() 309 service, err := o.ToService(context.Background(), &fakeClient{}, flags) 310 assert.NilError(t, err) 311 assert.Check(t, is.DeepEqual(service.TaskTemplate.ContainerSpec.Sysctls, expected)) 312 }