diff --git a/components/engine/api/swagger.yaml b/components/engine/api/swagger.yaml index 672b598929..3906899050 100644 --- a/components/engine/api/swagger.yaml +++ b/components/engine/api/swagger.yaml @@ -3904,6 +3904,19 @@ definitions: such as number of nodes, and expiration are included. type: "string" example: "Community Engine" + Warnings: + description: | + List of warnings / informational messages about missing features, or + issues related to the daemon configuration. + + These messages can be printed by the client as information to the user. + type: "array" + items: + type: "string" + example: + - "WARNING: No memory limit support" + - "WARNING: bridge-nf-call-iptables is disabled" + - "WARNING: bridge-nf-call-ip6tables is disabled" # PluginsInfo is a temp struct holding Plugins name diff --git a/components/engine/api/types/types.go b/components/engine/api/types/types.go index ae9e27dca1..ed62fd41e5 100644 --- a/components/engine/api/types/types.go +++ b/components/engine/api/types/types.go @@ -206,6 +206,7 @@ type Info struct { InitCommit Commit SecurityOptions []string ProductLicense string `json:",omitempty"` + Warnings []string } // KeyValue holds a key/value pair diff --git a/components/engine/daemon/info.go b/components/engine/daemon/info.go index 3575fdda38..cc9ad8ac61 100644 --- a/components/engine/daemon/info.go +++ b/components/engine/daemon/info.go @@ -134,6 +134,8 @@ func (daemon *Daemon) fillDriverInfo(v *types.Info) { v.Driver = drivers v.DriverStatus = ds + + fillDriverWarnings(v) } func (daemon *Daemon) fillPluginsInfo(v *types.Info) { diff --git a/components/engine/daemon/info_unix.go b/components/engine/daemon/info_unix.go index 864e8816a9..98935cca70 100644 --- a/components/engine/daemon/info_unix.go +++ b/components/engine/daemon/info_unix.go @@ -4,6 +4,7 @@ package daemon // import "github.com/docker/docker/daemon" import ( "context" + "fmt" "os/exec" "strings" @@ -68,6 +69,80 @@ func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo) logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err) v.InitCommit.ID = "N/A" } + + if !v.MemoryLimit { + v.Warnings = append(v.Warnings, "WARNING: No memory limit support") + } + if !v.SwapLimit { + v.Warnings = append(v.Warnings, "WARNING: No swap limit support") + } + if !v.KernelMemory { + v.Warnings = append(v.Warnings, "WARNING: No kernel memory limit support") + } + if !v.OomKillDisable { + v.Warnings = append(v.Warnings, "WARNING: No oom kill disable support") + } + if !v.CPUCfsQuota { + v.Warnings = append(v.Warnings, "WARNING: No cpu cfs quota support") + } + if !v.CPUCfsPeriod { + v.Warnings = append(v.Warnings, "WARNING: No cpu cfs period support") + } + if !v.CPUShares { + v.Warnings = append(v.Warnings, "WARNING: No cpu shares support") + } + if !v.CPUSet { + v.Warnings = append(v.Warnings, "WARNING: No cpuset support") + } + if !v.IPv4Forwarding { + v.Warnings = append(v.Warnings, "WARNING: IPv4 forwarding is disabled") + } + if !v.BridgeNfIptables { + v.Warnings = append(v.Warnings, "WARNING: bridge-nf-call-iptables is disabled") + } + if !v.BridgeNfIP6tables { + v.Warnings = append(v.Warnings, "WARNING: bridge-nf-call-ip6tables is disabled") + } +} + +func fillDriverWarnings(v *types.Info) { + if v.DriverStatus == nil { + return + } + for _, pair := range v.DriverStatus { + if pair[0] == "Data loop file" { + msg := fmt.Sprintf("WARNING: %s: usage of loopback devices is "+ + "strongly discouraged for production use.\n "+ + "Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.", v.Driver) + + v.Warnings = append(v.Warnings, msg) + continue + } + if pair[0] == "Supports d_type" && pair[1] == "false" { + backingFs := getBackingFs(v) + + msg := fmt.Sprintf("WARNING: %s: the backing %s filesystem is formatted without d_type support, which leads to incorrect behavior.\n", v.Driver, backingFs) + if backingFs == "xfs" { + msg += " Reformat the filesystem with ftype=1 to enable d_type support.\n" + } + msg += " Running without d_type support will not be supported in future releases." + + v.Warnings = append(v.Warnings, msg) + continue + } + } +} + +func getBackingFs(v *types.Info) string { + if v.DriverStatus == nil { + return "" + } + for _, pair := range v.DriverStatus { + if pair[0] == "Backing Filesystem" { + return pair[1] + } + } + return "" } // parseInitVersion parses a Tini version string, and extracts the version. diff --git a/components/engine/daemon/info_windows.go b/components/engine/daemon/info_windows.go index bf97971479..2c1ff460c3 100644 --- a/components/engine/daemon/info_windows.go +++ b/components/engine/daemon/info_windows.go @@ -8,3 +8,6 @@ import ( // fillPlatformInfo fills the platform related info. func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo) { } + +func fillDriverWarnings(v *types.Info) { +} diff --git a/components/engine/docs/api/version-history.md b/components/engine/docs/api/version-history.md index 7c18799fc5..6f0083e90e 100644 --- a/components/engine/docs/api/version-history.md +++ b/components/engine/docs/api/version-history.md @@ -21,6 +21,8 @@ keywords: "API, Docker, rcli, REST, documentation" and `OperatingSystem` if the daemon was unable to obtain this information. * `GET /info` now returns information about the product license, if a license has been applied to the daemon. +* `GET /info` now returns a `Warnings` field, containing warnings and informational + messages about missing features, or issues related to the daemon configuration. * `POST /swarm/init` now accepts a `DefaultAddrPool` property to set global scope default address pool * `POST /swarm/init` now accepts a `SubnetSize` property to set global scope networks by giving the length of the subnet masks for every such network diff --git a/components/engine/integration-cli/docker_cli_exec_test.go b/components/engine/integration-cli/docker_cli_exec_test.go index 2f72eb90e6..72059c79e6 100644 --- a/components/engine/integration-cli/docker_cli_exec_test.go +++ b/components/engine/integration-cli/docker_cli_exec_test.go @@ -11,6 +11,7 @@ import ( "reflect" "runtime" "sort" + "strconv" "strings" "sync" "time" @@ -19,6 +20,7 @@ import ( "github.com/docker/docker/integration-cli/checker" "github.com/docker/docker/integration-cli/cli" "github.com/docker/docker/integration-cli/cli/build" + "github.com/docker/docker/pkg/parsers/kernel" "github.com/go-check/check" "gotest.tools/icmd" ) @@ -543,6 +545,38 @@ func (s *DockerSuite) TestExecEnvLinksHost(c *check.C) { func (s *DockerSuite) TestExecWindowsOpenHandles(c *check.C) { testRequires(c, DaemonIsWindows) + + if runtime.GOOS == "windows" { + v, err := kernel.GetKernelVersion() + c.Assert(err, checker.IsNil) + build, _ := strconv.Atoi(strings.Split(strings.SplitN(v.String(), " ", 3)[2][1:], ".")[0]) + if build >= 17743 { + c.Skip("Temporarily disabled on RS5 17743+ builds due to platform bug") + + // This is being tracked internally. @jhowardmsft. Summary of failure + // from an email in early July 2018 below: + // + // Platform regression. In cmd.exe by the look of it. I can repro + // it outside of CI. It fails the same on 17681, 17676 and even as + // far back as 17663, over a month old. From investigating, I can see + // what's happening in the container, but not the reason. The test + // starts a long-running container based on the Windows busybox image. + // It then adds another process (docker exec) to that container to + // sleep. It loops waiting for two instances of busybox.exe running, + // and cmd.exe to quit. What's actually happening is that the second + // exec hangs indefinitely, and from docker top, I can see + // "OpenWith.exe" running. + + //Manual repro would be + //# Start the first long-running container + //docker run --rm -d --name test busybox sleep 300 + + //# In another window, docker top test. There should be a single instance of busybox.exe running + //# In a third window, docker exec test cmd /c start sleep 10 NOTE THIS HANGS UNTIL 5 MIN TIMEOUT + //# In the second window, run docker top test. Note that OpenWith.exe is running, one cmd.exe and only one busybox. I would expect no "OpenWith" and two busybox.exe's. + } + } + runSleepingContainer(c, "-d", "--name", "test") exec := make(chan bool) go func() {