Browse code

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>

Drew Erny authored on 2017/10/19 06:41:59
Showing 7 changed files
... ...
@@ -2688,6 +2688,13 @@ definitions:
2688 2688
               - "default"
2689 2689
               - "process"
2690 2690
               - "hyperv"
2691
+      NetworkAttachmentSpec:
2692
+        description: "Read-only spec type for non-swarm containers attached to swarm overlay networks"
2693
+        type: "object"
2694
+        properties:
2695
+          ContainerID:
2696
+            description: "ID of the container represented by this task"
2697
+            type: "string"
2691 2698
       Resources:
2692 2699
         description: "Resource requirements which apply to each individual container created as part of the service."
2693 2700
         type: "object"
... ...
@@ -11,9 +11,17 @@ const (
11 11
 	RuntimeContainer RuntimeType = "container"
12 12
 	// RuntimePlugin is the plugin based runtime
13 13
 	RuntimePlugin RuntimeType = "plugin"
14
+	// RuntimeNetworkAttachment is the network attachment runtime
15
+	RuntimeNetworkAttachment RuntimeType = "attachment"
14 16
 
15 17
 	// RuntimeURLContainer is the proto url for the container type
16 18
 	RuntimeURLContainer RuntimeURL = "types.docker.com/RuntimeContainer"
17 19
 	// RuntimeURLPlugin is the proto url for the plugin type
18 20
 	RuntimeURLPlugin RuntimeURL = "types.docker.com/RuntimePlugin"
19 21
 )
22
+
23
+// NetworkAttachmentSpec represents the runtime spec type for network
24
+// attachment tasks
25
+type NetworkAttachmentSpec struct {
26
+	ContainerID string
27
+}
... ...
@@ -60,10 +60,13 @@ type Task struct {
60 60
 
61 61
 // TaskSpec represents the spec of a task.
62 62
 type TaskSpec struct {
63
-	// ContainerSpec and PluginSpec are mutually exclusive.
64
-	// PluginSpec will only be used when the `Runtime` field is set to `plugin`
65
-	ContainerSpec *ContainerSpec      `json:",omitempty"`
66
-	PluginSpec    *runtime.PluginSpec `json:",omitempty"`
63
+	// ContainerSpec, NetworkAttachmentSpec, and PluginSpec are mutually exclusive.
64
+	// PluginSpec is only used when the `Runtime` field is set to `plugin`
65
+	// NetworkAttachmentSpec is used if the `Runtime` field is set to
66
+	// `attachment`.
67
+	ContainerSpec         *ContainerSpec         `json:",omitempty"`
68
+	PluginSpec            *runtime.PluginSpec    `json:",omitempty"`
69
+	NetworkAttachmentSpec *NetworkAttachmentSpec `json:",omitempty"`
67 70
 
68 71
 	Resources     *ResourceRequirements     `json:",omitempty"`
69 72
 	RestartPolicy *RestartPolicy            `json:",omitempty"`
... ...
@@ -17,6 +17,8 @@ import (
17 17
 var (
18 18
 	// ErrUnsupportedRuntime returns an error if the runtime is not supported by the daemon
19 19
 	ErrUnsupportedRuntime = errors.New("unsupported runtime")
20
+	// ErrMismatchedRuntime returns an error if the runtime does not match the provided spec
21
+	ErrMismatchedRuntime = errors.New("mismatched Runtime and *Spec fields")
20 22
 )
21 23
 
22 24
 // ServiceFromGRPC converts a grpc Service to a Service.
... ...
@@ -176,15 +178,18 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
176 176
 				return swarmapi.ServiceSpec{}, err
177 177
 			}
178 178
 			spec.Task.Runtime = &swarmapi.TaskSpec_Container{Container: containerSpec}
179
+		} else {
180
+			// If the ContainerSpec is nil, we can't set the task runtime
181
+			return swarmapi.ServiceSpec{}, ErrMismatchedRuntime
179 182
 		}
180 183
 	case types.RuntimePlugin:
181
-		if s.Mode.Replicated != nil {
182
-			return swarmapi.ServiceSpec{}, errors.New("plugins must not use replicated mode")
183
-		}
184
+		if s.TaskTemplate.PluginSpec != nil {
185
+			if s.Mode.Replicated != nil {
186
+				return swarmapi.ServiceSpec{}, errors.New("plugins must not use replicated mode")
187
+			}
184 188
 
185
-		s.Mode.Global = &types.GlobalService{} // must always be global
189
+			s.Mode.Global = &types.GlobalService{} // must always be global
186 190
 
187
-		if s.TaskTemplate.PluginSpec != nil {
188 191
 			pluginSpec, err := proto.Marshal(s.TaskTemplate.PluginSpec)
189 192
 			if err != nil {
190 193
 				return swarmapi.ServiceSpec{}, err
... ...
@@ -198,7 +203,16 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
198 198
 					},
199 199
 				},
200 200
 			}
201
-		}
201
+		} else {
202
+			return swarmapi.ServiceSpec{}, ErrMismatchedRuntime
203
+		}
204
+	case types.RuntimeNetworkAttachment:
205
+		// NOTE(dperny) I'm leaving this case here for completeness. The actual
206
+		// code is left out out deliberately, as we should refuse to parse a
207
+		// Network Attachment runtime; it will cause weird behavior all over
208
+		// the system if we do. Instead, fallthrough and return
209
+		// ErrUnsupportedRuntime if we get one.
210
+		fallthrough
202 211
 	default:
