1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00
moby--moby/daemon/cluster/convert/service_test.go
Drew Erny 6f1d7ddfa4 Use Runtime target
The Swarmkit api specifies a target for configs called called "Runtime"
which indicates that the config is not mounted into the container but
has some other use. This commit updates the Docker api to reflect this.

Signed-off-by: Drew Erny <drew.erny@docker.com>
2019-02-19 13:14:17 -06:00

613 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/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)
})
}
}