github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/devicestate/handlers_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 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 devicestate_test
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  
    26  	. "gopkg.in/check.v1"
    27  
    28  	"github.com/snapcore/snapd/asserts"
    29  	"github.com/snapcore/snapd/overlord/assertstate"
    30  	"github.com/snapcore/snapd/overlord/auth"
    31  	"github.com/snapcore/snapd/overlord/devicestate"
    32  	"github.com/snapcore/snapd/overlord/devicestate/devicestatetest"
    33  	"github.com/snapcore/snapd/overlord/snapstate"
    34  	"github.com/snapcore/snapd/overlord/state"
    35  	"github.com/snapcore/snapd/overlord/storecontext"
    36  	"github.com/snapcore/snapd/snap"
    37  )
    38  
    39  // TODO: should we move this into a new handlers suite?
    40  func (s *deviceMgrSuite) TestSetModelHandlerNewRevision(c *C) {
    41  	s.state.Lock()
    42  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
    43  		Brand: "canonical",
    44  		Model: "pc-model",
    45  	})
    46  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
    47  		"architecture":   "amd64",
    48  		"kernel":         "pc-kernel",
    49  		"gadget":         "pc",
    50  		"revision":       "1",
    51  		"required-snaps": []interface{}{"foo", "bar"},
    52  	})
    53  	// foo and bar
    54  	fooSI := &snap.SideInfo{
    55  		RealName: "foo",
    56  		Revision: snap.R(1),
    57  	}
    58  	barSI := &snap.SideInfo{
    59  		RealName: "foo",
    60  		Revision: snap.R(1),
    61  	}
    62  	pcKernelSI := &snap.SideInfo{
    63  		RealName: "pc-kernel",
    64  		Revision: snap.R(1),
    65  	}
    66  	snapstate.Set(s.state, "foo", &snapstate.SnapState{
    67  		SnapType: "app",
    68  		Active:   true,
    69  		Sequence: []*snap.SideInfo{fooSI},
    70  		Current:  fooSI.Revision,
    71  		Flags:    snapstate.Flags{Required: true},
    72  	})
    73  	snapstate.Set(s.state, "bar", &snapstate.SnapState{
    74  		SnapType: "app",
    75  		Active:   true,
    76  		Sequence: []*snap.SideInfo{barSI},
    77  		Current:  barSI.Revision,
    78  		Flags:    snapstate.Flags{Required: true},
    79  	})
    80  	snapstate.Set(s.state, "pc-kernel", &snapstate.SnapState{
    81  		SnapType: "kernel",
    82  		Active:   true,
    83  		Sequence: []*snap.SideInfo{pcKernelSI},
    84  		Current:  pcKernelSI.Revision,
    85  		Flags:    snapstate.Flags{Required: true},
    86  	})
    87  	s.state.Unlock()
    88  
    89  	newModel := s.brands.Model("canonical", "pc-model", map[string]interface{}{
    90  		"architecture":   "amd64",
    91  		"kernel":         "other-kernel",
    92  		"gadget":         "pc",
    93  		"revision":       "2",
    94  		"required-snaps": []interface{}{"foo"},
    95  	})
    96  
    97  	s.state.Lock()
    98  	t := s.state.NewTask("set-model", "set-model test")
    99  	chg := s.state.NewChange("dummy", "...")
   100  	chg.Set("new-model", string(asserts.Encode(newModel)))
   101  	chg.AddTask(t)
   102  
   103  	s.state.Unlock()
   104  
   105  	s.se.Ensure()
   106  	s.se.Wait()
   107  
   108  	s.state.Lock()
   109  	defer s.state.Unlock()
   110  	m, err := s.mgr.Model()
   111  	c.Assert(err, IsNil)
   112  	c.Assert(m, DeepEquals, newModel)
   113  
   114  	c.Assert(chg.Err(), IsNil)
   115  
   116  	// check required
   117  	var fooState snapstate.SnapState
   118  	var barState snapstate.SnapState
   119  	err = snapstate.Get(s.state, "foo", &fooState)
   120  	c.Assert(err, IsNil)
   121  	err = snapstate.Get(s.state, "bar", &barState)
   122  	c.Assert(err, IsNil)
   123  	c.Check(fooState.Flags.Required, Equals, true)
   124  	c.Check(barState.Flags.Required, Equals, false)
   125  	// the kernel is no longer required
   126  	var kernelState snapstate.SnapState
   127  	err = snapstate.Get(s.state, "pc-kernel", &kernelState)
   128  	c.Assert(err, IsNil)
   129  	c.Check(kernelState.Flags.Required, Equals, false)
   130  }
   131  
   132  func (s *deviceMgrSuite) TestSetModelHandlerSameRevisionNoError(c *C) {
   133  	model := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   134  		"architecture": "amd64",
   135  		"kernel":       "pc-kernel",
   136  		"gadget":       "pc",
   137  		"revision":     "1",
   138  	})
   139  
   140  	s.state.Lock()
   141  
   142  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   143  		Brand: "canonical",
   144  		Model: "pc-model",
   145  	})
   146  	err := assertstate.Add(s.state, model)
   147  	c.Assert(err, IsNil)
   148  
   149  	t := s.state.NewTask("set-model", "set-model test")
   150  	chg := s.state.NewChange("dummy", "...")
   151  	chg.Set("new-model", string(asserts.Encode(model)))
   152  	chg.AddTask(t)
   153  
   154  	s.state.Unlock()
   155  
   156  	s.se.Ensure()
   157  	s.se.Wait()
   158  
   159  	s.state.Lock()
   160  	defer s.state.Unlock()
   161  	c.Assert(chg.Err(), IsNil)
   162  }
   163  
   164  func (s *deviceMgrSuite) TestSetModelHandlerStoreSwitch(c *C) {
   165  	s.state.Lock()
   166  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   167  		Brand: "canonical",
   168  		Model: "pc-model",
   169  	})
   170  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   171  		"architecture": "amd64",
   172  		"kernel":       "pc-kernel",
   173  		"gadget":       "pc",
   174  		"revision":     "1",
   175  	})
   176  	s.state.Unlock()
   177  
   178  	newModel := s.brands.Model("canonical", "pc-model", map[string]interface{}{
   179  		"architecture": "amd64",
   180  		"kernel":       "pc-kernel",
   181  		"gadget":       "pc",
   182  		"store":        "switched-store",
   183  		"revision":     "2",
   184  	})
   185  
   186  	s.newFakeStore = func(devBE storecontext.DeviceBackend) snapstate.StoreService {
   187  		mod, err := devBE.Model()
   188  		c.Check(err, IsNil)
   189  		if err == nil {
   190  			c.Check(mod, DeepEquals, newModel)
   191  		}
   192  		return &freshSessionStore{}
   193  	}
   194  
   195  	s.state.Lock()
   196  	t := s.state.NewTask("set-model", "set-model test")
   197  	chg := s.state.NewChange("dummy", "...")
   198  	chg.Set("new-model", string(asserts.Encode(newModel)))
   199  	chg.Set("device", auth.DeviceState{
   200  		Brand:           "canonical",
   201  		Model:           "pc-model",
   202  		SessionMacaroon: "switched-store-session",
   203  	})
   204  	chg.AddTask(t)
   205  
   206  	s.state.Unlock()
   207  
   208  	s.se.Ensure()
   209  	s.se.Wait()
   210  
   211  	s.state.Lock()
   212  	defer s.state.Unlock()
   213  	c.Assert(chg.Err(), IsNil)
   214  
   215  	m, err := s.mgr.Model()
   216  	c.Assert(err, IsNil)
   217  	c.Assert(m, DeepEquals, newModel)
   218  
   219  	device, err := devicestatetest.Device(s.state)
   220  	c.Assert(err, IsNil)
   221  	c.Check(device, DeepEquals, &auth.DeviceState{
   222  		Brand:           "canonical",
   223  		Model:           "pc-model",
   224  		SessionMacaroon: "switched-store-session",
   225  	})
   226  
   227  	// cleanup
   228  	_, ok := devicestate.CachedRemodelCtx(chg)
   229  	c.Check(ok, Equals, true)
   230  
   231  	s.state.Unlock()
   232  
   233  	s.se.Ensure()
   234  	s.se.Wait()
   235  
   236  	s.state.Lock()
   237  
   238  	_, ok = devicestate.CachedRemodelCtx(chg)
   239  	c.Check(ok, Equals, false)
   240  }
   241  
   242  func (s *deviceMgrSuite) TestSetModelHandlerRereg(c *C) {
   243  	s.state.Lock()
   244  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   245  		Brand:  "canonical",
   246  		Model:  "pc-model",
   247  		Serial: "orig-serial",
   248  	})
   249  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   250  		"architecture": "amd64",
   251  		"kernel":       "pc-kernel",
   252  		"gadget":       "pc",
   253  	})
   254  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "orig-serial")
   255  	s.state.Unlock()
   256  
   257  	newModel := s.brands.Model("canonical", "rereg-model", map[string]interface{}{
   258  		"architecture": "amd64",
   259  		"kernel":       "pc-kernel",
   260  		"gadget":       "pc",
   261  	})
   262  
   263  	s.newFakeStore = func(devBE storecontext.DeviceBackend) snapstate.StoreService {
   264  		mod, err := devBE.Model()
   265  		c.Check(err, IsNil)
   266  		if err == nil {
   267  			c.Check(mod, DeepEquals, newModel)
   268  		}
   269  		return &freshSessionStore{}
   270  	}
   271  
   272  	s.state.Lock()
   273  	t := s.state.NewTask("set-model", "set-model test")
   274  	chg := s.state.NewChange("dummy", "...")
   275  	chg.Set("new-model", string(asserts.Encode(newModel)))
   276  	chg.Set("device", auth.DeviceState{
   277  		Brand:           "canonical",
   278  		Model:           "rereg-model",
   279  		Serial:          "orig-serial",
   280  		SessionMacaroon: "switched-store-session",
   281  	})
   282  	chg.AddTask(t)
   283  
   284  	s.state.Unlock()
   285  
   286  	s.se.Ensure()
   287  	s.se.Wait()
   288  
   289  	s.state.Lock()
   290  	defer s.state.Unlock()
   291  	c.Assert(chg.Err(), IsNil)
   292  
   293  	m, err := s.mgr.Model()
   294  	c.Assert(err, IsNil)
   295  	c.Assert(m, DeepEquals, newModel)
   296  
   297  	device, err := devicestatetest.Device(s.state)
   298  	c.Assert(err, IsNil)
   299  	c.Check(device, DeepEquals, &auth.DeviceState{
   300  		Brand:           "canonical",
   301  		Model:           "rereg-model",
   302  		Serial:          "orig-serial",
   303  		SessionMacaroon: "switched-store-session",
   304  	})
   305  }
   306  
   307  func (s *deviceMgrSuite) TestDoPrepareRemodeling(c *C) {
   308  	s.state.Lock()
   309  	defer s.state.Unlock()
   310  	s.state.Set("seeded", true)
   311  	s.state.Set("refresh-privacy-key", "some-privacy-key")
   312  
   313  	var testStore snapstate.StoreService
   314  
   315  	restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
   316  		c.Check(flags.Required, Equals, true)
   317  		c.Check(deviceCtx, NotNil)
   318  		c.Check(deviceCtx.ForRemodeling(), Equals, true)
   319  
   320  		tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
   321  		tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
   322  		tValidate.WaitFor(tDownload)
   323  		tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
   324  		tInstall.WaitFor(tValidate)
   325  		ts := state.NewTaskSet(tDownload, tValidate, tInstall)
   326  		ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
   327  		return ts, nil
   328  	})
   329  	defer restore()
   330  
   331  	// set a model assertion
   332  	s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
   333  		"architecture": "amd64",
   334  		"kernel":       "pc-kernel",
   335  		"gadget":       "pc",
   336  		"base":         "core18",
   337  	})
   338  	s.makeSerialAssertionInState(c, "canonical", "pc-model", "orig-serial")
   339  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   340  		Brand:           "canonical",
   341  		Model:           "pc-model",
   342  		Serial:          "orig-serial",
   343  		SessionMacaroon: "old-session",
   344  	})
   345  
   346  	new := s.brands.Model("canonical", "rereg-model", map[string]interface{}{
   347  		"architecture":   "amd64",
   348  		"kernel":         "pc-kernel",
   349  		"gadget":         "pc",
   350  		"base":           "core18",
   351  		"required-snaps": []interface{}{"new-required-snap-1", "new-required-snap-2"},
   352  	})
   353  
   354  	freshStore := &freshSessionStore{}
   355  	testStore = freshStore
   356  
   357  	s.newFakeStore = func(devBE storecontext.DeviceBackend) snapstate.StoreService {
   358  		mod, err := devBE.Model()
   359  		c.Check(err, IsNil)
   360  		if err == nil {
   361  			c.Check(mod, DeepEquals, new)
   362  		}
   363  		return testStore
   364  	}
   365  
   366  	cur, err := s.mgr.Model()
   367  	c.Assert(err, IsNil)
   368  
   369  	remodCtx, err := devicestate.RemodelCtx(s.state, cur, new)
   370  	c.Assert(err, IsNil)
   371  
   372  	c.Check(remodCtx.Kind(), Equals, devicestate.ReregRemodel)
   373  
   374  	chg := s.state.NewChange("remodel", "...")
   375  	remodCtx.Init(chg)
   376  	t := s.state.NewTask("prepare-remodeling", "...")
   377  	chg.AddTask(t)
   378  
   379  	// set new serial
   380  	s.makeSerialAssertionInState(c, "canonical", "rereg-model", "orig-serial")
   381  	chg.Set("device", auth.DeviceState{
   382  		Brand:           "canonical",
   383  		Model:           "rereg-model",
   384  		Serial:          "orig-serial",
   385  		SessionMacaroon: "switched-store-session",
   386  	})
   387  
   388  	s.state.Unlock()
   389  
   390  	s.se.Ensure()
   391  	s.se.Wait()
   392  
   393  	s.state.Lock()
   394  	c.Assert(chg.Err(), IsNil)
   395  
   396  	c.Check(freshStore.ensureDeviceSession, Equals, 1)
   397  
   398  	// check that the expected tasks were injected
   399  	tl := chg.Tasks()
   400  	// 1 prepare-remodeling
   401  	// 2 snaps * 3 tasks (from the mock install above) +
   402  	// 1 "set-model" task at the end
   403  	c.Assert(tl, HasLen, 1+2*3+1)
   404  
   405  	// sanity
   406  	c.Check(tl[1].Kind(), Equals, "fake-download")
   407  	c.Check(tl[1+2*3].Kind(), Equals, "set-model")
   408  
   409  	// cleanup
   410  	// fake completion
   411  	for _, t := range tl[1:] {
   412  		t.SetStatus(state.DoneStatus)
   413  	}
   414  	_, ok := devicestate.CachedRemodelCtx(chg)
   415  	c.Check(ok, Equals, true)
   416  
   417  	s.state.Unlock()
   418  
   419  	s.se.Ensure()
   420  	s.se.Wait()
   421  
   422  	s.state.Lock()
   423  
   424  	_, ok = devicestate.CachedRemodelCtx(chg)
   425  	c.Check(ok, Equals, false)
   426  }