github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/gadget/install/install_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 // +build !nosecboot 3 4 /* 5 * Copyright (C) 2019 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 "testing" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/dirs" 32 "github.com/snapcore/snapd/gadget" 33 "github.com/snapcore/snapd/gadget/install" 34 "github.com/snapcore/snapd/testutil" 35 ) 36 37 func TestInstall(t *testing.T) { TestingT(t) } 38 39 type installSuite struct { 40 testutil.BaseTest 41 42 dir string 43 } 44 45 var _ = Suite(&installSuite{}) 46 47 // XXX: write a very high level integration like test here that 48 // mocks the world (sfdisk,lsblk,mkfs,...)? probably silly as 49 // each part inside bootstrap is tested and we have a spread test 50 51 func (s *installSuite) SetUpTest(c *C) { 52 s.BaseTest.SetUpTest(c) 53 54 s.dir = c.MkDir() 55 dirs.SetRootDir(s.dir) 56 s.AddCleanup(func() { dirs.SetRootDir("/") }) 57 } 58 59 func (s *installSuite) TestInstallRunError(c *C) { 60 err := install.Run("", "", install.Options{}, nil) 61 c.Assert(err, ErrorMatches, "cannot use empty gadget root directory") 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 * gadget.SizeMiB, 104 }, 105 StartOffset: 1 * gadget.SizeMiB, 106 }, 107 Node: "/dev/node2", 108 }, 109 }, 110 ID: "anything", 111 Device: "/dev/node", 112 Schema: "gpt", 113 Size: 2 * gadget.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) 120 err := install.EnsureLayoutCompatibility(gadgetLayout, &mockDeviceLayout) 121 c.Assert(err, IsNil) 122 123 // missing structure (that's ok) 124 gadgetLayoutWithExtras := layoutFromYaml(c, mockGadgetYaml+mockExtraStructure) 125 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &mockDeviceLayout) 126 c.Assert(err, IsNil) 127 128 deviceLayoutWithExtras := mockDeviceLayout 129 deviceLayoutWithExtras.Structure = append(deviceLayoutWithExtras.Structure, 130 gadget.OnDiskStructure{ 131 LaidOutStructure: gadget.LaidOutStructure{ 132 VolumeStructure: &gadget.VolumeStructure{ 133 Name: "Extra partition", 134 Size: 10 * gadget.SizeMiB, 135 Label: "extra", 136 }, 137 StartOffset: 2 * gadget.SizeMiB, 138 }, 139 Node: "/dev/node3", 140 }, 141 ) 142 // extra structure (should fail) 143 err = install.EnsureLayoutCompatibility(gadgetLayout, &deviceLayoutWithExtras) 144 c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node3.* in gadget`) 145 146 // layout is not compatible if the device is too small 147 smallDeviceLayout := mockDeviceLayout 148 smallDeviceLayout.Size = 100 * gadget.SizeMiB 149 // sanity check 150 c.Check(gadgetLayoutWithExtras.Size > smallDeviceLayout.Size, Equals, true) 151 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &smallDeviceLayout) 152 c.Assert(err, ErrorMatches, `device /dev/node \(100 MiB\) is too small to fit the requested layout \(1\.17 GiB\)`) 153 } 154 155 func (s *installSuite) TestMBRLayoutCompatibility(c *C) { 156 const mockMBRGadgetYaml = `volumes: 157 pc: 158 schema: mbr 159 bootloader: grub 160 structure: 161 - name: mbr 162 type: mbr 163 size: 440 164 - name: BIOS Boot 165 type: DA,21686148-6449-6E6F-744E-656564454649 166 size: 1M 167 offset: 1M 168 offset-write: mbr+92 169 ` 170 var mockMBRDeviceLayout = gadget.OnDiskVolume{ 171 Structure: []gadget.OnDiskStructure{ 172 { 173 LaidOutStructure: gadget.LaidOutStructure{ 174 VolumeStructure: &gadget.VolumeStructure{ 175 // partition names have no 176 // meaning in MBR schema 177 Name: "other", 178 Size: 440, 179 }, 180 StartOffset: 0, 181 }, 182 Node: "/dev/node1", 183 }, 184 { 185 LaidOutStructure: gadget.LaidOutStructure{ 186 VolumeStructure: &gadget.VolumeStructure{ 187 // partition names have no 188 // meaning in MBR schema 189 Name: "different BIOS Boot", 190 Size: 1 * gadget.SizeMiB, 191 }, 192 StartOffset: 1 * gadget.SizeMiB, 193 }, 194 Node: "/dev/node2", 195 }, 196 }, 197 ID: "anything", 198 Device: "/dev/node", 199 Schema: "dos", 200 Size: 2 * gadget.SizeGiB, 201 SectorSize: 512, 202 } 203 gadgetLayout := layoutFromYaml(c, mockMBRGadgetYaml) 204 err := install.EnsureLayoutCompatibility(gadgetLayout, &mockMBRDeviceLayout) 205 c.Assert(err, IsNil) 206 // structure is missing from disk 207 gadgetLayoutWithExtras := layoutFromYaml(c, mockMBRGadgetYaml+mockExtraStructure) 208 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &mockMBRDeviceLayout) 209 c.Assert(err, IsNil) 210 // add it now 211 deviceLayoutWithExtras := mockMBRDeviceLayout 212 deviceLayoutWithExtras.Structure = append(deviceLayoutWithExtras.Structure, 213 gadget.OnDiskStructure{ 214 LaidOutStructure: gadget.LaidOutStructure{ 215 VolumeStructure: &gadget.VolumeStructure{ 216 // name is ignored with MBR schema 217 Name: "Extra partition", 218 Size: 1200 * gadget.SizeMiB, 219 Label: "extra", 220 Filesystem: "ext4", 221 Type: "83", 222 }, 223 StartOffset: 2 * gadget.SizeMiB, 224 }, 225 Node: "/dev/node3", 226 }, 227 ) 228 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayoutWithExtras) 229 c.Assert(err, IsNil) 230 // add another structure that's not part of the gadget 231 deviceLayoutWithExtras.Structure = append(deviceLayoutWithExtras.Structure, 232 gadget.OnDiskStructure{ 233 LaidOutStructure: gadget.LaidOutStructure{ 234 VolumeStructure: &gadget.VolumeStructure{ 235 // name is ignored with MBR schema 236 Name: "Extra extra partition", 237 Size: 1 * gadget.SizeMiB, 238 }, 239 StartOffset: 1202 * gadget.SizeMiB, 240 }, 241 Node: "/dev/node4", 242 }, 243 ) 244 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayoutWithExtras) 245 c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node4 .* in gadget`) 246 } 247 248 func (s *installSuite) TestLayoutCompatibilityWithCreatedPartitions(c *C) { 249 gadgetLayoutWithExtras := layoutFromYaml(c, mockGadgetYaml+mockExtraStructure) 250 deviceLayout := mockDeviceLayout 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 * gadget.SizeMiB, 258 Label: "writable", 259 Filesystem: "something_else", 260 }, 261 StartOffset: 2 * gadget.SizeMiB, 262 }, 263 Node: "/dev/node3", 264 CreatedDuringInstall: true, 265 }, 266 ) 267 err := install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayout) 268 c.Assert(err, IsNil) 269 270 // compare layouts without partitions created at install time (should fail) 271 deviceLayout.Structure[len(deviceLayout.Structure)-1].CreatedDuringInstall = false 272 err = install.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayout) 273 c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node3.* in gadget`) 274 275 } 276 277 func (s *installSuite) TestSchemaCompatibility(c *C) { 278 gadgetLayout := layoutFromYaml(c, mockGadgetYaml) 279 deviceLayout := mockDeviceLayout 280 281 error_msg := "disk partitioning.* doesn't match gadget.*" 282 283 for i, tc := range []struct { 284 gs string 285 ds string 286 e string 287 }{ 288 {"", "dos", error_msg}, 289 {"", "gpt", ""}, 290 {"", "xxx", error_msg}, 291 {"mbr", "dos", ""}, 292 {"mbr", "gpt", error_msg}, 293 {"mbr", "xxx", error_msg}, 294 {"gpt", "dos", error_msg}, 295 {"gpt", "gpt", ""}, 296 {"gpt", "xxx", error_msg}, 297 // XXX: "mbr,gpt" is currently unsupported 298 {"mbr,gpt", "dos", error_msg}, 299 {"mbr,gpt", "gpt", error_msg}, 300 {"mbr,gpt", "xxx", error_msg}, 301 } { 302 c.Logf("%d: %q %q\n", i, tc.gs, tc.ds) 303 gadgetLayout.Volume.Schema = tc.gs 304 deviceLayout.Schema = tc.ds 305 err := install.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout) 306 if tc.e == "" { 307 c.Assert(err, IsNil) 308 } else { 309 c.Assert(err, ErrorMatches, tc.e) 310 } 311 } 312 c.Logf("-----") 313 } 314 315 func (s *installSuite) TestIDCompatibility(c *C) { 316 gadgetLayout := layoutFromYaml(c, mockGadgetYaml) 317 deviceLayout := mockDeviceLayout 318 319 error_msg := "disk ID.* doesn't match gadget volume ID.*" 320 321 for i, tc := range []struct { 322 gid string 323 did string 324 e string 325 }{ 326 {"", "", ""}, 327 {"", "123", ""}, 328 {"123", "345", error_msg}, 329 {"123", "123", ""}, 330 } { 331 c.Logf("%d: %q %q\n", i, tc.gid, tc.did) 332 gadgetLayout.Volume.ID = tc.gid 333 deviceLayout.ID = tc.did 334 err := install.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout) 335 if tc.e == "" { 336 c.Assert(err, IsNil) 337 } else { 338 c.Assert(err, ErrorMatches, tc.e) 339 } 340 } 341 c.Logf("-----") 342 } 343 344 func layoutFromYaml(c *C, gadgetYaml string) *gadget.LaidOutVolume { 345 gadgetRoot := filepath.Join(c.MkDir(), "gadget") 346 err := os.MkdirAll(filepath.Join(gadgetRoot, "meta"), 0755) 347 c.Assert(err, IsNil) 348 err = ioutil.WriteFile(filepath.Join(gadgetRoot, "meta", "gadget.yaml"), []byte(gadgetYaml), 0644) 349 c.Assert(err, IsNil) 350 pv, err := gadget.PositionedVolumeFromGadget(gadgetRoot) 351 c.Assert(err, IsNil) 352 return pv 353 } 354 355 const mockUC20GadgetYaml = `volumes: 356 pc: 357 bootloader: grub 358 structure: 359 - name: mbr 360 type: mbr 361 size: 440 362 - name: BIOS Boot 363 type: DA,21686148-6449-6E6F-744E-656564454649 364 size: 1M 365 offset: 1M 366 offset-write: mbr+92 367 - name: ubuntu-seed 368 role: system-seed 369 filesystem: vfat 370 # UEFI will boot the ESP partition by default first 371 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 372 size: 1200M 373 - name: ubuntu-data 374 role: system-data 375 filesystem: ext4 376 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 377 size: 750M 378 ` 379 380 func (s *installSuite) setupMockSysfs(c *C) { 381 err := os.MkdirAll(filepath.Join(s.dir, "/dev/disk/by-partlabel"), 0755) 382 c.Assert(err, IsNil) 383 384 err = ioutil.WriteFile(filepath.Join(s.dir, "/dev/fakedevice0p1"), nil, 0644) 385 c.Assert(err, IsNil) 386 err = os.Symlink("../../fakedevice0p1", filepath.Join(s.dir, "/dev/disk/by-partlabel/ubuntu-seed")) 387 c.Assert(err, IsNil) 388 389 // make parent device 390 err = ioutil.WriteFile(filepath.Join(s.dir, "/dev/fakedevice0"), nil, 0644) 391 c.Assert(err, IsNil) 392 // and fake /sys/block structure 393 err = os.MkdirAll(filepath.Join(s.dir, "/sys/block/fakedevice0/fakedevice0p1"), 0755) 394 c.Assert(err, IsNil) 395 } 396 397 func (s *installSuite) TestDeviceFromRoleHappy(c *C) { 398 s.setupMockSysfs(c) 399 lv := layoutFromYaml(c, mockUC20GadgetYaml) 400 401 device, err := install.DeviceFromRole(lv, gadget.SystemSeed) 402 c.Assert(err, IsNil) 403 c.Check(device, Matches, ".*/dev/fakedevice0") 404 } 405 406 func (s *installSuite) TestDeviceFromRoleErrorNoMatchingSysfs(c *C) { 407 // note no sysfs mocking 408 lv := layoutFromYaml(c, mockUC20GadgetYaml) 409 410 _, err := install.DeviceFromRole(lv, gadget.SystemSeed) 411 c.Assert(err, ErrorMatches, `cannot find device for role "system-seed": device not found`) 412 } 413 414 func (s *installSuite) TestDeviceFromRoleErrorNoRole(c *C) { 415 s.setupMockSysfs(c) 416 lv := layoutFromYaml(c, mockGadgetYaml) 417 418 _, err := install.DeviceFromRole(lv, gadget.SystemSeed) 419 c.Assert(err, ErrorMatches, "cannot find role system-seed in gadget") 420 }