Browse code

Add "Warnings" to /info endpoint, and move detection to the daemon

When requesting information about the daemon's configuration through the `/info`
endpoint, missing features (or non-recommended settings) may have to be presented
to the user.

Detecting these situations, and printing warnings currently is handled by the
cli, which results in some complications:

- duplicated effort: each client has to re-implement detection and warnings.
- it's not possible to generate warnings for reasons outside of the information
returned in the `/info` response.
- cli-side detection has to be updated for new conditions. This means that an
older cli connecting to a new daemon may not print all warnings (due to
it not detecting the new conditions)
- some warnings (in particular, warnings about storage-drivers) depend on
driver-status (`DriverStatus`) information. The format of the information
returned in this field is not part of the API specification and can change
over time, resulting in cli-side detection no longer being functional.

This patch adds a new `Warnings` field to the `/info` response. This field is
to return warnings to be presented by the user.

Existing warnings that are currently handled by the CLI are copied to the daemon
as part of this patch; This change is backward-compatible with existing
clients; old client can continue to use the client-side warnings, whereas new
clients can skip client-side detection, and print warnings that are returned by
the daemon.

Example response with this patch applied;

```bash
curl --unix-socket /var/run/docker.sock http://localhost/info | jq .Warnings
```

```json
[
"WARNING: bridge-nf-call-iptables is disabled",
"WARNING: bridge-nf-call-ip6tables is disabled"
]
```

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2018/07/19 20:45:32
Showing 6 changed files
... ...
@@ -3904,6 +3904,19 @@ definitions:
3904 3904
           such as number of nodes, and expiration are included.
3905 3905
         type: "string"
3906 3906
         example: "Community Engine"
3907
+      Warnings:
3908
+        description: |
3909
+          List of warnings / informational messages about missing features, or
3910
+          issues related to the daemon configuration.
3911
+
3912
+          These messages can be printed by the client as information to the user.
3913
+        type: "array"
3914
+        items:
3915
+          type: "string"
3916
+        example:
3917
+          - "WARNING: No memory limit support"
3918
+          - "WARNING: bridge-nf-call-iptables is disabled"
3919
+          - "WARNING: bridge-nf-call-ip6tables is disabled"
3907 3920
 
3908 3921
 
3909 3922
   # PluginsInfo is a temp struct holding Plugins name
... ...
@@ -206,6 +206,7 @@ type Info struct {
206 206
 	InitCommit         Commit
207 207
 	SecurityOptions    []string
208 208
 	ProductLicense     string `json:",omitempty"`
209
+	Warnings           []string
209 210
 }
210 211
 
211 212
 // KeyValue holds a key/value pair
... ...
@@ -134,6 +134,8 @@ func (daemon *Daemon) fillDriverInfo(v *types.Info) {
134 134
 
135 135
 	v.Driver = drivers
136 136
 	v.DriverStatus = ds
137
+
138
+	fillDriverWarnings(v)
137 139
 }
138 140
 
139 141
 func (daemon *Daemon) fillPluginsInfo(v *types.Info) {
... ...
@@ -4,6 +4,7 @@ package daemon // import "github.com/docker/docker/daemon"
4 4
 
5 5
 import (
6 6
 	"context"
7
+	"fmt"
7 8
 	"os/exec"
8 9
 	"strings"
9 10
 
... ...
@@ -68,6 +69,80 @@ func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo)
68 68
 		logrus.Warnf("failed to retrieve %s version: %s", defaultInitBinary, err)
69 69
 		v.InitCommit.ID = "N/A"
70 70
 	}
