github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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/overlord"
    29  	"github.com/snapcore/snapd/overlord/configstate"
    30  	"github.com/snapcore/snapd/overlord/configstate/config"
    31  	"github.com/snapcore/snapd/overlord/hookstate"
    32  	"github.com/snapcore/snapd/overlord/snapstate"
    33  	"github.com/snapcore/snapd/overlord/state"
    34  	"github.com/snapcore/snapd/snap"
    35  	"github.com/snapcore/snapd/testutil"
    36  )
    37  
    38  type tasksetsSuite struct {
    39  	state *state.State
    40  }
    41  
    42  var _ = Suite(&tasksetsSuite{})
    43  var _ = Suite(&configcoreHijackSuite{})
    44  
    45  func (s *tasksetsSuite) SetUpTest(c *C) {
    46  	s.state = state.New(nil)
    47  }
    48  
    49  var configureTests = []struct {
    50  	patch       map[string]interface{}
    51  	optional    bool
    52  	ignoreError bool
    53  	useDefaults bool
    54  }{{
    55  	patch:       nil,
    56  	optional:    true,
    57  	ignoreError: false,
    58  }, {
    59  	patch:       map[string]interface{}{},
    60  	optional:    true,
    61  	ignoreError: false,
    62  }, {
    63  	patch:       map[string]interface{}{"foo": "bar"},
    64  	optional:    false,
    65  	ignoreError: false,
    66  }, {
    67  	patch:       nil,
    68  	optional:    true,
    69  	ignoreError: true,
    70  }, {
    71  	patch:       nil,
    72  	optional:    true,
    73  	ignoreError: true,
    74  	useDefaults: true,
    75  }}
    76  
    77  func (s *tasksetsSuite) TestConfigureInstalled(c *C) {
    78  	s.state.Lock()
    79  	snapstate.Set(s.state, "test-snap", &snapstate.SnapState{
    80  		Sequence: []*snap.SideInfo{
    81  			{RealName: "test-snap", Revision: snap.R(1)},
    82  		},
    83  		Current:  snap.R(1),
    84  		Active:   true,
    85  		SnapType: "app",
    86  	})
    87  	s.state.Unlock()
    88  
    89  	for _, test := range configureTests {
    90  		var flags int
    91  		if test.ignoreError {
    92  			flags |= snapstate.IgnoreHookError
    93  		}
    94  		if test.useDefaults {
    95  			flags |= snapstate.UseConfigDefaults
    96  		}
    97  
    98  		s.state.Lock()
    99  		taskset := configstate.Configure(s.state, "test-snap", test.patch, flags)
   100  		s.state.Unlock()
   101  
   102  		tasks := taskset.Tasks()
   103  		c.Assert(tasks, HasLen, 1)
   104  		task := tasks[0]
   105  
   106  		c.Assert(task.Kind(), Equals, "run-hook")
   107  
   108  		summary := `Run configure hook of "test-snap" snap`
   109  		if test.optional {
   110  			summary += " if present"
   111  		}
   112  		c.Assert(task.Summary(), Equals, summary)
   113  
   114  		var hooksup hookstate.HookSetup
   115  		s.state.Lock()
   116  		err := task.Get("hook-setup", &hooksup)
   117  		s.state.Unlock()
   118  		c.Check(err, IsNil)
   119  
   120  		c.Assert(hooksup.Snap, Equals, "test-snap")
   121  		c.Assert(hooksup.Hook, Equals, "configure")
   122  		c.Assert(hooksup.Optional, Equals, test.optional)
   123  		c.Assert(hooksup.IgnoreError, Equals, test.ignoreError)
   124  		c.Assert(hooksup.Timeout, Equals, 5*time.Minute)
   125  
   126  		context, err := hookstate.NewContext(task, task.State(), &hooksup, nil, "")
   127  		c.Check(err, IsNil)
   128  		c.Check(context.InstanceName(), Equals, "test-snap")
   129  		c.Check(context.SnapRevision(), Equals, snap.Revision{})
   130  		c.Check(context.HookName(), Equals, "configure")
   131  
   132  		var patch map[string]interface{}
   133  		var useDefaults bool
   134  		context.Lock()
   135  		context.Get("use-defaults", &useDefaults)
   136  		err = context.Get("patch", &patch)
   137  		context.Unlock()
   138  		if len(test.patch) > 0 {
   139  			c.Check(err, IsNil)
   140  			c.Check(patch, DeepEquals, test.patch)
   141  		} else {
   142  			c.Check(err, Equals, state.ErrNoState)
   143  			c.Check(patch, IsNil)
   144  		}
   145  		c.Check(useDefaults, Equals, test.useDefaults)
   146  	}
   147  }
   148  
   149  func (s *tasksetsSuite) TestConfigureInstalledConflict(c *C) {
   150  	s.state.Lock()
   151  	defer s.state.Unlock()
   152  	snapstate.Set(s.state, "test-snap", &snapstate.SnapState{
   153  		Sequence: []*snap.SideInfo{
   154  			{RealName: "test-snap", Revision: snap.R(1)},
   155  		},
   156  		Current:  snap.R(1),
   157  		Active:   true,
   158  		SnapType: "app",
   159  	})
   160  
   161  	ts, err := snapstate.Disable(s.state, "test-snap")
   162  	c.Assert(err, IsNil)
   163  	chg := s.state.NewChange("other-change", "...")
   164  	chg.AddAll(ts)
   165  
   166  	patch := map[string]interface{}{"foo": "bar"}
   167  	_, err = configstate.ConfigureInstalled(s.state, "test-snap", patch, 0)
   168  	c.Check(err, ErrorMatches, `snap "test-snap" has "other-change" change in progress`)
   169  }
   170  
   171  func (s *tasksetsSuite) TestConfigureNotInstalled(c *C) {
   172  	patch := map[string]interface{}{"foo": "bar"}
   173  	s.state.Lock()
   174  	defer s.state.Unlock()
   175  
   176  	_, err := configstate.ConfigureInstalled(s.state, "test-snap", patch, 0)
   177  	c.Check(err, ErrorMatches, `snap "test-snap" is not installed`)
   178  
   179  	// core can be configure before being installed
   180  	_, err = configstate.ConfigureInstalled(s.state, "core", patch, 0)
   181  	c.Check(err, IsNil)
   182  }
   183  
   184  func (s *tasksetsSuite) TestConfigureDenyBases(c *C) {
   185  	patch := map[string]interface{}{"foo": "bar"}
   186  	s.state.Lock()
   187  	defer s.state.Unlock()
   188  	snapstate.Set(s.state, "test-base", &snapstate.SnapState{
   189  		Sequence: []*snap.SideInfo{
   190  			{RealName: "test-base", Revision: snap.R(1)},
   191  		},
   192  		Current:  snap.R(1),
   193  		Active:   true,
   194  		SnapType: "base",
   195  	})
   196  
   197  	_, err := configstate.ConfigureInstalled(s.state, "test-base", patch, 0)
   198  	c.Check(err, ErrorMatches, `cannot configure snap "test-base" because it is of type 'base'`)
   199  }
   200  
   201  func (s *tasksetsSuite) TestConfigureDenySnapd(c *C) {
   202  	patch := map[string]interface{}{"foo": "bar"}
   203  	s.state.Lock()
   204  	defer s.state.Unlock()
   205  	snapstate.Set(s.state, "snapd", &snapstate.SnapState{
   206  		Sequence: []*snap.SideInfo{
   207  			{RealName: "snapd", Revision: snap.R(1)},
   208  		},
   209  		Current:  snap.R(1),
   210  		Active:   true,
   211  		SnapType: "snapd",
   212  	})
   213  
   214  	_, err := configstate.ConfigureInstalled(s.state, "snapd", patch, 0)
   215  	c.Check(err, ErrorMatches, `cannot configure the "snapd" snap, please use "system" instead`)
   216  }
   217  
   218  type configcoreHijackSuite struct {
   219  	testutil.BaseTest
   220  
   221  	o     *overlord.Overlord
   222  	state *state.State
   223  }
   224  
   225  func (s *configcoreHijackSuite) SetUpTest(c *C) {
   226  	s.BaseTest.SetUpTest(c)
   227  	s.o = overlord.Mock()
   228  	s.state = s.o.State()
   229  	hookMgr, err := hookstate.Manager(s.state, s.o.TaskRunner())
   230  	c.Assert(err, IsNil)
   231  	s.o.AddManager(hookMgr)
   232  	r := configstate.MockConfigcoreExportExperimentalFlags(func(_ config.ConfGetter) error {
   233  		return nil
   234  	})
   235  	s.AddCleanup(r)
   236  
   237  	err = configstate.Init(s.state, hookMgr)
   238  	c.Assert(err, IsNil)
   239  	s.o.AddManager(s.o.TaskRunner())
   240  }
   241  
   242  type witnessManager struct {
   243  	state     *state.State
   244  	committed bool
   245  }
   246  
   247  func (wm *witnessManager) Ensure() error {
   248  	wm.state.Lock()
   249  	defer wm.state.Unlock()
   250  	t := config.NewTransaction(wm.state)
   251  	var witnessCfg bool
   252  	t.GetMaybe("core", "witness", &witnessCfg)
   253  	if witnessCfg {
   254  		wm.committed = true
   255  	}
   256  	return nil
   257  }
   258  
   259  func (s *configcoreHijackSuite) TestHijack(c *C) {
   260  	configcoreRan := false
   261  	witnessCfg := false
   262  	witnessConfigcoreRun := func(conf config.Conf) error {
   263  		// called with no state lock!
   264  		conf.State().Lock()
   265  		defer conf.State().Unlock()
   266  		err := conf.Get("core", "witness", &witnessCfg)
   267  		c.Assert(err, IsNil)
   268  		configcoreRan = true
   269  		return nil
   270  	}
   271  	r := configstate.MockConfigcoreRun(witnessConfigcoreRun)
   272  	defer r()
   273  
   274  	witnessMgr := &witnessManager{
   275  		state: s.state,
   276  	}
   277  	s.o.AddManager(witnessMgr)
   278  
   279  	s.state.Lock()
   280  	defer s.state.Unlock()
   281  
   282  	ts := configstate.Configure(s.state, "core", map[string]interface{}{
   283  		"witness": true,
   284  	}, 0)
   285  
   286  	chg := s.state.NewChange("configure-core", "configure core")
   287  	chg.AddAll(ts)
   288  
   289  	// this will be run by settle helper once no more Ensure are
   290  	// scheduled, the witnessMgr Ensure would not see the
   291  	// committed config unless an additional Ensure Loop is
   292  	// scheduled when committing the configuration
   293  	observe := func() {
   294  		c.Check(witnessCfg, Equals, true)
   295  		c.Check(witnessMgr.committed, Equals, true)
   296  	}
   297  
   298  	s.state.Unlock()
   299  	err := s.o.SettleObserveBeforeCleanups(5*time.Second, observe)
   300  	s.state.Lock()
   301  	c.Assert(err, IsNil)
   302  
   303  	c.Check(chg.Err(), IsNil)
   304  	c.Check(configcoreRan, Equals, true)
   305  }
   306  
   307  type miscSuite struct{}
   308  
   309  var _ = Suite(&miscSuite{})
   310  
   311  func (s *miscSuite) TestRemappingFuncs(c *C) {
   312  	// We don't change those.
   313  	c.Assert(configstate.RemapSnapFromRequest("foo"), Equals, "foo")
   314  	c.Assert(configstate.RemapSnapFromRequest("snapd"), Equals, "snapd")
   315  	c.Assert(configstate.RemapSnapFromRequest("core"), Equals, "core")
   316  	c.Assert(configstate.RemapSnapToResponse("foo"), Equals, "foo")
   317  	c.Assert(configstate.RemapSnapToResponse("snapd"), Equals, "snapd")
   318  
   319  	// This is the one we alter.
   320  	c.Assert(configstate.RemapSnapFromRequest("system"), Equals, "core")
   321  	c.Assert(configstate.RemapSnapToResponse("core"), Equals, "system")
   322  }
   323  
   324  type configcoreExportSuite struct {
   325  	o       *overlord.Overlord
   326  	state   *state.State
   327  	hookMgr *hookstate.HookManager
   328  }
   329  
   330  func (s *configcoreExportSuite) SetUpTest(c *C) {
   331  	s.o = overlord.Mock()
   332  	s.state = s.o.State()
   333  	hookMgr, err := hookstate.Manager(s.state, s.o.TaskRunner())
   334  	c.Assert(err, IsNil)
   335  	s.o.AddManager(hookMgr)
   336  	s.hookMgr = hookMgr
   337  }
   338  
   339  func (s *configcoreExportSuite) TestExportHappy(c *C) {
   340  	var calls int
   341  	var val string
   342  
   343  	tr := config.NewTransaction(s.state)
   344  	tr.Set("core", "experimental.key", "foobar")
   345  	tr.Commit()
   346  
   347  	r := configstate.MockConfigcoreExportExperimentalFlags(func(conf config.ConfGetter) error {
   348  		calls++
   349  		err := conf.Get("core", "experimental.keys", &val)
   350  		c.Assert(err, IsNil)
   351  		return nil
   352  	})
   353  	defer r()
   354  	err := configstate.Init(s.state, s.hookMgr)
   355  	c.Assert(err, IsNil)
   356  	c.Assert(calls, Equals, 1)
   357  	c.Assert(val, Equals, "foobar")
   358  }
   359  
   360  func (s *configcoreExportSuite) TestExportErr(c *C) {
   361  	var calls int
   362  
   363  	r := configstate.MockConfigcoreExportExperimentalFlags(func(conf config.ConfGetter) error {
   364  		calls++
   365  		return fmt.Errorf("bad bad")
   366  	})
   367  	defer r()
   368  	err := configstate.Init(s.state, s.hookMgr)
   369  	c.Assert(err, ErrorMatches, "cannot export experimental config flags: bad bad")
   370  	c.Assert(calls, Equals, 1)
   371  }