From 7fed7d7eb4d7766d9342821f2667d160c5f958eb Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 24 Feb 2015 10:47:47 -0800 Subject: [PATCH 1/2] Move stats api types into api/types package Move the stats structs from the api/stats package into a new package api/types that will contain all the api structs for docker's api request and responses. Signed-off-by: Michael Crosby --- api/client/commands.go | 6 ++--- api/{stats => types}/stats.go | 2 +- daemon/stats.go | 24 +++++++++---------- integration-cli/docker_api_containers_test.go | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) rename api/{stats => types}/stats.go (99%) diff --git a/api/client/commands.go b/api/client/commands.go index 9776936506..8530c02285 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -26,7 +26,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/docker/api" - "github.com/docker/docker/api/stats" + "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/engine" "github.com/docker/docker/graph" @@ -2640,7 +2640,7 @@ func (s *containerStats) Collect(cli *DockerCli) { ) go func() { for { - var v *stats.Stats + var v *types.Stats if err := dec.Decode(&v); err != nil { u <- err return @@ -2757,7 +2757,7 @@ func (cli *DockerCli) CmdStats(args ...string) error { return nil } -func calculateCpuPercent(previousCpu, previousSystem uint64, v *stats.Stats) float64 { +func calculateCpuPercent(previousCpu, previousSystem uint64, v *types.Stats) float64 { var ( cpuPercent = 0.0 // calculate the change for the cpu usage of the container in between readings diff --git a/api/stats/stats.go b/api/types/stats.go similarity index 99% rename from api/stats/stats.go rename to api/types/stats.go index 8edf18fe0e..97804e95e6 100644 --- a/api/stats/stats.go +++ b/api/types/stats.go @@ -1,6 +1,6 @@ // This package is used for API stability in the types and response to the // consumers of the API stats endpoint. -package stats +package types import "time" diff --git a/daemon/stats.go b/daemon/stats.go index e047497ec6..b36b28ae72 100644 --- a/daemon/stats.go +++ b/daemon/stats.go @@ -3,7 +3,7 @@ package daemon import ( "encoding/json" - "github.com/docker/docker/api/stats" + "github.com/docker/docker/api/types" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/engine" "github.com/docker/libcontainer" @@ -33,10 +33,10 @@ func (daemon *Daemon) ContainerStats(job *engine.Job) engine.Status { // convertToAPITypes converts the libcontainer.ContainerStats to the api specific // structs. This is done to preserve API compatibility and versioning. -func convertToAPITypes(ls *libcontainer.ContainerStats) *stats.Stats { - s := &stats.Stats{} +func convertToAPITypes(ls *libcontainer.ContainerStats) *types.Stats { + s := &types.Stats{} if ls.NetworkStats != nil { - s.Network = stats.Network{ + s.Network = types.Network{ RxBytes: ls.NetworkStats.RxBytes, RxPackets: ls.NetworkStats.RxPackets, RxErrors: ls.NetworkStats.RxErrors, @@ -49,7 +49,7 @@ func convertToAPITypes(ls *libcontainer.ContainerStats) *stats.Stats { } cs := ls.CgroupStats if cs != nil { - s.BlkioStats = stats.BlkioStats{ + s.BlkioStats = types.BlkioStats{ IoServiceBytesRecursive: copyBlkioEntry(cs.BlkioStats.IoServiceBytesRecursive), IoServicedRecursive: copyBlkioEntry(cs.BlkioStats.IoServicedRecursive), IoQueuedRecursive: copyBlkioEntry(cs.BlkioStats.IoQueuedRecursive), @@ -60,21 +60,21 @@ func convertToAPITypes(ls *libcontainer.ContainerStats) *stats.Stats { SectorsRecursive: copyBlkioEntry(cs.BlkioStats.SectorsRecursive), } cpu := cs.CpuStats - s.CpuStats = stats.CpuStats{ - CpuUsage: stats.CpuUsage{ + s.CpuStats = types.CpuStats{ + CpuUsage: types.CpuUsage{ TotalUsage: cpu.CpuUsage.TotalUsage, PercpuUsage: cpu.CpuUsage.PercpuUsage, UsageInKernelmode: cpu.CpuUsage.UsageInKernelmode, UsageInUsermode: cpu.CpuUsage.UsageInUsermode, }, - ThrottlingData: stats.ThrottlingData{ + ThrottlingData: types.ThrottlingData{ Periods: cpu.ThrottlingData.Periods, ThrottledPeriods: cpu.ThrottlingData.ThrottledPeriods, ThrottledTime: cpu.ThrottlingData.ThrottledTime, }, } mem := cs.MemoryStats - s.MemoryStats = stats.MemoryStats{ + s.MemoryStats = types.MemoryStats{ Usage: mem.Usage, MaxUsage: mem.MaxUsage, Stats: mem.Stats, @@ -84,10 +84,10 @@ func convertToAPITypes(ls *libcontainer.ContainerStats) *stats.Stats { return s } -func copyBlkioEntry(entries []cgroups.BlkioStatEntry) []stats.BlkioStatEntry { - out := make([]stats.BlkioStatEntry, len(entries)) +func copyBlkioEntry(entries []cgroups.BlkioStatEntry) []types.BlkioStatEntry { + out := make([]types.BlkioStatEntry, len(entries)) for i, re := range entries { - out[i] = stats.BlkioStatEntry{ + out[i] = types.BlkioStatEntry{ Major: re.Major, Minor: re.Minor, Op: re.Op, diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 50c92c5149..b1477212a9 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/docker/docker/api/stats" + "github.com/docker/docker/api/types" "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) @@ -311,7 +311,7 @@ func TestGetContainerStats(t *testing.T) { } dec := json.NewDecoder(bytes.NewBuffer(sr.body)) - var s *stats.Stats + var s *types.Stats // decode only one object from the stream if err := dec.Decode(&s); err != nil { t.Fatal(err) From f57c26553b91e16fa3ee6a815943eaf8f29af82b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 24 Feb 2015 11:12:47 -0800 Subject: [PATCH 2/2] Add ContainerCreateResponse type This type is produced on the server side and is a type safe struct that can be encoded to json. It is consumed via the client. Signed-off-by: Michael Crosby --- api/client/commands.go | 55 +++++++++++++++--------------------------- api/server/server.go | 37 +++++++++++++++++----------- api/types/types.go | 11 +++++++++ 3 files changed, 53 insertions(+), 50 deletions(-) create mode 100644 api/types/types.go diff --git a/api/client/commands.go b/api/client/commands.go index 8530c02285..9710f0c563 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -2120,7 +2120,7 @@ func (cid *cidFile) Write(id string) error { return nil } -func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runconfig.HostConfig, cidfile, name string) (engine.Env, error) { +func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runconfig.HostConfig, cidfile, name string) (*types.ContainerCreateResponse, error) { containerValues := url.Values{} if name != "" { containerValues.Set("name", name) @@ -2159,23 +2159,19 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc return nil, err } - var result engine.Env - if err := result.Decode(stream); err != nil { + var response types.ContainerCreateResponse + if err := json.NewDecoder(stream).Decode(&response); err != nil { return nil, err } - - for _, warning := range result.GetList("Warnings") { + for _, warning := range response.Warnings { fmt.Fprintf(cli.err, "WARNING: %s\n", warning) } - if containerIDFile != nil { - if err = containerIDFile.Write(result.Get("Id")); err != nil { + if err = containerIDFile.Write(response.ID); err != nil { return nil, err } } - - return result, nil - + return &response, nil } func (cli *DockerCli) CmdCreate(args ...string) error { @@ -2194,14 +2190,11 @@ func (cli *DockerCli) CmdCreate(args ...string) error { cmd.Usage() return nil } - - createResult, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) + response, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) if err != nil { return err } - - fmt.Fprintf(cli.out, "%s\n", createResult.Get("Id")) - + fmt.Fprintf(cli.out, "%s\n", response.ID) return nil } @@ -2259,38 +2252,32 @@ func (cli *DockerCli) CmdRun(args ...string) error { sigProxy = false } - runResult, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) + createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) if err != nil { return err } - if sigProxy { - sigc := cli.forwardAllSignals(runResult.Get("Id")) + sigc := cli.forwardAllSignals(createResponse.ID) defer signal.StopCatch(sigc) } - var ( waitDisplayId chan struct{} errCh chan error ) - if !config.AttachStdout && !config.AttachStderr { // Make this asynchronous to allow the client to write to stdin before having to read the ID waitDisplayId = make(chan struct{}) go func() { defer close(waitDisplayId) - fmt.Fprintf(cli.out, "%s\n", runResult.Get("Id")) + fmt.Fprintf(cli.out, "%s\n", createResponse.ID) }() } - if *flAutoRemove && (hostConfig.RestartPolicy.Name == "always" || hostConfig.RestartPolicy.Name == "on-failure") { return ErrConflictRestartPolicyAndAutoRemove } - // We need to instantiate the chan because the select needs it. It can // be closed but can't be uninitialized. hijacked := make(chan io.Closer) - // Block the return until the chan gets closed defer func() { log.Debugf("End of CmdRun(), Waiting for hijack to finish.") @@ -2298,7 +2285,6 @@ func (cli *DockerCli) CmdRun(args ...string) error { log.Errorf("Hijack did not finish (chan still open)") } }() - if config.AttachStdin || config.AttachStdout || config.AttachStderr { var ( out, stderr io.Writer @@ -2306,7 +2292,6 @@ func (cli *DockerCli) CmdRun(args ...string) error { v = url.Values{} ) v.Set("stream", "1") - if config.AttachStdin { v.Set("stdin", "1") in = cli.in @@ -2323,14 +2308,12 @@ func (cli *DockerCli) CmdRun(args ...string) error { stderr = cli.err } } - errCh = promise.Go(func() error { - return cli.hijack("POST", "/containers/"+runResult.Get("Id")+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil) + return cli.hijack("POST", "/containers/"+createResponse.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil) }) } else { close(hijacked) } - // Acknowledge the hijack before starting select { case closer := <-hijacked: @@ -2347,12 +2330,12 @@ func (cli *DockerCli) CmdRun(args ...string) error { } //start the container - if _, _, err = readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/start", nil, false)); err != nil { + if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, false)); err != nil { return err } if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut { - if err := cli.monitorTtySize(runResult.Get("Id"), false); err != nil { + if err := cli.monitorTtySize(createResponse.ID, false); err != nil { log.Errorf("Error monitoring TTY size: %s", err) } } @@ -2377,26 +2360,26 @@ func (cli *DockerCli) CmdRun(args ...string) error { if *flAutoRemove { // Autoremove: wait for the container to finish, retrieve // the exit code and remove the container - if _, _, err := readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/wait", nil, false)); err != nil { + if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, false)); err != nil { return err } - if _, status, err = getExitCode(cli, runResult.Get("Id")); err != nil { + if _, status, err = getExitCode(cli, createResponse.ID); err != nil { return err } - if _, _, err := readBody(cli.call("DELETE", "/containers/"+runResult.Get("Id")+"?v=1", nil, false)); err != nil { + if _, _, err := readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, false)); err != nil { return err } } else { // No Autoremove: Simply retrieve the exit code if !config.Tty { // In non-TTY mode, we can't detach, so we must wait for container exit - if status, err = waitForExit(cli, runResult.Get("Id")); err != nil { + if status, err = waitForExit(cli, createResponse.ID); err != nil { return err } } else { // In TTY mode, there is a race: if the process dies too slowly, the state could // be updated after the getExitCode call and result in the wrong exit code being reported - if _, status, err = getExitCode(cli, runResult.Get("Id")); err != nil { + if _, status, err = getExitCode(cli, createResponse.ID); err != nil { return err } } diff --git a/api/server/server.go b/api/server/server.go index 78ca4b7829..02ff0375a0 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -27,6 +27,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/docker/api" + "github.com/docker/docker/api/types" "github.com/docker/docker/daemon/networkdriver/portallocator" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/listenbuffer" @@ -140,12 +141,22 @@ func httpError(w http.ResponseWriter, err error) { } } -func writeJSON(w http.ResponseWriter, code int, v engine.Env) error { +// writeJSONEnv writes the engine.Env values to the http response stream as a +// json encoded body. +func writeJSONEnv(w http.ResponseWriter, code int, v engine.Env) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) return v.Encode(w) } +// writeJSON writes the value v to the http response stream as json with standard +// json encoding. +func writeJSON(w http.ResponseWriter, code int, v interface{}) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + return json.NewEncoder(w).Encode(v) +} + func streamJSON(job *engine.Job, w http.ResponseWriter, flush bool) { w.Header().Set("Content-Type", "application/json") if flush { @@ -183,7 +194,7 @@ func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter if status := engine.Tail(stdoutBuffer, 1); status != "" { var env engine.Env env.Set("Status", status) - return writeJSON(w, http.StatusOK, env) + return writeJSONEnv(w, http.StatusOK, env) } w.WriteHeader(http.StatusNoContent) return nil @@ -525,7 +536,7 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit return err } env.Set("Id", engine.Tail(stdoutBuffer, 1)) - return writeJSON(w, http.StatusCreated, env) + return writeJSONEnv(w, http.StatusCreated, env) } // Creates an image from Pull or from Import @@ -703,18 +714,16 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re if err := parseForm(r); err != nil { return nil } + if err := checkForJson(r); err != nil { + return err + } var ( - out engine.Env job = eng.Job("create", r.Form.Get("name")) outWarnings []string stdoutBuffer = bytes.NewBuffer(nil) warnings = bytes.NewBuffer(nil) ) - if err := checkForJson(r); err != nil { - return err - } - if err := job.DecodeEnv(r.Body); err != nil { return err } @@ -730,10 +739,10 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re for scanner.Scan() { outWarnings = append(outWarnings, scanner.Text()) } - out.Set("Id", engine.Tail(stdoutBuffer, 1)) - out.SetList("Warnings", outWarnings) - - return writeJSON(w, http.StatusCreated, out) + return writeJSON(w, http.StatusCreated, &types.ContainerCreateResponse{ + ID: engine.Tail(stdoutBuffer, 1), + Warnings: outWarnings, + }) } func postContainersRestart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { @@ -876,7 +885,7 @@ func postContainersWait(eng *engine.Engine, version version.Version, w http.Resp } env.Set("StatusCode", engine.Tail(stdoutBuffer, 1)) - return writeJSON(w, http.StatusOK, env) + return writeJSONEnv(w, http.StatusOK, env) } func postContainersResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { @@ -1147,7 +1156,7 @@ func postContainerExecCreate(eng *engine.Engine, version version.Version, w http // Return the ID out.Set("Id", engine.Tail(stdoutBuffer, 1)) - return writeJSON(w, http.StatusCreated, out) + return writeJSONEnv(w, http.StatusCreated, out) } // TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start. diff --git a/api/types/types.go b/api/types/types.go new file mode 100644 index 0000000000..f1b1d041ea --- /dev/null +++ b/api/types/types.go @@ -0,0 +1,11 @@ +package types + +// ContainerCreateResponse contains the information returned to a client on the +// creation of a new container. +type ContainerCreateResponse struct { + // ID is the ID of the created container. + ID string `json:"Id"` + + // Warnings are any warnings encountered during the creation of the container. + Warnings []string `json:"Warnings"` +}