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