github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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 "time" 26 27 . "gopkg.in/check.v1" 28 29 "github.com/snapcore/snapd/asserts" 30 "github.com/snapcore/snapd/boot" 31 "github.com/snapcore/snapd/bootloader" 32 "github.com/snapcore/snapd/bootloader/bootloadertest" 33 "github.com/snapcore/snapd/dirs" 34 "github.com/snapcore/snapd/osutil" 35 "github.com/snapcore/snapd/overlord/devicestate" 36 "github.com/snapcore/snapd/overlord/ifacestate" 37 "github.com/snapcore/snapd/overlord/snapstate" 38 "github.com/snapcore/snapd/overlord/state" 39 "github.com/snapcore/snapd/seed/seedtest" 40 "github.com/snapcore/snapd/snap" 41 "github.com/snapcore/snapd/systemd" 42 "github.com/snapcore/snapd/testutil" 43 ) 44 45 type firstBoot20Suite struct { 46 firstBootBaseTest 47 48 snapYaml map[string]string 49 50 // TestingSeed20 helps populating seeds (it provides 51 // MakeAssertedSnap, MakeSeed) for tests. 52 *seedtest.TestingSeed20 53 } 54 55 var _ = Suite(&firstBoot20Suite{}) 56 57 func (s *firstBoot20Suite) SetUpTest(c *C) { 58 s.snapYaml = seedtest.SampleSnapYaml 59 60 s.TestingSeed20 = &seedtest.TestingSeed20{} 61 62 s.setupBaseTest(c, &s.TestingSeed20.SeedSnaps) 63 64 // don't start the overlord here so that we can mock different modeenvs 65 // later, which is needed by devicestart manager startup with uc20 booting 66 67 s.SeedDir = dirs.SnapSeedDir 68 69 // mock the snap mapper as snapd here 70 s.AddCleanup(ifacestate.MockSnapMapper(&ifacestate.CoreSnapdSystemMapper{})) 71 } 72 73 func (s *firstBoot20Suite) setupCore20Seed(c *C, sysLabel string) *asserts.Model { 74 gadgetYaml := ` 75 volumes: 76 volume-id: 77 bootloader: grub 78 structure: 79 - name: ubuntu-seed 80 role: system-seed 81 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 82 size: 1G 83 - name: ubuntu-data 84 role: system-data 85 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 86 size: 2G 87 ` 88 89 makeSnap := func(yamlKey string) { 90 var files [][]string 91 if yamlKey == "pc=20" { 92 files = append(files, []string{"meta/gadget.yaml", gadgetYaml}) 93 } 94 s.MakeAssertedSnap(c, s.snapYaml[yamlKey], files, snap.R(1), "canonical", s.StoreSigning.Database) 95 } 96 97 makeSnap("snapd") 98 makeSnap("pc-kernel=20") 99 makeSnap("core20") 100 makeSnap("pc=20") 101 102 return s.MakeSeed(c, sysLabel, "my-brand", "my-model", map[string]interface{}{ 103 "display-name": "my model", 104 "architecture": "amd64", 105 "base": "core20", 106 "snaps": []interface{}{ 107 map[string]interface{}{ 108 "name": "pc-kernel", 109 "id": s.AssertedSnapID("pc-kernel"), 110 "type": "kernel", 111 "default-channel": "20", 112 }, 113 map[string]interface{}{ 114 "name": "pc", 115 "id": s.AssertedSnapID("pc"), 116 "type": "gadget", 117 "default-channel": "20", 118 }}, 119 }, nil) 120 } 121 122 func (s *firstBoot20Suite) testPopulateFromSeedCore20Happy(c *C, m *boot.Modeenv) { 123 c.Assert(m, NotNil, Commentf("missing modeenv test data")) 124 err := m.WriteTo("") 125 c.Assert(err, IsNil) 126 127 // restart overlord to pick up the modeenv 128 s.startOverlord(c) 129 130 // XXX some things are not yet completely final/realistic 131 var sysdLog [][]string 132 systemctlRestorer := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) { 133 sysdLog = append(sysdLog, cmd) 134 return []byte("ActiveState=inactive\n"), nil 135 }) 136 defer systemctlRestorer() 137 138 sysLabel := m.RecoverySystem 139 model := s.setupCore20Seed(c, sysLabel) 140 141 bloader := bootloadertest.Mock("mock", c.MkDir()).WithExtractedRunKernelImage() 142 bootloader.Force(bloader) 143 defer bootloader.Force(nil) 144 145 // since we are in runmode, MakeBootable will already have run from install 146 // mode, and extracted the kernel assets for the kernel snap into the 147 // bootloader, so set the current kernel there 148 kernel, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap") 149 c.Assert(err, IsNil) 150 r := bloader.SetEnabledKernel(kernel) 151 defer r() 152 153 opts := devicestate.PopulateStateFromSeedOptions{ 154 Label: sysLabel, 155 Mode: m.Mode, 156 } 157 158 // run the firstboot stuff 159 st := s.overlord.State() 160 st.Lock() 161 defer st.Unlock() 162 tsAll, err := devicestate.PopulateStateFromSeedImpl(st, &opts, s.perfTimings) 163 c.Assert(err, IsNil) 164 165 checkOrder(c, tsAll, "snapd", "pc-kernel", "core20", "pc") 166 167 // now run the change and check the result 168 // use the expected kind otherwise settle with start another one 169 chg := st.NewChange("seed", "run the populate from seed changes") 170 for _, ts := range tsAll { 171 chg.AddAll(ts) 172 } 173 c.Assert(st.Changes(), HasLen, 1) 174 175 c.Assert(chg.Err(), IsNil) 176 177 // avoid device reg 178 chg1 := st.NewChange("become-operational", "init device") 179 chg1.SetStatus(state.DoingStatus) 180 181 // run change until it wants to restart 182 st.Unlock() 183 err = s.overlord.Settle(settleTimeout) 184 st.Lock() 185 c.Assert(err, IsNil) 186 187 // at this point the system is "restarting", pretend the restart has 188 // happened 189 c.Assert(chg.Status(), Equals, state.DoingStatus) 190 state.MockRestarting(st, state.RestartUnset) 191 st.Unlock() 192 err = s.overlord.Settle(settleTimeout) 193 st.Lock() 194 c.Assert(err, IsNil) 195 c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("%s", chg.Err())) 196 197 // verify 198 f, err := os.Open(dirs.SnapStateFile) 199 c.Assert(err, IsNil) 200 state, err := state.ReadState(nil, f) 201 c.Assert(err, IsNil) 202 203 state.Lock() 204 defer state.Unlock() 205 // check snapd, core20, kernel, gadget 206 _, err = snapstate.CurrentInfo(state, "snapd") 207 c.Check(err, IsNil) 208 _, err = snapstate.CurrentInfo(state, "core20") 209 c.Check(err, IsNil) 210 _, err = snapstate.CurrentInfo(state, "pc-kernel") 211 c.Check(err, IsNil) 212 _, err = snapstate.CurrentInfo(state, "pc") 213 c.Check(err, IsNil) 214 215 // ensure required flag is set on all essential snaps 216 var snapst snapstate.SnapState 217 for _, reqName := range []string{"snapd", "core20", "pc-kernel", "pc"} { 218 err = snapstate.Get(state, reqName, &snapst) 219 c.Assert(err, IsNil) 220 c.Assert(snapst.Required, Equals, true, Commentf("required not set for %v", reqName)) 221 222 if m.Mode == "run" { 223 // also ensure that in run mode none of the snaps are installed as 224 // symlinks, they must be copied onto ubuntu-data 225 files, err := filepath.Glob(filepath.Join(dirs.SnapBlobDir, reqName+"_*.snap")) 226 c.Assert(err, IsNil) 227 c.Assert(files, HasLen, 1) 228 c.Assert(osutil.IsSymlink(files[0]), Equals, false) 229 } 230 } 231 232 // the right systemd commands were run 233 c.Check(sysdLog, testutil.DeepContains, []string{"start", "usr-lib-snapd.mount"}) 234 235 // and ensure state is now considered seeded 236 var seeded bool 237 err = state.Get("seeded", &seeded) 238 c.Assert(err, IsNil) 239 c.Check(seeded, Equals, true) 240 241 // check we set seed-time 242 var seedTime time.Time 243 err = state.Get("seed-time", &seedTime) 244 c.Assert(err, IsNil) 245 c.Check(seedTime.IsZero(), Equals, false) 246 247 // check that we removed recovery_system from modeenv 248 m2, err := boot.ReadModeenv("") 249 c.Assert(err, IsNil) 250 if m.Mode == "run" { 251 // recovery system is cleared in run mode 252 c.Assert(m2.RecoverySystem, Equals, "") 253 } else { 254 // but kept intact in other modes 255 c.Assert(m2.RecoverySystem, Equals, m.RecoverySystem) 256 } 257 c.Assert(m2.Base, Equals, m.Base) 258 c.Assert(m2.Mode, Equals, m.Mode) 259 // Note that we don't check CurrentKernels in the modeenv, even though in a 260 // real first boot that would also be set here, because setting that is done 261 // in the snapstate manager, not the devicestate manager 262 263 // check that the default device ctx has a Modeenv 264 dev, err := devicestate.DeviceCtx(s.overlord.State(), nil, nil) 265 c.Assert(err, IsNil) 266 c.Assert(dev.HasModeenv(), Equals, true) 267 268 // check that we marked the boot successful with bootstate20 methods, namely 269 // that we called SetNext, which since it was called on the kernel we 270 // already booted from, we should only have checked what the current kernel 271 // is 272 273 if m.Mode == "run" { 274 // only relevant in run mode 275 276 // the 3 calls here are : 277 // * 1 from MarkBootSuccessful() from ensureBootOk() before we restart 278 // * 1 from boot.SetNextBoot() from LinkSnap() from doInstall() from InstallPath() from 279 // installSeedSnap() after restart 280 // * 1 from boot.GetCurrentBoot() from WaitRestart after restart 281 _, numKernelCalls := bloader.GetRunKernelImageFunctionSnapCalls("Kernel") 282 c.Assert(numKernelCalls, Equals, 3) 283 } 284 actual, _ := bloader.GetRunKernelImageFunctionSnapCalls("EnableKernel") 285 c.Assert(actual, HasLen, 0) 286 actual, _ = bloader.GetRunKernelImageFunctionSnapCalls("DisableTryKernel") 287 c.Assert(actual, HasLen, 0) 288 actual, _ = bloader.GetRunKernelImageFunctionSnapCalls("EnableTryKernel") 289 c.Assert(actual, HasLen, 0) 290 291 var whatseeded []devicestate.SeededSystem 292 err = state.Get("seeded-systems", &whatseeded) 293 if m.Mode == "run" { 294 c.Assert(err, IsNil) 295 c.Assert(whatseeded, DeepEquals, []devicestate.SeededSystem{{ 296 System: m.RecoverySystem, 297 Model: "my-model", 298 BrandID: "my-brand", 299 Revision: model.Revision(), 300 Timestamp: model.Timestamp(), 301 SeedTime: seedTime, 302 }}) 303 } else { 304 c.Assert(err, NotNil) 305 } 306 } 307 308 func (s *firstBoot20Suite) TestPopulateFromSeedCore20RunMode(c *C) { 309 m := boot.Modeenv{ 310 Mode: "run", 311 RecoverySystem: "20191018", 312 Base: "core20_1.snap", 313 } 314 s.testPopulateFromSeedCore20Happy(c, &m) 315 } 316 317 func (s *firstBoot20Suite) TestPopulateFromSeedCore20InstallMode(c *C) { 318 m := boot.Modeenv{ 319 Mode: "install", 320 RecoverySystem: "20191019", 321 Base: "core20_1.snap", 322 } 323 s.testPopulateFromSeedCore20Happy(c, &m) 324 } 325 326 func (s *firstBoot20Suite) TestPopulateFromSeedCore20RecoverMode(c *C) { 327 m := boot.Modeenv{ 328 Mode: "recover", 329 RecoverySystem: "20191020", 330 Base: "core20_1.snap", 331 } 332 s.testPopulateFromSeedCore20Happy(c, &m) 333 }