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