github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/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  }