From 8a455656dcbd04ba782dd9287a02b88cae9a1f01 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 2 Oct 2015 12:11:41 -0700 Subject: [PATCH 1/9] add 'docker network ls' support add 'docker network inspect' suport Signed-off-by: Victor Vieux --- Godeps/Godeps.json | 2 +- .../samalba/dockerclient/dockerclient.go | 76 +++++++++++++++++++ .../samalba/dockerclient/dockerclient_test.go | 1 + .../samalba/dockerclient/interface.go | 6 ++ .../samalba/dockerclient/mockclient/mock.go | 30 ++++++++ .../samalba/dockerclient/nopclient/nop.go | 25 ++++++ .../github.com/samalba/dockerclient/types.go | 41 ++++++++++ api/handlers.go | 17 +++++ api/primary.go | 2 + cluster/cluster.go | 6 ++ cluster/engine.go | 34 +++++++++ cluster/engine_test.go | 8 ++ cluster/mesos/cluster.go | 10 +++ cluster/network.go | 62 +++++++++++++++ cluster/swarm/cluster.go | 28 ++++++- cluster/swarm/cluster_test.go | 3 + test/integration/api/network.bats | 72 ++++++++++++++++++ 17 files changed, 421 insertions(+), 2 deletions(-) create mode 100644 cluster/network.go create mode 100644 test/integration/api/network.bats diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index e29b7d1bcb..4d952aed42 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -114,7 +114,7 @@ }, { "ImportPath": "github.com/samalba/dockerclient", - "Rev": "73edd1c3a9d280bcec6a22a12fc90c1a46558b4e" + "Rev": "32a9231a6d93f563010c5ffc2f6fb223b347b6b1" }, { "ImportPath": "github.com/samuel/go-zookeeper/zk", diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go index 81428b3cf8..c4302d539e 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go @@ -778,3 +778,79 @@ func (client *DockerClient) CreateVolume(request *VolumeCreateRequest) (*Volume, err = json.Unmarshal(data, volume) return volume, err } + +func (client *DockerClient) ListNetworks(filters string) ([]*NetworkResource, error) { + uri := fmt.Sprintf("/%s/networks", APIVersion) + + if filters != "" { + uri += "&filters=" + filters + } + + data, err := client.doRequest("GET", uri, nil, nil) + if err != nil { + return nil, err + } + ret := []*NetworkResource{} + err = json.Unmarshal(data, &ret) + if err != nil { + return nil, err + } + return ret, nil +} + +func (client *DockerClient) InspectNetwork(id string) (*NetworkResource, error) { + uri := fmt.Sprintf("/%s/networks/%s", APIVersion, id) + + data, err := client.doRequest("GET", uri, nil, nil) + if err != nil { + return nil, err + } + ret := &NetworkResource{} + err = json.Unmarshal(data, ret) + if err != nil { + return nil, err + } + + return ret, nil +} + +func (client *DockerClient) CreateNetwork(config *NetworkCreate) (*NetworkCreateResponse, error) { + data, err := json.Marshal(config) + if err != nil { + return nil, err + } + uri := fmt.Sprintf("/%s/networks/create", APIVersion) + data, err = client.doRequest("POST", uri, data, nil) + if err != nil { + return nil, err + } + ret := &NetworkCreateResponse{} + err = json.Unmarshal(data, ret) + return ret, nil +} + +func (client *DockerClient) ConnectNetwork(id, container string) error { + data, err := json.Marshal(NetworkConnect{Container: container}) + if err != nil { + return err + } + uri := fmt.Sprintf("/%s/networks/%s/connect", APIVersion, id) + _, err = client.doRequest("POST", uri, data, nil) + return err +} + +func (client *DockerClient) DisconnectNetwork(id, container string) error { + data, err := json.Marshal(NetworkDisconnect{Container: container}) + if err != nil { + return err + } + uri := fmt.Sprintf("/%s/networks/%s/disconnect", APIVersion, id) + _, err = client.doRequest("POST", uri, data, nil) + return err +} + +func (client *DockerClient) RemoveNetwork(id string) error { + uri := fmt.Sprintf("/%s/networks/%s", APIVersion, id) + _, err := client.doRequest("DELETE", uri, nil, nil) + return err +} diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient_test.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient_test.go index 88257e01b7..afc1b071b0 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient_test.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient_test.go @@ -119,6 +119,7 @@ func TestListContainersWithSize(t *testing.T) { cnt := containers[0] assertEqual(t, cnt.SizeRw, int64(123), "") } + func TestListContainersWithFilters(t *testing.T) { client := testDockerClient(t) containers, err := client.ListContainers(true, true, "{'id':['332375cfbc23edb921a21026314c3497674ba8bdcb2c85e0e65ebf2017f688ce']}") diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go index b173fc1171..4215ca4e2f 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go @@ -48,4 +48,10 @@ type Client interface { ListVolumes() ([]*Volume, error) RemoveVolume(name string) error CreateVolume(request *VolumeCreateRequest) (*Volume, error) + ListNetworks(filters string) ([]*NetworkResource, error) + InspectNetwork(id string) (*NetworkResource, error) + CreateNetwork(config *NetworkCreate) (*NetworkCreateResponse, error) + ConnectNetwork(id, container string) error + DisconnectNetwork(id, container string) error + RemoveNetwork(id string) error } diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go index b12195d0f8..9944512e93 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go @@ -185,3 +185,33 @@ func (client *MockClient) CreateVolume(request *dockerclient.VolumeCreateRequest args := client.Mock.Called(request) return args.Get(0).(*dockerclient.Volume), args.Error(1) } + +func (client *MockClient) ListNetworks(filters string) ([]*dockerclient.NetworkResource, error) { + args := client.Mock.Called(filters) + return args.Get(0).([]*dockerclient.NetworkResource), args.Error(1) +} + +func (client *MockClient) InspectNetwork(id string) (*dockerclient.NetworkResource, error) { + args := client.Mock.Called(id) + return args.Get(0).(*dockerclient.NetworkResource), args.Error(1) +} + +func (client *MockClient) CreateNetwork(config *dockerclient.NetworkCreate) (*dockerclient.NetworkCreateResponse, error) { + args := client.Mock.Called(config) + return args.Get(0).(*dockerclient.NetworkCreateResponse), args.Error(1) +} + +func (client *MockClient) ConnectNetwork(id, container string) error { + args := client.Mock.Called(id, container) + return args.Error(0) +} + +func (client *MockClient) DisconnectNetwork(id, container string) error { + args := client.Mock.Called(id, container) + return args.Error(0) +} + +func (client *MockClient) RemoveNetwork(id string) error { + args := client.Mock.Called(id) + return args.Error(0) +} diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/nopclient/nop.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/nopclient/nop.go index 5763509599..ef64536304 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/nopclient/nop.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/nopclient/nop.go @@ -153,6 +153,31 @@ func (client *NopClient) ListVolumes() ([]*dockerclient.Volume, error) { func (client *NopClient) RemoveVolume(name string) error { return ErrNoEngine } + func (client *NopClient) CreateVolume(request *dockerclient.VolumeCreateRequest) (*dockerclient.Volume, error) { return nil, ErrNoEngine } + +func (client *NopClient) ListNetworks(filters string) ([]*dockerclient.NetworkResource, error) { + return nil, ErrNoEngine +} + +func (client *NopClient) InspectNetwork(id string) (*dockerclient.NetworkResource, error) { + return nil, ErrNoEngine +} + +func (client *NopClient) CreateNetwork(config *dockerclient.NetworkCreate) (*dockerclient.NetworkCreateResponse, error) { + return nil, ErrNoEngine +} + +func (client *NopClient) ConnectNetwork(id, container string) error { + return ErrNoEngine +} + +func (client *NopClient) DisconnectNetwork(id, container string) error { + return ErrNoEngine +} + +func (client *NopClient) RemoveNetwork(id string) error { + return ErrNoEngine +} diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go index a1654b61a5..b1f5a006c3 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go @@ -460,3 +460,44 @@ type VolumeCreateRequest struct { Driver string // Driver is the name of the driver that should be used to create the volume DriverOpts map[string]string // DriverOpts holds the driver specific options to use for when creating the volume. } + +// NetworkResource is the body of the "get network" http response message +type NetworkResource struct { + Name string `json:"name"` + ID string `json:"id"` + Driver string `json:"driver"` + Containers map[string]EndpointResource `json:"containers"` + Options map[string]interface{} `json:"options,omitempty"` +} + +//EndpointResource contains network resources allocated and usd for a container in a network +type EndpointResource struct { + EndpointID string `json:"endpoint"` + MacAddress string `json:"mac_address"` + IPv4Address string `json:"ipv4_address"` + IPv6Address string `json:"ipv6_address"` +} + +// NetworkCreate is the expected body of the "create network" http request message +type NetworkCreate struct { + Name string `json:"name"` + CheckDuplicate bool `json:"check_duplicate"` + Driver string `json:"driver"` + Options map[string]interface{} `json:"options"` +} + +// NetworkCreateResponse is the response message sent by the server for network create call +type NetworkCreateResponse struct { + ID string `json:"id"` + Warning string `json:"warning"` +} + +// NetworkConnect represents the data to be used to connect a container to the network +type NetworkConnect struct { + Container string `json:"container"` +} + +// NetworkDisconnect represents the data to be used to disconnect a container from the network +type NetworkDisconnect struct { + Container string `json:"container"` +} diff --git a/api/handlers.go b/api/handlers.go index c92e74eb2e..7aaff54534 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -169,6 +169,13 @@ func getImagesJSON(c *context, w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(images) } +// GET /networks +func getNetworks(c *context, w http.ResponseWriter, r *http.Request) { + networks := c.cluster.Networks() + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(networks) +} + // GET /volumes func getVolumes(c *context, w http.ResponseWriter, r *http.Request) { volumes := struct { @@ -663,6 +670,16 @@ func ping(c *context, w http.ResponseWriter, r *http.Request) { w.Write([]byte{'O', 'K'}) } +// Proxy a request to the right node +func proxyNetwork(c *context, w http.ResponseWriter, r *http.Request) { + var id = mux.Vars(r)["networkid"] + if network := c.cluster.Network(id); network != nil { + proxy(c.tlsConfig, network.Engine.Addr, w, r) + return + } + httpError(w, fmt.Sprintf("No such network: %s", id), http.StatusNotFound) +} + // Proxy a request to the right node func proxyVolume(c *context, w http.ResponseWriter, r *http.Request) { var name = mux.Vars(r)["volumename"] diff --git a/api/primary.go b/api/primary.go index cc7fd2669b..05b8eb52e9 100644 --- a/api/primary.go +++ b/api/primary.go @@ -47,6 +47,8 @@ var routes = map[string]map[string]handler{ "/containers/{name:.*}/stats": proxyContainer, "/containers/{name:.*}/attach/ws": proxyHijack, "/exec/{execid:.*}/json": proxyContainer, + "/networks": getNetworks, + "/networks/{networkid:.*}": proxyNetwork, "/volumes": getVolumes, "/volumes/{volumename:.*}": proxyVolume, }, diff --git a/cluster/cluster.go b/cluster/cluster.go index f29db15dfd..778d374712 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -32,6 +32,12 @@ type Cluster interface { // cluster.Containers().Get(IDOrName) Container(IDOrName string) *Container + // Return all networks + Networks() Networks + + // Return network the matching `IDOrName` + Network(IDOrName string) *Network + // Create a volume CreateVolume(request *dockerclient.VolumeCreateRequest) (*Volume, error) diff --git a/cluster/engine.go b/cluster/engine.go index 2b43e927c8..5394f19672 100644 --- a/cluster/engine.go +++ b/cluster/engine.go @@ -73,6 +73,7 @@ type Engine struct { refreshDelayer *delayer containers map[string]*Container images []*Image + networks map[string]*Network volumes map[string]*Volume client dockerclient.Client eventHandler EventHandler @@ -89,6 +90,7 @@ func NewEngine(addr string, overcommitRatio float64) *Engine { Labels: make(map[string]string), stopCh: make(chan struct{}), containers: make(map[string]*Container), + networks: make(map[string]*Network), volumes: make(map[string]*Volume), healthy: true, overcommitRatio: int64(overcommitRatio * 100), @@ -138,6 +140,7 @@ func (e *Engine) ConnectWithClient(client dockerclient.Client) error { // Do not check error as older daemon don't support this call e.RefreshVolumes() + e.RefreshNetworks() // Start the update loop. go e.refreshLoop() @@ -249,6 +252,21 @@ func (e *Engine) RefreshImages() error { return nil } +// RefreshNetworks refreshes the list of networks on the engine. +func (e *Engine) RefreshNetworks() error { + networks, err := e.client.ListNetworks("") + if err != nil { + return err + } + e.Lock() + e.networks = make(map[string]*Network) + for _, network := range networks { + e.networks[network.ID] = &Network{NetworkResource: *network, Engine: e} + } + e.Unlock() + return nil +} + // RefreshVolumes refreshes the list of volumes on the engine. func (e *Engine) RefreshVolumes() error { volumes, err := e.client.ListVolumes() @@ -382,6 +400,7 @@ func (e *Engine) refreshLoop() { if err == nil { // Do not check error as older daemon don't support this call e.RefreshVolumes() + e.RefreshNetworks() err = e.RefreshImages() } @@ -496,6 +515,7 @@ func (e *Engine) Create(config *ContainerConfig, name string, pullImage bool) (* // Register the container immediately while waiting for a state refresh. // Force a state refresh to pick up the newly created container. e.refreshContainer(id, true) + e.RefreshNetworks() e.RLock() defer e.RUnlock() @@ -621,6 +641,18 @@ func (e *Engine) Images(all bool, filters dockerfilters.Args) []*Image { return images } +// Networks returns all the networks in the engine +func (e *Engine) Networks() Networks { + e.RLock() + + networks := Networks{} + for _, network := range e.networks { + networks = append(networks, network) + } + e.RUnlock() + return networks +} + // Volumes returns all the volumes in the engine func (e *Engine) Volumes() []*Volume { e.RLock() @@ -662,10 +694,12 @@ func (e *Engine) handler(ev *dockerclient.Event, _ chan error, args ...interface // order to update container.Info and get the new NetworkSettings. e.refreshContainer(ev.Id, true) e.RefreshVolumes() + e.RefreshNetworks() default: // Otherwise, do a "soft" refresh of the container. e.refreshContainer(ev.Id, false) e.RefreshVolumes() + e.RefreshNetworks() } // If there is no event handler registered, abort right now. diff --git a/cluster/engine_test.go b/cluster/engine_test.go index 80e15c1b34..a7d845d246 100644 --- a/cluster/engine_test.go +++ b/cluster/engine_test.go @@ -75,6 +75,7 @@ func TestEngineCpusMemory(t *testing.T) { client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil) client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil) client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) + client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return() assert.NoError(t, engine.ConnectWithClient(client)) @@ -97,6 +98,7 @@ func TestEngineSpecs(t *testing.T) { client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil) client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil) client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) + client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return() assert.NoError(t, engine.ConnectWithClient(client)) @@ -127,6 +129,7 @@ func TestEngineState(t *testing.T) { client.On("ListContainers", true, false, "").Return([]dockerclient.Container{{Id: "one"}}, nil).Once() client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil).Once() client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) + client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) client.On("InspectContainer", "one").Return(&dockerclient.ContainerInfo{Config: &dockerclient.ContainerConfig{CpuShares: 100}}, nil).Once() client.On("ListContainers", true, false, fmt.Sprintf("{%q:[%q]}", "id", "two")).Return([]dockerclient.Container{{Id: "two"}}, nil).Once() client.On("InspectContainer", "two").Return(&dockerclient.ContainerInfo{Config: &dockerclient.ContainerConfig{CpuShares: 100}}, nil).Once() @@ -173,6 +176,7 @@ func TestCreateContainer(t *testing.T) { client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil).Once() client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil).Once() client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) + client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) assert.NoError(t, engine.ConnectWithClient(client)) assert.True(t, engine.isConnected()) @@ -187,6 +191,7 @@ func TestCreateContainer(t *testing.T) { client.On("ListContainers", true, false, fmt.Sprintf(`{"id":[%q]}`, id)).Return([]dockerclient.Container{{Id: id}}, nil).Once() client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil).Once() client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) + client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) client.On("InspectContainer", id).Return(&dockerclient.ContainerInfo{Config: &config.ContainerConfig}, nil).Once() container, err := engine.Create(config, name, false) assert.Nil(t, err) @@ -211,6 +216,7 @@ func TestCreateContainer(t *testing.T) { client.On("ListContainers", true, false, fmt.Sprintf(`{"id":[%q]}`, id)).Return([]dockerclient.Container{{Id: id}}, nil).Once() client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil).Once() client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) + client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) client.On("InspectContainer", id).Return(&dockerclient.ContainerInfo{Config: &config.ContainerConfig}, nil).Once() container, err = engine.Create(config, name, true) assert.Nil(t, err) @@ -288,6 +294,7 @@ func TestUsedCpus(t *testing.T) { client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil).Once() client.On("ListContainers", true, false, "").Return([]dockerclient.Container{{Id: "test"}}, nil).Once() client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) + client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) client.On("InspectContainer", "test").Return(&dockerclient.ContainerInfo{Config: &dockerclient.ContainerConfig{CpuShares: cpuShares}}, nil).Once() engine.ConnectWithClient(client) @@ -317,6 +324,7 @@ func TestContainerRemovedDuringRefresh(t *testing.T) { client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return() client.On("ListContainers", true, false, "").Return([]dockerclient.Container{container1, container2}, nil) client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) + client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) client.On("InspectContainer", "c1").Return(info1, errors.New("Not found")) client.On("InspectContainer", "c2").Return(info2, nil) diff --git a/cluster/mesos/cluster.go b/cluster/mesos/cluster.go index 783b736b29..ef92093eaa 100644 --- a/cluster/mesos/cluster.go +++ b/cluster/mesos/cluster.go @@ -306,6 +306,16 @@ func (c *Cluster) RenameContainer(container *cluster.Container, newName string) return nil } +// Networks returns all the networks in the cluster. +func (c *Cluster) Networks() cluster.Networks { + return nil +} + +// Network returns the network name in the cluster +func (c *Cluster) Network(name string) *cluster.Network { + return nil +} + // Volumes returns all the volumes in the cluster. func (c *Cluster) Volumes() []*cluster.Volume { return nil diff --git a/cluster/network.go b/cluster/network.go new file mode 100644 index 0000000000..13ccf9647a --- /dev/null +++ b/cluster/network.go @@ -0,0 +1,62 @@ +package cluster + +import ( + "strings" + + "github.com/docker/docker/pkg/stringid" + "github.com/samalba/dockerclient" +) + +// Network is exported +type Network struct { + dockerclient.NetworkResource + + Engine *Engine +} + +// Networks represents a map of networks +type Networks []*Network + +// Get returns a network using it's ID or Name +func (networks Networks) Get(IDOrName string) *Network { + // Abort immediately if the name is empty. + if len(IDOrName) == 0 { + return nil + } + + // Match exact or short Network ID. + for _, network := range networks { + if network.ID == IDOrName || stringid.TruncateID(network.ID) == IDOrName { + return network + } + } + + candidates := []*Network{} + + // Match name, /name or engine/name. + for _, network := range networks { + if network.Name == IDOrName || network.Name == "/"+IDOrName || network.Engine.ID+network.Name == IDOrName || network.Engine.Name+network.Name == IDOrName { + return network + } + } + + if size := len(candidates); size == 1 { + return candidates[0] + } else if size > 1 { + return nil + } + + // Match Network ID prefix. + for _, network := range networks { + if strings.HasPrefix(network.ID, IDOrName) { + candidates = append(candidates, network) + } + } + + if len(candidates) == 1 { + return candidates[0] + } + + return nil + +} diff --git a/cluster/swarm/cluster.go b/cluster/swarm/cluster.go index 011f918633..17217d1fc4 100644 --- a/cluster/swarm/cluster.go +++ b/cluster/swarm/cluster.go @@ -569,7 +569,33 @@ func (c *Cluster) Container(IDOrName string) *cluster.Container { c.RLock() defer c.RUnlock() - return cluster.Containers(c.Containers()).Get(IDOrName) + return c.Containers().Get(IDOrName) +} + +// Networks returns all the networks in the cluster. +func (c *Cluster) Networks() cluster.Networks { + c.RLock() + defer c.RUnlock() + + out := cluster.Networks{} + for _, e := range c.engines { + out = append(out, e.Networks()...) + } + + return out +} + +// Network returns the network `IDOrName` in the cluster +func (c *Cluster) Network(IDOrName string) *cluster.Network { + // Abort immediately if the name is empty. + if len(IDOrName) == 0 { + return nil + } + + c.RLock() + defer c.RUnlock() + + return c.Networks().Get(IDOrName) } diff --git a/cluster/swarm/cluster_test.go b/cluster/swarm/cluster_test.go index 0efa96fedb..f99e9d4467 100644 --- a/cluster/swarm/cluster_test.go +++ b/cluster/swarm/cluster_test.go @@ -131,6 +131,7 @@ func TestImportImage(t *testing.T) { client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil).Once() client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil) client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) + client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) // connect client engine.ConnectWithClient(client) @@ -180,6 +181,7 @@ func TestLoadImage(t *testing.T) { client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil).Once() client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil) client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) + client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) // connect client engine.ConnectWithClient(client) @@ -232,6 +234,7 @@ func TestTagImage(t *testing.T) { client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil).Once() client.On("ListImages", mock.Anything).Return(images, nil) client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil) + client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil) // connect client engine.ConnectWithClient(client) diff --git a/test/integration/api/network.bats b/test/integration/api/network.bats new file mode 100644 index 0000000000..c1a1e6389e --- /dev/null +++ b/test/integration/api/network.bats @@ -0,0 +1,72 @@ +#!/usr/bin/env bats + +load ../helpers + +function teardown() { + swarm_manage_cleanup + stop_docker +} + +@test "docker network ls" { + start_docker 2 + swarm_manage + + run docker_swarm network ls + [ "${#lines[@]}" -eq 7 ] +} + +@test "docker network inspect" { + start_docker_with_busybox 2 + swarm_manage + + # run + docker_swarm run -d busybox sleep 100 + + run docker_swarm network inspect bridge + [ "${#lines[@]}" -eq 13 ] +} + +@test "docker volume create" { +skip + start_docker 2 + swarm_manage + + run docker_swarm volume ls + [ "${#lines[@]}" -eq 1 ] + + docker_swarm volume create --name=test_volume + run docker_swarm volume + [ "${#lines[@]}" -eq 3 ] + + docker_swarm run -d -v=/tmp busybox true + run docker_swarm volume + [ "${#lines[@]}" -eq 4 ] +} + +@test "docker volume rm" { +skip + start_docker_with_busybox 2 + swarm_manage + + run docker_swarm volume rm test_volume + [ "$status" -ne 0 ] + + docker_swarm run -d --name=test_container -v=/tmp busybox true + + run docker_swarm volume ls -q + volume=${output} + [ "${#lines[@]}" -eq 1 ] + + run docker_swarm volume rm $volume + [ "$status" -ne 0 ] + + docker_swarm rm test_container + + run docker_swarm volume rm $volume + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 1 ] + + run docker_swarm volume + echo $output + [ "${#lines[@]}" -eq 1 ] +} From 2032b0e8fa19b46436e3873252b08362bc9d51a0 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 7 Oct 2015 14:21:35 -0700 Subject: [PATCH 2/9] prepend engine name on network name Signed-off-by: Victor Vieux --- api/handlers.go | 13 +++++++++++-- cluster/network.go | 11 +++++++++-- test/integration/api/network.bats | 5 ++++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/api/handlers.go b/api/handlers.go index 7aaff54534..c38cd1e9e0 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -171,9 +171,14 @@ func getImagesJSON(c *context, w http.ResponseWriter, r *http.Request) { // GET /networks func getNetworks(c *context, w http.ResponseWriter, r *http.Request) { - networks := c.cluster.Networks() + out := []*dockerclient.NetworkResource{} + for _, network := range c.cluster.Networks() { + tmp := (*network).NetworkResource + tmp.Name = network.Engine.Name + "/" + network.Name + out = append(out, &tmp) + } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(networks) + json.NewEncoder(w).Encode(out) } // GET /volumes @@ -674,6 +679,10 @@ func ping(c *context, w http.ResponseWriter, r *http.Request) { func proxyNetwork(c *context, w http.ResponseWriter, r *http.Request) { var id = mux.Vars(r)["networkid"] if network := c.cluster.Network(id); network != nil { + + // Set the network ID in the proxied URL path. + r.URL.Path = strings.Replace(r.URL.Path, id, network.ID, 1) + proxy(c.tlsConfig, network.Engine.Addr, w, r) return } diff --git a/cluster/network.go b/cluster/network.go index 13ccf9647a..f69bdc8f2e 100644 --- a/cluster/network.go +++ b/cluster/network.go @@ -35,8 +35,8 @@ func (networks Networks) Get(IDOrName string) *Network { // Match name, /name or engine/name. for _, network := range networks { - if network.Name == IDOrName || network.Name == "/"+IDOrName || network.Engine.ID+network.Name == IDOrName || network.Engine.Name+network.Name == IDOrName { - return network + if network.Engine.ID+"/"+network.Name == IDOrName || network.Engine.Name+"/"+network.Name == IDOrName { + candidates = append(candidates, network) } } @@ -46,6 +46,13 @@ func (networks Networks) Get(IDOrName string) *Network { return nil } + // Match name, /name or engine/name. + for _, network := range networks { + if network.Name == IDOrName || network.Name == "/"+IDOrName { + return network + } + } + // Match Network ID prefix. for _, network := range networks { if strings.HasPrefix(network.ID, IDOrName) { diff --git a/test/integration/api/network.bats b/test/integration/api/network.bats index c1a1e6389e..e1f73ae7cc 100644 --- a/test/integration/api/network.bats +++ b/test/integration/api/network.bats @@ -20,9 +20,12 @@ function teardown() { swarm_manage # run - docker_swarm run -d busybox sleep 100 + docker_swarm run -d -e constraint:node==node-0 busybox sleep 100 run docker_swarm network inspect bridge + [ "$status" -ne 0 ] + + run docker_swarm network inspect node-0/bridge [ "${#lines[@]}" -eq 13 ] } From dbe899aa0f7398dd469c4118cfa144a58c76c7a4 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 8 Oct 2015 13:23:56 -0700 Subject: [PATCH 3/9] remove cluster.Network(IDOrName) Signed-off-by: Victor Vieux --- api/handlers.go | 2 +- cluster/cluster.go | 3 --- cluster/mesos/cluster.go | 7 +------ cluster/swarm/cluster.go | 14 -------------- 4 files changed, 2 insertions(+), 24 deletions(-) diff --git a/api/handlers.go b/api/handlers.go index c38cd1e9e0..ffd5480adf 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -678,7 +678,7 @@ func ping(c *context, w http.ResponseWriter, r *http.Request) { // Proxy a request to the right node func proxyNetwork(c *context, w http.ResponseWriter, r *http.Request) { var id = mux.Vars(r)["networkid"] - if network := c.cluster.Network(id); network != nil { + if network := c.cluster.Networks().Get(id); network != nil { // Set the network ID in the proxied URL path. r.URL.Path = strings.Replace(r.URL.Path, id, network.ID, 1) diff --git a/cluster/cluster.go b/cluster/cluster.go index 778d374712..a1ac4984ee 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -35,9 +35,6 @@ type Cluster interface { // Return all networks Networks() Networks - // Return network the matching `IDOrName` - Network(IDOrName string) *Network - // Create a volume CreateVolume(request *dockerclient.VolumeCreateRequest) (*Volume, error) diff --git a/cluster/mesos/cluster.go b/cluster/mesos/cluster.go index ef92093eaa..4a962df511 100644 --- a/cluster/mesos/cluster.go +++ b/cluster/mesos/cluster.go @@ -308,12 +308,7 @@ func (c *Cluster) RenameContainer(container *cluster.Container, newName string) // Networks returns all the networks in the cluster. func (c *Cluster) Networks() cluster.Networks { - return nil -} - -// Network returns the network name in the cluster -func (c *Cluster) Network(name string) *cluster.Network { - return nil + return cluster.Networks{} } // Volumes returns all the volumes in the cluster. diff --git a/cluster/swarm/cluster.go b/cluster/swarm/cluster.go index 17217d1fc4..bff328cfda 100644 --- a/cluster/swarm/cluster.go +++ b/cluster/swarm/cluster.go @@ -585,20 +585,6 @@ func (c *Cluster) Networks() cluster.Networks { return out } -// Network returns the network `IDOrName` in the cluster -func (c *Cluster) Network(IDOrName string) *cluster.Network { - // Abort immediately if the name is empty. - if len(IDOrName) == 0 { - return nil - } - - c.RLock() - defer c.RUnlock() - - return c.Networks().Get(IDOrName) - -} - // Volumes returns all the volumes in the cluster. func (c *Cluster) Volumes() []*cluster.Volume { c.RLock() From 7766186506bc930c944a093b4ad2795f34031217 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 8 Oct 2015 16:20:07 -0700 Subject: [PATCH 4/9] Add docker network create Signed-off-by: Victor Vieux --- api/handlers.go | 19 +++++++++++++++++++ api/primary.go | 1 + cluster/cluster.go | 3 +++ cluster/engine.go | 9 +++++++++ cluster/mesos/cluster.go | 5 +++++ cluster/network.go | 4 ++-- cluster/swarm/cluster.go | 23 +++++++++++++++++++++++ test/integration/api/network.bats | 22 ++++++++++++---------- 8 files changed, 74 insertions(+), 12 deletions(-) diff --git a/api/handlers.go b/api/handlers.go index ffd5480adf..97a950e752 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -427,6 +427,25 @@ func deleteContainers(c *context, w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) } +// POST /networks/create +func postNetworksCreate(c *context, w http.ResponseWriter, r *http.Request) { + var request dockerclient.NetworkCreate + + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + httpError(w, err.Error(), http.StatusBadRequest) + return + } + + response, err := c.cluster.CreateNetwork(&request) + if err != nil { + httpError(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + // POST /volumes func postVolumes(c *context, w http.ResponseWriter, r *http.Request) { var request dockerclient.VolumeCreateRequest diff --git a/api/primary.go b/api/primary.go index 05b8eb52e9..2054bede66 100644 --- a/api/primary.go +++ b/api/primary.go @@ -75,6 +75,7 @@ var routes = map[string]map[string]handler{ "/containers/{name:.*}/exec": postContainersExec, "/exec/{execid:.*}/start": postExecStart, "/exec/{execid:.*}/resize": proxyContainer, + "/networks/create": postNetworksCreate, "/volumes": postVolumes, }, "PUT": { diff --git a/cluster/cluster.go b/cluster/cluster.go index a1ac4984ee..5adc49424b 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -35,6 +35,9 @@ type Cluster interface { // Return all networks Networks() Networks + // Create a network + CreateNetwork(request *dockerclient.NetworkCreate) (*dockerclient.NetworkCreateResponse, error) + // Create a volume CreateVolume(request *dockerclient.VolumeCreateRequest) (*Volume, error) diff --git a/cluster/engine.go b/cluster/engine.go index 5394f19672..a8aa3011d7 100644 --- a/cluster/engine.go +++ b/cluster/engine.go @@ -542,6 +542,15 @@ func (e *Engine) RemoveContainer(container *Container, force, volumes bool) erro return nil } +// CreateNetwork creates a network in the engine +func (e *Engine) CreateNetwork(request *dockerclient.NetworkCreate) (*dockerclient.NetworkCreateResponse, error) { + response, err := e.client.CreateNetwork(request) + + e.RefreshNetworks() + + return response, err +} + // CreateVolume creates a volume in the engine func (e *Engine) CreateVolume(request *dockerclient.VolumeCreateRequest) (*Volume, error) { volume, err := e.client.CreateVolume(request) diff --git a/cluster/mesos/cluster.go b/cluster/mesos/cluster.go index 4a962df511..85d5da1bdf 100644 --- a/cluster/mesos/cluster.go +++ b/cluster/mesos/cluster.go @@ -226,6 +226,11 @@ func (c *Cluster) RemoveImages(name string, force bool) ([]*dockerclient.ImageDe return nil, errNotSupported } +// CreateNetwork creates a network in the cluster +func (c *Cluster) CreateNetwork(request *dockerclient.NetworkCreate) (*dockerclient.NetworkCreateResponse, error) { + return nil, errNotSupported +} + // CreateVolume creates a volume in the cluster func (c *Cluster) CreateVolume(request *dockerclient.VolumeCreateRequest) (*cluster.Volume, error) { return nil, errNotSupported diff --git a/cluster/network.go b/cluster/network.go index f69bdc8f2e..9cede7a2e4 100644 --- a/cluster/network.go +++ b/cluster/network.go @@ -35,7 +35,7 @@ func (networks Networks) Get(IDOrName string) *Network { // Match name, /name or engine/name. for _, network := range networks { - if network.Engine.ID+"/"+network.Name == IDOrName || network.Engine.Name+"/"+network.Name == IDOrName { + if network.Name == IDOrName || network.Engine.ID+"/"+network.Name == IDOrName || network.Engine.Name+"/"+network.Name == IDOrName { candidates = append(candidates, network) } } @@ -48,7 +48,7 @@ func (networks Networks) Get(IDOrName string) *Network { // Match name, /name or engine/name. for _, network := range networks { - if network.Name == IDOrName || network.Name == "/"+IDOrName { + if network.Name == "/"+IDOrName { return network } } diff --git a/cluster/swarm/cluster.go b/cluster/swarm/cluster.go index bff328cfda..b267fd95d2 100644 --- a/cluster/swarm/cluster.go +++ b/cluster/swarm/cluster.go @@ -329,6 +329,29 @@ func (c *Cluster) RemoveImages(name string, force bool) ([]*dockerclient.ImageDe return out, err } +// CreateNetwork creates a network in the cluster +func (c *Cluster) CreateNetwork(request *dockerclient.NetworkCreate) (response *dockerclient.NetworkCreateResponse, err error) { + var ( + parts = strings.SplitN(request.Name, "/", 2) + config = &cluster.ContainerConfig{} + ) + + if len(parts) == 2 { + // a node was specified, create the container only on this node + request.Name = parts[1] + config = cluster.BuildContainerConfig(dockerclient.ContainerConfig{Env: []string{"constraint:node==" + parts[0]}}) + } + + n, err := c.scheduler.SelectNodeForContainer(c.listNodes(), config) + if err != nil { + return nil, err + } + if n != nil { + return c.engines[n.ID].CreateNetwork(request) + } + return nil, nil +} + // CreateVolume creates a volume in the cluster func (c *Cluster) CreateVolume(request *dockerclient.VolumeCreateRequest) (*cluster.Volume, error) { var ( diff --git a/test/integration/api/network.bats b/test/integration/api/network.bats index e1f73ae7cc..6e898daff2 100644 --- a/test/integration/api/network.bats +++ b/test/integration/api/network.bats @@ -29,21 +29,23 @@ function teardown() { [ "${#lines[@]}" -eq 13 ] } -@test "docker volume create" { -skip +@test "docker network create" { start_docker 2 swarm_manage - run docker_swarm volume ls - [ "${#lines[@]}" -eq 1 ] + run docker_swarm network ls + [ "${#lines[@]}" -eq 7 ] - docker_swarm volume create --name=test_volume - run docker_swarm volume - [ "${#lines[@]}" -eq 3 ] + docker_swarm network create -d bridge test1 + run docker_swarm network ls + [ "${#lines[@]}" -eq 8 ] - docker_swarm run -d -v=/tmp busybox true - run docker_swarm volume - [ "${#lines[@]}" -eq 4 ] + docker_swarm network create -d bridge node-1/test2 + run docker_swarm network ls + [ "${#lines[@]}" -eq 9 ] + + run docker_swarm network create -d bridge node-2/test3 + [ "$status" -ne 0 ] } @test "docker volume rm" { From a512a35f740882005d5f5c3a7efd337eef0f43e6 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 8 Oct 2015 17:08:44 -0700 Subject: [PATCH 5/9] Add network rm Signed-off-by: Victor Vieux --- api/handlers.go | 21 +++++++++++++++++++++ api/primary.go | 7 ++++--- cluster/cluster.go | 3 +++ cluster/engine.go | 7 +++++++ cluster/mesos/cluster.go | 5 +++++ cluster/swarm/cluster.go | 5 +++++ test/integration/api/network.bats | 29 ++++++++++------------------- 7 files changed, 55 insertions(+), 22 deletions(-) diff --git a/api/handlers.go b/api/handlers.go index 97a950e752..6cb82f5714 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -668,6 +668,27 @@ func deleteImages(c *context, w http.ResponseWriter, r *http.Request) { json.NewEncoder(NewWriteFlusher(w)).Encode(out) } +// DELETE /networks/{networkid:.*} +func deleteNetworks(c *context, w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + httpError(w, err.Error(), http.StatusInternalServerError) + return + } + + var id = mux.Vars(r)["networkid"] + + if network := c.cluster.Networks().Get(id); network != nil { + if err := c.cluster.RemoveNetwork(network); err != nil { + httpError(w, err.Error(), http.StatusInternalServerError) + return + } + } else { + httpError(w, fmt.Sprintf("No such network %s", id), http.StatusNotFound) + return + } + w.WriteHeader(http.StatusNoContent) +} + // DELETE /volumes/{names:.*} func deleteVolumes(c *context, w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { diff --git a/api/primary.go b/api/primary.go index 2054bede66..f7eb28db94 100644 --- a/api/primary.go +++ b/api/primary.go @@ -82,9 +82,10 @@ var routes = map[string]map[string]handler{ "/containers/{name:.*}/archive": proxyContainer, }, "DELETE": { - "/containers/{name:.*}": deleteContainers, - "/images/{name:.*}": deleteImages, - "/volumes/{name:.*}": deleteVolumes, + "/containers/{name:.*}": deleteContainers, + "/images/{name:.*}": deleteImages, + "/networks/{networkid:.*}": deleteNetworks, + "/volumes/{name:.*}": deleteVolumes, }, "OPTIONS": { "": optionsHandler, diff --git a/cluster/cluster.go b/cluster/cluster.go index 5adc49424b..7b23c4cebd 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -38,6 +38,9 @@ type Cluster interface { // Create a network CreateNetwork(request *dockerclient.NetworkCreate) (*dockerclient.NetworkCreateResponse, error) + // Remove a network from the cluster + RemoveNetwork(network *Network) error + // Create a volume CreateVolume(request *dockerclient.VolumeCreateRequest) (*Volume, error) diff --git a/cluster/engine.go b/cluster/engine.go index a8aa3011d7..02e99ccdae 100644 --- a/cluster/engine.go +++ b/cluster/engine.go @@ -222,6 +222,13 @@ func (e *Engine) RemoveImage(image *Image, name string, force bool) ([]*dockercl return e.client.RemoveImage(name, force) } +// RemoveNetwork deletes a network from the engine. +func (e *Engine) RemoveNetwork(network *Network) error { + err := e.client.RemoveNetwork(network.ID) + e.RefreshNetworks() + return err +} + // RemoveVolume deletes a volume from the engine. func (e *Engine) RemoveVolume(name string) error { if err := e.client.RemoveVolume(name); err != nil { diff --git a/cluster/mesos/cluster.go b/cluster/mesos/cluster.go index 85d5da1bdf..3f7ba8a548 100644 --- a/cluster/mesos/cluster.go +++ b/cluster/mesos/cluster.go @@ -236,6 +236,11 @@ func (c *Cluster) CreateVolume(request *dockerclient.VolumeCreateRequest) (*clus return nil, errNotSupported } +// RemoveNetwork removes network from the cluster +func (c *Cluster) RemoveNetwork(network *cluster.Network) error { + return errNotSupported +} + // RemoveVolumes removes volumes from the cluster func (c *Cluster) RemoveVolumes(name string) (bool, error) { return false, errNotSupported diff --git a/cluster/swarm/cluster.go b/cluster/swarm/cluster.go index b267fd95d2..07570e0a97 100644 --- a/cluster/swarm/cluster.go +++ b/cluster/swarm/cluster.go @@ -173,6 +173,11 @@ func (c *Cluster) RemoveContainer(container *cluster.Container, force, volumes b return container.Engine.RemoveContainer(container, force, volumes) } +// RemoveNetwork removes a network from the cluster +func (c *Cluster) RemoveNetwork(network *cluster.Network) error { + return network.Engine.RemoveNetwork(network) +} + func (c *Cluster) getEngineByAddr(addr string) *cluster.Engine { c.RLock() defer c.RUnlock() diff --git a/test/integration/api/network.bats b/test/integration/api/network.bats index 6e898daff2..9a600b170f 100644 --- a/test/integration/api/network.bats +++ b/test/integration/api/network.bats @@ -48,30 +48,21 @@ function teardown() { [ "$status" -ne 0 ] } -@test "docker volume rm" { -skip +@test "docker network rm" { start_docker_with_busybox 2 swarm_manage - run docker_swarm volume rm test_volume + run docker_swarm network rm test_network [ "$status" -ne 0 ] - docker_swarm run -d --name=test_container -v=/tmp busybox true - - run docker_swarm volume ls -q - volume=${output} - [ "${#lines[@]}" -eq 1 ] - - run docker_swarm volume rm $volume + run docker_swarm network rm bridge [ "$status" -ne 0 ] - docker_swarm rm test_container - - run docker_swarm volume rm $volume - [ "$status" -eq 0 ] - [ "${#lines[@]}" -eq 1 ] - - run docker_swarm volume - echo $output - [ "${#lines[@]}" -eq 1 ] + docker_swarm network create -d bridge node-0/test + run docker_swarm network ls + [ "${#lines[@]}" -eq 8 ] + + docker_swarm network rm node-0/test + run docker_swarm network ls + [ "${#lines[@]}" -eq 7 ] } From 34cc60ed54ae0631c8706bf4a7c6c97adeb59eb4 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 8 Oct 2015 17:36:23 -0700 Subject: [PATCH 6/9] use overlay as default in swarm Signed-off-by: Victor Vieux --- api/handlers.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/handlers.go b/api/handlers.go index 6cb82f5714..4d6d0a8df6 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -436,6 +436,10 @@ func postNetworksCreate(c *context, w http.ResponseWriter, r *http.Request) { return } + if request.Driver == "" { + request.Driver = "overlay" + } + response, err := c.cluster.CreateNetwork(&request) if err != nil { httpError(w, err.Error(), http.StatusInternalServerError) From dc8501aa73ba7e7a24504388893a6b6eec8e3136 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 8 Oct 2015 20:24:47 -0700 Subject: [PATCH 7/9] add support docker connect / disconnect Signed-off-by: Victor Vieux --- api/primary.go | 50 ++++++++++++++++--------------- test/integration/api/network.bats | 26 ++++++++++++++++ 2 files changed, 52 insertions(+), 24 deletions(-) diff --git a/api/primary.go b/api/primary.go index f7eb28db94..314f356f8c 100644 --- a/api/primary.go +++ b/api/primary.go @@ -53,30 +53,32 @@ var routes = map[string]map[string]handler{ "/volumes/{volumename:.*}": proxyVolume, }, "POST": { - "/auth": proxyRandom, - "/commit": postCommit, - "/build": postBuild, - "/images/create": postImagesCreate, - "/images/load": postImagesLoad, - "/images/{name:.*}/push": proxyImageTagOptional, - "/images/{name:.*}/tag": postTagImage, - "/containers/create": postContainersCreate, - "/containers/{name:.*}/kill": proxyContainerAndForceRefresh, - "/containers/{name:.*}/pause": proxyContainerAndForceRefresh, - "/containers/{name:.*}/unpause": proxyContainerAndForceRefresh, - "/containers/{name:.*}/rename": postRenameContainer, - "/containers/{name:.*}/restart": proxyContainerAndForceRefresh, - "/containers/{name:.*}/start": proxyContainerAndForceRefresh, - "/containers/{name:.*}/stop": proxyContainerAndForceRefresh, - "/containers/{name:.*}/wait": proxyContainerAndForceRefresh, - "/containers/{name:.*}/resize": proxyContainer, - "/containers/{name:.*}/attach": proxyHijack, - "/containers/{name:.*}/copy": proxyContainer, - "/containers/{name:.*}/exec": postContainersExec, - "/exec/{execid:.*}/start": postExecStart, - "/exec/{execid:.*}/resize": proxyContainer, - "/networks/create": postNetworksCreate, - "/volumes": postVolumes, + "/auth": proxyRandom, + "/commit": postCommit, + "/build": postBuild, + "/images/create": postImagesCreate, + "/images/load": postImagesLoad, + "/images/{name:.*}/push": proxyImageTagOptional, + "/images/{name:.*}/tag": postTagImage, + "/containers/create": postContainersCreate, + "/containers/{name:.*}/kill": proxyContainerAndForceRefresh, + "/containers/{name:.*}/pause": proxyContainerAndForceRefresh, + "/containers/{name:.*}/unpause": proxyContainerAndForceRefresh, + "/containers/{name:.*}/rename": postRenameContainer, + "/containers/{name:.*}/restart": proxyContainerAndForceRefresh, + "/containers/{name:.*}/start": proxyContainerAndForceRefresh, + "/containers/{name:.*}/stop": proxyContainerAndForceRefresh, + "/containers/{name:.*}/wait": proxyContainerAndForceRefresh, + "/containers/{name:.*}/resize": proxyContainer, + "/containers/{name:.*}/attach": proxyHijack, + "/containers/{name:.*}/copy": proxyContainer, + "/containers/{name:.*}/exec": postContainersExec, + "/exec/{execid:.*}/start": postExecStart, + "/exec/{execid:.*}/resize": proxyContainer, + "/networks/create": postNetworksCreate, + "/networks/{networkid:.*}/connect": proxyNetwork, + "/networks/{networkid:.*}/disconnect": proxyNetwork, + "/volumes": postVolumes, }, "PUT": { "/containers/{name:.*}/archive": proxyContainer, diff --git a/test/integration/api/network.bats b/test/integration/api/network.bats index 9a600b170f..d5c52ce1fa 100644 --- a/test/integration/api/network.bats +++ b/test/integration/api/network.bats @@ -66,3 +66,29 @@ function teardown() { run docker_swarm network ls [ "${#lines[@]}" -eq 7 ] } + +@test "docker network disconnect connect" { + start_docker_with_busybox 2 + swarm_manage + + # run + docker_swarm run -d --name test_container -e constraint:node==node-0 busybox sleep 100 + + run docker_swarm network inspect node-0/bridge + [ "${#lines[@]}" -eq 13 ] + + docker_swarm network disconnect node-0/bridge test_container + + run docker_swarm network inspect node-0/bridge + [ "${#lines[@]}" -eq 6 ] + + docker_swarm network connect node-0/bridge test_container + + run docker_swarm network inspect node-0/bridge + [ "${#lines[@]}" -eq 13 ] + + docker_swarm rm -f test_container + + run docker_swarm network inspect node-0/bridge + [ "${#lines[@]}" -eq 6 ] +} From 7401bd274c500cc450b678f768f92a24bfb87afa Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 8 Oct 2015 21:07:41 -0700 Subject: [PATCH 8/9] add store test Signed-off-by: Victor Vieux --- test/integration/store.bats | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 test/integration/store.bats diff --git a/test/integration/store.bats b/test/integration/store.bats new file mode 100644 index 0000000000..e144195e01 --- /dev/null +++ b/test/integration/store.bats @@ -0,0 +1,47 @@ +#!/usr/bin/env bats + +load helpers + +function teardown() { + swarm_manage_cleanup + stop_docker + stop_store +} + +# Address on which the store will listen (random port between 8000 and 9000). +STORE_HOST=127.0.0.1:$(( ( RANDOM % 1000 ) + 8000 )) + +# Discovery parameter for Swarm +DISCOVERY="consul://${STORE_HOST}/test" + +# Container name for integration test +CONTAINER_NAME=swarm_consul + +function start_store() { + docker_host run -v $(pwd)/discovery/consul/config:/config --name=$CONTAINER_NAME -h $CONTAINER_NAME -p $STORE_HOST:8500 -d progrium/consul -server -bootstrap-expect 1 -config-file=/config/consul.json +} + +function stop_store() { + docker_host rm -f -v $CONTAINER_NAME || true +} + + +@test "docker setup cluster-store" { + start_store + + start_docker_with_busybox 1 --cluster-store $DISCOVERY --cluster-advertise $HOSTS[0] + start_docker_with_busybox 1 --cluster-store $DISCOVERY --cluster-advertise $HOSTS[1] + + run docker_host -H ${HOSTS[0]} info + [ "$status" -eq 0 ] + [[ "${output}" == *"$DISCOVERY"* ]] + + run docker_host -H ${HOSTS[1]} info + [ "$status" -eq 0 ] + [[ "${output}" == *"$DISCOVERY"* ]] + + swarm_manage + run docker_swarm info + [ "$status" -eq 0 ] + [[ "${output}" == *"Nodes: 2"* ]] +} From 0a1b60c6b84a7702855c62c52080da95502318b2 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 9 Oct 2015 00:26:51 -0700 Subject: [PATCH 9/9] add some docs Signed-off-by: Victor Vieux --- docs/networking.md | 106 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 docs/networking.md diff --git a/docs/networking.md b/docs/networking.md new file mode 100644 index 0000000000..242bca1ca7 --- /dev/null +++ b/docs/networking.md @@ -0,0 +1,106 @@ + + +# Networking + +Docker Swarm is fully compatible for the new networking model added in docker 1.9 + +## Setup + +To use multi-host networking you need to start your docker engines with +`--cluster-store` and `--cluster-advertise` as indicated in the docker +engine docs. + +### List networks + +This example assumes there are two nodes `node-0` and `node-1` in the cluster. + + $ docker networks ls + NETWORK ID NAME DRIVER + 3dd50db9706d node-0/host host + 09138343e80e node-0/bridge bridge + 8834dbd552e5 node-0/none null + 45782acfe427 node-1/host host + 8926accb25fd node-1/bridge bridge + 6382abccd23d node-1/none null + +As you can see, each network name is prefixed by the node name. + +## Create a network + +By default, swarm is using the `overlay` network driver, a global +scope driver. + + $ docker network create swarm_network + 42131321acab3233ba342443Ba4312 + $ docker networks ls + NETWORK ID NAME DRIVER + 3dd50db9706d node-0/host host + 09138343e80e node-0/bridge bridge + 8834dbd552e5 node-0/none null + 42131321acab node-0/swarm_network overlay + 45782acfe427 node-1/host host + 8926accb25fd node-1/bridge bridge + 6382abccd23d node-1/none null + 42131321acab node-1/swarm_network overlay + +As you can see here, the ID is the same on the two nodes, because it's the same +network. + +If you want to want to create a local scope network (for example with the bridge +driver) you should use `/` otherwise your network will be created on a +random node. + + $ docker network create node-0/bridge2 -b bridge + 921817fefea521673217123abab223 + $ docker network create node-1/bridge2 -b bridge + 5262bbfe5616fef6627771289aacc2 + $ docker networks ls + NETWORK ID NAME DRIVER + 3dd50db9706d node-0/host host + 09138343e80e node-0/bridge bridge + 8834dbd552e5 node-0/none null + 42131321acab node-0/swarm_network overlay + 921817fefea5 node-0/bridge2 brige + 45782acfe427 node-1/host host + 8926accb25fd node-1/bridge bridge + 6382abccd23d node-1/none null + 42131321acab node-1/swarm_network overlay + 5262bbfe5616 node-1/bridge2 bridge + +## Remove a network + +To remove a network you can use its ID or its name. +If two different network have the same name, use may use `/`. + + $ docker network rm swarm_network + 42131321acab3233ba342443Ba4312 + $ docker network rm node-0/bridge2 + 921817fefea521673217123abab223 + $ docker networks ls + NETWORK ID NAME DRIVER + 3dd50db9706d node-0/host host + 09138343e80e node-0/bridge bridge + 8834dbd552e5 node-0/none null + 45782acfe427 node-1/host host + 8926accb25fd node-1/bridge bridge + 6382abccd23d node-1/none null + 5262bbfe5616 node-1/bridge2 bridge + +`swarm_network` was removed from every node, `bridge2` was removed only +from `node-0`. + +## Docker Swarm documentation index + +- [User guide](/) +- [Scheduler strategies](/scheduler/strategy.md) +- [Scheduler filters](/scheduler/filter.md) +- [Swarm API](/api/swarm-api.md)