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