203 212
 		return swarmapi.ServiceSpec{}, ErrUnsupportedRuntime
204 213
 	}
... ...
@@ -573,6 +587,12 @@ func updateConfigToGRPC(updateConfig *types.UpdateConfig) (*swarmapi.UpdateConfi
573 573
 	return converted, nil
574 574
 }
575 575
 
576
+func networkAttachmentSpecFromGRPC(attachment swarmapi.NetworkAttachmentSpec) *types.NetworkAttachmentSpec {
577
+	return &types.NetworkAttachmentSpec{
578
+		ContainerID: attachment.ContainerID,
579
+	}
580
+}
581
+
576 582
 func taskSpecFromGRPC(taskSpec swarmapi.TaskSpec) (types.TaskSpec, error) {
577 583
 	taskNetworks := make([]types.NetworkAttachmentConfig, 0, len(taskSpec.Networks))
578 584
 	for _, n := range taskSpec.Networks {
... ...
@@ -607,6 +627,12 @@ func taskSpecFromGRPC(taskSpec swarmapi.TaskSpec) (types.TaskSpec, error) {
607 607
 				t.PluginSpec = &p
608 608
 			}
609 609
 		}
610
+	case *swarmapi.TaskSpec_Attachment:
611
+		a := taskSpec.GetAttachment()
612
+		if a != nil {
613
+			t.NetworkAttachmentSpec = networkAttachmentSpecFromGRPC(*a)
614
+		}
615
+		t.Runtime = types.RuntimeNetworkAttachment
610 616
 	}
611 617
 
612 618
 	return t, nil
... ...
@@ -232,3 +232,77 @@ func TestServiceConvertFromGRPCIsolation(t *testing.T) {
232 232
 		})
233 233
 	}
234 234
 }
