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