gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/devicestate/firstboot20_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 devicestate_test
    21  
    22  import (
    23  	"os"
    24  	"path/filepath"
    25  	"strings"
    26  	"time"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/asserts"
    31  	"github.com/snapcore/snapd/boot"
    32  	"github.com/snapcore/snapd/bootloader"
    33  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    34  	"github.com/snapcore/snapd/dirs"
    35  	"github.com/snapcore/snapd/features"
    36  	"github.com/snapcore/snapd/osutil"
    37  	"github.com/snapcore/snapd/overlord"
    38  	"github.com/snapcore/snapd/overlord/assertstate"
    39  	"github.com/snapcore/snapd/overlord/configstate/config"
    40  	"github.com/snapcore/snapd/overlord/devicestate"
    41  	"github.com/snapcore/snapd/overlord/ifacestate"
    42  	"github.com/snapcore/snapd/overlord/snapstate"
    43  	"github.com/snapcore/snapd/overlord/state"
    44  	"github.com/snapcore/snapd/release"
    45  	"github.com/snapcore/snapd/seed/seedtest"
    46  	"github.com/snapcore/snapd/snap"
    47  	"github.com/snapcore/snapd/strutil"
    48  	"github.com/snapcore/snapd/systemd"
    49  	"github.com/snapcore/snapd/testutil"
    50  )
    51  
    52  type firstBoot20Suite struct {
    53  	firstBootBaseTest
    54  
    55  	extraSnapYaml map[string]string
    56  
    57  	// TestingSeed20 helps populating seeds (it provides
    58  	// MakeAssertedSnap, MakeSeed) for tests.
    59  	*seedtest.TestingSeed20
    60  }
    61  
    62  var (
    63  	allGrades = []asserts.ModelGrade{
    64  		asserts.ModelDangerous,
    65  	}
    66  )
    67  
    68  var _ = Suite(&firstBoot20Suite{})
    69  
    70  func (s *firstBoot20Suite) SetUpTest(c *C) {
    71  	s.extraSnapYaml = make(map[string]string)
    72  
    73  	s.TestingSeed20 = &seedtest.TestingSeed20{}
    74  
    75  	s.setupBaseTest(c, &s.TestingSeed20.SeedSnaps)
    76  
    77  	// don't start the overlord here so that we can mock different modeenvs
    78  	// later, which is needed by devicestart manager startup with uc20 booting
    79  
    80  	s.SeedDir = dirs.SnapSeedDir
    81  
    82  	// mock the snap mapper as snapd here
    83  	s.AddCleanup(ifacestate.MockSnapMapper(&ifacestate.CoreSnapdSystemMapper{}))
    84  
    85  	r := release.MockReleaseInfo(&release.OS{ID: "ubuntu-core", VersionID: "20"})
    86  	s.AddCleanup(r)
    87  }
    88  
    89  func (s *firstBoot20Suite) snapYaml(snp string) string {
    90  	if yml, ok := seedtest.SampleSnapYaml[snp]; ok {
    91  		return yml
    92  	}
    93  	return s.extraSnapYaml[snp]
    94  }
    95  
    96  func (s *firstBoot20Suite) setupCore20Seed(c *C, sysLabel string, modelGrade asserts.ModelGrade, extraGadgetYaml string, extraSnaps ...string) *asserts.Model {
    97  	gadgetYaml := `
    98  volumes:
    99      volume-id:
   100          bootloader: grub
   101          structure:
   102          - name: ubuntu-seed
   103            role: system-seed
   104            type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
   105            size: 1G
   106          - name: ubuntu-data
   107            role: system-data
   108            type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
   109            size: 2G
   110  `
   111  
   112  	gadgetYaml += extraGadgetYaml
   113  
   114  	makeSnap := func(yamlKey string) {
   115  		var files [][]string
   116  		if yamlKey == "pc=20" {
   117  			files = append(files, []string{"meta/gadget.yaml", gadgetYaml})
   118  		}
   119  		s.MakeAssertedSnap(c, s.snapYaml(yamlKey), files, snap.R(1), "canonical", s.StoreSigning.Database)
   120  	}
   121  
   122  	makeSnap("snapd")
   123  	makeSnap("pc-kernel=20")
   124  	makeSnap("core20")
   125  	makeSnap("pc=20")
   126  	for _, sn := range extraSnaps {
   127  		makeSnap(sn)
   128  	}
   129  
   130  	model := map[string]interface{}{
   131  		"display-name": "my model",
   132  		"architecture": "amd64",
   133  		"base":         "core20",
   134  		"grade":        string(modelGrade),
   135  		"snaps": []interface{}{
   136  			map[string]interface{}{
   137  				"name":            "pc-kernel",
   138  				"id":              s.AssertedSnapID("pc-kernel"),
   139  				"type":            "kernel",
   140  				"default-channel": "20",
   141  			},
   142  			map[string]interface{}{
   143  				"name":            "pc",
   144  				"id":              s.AssertedSnapID("pc"),
   145  				"type":            "gadget",
   146  				"default-channel": "20",
   147  			},
   148  			map[string]interface{}{
   149  				"name": "snapd",
   150  				"id":   s.AssertedSnapID("snapd"),
   151  				"type": "snapd",
   152  			},
   153  			map[string]interface{}{
   154  				"name": "core20",
   155  				"id":   s.AssertedSnapID("core20"),
   156  				"type": "base",
   157  			},
   158  		},
   159  	}
   160  
   161  	for _, sn := range extraSnaps {
   162  		name, channel := splitSnapNameWithChannel(sn)
   163  		model["snaps"] = append(model["snaps"].([]interface{}), map[string]interface{}{
   164  			"name":            name,
   165  			"type":            "app",
   166  			"id":              s.AssertedSnapID(name),
   167  			"default-channel": channel,
   168  		})
   169  	}
   170  
   171  	return s.MakeSeed(c, sysLabel, "my-brand", "my-model", model, nil)
   172  }
   173  
   174  func splitSnapNameWithChannel(sn string) (name, channel string) {
   175  	nameParts := strings.SplitN(sn, "=", 2)
   176  	name = nameParts[0]
   177  	channel = ""
   178  	if len(nameParts) == 2 {
   179  		channel = nameParts[1]
   180  	}
   181  	return name, channel
   182  }
   183  
   184  func stripSnapNamesWithChannels(snaps []string) []string {
   185  	names := []string{}
   186  	for _, sn := range snaps {
   187  		name, _ := splitSnapNameWithChannel(sn)
   188  		names = append(names, name)
   189  	}
   190  	return names
   191  }
   192  
   193  func checkSnapstateDevModeFlags(c *C, tsAll []*state.TaskSet, snapsWithDevModeFlag ...string) {
   194  	allDevModeSnaps := stripSnapNamesWithChannels(snapsWithDevModeFlag)
   195  
   196  	// XXX: mostly same code from checkOrder helper in firstboot_test.go, maybe
   197  	// combine someday?
   198  	matched := 0
   199  	var prevTask *state.Task
   200  	for i, ts := range tsAll {
   201  		task0 := ts.Tasks()[0]
   202  		waitTasks := task0.WaitTasks()
   203  		if i == 0 {
   204  			c.Check(waitTasks, HasLen, 0)
   205  		} else {
   206  			c.Check(waitTasks, testutil.Contains, prevTask)
   207  		}
   208  		prevTask = task0
   209  		if task0.Kind() != "prerequisites" {
   210  			continue
   211  		}
   212  		snapsup, err := snapstate.TaskSnapSetup(task0)
   213  		c.Assert(err, IsNil, Commentf("%#v", task0))
   214  		if strutil.ListContains(allDevModeSnaps, snapsup.InstanceName()) {
   215  			c.Assert(snapsup.DevMode, Equals, true)
   216  			matched++
   217  		} else {
   218  			// it should not have DevMode true
   219  			c.Assert(snapsup.DevMode, Equals, false)
   220  		}
   221  	}
   222  	c.Check(matched, Equals, len(snapsWithDevModeFlag))
   223  }
   224  
   225  func (s *firstBoot20Suite) earlySetup(c *C, m *boot.Modeenv, modelGrade asserts.ModelGrade, extraGadgetYaml string, extraSnaps ...string) (model *asserts.Model, bloader *bootloadertest.MockExtractedRunKernelImageBootloader) {
   226  	c.Assert(m, NotNil, Commentf("missing modeenv test data"))
   227  	err := m.WriteTo("")
   228  	c.Assert(err, IsNil)
   229  
   230  	sysLabel := m.RecoverySystem
   231  	model = s.setupCore20Seed(c, sysLabel, modelGrade, extraGadgetYaml, extraSnaps...)
   232  	// sanity check that our returned model has the expected grade
   233  	c.Assert(model.Grade(), Equals, modelGrade)
   234  
   235  	bloader = bootloadertest.Mock("mock", c.MkDir()).WithExtractedRunKernelImage()
   236  	bootloader.Force(bloader)
   237  	s.AddCleanup(func() { bootloader.Force(nil) })
   238  
   239  	// since we are in runmode, MakeBootable will already have run from install
   240  	// mode, and extracted the kernel assets for the kernel snap into the
   241  	// bootloader, so set the current kernel there
   242  	kernel, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
   243  	c.Assert(err, IsNil)
   244  	r := bloader.SetEnabledKernel(kernel)
   245  	s.AddCleanup(r)
   246  
   247  	return model, bloader
   248  }
   249  
   250  func (s *firstBoot20Suite) testPopulateFromSeedCore20Happy(c *C, m *boot.Modeenv, modelGrade asserts.ModelGrade, extraDevModeSnaps ...string) {
   251  	var sysdLog [][]string
   252  	systemctlRestorer := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
   253  		sysdLog = append(sysdLog, cmd)
   254  		return []byte("ActiveState=inactive\n"), nil
   255  	})
   256  	defer systemctlRestorer()
   257  
   258  	model, bloader := s.earlySetup(c, m, modelGrade, "", extraDevModeSnaps...)
   259  	// create overlord and pick up the modeenv
   260  	s.startOverlord(c)
   261  
   262  	opts := devicestate.PopulateStateFromSeedOptions{
   263  		Label: m.RecoverySystem,
   264  		Mode:  m.Mode,
   265  	}
   266  
   267  	// run the firstboot stuff
   268  	st := s.overlord.State()
   269  	st.Lock()
   270  	defer st.Unlock()
   271  	tsAll, err := devicestate.PopulateStateFromSeedImpl(st, &opts, s.perfTimings)
   272  	c.Assert(err, IsNil)
   273  
   274  	snaps := []string{"snapd", "pc-kernel", "core20", "pc"}
   275  	allDevModeSnaps := stripSnapNamesWithChannels(extraDevModeSnaps)
   276  	if len(extraDevModeSnaps) != 0 {
   277  		snaps = append(snaps, allDevModeSnaps...)
   278  	}
   279  	checkOrder(c, tsAll, snaps...)
   280  
   281  	// if the model is dangerous check that the devmode snaps in the model have
   282  	// the flag set in snapstate for DevMode confinement
   283  	// XXX: eventually we may need more complicated checks here and for
   284  	// non-dangerous models only specific snaps may have this flag set
   285  	if modelGrade == asserts.ModelDangerous {
   286  		checkSnapstateDevModeFlags(c, tsAll, allDevModeSnaps...)
   287  	}
   288  
   289  	// now run the change and check the result
   290  	// use the expected kind otherwise settle with start another one
   291  	chg := st.NewChange("seed", "run the populate from seed changes")
   292  	for _, ts := range tsAll {
   293  		chg.AddAll(ts)
   294  	}
   295  	c.Assert(st.Changes(), HasLen, 1)
   296  
   297  	c.Assert(chg.Err(), IsNil)
   298  
   299  	// avoid device reg
   300  	chg1 := st.NewChange("become-operational", "init device")
   301  	chg1.SetStatus(state.DoingStatus)
   302  
   303  	// run change until it wants to restart
   304  	st.Unlock()
   305  	err = s.overlord.Settle(settleTimeout)
   306  	st.Lock()
   307  	c.Assert(err, IsNil)
   308  
   309  	// at this point the system is "restarting", pretend the restart has
   310  	// happened
   311  	c.Assert(chg.Status(), Equals, state.DoingStatus)
   312  	state.MockRestarting(st, state.RestartUnset)
   313  	st.Unlock()
   314  	err = s.overlord.Settle(settleTimeout)
   315  	st.Lock()
   316  	c.Assert(err, IsNil)
   317  	c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("%s", chg.Err()))
   318  
   319  	// verify
   320  	f, err := os.Open(dirs.SnapStateFile)
   321  	c.Assert(err, IsNil)
   322  	state, err := state.ReadState(nil, f)
   323  	c.Assert(err, IsNil)
   324  
   325  	state.Lock()
   326  	defer state.Unlock()
   327  	// check snapd, core20, kernel, gadget
   328  	_, err = snapstate.CurrentInfo(state, "snapd")
   329  	c.Check(err, IsNil)
   330  	_, err = snapstate.CurrentInfo(state, "core20")
   331  	c.Check(err, IsNil)
   332  	_, err = snapstate.CurrentInfo(state, "pc-kernel")
   333  	c.Check(err, IsNil)
   334  	_, err = snapstate.CurrentInfo(state, "pc")
   335  	c.Check(err, IsNil)
   336  
   337  	// No kernel extraction happens during seeding, the kernel is already
   338  	// there either from ubuntu-image or from "install" mode.
   339  	c.Check(bloader.ExtractKernelAssetsCalls, HasLen, 0)
   340  
   341  	// ensure required flag is set on all essential snaps
   342  	var snapst snapstate.SnapState
   343  	for _, reqName := range []string{"snapd", "core20", "pc-kernel", "pc"} {
   344  		err = snapstate.Get(state, reqName, &snapst)
   345  		c.Assert(err, IsNil)
   346  		c.Assert(snapst.Required, Equals, true, Commentf("required not set for %v", reqName))
   347  
   348  		if m.Mode == "run" {
   349  			// also ensure that in run mode none of the snaps are installed as
   350  			// symlinks, they must be copied onto ubuntu-data
   351  			files, err := filepath.Glob(filepath.Join(dirs.SnapBlobDir, reqName+"_*.snap"))
   352  			c.Assert(err, IsNil)
   353  			c.Assert(files, HasLen, 1)
   354  			c.Assert(osutil.IsSymlink(files[0]), Equals, false)
   355  		}
   356  	}
   357  
   358  	// the right systemd commands were run
   359  	c.Check(sysdLog, testutil.DeepContains, []string{"start", "usr-lib-snapd.mount"})
   360  
   361  	// and ensure state is now considered seeded
   362  	var seeded bool
   363  	err = state.Get("seeded", &seeded)
   364  	c.Assert(err, IsNil)
   365  	c.Check(seeded, Equals, true)
   366  
   367  	// check we set seed-time
   368  	var seedTime time.Time
   369  	err = state.Get("seed-time", &seedTime)
   370  	c.Assert(err, IsNil)
   371  	c.Check(seedTime.IsZero(), Equals, false)
   372  
   373  	// check that we removed recovery_system from modeenv
   374  	m2, err := boot.ReadModeenv("")
   375  	c.Assert(err, IsNil)
   376  	if m.Mode == "run" {
   377  		// recovery system is cleared in run mode
   378  		c.Assert(m2.RecoverySystem, Equals, "")
   379  	} else {
   380  		// but kept intact in other modes
   381  		c.Assert(m2.RecoverySystem, Equals, m.RecoverySystem)
   382  	}
   383  	c.Assert(m2.Base, Equals, m.Base)
   384  	c.Assert(m2.Mode, Equals, m.Mode)
   385  	// Note that we don't check CurrentKernels in the modeenv, even though in a
   386  	// real first boot that would also be set here, because setting that is done
   387  	// in the snapstate manager, not the devicestate manager
   388  
   389  	// check that the default device ctx has a Modeenv
   390  	dev, err := devicestate.DeviceCtx(s.overlord.State(), nil, nil)
   391  	c.Assert(err, IsNil)
   392  	c.Assert(dev.HasModeenv(), Equals, true)
   393  
   394  	// check that we marked the boot successful with bootstate20 methods, namely
   395  	// that we called SetNext, which since it was called on the kernel we
   396  	// already booted from, we should only have checked what the current kernel
   397  	// is
   398  
   399  	if m.Mode == "run" {
   400  		// only relevant in run mode
   401  
   402  		// the 3 calls here are :
   403  		// * 1 from MarkBootSuccessful() from ensureBootOk() before we restart
   404  		// * 1 from boot.SetNextBoot() from LinkSnap() from doInstall() from InstallPath() from
   405  		//     installSeedSnap() after restart
   406  		// * 1 from boot.GetCurrentBoot() from FinishRestart after restart
   407  		_, numKernelCalls := bloader.GetRunKernelImageFunctionSnapCalls("Kernel")
   408  		c.Assert(numKernelCalls, Equals, 3)
   409  	}
   410  	actual, _ := bloader.GetRunKernelImageFunctionSnapCalls("EnableKernel")
   411  	c.Assert(actual, HasLen, 0)
   412  	actual, _ = bloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel")
   413  	c.Assert(actual, HasLen, 0)
   414  	actual, _ = bloader.GetRunKernelImageFunctionSnapCalls("EnableTryKernel")
   415  	c.Assert(actual, HasLen, 0)
   416  
   417  	var whatseeded []devicestate.SeededSystem
   418  	err = state.Get("seeded-systems", &whatseeded)
   419  	if m.Mode == "run" {
   420  		c.Assert(err, IsNil)
   421  		c.Assert(whatseeded, DeepEquals, []devicestate.SeededSystem{{
   422  			System:    m.RecoverySystem,
   423  			Model:     "my-model",
   424  			BrandID:   "my-brand",
   425  			Revision:  model.Revision(),
   426  			Timestamp: model.Timestamp(),
   427  			SeedTime:  seedTime,
   428  		}})
   429  	} else {
   430  		c.Assert(err, NotNil)
   431  	}
   432  }
   433  
   434  func (s *firstBoot20Suite) TestPopulateFromSeedCore20RunModeDangerousWithDevmode(c *C) {
   435  	m := boot.Modeenv{
   436  		Mode:           "run",
   437  		RecoverySystem: "20191018",
   438  		Base:           "core20_1.snap",
   439  	}
   440  	s.testPopulateFromSeedCore20Happy(c, &m, asserts.ModelDangerous, "test-devmode=20")
   441  }
   442  
   443  func (s *firstBoot20Suite) TestPopulateFromSeedCore20RunMode(c *C) {
   444  	m := boot.Modeenv{
   445  		Mode:           "run",
   446  		RecoverySystem: "20191018",
   447  		Base:           "core20_1.snap",
   448  	}
   449  	for _, grade := range allGrades {
   450  		s.testPopulateFromSeedCore20Happy(c, &m, grade)
   451  	}
   452  }
   453  
   454  func (s *firstBoot20Suite) TestPopulateFromSeedCore20InstallMode(c *C) {
   455  	m := boot.Modeenv{
   456  		Mode:           "install",
   457  		RecoverySystem: "20191019",
   458  		Base:           "core20_1.snap",
   459  	}
   460  	for _, grade := range allGrades {
   461  		s.testPopulateFromSeedCore20Happy(c, &m, grade)
   462  	}
   463  }
   464  
   465  func (s *firstBoot20Suite) TestPopulateFromSeedCore20RecoverMode(c *C) {
   466  	m := boot.Modeenv{
   467  		Mode:           "recover",
   468  		RecoverySystem: "20191020",
   469  		Base:           "core20_1.snap",
   470  	}
   471  	for _, grade := range allGrades {
   472  		s.testPopulateFromSeedCore20Happy(c, &m, grade)
   473  	}
   474  }
   475  
   476  func (s *firstBoot20Suite) TestLoadDeviceSeedCore20(c *C) {
   477  	m := boot.Modeenv{
   478  		Mode:           "run",
   479  		RecoverySystem: "20191018",
   480  		Base:           "core20_1.snap",
   481  	}
   482  
   483  	s.earlySetup(c, &m, "signed", "")
   484  
   485  	o, err := overlord.New(nil)
   486  	c.Assert(err, IsNil)
   487  	st := o.State()
   488  
   489  	st.Lock()
   490  	defer st.Unlock()
   491  
   492  	deviceSeed, err := devicestate.LoadDeviceSeed(st, m.RecoverySystem)
   493  	c.Assert(err, IsNil)
   494  
   495  	c.Check(deviceSeed.Model().BrandID(), Equals, "my-brand")
   496  	c.Check(deviceSeed.Model().Model(), Equals, "my-model")
   497  	c.Check(deviceSeed.Model().Base(), Equals, "core20")
   498  
   499  	// verify that the model was added
   500  	db := assertstate.DB(st)
   501  	as, err := db.Find(asserts.ModelType, map[string]string{
   502  		"series":   "16",
   503  		"brand-id": "my-brand",
   504  		"model":    "my-model",
   505  	})
   506  	c.Assert(err, IsNil)
   507  	c.Check(as, DeepEquals, deviceSeed.Model())
   508  
   509  	// inconsistent seed request
   510  	_, err = devicestate.LoadDeviceSeed(st, "20210201")
   511  	c.Assert(err, ErrorMatches, `internal error: requested inconsistent device seed: 20210201 \(was 20191018\)`)
   512  }
   513  
   514  func (s *firstBoot20Suite) TestPopulateFromSeedCore20RunModeUserServiceTasks(c *C) {
   515  	// check that this is test is still valid
   516  	// TODO: have a test for an early config option that is not an
   517  	// experimental flag
   518  	c.Assert(features.UserDaemons.IsEnabledWhenUnset(), Equals, false, Commentf("user-daemons is not experimental anymore, this test is not useful anymore"))
   519  
   520  	s.extraSnapYaml["user-daemons1"] = `name: user-daemons1
   521  version: 1.0
   522  type: app
   523  base: core20
   524  
   525  apps:
   526    foo:
   527      daemon: simple
   528      daemon-scope: user
   529  `
   530  	m := boot.Modeenv{
   531  		Mode:           "run",
   532  		RecoverySystem: "20191018",
   533  		Base:           "core20_1.snap",
   534  	}
   535  
   536  	defaultsGadgetYaml := `
   537  defaults:
   538     system:
   539        experimental:
   540          user-daemons: true
   541  `
   542  
   543  	s.earlySetup(c, &m, "signed", defaultsGadgetYaml, "user-daemons1")
   544  
   545  	// create a new overlord and pick up the modeenv
   546  	// this overlord will use the proper EarlyConfig implementation
   547  	o, err := overlord.New(nil)
   548  	c.Assert(err, IsNil)
   549  	o.InterfaceManager().DisableUDevMonitor()
   550  	c.Assert(o.StartUp(), IsNil)
   551  
   552  	st := o.State()
   553  	st.Lock()
   554  	defer st.Unlock()
   555  
   556  	// early config set the flag to enabled
   557  	tr := config.NewTransaction(st)
   558  	enabled, _ := features.Flag(tr, features.UserDaemons)
   559  	c.Check(enabled, Equals, true)
   560  
   561  	opts := devicestate.PopulateStateFromSeedOptions{
   562  		Label: m.RecoverySystem,
   563  		Mode:  m.Mode,
   564  	}
   565  
   566  	_, err = devicestate.PopulateStateFromSeedImpl(st, &opts, s.perfTimings)
   567  	c.Assert(err, IsNil)
   568  }
   569  
   570  func (s *firstBoot20Suite) TestUsersCreateAutomaticIsAvailableEarly(c *C) {
   571  	m := boot.Modeenv{
   572  		Mode:           "run",
   573  		RecoverySystem: "20191018",
   574  		Base:           "core20_1.snap",
   575  	}
   576  
   577  	defaultsGadgetYaml := `
   578  defaults:
   579     system:
   580        users:
   581          create.automatic: false
   582  `
   583  
   584  	s.earlySetup(c, &m, "signed", defaultsGadgetYaml)
   585  
   586  	// create a new overlord and pick up the modeenv
   587  	// this overlord will use the proper EarlyConfig implementation
   588  	o, err := overlord.New(nil)
   589  	c.Assert(err, IsNil)
   590  	o.InterfaceManager().DisableUDevMonitor()
   591  	c.Assert(o.StartUp(), IsNil)
   592  
   593  	st := o.State()
   594  	st.Lock()
   595  	defer st.Unlock()
   596  
   597  	// early config in StartUp made the option available already
   598  	tr := config.NewTransaction(st)
   599  	var enabled bool
   600  	err = tr.Get("core", "users.create.automatic", &enabled)
   601  	c.Assert(err, IsNil)
   602  	c.Check(enabled, Equals, false)
   603  }