github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/gadget/update_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 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 gadget_test 21 22 import ( 23 "errors" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "strings" 29 30 . "gopkg.in/check.v1" 31 32 "github.com/snapcore/snapd/dirs" 33 "github.com/snapcore/snapd/gadget" 34 "github.com/snapcore/snapd/gadget/quantity" 35 "github.com/snapcore/snapd/logger" 36 "github.com/snapcore/snapd/osutil" 37 "github.com/snapcore/snapd/testutil" 38 ) 39 40 type updateTestSuite struct{} 41 42 var _ = Suite(&updateTestSuite{}) 43 44 func (u *updateTestSuite) TestResolveVolumeDifferentName(c *C) { 45 oldInfo := &gadget.Info{ 46 Volumes: map[string]*gadget.Volume{ 47 "old": {}, 48 }, 49 } 50 noMatchInfo := &gadget.Info{ 51 Volumes: map[string]*gadget.Volume{ 52 "not-old": {}, 53 }, 54 } 55 oldVol, newVol, err := gadget.ResolveVolume(oldInfo, noMatchInfo) 56 c.Assert(err, ErrorMatches, `cannot find entry for volume "old" in updated gadget info`) 57 c.Assert(oldVol, IsNil) 58 c.Assert(newVol, IsNil) 59 } 60 61 func (u *updateTestSuite) TestResolveVolumeTooMany(c *C) { 62 oldInfo := &gadget.Info{ 63 Volumes: map[string]*gadget.Volume{ 64 "old": {}, 65 "another-one": {}, 66 }, 67 } 68 noMatchInfo := &gadget.Info{ 69 Volumes: map[string]*gadget.Volume{ 70 "old": {}, 71 }, 72 } 73 oldVol, newVol, err := gadget.ResolveVolume(oldInfo, noMatchInfo) 74 c.Assert(err, ErrorMatches, `cannot update with more than one volume`) 75 c.Assert(oldVol, IsNil) 76 c.Assert(newVol, IsNil) 77 } 78 79 func (u *updateTestSuite) TestResolveVolumeSimple(c *C) { 80 oldInfo := &gadget.Info{ 81 Volumes: map[string]*gadget.Volume{ 82 "old": {Bootloader: "u-boot"}, 83 }, 84 } 85 noMatchInfo := &gadget.Info{ 86 Volumes: map[string]*gadget.Volume{ 87 "old": {Bootloader: "grub"}, 88 }, 89 } 90 oldVol, newVol, err := gadget.ResolveVolume(oldInfo, noMatchInfo) 91 c.Assert(err, IsNil) 92 c.Assert(oldVol, DeepEquals, &gadget.Volume{Bootloader: "u-boot"}) 93 c.Assert(newVol, DeepEquals, &gadget.Volume{Bootloader: "grub"}) 94 } 95 96 type canUpdateTestCase struct { 97 from gadget.LaidOutStructure 98 to gadget.LaidOutStructure 99 schema string 100 err string 101 } 102 103 func (u *updateTestSuite) testCanUpdate(c *C, testCases []canUpdateTestCase) { 104 for idx, tc := range testCases { 105 c.Logf("tc: %v", idx) 106 schema := tc.schema 107 if schema == "" { 108 schema = "gpt" 109 } 110 err := gadget.CanUpdateStructure(&tc.from, &tc.to, schema) 111 if tc.err == "" { 112 c.Check(err, IsNil) 113 } else { 114 c.Check(err, ErrorMatches, tc.err) 115 } 116 } 117 } 118 119 func (u *updateTestSuite) TestCanUpdateSize(c *C) { 120 121 cases := []canUpdateTestCase{ 122 { 123 // size change 124 from: gadget.LaidOutStructure{ 125 VolumeStructure: &gadget.VolumeStructure{Size: 1 * quantity.SizeMiB}, 126 }, 127 to: gadget.LaidOutStructure{ 128 VolumeStructure: &gadget.VolumeStructure{Size: 1*quantity.SizeMiB + 1*quantity.SizeKiB}, 129 }, 130 err: "cannot change structure size from [0-9]+ to [0-9]+", 131 }, { 132 // size change 133 from: gadget.LaidOutStructure{ 134 VolumeStructure: &gadget.VolumeStructure{Size: 1 * quantity.SizeMiB}, 135 }, 136 to: gadget.LaidOutStructure{ 137 VolumeStructure: &gadget.VolumeStructure{Size: 1 * quantity.SizeMiB}, 138 }, 139 err: "", 140 }, 141 } 142 143 u.testCanUpdate(c, cases) 144 } 145 146 func (u *updateTestSuite) TestCanUpdateOffsetWrite(c *C) { 147 148 cases := []canUpdateTestCase{ 149 { 150 // offset-write change 151 from: gadget.LaidOutStructure{ 152 VolumeStructure: &gadget.VolumeStructure{ 153 OffsetWrite: &gadget.RelativeOffset{Offset: 1024}, 154 }, 155 }, 156 to: gadget.LaidOutStructure{ 157 VolumeStructure: &gadget.VolumeStructure{ 158 OffsetWrite: &gadget.RelativeOffset{Offset: 2048}, 159 }, 160 }, 161 err: "cannot change structure offset-write from [0-9]+ to [0-9]+", 162 }, { 163 // offset-write, change in relative-to structure name 164 from: gadget.LaidOutStructure{ 165 VolumeStructure: &gadget.VolumeStructure{ 166 OffsetWrite: &gadget.RelativeOffset{RelativeTo: "foo", Offset: 1024}, 167 }, 168 }, 169 to: gadget.LaidOutStructure{ 170 VolumeStructure: &gadget.VolumeStructure{ 171 OffsetWrite: &gadget.RelativeOffset{RelativeTo: "bar", Offset: 1024}, 172 }, 173 }, 174 err: `cannot change structure offset-write from foo\+[0-9]+ to bar\+[0-9]+`, 175 }, { 176 // offset-write, unspecified in old 177 from: gadget.LaidOutStructure{ 178 VolumeStructure: &gadget.VolumeStructure{ 179 OffsetWrite: nil, 180 }, 181 }, 182 to: gadget.LaidOutStructure{ 183 VolumeStructure: &gadget.VolumeStructure{ 184 OffsetWrite: &gadget.RelativeOffset{RelativeTo: "bar", Offset: 1024}, 185 }, 186 }, 187 err: `cannot change structure offset-write from unspecified to bar\+[0-9]+`, 188 }, { 189 // offset-write, unspecified in new 190 from: gadget.LaidOutStructure{ 191 VolumeStructure: &gadget.VolumeStructure{ 192 OffsetWrite: &gadget.RelativeOffset{RelativeTo: "foo", Offset: 1024}, 193 }, 194 }, 195 to: gadget.LaidOutStructure{ 196 VolumeStructure: &gadget.VolumeStructure{ 197 OffsetWrite: nil, 198 }, 199 }, 200 err: `cannot change structure offset-write from foo\+[0-9]+ to unspecified`, 201 }, { 202 // all ok, both nils 203 from: gadget.LaidOutStructure{ 204 VolumeStructure: &gadget.VolumeStructure{ 205 OffsetWrite: nil, 206 }, 207 }, 208 to: gadget.LaidOutStructure{ 209 VolumeStructure: &gadget.VolumeStructure{ 210 OffsetWrite: nil, 211 }, 212 }, 213 err: ``, 214 }, { 215 // all ok, both fully specified 216 from: gadget.LaidOutStructure{ 217 VolumeStructure: &gadget.VolumeStructure{ 218 OffsetWrite: &gadget.RelativeOffset{RelativeTo: "foo", Offset: 1024}, 219 }, 220 }, 221 to: gadget.LaidOutStructure{ 222 VolumeStructure: &gadget.VolumeStructure{ 223 OffsetWrite: &gadget.RelativeOffset{RelativeTo: "foo", Offset: 1024}, 224 }, 225 }, 226 err: ``, 227 }, { 228 // all ok, both fully specified 229 from: gadget.LaidOutStructure{ 230 VolumeStructure: &gadget.VolumeStructure{ 231 OffsetWrite: &gadget.RelativeOffset{Offset: 1024}, 232 }, 233 }, 234 to: gadget.LaidOutStructure{ 235 VolumeStructure: &gadget.VolumeStructure{ 236 OffsetWrite: &gadget.RelativeOffset{Offset: 1024}, 237 }, 238 }, 239 err: ``, 240 }, 241 } 242 u.testCanUpdate(c, cases) 243 } 244 245 func (u *updateTestSuite) TestCanUpdateOffset(c *C) { 246 247 cases := []canUpdateTestCase{ 248 { 249 // explicitly declared start offset change 250 from: gadget.LaidOutStructure{ 251 VolumeStructure: &gadget.VolumeStructure{Size: 1 * quantity.SizeMiB, Offset: asOffsetPtr(1024)}, 252 StartOffset: 1024, 253 }, 254 to: gadget.LaidOutStructure{ 255 VolumeStructure: &gadget.VolumeStructure{Size: 1 * quantity.SizeMiB, Offset: asOffsetPtr(2048)}, 256 StartOffset: 2048, 257 }, 258 err: "cannot change structure offset from [0-9]+ to [0-9]+", 259 }, { 260 // explicitly declared start offset in new structure 261 from: gadget.LaidOutStructure{ 262 VolumeStructure: &gadget.VolumeStructure{Size: 1 * quantity.SizeMiB, Offset: nil}, 263 StartOffset: 1024, 264 }, 265 to: gadget.LaidOutStructure{ 266 VolumeStructure: &gadget.VolumeStructure{Size: 1 * quantity.SizeMiB, Offset: asOffsetPtr(2048)}, 267 StartOffset: 2048, 268 }, 269 err: "cannot change structure offset from unspecified to [0-9]+", 270 }, { 271 // explicitly declared start offset in old structure, 272 // missing from new 273 from: gadget.LaidOutStructure{ 274 VolumeStructure: &gadget.VolumeStructure{Size: 1 * quantity.SizeMiB, Offset: asOffsetPtr(1024)}, 275 StartOffset: 1024, 276 }, 277 to: gadget.LaidOutStructure{ 278 VolumeStructure: &gadget.VolumeStructure{Size: 1 * quantity.SizeMiB, Offset: nil}, 279 StartOffset: 2048, 280 }, 281 err: "cannot change structure offset from [0-9]+ to unspecified", 282 }, { 283 // start offset changed due to layout 284 from: gadget.LaidOutStructure{ 285 VolumeStructure: &gadget.VolumeStructure{Size: 1 * quantity.SizeMiB}, 286 StartOffset: 1 * quantity.OffsetMiB, 287 }, 288 to: gadget.LaidOutStructure{ 289 VolumeStructure: &gadget.VolumeStructure{Size: 1 * quantity.SizeMiB}, 290 StartOffset: 2 * quantity.OffsetMiB, 291 }, 292 err: "cannot change structure start offset from [0-9]+ to [0-9]+", 293 }, 294 } 295 u.testCanUpdate(c, cases) 296 } 297 298 func (u *updateTestSuite) TestCanUpdateRole(c *C) { 299 300 cases := []canUpdateTestCase{ 301 { 302 // new role 303 from: gadget.LaidOutStructure{ 304 VolumeStructure: &gadget.VolumeStructure{Role: ""}, 305 }, 306 to: gadget.LaidOutStructure{ 307 VolumeStructure: &gadget.VolumeStructure{Role: "system-data"}, 308 }, 309 err: `cannot change structure role from "" to "system-data"`, 310 }, { 311 // explicitly set tole 312 from: gadget.LaidOutStructure{ 313 VolumeStructure: &gadget.VolumeStructure{Role: "mbr"}, 314 }, 315 to: gadget.LaidOutStructure{ 316 VolumeStructure: &gadget.VolumeStructure{Role: "system-data"}, 317 }, 318 err: `cannot change structure role from "mbr" to "system-data"`, 319 }, { 320 // implicit legacy role to proper explicit role 321 from: gadget.LaidOutStructure{ 322 VolumeStructure: &gadget.VolumeStructure{Type: "mbr", Role: "mbr"}, 323 }, 324 to: gadget.LaidOutStructure{ 325 VolumeStructure: &gadget.VolumeStructure{Type: "bare", Role: "mbr"}, 326 }, 327 err: "", 328 }, { 329 // but not in the opposite direction 330 from: gadget.LaidOutStructure{ 331 VolumeStructure: &gadget.VolumeStructure{Type: "bare", Role: "mbr"}, 332 }, 333 to: gadget.LaidOutStructure{ 334 VolumeStructure: &gadget.VolumeStructure{Type: "mbr", Role: "mbr"}, 335 }, 336 err: `cannot change structure type from "bare" to "mbr"`, 337 }, { 338 // start offset changed due to layout 339 from: gadget.LaidOutStructure{ 340 VolumeStructure: &gadget.VolumeStructure{Role: ""}, 341 }, 342 to: gadget.LaidOutStructure{ 343 VolumeStructure: &gadget.VolumeStructure{Role: ""}, 344 }, 345 err: "", 346 }, 347 } 348 u.testCanUpdate(c, cases) 349 } 350 351 func (u *updateTestSuite) TestCanUpdateType(c *C) { 352 353 cases := []canUpdateTestCase{ 354 { 355 // from hybrid type to GUID 356 from: gadget.LaidOutStructure{ 357 VolumeStructure: &gadget.VolumeStructure{Type: "0C,00000000-0000-0000-0000-dd00deadbeef"}, 358 }, 359 to: gadget.LaidOutStructure{ 360 VolumeStructure: &gadget.VolumeStructure{Type: "00000000-0000-0000-0000-dd00deadbeef"}, 361 }, 362 err: `cannot change structure type from "0C,00000000-0000-0000-0000-dd00deadbeef" to "00000000-0000-0000-0000-dd00deadbeef"`, 363 }, { 364 // from MBR type to GUID (would be stopped at volume update checks) 365 from: gadget.LaidOutStructure{ 366 VolumeStructure: &gadget.VolumeStructure{Type: "0C"}, 367 }, 368 to: gadget.LaidOutStructure{ 369 VolumeStructure: &gadget.VolumeStructure{Type: "00000000-0000-0000-0000-dd00deadbeef"}, 370 }, 371 err: `cannot change structure type from "0C" to "00000000-0000-0000-0000-dd00deadbeef"`, 372 }, { 373 // from one MBR type to another 374 from: gadget.LaidOutStructure{ 375 VolumeStructure: &gadget.VolumeStructure{Type: "0C"}, 376 }, 377 to: gadget.LaidOutStructure{ 378 VolumeStructure: &gadget.VolumeStructure{Type: "0A"}, 379 }, 380 err: `cannot change structure type from "0C" to "0A"`, 381 }, { 382 // from one MBR type to another 383 from: gadget.LaidOutStructure{ 384 VolumeStructure: &gadget.VolumeStructure{Type: "0C"}, 385 }, 386 to: gadget.LaidOutStructure{ 387 VolumeStructure: &gadget.VolumeStructure{Type: "bare"}, 388 }, 389 err: `cannot change structure type from "0C" to "bare"`, 390 }, { 391 // from one GUID to another 392 from: gadget.LaidOutStructure{ 393 VolumeStructure: &gadget.VolumeStructure{Type: "00000000-0000-0000-0000-dd00deadcafe"}, 394 }, 395 to: gadget.LaidOutStructure{ 396 VolumeStructure: &gadget.VolumeStructure{Type: "00000000-0000-0000-0000-dd00deadbeef"}, 397 }, 398 err: `cannot change structure type from "00000000-0000-0000-0000-dd00deadcafe" to "00000000-0000-0000-0000-dd00deadbeef"`, 399 }, { 400 from: gadget.LaidOutStructure{ 401 VolumeStructure: &gadget.VolumeStructure{Type: "bare"}, 402 }, 403 to: gadget.LaidOutStructure{ 404 VolumeStructure: &gadget.VolumeStructure{Type: "bare"}, 405 }, 406 }, { 407 from: gadget.LaidOutStructure{ 408 VolumeStructure: &gadget.VolumeStructure{Type: "0C"}, 409 }, 410 to: gadget.LaidOutStructure{ 411 VolumeStructure: &gadget.VolumeStructure{Type: "0C"}, 412 }, 413 }, { 414 from: gadget.LaidOutStructure{ 415 VolumeStructure: &gadget.VolumeStructure{Type: "00000000-0000-0000-0000-dd00deadbeef"}, 416 }, 417 to: gadget.LaidOutStructure{ 418 VolumeStructure: &gadget.VolumeStructure{Type: "00000000-0000-0000-0000-dd00deadbeef"}, 419 }, 420 }, { 421 from: gadget.LaidOutStructure{ 422 VolumeStructure: &gadget.VolumeStructure{Type: "0C,00000000-0000-0000-0000-dd00deadbeef"}, 423 }, 424 to: gadget.LaidOutStructure{ 425 VolumeStructure: &gadget.VolumeStructure{Type: "0C,00000000-0000-0000-0000-dd00deadbeef"}, 426 }, 427 }, 428 } 429 u.testCanUpdate(c, cases) 430 } 431 432 func (u *updateTestSuite) TestCanUpdateID(c *C) { 433 434 cases := []canUpdateTestCase{ 435 { 436 from: gadget.LaidOutStructure{ 437 VolumeStructure: &gadget.VolumeStructure{ID: "00000000-0000-0000-0000-dd00deadbeef"}, 438 }, 439 to: gadget.LaidOutStructure{ 440 VolumeStructure: &gadget.VolumeStructure{ID: "00000000-0000-0000-0000-dd00deadcafe"}, 441 }, 442 err: `cannot change structure ID from "00000000-0000-0000-0000-dd00deadbeef" to "00000000-0000-0000-0000-dd00deadcafe"`, 443 }, 444 } 445 u.testCanUpdate(c, cases) 446 } 447 448 func (u *updateTestSuite) TestCanUpdateBareOrFilesystem(c *C) { 449 450 cases := []canUpdateTestCase{ 451 { 452 from: gadget.LaidOutStructure{ 453 VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "ext4"}, 454 }, 455 to: gadget.LaidOutStructure{ 456 VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: ""}, 457 }, 458 err: `cannot change a filesystem structure to a bare one`, 459 }, { 460 from: gadget.LaidOutStructure{ 461 VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: ""}, 462 }, 463 to: gadget.LaidOutStructure{ 464 VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "ext4"}, 465 }, 466 err: `cannot change a bare structure to filesystem one`, 467 }, { 468 from: gadget.LaidOutStructure{ 469 VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "ext4"}, 470 }, 471 to: gadget.LaidOutStructure{ 472 VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "vfat"}, 473 }, 474 err: `cannot change filesystem from "ext4" to "vfat"`, 475 }, { 476 from: gadget.LaidOutStructure{ 477 VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "ext4", Label: "writable"}, 478 }, 479 to: gadget.LaidOutStructure{ 480 VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "ext4"}, 481 }, 482 err: `cannot change filesystem label from "writable" to ""`, 483 }, { 484 // all ok 485 from: gadget.LaidOutStructure{ 486 VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "ext4", Label: "do-not-touch"}, 487 }, 488 to: gadget.LaidOutStructure{ 489 VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "ext4", Label: "do-not-touch"}, 490 }, 491 err: ``, 492 }, 493 } 494 u.testCanUpdate(c, cases) 495 } 496 497 func (u *updateTestSuite) TestCanUpdateName(c *C) { 498 499 cases := []canUpdateTestCase{ 500 { 501 from: gadget.LaidOutStructure{ 502 VolumeStructure: &gadget.VolumeStructure{Name: "foo", Type: "0C"}, 503 }, 504 to: gadget.LaidOutStructure{ 505 VolumeStructure: &gadget.VolumeStructure{Name: "mbr-ok", Type: "0C"}, 506 }, 507 err: ``, 508 schema: "mbr", 509 }, { 510 from: gadget.LaidOutStructure{ 511 VolumeStructure: &gadget.VolumeStructure{Name: "foo", Type: "00000000-0000-0000-0000-dd00deadbeef"}, 512 }, 513 to: gadget.LaidOutStructure{ 514 VolumeStructure: &gadget.VolumeStructure{Name: "gpt-unhappy", Type: "00000000-0000-0000-0000-dd00deadbeef"}, 515 }, 516 err: `cannot change structure name from "foo" to "gpt-unhappy"`, 517 schema: "gpt", 518 }, 519 } 520 u.testCanUpdate(c, cases) 521 } 522 523 func (u *updateTestSuite) TestCanUpdateVolume(c *C) { 524 525 for idx, tc := range []struct { 526 from gadget.PartiallyLaidOutVolume 527 to gadget.LaidOutVolume 528 err string 529 }{ 530 { 531 from: gadget.PartiallyLaidOutVolume{ 532 Volume: &gadget.Volume{Schema: "gpt"}, 533 }, 534 to: gadget.LaidOutVolume{ 535 Volume: &gadget.Volume{Schema: "mbr"}, 536 }, 537 err: `cannot change volume schema from "gpt" to "mbr"`, 538 }, { 539 from: gadget.PartiallyLaidOutVolume{ 540 Volume: &gadget.Volume{ID: "00000000-0000-0000-0000-0000deadbeef"}, 541 }, 542 to: gadget.LaidOutVolume{ 543 Volume: &gadget.Volume{ID: "00000000-0000-0000-0000-0000deadcafe"}, 544 }, 545 err: `cannot change volume ID from "00000000-0000-0000-0000-0000deadbeef" to "00000000-0000-0000-0000-0000deadcafe"`, 546 }, { 547 from: gadget.PartiallyLaidOutVolume{ 548 Volume: &gadget.Volume{}, 549 LaidOutStructure: []gadget.LaidOutStructure{ 550 {}, {}, 551 }, 552 }, 553 to: gadget.LaidOutVolume{ 554 Volume: &gadget.Volume{}, 555 LaidOutStructure: []gadget.LaidOutStructure{ 556 {}, 557 }, 558 }, 559 err: `cannot change the number of structures within volume from 2 to 1`, 560 }, { 561 // valid 562 from: gadget.PartiallyLaidOutVolume{ 563 Volume: &gadget.Volume{Schema: "mbr"}, 564 LaidOutStructure: []gadget.LaidOutStructure{ 565 {}, {}, 566 }, 567 }, 568 to: gadget.LaidOutVolume{ 569 Volume: &gadget.Volume{Schema: "mbr"}, 570 LaidOutStructure: []gadget.LaidOutStructure{ 571 {}, {}, 572 }, 573 }, 574 err: ``, 575 }, 576 } { 577 c.Logf("tc: %v", idx) 578 err := gadget.CanUpdateVolume(&tc.from, &tc.to) 579 if tc.err != "" { 580 c.Check(err, ErrorMatches, tc.err) 581 } else { 582 c.Check(err, IsNil) 583 } 584 585 } 586 } 587 588 type mockUpdater struct { 589 updateCb func() error 590 backupCb func() error 591 rollbackCb func() error 592 } 593 594 func callOrNil(f func() error) error { 595 if f != nil { 596 return f() 597 } 598 return nil 599 } 600 601 func (m *mockUpdater) Backup() error { 602 return callOrNil(m.backupCb) 603 } 604 605 func (m *mockUpdater) Rollback() error { 606 return callOrNil(m.rollbackCb) 607 } 608 609 func (m *mockUpdater) Update() error { 610 return callOrNil(m.updateCb) 611 } 612 613 func updateDataSet(c *C) (oldData gadget.GadgetData, newData gadget.GadgetData, rollbackDir string) { 614 // prepare the stage 615 bareStruct := gadget.VolumeStructure{ 616 Name: "first", 617 Size: 5 * quantity.SizeMiB, 618 Content: []gadget.VolumeContent{ 619 {Image: "first.img"}, 620 }, 621 } 622 fsStruct := gadget.VolumeStructure{ 623 Name: "second", 624 Size: 10 * quantity.SizeMiB, 625 Filesystem: "ext4", 626 Content: []gadget.VolumeContent{ 627 {UnresolvedSource: "/second-content", Target: "/"}, 628 }, 629 } 630 lastStruct := gadget.VolumeStructure{ 631 Name: "third", 632 Size: 5 * quantity.SizeMiB, 633 Filesystem: "vfat", 634 Content: []gadget.VolumeContent{ 635 {UnresolvedSource: "/third-content", Target: "/"}, 636 }, 637 } 638 // start with identical data for new and old infos, they get updated by 639 // the caller as needed 640 oldInfo := &gadget.Info{ 641 Volumes: map[string]*gadget.Volume{ 642 "foo": { 643 Bootloader: "grub", 644 Schema: "gpt", 645 Structure: []gadget.VolumeStructure{bareStruct, fsStruct, lastStruct}, 646 }, 647 }, 648 } 649 newInfo := &gadget.Info{ 650 Volumes: map[string]*gadget.Volume{ 651 "foo": { 652 Bootloader: "grub", 653 Schema: "gpt", 654 Structure: []gadget.VolumeStructure{bareStruct, fsStruct, lastStruct}, 655 }, 656 }, 657 } 658 659 oldRootDir := c.MkDir() 660 makeSizedFile(c, filepath.Join(oldRootDir, "first.img"), quantity.SizeMiB, nil) 661 makeSizedFile(c, filepath.Join(oldRootDir, "/second-content/foo"), 0, nil) 662 makeSizedFile(c, filepath.Join(oldRootDir, "/third-content/bar"), 0, nil) 663 oldData = gadget.GadgetData{Info: oldInfo, RootDir: oldRootDir} 664 665 newRootDir := c.MkDir() 666 makeSizedFile(c, filepath.Join(newRootDir, "first.img"), 900*quantity.SizeKiB, nil) 667 makeSizedFile(c, filepath.Join(newRootDir, "/second-content/foo"), quantity.SizeKiB, nil) 668 makeSizedFile(c, filepath.Join(newRootDir, "/third-content/bar"), quantity.SizeKiB, nil) 669 newData = gadget.GadgetData{Info: newInfo, RootDir: newRootDir} 670 671 rollbackDir = c.MkDir() 672 return oldData, newData, rollbackDir 673 } 674 675 type mockUpdateProcessObserver struct { 676 beforeWriteCalled int 677 canceledCalled int 678 beforeWriteErr error 679 canceledErr error 680 } 681 682 func (m *mockUpdateProcessObserver) Observe(op gadget.ContentOperation, sourceStruct *gadget.LaidOutStructure, 683 targetRootDir, relativeTargetPath string, data *gadget.ContentChange) (gadget.ContentChangeAction, error) { 684 return gadget.ChangeAbort, errors.New("unexpected call") 685 } 686 687 func (m *mockUpdateProcessObserver) BeforeWrite() error { 688 m.beforeWriteCalled++ 689 return m.beforeWriteErr 690 } 691 692 func (m *mockUpdateProcessObserver) Canceled() error { 693 m.canceledCalled++ 694 return m.canceledErr 695 } 696 697 func (u *updateTestSuite) TestUpdateApplyHappy(c *C) { 698 oldData, newData, rollbackDir := updateDataSet(c) 699 // update two structs 700 newData.Info.Volumes["foo"].Structure[0].Update.Edition = 1 701 newData.Info.Volumes["foo"].Structure[1].Update.Edition = 1 702 703 muo := &mockUpdateProcessObserver{} 704 updaterForStructureCalls := 0 705 updateCalls := make(map[string]bool) 706 backupCalls := make(map[string]bool) 707 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 708 c.Assert(psRootDir, Equals, newData.RootDir) 709 c.Assert(psRollbackDir, Equals, rollbackDir) 710 c.Assert(observer, Equals, muo) 711 // TODO:UC20 verify observer 712 713 switch updaterForStructureCalls { 714 case 0: 715 c.Check(ps.Name, Equals, "first") 716 c.Check(ps.HasFilesystem(), Equals, false) 717 c.Check(ps.Size, Equals, 5*quantity.SizeMiB) 718 c.Check(ps.IsPartition(), Equals, true) 719 // non MBR start offset defaults to 1MiB 720 c.Check(ps.StartOffset, Equals, 1*quantity.OffsetMiB) 721 c.Assert(ps.LaidOutContent, HasLen, 1) 722 c.Check(ps.LaidOutContent[0].Image, Equals, "first.img") 723 c.Check(ps.LaidOutContent[0].Size, Equals, 900*quantity.SizeKiB) 724 case 1: 725 c.Check(ps.Name, Equals, "second") 726 c.Check(ps.HasFilesystem(), Equals, true) 727 c.Check(ps.Filesystem, Equals, "ext4") 728 c.Check(ps.IsPartition(), Equals, true) 729 c.Check(ps.Size, Equals, 10*quantity.SizeMiB) 730 // foo's start offset + foo's size 731 c.Check(ps.StartOffset, Equals, (1+5)*quantity.OffsetMiB) 732 c.Assert(ps.LaidOutContent, HasLen, 0) 733 c.Assert(ps.Content, HasLen, 1) 734 c.Check(ps.Content[0].UnresolvedSource, Equals, "/second-content") 735 c.Check(ps.Content[0].Target, Equals, "/") 736 default: 737 c.Fatalf("unexpected call") 738 } 739 updaterForStructureCalls++ 740 mu := &mockUpdater{ 741 backupCb: func() error { 742 backupCalls[ps.Name] = true 743 return nil 744 }, 745 updateCb: func() error { 746 updateCalls[ps.Name] = true 747 return nil 748 }, 749 rollbackCb: func() error { 750 c.Fatalf("unexpected call") 751 return errors.New("not called") 752 }, 753 } 754 return mu, nil 755 }) 756 defer restore() 757 758 // go go go 759 err := gadget.Update(oldData, newData, rollbackDir, nil, muo) 760 c.Assert(err, IsNil) 761 c.Assert(backupCalls, DeepEquals, map[string]bool{ 762 "first": true, 763 "second": true, 764 }) 765 c.Assert(updateCalls, DeepEquals, map[string]bool{ 766 "first": true, 767 "second": true, 768 }) 769 c.Assert(updaterForStructureCalls, Equals, 2) 770 c.Assert(muo.beforeWriteCalled, Equals, 1) 771 c.Assert(muo.canceledCalled, Equals, 0) 772 } 773 774 func (u *updateTestSuite) TestUpdateApplyOnlyWhenNeeded(c *C) { 775 oldData, newData, rollbackDir := updateDataSet(c) 776 // first structure is updated 777 oldData.Info.Volumes["foo"].Structure[0].Update.Edition = 0 778 newData.Info.Volumes["foo"].Structure[0].Update.Edition = 1 779 // second one is not, lower edition 780 oldData.Info.Volumes["foo"].Structure[1].Update.Edition = 2 781 newData.Info.Volumes["foo"].Structure[1].Update.Edition = 1 782 // third one is not, same edition 783 oldData.Info.Volumes["foo"].Structure[2].Update.Edition = 3 784 newData.Info.Volumes["foo"].Structure[2].Update.Edition = 3 785 786 muo := &mockUpdateProcessObserver{} 787 updaterForStructureCalls := 0 788 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 789 c.Assert(psRootDir, Equals, newData.RootDir) 790 c.Assert(psRollbackDir, Equals, rollbackDir) 791 792 switch updaterForStructureCalls { 793 case 0: 794 // only called for the first structure 795 c.Assert(ps.Name, Equals, "first") 796 default: 797 c.Fatalf("unexpected call") 798 } 799 updaterForStructureCalls++ 800 mu := &mockUpdater{ 801 rollbackCb: func() error { 802 c.Fatalf("unexpected call") 803 return errors.New("not called") 804 }, 805 } 806 return mu, nil 807 }) 808 defer restore() 809 810 // go go go 811 err := gadget.Update(oldData, newData, rollbackDir, nil, muo) 812 c.Assert(err, IsNil) 813 814 c.Assert(muo.beforeWriteCalled, Equals, 1) 815 c.Assert(muo.canceledCalled, Equals, 0) 816 } 817 818 func (u *updateTestSuite) TestUpdateApplyErrorLayout(c *C) { 819 // prepare the stage 820 bareStruct := gadget.VolumeStructure{ 821 Name: "foo", 822 Size: 5 * quantity.SizeMiB, 823 Content: []gadget.VolumeContent{ 824 {Image: "first.img"}, 825 }, 826 } 827 bareStructUpdate := bareStruct 828 oldInfo := &gadget.Info{ 829 Volumes: map[string]*gadget.Volume{ 830 "foo": { 831 Bootloader: "grub", 832 Schema: "gpt", 833 Structure: []gadget.VolumeStructure{bareStruct}, 834 }, 835 }, 836 } 837 newInfo := &gadget.Info{ 838 Volumes: map[string]*gadget.Volume{ 839 "foo": { 840 Bootloader: "grub", 841 Schema: "gpt", 842 Structure: []gadget.VolumeStructure{bareStructUpdate}, 843 }, 844 }, 845 } 846 847 newRootDir := c.MkDir() 848 newData := gadget.GadgetData{Info: newInfo, RootDir: newRootDir} 849 850 oldRootDir := c.MkDir() 851 oldData := gadget.GadgetData{Info: oldInfo, RootDir: oldRootDir} 852 853 rollbackDir := c.MkDir() 854 855 // both old and new bare struct data is missing 856 857 // cannot lay out the new volume when bare struct data is missing 858 err := gadget.Update(oldData, newData, rollbackDir, nil, nil) 859 c.Assert(err, ErrorMatches, `cannot lay out the new volume: cannot lay out structure #0 \("foo"\): content "first.img": .* no such file or directory`) 860 861 makeSizedFile(c, filepath.Join(newRootDir, "first.img"), quantity.SizeMiB, nil) 862 863 // Update does not error out when when the bare struct data of the old volume is missing 864 err = gadget.Update(oldData, newData, rollbackDir, nil, nil) 865 c.Assert(err, Equals, gadget.ErrNoUpdate) 866 } 867 868 func (u *updateTestSuite) TestUpdateApplyErrorIllegalVolumeUpdate(c *C) { 869 // prepare the stage 870 bareStruct := gadget.VolumeStructure{ 871 Name: "foo", 872 Size: 5 * quantity.SizeMiB, 873 Content: []gadget.VolumeContent{ 874 {Image: "first.img"}, 875 }, 876 } 877 bareStructUpdate := bareStruct 878 bareStructUpdate.Name = "foo update" 879 bareStructUpdate.Update.Edition = 1 880 oldInfo := &gadget.Info{ 881 Volumes: map[string]*gadget.Volume{ 882 "foo": { 883 Bootloader: "grub", 884 Schema: "gpt", 885 Structure: []gadget.VolumeStructure{bareStruct}, 886 }, 887 }, 888 } 889 newInfo := &gadget.Info{ 890 Volumes: map[string]*gadget.Volume{ 891 "foo": { 892 Bootloader: "grub", 893 Schema: "gpt", 894 // more structures than old 895 Structure: []gadget.VolumeStructure{bareStruct, bareStructUpdate}, 896 }, 897 }, 898 } 899 900 newRootDir := c.MkDir() 901 newData := gadget.GadgetData{Info: newInfo, RootDir: newRootDir} 902 903 oldRootDir := c.MkDir() 904 oldData := gadget.GadgetData{Info: oldInfo, RootDir: oldRootDir} 905 906 rollbackDir := c.MkDir() 907 908 makeSizedFile(c, filepath.Join(oldRootDir, "first.img"), quantity.SizeMiB, nil) 909 makeSizedFile(c, filepath.Join(newRootDir, "first.img"), 900*quantity.SizeKiB, nil) 910 911 err := gadget.Update(oldData, newData, rollbackDir, nil, nil) 912 c.Assert(err, ErrorMatches, `cannot apply update to volume: cannot change the number of structures within volume from 1 to 2`) 913 } 914 915 func (u *updateTestSuite) TestUpdateApplyErrorIllegalStructureUpdate(c *C) { 916 // prepare the stage 917 bareStruct := gadget.VolumeStructure{ 918 Name: "foo", 919 Size: 5 * quantity.SizeMiB, 920 Content: []gadget.VolumeContent{ 921 {Image: "first.img"}, 922 }, 923 } 924 fsStruct := gadget.VolumeStructure{ 925 Name: "foo", 926 Filesystem: "ext4", 927 Size: 5 * quantity.SizeMiB, 928 Content: []gadget.VolumeContent{ 929 {UnresolvedSource: "/", Target: "/"}, 930 }, 931 Update: gadget.VolumeUpdate{Edition: 5}, 932 } 933 oldInfo := &gadget.Info{ 934 Volumes: map[string]*gadget.Volume{ 935 "foo": { 936 Bootloader: "grub", 937 Schema: "gpt", 938 Structure: []gadget.VolumeStructure{bareStruct}, 939 }, 940 }, 941 } 942 newInfo := &gadget.Info{ 943 Volumes: map[string]*gadget.Volume{ 944 "foo": { 945 Bootloader: "grub", 946 Schema: "gpt", 947 Structure: []gadget.VolumeStructure{fsStruct}, 948 }, 949 }, 950 } 951 952 newRootDir := c.MkDir() 953 newData := gadget.GadgetData{Info: newInfo, RootDir: newRootDir} 954 955 oldRootDir := c.MkDir() 956 oldData := gadget.GadgetData{Info: oldInfo, RootDir: oldRootDir} 957 958 rollbackDir := c.MkDir() 959 960 makeSizedFile(c, filepath.Join(oldRootDir, "first.img"), quantity.SizeMiB, nil) 961 962 err := gadget.Update(oldData, newData, rollbackDir, nil, nil) 963 c.Assert(err, ErrorMatches, `cannot update volume structure #0 \("foo"\): cannot change a bare structure to filesystem one`) 964 } 965 966 func (u *updateTestSuite) TestUpdateApplyErrorDifferentVolume(c *C) { 967 // prepare the stage 968 bareStruct := gadget.VolumeStructure{ 969 Name: "foo", 970 Size: 5 * quantity.SizeMiB, 971 Content: []gadget.VolumeContent{ 972 {Image: "first.img"}, 973 }, 974 } 975 oldInfo := &gadget.Info{ 976 Volumes: map[string]*gadget.Volume{ 977 "foo": { 978 Bootloader: "grub", 979 Schema: "gpt", 980 Structure: []gadget.VolumeStructure{bareStruct}, 981 }, 982 }, 983 } 984 newInfo := &gadget.Info{ 985 Volumes: map[string]*gadget.Volume{ 986 // same volume info but using a different name 987 "foo-new": oldInfo.Volumes["foo"], 988 }, 989 } 990 991 oldData := gadget.GadgetData{Info: oldInfo, RootDir: c.MkDir()} 992 newData := gadget.GadgetData{Info: newInfo, RootDir: c.MkDir()} 993 rollbackDir := c.MkDir() 994 995 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 996 c.Fatalf("unexpected call") 997 return &mockUpdater{}, nil 998 }) 999 defer restore() 1000 1001 err := gadget.Update(oldData, newData, rollbackDir, nil, nil) 1002 c.Assert(err, ErrorMatches, `cannot find entry for volume "foo" in updated gadget info`) 1003 } 1004 1005 func (u *updateTestSuite) TestUpdateApplyUpdatesAreOptInWithDefaultPolicy(c *C) { 1006 // prepare the stage 1007 bareStruct := gadget.VolumeStructure{ 1008 Name: "foo", 1009 Size: 5 * quantity.SizeMiB, 1010 Content: []gadget.VolumeContent{ 1011 {Image: "first.img"}, 1012 }, 1013 Update: gadget.VolumeUpdate{ 1014 Edition: 5, 1015 }, 1016 } 1017 oldInfo := &gadget.Info{ 1018 Volumes: map[string]*gadget.Volume{ 1019 "foo": { 1020 Bootloader: "grub", 1021 Schema: "gpt", 1022 Structure: []gadget.VolumeStructure{bareStruct}, 1023 }, 1024 }, 1025 } 1026 1027 oldRootDir := c.MkDir() 1028 oldData := gadget.GadgetData{Info: oldInfo, RootDir: oldRootDir} 1029 makeSizedFile(c, filepath.Join(oldRootDir, "first.img"), quantity.SizeMiB, nil) 1030 1031 newRootDir := c.MkDir() 1032 // same volume description 1033 newData := gadget.GadgetData{Info: oldInfo, RootDir: newRootDir} 1034 // different content, but updates are opt in 1035 makeSizedFile(c, filepath.Join(newRootDir, "first.img"), 900*quantity.SizeKiB, nil) 1036 1037 rollbackDir := c.MkDir() 1038 1039 muo := &mockUpdateProcessObserver{} 1040 1041 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1042 c.Fatalf("unexpected call") 1043 return &mockUpdater{}, nil 1044 }) 1045 defer restore() 1046 1047 err := gadget.Update(oldData, newData, rollbackDir, nil, muo) 1048 c.Assert(err, Equals, gadget.ErrNoUpdate) 1049 1050 // nothing was updated 1051 c.Assert(muo.beforeWriteCalled, Equals, 0) 1052 } 1053 1054 func policyDataSet(c *C) (oldData gadget.GadgetData, newData gadget.GadgetData, rollbackDir string) { 1055 oldData, newData, rollbackDir = updateDataSet(c) 1056 noPartitionStruct := gadget.VolumeStructure{ 1057 Name: "no-partition", 1058 Type: "bare", 1059 Size: 5 * quantity.SizeMiB, 1060 Content: []gadget.VolumeContent{ 1061 {Image: "first.img"}, 1062 }, 1063 } 1064 mbrStruct := gadget.VolumeStructure{ 1065 Name: "mbr", 1066 Role: "mbr", 1067 Size: 446, 1068 Offset: asOffsetPtr(0), 1069 } 1070 1071 oldVol := oldData.Info.Volumes["foo"] 1072 oldVol.Structure = append(oldVol.Structure, noPartitionStruct, mbrStruct) 1073 oldData.Info.Volumes["foo"] = oldVol 1074 1075 newVol := newData.Info.Volumes["foo"] 1076 newVol.Structure = append(newVol.Structure, noPartitionStruct, mbrStruct) 1077 newData.Info.Volumes["foo"] = newVol 1078 1079 c.Assert(oldData.Info.Volumes["foo"].Structure, HasLen, 5) 1080 c.Assert(newData.Info.Volumes["foo"].Structure, HasLen, 5) 1081 return oldData, newData, rollbackDir 1082 } 1083 1084 func (u *updateTestSuite) TestUpdateApplyUpdatesArePolicyControlled(c *C) { 1085 oldData, newData, rollbackDir := policyDataSet(c) 1086 c.Assert(oldData.Info.Volumes["foo"].Structure, HasLen, 5) 1087 c.Assert(newData.Info.Volumes["foo"].Structure, HasLen, 5) 1088 // all structures have higher Edition, thus all would be updated under 1089 // the default policy 1090 newData.Info.Volumes["foo"].Structure[0].Update.Edition = 1 1091 newData.Info.Volumes["foo"].Structure[1].Update.Edition = 1 1092 newData.Info.Volumes["foo"].Structure[2].Update.Edition = 3 1093 newData.Info.Volumes["foo"].Structure[3].Update.Edition = 4 1094 newData.Info.Volumes["foo"].Structure[4].Update.Edition = 5 1095 1096 toUpdate := map[string]int{} 1097 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1098 toUpdate[ps.Name]++ 1099 return &mockUpdater{}, nil 1100 }) 1101 defer restore() 1102 1103 policySeen := map[string]int{} 1104 err := gadget.Update(oldData, newData, rollbackDir, func(_, to *gadget.LaidOutStructure) (bool, gadget.ResolvedContentFilterFunc) { 1105 policySeen[to.Name]++ 1106 return false, nil 1107 }, nil) 1108 c.Assert(err, Equals, gadget.ErrNoUpdate) 1109 c.Assert(policySeen, DeepEquals, map[string]int{ 1110 "first": 1, 1111 "second": 1, 1112 "third": 1, 1113 "no-partition": 1, 1114 "mbr": 1, 1115 }) 1116 c.Assert(toUpdate, DeepEquals, map[string]int{}) 1117 1118 // try with different policy 1119 policySeen = map[string]int{} 1120 err = gadget.Update(oldData, newData, rollbackDir, func(_, to *gadget.LaidOutStructure) (bool, gadget.ResolvedContentFilterFunc) { 1121 policySeen[to.Name]++ 1122 return to.Name == "second", nil 1123 }, nil) 1124 c.Assert(err, IsNil) 1125 c.Assert(policySeen, DeepEquals, map[string]int{ 1126 "first": 1, 1127 "second": 1, 1128 "third": 1, 1129 "no-partition": 1, 1130 "mbr": 1, 1131 }) 1132 c.Assert(toUpdate, DeepEquals, map[string]int{ 1133 "second": 1, 1134 }) 1135 } 1136 1137 func (u *updateTestSuite) TestUpdateApplyUpdatesRemodelPolicy(c *C) { 1138 oldData, newData, rollbackDir := policyDataSet(c) 1139 1140 // old structures have higher Edition, no update would occur under the default policy 1141 oldData.Info.Volumes["foo"].Structure[0].Update.Edition = 1 1142 oldData.Info.Volumes["foo"].Structure[1].Update.Edition = 1 1143 oldData.Info.Volumes["foo"].Structure[2].Update.Edition = 3 1144 oldData.Info.Volumes["foo"].Structure[3].Update.Edition = 4 1145 oldData.Info.Volumes["foo"].Structure[4].Update.Edition = 5 1146 1147 toUpdate := map[string]int{} 1148 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1149 toUpdate[ps.Name] = toUpdate[ps.Name] + 1 1150 return &mockUpdater{}, nil 1151 }) 1152 defer restore() 1153 1154 err := gadget.Update(oldData, newData, rollbackDir, gadget.RemodelUpdatePolicy, nil) 1155 c.Assert(err, IsNil) 1156 c.Assert(toUpdate, DeepEquals, map[string]int{ 1157 "first": 1, 1158 "second": 1, 1159 "third": 1, 1160 "no-partition": 1, 1161 // 'mbr' is skipped by the remodel update 1162 }) 1163 } 1164 1165 func (u *updateTestSuite) TestUpdateApplyBackupFails(c *C) { 1166 oldData, newData, rollbackDir := updateDataSet(c) 1167 // update both structs 1168 newData.Info.Volumes["foo"].Structure[0].Update.Edition = 1 1169 newData.Info.Volumes["foo"].Structure[1].Update.Edition = 1 1170 newData.Info.Volumes["foo"].Structure[2].Update.Edition = 3 1171 1172 muo := &mockUpdateProcessObserver{} 1173 updaterForStructureCalls := 0 1174 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1175 updater := &mockUpdater{ 1176 updateCb: func() error { 1177 c.Fatalf("unexpected update call") 1178 return errors.New("not called") 1179 }, 1180 rollbackCb: func() error { 1181 c.Fatalf("unexpected rollback call") 1182 return errors.New("not called") 1183 }, 1184 } 1185 if updaterForStructureCalls == 1 { 1186 c.Assert(ps.Name, Equals, "second") 1187 updater.backupCb = func() error { 1188 return errors.New("failed") 1189 } 1190 } 1191 updaterForStructureCalls++ 1192 return updater, nil 1193 }) 1194 defer restore() 1195 1196 // go go go 1197 err := gadget.Update(oldData, newData, rollbackDir, nil, muo) 1198 c.Assert(err, ErrorMatches, `cannot backup volume structure #1 \("second"\): failed`) 1199 1200 // update was canceled before backup pass completed 1201 c.Check(muo.canceledCalled, Equals, 1) 1202 c.Check(muo.beforeWriteCalled, Equals, 0) 1203 } 1204 1205 func (u *updateTestSuite) TestUpdateApplyUpdateFailsThenRollback(c *C) { 1206 oldData, newData, rollbackDir := updateDataSet(c) 1207 // update all structs 1208 newData.Info.Volumes["foo"].Structure[0].Update.Edition = 1 1209 newData.Info.Volumes["foo"].Structure[1].Update.Edition = 2 1210 newData.Info.Volumes["foo"].Structure[2].Update.Edition = 3 1211 1212 muo := &mockUpdateProcessObserver{} 1213 updateCalls := make(map[string]bool) 1214 backupCalls := make(map[string]bool) 1215 rollbackCalls := make(map[string]bool) 1216 updaterForStructureCalls := 0 1217 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1218 updater := &mockUpdater{ 1219 backupCb: func() error { 1220 backupCalls[ps.Name] = true 1221 return nil 1222 }, 1223 rollbackCb: func() error { 1224 rollbackCalls[ps.Name] = true 1225 return nil 1226 }, 1227 updateCb: func() error { 1228 updateCalls[ps.Name] = true 1229 return nil 1230 }, 1231 } 1232 if updaterForStructureCalls == 1 { 1233 c.Assert(ps.Name, Equals, "second") 1234 // fail update of 2nd structure 1235 updater.updateCb = func() error { 1236 updateCalls[ps.Name] = true 1237 return errors.New("failed") 1238 } 1239 } 1240 updaterForStructureCalls++ 1241 return updater, nil 1242 }) 1243 defer restore() 1244 1245 // go go go 1246 err := gadget.Update(oldData, newData, rollbackDir, nil, muo) 1247 c.Assert(err, ErrorMatches, `cannot update volume structure #1 \("second"\): failed`) 1248 c.Assert(backupCalls, DeepEquals, map[string]bool{ 1249 // all were backed up 1250 "first": true, 1251 "second": true, 1252 "third": true, 1253 }) 1254 c.Assert(updateCalls, DeepEquals, map[string]bool{ 1255 "first": true, 1256 "second": true, 1257 // third was never updated, as second failed 1258 }) 1259 c.Assert(rollbackCalls, DeepEquals, map[string]bool{ 1260 "first": true, 1261 "second": true, 1262 // third does not need as it was not updated 1263 }) 1264 // backup pass completed 1265 c.Check(muo.beforeWriteCalled, Equals, 1) 1266 // and then the update was canceled 1267 c.Check(muo.canceledCalled, Equals, 1) 1268 } 1269 1270 func (u *updateTestSuite) TestUpdateApplyUpdateErrorRollbackFail(c *C) { 1271 logbuf, restore := logger.MockLogger() 1272 defer restore() 1273 1274 oldData, newData, rollbackDir := updateDataSet(c) 1275 // update all structs 1276 newData.Info.Volumes["foo"].Structure[0].Update.Edition = 1 1277 newData.Info.Volumes["foo"].Structure[1].Update.Edition = 2 1278 newData.Info.Volumes["foo"].Structure[2].Update.Edition = 3 1279 1280 updateCalls := make(map[string]bool) 1281 backupCalls := make(map[string]bool) 1282 rollbackCalls := make(map[string]bool) 1283 updaterForStructureCalls := 0 1284 restore = gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1285 updater := &mockUpdater{ 1286 backupCb: func() error { 1287 backupCalls[ps.Name] = true 1288 return nil 1289 }, 1290 rollbackCb: func() error { 1291 rollbackCalls[ps.Name] = true 1292 return nil 1293 }, 1294 updateCb: func() error { 1295 updateCalls[ps.Name] = true 1296 return nil 1297 }, 1298 } 1299 switch updaterForStructureCalls { 1300 case 1: 1301 c.Assert(ps.Name, Equals, "second") 1302 // rollback fails on 2nd structure 1303 updater.rollbackCb = func() error { 1304 rollbackCalls[ps.Name] = true 1305 return errors.New("rollback failed with different error") 1306 } 1307 case 2: 1308 c.Assert(ps.Name, Equals, "third") 1309 // fail update of 3rd structure 1310 updater.updateCb = func() error { 1311 updateCalls[ps.Name] = true 1312 return errors.New("update error") 1313 } 1314 } 1315 updaterForStructureCalls++ 1316 return updater, nil 1317 }) 1318 defer restore() 1319 1320 // go go go 1321 err := gadget.Update(oldData, newData, rollbackDir, nil, nil) 1322 // preserves update error 1323 c.Assert(err, ErrorMatches, `cannot update volume structure #2 \("third"\): update error`) 1324 c.Assert(backupCalls, DeepEquals, map[string]bool{ 1325 // all were backed up 1326 "first": true, 1327 "second": true, 1328 "third": true, 1329 }) 1330 c.Assert(updateCalls, DeepEquals, map[string]bool{ 1331 "first": true, 1332 "second": true, 1333 "third": true, 1334 }) 1335 c.Assert(rollbackCalls, DeepEquals, map[string]bool{ 1336 "first": true, 1337 "second": true, 1338 "third": true, 1339 }) 1340 1341 c.Check(logbuf.String(), testutil.Contains, `cannot update gadget: cannot update volume structure #2 ("third"): update error`) 1342 c.Check(logbuf.String(), testutil.Contains, `cannot rollback volume structure #1 ("second") update: rollback failed with different error`) 1343 } 1344 1345 func (u *updateTestSuite) TestUpdateApplyBadUpdater(c *C) { 1346 oldData, newData, rollbackDir := updateDataSet(c) 1347 // update all structs 1348 newData.Info.Volumes["foo"].Structure[0].Update.Edition = 1 1349 newData.Info.Volumes["foo"].Structure[1].Update.Edition = 2 1350 newData.Info.Volumes["foo"].Structure[2].Update.Edition = 3 1351 1352 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1353 return nil, errors.New("bad updater for structure") 1354 }) 1355 defer restore() 1356 1357 // go go go 1358 err := gadget.Update(oldData, newData, rollbackDir, nil, nil) 1359 c.Assert(err, ErrorMatches, `cannot prepare update for volume structure #0 \("first"\): bad updater for structure`) 1360 } 1361 1362 func (u *updateTestSuite) TestUpdaterForStructure(c *C) { 1363 gadgetRootDir := c.MkDir() 1364 rollbackDir := c.MkDir() 1365 rootDir := c.MkDir() 1366 1367 dirs.SetRootDir(rootDir) 1368 defer dirs.SetRootDir("/") 1369 1370 // prepare some state for mocked mount point lookup 1371 err := os.MkdirAll(filepath.Join(rootDir, "/dev"), 0755) 1372 c.Assert(err, IsNil) 1373 err = os.MkdirAll(filepath.Join(rootDir, "/dev/disk/by-label"), 0755) 1374 c.Assert(err, IsNil) 1375 fakedevice := filepath.Join(rootDir, "/dev/sdxxx2") 1376 err = ioutil.WriteFile(fakedevice, []byte(""), 0644) 1377 c.Assert(err, IsNil) 1378 err = os.Symlink(fakedevice, filepath.Join(rootDir, "/dev/disk/by-label/writable")) 1379 c.Assert(err, IsNil) 1380 mountInfo := `170 27 8:2 / /some/mount/point rw,relatime shared:58 - ext4 %s/dev/sdxxx2 rw 1381 ` 1382 restore := osutil.MockMountInfo(fmt.Sprintf(mountInfo, rootDir)) 1383 defer restore() 1384 1385 psBare := &gadget.LaidOutStructure{ 1386 VolumeStructure: &gadget.VolumeStructure{ 1387 Filesystem: "none", 1388 Size: 10 * quantity.SizeMiB, 1389 }, 1390 StartOffset: 1 * quantity.OffsetMiB, 1391 } 1392 updater, err := gadget.UpdaterForStructure(psBare, gadgetRootDir, rollbackDir, nil) 1393 c.Assert(err, IsNil) 1394 c.Assert(updater, FitsTypeOf, &gadget.RawStructureUpdater{}) 1395 1396 psFs := &gadget.LaidOutStructure{ 1397 VolumeStructure: &gadget.VolumeStructure{ 1398 Filesystem: "ext4", 1399 Size: 10 * quantity.SizeMiB, 1400 Label: "writable", 1401 }, 1402 StartOffset: 1 * quantity.OffsetMiB, 1403 } 1404 updater, err = gadget.UpdaterForStructure(psFs, gadgetRootDir, rollbackDir, nil) 1405 c.Assert(err, IsNil) 1406 c.Assert(updater, FitsTypeOf, &gadget.MountedFilesystemUpdater{}) 1407 1408 // trigger errors 1409 updater, err = gadget.UpdaterForStructure(psBare, gadgetRootDir, "", nil) 1410 c.Assert(err, ErrorMatches, "internal error: backup directory cannot be unset") 1411 c.Assert(updater, IsNil) 1412 } 1413 1414 func (u *updateTestSuite) TestUpdaterMultiVolumesDoesNotError(c *C) { 1415 logbuf, restore := logger.MockLogger() 1416 defer restore() 1417 1418 multiVolume := gadget.GadgetData{ 1419 Info: &gadget.Info{ 1420 Volumes: map[string]*gadget.Volume{ 1421 "1": {}, 1422 "2": {}, 1423 }, 1424 }, 1425 } 1426 singleVolume := gadget.GadgetData{ 1427 Info: &gadget.Info{ 1428 Volumes: map[string]*gadget.Volume{ 1429 "1": {}, 1430 }, 1431 }, 1432 } 1433 1434 // a new multi volume gadget update gives no error 1435 err := gadget.Update(singleVolume, multiVolume, "some-rollback-dir", nil, nil) 1436 c.Assert(err, IsNil) 1437 // but it warns that nothing happens either 1438 c.Assert(logbuf.String(), testutil.Contains, "WARNING: gadget assests cannot be updated yet when multiple volumes are used") 1439 1440 // same for old 1441 err = gadget.Update(multiVolume, singleVolume, "some-rollback-dir", nil, nil) 1442 c.Assert(err, IsNil) 1443 c.Assert(strings.Count(logbuf.String(), "WARNING: gadget assests cannot be updated yet when multiple volumes are used"), Equals, 2) 1444 } 1445 1446 func (u *updateTestSuite) TestUpdateApplyNoChangedContentInAll(c *C) { 1447 oldData, newData, rollbackDir := updateDataSet(c) 1448 // first structure is updated 1449 oldData.Info.Volumes["foo"].Structure[0].Update.Edition = 0 1450 newData.Info.Volumes["foo"].Structure[0].Update.Edition = 1 1451 // so is the second structure 1452 oldData.Info.Volumes["foo"].Structure[1].Update.Edition = 1 1453 newData.Info.Volumes["foo"].Structure[1].Update.Edition = 2 1454 1455 muo := &mockUpdateProcessObserver{} 1456 expectedStructs := []string{"first", "second"} 1457 updateCalls := 0 1458 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1459 mu := &mockUpdater{ 1460 updateCb: func() error { 1461 c.Assert(expectedStructs, testutil.Contains, ps.Name) 1462 updateCalls++ 1463 return gadget.ErrNoUpdate 1464 }, 1465 rollbackCb: func() error { 1466 c.Fatalf("unexpected rollback call for structure: %v", ps) 1467 return errors.New("not called") 1468 }, 1469 } 1470 return mu, nil 1471 }) 1472 defer restore() 1473 1474 // go go go 1475 err := gadget.Update(oldData, newData, rollbackDir, nil, muo) 1476 c.Assert(err, Equals, gadget.ErrNoUpdate) 1477 // update called for 2 structures 1478 c.Assert(updateCalls, Equals, 2) 1479 // nothing was updated, but the backup pass still executed 1480 c.Assert(muo.beforeWriteCalled, Equals, 1) 1481 c.Assert(muo.canceledCalled, Equals, 0) 1482 } 1483 1484 func (u *updateTestSuite) TestUpdateApplyNoChangedContentInSome(c *C) { 1485 oldData, newData, rollbackDir := updateDataSet(c) 1486 // first structure is updated 1487 oldData.Info.Volumes["foo"].Structure[0].Update.Edition = 0 1488 newData.Info.Volumes["foo"].Structure[0].Update.Edition = 1 1489 // so is the second structure 1490 oldData.Info.Volumes["foo"].Structure[1].Update.Edition = 1 1491 newData.Info.Volumes["foo"].Structure[1].Update.Edition = 2 1492 1493 muo := &mockUpdateProcessObserver{} 1494 expectedStructs := []string{"first", "second"} 1495 updateCalls := 0 1496 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1497 mu := &mockUpdater{ 1498 updateCb: func() error { 1499 c.Assert(expectedStructs, testutil.Contains, ps.Name) 1500 updateCalls++ 1501 if ps.Name == "first" { 1502 return gadget.ErrNoUpdate 1503 } 1504 return nil 1505 }, 1506 rollbackCb: func() error { 1507 c.Fatalf("unexpected rollback call for structure: %v", ps) 1508 return errors.New("not called") 1509 }, 1510 } 1511 return mu, nil 1512 }) 1513 defer restore() 1514 1515 // go go go 1516 err := gadget.Update(oldData, newData, rollbackDir, nil, muo) 1517 c.Assert(err, IsNil) 1518 // update called for 2 structures 1519 c.Assert(updateCalls, Equals, 2) 1520 // at least one structure had an update 1521 c.Assert(muo.beforeWriteCalled, Equals, 1) 1522 c.Assert(muo.canceledCalled, Equals, 0) 1523 } 1524 1525 func (u *updateTestSuite) TestUpdateApplyObserverBeforeWriteErrs(c *C) { 1526 oldData, newData, rollbackDir := updateDataSet(c) 1527 newData.Info.Volumes["foo"].Structure[0].Update.Edition = 1 1528 1529 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1530 updater := &mockUpdater{ 1531 updateCb: func() error { 1532 c.Fatalf("unexpected call") 1533 return fmt.Errorf("unexpected call") 1534 }, 1535 } 1536 return updater, nil 1537 }) 1538 defer restore() 1539 1540 // go go go 1541 muo := &mockUpdateProcessObserver{ 1542 beforeWriteErr: errors.New("before write fail"), 1543 } 1544 err := gadget.Update(oldData, newData, rollbackDir, nil, muo) 1545 c.Assert(err, ErrorMatches, `cannot observe prepared update: before write fail`) 1546 // update was canceled before backup pass completed 1547 c.Check(muo.canceledCalled, Equals, 0) 1548 c.Check(muo.beforeWriteCalled, Equals, 1) 1549 } 1550 1551 func (u *updateTestSuite) TestUpdateApplyObserverCanceledErrs(c *C) { 1552 logbuf, restore := logger.MockLogger() 1553 defer restore() 1554 1555 oldData, newData, rollbackDir := updateDataSet(c) 1556 newData.Info.Volumes["foo"].Structure[0].Update.Edition = 1 1557 1558 backupErr := errors.New("backup fails") 1559 updateErr := errors.New("update fails") 1560 restore = gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1561 updater := &mockUpdater{ 1562 backupCb: func() error { return backupErr }, 1563 updateCb: func() error { return updateErr }, 1564 } 1565 return updater, nil 1566 }) 1567 defer restore() 1568 1569 // go go go 1570 muo := &mockUpdateProcessObserver{ 1571 canceledErr: errors.New("canceled fail"), 1572 } 1573 err := gadget.Update(oldData, newData, rollbackDir, nil, muo) 1574 c.Assert(err, ErrorMatches, `cannot backup volume structure #0 .*: backup fails`) 1575 // canceled called after backup pass 1576 c.Check(muo.canceledCalled, Equals, 1) 1577 c.Check(muo.beforeWriteCalled, Equals, 0) 1578 1579 c.Check(logbuf.String(), testutil.Contains, `cannot observe canceled prepare update: canceled fail`) 1580 1581 // backup works, update fails, triggers another canceled call 1582 backupErr = nil 1583 err = gadget.Update(oldData, newData, rollbackDir, nil, muo) 1584 c.Assert(err, ErrorMatches, `cannot update volume structure #0 .*: update fails`) 1585 // canceled called after backup pass 1586 c.Check(muo.canceledCalled, Equals, 2) 1587 c.Check(muo.beforeWriteCalled, Equals, 1) 1588 1589 c.Check(logbuf.String(), testutil.Contains, `cannot observe canceled update: canceled fail`) 1590 } 1591 1592 func (u *updateTestSuite) TestKernelUpdatePolicy(c *C) { 1593 for _, tc := range []struct { 1594 from, to *gadget.LaidOutStructure 1595 update bool 1596 }{ 1597 // trivial 1598 { 1599 from: &gadget.LaidOutStructure{}, 1600 to: &gadget.LaidOutStructure{ 1601 VolumeStructure: &gadget.VolumeStructure{}, 1602 }, 1603 update: false, 1604 }, 1605 // gadget content only, nothing for the kernel 1606 { 1607 from: &gadget.LaidOutStructure{}, 1608 to: &gadget.LaidOutStructure{ 1609 VolumeStructure: &gadget.VolumeStructure{ 1610 Content: []gadget.VolumeContent{ 1611 {UnresolvedSource: "something"}, 1612 }, 1613 }, 1614 }, 1615 }, 1616 // ensure that only the `KernelUpdate` of the `to` 1617 // structure is relevant 1618 { 1619 from: &gadget.LaidOutStructure{ 1620 VolumeStructure: &gadget.VolumeStructure{ 1621 Content: []gadget.VolumeContent{ 1622 { 1623 UnresolvedSource: "$kernel:ref", 1624 }, 1625 }, 1626 }, 1627 }, 1628 to: &gadget.LaidOutStructure{ 1629 VolumeStructure: &gadget.VolumeStructure{}, 1630 }, 1631 update: false, 1632 }, 1633 // happy case, kernelUpdate is true 1634 { 1635 from: &gadget.LaidOutStructure{}, 1636 to: &gadget.LaidOutStructure{ 1637 VolumeStructure: &gadget.VolumeStructure{ 1638 Content: []gadget.VolumeContent{ 1639 { 1640 UnresolvedSource: "other", 1641 }, 1642 { 1643 UnresolvedSource: "$kernel:ref", 1644 }, 1645 }, 1646 }, 1647 }, 1648 update: true, 1649 }, 1650 } { 1651 needsUpdate, filter := gadget.KernelUpdatePolicy(tc.from, tc.to) 1652 if tc.update { 1653 c.Check(needsUpdate, Equals, true, Commentf("%v", tc)) 1654 c.Check(filter, NotNil) 1655 } else { 1656 c.Check(needsUpdate, Equals, false, Commentf("%v", tc)) 1657 c.Check(filter, IsNil) 1658 } 1659 } 1660 } 1661 1662 func (u *updateTestSuite) TestKernelUpdatePolicyFunc(c *C) { 1663 from := &gadget.LaidOutStructure{} 1664 to := &gadget.LaidOutStructure{ 1665 VolumeStructure: &gadget.VolumeStructure{ 1666 Content: []gadget.VolumeContent{ 1667 { 1668 UnresolvedSource: "other", 1669 }, 1670 { 1671 UnresolvedSource: "$kernel:ref", 1672 }, 1673 }, 1674 }, 1675 ResolvedContent: []gadget.ResolvedContent{ 1676 { 1677 ResolvedSource: "/gadget/path/to/other", 1678 }, 1679 { 1680 ResolvedSource: "/kernel/path/to/ref", 1681 KernelUpdate: true, 1682 }, 1683 }, 1684 } 1685 needsUpdate, filter := gadget.KernelUpdatePolicy(from, to) 1686 c.Check(needsUpdate, Equals, true) 1687 c.Assert(filter, NotNil) 1688 c.Check(filter(&to.ResolvedContent[0]), Equals, false) 1689 c.Check(filter(&to.ResolvedContent[1]), Equals, true) 1690 } 1691 1692 func (u *updateTestSuite) TestUpdateApplyUpdatesWithKernelPolicy(c *C) { 1693 // prepare the stage 1694 fsStruct := gadget.VolumeStructure{ 1695 Name: "foo", 1696 Size: 5 * quantity.SizeMiB, 1697 Filesystem: "ext4", 1698 Content: []gadget.VolumeContent{ 1699 {UnresolvedSource: "/second-content", Target: "/"}, 1700 {UnresolvedSource: "$kernel:ref/kernel-content", Target: "/"}, 1701 }, 1702 } 1703 oldInfo := &gadget.Info{ 1704 Volumes: map[string]*gadget.Volume{ 1705 "foo": { 1706 Bootloader: "grub", 1707 Schema: "gpt", 1708 Structure: []gadget.VolumeStructure{fsStruct}, 1709 }, 1710 }, 1711 } 1712 1713 oldRootDir := c.MkDir() 1714 oldKernelDir := c.MkDir() 1715 oldData := gadget.GadgetData{Info: oldInfo, RootDir: oldRootDir, KernelRootDir: oldKernelDir} 1716 makeSizedFile(c, filepath.Join(oldRootDir, "some-content"), quantity.SizeMiB, nil) 1717 makeSizedFile(c, filepath.Join(oldKernelDir, "kernel-content"), quantity.SizeMiB, nil) 1718 1719 newRootDir := oldRootDir 1720 newKernelDir := c.MkDir() 1721 kernelYamlFn := filepath.Join(newKernelDir, "meta/kernel.yaml") 1722 makeSizedFile(c, kernelYamlFn, 0, []byte(` 1723 assets: 1724 ref: 1725 update: true 1726 content: 1727 - kernel-content`)) 1728 1729 // same volume description 1730 newData := gadget.GadgetData{Info: oldInfo, RootDir: newRootDir, KernelRootDir: newKernelDir} 1731 // different file from gadget 1732 makeSizedFile(c, filepath.Join(newRootDir, "some-content"), 2*quantity.SizeMiB, nil) 1733 // same file from kernel, it is still updated because kernel sets 1734 // the update flag 1735 makeSizedFile(c, filepath.Join(newKernelDir, "kernel-content"), quantity.SizeMiB, nil) 1736 1737 rollbackDir := c.MkDir() 1738 muo := &mockUpdateProcessObserver{} 1739 1740 // Check that filtering happened via the KernelUpdatePolicy and the 1741 // updater is only called with the kernel content, not with the 1742 // gadget content. 1743 mockUpdaterCalls := 0 1744 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1745 mockUpdaterCalls++ 1746 c.Check(ps.ResolvedContent, DeepEquals, []gadget.ResolvedContent{ 1747 { 1748 VolumeContent: &gadget.VolumeContent{ 1749 UnresolvedSource: "$kernel:ref/kernel-content", 1750 Target: "/", 1751 }, 1752 ResolvedSource: filepath.Join(newKernelDir, "kernel-content"), 1753 KernelUpdate: true, 1754 }, 1755 }) 1756 return &mockUpdater{}, nil 1757 }) 1758 defer restore() 1759 1760 // exercise KernelUpdatePolicy here 1761 err := gadget.Update(oldData, newData, rollbackDir, gadget.KernelUpdatePolicy, muo) 1762 c.Assert(err, IsNil) 1763 1764 // ensure update for kernel content happened 1765 c.Assert(mockUpdaterCalls, Equals, 1) 1766 c.Assert(muo.beforeWriteCalled, Equals, 1) 1767 } 1768 1769 func (u *updateTestSuite) TestUpdateApplyUpdatesWithMissingKernelRefInGadget(c *C) { 1770 // kernel.yaml has "$kernel:ref" style content 1771 kernelYaml := []byte(` 1772 assets: 1773 ref: 1774 update: true 1775 content: 1776 - kernel-content`) 1777 // but gadget.yaml does not have this, which violates kernel 1778 // update policy rule no. 1 from update.go 1779 fsStruct := gadget.VolumeStructure{ 1780 Name: "foo", 1781 Size: 5 * quantity.SizeMiB, 1782 Filesystem: "ext4", 1783 Content: []gadget.VolumeContent{ 1784 // Note that there is no "$kernel:ref" here 1785 {UnresolvedSource: "/content", Target: "/"}, 1786 }, 1787 } 1788 info := &gadget.Info{ 1789 Volumes: map[string]*gadget.Volume{ 1790 "foo": { 1791 Bootloader: "grub", 1792 Schema: "gpt", 1793 Structure: []gadget.VolumeStructure{fsStruct}, 1794 }, 1795 }, 1796 } 1797 1798 gadgetDir := c.MkDir() 1799 oldKernelDir := c.MkDir() 1800 oldData := gadget.GadgetData{Info: info, RootDir: gadgetDir, KernelRootDir: oldKernelDir} 1801 makeSizedFile(c, filepath.Join(gadgetDir, "some-content"), quantity.SizeMiB, nil) 1802 makeSizedFile(c, filepath.Join(oldKernelDir, "kernel-content"), quantity.SizeMiB, nil) 1803 1804 newKernelDir := c.MkDir() 1805 kernelYamlFn := filepath.Join(newKernelDir, "meta/kernel.yaml") 1806 makeSizedFile(c, kernelYamlFn, 0, kernelYaml) 1807 1808 newData := gadget.GadgetData{Info: info, RootDir: gadgetDir, KernelRootDir: newKernelDir} 1809 makeSizedFile(c, filepath.Join(gadgetDir, "content"), 2*quantity.SizeMiB, nil) 1810 rollbackDir := c.MkDir() 1811 muo := &mockUpdateProcessObserver{} 1812 1813 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1814 panic("should not get called") 1815 }) 1816 defer restore() 1817 1818 // exercise KernelUpdatePolicy here 1819 err := gadget.Update(oldData, newData, rollbackDir, gadget.KernelUpdatePolicy, muo) 1820 c.Assert(err, ErrorMatches, `gadget does not consume any of the kernel assets needing synced update "ref"`) 1821 1822 // ensure update for kernel content didn't happen 1823 c.Assert(muo.beforeWriteCalled, Equals, 0) 1824 }