github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/snapstate/backend/setup_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2016 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 backend_test 21 22 import ( 23 "fmt" 24 "os" 25 "path/filepath" 26 "strings" 27 28 . "gopkg.in/check.v1" 29 30 "github.com/snapcore/snapd/boot/boottest" 31 "github.com/snapcore/snapd/bootloader" 32 "github.com/snapcore/snapd/bootloader/bootloadertest" 33 "github.com/snapcore/snapd/dirs" 34 "github.com/snapcore/snapd/osutil" 35 "github.com/snapcore/snapd/overlord/snapstate/backend" 36 "github.com/snapcore/snapd/progress" 37 "github.com/snapcore/snapd/snap" 38 "github.com/snapcore/snapd/snap/snaptest" 39 "github.com/snapcore/snapd/systemd" 40 "github.com/snapcore/snapd/testutil" 41 ) 42 43 type setupSuite struct { 44 testutil.BaseTest 45 be backend.Backend 46 umount *testutil.MockCmd 47 systemctlRestorer func() 48 } 49 50 var _ = Suite(&setupSuite{}) 51 52 func (s *setupSuite) SetUpTest(c *C) { 53 s.BaseTest.SetUpTest(c) 54 s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})) 55 56 dirs.SetRootDir(c.MkDir()) 57 58 // needed for system key generation 59 restore := osutil.MockMountInfo("") 60 s.AddCleanup(restore) 61 62 err := os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc", "systemd", "system", "multi-user.target.wants"), 0755) 63 c.Assert(err, IsNil) 64 65 s.systemctlRestorer = systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 66 return []byte("ActiveState=inactive\n"), nil 67 }) 68 69 s.umount = testutil.MockCommand(c, "umount", "") 70 } 71 72 func (s *setupSuite) TearDownTest(c *C) { 73 s.BaseTest.TearDownTest(c) 74 dirs.SetRootDir("") 75 bootloader.Force(nil) 76 s.umount.Restore() 77 s.systemctlRestorer() 78 } 79 80 var ( 81 mockClassicDev = boottest.MockDevice("") 82 mockDev = boottest.MockDevice("boot-snap") 83 mockDevWithKernel = boottest.MockDevice("kernel") 84 ) 85 86 func (s *setupSuite) TestSetupDoUndoSimple(c *C) { 87 snapPath := makeTestSnap(c, helloYaml1) 88 89 si := snap.SideInfo{ 90 RealName: "hello", 91 Revision: snap.R(14), 92 } 93 94 snapType, installRecord, err := s.be.SetupSnap(snapPath, "hello", &si, mockDev, progress.Null) 95 c.Assert(err, IsNil) 96 c.Assert(installRecord, NotNil) 97 c.Check(snapType, Equals, snap.TypeApp) 98 99 // after setup the snap file is in the right dir 100 c.Assert(osutil.FileExists(filepath.Join(dirs.SnapBlobDir, "hello_14.snap")), Equals, true) 101 102 // ensure the right unit is created 103 mup := systemd.MountUnitPath(filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "hello/14")) 104 c.Assert(mup, testutil.FileMatches, fmt.Sprintf("(?ms).*^Where=%s", filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "hello/14"))) 105 c.Assert(mup, testutil.FileMatches, "(?ms).*^What=/var/lib/snapd/snaps/hello_14.snap") 106 107 minInfo := snap.MinimalPlaceInfo("hello", snap.R(14)) 108 // mount dir was created 109 c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, true) 110 111 // undo undoes the mount unit and the instdir creation 112 err = s.be.UndoSetupSnap(minInfo, "app", nil, mockDev, progress.Null) 113 c.Assert(err, IsNil) 114 115 l, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.mount")) 116 c.Assert(l, HasLen, 0) 117 c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, false) 118 119 c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, false) 120 121 } 122 123 func (s *setupSuite) TestSetupDoUndoInstance(c *C) { 124 snapPath := makeTestSnap(c, helloYaml1) 125 126 si := snap.SideInfo{ 127 RealName: "hello", 128 Revision: snap.R(14), 129 } 130 131 snapType, installRecord, err := s.be.SetupSnap(snapPath, "hello_instance", &si, mockDev, progress.Null) 132 c.Assert(err, IsNil) 133 c.Assert(installRecord, NotNil) 134 c.Check(snapType, Equals, snap.TypeApp) 135 136 // after setup the snap file is in the right dir 137 c.Assert(osutil.FileExists(filepath.Join(dirs.SnapBlobDir, "hello_instance_14.snap")), Equals, true) 138 139 // ensure the right unit is created 140 mup := systemd.MountUnitPath(filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "hello_instance/14")) 141 c.Assert(mup, testutil.FileMatches, fmt.Sprintf("(?ms).*^Where=%s", filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "hello_instance/14"))) 142 c.Assert(mup, testutil.FileMatches, "(?ms).*^What=/var/lib/snapd/snaps/hello_instance_14.snap") 143 144 minInfo := snap.MinimalPlaceInfo("hello_instance", snap.R(14)) 145 // mount dir was created 146 c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, true) 147 148 // undo undoes the mount unit and the instdir creation 149 err = s.be.UndoSetupSnap(minInfo, "app", nil, mockDev, progress.Null) 150 c.Assert(err, IsNil) 151 152 l, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.mount")) 153 c.Assert(l, HasLen, 0) 154 c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, false) 155 156 c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, false) 157 } 158 159 func (s *setupSuite) TestSetupDoUndoKernel(c *C) { 160 bloader := bootloadertest.Mock("mock", c.MkDir()) 161 bootloader.Force(bloader) 162 163 // we don't get real mounting 164 os.Setenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS", "1") 165 defer os.Unsetenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS") 166 167 testFiles := [][]string{ 168 {"kernel.img", "kernel"}, 169 {"initrd.img", "initrd"}, 170 {"modules/4.4.0-14-generic/foo.ko", "a module"}, 171 {"firmware/bar.bin", "some firmware"}, 172 {"meta/kernel.yaml", "version: 4.2"}, 173 } 174 snapPath := snaptest.MakeTestSnapWithFiles(c, `name: kernel 175 version: 1.0 176 type: kernel 177 `, testFiles) 178 179 si := snap.SideInfo{ 180 RealName: "kernel", 181 Revision: snap.R(140), 182 } 183 184 snapType, installRecord, err := s.be.SetupSnap(snapPath, "kernel", &si, mockDevWithKernel, progress.Null) 185 c.Assert(err, IsNil) 186 c.Check(snapType, Equals, snap.TypeKernel) 187 c.Assert(installRecord, NotNil) 188 c.Assert(bloader.ExtractKernelAssetsCalls, HasLen, 1) 189 c.Assert(bloader.ExtractKernelAssetsCalls[0].InstanceName(), Equals, "kernel") 190 minInfo := snap.MinimalPlaceInfo("kernel", snap.R(140)) 191 192 // undo deletes the kernel assets again 193 err = s.be.UndoSetupSnap(minInfo, "kernel", nil, mockDevWithKernel, progress.Null) 194 c.Assert(err, IsNil) 195 c.Assert(bloader.RemoveKernelAssetsCalls, HasLen, 1) 196 c.Assert(bloader.RemoveKernelAssetsCalls[0].InstanceName(), Equals, "kernel") 197 } 198 199 func (s *setupSuite) TestSetupDoIdempotent(c *C) { 200 // make sure that a retry wouldn't stumble on partial work 201 // use a kernel because that does and need to do strictly more 202 203 // this cannot check systemd own behavior though around mounts! 204 205 bloader := bootloadertest.Mock("mock", c.MkDir()) 206 bootloader.Force(bloader) 207 // we don't get real mounting 208 os.Setenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS", "1") 209 defer os.Unsetenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS") 210 211 testFiles := [][]string{ 212 {"kernel.img", "kernel"}, 213 {"initrd.img", "initrd"}, 214 {"modules/4.4.0-14-generic/foo.ko", "a module"}, 215 {"firmware/bar.bin", "some firmware"}, 216 {"meta/kernel.yaml", "version: 4.2"}, 217 } 218 snapPath := snaptest.MakeTestSnapWithFiles(c, `name: kernel 219 version: 1.0 220 type: kernel 221 `, testFiles) 222 223 si := snap.SideInfo{ 224 RealName: "kernel", 225 Revision: snap.R(140), 226 } 227 228 _, installRecord, err := s.be.SetupSnap(snapPath, "kernel", &si, mockDevWithKernel, progress.Null) 229 c.Assert(err, IsNil) 230 c.Assert(installRecord, NotNil) 231 c.Assert(bloader.ExtractKernelAssetsCalls, HasLen, 1) 232 c.Assert(bloader.ExtractKernelAssetsCalls[0].InstanceName(), Equals, "kernel") 233 234 // retry run 235 _, installRecord, err = s.be.SetupSnap(snapPath, "kernel", &si, mockDevWithKernel, progress.Null) 236 c.Assert(err, IsNil) 237 c.Assert(installRecord, NotNil) 238 c.Assert(bloader.ExtractKernelAssetsCalls, HasLen, 2) 239 c.Assert(bloader.ExtractKernelAssetsCalls[1].InstanceName(), Equals, "kernel") 240 minInfo := snap.MinimalPlaceInfo("kernel", snap.R(140)) 241 242 // sanity checks 243 l, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.mount")) 244 c.Assert(l, HasLen, 1) 245 c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, true) 246 247 c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, true) 248 } 249 250 func (s *setupSuite) TestSetupUndoIdempotent(c *C) { 251 // make sure that a retry wouldn't stumble on partial work 252 // use a kernel because that does and need to do strictly more 253 254 // this cannot check systemd own behavior though around mounts! 255 256 bloader := bootloadertest.Mock("mock", c.MkDir()) 257 bootloader.Force(bloader) 258 // we don't get real mounting 259 os.Setenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS", "1") 260 defer os.Unsetenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS") 261 262 testFiles := [][]string{ 263 {"kernel.img", "kernel"}, 264 {"initrd.img", "initrd"}, 265 {"modules/4.4.0-14-generic/foo.ko", "a module"}, 266 {"firmware/bar.bin", "some firmware"}, 267 {"meta/kernel.yaml", "version: 4.2"}, 268 } 269 snapPath := snaptest.MakeTestSnapWithFiles(c, `name: kernel 270 version: 1.0 271 type: kernel 272 `, testFiles) 273 274 si := snap.SideInfo{ 275 RealName: "kernel", 276 Revision: snap.R(140), 277 } 278 279 _, installRecord, err := s.be.SetupSnap(snapPath, "kernel", &si, mockDevWithKernel, progress.Null) 280 c.Assert(err, IsNil) 281 c.Assert(installRecord, NotNil) 282 283 minInfo := snap.MinimalPlaceInfo("kernel", snap.R(140)) 284 285 err = s.be.UndoSetupSnap(minInfo, "kernel", nil, mockDevWithKernel, progress.Null) 286 c.Assert(err, IsNil) 287 288 // retry run 289 err = s.be.UndoSetupSnap(minInfo, "kernel", nil, mockDevWithKernel, progress.Null) 290 c.Assert(err, IsNil) 291 292 // sanity checks 293 l, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.mount")) 294 c.Assert(l, HasLen, 0) 295 c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, false) 296 297 c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, false) 298 299 // assets got extracted and then removed again 300 c.Assert(bloader.ExtractKernelAssetsCalls, HasLen, 1) 301 c.Assert(bloader.RemoveKernelAssetsCalls, HasLen, 1) 302 } 303 304 func (s *setupSuite) TestSetupUndoKeepsTargetSnapIfSymlink(c *C) { 305 snapPath := makeTestSnap(c, helloYaml1) 306 c.Assert(os.MkdirAll(dirs.SnapBlobDir, 0755), IsNil) 307 // symlink the test snap under target blob dir where SetupSnap would normally 308 // install it, so that it realizes there is nothing to do. 309 tmpPath := filepath.Join(dirs.SnapBlobDir, "hello_14.snap") 310 c.Assert(os.Symlink(snapPath, tmpPath), IsNil) 311 312 si := snap.SideInfo{RealName: "hello", Revision: snap.R(14)} 313 _, installRecord, err := s.be.SetupSnap(snapPath, "hello", &si, mockDev, progress.Null) 314 c.Assert(err, IsNil) 315 c.Assert(installRecord, NotNil) 316 c.Check(installRecord.TargetSnapExisted, Equals, true) 317 318 minInfo := snap.MinimalPlaceInfo("hello", snap.R(14)) 319 320 // after setup the snap file is in the right dir 321 c.Assert(osutil.FileExists(filepath.Join(dirs.SnapBlobDir, "hello_14.snap")), Equals, true) 322 c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, true) 323 // sanity 324 c.Assert(osutil.IsSymlink(minInfo.MountFile()), Equals, true) 325 326 // undo keeps the target .snap file intact if requested 327 installRecord = &backend.InstallRecord{TargetSnapExisted: true} 328 c.Assert(s.be.UndoSetupSnap(minInfo, "app", installRecord, mockDev, progress.Null), IsNil) 329 c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, true) 330 } 331 332 func (s *setupSuite) TestSetupUndoKeepsTargetSnapIgnoredIfNotSymlink(c *C) { 333 snapPath := makeTestSnap(c, helloYaml1) 334 c.Assert(os.MkdirAll(dirs.SnapBlobDir, 0755), IsNil) 335 // copy test snap to target blob dir where SetupSnap would normally install it, 336 // so that it realizes there is nothing to do. 337 tmpPath := filepath.Join(dirs.SnapBlobDir, "hello_14.snap") 338 c.Assert(osutil.CopyFile(snapPath, tmpPath, 0), IsNil) 339 340 si := snap.SideInfo{RealName: "hello", Revision: snap.R(14)} 341 _, installRecord, err := s.be.SetupSnap(snapPath, "hello", &si, mockDev, progress.Null) 342 c.Assert(err, IsNil) 343 c.Assert(installRecord, NotNil) 344 c.Check(installRecord.TargetSnapExisted, Equals, true) 345 346 minInfo := snap.MinimalPlaceInfo("hello", snap.R(14)) 347 348 // after setup the snap file is in the right dir 349 c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, true) 350 351 installRecord = &backend.InstallRecord{TargetSnapExisted: true} 352 c.Assert(s.be.UndoSetupSnap(minInfo, "app", installRecord, mockDev, progress.Null), IsNil) 353 c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, false) 354 } 355 356 func (s *setupSuite) TestSetupCleanupAfterFail(c *C) { 357 snapPath := makeTestSnap(c, helloYaml1) 358 359 si := snap.SideInfo{ 360 RealName: "hello", 361 Revision: snap.R(14), 362 } 363 364 r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 365 // mount unit start fails 366 if len(cmd) >= 2 && cmd[0] == "start" && strings.HasSuffix(cmd[1], ".mount") { 367 return nil, fmt.Errorf("failed") 368 } 369 return []byte("ActiveState=inactive\n"), nil 370 }) 371 defer r() 372 373 _, installRecord, err := s.be.SetupSnap(snapPath, "hello", &si, mockDev, progress.Null) 374 c.Assert(err, ErrorMatches, "failed") 375 c.Check(installRecord, IsNil) 376 377 // everything is gone 378 l, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.mount")) 379 c.Check(l, HasLen, 0) 380 381 minInfo := snap.MinimalPlaceInfo("hello", snap.R(14)) 382 c.Check(osutil.FileExists(minInfo.MountDir()), Equals, false) 383 c.Check(osutil.FileExists(minInfo.MountFile()), Equals, false) 384 c.Check(osutil.FileExists(filepath.Join(dirs.SnapBlobDir, "hello_14.snap")), Equals, false) 385 } 386 387 func (s *setupSuite) TestRemoveSnapFilesDir(c *C) { 388 snapPath := makeTestSnap(c, helloYaml1) 389 390 si := snap.SideInfo{ 391 RealName: "hello", 392 Revision: snap.R(14), 393 } 394 395 snapType, installRecord, err := s.be.SetupSnap(snapPath, "hello_instance", &si, mockDev, progress.Null) 396 c.Assert(err, IsNil) 397 c.Assert(installRecord, NotNil) 398 c.Check(snapType, Equals, snap.TypeApp) 399 400 minInfo := snap.MinimalPlaceInfo("hello_instance", snap.R(14)) 401 // mount dir was created 402 c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, true) 403 404 installRecord = &backend.InstallRecord{} 405 s.be.RemoveSnapFiles(minInfo, snapType, installRecord, mockDev, progress.Null) 406 c.Assert(err, IsNil) 407 408 l, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.mount")) 409 c.Assert(l, HasLen, 0) 410 c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, false) 411 c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, false) 412 c.Assert(osutil.FileExists(snap.BaseDir(minInfo.InstanceName())), Equals, true) 413 c.Assert(osutil.FileExists(snap.BaseDir(minInfo.SnapName())), Equals, true) 414 415 // /snap/hello is kept as other instances exist 416 err = s.be.RemoveSnapDir(minInfo, true) 417 c.Assert(err, IsNil) 418 c.Assert(osutil.FileExists(snap.BaseDir(minInfo.InstanceName())), Equals, false) 419 c.Assert(osutil.FileExists(snap.BaseDir(minInfo.SnapName())), Equals, true) 420 421 // /snap/hello is removed when there are no more instances 422 err = s.be.RemoveSnapDir(minInfo, false) 423 c.Assert(err, IsNil) 424 c.Assert(osutil.FileExists(snap.BaseDir(minInfo.SnapName())), Equals, false) 425 }