github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/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 { 1105 policySeen[to.Name]++ 1106 return false 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 { 1121 policySeen[to.Name]++ 1122 return to.Name == "second" 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 updater, err = gadget.UpdaterForStructure(psFs, "", rollbackDir, nil) 1414 c.Assert(err, ErrorMatches, "internal error: gadget content directory cannot be unset") 1415 c.Assert(updater, IsNil) 1416 } 1417 1418 func (u *updateTestSuite) TestUpdaterMultiVolumesDoesNotError(c *C) { 1419 logbuf, restore := logger.MockLogger() 1420 defer restore() 1421 1422 multiVolume := gadget.GadgetData{ 1423 Info: &gadget.Info{ 1424 Volumes: map[string]*gadget.Volume{ 1425 "1": {}, 1426 "2": {}, 1427 }, 1428 }, 1429 } 1430 singleVolume := gadget.GadgetData{ 1431 Info: &gadget.Info{ 1432 Volumes: map[string]*gadget.Volume{ 1433 "1": {}, 1434 }, 1435 }, 1436 } 1437 1438 // a new multi volume gadget update gives no error 1439 err := gadget.Update(singleVolume, multiVolume, "some-rollback-dir", nil, nil) 1440 c.Assert(err, IsNil) 1441 // but it warns that nothing happens either 1442 c.Assert(logbuf.String(), testutil.Contains, "WARNING: gadget assests cannot be updated yet when multiple volumes are used") 1443 1444 // same for old 1445 err = gadget.Update(multiVolume, singleVolume, "some-rollback-dir", nil, nil) 1446 c.Assert(err, IsNil) 1447 c.Assert(strings.Count(logbuf.String(), "WARNING: gadget assests cannot be updated yet when multiple volumes are used"), Equals, 2) 1448 } 1449 1450 func (u *updateTestSuite) TestUpdateApplyNoChangedContentInAll(c *C) { 1451 oldData, newData, rollbackDir := updateDataSet(c) 1452 // first structure is updated 1453 oldData.Info.Volumes["foo"].Structure[0].Update.Edition = 0 1454 newData.Info.Volumes["foo"].Structure[0].Update.Edition = 1 1455 // so is the second structure 1456 oldData.Info.Volumes["foo"].Structure[1].Update.Edition = 1 1457 newData.Info.Volumes["foo"].Structure[1].Update.Edition = 2 1458 1459 muo := &mockUpdateProcessObserver{} 1460 expectedStructs := []string{"first", "second"} 1461 updateCalls := 0 1462 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1463 mu := &mockUpdater{ 1464 updateCb: func() error { 1465 c.Assert(expectedStructs, testutil.Contains, ps.Name) 1466 updateCalls++ 1467 return gadget.ErrNoUpdate 1468 }, 1469 rollbackCb: func() error { 1470 c.Fatalf("unexpected rollback call for structure: %v", ps) 1471 return errors.New("not called") 1472 }, 1473 } 1474 return mu, nil 1475 }) 1476 defer restore() 1477 1478 // go go go 1479 err := gadget.Update(oldData, newData, rollbackDir, nil, muo) 1480 c.Assert(err, Equals, gadget.ErrNoUpdate) 1481 // update called for 2 structures 1482 c.Assert(updateCalls, Equals, 2) 1483 // nothing was updated, but the backup pass still executed 1484 c.Assert(muo.beforeWriteCalled, Equals, 1) 1485 c.Assert(muo.canceledCalled, Equals, 0) 1486 } 1487 1488 func (u *updateTestSuite) TestUpdateApplyNoChangedContentInSome(c *C) { 1489 oldData, newData, rollbackDir := updateDataSet(c) 1490 // first structure is updated 1491 oldData.Info.Volumes["foo"].Structure[0].Update.Edition = 0 1492 newData.Info.Volumes["foo"].Structure[0].Update.Edition = 1 1493 // so is the second structure 1494 oldData.Info.Volumes["foo"].Structure[1].Update.Edition = 1 1495 newData.Info.Volumes["foo"].Structure[1].Update.Edition = 2 1496 1497 muo := &mockUpdateProcessObserver{} 1498 expectedStructs := []string{"first", "second"} 1499 updateCalls := 0 1500 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1501 mu := &mockUpdater{ 1502 updateCb: func() error { 1503 c.Assert(expectedStructs, testutil.Contains, ps.Name) 1504 updateCalls++ 1505 if ps.Name == "first" { 1506 return gadget.ErrNoUpdate 1507 } 1508 return nil 1509 }, 1510 rollbackCb: func() error { 1511 c.Fatalf("unexpected rollback call for structure: %v", ps) 1512 return errors.New("not called") 1513 }, 1514 } 1515 return mu, nil 1516 }) 1517 defer restore() 1518 1519 // go go go 1520 err := gadget.Update(oldData, newData, rollbackDir, nil, muo) 1521 c.Assert(err, IsNil) 1522 // update called for 2 structures 1523 c.Assert(updateCalls, Equals, 2) 1524 // at least one structure had an update 1525 c.Assert(muo.beforeWriteCalled, Equals, 1) 1526 c.Assert(muo.canceledCalled, Equals, 0) 1527 } 1528 1529 func (u *updateTestSuite) TestUpdateApplyObserverBeforeWriteErrs(c *C) { 1530 oldData, newData, rollbackDir := updateDataSet(c) 1531 newData.Info.Volumes["foo"].Structure[0].Update.Edition = 1 1532 1533 restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1534 updater := &mockUpdater{ 1535 updateCb: func() error { 1536 c.Fatalf("unexpected call") 1537 return fmt.Errorf("unexpected call") 1538 }, 1539 } 1540 return updater, nil 1541 }) 1542 defer restore() 1543 1544 // go go go 1545 muo := &mockUpdateProcessObserver{ 1546 beforeWriteErr: errors.New("before write fail"), 1547 } 1548 err := gadget.Update(oldData, newData, rollbackDir, nil, muo) 1549 c.Assert(err, ErrorMatches, `cannot observe prepared update: before write fail`) 1550 // update was canceled before backup pass completed 1551 c.Check(muo.canceledCalled, Equals, 0) 1552 c.Check(muo.beforeWriteCalled, Equals, 1) 1553 } 1554 1555 func (u *updateTestSuite) TestUpdateApplyObserverCanceledErrs(c *C) { 1556 logbuf, restore := logger.MockLogger() 1557 defer restore() 1558 1559 oldData, newData, rollbackDir := updateDataSet(c) 1560 newData.Info.Volumes["foo"].Structure[0].Update.Edition = 1 1561 1562 backupErr := errors.New("backup fails") 1563 updateErr := errors.New("update fails") 1564 restore = gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { 1565 updater := &mockUpdater{ 1566 backupCb: func() error { return backupErr }, 1567 updateCb: func() error { return updateErr }, 1568 } 1569 return updater, nil 1570 }) 1571 defer restore() 1572 1573 // go go go 1574 muo := &mockUpdateProcessObserver{ 1575 canceledErr: errors.New("canceled fail"), 1576 } 1577 err := gadget.Update(oldData, newData, rollbackDir, nil, muo) 1578 c.Assert(err, ErrorMatches, `cannot backup volume structure #0 .*: backup fails`) 1579 // canceled called after backup pass 1580 c.Check(muo.canceledCalled, Equals, 1) 1581 c.Check(muo.beforeWriteCalled, Equals, 0) 1582 1583 c.Check(logbuf.String(), testutil.Contains, `cannot observe canceled prepare update: canceled fail`) 1584 1585 // backup works, update fails, triggers another canceled call 1586 backupErr = nil 1587 err = gadget.Update(oldData, newData, rollbackDir, nil, muo) 1588 c.Assert(err, ErrorMatches, `cannot update volume structure #0 .*: update fails`) 1589 // canceled called after backup pass 1590 c.Check(muo.canceledCalled, Equals, 2) 1591 c.Check(muo.beforeWriteCalled, Equals, 1) 1592 1593 c.Check(logbuf.String(), testutil.Contains, `cannot observe canceled update: canceled fail`) 1594 }