Output network attachment task information

Adds functionality to parse and return network attachment spec
information. Network attachment tasks are phony tasks created in
swarmkit to deal with unmanaged containers attached to swarmkit. Before
this change, attempting `docker inspect` on the task id of a network
attachment task would result in an empty task object. After this change,
a full task object is returned

Fixes #26548 the correct way.

Signed-off-by: Drew Erny <drew.erny@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Drew Erny 2017-10-18 14:41:59 -07:00 committed by Sebastiaan van Stijn
parent bcaf891369
commit 5b69ff466e
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
7 changed files with 132 additions and 13 deletions

View File

@ -2688,6 +2688,13 @@ definitions:
- "default"
- "process"
- "hyperv"
NetworkAttachmentSpec:
description: "Read-only spec type for non-swarm containers attached to swarm overlay networks"
type: "object"
properties:
ContainerID:
description: "ID of the container represented by this task"
type: "string"
Resources:
description: "Resource requirements which apply to each individual container created as part of the service."
type: "object"

View File

@ -11,9 +11,17 @@ const (
RuntimeContainer RuntimeType = "container"
// RuntimePlugin is the plugin based runtime
RuntimePlugin RuntimeType = "plugin"
// RuntimeNetworkAttachment is the network attachment runtime
RuntimeNetworkAttachment RuntimeType = "attachment"
// RuntimeURLContainer is the proto url for the container type
RuntimeURLContainer RuntimeURL = "types.docker.com/RuntimeContainer"
// RuntimeURLPlugin is the proto url for the plugin type
RuntimeURLPlugin RuntimeURL = "types.docker.com/RuntimePlugin"
)
// NetworkAttachmentSpec represents the runtime spec type for network
// attachment tasks
type NetworkAttachmentSpec struct {
ContainerID string
}

View File

@ -60,10 +60,13 @@ type Task struct {
// TaskSpec represents the spec of a task.
type TaskSpec struct {
// ContainerSpec and PluginSpec are mutually exclusive.
// PluginSpec will only be used when the `Runtime` field is set to `plugin`
ContainerSpec *ContainerSpec `json:",omitempty"`
PluginSpec *runtime.PluginSpec `json:",omitempty"`
// ContainerSpec, NetworkAttachmentSpec, and PluginSpec are mutually exclusive.
// PluginSpec is only used when the `Runtime` field is set to `plugin`
// NetworkAttachmentSpec is used if the `Runtime` field is set to
// `attachment`.
ContainerSpec *ContainerSpec `json:",omitempty"`
PluginSpec *runtime.PluginSpec `json:",omitempty"`
NetworkAttachmentSpec *NetworkAttachmentSpec `json:",omitempty"`
Resources *ResourceRequirements `json:",omitempty"`
RestartPolicy *RestartPolicy `json:",omitempty"`

View File

@ -17,6 +17,8 @@ import (
var (
// ErrUnsupportedRuntime returns an error if the runtime is not supported by the daemon
ErrUnsupportedRuntime = errors.New("unsupported runtime")
// ErrMismatchedRuntime returns an error if the runtime does not match the provided spec
ErrMismatchedRuntime = errors.New("mismatched Runtime and *Spec fields")
)
// ServiceFromGRPC converts a grpc Service to a Service.
@ -176,15 +178,18 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
return swarmapi.ServiceSpec{}, err
}
spec.Task.Runtime = &swarmapi.TaskSpec_Container{Container: containerSpec}
} else {
// If the ContainerSpec is nil, we can't set the task runtime
return swarmapi.ServiceSpec{}, ErrMismatchedRuntime
}
case types.RuntimePlugin:
if s.Mode.Replicated != nil {
return swarmapi.ServiceSpec{}, errors.New("plugins must not use replicated mode")
}
s.Mode.Global = &types.GlobalService{} // must always be global
if s.TaskTemplate.PluginSpec != nil {
if s.Mode.Replicated != nil {
return swarmapi.ServiceSpec{}, errors.New("plugins must not use replicated mode")
}
s.Mode.Global = &types.GlobalService{} // must always be global
pluginSpec, err := proto.Marshal(s.TaskTemplate.PluginSpec)
if err != nil {
return swarmapi.ServiceSpec{}, err
@ -198,7 +203,16 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
},
},
}
} else {
return swarmapi.ServiceSpec{}, ErrMismatchedRuntime
}
case types.RuntimeNetworkAttachment:
// NOTE(dperny) I'm leaving this case here for completeness. The actual
// code is left out out deliberately, as we should refuse to parse a
// Network Attachment runtime; it will cause weird behavior all over
// the system if we do. Instead, fallthrough and return
// ErrUnsupportedRuntime if we get one.
fallthrough
default:
return swarmapi.ServiceSpec{}, ErrUnsupportedRuntime
}
@ -573,6 +587,12 @@ func updateConfigToGRPC(updateConfig *types.UpdateConfig) (*swarmapi.UpdateConfi
return converted, nil
}
func networkAttachmentSpecFromGRPC(attachment swarmapi.NetworkAttachmentSpec) *types.NetworkAttachmentSpec {
return &types.NetworkAttachmentSpec{
ContainerID: attachment.ContainerID,
}
}
func taskSpecFromGRPC(taskSpec swarmapi.TaskSpec) (types.TaskSpec, error) {
taskNetworks := make([]types.NetworkAttachmentConfig, 0, len(taskSpec.Networks))
for _, n := range taskSpec.Networks {
@ -607,6 +627,12 @@ func taskSpecFromGRPC(taskSpec swarmapi.TaskSpec) (types.TaskSpec, error) {
t.PluginSpec = &p
}
}
case *swarmapi.TaskSpec_Attachment:
a := taskSpec.GetAttachment()
if a != nil {
t.NetworkAttachmentSpec = networkAttachmentSpecFromGRPC(*a)
}
t.Runtime = types.RuntimeNetworkAttachment
}
return t, nil

View File

@ -232,3 +232,77 @@ func TestServiceConvertFromGRPCIsolation(t *testing.T) {
})
}
}
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)
}
}

View File

@ -10,9 +10,6 @@ import (
// TaskFromGRPC converts a grpc Task to a Task.
func TaskFromGRPC(t swarmapi.Task) (types.Task, error) {
if t.Spec.GetAttachment() != nil {
return types.Task{}, nil
}
containerStatus := t.Status.GetContainer()
taskSpec, err := taskSpecFromGRPC(t.Spec)
if err != nil {

View File

@ -135,6 +135,8 @@ func (c *Cluster) CreateService(s types.ServiceSpec, encodedAuth string, queryRe
resp = &apitypes.ServiceCreateResponse{}
switch serviceSpec.Task.Runtime.(type) {
case *swarmapi.TaskSpec_Attachment:
return fmt.Errorf("invalid task spec: spec type %q not supported", types.RuntimeNetworkAttachment)
// handle other runtimes here
case *swarmapi.TaskSpec_Generic:
switch serviceSpec.Task.GetGeneric().Kind {
@ -244,6 +246,8 @@ func (c *Cluster) UpdateService(serviceIDOrName string, version uint64, spec typ
resp = &apitypes.ServiceUpdateResponse{}
switch serviceSpec.Task.Runtime.(type) {
case *swarmapi.TaskSpec_Attachment:
return fmt.Errorf("invalid task spec: spec type %q not supported", types.RuntimeNetworkAttachment)
case *swarmapi.TaskSpec_Generic:
switch serviceSpec.Task.GetGeneric().Kind {
case string(types.RuntimePlugin):