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  }