github.com/freetocompute/snapd@v0.0.0-20210618182524-2fb355d72fd9/gadget/install/install_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 // +build !nosecboot 3 4 /* 5 * Copyright (C) 2019-2020 Canonical Ltd 6 * 7 * This program is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 3 as 9 * published by the Free Software Foundation. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 * 19 */ 20 21 package install_test 22 23 import ( 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 28 . "gopkg.in/check.v1" 29 30 "github.com/snapcore/snapd/dirs" 31 "github.com/snapcore/snapd/gadget" 32 "github.com/snapcore/snapd/gadget/install" 33 "github.com/snapcore/snapd/gadget/quantity" 34 "github.com/snapcore/snapd/testutil" 35 ) 36 37 type installSuite struct { 38 testutil.BaseTest 39 40 dir string 41 } 42 43 var _ = Suite(&installSuite{}) 44 45 // XXX: write a very high level integration like test here that 46 // mocks the world (sfdisk,lsblk,mkfs,...)? probably silly as 47 // each part inside bootstrap is tested and we have a spread test 48 49 func (s *installSuite) SetUpTest(c *C) { 50 s.BaseTest.SetUpTest(c) 51 52 s.dir = c.MkDir() 53 dirs.SetRootDir(s.dir) 54 s.AddCleanup(func() { dirs.SetRootDir("/") }) 55 } 56 57 func (s *installSuite) TestInstallRunError(c *C) { 58 sys, err := install.Run(nil, "", "", "", install.Options{}, nil) 59 c.Assert(err, ErrorMatches, "cannot use empty gadget root directory") 60 c.Check(sys, IsNil) 61 } 62 63 const mockGadgetYaml = `volumes: 64 pc: 65 bootloader: grub 66 structure: 67 - name: mbr 68 type: mbr 69 size: 440 70 - name: BIOS Boot 71 type: DA,21686148-6449-6E6F-744E-656564454649 72 size: 1M 73 offset: 1M 74 offset-write: mbr+92 75 ` 76 77 const mockExtraStructure = ` 78 - name: Writable 79 role: system-data 80 filesystem-label: writable 81 filesystem: ext4 82 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 83 size: 1200M 84 ` 85 86 var mockDeviceLayout = gadget.OnDiskVolume{ 87 Structure: []gadget.OnDiskStructure{ 88 { 89 LaidOutStructure: gadget.LaidOutStructure{ 90 VolumeStructure: &gadget.VolumeStructure{ 91 Name: "mbr", 92 Size: 440, 93 }, 94 StartOffset: 0, 95 }, 96 Node: "/dev/node1", 97 }, 98 { 99 LaidOutStructure: gadget.LaidOutStructure{ 100 VolumeStructure: &gadget.VolumeStructure{ 101 Name: "BIOS Boot", 102 Size: 1 * quantity.SizeMiB, 103 }, 104 StartOffset: 1 * quantity.OffsetMiB, 105 }, 106 Node: "/dev/node2", 107 }, 108 }, 109 ID: "anything", 110 Device: "/dev/node", 111 Schema: "gpt", 112 Size: 2 * quantity.SizeGiB, 113 SectorSize: 512, 114 } 115 116 func (s *installSuite) TestLayoutCompatibility(c *C) { 117 // same contents (the locally created structure should be ignored) 118 gadgetLayout := layoutFromYaml(c, mockGadgetYaml, nil) 119 err := install.EnsureLayoutCompatibility(gadgetLayout, &mockDeviceLayout) 120 c.Assert(err, IsNil) 121 122 // layout still compatible with a larger disk sector size 123 mockDeviceLayout.SectorSize = 4096 124 err = install.EnsureLayoutCompatibility(gadgetLayout, &mockDeviceLayout) 125 c.Assert(err, IsNil) 126 127 // layout not compatible with a sector size that's not a factor of the 128 // structure sizes 129 mockDeviceLayout.SectorSize = 513 130 err = install.EnsureLayoutCompatibility(gadgetLayout, &mockDeviceLayout) 131 c.Assert(err, ErrorMatches, `gadget volume structure #1 \(\"BIOS Boot\"\) size is not a multiple of disk sector size 513`) 132 133 // rest for the rest of the test 134 mockDeviceLayout.SectorSize = 512 135 136 // missing structure (that's ok) 137 gadgetLayoutWithExtras := layoutFromYaml(c, mockGadgetYaml+mockExtraStructure, nil) 138 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &mockDeviceLayout) 139 c.Assert(err, IsNil) 140 141 deviceLayoutWithExtras := mockDeviceLayout 142 deviceLayoutWithExtras.Structure = append(deviceLayoutWithExtras.Structure, 143 gadget.OnDiskStructure{ 144 LaidOutStructure: gadget.LaidOutStructure{ 145 VolumeStructure: &gadget.VolumeStructure{ 146 Name: "Extra partition", 147 Size: 10 * quantity.SizeMiB, 148 Label: "extra", 149 }, 150 StartOffset: 2 * quantity.OffsetMiB, 151 }, 152 Node: "/dev/node3", 153 }, 154 ) 155 // extra structure (should fail) 156 err = install.EnsureLayoutCompatibility(gadgetLayout, &deviceLayoutWithExtras) 157 c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node3.* in gadget`) 158 159 // layout is not compatible if the device is too small 160 smallDeviceLayout := mockDeviceLayout 161 smallDeviceLayout.Size = 100 * quantity.SizeMiB 162 // sanity check 163 c.Check(gadgetLayoutWithExtras.Size > smallDeviceLayout.Size, Equals, true) 164 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &smallDeviceLayout) 165 c.Assert(err, ErrorMatches, `device /dev/node \(100 MiB\) is too small to fit the requested layout \(1\.17 GiB\)`) 166 167 } 168 169 func (s *installSuite) TestMBRLayoutCompatibility(c *C) { 170 const mockMBRGadgetYaml = `volumes: 171 pc: 172 schema: mbr 173 bootloader: grub 174 structure: 175 - name: mbr 176 type: mbr 177 size: 440 178 - name: BIOS Boot 179 type: DA,21686148-6449-6E6F-744E-656564454649 180 size: 1M 181 offset: 1M 182 offset-write: mbr+92 183 ` 184 var mockMBRDeviceLayout = gadget.OnDiskVolume{ 185 Structure: []gadget.OnDiskStructure{ 186 { 187 LaidOutStructure: gadget.LaidOutStructure{ 188 VolumeStructure: &gadget.VolumeStructure{ 189 // partition names have no 190 // meaning in MBR schema 191 Name: "other", 192 Size: 440, 193 }, 194 StartOffset: 0, 195 }, 196 Node: "/dev/node1", 197 }, 198 { 199 LaidOutStructure: gadget.LaidOutStructure{ 200 VolumeStructure: &gadget.VolumeStructure{ 201 // partition names have no 202 // meaning in MBR schema 203 Name: "different BIOS Boot", 204 Size: 1 * quantity.SizeMiB, 205 }, 206 StartOffset: 1 * quantity.OffsetMiB, 207 }, 208 Node: "/dev/node2", 209 }, 210 }, 211 ID: "anything", 212 Device: "/dev/node", 213 Schema: "dos", 214 Size: 2 * quantity.SizeGiB, 215 SectorSize: 512, 216 } 217 gadgetLayout := layoutFromYaml(c, mockMBRGadgetYaml, nil) 218 err := install.EnsureLayoutCompatibility(gadgetLayout, &mockMBRDeviceLayout) 219 c.Assert(err, IsNil) 220 // structure is missing from disk 221 gadgetLayoutWithExtras := layoutFromYaml(c, mockMBRGadgetYaml+mockExtraStructure, nil) 222 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &mockMBRDeviceLayout) 223 c.Assert(err, IsNil) 224 // add it now 225 deviceLayoutWithExtras := mockMBRDeviceLayout 226 deviceLayoutWithExtras.Structure = append(deviceLayoutWithExtras.Structure, 227 gadget.OnDiskStructure{ 228 LaidOutStructure: gadget.LaidOutStructure{ 229 VolumeStructure: &gadget.VolumeStructure{ 230 // name is ignored with MBR schema 231 Name: "Extra partition", 232 Size: 1200 * quantity.SizeMiB, 233 Label: "extra", 234 Filesystem: "ext4", 235 Type: "83", 236 }, 237 StartOffset: 2 * quantity.OffsetMiB, 238 }, 239 Node: "/dev/node3", 240 }, 241 ) 242 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayoutWithExtras) 243 c.Assert(err, IsNil) 244 245 // test with a larger sector size that is still an even multiple of the 246 // structure sizes in the gadget 247 mockMBRDeviceLayout.SectorSize = 4096 248 err = install.EnsureLayoutCompatibility(gadgetLayout, &mockMBRDeviceLayout) 249 c.Assert(err, IsNil) 250 251 // but with a sector size that is not an even multiple of the structure size 252 // then we have an error 253 mockMBRDeviceLayout.SectorSize = 513 254 err = install.EnsureLayoutCompatibility(gadgetLayout, &mockMBRDeviceLayout) 255 c.Assert(err, ErrorMatches, `gadget volume structure #1 \(\"BIOS Boot\"\) size is not a multiple of disk sector size 513`) 256 257 // add another structure that's not part of the gadget 258 deviceLayoutWithExtras.Structure = append(deviceLayoutWithExtras.Structure, 259 gadget.OnDiskStructure{ 260 LaidOutStructure: gadget.LaidOutStructure{ 261 VolumeStructure: &gadget.VolumeStructure{ 262 // name is ignored with MBR schema 263 Name: "Extra extra partition", 264 Size: 1 * quantity.SizeMiB, 265 }, 266 StartOffset: 1202 * quantity.OffsetMiB, 267 }, 268 Node: "/dev/node4", 269 }, 270 ) 271 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayoutWithExtras) 272 c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node4 \(starting at 1260388352\) in gadget: start offsets do not match \(disk: 1260388352 \(1.17 GiB\) and gadget: 2097152 \(2 MiB\)\)`) 273 } 274 275 func (s *installSuite) TestLayoutCompatibilityWithCreatedPartitions(c *C) { 276 gadgetLayoutWithExtras := layoutFromYaml(c, mockGadgetYaml+mockExtraStructure, nil) 277 deviceLayout := mockDeviceLayout 278 279 // device matches gadget except for the filesystem type 280 deviceLayout.Structure = append(deviceLayout.Structure, 281 gadget.OnDiskStructure{ 282 LaidOutStructure: gadget.LaidOutStructure{ 283 VolumeStructure: &gadget.VolumeStructure{ 284 Name: "Writable", 285 Size: 1200 * quantity.SizeMiB, 286 Label: "writable", 287 Filesystem: "something_else", 288 }, 289 StartOffset: 2 * quantity.OffsetMiB, 290 }, 291 Node: "/dev/node3", 292 }, 293 ) 294 err := install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayout) 295 c.Assert(err, IsNil) 296 297 // we are going to manipulate last structure, which has system-data role 298 c.Assert(gadgetLayoutWithExtras.Structure[len(deviceLayout.Structure)-1].Role, Equals, gadget.SystemData) 299 300 // change the role for the laid out volume to not be a partition role that 301 // is created at install time (note that the duplicated seed role here is 302 // technically incorrect, you can't have duplicated roles, but this 303 // demonstrates that a structure that otherwise fits the bill but isn't a 304 // role that is created during install will fail the filesystem match check) 305 gadgetLayoutWithExtras.Structure[len(deviceLayout.Structure)-1].Role = gadget.SystemSeed 306 307 // now we fail to find the /dev/node3 structure from the gadget on disk 308 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayout) 309 c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node3 \(starting at 2097152\) in gadget: filesystems do not match and the partition is not creatable at install`) 310 311 // undo the role change 312 gadgetLayoutWithExtras.Structure[len(deviceLayout.Structure)-1].Role = gadget.SystemData 313 314 // change the gadget size to be bigger than the on disk size 315 gadgetLayoutWithExtras.Structure[len(deviceLayout.Structure)-1].Size = 10000000 * quantity.SizeMiB 316 317 // now we fail to find the /dev/node3 structure from the gadget on disk because the gadget says it must be bigger 318 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayout) 319 c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node3 \(starting at 2097152\) in gadget: on disk size is smaller than gadget size`) 320 321 // change the gadget size to be smaller than the on disk size and the role to be one that is not expanded 322 gadgetLayoutWithExtras.Structure[len(deviceLayout.Structure)-1].Size = 1 * quantity.SizeMiB 323 gadgetLayoutWithExtras.Structure[len(deviceLayout.Structure)-1].Role = gadget.SystemBoot 324 325 // now we fail because the gadget says it should be smaller and it can't be expanded 326 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayout) 327 c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node3 \(starting at 2097152\) in gadget: on disk size is larger than gadget size \(and the role should not be expanded\)`) 328 329 // but a smaller partition on disk for SystemData role is okay 330 gadgetLayoutWithExtras.Structure[len(deviceLayout.Structure)-1].Role = gadget.SystemData 331 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayout) 332 c.Assert(err, IsNil) 333 } 334 335 func (s *installSuite) TestSchemaCompatibility(c *C) { 336 gadgetLayout := layoutFromYaml(c, mockGadgetYaml, nil) 337 deviceLayout := mockDeviceLayout 338 339 error_msg := "disk partitioning.* doesn't match gadget.*" 340 341 for i, tc := range []struct { 342 gs string 343 ds string 344 e string 345 }{ 346 {"", "dos", error_msg}, 347 {"", "gpt", ""}, 348 {"", "xxx", error_msg}, 349 {"mbr", "dos", ""}, 350 {"mbr", "gpt", error_msg}, 351 {"mbr", "xxx", error_msg}, 352 {"gpt", "dos", error_msg}, 353 {"gpt", "gpt", ""}, 354 {"gpt", "xxx", error_msg}, 355 // XXX: "mbr,gpt" is currently unsupported 356 {"mbr,gpt", "dos", error_msg}, 357 {"mbr,gpt", "gpt", error_msg}, 358 {"mbr,gpt", "xxx", error_msg}, 359 } { 360 c.Logf("%d: %q %q\n", i, tc.gs, tc.ds) 361 gadgetLayout.Volume.Schema = tc.gs 362 deviceLayout.Schema = tc.ds 363 err := install.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout) 364 if tc.e == "" { 365 c.Assert(err, IsNil) 366 } else { 367 c.Assert(err, ErrorMatches, tc.e) 368 } 369 } 370 c.Logf("-----") 371 } 372 373 func (s *installSuite) TestIDCompatibility(c *C) { 374 gadgetLayout := layoutFromYaml(c, mockGadgetYaml, nil) 375 deviceLayout := mockDeviceLayout 376 377 error_msg := "disk ID.* doesn't match gadget volume ID.*" 378 379 for i, tc := range []struct { 380 gid string 381 did string 382 e string 383 }{ 384 {"", "", ""}, 385 {"", "123", ""}, 386 {"123", "345", error_msg}, 387 {"123", "123", ""}, 388 } { 389 c.Logf("%d: %q %q\n", i, tc.gid, tc.did) 390 gadgetLayout.Volume.ID = tc.gid 391 deviceLayout.ID = tc.did 392 err := install.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout) 393 if tc.e == "" { 394 c.Assert(err, IsNil) 395 } else { 396 c.Assert(err, ErrorMatches, tc.e) 397 } 398 } 399 c.Logf("-----") 400 } 401 402 func layoutFromYaml(c *C, gadgetYaml string, model gadget.Model) *gadget.LaidOutVolume { 403 gadgetRoot := filepath.Join(c.MkDir(), "gadget") 404 err := os.MkdirAll(filepath.Join(gadgetRoot, "meta"), 0755) 405 c.Assert(err, IsNil) 406 err = ioutil.WriteFile(filepath.Join(gadgetRoot, "meta", "gadget.yaml"), []byte(gadgetYaml), 0644) 407 c.Assert(err, IsNil) 408 pv, err := mustLayOutVolumeFromGadget(c, gadgetRoot, "", model) 409 c.Assert(err, IsNil) 410 return pv 411 } 412 413 const mockUC20GadgetYaml = `volumes: 414 pc: 415 bootloader: grub 416 structure: 417 - name: mbr 418 type: mbr 419 size: 440 420 - name: BIOS Boot 421 type: DA,21686148-6449-6E6F-744E-656564454649 422 size: 1M 423 offset: 1M 424 offset-write: mbr+92 425 - name: ubuntu-seed 426 role: system-seed 427 filesystem: vfat 428 # UEFI will boot the ESP partition by default first 429 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 430 size: 1200M 431 - name: ubuntu-boot 432 role: system-boot 433 filesystem: ext4 434 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 435 size: 1200M 436 - name: ubuntu-data 437 role: system-data 438 filesystem: ext4 439 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 440 size: 750M 441 ` 442 443 func (s *installSuite) setupMockSysfs(c *C) { 444 err := os.MkdirAll(filepath.Join(s.dir, "/dev/disk/by-partlabel"), 0755) 445 c.Assert(err, IsNil) 446 447 err = ioutil.WriteFile(filepath.Join(s.dir, "/dev/fakedevice0p1"), nil, 0644) 448 c.Assert(err, IsNil) 449 err = os.Symlink("../../fakedevice0p1", filepath.Join(s.dir, "/dev/disk/by-partlabel/ubuntu-seed")) 450 c.Assert(err, IsNil) 451 452 // make parent device 453 err = ioutil.WriteFile(filepath.Join(s.dir, "/dev/fakedevice0"), nil, 0644) 454 c.Assert(err, IsNil) 455 // and fake /sys/block structure 456 err = os.MkdirAll(filepath.Join(s.dir, "/sys/block/fakedevice0/fakedevice0p1"), 0755) 457 c.Assert(err, IsNil) 458 } 459 460 func (s *installSuite) TestDeviceFromRoleHappy(c *C) { 461 s.setupMockSysfs(c) 462 lv := layoutFromYaml(c, mockUC20GadgetYaml, uc20Mod) 463 464 device, err := install.DeviceFromRole(lv, gadget.SystemSeed) 465 c.Assert(err, IsNil) 466 c.Check(device, Matches, ".*/dev/fakedevice0") 467 } 468 469 func (s *installSuite) TestDeviceFromRoleErrorNoMatchingSysfs(c *C) { 470 // note no sysfs mocking 471 lv := layoutFromYaml(c, mockUC20GadgetYaml, uc20Mod) 472 473 _, err := install.DeviceFromRole(lv, gadget.SystemSeed) 474 c.Assert(err, ErrorMatches, `cannot find device for role "system-seed": device not found`) 475 } 476 477 func (s *installSuite) TestDeviceFromRoleErrorNoRole(c *C) { 478 s.setupMockSysfs(c) 479 lv := layoutFromYaml(c, mockGadgetYaml, nil) 480 481 _, err := install.DeviceFromRole(lv, gadget.SystemSeed) 482 c.Assert(err, ErrorMatches, "cannot find role system-seed in gadget") 483 }