github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/devicestate/systems_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2021 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 devicestate_test
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"sort"
    29  	"strings"
    30  
    31  	. "gopkg.in/check.v1"
    32  
    33  	"github.com/snapcore/snapd/asserts"
    34  	"github.com/snapcore/snapd/boot"
    35  	"github.com/snapcore/snapd/bootloader"
    36  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    37  	"github.com/snapcore/snapd/logger"
    38  	"github.com/snapcore/snapd/osutil"
    39  	"github.com/snapcore/snapd/overlord/devicestate"
    40  	"github.com/snapcore/snapd/seed/seedtest"
    41  	"github.com/snapcore/snapd/snap"
    42  	"github.com/snapcore/snapd/snap/snaptest"
    43  	"github.com/snapcore/snapd/testutil"
    44  )
    45  
    46  type createSystemSuite struct {
    47  	deviceMgrBaseSuite
    48  
    49  	ss *seedtest.SeedSnaps
    50  
    51  	logbuf *bytes.Buffer
    52  }
    53  
    54  var _ = Suite(&createSystemSuite{})
    55  
    56  var (
    57  	genericSnapYaml = "name: %s\nversion: 1.0\n%s"
    58  	snapYamls       = map[string]string{
    59  		"pc-kernel":        "name: pc-kernel\nversion: 1.0\ntype: kernel",
    60  		"pc":               "name: pc\nversion: 1.0\ntype: gadget\nbase: core20",
    61  		"core20":           "name: core20\nversion: 20.1\ntype: base",
    62  		"core18":           "name: core18\nversion: 18.1\ntype: base",
    63  		"snapd":            "name: snapd\nversion: 2.2.2\ntype: snapd",
    64  		"other-required":   fmt.Sprintf(genericSnapYaml, "other-required", "base: core20"),
    65  		"other-present":    fmt.Sprintf(genericSnapYaml, "other-present", "base: core20"),
    66  		"other-core18":     fmt.Sprintf(genericSnapYaml, "other-present", "base: core18"),
    67  		"other-unasserted": fmt.Sprintf(genericSnapYaml, "other-unasserted", "base: core20"),
    68  	}
    69  	snapFiles = map[string][][]string{
    70  		"pc": {
    71  			{"meta/gadget.yaml", gadgetYaml},
    72  			{"cmdline.extra", "args from gadget"},
    73  		},
    74  	}
    75  )
    76  
    77  func (s *createSystemSuite) SetUpTest(c *C) {
    78  	s.deviceMgrBaseSuite.SetUpTest(c)
    79  
    80  	s.ss = &seedtest.SeedSnaps{
    81  		StoreSigning: s.storeSigning,
    82  		Brands:       s.brands,
    83  	}
    84  	s.AddCleanup(func() { bootloader.Force(nil) })
    85  
    86  	buf, restore := logger.MockLogger()
    87  	s.AddCleanup(restore)
    88  	s.logbuf = buf
    89  }
    90  
    91  func (s *createSystemSuite) makeSnap(c *C, name string, rev snap.Revision) *snap.Info {
    92  	snapID := s.ss.AssertedSnapID(name)
    93  	if rev.Unset() || rev.Local() {
    94  		snapID = ""
    95  	}
    96  	si := &snap.SideInfo{
    97  		RealName: name,
    98  		SnapID:   snapID,
    99  		Revision: rev,
   100  	}
   101  	where, info := snaptest.MakeTestSnapInfoWithFiles(c, snapYamls[name], snapFiles[name], si)
   102  	c.Assert(os.MkdirAll(filepath.Dir(info.MountFile()), 0755), IsNil)
   103  	c.Assert(os.Rename(where, info.MountFile()), IsNil)
   104  	if !rev.Unset() && !rev.Local() {
   105  		// snap is non local, generate relevant assertions
   106  		s.setupSnapDecl(c, info, "my-brand")
   107  		s.setupSnapRevision(c, info, "my-brand", rev)
   108  	}
   109  	return info
   110  }
   111  
   112  func (s *createSystemSuite) makeEssentialSnapInfos(c *C) map[string]*snap.Info {
   113  	infos := map[string]*snap.Info{}
   114  	infos["pc-kernel"] = s.makeSnap(c, "pc-kernel", snap.R(1))
   115  	infos["pc"] = s.makeSnap(c, "pc", snap.R(2))
   116  	infos["core20"] = s.makeSnap(c, "core20", snap.R(3))
   117  	infos["snapd"] = s.makeSnap(c, "snapd", snap.R(4))
   118  	return infos
   119  }
   120  
   121  func validateCore20Seed(c *C, name string, trusted []asserts.Assertion, runModeSnapNames ...string) {
   122  	const usesSnapd = true
   123  	sd := seedtest.ValidateSeed(c, boot.InitramfsUbuntuSeedDir, name, usesSnapd, trusted)
   124  
   125  	snaps, err := sd.ModeSnaps(boot.ModeRun)
   126  	c.Assert(err, IsNil)
   127  	seenSnaps := []string{}
   128  	for _, sn := range snaps {
   129  		seenSnaps = append(seenSnaps, sn.SnapName())
   130  	}
   131  	sort.Strings(seenSnaps)
   132  	sort.Strings(runModeSnapNames)
   133  	if len(runModeSnapNames) != 0 {
   134  		c.Check(seenSnaps, DeepEquals, runModeSnapNames)
   135  	} else {
   136  		c.Check(seenSnaps, HasLen, 0)
   137  	}
   138  }
   139  
   140  func (s *createSystemSuite) TestCreateSystemFromAssertedSnaps(c *C) {
   141  	bl := bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets()
   142  	// make it simple for now, no assets
   143  	bl.TrustedAssetsList = nil
   144  	bl.StaticCommandLine = "mock static"
   145  	bl.CandidateStaticCommandLine = "unused"
   146  	bootloader.Force(bl)
   147  
   148  	s.state.Lock()
   149  	defer s.state.Unlock()
   150  	s.setupBrands(c)
   151  	infos := s.makeEssentialSnapInfos(c)
   152  	infos["other-present"] = s.makeSnap(c, "other-present", snap.R(5))
   153  	infos["other-required"] = s.makeSnap(c, "other-required", snap.R(6))
   154  	infos["other-core18"] = s.makeSnap(c, "other-core18", snap.R(7))
   155  	infos["core18"] = s.makeSnap(c, "core18", snap.R(8))
   156  
   157  	model := s.makeModelAssertionInState(c, "my-brand", "pc", map[string]interface{}{
   158  		"architecture": "amd64",
   159  		"grade":        "dangerous",
   160  		"base":         "core20",
   161  		"snaps": []interface{}{
   162  			map[string]interface{}{
   163  				"name":            "pc-kernel",
   164  				"id":              s.ss.AssertedSnapID("pc-kernel"),
   165  				"type":            "kernel",
   166  				"default-channel": "20",
   167  			},
   168  			map[string]interface{}{
   169  				"name":            "pc",
   170  				"id":              s.ss.AssertedSnapID("pc"),
   171  				"type":            "gadget",
   172  				"default-channel": "20",
   173  			},
   174  			map[string]interface{}{
   175  				"name": "snapd",
   176  				"id":   s.ss.AssertedSnapID("snapd"),
   177  				"type": "snapd",
   178  			},
   179  			// optional but not present
   180  			map[string]interface{}{
   181  				"name":     "other-not-present",
   182  				"id":       s.ss.AssertedSnapID("other-not-present"),
   183  				"presence": "optional",
   184  			},
   185  			// optional and present
   186  			map[string]interface{}{
   187  				"name":     "other-present",
   188  				"id":       s.ss.AssertedSnapID("other-present"),
   189  				"presence": "optional",
   190  			},
   191  			// required
   192  			map[string]interface{}{
   193  				"name":     "other-required",
   194  				"id":       s.ss.AssertedSnapID("other-required"),
   195  				"presence": "required",
   196  			},
   197  			// different base
   198  			map[string]interface{}{
   199  				"name": "other-core18",
   200  				"id":   s.ss.AssertedSnapID("other-core18"),
   201  			},
   202  			// and the actual base for that snap
   203  			map[string]interface{}{
   204  				"name": "core18",
   205  				"id":   s.ss.AssertedSnapID("core18"),
   206  				"type": "base",
   207  			},
   208  		},
   209  	})
   210  	expectedDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234")
   211  
   212  	infoGetter := func(name string) (*snap.Info, bool, error) {
   213  		c.Logf("called for: %q", name)
   214  		info, present := infos[name]
   215  		return info, present, nil
   216  	}
   217  	var newFiles []string
   218  	snapWriteObserver := func(dir, where string) error {
   219  		c.Check(dir, Equals, expectedDir)
   220  		c.Check(where, testutil.FileAbsent)
   221  		newFiles = append(newFiles, where)
   222  		return nil
   223  	}
   224  
   225  	dir, err := devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db, infoGetter, snapWriteObserver)
   226  	c.Assert(err, IsNil)
   227  	c.Check(newFiles, DeepEquals, []string{
   228  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/snapd_4.snap"),
   229  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc-kernel_1.snap"),
   230  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_3.snap"),
   231  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc_2.snap"),
   232  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/other-present_5.snap"),
   233  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/other-required_6.snap"),
   234  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/other-core18_7.snap"),
   235  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core18_8.snap"),
   236  	})
   237  	c.Check(dir, Equals, expectedDir)
   238  	// naive check for files being present
   239  	for _, info := range infos {
   240  		c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps", filepath.Base(info.MountFile())),
   241  			testutil.FileEquals,
   242  			testutil.FileContentRef(info.MountFile()))
   243  	}
   244  	// recovery system bootenv was set
   245  	c.Check(bl.RecoverySystemDir, Equals, "/systems/1234")
   246  	c.Check(bl.RecoverySystemBootVars, DeepEquals, map[string]string{
   247  		"snapd_full_cmdline_args":  "",
   248  		"snapd_extra_cmdline_args": "args from gadget",
   249  		"snapd_recovery_kernel":    "/snaps/pc-kernel_1.snap",
   250  	})
   251  	// load the seed
   252  	validateCore20Seed(c, "1234", s.storeSigning.Trusted,
   253  		"other-core18", "core18", "other-present", "other-required")
   254  }
   255  
   256  func (s *createSystemSuite) TestCreateSystemFromUnassertedSnaps(c *C) {
   257  	bl := bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets()
   258  	// make it simple for now, no assets
   259  	bl.TrustedAssetsList = nil
   260  	bl.StaticCommandLine = "mock static"
   261  	bl.CandidateStaticCommandLine = "unused"
   262  	bootloader.Force(bl)
   263  
   264  	s.state.Lock()
   265  	defer s.state.Unlock()
   266  	s.setupBrands(c)
   267  	infos := s.makeEssentialSnapInfos(c)
   268  	// unasserted with local revision
   269  	infos["other-unasserted"] = s.makeSnap(c, "other-unasserted", snap.R(-1))
   270  
   271  	model := s.makeModelAssertionInState(c, "my-brand", "pc", map[string]interface{}{
   272  		"architecture": "amd64",
   273  		"grade":        "dangerous",
   274  		"base":         "core20",
   275  		"snaps": []interface{}{
   276  			map[string]interface{}{
   277  				"name":            "pc-kernel",
   278  				"id":              s.ss.AssertedSnapID("pc-kernel"),
   279  				"type":            "kernel",
   280  				"default-channel": "20",
   281  			},
   282  			map[string]interface{}{
   283  				"name":            "pc",
   284  				"id":              s.ss.AssertedSnapID("pc"),
   285  				"type":            "gadget",
   286  				"default-channel": "20",
   287  			},
   288  			map[string]interface{}{
   289  				"name": "snapd",
   290  				"id":   s.ss.AssertedSnapID("snapd"),
   291  				"type": "snapd",
   292  			},
   293  			// required
   294  			map[string]interface{}{
   295  				"name":     "other-unasserted",
   296  				"presence": "required",
   297  			},
   298  		},
   299  	})
   300  	expectedDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234")
   301  
   302  	infoGetter := func(name string) (*snap.Info, bool, error) {
   303  		c.Logf("called for: %q", name)
   304  		info, present := infos[name]
   305  		return info, present, nil
   306  	}
   307  	var newFiles []string
   308  	snapWriteObserver := func(dir, where string) error {
   309  		c.Check(dir, Equals, expectedDir)
   310  		c.Check(where, testutil.FileAbsent)
   311  		newFiles = append(newFiles, where)
   312  		return nil
   313  	}
   314  
   315  	dir, err := devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db, infoGetter, snapWriteObserver)
   316  	c.Assert(err, IsNil)
   317  	c.Check(newFiles, DeepEquals, []string{
   318  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/snapd_4.snap"),
   319  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc-kernel_1.snap"),
   320  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_3.snap"),
   321  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc_2.snap"),
   322  		// this snap unasserted and lands under the system
   323  		filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234/snaps/other-unasserted_1.0.snap"),
   324  	})
   325  	c.Check(dir, Equals, filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234"))
   326  	// naive check for files being present
   327  	for _, info := range infos {
   328  		if info.Revision.Store() {
   329  			c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps", filepath.Base(info.MountFile())),
   330  				testutil.FileEquals,
   331  				testutil.FileContentRef(info.MountFile()))
   332  		} else {
   333  			fileName := fmt.Sprintf("%s_%s.snap", info.SnapName(), info.Version)
   334  			c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234/snaps", fileName),
   335  				testutil.FileEquals,
   336  				testutil.FileContentRef(info.MountFile()))
   337  		}
   338  	}
   339  	// load the seed
   340  	validateCore20Seed(c, "1234", s.storeSigning.Trusted, "other-unasserted")
   341  	// we have unasserted snaps, so a warning should have been logged
   342  	c.Check(s.logbuf.String(), testutil.Contains, `system "1234" contains unasserted snaps "other-unasserted"`)
   343  }
   344  
   345  func (s *createSystemSuite) TestCreateSystemWithSomeSnapsAlreadyExisting(c *C) {
   346  	bl := bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets()
   347  	bootloader.Force(bl)
   348  
   349  	s.state.Lock()
   350  	defer s.state.Unlock()
   351  	s.setupBrands(c)
   352  	infos := s.makeEssentialSnapInfos(c)
   353  	model := s.makeModelAssertionInState(c, "my-brand", "pc", map[string]interface{}{
   354  		"architecture": "amd64",
   355  		"grade":        "dangerous",
   356  		"base":         "core20",
   357  		"snaps": []interface{}{
   358  			map[string]interface{}{
   359  				"name":            "pc-kernel",
   360  				"id":              s.ss.AssertedSnapID("pc-kernel"),
   361  				"type":            "kernel",
   362  				"default-channel": "20",
   363  			},
   364  			map[string]interface{}{
   365  				"name":            "pc",
   366  				"id":              s.ss.AssertedSnapID("pc"),
   367  				"type":            "gadget",
   368  				"default-channel": "20",
   369  			},
   370  			map[string]interface{}{
   371  				"name": "snapd",
   372  				"id":   s.ss.AssertedSnapID("snapd"),
   373  				"type": "snapd",
   374  			},
   375  		},
   376  	})
   377  	expectedDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234")
   378  
   379  	infoGetter := func(name string) (*snap.Info, bool, error) {
   380  		c.Logf("called for: %q", name)
   381  		info, present := infos[name]
   382  		return info, present, nil
   383  	}
   384  	var newFiles []string
   385  	snapWriteObserver := func(dir, where string) error {
   386  		c.Check(dir, Equals, expectedDir)
   387  		// we are not called for the snap which already exists
   388  		c.Check(where, testutil.FileAbsent)
   389  		newFiles = append(newFiles, where)
   390  		return nil
   391  	}
   392  
   393  	assertedSnapsDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps")
   394  	c.Assert(os.MkdirAll(assertedSnapsDir, 0755), IsNil)
   395  	// procure the file in place
   396  	err := osutil.CopyFile(infos["core20"].MountFile(), filepath.Join(assertedSnapsDir, "core20_3.snap"), 0)
   397  	c.Assert(err, IsNil)
   398  
   399  	// when a given snap in asserted snaps directory already exists, it is
   400  	// not copied over
   401  	dir, err := devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db, infoGetter, snapWriteObserver)
   402  	c.Assert(err, IsNil)
   403  	c.Check(newFiles, DeepEquals, []string{
   404  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/snapd_4.snap"),
   405  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc-kernel_1.snap"),
   406  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc_2.snap"),
   407  	})
   408  	c.Check(dir, Equals, filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234"))
   409  	// naive check for files being present
   410  	for _, info := range infos {
   411  		c.Check(filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps", filepath.Base(info.MountFile())),
   412  			testutil.FileEquals,
   413  			testutil.FileContentRef(info.MountFile()))
   414  	}
   415  	// recovery system bootenv was set
   416  	c.Check(bl.RecoverySystemDir, Equals, "/systems/1234")
   417  	c.Check(bl.RecoverySystemBootVars, DeepEquals, map[string]string{
   418  		"snapd_full_cmdline_args":  "",
   419  		"snapd_extra_cmdline_args": "args from gadget",
   420  		"snapd_recovery_kernel":    "/snaps/pc-kernel_1.snap",
   421  	})
   422  	// load the seed
   423  	validateCore20Seed(c, "1234", s.storeSigning.Trusted)
   424  
   425  	// add an unasserted snap
   426  	infos["other-unasserted"] = s.makeSnap(c, "other-unasserted", snap.R(-1))
   427  	modelWithUnasserted := s.makeModelAssertionInState(c, "my-brand", "pc-with-unasserted", map[string]interface{}{
   428  		"architecture": "amd64",
   429  		"grade":        "dangerous",
   430  		"base":         "core20",
   431  		"snaps": []interface{}{
   432  			map[string]interface{}{
   433  				"name":            "pc-kernel",
   434  				"id":              s.ss.AssertedSnapID("pc-kernel"),
   435  				"type":            "kernel",
   436  				"default-channel": "20",
   437  			},
   438  			map[string]interface{}{
   439  				"name":            "pc",
   440  				"id":              s.ss.AssertedSnapID("pc"),
   441  				"type":            "gadget",
   442  				"default-channel": "20",
   443  			},
   444  			map[string]interface{}{
   445  				"name": "snapd",
   446  				"id":   s.ss.AssertedSnapID("snapd"),
   447  				"type": "snapd",
   448  			},
   449  			// required
   450  			map[string]interface{}{
   451  				"name":     "other-unasserted",
   452  				"presence": "required",
   453  			},
   454  		},
   455  	})
   456  
   457  	unassertedSnapsDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234unasserted/snaps")
   458  	c.Assert(os.MkdirAll(unassertedSnapsDir, 0755), IsNil)
   459  	err = osutil.CopyFile(infos["other-unasserted"].MountFile(),
   460  		filepath.Join(unassertedSnapsDir, "other-unasserted_1.0.snap"), 0)
   461  	c.Assert(err, IsNil)
   462  
   463  	newFiles = nil
   464  	// the unasserted snap goes into the snaps directory under the system
   465  	// directory, which triggers the error in creating the directory by
   466  	// seed writer
   467  	dir, err = devicestate.CreateSystemForModelFromValidatedSnaps(modelWithUnasserted, "1234unasserted", s.db,
   468  		infoGetter, snapWriteObserver)
   469  
   470  	c.Assert(err, ErrorMatches, `system "1234unasserted" already exists`)
   471  	// we failed early, no files were written yet
   472  	c.Check(dir, Equals, "")
   473  	c.Check(newFiles, IsNil)
   474  }
   475  
   476  func (s *createSystemSuite) TestCreateSystemInfoAndAssertsChecks(c *C) {
   477  	bl := bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets()
   478  	bootloader.Force(bl)
   479  	infos := map[string]*snap.Info{}
   480  
   481  	s.state.Lock()
   482  	defer s.state.Unlock()
   483  	s.setupBrands(c)
   484  	// missing info for the pc snap
   485  	infos["pc-kernel"] = s.makeSnap(c, "pc-kernel", snap.R(1))
   486  	infos["core20"] = s.makeSnap(c, "core20", snap.R(3))
   487  	infos["snapd"] = s.makeSnap(c, "snapd", snap.R(4))
   488  	model := s.makeModelAssertionInState(c, "my-brand", "pc", map[string]interface{}{
   489  		"architecture": "amd64",
   490  		"grade":        "dangerous",
   491  		"base":         "core20",
   492  		"snaps": []interface{}{
   493  			map[string]interface{}{
   494  				"name":            "pc-kernel",
   495  				"id":              s.ss.AssertedSnapID("pc-kernel"),
   496  				"type":            "kernel",
   497  				"default-channel": "20",
   498  			},
   499  			// pc snap is the gadget, but we have no info for it
   500  			map[string]interface{}{
   501  				"name":            "pc",
   502  				"id":              s.ss.AssertedSnapID("pc"),
   503  				"type":            "gadget",
   504  				"default-channel": "20",
   505  			},
   506  			map[string]interface{}{
   507  				"name": "snapd",
   508  				"id":   s.ss.AssertedSnapID("snapd"),
   509  				"type": "snapd",
   510  			},
   511  			map[string]interface{}{
   512  				"name":     "other-required",
   513  				"id":       s.ss.AssertedSnapID("other-required"),
   514  				"presence": "required",
   515  			},
   516  		},
   517  	})
   518  
   519  	infoGetter := func(name string) (*snap.Info, bool, error) {
   520  		c.Logf("called for: %q", name)
   521  		info, present := infos[name]
   522  		return info, present, nil
   523  	}
   524  	var observerCalls int
   525  	snapWriteObserver := func(dir, where string) error {
   526  		observerCalls++
   527  		return fmt.Errorf("unexpected call")
   528  	}
   529  
   530  	systemDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234")
   531  
   532  	// when a given snap in asserted snaps directory already exists, it is
   533  	// not copied over
   534  	dir, err := devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db,
   535  		infoGetter, snapWriteObserver)
   536  	c.Assert(err, ErrorMatches, `internal error: essential snap "pc" not present`)
   537  	c.Check(dir, Equals, "")
   538  	c.Check(observerCalls, Equals, 0)
   539  
   540  	// the directory shouldn't be there, as we haven't written anything yet
   541  	c.Check(osutil.IsDirectory(systemDir), Equals, false)
   542  
   543  	// create the info now
   544  	infos["pc"] = s.makeSnap(c, "pc", snap.R(2))
   545  
   546  	// and try with with a non essential snap
   547  	dir, err = devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db,
   548  		infoGetter, snapWriteObserver)
   549  	c.Assert(err, ErrorMatches, `internal error: non-essential but required snap "other-required" not present`)
   550  	c.Check(dir, Equals, "")
   551  	c.Check(observerCalls, Equals, 0)
   552  	// the directory shouldn't be there, as we haven't written anything yet
   553  	c.Check(osutil.IsDirectory(systemDir), Equals, false)
   554  
   555  	// create the info now
   556  	infos["other-required"] = s.makeSnap(c, "other-required", snap.R(5))
   557  
   558  	// but change the file contents of 'pc' snap so that deriving side info fails
   559  	c.Assert(ioutil.WriteFile(infos["pc"].MountFile(), []byte("canary"), 0644), IsNil)
   560  	dir, err = devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db,
   561  		infoGetter, snapWriteObserver)
   562  	c.Assert(err, ErrorMatches, `internal error: no assertions for asserted snap with ID: pcididididididididididididididid`)
   563  	// we're past the start, so the system directory is there
   564  	c.Check(dir, Equals, systemDir)
   565  	c.Check(osutil.IsDirectory(systemDir), Equals, true)
   566  	// but no files were copied
   567  	c.Check(observerCalls, Equals, 0)
   568  }
   569  
   570  func (s *createSystemSuite) TestCreateSystemGetInfoErr(c *C) {
   571  	bl := bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets()
   572  	bootloader.Force(bl)
   573  
   574  	s.state.Lock()
   575  	defer s.state.Unlock()
   576  	s.setupBrands(c)
   577  	// missing info for the pc snap
   578  	infos := s.makeEssentialSnapInfos(c)
   579  	infos["other-required"] = s.makeSnap(c, "other-required", snap.R(5))
   580  	model := s.makeModelAssertionInState(c, "my-brand", "pc", map[string]interface{}{
   581  		"architecture": "amd64",
   582  		"grade":        "dangerous",
   583  		"base":         "core20",
   584  		"snaps": []interface{}{
   585  			map[string]interface{}{
   586  				"name":            "pc-kernel",
   587  				"id":              s.ss.AssertedSnapID("pc-kernel"),
   588  				"type":            "kernel",
   589  				"default-channel": "20",
   590  			},
   591  			// pc snap is the gadget, but we have no info for it
   592  			map[string]interface{}{
   593  				"name":            "pc",
   594  				"id":              s.ss.AssertedSnapID("pc"),
   595  				"type":            "gadget",
   596  				"default-channel": "20",
   597  			},
   598  			map[string]interface{}{
   599  				"name": "snapd",
   600  				"id":   s.ss.AssertedSnapID("snapd"),
   601  				"type": "snapd",
   602  			},
   603  			map[string]interface{}{
   604  				"name":     "other-required",
   605  				"id":       s.ss.AssertedSnapID("other-required"),
   606  				"presence": "required",
   607  			},
   608  		},
   609  	})
   610  
   611  	failOn := map[string]bool{}
   612  
   613  	infoGetter := func(name string) (*snap.Info, bool, error) {
   614  		c.Logf("called for: %q", name)
   615  		if failOn[name] {
   616  			return nil, false, fmt.Errorf("mock failure for snap %q", name)
   617  		}
   618  		info, present := infos[name]
   619  		return info, present, nil
   620  	}
   621  	var observerCalls int
   622  	snapWriteObserver := func(dir, where string) error {
   623  		observerCalls++
   624  		return fmt.Errorf("unexpected call")
   625  	}
   626  
   627  	systemDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234")
   628  
   629  	// when a given snap in asserted snaps directory already exists, it is
   630  	// not copied over
   631  
   632  	failOn["pc"] = true
   633  	dir, err := devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db,
   634  		infoGetter, snapWriteObserver)
   635  	c.Assert(err, ErrorMatches, `cannot obtain essential snap information: mock failure for snap "pc"`)
   636  	c.Check(dir, Equals, "")
   637  	c.Check(observerCalls, Equals, 0)
   638  	c.Check(osutil.IsDirectory(systemDir), Equals, false)
   639  
   640  	failOn["pc"] = false
   641  	failOn["other-required"] = true
   642  	dir, err = devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db,
   643  		infoGetter, snapWriteObserver)
   644  	c.Assert(err, ErrorMatches, `cannot obtain non-essential but required snap information: mock failure for snap "other-required"`)
   645  	c.Check(dir, Equals, "")
   646  	c.Check(observerCalls, Equals, 0)
   647  	c.Check(osutil.IsDirectory(systemDir), Equals, false)
   648  }
   649  
   650  func (s *createSystemSuite) TestCreateSystemNonUC20(c *C) {
   651  	bl := bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets()
   652  	bootloader.Force(bl)
   653  
   654  	s.state.Lock()
   655  	defer s.state.Unlock()
   656  	s.setupBrands(c)
   657  	model := s.makeModelAssertionInState(c, "my-brand", "pc", map[string]interface{}{
   658  		"architecture": "amd64",
   659  		"base":         "core18",
   660  		"kernel":       "pc-kernel",
   661  		"gadget":       "pc",
   662  	})
   663  
   664  	infoGetter := func(name string) (*snap.Info, bool, error) {
   665  		c.Fatalf("unexpected call")
   666  		return nil, false, fmt.Errorf("unexpected call")
   667  	}
   668  	snapWriteObserver := func(dir, where string) error {
   669  		c.Fatalf("unexpected call")
   670  		return fmt.Errorf("unexpected call")
   671  	}
   672  	dir, err := devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db,
   673  		infoGetter, snapWriteObserver)
   674  	c.Assert(err, ErrorMatches, `cannot create a system for non UC20 model`)
   675  	c.Check(dir, Equals, "")
   676  }
   677  
   678  func (s *createSystemSuite) TestCreateSystemImplicitSnaps(c *C) {
   679  	bl := bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets()
   680  	bootloader.Force(bl)
   681  
   682  	s.state.Lock()
   683  	defer s.state.Unlock()
   684  	s.setupBrands(c)
   685  	infos := s.makeEssentialSnapInfos(c)
   686  
   687  	// snapd snap is implicitly required
   688  	model := s.makeModelAssertionInState(c, "my-brand", "pc", map[string]interface{}{
   689  		"architecture": "amd64",
   690  		"grade":        "dangerous",
   691  		// base does not need to be listed among snaps
   692  		"base": "core20",
   693  		"snaps": []interface{}{
   694  			map[string]interface{}{
   695  				"name":            "pc-kernel",
   696  				"id":              s.ss.AssertedSnapID("pc-kernel"),
   697  				"type":            "kernel",
   698  				"default-channel": "20",
   699  			},
   700  			map[string]interface{}{
   701  				"name":            "pc",
   702  				"id":              s.ss.AssertedSnapID("pc"),
   703  				"type":            "gadget",
   704  				"default-channel": "20",
   705  			},
   706  		},
   707  	})
   708  	expectedDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234")
   709  
   710  	infoGetter := func(name string) (*snap.Info, bool, error) {
   711  		c.Logf("called for: %q", name)
   712  		info, present := infos[name]
   713  		return info, present, nil
   714  	}
   715  	var newFiles []string
   716  	snapWriteObserver := func(dir, where string) error {
   717  		c.Check(dir, Equals, expectedDir)
   718  		newFiles = append(newFiles, where)
   719  		return nil
   720  	}
   721  
   722  	dir, err := devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db,
   723  		infoGetter, snapWriteObserver)
   724  	c.Assert(err, IsNil)
   725  	c.Check(newFiles, DeepEquals, []string{
   726  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/snapd_4.snap"),
   727  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc-kernel_1.snap"),
   728  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_3.snap"),
   729  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc_2.snap"),
   730  	})
   731  	c.Check(dir, Equals, filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234"))
   732  	// validate the seed
   733  	validateCore20Seed(c, "1234", s.ss.StoreSigning.Trusted)
   734  }
   735  
   736  func (s *createSystemSuite) TestCreateSystemObserverErr(c *C) {
   737  	bl := bootloadertest.Mock("trusted", c.MkDir()).WithRecoveryAwareTrustedAssets()
   738  	bootloader.Force(bl)
   739  
   740  	s.state.Lock()
   741  	defer s.state.Unlock()
   742  	s.setupBrands(c)
   743  	infos := s.makeEssentialSnapInfos(c)
   744  
   745  	// snapd snap is implicitly required
   746  	model := s.makeModelAssertionInState(c, "my-brand", "pc", map[string]interface{}{
   747  		"architecture": "amd64",
   748  		"grade":        "dangerous",
   749  		// base does not need to be listed among snaps
   750  		"base": "core20",
   751  		"snaps": []interface{}{
   752  			map[string]interface{}{
   753  				"name":            "pc-kernel",
   754  				"id":              s.ss.AssertedSnapID("pc-kernel"),
   755  				"type":            "kernel",
   756  				"default-channel": "20",
   757  			},
   758  			map[string]interface{}{
   759  				"name":            "pc",
   760  				"id":              s.ss.AssertedSnapID("pc"),
   761  				"type":            "gadget",
   762  				"default-channel": "20",
   763  			},
   764  		},
   765  	})
   766  
   767  	infoGetter := func(name string) (*snap.Info, bool, error) {
   768  		info, present := infos[name]
   769  		return info, present, nil
   770  	}
   771  	var newFiles []string
   772  	snapWriteObserver := func(dir, where string) error {
   773  		newFiles = append(newFiles, where)
   774  		if strings.HasSuffix(where, "/core20_3.snap") {
   775  			return fmt.Errorf("mocked observer failure")
   776  		}
   777  		return nil
   778  	}
   779  
   780  	dir, err := devicestate.CreateSystemForModelFromValidatedSnaps(model, "1234", s.db,
   781  		infoGetter, snapWriteObserver)
   782  	c.Assert(err, ErrorMatches, "mocked observer failure")
   783  	c.Check(newFiles, DeepEquals, []string{
   784  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/snapd_4.snap"),
   785  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/pc-kernel_1.snap"),
   786  		// we failed on this one
   787  		filepath.Join(boot.InitramfsUbuntuSeedDir, "snaps/core20_3.snap"),
   788  	})
   789  	c.Check(dir, Equals, filepath.Join(boot.InitramfsUbuntuSeedDir, "systems/1234"))
   790  }