add 'docker network ls' support

add 'docker network inspect' suport

Signed-off-by: Victor Vieux <vieux@docker.com>
This commit is contained in:
Victor Vieux 2015-10-02 12:11:41 -07:00
parent 267d7e6701
commit e634df03a7
17 changed files with 421 additions and 2 deletions

2
Godeps/Godeps.json generated
View File

@ -114,7 +114,7 @@
},
{
"ImportPath": "github.com/samalba/dockerclient",
"Rev": "73edd1c3a9d280bcec6a22a12fc90c1a46558b4e"
"Rev": "32a9231a6d93f563010c5ffc2f6fb223b347b6b1"
},
{
"ImportPath": "github.com/samuel/go-zookeeper/zk",

View File

@ -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
}

View File

@ -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']}")

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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"]

View File

@ -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,
},

View File

@ -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)

View File

@ -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.

View File

@ -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)

View File

@ -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

62
cluster/network.go Normal file
View File

@ -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
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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 ]
}