github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/bootloader/bootloadertest/bootloadertest.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-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 20 package bootloadertest 21 22 import ( 23 "fmt" 24 "path/filepath" 25 "strings" 26 27 "github.com/snapcore/snapd/bootloader" 28 "github.com/snapcore/snapd/snap" 29 ) 30 31 // MockBootloader mocks the bootloader interface and records all 32 // set/get calls. 33 type MockBootloader struct { 34 BootVars map[string]string 35 SetBootVarsCalls int 36 SetErr error 37 GetErr error 38 39 name string 40 bootdir string 41 42 ExtractKernelAssetsCalls []snap.PlaceInfo 43 RemoveKernelAssetsCalls []snap.PlaceInfo 44 45 InstallBootConfigCalled []string 46 InstallBootConfigResult bool 47 InstallBootConfigErr error 48 49 enabledKernel snap.PlaceInfo 50 enabledTryKernel snap.PlaceInfo 51 52 panicMethods map[string]bool 53 } 54 55 // ensure MockBootloader(s) implement the Bootloader interface 56 var _ bootloader.Bootloader = (*MockBootloader)(nil) 57 var _ bootloader.RecoveryAwareBootloader = (*MockRecoveryAwareBootloader)(nil) 58 var _ bootloader.TrustedAssetsBootloader = (*MockTrustedAssetsBootloader)(nil) 59 var _ bootloader.ExtractedRunKernelImageBootloader = (*MockExtractedRunKernelImageBootloader)(nil) 60 var _ bootloader.ExtractedRecoveryKernelImageBootloader = (*MockExtractedRecoveryKernelImageBootloader)(nil) 61 62 func Mock(name, bootdir string) *MockBootloader { 63 return &MockBootloader{ 64 name: name, 65 bootdir: bootdir, 66 67 BootVars: make(map[string]string), 68 69 panicMethods: make(map[string]bool), 70 } 71 } 72 73 func (b *MockBootloader) maybePanic(which string) { 74 if b.panicMethods[which] { 75 panic(fmt.Sprintf("mocked reboot panic in %s", which)) 76 } 77 } 78 79 func (b *MockBootloader) SetBootVars(values map[string]string) error { 80 b.maybePanic("SetBootVars") 81 b.SetBootVarsCalls++ 82 for k, v := range values { 83 b.BootVars[k] = v 84 } 85 return b.SetErr 86 } 87 88 func (b *MockBootloader) GetBootVars(keys ...string) (map[string]string, error) { 89 b.maybePanic("GetBootVars") 90 91 out := map[string]string{} 92 for _, k := range keys { 93 out[k] = b.BootVars[k] 94 } 95 96 return out, b.GetErr 97 } 98 99 func (b *MockBootloader) Name() string { 100 return b.name 101 } 102 103 func (b *MockBootloader) ConfigFile() string { 104 return filepath.Join(b.bootdir, "mockboot/mockboot.cfg") 105 } 106 107 func (b *MockBootloader) ExtractKernelAssets(s snap.PlaceInfo, snapf snap.Container) error { 108 b.ExtractKernelAssetsCalls = append(b.ExtractKernelAssetsCalls, s) 109 return nil 110 } 111 112 func (b *MockBootloader) RemoveKernelAssets(s snap.PlaceInfo) error { 113 b.RemoveKernelAssetsCalls = append(b.RemoveKernelAssetsCalls, s) 114 return nil 115 } 116 117 func (b *MockBootloader) SetEnabledKernel(s snap.PlaceInfo) (restore func()) { 118 oldSn := b.enabledTryKernel 119 oldVar := b.BootVars["snap_kernel"] 120 b.enabledKernel = s 121 b.BootVars["snap_kernel"] = s.Filename() 122 return func() { 123 b.BootVars["snap_kernel"] = oldVar 124 b.enabledKernel = oldSn 125 } 126 } 127 128 func (b *MockBootloader) SetEnabledTryKernel(s snap.PlaceInfo) (restore func()) { 129 oldSn := b.enabledTryKernel 130 oldVar := b.BootVars["snap_try_kernel"] 131 b.enabledTryKernel = s 132 b.BootVars["snap_try_kernel"] = s.Filename() 133 return func() { 134 b.BootVars["snap_try_kernel"] = oldVar 135 b.enabledTryKernel = oldSn 136 } 137 } 138 139 // InstallBootConfig installs the boot config in the gadget directory to the 140 // mock bootloader's root directory. 141 func (b *MockBootloader) InstallBootConfig(gadgetDir string, opts *bootloader.Options) (bool, error) { 142 b.InstallBootConfigCalled = append(b.InstallBootConfigCalled, gadgetDir) 143 return b.InstallBootConfigResult, b.InstallBootConfigErr 144 } 145 146 // MockRecoveryAwareBootloader mocks a bootloader implementing the 147 // RecoveryAware interface. 148 type MockRecoveryAwareBootloader struct { 149 *MockBootloader 150 151 RecoverySystemDir string 152 RecoverySystemBootVars map[string]string 153 } 154 155 type ExtractedRecoveryKernelCall struct { 156 RecoverySystemDir string 157 S snap.PlaceInfo 158 } 159 160 // MockExtractedRecoveryKernelImageBootloader mocks a bootloader implementing 161 // the ExtractedRecoveryKernelImage interface. 162 type MockExtractedRecoveryKernelImageBootloader struct { 163 *MockBootloader 164 165 ExtractRecoveryKernelAssetsCalls []ExtractedRecoveryKernelCall 166 } 167 168 // ExtractedRecoveryKernelImage derives a MockRecoveryAwareBootloader from a base 169 // MockBootloader. 170 func (b *MockBootloader) ExtractedRecoveryKernelImage() *MockExtractedRecoveryKernelImageBootloader { 171 return &MockExtractedRecoveryKernelImageBootloader{MockBootloader: b} 172 } 173 174 // ExtractRecoveryKernelAssets extracts the kernel assets for the provided 175 // kernel snap into the specified recovery system dir; part of 176 // RecoveryAwareBootloader. 177 func (b *MockExtractedRecoveryKernelImageBootloader) ExtractRecoveryKernelAssets(recoverySystemDir string, s snap.PlaceInfo, snapf snap.Container) error { 178 if recoverySystemDir == "" { 179 panic("MockBootloader.ExtractRecoveryKernelAssets called without recoverySystemDir") 180 } 181 182 b.ExtractRecoveryKernelAssetsCalls = append( 183 b.ExtractRecoveryKernelAssetsCalls, 184 ExtractedRecoveryKernelCall{ 185 S: s, 186 RecoverySystemDir: recoverySystemDir}, 187 ) 188 return nil 189 } 190 191 // RecoveryAware derives a MockRecoveryAwareBootloader from a base 192 // MockBootloader. 193 func (b *MockBootloader) RecoveryAware() *MockRecoveryAwareBootloader { 194 return &MockRecoveryAwareBootloader{MockBootloader: b} 195 } 196 197 // SetRecoverySystemEnv sets the recovery system environment bootloader 198 // variables; part of RecoveryAwareBootloader. 199 func (b *MockRecoveryAwareBootloader) SetRecoverySystemEnv(recoverySystemDir string, blVars map[string]string) error { 200 if recoverySystemDir == "" { 201 panic("MockBootloader.SetRecoverySystemEnv called without recoverySystemDir") 202 } 203 b.RecoverySystemDir = recoverySystemDir 204 b.RecoverySystemBootVars = blVars 205 return nil 206 } 207 208 // GetRecoverySystemEnv gets the recovery system environment bootloader 209 // variables; part of RecoveryAwareBootloader. 210 func (b *MockRecoveryAwareBootloader) GetRecoverySystemEnv(recoverySystemDir, key string) (string, error) { 211 if recoverySystemDir == "" { 212 panic("MockBootloader.GetRecoverySystemEnv called without recoverySystemDir") 213 } 214 b.RecoverySystemDir = recoverySystemDir 215 return b.RecoverySystemBootVars[key], nil 216 } 217 218 // MockExtractedRunKernelImageBootloader mocks a bootloader 219 // implementing the ExtractedRunKernelImageBootloader interface. 220 type MockExtractedRunKernelImageBootloader struct { 221 *MockBootloader 222 223 runKernelImageEnableKernelCalls []snap.PlaceInfo 224 runKernelImageEnableTryKernelCalls []snap.PlaceInfo 225 runKernelImageDisableTryKernelCalls []snap.PlaceInfo 226 runKernelImageEnabledKernel snap.PlaceInfo 227 runKernelImageEnabledTryKernel snap.PlaceInfo 228 229 runKernelImageMockedErrs map[string]error 230 runKernelImageMockedNumCalls map[string]int 231 } 232 233 // WithExtractedRunKernelImage derives a MockExtractedRunKernelImageBootloader 234 // from a base MockBootloader. 235 func (b *MockBootloader) WithExtractedRunKernelImage() *MockExtractedRunKernelImageBootloader { 236 return &MockExtractedRunKernelImageBootloader{ 237 MockBootloader: b, 238 239 runKernelImageMockedErrs: make(map[string]error), 240 runKernelImageMockedNumCalls: make(map[string]int), 241 } 242 } 243 244 // SetEnabledKernel sets the current kernel "symlink" as returned 245 // by Kernel(); returns' a restore function to set it back to what it was 246 // before. 247 func (b *MockExtractedRunKernelImageBootloader) SetEnabledKernel(kernel snap.PlaceInfo) (restore func()) { 248 old := b.runKernelImageEnabledKernel 249 b.runKernelImageEnabledKernel = kernel 250 return func() { 251 b.runKernelImageEnabledKernel = old 252 } 253 } 254 255 // SetEnabledTryKernel sets the current try-kernel "symlink" as 256 // returned by TryKernel(). If set to nil, TryKernel()'s second return value 257 // will be false; returns' a restore function to set it back to what it was 258 // before. 259 func (b *MockExtractedRunKernelImageBootloader) SetEnabledTryKernel(kernel snap.PlaceInfo) (restore func()) { 260 old := b.runKernelImageEnabledTryKernel 261 b.runKernelImageEnabledTryKernel = kernel 262 return func() { 263 b.runKernelImageEnabledTryKernel = old 264 } 265 } 266 267 // SetRunKernelImageFunctionError allows setting an error to be returned for the 268 // specified function; it returns a restore function to set it back to what it 269 // was before. 270 func (b *MockExtractedRunKernelImageBootloader) SetRunKernelImageFunctionError(f string, err error) (restore func()) { 271 // check the function 272 switch f { 273 case "EnableKernel", "EnableTryKernel", "Kernel", "TryKernel", "DisableTryKernel": 274 old := b.runKernelImageMockedErrs[f] 275 b.runKernelImageMockedErrs[f] = err 276 return func() { 277 b.runKernelImageMockedErrs[f] = old 278 } 279 default: 280 panic(fmt.Sprintf("unknown ExtractedRunKernelImageBootloader method %q to mock error for", f)) 281 } 282 } 283 284 // SetRunKernelImagePanic allows setting any method in the 285 // ExtractedRunKernelImageBootloader interface on 286 // MockExtractedRunKernelImageBootloader to panic instead of 287 // returning. This allows one to test what would happen if the system 288 // was rebooted during execution of a particular 289 // function. Specifically, the panic will be done immediately entering 290 // the function so setting SetBootVars to panic will emulate a reboot 291 // before any boot vars are set persistently 292 func (b *MockExtractedRunKernelImageBootloader) SetRunKernelImagePanic(f string) (restore func()) { 293 switch f { 294 case "EnableKernel", "EnableTryKernel", "Kernel", "TryKernel", "DisableTryKernel", "SetBootVars", "GetBootVars": 295 old := b.panicMethods[f] 296 b.panicMethods[f] = true 297 return func() { 298 b.panicMethods[f] = old 299 } 300 default: 301 panic(fmt.Sprintf("unknown ExtractedRunKernelImageBootloader method %q to mock reboot via panic for", f)) 302 } 303 } 304 305 // GetRunKernelImageFunctionSnapCalls returns which snaps were specified during 306 // execution, in order of calls, as well as the number of calls for methods that 307 // don't take a snap to set. 308 func (b *MockExtractedRunKernelImageBootloader) GetRunKernelImageFunctionSnapCalls(f string) ([]snap.PlaceInfo, int) { 309 switch f { 310 case "EnableKernel": 311 l := b.runKernelImageEnableKernelCalls 312 return l, len(l) 313 case "EnableTryKernel": 314 l := b.runKernelImageEnableTryKernelCalls 315 return l, len(l) 316 case "Kernel", "TryKernel", "DisableTryKernel": 317 return nil, b.runKernelImageMockedNumCalls[f] 318 default: 319 panic(fmt.Sprintf("unknown ExtractedRunKernelImageBootloader method %q to return snap args for", f)) 320 } 321 } 322 323 // EnableKernel enables the kernel; part of ExtractedRunKernelImageBootloader. 324 func (b *MockExtractedRunKernelImageBootloader) EnableKernel(s snap.PlaceInfo) error { 325 b.maybePanic("EnableKernel") 326 b.runKernelImageEnableKernelCalls = append(b.runKernelImageEnableKernelCalls, s) 327 b.runKernelImageEnabledKernel = s 328 return b.runKernelImageMockedErrs["EnableKernel"] 329 } 330 331 // EnableTryKernel enables a try-kernel; part of 332 // ExtractedRunKernelImageBootloader. 333 func (b *MockExtractedRunKernelImageBootloader) EnableTryKernel(s snap.PlaceInfo) error { 334 b.maybePanic("EnableTryKernel") 335 b.runKernelImageEnableTryKernelCalls = append(b.runKernelImageEnableTryKernelCalls, s) 336 b.runKernelImageEnabledTryKernel = s 337 return b.runKernelImageMockedErrs["EnableTryKernel"] 338 } 339 340 // Kernel returns the current kernel set in the bootloader; part of 341 // ExtractedRunKernelImageBootloader. 342 func (b *MockExtractedRunKernelImageBootloader) Kernel() (snap.PlaceInfo, error) { 343 b.maybePanic("Kernel") 344 b.runKernelImageMockedNumCalls["Kernel"]++ 345 err := b.runKernelImageMockedErrs["Kernel"] 346 if err != nil { 347 return nil, err 348 } 349 return b.runKernelImageEnabledKernel, nil 350 } 351 352 // TryKernel returns the current kernel set in the bootloader; part of 353 // ExtractedRunKernelImageBootloader. 354 func (b *MockExtractedRunKernelImageBootloader) TryKernel() (snap.PlaceInfo, error) { 355 b.maybePanic("TryKernel") 356 b.runKernelImageMockedNumCalls["TryKernel"]++ 357 err := b.runKernelImageMockedErrs["TryKernel"] 358 if err != nil { 359 return nil, err 360 } 361 if b.runKernelImageEnabledTryKernel == nil { 362 return nil, bootloader.ErrNoTryKernelRef 363 } 364 return b.runKernelImageEnabledTryKernel, nil 365 } 366 367 // DisableTryKernel removes the current try-kernel "symlink" set in the 368 // bootloader; part of ExtractedRunKernelImageBootloader. 369 func (b *MockExtractedRunKernelImageBootloader) DisableTryKernel() error { 370 b.maybePanic("DisableTryKernel") 371 b.runKernelImageMockedNumCalls["DisableTryKernel"]++ 372 b.runKernelImageEnabledTryKernel = nil 373 return b.runKernelImageMockedErrs["DisableTryKernel"] 374 } 375 376 // MockTrustedAssetsBootloader mocks a bootloader implementing the 377 // bootloader.TrustedAssetsBootloader interface. 378 type MockTrustedAssetsBootloader struct { 379 *MockBootloader 380 381 TrustedAssetsList []string 382 TrustedAssetsErr error 383 TrustedAssetsCalls int 384 385 RecoveryBootChainList []bootloader.BootFile 386 RecoveryBootChainErr error 387 BootChainList []bootloader.BootFile 388 BootChainErr error 389 390 RecoveryBootChainCalls []string 391 BootChainRunBl []bootloader.Bootloader 392 BootChainKernelPath []string 393 394 UpdateErr error 395 UpdateCalls int 396 ManagedAssetsList []string 397 StaticCommandLine string 398 CandidateStaticCommandLine string 399 CommandLineErr error 400 } 401 402 func (b *MockBootloader) WithTrustedAssets() *MockTrustedAssetsBootloader { 403 return &MockTrustedAssetsBootloader{ 404 MockBootloader: b, 405 } 406 } 407 408 func (b *MockTrustedAssetsBootloader) ManagedAssets() []string { 409 return b.ManagedAssetsList 410 } 411 412 func (b *MockTrustedAssetsBootloader) UpdateBootConfig(opts *bootloader.Options) error { 413 b.UpdateCalls++ 414 return b.UpdateErr 415 } 416 417 func glueCommandLine(modeArg, systemArg, staticArgs, extraArgs string) string { 418 args := []string(nil) 419 for _, argSet := range []string{modeArg, systemArg, staticArgs, extraArgs} { 420 if argSet != "" { 421 args = append(args, argSet) 422 } 423 } 424 line := strings.Join(args, " ") 425 return strings.TrimSpace(line) 426 } 427 428 func (b *MockTrustedAssetsBootloader) CommandLine(modeArg, systemArg, extraArgs string) (string, error) { 429 if b.CommandLineErr != nil { 430 return "", b.CommandLineErr 431 } 432 return glueCommandLine(modeArg, systemArg, b.StaticCommandLine, extraArgs), nil 433 } 434 435 func (b *MockTrustedAssetsBootloader) CandidateCommandLine(modeArg, systemArg, extraArgs string) (string, error) { 436 if b.CommandLineErr != nil { 437 return "", b.CommandLineErr 438 } 439 return glueCommandLine(modeArg, systemArg, b.CandidateStaticCommandLine, extraArgs), nil 440 } 441 442 func (b *MockTrustedAssetsBootloader) TrustedAssets() ([]string, error) { 443 b.TrustedAssetsCalls++ 444 return b.TrustedAssetsList, b.TrustedAssetsErr 445 } 446 447 func (b *MockTrustedAssetsBootloader) RecoveryBootChain(kernelPath string) ([]bootloader.BootFile, error) { 448 b.RecoveryBootChainCalls = append(b.RecoveryBootChainCalls, kernelPath) 449 return b.RecoveryBootChainList, b.RecoveryBootChainErr 450 } 451 452 func (b *MockTrustedAssetsBootloader) BootChain(runBl bootloader.Bootloader, kernelPath string) ([]bootloader.BootFile, error) { 453 b.BootChainRunBl = append(b.BootChainRunBl, runBl) 454 b.BootChainKernelPath = append(b.BootChainKernelPath, kernelPath) 455 return b.BootChainList, b.BootChainErr 456 }