github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/configstate/handler_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  	"io/ioutil"
    24  	"path/filepath"
    25  	"testing"
    26  	"time"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/asserts"
    31  	"github.com/snapcore/snapd/asserts/assertstest"
    32  	"github.com/snapcore/snapd/dirs"
    33  	"github.com/snapcore/snapd/overlord"
    34  	"github.com/snapcore/snapd/overlord/configstate"
    35  	"github.com/snapcore/snapd/overlord/configstate/config"
    36  	"github.com/snapcore/snapd/overlord/hookstate"
    37  	"github.com/snapcore/snapd/overlord/hookstate/hooktest"
    38  	"github.com/snapcore/snapd/overlord/snapstate"
    39  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    40  	"github.com/snapcore/snapd/overlord/state"
    41  	"github.com/snapcore/snapd/release"
    42  	"github.com/snapcore/snapd/snap"
    43  	"github.com/snapcore/snapd/snap/snaptest"
    44  	"github.com/snapcore/snapd/testutil"
    45  )
    46  
    47  func TestConfigState(t *testing.T) { TestingT(t) }
    48  
    49  type configureHandlerSuite struct {
    50  	state   *state.State
    51  	context *hookstate.Context
    52  	handler hookstate.Handler
    53  	restore func()
    54  }
    55  
    56  var _ = Suite(&configureHandlerSuite{})
    57  
    58  func (s *configureHandlerSuite) SetUpTest(c *C) {
    59  	dirs.SetRootDir(c.MkDir())
    60  
    61  	s.state = state.New(nil)
    62  	s.state.Lock()
    63  	defer s.state.Unlock()
    64  
    65  	coreSnapYaml := `name: core
    66  version: 1.0
    67  type: os
    68  `
    69  	snaptest.MockSnap(c, coreSnapYaml, &snap.SideInfo{
    70  		RealName: "core",
    71  		Revision: snap.R(1),
    72  	})
    73  	snapstate.Set(s.state, "core", &snapstate.SnapState{
    74  		Active: true,
    75  		Sequence: []*snap.SideInfo{
    76  			{RealName: "core", Revision: snap.R(1), SnapID: "core-snap-id"},
    77  		},
    78  		Current:  snap.R(1),
    79  		SnapType: "os",
    80  	})
    81  
    82  	s.restore = snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})
    83  
    84  	task := s.state.NewTask("test-task", "my test task")
    85  	setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"}
    86  
    87  	var err error
    88  	s.context, err = hookstate.NewContext(task, task.State(), setup, hooktest.NewMockHandler(), "")
    89  	c.Assert(err, IsNil)
    90  
    91  	s.handler = configstate.NewConfigureHandler(s.context)
    92  }
    93  
    94  func (s *configureHandlerSuite) TearDownTest(c *C) {
    95  	s.restore()
    96  	dirs.SetRootDir("/")
    97  }
    98  
    99  func (s *configureHandlerSuite) TestBeforeInitializesTransaction(c *C) {
   100  	// Initialize context
   101  	s.context.Lock()
   102  	s.context.Set("patch", map[string]interface{}{
   103  		"foo": "bar",
   104  	})
   105  	s.context.Unlock()
   106  
   107  	c.Check(s.handler.Before(), IsNil)
   108  
   109  	s.context.Lock()
   110  	tr := configstate.ContextTransaction(s.context)
   111  	s.context.Unlock()
   112  
   113  	var value string
   114  	c.Check(tr.Get("test-snap", "foo", &value), IsNil)
   115  	c.Check(value, Equals, "bar")
   116  }
   117  
   118  func makeModel(override map[string]interface{}) *asserts.Model {
   119  	model := map[string]interface{}{
   120  		"type":         "model",
   121  		"authority-id": "brand",
   122  		"series":       "16",
   123  		"brand-id":     "brand",
   124  		"model":        "baz-3000",
   125  		"architecture": "armhf",
   126  		"gadget":       "brand-gadget",
   127  		"kernel":       "kernel",
   128  		"timestamp":    "2018-01-01T08:00:00+00:00",
   129  	}
   130  	return assertstest.FakeAssertion(model, override).(*asserts.Model)
   131  }
   132  
   133  func (s *configureHandlerSuite) TestBeforeInitializesTransactionUseDefaults(c *C) {
   134  	r := release.MockOnClassic(false)
   135  	defer r()
   136  
   137  	const mockGadgetSnapYaml = `
   138  name: canonical-pc
   139  type: gadget
   140  `
   141  	var mockGadgetYaml = []byte(`
   142  defaults:
   143    testsnapidididididididididididid:
   144        bar: baz
   145        num: 1.305
   146  
   147  volumes:
   148      volume-id:
   149          bootloader: grub
   150  `)
   151  
   152  	info := snaptest.MockSnap(c, mockGadgetSnapYaml, &snap.SideInfo{Revision: snap.R(1)})
   153  	err := ioutil.WriteFile(filepath.Join(info.MountDir(), "meta", "gadget.yaml"), mockGadgetYaml, 0644)
   154  	c.Assert(err, IsNil)
   155  
   156  	s.state.Lock()
   157  	snapstate.Set(s.state, "canonical-pc", &snapstate.SnapState{
   158  		Active: true,
   159  		Sequence: []*snap.SideInfo{
   160  			{RealName: "canonical-pc", Revision: snap.R(1)},
   161  		},
   162  		Current:  snap.R(1),
   163  		SnapType: "gadget",
   164  	})
   165  
   166  	r = snapstatetest.MockDeviceModel(makeModel(map[string]interface{}{
   167  		"gadget": "canonical-pc",
   168  	}))
   169  	defer r()
   170  
   171  	const mockTestSnapYaml = `
   172  name: test-snap
   173  hooks:
   174      configure:
   175  `
   176  
   177  	snaptest.MockSnap(c, mockTestSnapYaml, &snap.SideInfo{Revision: snap.R(11)})
   178  	snapstate.Set(s.state, "test-snap", &snapstate.SnapState{
   179  		Active: true,
   180  		Sequence: []*snap.SideInfo{
   181  			{RealName: "test-snap", Revision: snap.R(11), SnapID: "testsnapidididididididididididid"},
   182  		},
   183  		Current:  snap.R(11),
   184  		SnapType: "app",
   185  	})
   186  	s.state.Unlock()
   187  
   188  	// Initialize context
   189  	s.context.Lock()
   190  	s.context.Set("use-defaults", true)
   191  	s.context.Unlock()
   192  
   193  	c.Assert(s.handler.Before(), IsNil)
   194  
   195  	s.context.Lock()
   196  	tr := configstate.ContextTransaction(s.context)
   197  	s.context.Unlock()
   198  
   199  	var value string
   200  	c.Check(tr.Get("test-snap", "bar", &value), IsNil)
   201  	c.Check(value, Equals, "baz")
   202  	var fl float64
   203  	c.Check(tr.Get("test-snap", "num", &fl), IsNil)
   204  	c.Check(fl, Equals, 1.305)
   205  }
   206  
   207  func (s *configureHandlerSuite) TestBeforeUseDefaultsMissingHook(c *C) {
   208  	r := release.MockOnClassic(false)
   209  	defer r()
   210  
   211  	const mockGadgetSnapYaml = `
   212  name: canonical-pc
   213  type: gadget
   214  `
   215  	var mockGadgetYaml = []byte(`
   216  defaults:
   217    testsnapidididididididididididid:
   218        bar: baz
   219        num: 1.305
   220  
   221  volumes:
   222      volume-id:
   223          bootloader: grub
   224  `)
   225  
   226  	info := snaptest.MockSnap(c, mockGadgetSnapYaml, &snap.SideInfo{Revision: snap.R(1)})
   227  	err := ioutil.WriteFile(filepath.Join(info.MountDir(), "meta", "gadget.yaml"), mockGadgetYaml, 0644)
   228  	c.Assert(err, IsNil)
   229  
   230  	s.state.Lock()
   231  	snapstate.Set(s.state, "canonical-pc", &snapstate.SnapState{
   232  		Active: true,
   233  		Sequence: []*snap.SideInfo{
   234  			{RealName: "canonical-pc", Revision: snap.R(1)},
   235  		},
   236  		Current:  snap.R(1),
   237  		SnapType: "gadget",
   238  	})
   239  
   240  	r = snapstatetest.MockDeviceModel(makeModel(map[string]interface{}{
   241  		"gadget": "canonical-pc",
   242  	}))
   243  	defer r()
   244  
   245  	snapstate.Set(s.state, "test-snap", &snapstate.SnapState{
   246  		Active: true,
   247  		Sequence: []*snap.SideInfo{
   248  			{RealName: "test-snap", Revision: snap.R(11), SnapID: "testsnapidididididididididididid"},
   249  		},
   250  		Current:  snap.R(11),
   251  		SnapType: "app",
   252  	})
   253  	s.state.Unlock()
   254  
   255  	// Initialize context
   256  	s.context.Lock()
   257  	s.context.Set("use-defaults", true)
   258  	s.context.Unlock()
   259  
   260  	err = s.handler.Before()
   261  	c.Check(err, ErrorMatches, `cannot apply gadget config defaults for snap "test-snap", no configure hook`)
   262  }
   263  
   264  type configcoreHandlerSuite struct {
   265  	testutil.BaseTest
   266  
   267  	o     *overlord.Overlord
   268  	state *state.State
   269  }
   270  
   271  var _ = Suite(&configcoreHandlerSuite{})
   272  
   273  func (s *configcoreHandlerSuite) SetUpTest(c *C) {
   274  	s.BaseTest.SetUpTest(c)
   275  
   276  	dirs.SetRootDir(c.MkDir())
   277  	s.AddCleanup(func() { dirs.SetRootDir("/") })
   278  
   279  	s.o = overlord.Mock()
   280  	s.state = s.o.State()
   281  
   282  	restore := snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})
   283  	s.AddCleanup(restore)
   284  
   285  	hookMgr, err := hookstate.Manager(s.state, s.o.TaskRunner())
   286  	c.Assert(err, IsNil)
   287  	s.o.AddManager(hookMgr)
   288  	r := configstate.MockConfigcoreExportExperimentalFlags(func(_ config.ConfGetter) error {
   289  		return nil
   290  	})
   291  	s.AddCleanup(r)
   292  
   293  	err = configstate.Init(s.state, hookMgr)
   294  
   295  	c.Assert(err, IsNil)
   296  	s.o.AddManager(s.o.TaskRunner())
   297  
   298  	r = snapstatetest.MockDeviceModel(makeModel(map[string]interface{}{
   299  		"gadget": "canonical-pc",
   300  	}))
   301  	s.AddCleanup(r)
   302  
   303  	s.state.Lock()
   304  	defer s.state.Unlock()
   305  
   306  	s.state.Set("seeded", true)
   307  	snapstate.Set(s.state, "canonical-pc", &snapstate.SnapState{
   308  		Active: true,
   309  		Sequence: []*snap.SideInfo{
   310  			{RealName: "canonical-pc", Revision: snap.R(1)},
   311  		},
   312  		Current:  snap.R(1),
   313  		SnapType: "gadget",
   314  	})
   315  }
   316  
   317  const mockGadgetSnapYaml = `
   318  name: canonical-pc
   319  type: gadget
   320  `
   321  
   322  func (s *configcoreHandlerSuite) TestRunsWhenSnapdOnly(c *C) {
   323  	r := release.MockOnClassic(false)
   324  	defer r()
   325  
   326  	var mockGadgetYaml = `
   327  defaults:
   328    system:
   329        foo: bar
   330  
   331  volumes:
   332      volume-id:
   333          bootloader: grub
   334  `
   335  	s.state.Lock()
   336  	defer s.state.Unlock()
   337  
   338  	ts := configstate.Configure(s.state, "core", nil, snapstate.UseConfigDefaults)
   339  	chg := s.state.NewChange("configure-core", "configure core")
   340  	chg.AddAll(ts)
   341  
   342  	snaptest.MockSnapWithFiles(c, mockGadgetSnapYaml, &snap.SideInfo{Revision: snap.R(1)}, [][]string{
   343  		{"meta/gadget.yaml", mockGadgetYaml},
   344  	})
   345  
   346  	snapstate.Set(s.state, "snapd", &snapstate.SnapState{
   347  		Active: true,
   348  		Sequence: []*snap.SideInfo{
   349  			{RealName: "snapd", Revision: snap.R(1)},
   350  		},
   351  		Current:  snap.R(1),
   352  		SnapType: "snapd",
   353  	})
   354  
   355  	witnessConfigcoreRun := func(conf config.Conf) error {
   356  		// called with no state lock!
   357  		conf.State().Lock()
   358  		defer conf.State().Unlock()
   359  		var val string
   360  		err := conf.Get("core", "foo", &val)
   361  		c.Assert(err, IsNil)
   362  		c.Check(val, Equals, "bar")
   363  		return nil
   364  	}
   365  	r = configstate.MockConfigcoreRun(witnessConfigcoreRun)
   366  	defer r()
   367  
   368  	s.state.Unlock()
   369  	err := s.o.Settle(5 * time.Second)
   370  	s.state.Lock()
   371  	// Initialize context
   372  	c.Assert(err, IsNil)
   373  
   374  	tr := config.NewTransaction(s.state)
   375  	var foo string
   376  	err = tr.Get("core", "foo", &foo)
   377  	c.Assert(err, IsNil)
   378  	c.Check(foo, Equals, "bar")
   379  }
   380  
   381  func (s *configcoreHandlerSuite) TestRunsWhenCoreOnly(c *C) {
   382  	r := release.MockOnClassic(false)
   383  	defer r()
   384  
   385  	var mockGadgetYaml = `
   386  defaults:
   387    system:
   388        foo: bar
   389  
   390  volumes:
   391      volume-id:
   392          bootloader: grub
   393  `
   394  	s.state.Lock()
   395  	defer s.state.Unlock()
   396  
   397  	ts := configstate.Configure(s.state, "core", nil, snapstate.UseConfigDefaults)
   398  	chg := s.state.NewChange("configure-core", "configure core")
   399  	chg.AddAll(ts)
   400  
   401  	snaptest.MockSnapWithFiles(c, mockGadgetSnapYaml, &snap.SideInfo{Revision: snap.R(1)}, [][]string{
   402  		{"meta/gadget.yaml", mockGadgetYaml},
   403  	})
   404  
   405  	snapstate.Set(s.state, "core", &snapstate.SnapState{
   406  		Active: true,
   407  		Sequence: []*snap.SideInfo{
   408  			{RealName: "core", Revision: snap.R(1)},
   409  		},
   410  		Current:  snap.R(1),
   411  		SnapType: "os",
   412  	})
   413  
   414  	witnessConfigcoreRun := func(conf config.Conf) error {
   415  		// called with no state lock!
   416  		conf.State().Lock()
   417  		defer conf.State().Unlock()
   418  		var val string
   419  		err := conf.Get("core", "foo", &val)
   420  		c.Assert(err, IsNil)
   421  		c.Check(val, Equals, "bar")
   422  		return nil
   423  	}
   424  	r = configstate.MockConfigcoreRun(witnessConfigcoreRun)
   425  	defer r()
   426  
   427  	s.state.Unlock()
   428  	err := s.o.Settle(5 * time.Second)
   429  	s.state.Lock()
   430  	// Initialize context
   431  	c.Assert(err, IsNil)
   432  
   433  	tr := config.NewTransaction(s.state)
   434  	var foo string
   435  	err = tr.Get("core", "foo", &foo)
   436  	c.Assert(err, IsNil)
   437  	c.Check(foo, Equals, "bar")
   438  }