github.com/ubuntu-core/snappy@v0.0.0-20210827154228-9e584df982bb/boot/boot_robustness_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020 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 package boot_test 20 21 import ( 22 "fmt" 23 24 . "gopkg.in/check.v1" 25 26 "github.com/snapcore/snapd/boot" 27 "github.com/snapcore/snapd/boot/boottest" 28 "github.com/snapcore/snapd/bootloader" 29 "github.com/snapcore/snapd/snap" 30 ) 31 32 // TODO:UC20: move this to bootloadertest package and use from i.e. managers_test.go ? 33 func runBootloaderLogic(c *C, bl bootloader.Bootloader) (snap.PlaceInfo, error) { 34 // switch on which kind of bootloader we have 35 ebl, ok := bl.(bootloader.ExtractedRunKernelImageBootloader) 36 if ok { 37 return extractedRunKernelImageBootloaderLogic(c, ebl) 38 } 39 40 return pureenvBootloaderLogic(c, "kernel_status", bl) 41 } 42 43 // runBootloaderLogic implements the logic from the gadget snap bootloader, 44 // namely that we transition kernel_status "try" -> "trying" and "trying" -> "" 45 // and use try-kernel.efi when kernel_status is "try" and kernel.efi in all 46 // other situations 47 func extractedRunKernelImageBootloaderLogic(c *C, ebl bootloader.ExtractedRunKernelImageBootloader) (snap.PlaceInfo, error) { 48 m, err := ebl.GetBootVars("kernel_status") 49 c.Assert(err, IsNil) 50 kernStatus := m["kernel_status"] 51 52 kern, err := ebl.Kernel() 53 c.Assert(err, IsNil) 54 c.Assert(kern, Not(IsNil)) 55 56 switch kernStatus { 57 case boot.DefaultStatus: 58 case boot.TryStatus: 59 // move to trying, use the try-kernel 60 m["kernel_status"] = boot.TryingStatus 61 62 // ensure that the try-kernel exists 63 tryKern, err := ebl.TryKernel() 64 c.Assert(err, IsNil) 65 c.Assert(tryKern, Not(IsNil)) 66 kern = tryKern 67 68 case boot.TryingStatus: 69 // boot failed, move back to default 70 m["kernel_status"] = boot.DefaultStatus 71 } 72 73 err = ebl.SetBootVars(m) 74 c.Assert(err, IsNil) 75 76 return kern, nil 77 } 78 79 func pureenvBootloaderLogic(c *C, modeVar string, bl bootloader.Bootloader) (snap.PlaceInfo, error) { 80 m, err := bl.GetBootVars(modeVar, "snap_kernel", "snap_try_kernel") 81 c.Assert(err, IsNil) 82 var kern snap.PlaceInfo 83 84 kernStatus := m[modeVar] 85 86 kern, err = snap.ParsePlaceInfoFromSnapFileName(m["snap_kernel"]) 87 c.Assert(err, IsNil) 88 c.Assert(kern, Not(IsNil)) 89 90 switch kernStatus { 91 case boot.DefaultStatus: 92 // nothing to do, use normal kernel 93 94 case boot.TryStatus: 95 // move to trying, use the try-kernel 96 m[modeVar] = boot.TryingStatus 97 98 tryKern, err := snap.ParsePlaceInfoFromSnapFileName(m["snap_try_kernel"]) 99 c.Assert(err, IsNil) 100 c.Assert(tryKern, Not(IsNil)) 101 kern = tryKern 102 103 case boot.TryingStatus: 104 // boot failed, move back to default status 105 m[modeVar] = boot.DefaultStatus 106 107 } 108 109 err = bl.SetBootVars(m) 110 c.Assert(err, IsNil) 111 112 return kern, nil 113 } 114 115 // note: this could be implemented just as a function which takes a bootloader 116 // as an argument and then inspect the type of MockBootloader that was passed 117 // in, but the gains are little, since we don't need to use this function for 118 // the non-ExtractedRunKernelImageBootloader implementations, as those 119 // implementations just have one critical function to run which is just 120 // SetBootVars 121 func (s *bootenv20Suite) checkBootStateAfterUnexpectedRebootAndCleanup( 122 c *C, 123 dev boot.Device, 124 bootFunc func(boot.Device) error, 125 panicFunc string, 126 expectedBootedKernel snap.PlaceInfo, 127 expectedModeenvCurrentKernels []snap.PlaceInfo, 128 blKernelAfterReboot snap.PlaceInfo, 129 comment string, 130 ) { 131 if panicFunc != "" { 132 // setup a panic during the given bootloader function 133 restoreBootloaderPanic := s.bootloader.SetMockToPanic(panicFunc) 134 135 // run the boot function that will now panic 136 c.Assert( 137 func() { bootFunc(dev) }, 138 PanicMatches, 139 fmt.Sprintf("mocked reboot panic in %s", panicFunc), 140 Commentf(comment), 141 ) 142 143 // don't panic anymore 144 restoreBootloaderPanic() 145 } else { 146 // just run the function directly 147 err := bootFunc(dev) 148 c.Assert(err, IsNil, Commentf(comment)) 149 } 150 151 // do the bootloader kernel failover logic handling 152 nextBootingKernel, err := runBootloaderLogic(c, s.bootloader) 153 c.Assert(err, IsNil, Commentf(comment)) 154 155 // check that the kernel we booted now is expected 156 c.Assert(nextBootingKernel, Equals, expectedBootedKernel, Commentf(comment)) 157 158 // also check that the normal kernel on the bootloader is what we expect 159 kern, err := s.bootloader.Kernel() 160 c.Assert(err, IsNil, Commentf(comment)) 161 c.Assert(kern, Equals, blKernelAfterReboot, Commentf(comment)) 162 163 // mark the boot successful like we were rebooted 164 err = boot.MarkBootSuccessful(dev) 165 c.Assert(err, IsNil, Commentf(comment)) 166 167 // the boot vars should be empty now too 168 afterVars, err := s.bootloader.GetBootVars("kernel_status") 169 c.Assert(err, IsNil, Commentf(comment)) 170 c.Assert(afterVars["kernel_status"], DeepEquals, boot.DefaultStatus, Commentf(comment)) 171 172 // the modeenv's setting for CurrentKernels also matches 173 m, err := boot.ReadModeenv("") 174 c.Assert(err, IsNil, Commentf(comment)) 175 // it's nicer to pass in just the snap.PlaceInfo's, but to compare we need 176 // the string filenames 177 currentKernels := make([]string, len(expectedModeenvCurrentKernels)) 178 for i, sn := range expectedModeenvCurrentKernels { 179 currentKernels[i] = sn.Filename() 180 } 181 c.Assert(m.CurrentKernels, DeepEquals, currentKernels, Commentf(comment)) 182 183 // the final kernel on the bootloader should always match what we booted - 184 // after MarkSuccessful runs that is 185 afterKernel, err := s.bootloader.Kernel() 186 c.Assert(err, IsNil, Commentf(comment)) 187 c.Assert(afterKernel, DeepEquals, expectedBootedKernel, Commentf(comment)) 188 189 // we should never have a leftover try kernel 190 _, err = s.bootloader.TryKernel() 191 c.Assert(err, Equals, bootloader.ErrNoTryKernelRef, Commentf(comment)) 192 } 193 194 func (s *bootenv20Suite) TestHappyMarkBootSuccessful20KernelUpgradeUnexpectedReboots(c *C) { 195 coreDev := boottest.MockUC20Device("", nil) 196 c.Assert(coreDev.HasModeenv(), Equals, true) 197 198 tt := []struct { 199 rebootBeforeFunc string 200 expBootKernel snap.PlaceInfo 201 expModeenvKernels []snap.PlaceInfo 202 expBlKernel snap.PlaceInfo 203 comment string 204 }{ 205 { 206 "", // don't do any reboots for the happy path 207 s.kern2, // we should boot the new kernel 208 []snap.PlaceInfo{s.kern2}, // expected modeenv kernel is new one 209 s.kern2, // after reboot, current kernel on bl is new one 210 "happy path", 211 }, 212 { 213 "SetBootVars", // reboot right before SetBootVars 214 s.kern1, // we should boot the old kernel 215 []snap.PlaceInfo{s.kern1}, // expected modeenv kernel is old one 216 s.kern1, // after reboot, current kernel on bl is old one 217 "reboot before SetBootVars results in old kernel", 218 }, 219 { 220 "EnableKernel", // reboot right before EnableKernel 221 s.kern1, // we should boot the old kernel 222 []snap.PlaceInfo{s.kern1}, // expected modeenv kernel is old one 223 s.kern1, // after reboot, current kernel on bl is old one 224 "reboot before EnableKernel results in old kernel", 225 }, 226 { 227 "DisableTryKernel", // reboot right before DisableTryKernel 228 s.kern2, // we should boot the new kernel 229 []snap.PlaceInfo{s.kern2}, // expected modeenv kernel is new one 230 s.kern2, // after reboot, current kernel on bl is new one 231 "reboot before DisableTryKernel results in new kernel", 232 }, 233 } 234 235 for _, t := range tt { 236 // setup the bootloader per test 237 restore := setupUC20Bootenv( 238 c, 239 s.bootloader, 240 s.normalTryingKernelState, 241 ) 242 243 s.checkBootStateAfterUnexpectedRebootAndCleanup( 244 c, 245 coreDev, 246 boot.MarkBootSuccessful, 247 t.rebootBeforeFunc, 248 t.expBlKernel, 249 t.expModeenvKernels, 250 t.expBlKernel, 251 t.comment, 252 ) 253 254 restore() 255 } 256 } 257 258 func (s *bootenv20Suite) TestHappySetNextBoot20KernelUpgradeUnexpectedReboots(c *C) { 259 coreDev := boottest.MockUC20Device("", nil) 260 c.Assert(coreDev.HasModeenv(), Equals, true) 261 262 tt := []struct { 263 rebootBeforeFunc string 264 expBootKernel snap.PlaceInfo 265 expModeenvKernels []snap.PlaceInfo 266 expBlKernel snap.PlaceInfo 267 comment string 268 }{ 269 { 270 "", // don't do any reboots for the happy path 271 s.kern2, // we should boot the new kernel 272 []snap.PlaceInfo{s.kern2}, // final expected modeenv kernel is new one 273 s.kern1, // after reboot, current kernel on bl is old one 274 "happy path", 275 }, 276 { 277 "EnableTryKernel", // reboot right before EnableTryKernel 278 s.kern1, // we should boot the old kernel 279 []snap.PlaceInfo{s.kern1}, // final expected modeenv kernel is old one 280 s.kern1, // after reboot, current kernel on bl is old one 281 "reboot before EnableTryKernel results in old kernel", 282 }, 283 { 284 "SetBootVars", // reboot right before SetBootVars 285 s.kern1, // we should boot the old kernel 286 []snap.PlaceInfo{s.kern1}, // final expected modeenv kernel is old one 287 s.kern1, // after reboot, current kernel on bl is old one 288 "reboot before SetBootVars results in old kernel", 289 }, 290 } 291 292 for _, t := range tt { 293 // setup the bootloader per test 294 restore := setupUC20Bootenv( 295 c, 296 s.bootloader, 297 s.normalDefaultState, 298 ) 299 300 // get the boot kernel participant from our new kernel snap 301 bootKern := boot.Participant(s.kern2, snap.TypeKernel, coreDev) 302 // make sure it's not a trivial boot participant 303 c.Assert(bootKern.IsTrivial(), Equals, false) 304 305 setNextFunc := func(boot.Device) error { 306 // we don't care about the reboot required logic here 307 _, err := bootKern.SetNextBoot() 308 return err 309 } 310 311 s.checkBootStateAfterUnexpectedRebootAndCleanup( 312 c, 313 coreDev, 314 setNextFunc, 315 t.rebootBeforeFunc, 316 t.expBootKernel, 317 t.expModeenvKernels, 318 t.expBlKernel, 319 t.comment, 320 ) 321 322 restore() 323 } 324 }