gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/test/dockerutil/dockerutil.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package dockerutil is a collection of utility functions. 16 package dockerutil 17 18 import ( 19 "context" 20 "encoding/json" 21 "flag" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "log" 26 "os" 27 "os/exec" 28 "regexp" 29 "strconv" 30 "strings" 31 "testing" 32 "time" 33 34 "gvisor.dev/gvisor/pkg/test/testutil" 35 "gvisor.dev/gvisor/runsc/cgroup" 36 ) 37 38 var ( 39 // runtime is the runtime to use for tests. This will be applied to all 40 // containers. Note that the default here ("runsc") corresponds to the 41 // default used by the installations. 42 runtime = flag.String("runtime", os.Getenv("RUNTIME"), "specify which runtime to use") 43 44 // config is the default Docker daemon configuration path. 45 config = flag.String("config_path", "/etc/docker/daemon.json", "configuration file for reading paths") 46 47 // The following flags are for the "pprof" profiler tool. 48 49 // pprofBaseDir allows the user to change the directory to which profiles are 50 // written. By default, profiles will appear under: 51 // /tmp/profile/RUNTIME/CONTAINER_NAME/*.pprof. 52 pprofBaseDir = flag.String("pprof-dir", "/tmp/profile", "base directory in: BASEDIR/RUNTIME/CONTINER_NAME/FILENAME (e.g. /tmp/profile/runtime/mycontainer/cpu.pprof)") 53 pprofDuration = flag.Duration("pprof-duration", time.Hour, "profiling duration (automatically stopped at container exit)") 54 55 // The below flags enable each type of profile. Multiple profiles can be 56 // enabled for each run. The profile will be collected from the start. 57 pprofBlock = flag.Bool("pprof-block", false, "enables block profiling with runsc debug") 58 pprofCPU = flag.Bool("pprof-cpu", false, "enables CPU profiling with runsc debug") 59 pprofHeap = flag.Bool("pprof-heap", false, "enables heap profiling with runsc debug") 60 pprofMutex = flag.Bool("pprof-mutex", false, "enables mutex profiling with runsc debug") 61 trace = flag.Bool("go-trace", false, "enables collecting a go trace with runsc debug") 62 63 // This matches the string "native.cgroupdriver=systemd" (including optional 64 // whitespace), which can be found in a docker daemon configuration file's 65 // exec-opts field. 66 useSystemdRgx = regexp.MustCompile("\\s*(native\\.cgroupdriver)\\s*=\\s*(systemd)\\s*") 67 ) 68 69 // PrintDockerConfig prints the whole Docker configuration file to the log. 70 func PrintDockerConfig() { 71 configBytes, err := ioutil.ReadFile(*config) 72 if err != nil { 73 log.Fatalf("Cannot read Docker config at %v: %v", *config, err) 74 } 75 log.Printf("Docker config (from %v):\n--------\n%v\n--------\n", *config, string(configBytes)) 76 } 77 78 // EnsureSupportedDockerVersion checks if correct docker is installed. 79 // 80 // This logs directly to stderr, as it is typically called from a Main wrapper. 81 func EnsureSupportedDockerVersion() { 82 cmd := exec.Command("docker", "version") 83 out, err := cmd.CombinedOutput() 84 if err != nil { 85 log.Fatalf("error running %q: %v", "docker version", err) 86 } 87 re := regexp.MustCompile(`Version:\s+(\d+)\.(\d+)\.\d.*`) 88 matches := re.FindStringSubmatch(string(out)) 89 if len(matches) != 3 { 90 log.Fatalf("Invalid docker output: %s", out) 91 } 92 major, _ := strconv.Atoi(matches[1]) 93 minor, _ := strconv.Atoi(matches[2]) 94 if major < 17 || (major == 17 && minor < 9) { 95 log.Fatalf("Docker version 17.09.0 or greater is required, found: %02d.%02d", major, minor) 96 } 97 } 98 99 // EnsureDockerExperimentalEnabled ensures that Docker has experimental features enabled. 100 func EnsureDockerExperimentalEnabled() { 101 cmd := exec.Command("docker", "version", "--format={{.Server.Experimental}}") 102 out, err := cmd.CombinedOutput() 103 if err != nil { 104 log.Fatalf("error running %s: %v", "docker version --format='{{.Server.Experimental}}'", err) 105 } 106 if strings.TrimSpace(string(out)) != "true" { 107 PrintDockerConfig() 108 log.Fatalf("Docker is running without experimental features enabled.") 109 } 110 } 111 112 // RuntimePath returns the binary path for the current runtime. 113 func RuntimePath() (string, error) { 114 rs, err := runtimeMap() 115 if err != nil { 116 return "", err 117 } 118 119 p, ok := rs["path"].(string) 120 if !ok { 121 // The runtime does not declare a path. 122 return "", fmt.Errorf("runtime does not declare a path: %v", rs) 123 } 124 return p, nil 125 } 126 127 // IsGVisorRuntime returns whether the default container runtime used by 128 // `dockerutil` is gVisor-based or not. 129 func IsGVisorRuntime(ctx context.Context, t *testing.T) (bool, error) { 130 output, err := MakeContainer(ctx, t).Run(ctx, RunOpts{Image: "basic/alpine"}, "dmesg") 131 if err != nil { 132 if strings.Contains(output, "dmesg: klogctl: Operation not permitted") { 133 return false, nil 134 } 135 return false, fmt.Errorf("failed to run dmesg: %v (output: %q)", err, output) 136 } 137 return strings.Contains(output, "gVisor"), nil 138 } 139 140 // UsingSystemdCgroup returns true if the docker configuration has the 141 // native.cgroupdriver=systemd option set in "exec-opts", or if the 142 // system is using cgroupv2, in which case systemd is the default driver. 143 func UsingSystemdCgroup() (bool, error) { 144 // Read the configuration data; the file must exist. 145 configBytes, err := ioutil.ReadFile(*config) 146 if err != nil { 147 return false, err 148 } 149 // Unmarshal the configuration. 150 c := make(map[string]any) 151 if err := json.Unmarshal(configBytes, &c); err != nil { 152 return false, err 153 } 154 // Decode the expected configuration. 155 e, ok := c["exec-opts"] 156 if !ok { 157 // No exec-opts. Default is true on cgroupv2, false otherwise. 158 return cgroup.IsOnlyV2(), nil 159 } 160 eos, ok := e.([]any) 161 if !ok { 162 // The exec opts are not an array. 163 return false, fmt.Errorf("unexpected format: %+v", eos) 164 } 165 for _, opt := range eos { 166 if optStr, ok := opt.(string); ok && useSystemdRgx.MatchString(optStr) { 167 return true, nil 168 } 169 } 170 return false, nil 171 } 172 173 func runtimeMap() (map[string]any, error) { 174 // Read the configuration data; the file must exist. 175 configBytes, err := ioutil.ReadFile(*config) 176 if err != nil { 177 return nil, err 178 } 179 180 // Unmarshal the configuration. 181 c := make(map[string]any) 182 if err := json.Unmarshal(configBytes, &c); err != nil { 183 return nil, err 184 } 185 186 // Decode the expected configuration. 187 r, ok := c["runtimes"] 188 if !ok { 189 return nil, fmt.Errorf("no runtimes declared: %v", c) 190 } 191 rs, ok := r.(map[string]any) 192 if !ok { 193 // The runtimes are not a map. 194 return nil, fmt.Errorf("unexpected format: %v", rs) 195 } 196 r, ok = rs[*runtime] 197 if !ok { 198 // The expected runtime is not declared. 199 return nil, fmt.Errorf("runtime %q not found: %v", *runtime, rs) 200 } 201 rs, ok = r.(map[string]any) 202 if !ok { 203 // The runtime is not a map. 204 return nil, fmt.Errorf("unexpected format: %v", r) 205 } 206 return rs, nil 207 } 208 209 // Save exports a container image to the given Writer. 210 // 211 // Note that the writer should be actively consuming the output, otherwise it 212 // is not guaranteed that the Save will make any progress and the call may 213 // stall indefinitely. 214 // 215 // This is called by criutil in order to import imports. 216 func Save(logger testutil.Logger, image string, w io.Writer) error { 217 cmd := testutil.Command(logger, "docker", "save", testutil.ImageByName(image)) 218 cmd.Stdout = w // Send directly to the writer. 219 return cmd.Run() 220 } 221 222 // Runtime returns the value of the flag runtime. 223 func Runtime() string { 224 return *runtime 225 }