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 }