github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/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 // missing structure (that's ok) 123 gadgetLayoutWithExtras := layoutFromYaml(c, mockGadgetYaml+mockExtraStructure, nil) 124 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &mockDeviceLayout) 125 c.Assert(err, IsNil) 126 127 deviceLayoutWithExtras := mockDeviceLayout 128 deviceLayoutWithExtras.Structure = append(deviceLayoutWithExtras.Structure, 129 gadget.OnDiskStructure{ 130 LaidOutStructure: gadget.LaidOutStructure{ 131 VolumeStructure: &gadget.VolumeStructure{ 132 Name: "Extra partition", 133 Size: 10 * quantity.SizeMiB, 134 Label: "extra", 135 }, 136 StartOffset: 2 * quantity.OffsetMiB, 137 }, 138 Node: "/dev/node3", 139 }, 140 ) 141 // extra structure (should fail) 142 err = install.EnsureLayoutCompatibility(gadgetLayout, &deviceLayoutWithExtras) 143 c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node3.* in gadget`) 144 145 // layout is not compatible if the device is too small 146 smallDeviceLayout := mockDeviceLayout 147 smallDeviceLayout.Size = 100 * quantity.SizeMiB 148 // sanity check 149 c.Check(gadgetLayoutWithExtras.Size > smallDeviceLayout.Size, Equals, true) 150 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &smallDeviceLayout) 151 c.Assert(err, ErrorMatches, `device /dev/node \(100 MiB\) is too small to fit the requested layout \(1\.17 GiB\)`) 152 } 153 154 func (s *installSuite) TestMBRLayoutCompatibility(c *C) { 155 const mockMBRGadgetYaml = `volumes: 156 pc: 157 schema: mbr 158 bootloader: grub 159 structure: 160 - name: mbr 161 type: mbr 162 size: 440 163 - name: BIOS Boot 164 type: DA,21686148-6449-6E6F-744E-656564454649 165 size: 1M 166 offset: 1M 167 offset-write: mbr+92 168 ` 169 var mockMBRDeviceLayout = gadget.OnDiskVolume{ 170 Structure: []gadget.OnDiskStructure{ 171 { 172 LaidOutStructure: gadget.LaidOutStructure{ 173 VolumeStructure: &gadget.VolumeStructure{ 174 // partition names have no 175 // meaning in MBR schema 176 Name: "other", 177 Size: 440, 178 }, 179 StartOffset: 0, 180 }, 181 Node: "/dev/node1", 182 }, 183 { 184 LaidOutStructure: gadget.LaidOutStructure{ 185 VolumeStructure: &gadget.VolumeStructure{ 186 // partition names have no 187 // meaning in MBR schema 188 Name: "different BIOS Boot", 189 Size: 1 * quantity.SizeMiB, 190 }, 191 StartOffset: 1 * quantity.OffsetMiB, 192 }, 193 Node: "/dev/node2", 194 }, 195 }, 196 ID: "anything", 197 Device: "/dev/node", 198 Schema: "dos", 199 Size: 2 * quantity.SizeGiB, 200 SectorSize: 512, 201 } 202 gadgetLayout := layoutFromYaml(c, mockMBRGadgetYaml, nil) 203 err := install.EnsureLayoutCompatibility(gadgetLayout, &mockMBRDeviceLayout) 204 c.Assert(err, IsNil) 205 // structure is missing from disk 206 gadgetLayoutWithExtras := layoutFromYaml(c, mockMBRGadgetYaml+mockExtraStructure, nil) 207 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &mockMBRDeviceLayout) 208 c.Assert(err, IsNil) 209 // add it now 210 deviceLayoutWithExtras := mockMBRDeviceLayout 211 deviceLayoutWithExtras.Structure = append(deviceLayoutWithExtras.Structure, 212 gadget.OnDiskStructure{ 213 LaidOutStructure: gadget.LaidOutStructure{ 214 VolumeStructure: &gadget.VolumeStructure{ 215 // name is ignored with MBR schema 216 Name: "Extra partition", 217 Size: 1200 * quantity.SizeMiB, 218 Label: "extra", 219 Filesystem: "ext4", 220 Type: "83", 221 }, 222 StartOffset: 2 * quantity.OffsetMiB, 223 }, 224 Node: "/dev/node3", 225 }, 226 ) 227 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayoutWithExtras) 228 c.Assert(err, IsNil) 229 // add another structure that's not part of the gadget 230 deviceLayoutWithExtras.Structure = append(deviceLayoutWithExtras.Structure, 231 gadget.OnDiskStructure{ 232 LaidOutStructure: gadget.LaidOutStructure{ 233 VolumeStructure: &gadget.VolumeStructure{ 234 // name is ignored with MBR schema 235 Name: "Extra extra partition", 236 Size: 1 * quantity.SizeMiB, 237 }, 238 StartOffset: 1202 * quantity.OffsetMiB, 239 }, 240 Node: "/dev/node4", 241 }, 242 ) 243 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayoutWithExtras) 244 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\)\)`) 245 } 246 247 func (s *installSuite) TestLayoutCompatibilityWithCreatedPartitions(c *C) { 248 gadgetLayoutWithExtras := layoutFromYaml(c, mockGadgetYaml+mockExtraStructure, nil) 249 deviceLayout := mockDeviceLayout 250 251 // device matches gadget except for the filesystem type 252 deviceLayout.Structure = append(deviceLayout.Structure, 253 gadget.OnDiskStructure{ 254 LaidOutStructure: gadget.LaidOutStructure{ 255 VolumeStructure: &gadget.VolumeStructure{ 256 Name: "Writable", 257 Size: 1200 * quantity.SizeMiB, 258 Label: "writable", 259 Filesystem: "something_else", 260 }, 261 StartOffset: 2 * quantity.OffsetMiB, 262 }, 263 Node: "/dev/node3", 264 }, 265 ) 266 err := install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayout) 267 c.Assert(err, IsNil) 268 269 // we are going to manipulate last structure, which has system-data role 270 c.Assert(gadgetLayoutWithExtras.Structure[len(deviceLayout.Structure)-1].Role, Equals, gadget.SystemData) 271 272 // change the role for the laid out volume to not be a partition role that 273 // is created at install time (note that the duplicated seed role here is 274 // technically incorrect, you can't have duplicated roles, but this 275 // demonstrates that a structure that otherwise fits the bill but isn't a 276 // role that is created during install will fail the filesystem match check) 277 gadgetLayoutWithExtras.Structure[len(deviceLayout.Structure)-1].Role = gadget.SystemSeed 278 279 // now we fail to find the /dev/node3 structure from the gadget on disk 280 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayout) 281 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`) 282 283 // undo the role change 284 gadgetLayoutWithExtras.Structure[len(deviceLayout.Structure)-1].Role = gadget.SystemData 285 286 // change the gadget size to be bigger than the on disk size 287 gadgetLayoutWithExtras.Structure[len(deviceLayout.Structure)-1].Size = 10000000 * quantity.SizeMiB 288 289 // now we fail to find the /dev/node3 structure from the gadget on disk because the gadget says it must be bigger 290 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayout) 291 c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node3 \(starting at 2097152\) in gadget: on disk size is smaller than gadget size`) 292 293 // change the gadget size to be smaller than the on disk size and the role to be one that is not expanded 294 gadgetLayoutWithExtras.Structure[len(deviceLayout.Structure)-1].Size = 1 * quantity.SizeMiB 295 gadgetLayoutWithExtras.Structure[len(deviceLayout.Structure)-1].Role = gadget.SystemBoot 296 297 // now we fail because the gadget says it should be smaller and it can't be expanded 298 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayout) 299 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\)`) 300 301 // but a smaller partition on disk for SystemData role is okay 302 gadgetLayoutWithExtras.Structure[len(deviceLayout.Structure)-1].Role = gadget.SystemData 303 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayout) 304 c.Assert(err, IsNil) 305 } 306 307 func (s *installSuite) TestSchemaCompatibility(c *C) { 308 gadgetLayout := layoutFromYaml(c, mockGadgetYaml, nil) 309 deviceLayout := mockDeviceLayout 310 311 error_msg := "disk partitioning.* doesn't match gadget.*" 312 313 for i, tc := range []struct { 314 gs string 315 ds string 316 e string 317 }{ 318 {"", "dos", error_msg}, 319 {"", "gpt", ""}, 320 {"", "xxx", error_msg}, 321 {"mbr", "dos", ""}, 322 {"mbr", "gpt", error_msg}, 323 {"mbr", "xxx", error_msg}, 324 {"gpt", "dos", error_msg}, 325 {"gpt", "gpt", ""}, 326 {"gpt", "xxx", error_msg}, 327 // XXX: "mbr,gpt" is currently unsupported 328 {"mbr,gpt", "dos", error_msg}, 329 {"mbr,gpt", "gpt", error_msg}, 330 {"mbr,gpt", "xxx", error_msg}, 331 } { 332 c.Logf("%d: %q %q\n", i, tc.gs, tc.ds) 333 gadgetLayout.Volume.Schema = tc.gs 334 deviceLayout.Schema = tc.ds 335 err := install.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout) 336 if tc.e == "" { 337 c.Assert(err, IsNil) 338 } else { 339 c.Assert(err, ErrorMatches, tc.e) 340 } 341 } 342 c.Logf("-----") 343 } 344 345 func (s *installSuite) TestIDCompatibility(c *C) { 346 gadgetLayout := layoutFromYaml(c, mockGadgetYaml, nil) 347 deviceLayout := mockDeviceLayout 348 349 error_msg := "disk ID.* doesn't match gadget volume ID.*" 350 351 for i, tc := range []struct { 352 gid string 353 did string 354 e string 355 }{ 356 {"", "", ""}, 357 {"", "123", ""}, 358 {"123", "345", error_msg}, 359 {"123", "123", ""}, 360 } { 361 c.Logf("%d: %q %q\n", i, tc.gid, tc.did) 362 gadgetLayout.Volume.ID = tc.gid 363 deviceLayout.ID = tc.did 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 layoutFromYaml(c *C, gadgetYaml string, model gadget.Model) *gadget.LaidOutVolume { 375 gadgetRoot := filepath.Join(c.MkDir(), "gadget") 376 err := os.MkdirAll(filepath.Join(gadgetRoot, "meta"), 0755) 377 c.Assert(err, IsNil) 378 err = ioutil.WriteFile(filepath.Join(gadgetRoot, "meta", "gadget.yaml"), []byte(gadgetYaml), 0644) 379 c.Assert(err, IsNil) 380 pv, err := gadget.LaidOutVolumeFromGadget(gadgetRoot, model) 381 c.Assert(err, IsNil) 382 return pv 383 } 384 385 const mockUC20GadgetYaml = `volumes: 386 pc: 387 bootloader: grub 388 structure: 389 - name: mbr 390 type: mbr 391 size: 440 392 - name: BIOS Boot 393 type: DA,21686148-6449-6E6F-744E-656564454649 394 size: 1M 395 offset: 1M 396 offset-write: mbr+92 397 - name: ubuntu-seed 398 role: system-seed 399 filesystem: vfat 400 # UEFI will boot the ESP partition by default first 401 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 402 size: 1200M 403 - name: ubuntu-data 404 role: system-data 405 filesystem: ext4 406 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 407 size: 750M 408 ` 409 410 func (s *installSuite) setupMockSysfs(c *C) { 411 err := os.MkdirAll(filepath.Join(s.dir, "/dev/disk/by-partlabel"), 0755) 412 c.Assert(err, IsNil) 413 414 err = ioutil.WriteFile(filepath.Join(s.dir, "/dev/fakedevice0p1"), nil, 0644) 415 c.Assert(err, IsNil) 416 err = os.Symlink("../../fakedevice0p1", filepath.Join(s.dir, "/dev/disk/by-partlabel/ubuntu-seed")) 417 c.Assert(err, IsNil) 418 419 // make parent device 420 err = ioutil.WriteFile(filepath.Join(s.dir, "/dev/fakedevice0"), nil, 0644) 421 c.Assert(err, IsNil) 422 // and fake /sys/block structure 423 err = os.MkdirAll(filepath.Join(s.dir, "/sys/block/fakedevice0/fakedevice0p1"), 0755) 424 c.Assert(err, IsNil) 425 } 426 427 func (s *installSuite) TestDeviceFromRoleHappy(c *C) { 428 s.setupMockSysfs(c) 429 lv := layoutFromYaml(c, mockUC20GadgetYaml, uc20mod) 430 431 device, err := install.DeviceFromRole(lv, gadget.SystemSeed) 432 c.Assert(err, IsNil) 433 c.Check(device, Matches, ".*/dev/fakedevice0") 434 } 435 436 func (s *installSuite) TestDeviceFromRoleErrorNoMatchingSysfs(c *C) { 437 // note no sysfs mocking 438 lv := layoutFromYaml(c, mockUC20GadgetYaml, uc20mod) 439 440 _, err := install.DeviceFromRole(lv, gadget.SystemSeed) 441 c.Assert(err, ErrorMatches, `cannot find device for role "system-seed": device not found`) 442 } 443 444 func (s *installSuite) TestDeviceFromRoleErrorNoRole(c *C) { 445 s.setupMockSysfs(c) 446 lv := layoutFromYaml(c, mockGadgetYaml, nil) 447 448 _, err := install.DeviceFromRole(lv, gadget.SystemSeed) 449 c.Assert(err, ErrorMatches, "cannot find role system-seed in gadget") 450 }