github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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  	main "github.com/snapcore/snapd/cmd/snap-bootstrap"
    28  	"github.com/snapcore/snapd/dirs"
    29  	"github.com/snapcore/snapd/systemd"
    30  	"github.com/snapcore/snapd/testutil"
    31  
    32  	. "gopkg.in/check.v1"
    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  
   135  	for _, t := range tt {
   136  		comment := Commentf(t.comment)
   137  
   138  		var cleanups []func()
   139  
   140  		opts := t.opts
   141  		if opts == nil {
   142  			opts = &main.SystemdMountOptions{}
   143  		}
   144  		dirs.SetRootDir(c.MkDir())
   145  		cleanups = append(cleanups, func() { dirs.SetRootDir("") })
   146  
   147  		cmd := testutil.MockCommand(c, "systemd-mount", ``)
   148  		cleanups = append(cleanups, cmd.Restore)
   149  
   150  		timeCalls := 0
   151  		restore := main.MockTimeNow(func() time.Time {
   152  			timeCalls++
   153  			c.Assert(timeCalls <= len(t.timeNowTimes), Equals, true, comment)
   154  			if timeCalls > len(t.timeNowTimes) {
   155  				c.Errorf("too many time.Now calls (%d)", timeCalls)
   156  				// we want the test to fail at some point and not run forever, so
   157  				// move time way forward to make it for sure time out
   158  				return testStart.Add(10000 * time.Hour)
   159  			}
   160  			return t.timeNowTimes[timeCalls-1]
   161  		})
   162  		cleanups = append(cleanups, restore)
   163  
   164  		cleanups = append(cleanups, func() {
   165  			c.Assert(timeCalls, Equals, len(t.timeNowTimes), comment)
   166  		})
   167  
   168  		isMountedCalls := 0
   169  		restore = main.MockOsutilIsMounted(func(where string) (bool, error) {
   170  			isMountedCalls++
   171  			c.Assert(isMountedCalls <= len(t.isMountedReturns), Equals, true, comment)
   172  			if isMountedCalls > len(t.isMountedReturns) {
   173  				e := fmt.Sprintf("too many osutil.IsMounted calls (%d)", isMountedCalls)
   174  				c.Errorf(e)
   175  				// we want the test to fail at some point and not run forever, so
   176  				// move time way forward to make it for sure time out
   177  				return false, fmt.Errorf(e)
   178  			}
   179  			return t.isMountedReturns[isMountedCalls-1], nil
   180  		})
   181  		cleanups = append(cleanups, restore)
   182  
   183  		cleanups = append(cleanups, func() {
   184  			c.Assert(isMountedCalls, Equals, len(t.isMountedReturns), comment)
   185  		})
   186  
   187  		err := main.DoSystemdMount(t.what, t.where, t.opts)
   188  		if t.expErr != "" {
   189  			c.Assert(err, ErrorMatches, t.expErr)
   190  		} else {
   191  			c.Assert(err, IsNil)
   192  
   193  			args := []string{
   194  				"systemd-mount", t.what, t.where, "--no-pager", "--no-ask-password",
   195  			}
   196  			if opts.Tmpfs {
   197  				args = append(args, "--type=tmpfs")
   198  			}
   199  			if opts.NeedsFsck {
   200  				args = append(args, "--fsck=yes")
   201  			} else {
   202  				args = append(args, "--fsck=no")
   203  			}
   204  			if opts.NoWait {
   205  				args = append(args, "--no-block")
   206  			}
   207  			c.Assert(cmd.Calls(), DeepEquals, [][]string{args})
   208  
   209  			// check that the overrides are present if opts.Ephemeral is false,
   210  			// or check the overrides are not present if opts.Ephemeral is true
   211  			for _, initrdUnit := range []string{
   212  				"initrd.target",
   213  				"initrd-fs.target",
   214  				"initrd-switch-root.target",
   215  				"local-fs.target",
   216  			} {
   217  				mountUnit := systemd.EscapeUnitNamePath(t.where)
   218  				fname := fmt.Sprintf("snap_bootstrap_%s.conf", mountUnit)
   219  				unitFile := filepath.Join(dirs.GlobalRootDir, "/run/systemd/system", initrdUnit+".d", fname)
   220  				if opts.Ephemeral {
   221  					c.Assert(unitFile, testutil.FileAbsent)
   222  				} else {
   223  					c.Assert(unitFile, testutil.FileEquals, fmt.Sprintf(`[Unit]
   224  Requires=%[1]s
   225  After=%[1]s
   226  `, mountUnit+".mount"))
   227  				}
   228  			}
   229  		}
   230  
   231  		for _, r := range cleanups {
   232  			r()
   233  		}
   234  	}
   235  }