gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/gadget/update_test.go (about)

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