From b8463a738cfae55cf7904d503c7dde525d404b94 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Mon, 12 Jun 2017 17:33:21 -0700 Subject: [PATCH] Add support for DCOS host detection, improve Docker detection. (#4525) isDocker was currently reading from `/proc/cgroup` file. But this file alone is rather not conclusive evidence. Docker internally has `.dockerenv` as a special file which we should use instead. Fixes #4456 --- cmd/main.go | 6 +-- cmd/update-main.go | 65 +++++++++++++++++++------ cmd/update-main_test.go | 104 ++++++++++++++++++++++++++-------------- 3 files changed, 122 insertions(+), 53 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 8f871beb3..f7e931e87 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -138,11 +138,11 @@ func newApp(name string) *cli.App { // Main main for minio server. func Main(args []string) { - name := filepath.Base(args[0]) - app := newApp(name) + // Set the minio app name. + appName := filepath.Base(args[0]) // Run the app - exit on error. - if err := app.Run(args); err != nil { + if err := newApp(appName).Run(args); err != nil { os.Exit(1) } } diff --git a/cmd/update-main.go b/cmd/update-main.go index 2639ef9cb..a0e7953ba 100644 --- a/cmd/update-main.go +++ b/cmd/update-main.go @@ -17,7 +17,6 @@ package cmd import ( - "bytes" "fmt" "io/ioutil" "net/http" @@ -98,26 +97,41 @@ func GetCurrentReleaseTime() (releaseTime time.Time, err error) { return getCurrentReleaseTime(Version, os.Args[0]) } -func isDocker(cgroupFile string) (bool, error) { - cgroup, err := ioutil.ReadFile(cgroupFile) - if os.IsNotExist(err) { - err = nil +// Check if we are indeed inside docker. +// https://github.com/moby/moby/blob/master/daemon/initlayer/setup_unix.go#L25 +// +// "/.dockerenv": "file", +// +func isDocker(dockerEnvFile string) (ok bool, err error) { + _, err = os.Stat(dockerEnvFile) + if err != nil { + if os.IsNotExist(err) { + err = nil + } + return false, err } - - return bytes.Contains(cgroup, []byte("docker")), err + return true, nil } // IsDocker - returns if the environment minio is running // is docker or not. func IsDocker() bool { - found, err := isDocker("/proc/self/cgroup") - fatalIf(err, "Error in docker check.") + found, err := isDocker("/.dockerenv") + // We don't need to fail for this check, log + // an error and return false. + errorIf(err, "Error in docker check.") return found } -// IsKubernetes returns if the environment minio is -// running is kubernetes or not. +// IsDCOS returns true if minio is running in DCOS. +func IsDCOS() bool { + // http://mesos.apache.org/documentation/latest/docker-containerizer/ + // Mesos docker containerizer sets this value + return os.Getenv("MESOS_CONTAINER_NAME") != "" +} + +// IsKubernetes returns true if minio is running in kubernetes. func IsKubernetes() bool { // Kubernetes env used to validate if we are // indeed running inside a kubernetes pod @@ -139,7 +153,7 @@ func IsSourceBuild() bool { // DO NOT CHANGE USER AGENT STYLE. // The style should be // -// Minio (; [; kubernetes][; docker][; source]) Minio/ Minio/ Minio/ +// Minio (; [; dcos][; kubernetes][; docker][; source]) Minio/ Minio/ Minio/ [Minio/univers-] // // For any change here should be discussed by openning an issue at https://github.com/minio/minio/issues. func getUserAgent(mode string) string { @@ -147,6 +161,9 @@ func getUserAgent(mode string) string { if mode != "" { userAgent += "; " + mode } + if IsDCOS() { + userAgent += "; dcos" + } if IsKubernetes() { userAgent += "; kubernetes" } @@ -156,8 +173,15 @@ func getUserAgent(mode string) string { if IsSourceBuild() { userAgent += "; source" } - userAgent += ") " + " Minio/" + Version + " Minio/" + ReleaseTag + " Minio/" + CommitID + userAgent += ") Minio/" + Version + " Minio/" + ReleaseTag + " Minio/" + CommitID + if IsDCOS() { + universePkgVersion := os.Getenv("MARATHON_APP_LABEL_DCOS_PACKAGE_VERSION") + // On DC/OS environment try to the get universe package version. + if universePkgVersion != "" { + userAgent += " Minio/" + "universe-" + universePkgVersion + } + } return userAgent } @@ -241,10 +265,21 @@ func getLatestReleaseTime(timeout time.Duration, mode string) (releaseTime time. return parseReleaseData(data) } -// Kubernetes deploy doc link. -const kubernetesDeploymentDoc = "https://docs.minio.io/docs/deploy-minio-on-kubernetes" +const ( + // Kubernetes deployment doc link. + kubernetesDeploymentDoc = "https://docs.minio.io/docs/deploy-minio-on-kubernetes" + + // Mesos deployment doc link. + mesosDeploymentDoc = "https://docs.minio.io/docs/deploy-minio-on-dc-os" +) func getDownloadURL(buildDate time.Time) (downloadURL string) { + // Check if we are in DCOS environment, return + // deployment guide for update procedures. + if IsDCOS() { + return mesosDeploymentDoc + } + // Check if we are in kubernetes environment, return // deployment guide for update procedures. if IsKubernetes() { diff --git a/cmd/update-main_test.go b/cmd/update-main_test.go index 0946b34aa..175f97f2b 100644 --- a/cmd/update-main_test.go +++ b/cmd/update-main_test.go @@ -44,12 +44,18 @@ func TestDownloadURL(t *testing.T) { } os.Setenv("KUBERNETES_SERVICE_HOST", "10.11.148.5") - defer os.Unsetenv("KUBERNETES_SERVICE_HOST") - durl = getDownloadURL(minioVersion1) if durl != kubernetesDeploymentDoc { t.Errorf("Expected %s, got %s", kubernetesDeploymentDoc, durl) } + os.Unsetenv("KUBERNETES_SERVICE_HOST") + + os.Setenv("MESOS_CONTAINER_NAME", "mesos-1111") + durl = getDownloadURL(minioVersion1) + if durl != mesosDeploymentDoc { + t.Errorf("Expected %s, got %s", mesosDeploymentDoc, durl) + } + os.Unsetenv("MESOS_CONTAINER_NAME") } func TestGetCurrentReleaseTime(t *testing.T) { @@ -158,6 +164,63 @@ func TestGetCurrentReleaseTime(t *testing.T) { } } +// Tests user agent string. +func TestUserAgent(t *testing.T) { + testCases := []struct { + envName string + envValue string + mode string + expectedStr string + }{ + { + envName: "", + envValue: "", + mode: globalMinioModeFS, + expectedStr: fmt.Sprintf("Minio (%s; %s; %s; source) Minio/DEVELOPMENT.GOGET Minio/DEVELOPMENT.GOGET Minio/DEVELOPMENT.GOGET", runtime.GOOS, runtime.GOARCH, globalMinioModeFS), + }, + { + envName: "MESOS_CONTAINER_NAME", + envValue: "mesos-11111", + mode: globalMinioModeXL, + expectedStr: fmt.Sprintf("Minio (%s; %s; %s; %s; source) Minio/DEVELOPMENT.GOGET Minio/DEVELOPMENT.GOGET Minio/DEVELOPMENT.GOGET Minio/universe-%s", runtime.GOOS, runtime.GOARCH, globalMinioModeXL, "dcos", "mesos-1111"), + }, + { + envName: "KUBERNETES_SERVICE_HOST", + envValue: "10.11.148.5", + mode: globalMinioModeXL, + expectedStr: fmt.Sprintf("Minio (%s; %s; %s; %s; source) Minio/DEVELOPMENT.GOGET Minio/DEVELOPMENT.GOGET Minio/DEVELOPMENT.GOGET", runtime.GOOS, runtime.GOARCH, globalMinioModeXL, "kubernetes"), + }, + } + + for i, testCase := range testCases { + os.Setenv(testCase.envName, testCase.envValue) + if testCase.envName == "MESOS_CONTAINER_NAME" { + os.Setenv("MARATHON_APP_LABEL_DCOS_PACKAGE_VERSION", "mesos-1111") + } + str := getUserAgent(testCase.mode) + if str != testCase.expectedStr { + t.Errorf("Test %d: expected: %s, got: %s", i+1, testCase.expectedStr, str) + } + os.Unsetenv("MARATHON_APP_LABEL_DCOS_PACKAGE_VERSION") + os.Unsetenv(testCase.envName) + } +} + +// Tests if the environment we are running is in DCOS. +func TestIsDCOS(t *testing.T) { + os.Setenv("MESOS_CONTAINER_NAME", "mesos-1111") + dcos := IsDCOS() + if !dcos { + t.Fatalf("Expected %t, got %t", true, dcos) + } + + os.Unsetenv("MESOS_CONTAINER_NAME") + dcos = IsDCOS() + if dcos { + t.Fatalf("Expected %t, got %t", false, dcos) + } +} + // Tests if the environment we are running is in kubernetes. func TestIsKubernetes(t *testing.T) { os.Setenv("KUBERNETES_SERVICE_HOST", "10.11.148.5") @@ -188,36 +251,8 @@ func TestIsDocker(t *testing.T) { return tmpfile.Name() } - filename1 := createTempFile(`11:pids:/user.slice/user-1000.slice/user@1000.service -10:blkio:/ -9:hugetlb:/ -8:perf_event:/ -7:cpuset:/ -6:devices:/user.slice -5:net_cls,net_prio:/ -4:cpu,cpuacct:/ -3:memory:/user/bala/0 -2:freezer:/user/bala/0 -1:name=systemd:/user.slice/user-1000.slice/user@1000.service/gnome-terminal-server.service -`) - defer os.Remove(filename1) - - filename2 := createTempFile(`14:name=systemd:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -13:pids:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -12:hugetlb:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -11:net_prio:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -10:perf_event:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -9:net_cls:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -8:freezer:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -7:devices:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -6:memory:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -5:blkio:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -4:cpuacct:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -3:cpu:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -2:cpuset:/docker/d5eb950884d828237f60f624ff575a1a7a4daa28a8d4d750040527ed9545e727 -1:name=openrc:/docker -`) - defer os.Remove(filename2) + filename := createTempFile("") + defer os.Remove(filename) testCases := []struct { filename string @@ -226,8 +261,7 @@ func TestIsDocker(t *testing.T) { }{ {"", false, nil}, {"/tmp/non-existing-file", false, nil}, - {filename1, false, nil}, - {filename2, true, nil}, + {filename, true, nil}, } if runtime.GOOS == "linux" { @@ -235,7 +269,7 @@ func TestIsDocker(t *testing.T) { filename string expectedResult bool expectedErr error - }{"/proc/1/cwd", false, fmt.Errorf("open /proc/1/cwd: permission denied")}) + }{"/proc/1/cwd", false, errors.New("stat /proc/1/cwd: permission denied")}) } for _, testCase := range testCases {