diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 7fac7b37dc..bccf07bce1 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -171,7 +171,7 @@ }, { "ImportPath": "github.com/samalba/dockerclient", - "Rev": "b5aaea9adc8168fe8ce00731d3025ffbfb389c87" + "Rev": "5747eb30667cf7088aea1de37b93856b5cd2f010" }, { "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 0417f72365..51a00b2910 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/dockerclient.go @@ -16,7 +16,16 @@ import ( "time" ) +var _ Client = (*DockerClient)(nil) + const ( + // APIVersion is currently hardcoded to v1.15 + // TODO: bump the API version or allow users to choose which API version to + // use the client with. The current value does not make sense for many + // methods, such as ContainerStats, StartMonitorStats, and StopAllMonitorStats + // (v1.17) and + // ListVolumes, {Remove,Create}Volume, ListNetworks, + // {Inspect,Create,Connect,Disconnect,Remove}Network (v1.21) APIVersion = "v1.15" ) @@ -256,6 +265,36 @@ func (client *DockerClient) ContainerChanges(id string) ([]*ContainerChanges, er return changes, nil } +func (client *DockerClient) ContainerStats(id string, stopChan <-chan struct{}) (<-chan StatsOrError, error) { + uri := fmt.Sprintf("/%s/containers/%s/stats", APIVersion, id) + resp, err := client.HTTPClient.Get(client.URL.String() + uri) + if err != nil { + return nil, err + } + + decode := func(decoder *json.Decoder) decodingResult { + var containerStats Stats + if err := decoder.Decode(&containerStats); err != nil { + return decodingResult{err: err} + } else { + return decodingResult{result: containerStats} + } + } + decodingResultChan := client.readJSONStream(resp.Body, decode, stopChan) + statsOrErrorChan := make(chan StatsOrError) + go func() { + for decodingResult := range decodingResultChan { + stats, _ := decodingResult.result.(Stats) + statsOrErrorChan <- StatsOrError{ + Stats: stats, + Error: decodingResult.err, + } + } + close(statsOrErrorChan) + }() + return statsOrErrorChan, nil +} + func (client *DockerClient) readJSONStream(stream io.ReadCloser, decode func(*json.Decoder) decodingResult, stopChan <-chan struct{}) <-chan decodingResult { resultChan := make(chan decodingResult) @@ -719,6 +758,31 @@ func (client *DockerClient) RemoveImage(name string, force bool) ([]*ImageDelete return imageDelete, nil } +func (client *DockerClient) SearchImages(query, registry string, auth *AuthConfig) ([]ImageSearch, error) { + term := query + if registry != "" { + term = registry + "/" + term + } + uri := fmt.Sprintf("/%s/images/search?term=%s", APIVersion, term) + headers := map[string]string{} + if auth != nil { + if encodedAuth, err := auth.encode(); err != nil { + return nil, err + } else { + headers["X-Registry-Auth"] = encodedAuth + } + } + data, err := client.doRequest("GET", uri, nil, headers) + if err != nil { + return nil, err + } + var imageSearches []ImageSearch + if err := json.Unmarshal(data, &imageSearches); err != nil { + return nil, err + } + return imageSearches, nil +} + func (client *DockerClient) PauseContainer(id string) error { uri := fmt.Sprintf("/%s/containers/%s/pause", APIVersion, id) _, err := client.doRequest("POST", uri, nil, nil) diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/example_responses.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/example_responses.go index 670508c074..e4c890dc4d 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/example_responses.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/example_responses.go @@ -10,4 +10,6 @@ var haproxyPullOutput = `{"status":"The image you are pulling has been verified" {"status":"Already exists","progressDetail":{},"id":"511136ea3c5a"}{"status":"Already exists","progressDetail":{},"id":"1aeada447715"}{"status":"Already exists","progressDetail":{},"id":"479215127fa7"}{"status":"Already exists","progressDetail":{},"id":"66301eb54a7d"}{"status":"Already exists","progressDetail":{},"id":"e3990b07573f"}{"status":"Already exists","progressDetail":{},"id":"ecb4b23ca7ce"}{"status":"Already exists","progressDetail":{},"id":"f453e940c177"}{"status":"Already exists","progressDetail":{},"id":"fc5ea1bc05ab"}{"status":"Already exists","progressDetail":{},"id":"380557f8f7b3"}{"status":"Status: Image is up to date for haproxy"} ` +var statsResp = `{"read":"2015-02-02T17:06:08.187833376-05:00","network":{"rx_bytes":99988,"rx_packets":928,"rx_errors":0,"rx_dropped":0,"tx_bytes":1786548,"tx_packets":877,"tx_errors":0,"tx_dropped":0},"cpu_stats":{"cpu_usage":{"total_usage":170018598,"percpu_usage":[170018598],"usage_in_kernelmode":30000000,"usage_in_usermode":70000000},"system_cpu_usage":9020930000000,"throttling_data":{"periods":0,"throttled_periods":0,"throttled_time":0}},"memory_stats":{"usage":18022400,"max_usage":20541440,"stats":{"active_anon":6213632,"active_file":176128,"cache":11808768,"hierarchical_memory_limit":9223372036854775807,"hierarchical_memsw_limit":9223372036854775807,"inactive_anon":0,"inactive_file":11632640,"mapped_file":5165056,"pgfault":2535,"pgmajfault":13,"pgpgin":4293,"pgpgout":1937,"rss":6213632,"rss_huge":2097152,"swap":0,"total_active_anon":6213632,"total_active_file":176128,"total_cache":11808768,"total_inactive_anon":0,"total_inactive_file":11632640,"total_mapped_file":5165056,"total_pgfault":2535,"total_pgmajfault":13,"total_pgpgin":4293,"total_pgpgout":1937,"total_rss":6213632,"total_rss_huge":2097152,"total_swap":0,"total_unevictable":0,"unevictable":0},"failcnt":0,"limit":1041051648},"blkio_stats":{"io_service_bytes_recursive":[{"major":7,"minor":0,"op":"Read","value":28672},{"major":7,"minor":0,"op":"Write","value":0},{"major":7,"minor":0,"op":"Sync","value":0},{"major":7,"minor":0,"op":"Async","value":28672},{"major":7,"minor":0,"op":"Total","value":28672},{"major":253,"minor":0,"op":"Read","value":28672},{"major":253,"minor":0,"op":"Write","value":0},{"major":253,"minor":0,"op":"Sync","value":0},{"major":253,"minor":0,"op":"Async","value":28672},{"major":253,"minor":0,"op":"Total","value":28672},{"major":253,"minor":7,"op":"Read","value":11718656},{"major":253,"minor":7,"op":"Write","value":0},{"major":253,"minor":7,"op":"Sync","value":0},{"major":253,"minor":7,"op":"Async","value":11718656},{"major":253,"minor":7,"op":"Total","value":11718656},{"major":202,"minor":0,"op":"Read","value":0},{"major":202,"minor":0,"op":"Write","value":0},{"major":202,"minor":0,"op":"Sync","value":0},{"major":202,"minor":0,"op":"Async","value":0},{"major":202,"minor":0,"op":"Total","value":0}],"io_serviced_recursive":[{"major":7,"minor":0,"op":"Read","value":7},{"major":7,"minor":0,"op":"Write","value":0},{"major":7,"minor":0,"op":"Sync","value":0},{"major":7,"minor":0,"op":"Async","value":7},{"major":7,"minor":0,"op":"Total","value":7},{"major":253,"minor":0,"op":"Read","value":7},{"major":253,"minor":0,"op":"Write","value":0},{"major":253,"minor":0,"op":"Sync","value":0},{"major":253,"minor":0,"op":"Async","value":7},{"major":253,"minor":0,"op":"Total","value":7},{"major":253,"minor":7,"op":"Read","value":312},{"major":253,"minor":7,"op":"Write","value":0},{"major":253,"minor":7,"op":"Sync","value":0},{"major":253,"minor":7,"op":"Async","value":312},{"major":253,"minor":7,"op":"Total","value":312},{"major":202,"minor":0,"op":"Read","value":0},{"major":202,"minor":0,"op":"Write","value":0},{"major":202,"minor":0,"op":"Sync","value":0},{"major":202,"minor":0,"op":"Async","value":0},{"major":202,"minor":0,"op":"Total","value":0}],"io_queue_recursive":[],"io_service_time_recursive":[],"io_wait_time_recursive":[],"io_merged_recursive":[],"io_time_recursive":[],"sectors_recursive":[]}}` + var eventsResp = `{"status":"pull","id":"nginx:latest","time":1428620433}{"status":"create","id":"9b818c3b8291708fdcecd7c4086b75c222cb503be10a93d9c11040886032a48b","from":"nginx:latest","time":1428620433}{"status":"start","id":"9b818c3b8291708fdcecd7c4086b75c222cb503be10a93d9c11040886032a48b","from":"nginx:latest","time":1428620433}{"status":"die","id":"9b818c3b8291708fdcecd7c4086b75c222cb503be10a93d9c11040886032a48b","from":"nginx:latest","time":1428620442}{"status":"create","id":"352d0b412aae5a5d2b14ae9d88be59dc276602d9edb9dcc33e138e475b3e4720","from":"52.11.96.81/foobar/ubuntu:latest","time":1428620444}{"status":"start","id":"352d0b412aae5a5d2b14ae9d88be59dc276602d9edb9dcc33e138e475b3e4720","from":"52.11.96.81/foobar/ubuntu:latest","time":1428620444}{"status":"die","id":"352d0b412aae5a5d2b14ae9d88be59dc276602d9edb9dcc33e138e475b3e4720","from":"52.11.96.81/foobar/ubuntu:latest","time":1428620444}{"status":"pull","id":"debian:latest","time":1428620453}{"status":"create","id":"668887b5729946546b3072655dc6da08f0e3210111b68b704eb842adfce53f6c","from":"debian:latest","time":1428620453}{"status":"start","id":"668887b5729946546b3072655dc6da08f0e3210111b68b704eb842adfce53f6c","from":"debian:latest","time":1428620453}{"status":"die","id":"668887b5729946546b3072655dc6da08f0e3210111b68b704eb842adfce53f6c","from":"debian:latest","time":1428620453}{"status":"create","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620458}{"status":"start","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620458}{"status":"pause","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620462}{"status":"unpause","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620466}{"status":"die","id":"eb4a19ec21ab29bbbffbf3ee2e2df9d99cb749780e1eff06a591cee5ba505180","from":"nginx:latest","time":1428620469}` diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go index 45e1a2daf4..b373055e6f 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/interface.go @@ -16,6 +16,12 @@ type Client interface { CreateContainer(config *ContainerConfig, name string, authConfig *AuthConfig) (string, error) ContainerLogs(id string, options *LogOptions) (io.ReadCloser, error) ContainerChanges(id string) ([]*ContainerChanges, error) + // ContainerStats takes a container ID and an optional stop channel and + // returns a StatsOrError channel. If an error is ever sent, then no + // more stats will be sent on that channel. If a stop channel is + // provided, events will stop being monitored after the stop channel is + // closed. + ContainerStats(id string, stopChan <-chan struct{}) (<-chan StatsOrError, error) ExecCreate(config *ExecConfig) (string, error) ExecStart(id string, config *ExecConfig) error ExecResize(id string, width, height int) error @@ -42,6 +48,7 @@ type Client interface { RemoveContainer(id string, force, volumes bool) error ListImages(all bool) ([]*Image, error) RemoveImage(name string, force bool) ([]*ImageDelete, error) + SearchImages(query, registry string, auth *AuthConfig) ([]ImageSearch, error) PauseContainer(name string) error UnpauseContainer(name string) error RenameContainer(oldName string, newName 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 eff16d5787..9da36b876a 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/mockclient/mock.go @@ -50,6 +50,11 @@ func (client *MockClient) ContainerChanges(id string) ([]*dockerclient.Container return args.Get(0).([]*dockerclient.ContainerChanges), args.Error(1) } +func (client *MockClient) ContainerStats(id string, stopChan <-chan struct{}) (<-chan dockerclient.StatsOrError, error) { + args := client.Mock.Called(id, stopChan) + return args.Get(0).(<-chan dockerclient.StatsOrError), args.Error(1) +} + func (client *MockClient) AttachContainer(id string, options *dockerclient.AttachOptions) (io.ReadCloser, error) { args := client.Mock.Called(id, options) return args.Get(0).(io.ReadCloser), args.Error(1) @@ -141,6 +146,11 @@ func (client *MockClient) RemoveImage(name string, force bool) ([]*dockerclient. return args.Get(0).([]*dockerclient.ImageDelete), args.Error(1) } +func (client *MockClient) SearchImages(query, registry string, authConfig *dockerclient.AuthConfig) ([]dockerclient.ImageSearch, error) { + args := client.Mock.Called(query, registry, authConfig) + return args.Get(0).([]dockerclient.ImageSearch), args.Error(1) +} + func (client *MockClient) PauseContainer(name string) error { args := client.Mock.Called(name) 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 cea96a2807..659b57b791 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/nopclient/nop.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/nopclient/nop.go @@ -46,6 +46,10 @@ func (client *NopClient) ContainerChanges(id string) ([]*dockerclient.ContainerC return nil, ErrNoEngine } +func (client *NopClient) ContainerStats(id string, stopChan <-chan struct{}) (<-chan dockerclient.StatsOrError, error) { + return nil, ErrNoEngine +} + func (client *NopClient) AttachContainer(id string, options *dockerclient.AttachOptions) (io.ReadCloser, error) { return nil, ErrNoEngine } @@ -122,6 +126,10 @@ func (client *NopClient) RemoveImage(name string, force bool) ([]*dockerclient.I return nil, ErrNoEngine } +func (client *NopClient) SearchImages(query, registry string, authConfig *dockerclient.AuthConfig) ([]dockerclient.ImageSearch, error) { + return nil, ErrNoEngine +} + func (client *NopClient) PauseContainer(name 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 4474c2d2be..7ba7991580 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go @@ -49,46 +49,64 @@ type ContainerConfig struct { } type HostConfig struct { - Binds []string - ContainerIDFile string - LxcConf []map[string]string - Memory int64 - MemoryReservation int64 - MemorySwap int64 - KernelMemory int64 - CpuShares int64 - CpuPeriod int64 - CpusetCpus string - CpusetMems string - CpuQuota int64 - BlkioWeight int64 - OomKillDisable bool - MemorySwappiness int64 - Privileged bool - PortBindings map[string][]PortBinding - Links []string - PublishAllPorts bool - Dns []string - DNSOptions []string - DnsSearch []string - ExtraHosts []string - VolumesFrom []string - Devices []DeviceMapping - NetworkMode string - IpcMode string - PidMode string - UTSMode string - CapAdd []string - CapDrop []string - GroupAdd []string - RestartPolicy RestartPolicy - SecurityOpt []string - ReadonlyRootfs bool - Ulimits []Ulimit - LogConfig LogConfig - CgroupParent string - ConsoleSize [2]int - VolumeDriver string + Binds []string + ContainerIDFile string + LxcConf []map[string]string + Memory int64 + MemoryReservation int64 + MemorySwap int64 + KernelMemory int64 + CpuShares int64 + CpuPeriod int64 + CpusetCpus string + CpusetMems string + CpuQuota int64 + BlkioWeight int64 + OomKillDisable bool + MemorySwappiness int64 + Privileged bool + PortBindings map[string][]PortBinding + Links []string + PublishAllPorts bool + Dns []string + DNSOptions []string + DnsSearch []string + ExtraHosts []string + VolumesFrom []string + Devices []DeviceMapping + NetworkMode string + IpcMode string + PidMode string + UTSMode string + CapAdd []string + CapDrop []string + GroupAdd []string + RestartPolicy RestartPolicy + SecurityOpt []string + ReadonlyRootfs bool + Ulimits []Ulimit + LogConfig LogConfig + CgroupParent string + ConsoleSize [2]int + VolumeDriver string + OomScoreAdj int + Tmpfs map[string]string + ShmSize int64 + BlkioWeightDevice []WeightDevice + BlkioDeviceReadBps []ThrottleDevice + BlkioDeviceWriteBps []ThrottleDevice + BlkioDeviceReadIOps []ThrottleDevice + BlkioDeviceWriteIOps []ThrottleDevice +} + +type WeightDevice struct { + Path string + Weight uint16 +} + +type ThrottleDevice struct { + Path string + Rate uint64 } type DeviceMapping struct { @@ -220,6 +238,14 @@ type ImageInfo struct { VirtualSize int64 } +type ImageSearch struct { + Description string `json:"description,omitempty" yaml:"description,omitempty"` + IsOfficial bool `json:"is_official,omitempty" yaml:"is_official,omitempty"` + IsAutomated bool `json:"is_automated,omitempty" yaml:"is_automated,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + StarCount int `json:"star_count,omitempty" yaml:"star_count,omitempty"` +} + type ContainerInfo struct { Id string Created string @@ -381,6 +407,11 @@ type ImageDelete struct { Untagged string } +type StatsOrError struct { + Stats + Error error +} + type EventOrError struct { Event Error error @@ -406,6 +437,7 @@ type ThrottlingData struct { ThrottledTime uint64 `json:"throttled_time"` } +// All CPU stats are aggregated since container inception. type CpuUsage struct { // Total CPU time consumed. // Units: nanoseconds. diff --git a/test/integration/api/run.bats b/test/integration/api/run.bats index 6d3d21912e..ec2c6dd8db 100644 --- a/test/integration/api/run.bats +++ b/test/integration/api/run.bats @@ -99,6 +99,35 @@ function teardown() { [[ "${output}" == *"someDnsOption"* ]] # stop-signal [[ "${output}" == *"\"StopSignal\": \"SIGKILL\""* ]] + + # following options are introduced in docker 1.10, skip older version + run docker --version + if [[ "${output}" == "Docker version 1.9"* ]]; then + skip + fi + + docker_swarm run -d --name test_container2 \ + --oom-score-adj=350 \ + --tmpfs=/tempfs:rw \ + --device-read-iops=/dev/null:351 \ + --device-write-iops=/dev/null:352 \ + --device-read-bps=/dev/null:1mb \ + --device-write-bps=/dev/null:2mb \ + busybox sleep 1000 + + run docker_swarm inspect test_container2 + # oom-score-adj + [[ "${output}" == *"\"OomScoreAdj\": 350"* ]] + # tmpfs + [[ "${output}" == *"\"/tempfs\": \"rw\""* ]] + # device-read-iops + [[ "${output}" == *"351"* ]] + # device-write-iops + [[ "${output}" == *"352"* ]] + # device-read-bps + [[ "${output}" == *"1048576"* ]] + # device-write-bps + [[ "${output}" == *"2097152"* ]] } @test "docker run --ip" {