moby--moby/daemon/cluster/convert/service_test.go

614 lines
17 KiB
Go

package convert // import "github.com/docker/docker/daemon/cluster/convert"
import (
"testing"
containertypes "github.com/docker/docker/api/types/container"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/swarm/runtime"
swarmapi "github.com/docker/swarmkit/api"
google_protobuf3 "github.com/gogo/protobuf/types"
"gotest.tools/v3/assert"
)
func TestServiceConvertFromGRPCRuntimeContainer(t *testing.T) {
gs := swarmapi.Service{
Meta: swarmapi.Meta{
Version: swarmapi.Version{
Index: 1,
},
CreatedAt: nil,
UpdatedAt: nil,
},
SpecVersion: &swarmapi.Version{
Index: 1,
},
Spec: swarmapi.ServiceSpec{
Task: swarmapi.TaskSpec{
Runtime: &swarmapi.TaskSpec_Container{
Container: &swarmapi.ContainerSpec{
Image: "alpine:latest",
},
},
},
},
}
svc, err := ServiceFromGRPC(gs)
if err != nil {
t.Fatal(err)
}
if svc.Spec.TaskTemplate.Runtime != swarmtypes.RuntimeContainer {
t.Fatalf("expected type %s; received %T", swarmtypes.RuntimeContainer, svc.Spec.TaskTemplate.Runtime)
}
}
func TestServiceConvertFromGRPCGenericRuntimePlugin(t *testing.T) {
kind := string(swarmtypes.RuntimePlugin)
url := swarmtypes.RuntimeURLPlugin
gs := swarmapi.Service{
Meta: swarmapi.Meta{
Version: swarmapi.Version{
Index: 1,
},
CreatedAt: nil,
UpdatedAt: nil,
},
SpecVersion: &swarmapi.Version{
Index: 1,
},
Spec: swarmapi.ServiceSpec{
Task: swarmapi.TaskSpec{
Runtime: &swarmapi.TaskSpec_Generic{
Generic: &swarmapi.GenericRuntimeSpec{
Kind: kind,
Payload: &google_protobuf3.Any{
TypeUrl: string(url),
},
},
},
},
},
}
svc, err := ServiceFromGRPC(gs)
if err != nil {
t.Fatal(err)
}
if svc.Spec.TaskTemplate.Runtime != swarmtypes.RuntimePlugin {
t.Fatalf("expected type %s; received %T", swarmtypes.RuntimePlugin, svc.Spec.TaskTemplate.Runtime)
}
}
func TestServiceConvertToGRPCGenericRuntimePlugin(t *testing.T) {
s := swarmtypes.ServiceSpec{
TaskTemplate: swarmtypes.TaskSpec{
Runtime: swarmtypes.RuntimePlugin,
PluginSpec: &runtime.PluginSpec{},
},
Mode: swarmtypes.ServiceMode{
Global: &swarmtypes.GlobalService{},
},
}
svc, err := ServiceSpecToGRPC(s)
if err != nil {
t.Fatal(err)
}
v, ok := svc.Task.Runtime.(*swarmapi.TaskSpec_Generic)
if !ok {
t.Fatal("expected type swarmapi.TaskSpec_Generic")
}
if v.Generic.Payload.TypeUrl != string(swarmtypes.RuntimeURLPlugin) {
t.Fatalf("expected url %s; received %s", swarmtypes.RuntimeURLPlugin, v.Generic.Payload.TypeUrl)
}
}
func TestServiceConvertToGRPCContainerRuntime(t *testing.T) {
image := "alpine:latest"
s := swarmtypes.ServiceSpec{
TaskTemplate: swarmtypes.TaskSpec{
ContainerSpec: &swarmtypes.ContainerSpec{
Image: image,
},
},
Mode: swarmtypes.ServiceMode{
Global: &swarmtypes.GlobalService{},
},
}
svc, err := ServiceSpecToGRPC(s)
if err != nil {
t.Fatal(err)
}
v, ok := svc.Task.Runtime.(*swarmapi.TaskSpec_Container)
if !ok {
t.Fatal("expected type swarmapi.TaskSpec_Container")
}
if v.Container.Image != image {
t.Fatalf("expected image %s; received %s", image, v.Container.Image)
}
}
func TestServiceConvertToGRPCGenericRuntimeCustom(t *testing.T) {
s := swarmtypes.ServiceSpec{
TaskTemplate: swarmtypes.TaskSpec{
Runtime: "customruntime",
},
Mode: swarmtypes.ServiceMode{
Global: &swarmtypes.GlobalService{},
},
}
if _, err := ServiceSpecToGRPC(s); err != ErrUnsupportedRuntime {
t.Fatal(err)
}
}
func TestServiceConvertToGRPCIsolation(t *testing.T) {
cases := []struct {
name string
from containertypes.Isolation
to swarmapi.ContainerSpec_Isolation
}{
{name: "empty", from: containertypes.IsolationEmpty, to: swarmapi.ContainerIsolationDefault},
{name: "default", from: containertypes.IsolationDefault, to: swarmapi.ContainerIsolationDefault},
{name: "process", from: containertypes.IsolationProcess, to: swarmapi.ContainerIsolationProcess},
{name: "hyperv", from: containertypes.IsolationHyperV, to: swarmapi.ContainerIsolationHyperV},
{name: "proCess", from: containertypes.Isolation("proCess"), to: swarmapi.ContainerIsolationProcess},
{name: "hypErv", from: containertypes.Isolation("hypErv"), to: swarmapi.ContainerIsolationHyperV},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
s := swarmtypes.ServiceSpec{
TaskTemplate: swarmtypes.TaskSpec{
ContainerSpec: &swarmtypes.ContainerSpec{
Image: "alpine:latest",
Isolation: c.from,
},
},
Mode: swarmtypes.ServiceMode{
Global: &swarmtypes.GlobalService{},
},
}
res, err := ServiceSpecToGRPC(s)
assert.NilError(t, err)
v, ok := res.Task.Runtime.(*swarmapi.TaskSpec_Container)
if !ok {
t.Fatal("expected type swarmapi.TaskSpec_Container")
}
assert.Equal(t, c.to, v.Container.Isolation)
})
}
}
func TestServiceConvertFromGRPCIsolation(t *testing.T) {
cases := []struct {
name string
from swarmapi.ContainerSpec_Isolation
to containertypes.Isolation
}{
{name: "default", to: containertypes.IsolationDefault, from: swarmapi.ContainerIsolationDefault},
{name: "process", to: containertypes.IsolationProcess, from: swarmapi.ContainerIsolationProcess},
{name: "hyperv", to: containertypes.IsolationHyperV, from: swarmapi.ContainerIsolationHyperV},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
gs := swarmapi.Service{
Meta: swarmapi.Meta{
Version: swarmapi.Version{
Index: 1,
},
CreatedAt: nil,
UpdatedAt: nil,
},
SpecVersion: &swarmapi.Version{
Index: 1,
},
Spec: swarmapi.ServiceSpec{
Task: swarmapi.TaskSpec{
Runtime: &swarmapi.TaskSpec_Container{
Container: &swarmapi.ContainerSpec{
Image: "alpine:latest",
Isolation: c.from,
},
},
},
},
}
svc, err := ServiceFromGRPC(gs)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, c.to, svc.Spec.TaskTemplate.ContainerSpec.Isolation)
})
}
}
func TestServiceConvertToGRPCCredentialSpec(t *testing.T) {
cases := []struct {
name string
from swarmtypes.CredentialSpec
to swarmapi.Privileges_CredentialSpec
expectedErr string
}{
{
name: "empty credential spec",
from: swarmtypes.CredentialSpec{},
to: swarmapi.Privileges_CredentialSpec{},
expectedErr: `invalid CredentialSpec: must either provide "file", "registry", or "config" for credential spec`,
},
{
name: "config and file credential spec",
from: swarmtypes.CredentialSpec{
Config: "0bt9dmxjvjiqermk6xrop3ekq",
File: "spec.json",
},
to: swarmapi.Privileges_CredentialSpec{},
expectedErr: `invalid CredentialSpec: cannot specify both "config" and "file" credential specs`,
},
{
name: "config and registry credential spec",
from: swarmtypes.CredentialSpec{
Config: "0bt9dmxjvjiqermk6xrop3ekq",
Registry: "testing",
},
to: swarmapi.Privileges_CredentialSpec{},
expectedErr: `invalid CredentialSpec: cannot specify both "config" and "registry" credential specs`,
},
{
name: "file and registry credential spec",
from: swarmtypes.CredentialSpec{
File: "spec.json",
Registry: "testing",
},
to: swarmapi.Privileges_CredentialSpec{},
expectedErr: `invalid CredentialSpec: cannot specify both "file" and "registry" credential specs`,
},
{
name: "config and file and registry credential spec",
from: swarmtypes.CredentialSpec{
Config: "0bt9dmxjvjiqermk6xrop3ekq",
File: "spec.json",
Registry: "testing",
},
to: swarmapi.Privileges_CredentialSpec{},
expectedErr: `invalid CredentialSpec: cannot specify both "config", "file", and "registry" credential specs`,
},
{
name: "config credential spec",
from: swarmtypes.CredentialSpec{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
to: swarmapi.Privileges_CredentialSpec{
Source: &swarmapi.Privileges_CredentialSpec_Config{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
},
},
{
name: "file credential spec",
from: swarmtypes.CredentialSpec{File: "foo.json"},
to: swarmapi.Privileges_CredentialSpec{
Source: &swarmapi.Privileges_CredentialSpec_File{File: "foo.json"},
},
},
{
name: "registry credential spec",
from: swarmtypes.CredentialSpec{Registry: "testing"},
to: swarmapi.Privileges_CredentialSpec{
Source: &swarmapi.Privileges_CredentialSpec_Registry{Registry: "testing"},
},
},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
s := swarmtypes.ServiceSpec{
TaskTemplate: swarmtypes.TaskSpec{
ContainerSpec: &swarmtypes.ContainerSpec{
Privileges: &swarmtypes.Privileges{
CredentialSpec: &c.from,
},
},
},
}
res, err := ServiceSpecToGRPC(s)
if c.expectedErr != "" {
assert.Error(t, err, c.expectedErr)
return
}
assert.NilError(t, err)
v, ok := res.Task.Runtime.(*swarmapi.TaskSpec_Container)
if !ok {
t.Fatal("expected type swarmapi.TaskSpec_Container")
}
assert.DeepEqual(t, c.to, *v.Container.Privileges.CredentialSpec)
})
}
}
func TestServiceConvertFromGRPCCredentialSpec(t *testing.T) {
cases := []struct {
name string
from swarmapi.Privileges_CredentialSpec
to *swarmtypes.CredentialSpec
}{
{
name: "empty credential spec",
from: swarmapi.Privileges_CredentialSpec{},
to: &swarmtypes.CredentialSpec{},
},
{
name: "config credential spec",
from: swarmapi.Privileges_CredentialSpec{
Source: &swarmapi.Privileges_CredentialSpec_Config{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
},
to: &swarmtypes.CredentialSpec{Config: "0bt9dmxjvjiqermk6xrop3ekq"},
},
{
name: "file credential spec",
from: swarmapi.Privileges_CredentialSpec{
Source: &swarmapi.Privileges_CredentialSpec_File{File: "foo.json"},
},
to: &swarmtypes.CredentialSpec{File: "foo.json"},
},
{
name: "registry credential spec",
from: swarmapi.Privileges_CredentialSpec{
Source: &swarmapi.Privileges_CredentialSpec_Registry{Registry: "testing"},
},
to: &swarmtypes.CredentialSpec{Registry: "testing"},
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
gs := swarmapi.Service{
Spec: swarmapi.ServiceSpec{
Task: swarmapi.TaskSpec{
Runtime: &swarmapi.TaskSpec_Container{
Container: &swarmapi.ContainerSpec{
Privileges: &swarmapi.Privileges{
CredentialSpec: &tc.from,
},
},
},
},
},
}
svc, err := ServiceFromGRPC(gs)
assert.NilError(t, err)
assert.DeepEqual(t, svc.Spec.TaskTemplate.ContainerSpec.Privileges.CredentialSpec, tc.to)
})
}
}
func TestServiceConvertToGRPCNetworkAtachmentRuntime(t *testing.T) {
someid := "asfjkl"
s := swarmtypes.ServiceSpec{
TaskTemplate: swarmtypes.TaskSpec{
Runtime: swarmtypes.RuntimeNetworkAttachment,
NetworkAttachmentSpec: &swarmtypes.NetworkAttachmentSpec{
ContainerID: someid,
},
},
}
// discard the service, which will be empty
_, err := ServiceSpecToGRPC(s)
if err == nil {
t.Fatalf("expected error %v but got no error", ErrUnsupportedRuntime)
}
if err != ErrUnsupportedRuntime {
t.Fatalf("expected error %v but got error %v", ErrUnsupportedRuntime, err)
}
}
func TestServiceConvertToGRPCMismatchedRuntime(t *testing.T) {
// NOTE(dperny): an earlier version of this test was for code that also
// converted network attachment tasks to GRPC. that conversion code was
// removed, so if this loop body seems a bit complicated, that's why.
for i, rt := range []swarmtypes.RuntimeType{
swarmtypes.RuntimeContainer,
swarmtypes.RuntimePlugin,
} {
for j, spec := range []swarmtypes.TaskSpec{
{ContainerSpec: &swarmtypes.ContainerSpec{}},
{PluginSpec: &runtime.PluginSpec{}},
} {
// skip the cases, where the indices match, which would not error
if i == j {
continue
}
// set the task spec, then change the runtime
s := swarmtypes.ServiceSpec{
TaskTemplate: spec,
}
s.TaskTemplate.Runtime = rt
if _, err := ServiceSpecToGRPC(s); err != ErrMismatchedRuntime {
t.Fatalf("expected %v got %v", ErrMismatchedRuntime, err)
}
}
}
}
func TestTaskConvertFromGRPCNetworkAttachment(t *testing.T) {
containerID := "asdfjkl"
s := swarmapi.TaskSpec{
Runtime: &swarmapi.TaskSpec_Attachment{
Attachment: &swarmapi.NetworkAttachmentSpec{
ContainerID: containerID,
},
},
}
ts, err := taskSpecFromGRPC(s)
if err != nil {
t.Fatal(err)
}
if ts.NetworkAttachmentSpec == nil {
t.Fatal("expected task spec to have network attachment spec")
}
if ts.NetworkAttachmentSpec.ContainerID != containerID {
t.Fatalf("expected network attachment spec container id to be %q, was %q", containerID, ts.NetworkAttachmentSpec.ContainerID)
}
if ts.Runtime != swarmtypes.RuntimeNetworkAttachment {
t.Fatalf("expected Runtime to be %v", swarmtypes.RuntimeNetworkAttachment)
}
}
// TestServiceConvertFromGRPCConfigs tests that converting config references
// from GRPC is correct
func TestServiceConvertFromGRPCConfigs(t *testing.T) {
cases := []struct {
name string
from *swarmapi.ConfigReference
to *swarmtypes.ConfigReference
}{
{
name: "file",
from: &swarmapi.ConfigReference{
ConfigID: "configFile",
ConfigName: "configFile",
Target: &swarmapi.ConfigReference_File{
// skip mode, if everything else here works mode will too. otherwise we'd need to import os.
File: &swarmapi.FileTarget{Name: "foo", UID: "bar", GID: "baz"},
},
},
to: &swarmtypes.ConfigReference{
ConfigID: "configFile",
ConfigName: "configFile",
File: &swarmtypes.ConfigReferenceFileTarget{Name: "foo", UID: "bar", GID: "baz"},
},
},
{
name: "runtime",
from: &swarmapi.ConfigReference{
ConfigID: "configRuntime",
ConfigName: "configRuntime",
Target: &swarmapi.ConfigReference_Runtime{Runtime: &swarmapi.RuntimeTarget{}},
},
to: &swarmtypes.ConfigReference{
ConfigID: "configRuntime",
ConfigName: "configRuntime",
Runtime: &swarmtypes.ConfigReferenceRuntimeTarget{},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
grpcService := swarmapi.Service{
Spec: swarmapi.ServiceSpec{
Task: swarmapi.TaskSpec{
Runtime: &swarmapi.TaskSpec_Container{
Container: &swarmapi.ContainerSpec{
Configs: []*swarmapi.ConfigReference{tc.from},
},
},
},
},
}
engineService, err := ServiceFromGRPC(grpcService)
assert.NilError(t, err)
assert.DeepEqual(t,
engineService.Spec.TaskTemplate.ContainerSpec.Configs[0],
tc.to,
)
})
}
}
// TestServiceConvertToGRPCConfigs tests that converting config references to
// GRPC is correct
func TestServiceConvertToGRPCConfigs(t *testing.T) {
cases := []struct {
name string
from *swarmtypes.ConfigReference
to *swarmapi.ConfigReference
expectedErr string
}{
{
name: "file",
from: &swarmtypes.ConfigReference{
ConfigID: "configFile",
ConfigName: "configFile",
File: &swarmtypes.ConfigReferenceFileTarget{Name: "foo", UID: "bar", GID: "baz"},
},
to: &swarmapi.ConfigReference{
ConfigID: "configFile",
ConfigName: "configFile",
Target: &swarmapi.ConfigReference_File{
// skip mode, if everything else here works mode will too. otherwise we'd need to import os.
File: &swarmapi.FileTarget{Name: "foo", UID: "bar", GID: "baz"},
},
},
},
{
name: "runtime",
from: &swarmtypes.ConfigReference{
ConfigID: "configRuntime",
ConfigName: "configRuntime",
Runtime: &swarmtypes.ConfigReferenceRuntimeTarget{},
},
to: &swarmapi.ConfigReference{
ConfigID: "configRuntime",
ConfigName: "configRuntime",
Target: &swarmapi.ConfigReference_Runtime{Runtime: &swarmapi.RuntimeTarget{}},
},
},
{
name: "file and runtime",
from: &swarmtypes.ConfigReference{
ConfigID: "fileAndRuntime",
ConfigName: "fileAndRuntime",
File: &swarmtypes.ConfigReferenceFileTarget{},
Runtime: &swarmtypes.ConfigReferenceRuntimeTarget{},
},
expectedErr: "invalid Config: cannot specify both File and Runtime",
},
{
name: "none",
from: &swarmtypes.ConfigReference{
ConfigID: "none",
ConfigName: "none",
},
expectedErr: "invalid Config: either File or Runtime should be set",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
engineServiceSpec := swarmtypes.ServiceSpec{
TaskTemplate: swarmtypes.TaskSpec{
ContainerSpec: &swarmtypes.ContainerSpec{
Configs: []*swarmtypes.ConfigReference{tc.from},
},
},
}
grpcServiceSpec, err := ServiceSpecToGRPC(engineServiceSpec)
if tc.expectedErr != "" {
assert.Error(t, err, tc.expectedErr)
return
}
assert.NilError(t, err)
taskRuntime := grpcServiceSpec.Task.Runtime.(*swarmapi.TaskSpec_Container)
assert.DeepEqual(t, taskRuntime.Container.Configs[0], tc.to)
})
}
}