github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/ifacestate/helpers_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2018 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 ifacestate_test
    21  
    22  import (
    23  	"errors"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"strings"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/dirs"
    33  	"github.com/snapcore/snapd/interfaces"
    34  	"github.com/snapcore/snapd/interfaces/ifacetest"
    35  	"github.com/snapcore/snapd/logger"
    36  	"github.com/snapcore/snapd/osutil"
    37  	"github.com/snapcore/snapd/overlord"
    38  	"github.com/snapcore/snapd/overlord/ifacestate"
    39  	"github.com/snapcore/snapd/overlord/snapstate"
    40  	"github.com/snapcore/snapd/overlord/state"
    41  	"github.com/snapcore/snapd/snap"
    42  	"github.com/snapcore/snapd/snap/snaptest"
    43  	"github.com/snapcore/snapd/timings"
    44  )
    45  
    46  type helpersSuite struct {
    47  	st *state.State
    48  }
    49  
    50  var _ = Suite(&helpersSuite{})
    51  
    52  func (s *helpersSuite) SetUpTest(c *C) {
    53  	s.st = state.New(nil)
    54  	dirs.SetRootDir(c.MkDir())
    55  }
    56  
    57  func (s *helpersSuite) TearDownTest(c *C) {
    58  	dirs.SetRootDir("")
    59  }
    60  
    61  func (s *helpersSuite) TestIdentityMapper(c *C) {
    62  	var m ifacestate.SnapMapper = &ifacestate.IdentityMapper{}
    63  
    64  	// Nothing is altered.
    65  	c.Assert(m.RemapSnapFromState("example"), Equals, "example")
    66  	c.Assert(m.RemapSnapToState("example"), Equals, "example")
    67  	c.Assert(m.RemapSnapFromRequest("example"), Equals, "example")
    68  
    69  	c.Assert(m.SystemSnapName(), Equals, "unknown")
    70  }
    71  
    72  func (s *helpersSuite) TestCoreCoreSystemMapper(c *C) {
    73  	var m ifacestate.SnapMapper = &ifacestate.CoreCoreSystemMapper{}
    74  
    75  	// Snaps are not renamed when interacting with the state.
    76  	c.Assert(m.RemapSnapFromState("core"), Equals, "core")
    77  	c.Assert(m.RemapSnapToState("core"), Equals, "core")
    78  
    79  	// The "core" snap is renamed to the "system" in API response
    80  	// and back in the API requests.
    81  	c.Assert(m.RemapSnapFromRequest("system"), Equals, "core")
    82  
    83  	// Other snap names are unchanged.
    84  	c.Assert(m.RemapSnapFromState("potato"), Equals, "potato")
    85  	c.Assert(m.RemapSnapToState("potato"), Equals, "potato")
    86  	c.Assert(m.RemapSnapFromRequest("potato"), Equals, "potato")
    87  
    88  	c.Assert(m.SystemSnapName(), Equals, "core")
    89  }
    90  
    91  func (s *helpersSuite) TestCoreSnapdSystemMapper(c *C) {
    92  	var m ifacestate.SnapMapper = &ifacestate.CoreSnapdSystemMapper{}
    93  
    94  	// The "snapd" snap is renamed to the "core" in when saving the state
    95  	// and back when loading the state.
    96  	c.Assert(m.RemapSnapFromState("core"), Equals, "snapd")
    97  	c.Assert(m.RemapSnapToState("snapd"), Equals, "core")
    98  
    99  	// The "snapd" snap is renamed to the "system" in API response and back in
   100  	// the API requests.
   101  	c.Assert(m.RemapSnapFromRequest("system"), Equals, "snapd")
   102  
   103  	// The "core" snap is also renamed to "snapd" in API requests, for
   104  	// compatibility.
   105  	c.Assert(m.RemapSnapFromRequest("core"), Equals, "snapd")
   106  
   107  	// Other snap names are unchanged.
   108  	c.Assert(m.RemapSnapFromState("potato"), Equals, "potato")
   109  	c.Assert(m.RemapSnapToState("potato"), Equals, "potato")
   110  	c.Assert(m.RemapSnapFromRequest("potato"), Equals, "potato")
   111  
   112  	c.Assert(m.SystemSnapName(), Equals, "snapd")
   113  }
   114  
   115  // caseMapper implements SnapMapper to use upper case internally and lower case externally.
   116  type caseMapper struct{}
   117  
   118  func (m *caseMapper) RemapSnapFromState(snapName string) string {
   119  	return strings.ToUpper(snapName)
   120  }
   121  
   122  func (m *caseMapper) RemapSnapToState(snapName string) string {
   123  	return strings.ToLower(snapName)
   124  }
   125  
   126  func (m *caseMapper) RemapSnapFromRequest(snapName string) string {
   127  	return strings.ToUpper(snapName)
   128  }
   129  
   130  func (m *caseMapper) SystemSnapName() string {
   131  	return "unknown"
   132  }
   133  
   134  func (s *helpersSuite) TestMappingFunctions(c *C) {
   135  	restore := ifacestate.MockSnapMapper(&caseMapper{})
   136  	defer restore()
   137  
   138  	c.Assert(ifacestate.RemapSnapFromState("example"), Equals, "EXAMPLE")
   139  	c.Assert(ifacestate.RemapSnapToState("EXAMPLE"), Equals, "example")
   140  	c.Assert(ifacestate.RemapSnapFromRequest("example"), Equals, "EXAMPLE")
   141  	c.Assert(ifacestate.SystemSnapName(), Equals, "unknown")
   142  }
   143  
   144  func (s *helpersSuite) TestGetConns(c *C) {
   145  	s.st.Lock()
   146  	defer s.st.Unlock()
   147  	s.st.Set("conns", map[string]interface{}{
   148  		"app:network core:network": map[string]interface{}{
   149  			"auto":      true,
   150  			"interface": "network",
   151  			"slot-static": map[string]interface{}{
   152  				"number": int(78),
   153  			},
   154  		},
   155  	})
   156  
   157  	restore := ifacestate.MockSnapMapper(&caseMapper{})
   158  	defer restore()
   159  
   160  	conns, err := ifacestate.GetConns(s.st)
   161  	c.Assert(err, IsNil)
   162  	for id, connState := range conns {
   163  		c.Assert(id, Equals, "APP:network CORE:network")
   164  		c.Assert(connState.Auto, Equals, true)
   165  		c.Assert(connState.Interface, Equals, "network")
   166  		c.Assert(connState.StaticSlotAttrs["number"], Equals, int64(78))
   167  	}
   168  }
   169  
   170  func (s *helpersSuite) TestSetConns(c *C) {
   171  	s.st.Lock()
   172  	defer s.st.Unlock()
   173  
   174  	restore := ifacestate.MockSnapMapper(&caseMapper{})
   175  	defer restore()
   176  
   177  	// This has upper-case data internally, see export_test.go
   178  	ifacestate.SetConns(s.st, ifacestate.UpperCaseConnState())
   179  	var conns map[string]interface{}
   180  	err := s.st.Get("conns", &conns)
   181  	c.Assert(err, IsNil)
   182  	c.Assert(conns, DeepEquals, map[string]interface{}{
   183  		"app:network core:network": map[string]interface{}{
   184  			"auto":      true,
   185  			"interface": "network",
   186  		}})
   187  }
   188  
   189  func (s *helpersSuite) TestHotplugTaskHelpers(c *C) {
   190  	s.st.Lock()
   191  	defer s.st.Unlock()
   192  
   193  	t := s.st.NewTask("foo", "")
   194  	_, _, err := ifacestate.GetHotplugAttrs(t)
   195  	c.Assert(err, ErrorMatches, `internal error: cannot get interface name from hotplug task: no state entry for key`)
   196  
   197  	t.Set("interface", "x")
   198  	_, _, err = ifacestate.GetHotplugAttrs(t)
   199  	c.Assert(err, ErrorMatches, `internal error: cannot get hotplug key from hotplug task: no state entry for key`)
   200  
   201  	ifacestate.SetHotplugAttrs(t, "iface", "key")
   202  
   203  	var iface string
   204  	var key snap.HotplugKey
   205  	c.Assert(t.Get("hotplug-key", &key), IsNil)
   206  	c.Assert(key, DeepEquals, snap.HotplugKey("key"))
   207  
   208  	c.Assert(t.Get("interface", &iface), IsNil)
   209  	c.Assert(iface, Equals, "iface")
   210  
   211  	iface, key, err = ifacestate.GetHotplugAttrs(t)
   212  	c.Assert(err, IsNil)
   213  	c.Assert(key, DeepEquals, snap.HotplugKey("key"))
   214  	c.Assert(iface, Equals, "iface")
   215  }
   216  
   217  func (s *helpersSuite) TestHotplugSlotInfo(c *C) {
   218  	s.st.Lock()
   219  	defer s.st.Unlock()
   220  
   221  	slots, err := ifacestate.GetHotplugSlots(s.st)
   222  	c.Assert(err, IsNil)
   223  	c.Assert(slots, HasLen, 0)
   224  
   225  	defs := map[string]*ifacestate.HotplugSlotInfo{}
   226  	defs["foo"] = &ifacestate.HotplugSlotInfo{
   227  		Name:        "foo",
   228  		Interface:   "iface",
   229  		StaticAttrs: map[string]interface{}{"attr": "value"},
   230  		HotplugKey:  "key",
   231  	}
   232  	ifacestate.SetHotplugSlots(s.st, defs)
   233  
   234  	var data map[string]interface{}
   235  	c.Assert(s.st.Get("hotplug-slots", &data), IsNil)
   236  	c.Assert(data, DeepEquals, map[string]interface{}{
   237  		"foo": map[string]interface{}{
   238  			"name":         "foo",
   239  			"interface":    "iface",
   240  			"static-attrs": map[string]interface{}{"attr": "value"},
   241  			"hotplug-key":  "key",
   242  			"hotplug-gone": false,
   243  		}})
   244  
   245  	slots, err = ifacestate.GetHotplugSlots(s.st)
   246  	c.Assert(err, IsNil)
   247  	c.Assert(slots, DeepEquals, defs)
   248  }
   249  
   250  func (s *helpersSuite) TestFindConnsForHotplugKey(c *C) {
   251  	st := s.st
   252  	st.Lock()
   253  	defer st.Unlock()
   254  
   255  	// Set conns in the state and get them via GetConns to avoid having to
   256  	// know the internals of connState struct.
   257  	st.Set("conns", map[string]interface{}{
   258  		"snap1:plug1 core:slot1": map[string]interface{}{
   259  			"interface":   "iface1",
   260  			"hotplug-key": "key1",
   261  		},
   262  		"snap1:plug2 core:slot2": map[string]interface{}{
   263  			"interface":   "iface2",
   264  			"hotplug-key": "key1",
   265  		},
   266  		"snap1:plug3 core:slot3": map[string]interface{}{
   267  			"interface":   "iface2",
   268  			"hotplug-key": "key2",
   269  		},
   270  		"snap2:plug1 core:slot1": map[string]interface{}{
   271  			"interface":   "iface2",
   272  			"hotplug-key": "key2",
   273  		},
   274  	})
   275  
   276  	conns, err := ifacestate.GetConns(st)
   277  	c.Assert(err, IsNil)
   278  
   279  	hotplugConns := ifacestate.FindConnsForHotplugKey(conns, "iface1", "key1")
   280  	c.Assert(hotplugConns, DeepEquals, []string{"snap1:plug1 core:slot1"})
   281  
   282  	hotplugConns = ifacestate.FindConnsForHotplugKey(conns, "iface2", "key2")
   283  	c.Assert(hotplugConns, DeepEquals, []string{"snap1:plug3 core:slot3", "snap2:plug1 core:slot1"})
   284  
   285  	hotplugConns = ifacestate.FindConnsForHotplugKey(conns, "unknown", "key1")
   286  	c.Assert(hotplugConns, HasLen, 0)
   287  }
   288  
   289  func (s *helpersSuite) TestCheckIsSystemSnapPresentWithCore(c *C) {
   290  	restore := ifacestate.MockSnapMapper(&ifacestate.CoreCoreSystemMapper{})
   291  	defer restore()
   292  
   293  	// no core snap yet
   294  	c.Assert(ifacestate.CheckSystemSnapIsPresent(s.st), Equals, false)
   295  
   296  	s.st.Lock()
   297  
   298  	// add "core" snap
   299  	sideInfo := &snap.SideInfo{Revision: snap.R(1)}
   300  	snapInfo := snaptest.MockSnapInstance(c, "", coreSnapYaml, sideInfo)
   301  	sideInfo.RealName = snapInfo.SnapName()
   302  
   303  	snapstate.Set(s.st, snapInfo.InstanceName(), &snapstate.SnapState{
   304  		Active:      true,
   305  		Sequence:    []*snap.SideInfo{sideInfo},
   306  		Current:     sideInfo.Revision,
   307  		SnapType:    string(snapInfo.Type()),
   308  		InstanceKey: snapInfo.InstanceKey,
   309  	})
   310  	s.st.Unlock()
   311  
   312  	c.Assert(ifacestate.CheckSystemSnapIsPresent(s.st), Equals, true)
   313  }
   314  
   315  var snapdYaml = `name: snapd
   316  version: 1.0
   317  `
   318  
   319  func (s *helpersSuite) TestCheckIsSystemSnapPresentWithSnapd(c *C) {
   320  	restore := ifacestate.MockSnapMapper(&ifacestate.CoreSnapdSystemMapper{})
   321  	defer restore()
   322  
   323  	// no snapd snap yet
   324  	c.Assert(ifacestate.CheckSystemSnapIsPresent(s.st), Equals, false)
   325  
   326  	s.st.Lock()
   327  
   328  	// "snapd" snap
   329  	sideInfo := &snap.SideInfo{Revision: snap.R(1)}
   330  	snapInfo := snaptest.MockSnapInstance(c, "", snapdYaml, sideInfo)
   331  	sideInfo.RealName = snapInfo.SnapName()
   332  
   333  	snapstate.Set(s.st, snapInfo.InstanceName(), &snapstate.SnapState{
   334  		Active:      true,
   335  		Sequence:    []*snap.SideInfo{sideInfo},
   336  		Current:     sideInfo.Revision,
   337  		SnapType:    string(snapInfo.Type()),
   338  		InstanceKey: snapInfo.InstanceKey,
   339  	})
   340  
   341  	inf, err := ifacestate.SystemSnapInfo(s.st)
   342  	c.Assert(err, IsNil)
   343  	c.Assert(inf.InstanceName(), Equals, "snapd")
   344  
   345  	s.st.Unlock()
   346  
   347  	c.Assert(ifacestate.CheckSystemSnapIsPresent(s.st), Equals, true)
   348  }
   349  
   350  // Check what happens with system-key when security profile regeneration fails.
   351  func (s *helpersSuite) TestSystemKeyAndFailingProfileRegeneration(c *C) {
   352  	dirs.SetRootDir(c.MkDir())
   353  	defer dirs.SetRootDir("")
   354  
   355  	// Create a fake security backend with failing Setup method and mock all
   356  	// security backends away so that we only use this special one. Note that
   357  	// the backend is given a non-empty name as the interface manager skips
   358  	// test backends with empty name for convenience.
   359  	backend := &ifacetest.TestSecurityBackend{
   360  		BackendName: "BROKEN",
   361  		SetupCallback: func(snapInfo *snap.Info, opts interfaces.ConfinementOptions, repo *interfaces.Repository) error {
   362  			return errors.New("FAILED")
   363  		},
   364  	}
   365  	restore := ifacestate.MockSecurityBackends([]interfaces.SecurityBackend{backend})
   366  	defer restore()
   367  
   368  	// Create a mock overlord, mainly to have state.
   369  	ovld := overlord.Mock()
   370  	st := ovld.State()
   371  
   372  	// Put a fake snap in the state, we need to setup security for at least one
   373  	// snap to give the fake security backend a chance to fail.
   374  	yamlText := `
   375  name: test-snapd-canary
   376  version: 1
   377  apps:
   378    test-snapd-canary:
   379      command: bin/canary
   380  `
   381  	si := &snap.SideInfo{Revision: snap.R(1), RealName: "test-snapd-canary"}
   382  	snapInfo := snaptest.MockSnap(c, yamlText, si)
   383  	st.Lock()
   384  	snapst := &snapstate.SnapState{
   385  		SnapType: string(snap.TypeApp),
   386  		Sequence: []*snap.SideInfo{si},
   387  		Active:   true,
   388  		Current:  snap.R(1),
   389  	}
   390  	snapstate.Set(st, snapInfo.InstanceName(), snapst)
   391  	st.Unlock()
   392  
   393  	// Pretend that security profiles are out of date and mock the
   394  	// function that writes the new system key with one always panics.
   395  	restore = ifacestate.MockProfilesNeedRegeneration(func() bool { return true })
   396  	defer restore()
   397  	restore = ifacestate.MockWriteSystemKey(func() error { panic("should not attempt to write system key") })
   398  	defer restore()
   399  	// Put a fake system key in place, we just want to see that file being removed.
   400  	err := os.MkdirAll(filepath.Dir(dirs.SnapSystemKeyFile), 0755)
   401  	c.Assert(err, IsNil)
   402  	err = ioutil.WriteFile(dirs.SnapSystemKeyFile, []byte("system-key"), 0755)
   403  	c.Assert(err, IsNil)
   404  
   405  	// Put up a fake logger to capture logged messages.
   406  	log, restore := logger.MockLogger()
   407  	defer restore()
   408  
   409  	// Construct and start up the interface manager.
   410  	mgr, err := ifacestate.Manager(st, nil, ovld.TaskRunner(), nil, nil)
   411  	c.Assert(err, IsNil)
   412  	err = mgr.StartUp()
   413  	c.Assert(err, IsNil)
   414  
   415  	// Check that system key is not on disk.
   416  	c.Check(log.String(), Matches, `.*cannot regenerate BROKEN profiles\n.*FAILED.*\n`)
   417  	c.Check(osutil.FileExists(dirs.SnapSystemKeyFile), Equals, false)
   418  }
   419  
   420  func mockSnaps(c *C, st *state.State) {
   421  	// Put fake snaps in the state
   422  	for _, name := range []string{"foo", "bar"} {
   423  		yamlText := `
   424  name: %NAME%
   425  version: 1
   426  apps:
   427    test:
   428      command: bin/test
   429  `
   430  		si := &snap.SideInfo{Revision: snap.R(1), RealName: name}
   431  		snapInfo := snaptest.MockSnap(c, strings.Replace(yamlText, "%NAME%", name, -1), si)
   432  		st.Lock()
   433  		snapst := &snapstate.SnapState{
   434  			SnapType: string(snap.TypeApp),
   435  			Sequence: []*snap.SideInfo{si},
   436  			Active:   true,
   437  			Current:  snap.R(1),
   438  		}
   439  		snapstate.Set(st, snapInfo.InstanceName(), snapst)
   440  		st.Unlock()
   441  	}
   442  }
   443  
   444  func (s *helpersSuite) TestProfileRegenerationSetupMany(c *C) {
   445  	dirs.SetRootDir(c.MkDir())
   446  	defer dirs.SetRootDir("")
   447  
   448  	var setupManyCalls int
   449  	var writeKey bool
   450  
   451  	// Create a fake security backend
   452  	backend := &ifacetest.TestSecurityBackendSetupMany{
   453  		TestSecurityBackend: ifacetest.TestSecurityBackend{BackendName: "fake"},
   454  		SetupManyCallback: func(snaps []*snap.Info, confinement func(snapName string) interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) []error {
   455  			c.Check(snaps, HasLen, 2)
   456  			setupManyCalls++
   457  			return nil
   458  		},
   459  	}
   460  	restore := ifacestate.MockSecurityBackends([]interfaces.SecurityBackend{backend})
   461  	defer restore()
   462  
   463  	// Create a mock overlord, mainly to have state.
   464  	ovld := overlord.Mock()
   465  	st := ovld.State()
   466  
   467  	mockSnaps(c, st)
   468  
   469  	// Pretend that security profiles are out of date.
   470  	restore = ifacestate.MockProfilesNeedRegeneration(func() bool { return true })
   471  	defer restore()
   472  	restore = ifacestate.MockWriteSystemKey(func() error {
   473  		writeKey = true
   474  		return nil
   475  	})
   476  	defer restore()
   477  
   478  	// Construct and start up the interface manager.
   479  	mgr, err := ifacestate.Manager(st, nil, ovld.TaskRunner(), nil, nil)
   480  	c.Assert(err, IsNil)
   481  	err = mgr.StartUp()
   482  	c.Assert(err, IsNil)
   483  
   484  	c.Check(writeKey, Equals, true)
   485  	c.Check(setupManyCalls, Equals, 1)
   486  }
   487  
   488  func (s *helpersSuite) TestProfileRegenerationSetupManyFailsSystemKeyNotWritten(c *C) {
   489  	dirs.SetRootDir(c.MkDir())
   490  	defer dirs.SetRootDir("")
   491  
   492  	var setupManyCalls int
   493  	var writeKey bool
   494  
   495  	// Create a fake security backend
   496  	backend := &ifacetest.TestSecurityBackendSetupMany{
   497  		TestSecurityBackend: ifacetest.TestSecurityBackend{BackendName: "fake"},
   498  		SetupManyCallback: func(snaps []*snap.Info, confinement func(snapName string) interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) []error {
   499  			c.Check(snaps, HasLen, 2)
   500  			setupManyCalls++
   501  			return []error{fmt.Errorf("FAILED")}
   502  		},
   503  	}
   504  	restore := ifacestate.MockSecurityBackends([]interfaces.SecurityBackend{backend})
   505  	defer restore()
   506  
   507  	// Put up a fake logger to capture logged messages.
   508  	log, restoreLog := logger.MockLogger()
   509  	defer restoreLog()
   510  
   511  	// Create a mock overlord, mainly to have state.
   512  	ovld := overlord.Mock()
   513  	st := ovld.State()
   514  
   515  	mockSnaps(c, st)
   516  
   517  	// Pretend that security profiles are out of date.
   518  	restore = ifacestate.MockProfilesNeedRegeneration(func() bool { return true })
   519  	defer restore()
   520  	restore = ifacestate.MockWriteSystemKey(func() error {
   521  		writeKey = true
   522  		return nil
   523  	})
   524  	defer restore()
   525  
   526  	// Construct and start up the interface manager.
   527  	mgr, err := ifacestate.Manager(st, nil, ovld.TaskRunner(), nil, nil)
   528  	c.Assert(err, IsNil)
   529  	err = mgr.StartUp()
   530  	c.Assert(err, IsNil)
   531  
   532  	// Check that system key is not on disk.
   533  	c.Check(writeKey, Equals, false)
   534  	c.Check(setupManyCalls, Equals, 1)
   535  	c.Check(log.String(), Matches, ".*cannot regenerate fake profiles\n.*FAILED\n")
   536  }
   537  
   538  func (s *helpersSuite) TestIsHotplugChange(c *C) {
   539  	s.st.Lock()
   540  	defer s.st.Unlock()
   541  
   542  	chg := s.st.NewChange("foo", "")
   543  	c.Assert(ifacestate.IsHotplugChange(chg), Equals, false)
   544  
   545  	chg = s.st.NewChange("hotplugfoo", "")
   546  	c.Assert(ifacestate.IsHotplugChange(chg), Equals, false)
   547  
   548  	chg = s.st.NewChange("hotplug-foo", "")
   549  	c.Assert(ifacestate.IsHotplugChange(chg), Equals, true)
   550  }
   551  
   552  func (s *helpersSuite) TestGetHotplugChangeAttrs(c *C) {
   553  	s.st.Lock()
   554  	defer s.st.Unlock()
   555  
   556  	chg := s.st.NewChange("none-set", "")
   557  	_, _, err := ifacestate.GetHotplugChangeAttrs(chg)
   558  	c.Assert(err, ErrorMatches, `internal error: hotplug-key not set on change "none-set"`)
   559  
   560  	chg = s.st.NewChange("foo", "")
   561  	chg.Set("hotplug-seq", 1)
   562  	_, _, err = ifacestate.GetHotplugChangeAttrs(chg)
   563  	c.Assert(err, ErrorMatches, `internal error: hotplug-key not set on change "foo"`)
   564  
   565  	chg = s.st.NewChange("bar", "")
   566  	chg.Set("hotplug-key", "2222")
   567  	_, _, err = ifacestate.GetHotplugChangeAttrs(chg)
   568  	c.Assert(err, ErrorMatches, `internal error: hotplug-seq not set on change "bar"`)
   569  
   570  	chg = s.st.NewChange("baz", "")
   571  	chg.Set("hotplug-key", "1234")
   572  	chg.Set("hotplug-seq", 7)
   573  
   574  	seq, key, err := ifacestate.GetHotplugChangeAttrs(chg)
   575  	c.Assert(err, IsNil)
   576  	c.Check(key, DeepEquals, snap.HotplugKey("1234"))
   577  	c.Check(seq, Equals, 7)
   578  }
   579  
   580  func (s *helpersSuite) TestSetHotplugChangeAttrs(c *C) {
   581  	s.st.Lock()
   582  	defer s.st.Unlock()
   583  
   584  	chg := s.st.NewChange("foo", "")
   585  	ifacestate.SetHotplugChangeAttrs(chg, 12, "abcd")
   586  
   587  	var seq int
   588  	var hotplugKey string
   589  	c.Assert(chg.Get("hotplug-seq", &seq), IsNil)
   590  	c.Assert(chg.Get("hotplug-key", &hotplugKey), IsNil)
   591  	c.Check(seq, Equals, 12)
   592  	c.Check(hotplugKey, Equals, "abcd")
   593  }
   594  
   595  func (s *helpersSuite) TestAllocHotplugSeq(c *C) {
   596  	s.st.Lock()
   597  	defer s.st.Unlock()
   598  
   599  	var stateSeq int
   600  
   601  	// sanity
   602  	c.Assert(s.st.Get("hotplug-seq", &stateSeq), Equals, state.ErrNoState)
   603  
   604  	seq, err := ifacestate.AllocHotplugSeq(s.st)
   605  	c.Assert(err, IsNil)
   606  	c.Assert(seq, Equals, 1)
   607  
   608  	seq, err = ifacestate.AllocHotplugSeq(s.st)
   609  	c.Assert(err, IsNil)
   610  	c.Assert(seq, Equals, 2)
   611  
   612  	c.Assert(s.st.Get("hotplug-seq", &stateSeq), IsNil)
   613  	c.Check(stateSeq, Equals, 2)
   614  }
   615  
   616  func (s *helpersSuite) TestAddHotplugSeqWaitTask(c *C) {
   617  	s.st.Lock()
   618  	defer s.st.Unlock()
   619  
   620  	chg := s.st.NewChange("foo", "")
   621  	t1 := s.st.NewTask("task1", "")
   622  	t2 := s.st.NewTask("task2", "")
   623  	chg.AddTask(t1)
   624  	chg.AddTask(t2)
   625  
   626  	ifacestate.AddHotplugSeqWaitTask(chg, "1234", 1)
   627  	// hotplug change got an extra task
   628  	c.Assert(chg.Tasks(), HasLen, 3)
   629  	seq, key, err := ifacestate.GetHotplugChangeAttrs(chg)
   630  	c.Assert(err, IsNil)
   631  	c.Check(seq, Equals, 1)
   632  	c.Check(key, DeepEquals, snap.HotplugKey("1234"))
   633  
   634  	var seqTask *state.Task
   635  	for _, t := range chg.Tasks() {
   636  		if t.Kind() == "hotplug-seq-wait" {
   637  			seqTask = t
   638  			break
   639  		}
   640  	}
   641  	c.Assert(seqTask, NotNil)
   642  
   643  	// existing tasks wait for the hotplug-seq-wait task
   644  	c.Assert(t1.WaitTasks(), HasLen, 1)
   645  	c.Assert(t1.WaitTasks()[0].ID(), Equals, seqTask.ID())
   646  	c.Assert(t2.WaitTasks(), HasLen, 1)
   647  	c.Assert(t2.WaitTasks()[0].ID(), Equals, seqTask.ID())
   648  }
   649  
   650  func (s *helpersSuite) TestAddHotplugSlot(c *C) {
   651  	s.st.Lock()
   652  	defer s.st.Unlock()
   653  
   654  	var beforePrepareSlotCalled int
   655  	repo := interfaces.NewRepository()
   656  	iface := &ifacetest.TestInterface{
   657  		InterfaceName: "test",
   658  		BeforePrepareSlotCallback: func(*snap.SlotInfo) error {
   659  			beforePrepareSlotCalled += 1
   660  			return nil
   661  		},
   662  	}
   663  	repo.AddInterface(iface)
   664  
   665  	stateSlots, err := ifacestate.GetHotplugSlots(s.st)
   666  	c.Assert(err, IsNil)
   667  	c.Check(stateSlots, HasLen, 0)
   668  
   669  	si := &snap.SideInfo{Revision: snap.R(1)}
   670  	coreInfo := snaptest.MockSnap(c, coreSnapYaml, si)
   671  
   672  	slot := &snap.SlotInfo{
   673  		Name:       "slot",
   674  		Label:      "label",
   675  		Snap:       coreInfo,
   676  		Interface:  "test",
   677  		Attrs:      map[string]interface{}{"foo": "bar"},
   678  		HotplugKey: "key",
   679  	}
   680  	c.Assert(ifacestate.AddHotplugSlot(s.st, repo, stateSlots, iface, slot), IsNil)
   681  	c.Assert(beforePrepareSlotCalled, Equals, 1)
   682  
   683  	// same slot cannot be re-added to repo
   684  	c.Assert(ifacestate.AddHotplugSlot(s.st, repo, stateSlots, iface, slot), ErrorMatches, `cannot add hotplug slot "slot" for interface test: snap "core" has slots conflicting on name "slot"`)
   685  
   686  	stateSlots, err = ifacestate.GetHotplugSlots(s.st)
   687  	c.Assert(err, IsNil)
   688  	c.Assert(stateSlots, HasLen, 1)
   689  
   690  	stateSlot := stateSlots["slot"]
   691  	c.Assert(stateSlot, NotNil)
   692  	c.Check(stateSlot, DeepEquals, &ifacestate.HotplugSlotInfo{
   693  		Name:        "slot",
   694  		Interface:   "test",
   695  		StaticAttrs: map[string]interface{}{"foo": "bar"},
   696  		HotplugKey:  "key",
   697  		HotplugGone: false})
   698  }
   699  
   700  func (s *helpersSuite) TestAddHotplugSlotValidationErrors(c *C) {
   701  	s.st.Lock()
   702  	defer s.st.Unlock()
   703  
   704  	repo := interfaces.NewRepository()
   705  	iface := &ifacetest.TestInterface{
   706  		InterfaceName:             "test",
   707  		BeforePrepareSlotCallback: func(slot *snap.SlotInfo) error { return fmt.Errorf("fail") },
   708  	}
   709  	repo.AddInterface(iface)
   710  
   711  	stateSlots, err := ifacestate.GetHotplugSlots(s.st)
   712  	c.Assert(err, IsNil)
   713  	c.Check(stateSlots, HasLen, 0)
   714  
   715  	si := &snap.SideInfo{Revision: snap.R(1)}
   716  	coreInfo := snaptest.MockSnap(c, coreSnapYaml, si)
   717  
   718  	slot := &snap.SlotInfo{
   719  		Name:      "slot",
   720  		Snap:      coreInfo,
   721  		Interface: "test",
   722  	}
   723  	// hotplug key missing
   724  	c.Assert(ifacestate.AddHotplugSlot(s.st, repo, stateSlots, iface, slot), ErrorMatches, `internal error: cannot store slot "slot", not a hotplug slot`)
   725  	slot.HotplugKey = "key"
   726  
   727  	// sanitization failure
   728  	c.Assert(ifacestate.AddHotplugSlot(s.st, repo, stateSlots, iface, slot), ErrorMatches, `cannot sanitize hotplug slot \"slot\" for interface test: fail`)
   729  }
   730  
   731  func (s *helpersSuite) TestDiscardLateBackendViaSnapstate(c *C) {
   732  	s.st.Lock()
   733  	defer s.st.Unlock()
   734  	dirs.SetRootDir(c.MkDir())
   735  	defer dirs.SetRootDir("")
   736  
   737  	// security profiles do not need regeneration when crating the manager
   738  	restore := ifacestate.MockProfilesNeedRegeneration(func() bool { return false })
   739  	defer restore()
   740  
   741  	backend := &ifacetest.TestSecurityBackendDiscardingLate{
   742  		RemoveLateCallback: func(snapName string, rev snap.Revision, typ snap.Type) error {
   743  			if snapName == "this-fails" {
   744  				return fmt.Errorf("remove late fails")
   745  			}
   746  			return nil
   747  		},
   748  	}
   749  	restore = ifacestate.MockSecurityBackends([]interfaces.SecurityBackend{backend})
   750  	defer restore()
   751  
   752  	// mock overlord
   753  	ovld := overlord.Mock()
   754  	st := ovld.State()
   755  	// manager
   756  	mgr, err := ifacestate.Manager(st, nil, ovld.TaskRunner(), nil, nil)
   757  	c.Assert(err, IsNil)
   758  	// installs the ifacemgr helper
   759  	err = mgr.StartUp()
   760  	c.Assert(err, IsNil)
   761  
   762  	// call via the snapstate hook
   763  	err = snapstate.SecurityProfilesRemoveLate("snapd", snap.R(1234), snap.TypeSnapd)
   764  	c.Assert(err, IsNil)
   765  	err = snapstate.SecurityProfilesRemoveLate("this-fails", snap.R(12), snap.TypeApp)
   766  	c.Assert(err, ErrorMatches, "remove late fails")
   767  	c.Check(backend.RemoveLateCalledFor, DeepEquals, [][]string{
   768  		{"snapd", "1234", "snapd"},
   769  		{"this-fails", "12", "app"},
   770  	})
   771  }