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  }