github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/daemon/cluster/convert/service_test.go (about) 1 package convert // import "github.com/demonoid81/moby/daemon/cluster/convert" 2 3 import ( 4 "testing" 5 6 containertypes "github.com/demonoid81/moby/api/types/container" 7 swarmtypes "github.com/demonoid81/moby/api/types/swarm" 8 "github.com/demonoid81/moby/api/types/swarm/runtime" 9 swarmapi "github.com/docker/swarmkit/api" 10 google_protobuf3 "github.com/gogo/protobuf/types" 11 "gotest.tools/v3/assert" 12 ) 13 14 func TestServiceConvertFromGRPCRuntimeContainer(t *testing.T) { 15 gs := swarmapi.Service{ 16 Meta: swarmapi.Meta{ 17 Version: swarmapi.Version{ 18 Index: 1, 19 }, 20 CreatedAt: nil, 21 UpdatedAt: nil, 22 }, 23 SpecVersion: &swarmapi.Version{ 24 Index: 1, 25 }, 26 Spec: swarmapi.ServiceSpec{ 27 Task: swarmapi.TaskSpec{ 28 Runtime: &swarmapi.TaskSpec_Container{ 29 Container: &swarmapi.ContainerSpec{ 30 Image: "alpine:latest", 31 }, 32 }, 33 }, 34 }, 35 } 36 37 svc, err := ServiceFromGRPC(gs) 38 if err != nil { 39 t.Fatal(err) 40 } 41 42 if svc.Spec.TaskTemplate.Runtime != swarmtypes.RuntimeContainer { 43 t.Fatalf("expected type %s; received %T", swarmtypes.RuntimeContainer, svc.Spec.TaskTemplate.Runtime) 44 } 45 } 46 47 func TestServiceConvertFromGRPCGenericRuntimePlugin(t *testing.T) { 48 kind := string(swarmtypes.RuntimePlugin) 49 url := swarmtypes.RuntimeURLPlugin 50 gs := swarmapi.Service{ 51 Meta: swarmapi.Meta{ 52 Version: swarmapi.Version{ 53 Index: 1, 54 }, 55 CreatedAt: nil, 56 UpdatedAt: nil, 57 }, 58 SpecVersion: &swarmapi.Version{ 59 Index: 1, 60 }, 61 Spec: swarmapi.ServiceSpec{ 62 Task: swarmapi.TaskSpec{ 63 Runtime: &swarmapi.TaskSpec_Generic{ 64 Generic: &swarmapi.GenericRuntimeSpec{ 65 Kind: kind, 66 Payload: &google_protobuf3.Any{ 67 TypeUrl: string(url), 68 }, 69 }, 70 }, 71 }, 72 }, 73 } 74 75 svc, err := ServiceFromGRPC(gs) 76 if err != nil { 77 t.Fatal(err) 78 } 79 80 if svc.Spec.TaskTemplate.Runtime != swarmtypes.RuntimePlugin { 81 t.Fatalf("expected type %s; received %T", swarmtypes.RuntimePlugin, svc.Spec.TaskTemplate.Runtime) 82 } 83 } 84 85 func TestServiceConvertToGRPCGenericRuntimePlugin(t *testing.T) { 86 s := swarmtypes.ServiceSpec{ 87 TaskTemplate: swarmtypes.TaskSpec{ 88 Runtime: swarmtypes.RuntimePlugin, 89 PluginSpec: &runtime.PluginSpec{}, 90 }, 91 Mode: swarmtypes.ServiceMode{ 92 Global: &swarmtypes.GlobalService{}, 93 }, 94 } 95 96 svc, err := ServiceSpecToGRPC(s) 97 if err != nil { 98 t.Fatal(err) 99 } 100 101 v, ok := svc.Task.Runtime.(*swarmapi.TaskSpec_Generic) 102 if !ok { 103 t.Fatal("expected type swarmapi.TaskSpec_Generic") 104 } 105 106 if v.Generic.Payload.TypeUrl != string(swarmtypes.RuntimeURLPlugin) { 107 t.Fatalf("expected url %s; received %s", swarmtypes.RuntimeURLPlugin, v.Generic.Payload.TypeUrl) 108 } 109 } 110 111 func TestServiceConvertToGRPCContainerRuntime(t *testing.T) { 112 image := "alpine:latest" 113 s := swarmtypes.ServiceSpec{ 114 TaskTemplate: swarmtypes.TaskSpec{ 115 ContainerSpec: &swarmtypes.ContainerSpec{ 116 Image: image, 117 }, 118 }, 119 Mode: swarmtypes.ServiceMode{ 120 Global: &swarmtypes.GlobalService{}, 121 }, 122 } 123 124 svc, err := ServiceSpecToGRPC(s) 125 if err != nil { 126 t.Fatal(err) 127 } 128 129 v, ok := svc.Task.Runtime.(*swarmapi.TaskSpec_Container) 130 if !ok { 131 t.Fatal("expected type swarmapi.TaskSpec_Container") 132 } 133 134 if v.Container.Image != image { 135 t.Fatalf("expected image %s; received %s", image, v.Container.Image) 136 } 137 } 138 139 func TestServiceConvertToGRPCGenericRuntimeCustom(t *testing.T) { 140 s := swarmtypes.ServiceSpec{ 141 TaskTemplate: swarmtypes.TaskSpec{ 142 Runtime: "customruntime", 143 }, 144 Mode: swarmtypes.ServiceMode{ 145 Global: &swarmtypes.GlobalService{}, 146 }, 147 } 148 149 if _, err := ServiceSpecToGRPC(s); err != ErrUnsupportedRuntime { 150 t.Fatal(err) 151 } 152 } 153 154 func TestServiceConvertToGRPCIsolation(t *testing.T) { 155 cases := []struct { 156 name string 157 from containertypes.Isolation 158 to swarmapi.ContainerSpec_Isolation 159 }{ 160 {name: "empty", from: containertypes.IsolationEmpty, to: swarmapi.ContainerIsolationDefault}, 161 {name: "default", from: containertypes.IsolationDefault, to: swarmapi.ContainerIsolationDefault}, 162 {name: "process", from: containertypes.IsolationProcess, to: swarmapi.ContainerIsolationProcess}, 163 {name: "hyperv", from: containertypes.IsolationHyperV, to: swarmapi.ContainerIsolationHyperV}, 164 {name: "proCess", from: containertypes.Isolation("proCess"), to: swarmapi.ContainerIsolationProcess}, 165 {name: "hypErv", from: containertypes.Isolation("hypErv"), to: swarmapi.ContainerIsolationHyperV}, 166 } 167 for _, c := range cases { 168 t.Run(c.name, func(t *testing.T) { 169 s := swarmtypes.ServiceSpec{ 170 TaskTemplate: swarmtypes.TaskSpec{ 171 ContainerSpec: &swarmtypes.ContainerSpec{ 172 Image: "alpine:latest", 173 Isolation: c.from, 174 }, 175 }, 176 Mode: swarmtypes.ServiceMode{ 177 Global: &swarmtypes.GlobalService{}, 178 }, 179 } 180 res, err := ServiceSpecToGRPC(s) 181 assert.NilError(t, err) 182 v, ok := res.Task.Runtime.(*swarmapi.TaskSpec_Container) 183 if !ok { 184 t.Fatal("expected type swarmapi.TaskSpec_Container") 185 } 186 assert.Equal(t, c.to, v.Container.Isolation) 187 }) 188 } 189 } 190 191 func TestServiceConvertFromGRPCIsolation(t *testing.T) { 192 cases := []struct { 193 name string 194 from swarmapi.ContainerSpec_Isolation 195 to containertypes.Isolation 196 }{ 197 {name: "default", to: containertypes.IsolationDefault, from: swarmapi.ContainerIsolationDefault}, 198 {name: "process", to: containertypes.IsolationProcess, from: swarmapi.ContainerIsolationProcess}, 199 {name: "hyperv", to: containertypes.IsolationHyperV, from: swarmapi.ContainerIsolationHyperV}, 200 } 201 for _, c := range cases { 202 t.Run(c.name, func(t *testing.T) { 203 gs := swarmapi.Service{ 204 Meta: swarmapi.Meta{ 205 Version: swarmapi.Version{ 206 Index: 1, 207 }, 208 CreatedAt: nil, 209 UpdatedAt: nil, 210 }, 211 SpecVersion: &swarmapi.Version{ 212 Index: 1, 213 }, 214 Spec: swarmapi.ServiceSpec{ 215 Task: swarmapi.TaskSpec{ 216 Runtime: &swarmapi.TaskSpec_Container{ 217 Container: &swarmapi.ContainerSpec{ 218 Image: "alpine:latest", 219 Isolation: c.from, 220 }, 221 }, 222 }, 223 }, 224 } 225 226 svc, err := ServiceFromGRPC(gs) 227 if err != nil { 228 t.Fatal(err) 229 } 230 231 assert.Equal(t, c.to, svc.Spec.TaskTemplate.ContainerSpec.Isolation) 232 }) 233 } 234 } 235 236 func TestServiceConvertToGRPCCredentialSpec(t *testing.T) { 237 cases := []struct { 238 name string 239 from swarmtypes.CredentialSpec 240 to swarmapi.Privileges_CredentialSpec 241 expectedErr string 242 }{ 243 { 244 name: "empty credential spec", 245 from: swarmtypes.CredentialSpec{}, 246 to: swarmapi.Privileges_CredentialSpec{}, 247 expectedErr: `invalid CredentialSpec: must either provide "file", "registry", or "config" for credential spec`, 248 }, 249 { 250 name: "config and file credential spec", 251 from: swarmtypes.CredentialSpec{ 252 Config: "0bt9dmxjvjiqermk6xrop3ekq", 253 File: "spec.json", 254 }, 255 to: swarmapi.Privileges_CredentialSpec{}, 256 expectedErr: `invalid CredentialSpec: cannot specify both "config" and "file" credential specs`, 257 }, 258 { 259 name: "config and registry credential spec", 260 from: swarmtypes.CredentialSpec{ 261 Config: "0bt9dmxjvjiqermk6xrop3ekq", 262 Registry: "testing", 263 }, 264 to: swarmapi.Privileges_CredentialSpec{}, 265 expectedErr: `invalid CredentialSpec: cannot specify both "config" and "registry" credential specs`, 266 }, 267 { 268 name: "file and registry credential spec", 269 from: swarmtypes.CredentialSpec{ 270 File: "spec.json", 271 Registry: "testing", 272 }, 273 to: swarmapi.Privileges_CredentialSpec{}, 274 expectedErr: `invalid CredentialSpec: cannot specify both "file" and "registry" credential specs`, 275 }, 276 { 277 name: "config and file and registry credential spec", 278 from: swarmtypes.CredentialSpec{ 279 Config: "0bt9dmxjvjiqermk6xrop3ekq", 280 File: "spec.json", 281 Registry: "testing", 282 }, 283 to: swarmapi.Privileges_CredentialSpec{}, 284 expectedErr: `invalid CredentialSpec: cannot specify both "config", "file", and "registry" credential specs`, 285 }, 286 { 287 name: "config credential spec", 288 from: swarmtypes.CredentialSpec{Config: "0bt9dmxjvjiqermk6xrop3ekq"}, 289 to: swarmapi.Privileges_CredentialSpec{ 290 Source: &swarmapi.Privileges_CredentialSpec_Config{Config: "0bt9dmxjvjiqermk6xrop3ekq"}, 291 }, 292 }, 293 { 294 name: "file credential spec", 295 from: swarmtypes.CredentialSpec{File: "foo.json"}, 296 to: swarmapi.Privileges_CredentialSpec{ 297 Source: &swarmapi.Privileges_CredentialSpec_File{File: "foo.json"}, 298 }, 299 }, 300 { 301 name: "registry credential spec", 302 from: swarmtypes.CredentialSpec{Registry: "testing"}, 303 to: swarmapi.Privileges_CredentialSpec{ 304 Source: &swarmapi.Privileges_CredentialSpec_Registry{Registry: "testing"}, 305 }, 306 }, 307 } 308 309 for _, c := range cases { 310 c := c 311 t.Run(c.name, func(t *testing.T) { 312 s := swarmtypes.ServiceSpec{ 313 TaskTemplate: swarmtypes.TaskSpec{ 314 ContainerSpec: &swarmtypes.ContainerSpec{ 315 Privileges: &swarmtypes.Privileges{ 316 CredentialSpec: &c.from, 317 }, 318 }, 319 }, 320 } 321 322 res, err := ServiceSpecToGRPC(s) 323 if c.expectedErr != "" { 324 assert.Error(t, err, c.expectedErr) 325 return 326 } 327 328 assert.NilError(t, err) 329 v, ok := res.Task.Runtime.(*swarmapi.TaskSpec_Container) 330 if !ok { 331 t.Fatal("expected type swarmapi.TaskSpec_Container") 332 } 333 assert.DeepEqual(t, c.to, *v.Container.Privileges.CredentialSpec) 334 }) 335 } 336 } 337 338 func TestServiceConvertFromGRPCCredentialSpec(t *testing.T) { 339 cases := []struct { 340 name string 341 from swarmapi.Privileges_CredentialSpec 342 to *swarmtypes.CredentialSpec 343 }{ 344 { 345 name: "empty credential spec", 346 from: swarmapi.Privileges_CredentialSpec{}, 347 to: &swarmtypes.CredentialSpec{}, 348 }, 349 { 350 name: "config credential spec", 351 from: swarmapi.Privileges_CredentialSpec{ 352 Source: &swarmapi.Privileges_CredentialSpec_Config{Config: "0bt9dmxjvjiqermk6xrop3ekq"}, 353 }, 354 to: &swarmtypes.CredentialSpec{Config: "0bt9dmxjvjiqermk6xrop3ekq"}, 355 }, 356 { 357 name: "file credential spec", 358 from: swarmapi.Privileges_CredentialSpec{ 359 Source: &swarmapi.Privileges_CredentialSpec_File{File: "foo.json"}, 360 }, 361 to: &swarmtypes.CredentialSpec{File: "foo.json"}, 362 }, 363 { 364 name: "registry credential spec", 365 from: swarmapi.Privileges_CredentialSpec{ 366 Source: &swarmapi.Privileges_CredentialSpec_Registry{Registry: "testing"}, 367 }, 368 to: &swarmtypes.CredentialSpec{Registry: "testing"}, 369 }, 370 } 371 372 for _, tc := range cases { 373 tc := tc 374 375 t.Run(tc.name, func(t *testing.T) { 376 gs := swarmapi.Service{ 377 Spec: swarmapi.ServiceSpec{ 378 Task: swarmapi.TaskSpec{ 379 Runtime: &swarmapi.TaskSpec_Container{ 380 Container: &swarmapi.ContainerSpec{ 381 Privileges: &swarmapi.Privileges{ 382 CredentialSpec: &tc.from, 383 }, 384 }, 385 }, 386 }, 387 }, 388 } 389 390 svc, err := ServiceFromGRPC(gs) 391 assert.NilError(t, err) 392 assert.DeepEqual(t, svc.Spec.TaskTemplate.ContainerSpec.Privileges.CredentialSpec, tc.to) 393 }) 394 } 395 } 396 397 func TestServiceConvertToGRPCNetworkAtachmentRuntime(t *testing.T) { 398 someid := "asfjkl" 399 s := swarmtypes.ServiceSpec{ 400 TaskTemplate: swarmtypes.TaskSpec{ 401 Runtime: swarmtypes.RuntimeNetworkAttachment, 402 NetworkAttachmentSpec: &swarmtypes.NetworkAttachmentSpec{ 403 ContainerID: someid, 404 }, 405 }, 406 } 407 408 // discard the service, which will be empty 409 _, err := ServiceSpecToGRPC(s) 410 if err == nil { 411 t.Fatalf("expected error %v but got no error", ErrUnsupportedRuntime) 412 } 413 if err != ErrUnsupportedRuntime { 414 t.Fatalf("expected error %v but got error %v", ErrUnsupportedRuntime, err) 415 } 416 } 417 418 func TestServiceConvertToGRPCMismatchedRuntime(t *testing.T) { 419 // NOTE(dperny): an earlier version of this test was for code that also 420 // converted network attachment tasks to GRPC. that conversion code was 421 // removed, so if this loop body seems a bit complicated, that's why. 422 for i, rt := range []swarmtypes.RuntimeType{ 423 swarmtypes.RuntimeContainer, 424 swarmtypes.RuntimePlugin, 425 } { 426 for j, spec := range []swarmtypes.TaskSpec{ 427 {ContainerSpec: &swarmtypes.ContainerSpec{}}, 428 {PluginSpec: &runtime.PluginSpec{}}, 429 } { 430 // skip the cases, where the indices match, which would not error 431 if i == j { 432 continue 433 } 434 // set the task spec, then change the runtime 435 s := swarmtypes.ServiceSpec{ 436 TaskTemplate: spec, 437 } 438 s.TaskTemplate.Runtime = rt 439 440 if _, err := ServiceSpecToGRPC(s); err != ErrMismatchedRuntime { 441 t.Fatalf("expected %v got %v", ErrMismatchedRuntime, err) 442 } 443 } 444 } 445 } 446 447 func TestTaskConvertFromGRPCNetworkAttachment(t *testing.T) { 448 containerID := "asdfjkl" 449 s := swarmapi.TaskSpec{ 450 Runtime: &swarmapi.TaskSpec_Attachment{ 451 Attachment: &swarmapi.NetworkAttachmentSpec{ 452 ContainerID: containerID, 453 }, 454 }, 455 } 456 ts, err := taskSpecFromGRPC(s) 457 if err != nil { 458 t.Fatal(err) 459 } 460 if ts.NetworkAttachmentSpec == nil { 461 t.Fatal("expected task spec to have network attachment spec") 462 } 463 if ts.NetworkAttachmentSpec.ContainerID != containerID { 464 t.Fatalf("expected network attachment spec container id to be %q, was %q", containerID, ts.NetworkAttachmentSpec.ContainerID) 465 } 466 if ts.Runtime != swarmtypes.RuntimeNetworkAttachment { 467 t.Fatalf("expected Runtime to be %v", swarmtypes.RuntimeNetworkAttachment) 468 } 469 } 470 471 // TestServiceConvertFromGRPCConfigs tests that converting config references 472 // from GRPC is correct 473 func TestServiceConvertFromGRPCConfigs(t *testing.T) { 474 cases := []struct { 475 name string 476 from *swarmapi.ConfigReference 477 to *swarmtypes.ConfigReference 478 }{ 479 { 480 name: "file", 481 from: &swarmapi.ConfigReference{ 482 ConfigID: "configFile", 483 ConfigName: "configFile", 484 Target: &swarmapi.ConfigReference_File{ 485 // skip mode, if everything else here works mode will too. otherwise we'd need to import os. 486 File: &swarmapi.FileTarget{Name: "foo", UID: "bar", GID: "baz"}, 487 }, 488 }, 489 to: &swarmtypes.ConfigReference{ 490 ConfigID: "configFile", 491 ConfigName: "configFile", 492 File: &swarmtypes.ConfigReferenceFileTarget{Name: "foo", UID: "bar", GID: "baz"}, 493 }, 494 }, 495 { 496 name: "runtime", 497 from: &swarmapi.ConfigReference{ 498 ConfigID: "configRuntime", 499 ConfigName: "configRuntime", 500 Target: &swarmapi.ConfigReference_Runtime{Runtime: &swarmapi.RuntimeTarget{}}, 501 }, 502 to: &swarmtypes.ConfigReference{ 503 ConfigID: "configRuntime", 504 ConfigName: "configRuntime", 505 Runtime: &swarmtypes.ConfigReferenceRuntimeTarget{}, 506 }, 507 }, 508 } 509 510 for _, tc := range cases { 511 t.Run(tc.name, func(t *testing.T) { 512 grpcService := swarmapi.Service{ 513 Spec: swarmapi.ServiceSpec{ 514 Task: swarmapi.TaskSpec{ 515 Runtime: &swarmapi.TaskSpec_Container{ 516 Container: &swarmapi.ContainerSpec{ 517 Configs: []*swarmapi.ConfigReference{tc.from}, 518 }, 519 }, 520 }, 521 }, 522 } 523 524 engineService, err := ServiceFromGRPC(grpcService) 525 assert.NilError(t, err) 526 assert.DeepEqual(t, 527 engineService.Spec.TaskTemplate.ContainerSpec.Configs[0], 528 tc.to, 529 ) 530 }) 531 } 532 } 533 534 // TestServiceConvertToGRPCConfigs tests that converting config references to 535 // GRPC is correct 536 func TestServiceConvertToGRPCConfigs(t *testing.T) { 537 cases := []struct { 538 name string 539 from *swarmtypes.ConfigReference 540 to *swarmapi.ConfigReference 541 expectedErr string 542 }{ 543 { 544 name: "file", 545 from: &swarmtypes.ConfigReference{ 546 ConfigID: "configFile", 547 ConfigName: "configFile", 548 File: &swarmtypes.ConfigReferenceFileTarget{Name: "foo", UID: "bar", GID: "baz"}, 549 }, 550 to: &swarmapi.ConfigReference{ 551 ConfigID: "configFile", 552 ConfigName: "configFile", 553 Target: &swarmapi.ConfigReference_File{ 554 // skip mode, if everything else here works mode will too. otherwise we'd need to import os. 555 File: &swarmapi.FileTarget{Name: "foo", UID: "bar", GID: "baz"}, 556 }, 557 }, 558 }, 559 { 560 name: "runtime", 561 from: &swarmtypes.ConfigReference{ 562 ConfigID: "configRuntime", 563 ConfigName: "configRuntime", 564 Runtime: &swarmtypes.ConfigReferenceRuntimeTarget{}, 565 }, 566 to: &swarmapi.ConfigReference{ 567 ConfigID: "configRuntime", 568 ConfigName: "configRuntime", 569 Target: &swarmapi.ConfigReference_Runtime{Runtime: &swarmapi.RuntimeTarget{}}, 570 }, 571 }, 572 { 573 name: "file and runtime", 574 from: &swarmtypes.ConfigReference{ 575 ConfigID: "fileAndRuntime", 576 ConfigName: "fileAndRuntime", 577 File: &swarmtypes.ConfigReferenceFileTarget{}, 578 Runtime: &swarmtypes.ConfigReferenceRuntimeTarget{}, 579 }, 580 expectedErr: "invalid Config: cannot specify both File and Runtime", 581 }, 582 { 583 name: "none", 584 from: &swarmtypes.ConfigReference{ 585 ConfigID: "none", 586 ConfigName: "none", 587 }, 588 expectedErr: "invalid Config: either File or Runtime should be set", 589 }, 590 } 591 592 for _, tc := range cases { 593 t.Run(tc.name, func(t *testing.T) { 594 engineServiceSpec := swarmtypes.ServiceSpec{ 595 TaskTemplate: swarmtypes.TaskSpec{ 596 ContainerSpec: &swarmtypes.ContainerSpec{ 597 Configs: []*swarmtypes.ConfigReference{tc.from}, 598 }, 599 }, 600 } 601 602 grpcServiceSpec, err := ServiceSpecToGRPC(engineServiceSpec) 603 if tc.expectedErr != "" { 604 assert.Error(t, err, tc.expectedErr) 605 return 606 } 607 608 assert.NilError(t, err) 609 taskRuntime := grpcServiceSpec.Task.Runtime.(*swarmapi.TaskSpec_Container) 610 assert.DeepEqual(t, taskRuntime.Container.Configs[0], tc.to) 611 }) 612 } 613 }