mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Adding OS version info to the nodes' Info
struct
This is needed so that we can add OS version constraints in Swarmkit, which does require the engine to report its host's OS version (see https://github.com/docker/swarmkit/issues/2770). The OS version is parsed from the `os-release` file on Linux, and from the `ReleaseId` string value of the `SOFTWARE\Microsoft\Windows NT\CurrentVersion` registry key on Windows. Added unit tests when possible, as well as Prometheus metrics. Signed-off-by: Jean Rouge <rougej+github@gmail.com>
This commit is contained in:
parent
8d760280a2
commit
d363a1881e
9 changed files with 207 additions and 95 deletions
|
@ -3840,6 +3840,17 @@ definitions:
|
|||
or "Windows Server 2016 Datacenter"
|
||||
type: "string"
|
||||
example: "Alpine Linux v3.5"
|
||||
OSVersion:
|
||||
description: |
|
||||
Version of the host's operating system
|
||||
|
||||
<p><br /></p>
|
||||
|
||||
> **Note**: The information returned in this field, including its
|
||||
> very existence, and the formatting of values, should not be considered
|
||||
> stable, and may change without notice.
|
||||
type: "string"
|
||||
example: "16.04"
|
||||
OSType:
|
||||
description: |
|
||||
Generic type of the operating system of the host, as returned by the
|
||||
|
|
|
@ -177,6 +177,7 @@ type Info struct {
|
|||
NEventsListener int
|
||||
KernelVersion string
|
||||
OperatingSystem string
|
||||
OSVersion string
|
||||
OSType string
|
||||
Architecture string
|
||||
IndexServerAddress string
|
||||
|
|
|
@ -1061,6 +1061,7 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
|
|||
info.KernelVersion,
|
||||
info.OperatingSystem,
|
||||
info.OSType,
|
||||
info.OSVersion,
|
||||
info.ID,
|
||||
).Set(1)
|
||||
engineCpus.Set(float64(info.NCPU))
|
||||
|
|
|
@ -21,11 +21,14 @@ import (
|
|||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/go-connections/sockets"
|
||||
"github.com/docker/go-metrics"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// SystemInfo returns information about the host server the daemon is running on.
|
||||
func (daemon *Daemon) SystemInfo() (*types.Info, error) {
|
||||
defer metrics.StartTimer(hostInfoFunctions.WithValues("system_info"))()
|
||||
|
||||
sysInfo := sysinfo.New(true)
|
||||
cRunning, cPaused, cStopped := stateCtr.get()
|
||||
|
||||
|
@ -49,6 +52,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
|
|||
NEventsListener: daemon.EventsService.SubscribersCount(),
|
||||
KernelVersion: kernelVersion(),
|
||||
OperatingSystem: operatingSystem(),
|
||||
OSVersion: osVersion(),
|
||||
IndexServerAddress: registry.IndexServer,
|
||||
OSType: platform.OSType,
|
||||
Architecture: platform.Architecture,
|
||||
|
@ -82,6 +86,8 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
|
|||
|
||||
// SystemVersion returns version information about the daemon.
|
||||
func (daemon *Daemon) SystemVersion() types.Version {
|
||||
defer metrics.StartTimer(hostInfoFunctions.WithValues("system_version"))()
|
||||
|
||||
kernelVersion := kernelVersion()
|
||||
|
||||
v := types.Version{
|
||||
|
@ -240,8 +246,9 @@ func memInfo() *system.MemInfo {
|
|||
return memInfo
|
||||
}
|
||||
|
||||
func operatingSystem() string {
|
||||
var operatingSystem string
|
||||
func operatingSystem() (operatingSystem string) {
|
||||
defer metrics.StartTimer(hostInfoFunctions.WithValues("operating_system"))()
|
||||
|
||||
if s, err := operatingsystem.GetOperatingSystem(); err != nil {
|
||||
logrus.Warnf("Could not get operating system name: %v", err)
|
||||
} else {
|
||||
|
@ -256,9 +263,21 @@ func operatingSystem() string {
|
|||
operatingSystem += " (containerized)"
|
||||
}
|
||||
}
|
||||
|
||||
return operatingSystem
|
||||
}
|
||||
|
||||
func osVersion() (version string) {
|
||||
defer metrics.StartTimer(hostInfoFunctions.WithValues("os_version"))()
|
||||
|
||||
version, err := operatingsystem.GetOperatingSystemVersion()
|
||||
if err != nil {
|
||||
logrus.Warnf("Could not get operating system version: %v", err)
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
func maskCredentials(rawURL string) string {
|
||||
parsedURL, err := url.Parse(rawURL)
|
||||
if err != nil || parsedURL.User == nil {
|
||||
|
|
|
@ -17,6 +17,7 @@ const metricsPluginType = "MetricsCollector"
|
|||
var (
|
||||
containerActions metrics.LabeledTimer
|
||||
networkActions metrics.LabeledTimer
|
||||
hostInfoFunctions metrics.LabeledTimer
|
||||
engineInfo metrics.LabeledGauge
|
||||
engineCpus metrics.Gauge
|
||||
engineMemory metrics.Gauge
|
||||
|
@ -38,6 +39,7 @@ func init() {
|
|||
} {
|
||||
containerActions.WithValues(a).Update(0)
|
||||
}
|
||||
hostInfoFunctions = ns.NewLabeledTimer("host_info_functions", "The number of seconds it takes to call functions gathering info about the host", "function")
|
||||
|
||||
networkActions = ns.NewLabeledTimer("network_actions", "The number of seconds it takes to process each network action", "action")
|
||||
engineInfo = ns.NewLabeledGauge("engine", "The information related to the engine and the OS it is running on", metrics.Unit("info"),
|
||||
|
@ -45,8 +47,10 @@ func init() {
|
|||
"commit",
|
||||
"architecture",
|
||||
"graphdriver",
|
||||
"kernel", "os",
|
||||
"kernel",
|
||||
"os",
|
||||
"os_type",
|
||||
"os_version",
|
||||
"daemon_id", // ID is a randomly generated unique identifier (e.g. UUID4)
|
||||
)
|
||||
engineCpus = ns.NewGauge("engine_cpus", "The number of cpus that the host system of the engine has", metrics.Unit("cpus"))
|
||||
|
|
|
@ -26,6 +26,24 @@ var (
|
|||
|
||||
// GetOperatingSystem gets the name of the current operating system.
|
||||
func GetOperatingSystem() (string, error) {
|
||||
if prettyName, err := getValueFromOsRelease("PRETTY_NAME"); err != nil {
|
||||
return "", err
|
||||
} else if prettyName != "" {
|
||||
return prettyName, nil
|
||||
}
|
||||
|
||||
// If not set, defaults to PRETTY_NAME="Linux"
|
||||
// c.f. http://www.freedesktop.org/software/systemd/man/os-release.html
|
||||
return "Linux", nil
|
||||
}
|
||||
|
||||
// GetOperatingSystemVersion gets the version of the current operating system, as a string.
|
||||
func GetOperatingSystemVersion() (string, error) {
|
||||
return getValueFromOsRelease("VERSION_ID")
|
||||
}
|
||||
|
||||
// parses the os-release file and returns the value associated with `key`
|
||||
func getValueFromOsRelease(key string) (string, error) {
|
||||
osReleaseFile, err := os.Open(etcOsRelease)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
|
@ -38,28 +56,25 @@ func GetOperatingSystem() (string, error) {
|
|||
}
|
||||
defer osReleaseFile.Close()
|
||||
|
||||
var prettyName string
|
||||
var value string
|
||||
keyWithTrailingEqual := key + "="
|
||||
scanner := bufio.NewScanner(osReleaseFile)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "PRETTY_NAME=") {
|
||||
if strings.HasPrefix(line, keyWithTrailingEqual) {
|
||||
data := strings.SplitN(line, "=", 2)
|
||||
prettyNames, err := shellwords.Parse(data[1])
|
||||
values, err := shellwords.Parse(data[1])
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("PRETTY_NAME is invalid: %s", err.Error())
|
||||
return "", fmt.Errorf("%s is invalid: %s", key, err.Error())
|
||||
}
|
||||
if len(prettyNames) != 1 {
|
||||
return "", fmt.Errorf("PRETTY_NAME needs to be enclosed by quotes if they have spaces: %s", data[1])
|
||||
if len(values) != 1 {
|
||||
return "", fmt.Errorf("%s needs to be enclosed by quotes if they have spaces: %s", key, data[1])
|
||||
}
|
||||
prettyName = prettyNames[0]
|
||||
value = values[0]
|
||||
}
|
||||
}
|
||||
if prettyName != "" {
|
||||
return prettyName, nil
|
||||
}
|
||||
// If not set, defaults to PRETTY_NAME="Linux"
|
||||
// c.f. http://www.freedesktop.org/software/systemd/man/os-release.html
|
||||
return "Linux", nil
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// IsContainerized returns true if we are running inside a container.
|
||||
|
|
|
@ -7,43 +7,41 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
type EtcReleaseParsingTest struct {
|
||||
name string
|
||||
content string
|
||||
expected string
|
||||
expectedErr string
|
||||
}
|
||||
|
||||
func TestGetOperatingSystem(t *testing.T) {
|
||||
var backup = etcOsRelease
|
||||
|
||||
invalids := []struct {
|
||||
content string
|
||||
errorExpected string
|
||||
}{
|
||||
tests := []EtcReleaseParsingTest{
|
||||
{
|
||||
`PRETTY_NAME=Source Mage GNU/Linux
|
||||
content: `PRETTY_NAME=Source Mage GNU/Linux
|
||||
PRETTY_NAME=Ubuntu 14.04.LTS`,
|
||||
"PRETTY_NAME needs to be enclosed by quotes if they have spaces: Source Mage GNU/Linux",
|
||||
expectedErr: "PRETTY_NAME needs to be enclosed by quotes if they have spaces: Source Mage GNU/Linux",
|
||||
},
|
||||
{
|
||||
`PRETTY_NAME="Ubuntu Linux
|
||||
content: `PRETTY_NAME="Ubuntu Linux
|
||||
PRETTY_NAME=Ubuntu 14.04.LTS`,
|
||||
"PRETTY_NAME is invalid: invalid command line string",
|
||||
expectedErr: "PRETTY_NAME is invalid: invalid command line string",
|
||||
},
|
||||
{
|
||||
`PRETTY_NAME=Ubuntu'
|
||||
content: `PRETTY_NAME=Ubuntu'
|
||||
PRETTY_NAME=Ubuntu 14.04.LTS`,
|
||||
"PRETTY_NAME is invalid: invalid command line string",
|
||||
expectedErr: "PRETTY_NAME is invalid: invalid command line string",
|
||||
},
|
||||
{
|
||||
`PRETTY_NAME'
|
||||
content: `PRETTY_NAME'
|
||||
PRETTY_NAME=Ubuntu 14.04.LTS`,
|
||||
"PRETTY_NAME needs to be enclosed by quotes if they have spaces: Ubuntu 14.04.LTS",
|
||||
expectedErr: "PRETTY_NAME needs to be enclosed by quotes if they have spaces: Ubuntu 14.04.LTS",
|
||||
},
|
||||
}
|
||||
|
||||
valids := []struct {
|
||||
content string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
`NAME="Ubuntu"
|
||||
content: `NAME="Ubuntu"
|
||||
PRETTY_NAME_AGAIN="Ubuntu 14.04.LTS"
|
||||
VERSION="14.04, Trusty Tahr"
|
||||
ID=ubuntu
|
||||
|
@ -52,10 +50,10 @@ VERSION_ID="14.04"
|
|||
HOME_URL="http://www.ubuntu.com/"
|
||||
SUPPORT_URL="http://help.ubuntu.com/"
|
||||
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`,
|
||||
"Linux",
|
||||
expected: "Linux",
|
||||
},
|
||||
{
|
||||
`NAME="Ubuntu"
|
||||
content: `NAME="Ubuntu"
|
||||
VERSION="14.04, Trusty Tahr"
|
||||
ID=ubuntu
|
||||
ID_LIKE=debian
|
||||
|
@ -63,10 +61,10 @@ VERSION_ID="14.04"
|
|||
HOME_URL="http://www.ubuntu.com/"
|
||||
SUPPORT_URL="http://help.ubuntu.com/"
|
||||
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`,
|
||||
"Linux",
|
||||
expected: "Linux",
|
||||
},
|
||||
{
|
||||
`NAME=Gentoo
|
||||
content: `NAME=Gentoo
|
||||
ID=gentoo
|
||||
PRETTY_NAME="Gentoo/Linux"
|
||||
ANSI_COLOR="1;32"
|
||||
|
@ -74,10 +72,10 @@ HOME_URL="http://www.gentoo.org/"
|
|||
SUPPORT_URL="http://www.gentoo.org/main/en/support.xml"
|
||||
BUG_REPORT_URL="https://bugs.gentoo.org/"
|
||||
`,
|
||||
"Gentoo/Linux",
|
||||
expected: "Gentoo/Linux",
|
||||
},
|
||||
{
|
||||
`NAME="Ubuntu"
|
||||
content: `NAME="Ubuntu"
|
||||
VERSION="14.04, Trusty Tahr"
|
||||
ID=ubuntu
|
||||
ID_LIKE=debian
|
||||
|
@ -86,28 +84,77 @@ VERSION_ID="14.04"
|
|||
HOME_URL="http://www.ubuntu.com/"
|
||||
SUPPORT_URL="http://help.ubuntu.com/"
|
||||
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`,
|
||||
"Ubuntu 14.04 LTS",
|
||||
expected: "Ubuntu 14.04 LTS",
|
||||
},
|
||||
{
|
||||
`NAME="Ubuntu"
|
||||
content: `NAME="Ubuntu"
|
||||
VERSION="14.04, Trusty Tahr"
|
||||
ID=ubuntu
|
||||
ID_LIKE=debian
|
||||
PRETTY_NAME='Ubuntu 14.04 LTS'`,
|
||||
"Ubuntu 14.04 LTS",
|
||||
expected: "Ubuntu 14.04 LTS",
|
||||
},
|
||||
{
|
||||
`PRETTY_NAME=Source
|
||||
content: `PRETTY_NAME=Source
|
||||
NAME="Source Mage"`,
|
||||
"Source",
|
||||
expected: "Source",
|
||||
},
|
||||
{
|
||||
`PRETTY_NAME=Source
|
||||
content: `PRETTY_NAME=Source
|
||||
PRETTY_NAME="Source Mage"`,
|
||||
"Source Mage",
|
||||
expected: "Source Mage",
|
||||
},
|
||||
}
|
||||
|
||||
runEtcReleaseParsingTests(t, tests, GetOperatingSystem)
|
||||
}
|
||||
|
||||
func TestGetOperatingSystemVersion(t *testing.T) {
|
||||
tests := []EtcReleaseParsingTest{
|
||||
{
|
||||
name: "invalid version id",
|
||||
content: `VERSION_ID="18.04
|
||||
VERSION_ID=18.04`,
|
||||
expectedErr: "VERSION_ID is invalid: invalid command line string",
|
||||
},
|
||||
{
|
||||
name: "ubuntu 14.04",
|
||||
content: `NAME="Ubuntu"
|
||||
PRETTY_NAME="Ubuntu 14.04.LTS"
|
||||
VERSION="14.04, Trusty Tahr"
|
||||
ID=ubuntu
|
||||
ID_LIKE=debian
|
||||
VERSION_ID="14.04"
|
||||
HOME_URL="http://www.ubuntu.com/"
|
||||
SUPPORT_URL="http://help.ubuntu.com/"
|
||||
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`,
|
||||
expected: "14.04",
|
||||
},
|
||||
{
|
||||
name: "gentoo",
|
||||
content: `NAME=Gentoo
|
||||
ID=gentoo
|
||||
PRETTY_NAME="Gentoo/Linux"
|
||||
ANSI_COLOR="1;32"
|
||||
HOME_URL="http://www.gentoo.org/"
|
||||
SUPPORT_URL="http://www.gentoo.org/main/en/support.xml"
|
||||
BUG_REPORT_URL="https://bugs.gentoo.org/"
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "dual version id",
|
||||
content: `VERSION_ID="14.04"
|
||||
VERSION_ID=18.04`,
|
||||
expected: "18.04",
|
||||
},
|
||||
}
|
||||
|
||||
runEtcReleaseParsingTests(t, tests, GetOperatingSystemVersion)
|
||||
}
|
||||
|
||||
func runEtcReleaseParsingTests(t *testing.T, tests []EtcReleaseParsingTest, parsingFunc func() (string, error)) {
|
||||
var backup = etcOsRelease
|
||||
|
||||
dir := os.TempDir()
|
||||
etcOsRelease = filepath.Join(dir, "etcOsRelease")
|
||||
|
||||
|
@ -116,24 +163,19 @@ PRETTY_NAME="Source Mage"`,
|
|||
etcOsRelease = backup
|
||||
}()
|
||||
|
||||
for _, elt := range invalids {
|
||||
if err := ioutil.WriteFile(etcOsRelease, []byte(elt.content), 0600); err != nil {
|
||||
t.Fatalf("failed to write to %s: %v", etcOsRelease, err)
|
||||
}
|
||||
s, err := GetOperatingSystem()
|
||||
if err == nil || err.Error() != elt.errorExpected {
|
||||
t.Fatalf("Expected an error %q, got %q (err: %v)", elt.errorExpected, s, err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, elt := range valids {
|
||||
if err := ioutil.WriteFile(etcOsRelease, []byte(elt.content), 0600); err != nil {
|
||||
t.Fatalf("failed to write to %s: %v", etcOsRelease, err)
|
||||
}
|
||||
s, err := GetOperatingSystem()
|
||||
if err != nil || s != elt.expected {
|
||||
t.Fatalf("Expected %q, got %q (err: %v)", elt.expected, s, err)
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if err := ioutil.WriteFile(etcOsRelease, []byte(test.content), 0600); err != nil {
|
||||
t.Fatalf("failed to write to %s: %v", etcOsRelease, err)
|
||||
}
|
||||
s, err := parsingFunc()
|
||||
if test.expectedErr == "" {
|
||||
assert.NilError(t, err)
|
||||
} else {
|
||||
assert.Error(t, err, test.expectedErr)
|
||||
}
|
||||
assert.Equal(t, s, test.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ package operatingsystem // import "github.com/docker/docker/pkg/parsers/operatin
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
|
@ -17,6 +18,12 @@ func GetOperatingSystem() (string, error) {
|
|||
return string(osName), nil
|
||||
}
|
||||
|
||||
// GetOperatingSystemVersion gets the version of the current operating system, as a string.
|
||||
func GetOperatingSystemVersion() (string, error) {
|
||||
// there's no standard unix way of getting this, sadly...
|
||||
return "", fmt.Error("Unsupported on generic unix")
|
||||
}
|
||||
|
||||
// IsContainerized returns true if we are running inside a container.
|
||||
// No-op on FreeBSD and Darwin, always returns false.
|
||||
func IsContainerized() (bool, error) {
|
||||
|
|
|
@ -3,45 +3,57 @@ package operatingsystem // import "github.com/docker/docker/pkg/parsers/operatin
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
// GetOperatingSystem gets the name of the current operating system.
|
||||
func GetOperatingSystem() (string, error) {
|
||||
os, err := withCurrentVersionRegistryKey(func(key registry.Key) (os string, err error) {
|
||||
if os, _, err = key.GetStringValue("ProductName"); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Default return value
|
||||
ret := "Unknown Operating System"
|
||||
releaseId, _, err := key.GetStringValue("ReleaseId")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
os = fmt.Sprintf("%s Version %s", os, releaseId)
|
||||
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
defer k.Close()
|
||||
buildNumber, _, err := key.GetStringValue("CurrentBuildNumber")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ubr, _, err := key.GetIntegerValue("UBR")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
os = fmt.Sprintf("%s (OS Build %s.%d)", os, buildNumber, ubr)
|
||||
|
||||
pn, _, err := k.GetStringValue("ProductName")
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
ret = pn
|
||||
return
|
||||
})
|
||||
|
||||
ri, _, err := k.GetStringValue("ReleaseId")
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
ret = fmt.Sprintf("%s Version %s", ret, ri)
|
||||
|
||||
cbn, _, err := k.GetStringValue("CurrentBuildNumber")
|
||||
if err != nil {
|
||||
return ret, err
|
||||
if os == "" {
|
||||
// Default return value
|
||||
os = "Unknown Operating System"
|
||||
}
|
||||
|
||||
ubr, _, err := k.GetIntegerValue("UBR")
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
ret = fmt.Sprintf("%s (OS Build %s.%d)", ret, cbn, ubr)
|
||||
return os, err
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
func withCurrentVersionRegistryKey(f func(registry.Key) (string, error)) (string, error) {
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer key.Close()
|
||||
return f(key)
|
||||
}
|
||||
|
||||
// GetOperatingSystemVersion gets the version of the current operating system, as a string.
|
||||
func GetOperatingSystemVersion() (string, error) {
|
||||
version := system.GetOSVersion()
|
||||
return fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.Build), nil
|
||||
}
|
||||
|
||||
// IsContainerized returns true if we are running inside a container.
|
||||
|
|
Loading…
Reference in a new issue