gitee.com/mysnapcore/mysnapd@v0.1.0/cmd/snap-update-ns/main_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 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  
    20  package main_test
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"testing"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	update "gitee.com/mysnapcore/mysnapd/cmd/snap-update-ns"
    33  	"gitee.com/mysnapcore/mysnapd/dirs"
    34  	"gitee.com/mysnapcore/mysnapd/features"
    35  	"gitee.com/mysnapcore/mysnapd/logger"
    36  	"gitee.com/mysnapcore/mysnapd/osutil"
    37  	"gitee.com/mysnapcore/mysnapd/sandbox/cgroup"
    38  	"gitee.com/mysnapcore/mysnapd/testutil"
    39  )
    40  
    41  func Test(t *testing.T) { TestingT(t) }
    42  
    43  type mainSuite struct {
    44  	testutil.BaseTest
    45  	as  *update.Assumptions
    46  	log *bytes.Buffer
    47  }
    48  
    49  var _ = Suite(&mainSuite{})
    50  
    51  func (s *mainSuite) SetUpTest(c *C) {
    52  	s.BaseTest.SetUpTest(c)
    53  	s.as = &update.Assumptions{}
    54  	buf, restore := logger.MockLogger()
    55  	s.AddCleanup(restore)
    56  	s.log = buf
    57  	s.AddCleanup(cgroup.MockVersion(cgroup.V1, nil))
    58  }
    59  
    60  func (s *mainSuite) TestExecuteMountProfileUpdate(c *C) {
    61  	dirs.SetRootDir(c.MkDir())
    62  	defer dirs.SetRootDir("/")
    63  
    64  	// mount targets look at the the actual local filesystem
    65  	if !osutil.IsDirectory("/usr/share/fonts") || !osutil.IsDirectory("/usr/local/share/fonts") {
    66  		c.Skip("missing local directories (/usr/share/fonts or /usr/local/share/fonts)")
    67  	}
    68  
    69  	restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) {
    70  		return nil, nil
    71  	})
    72  	defer restore()
    73  
    74  	snapName := "foo"
    75  	desiredProfileContent := `/var/lib/snapd/hostfs/usr/share/fonts /usr/share/fonts none bind,ro 0 0
    76  /var/lib/snapd/hostfs/usr/local/share/fonts /usr/local/share/fonts none bind,ro 0 0`
    77  
    78  	desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName)
    79  	err := os.MkdirAll(filepath.Dir(desiredProfilePath), 0755)
    80  	c.Assert(err, IsNil)
    81  	err = ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644)
    82  	c.Assert(err, IsNil)
    83  
    84  	currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName)
    85  	err = os.MkdirAll(filepath.Dir(currentProfilePath), 0755)
    86  	c.Assert(err, IsNil)
    87  	err = ioutil.WriteFile(currentProfilePath, nil, 0644)
    88  	c.Assert(err, IsNil)
    89  
    90  	upCtx := update.NewSystemProfileUpdateContext(snapName, false)
    91  	err = update.ExecuteMountProfileUpdate(upCtx)
    92  	c.Assert(err, IsNil)
    93  
    94  	c.Check(currentProfilePath, testutil.FileEquals, `/var/lib/snapd/hostfs/usr/local/share/fonts /usr/local/share/fonts none bind,ro 0 0
    95  /var/lib/snapd/hostfs/usr/share/fonts /usr/share/fonts none bind,ro 0 0
    96  `)
    97  }
    98  
    99  func (s *mainSuite) TestAddingSyntheticChanges(c *C) {
   100  	dirs.SetRootDir(c.MkDir())
   101  	defer dirs.SetRootDir("/")
   102  
   103  	// The snap `mysnap` wishes to export it's usr/share/mysnap directory and
   104  	// make it appear as if it was in /usr/share/mysnap directly.
   105  	const snapName = "mysnap"
   106  	const currentProfileContent = ""
   107  	const desiredProfileContent = "/snap/mysnap/42/usr/share/mysnap /usr/share/mysnap none bind,ro 0 0"
   108  
   109  	currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName)
   110  	desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName)
   111  
   112  	c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil)
   113  	c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil)
   114  	c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil)
   115  	c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil)
   116  
   117  	// In order to make that work, /usr/share had to be converted to a writable
   118  	// mimic. Some actions were performed under the hood and now we see a
   119  	// subset of them as synthetic changes here.
   120  	//
   121  	// Note that if you compare this to the code that plans a writable mimic
   122  	// you will see that there are additional changes that are _not_
   123  	// represented here. The changes have only one goal: tell
   124  	// snap-update-ns how the mimic can be undone in case it is no longer
   125  	// needed.
   126  	restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) {
   127  		// The change that we were asked to perform is to create a bind mount
   128  		// from within the snap to /usr/share/mysnap.
   129  		c.Assert(chg, DeepEquals, &update.Change{
   130  			Action: update.Mount, Entry: osutil.MountEntry{
   131  				Name: "/snap/mysnap/42/usr/share/mysnap",
   132  				Dir:  "/usr/share/mysnap", Type: "none",
   133  				Options: []string{"bind", "ro"}}})
   134  		synthetic := []*update.Change{
   135  			// The original directory (which was a part of the core snap and is
   136  			// read only) was hidden with a tmpfs.
   137  			{Action: update.Mount, Entry: osutil.MountEntry{
   138  				Dir: "/usr/share", Name: "tmpfs", Type: "tmpfs",
   139  				Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap"}}},
   140  			// For the sake of brevity we will only represent a few of the
   141  			// entries typically there. Normally this list can get quite long.
   142  			// Also note that the entry is a little fake. In reality it was
   143  			// constructed using a temporary bind mount that contained the
   144  			// original mount entries of /usr/share but this fact was lost.
   145  			// Again, the only point of this entry is to correctly perform an
   146  			// undo operation when /usr/share/mysnap is no longer needed.
   147  			{Action: update.Mount, Entry: osutil.MountEntry{
   148  				Dir: "/usr/share/adduser", Name: "/usr/share/adduser",
   149  				Options: []string{"bind", "ro", "x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap"}}},
   150  			{Action: update.Mount, Entry: osutil.MountEntry{
   151  				Dir: "/usr/share/awk", Name: "/usr/share/awk",
   152  				Options: []string{"bind", "ro", "x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap"}}},
   153  		}
   154  		return synthetic, nil
   155  	})
   156  	defer restore()
   157  
   158  	upCtx := update.NewSystemProfileUpdateContext(snapName, false)
   159  	c.Assert(update.ExecuteMountProfileUpdate(upCtx), IsNil)
   160  
   161  	c.Check(currentProfilePath, testutil.FileEquals,
   162  		`tmpfs /usr/share tmpfs x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0
   163  /usr/share/adduser /usr/share/adduser none bind,ro,x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0
   164  /usr/share/awk /usr/share/awk none bind,ro,x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0
   165  /snap/mysnap/42/usr/share/mysnap /usr/share/mysnap none bind,ro 0 0
   166  `)
   167  }
   168  
   169  func (s *mainSuite) TestRemovingSyntheticChanges(c *C) {
   170  	dirs.SetRootDir(c.MkDir())
   171  	defer dirs.SetRootDir("/")
   172  
   173  	c.Assert(os.MkdirAll(dirs.FeaturesDir, 0755), IsNil)
   174  	c.Assert(ioutil.WriteFile(features.RobustMountNamespaceUpdates.ControlFile(), []byte(nil), 0644), IsNil)
   175  
   176  	// The snap `mysnap` no longer wishes to export it's usr/share/mysnap
   177  	// directory. All the synthetic changes that were associated with that mount
   178  	// entry can be discarded.
   179  	const snapName = "mysnap"
   180  	const currentProfileContent = `tmpfs /usr/share tmpfs x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0
   181  /usr/share/adduser /usr/share/adduser none bind,ro,x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0
   182  /usr/share/awk /usr/share/awk none bind,ro,x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0
   183  /snap/mysnap/42/usr/share/mysnap /usr/share/mysnap none bind,ro 0 0
   184  `
   185  	const desiredProfileContent = ""
   186  
   187  	currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName)
   188  	desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName)
   189  
   190  	c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil)
   191  	c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil)
   192  	c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil)
   193  	c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil)
   194  
   195  	n := -1
   196  	restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) {
   197  		n++
   198  		switch n {
   199  		case 0:
   200  			c.Assert(chg, DeepEquals, &update.Change{
   201  				Action: update.Unmount,
   202  				Entry: osutil.MountEntry{
   203  					Name: "/snap/mysnap/42/usr/share/mysnap",
   204  					Dir:  "/usr/share/mysnap", Type: "none",
   205  					Options: []string{"bind", "ro", "x-snapd.detach"},
   206  				},
   207  			})
   208  		case 1:
   209  			c.Check(chg, DeepEquals, &update.Change{
   210  				Action: update.Unmount,
   211  				Entry: osutil.MountEntry{
   212  					Name: "/usr/share/awk", Dir: "/usr/share/awk", Type: "none",
   213  					Options: []string{"bind", "ro", "x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap", "x-snapd.detach"},
   214  				},
   215  			})
   216  		case 2:
   217  			c.Check(chg, DeepEquals, &update.Change{
   218  				Action: update.Unmount,
   219  				Entry: osutil.MountEntry{
   220  					Name: "/usr/share/adduser", Dir: "/usr/share/adduser", Type: "none",
   221  					Options: []string{"bind", "ro", "x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap", "x-snapd.detach"},
   222  				},
   223  			})
   224  		case 3:
   225  			c.Check(chg, DeepEquals, &update.Change{
   226  				Action: update.Unmount,
   227  				Entry: osutil.MountEntry{
   228  					Name: "tmpfs", Dir: "/usr/share", Type: "tmpfs",
   229  					Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap", "x-snapd.detach"},
   230  				},
   231  			})
   232  		default:
   233  			panic(fmt.Sprintf("unexpected call n=%d, chg: %v", n, *chg))
   234  		}
   235  		return nil, nil
   236  	})
   237  	defer restore()
   238  
   239  	upCtx := update.NewSystemProfileUpdateContext(snapName, false)
   240  	c.Assert(update.ExecuteMountProfileUpdate(upCtx), IsNil)
   241  
   242  	c.Check(currentProfilePath, testutil.FileEquals, "")
   243  }
   244  
   245  func (s *mainSuite) TestApplyingLayoutChanges(c *C) {
   246  	dirs.SetRootDir(c.MkDir())
   247  	defer dirs.SetRootDir("/")
   248  
   249  	const snapName = "mysnap"
   250  	const currentProfileContent = ""
   251  	const desiredProfileContent = "/snap/mysnap/42/usr/share/mysnap /usr/share/mysnap none bind,ro,x-snapd.origin=layout 0 0"
   252  
   253  	currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName)
   254  	desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName)
   255  
   256  	c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil)
   257  	c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil)
   258  	c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil)
   259  	c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil)
   260  
   261  	n := -1
   262  	restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) {
   263  		n++
   264  		switch n {
   265  		case 0:
   266  			c.Assert(chg, DeepEquals, &update.Change{
   267  				Action: update.Mount,
   268  				Entry: osutil.MountEntry{
   269  					Name: "/snap/mysnap/42/usr/share/mysnap",
   270  					Dir:  "/usr/share/mysnap", Type: "none",
   271  					Options: []string{"bind", "ro", "x-snapd.origin=layout"},
   272  				},
   273  			})
   274  			return nil, fmt.Errorf("testing")
   275  		default:
   276  			panic(fmt.Sprintf("unexpected call n=%d, chg: %v", n, *chg))
   277  		}
   278  	})
   279  	defer restore()
   280  
   281  	// The error was not ignored, we bailed out.
   282  	upCtx := update.NewSystemProfileUpdateContext(snapName, false)
   283  	c.Assert(update.ExecuteMountProfileUpdate(upCtx), ErrorMatches, "testing")
   284  
   285  	c.Check(currentProfilePath, testutil.FileEquals, "")
   286  }
   287  
   288  func (s *mainSuite) TestApplyingParallelInstanceChanges(c *C) {
   289  	dirs.SetRootDir(c.MkDir())
   290  	defer dirs.SetRootDir("/")
   291  
   292  	const snapName = "mysnap"
   293  	const currentProfileContent = ""
   294  	const desiredProfileContent = "/snap/mysnap_foo /snap/mysnap none rbind,x-snapd.origin=overname 0 0"
   295  
   296  	currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName)
   297  	desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName)
   298  
   299  	c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil)
   300  	c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil)
   301  	c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil)
   302  	c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil)
   303  
   304  	n := -1
   305  	restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) {
   306  		n++
   307  		switch n {
   308  		case 0:
   309  			c.Assert(chg, DeepEquals, &update.Change{
   310  				Action: update.Mount,
   311  				Entry: osutil.MountEntry{
   312  					Name: "/snap/mysnap_foo",
   313  					Dir:  "/snap/mysnap", Type: "none",
   314  					Options: []string{"rbind", "x-snapd.origin=overname"},
   315  				},
   316  			})
   317  			return nil, fmt.Errorf("testing")
   318  		default:
   319  			panic(fmt.Sprintf("unexpected call n=%d, chg: %v", n, *chg))
   320  		}
   321  	})
   322  	defer restore()
   323  
   324  	// The error was not ignored, we bailed out.
   325  	upCtx := update.NewSystemProfileUpdateContext(snapName, false)
   326  	c.Assert(update.ExecuteMountProfileUpdate(upCtx), ErrorMatches, "testing")
   327  
   328  	c.Check(currentProfilePath, testutil.FileEquals, "")
   329  }
   330  
   331  func (s *mainSuite) TestApplyIgnoredMissingMount(c *C) {
   332  	dirs.SetRootDir(c.MkDir())
   333  	defer dirs.SetRootDir("/")
   334  
   335  	const snapName = "mysnap"
   336  	const currentProfileContent = ""
   337  	const desiredProfileContent = "/source /target none bind,x-snapd.ignore-missing 0 0"
   338  
   339  	currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName)
   340  	desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName)
   341  
   342  	c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil)
   343  	c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil)
   344  	c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil)
   345  	c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil)
   346  
   347  	n := -1
   348  	restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) {
   349  		n++
   350  		switch n {
   351  		case 0:
   352  			c.Assert(chg, DeepEquals, &update.Change{
   353  				Action: update.Mount,
   354  				Entry: osutil.MountEntry{
   355  					Name:    "/source",
   356  					Dir:     "/target",
   357  					Type:    "none",
   358  					Options: []string{"bind", "x-snapd.ignore-missing"},
   359  				},
   360  			})
   361  			return nil, update.ErrIgnoredMissingMount
   362  		default:
   363  			panic(fmt.Sprintf("unexpected call n=%d, chg: %v", n, *chg))
   364  		}
   365  	})
   366  	defer restore()
   367  
   368  	// The error was ignored, and no mount was recorded in the profile
   369  	upCtx := update.NewSystemProfileUpdateContext(snapName, false)
   370  	c.Assert(update.ExecuteMountProfileUpdate(upCtx), IsNil)
   371  	c.Check(s.log.String(), Equals, "")
   372  	c.Check(currentProfilePath, testutil.FileEquals, "")
   373  }
   374  
   375  func (s *mainSuite) TestApplyUserFstab(c *C) {
   376  	dirs.SetRootDir(c.MkDir())
   377  	defer dirs.SetRootDir("/")
   378  
   379  	var changes []update.Change
   380  	restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) {
   381  		changes = append(changes, *chg)
   382  		return nil, nil
   383  	})
   384  	defer restore()
   385  
   386  	snapName := "foo"
   387  	desiredProfileContent := `$XDG_RUNTIME_DIR/doc/by-app/snap.foo $XDG_RUNTIME_DIR/doc none bind,rw 0 0`
   388  
   389  	desiredProfilePath := fmt.Sprintf("%s/snap.%s.user-fstab", dirs.SnapMountPolicyDir, snapName)
   390  	err := os.MkdirAll(filepath.Dir(desiredProfilePath), 0755)
   391  	c.Assert(err, IsNil)
   392  	err = ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644)
   393  	c.Assert(err, IsNil)
   394  
   395  	upCtx := update.NewUserProfileUpdateContext(snapName, true, 1000)
   396  	err = update.ExecuteMountProfileUpdate(upCtx)
   397  	c.Assert(err, IsNil)
   398  
   399  	xdgRuntimeDir := fmt.Sprintf("%s/%d", dirs.XdgRuntimeDirBase, 1000)
   400  
   401  	c.Assert(changes, HasLen, 1)
   402  	c.Assert(changes[0].Action, Equals, update.Mount)
   403  	c.Assert(changes[0].Entry.Name, Equals, xdgRuntimeDir+"/doc/by-app/snap.foo")
   404  	c.Assert(changes[0].Entry.Dir, Matches, xdgRuntimeDir+"/doc")
   405  }