github.com/containerd/Containerd@v1.4.13/daemon_config_linux_test.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package containerd 18 19 import ( 20 "bufio" 21 "bytes" 22 "context" 23 "fmt" 24 "io/ioutil" 25 "os" 26 "os/exec" 27 "path/filepath" 28 "strings" 29 "syscall" 30 "testing" 31 "time" 32 33 "github.com/containerd/cgroups" 34 "github.com/containerd/containerd/oci" 35 "github.com/containerd/containerd/pkg/testutil" 36 "github.com/containerd/containerd/plugin" 37 "github.com/containerd/containerd/runtime/v2/runc/options" 38 srvconfig "github.com/containerd/containerd/services/server/config" 39 ) 40 41 // the following nolint is for shutting up gometalinter on non-linux. 42 // nolint: unused 43 func newDaemonWithConfig(t *testing.T, configTOML string) (*Client, *daemon, func()) { 44 if testing.Short() { 45 t.Skip() 46 } 47 testutil.RequiresRoot(t) 48 var ( 49 ctrd = daemon{} 50 configTOMLDecoded srvconfig.Config 51 buf = bytes.NewBuffer(nil) 52 ) 53 54 tempDir, err := ioutil.TempDir("", "containerd-test-new-daemon-with-config") 55 if err != nil { 56 t.Fatal(err) 57 } 58 defer func() { 59 if err != nil { 60 os.RemoveAll(tempDir) 61 } 62 }() 63 64 configTOMLFile := filepath.Join(tempDir, "config.toml") 65 if err = ioutil.WriteFile(configTOMLFile, []byte(configTOML), 0600); err != nil { 66 t.Fatal(err) 67 } 68 69 if err = srvconfig.LoadConfig(configTOMLFile, &configTOMLDecoded); err != nil { 70 t.Fatal(err) 71 } 72 73 address := configTOMLDecoded.GRPC.Address 74 if address == "" { 75 address = filepath.Join(tempDir, "containerd.sock") 76 } 77 args := []string{"-c", configTOMLFile} 78 if configTOMLDecoded.Root == "" { 79 args = append(args, "--root", filepath.Join(tempDir, "root")) 80 } 81 if configTOMLDecoded.State == "" { 82 args = append(args, "--state", filepath.Join(tempDir, "state")) 83 } 84 if err = ctrd.start("containerd", address, args, buf, buf); err != nil { 85 t.Fatalf("%v: %s", err, buf.String()) 86 } 87 88 waitCtx, waitCancel := context.WithTimeout(context.TODO(), 2*time.Second) 89 client, err := ctrd.waitForStart(waitCtx) 90 waitCancel() 91 if err != nil { 92 ctrd.Kill() 93 ctrd.Wait() 94 t.Fatalf("%v: %s", err, buf.String()) 95 } 96 97 cleanup := func() { 98 if err := client.Close(); err != nil { 99 t.Fatalf("failed to close client: %v", err) 100 } 101 if err := ctrd.Stop(); err != nil { 102 if err := ctrd.Kill(); err != nil { 103 t.Fatalf("failed to signal containerd: %v", err) 104 } 105 } 106 if err := ctrd.Wait(); err != nil { 107 if _, ok := err.(*exec.ExitError); !ok { 108 t.Fatalf("failed to wait for: %v", err) 109 } 110 } 111 if err := os.RemoveAll(tempDir); err != nil { 112 t.Fatalf("failed to remove %s: %v", tempDir, err) 113 } 114 // cleaning config-specific resources is up to the caller 115 } 116 return client, &ctrd, cleanup 117 } 118 119 // TestDaemonRuntimeRoot ensures plugin.linux.runtime_root is not ignored 120 func TestDaemonRuntimeRoot(t *testing.T) { 121 runtimeRoot, err := ioutil.TempDir("", "containerd-test-runtime-root") 122 if err != nil { 123 t.Fatal(err) 124 } 125 defer func() { 126 if err != nil { 127 os.RemoveAll(runtimeRoot) 128 } 129 }() 130 configTOML := ` 131 version = 1 132 [plugins] 133 [plugins.cri] 134 stream_server_port = "0" 135 ` 136 137 client, _, cleanup := newDaemonWithConfig(t, configTOML) 138 defer cleanup() 139 140 ctx, cancel := testContext(t) 141 defer cancel() 142 // FIXME(AkihiroSuda): import locally frozen image? 143 image, err := client.Pull(ctx, testImage, WithPullUnpack) 144 if err != nil { 145 t.Fatal(err) 146 } 147 148 id := t.Name() 149 container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image), withProcessArgs("top")), WithRuntime(plugin.RuntimeRuncV1, &options.Options{ 150 Root: runtimeRoot, 151 })) 152 if err != nil { 153 t.Fatal(err) 154 } 155 defer container.Delete(ctx, WithSnapshotCleanup) 156 157 task, err := container.NewTask(ctx, empty()) 158 if err != nil { 159 t.Fatal(err) 160 } 161 defer task.Delete(ctx) 162 163 status, err := task.Wait(ctx) 164 if err != nil { 165 t.Fatal(err) 166 } 167 168 containerPath := filepath.Join(runtimeRoot, testNamespace, id) 169 if _, err = os.Stat(containerPath); err != nil { 170 t.Errorf("error while getting stat for %s: %v", containerPath, err) 171 } 172 173 if err = task.Kill(ctx, syscall.SIGKILL); err != nil { 174 t.Error(err) 175 } 176 <-status 177 } 178 179 // code most copy from https://github.com/opencontainers/runc 180 func getCgroupPath() (map[string]string, error) { 181 cgroupPath := make(map[string]string) 182 f, err := os.Open("/proc/self/mountinfo") 183 if err != nil { 184 return nil, err 185 } 186 defer f.Close() 187 188 scanner := bufio.NewScanner(f) 189 for scanner.Scan() { 190 text := scanner.Text() 191 fields := strings.Split(text, " ") 192 // Safe as mountinfo encodes mountpoints with spaces as \040. 193 index := strings.Index(text, " - ") 194 postSeparatorFields := strings.Fields(text[index+3:]) 195 numPostFields := len(postSeparatorFields) 196 197 // This is an error as we can't detect if the mount is for "cgroup" 198 if numPostFields == 0 { 199 continue 200 } 201 202 if postSeparatorFields[0] == "cgroup" { 203 // Check that the mount is properly formatted. 204 if numPostFields < 3 { 205 continue 206 } 207 cgroupPath[filepath.Base(fields[4])] = fields[4] 208 } 209 } 210 211 return cgroupPath, nil 212 } 213 214 // TestDaemonCustomCgroup ensures plugin.cgroup.path is not ignored 215 func TestDaemonCustomCgroup(t *testing.T) { 216 if cgroups.Mode() == cgroups.Unified { 217 t.Skip("test requires cgroup1") 218 } 219 cgroupPath, err := getCgroupPath() 220 if err != nil { 221 t.Fatal(err) 222 } 223 if len(cgroupPath) == 0 { 224 t.Skip("skip TestDaemonCustomCgroup since no cgroup path available") 225 } 226 227 customCgroup := fmt.Sprintf("%d", time.Now().Nanosecond()) 228 configTOML := ` 229 version = 1 230 [cgroup] 231 path = "` + customCgroup + `"` 232 233 _, _, cleanup := newDaemonWithConfig(t, configTOML) 234 235 defer func() { 236 // do cgroup path clean 237 for _, v := range cgroupPath { 238 if _, err := os.Stat(filepath.Join(v, customCgroup)); err == nil { 239 if err := os.RemoveAll(filepath.Join(v, customCgroup)); err != nil { 240 t.Logf("failed to remove cgroup path %s", filepath.Join(v, customCgroup)) 241 } 242 } 243 } 244 }() 245 246 defer cleanup() 247 248 paths := []string{ 249 "devices", 250 "memory", 251 "cpu", 252 "blkio", 253 } 254 255 for _, p := range paths { 256 v := cgroupPath[p] 257 if v == "" { 258 continue 259 } 260 path := filepath.Join(v, customCgroup) 261 if _, err := os.Stat(path); err != nil { 262 if os.IsNotExist(err) { 263 t.Fatalf("custom cgroup path %s should exist, actually not", path) 264 } 265 } 266 } 267 }