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