github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/overlord/configstate/configstate_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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 configstate_test
    21  
    22  import (
    23  	"fmt"
    24  	"time"
    25  
    26  	. "gopkg.in/check.v1"
    27  
    28  	"github.com/snapcore/snapd/dirs"
    29  	"github.com/snapcore/snapd/features"
    30  	"github.com/snapcore/snapd/gadget"
    31  	"github.com/snapcore/snapd/overlord"
    32  	"github.com/snapcore/snapd/overlord/configstate"
    33  	"github.com/snapcore/snapd/overlord/configstate/config"
    34  	"github.com/snapcore/snapd/overlord/hookstate"
    35  	"github.com/snapcore/snapd/overlord/snapstate"
    36  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    37  	"github.com/snapcore/snapd/overlord/state"
    38  	"github.com/snapcore/snapd/snap"
    39  	"github.com/snapcore/snapd/sysconfig"
    40  	"github.com/snapcore/snapd/testutil"
    41  )
    42  
    43  type tasksetsSuite struct {
    44  	state *state.State
    45  }
    46  
    47  var (
    48  	_ = Suite(&tasksetsSuite{})
    49  	_ = Suite(&configcoreHijackSuite{})
    50  	_ = Suite(&miscSuite{})
    51  	_ = Suite(&earlyConfigSuite{})
    52  )
    53  
    54  func (s *tasksetsSuite) SetUpTest(c *C) {
    55  	s.state = state.New(nil)
    56  }
    57  
    58  var configureTests = []struct {
    59  	patch       map[string]interface{}
    60  	optional    bool
    61  	ignoreError bool
    62  	useDefaults bool
    63  }{{
    64  	patch:       nil,
    65  	optional:    true,
    66  	ignoreError: false,
    67  }, {
    68  	patch:       map[string]interface{}{},
    69  	optional:    true,
    70  	ignoreError: false,
    71  }, {
    72  	patch:       map[string]interface{}{"foo": "bar"},
    73  	optional:    false,
    74  	ignoreError: false,
    75  }, {
    76  	patch:       nil,
    77  	optional:    true,
    78  	ignoreError: true,
    79  }, {
    80  	patch:       nil,
    81  	optional:    true,
    82  	ignoreError: true,
    83  	useDefaults: true,
    84  }}
    85  
    86  func (s *tasksetsSuite) TestConfigureInstalled(c *C) {
    87  	s.state.Lock()
    88  	snapstate.Set(s.state, "test-snap", &snapstate.SnapState{
    89  		Sequence: []*snap.SideInfo{
    90  			{RealName: "test-snap", Revision: snap.R(1)},
    91  		},
    92  		Current:  snap.R(1),
    93  		Active:   true,
    94  		SnapType: "app",
    95  	})
    96  	s.state.Unlock()
    97  
    98  	for _, test := range configureTests {
    99  		var flags int
   100  		if test.ignoreError {
   101  			flags |= snapstate.IgnoreHookError
   102  		}
   103  		if test.useDefaults {
   104  			flags |= snapstate.UseConfigDefaults
   105  		}
   106  
   107  		s.state.Lock()
   108  		taskset := configstate.Configure(s.state, "test-snap", test.patch, flags)
   109  		s.state.Unlock()
   110  
   111  		tasks := taskset.Tasks()
   112  		c.Assert(tasks, HasLen, 1)
   113  		task := tasks[0]
   114  
   115  		c.Assert(task.Kind(), Equals, "run-hook")
   116  
   117  		summary := `Run configure hook of "test-snap" snap`
   118  		if test.optional {
   119  			summary += " if present"
   120  		}
   121  		c.Assert(task.Summary(), Equals, summary)
   122  
   123  		var hooksup hookstate.HookSetup
   124  		s.state.Lock()
   125  		err := task.Get("hook-setup", &hooksup)
   126  		s.state.Unlock()
   127  		c.Check(err, IsNil)
   128  
   129  		c.Assert(hooksup.Snap, Equals, "test-snap")
   130  		c.Assert(hooksup.Hook, Equals, "configure")
   131  		c.Assert(hooksup.Optional, Equals, test.optional)
   132  		c.Assert(hooksup.IgnoreError, Equals, test.ignoreError)
   133  		c.Assert(hooksup.Timeout, Equals, 5*time.Minute)
   134  
   135  		context, err := hookstate.NewContext(task, task.State(), &hooksup, nil, "")
   136  		c.Check(err, IsNil)
   137  		c.Check(context.InstanceName(), Equals, "test-snap")
   138  		c.Check(context.SnapRevision(), Equals, snap.Revision{})
   139  		c.Check(context.HookName(), Equals, "configure")
   140  
   141  		var patch map[string]interface{}
   142  		var useDefaults bool
   143  		context.Lock()
   144  		context.Get("use-defaults", &useDefaults)
   145  		err = context.Get("patch", &patch)
   146  		context.Unlock()
   147  		if len(test.patch) > 0 {
   148  			c.Check(err, IsNil)
   149  			c.Check(patch, DeepEquals, test.patch)
   150  		} else {
   151  			c.Check(err, Equals, state.ErrNoState)
   152  			c.Check(patch, IsNil)
   153  		}
   154  		c.Check(useDefaults, Equals, test.useDefaults)
   155  	}
   156  }
   157  
   158  func (s *tasksetsSuite) TestConfigureInstalledConflict(c *C) {
   159  	s.state.Lock()
   160  	defer s.state.Unlock()
   161  	snapstate.Set(s.state, "test-snap", &snapstate.SnapState{
   162  		Sequence: []*snap.SideInfo{
   163  			{RealName: "test-snap", Revision: snap.R(1)},
   164  		},
   165  		Current:  snap.R(1),
   166  		Active:   true,
   167  		SnapType: "app",
   168  	})
   169  
   170  	ts, err := snapstate.Disable(s.state, "test-snap")
   171  	c.Assert(err, IsNil)
   172  	chg := s.state.NewChange("other-change", "...")
   173  	chg.AddAll(ts)
   174  
   175  	patch := map[string]interface{}{"foo": "bar"}
   176  	_, err = configstate.ConfigureInstalled(s.state, "test-snap", patch, 0)
   177  	c.Check(err, ErrorMatches, `snap "test-snap" has "other-change" change in progress`)
   178  }
   179  
   180  func (s *tasksetsSuite) TestConfigureNotInstalled(c *C) {
   181  	patch := map[string]interface{}{"foo": "bar"}
   182  	s.state.Lock()
   183  	defer s.state.Unlock()
   184  
   185  	_, err := configstate.ConfigureInstalled(s.state, "test-snap", patch, 0)
   186  	c.Check(err, ErrorMatches, `snap "test-snap" is not installed`)
   187  
   188  	// core can be configure before being installed
   189  	_, err = configstate.ConfigureInstalled(s.state, "core", patch, 0)
   190  	c.Check(err, IsNil)
   191  }
   192  
   193  func (s *tasksetsSuite) TestConfigureDenyBases(c *C) {
   194  	patch := map[string]interface{}{"foo": "bar"}
   195  	s.state.Lock()
   196  	defer s.state.Unlock()
   197  	snapstate.Set(s.state, "test-base", &snapstate.SnapState{
   198  		Sequence: []*snap.SideInfo{
   199  			{RealName: "test-base", Revision: snap.R(1)},
   200  		},
   201  		Current:  snap.R(1),
   202  		Active:   true,
   203  		SnapType: "base",
   204  	})
   205  
   206  	_, err := configstate.ConfigureInstalled(s.state, "test-base", patch, 0)
   207  	c.Check(err, ErrorMatches, `cannot configure snap "test-base" because it is of type 'base'`)
   208  }
   209  
   210  func (s *tasksetsSuite) TestConfigureDenySnapd(c *C) {
   211  	patch := map[string]interface{}{"foo": "bar"}
   212  	s.state.Lock()
   213  	defer s.state.Unlock()
   214  	snapstate.Set(s.state, "snapd", &snapstate.SnapState{
   215  		Sequence: []*snap.SideInfo{
   216  			{RealName: "snapd", Revision: snap.R(1)},
   217  		},
   218  		Current:  snap.R(1),
   219  		Active:   true,
   220  		SnapType: "snapd",
   221  	})
   222  
   223  	_, err := configstate.ConfigureInstalled(s.state, "snapd", patch, 0)
   224  	c.Check(err, ErrorMatches, `cannot configure the "snapd" snap, please use "system" instead`)
   225  }
   226  
   227  type configcoreHijackSuite struct {
   228  	testutil.BaseTest
   229  
   230  	o     *overlord.Overlord
   231  	state *state.State
   232  }
   233  
   234  func (s *configcoreHijackSuite) SetUpTest(c *C) {
   235  	s.BaseTest.SetUpTest(c)
   236  	s.o = overlord.Mock()
   237  	s.state = s.o.State()
   238  	hookMgr, err := hookstate.Manager(s.state, s.o.TaskRunner())
   239  	c.Assert(err, IsNil)
   240  	s.o.AddManager(hookMgr)
   241  	r := configstate.MockConfigcoreExportExperimentalFlags(func(_ config.ConfGetter) error {
   242  		return nil
   243  	})
   244  	s.AddCleanup(r)
   245  
   246  	err = configstate.Init(s.state, hookMgr)
   247  	c.Assert(err, IsNil)
   248  	s.o.AddManager(s.o.TaskRunner())
   249  
   250  	r = snapstatetest.MockDeviceModel(makeModel(nil))
   251  	s.AddCleanup(r)
   252  
   253  }
   254  
   255  type witnessManager struct {
   256  	state     *state.State
   257  	committed bool
   258  }
   259  
   260  func (wm *witnessManager) Ensure() error {
   261  	wm.state.Lock()
   262  	defer wm.state.Unlock()
   263  	t := config.NewTransaction(wm.state)
   264  	var witnessCfg bool
   265  	t.GetMaybe("core", "witness", &witnessCfg)
   266  	if witnessCfg {
   267  		wm.committed = true
   268  	}
   269  	return nil
   270  }
   271  
   272  func (s *configcoreHijackSuite) TestHijack(c *C) {
   273  	configcoreRan := false
   274  	witnessCfg := false
   275  	witnessConfigcoreRun := func(dev sysconfig.Device, conf config.Conf) error {
   276  		// called with no state lock!
   277  		conf.State().Lock()
   278  		defer conf.State().Unlock()
   279  		err := conf.Get("core", "witness", &witnessCfg)
   280  		c.Assert(err, IsNil)
   281  		configcoreRan = true
   282  		return nil
   283  	}
   284  	r := configstate.MockConfigcoreRun(witnessConfigcoreRun)
   285  	defer r()
   286  
   287  	witnessMgr := &witnessManager{
   288  		state: s.state,
   289  	}
   290  	s.o.AddManager(witnessMgr)
   291  
   292  	s.state.Lock()
   293  	defer s.state.Unlock()
   294  
   295  	ts := configstate.Configure(s.state, "core", map[string]interface{}{
   296  		"witness": true,
   297  	}, 0)
   298  
   299  	chg := s.state.NewChange("configure-core", "configure core")
   300  	chg.AddAll(ts)
   301  
   302  	// this will be run by settle helper once no more Ensure are
   303  	// scheduled, the witnessMgr Ensure would not see the
   304  	// committed config unless an additional Ensure Loop is
   305  	// scheduled when committing the configuration
   306  	observe := func() {
   307  		c.Check(witnessCfg, Equals, true)
   308  		c.Check(witnessMgr.committed, Equals, true)
   309  	}
   310  
   311  	s.state.Unlock()
   312  	err := s.o.SettleObserveBeforeCleanups(5*time.Second, observe)
   313  	s.state.Lock()
   314  	c.Assert(err, IsNil)
   315  
   316  	c.Check(chg.Err(), IsNil)
   317  	c.Check(configcoreRan, Equals, true)
   318  }
   319  
   320  type miscSuite struct{}
   321  
   322  func (s *miscSuite) TestRemappingFuncs(c *C) {
   323  	// We don't change those.
   324  	c.Assert(configstate.RemapSnapFromRequest("foo"), Equals, "foo")
   325  	c.Assert(configstate.RemapSnapFromRequest("snapd"), Equals, "snapd")
   326  	c.Assert(configstate.RemapSnapFromRequest("core"), Equals, "core")
   327  	c.Assert(configstate.RemapSnapToResponse("foo"), Equals, "foo")
   328  	c.Assert(configstate.RemapSnapToResponse("snapd"), Equals, "snapd")
   329  
   330  	// This is the one we alter.
   331  	c.Assert(configstate.RemapSnapFromRequest("system"), Equals, "core")
   332  	c.Assert(configstate.RemapSnapToResponse("core"), Equals, "system")
   333  }
   334  
   335  type earlyConfigSuite struct {
   336  	testutil.BaseTest
   337  
   338  	state   *state.State
   339  	rootdir string
   340  }
   341  
   342  func (s *earlyConfigSuite) SetUpTest(c *C) {
   343  	s.BaseTest.SetUpTest(c)
   344  
   345  	s.state = state.New(nil)
   346  
   347  	s.rootdir = c.MkDir()
   348  
   349  	dirs.SetRootDir(s.rootdir)
   350  	s.AddCleanup(func() { dirs.SetRootDir("") })
   351  }
   352  
   353  func (s *earlyConfigSuite) sysConfig(c *C) {
   354  	t := config.NewTransaction(s.state)
   355  	err := t.Set("core", "experimental.parallel-instances", true)
   356  	c.Assert(err, IsNil)
   357  	err = t.Set("core", "experimental.user-daemons", true)
   358  	c.Assert(err, IsNil)
   359  	t.Commit()
   360  }
   361  
   362  func (s *earlyConfigSuite) TestEarlyConfigSeeded(c *C) {
   363  	s.state.Lock()
   364  	defer s.state.Unlock()
   365  	s.sysConfig(c)
   366  	s.state.Set("seeded", true)
   367  
   368  	err := configstate.EarlyConfig(s.state, nil)
   369  	c.Assert(err, IsNil)
   370  	// parallel-instances was exported
   371  	c.Check(features.ParallelInstances.IsEnabled(), Equals, true)
   372  }
   373  
   374  func (s *earlyConfigSuite) TestEarlyConfigSeededErr(c *C) {
   375  	r := configstate.MockConfigcoreExportExperimentalFlags(func(conf config.ConfGetter) error {
   376  		return fmt.Errorf("bad bad")
   377  	})
   378  	defer r()
   379  
   380  	s.state.Lock()
   381  	defer s.state.Unlock()
   382  	s.state.Set("seeded", true)
   383  
   384  	err := configstate.EarlyConfig(s.state, nil)
   385  	c.Assert(err, ErrorMatches, "cannot export experimental config flags: bad bad")
   386  }
   387  
   388  func (s *earlyConfigSuite) TestEarlyConfigSysConfigured(c *C) {
   389  	s.state.Lock()
   390  	defer s.state.Unlock()
   391  	s.sysConfig(c)
   392  
   393  	preloadGadget := func() (sysconfig.Device, *gadget.Info, error) {
   394  		panic("unexpected")
   395  	}
   396  
   397  	err := configstate.EarlyConfig(s.state, preloadGadget)
   398  	c.Assert(err, IsNil)
   399  	// parallel-instances was exported
   400  	c.Check(features.ParallelInstances.IsEnabled(), Equals, true)
   401  }
   402  
   403  const preloadedGadgetYaml = `
   404  defaults:
   405     system:
   406       experimental:
   407         parallel-instances: true
   408         user-daemons: true
   409       services:
   410         ssh.disable
   411  `
   412  
   413  func (s *earlyConfigSuite) TestEarlyConfigFromGadget(c *C) {
   414  	s.state.Lock()
   415  	defer s.state.Unlock()
   416  
   417  	preloadGadget := func() (sysconfig.Device, *gadget.Info, error) {
   418  		gi, err := gadget.InfoFromGadgetYaml([]byte(preloadedGadgetYaml), nil)
   419  		if err != nil {
   420  			return nil, nil, err
   421  		}
   422  		dev := &snapstatetest.TrivialDeviceContext{}
   423  		return dev, gi, nil
   424  	}
   425  
   426  	err := configstate.EarlyConfig(s.state, preloadGadget)
   427  	c.Assert(err, IsNil)
   428  
   429  	// parallel-instances was exported
   430  	c.Check(features.ParallelInstances.IsEnabled(), Equals, true)
   431  	tr := config.NewTransaction(s.state)
   432  	ok, err := features.Flag(tr, features.ParallelInstances)
   433  	c.Assert(err, IsNil)
   434  	c.Check(ok, Equals, true)
   435  	ok, err = features.Flag(tr, features.UserDaemons)
   436  	c.Assert(err, IsNil)
   437  	c.Check(ok, Equals, true)
   438  	var serviceCfg map[string]interface{}
   439  	err = tr.Get("core", "services", &serviceCfg)
   440  	// nothing of this was set
   441  	c.Assert(config.IsNoOption(err), Equals, true)
   442  }
   443  
   444  func (s *earlyConfigSuite) TestEarlyConfigFromGadgetErr(c *C) {
   445  	defer configstate.MockConfigcoreEarly(func(sysconfig.Device, config.Conf, map[string]interface{}) error {
   446  		return fmt.Errorf("boom")
   447  	})()
   448  
   449  	s.state.Lock()
   450  	defer s.state.Unlock()
   451  
   452  	preloadGadget := func() (sysconfig.Device, *gadget.Info, error) {
   453  		return nil, &gadget.Info{}, nil
   454  	}
   455  
   456  	err := configstate.EarlyConfig(s.state, preloadGadget)
   457  	c.Assert(err, ErrorMatches, "boom")
   458  }
   459  
   460  func (s *earlyConfigSuite) TestEarlyConfigPreloadGadgetErr(c *C) {
   461  	s.state.Lock()
   462  	defer s.state.Unlock()
   463  
   464  	preloadGadget := func() (sysconfig.Device, *gadget.Info, error) {
   465  		return nil, nil, fmt.Errorf("cannot load gadget")
   466  	}
   467  
   468  	err := configstate.EarlyConfig(s.state, preloadGadget)
   469  	c.Assert(err, ErrorMatches, "cannot load gadget")
   470  }
   471  
   472  func (s *earlyConfigSuite) TestEarlyConfigNoGadget(c *C) {
   473  	s.state.Lock()
   474  	defer s.state.Unlock()
   475  
   476  	preloadGadget := func() (sysconfig.Device, *gadget.Info, error) {
   477  		return nil, nil, state.ErrNoState
   478  	}
   479  
   480  	err := configstate.EarlyConfig(s.state, preloadGadget)
   481  	c.Assert(err, IsNil)
   482  
   483  	sysCfg, err := config.GetSnapConfig(s.state, "core")
   484  	c.Assert(err, IsNil)
   485  	c.Check(sysCfg, IsNil)
   486  }