| package container // import "github.com/docker/docker/daemon/cluster/executor/container" |
| |
| import ( |
| "testing" |
| |
| "context" |
| "time" |
| |
| "github.com/docker/docker/daemon" |
| "github.com/docker/swarmkit/api" |
| ) |
| |
| // TestWaitNodeAttachment tests that the waitNodeAttachment method successfully |
| // blocks until the required node attachment becomes available. |
| func TestWaitNodeAttachment(t *testing.T) { |
| emptyDaemon := &daemon.Daemon{} |
| |
| // the daemon creates an attachment store as an object, which means it's |
| // initialized to an empty store by default. get that attachment store here |
| // and add some attachments to it |
| attachmentStore := emptyDaemon.GetAttachmentStore() |
| |
| // create a set of attachments to put into the attahcment store |
| attachments := map[string]string{ |
| "network1": "10.1.2.3/24", |
| } |
| |
| // this shouldn't fail, but check it anyway just in case |
| err := attachmentStore.ResetAttachments(attachments) |
| if err != nil { |
| t.Fatalf("error resetting attachments: %v", err) |
| } |
| |
| // create a containerConfig to put in the adapter. we don't need the task, |
| // actually; only the networkAttachments are needed. |
| container := &containerConfig{ |
| task: nil, |
| networksAttachments: map[string]*api.NetworkAttachment{ |
| // network1 is already present in the attachment store. |
| "network1": { |
| Network: &api.Network{ |
| ID: "network1", |
| DriverState: &api.Driver{ |
| Name: "overlay", |
| }, |
| }, |
| }, |
| // network2 is not yet present in the attachment store, and we |
| // should block while waiting for it. |
| "network2": { |
| Network: &api.Network{ |
| ID: "network2", |
| DriverState: &api.Driver{ |
| Name: "overlay", |
| }, |
| }, |
| }, |
| // localnetwork is not and will never be in the attachment store, |
| // but we should not block on it, because it is not an overlay |
| // network |
| "localnetwork": { |
| Network: &api.Network{ |
| ID: "localnetwork", |
| DriverState: &api.Driver{ |
| Name: "bridge", |
| }, |
| }, |
| }, |
| }, |
| } |
| |
| // we don't create an adapter using the newContainerAdapter package, |
| // because it does a bunch of checks and validations. instead, create one |
| // "from scratch" so we only have the fields we need. |
| adapter := &containerAdapter{ |
| backend: emptyDaemon, |
| container: container, |
| } |
| |
| // create a context to do call the method with |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| // create a channel to allow the goroutine that we run the method call in |
| // to signal that it's done. |
| doneChan := make(chan struct{}) |
| |
| // store the error return value of waitNodeAttachments in this variable |
| var waitNodeAttachmentsErr error |
| // NOTE(dperny): be careful running goroutines in test code. if a test |
| // terminates with ie t.Fatalf or a failed requirement, runtime.Goexit gets |
| // called, which does run defers but does not clean up child goroutines. |
| // we defer canceling the context here, which should stop this goroutine |
| // from leaking |
| go func() { |
| waitNodeAttachmentsErr = adapter.waitNodeAttachments(ctx) |
| // signal that we've completed |
| close(doneChan) |
| }() |
| |
| // wait 200ms to allow the waitNodeAttachments call to spin for a bit |
| time.Sleep(200 * time.Millisecond) |
| select { |
| case <-doneChan: |
| if waitNodeAttachmentsErr == nil { |
| t.Fatalf("waitNodeAttachments exited early with no error") |
| } else { |
| t.Fatalf( |
| "waitNodeAttachments exited early with an error: %v", |
| waitNodeAttachmentsErr, |
| ) |
| } |
| default: |
| // allow falling through; this is the desired case |
| } |
| |
| // now update the node attachments to include another network attachment |
| attachments["network2"] = "10.3.4.5/24" |
| err = attachmentStore.ResetAttachments(attachments) |
| if err != nil { |
| t.Fatalf("error resetting attachments: %v", err) |
| } |
| |
| // now wait 200 ms for waitNodeAttachments to pick up the change |
| time.Sleep(200 * time.Millisecond) |
| |
| // and check that waitNodeAttachments has exited with no error |
| select { |
| case <-doneChan: |
| if waitNodeAttachmentsErr != nil { |
| t.Fatalf( |
| "waitNodeAttachments returned an error: %v", |
| waitNodeAttachmentsErr, |
| ) |
| } |
| default: |
| t.Fatalf("waitNodeAttachments did not exit yet, but should have") |
| } |
| } |