github.com/ubuntu-core/snappy@v0.0.0-20210827154228-9e584df982bb/cmd/snap-bootstrap/initramfs_systemd_mount_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 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  	"fmt"
    24  	"path/filepath"
    25  	"time"
    26  
    27  	. "gopkg.in/check.v1"
    28  
    29  	main "github.com/snapcore/snapd/cmd/snap-bootstrap"
    30  	"github.com/snapcore/snapd/dirs"
    31  	"github.com/snapcore/snapd/systemd"
    32  	"github.com/snapcore/snapd/testutil"
    33  )
    34  
    35  type doSystemdMountSuite struct {
    36  	testutil.BaseTest
    37  }
    38  
    39  var _ = Suite(&doSystemdMountSuite{})
    40  
    41  func (s *doSystemdMountSuite) SetUpTest(c *C) {
    42  	dirs.SetRootDir(c.MkDir())
    43  	s.AddCleanup(func() { dirs.SetRootDir("") })
    44  }
    45  
    46  func (s *doSystemdMountSuite) TestDoSystemdMountUnhappy(c *C) {
    47  	cmd := testutil.MockCommand(c, "systemd-mount", `
    48  echo "mocked error"
    49  exit 1
    50  `)
    51  	defer cmd.Restore()
    52  
    53  	err := main.DoSystemdMount("something", "somewhere only we know", nil)
    54  	c.Assert(err, ErrorMatches, "mocked error")
    55  }
    56  
    57  func (s *doSystemdMountSuite) TestDoSystemdMount(c *C) {
    58  
    59  	testStart := time.Now()
    60  
    61  	tt := []struct {
    62  		what             string
    63  		where            string
    64  		opts             *main.SystemdMountOptions
    65  		timeNowTimes     []time.Time
    66  		isMountedReturns []bool
    67  		expErr           string
    68  		comment          string
    69  	}{
    70  		{
    71  			what:             "/dev/sda3",
    72  			where:            "/run/mnt/data",
    73  			timeNowTimes:     []time.Time{testStart, testStart},
    74  			isMountedReturns: []bool{true},
    75  			comment:          "happy default",
    76  		},
    77  		{
    78  			what:  "tmpfs",
    79  			where: "/run/mnt/data",
    80  			opts: &main.SystemdMountOptions{
    81  				Tmpfs: true,
    82  			},
    83  			timeNowTimes:     []time.Time{testStart, testStart},
    84  			isMountedReturns: []bool{true},
    85  			comment:          "happy tmpfs",
    86  		},
    87  		{
    88  			what:  "tmpfs",
    89  			where: "/run/mnt/data",
    90  			opts: &main.SystemdMountOptions{
    91  				NeedsFsck: true,
    92  			},
    93  			timeNowTimes:     []time.Time{testStart, testStart},
    94  			isMountedReturns: []bool{true},
    95  			comment:          "happy fsck",
    96  		},
    97  		{
    98  			what:  "tmpfs",
    99  			where: "/run/mnt/data",
   100  			opts: &main.SystemdMountOptions{
   101  				Ephemeral: true,
   102  			},
   103  			timeNowTimes:     []time.Time{testStart, testStart},
   104  			isMountedReturns: []bool{true},
   105  			comment:          "happy initramfs ephemeral",
   106  		},
   107  		{
   108  			what:  "tmpfs",
   109  			where: "/run/mnt/data",
   110  			opts: &main.SystemdMountOptions{
   111  				NoWait: true,
   112  			},
   113  			comment: "happy no wait",
   114  		},
   115  		{
   116  			what:             "what",
   117  			where:            "where",
   118  			timeNowTimes:     []time.Time{testStart, testStart, testStart, testStart.Add(2 * time.Minute)},
   119  			isMountedReturns: []bool{false, false},
   120  			expErr:           "timed out after 1m30s waiting for mount what on where",
   121  			comment:          "times out waiting for mount to appear",
   122  		},
   123  		{
   124  			what:  "what",
   125  			where: "where",
   126  			opts: &main.SystemdMountOptions{
   127  				Tmpfs:     true,
   128  				NeedsFsck: true,
   129  			},
   130  			expErr:  "cannot mount \"what\" at \"where\": impossible to fsck a tmpfs",
   131  			comment: "invalid tmpfs + fsck",
   132  		},
   133  		{
   134  			what:  "tmpfs",
   135  			where: "/run/mnt/data",
   136  			opts: &main.SystemdMountOptions{
   137  				NoSuid: true,
   138  			},
   139  			timeNowTimes:     []time.Time{testStart, testStart},
   140  			isMountedReturns: []bool{true},
   141  			comment:          "happy nosuid",
   142  		},
   143  		{
   144  			what:  "tmpfs",
   145  			where: "/run/mnt/data",
   146  			opts: &main.SystemdMountOptions{
   147  				Bind: true,
   148  			},
   149  			timeNowTimes:     []time.Time{testStart, testStart},
   150  			isMountedReturns: []bool{true},
   151  			comment:          "happy bind",
   152  		},
   153  		{
   154  			what:  "tmpfs",
   155  			where: "/run/mnt/data",
   156  			opts: &main.SystemdMountOptions{
   157  				NoSuid: true,
   158  				Bind: true,
   159  			},
   160  			timeNowTimes:     []time.Time{testStart, testStart},
   161  			isMountedReturns: []bool{true},
   162  			comment:          "happy nosuid+bind",
   163  		},
   164  	}
   165  
   166  	for _, t := range tt {
   167  		comment := Commentf(t.comment)
   168  
   169  		var cleanups []func()
   170  
   171  		opts := t.opts
   172  		if opts == nil {
   173  			opts = &main.SystemdMountOptions{}
   174  		}
   175  		dirs.SetRootDir(c.MkDir())
   176  		cleanups = append(cleanups, func() { dirs.SetRootDir("") })
   177  
   178  		cmd := testutil.MockCommand(c, "systemd-mount", ``)
   179  		cleanups = append(cleanups, cmd.Restore)
   180  
   181  		timeCalls := 0
   182  		restore := main.MockTimeNow(func() time.Time {
   183  			timeCalls++
   184  			c.Assert(timeCalls <= len(t.timeNowTimes), Equals, true, comment)
   185  			if timeCalls > len(t.timeNowTimes) {
   186  				c.Errorf("too many time.Now calls (%d)", timeCalls)
   187  				// we want the test to fail at some point and not run forever, so
   188  				// move time way forward to make it for sure time out
   189  				return testStart.Add(10000 * time.Hour)
   190  			}
   191  			return t.timeNowTimes[timeCalls-1]
   192  		})
   193  		cleanups = append(cleanups, restore)
   194  
   195  		cleanups = append(cleanups, func() {
   196  			c.Assert(timeCalls, Equals, len(t.timeNowTimes), comment)
   197  		})
   198  
   199  		isMountedCalls := 0
   200  		restore = main.MockOsutilIsMounted(func(where string) (bool, error) {
   201  			isMountedCalls++
   202  			c.Assert(isMountedCalls <= len(t.isMountedReturns), Equals, true, comment)
   203  			if isMountedCalls > len(t.isMountedReturns) {
   204  				e := fmt.Sprintf("too many osutil.IsMounted calls (%d)", isMountedCalls)
   205  				c.Errorf(e)
   206  				// we want the test to fail at some point and not run forever, so
   207  				// move time way forward to make it for sure time out
   208  				return false, fmt.Errorf(e)
   209  			}
   210  			return t.isMountedReturns[isMountedCalls-1], nil
   211  		})
   212  		cleanups = append(cleanups, restore)
   213  
   214  		cleanups = append(cleanups, func() {
   215  			c.Assert(isMountedCalls, Equals, len(t.isMountedReturns), comment)
   216  		})
   217  
   218  		err := main.DoSystemdMount(t.what, t.where, t.opts)
   219  		if t.expErr != "" {
   220  			c.Assert(err, ErrorMatches, t.expErr)
   221  		} else {
   222  			c.Assert(err, IsNil)
   223  
   224  			args := []string{
   225  				"systemd-mount", t.what, t.where, "--no-pager", "--no-ask-password",
   226  			}
   227  			if opts.Tmpfs {
   228  				args = append(args, "--type=tmpfs")
   229  			}
   230  			if opts.NeedsFsck {
   231  				args = append(args, "--fsck=yes")
   232  			} else {
   233  				args = append(args, "--fsck=no")
   234  			}
   235  			if opts.NoWait {
   236  				args = append(args, "--no-block")
   237  			}
   238  			if opts.Bind && opts.NoSuid {
   239  				args = append(args, "--options=nosuid,bind")
   240  			} else if opts.NoSuid {
   241  				args = append(args, "--options=nosuid")
   242  			} else if opts.Bind {
   243  				args = append(args, "--options=bind")
   244  			}
   245  
   246  			c.Assert(cmd.Calls(), DeepEquals, [][]string{args})
   247  
   248  			// check that the overrides are present if opts.Ephemeral is false,
   249  			// or check the overrides are not present if opts.Ephemeral is true
   250  			for _, initrdUnit := range []string{
   251  				"initrd.target",
   252  				"initrd-fs.target",
   253  				"initrd-switch-root.target",
   254  				"local-fs.target",
   255  			} {
   256  				mountUnit := systemd.EscapeUnitNamePath(t.where)
   257  				fname := fmt.Sprintf("snap_bootstrap_%s.conf", mountUnit)
   258  				unitFile := filepath.Join(dirs.GlobalRootDir, "/run/systemd/system", initrdUnit+".d", fname)
   259  				if opts.Ephemeral {
   260  					c.Assert(unitFile, testutil.FileAbsent)
   261  				} else {
   262  					c.Assert(unitFile, testutil.FileEquals, fmt.Sprintf(`[Unit]
   263  Requires=%[1]s
   264  After=%[1]s
   265  `, mountUnit+".mount"))
   266  				}
   267  			}
   268  		}
   269  
   270  		for _, r := range cleanups {
   271  			r()
   272  		}
   273  	}
   274  }