github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/sandbox/cgroup/scanning_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  package cgroup_test
    20  
    21  import (
    22  	"bytes"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/sandbox/cgroup"
    31  	"github.com/snapcore/snapd/snap/naming"
    32  	"github.com/snapcore/snapd/testutil"
    33  )
    34  
    35  type scanningSuite struct {
    36  	testutil.BaseTest
    37  	rootDir string
    38  }
    39  
    40  var _ = Suite(&scanningSuite{})
    41  
    42  func (s *scanningSuite) SetUpTest(c *C) {
    43  	s.BaseTest.SetUpTest(c)
    44  
    45  	s.rootDir = c.MkDir()
    46  	s.AddCleanup(cgroup.MockFsRootPath(s.rootDir))
    47  }
    48  
    49  func mustParseTag(tag string) naming.SecurityTag {
    50  	parsedTag, err := naming.ParseSecurityTag(tag)
    51  	if err != nil {
    52  		panic(err)
    53  	}
    54  	return parsedTag
    55  }
    56  
    57  func (s *scanningSuite) TestSecurityTagFromCgroupPath(c *C) {
    58  	c.Check(cgroup.SecurityTagFromCgroupPath("/a/b/snap.foo.foo.service"), DeepEquals, mustParseTag("snap.foo.foo"))
    59  	c.Check(cgroup.SecurityTagFromCgroupPath("/a/b/snap.foo.bar.service"), DeepEquals, mustParseTag("snap.foo.bar"))
    60  	c.Check(cgroup.SecurityTagFromCgroupPath("/a/b/snap.foo.bar.$RANDOM.scope"), DeepEquals, mustParseTag("snap.foo.bar"))
    61  	c.Check(cgroup.SecurityTagFromCgroupPath("/a/b/snap.foo.hook.bar.$RANDOM.scope"), DeepEquals, mustParseTag("snap.foo.hook.bar"))
    62  	// We are not confused by snapd things.
    63  	c.Check(cgroup.SecurityTagFromCgroupPath("/a/b/snap.service"), IsNil)
    64  	c.Check(cgroup.SecurityTagFromCgroupPath("/a/b/snapd.service"), IsNil)
    65  	c.Check(cgroup.SecurityTagFromCgroupPath("/a/b/snap.foo.mount"), IsNil)
    66  	// Real data looks like this.
    67  	c.Check(cgroup.SecurityTagFromCgroupPath("snap.test-snapd-refresh.sh.d854bd35-2457-4ac8-b494-06061d74df33.scope"), DeepEquals, mustParseTag("snap.test-snapd-refresh.sh"))
    68  	c.Check(cgroup.SecurityTagFromCgroupPath("snap.test-snapd-refresh.hook.configure.d854bd35-2457-4ac8-b494-06061d74df33.scope"), DeepEquals, mustParseTag("snap.test-snapd-refresh.hook.configure"))
    69  	// Trailing slashes are automatically handled.
    70  	c.Check(cgroup.SecurityTagFromCgroupPath("/a/b/snap.foo.foo.service/"), DeepEquals, mustParseTag("snap.foo.foo"))
    71  }
    72  
    73  func (s *scanningSuite) writePids(c *C, dir string, pids []int) {
    74  	var buf bytes.Buffer
    75  	for _, pid := range pids {
    76  		fmt.Fprintf(&buf, "%d\n", pid)
    77  	}
    78  
    79  	var path string
    80  	ver, err := cgroup.Version()
    81  	c.Assert(err, IsNil)
    82  	c.Assert(ver == cgroup.V1 || ver == cgroup.V2, Equals, true)
    83  	switch ver {
    84  	case cgroup.V1:
    85  		path = filepath.Join(s.rootDir, "/sys/fs/cgroup/systemd", dir)
    86  	case cgroup.V2:
    87  		path = filepath.Join(s.rootDir, "/sys/fs/cgroup", dir)
    88  	}
    89  
    90  	c.Assert(os.MkdirAll(path, 0755), IsNil)
    91  	c.Assert(ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), buf.Bytes(), 0644), IsNil)
    92  }
    93  
    94  func (s *scanningSuite) TestPidsOfSnapEmpty(c *C) {
    95  	restore := cgroup.MockVersion(cgroup.V1, nil)
    96  	defer restore()
    97  
    98  	// Not having any cgroup directories is not an error.
    99  	pids, err := cgroup.PidsOfSnap("pkg")
   100  	c.Assert(err, IsNil)
   101  	c.Check(pids, HasLen, 0)
   102  }
   103  
   104  func (s *scanningSuite) TestPidsOfSnapUnrelatedStuff(c *C) {
   105  	for _, ver := range []int{cgroup.V2, cgroup.V1} {
   106  		comment := Commentf("cgroup version %v", ver)
   107  		restore := cgroup.MockVersion(ver, nil)
   108  		defer restore()
   109  
   110  		// Things that are not related to the snap are not being picked up.
   111  		s.writePids(c, "udisks2.service", []int{100})
   112  		s.writePids(c, "snap..service", []int{101})
   113  		s.writePids(c, "snap..scope", []int{102})
   114  		s.writePids(c, "snap.*.service", []int{103})
   115  		s.writePids(c, "snap.*.scope", []int{104})
   116  		s.writePids(c, "snapd.service", []int{105})
   117  		s.writePids(c, "snap-spotify-35.mount", []int{106})
   118  
   119  		pids, err := cgroup.PidsOfSnap("pkg")
   120  		c.Assert(err, IsNil, comment)
   121  		c.Check(pids, HasLen, 0, comment)
   122  	}
   123  }
   124  
   125  func (s *scanningSuite) TestPidsOfSnapSecurityTags(c *C) {
   126  	for _, ver := range []int{cgroup.V2, cgroup.V1} {
   127  		comment := Commentf("cgroup version %v", ver)
   128  		restore := cgroup.MockVersion(ver, nil)
   129  		defer restore()
   130  
   131  		// Pids are collected and assigned to bins by security tag
   132  		s.writePids(c, "system.slice/snap.pkg.hook.configure.$RANDOM.scope", []int{1})
   133  		s.writePids(c, "system.slice/snap.pkg.daemon.service", []int{2})
   134  
   135  		pids, err := cgroup.PidsOfSnap("pkg")
   136  		c.Assert(err, IsNil, comment)
   137  		c.Check(pids, DeepEquals, map[string][]int{
   138  			"snap.pkg.hook.configure": {1},
   139  			"snap.pkg.daemon":         {2},
   140  		}, comment)
   141  	}
   142  }
   143  
   144  func (s *scanningSuite) TestPidsOfInstances(c *C) {
   145  	for _, ver := range []int{cgroup.V2, cgroup.V1} {
   146  		comment := Commentf("cgroup version %v", ver)
   147  		restore := cgroup.MockVersion(ver, nil)
   148  		defer restore()
   149  
   150  		// Instances are not confused between themselves and between the non-instance version.
   151  		s.writePids(c, "system.slice/snap.pkg_prod.daemon.service", []int{1})
   152  		s.writePids(c, "system.slice/snap.pkg_devel.daemon.service", []int{2})
   153  		s.writePids(c, "system.slice/snap.pkg.daemon.service", []int{3})
   154  
   155  		// The main one
   156  		pids, err := cgroup.PidsOfSnap("pkg")
   157  		c.Assert(err, IsNil, comment)
   158  		c.Check(pids, DeepEquals, map[string][]int{
   159  			"snap.pkg.daemon": {3},
   160  		}, comment)
   161  
   162  		// The development one
   163  		pids, err = cgroup.PidsOfSnap("pkg_devel")
   164  		c.Assert(err, IsNil, comment)
   165  		c.Check(pids, DeepEquals, map[string][]int{
   166  			"snap.pkg_devel.daemon": {2},
   167  		}, comment)
   168  
   169  		// The production one
   170  		pids, err = cgroup.PidsOfSnap("pkg_prod")
   171  		c.Assert(err, IsNil, comment)
   172  		c.Check(pids, DeepEquals, map[string][]int{
   173  			"snap.pkg_prod.daemon": {1},
   174  		}, comment)
   175  	}
   176  }
   177  
   178  func (s *scanningSuite) TestPidsOfAggregation(c *C) {
   179  	for _, ver := range []int{cgroup.V2, cgroup.V1} {
   180  		comment := Commentf("cgroup version %v", ver)
   181  		restore := cgroup.MockVersion(ver, nil)
   182  		defer restore()
   183  
   184  		// A single snap may be invoked by multiple users in different sessions.
   185  		// All of their PIDs are collected though.
   186  		s.writePids(c, "user.slice/user-1000.slice/user@1000.service/gnome-shell-wayland.service/snap.pkg.app.$RANDOM1.scope", []int{1}) // mock 1st invocation
   187  		s.writePids(c, "user.slice/user-1000.slice/user@1000.service/gnome-shell-wayland.service/snap.pkg.app.$RANDOM2.scope", []int{2}) // mock fork() by pid 1
   188  		s.writePids(c, "user.slice/user-1001.slice/user@1001.service/gnome-shell-wayland.service/snap.pkg.app.$RANDOM3.scope", []int{3}) // mock 2nd invocation
   189  		s.writePids(c, "user.slice/user-1001.slice/user@1001.service/gnome-shell-wayland.service/snap.pkg.app.$RANDOM4.scope", []int{4}) // mock fork() by pid 3
   190  
   191  		pids, err := cgroup.PidsOfSnap("pkg")
   192  		c.Assert(err, IsNil, comment)
   193  		c.Check(pids, DeepEquals, map[string][]int{
   194  			"snap.pkg.app": {1, 2, 3, 4},
   195  		}, comment)
   196  	}
   197  }
   198  
   199  func (s *scanningSuite) TestPidsOfSnapUnrelated(c *C) {
   200  	for _, ver := range []int{cgroup.V2, cgroup.V1} {
   201  		comment := Commentf("cgroup version %v", ver)
   202  		restore := cgroup.MockVersion(ver, nil)
   203  		defer restore()
   204  
   205  		// We are not confusing snaps with other snaps, instances of our snap, and
   206  		// with non-snap hierarchies.
   207  		s.writePids(c, "user.slice/.../snap.pkg.app.$RANDOM1.scope", []int{1})
   208  		s.writePids(c, "user.slice/.../snap.other.snap.$RANDOM2.scope", []int{2})
   209  		s.writePids(c, "user.slice/.../pkg.service", []int{3})
   210  		s.writePids(c, "user.slice/.../snap.pkg_instance.app.$RANDOM3.scope", []int{4})
   211  
   212  		// Write a file which is not cgroup.procs with the number 666 inside.
   213  		// We want to ensure this is not read by accident.
   214  		f := filepath.Join(s.rootDir, "/sys/fs/cgroup/unrelated.txt")
   215  		c.Assert(os.MkdirAll(filepath.Dir(f), 0755), IsNil)
   216  		c.Assert(ioutil.WriteFile(f, []byte("666"), 0644), IsNil)
   217  
   218  		pids, err := cgroup.PidsOfSnap("pkg")
   219  		c.Assert(err, IsNil, comment)
   220  		c.Check(pids, DeepEquals, map[string][]int{
   221  			"snap.pkg.app": {1},
   222  		}, comment)
   223  	}
   224  }