235
+
236
+func TestServiceConvertToGRPCNetworkAtachmentRuntime(t *testing.T) {
237
+	someid := "asfjkl"
238
+	s := swarmtypes.ServiceSpec{
239
+		TaskTemplate: swarmtypes.TaskSpec{
240
+			Runtime: swarmtypes.RuntimeNetworkAttachment,
241
+			NetworkAttachmentSpec: &swarmtypes.NetworkAttachmentSpec{
242
+				ContainerID: someid,
243
+			},
244
+		},
245
+	}
246
+
247
+	// discard the service, which will be empty
248
+	_, err := ServiceSpecToGRPC(s)
249
+	if err == nil {
250
+		t.Fatalf("expected error %v but got no error", ErrUnsupportedRuntime)
251
+	}
252
+	if err != ErrUnsupportedRuntime {
253
+		t.Fatalf("expected error %v but got error %v", ErrUnsupportedRuntime, err)
254
+	}
255
+}
256
+
257
+func TestServiceConvertToGRPCMismatchedRuntime(t *testing.T) {
258
+	// NOTE(dperny): an earlier version of this test was for code that also
259
+	// converted network attachment tasks to GRPC. that conversion code was
260
+	// removed, so if this loop body seems a bit complicated, that's why.
261
+	for i, rt := range []swarmtypes.RuntimeType{
262
+		swarmtypes.RuntimeContainer,
263
+		swarmtypes.RuntimePlugin,
264
+	} {
265
+		for j, spec := range []swarmtypes.TaskSpec{
266
+			{ContainerSpec: &swarmtypes.ContainerSpec{}},
267
+			{PluginSpec: &runtime.PluginSpec{}},
268
+		} {
269
+			// skip the cases, where the indices match, which would not error
270
+			if i == j {
271
+				continue
272
+			}
273
+			// set the task spec, then change the runtime
274
+			s := swarmtypes.ServiceSpec{
275
+				TaskTemplate: spec,
276
+			}
277
+			s.TaskTemplate.Runtime = rt
278
+
279
+			if _, err := ServiceSpecToGRPC(s); err != ErrMismatchedRuntime {
280
+				t.Fatalf("expected %v got %v", ErrMismatchedRuntime, err)
281
+			}
282
+		}
283
+	}
284
+}
285
+
286
+func TestTaskConvertFromGRPCNetworkAttachment(t *testing.T) {
287
+	containerID := "asdfjkl"
288
+	s := swarmapi.TaskSpec{
289
+		Runtime: &swarmapi.TaskSpec_Attachment{
290
+			Attachment: &swarmapi.NetworkAttachmentSpec{
291
+				ContainerID: containerID,
292
+			},
293
+		},
294
+	}
295
+	ts, err := taskSpecFromGRPC(s)
296
+	if err != nil {
297
+		t.Fatal(err)
298
+	}
299
+	if ts.NetworkAttachmentSpec == nil {
300
+		t.Fatal("expected task spec to have network attachment spec")
301
+	}
302
+	if ts.NetworkAttachmentSpec.ContainerID != containerID {
303
+		t.Fatalf("expected network attachment spec container id to be %q, was %q", containerID, ts.NetworkAttachmentSpec.ContainerID)
304
+	}
305
+	if ts.Runtime != swarmtypes.RuntimeNetworkAttachment {
306
+		t.Fatalf("expected Runtime to be %v", swarmtypes.RuntimeNetworkAttachment)
307
+	}
308
+}
... ...
@@ -10,9 +10,6 @@ import (
10 10
 
11 11
 // TaskFromGRPC converts a grpc Task to a Task.
12 12
 func TaskFromGRPC(t swarmapi.Task) (types.Task, error) {
13
-	if t.Spec.GetAttachment() != nil {
14
-		return types.Task{}, nil
15
-	}
16 13
 	containerStatus := t.Status.GetContainer()
17 14
 	taskSpec, err := taskSpecFromGRPC(t.Spec)
18 15
 	if err != nil {
... ...
@@ -135,6 +135,8 @@ func (c *Cluster) CreateService(s types.ServiceSpec, encodedAuth string, queryRe
135 135
 		resp = &apitypes.ServiceCreateResponse{}
136 136
 
137 137
 		switch serviceSpec.Task.Runtime.(type) {
138
+		case *swarmapi.TaskSpec_Attachment:
139
+			return fmt.Errorf("invalid task spec: spec type %q not supported", types.RuntimeNetworkAttachment)
138 140
 		// handle other runtimes here
139 141
 		case *swarmapi.TaskSpec_Generic:
140 142
 			switch serviceSpec.Task.GetGeneric().Kind {
... ...
@@ -244,6 +246,8 @@ func (c *Cluster) UpdateService(serviceIDOrName string, version uint64, spec typ
244 244
 		resp = &apitypes.ServiceUpdateResponse{}
245 245
 
246 246
 		switch serviceSpec.Task.Runtime.(type) {
247
+		case *swarmapi.TaskSpec_Attachment:
248
+			return fmt.Errorf("invalid task spec: spec type %q not supported", types.RuntimeNetworkAttachment)
247 249
 		case *swarmapi.TaskSpec_Generic:
248 250
 			switch serviceSpec.Task.GetGeneric().Kind {
249 251
 			case string(types.RuntimePlugin):