71
+
72
+	if !v.MemoryLimit {
73
+		v.Warnings = append(v.Warnings, "WARNING: No memory limit support")
74
+	}
75
+	if !v.SwapLimit {
76
+		v.Warnings = append(v.Warnings, "WARNING: No swap limit support")
77
+	}
78
+	if !v.KernelMemory {
79
+		v.Warnings = append(v.Warnings, "WARNING: No kernel memory limit support")
80
+	}
81
+	if !v.OomKillDisable {
82
+		v.Warnings = append(v.Warnings, "WARNING: No oom kill disable support")
83
+	}
84
+	if !v.CPUCfsQuota {
85
+		v.Warnings = append(v.Warnings, "WARNING: No cpu cfs quota support")
86
+	}
87
+	if !v.CPUCfsPeriod {
88
+		v.Warnings = append(v.Warnings, "WARNING: No cpu cfs period support")
89
+	}
90
+	if !v.CPUShares {
91
+		v.Warnings = append(v.Warnings, "WARNING: No cpu shares support")
92
+	}
93
+	if !v.CPUSet {
94
+		v.Warnings = append(v.Warnings, "WARNING: No cpuset support")
95
+	}
96
+	if !v.IPv4Forwarding {
97
+		v.Warnings = append(v.Warnings, "WARNING: IPv4 forwarding is disabled")
98
+	}
99
+	if !v.BridgeNfIptables {
100
+		v.Warnings = append(v.Warnings, "WARNING: bridge-nf-call-iptables is disabled")
101
+	}
102
+	if !v.BridgeNfIP6tables {
103
+		v.Warnings = append(v.Warnings, "WARNING: bridge-nf-call-ip6tables is disabled")
104
+	}
105
+}
106
+
107
+func fillDriverWarnings(v *types.Info) {
108
+	if v.DriverStatus == nil {
109
+		return
110
+	}
111
+	for _, pair := range v.DriverStatus {
112
+		if pair[0] == "Data loop file" {
113
+			msg := fmt.Sprintf("WARNING: %s: usage of loopback devices is "+
114
+				"strongly discouraged for production use.\n         "+
115
+				"Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.", v.Driver)
116
+
117
+			v.Warnings = append(v.Warnings, msg)
118
+			continue
119
+		}
120
+		if pair[0] == "Supports d_type" && pair[1] == "false" {
121
+			backingFs := getBackingFs(v)
122
+
123
+			msg := fmt.Sprintf("WARNING: %s: the backing %s filesystem is formatted without d_type support, which leads to incorrect behavior.\n", v.Driver, backingFs)
124
+			if backingFs == "xfs" {
125
+				msg += "         Reformat the filesystem with ftype=1 to enable d_type support.\n"
126
+			}
127
+			msg += "         Running without d_type support will not be supported in future releases."
128
+
129
+			v.Warnings = append(v.Warnings, msg)
130
+			continue
131
+		}
132
+	}
133
+}
134
+
135
+func getBackingFs(v *types.Info) string {
136
+	if v.DriverStatus == nil {
137
+		return ""
138
+	}
139
+	for _, pair := range v.DriverStatus {
140
+		if pair[0] == "Backing Filesystem" {
141
+			return pair[1]
142
+		}
143
+	}
144
+	return ""
71 145
 }
72 146
 
73 147
 // parseInitVersion parses a Tini version string, and extracts the version.
... ...
@@ -8,3 +8,6 @@ import (
8 8
 // fillPlatformInfo fills the platform related info.
9 9
 func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo) {
10 10
 }
11
+
12
+func fillDriverWarnings(v *types.Info) {
13
+}
... ...
@@ -21,6 +21,8 @@ keywords: "API, Docker, rcli, REST, documentation"
21 21
   and `OperatingSystem` if the daemon was unable to obtain this information.
22 22
 * `GET /info` now returns information about the product license, if a license
23 23
   has been applied to the daemon.
24
+* `GET /info` now returns a `Warnings` field, containing warnings and informational
25
+  messages about missing features, or issues related to the daemon configuration.
24 26
 * `POST /swarm/init` now accepts a `DefaultAddrPool` property to set global scope default address pool
25 27
 * `POST /swarm/init` now accepts a `SubnetSize` property to set global scope networks by giving the
26 28
   length of the subnet masks for every such network