diff --git a/daemon/cluster/executor/container/adapter.go b/daemon/cluster/executor/container/adapter.go index 7395036cc8..4af6e3dd97 100644 --- a/daemon/cluster/executor/container/adapter.go +++ b/daemon/cluster/executor/container/adapter.go @@ -18,6 +18,7 @@ import ( containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/events" containerpkg "github.com/docker/docker/container" + "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/cluster/convert" executorpkg "github.com/docker/docker/daemon/cluster/executor" "github.com/docker/libnetwork" @@ -155,7 +156,11 @@ func (c *containerAdapter) createNetworks(ctx context.Context) error { if _, ok := err.(libnetwork.NetworkNameError); ok { continue } - + // We will continue if CreateManagedNetwork returns PredefinedNetworkError error. + // Other callers still can treat it as Error. + if _, ok := err.(daemon.PredefinedNetworkError); ok { + continue + } return err } } diff --git a/daemon/network.go b/daemon/network.go index 2c2a96b343..e5dcd06862 100644 --- a/daemon/network.go +++ b/daemon/network.go @@ -24,6 +24,16 @@ import ( "golang.org/x/net/context" ) +// PredefinedNetworkError is returned when user tries to create predefined network that already exists. +type PredefinedNetworkError string + +func (pnr PredefinedNetworkError) Error() string { + return fmt.Sprintf("operation is not permitted on predefined %s network ", string(pnr)) +} + +// Forbidden denotes the type of this error +func (pnr PredefinedNetworkError) Forbidden() {} + // NetworkControllerEnabled checks if the networking stack is enabled. // This feature depends on OS primitives and it's disabled in systems like Windows. func (daemon *Daemon) NetworkControllerEnabled() bool { @@ -267,9 +277,8 @@ func (daemon *Daemon) CreateNetwork(create types.NetworkCreateRequest) (*types.N } func (daemon *Daemon) createNetwork(create types.NetworkCreateRequest, id string, agent bool) (*types.NetworkCreateResponse, error) { - if runconfig.IsPreDefinedNetwork(create.Name) && !agent { - err := fmt.Errorf("%s is a pre-defined network and cannot be created", create.Name) - return nil, errdefs.Forbidden(err) + if runconfig.IsPreDefinedNetwork(create.Name) { + return nil, PredefinedNetworkError(create.Name) } var warning string diff --git a/integration/network/service_test.go b/integration/network/service_test.go new file mode 100644 index 0000000000..684b29c1c1 --- /dev/null +++ b/integration/network/service_test.go @@ -0,0 +1,70 @@ +package network // import "github.com/docker/docker/integration/network" + +import ( + "runtime" + "testing" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/client" + "github.com/gotestyourself/gotestyourself/poll" + "github.com/stretchr/testify/require" + "golang.org/x/net/context" +) + +func TestServiceWithPredefinedNetwork(t *testing.T) { + defer setupTest(t)() + d := newSwarm(t) + defer d.Stop(t) + client, err := client.NewClientWithOpts(client.WithHost((d.Sock()))) + require.NoError(t, err) + + hostName := "host" + var instances uint64 = 1 + serviceName := "TestService" + serviceSpec := swarmServiceSpec(serviceName, instances) + serviceSpec.TaskTemplate.Networks = append(serviceSpec.TaskTemplate.Networks, swarm.NetworkAttachmentConfig{Target: hostName}) + + serviceResp, err := client.ServiceCreate(context.Background(), serviceSpec, types.ServiceCreateOptions{ + QueryRegistry: false, + }) + require.NoError(t, err) + + pollSettings := func(config *poll.Settings) { + if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" { + config.Timeout = 50 * time.Second + config.Delay = 100 * time.Millisecond + } + } + + serviceID := serviceResp.ID + poll.WaitOn(t, serviceRunningCount(client, serviceID, instances), pollSettings) + + _, _, err = client.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{}) + require.NoError(t, err) + + err = client.ServiceRemove(context.Background(), serviceID) + require.NoError(t, err) + + poll.WaitOn(t, serviceIsRemoved(client, serviceID), pollSettings) + poll.WaitOn(t, noTasks(client), pollSettings) + +} + +func serviceRunningCount(client client.ServiceAPIClient, serviceID string, instances uint64) func(log poll.LogT) poll.Result { + return func(log poll.LogT) poll.Result { + filter := filters.NewArgs() + filter.Add("service", serviceID) + services, err := client.ServiceList(context.Background(), types.ServiceListOptions{}) + if err != nil { + return poll.Error(err) + } + + if len(services) != int(instances) { + return poll.Continue("Service count at %d waiting for %d", len(services), instances) + } + return poll.Success() + } +}