github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/ifacestate/ifacestate_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 ifacestate_test
    21  
    22  import (
    23  	"bytes"
    24  	"errors"
    25  	"fmt"
    26  	"io/ioutil"
    27  	"os"
    28  	"path/filepath"
    29  	"strings"
    30  	"testing"
    31  	"time"
    32  
    33  	. "gopkg.in/check.v1"
    34  	"gopkg.in/tomb.v2"
    35  
    36  	"github.com/snapcore/snapd/asserts"
    37  	"github.com/snapcore/snapd/asserts/assertstest"
    38  	"github.com/snapcore/snapd/dirs"
    39  	"github.com/snapcore/snapd/interfaces"
    40  	"github.com/snapcore/snapd/interfaces/hotplug"
    41  	"github.com/snapcore/snapd/interfaces/ifacetest"
    42  	"github.com/snapcore/snapd/logger"
    43  	"github.com/snapcore/snapd/osutil"
    44  	"github.com/snapcore/snapd/overlord"
    45  	"github.com/snapcore/snapd/overlord/assertstate"
    46  	"github.com/snapcore/snapd/overlord/hookstate"
    47  	"github.com/snapcore/snapd/overlord/ifacestate"
    48  	"github.com/snapcore/snapd/overlord/ifacestate/ifacerepo"
    49  	"github.com/snapcore/snapd/overlord/ifacestate/udevmonitor"
    50  	"github.com/snapcore/snapd/overlord/snapstate"
    51  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    52  	"github.com/snapcore/snapd/overlord/state"
    53  	"github.com/snapcore/snapd/release"
    54  	seccomp_compiler "github.com/snapcore/snapd/sandbox/seccomp"
    55  	"github.com/snapcore/snapd/snap"
    56  	"github.com/snapcore/snapd/snap/snaptest"
    57  	"github.com/snapcore/snapd/testutil"
    58  	"github.com/snapcore/snapd/timings"
    59  )
    60  
    61  func TestInterfaceManager(t *testing.T) { TestingT(t) }
    62  
    63  type cleaner interface {
    64  	AddCleanup(func())
    65  }
    66  
    67  type AssertsMock struct {
    68  	Db           *asserts.Database
    69  	storeSigning *assertstest.StoreStack
    70  	st           *state.State
    71  
    72  	cleaner cleaner
    73  }
    74  
    75  func (am *AssertsMock) SetupAsserts(c *C, st *state.State, cleaner cleaner) {
    76  	am.st = st
    77  	am.cleaner = cleaner
    78  	am.storeSigning = assertstest.NewStoreStack("canonical", nil)
    79  
    80  	db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
    81  		Backstore: asserts.NewMemoryBackstore(),
    82  		Trusted:   am.storeSigning.Trusted,
    83  	})
    84  	c.Assert(err, IsNil)
    85  	am.Db = db
    86  	err = db.Add(am.storeSigning.StoreAccountKey(""))
    87  	c.Assert(err, IsNil)
    88  
    89  	st.Lock()
    90  	assertstate.ReplaceDB(st, am.Db)
    91  	st.Unlock()
    92  }
    93  
    94  func (am *AssertsMock) mockModel(c *C, extraHeaders map[string]interface{}) *asserts.Model {
    95  	model := map[string]interface{}{
    96  		"type":         "model",
    97  		"authority-id": "my-brand",
    98  		"series":       "16",
    99  		"brand-id":     "my-brand",
   100  		"model":        "my-model",
   101  		"gadget":       "gadget",
   102  		"kernel":       "krnl",
   103  		"architecture": "amd64",
   104  		"timestamp":    time.Now().Format(time.RFC3339),
   105  	}
   106  	return assertstest.FakeAssertion(model, extraHeaders).(*asserts.Model)
   107  }
   108  
   109  func (am *AssertsMock) MockModel(c *C, extraHeaders map[string]interface{}) {
   110  	model := am.mockModel(c, extraHeaders)
   111  	am.cleaner.AddCleanup(snapstatetest.MockDeviceModel(model))
   112  }
   113  
   114  func (am *AssertsMock) TrivialDeviceContext(c *C, extraHeaders map[string]interface{}) *snapstatetest.TrivialDeviceContext {
   115  	model := am.mockModel(c, extraHeaders)
   116  	return &snapstatetest.TrivialDeviceContext{DeviceModel: model}
   117  }
   118  
   119  func (am *AssertsMock) MockSnapDecl(c *C, name, publisher string, extraHeaders map[string]interface{}) {
   120  	_, err := am.Db.Find(asserts.AccountType, map[string]string{
   121  		"account-id": publisher,
   122  	})
   123  	if asserts.IsNotFound(err) {
   124  		acct := assertstest.NewAccount(am.storeSigning, publisher, map[string]interface{}{
   125  			"account-id": publisher,
   126  		}, "")
   127  		err = am.Db.Add(acct)
   128  	}
   129  	c.Assert(err, IsNil)
   130  
   131  	headers := map[string]interface{}{
   132  		"series":       "16",
   133  		"snap-name":    name,
   134  		"publisher-id": publisher,
   135  		"snap-id":      (name + strings.Repeat("id", 16))[:32],
   136  		"timestamp":    time.Now().Format(time.RFC3339),
   137  	}
   138  	for k, v := range extraHeaders {
   139  		headers[k] = v
   140  	}
   141  
   142  	snapDecl, err := am.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
   143  	c.Assert(err, IsNil)
   144  
   145  	err = am.Db.Add(snapDecl)
   146  	c.Assert(err, IsNil)
   147  }
   148  
   149  func (am *AssertsMock) MockStore(c *C, st *state.State, storeID string, extraHeaders map[string]interface{}) {
   150  	headers := map[string]interface{}{
   151  		"store":       storeID,
   152  		"operator-id": am.storeSigning.AuthorityID,
   153  		"timestamp":   time.Now().Format(time.RFC3339),
   154  	}
   155  	for k, v := range extraHeaders {
   156  		headers[k] = v
   157  	}
   158  	storeAs, err := am.storeSigning.Sign(asserts.StoreType, headers, nil, "")
   159  	c.Assert(err, IsNil)
   160  	st.Lock()
   161  	defer st.Unlock()
   162  	err = assertstate.Add(st, storeAs)
   163  	c.Assert(err, IsNil)
   164  }
   165  
   166  type interfaceManagerSuite struct {
   167  	testutil.BaseTest
   168  	AssertsMock
   169  	o              *overlord.Overlord
   170  	state          *state.State
   171  	se             *overlord.StateEngine
   172  	privateMgr     *ifacestate.InterfaceManager
   173  	privateHookMgr *hookstate.HookManager
   174  	extraIfaces    []interfaces.Interface
   175  	extraBackends  []interfaces.SecurityBackend
   176  	secBackend     *ifacetest.TestSecurityBackend
   177  	mockSnapCmd    *testutil.MockCmd
   178  	log            *bytes.Buffer
   179  }
   180  
   181  var _ = Suite(&interfaceManagerSuite{})
   182  
   183  func (s *interfaceManagerSuite) SetUpTest(c *C) {
   184  	s.BaseTest.SetUpTest(c)
   185  	s.mockSnapCmd = testutil.MockCommand(c, "snap", "")
   186  
   187  	dirs.SetRootDir(c.MkDir())
   188  	c.Assert(os.MkdirAll(filepath.Dir(dirs.SnapSystemKeyFile), 0755), IsNil)
   189  
   190  	s.o = overlord.Mock()
   191  	s.state = s.o.State()
   192  	s.se = s.o.StateEngine()
   193  
   194  	s.SetupAsserts(c, s.state, &s.BaseTest)
   195  
   196  	s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
   197  
   198  	s.state.Lock()
   199  	defer s.state.Unlock()
   200  
   201  	s.privateHookMgr = nil
   202  	s.privateMgr = nil
   203  	s.extraIfaces = nil
   204  	s.extraBackends = nil
   205  	s.secBackend = &ifacetest.TestSecurityBackend{}
   206  	// TODO: transition this so that we don't load real backends and instead
   207  	// just load the test backend here and this is nicely integrated with
   208  	// extraBackends above.
   209  	s.BaseTest.AddCleanup(ifacestate.MockSecurityBackends([]interfaces.SecurityBackend{s.secBackend}))
   210  
   211  	buf, restore := logger.MockLogger()
   212  	s.BaseTest.AddCleanup(restore)
   213  	s.log = buf
   214  
   215  	s.BaseTest.AddCleanup(ifacestate.MockConnectRetryTimeout(0))
   216  	restore = seccomp_compiler.MockCompilerVersionInfo("abcdef 1.2.3 1234abcd -")
   217  	s.BaseTest.AddCleanup(restore)
   218  }
   219  
   220  func (s *interfaceManagerSuite) TearDownTest(c *C) {
   221  	s.BaseTest.TearDownTest(c)
   222  
   223  	s.mockSnapCmd.Restore()
   224  
   225  	if s.privateMgr != nil {
   226  		s.se.Stop()
   227  	}
   228  	dirs.SetRootDir("")
   229  }
   230  
   231  func addForeignTaskHandlers(runner *state.TaskRunner) {
   232  	// Add handler to test full aborting of changes
   233  	erroringHandler := func(task *state.Task, _ *tomb.Tomb) error {
   234  		return errors.New("error out")
   235  	}
   236  	runner.AddHandler("error-trigger", erroringHandler, nil)
   237  }
   238  
   239  func (s *interfaceManagerSuite) manager(c *C) *ifacestate.InterfaceManager {
   240  	if s.privateMgr == nil {
   241  		mgr, err := ifacestate.Manager(s.state, s.hookManager(c), s.o.TaskRunner(), s.extraIfaces, s.extraBackends)
   242  		c.Assert(err, IsNil)
   243  		addForeignTaskHandlers(s.o.TaskRunner())
   244  		mgr.DisableUDevMonitor()
   245  		s.privateMgr = mgr
   246  		s.o.AddManager(mgr)
   247  
   248  		s.o.AddManager(s.o.TaskRunner())
   249  
   250  		c.Assert(s.o.StartUp(), IsNil)
   251  
   252  		// ensure the re-generation of security profiles did not
   253  		// confuse the tests
   254  		s.secBackend.SetupCalls = nil
   255  	}
   256  	return s.privateMgr
   257  }
   258  
   259  func (s *interfaceManagerSuite) hookManager(c *C) *hookstate.HookManager {
   260  	if s.privateHookMgr == nil {
   261  		mgr, err := hookstate.Manager(s.state, s.o.TaskRunner())
   262  		c.Assert(err, IsNil)
   263  		s.privateHookMgr = mgr
   264  		s.o.AddManager(mgr)
   265  	}
   266  	return s.privateHookMgr
   267  }
   268  
   269  func (s *interfaceManagerSuite) settle(c *C) {
   270  	err := s.o.Settle(5 * time.Second)
   271  	c.Assert(err, IsNil)
   272  }
   273  
   274  func (s *interfaceManagerSuite) TestSmoke(c *C) {
   275  	s.manager(c)
   276  	s.se.Ensure()
   277  	s.se.Wait()
   278  }
   279  
   280  func (s *interfaceManagerSuite) TestRepoAvailable(c *C) {
   281  	_ = s.manager(c)
   282  	s.state.Lock()
   283  	defer s.state.Unlock()
   284  	repo := ifacerepo.Get(s.state)
   285  	c.Check(repo, FitsTypeOf, &interfaces.Repository{})
   286  }
   287  
   288  func (s *interfaceManagerSuite) TestConnectTask(c *C) {
   289  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
   290  	s.mockSnap(c, consumerYaml)
   291  	s.mockSnap(c, producerYaml)
   292  	_ = s.manager(c)
   293  
   294  	s.state.Lock()
   295  	defer s.state.Unlock()
   296  
   297  	ts, err := ifacestate.Connect(s.state, "consumer", "plug", "producer", "slot")
   298  	c.Assert(err, IsNil)
   299  
   300  	var hs hookstate.HookSetup
   301  	i := 0
   302  	task := ts.Tasks()[i]
   303  	c.Check(task.Kind(), Equals, "run-hook")
   304  	var hookSetup, undoHookSetup hookstate.HookSetup
   305  	c.Assert(task.Get("hook-setup", &hookSetup), IsNil)
   306  	c.Assert(hookSetup, Equals, hookstate.HookSetup{Snap: "consumer", Hook: "prepare-plug-plug", Optional: true})
   307  	c.Assert(task.Get("undo-hook-setup", &undoHookSetup), IsNil)
   308  	c.Assert(undoHookSetup, Equals, hookstate.HookSetup{Snap: "consumer", Hook: "unprepare-plug-plug", Optional: true, IgnoreError: true})
   309  	i++
   310  	task = ts.Tasks()[i]
   311  	c.Check(task.Kind(), Equals, "run-hook")
   312  	c.Assert(task.Get("hook-setup", &hookSetup), IsNil)
   313  	c.Assert(hookSetup, Equals, hookstate.HookSetup{Snap: "producer", Hook: "prepare-slot-slot", Optional: true})
   314  	c.Assert(task.Get("undo-hook-setup", &undoHookSetup), IsNil)
   315  	c.Assert(undoHookSetup, Equals, hookstate.HookSetup{Snap: "producer", Hook: "unprepare-slot-slot", Optional: true, IgnoreError: true})
   316  	i++
   317  	task = ts.Tasks()[i]
   318  	c.Assert(task.Kind(), Equals, "connect")
   319  	var flag bool
   320  	c.Assert(task.Get("auto", &flag), Equals, state.ErrNoState)
   321  	c.Assert(task.Get("delayed-setup-profiles", &flag), Equals, state.ErrNoState)
   322  	c.Assert(task.Get("by-gadget", &flag), Equals, state.ErrNoState)
   323  	var plug interfaces.PlugRef
   324  	c.Assert(task.Get("plug", &plug), IsNil)
   325  	c.Assert(plug.Snap, Equals, "consumer")
   326  	c.Assert(plug.Name, Equals, "plug")
   327  	var slot interfaces.SlotRef
   328  	c.Assert(task.Get("slot", &slot), IsNil)
   329  	c.Assert(slot.Snap, Equals, "producer")
   330  	c.Assert(slot.Name, Equals, "slot")
   331  
   332  	// "connect" task edge is not present
   333  	_, err = ts.Edge(ifacestate.ConnectTaskEdge)
   334  	c.Assert(err, ErrorMatches, `internal error: missing .* edge in task set`)
   335  
   336  	var autoconnect bool
   337  	err = task.Get("auto", &autoconnect)
   338  	c.Assert(err, Equals, state.ErrNoState)
   339  	c.Assert(autoconnect, Equals, false)
   340  
   341  	// verify initial attributes are present in connect task
   342  	var plugStaticAttrs map[string]interface{}
   343  	var plugDynamicAttrs map[string]interface{}
   344  	c.Assert(task.Get("plug-static", &plugStaticAttrs), IsNil)
   345  	c.Assert(plugStaticAttrs, DeepEquals, map[string]interface{}{"attr1": "value1"})
   346  	c.Assert(task.Get("plug-dynamic", &plugDynamicAttrs), IsNil)
   347  	c.Assert(plugDynamicAttrs, DeepEquals, map[string]interface{}{})
   348  
   349  	var slotStaticAttrs map[string]interface{}
   350  	var slotDynamicAttrs map[string]interface{}
   351  	c.Assert(task.Get("slot-static", &slotStaticAttrs), IsNil)
   352  	c.Assert(slotStaticAttrs, DeepEquals, map[string]interface{}{"attr2": "value2"})
   353  	c.Assert(task.Get("slot-dynamic", &slotDynamicAttrs), IsNil)
   354  	c.Assert(slotDynamicAttrs, DeepEquals, map[string]interface{}{})
   355  
   356  	i++
   357  	task = ts.Tasks()[i]
   358  	c.Check(task.Kind(), Equals, "run-hook")
   359  	c.Assert(task.Get("hook-setup", &hs), IsNil)
   360  	c.Assert(hs, Equals, hookstate.HookSetup{Snap: "producer", Hook: "connect-slot-slot", Optional: true})
   361  	c.Assert(task.Get("undo-hook-setup", &undoHookSetup), IsNil)
   362  	c.Assert(undoHookSetup, Equals, hookstate.HookSetup{Snap: "producer", Hook: "disconnect-slot-slot", Optional: true, IgnoreError: true})
   363  	i++
   364  	task = ts.Tasks()[i]
   365  	c.Check(task.Kind(), Equals, "run-hook")
   366  	c.Assert(task.Get("hook-setup", &hs), IsNil)
   367  	c.Assert(hs, Equals, hookstate.HookSetup{Snap: "consumer", Hook: "connect-plug-plug", Optional: true})
   368  	c.Assert(task.Get("undo-hook-setup", &undoHookSetup), IsNil)
   369  	c.Assert(undoHookSetup, Equals, hookstate.HookSetup{Snap: "consumer", Hook: "disconnect-plug-plug", Optional: true, IgnoreError: true})
   370  
   371  	// after-connect-hooks task edge is not present
   372  	_, err = ts.Edge(ifacestate.AfterConnectHooksEdge)
   373  	c.Assert(err, ErrorMatches, `internal error: missing .* edge in task set`)
   374  }
   375  
   376  func (s *interfaceManagerSuite) TestConnectTasksDelayProfilesFlag(c *C) {
   377  	s.mockSnap(c, consumerYaml)
   378  	s.mockSnap(c, producerYaml)
   379  
   380  	s.state.Lock()
   381  	defer s.state.Unlock()
   382  
   383  	ts, err := ifacestate.ConnectPriv(s.state, "consumer", "plug", "producer", "slot", ifacestate.NewConnectOptsWithDelayProfilesSet())
   384  	c.Assert(err, IsNil)
   385  	c.Assert(ts.Tasks(), HasLen, 5)
   386  	connectTask := ts.Tasks()[2]
   387  	c.Assert(connectTask.Kind(), Equals, "connect")
   388  	var delayedSetupProfiles bool
   389  	connectTask.Get("delayed-setup-profiles", &delayedSetupProfiles)
   390  	c.Assert(delayedSetupProfiles, Equals, true)
   391  }
   392  
   393  func (s *interfaceManagerSuite) TestBatchConnectTasks(c *C) {
   394  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
   395  	s.mockSnap(c, consumerYaml)
   396  	s.mockSnap(c, consumer2Yaml)
   397  	s.mockSnap(c, producerYaml)
   398  	_ = s.manager(c)
   399  
   400  	s.state.Lock()
   401  	defer s.state.Unlock()
   402  
   403  	snapsup := &snapstate.SnapSetup{SideInfo: &snap.SideInfo{RealName: "snap"}}
   404  	conns := make(map[string]*interfaces.ConnRef)
   405  
   406  	// no connections
   407  	autoconnect := true
   408  	ts, err := ifacestate.BatchConnectTasks(s.state, snapsup, conns, autoconnect)
   409  	c.Assert(err, IsNil)
   410  	c.Check(ts.Tasks(), HasLen, 0)
   411  
   412  	// two connections
   413  	cref1 := interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}}
   414  	cref2 := interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "consumer2", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}}
   415  	conns[cref1.ID()] = &cref1
   416  	conns[cref2.ID()] = &cref2
   417  
   418  	ts, err = ifacestate.BatchConnectTasks(s.state, snapsup, conns, autoconnect)
   419  	c.Assert(err, IsNil)
   420  	c.Check(ts.Tasks(), HasLen, 9)
   421  
   422  	// "setup-profiles" task waits for "connect" tasks of both connections
   423  	setupProfiles := ts.Tasks()[len(ts.Tasks())-1]
   424  	c.Assert(setupProfiles.Kind(), Equals, "setup-profiles")
   425  
   426  	wt := setupProfiles.WaitTasks()
   427  	c.Assert(wt, HasLen, 2)
   428  	for i := 0; i < 2; i++ {
   429  		c.Check(wt[i].Kind(), Equals, "connect")
   430  
   431  		// sanity, check flags on "connect" tasks
   432  		var flag bool
   433  		c.Assert(wt[i].Get("delayed-setup-profiles", &flag), IsNil)
   434  		c.Check(flag, Equals, true)
   435  		c.Assert(wt[i].Get("auto", &flag), IsNil)
   436  		c.Check(flag, Equals, true)
   437  	}
   438  
   439  	// connect-slot-slot hooks wait for "setup-profiles"
   440  	ht := setupProfiles.HaltTasks()
   441  	c.Assert(ht, HasLen, 2)
   442  	for i := 0; i < 2; i++ {
   443  		c.Check(ht[i].Kind(), Equals, "run-hook")
   444  		c.Check(ht[i].Summary(), Matches, "Run hook connect-slot-slot .*")
   445  	}
   446  }
   447  
   448  type interfaceHooksTestData struct {
   449  	consumer  []string
   450  	producer  []string
   451  	waitChain []string
   452  }
   453  
   454  func hookNameOrTaskKind(c *C, t *state.Task) string {
   455  	if t.Kind() == "run-hook" {
   456  		var hookSup hookstate.HookSetup
   457  		c.Assert(t.Get("hook-setup", &hookSup), IsNil)
   458  		return fmt.Sprintf("hook:%s", hookSup.Hook)
   459  	}
   460  	return fmt.Sprintf("task:%s", t.Kind())
   461  }
   462  
   463  func testInterfaceHooksTasks(c *C, tasks []*state.Task, waitChain []string, undoHooks map[string]string) {
   464  	for i, t := range tasks {
   465  		c.Assert(waitChain[i], Equals, hookNameOrTaskKind(c, t))
   466  		waits := t.WaitTasks()
   467  		if i == 0 {
   468  			c.Assert(waits, HasLen, 0)
   469  		} else {
   470  			c.Assert(waits, HasLen, 1)
   471  			waiting := hookNameOrTaskKind(c, waits[0])
   472  			// check that this task waits on previous one
   473  			c.Assert(waiting, Equals, waitChain[i-1])
   474  		}
   475  
   476  		// check undo hook setup if applicable
   477  		if t.Kind() == "run-hook" {
   478  			var hooksup hookstate.HookSetup
   479  			var undosup hookstate.HookSetup
   480  			c.Assert(t.Get("hook-setup", &hooksup), IsNil)
   481  			c.Assert(t.Get("undo-hook-setup", &undosup), IsNil)
   482  			c.Assert(undosup.Hook, Equals, undoHooks[hooksup.Hook], Commentf("unexpected undo hook: %s", undosup.Hook))
   483  		}
   484  	}
   485  
   486  }
   487  
   488  var connectHooksTests = []interfaceHooksTestData{{
   489  	consumer:  []string{"prepare-plug-plug"},
   490  	producer:  []string{"prepare-slot-slot"},
   491  	waitChain: []string{"hook:prepare-plug-plug", "hook:prepare-slot-slot", "task:connect"},
   492  }, {
   493  	consumer:  []string{"prepare-plug-plug"},
   494  	producer:  []string{"prepare-slot-slot", "connect-slot-slot"},
   495  	waitChain: []string{"hook:prepare-plug-plug", "hook:prepare-slot-slot", "task:connect", "hook:connect-slot-slot"},
   496  }, {
   497  	consumer:  []string{"prepare-plug-plug"},
   498  	producer:  []string{"connect-slot-slot"},
   499  	waitChain: []string{"hook:prepare-plug-plug", "task:connect", "hook:connect-slot-slot"},
   500  }, {
   501  	consumer:  []string{"connect-plug-plug"},
   502  	producer:  []string{"prepare-slot-slot", "connect-slot-slot"},
   503  	waitChain: []string{"hook:prepare-slot-slot", "task:connect", "hook:connect-slot-slot", "hook:connect-plug-plug"},
   504  }, {
   505  	consumer:  []string{"connect-plug-plug"},
   506  	producer:  []string{"connect-slot-slot"},
   507  	waitChain: []string{"task:connect", "hook:connect-slot-slot", "hook:connect-plug-plug"},
   508  }, {
   509  	consumer:  []string{"prepare-plug-plug", "connect-plug-plug"},
   510  	producer:  []string{"prepare-slot-slot"},
   511  	waitChain: []string{"hook:prepare-plug-plug", "hook:prepare-slot-slot", "task:connect", "hook:connect-plug-plug"},
   512  }, {
   513  	consumer:  []string{"prepare-plug-plug", "connect-plug-plug"},
   514  	producer:  []string{"prepare-slot-slot", "connect-slot-slot"},
   515  	waitChain: []string{"hook:prepare-plug-plug", "hook:prepare-slot-slot", "task:connect", "hook:connect-slot-slot", "hook:connect-plug-plug"},
   516  }}
   517  
   518  func (s *interfaceManagerSuite) TestConnectTaskHookdEdges(c *C) {
   519  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
   520  
   521  	_ = s.manager(c)
   522  	for _, hooks := range connectHooksTests {
   523  		var hooksYaml string
   524  		for _, name := range hooks.consumer {
   525  			hooksYaml = fmt.Sprintf("%s %s:\n", hooksYaml, name)
   526  		}
   527  		consumer := fmt.Sprintf(consumerYaml3, hooksYaml)
   528  
   529  		hooksYaml = ""
   530  		for _, name := range hooks.producer {
   531  			hooksYaml = fmt.Sprintf("%s %s:\n", hooksYaml, name)
   532  		}
   533  		producer := fmt.Sprintf(producerYaml3, hooksYaml)
   534  
   535  		s.mockSnap(c, consumer)
   536  		s.mockSnap(c, producer)
   537  
   538  		s.state.Lock()
   539  
   540  		ts, err := ifacestate.ConnectPriv(s.state, "consumer", "plug", "producer", "slot", ifacestate.NewConnectOptsWithDelayProfilesSet())
   541  		c.Assert(err, IsNil)
   542  
   543  		// check task edges
   544  		edge, err := ts.Edge(ifacestate.ConnectTaskEdge)
   545  		c.Assert(err, IsNil)
   546  		c.Check(edge.Kind(), Equals, "connect")
   547  
   548  		// AfterConnectHooks task edge is set on "connect-slot-" or "connect-plug-" hook task (whichever comes first after "connect")
   549  		// and is not present if neither of them exists.
   550  		var expectedAfterConnectEdge string
   551  		for _, hookName := range hooks.producer {
   552  			if strings.HasPrefix(hookName, "connect-") {
   553  				expectedAfterConnectEdge = "hook:" + hookName
   554  			}
   555  		}
   556  		if expectedAfterConnectEdge == "" {
   557  			for _, hookName := range hooks.consumer {
   558  				if strings.HasPrefix(hookName, "connect-") {
   559  					expectedAfterConnectEdge = "hook:" + hookName
   560  				}
   561  			}
   562  		}
   563  		edge, err = ts.Edge(ifacestate.AfterConnectHooksEdge)
   564  		if expectedAfterConnectEdge != "" {
   565  			c.Assert(err, IsNil)
   566  			c.Check(hookNameOrTaskKind(c, edge), Equals, expectedAfterConnectEdge)
   567  		} else {
   568  			c.Assert(err, ErrorMatches, `internal error: missing .* edge in task set`)
   569  		}
   570  
   571  		s.state.Unlock()
   572  	}
   573  }
   574  
   575  func (s *interfaceManagerSuite) TestConnectTaskHooksConditionals(c *C) {
   576  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
   577  
   578  	_ = s.manager(c)
   579  	for _, hooks := range connectHooksTests {
   580  		var hooksYaml string
   581  		for _, name := range hooks.consumer {
   582  			hooksYaml = fmt.Sprintf("%s %s:\n", hooksYaml, name)
   583  		}
   584  		consumer := fmt.Sprintf(consumerYaml3, hooksYaml)
   585  
   586  		hooksYaml = ""
   587  		for _, name := range hooks.producer {
   588  			hooksYaml = fmt.Sprintf("%s %s:\n", hooksYaml, name)
   589  		}
   590  		producer := fmt.Sprintf(producerYaml3, hooksYaml)
   591  
   592  		s.mockSnap(c, consumer)
   593  		s.mockSnap(c, producer)
   594  
   595  		s.state.Lock()
   596  
   597  		ts, err := ifacestate.Connect(s.state, "consumer", "plug", "producer", "slot")
   598  		c.Assert(err, IsNil)
   599  		c.Assert(ts.Tasks(), HasLen, len(hooks.producer)+len(hooks.consumer)+1)
   600  		c.Assert(ts.Tasks(), HasLen, len(hooks.waitChain))
   601  
   602  		undoHooks := map[string]string{
   603  			"prepare-plug-plug": "unprepare-plug-plug",
   604  			"prepare-slot-slot": "unprepare-slot-slot",
   605  			"connect-plug-plug": "disconnect-plug-plug",
   606  			"connect-slot-slot": "disconnect-slot-slot",
   607  		}
   608  
   609  		testInterfaceHooksTasks(c, ts.Tasks(), hooks.waitChain, undoHooks)
   610  		s.state.Unlock()
   611  	}
   612  }
   613  
   614  func (s *interfaceManagerSuite) TestDisconnectTaskHooksConditionals(c *C) {
   615  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
   616  
   617  	hooksTests := []interfaceHooksTestData{{
   618  		consumer:  []string{"disconnect-plug-plug"},
   619  		producer:  []string{"disconnect-slot-slot"},
   620  		waitChain: []string{"hook:disconnect-slot-slot", "hook:disconnect-plug-plug", "task:disconnect"},
   621  	}, {
   622  		producer:  []string{"disconnect-slot-slot"},
   623  		waitChain: []string{"hook:disconnect-slot-slot", "task:disconnect"},
   624  	}, {
   625  		consumer:  []string{"disconnect-plug-plug"},
   626  		waitChain: []string{"hook:disconnect-plug-plug", "task:disconnect"},
   627  	}, {
   628  		waitChain: []string{"task:disconnect"},
   629  	}}
   630  
   631  	_ = s.manager(c)
   632  	for _, hooks := range hooksTests {
   633  		var hooksYaml string
   634  		for _, name := range hooks.consumer {
   635  			hooksYaml = fmt.Sprintf("%s %s:\n", hooksYaml, name)
   636  		}
   637  		consumer := fmt.Sprintf(consumerYaml3, hooksYaml)
   638  
   639  		hooksYaml = ""
   640  		for _, name := range hooks.producer {
   641  			hooksYaml = fmt.Sprintf("%s %s:\n", hooksYaml, name)
   642  		}
   643  		producer := fmt.Sprintf(producerYaml3, hooksYaml)
   644  
   645  		plugSnap := s.mockSnap(c, consumer)
   646  		slotSnap := s.mockSnap(c, producer)
   647  
   648  		conn := &interfaces.Connection{
   649  			Plug: interfaces.NewConnectedPlug(plugSnap.Plugs["plug"], nil, nil),
   650  			Slot: interfaces.NewConnectedSlot(slotSnap.Slots["slot"], nil, nil),
   651  		}
   652  
   653  		s.state.Lock()
   654  
   655  		ts, err := ifacestate.Disconnect(s.state, conn)
   656  		c.Assert(err, IsNil)
   657  		c.Assert(ts.Tasks(), HasLen, len(hooks.producer)+len(hooks.consumer)+1)
   658  		c.Assert(ts.Tasks(), HasLen, len(hooks.waitChain))
   659  
   660  		undoHooks := map[string]string{
   661  			"disconnect-plug-plug": "connect-plug-plug",
   662  			"disconnect-slot-slot": "connect-slot-slot",
   663  		}
   664  
   665  		testInterfaceHooksTasks(c, ts.Tasks(), hooks.waitChain, undoHooks)
   666  		s.state.Unlock()
   667  	}
   668  }
   669  
   670  func (s *interfaceManagerSuite) TestParallelInstallConnectTask(c *C) {
   671  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
   672  	s.mockSnapInstance(c, "consumer_foo", consumerYaml)
   673  	s.mockSnapInstance(c, "producer", producerYaml)
   674  	_ = s.manager(c)
   675  
   676  	s.state.Lock()
   677  	defer s.state.Unlock()
   678  
   679  	ts, err := ifacestate.Connect(s.state, "consumer_foo", "plug", "producer", "slot")
   680  	c.Assert(err, IsNil)
   681  
   682  	var hs hookstate.HookSetup
   683  	i := 0
   684  	task := ts.Tasks()[i]
   685  	c.Check(task.Kind(), Equals, "run-hook")
   686  	var hookSetup hookstate.HookSetup
   687  	err = task.Get("hook-setup", &hookSetup)
   688  	c.Assert(err, IsNil)
   689  	c.Assert(hookSetup, Equals, hookstate.HookSetup{Snap: "consumer_foo", Hook: "prepare-plug-plug", Optional: true})
   690  	i++
   691  	task = ts.Tasks()[i]
   692  	c.Check(task.Kind(), Equals, "run-hook")
   693  	err = task.Get("hook-setup", &hookSetup)
   694  	c.Assert(err, IsNil)
   695  	c.Assert(hookSetup, Equals, hookstate.HookSetup{Snap: "producer", Hook: "prepare-slot-slot", Optional: true})
   696  	i++
   697  	task = ts.Tasks()[i]
   698  	c.Assert(task.Kind(), Equals, "connect")
   699  	var plug interfaces.PlugRef
   700  	err = task.Get("plug", &plug)
   701  	c.Assert(err, IsNil)
   702  	c.Assert(plug.Snap, Equals, "consumer_foo")
   703  	c.Assert(plug.Name, Equals, "plug")
   704  	var slot interfaces.SlotRef
   705  	err = task.Get("slot", &slot)
   706  	c.Assert(err, IsNil)
   707  	c.Assert(slot.Snap, Equals, "producer")
   708  	c.Assert(slot.Name, Equals, "slot")
   709  
   710  	var autoconnect bool
   711  	err = task.Get("auto", &autoconnect)
   712  	c.Assert(err, Equals, state.ErrNoState)
   713  	c.Assert(autoconnect, Equals, false)
   714  
   715  	// verify initial attributes are present in connect task
   716  	var plugStaticAttrs map[string]interface{}
   717  	var plugDynamicAttrs map[string]interface{}
   718  	err = task.Get("plug-static", &plugStaticAttrs)
   719  	c.Assert(err, IsNil)
   720  	c.Assert(plugStaticAttrs, DeepEquals, map[string]interface{}{"attr1": "value1"})
   721  	err = task.Get("plug-dynamic", &plugDynamicAttrs)
   722  	c.Assert(err, IsNil)
   723  	c.Assert(plugDynamicAttrs, DeepEquals, map[string]interface{}{})
   724  
   725  	var slotStaticAttrs map[string]interface{}
   726  	var slotDynamicAttrs map[string]interface{}
   727  	err = task.Get("slot-static", &slotStaticAttrs)
   728  	c.Assert(err, IsNil)
   729  	c.Assert(slotStaticAttrs, DeepEquals, map[string]interface{}{"attr2": "value2"})
   730  	err = task.Get("slot-dynamic", &slotDynamicAttrs)
   731  	c.Assert(err, IsNil)
   732  	c.Assert(slotDynamicAttrs, DeepEquals, map[string]interface{}{})
   733  
   734  	i++
   735  	task = ts.Tasks()[i]
   736  	c.Check(task.Kind(), Equals, "run-hook")
   737  	err = task.Get("hook-setup", &hs)
   738  	c.Assert(err, IsNil)
   739  	c.Assert(hs, Equals, hookstate.HookSetup{Snap: "producer", Hook: "connect-slot-slot", Optional: true})
   740  	i++
   741  	task = ts.Tasks()[i]
   742  	c.Check(task.Kind(), Equals, "run-hook")
   743  	err = task.Get("hook-setup", &hs)
   744  	c.Assert(err, IsNil)
   745  	c.Assert(hs, Equals, hookstate.HookSetup{Snap: "consumer_foo", Hook: "connect-plug-plug", Optional: true})
   746  }
   747  
   748  func (s *interfaceManagerSuite) TestConnectAlreadyConnected(c *C) {
   749  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
   750  	s.mockSnap(c, consumerYaml)
   751  	s.mockSnap(c, producerYaml)
   752  	_ = s.manager(c)
   753  
   754  	s.state.Lock()
   755  	defer s.state.Unlock()
   756  
   757  	conns := map[string]interface{}{
   758  		"consumer:plug producer:slot": map[string]interface{}{
   759  			"auto": false,
   760  		},
   761  	}
   762  	s.state.Set("conns", conns)
   763  
   764  	ts, err := ifacestate.Connect(s.state, "consumer", "plug", "producer", "slot")
   765  	c.Assert(err, NotNil)
   766  	c.Assert(ts, IsNil)
   767  	alreadyConnected, ok := err.(*ifacestate.ErrAlreadyConnected)
   768  	c.Assert(ok, Equals, true)
   769  	c.Assert(alreadyConnected.Connection, DeepEquals, interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}})
   770  	c.Assert(err, ErrorMatches, `already connected: "consumer:plug producer:slot"`)
   771  
   772  	conns = map[string]interface{}{
   773  		"consumer:plug producer:slot": map[string]interface{}{
   774  			"auto":      true,
   775  			"undesired": true,
   776  		},
   777  	}
   778  	s.state.Set("conns", conns)
   779  
   780  	// ErrAlreadyConnected is not reported if connection exists but is undesired
   781  	ts, err = ifacestate.Connect(s.state, "consumer", "plug", "producer", "slot")
   782  	c.Assert(err, IsNil)
   783  	c.Assert(ts, NotNil)
   784  
   785  	conns = map[string]interface{}{"consumer:plug producer:slot": map[string]interface{}{"hotplug-gone": true}}
   786  	s.state.Set("conns", conns)
   787  
   788  	// ErrAlreadyConnected is not reported if connection was removed by hotplug
   789  	ts, err = ifacestate.Connect(s.state, "consumer", "plug", "producer", "slot")
   790  	c.Assert(err, IsNil)
   791  	c.Assert(ts, NotNil)
   792  }
   793  
   794  func (s *interfaceManagerSuite) testConnectDisconnectConflicts(c *C, f func(*state.State, string, string, string, string) (*state.TaskSet, error), snapName string, otherTaskKind string, expectedErr string) {
   795  	s.state.Lock()
   796  	defer s.state.Unlock()
   797  
   798  	chg := s.state.NewChange("other-chg", "...")
   799  	t := s.state.NewTask(otherTaskKind, "...")
   800  	t.Set("snap-setup", &snapstate.SnapSetup{
   801  		SideInfo: &snap.SideInfo{
   802  			RealName: snapName},
   803  	})
   804  	chg.AddTask(t)
   805  
   806  	_, err := f(s.state, "consumer", "plug", "producer", "slot")
   807  	c.Assert(err, ErrorMatches, expectedErr)
   808  }
   809  
   810  func (s *interfaceManagerSuite) testDisconnectConflicts(c *C, snapName string, otherTaskKind string, expectedErr string) {
   811  	s.state.Lock()
   812  	defer s.state.Unlock()
   813  
   814  	chg := s.state.NewChange("other-chg", "...")
   815  	t := s.state.NewTask(otherTaskKind, "...")
   816  	t.Set("snap-setup", &snapstate.SnapSetup{
   817  		SideInfo: &snap.SideInfo{
   818  			RealName: snapName},
   819  	})
   820  	chg.AddTask(t)
   821  
   822  	conn := &interfaces.Connection{
   823  		Plug: interfaces.NewConnectedPlug(&snap.PlugInfo{Snap: &snap.Info{SuggestedName: "consumer"}, Name: "plug"}, nil, nil),
   824  		Slot: interfaces.NewConnectedSlot(&snap.SlotInfo{Snap: &snap.Info{SuggestedName: "producer"}, Name: "slot"}, nil, nil),
   825  	}
   826  
   827  	_, err := ifacestate.Disconnect(s.state, conn)
   828  	c.Assert(err, ErrorMatches, expectedErr)
   829  }
   830  
   831  func (s *interfaceManagerSuite) TestConnectConflictsPlugSnapOnLinkSnap(c *C) {
   832  	s.testConnectDisconnectConflicts(c, ifacestate.Connect, "consumer", "link-snap", `snap "consumer" has "other-chg" change in progress`)
   833  }
   834  
   835  func (s *interfaceManagerSuite) TestConnectConflictsPlugSnapOnUnlink(c *C) {
   836  	s.testConnectDisconnectConflicts(c, ifacestate.Connect, "consumer", "unlink-snap", `snap "consumer" has "other-chg" change in progress`)
   837  }
   838  
   839  func (s *interfaceManagerSuite) TestConnectConflictsSlotSnap(c *C) {
   840  	s.testConnectDisconnectConflicts(c, ifacestate.Connect, "producer", "link-snap", `snap "producer" has "other-chg" change in progress`)
   841  }
   842  
   843  func (s *interfaceManagerSuite) TestConnectConflictsSlotSnapOnUnlink(c *C) {
   844  	s.testConnectDisconnectConflicts(c, ifacestate.Connect, "producer", "unlink-snap", `snap "producer" has "other-chg" change in progress`)
   845  }
   846  
   847  func (s *interfaceManagerSuite) TestDisconnectConflictsPlugSnapOnLink(c *C) {
   848  	s.testDisconnectConflicts(c, "consumer", "link-snap", `snap "consumer" has "other-chg" change in progress`)
   849  }
   850  
   851  func (s *interfaceManagerSuite) TestDisconnectConflictsSlotSnapOnLink(c *C) {
   852  	s.testDisconnectConflicts(c, "producer", "link-snap", `snap "producer" has "other-chg" change in progress`)
   853  }
   854  
   855  func (s *interfaceManagerSuite) TestConnectDoesConflict(c *C) {
   856  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
   857  	s.mockSnap(c, consumerYaml)
   858  	s.mockSnap(c, producerYaml)
   859  
   860  	s.state.Lock()
   861  	defer s.state.Unlock()
   862  
   863  	chg := s.state.NewChange("other-connect", "...")
   864  	t := s.state.NewTask("connect", "other connect task")
   865  	t.Set("slot", interfaces.SlotRef{Snap: "producer", Name: "slot"})
   866  	t.Set("plug", interfaces.PlugRef{Snap: "consumer", Name: "plug"})
   867  	chg.AddTask(t)
   868  
   869  	_, err := ifacestate.Connect(s.state, "consumer", "plug", "producer", "slot")
   870  	c.Assert(err, ErrorMatches, `snap "consumer" has "other-connect" change in progress`)
   871  
   872  	conn := &interfaces.Connection{
   873  		Plug: interfaces.NewConnectedPlug(&snap.PlugInfo{Snap: &snap.Info{SuggestedName: "consumer"}, Name: "plug"}, nil, nil),
   874  		Slot: interfaces.NewConnectedSlot(&snap.SlotInfo{Snap: &snap.Info{SuggestedName: "producer"}, Name: "slot"}, nil, nil),
   875  	}
   876  	_, err = ifacestate.Disconnect(s.state, conn)
   877  	c.Assert(err, ErrorMatches, `snap "consumer" has "other-connect" change in progress`)
   878  }
   879  
   880  func (s *interfaceManagerSuite) TestConnectBecomeOperationalNoConflict(c *C) {
   881  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
   882  	s.mockSnap(c, consumerYaml)
   883  	s.mockSnap(c, producerYaml)
   884  
   885  	s.state.Lock()
   886  	defer s.state.Unlock()
   887  
   888  	chg := s.state.NewChange("become-operational", "...")
   889  	hooksup := &hookstate.HookSetup{
   890  		Snap: "producer",
   891  		Hook: "prepare-device",
   892  	}
   893  	t := hookstate.HookTask(s.state, "prep", hooksup, nil)
   894  	chg.AddTask(t)
   895  
   896  	_, err := ifacestate.Connect(s.state, "consumer", "plug", "producer", "slot")
   897  	c.Assert(err, IsNil)
   898  }
   899  
   900  func (s *interfaceManagerSuite) TestAutoconnectDoesntConflictOnInstallingDifferentSnap(c *C) {
   901  	s.mockSnap(c, consumerYaml)
   902  	s.mockSnap(c, producerYaml)
   903  
   904  	s.state.Lock()
   905  	defer s.state.Unlock()
   906  
   907  	sup1 := &snapstate.SnapSetup{
   908  		SideInfo: &snap.SideInfo{
   909  			RealName: "consumer"},
   910  	}
   911  	sup2 := &snapstate.SnapSetup{
   912  		SideInfo: &snap.SideInfo{
   913  			RealName: "othersnap"},
   914  	}
   915  
   916  	chg := s.state.NewChange("install", "...")
   917  	t := s.state.NewTask("link-snap", "...")
   918  	t.Set("snap-setup", sup2)
   919  	chg.AddTask(t)
   920  
   921  	t = s.state.NewTask("auto-connect", "...")
   922  	t.Set("snap-setup", sup1)
   923  	chg.AddTask(t)
   924  
   925  	ignore, err := ifacestate.FindSymmetricAutoconnectTask(s.state, "consumer", "producer", t)
   926  	c.Assert(err, IsNil)
   927  	c.Assert(ignore, Equals, false)
   928  	c.Assert(ifacestate.CheckAutoconnectConflicts(s.state, t, "consumer", "producer"), IsNil)
   929  
   930  	ts, err := ifacestate.ConnectPriv(s.state, "consumer", "plug", "producer", "slot", ifacestate.NewConnectOptsWithAutoSet())
   931  	c.Assert(err, IsNil)
   932  	c.Assert(ts.Tasks(), HasLen, 5)
   933  	connectTask := ts.Tasks()[2]
   934  	c.Assert(connectTask.Kind(), Equals, "connect")
   935  	var auto bool
   936  	connectTask.Get("auto", &auto)
   937  	c.Assert(auto, Equals, true)
   938  }
   939  
   940  func (s *interfaceManagerSuite) createAutoconnectChange(c *C, conflictingTask *state.Task) error {
   941  	s.mockSnap(c, consumerYaml)
   942  	s.mockSnap(c, producerYaml)
   943  
   944  	s.state.Lock()
   945  	defer s.state.Unlock()
   946  
   947  	chg1 := s.state.NewChange("a change", "...")
   948  	conflictingTask.Set("snap-setup", &snapstate.SnapSetup{
   949  		SideInfo: &snap.SideInfo{
   950  			RealName: "consumer"},
   951  	})
   952  	chg1.AddTask(conflictingTask)
   953  
   954  	chg := s.state.NewChange("other-chg", "...")
   955  	t2 := s.state.NewTask("auto-connect", "...")
   956  	t2.Set("snap-setup", &snapstate.SnapSetup{
   957  		SideInfo: &snap.SideInfo{
   958  			RealName: "producer"},
   959  	})
   960  
   961  	chg.AddTask(t2)
   962  
   963  	ignore, err := ifacestate.FindSymmetricAutoconnectTask(s.state, "consumer", "producer", t2)
   964  	c.Assert(err, IsNil)
   965  	c.Assert(ignore, Equals, false)
   966  
   967  	return ifacestate.CheckAutoconnectConflicts(s.state, t2, "consumer", "producer")
   968  }
   969  
   970  func (s *interfaceManagerSuite) testRetryError(c *C, err error) {
   971  	c.Assert(err, NotNil)
   972  	c.Assert(err, ErrorMatches, `task should be retried`)
   973  	rerr, ok := err.(*state.Retry)
   974  	c.Assert(ok, Equals, true)
   975  	c.Assert(rerr, NotNil)
   976  }
   977  
   978  func (s *interfaceManagerSuite) TestAutoconnectConflictOnUnlink(c *C) {
   979  	s.state.Lock()
   980  	task := s.state.NewTask("unlink-snap", "")
   981  	s.state.Unlock()
   982  	err := s.createAutoconnectChange(c, task)
   983  	s.testRetryError(c, err)
   984  }
   985  
   986  func (s *interfaceManagerSuite) TestAutoconnectConflictOnDiscardSnap(c *C) {
   987  	s.state.Lock()
   988  	task := s.state.NewTask("discard-snap", "")
   989  	s.state.Unlock()
   990  	err := s.createAutoconnectChange(c, task)
   991  	s.testRetryError(c, err)
   992  }
   993  
   994  func (s *interfaceManagerSuite) TestAutoconnectConflictOnLink(c *C) {
   995  	s.state.Lock()
   996  	task := s.state.NewTask("link-snap", "")
   997  	s.state.Unlock()
   998  	err := s.createAutoconnectChange(c, task)
   999  	s.testRetryError(c, err)
  1000  }
  1001  
  1002  func (s *interfaceManagerSuite) TestAutoconnectConflictOnSetupProfiles(c *C) {
  1003  	s.state.Lock()
  1004  	task := s.state.NewTask("setup-profiles", "")
  1005  	s.state.Unlock()
  1006  	err := s.createAutoconnectChange(c, task)
  1007  	s.testRetryError(c, err)
  1008  }
  1009  
  1010  func (s *interfaceManagerSuite) TestSymmetricAutoconnectIgnore(c *C) {
  1011  	s.mockSnap(c, consumerYaml)
  1012  	s.mockSnap(c, producerYaml)
  1013  
  1014  	s.state.Lock()
  1015  	defer s.state.Unlock()
  1016  
  1017  	sup1 := &snapstate.SnapSetup{
  1018  		SideInfo: &snap.SideInfo{
  1019  			RealName: "consumer"},
  1020  	}
  1021  	sup2 := &snapstate.SnapSetup{
  1022  		SideInfo: &snap.SideInfo{
  1023  			RealName: "producer"},
  1024  	}
  1025  
  1026  	chg1 := s.state.NewChange("install", "...")
  1027  	t1 := s.state.NewTask("auto-connect", "...")
  1028  	t1.Set("snap-setup", sup1)
  1029  	chg1.AddTask(t1)
  1030  
  1031  	chg2 := s.state.NewChange("install", "...")
  1032  	t2 := s.state.NewTask("auto-connect", "...")
  1033  	t2.Set("snap-setup", sup2)
  1034  	chg2.AddTask(t2)
  1035  
  1036  	ignore, err := ifacestate.FindSymmetricAutoconnectTask(s.state, "consumer", "producer", t1)
  1037  	c.Assert(err, IsNil)
  1038  	c.Assert(ignore, Equals, true)
  1039  
  1040  	ignore, err = ifacestate.FindSymmetricAutoconnectTask(s.state, "consumer", "producer", t2)
  1041  	c.Assert(err, IsNil)
  1042  	c.Assert(ignore, Equals, true)
  1043  }
  1044  
  1045  func (s *interfaceManagerSuite) TestAutoconnectConflictOnConnectWithAutoFlag(c *C) {
  1046  	s.state.Lock()
  1047  	task := s.state.NewTask("connect", "")
  1048  	task.Set("slot", interfaces.SlotRef{Snap: "producer", Name: "slot"})
  1049  	task.Set("plug", interfaces.PlugRef{Snap: "consumer", Name: "plug"})
  1050  	task.Set("auto", true)
  1051  	s.state.Unlock()
  1052  
  1053  	err := s.createAutoconnectChange(c, task)
  1054  	c.Assert(err, NotNil)
  1055  	c.Assert(err, ErrorMatches, `task should be retried`)
  1056  }
  1057  
  1058  func (s *interfaceManagerSuite) TestAutoconnectRetryOnConnect(c *C) {
  1059  	s.state.Lock()
  1060  	task := s.state.NewTask("connect", "")
  1061  	task.Set("slot", interfaces.SlotRef{Snap: "producer", Name: "slot"})
  1062  	task.Set("plug", interfaces.PlugRef{Snap: "consumer", Name: "plug"})
  1063  	task.Set("auto", false)
  1064  	s.state.Unlock()
  1065  
  1066  	err := s.createAutoconnectChange(c, task)
  1067  	c.Assert(err, ErrorMatches, `task should be retried`)
  1068  }
  1069  
  1070  func (s *interfaceManagerSuite) TestAutoconnectIgnoresSetupProfilesPhase2(c *C) {
  1071  	s.MockModel(c, nil)
  1072  
  1073  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
  1074  	s.mockSnap(c, consumerYaml)
  1075  	s.mockSnap(c, producerYaml)
  1076  
  1077  	_ = s.manager(c)
  1078  
  1079  	s.state.Lock()
  1080  	defer s.state.Unlock()
  1081  
  1082  	sup := &snapstate.SnapSetup{
  1083  		SideInfo: &snap.SideInfo{
  1084  			Revision: snap.R(1),
  1085  			RealName: "consumer"},
  1086  	}
  1087  
  1088  	chg := s.state.NewChange("install", "...")
  1089  	t1 := s.state.NewTask("auto-connect", "...")
  1090  	t1.Set("snap-setup", sup)
  1091  
  1092  	t2 := s.state.NewTask("setup-profiles", "...")
  1093  	corePhase2 := true
  1094  	t2.Set("core-phase-2", corePhase2)
  1095  	t2.Set("snap-setup", sup)
  1096  	t2.WaitFor(t1)
  1097  	chg.AddTask(t1)
  1098  	chg.AddTask(t2)
  1099  
  1100  	s.state.Unlock()
  1101  	s.se.Ensure()
  1102  	s.se.Wait()
  1103  	s.state.Lock()
  1104  
  1105  	c.Assert(chg.Err(), IsNil)
  1106  	// auto-connect task is done
  1107  	c.Assert(t1.Status(), Equals, state.DoneStatus)
  1108  	// change not finished because of hook tasks
  1109  	c.Assert(chg.Status(), Equals, state.DoStatus)
  1110  }
  1111  
  1112  func (s *interfaceManagerSuite) TestEnsureProcessesConnectTask(c *C) {
  1113  	s.MockModel(c, nil)
  1114  
  1115  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  1116  	s.mockSnap(c, consumerYaml)
  1117  	s.mockSnap(c, producerYaml)
  1118  	_ = s.manager(c)
  1119  
  1120  	s.state.Lock()
  1121  	change := s.state.NewChange("kind", "summary")
  1122  	ts, err := ifacestate.Connect(s.state, "consumer", "plug", "producer", "slot")
  1123  
  1124  	c.Assert(err, IsNil)
  1125  	c.Assert(ts.Tasks(), HasLen, 5)
  1126  	ts.Tasks()[2].Set("snap-setup", &snapstate.SnapSetup{
  1127  		SideInfo: &snap.SideInfo{
  1128  			RealName: "consumer",
  1129  		},
  1130  	})
  1131  
  1132  	change.AddAll(ts)
  1133  	s.state.Unlock()
  1134  
  1135  	s.settle(c)
  1136  
  1137  	s.state.Lock()
  1138  	defer s.state.Unlock()
  1139  
  1140  	i := 0
  1141  	c.Assert(change.Err(), IsNil)
  1142  	task := change.Tasks()[i]
  1143  	c.Check(task.Kind(), Equals, "run-hook")
  1144  	c.Check(task.Status(), Equals, state.DoneStatus)
  1145  	i++
  1146  	task = change.Tasks()[i]
  1147  	c.Check(task.Kind(), Equals, "run-hook")
  1148  	c.Check(task.Status(), Equals, state.DoneStatus)
  1149  	i++
  1150  	task = change.Tasks()[i]
  1151  	c.Check(task.Kind(), Equals, "connect")
  1152  	c.Check(task.Status(), Equals, state.DoneStatus)
  1153  	c.Check(change.Status(), Equals, state.DoneStatus)
  1154  
  1155  	repo := s.manager(c).Repository()
  1156  	ifaces := repo.Interfaces()
  1157  	c.Assert(ifaces.Connections, HasLen, 1)
  1158  	c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{
  1159  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  1160  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}}})
  1161  }
  1162  
  1163  func (s *interfaceManagerSuite) TestConnectTaskCheckInterfaceMismatch(c *C) {
  1164  	s.MockModel(c, nil)
  1165  
  1166  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  1167  	s.mockSnap(c, consumerYaml)
  1168  	s.mockSnap(c, producerYaml)
  1169  	_ = s.manager(c)
  1170  
  1171  	s.state.Lock()
  1172  	change := s.state.NewChange("kind", "summary")
  1173  	ts, err := ifacestate.Connect(s.state, "consumer", "otherplug", "producer", "slot")
  1174  	c.Assert(err, IsNil)
  1175  
  1176  	c.Assert(ts.Tasks(), HasLen, 5)
  1177  	c.Check(ts.Tasks()[2].Kind(), Equals, "connect")
  1178  	ts.Tasks()[2].Set("snap-setup", &snapstate.SnapSetup{
  1179  		SideInfo: &snap.SideInfo{
  1180  			RealName: "consumer",
  1181  		},
  1182  	})
  1183  
  1184  	change.AddAll(ts)
  1185  	s.state.Unlock()
  1186  
  1187  	s.settle(c)
  1188  
  1189  	s.state.Lock()
  1190  	defer s.state.Unlock()
  1191  
  1192  	c.Check(change.Err(), ErrorMatches, `cannot perform the following tasks:\n- Connect consumer:otherplug to producer:slot \(cannot connect plug "consumer:otherplug" \(interface "test2"\) to "producer:slot" \(interface "test".*`)
  1193  	task := change.Tasks()[2]
  1194  	c.Check(task.Kind(), Equals, "connect")
  1195  	c.Check(task.Status(), Equals, state.ErrorStatus)
  1196  	c.Check(change.Status(), Equals, state.ErrorStatus)
  1197  }
  1198  
  1199  func (s *interfaceManagerSuite) TestConnectTaskNoSuchSlot(c *C) {
  1200  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  1201  	s.mockSnap(c, consumerYaml)
  1202  	s.mockSnap(c, producerYaml)
  1203  	_ = s.manager(c)
  1204  
  1205  	s.state.Lock()
  1206  	_ = s.state.NewChange("kind", "summary")
  1207  	_, err := ifacestate.Connect(s.state, "consumer", "plug", "producer", "whatslot")
  1208  	c.Assert(err, ErrorMatches, `snap "producer" has no slot named "whatslot"`)
  1209  }
  1210  
  1211  func (s *interfaceManagerSuite) TestConnectTaskNoSuchPlug(c *C) {
  1212  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  1213  	s.mockSnap(c, consumerYaml)
  1214  	s.mockSnap(c, producerYaml)
  1215  	_ = s.manager(c)
  1216  
  1217  	s.state.Lock()
  1218  	_ = s.state.NewChange("kind", "summary")
  1219  	_, err := ifacestate.Connect(s.state, "consumer", "whatplug", "producer", "slot")
  1220  	c.Assert(err, ErrorMatches, `snap "consumer" has no plug named "whatplug"`)
  1221  }
  1222  
  1223  func (s *interfaceManagerSuite) TestConnectTaskCheckNotAllowed(c *C) {
  1224  	s.MockModel(c, nil)
  1225  
  1226  	s.testConnectTaskCheck(c, func() {
  1227  		s.MockSnapDecl(c, "consumer", "consumer-publisher", nil)
  1228  		s.mockSnap(c, consumerYaml)
  1229  		s.MockSnapDecl(c, "producer", "producer-publisher", nil)
  1230  		s.mockSnap(c, producerYaml)
  1231  	}, func(change *state.Change) {
  1232  		c.Check(change.Err(), ErrorMatches, `(?s).*connection not allowed by slot rule of interface "test".*`)
  1233  		c.Check(change.Status(), Equals, state.ErrorStatus)
  1234  
  1235  		repo := s.manager(c).Repository()
  1236  		ifaces := repo.Interfaces()
  1237  		c.Check(ifaces.Connections, HasLen, 0)
  1238  	})
  1239  }
  1240  
  1241  func (s *interfaceManagerSuite) TestConnectTaskCheckNotAllowedButNoDecl(c *C) {
  1242  	s.MockModel(c, nil)
  1243  
  1244  	s.testConnectTaskCheck(c, func() {
  1245  		s.mockSnap(c, consumerYaml)
  1246  		s.mockSnap(c, producerYaml)
  1247  	}, func(change *state.Change) {
  1248  		c.Check(change.Err(), IsNil)
  1249  		c.Check(change.Status(), Equals, state.DoneStatus)
  1250  
  1251  		repo := s.manager(c).Repository()
  1252  		ifaces := repo.Interfaces()
  1253  		c.Assert(ifaces.Connections, HasLen, 1)
  1254  		c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{
  1255  			PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  1256  			SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}}})
  1257  	})
  1258  }
  1259  
  1260  func (s *interfaceManagerSuite) TestConnectTaskCheckAllowed(c *C) {
  1261  	s.MockModel(c, nil)
  1262  
  1263  	s.testConnectTaskCheck(c, func() {
  1264  		s.MockSnapDecl(c, "consumer", "one-publisher", nil)
  1265  		s.mockSnap(c, consumerYaml)
  1266  		s.MockSnapDecl(c, "producer", "one-publisher", nil)
  1267  		s.mockSnap(c, producerYaml)
  1268  	}, func(change *state.Change) {
  1269  		c.Assert(change.Err(), IsNil)
  1270  		c.Check(change.Status(), Equals, state.DoneStatus)
  1271  
  1272  		repo := s.manager(c).Repository()
  1273  		ifaces := repo.Interfaces()
  1274  		c.Assert(ifaces.Connections, HasLen, 1)
  1275  		c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{
  1276  			PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  1277  			SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}}})
  1278  	})
  1279  }
  1280  
  1281  func (s *interfaceManagerSuite) testConnectTaskCheck(c *C, setup func(), check func(*state.Change)) {
  1282  	restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
  1283  type: base-declaration
  1284  authority-id: canonical
  1285  series: 16
  1286  slots:
  1287    test:
  1288      allow-connection:
  1289        plug-publisher-id:
  1290          - $SLOT_PUBLISHER_ID
  1291  `))
  1292  	defer restore()
  1293  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  1294  
  1295  	setup()
  1296  	_ = s.manager(c)
  1297  
  1298  	s.state.Lock()
  1299  	change := s.state.NewChange("kind", "summary")
  1300  	ts, err := ifacestate.Connect(s.state, "consumer", "plug", "producer", "slot")
  1301  	c.Assert(err, IsNil)
  1302  	c.Assert(ts.Tasks(), HasLen, 5)
  1303  	ts.Tasks()[0].Set("snap-setup", &snapstate.SnapSetup{
  1304  		SideInfo: &snap.SideInfo{
  1305  			RealName: "consumer",
  1306  		},
  1307  	})
  1308  
  1309  	change.AddAll(ts)
  1310  	s.state.Unlock()
  1311  
  1312  	s.settle(c)
  1313  
  1314  	s.state.Lock()
  1315  	defer s.state.Unlock()
  1316  
  1317  	check(change)
  1318  }
  1319  
  1320  func (s *interfaceManagerSuite) TestConnectTaskCheckDeviceScopeNoStore(c *C) {
  1321  	s.MockModel(c, nil)
  1322  
  1323  	s.testConnectTaskCheckDeviceScope(c, func(change *state.Change) {
  1324  		c.Check(change.Err(), ErrorMatches, `(?s).*connection not allowed by plug rule of interface "test".*`)
  1325  		c.Check(change.Status(), Equals, state.ErrorStatus)
  1326  
  1327  		repo := s.manager(c).Repository()
  1328  		ifaces := repo.Interfaces()
  1329  		c.Check(ifaces.Connections, HasLen, 0)
  1330  	})
  1331  }
  1332  
  1333  func (s *interfaceManagerSuite) TestConnectTaskCheckDeviceScopeWrongStore(c *C) {
  1334  	s.MockModel(c, map[string]interface{}{
  1335  		"store": "other-store",
  1336  	})
  1337  
  1338  	s.testConnectTaskCheckDeviceScope(c, func(change *state.Change) {
  1339  		c.Check(change.Err(), ErrorMatches, `(?s).*connection not allowed by plug rule of interface "test".*`)
  1340  		c.Check(change.Status(), Equals, state.ErrorStatus)
  1341  
  1342  		repo := s.manager(c).Repository()
  1343  		ifaces := repo.Interfaces()
  1344  		c.Check(ifaces.Connections, HasLen, 0)
  1345  	})
  1346  }
  1347  
  1348  func (s *interfaceManagerSuite) TestConnectTaskCheckDeviceScopeRightStore(c *C) {
  1349  	s.MockModel(c, map[string]interface{}{
  1350  		"store": "my-store",
  1351  	})
  1352  
  1353  	s.testConnectTaskCheckDeviceScope(c, func(change *state.Change) {
  1354  		c.Assert(change.Err(), IsNil)
  1355  		c.Check(change.Status(), Equals, state.DoneStatus)
  1356  
  1357  		repo := s.manager(c).Repository()
  1358  		ifaces := repo.Interfaces()
  1359  		c.Assert(ifaces.Connections, HasLen, 1)
  1360  		c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{
  1361  			PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  1362  			SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}}})
  1363  	})
  1364  }
  1365  
  1366  func (s *interfaceManagerSuite) TestConnectTaskCheckDeviceScopeWrongFriendlyStore(c *C) {
  1367  	s.MockModel(c, map[string]interface{}{
  1368  		"store": "my-substore",
  1369  	})
  1370  
  1371  	s.MockStore(c, s.state, "my-substore", map[string]interface{}{
  1372  		"friendly-stores": []interface{}{"other-store"},
  1373  	})
  1374  
  1375  	s.testConnectTaskCheckDeviceScope(c, func(change *state.Change) {
  1376  		c.Check(change.Err(), ErrorMatches, `(?s).*connection not allowed by plug rule of interface "test".*`)
  1377  		c.Check(change.Status(), Equals, state.ErrorStatus)
  1378  
  1379  		repo := s.manager(c).Repository()
  1380  		ifaces := repo.Interfaces()
  1381  		c.Check(ifaces.Connections, HasLen, 0)
  1382  	})
  1383  }
  1384  
  1385  func (s *interfaceManagerSuite) TestConnectTaskCheckDeviceScopeRightFriendlyStore(c *C) {
  1386  	s.MockModel(c, map[string]interface{}{
  1387  		"store": "my-substore",
  1388  	})
  1389  
  1390  	s.MockStore(c, s.state, "my-substore", map[string]interface{}{
  1391  		"friendly-stores": []interface{}{"my-store"},
  1392  	})
  1393  
  1394  	s.testConnectTaskCheckDeviceScope(c, func(change *state.Change) {
  1395  		c.Assert(change.Err(), IsNil)
  1396  		c.Check(change.Status(), Equals, state.DoneStatus)
  1397  
  1398  		repo := s.manager(c).Repository()
  1399  		ifaces := repo.Interfaces()
  1400  		c.Assert(ifaces.Connections, HasLen, 1)
  1401  		c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{
  1402  			PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  1403  			SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}}})
  1404  	})
  1405  }
  1406  
  1407  func (s *interfaceManagerSuite) testConnectTaskCheckDeviceScope(c *C, check func(*state.Change)) {
  1408  	restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
  1409  type: base-declaration
  1410  authority-id: canonical
  1411  series: 16
  1412  slots:
  1413    test:
  1414      allow-connection: false
  1415  `))
  1416  	defer restore()
  1417  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
  1418  
  1419  	s.MockSnapDecl(c, "producer", "one-publisher", nil)
  1420  	s.mockSnap(c, producerYaml)
  1421  	s.MockSnapDecl(c, "consumer", "one-publisher", map[string]interface{}{
  1422  		"format": "3",
  1423  		"plugs": map[string]interface{}{
  1424  			"test": map[string]interface{}{
  1425  				"allow-connection": map[string]interface{}{
  1426  					"on-store": []interface{}{"my-store"},
  1427  				},
  1428  			},
  1429  		},
  1430  	})
  1431  	s.mockSnap(c, consumerYaml)
  1432  
  1433  	s.manager(c)
  1434  
  1435  	s.state.Lock()
  1436  	change := s.state.NewChange("kind", "summary")
  1437  	ts, err := ifacestate.Connect(s.state, "consumer", "plug", "producer", "slot")
  1438  	c.Assert(err, IsNil)
  1439  	c.Assert(ts.Tasks(), HasLen, 5)
  1440  
  1441  	change.AddAll(ts)
  1442  	s.state.Unlock()
  1443  
  1444  	s.settle(c)
  1445  
  1446  	s.state.Lock()
  1447  	defer s.state.Unlock()
  1448  
  1449  	check(change)
  1450  }
  1451  
  1452  func (s *interfaceManagerSuite) TestDisconnectTask(c *C) {
  1453  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  1454  	plugSnap := s.mockSnap(c, consumerYaml)
  1455  	slotSnap := s.mockSnap(c, producerYaml)
  1456  
  1457  	conn := &interfaces.Connection{
  1458  		Plug: interfaces.NewConnectedPlug(plugSnap.Plugs["plug"], nil, map[string]interface{}{"attr3": "value3"}),
  1459  		Slot: interfaces.NewConnectedSlot(slotSnap.Slots["slot"], nil, map[string]interface{}{"attr4": "value4"}),
  1460  	}
  1461  
  1462  	s.state.Lock()
  1463  	defer s.state.Unlock()
  1464  
  1465  	ts, err := ifacestate.Disconnect(s.state, conn)
  1466  	c.Assert(err, IsNil)
  1467  	c.Assert(ts.Tasks(), HasLen, 3)
  1468  
  1469  	var hookSetup, undoHookSetup hookstate.HookSetup
  1470  	task := ts.Tasks()[0]
  1471  	c.Assert(task.Kind(), Equals, "run-hook")
  1472  	c.Assert(task.Get("hook-setup", &hookSetup), IsNil)
  1473  	c.Assert(hookSetup, Equals, hookstate.HookSetup{Snap: "producer", Hook: "disconnect-slot-slot", Optional: true, IgnoreError: false})
  1474  	c.Assert(task.Get("undo-hook-setup", &undoHookSetup), IsNil)
  1475  	c.Assert(undoHookSetup, Equals, hookstate.HookSetup{Snap: "producer", Hook: "connect-slot-slot", Optional: true, IgnoreError: false})
  1476  
  1477  	task = ts.Tasks()[1]
  1478  	c.Assert(task.Kind(), Equals, "run-hook")
  1479  	err = task.Get("hook-setup", &hookSetup)
  1480  	c.Assert(err, IsNil)
  1481  	c.Assert(hookSetup, Equals, hookstate.HookSetup{Snap: "consumer", Hook: "disconnect-plug-plug", Optional: true})
  1482  	c.Assert(task.Get("undo-hook-setup", &undoHookSetup), IsNil)
  1483  	c.Assert(undoHookSetup, Equals, hookstate.HookSetup{Snap: "consumer", Hook: "connect-plug-plug", Optional: true, IgnoreError: false})
  1484  
  1485  	task = ts.Tasks()[2]
  1486  	c.Assert(task.Kind(), Equals, "disconnect")
  1487  	var autoDisconnect bool
  1488  	c.Assert(task.Get("auto-disconnect", &autoDisconnect), Equals, state.ErrNoState)
  1489  	c.Assert(autoDisconnect, Equals, false)
  1490  
  1491  	var plug interfaces.PlugRef
  1492  	err = task.Get("plug", &plug)
  1493  	c.Assert(err, IsNil)
  1494  	c.Assert(plug.Snap, Equals, "consumer")
  1495  	c.Assert(plug.Name, Equals, "plug")
  1496  	var slot interfaces.SlotRef
  1497  	err = task.Get("slot", &slot)
  1498  	c.Assert(err, IsNil)
  1499  	c.Assert(slot.Snap, Equals, "producer")
  1500  	c.Assert(slot.Name, Equals, "slot")
  1501  
  1502  	// verify connection attributes are present in the disconnect task
  1503  	var plugStaticAttrs1, plugDynamicAttrs1, slotStaticAttrs1, slotDynamicAttrs1 map[string]interface{}
  1504  
  1505  	c.Assert(task.Get("plug-static", &plugStaticAttrs1), IsNil)
  1506  	c.Assert(plugStaticAttrs1, DeepEquals, map[string]interface{}{"attr1": "value1"})
  1507  	c.Assert(task.Get("plug-dynamic", &plugDynamicAttrs1), IsNil)
  1508  	c.Assert(plugDynamicAttrs1, DeepEquals, map[string]interface{}{"attr3": "value3"})
  1509  
  1510  	c.Assert(task.Get("slot-static", &slotStaticAttrs1), IsNil)
  1511  	c.Assert(slotStaticAttrs1, DeepEquals, map[string]interface{}{"attr2": "value2"})
  1512  	c.Assert(task.Get("slot-dynamic", &slotDynamicAttrs1), IsNil)
  1513  	c.Assert(slotDynamicAttrs1, DeepEquals, map[string]interface{}{"attr4": "value4"})
  1514  }
  1515  
  1516  // Disconnect works when both plug and slot are specified
  1517  func (s *interfaceManagerSuite) TestDisconnectFull(c *C) {
  1518  	s.testDisconnect(c, "consumer", "plug", "producer", "slot")
  1519  }
  1520  
  1521  func (s *interfaceManagerSuite) getConnection(c *C, plugSnap, plugName, slotSnap, slotName string) *interfaces.Connection {
  1522  	conn, err := s.manager(c).Repository().Connection(&interfaces.ConnRef{
  1523  		PlugRef: interfaces.PlugRef{Snap: plugSnap, Name: plugName},
  1524  		SlotRef: interfaces.SlotRef{Snap: slotSnap, Name: slotName},
  1525  	})
  1526  	c.Assert(err, IsNil)
  1527  	c.Assert(conn, NotNil)
  1528  	return conn
  1529  }
  1530  
  1531  func (s *interfaceManagerSuite) testDisconnect(c *C, plugSnap, plugName, slotSnap, slotName string) {
  1532  	// Put two snaps in place They consumer has an plug that can be connected
  1533  	// to slot on the producer.
  1534  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  1535  	s.mockSnap(c, consumerYaml)
  1536  	s.mockSnap(c, producerYaml)
  1537  
  1538  	// Put a connection in the state so that it automatically gets set up when
  1539  	// we create the manager.
  1540  	s.state.Lock()
  1541  	s.state.Set("conns", map[string]interface{}{
  1542  		"consumer:plug producer:slot": map[string]interface{}{"interface": "test"},
  1543  	})
  1544  	s.state.Unlock()
  1545  
  1546  	// Initialize the manager. This registers both snaps and reloads the connection.
  1547  	mgr := s.manager(c)
  1548  
  1549  	conn := s.getConnection(c, plugSnap, plugName, slotSnap, slotName)
  1550  
  1551  	// Run the disconnect task and let it finish.
  1552  	s.state.Lock()
  1553  	change := s.state.NewChange("disconnect", "...")
  1554  	ts, err := ifacestate.Disconnect(s.state, conn)
  1555  	ts.Tasks()[0].Set("snap-setup", &snapstate.SnapSetup{
  1556  		SideInfo: &snap.SideInfo{
  1557  			RealName: "consumer",
  1558  		},
  1559  	})
  1560  
  1561  	c.Assert(err, IsNil)
  1562  	change.AddAll(ts)
  1563  	s.state.Unlock()
  1564  
  1565  	s.settle(c)
  1566  
  1567  	s.state.Lock()
  1568  	defer s.state.Unlock()
  1569  
  1570  	// Ensure that the task succeeded.
  1571  	c.Assert(change.Err(), IsNil)
  1572  	c.Assert(change.Tasks(), HasLen, 3)
  1573  	task := change.Tasks()[2]
  1574  	c.Check(task.Kind(), Equals, "disconnect")
  1575  	c.Check(task.Status(), Equals, state.DoneStatus)
  1576  
  1577  	c.Check(change.Status(), Equals, state.DoneStatus)
  1578  
  1579  	// Ensure that the connection has been removed from the state
  1580  	var conns map[string]interface{}
  1581  	err = s.state.Get("conns", &conns)
  1582  	c.Assert(err, IsNil)
  1583  	c.Check(conns, HasLen, 0)
  1584  
  1585  	// Ensure that the connection has been removed from the repository
  1586  	repo := mgr.Repository()
  1587  	ifaces := repo.Interfaces()
  1588  	c.Assert(ifaces.Connections, HasLen, 0)
  1589  
  1590  	// Ensure that the backend was used to setup security of both snaps
  1591  	c.Assert(s.secBackend.SetupCalls, HasLen, 2)
  1592  	c.Assert(s.secBackend.RemoveCalls, HasLen, 0)
  1593  	c.Check(s.secBackend.SetupCalls[0].SnapInfo.InstanceName(), Equals, "consumer")
  1594  	c.Check(s.secBackend.SetupCalls[1].SnapInfo.InstanceName(), Equals, "producer")
  1595  
  1596  	c.Check(s.secBackend.SetupCalls[0].Options, Equals, interfaces.ConfinementOptions{})
  1597  	c.Check(s.secBackend.SetupCalls[1].Options, Equals, interfaces.ConfinementOptions{})
  1598  }
  1599  
  1600  func (s *interfaceManagerSuite) TestDisconnectUndo(c *C) {
  1601  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  1602  	var consumerYaml = `
  1603  name: consumer
  1604  version: 1
  1605  plugs:
  1606   plug:
  1607    interface: test
  1608    static: plug-static-value
  1609  `
  1610  	var producerYaml = `
  1611  name: producer
  1612  version: 1
  1613  slots:
  1614   slot:
  1615    interface: test
  1616    static: slot-static-value
  1617  `
  1618  	s.mockSnap(c, consumerYaml)
  1619  	s.mockSnap(c, producerYaml)
  1620  
  1621  	connState := map[string]interface{}{
  1622  		"consumer:plug producer:slot": map[string]interface{}{
  1623  			"interface":    "test",
  1624  			"slot-static":  map[string]interface{}{"static": "slot-static-value"},
  1625  			"slot-dynamic": map[string]interface{}{"dynamic": "slot-dynamic-value"},
  1626  			"plug-static":  map[string]interface{}{"static": "plug-static-value"},
  1627  			"plug-dynamic": map[string]interface{}{"dynamic": "plug-dynamic-value"},
  1628  		},
  1629  	}
  1630  
  1631  	s.state.Lock()
  1632  	s.state.Set("conns", connState)
  1633  	s.state.Unlock()
  1634  
  1635  	// Initialize the manager. This registers both snaps and reloads the connection.
  1636  	_ = s.manager(c)
  1637  
  1638  	conn := s.getConnection(c, "consumer", "plug", "producer", "slot")
  1639  
  1640  	// Run the disconnect task and let it finish.
  1641  	s.state.Lock()
  1642  	change := s.state.NewChange("disconnect", "...")
  1643  	ts, err := ifacestate.Disconnect(s.state, conn)
  1644  
  1645  	c.Assert(err, IsNil)
  1646  	change.AddAll(ts)
  1647  	terr := s.state.NewTask("error-trigger", "provoking total undo")
  1648  	terr.WaitAll(ts)
  1649  	change.AddTask(terr)
  1650  	c.Assert(change.Tasks(), HasLen, 2)
  1651  	s.state.Unlock()
  1652  
  1653  	s.settle(c)
  1654  
  1655  	s.state.Lock()
  1656  	defer s.state.Unlock()
  1657  
  1658  	// Ensure that disconnect tasks were undone
  1659  	for _, t := range ts.Tasks() {
  1660  		c.Assert(t.Status(), Equals, state.UndoneStatus)
  1661  	}
  1662  
  1663  	var conns map[string]interface{}
  1664  	c.Assert(s.state.Get("conns", &conns), IsNil)
  1665  	c.Assert(conns, DeepEquals, connState)
  1666  
  1667  	_ = s.getConnection(c, "consumer", "plug", "producer", "slot")
  1668  }
  1669  
  1670  func (s *interfaceManagerSuite) TestStaleConnectionsIgnoredInReloadConnections(c *C) {
  1671  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
  1672  
  1673  	// Put a stray connection in the state so that it automatically gets set up
  1674  	// when we create the manager.
  1675  	s.state.Lock()
  1676  	s.state.Set("conns", map[string]interface{}{
  1677  		"consumer:plug producer:slot": map[string]interface{}{"interface": "test"},
  1678  	})
  1679  	s.state.Unlock()
  1680  
  1681  	restore := ifacestate.MockRemoveStaleConnections(func(s *state.State) error { return nil })
  1682  	defer restore()
  1683  	mgr := s.manager(c)
  1684  
  1685  	s.state.Lock()
  1686  	defer s.state.Unlock()
  1687  
  1688  	// Ensure that nothing got connected.
  1689  	repo := mgr.Repository()
  1690  	ifaces := repo.Interfaces()
  1691  	c.Assert(ifaces.Connections, HasLen, 0)
  1692  
  1693  	// Ensure that nothing to setup.
  1694  	c.Assert(s.secBackend.SetupCalls, HasLen, 0)
  1695  	c.Assert(s.secBackend.RemoveCalls, HasLen, 0)
  1696  
  1697  	// Ensure that nothing, crucially, got logged about that connection.
  1698  	// We still have an error logged about the system key but this is just
  1699  	// a bit of test mocking missing.
  1700  	logLines := strings.Split(s.log.String(), "\n")
  1701  	c.Assert(logLines, HasLen, 2)
  1702  	c.Assert(logLines[0], testutil.Contains, "error trying to compare the snap system key:")
  1703  	c.Assert(logLines[1], Equals, "")
  1704  }
  1705  
  1706  func (s *interfaceManagerSuite) TestStaleConnectionsRemoved(c *C) {
  1707  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
  1708  
  1709  	s.state.Lock()
  1710  	// Add stale connection to the state
  1711  	s.state.Set("conns", map[string]interface{}{
  1712  		"consumer:plug producer:slot": map[string]interface{}{"interface": "test"},
  1713  	})
  1714  	s.state.Unlock()
  1715  
  1716  	// Create the manager, this removes stale connections
  1717  	mgr := s.manager(c)
  1718  
  1719  	s.state.Lock()
  1720  	defer s.state.Unlock()
  1721  
  1722  	// Ensure that nothing got connected and connection was removed
  1723  	var conns map[string]interface{}
  1724  	err := s.state.Get("conns", &conns)
  1725  	c.Assert(err, IsNil)
  1726  	c.Check(conns, HasLen, 0)
  1727  
  1728  	repo := mgr.Repository()
  1729  	ifaces := repo.Interfaces()
  1730  	c.Assert(ifaces.Connections, HasLen, 0)
  1731  }
  1732  
  1733  func (s *interfaceManagerSuite) mockSecBackend(c *C, backend interfaces.SecurityBackend) {
  1734  	s.extraBackends = append(s.extraBackends, backend)
  1735  }
  1736  
  1737  func (s *interfaceManagerSuite) mockIface(c *C, iface interfaces.Interface) {
  1738  	s.extraIfaces = append(s.extraIfaces, iface)
  1739  }
  1740  
  1741  func (s *interfaceManagerSuite) mockIfaces(c *C, ifaces ...interfaces.Interface) {
  1742  	s.extraIfaces = append(s.extraIfaces, ifaces...)
  1743  }
  1744  
  1745  func (s *interfaceManagerSuite) mockSnap(c *C, yamlText string) *snap.Info {
  1746  	return s.mockSnapInstance(c, "", yamlText)
  1747  }
  1748  
  1749  func (s *interfaceManagerSuite) mockSnapInstance(c *C, instanceName, yamlText string) *snap.Info {
  1750  	sideInfo := &snap.SideInfo{
  1751  		Revision: snap.R(1),
  1752  	}
  1753  	snapInfo := snaptest.MockSnapInstance(c, instanceName, yamlText, sideInfo)
  1754  	sideInfo.RealName = snapInfo.SnapName()
  1755  
  1756  	a, err := s.Db.FindMany(asserts.SnapDeclarationType, map[string]string{
  1757  		"snap-name": sideInfo.RealName,
  1758  	})
  1759  	if err == nil {
  1760  		decl := a[0].(*asserts.SnapDeclaration)
  1761  		snapInfo.SnapID = decl.SnapID()
  1762  		sideInfo.SnapID = decl.SnapID()
  1763  	} else if asserts.IsNotFound(err) {
  1764  		err = nil
  1765  	}
  1766  	c.Assert(err, IsNil)
  1767  
  1768  	s.state.Lock()
  1769  	defer s.state.Unlock()
  1770  
  1771  	// Put a side info into the state
  1772  	snapstate.Set(s.state, snapInfo.InstanceName(), &snapstate.SnapState{
  1773  		Active:      true,
  1774  		Sequence:    []*snap.SideInfo{sideInfo},
  1775  		Current:     sideInfo.Revision,
  1776  		SnapType:    string(snapInfo.GetType()),
  1777  		InstanceKey: snapInfo.InstanceKey,
  1778  	})
  1779  	return snapInfo
  1780  }
  1781  
  1782  func (s *interfaceManagerSuite) mockUpdatedSnap(c *C, yamlText string, revision int) *snap.Info {
  1783  	sideInfo := &snap.SideInfo{Revision: snap.R(revision)}
  1784  	snapInfo := snaptest.MockSnap(c, yamlText, sideInfo)
  1785  	sideInfo.RealName = snapInfo.SnapName()
  1786  
  1787  	s.state.Lock()
  1788  	defer s.state.Unlock()
  1789  
  1790  	// Put the new revision (stored in SideInfo) into the state
  1791  	var snapst snapstate.SnapState
  1792  	err := snapstate.Get(s.state, snapInfo.InstanceName(), &snapst)
  1793  	c.Assert(err, IsNil)
  1794  	snapst.Sequence = append(snapst.Sequence, sideInfo)
  1795  	snapstate.Set(s.state, snapInfo.InstanceName(), &snapst)
  1796  
  1797  	return snapInfo
  1798  }
  1799  
  1800  func (s *interfaceManagerSuite) addSetupSnapSecurityChange(c *C, snapsup *snapstate.SnapSetup) *state.Change {
  1801  	s.state.Lock()
  1802  	defer s.state.Unlock()
  1803  
  1804  	change := s.state.NewChange("test", "")
  1805  
  1806  	task1 := s.state.NewTask("setup-profiles", "")
  1807  	task1.Set("snap-setup", snapsup)
  1808  	change.AddTask(task1)
  1809  
  1810  	task2 := s.state.NewTask("auto-connect", "")
  1811  	task2.Set("snap-setup", snapsup)
  1812  	task2.WaitFor(task1)
  1813  	change.AddTask(task2)
  1814  
  1815  	return change
  1816  }
  1817  
  1818  func (s *interfaceManagerSuite) addRemoveSnapSecurityChange(c *C, snapName string) *state.Change {
  1819  	s.state.Lock()
  1820  	defer s.state.Unlock()
  1821  
  1822  	task := s.state.NewTask("remove-profiles", "")
  1823  	snapsup := snapstate.SnapSetup{
  1824  		SideInfo: &snap.SideInfo{
  1825  			RealName: snapName,
  1826  		},
  1827  	}
  1828  	task.Set("snap-setup", snapsup)
  1829  	taskset := state.NewTaskSet(task)
  1830  	change := s.state.NewChange("test", "")
  1831  	change.AddAll(taskset)
  1832  	return change
  1833  }
  1834  
  1835  func (s *interfaceManagerSuite) addDiscardConnsChange(c *C, snapName string) (*state.Change, *state.Task) {
  1836  	s.state.Lock()
  1837  	defer s.state.Unlock()
  1838  
  1839  	task := s.state.NewTask("discard-conns", "")
  1840  	snapsup := snapstate.SnapSetup{
  1841  		SideInfo: &snap.SideInfo{
  1842  			RealName: snapName,
  1843  		},
  1844  	}
  1845  	task.Set("snap-setup", snapsup)
  1846  	taskset := state.NewTaskSet(task)
  1847  	change := s.state.NewChange("test", "")
  1848  	change.AddAll(taskset)
  1849  	return change, task
  1850  }
  1851  
  1852  var ubuntuCoreSnapYaml = `
  1853  name: ubuntu-core
  1854  version: 1
  1855  type: os
  1856  `
  1857  
  1858  var coreSnapYaml = `
  1859  name: core
  1860  version: 1
  1861  type: os
  1862  `
  1863  
  1864  var sampleSnapYaml = `
  1865  name: snap
  1866  version: 1
  1867  apps:
  1868   app:
  1869     command: foo
  1870  plugs:
  1871   network:
  1872    interface: network
  1873  `
  1874  
  1875  var sampleSnapYamlManyPlugs = `
  1876  name: snap
  1877  version: 1
  1878  apps:
  1879   app:
  1880     command: foo
  1881  plugs:
  1882   network:
  1883    interface: network
  1884   home:
  1885    interface: home
  1886   x11:
  1887    interface: x11
  1888   wayland:
  1889    interface: wayland
  1890  `
  1891  
  1892  var consumerYaml = `
  1893  name: consumer
  1894  version: 1
  1895  plugs:
  1896   plug:
  1897    interface: test
  1898    attr1: value1
  1899   otherplug:
  1900    interface: test2
  1901  hooks:
  1902   prepare-plug-plug:
  1903   unprepare-plug-plug:
  1904   connect-plug-plug:
  1905   disconnect-plug-plug:
  1906   prepare-plug-otherplug:
  1907   unprepare-plug-otherplug:
  1908   connect-plug-otherplug:
  1909   disconnect-plug-otherplug:
  1910  `
  1911  
  1912  var consumer2Yaml = `
  1913  name: consumer2
  1914  version: 1
  1915  plugs:
  1916   plug:
  1917    interface: test
  1918    attr1: value1
  1919  `
  1920  
  1921  var consumerYaml3 = `
  1922  name: consumer
  1923  version: 1
  1924  plugs:
  1925   plug:
  1926    interface: test
  1927  hooks:
  1928  %s
  1929  `
  1930  
  1931  var producerYaml = `
  1932  name: producer
  1933  version: 1
  1934  slots:
  1935   slot:
  1936    interface: test
  1937    attr2: value2
  1938  hooks:
  1939    prepare-slot-slot:
  1940    unprepare-slot-slot:
  1941    connect-slot-slot:
  1942    disconnect-slot-slot:
  1943  `
  1944  
  1945  var producer2Yaml = `
  1946  name: producer2
  1947  version: 1
  1948  slots:
  1949   slot:
  1950    interface: test
  1951    attr2: value2
  1952    number: 1
  1953  `
  1954  
  1955  var producerYaml3 = `
  1956  name: producer
  1957  version: 1
  1958  slots:
  1959   slot:
  1960    interface: test
  1961  hooks:
  1962  %s
  1963  `
  1964  
  1965  var httpdSnapYaml = `name: httpd
  1966  version: 1
  1967  plugs:
  1968   network:
  1969    interface: network
  1970  `
  1971  
  1972  var selfconnectSnapYaml = `
  1973  name: producerconsumer
  1974  version: 1
  1975  slots:
  1976   slot:
  1977    interface: test
  1978  plugs:
  1979   plug:
  1980    interface: test
  1981  hooks:
  1982   prepare-plug-plug:
  1983   unprepare-plug-plug:
  1984   connect-plug-plug:
  1985   disconnect-plug-plug:
  1986   prepare-slot-slot:
  1987   unprepare-slot-slot:
  1988   connect-slot-slot:
  1989   disconnect-slot-slot:
  1990  `
  1991  
  1992  // The auto-connect task will not auto-connect a plug that was previously
  1993  // explicitly disconnected by the user.
  1994  func (s *interfaceManagerSuite) TestDoSetupSnapSecurityHonorsUndesiredFlag(c *C) {
  1995  	s.MockModel(c, nil)
  1996  
  1997  	s.state.Lock()
  1998  	s.state.Set("conns", map[string]interface{}{
  1999  		"snap:network ubuntu-core:network": map[string]interface{}{
  2000  			"undesired": true,
  2001  		},
  2002  	})
  2003  	s.state.Unlock()
  2004  
  2005  	// Add an OS snap as well as a sample snap with a "network" plug.
  2006  	// The plug is normally auto-connected.
  2007  	s.mockSnap(c, ubuntuCoreSnapYaml)
  2008  	snapInfo := s.mockSnap(c, sampleSnapYaml)
  2009  
  2010  	// Initialize the manager. This registers the two snaps.
  2011  	mgr := s.manager(c)
  2012  
  2013  	// Run the setup-snap-security task and let it finish.
  2014  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  2015  		SideInfo: &snap.SideInfo{
  2016  			RealName: snapInfo.SnapName(),
  2017  			Revision: snapInfo.Revision,
  2018  		},
  2019  	})
  2020  
  2021  	s.settle(c)
  2022  
  2023  	s.state.Lock()
  2024  	defer s.state.Unlock()
  2025  
  2026  	// Ensure that the task succeeded
  2027  	c.Assert(change.Status(), Equals, state.DoneStatus)
  2028  
  2029  	var conns map[string]interface{}
  2030  	err := s.state.Get("conns", &conns)
  2031  	c.Assert(err, IsNil)
  2032  	c.Check(conns, DeepEquals, map[string]interface{}{
  2033  		"snap:network ubuntu-core:network": map[string]interface{}{
  2034  			"undesired": true,
  2035  		},
  2036  	})
  2037  
  2038  	// Ensure that "network" is not connected
  2039  	repo := mgr.Repository()
  2040  	plug := repo.Plug("snap", "network")
  2041  	c.Assert(plug, Not(IsNil))
  2042  	ifaces := repo.Interfaces()
  2043  	c.Assert(ifaces.Connections, HasLen, 0)
  2044  }
  2045  
  2046  // The auto-connect task will auto-connect plugs with viable candidates.
  2047  func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsPlugs(c *C) {
  2048  	s.MockModel(c, nil)
  2049  
  2050  	// Add an OS snap.
  2051  	s.mockSnap(c, ubuntuCoreSnapYaml)
  2052  
  2053  	// Initialize the manager. This registers the OS snap.
  2054  	mgr := s.manager(c)
  2055  
  2056  	// Add a sample snap with a "network" plug which should be auto-connected.
  2057  	snapInfo := s.mockSnap(c, sampleSnapYaml)
  2058  
  2059  	// Run the setup-snap-security task and let it finish.
  2060  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  2061  		SideInfo: &snap.SideInfo{
  2062  			RealName: snapInfo.SnapName(),
  2063  			Revision: snapInfo.Revision,
  2064  		},
  2065  	})
  2066  	s.settle(c)
  2067  
  2068  	s.state.Lock()
  2069  	defer s.state.Unlock()
  2070  
  2071  	// Ensure that the task succeeded.
  2072  	c.Assert(change.Status(), Equals, state.DoneStatus)
  2073  
  2074  	// Ensure that "network" is now saved in the state as auto-connected.
  2075  	var conns map[string]interface{}
  2076  	err := s.state.Get("conns", &conns)
  2077  	c.Assert(err, IsNil)
  2078  	c.Check(conns, DeepEquals, map[string]interface{}{
  2079  		"snap:network ubuntu-core:network": map[string]interface{}{
  2080  			"interface": "network", "auto": true,
  2081  		},
  2082  	})
  2083  
  2084  	// Ensure that "network" is really connected.
  2085  	repo := mgr.Repository()
  2086  	plug := repo.Plug("snap", "network")
  2087  	c.Assert(plug, Not(IsNil))
  2088  	ifaces := repo.Interfaces()
  2089  	c.Assert(ifaces.Connections, HasLen, 1) //FIXME add deep eq
  2090  }
  2091  
  2092  // The auto-connect task will auto-connect slots with viable candidates.
  2093  func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsSlots(c *C) {
  2094  	s.MockModel(c, nil)
  2095  
  2096  	// Mock the interface that will be used by the test
  2097  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  2098  	// Add an OS snap.
  2099  	s.mockSnap(c, ubuntuCoreSnapYaml)
  2100  	// Add a consumer snap with unconnect plug (interface "test")
  2101  	s.mockSnap(c, consumerYaml)
  2102  
  2103  	// Initialize the manager. This registers the OS snap.
  2104  	mgr := s.manager(c)
  2105  
  2106  	// Add a producer snap with a "slot" slot of the "test" interface.
  2107  	snapInfo := s.mockSnap(c, producerYaml)
  2108  
  2109  	// Run the setup-snap-security task and let it finish.
  2110  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  2111  		SideInfo: &snap.SideInfo{
  2112  			RealName: snapInfo.SnapName(),
  2113  			Revision: snapInfo.Revision,
  2114  		},
  2115  	})
  2116  	s.settle(c)
  2117  
  2118  	s.state.Lock()
  2119  	defer s.state.Unlock()
  2120  
  2121  	// Ensure that the task succeeded.
  2122  	c.Assert(change.Status(), Equals, state.DoneStatus)
  2123  
  2124  	// Ensure that "slot" is now saved in the state as auto-connected.
  2125  	var conns map[string]interface{}
  2126  	err := s.state.Get("conns", &conns)
  2127  	c.Assert(err, IsNil)
  2128  	c.Check(conns, DeepEquals, map[string]interface{}{
  2129  		"consumer:plug producer:slot": map[string]interface{}{
  2130  			"interface": "test", "auto": true,
  2131  			"plug-static": map[string]interface{}{"attr1": "value1"},
  2132  			"slot-static": map[string]interface{}{"attr2": "value2"},
  2133  		},
  2134  	})
  2135  
  2136  	// Ensure that "slot" is really connected.
  2137  	repo := mgr.Repository()
  2138  	slot := repo.Slot("producer", "slot")
  2139  	c.Assert(slot, Not(IsNil))
  2140  	ifaces := repo.Interfaces()
  2141  	c.Assert(ifaces.Connections, HasLen, 1)
  2142  	c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{
  2143  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  2144  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}}})
  2145  }
  2146  
  2147  // The auto-connect task will auto-connect slots with viable multiple candidates.
  2148  func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsSlotsMultiplePlugs(c *C) {
  2149  	s.MockModel(c, nil)
  2150  
  2151  	// Mock the interface that will be used by the test
  2152  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  2153  	// Add an OS snap.
  2154  	s.mockSnap(c, ubuntuCoreSnapYaml)
  2155  	// Add a consumer snap with unconnect plug (interface "test")
  2156  	s.mockSnap(c, consumerYaml)
  2157  	// Add a 2nd consumer snap with unconnect plug (interface "test")
  2158  	s.mockSnap(c, consumer2Yaml)
  2159  
  2160  	// Initialize the manager. This registers the OS snap.
  2161  	mgr := s.manager(c)
  2162  
  2163  	// Add a producer snap with a "slot" slot of the "test" interface.
  2164  	snapInfo := s.mockSnap(c, producerYaml)
  2165  
  2166  	// Run the setup-snap-security task and let it finish.
  2167  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  2168  		SideInfo: &snap.SideInfo{
  2169  			RealName: snapInfo.SnapName(),
  2170  			Revision: snapInfo.Revision,
  2171  		},
  2172  	})
  2173  
  2174  	s.settle(c)
  2175  
  2176  	s.state.Lock()
  2177  	defer s.state.Unlock()
  2178  
  2179  	// Ensure that the task succeeded.
  2180  	c.Assert(change.Status(), Equals, state.DoneStatus)
  2181  
  2182  	// Ensure that "slot" is now saved in the state as auto-connected.
  2183  	var conns map[string]interface{}
  2184  	err := s.state.Get("conns", &conns)
  2185  	c.Assert(err, IsNil)
  2186  	c.Check(conns, DeepEquals, map[string]interface{}{
  2187  		"consumer:plug producer:slot": map[string]interface{}{
  2188  			"interface": "test", "auto": true,
  2189  			"plug-static": map[string]interface{}{"attr1": "value1"},
  2190  			"slot-static": map[string]interface{}{"attr2": "value2"},
  2191  		},
  2192  		"consumer2:plug producer:slot": map[string]interface{}{
  2193  			"interface": "test", "auto": true,
  2194  			"plug-static": map[string]interface{}{"attr1": "value1"},
  2195  			"slot-static": map[string]interface{}{"attr2": "value2"},
  2196  		},
  2197  	})
  2198  
  2199  	// Ensure that "slot" is really connected.
  2200  	repo := mgr.Repository()
  2201  	slot := repo.Slot("producer", "slot")
  2202  	c.Assert(slot, Not(IsNil))
  2203  	ifaces := repo.Interfaces()
  2204  	c.Assert(ifaces.Connections, HasLen, 2)
  2205  	c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{
  2206  		{PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}},
  2207  		{PlugRef: interfaces.PlugRef{Snap: "consumer2", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}},
  2208  	})
  2209  }
  2210  
  2211  // The auto-connect task will not auto-connect slots if viable alternative slots are present.
  2212  func (s *interfaceManagerSuite) TestDoSetupSnapSecurityNoAutoConnectSlotsIfAlternative(c *C) {
  2213  	s.MockModel(c, nil)
  2214  
  2215  	// Mock the interface that will be used by the test
  2216  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  2217  	// Add an OS snap.
  2218  	s.mockSnap(c, ubuntuCoreSnapYaml)
  2219  	// Add a consumer snap with unconnect plug (interface "test")
  2220  	s.mockSnap(c, consumerYaml)
  2221  
  2222  	// alternative conflicting producer
  2223  	s.mockSnap(c, producer2Yaml)
  2224  
  2225  	// Initialize the manager. This registers the OS snap.
  2226  	_ = s.manager(c)
  2227  
  2228  	// Add a producer snap with a "slot" slot of the "test" interface.
  2229  	snapInfo := s.mockSnap(c, producerYaml)
  2230  
  2231  	// Run the setup-snap-security task and let it finish.
  2232  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  2233  		SideInfo: &snap.SideInfo{
  2234  			RealName: snapInfo.SnapName(),
  2235  			Revision: snapInfo.Revision,
  2236  		},
  2237  	})
  2238  	s.settle(c)
  2239  
  2240  	s.state.Lock()
  2241  	defer s.state.Unlock()
  2242  
  2243  	// Ensure that the task succeeded.
  2244  	c.Assert(change.Status(), Equals, state.DoneStatus)
  2245  
  2246  	// Ensure that no connections were made
  2247  	var conns map[string]interface{}
  2248  	err := s.state.Get("conns", &conns)
  2249  	c.Assert(err, Equals, state.ErrNoState)
  2250  	c.Check(conns, HasLen, 0)
  2251  }
  2252  
  2253  // The auto-connect task will auto-connect plugs with viable candidates also condidering snap declarations.
  2254  func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsDeclBased(c *C) {
  2255  	s.testDoSetupSnapSecurityAutoConnectsDeclBased(c, true, func(conns map[string]interface{}, repoConns []*interfaces.ConnRef) {
  2256  		// Ensure that "test" plug is now saved in the state as auto-connected.
  2257  		c.Check(conns, DeepEquals, map[string]interface{}{
  2258  			"consumer:plug producer:slot": map[string]interface{}{"auto": true, "interface": "test",
  2259  				"plug-static": map[string]interface{}{"attr1": "value1"},
  2260  				"slot-static": map[string]interface{}{"attr2": "value2"},
  2261  			}})
  2262  		// Ensure that "test" is really connected.
  2263  		c.Check(repoConns, HasLen, 1)
  2264  	})
  2265  }
  2266  
  2267  // The auto-connect task will *not* auto-connect plugs with viable candidates when snap declarations are missing.
  2268  func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsDeclBasedWhenMissingDecl(c *C) {
  2269  	s.testDoSetupSnapSecurityAutoConnectsDeclBased(c, false, func(conns map[string]interface{}, repoConns []*interfaces.ConnRef) {
  2270  		// Ensure nothing is connected.
  2271  		c.Check(conns, HasLen, 0)
  2272  		c.Check(repoConns, HasLen, 0)
  2273  	})
  2274  }
  2275  
  2276  func (s *interfaceManagerSuite) testDoSetupSnapSecurityAutoConnectsDeclBased(c *C, withDecl bool, check func(map[string]interface{}, []*interfaces.ConnRef)) {
  2277  	s.MockModel(c, nil)
  2278  
  2279  	restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
  2280  type: base-declaration
  2281  authority-id: canonical
  2282  series: 16
  2283  slots:
  2284    test:
  2285      allow-auto-connection:
  2286        plug-publisher-id:
  2287          - $SLOT_PUBLISHER_ID
  2288  `))
  2289  	defer restore()
  2290  	// Add the producer snap
  2291  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  2292  	s.MockSnapDecl(c, "producer", "one-publisher", nil)
  2293  	s.mockSnap(c, producerYaml)
  2294  
  2295  	// Initialize the manager. This registers the producer snap.
  2296  	mgr := s.manager(c)
  2297  
  2298  	// Add a sample snap with a plug with the "test" interface which should be auto-connected.
  2299  	if withDecl {
  2300  		s.MockSnapDecl(c, "consumer", "one-publisher", nil)
  2301  	}
  2302  	snapInfo := s.mockSnap(c, consumerYaml)
  2303  
  2304  	// Run the setup-snap-security task and let it finish.
  2305  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  2306  		SideInfo: &snap.SideInfo{
  2307  			RealName: snapInfo.SnapName(),
  2308  			SnapID:   snapInfo.SnapID,
  2309  			Revision: snapInfo.Revision,
  2310  		},
  2311  	})
  2312  	s.settle(c)
  2313  
  2314  	s.state.Lock()
  2315  	defer s.state.Unlock()
  2316  
  2317  	// Ensure that the task succeeded.
  2318  	c.Assert(change.Status(), Equals, state.DoneStatus)
  2319  
  2320  	var conns map[string]interface{}
  2321  	_ = s.state.Get("conns", &conns)
  2322  
  2323  	repo := mgr.Repository()
  2324  	plug := repo.Plug("consumer", "plug")
  2325  	c.Assert(plug, Not(IsNil))
  2326  
  2327  	check(conns, repo.Interfaces().Connections)
  2328  }
  2329  
  2330  // The auto-connect task will check snap declarations providing the
  2331  // model assertion to fulfill device scope constraints: here no store
  2332  // in the model assertion fails an on-store constraint.
  2333  func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsDeclBasedDeviceScopeNoStore(c *C) {
  2334  
  2335  	s.MockModel(c, nil)
  2336  
  2337  	s.testDoSetupSnapSecurityAutoConnectsDeclBasedDeviceScope(c, func(conns map[string]interface{}, repoConns []*interfaces.ConnRef) {
  2338  		// Ensure nothing is connected.
  2339  		c.Check(conns, HasLen, 0)
  2340  		c.Check(repoConns, HasLen, 0)
  2341  	})
  2342  }
  2343  
  2344  // The auto-connect task will check snap declarations providing the
  2345  // model assertion to fulfill device scope constraints: here the wrong
  2346  // store in the model assertion fails an on-store constraint.
  2347  func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsDeclBasedDeviceScopeWrongStore(c *C) {
  2348  
  2349  	s.MockModel(c, map[string]interface{}{
  2350  		"store": "other-store",
  2351  	})
  2352  
  2353  	s.testDoSetupSnapSecurityAutoConnectsDeclBasedDeviceScope(c, func(conns map[string]interface{}, repoConns []*interfaces.ConnRef) {
  2354  		// Ensure nothing is connected.
  2355  		c.Check(conns, HasLen, 0)
  2356  		c.Check(repoConns, HasLen, 0)
  2357  	})
  2358  }
  2359  
  2360  // The auto-connect task will check snap declarations providing the
  2361  // model assertion to fulfill device scope constraints: here the right
  2362  // store in the model assertion passes an on-store constraint.
  2363  func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsDeclBasedDeviceScopeRightStore(c *C) {
  2364  
  2365  	s.MockModel(c, map[string]interface{}{
  2366  		"store": "my-store",
  2367  	})
  2368  
  2369  	s.testDoSetupSnapSecurityAutoConnectsDeclBasedDeviceScope(c, func(conns map[string]interface{}, repoConns []*interfaces.ConnRef) {
  2370  		// Ensure that "test" plug is now saved in the state as auto-connected.
  2371  		c.Check(conns, DeepEquals, map[string]interface{}{
  2372  			"consumer:plug producer:slot": map[string]interface{}{"auto": true, "interface": "test",
  2373  				"plug-static": map[string]interface{}{"attr1": "value1"},
  2374  				"slot-static": map[string]interface{}{"attr2": "value2"},
  2375  			}})
  2376  		// Ensure that "test" is really connected.
  2377  		c.Check(repoConns, HasLen, 1)
  2378  	})
  2379  }
  2380  
  2381  // The auto-connect task will check snap declarations providing the
  2382  // model assertion to fulfill device scope constraints: here the
  2383  // wrong "friendly store"s of the store in the model assertion fail an
  2384  // on-store constraint.
  2385  func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsDeclBasedDeviceScopeWrongFriendlyStore(c *C) {
  2386  
  2387  	s.MockModel(c, map[string]interface{}{
  2388  		"store": "my-substore",
  2389  	})
  2390  
  2391  	s.MockStore(c, s.state, "my-substore", map[string]interface{}{
  2392  		"friendly-stores": []interface{}{"other-store"},
  2393  	})
  2394  
  2395  	s.testDoSetupSnapSecurityAutoConnectsDeclBasedDeviceScope(c, func(conns map[string]interface{}, repoConns []*interfaces.ConnRef) {
  2396  		// Ensure nothing is connected.
  2397  		c.Check(conns, HasLen, 0)
  2398  		c.Check(repoConns, HasLen, 0)
  2399  	})
  2400  }
  2401  
  2402  // The auto-connect task will check snap declarations providing the
  2403  // model assertion to fulfill device scope constraints: here a
  2404  // "friendly store" of the store in the model assertion passes an
  2405  // on-store constraint.
  2406  func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsDeclBasedDeviceScopeFriendlyStore(c *C) {
  2407  
  2408  	s.MockModel(c, map[string]interface{}{
  2409  		"store": "my-substore",
  2410  	})
  2411  
  2412  	s.MockStore(c, s.state, "my-substore", map[string]interface{}{
  2413  		"friendly-stores": []interface{}{"my-store"},
  2414  	})
  2415  
  2416  	s.testDoSetupSnapSecurityAutoConnectsDeclBasedDeviceScope(c, func(conns map[string]interface{}, repoConns []*interfaces.ConnRef) {
  2417  		// Ensure that "test" plug is now saved in the state as auto-connected.
  2418  		c.Check(conns, DeepEquals, map[string]interface{}{
  2419  			"consumer:plug producer:slot": map[string]interface{}{"auto": true, "interface": "test",
  2420  				"plug-static": map[string]interface{}{"attr1": "value1"},
  2421  				"slot-static": map[string]interface{}{"attr2": "value2"},
  2422  			}})
  2423  		// Ensure that "test" is really connected.
  2424  		c.Check(repoConns, HasLen, 1)
  2425  	})
  2426  }
  2427  
  2428  func (s *interfaceManagerSuite) testDoSetupSnapSecurityAutoConnectsDeclBasedDeviceScope(c *C, check func(map[string]interface{}, []*interfaces.ConnRef)) {
  2429  	restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
  2430  type: base-declaration
  2431  authority-id: canonical
  2432  series: 16
  2433  slots:
  2434    test:
  2435      allow-auto-connection: false
  2436  `))
  2437  	defer restore()
  2438  	// Add the producer snap
  2439  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
  2440  	s.MockSnapDecl(c, "producer", "one-publisher", nil)
  2441  	s.mockSnap(c, producerYaml)
  2442  
  2443  	// Initialize the manager. This registers the producer snap.
  2444  	mgr := s.manager(c)
  2445  
  2446  	s.MockSnapDecl(c, "consumer", "one-publisher", map[string]interface{}{
  2447  		"format": "3",
  2448  		"plugs": map[string]interface{}{
  2449  			"test": map[string]interface{}{
  2450  				"allow-auto-connection": map[string]interface{}{
  2451  					"on-store": []interface{}{"my-store"},
  2452  				},
  2453  			},
  2454  		},
  2455  	})
  2456  	snapInfo := s.mockSnap(c, consumerYaml)
  2457  
  2458  	// Run the setup-snap-security task and let it finish.
  2459  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  2460  		SideInfo: &snap.SideInfo{
  2461  			RealName: snapInfo.SnapName(),
  2462  			SnapID:   snapInfo.SnapID,
  2463  			Revision: snapInfo.Revision,
  2464  		},
  2465  	})
  2466  	s.settle(c)
  2467  
  2468  	s.state.Lock()
  2469  	defer s.state.Unlock()
  2470  
  2471  	// Ensure that the task succeeded.
  2472  	c.Assert(change.Status(), Equals, state.DoneStatus)
  2473  
  2474  	var conns map[string]interface{}
  2475  	_ = s.state.Get("conns", &conns)
  2476  
  2477  	repo := mgr.Repository()
  2478  	plug := repo.Plug("consumer", "plug")
  2479  	c.Assert(plug, Not(IsNil))
  2480  
  2481  	check(conns, repo.Interfaces().Connections)
  2482  }
  2483  
  2484  // The setup-profiles task will only touch connection state for the task it
  2485  // operates on or auto-connects to and will leave other state intact.
  2486  func (s *interfaceManagerSuite) TestDoSetupSnapSecurityKeepsExistingConnectionState(c *C) {
  2487  	s.MockModel(c, nil)
  2488  
  2489  	// Add an OS snap in place.
  2490  	s.mockSnap(c, ubuntuCoreSnapYaml)
  2491  
  2492  	// Initialize the manager. This registers the two snaps.
  2493  	_ = s.manager(c)
  2494  
  2495  	// Add a sample snap with a "network" plug which should be auto-connected.
  2496  	snapInfo := s.mockSnap(c, sampleSnapYaml)
  2497  
  2498  	// Put fake information about connections for another snap into the state.
  2499  	s.state.Lock()
  2500  	s.state.Set("conns", map[string]interface{}{
  2501  		"other-snap:network ubuntu-core:network": map[string]interface{}{
  2502  			"interface": "network",
  2503  		},
  2504  	})
  2505  	s.state.Unlock()
  2506  
  2507  	// Run the setup-snap-security task and let it finish.
  2508  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  2509  		SideInfo: &snap.SideInfo{
  2510  			RealName: snapInfo.SnapName(),
  2511  			Revision: snapInfo.Revision,
  2512  		},
  2513  	})
  2514  	s.settle(c)
  2515  
  2516  	s.state.Lock()
  2517  	defer s.state.Unlock()
  2518  
  2519  	// Ensure that the task succeeded.
  2520  	c.Assert(change.Status(), Equals, state.DoneStatus)
  2521  
  2522  	var conns map[string]interface{}
  2523  	err := s.state.Get("conns", &conns)
  2524  	c.Assert(err, IsNil)
  2525  	c.Check(conns, DeepEquals, map[string]interface{}{
  2526  		// The sample snap was auto-connected, as expected.
  2527  		"snap:network ubuntu-core:network": map[string]interface{}{
  2528  			"interface": "network", "auto": true,
  2529  		},
  2530  		// Connection state for the fake snap is preserved.
  2531  		// The task didn't alter state of other snaps.
  2532  		"other-snap:network ubuntu-core:network": map[string]interface{}{
  2533  			"interface": "network",
  2534  		},
  2535  	})
  2536  }
  2537  
  2538  func (s *interfaceManagerSuite) TestReloadingConnectionsOnStartupUpdatesStaticAttributes(c *C) {
  2539  	// Put a connection in the state. The connection binds the two snaps we are
  2540  	// adding below. The connection contains a copy of the static attributes
  2541  	// but refers to the "old" values, in contrast to what the snaps define.
  2542  	s.state.Lock()
  2543  	s.state.Set("conns", map[string]interface{}{
  2544  		"consumer:plug producer:slot": map[string]interface{}{
  2545  			"interface":   "content",
  2546  			"plug-static": map[string]interface{}{"content": "foo", "attr": "old-plug-attr"},
  2547  			"slot-static": map[string]interface{}{"content": "foo", "attr": "old-slot-attr"},
  2548  		},
  2549  	})
  2550  	s.state.Unlock()
  2551  
  2552  	// Add consumer and producer snaps, with a plug and slot respectively, each
  2553  	// carrying a single attribute with a "new" value. The "new" value is in
  2554  	// contrast to the old value in the connection state.
  2555  	const consumerYaml = `
  2556  name: consumer
  2557  version: 1
  2558  plugs:
  2559   plug:
  2560    interface: content
  2561    content: foo 
  2562    attr: new-plug-attr
  2563  `
  2564  	const producerYaml = `
  2565  name: producer
  2566  version: 1
  2567  slots:
  2568   slot:
  2569    interface: content
  2570    content: foo 
  2571    attr: new-slot-attr
  2572  `
  2573  	s.mockSnap(c, producerYaml)
  2574  	s.mockSnap(c, consumerYaml)
  2575  
  2576  	// Create a connection reference, it's just verbose and used a few times
  2577  	// below so it's put up here.
  2578  	connRef := &interfaces.ConnRef{
  2579  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  2580  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}}
  2581  
  2582  	// Add a test security backend and a test interface. We want to use them to
  2583  	// observe the interaction with the security backend and to allow the
  2584  	// interface manager to keep the test plug and slot of the consumer and
  2585  	// producer snaps we introduce below.
  2586  	secBackend := &ifacetest.TestSecurityBackend{
  2587  		BackendName: "test",
  2588  		SetupCallback: func(snapInfo *snap.Info, opts interfaces.ConfinementOptions, repo *interfaces.Repository) error {
  2589  			// Whenever this function is invoked to setup security for a snap
  2590  			// we check the connection attributes that it would act upon.
  2591  			// Because of how connection state is refreshed we never expect to
  2592  			// see the old attribute values.
  2593  			conn, err := repo.Connection(connRef)
  2594  			c.Assert(err, IsNil)
  2595  			c.Check(conn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{"content": "foo", "attr": "new-plug-attr"})
  2596  			c.Check(conn.Slot.StaticAttrs(), DeepEquals, map[string]interface{}{"content": "foo", "attr": "new-slot-attr"})
  2597  			return nil
  2598  		},
  2599  	}
  2600  	s.mockSecBackend(c, secBackend)
  2601  	//s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "content"})
  2602  
  2603  	// Create the interface manager. This indirectly adds the snaps to the
  2604  	// repository and re-connects them using the stored connection information.
  2605  	mgr := s.manager(c)
  2606  
  2607  	// Inspect the repository connection data. The data no longer refers to the
  2608  	// old connection attributes because they were updated when the connections
  2609  	// were reloaded from the state.
  2610  	repo := mgr.Repository()
  2611  	conn, err := repo.Connection(connRef)
  2612  	c.Assert(err, IsNil)
  2613  	c.Check(conn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{"content": "foo", "attr": "new-plug-attr"})
  2614  	c.Check(conn.Slot.StaticAttrs(), DeepEquals, map[string]interface{}{"content": "foo", "attr": "new-slot-attr"})
  2615  
  2616  	// Because of the fact that during testing the system key always
  2617  	// mismatches, the security setup is performed.
  2618  	c.Check(secBackend.SetupCalls, HasLen, 2)
  2619  }
  2620  
  2621  // LP:#1825883; make sure static attributes in conns state are updated from the snap yaml on snap refresh (content interface only)
  2622  func (s *interfaceManagerSuite) testDoSetupProfilesUpdatesStaticAttributes(c *C, snapNameToSetup string) {
  2623  	// Put a connection in the state. The connection binds the two snaps we are
  2624  	// adding below. The connection reflects the snaps as they are now, and
  2625  	// carries no attribute data.
  2626  	s.state.Lock()
  2627  	s.state.Set("conns", map[string]interface{}{
  2628  		"consumer:plug producer:slot": map[string]interface{}{
  2629  			"interface": "content",
  2630  		},
  2631  		"unrelated-a:plug unrelated-b:slot": map[string]interface{}{
  2632  			"interface":   "unrelated",
  2633  			"plug-static": map[string]interface{}{"attr": "unrelated-stale"},
  2634  			"slot-static": map[string]interface{}{"attr": "unrelated-stale"},
  2635  		},
  2636  	})
  2637  	s.state.Unlock()
  2638  
  2639  	// Add a pair of snap versions for producer and consumer snaps, with a plug
  2640  	// and slot respectively. The second version producer and consumer snaps
  2641  	// where the interfaces carry additional attributes.
  2642  	const consumerV1Yaml = `
  2643  name: consumer
  2644  version: 1
  2645  plugs:
  2646   plug:
  2647    interface: content
  2648    content: foo
  2649   plug2:
  2650    interface: content
  2651    content: bar
  2652  `
  2653  	const producerV1Yaml = `
  2654  name: producer
  2655  version: 1
  2656  slots:
  2657   slot:
  2658    interface: content
  2659    content: foo
  2660  `
  2661  	const consumerV2Yaml = `
  2662  name: consumer
  2663  version: 2
  2664  plugs:
  2665   plug:
  2666    interface: content
  2667    content: foo
  2668    attr: plug-value
  2669   plug2:
  2670    interface: content
  2671    content: bar-changed
  2672    attr: plug-value
  2673  `
  2674  	const producerV2Yaml = `
  2675  name: producer
  2676  version: 2
  2677  slots:
  2678   slot:
  2679    interface: content
  2680    content: foo
  2681    attr: slot-value
  2682  `
  2683  
  2684  	const unrelatedAYaml = `
  2685  name: unrelated-a
  2686  version: 1
  2687  plugs:
  2688    plug:
  2689     interface: unrelated
  2690     attr: unrelated-new
  2691  `
  2692  	const unrelatedBYaml = `
  2693  name: unrelated-b
  2694  version: 1
  2695  slots:
  2696    slot:
  2697     interface: unrelated
  2698     attr: unrelated-new
  2699  `
  2700  
  2701  	// NOTE: s.mockSnap sets the state and calls MockSnapInstance internally,
  2702  	// which puts the snap on disk. This gives us all four YAMLs on disk and
  2703  	// just the first version of both in the state.
  2704  	s.mockSnap(c, producerV1Yaml)
  2705  	s.mockSnap(c, consumerV1Yaml)
  2706  	snaptest.MockSnapInstance(c, "", consumerV2Yaml, &snap.SideInfo{Revision: snap.R(2)})
  2707  	snaptest.MockSnapInstance(c, "", producerV2Yaml, &snap.SideInfo{Revision: snap.R(2)})
  2708  
  2709  	// Mock two unrelated snaps, those will show that the state of unrelated
  2710  	// snaps is not clobbered by the refresh process.
  2711  	s.mockSnap(c, unrelatedAYaml)
  2712  	s.mockSnap(c, unrelatedBYaml)
  2713  
  2714  	// Create a connection reference, it's just verbose and used a few times
  2715  	// below so it's put up here.
  2716  	connRef := &interfaces.ConnRef{
  2717  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  2718  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}}
  2719  
  2720  	// Add a test security backend and a test interface. We want to use them to
  2721  	// observe the interaction with the security backend and to allow the
  2722  	// interface manager to keep the test plug and slot of the consumer and
  2723  	// producer snaps we introduce below.
  2724  	secBackend := &ifacetest.TestSecurityBackend{
  2725  		BackendName: "test",
  2726  		SetupCallback: func(snapInfo *snap.Info, opts interfaces.ConfinementOptions, repo *interfaces.Repository) error {
  2727  			// Whenever this function is invoked to setup security for a snap
  2728  			// we check the connection attributes that it would act upon.
  2729  			// Those attributes should always match those of the snap version.
  2730  			conn, err := repo.Connection(connRef)
  2731  			c.Assert(err, IsNil)
  2732  			switch snapInfo.Version {
  2733  			case "1":
  2734  				c.Check(conn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{"content": "foo"})
  2735  				c.Check(conn.Slot.StaticAttrs(), DeepEquals, map[string]interface{}{"content": "foo"})
  2736  			case "2":
  2737  				switch snapNameToSetup {
  2738  				case "consumer":
  2739  					// When the consumer has security setup the consumer's plug attribute is updated.
  2740  					c.Check(conn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{"content": "foo", "attr": "plug-value"})
  2741  					c.Check(conn.Slot.StaticAttrs(), DeepEquals, map[string]interface{}{"content": "foo"})
  2742  				case "producer":
  2743  					// When the producer has security setup the producer's slot attribute is updated.
  2744  					c.Check(conn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{"content": "foo"})
  2745  					c.Check(conn.Slot.StaticAttrs(), DeepEquals, map[string]interface{}{"content": "foo", "attr": "slot-value"})
  2746  				}
  2747  			}
  2748  			return nil
  2749  		},
  2750  	}
  2751  	s.mockSecBackend(c, secBackend)
  2752  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "unrelated"})
  2753  
  2754  	// Create the interface manager. This indirectly adds the snaps to the
  2755  	// repository and reloads the connection.
  2756  	s.manager(c)
  2757  
  2758  	// Because in tests the system key mismatch always occurs, the backend is
  2759  	// invoked during the startup of the interface manager. The count
  2760  	// represents the number of snaps that are in the system.
  2761  	c.Check(secBackend.SetupCalls, HasLen, 4)
  2762  
  2763  	// Alter the state of producer and consumer snaps to get new revisions.
  2764  	s.state.Lock()
  2765  	for _, snapName := range []string{"producer", "consumer"} {
  2766  		snapstate.Set(s.state, snapName, &snapstate.SnapState{
  2767  			Active:   true,
  2768  			Sequence: []*snap.SideInfo{{Revision: snap.R(1)}, {Revision: snap.R(2)}},
  2769  			Current:  snap.R(2),
  2770  			SnapType: string("app"),
  2771  		})
  2772  	}
  2773  	s.state.Unlock()
  2774  
  2775  	// Setup profiles for the given snap, either consumer or producer.
  2776  	s.state.Lock()
  2777  	change := s.state.NewChange("test", "")
  2778  	task := s.state.NewTask("setup-profiles", "")
  2779  	task.Set("snap-setup", &snapstate.SnapSetup{
  2780  		SideInfo: &snap.SideInfo{RealName: snapNameToSetup, Revision: snap.R(2)}})
  2781  	change.AddTask(task)
  2782  	s.state.Unlock()
  2783  
  2784  	// Spin the wheels to run the tasks we added.
  2785  	s.settle(c)
  2786  	s.state.Lock()
  2787  	defer s.state.Unlock()
  2788  	c.Assert(change.Status(), Equals, state.DoneStatus)
  2789  
  2790  	// We expect our security backend to be invoked for both snaps. See above
  2791  	// for explanation about why it has four calls already.
  2792  	c.Check(secBackend.SetupCalls, HasLen, 4+2)
  2793  }
  2794  
  2795  func (s *interfaceManagerSuite) TestDoSetupProfilesUpdatesStaticAttributesPlugSnap(c *C) {
  2796  	s.testDoSetupProfilesUpdatesStaticAttributes(c, "consumer")
  2797  }
  2798  
  2799  func (s *interfaceManagerSuite) TestDoSetupProfilesUpdatesStaticAttributesSlotSnap(c *C) {
  2800  	s.testDoSetupProfilesUpdatesStaticAttributes(c, "producer")
  2801  }
  2802  
  2803  func (s *interfaceManagerSuite) TestUpdateStaticAttributesIgnoresContentMismatch(c *C) {
  2804  	s.state.Lock()
  2805  	s.state.Set("conns", map[string]interface{}{
  2806  		"consumer:plug producer:slot": map[string]interface{}{
  2807  			"interface": "content",
  2808  			"content":   "foo",
  2809  		},
  2810  	})
  2811  	s.state.Unlock()
  2812  
  2813  	// Add a pair of snap versions for producer and consumer snaps, with a plug
  2814  	// and slot respectively. The second version are producer and consumer snaps
  2815  	// where the interfaces carry additional attributes but there is a mismatch
  2816  	// on "content" attribute value.
  2817  	const consumerV1Yaml = `
  2818  name: consumer
  2819  version: 1
  2820  plugs:
  2821   plug:
  2822    interface: content
  2823    content: foo
  2824  `
  2825  	const producerV1Yaml = `
  2826  name: producer
  2827  version: 1
  2828  slots:
  2829   slot:
  2830    interface: content
  2831    content: foo
  2832  `
  2833  	const consumerV2Yaml = `
  2834  name: consumer
  2835  version: 2
  2836  plugs:
  2837   plug:
  2838    interface: content
  2839    content: foo-mismatch
  2840    attr: plug-value
  2841  `
  2842  	const producerV2Yaml = `
  2843  name: producer
  2844  version: 2
  2845  slots:
  2846   slot:
  2847    interface: content
  2848    content: foo
  2849    attr: slot-value
  2850  `
  2851  
  2852  	// NOTE: s.mockSnap sets the state and calls MockSnapInstance internally,
  2853  	// which puts the snap on disk. This gives us all four YAMLs on disk and
  2854  	// just the first version of both in the state.
  2855  	s.mockSnap(c, producerV1Yaml)
  2856  	s.mockSnap(c, consumerV1Yaml)
  2857  	snaptest.MockSnapInstance(c, "", consumerV2Yaml, &snap.SideInfo{Revision: snap.R(2)})
  2858  	snaptest.MockSnapInstance(c, "", producerV2Yaml, &snap.SideInfo{Revision: snap.R(2)})
  2859  
  2860  	secBackend := &ifacetest.TestSecurityBackend{BackendName: "test"}
  2861  	s.mockSecBackend(c, secBackend)
  2862  
  2863  	// Create the interface manager. This indirectly adds the snaps to the
  2864  	// repository and reloads the connection.
  2865  	s.manager(c)
  2866  
  2867  	// Alter the state of producer and consumer snaps to get new revisions.
  2868  	s.state.Lock()
  2869  	for _, snapName := range []string{"producer", "consumer"} {
  2870  		snapstate.Set(s.state, snapName, &snapstate.SnapState{
  2871  			Active:   true,
  2872  			Sequence: []*snap.SideInfo{{Revision: snap.R(1)}, {Revision: snap.R(2)}},
  2873  			Current:  snap.R(2),
  2874  			SnapType: string("app"),
  2875  		})
  2876  	}
  2877  	s.state.Unlock()
  2878  
  2879  	s.state.Lock()
  2880  	change := s.state.NewChange("test", "")
  2881  	task := s.state.NewTask("setup-profiles", "")
  2882  	task.Set("snap-setup", &snapstate.SnapSetup{
  2883  		SideInfo: &snap.SideInfo{RealName: "consumer", Revision: snap.R(2)}})
  2884  	change.AddTask(task)
  2885  	s.state.Unlock()
  2886  
  2887  	s.settle(c)
  2888  	s.state.Lock()
  2889  	defer s.state.Unlock()
  2890  	c.Assert(change.Status(), Equals, state.DoneStatus)
  2891  
  2892  	var conns map[string]interface{}
  2893  	s.state.Get("conns", &conns)
  2894  	c.Check(conns, DeepEquals, map[string]interface{}{
  2895  		"consumer:plug producer:slot": map[string]interface{}{
  2896  			"interface":   "content",
  2897  			"plug-static": map[string]interface{}{"content": "foo"},
  2898  			"slot-static": map[string]interface{}{"content": "foo"},
  2899  		},
  2900  	})
  2901  }
  2902  
  2903  func (s *interfaceManagerSuite) TestDoSetupSnapSecurityIgnoresStrayConnection(c *C) {
  2904  	s.MockModel(c, nil)
  2905  
  2906  	// Add an OS snap
  2907  	snapInfo := s.mockSnap(c, ubuntuCoreSnapYaml)
  2908  
  2909  	_ = s.manager(c)
  2910  
  2911  	// Put fake information about connections for another snap into the state.
  2912  	s.state.Lock()
  2913  	s.state.Set("conns", map[string]interface{}{
  2914  		"removed-snap:network ubuntu-core:network": map[string]interface{}{
  2915  			"interface": "network",
  2916  		},
  2917  	})
  2918  	s.state.Unlock()
  2919  
  2920  	// Run the setup-snap-security task and let it finish.
  2921  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  2922  		SideInfo: &snap.SideInfo{
  2923  			RealName: snapInfo.SnapName(),
  2924  			Revision: snapInfo.Revision,
  2925  		},
  2926  	})
  2927  	s.settle(c)
  2928  
  2929  	s.state.Lock()
  2930  	defer s.state.Unlock()
  2931  
  2932  	// Ensure that the task succeeded.
  2933  	c.Assert(change.Status(), Equals, state.DoneStatus)
  2934  
  2935  	// Ensure that the tasks don't report errors caused by bad connections
  2936  	for _, t := range change.Tasks() {
  2937  		c.Assert(t.Log(), HasLen, 0)
  2938  	}
  2939  }
  2940  
  2941  // The setup-profiles task will add implicit slots necessary for the OS snap.
  2942  func (s *interfaceManagerSuite) TestDoSetupProfilesAddsImplicitSlots(c *C) {
  2943  	s.MockModel(c, nil)
  2944  
  2945  	// Initialize the manager.
  2946  	mgr := s.manager(c)
  2947  
  2948  	// Add an OS snap.
  2949  	snapInfo := s.mockSnap(c, ubuntuCoreSnapYaml)
  2950  
  2951  	// Run the setup-profiles task and let it finish.
  2952  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  2953  		SideInfo: &snap.SideInfo{
  2954  			RealName: snapInfo.SnapName(),
  2955  			Revision: snapInfo.Revision,
  2956  		},
  2957  	})
  2958  	s.settle(c)
  2959  
  2960  	s.state.Lock()
  2961  	defer s.state.Unlock()
  2962  
  2963  	// Ensure that the task succeeded.
  2964  	c.Assert(change.Status(), Equals, state.DoneStatus)
  2965  
  2966  	// Ensure that we have slots on the OS snap.
  2967  	repo := mgr.Repository()
  2968  	slots := repo.Slots(snapInfo.InstanceName())
  2969  	// NOTE: This is not an exact test as it duplicates functionality elsewhere
  2970  	// and is was a pain to update each time. This is correctly handled by the
  2971  	// implicit slot tests in snap/implicit_test.go
  2972  	c.Assert(len(slots) > 18, Equals, true)
  2973  }
  2974  
  2975  func (s *interfaceManagerSuite) TestDoSetupSnapSecurityReloadsConnectionsWhenInvokedOnPlugSide(c *C) {
  2976  	s.MockModel(c, nil)
  2977  
  2978  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  2979  	snapInfo := s.mockSnap(c, consumerYaml)
  2980  	s.mockSnap(c, producerYaml)
  2981  	s.testDoSetupSnapSecurityReloadsConnectionsWhenInvokedOn(c, snapInfo.InstanceName(), snapInfo.Revision)
  2982  
  2983  	// Ensure that the backend was used to setup security of both snaps
  2984  	c.Assert(s.secBackend.SetupCalls, HasLen, 2)
  2985  	c.Assert(s.secBackend.RemoveCalls, HasLen, 0)
  2986  	c.Check(s.secBackend.SetupCalls[0].SnapInfo.InstanceName(), Equals, "consumer")
  2987  	c.Check(s.secBackend.SetupCalls[1].SnapInfo.InstanceName(), Equals, "producer")
  2988  
  2989  	c.Check(s.secBackend.SetupCalls[0].Options, Equals, interfaces.ConfinementOptions{})
  2990  	c.Check(s.secBackend.SetupCalls[1].Options, Equals, interfaces.ConfinementOptions{})
  2991  }
  2992  
  2993  func (s *interfaceManagerSuite) TestDoSetupSnapSecurityReloadsConnectionsWhenInvokedOnSlotSide(c *C) {
  2994  	s.MockModel(c, nil)
  2995  
  2996  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  2997  	s.mockSnap(c, consumerYaml)
  2998  	snapInfo := s.mockSnap(c, producerYaml)
  2999  	s.testDoSetupSnapSecurityReloadsConnectionsWhenInvokedOn(c, snapInfo.InstanceName(), snapInfo.Revision)
  3000  
  3001  	// Ensure that the backend was used to setup security of both snaps
  3002  	c.Assert(s.secBackend.SetupCalls, HasLen, 2)
  3003  	c.Assert(s.secBackend.RemoveCalls, HasLen, 0)
  3004  	c.Check(s.secBackend.SetupCalls[0].SnapInfo.InstanceName(), Equals, "producer")
  3005  	c.Check(s.secBackend.SetupCalls[1].SnapInfo.InstanceName(), Equals, "consumer")
  3006  
  3007  	c.Check(s.secBackend.SetupCalls[0].Options, Equals, interfaces.ConfinementOptions{})
  3008  	c.Check(s.secBackend.SetupCalls[1].Options, Equals, interfaces.ConfinementOptions{})
  3009  }
  3010  
  3011  func (s *interfaceManagerSuite) testDoSetupSnapSecurityReloadsConnectionsWhenInvokedOn(c *C, snapName string, revision snap.Revision) {
  3012  	s.state.Lock()
  3013  	s.state.Set("conns", map[string]interface{}{
  3014  		"consumer:plug producer:slot": map[string]interface{}{"interface": "test"},
  3015  	})
  3016  	s.state.Unlock()
  3017  
  3018  	mgr := s.manager(c)
  3019  
  3020  	// Run the setup-profiles task
  3021  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  3022  		SideInfo: &snap.SideInfo{
  3023  			RealName: snapName,
  3024  			Revision: revision,
  3025  		},
  3026  	})
  3027  	s.settle(c)
  3028  
  3029  	// Change succeeds
  3030  	s.state.Lock()
  3031  	defer s.state.Unlock()
  3032  	c.Check(change.Status(), Equals, state.DoneStatus)
  3033  
  3034  	repo := mgr.Repository()
  3035  
  3036  	// Repository shows the connection
  3037  	ifaces := repo.Interfaces()
  3038  	c.Assert(ifaces.Connections, HasLen, 1)
  3039  	c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{
  3040  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  3041  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}}})
  3042  }
  3043  
  3044  // The setup-profiles task will honor snapstate.DevMode flag by storing it
  3045  // in the SnapState.Flags and by actually setting up security
  3046  // using that flag. Old copy of SnapState.Flag's DevMode is saved for the undo
  3047  // handler under `old-devmode`.
  3048  func (s *interfaceManagerSuite) TestSetupProfilesHonorsDevMode(c *C) {
  3049  	s.MockModel(c, nil)
  3050  
  3051  	// Put the OS snap in place.
  3052  	_ = s.manager(c)
  3053  
  3054  	// Initialize the manager. This registers the OS snap.
  3055  	snapInfo := s.mockSnap(c, sampleSnapYaml)
  3056  
  3057  	// Run the setup-profiles task and let it finish.
  3058  	// Note that the task will see SnapSetup.Flags equal to DeveloperMode.
  3059  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  3060  		SideInfo: &snap.SideInfo{
  3061  			RealName: snapInfo.SnapName(),
  3062  			Revision: snapInfo.Revision,
  3063  		},
  3064  		Flags: snapstate.Flags{DevMode: true},
  3065  	})
  3066  	s.settle(c)
  3067  
  3068  	s.state.Lock()
  3069  	defer s.state.Unlock()
  3070  
  3071  	// Ensure that the task succeeded.
  3072  	c.Check(change.Status(), Equals, state.DoneStatus)
  3073  
  3074  	// The snap was setup with DevModeConfinement
  3075  	c.Assert(s.secBackend.SetupCalls, HasLen, 1)
  3076  	c.Assert(s.secBackend.RemoveCalls, HasLen, 0)
  3077  	c.Check(s.secBackend.SetupCalls[0].SnapInfo.InstanceName(), Equals, "snap")
  3078  	c.Check(s.secBackend.SetupCalls[0].Options, Equals, interfaces.ConfinementOptions{DevMode: true})
  3079  }
  3080  
  3081  // setup-profiles uses the new snap.Info when setting up security for the new
  3082  // snap when it had prior connections and DisconnectSnap() returns it as a part
  3083  // of the affected set.
  3084  func (s *interfaceManagerSuite) TestSetupProfilesUsesFreshSnapInfo(c *C) {
  3085  	s.MockModel(c, nil)
  3086  
  3087  	// Put the OS and the sample snaps in place.
  3088  	coreSnapInfo := s.mockSnap(c, ubuntuCoreSnapYaml)
  3089  	oldSnapInfo := s.mockSnap(c, sampleSnapYaml)
  3090  
  3091  	// Put connection information between the OS snap and the sample snap.
  3092  	// This is done so that DisconnectSnap returns both snaps as "affected"
  3093  	// and so that the previously broken code path is exercised.
  3094  	s.state.Lock()
  3095  	s.state.Set("conns", map[string]interface{}{
  3096  		"snap:network ubuntu-core:network": map[string]interface{}{"interface": "network"},
  3097  	})
  3098  	s.state.Unlock()
  3099  
  3100  	// Initialize the manager. This registers both of the snaps and reloads the
  3101  	// connection between them.
  3102  	_ = s.manager(c)
  3103  
  3104  	// Put a new revision of the sample snap in place.
  3105  	newSnapInfo := s.mockUpdatedSnap(c, sampleSnapYaml, 42)
  3106  
  3107  	// Sanity check, the revisions are different.
  3108  	c.Assert(oldSnapInfo.Revision, Not(Equals), 42)
  3109  	c.Assert(newSnapInfo.Revision, Equals, snap.R(42))
  3110  
  3111  	// Run the setup-profiles task for the new revision and let it finish.
  3112  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  3113  		SideInfo: &snap.SideInfo{
  3114  			RealName: newSnapInfo.SnapName(),
  3115  			Revision: newSnapInfo.Revision,
  3116  		},
  3117  	})
  3118  	s.settle(c)
  3119  
  3120  	s.state.Lock()
  3121  	defer s.state.Unlock()
  3122  
  3123  	// Ensure that the task succeeded.
  3124  	c.Assert(change.Err(), IsNil)
  3125  	c.Check(change.Status(), Equals, state.DoneStatus)
  3126  
  3127  	// Ensure that both snaps were setup correctly.
  3128  	c.Assert(s.secBackend.SetupCalls, HasLen, 2)
  3129  	c.Assert(s.secBackend.RemoveCalls, HasLen, 0)
  3130  	// The sample snap was setup, with the correct new revision.
  3131  	c.Check(s.secBackend.SetupCalls[0].SnapInfo.InstanceName(), Equals, newSnapInfo.InstanceName())
  3132  	c.Check(s.secBackend.SetupCalls[0].SnapInfo.Revision, Equals, newSnapInfo.Revision)
  3133  	// The OS snap was setup (because it was affected).
  3134  	c.Check(s.secBackend.SetupCalls[1].SnapInfo.InstanceName(), Equals, coreSnapInfo.InstanceName())
  3135  	c.Check(s.secBackend.SetupCalls[1].SnapInfo.Revision, Equals, coreSnapInfo.Revision)
  3136  }
  3137  
  3138  // auto-connect needs to setup security for connected slots after autoconnection
  3139  func (s *interfaceManagerSuite) TestAutoConnectSetupSecurityForConnectedSlots(c *C) {
  3140  	s.MockModel(c, nil)
  3141  
  3142  	// Add an OS snap.
  3143  	coreSnapInfo := s.mockSnap(c, ubuntuCoreSnapYaml)
  3144  
  3145  	// Initialize the manager. This registers the OS snap.
  3146  	_ = s.manager(c)
  3147  
  3148  	// Add a sample snap with a "network" plug which should be auto-connected.
  3149  	snapInfo := s.mockSnap(c, sampleSnapYaml)
  3150  
  3151  	// Run the setup-snap-security task and let it finish.
  3152  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  3153  		SideInfo: &snap.SideInfo{
  3154  			RealName: snapInfo.SnapName(),
  3155  			Revision: snapInfo.Revision,
  3156  		},
  3157  	})
  3158  	s.settle(c)
  3159  
  3160  	s.state.Lock()
  3161  	defer s.state.Unlock()
  3162  
  3163  	// Ensure that the task succeeded.
  3164  	c.Assert(change.Err(), IsNil)
  3165  	c.Assert(change.Status(), Equals, state.DoneStatus)
  3166  
  3167  	// Ensure that both snaps were setup correctly.
  3168  	c.Assert(s.secBackend.SetupCalls, HasLen, 3)
  3169  	c.Assert(s.secBackend.RemoveCalls, HasLen, 0)
  3170  
  3171  	// The sample snap was setup, with the correct new revision:
  3172  	// 1st call is for initial setup-profiles, 2nd call is for setup-profiles after connect task.
  3173  	c.Check(s.secBackend.SetupCalls[0].SnapInfo.InstanceName(), Equals, snapInfo.InstanceName())
  3174  	c.Check(s.secBackend.SetupCalls[0].SnapInfo.Revision, Equals, snapInfo.Revision)
  3175  	c.Check(s.secBackend.SetupCalls[1].SnapInfo.InstanceName(), Equals, snapInfo.InstanceName())
  3176  	c.Check(s.secBackend.SetupCalls[1].SnapInfo.Revision, Equals, snapInfo.Revision)
  3177  
  3178  	// The OS snap was setup (because its connected to sample snap).
  3179  	c.Check(s.secBackend.SetupCalls[2].SnapInfo.InstanceName(), Equals, coreSnapInfo.InstanceName())
  3180  	c.Check(s.secBackend.SetupCalls[2].SnapInfo.Revision, Equals, coreSnapInfo.Revision)
  3181  }
  3182  
  3183  // auto-connect needs to setup security for connected slots after autoconnection
  3184  func (s *interfaceManagerSuite) TestAutoConnectSetupSecurityOnceWithMultiplePlugs(c *C) {
  3185  	s.MockModel(c, nil)
  3186  
  3187  	// Add an OS snap.
  3188  	_ = s.mockSnap(c, ubuntuCoreSnapYaml)
  3189  
  3190  	// Initialize the manager. This registers the OS snap.
  3191  	mgr := s.manager(c)
  3192  
  3193  	// Add a sample snap with a multiple plugs which should be auto-connected.
  3194  	snapInfo := s.mockSnap(c, sampleSnapYamlManyPlugs)
  3195  
  3196  	// Run the setup-snap-security task and let it finish.
  3197  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  3198  		SideInfo: &snap.SideInfo{
  3199  			RealName: snapInfo.SnapName(),
  3200  			Revision: snapInfo.Revision,
  3201  		},
  3202  	})
  3203  	s.settle(c)
  3204  
  3205  	s.state.Lock()
  3206  	defer s.state.Unlock()
  3207  
  3208  	// Ensure that the task succeeded.
  3209  	c.Assert(change.Err(), IsNil)
  3210  	c.Assert(change.Status(), Equals, state.DoneStatus)
  3211  
  3212  	repo := mgr.Repository()
  3213  
  3214  	for _, ifaceName := range []string{"network", "home", "x11", "wayland"} {
  3215  		cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap", Name: ifaceName}, SlotRef: interfaces.SlotRef{Snap: "ubuntu-core", Name: ifaceName}}
  3216  		conn, _ := repo.Connection(cref)
  3217  		c.Check(conn, NotNil, Commentf("missing connection for %s interface", ifaceName))
  3218  	}
  3219  
  3220  	// Three backend calls: initial setup profiles, 2 setup calls for both core and snap.
  3221  	c.Assert(s.secBackend.SetupCalls, HasLen, 3)
  3222  	c.Assert(s.secBackend.RemoveCalls, HasLen, 0)
  3223  	setupCalls := make(map[string]int)
  3224  	for _, sc := range s.secBackend.SetupCalls {
  3225  		setupCalls[sc.SnapInfo.InstanceName()]++
  3226  	}
  3227  	c.Check(setupCalls["snap"], Equals, 2)
  3228  	c.Check(setupCalls["ubuntu-core"], Equals, 1)
  3229  }
  3230  
  3231  func (s *interfaceManagerSuite) TestDoDiscardConnsPlug(c *C) {
  3232  	s.testDoDiscardConns(c, "consumer")
  3233  }
  3234  
  3235  func (s *interfaceManagerSuite) TestDoDiscardConnsSlot(c *C) {
  3236  	s.testDoDiscardConns(c, "producer")
  3237  }
  3238  
  3239  func (s *interfaceManagerSuite) TestUndoDiscardConnsPlug(c *C) {
  3240  	s.testUndoDiscardConns(c, "consumer")
  3241  }
  3242  
  3243  func (s *interfaceManagerSuite) TestUndoDiscardConnsSlot(c *C) {
  3244  	s.testUndoDiscardConns(c, "producer")
  3245  }
  3246  
  3247  func (s *interfaceManagerSuite) testDoDiscardConns(c *C, snapName string) {
  3248  	s.state.Lock()
  3249  	// Store information about a connection in the state.
  3250  	s.state.Set("conns", map[string]interface{}{
  3251  		"consumer:plug producer:slot": map[string]interface{}{
  3252  			"interface": "test",
  3253  		},
  3254  	})
  3255  
  3256  	// Store empty snap state. This snap has an empty sequence now.
  3257  	s.state.Unlock()
  3258  
  3259  	// mock the snaps or otherwise the manager will remove stale connections
  3260  	s.mockSnap(c, consumerYaml)
  3261  	s.mockSnap(c, producerYaml)
  3262  
  3263  	s.manager(c)
  3264  
  3265  	s.state.Lock()
  3266  	// remove the snaps so that discard-conns doesn't complain about snaps still installed
  3267  	snapstate.Set(s.state, "producer", nil)
  3268  	snapstate.Set(s.state, "consumer", nil)
  3269  	s.state.Unlock()
  3270  
  3271  	// Run the discard-conns task and let it finish
  3272  	change, _ := s.addDiscardConnsChange(c, snapName)
  3273  
  3274  	s.settle(c)
  3275  
  3276  	s.state.Lock()
  3277  	defer s.state.Unlock()
  3278  
  3279  	c.Check(change.Status(), Equals, state.DoneStatus)
  3280  
  3281  	// Information about the connection was removed
  3282  	var conns map[string]interface{}
  3283  	err := s.state.Get("conns", &conns)
  3284  	c.Assert(err, IsNil)
  3285  	c.Check(conns, DeepEquals, map[string]interface{}{})
  3286  
  3287  	// But removed connections are preserved in the task for undo.
  3288  	var removed map[string]interface{}
  3289  	err = change.Tasks()[0].Get("removed", &removed)
  3290  	c.Assert(err, IsNil)
  3291  	c.Check(removed, DeepEquals, map[string]interface{}{
  3292  		"consumer:plug producer:slot": map[string]interface{}{"interface": "test"},
  3293  	})
  3294  }
  3295  
  3296  func (s *interfaceManagerSuite) testUndoDiscardConns(c *C, snapName string) {
  3297  	s.manager(c)
  3298  
  3299  	s.state.Lock()
  3300  	// Store information about a connection in the state.
  3301  	s.state.Set("conns", map[string]interface{}{
  3302  		"consumer:plug producer:slot": map[string]interface{}{"interface": "test"},
  3303  	})
  3304  
  3305  	// Store empty snap state. This snap has an empty sequence now.
  3306  	snapstate.Set(s.state, snapName, &snapstate.SnapState{})
  3307  	s.state.Unlock()
  3308  
  3309  	// Run the discard-conns task and let it finish
  3310  	change, t := s.addDiscardConnsChange(c, snapName)
  3311  	s.state.Lock()
  3312  	terr := s.state.NewTask("error-trigger", "provoking undo")
  3313  	terr.WaitFor(t)
  3314  	change.AddTask(terr)
  3315  	s.state.Unlock()
  3316  
  3317  	s.settle(c)
  3318  
  3319  	s.state.Lock()
  3320  	defer s.state.Unlock()
  3321  	c.Assert(change.Status().Ready(), Equals, true)
  3322  	c.Assert(t.Status(), Equals, state.UndoneStatus)
  3323  
  3324  	// Information about the connection is intact
  3325  	var conns map[string]interface{}
  3326  	err := s.state.Get("conns", &conns)
  3327  	c.Assert(err, IsNil)
  3328  	c.Check(conns, DeepEquals, map[string]interface{}{
  3329  		"consumer:plug producer:slot": map[string]interface{}{"interface": "test"},
  3330  	})
  3331  
  3332  	var removed map[string]interface{}
  3333  	err = change.Tasks()[0].Get("removed", &removed)
  3334  	c.Check(err, Equals, state.ErrNoState)
  3335  }
  3336  
  3337  func (s *interfaceManagerSuite) TestDoRemove(c *C) {
  3338  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  3339  	var consumerYaml = `
  3340  name: consumer
  3341  version: 1
  3342  plugs:
  3343   plug:
  3344    interface: test
  3345  `
  3346  	var producerYaml = `
  3347  name: producer
  3348  version: 1
  3349  slots:
  3350   slot:
  3351    interface: test
  3352  `
  3353  	s.mockSnap(c, consumerYaml)
  3354  	s.mockSnap(c, producerYaml)
  3355  
  3356  	s.state.Lock()
  3357  	s.state.Set("conns", map[string]interface{}{
  3358  		"consumer:plug producer:slot": map[string]interface{}{"interface": "test"},
  3359  	})
  3360  	s.state.Unlock()
  3361  
  3362  	mgr := s.manager(c)
  3363  
  3364  	// Run the remove-security task
  3365  	change := s.addRemoveSnapSecurityChange(c, "consumer")
  3366  	s.se.Ensure()
  3367  	s.se.Wait()
  3368  	s.se.Stop()
  3369  
  3370  	// Change succeeds
  3371  	s.state.Lock()
  3372  	defer s.state.Unlock()
  3373  	c.Check(change.Status(), Equals, state.DoneStatus)
  3374  
  3375  	repo := mgr.Repository()
  3376  
  3377  	// Snap is removed from repository
  3378  	c.Check(repo.Plug("consumer", "slot"), IsNil)
  3379  
  3380  	// Security of the snap was removed
  3381  	c.Check(s.secBackend.RemoveCalls, DeepEquals, []string{"consumer"})
  3382  
  3383  	// Security of the related snap was configured
  3384  	c.Check(s.secBackend.SetupCalls, HasLen, 1)
  3385  	c.Check(s.secBackend.SetupCalls[0].SnapInfo.InstanceName(), Equals, "producer")
  3386  
  3387  	// Connection state was left intact
  3388  	var conns map[string]interface{}
  3389  	err := s.state.Get("conns", &conns)
  3390  	c.Assert(err, IsNil)
  3391  	c.Check(conns, DeepEquals, map[string]interface{}{
  3392  		"consumer:plug producer:slot": map[string]interface{}{"interface": "test"},
  3393  	})
  3394  }
  3395  
  3396  func (s *interfaceManagerSuite) TestConnectTracksConnectionsInState(c *C) {
  3397  	s.MockModel(c, nil)
  3398  
  3399  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  3400  	s.mockSnap(c, consumerYaml)
  3401  	s.mockSnap(c, producerYaml)
  3402  
  3403  	_ = s.manager(c)
  3404  
  3405  	s.state.Lock()
  3406  
  3407  	ts, err := ifacestate.Connect(s.state, "consumer", "plug", "producer", "slot")
  3408  	c.Assert(err, IsNil)
  3409  	c.Assert(ts.Tasks(), HasLen, 5)
  3410  
  3411  	ts.Tasks()[2].Set("snap-setup", &snapstate.SnapSetup{
  3412  		SideInfo: &snap.SideInfo{
  3413  			RealName: "consumer",
  3414  		},
  3415  	})
  3416  
  3417  	change := s.state.NewChange("connect", "")
  3418  	change.AddAll(ts)
  3419  	s.state.Unlock()
  3420  
  3421  	s.settle(c)
  3422  
  3423  	s.state.Lock()
  3424  	defer s.state.Unlock()
  3425  
  3426  	c.Assert(change.Err(), IsNil)
  3427  	c.Check(change.Status(), Equals, state.DoneStatus)
  3428  	var conns map[string]interface{}
  3429  	err = s.state.Get("conns", &conns)
  3430  	c.Assert(err, IsNil)
  3431  	c.Check(conns, DeepEquals, map[string]interface{}{
  3432  		"consumer:plug producer:slot": map[string]interface{}{
  3433  			"interface":   "test",
  3434  			"plug-static": map[string]interface{}{"attr1": "value1"},
  3435  			"slot-static": map[string]interface{}{"attr2": "value2"},
  3436  		},
  3437  	})
  3438  }
  3439  
  3440  func (s *interfaceManagerSuite) TestConnectSetsUpSecurity(c *C) {
  3441  	s.MockModel(c, nil)
  3442  
  3443  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  3444  
  3445  	s.mockSnap(c, consumerYaml)
  3446  	s.mockSnap(c, producerYaml)
  3447  	_ = s.manager(c)
  3448  
  3449  	s.state.Lock()
  3450  	ts, err := ifacestate.Connect(s.state, "consumer", "plug", "producer", "slot")
  3451  	c.Assert(err, IsNil)
  3452  	ts.Tasks()[0].Set("snap-setup", &snapstate.SnapSetup{
  3453  		SideInfo: &snap.SideInfo{
  3454  			RealName: "consumer",
  3455  		},
  3456  	})
  3457  
  3458  	change := s.state.NewChange("connect", "")
  3459  	change.AddAll(ts)
  3460  	s.state.Unlock()
  3461  
  3462  	s.settle(c)
  3463  
  3464  	s.state.Lock()
  3465  	defer s.state.Unlock()
  3466  
  3467  	c.Assert(change.Err(), IsNil)
  3468  	c.Check(change.Status(), Equals, state.DoneStatus)
  3469  
  3470  	c.Assert(s.secBackend.SetupCalls, HasLen, 2)
  3471  	c.Assert(s.secBackend.RemoveCalls, HasLen, 0)
  3472  	c.Check(s.secBackend.SetupCalls[0].SnapInfo.InstanceName(), Equals, "producer")
  3473  	c.Check(s.secBackend.SetupCalls[1].SnapInfo.InstanceName(), Equals, "consumer")
  3474  
  3475  	c.Check(s.secBackend.SetupCalls[0].Options, Equals, interfaces.ConfinementOptions{})
  3476  	c.Check(s.secBackend.SetupCalls[1].Options, Equals, interfaces.ConfinementOptions{})
  3477  }
  3478  
  3479  func (s *interfaceManagerSuite) TestConnectSetsHotplugKeyFromTheSlot(c *C) {
  3480  	s.MockModel(c, nil)
  3481  
  3482  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
  3483  	s.mockSnap(c, consumer2Yaml)
  3484  	s.mockSnap(c, coreSnapYaml)
  3485  
  3486  	s.state.Lock()
  3487  	s.state.Set("hotplug-slots", map[string]interface{}{
  3488  		"slot": map[string]interface{}{
  3489  			"name":         "slot",
  3490  			"interface":    "test",
  3491  			"hotplug-key":  "1234",
  3492  			"static-attrs": map[string]interface{}{"attr2": "value2"}}})
  3493  	s.state.Unlock()
  3494  
  3495  	_ = s.manager(c)
  3496  
  3497  	s.state.Lock()
  3498  	ts, err := ifacestate.Connect(s.state, "consumer2", "plug", "core", "slot")
  3499  	c.Assert(err, IsNil)
  3500  
  3501  	change := s.state.NewChange("connect", "")
  3502  	change.AddAll(ts)
  3503  	s.state.Unlock()
  3504  
  3505  	s.settle(c)
  3506  
  3507  	s.state.Lock()
  3508  	defer s.state.Unlock()
  3509  
  3510  	c.Assert(change.Err(), IsNil)
  3511  	c.Check(change.Status(), Equals, state.DoneStatus)
  3512  
  3513  	var conns map[string]interface{}
  3514  	c.Assert(s.state.Get("conns", &conns), IsNil)
  3515  	c.Check(conns, DeepEquals, map[string]interface{}{
  3516  		"consumer2:plug core:slot": map[string]interface{}{
  3517  			"interface":   "test",
  3518  			"hotplug-key": "1234",
  3519  			"plug-static": map[string]interface{}{"attr1": "value1"},
  3520  			"slot-static": map[string]interface{}{"attr2": "value2"},
  3521  		},
  3522  	})
  3523  }
  3524  
  3525  func (s *interfaceManagerSuite) TestDisconnectSetsUpSecurity(c *C) {
  3526  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  3527  	s.mockSnap(c, consumerYaml)
  3528  	s.mockSnap(c, producerYaml)
  3529  
  3530  	s.state.Lock()
  3531  	s.state.Set("conns", map[string]interface{}{
  3532  		"consumer:plug producer:slot": map[string]interface{}{"interface": "test"},
  3533  	})
  3534  	s.state.Unlock()
  3535  
  3536  	s.manager(c)
  3537  	conn := s.getConnection(c, "consumer", "plug", "producer", "slot")
  3538  
  3539  	s.state.Lock()
  3540  	ts, err := ifacestate.Disconnect(s.state, conn)
  3541  	c.Assert(err, IsNil)
  3542  	ts.Tasks()[0].Set("snap-setup", &snapstate.SnapSetup{
  3543  		SideInfo: &snap.SideInfo{
  3544  			RealName: "consumer",
  3545  		},
  3546  	})
  3547  
  3548  	change := s.state.NewChange("disconnect", "")
  3549  	change.AddAll(ts)
  3550  	s.state.Unlock()
  3551  
  3552  	s.settle(c)
  3553  
  3554  	s.state.Lock()
  3555  	defer s.state.Unlock()
  3556  
  3557  	c.Assert(change.Err(), IsNil)
  3558  	c.Check(change.Status(), Equals, state.DoneStatus)
  3559  
  3560  	c.Assert(s.secBackend.SetupCalls, HasLen, 2)
  3561  	c.Assert(s.secBackend.RemoveCalls, HasLen, 0)
  3562  	c.Check(s.secBackend.SetupCalls[0].SnapInfo.InstanceName(), Equals, "consumer")
  3563  	c.Check(s.secBackend.SetupCalls[1].SnapInfo.InstanceName(), Equals, "producer")
  3564  
  3565  	c.Check(s.secBackend.SetupCalls[0].Options, Equals, interfaces.ConfinementOptions{})
  3566  	c.Check(s.secBackend.SetupCalls[1].Options, Equals, interfaces.ConfinementOptions{})
  3567  }
  3568  
  3569  func (s *interfaceManagerSuite) TestDisconnectTracksConnectionsInState(c *C) {
  3570  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  3571  	s.mockSnap(c, consumerYaml)
  3572  	s.mockSnap(c, producerYaml)
  3573  	s.state.Lock()
  3574  	s.state.Set("conns", map[string]interface{}{
  3575  		"consumer:plug producer:slot": map[string]interface{}{"interface": "test"},
  3576  	})
  3577  	s.state.Unlock()
  3578  
  3579  	s.manager(c)
  3580  
  3581  	conn := s.getConnection(c, "consumer", "plug", "producer", "slot")
  3582  	s.state.Lock()
  3583  	ts, err := ifacestate.Disconnect(s.state, conn)
  3584  	c.Assert(err, IsNil)
  3585  	ts.Tasks()[0].Set("snap-setup", &snapstate.SnapSetup{
  3586  		SideInfo: &snap.SideInfo{
  3587  			RealName: "consumer",
  3588  		},
  3589  	})
  3590  
  3591  	change := s.state.NewChange("disconnect", "")
  3592  	change.AddAll(ts)
  3593  	s.state.Unlock()
  3594  
  3595  	s.settle(c)
  3596  
  3597  	s.state.Lock()
  3598  	defer s.state.Unlock()
  3599  
  3600  	c.Assert(change.Err(), IsNil)
  3601  	c.Check(change.Status(), Equals, state.DoneStatus)
  3602  	var conns map[string]interface{}
  3603  	err = s.state.Get("conns", &conns)
  3604  	c.Assert(err, IsNil)
  3605  	c.Check(conns, DeepEquals, map[string]interface{}{})
  3606  }
  3607  
  3608  func (s *interfaceManagerSuite) TestDisconnectDisablesAutoConnect(c *C) {
  3609  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  3610  	s.mockSnap(c, consumerYaml)
  3611  	s.mockSnap(c, producerYaml)
  3612  	s.state.Lock()
  3613  	s.state.Set("conns", map[string]interface{}{
  3614  		"consumer:plug producer:slot": map[string]interface{}{"interface": "test", "auto": true},
  3615  	})
  3616  	s.state.Unlock()
  3617  
  3618  	s.manager(c)
  3619  
  3620  	s.state.Lock()
  3621  	conn := &interfaces.Connection{
  3622  		Plug: interfaces.NewConnectedPlug(&snap.PlugInfo{Snap: &snap.Info{SuggestedName: "consumer"}, Name: "plug"}, nil, nil),
  3623  		Slot: interfaces.NewConnectedSlot(&snap.SlotInfo{Snap: &snap.Info{SuggestedName: "producer"}, Name: "slot"}, nil, nil),
  3624  	}
  3625  
  3626  	ts, err := ifacestate.Disconnect(s.state, conn)
  3627  	c.Assert(err, IsNil)
  3628  	ts.Tasks()[0].Set("snap-setup", &snapstate.SnapSetup{
  3629  		SideInfo: &snap.SideInfo{
  3630  			RealName: "consumer",
  3631  		},
  3632  	})
  3633  
  3634  	change := s.state.NewChange("disconnect", "")
  3635  	change.AddAll(ts)
  3636  	s.state.Unlock()
  3637  
  3638  	s.settle(c)
  3639  
  3640  	s.state.Lock()
  3641  	defer s.state.Unlock()
  3642  
  3643  	c.Assert(change.Err(), IsNil)
  3644  	c.Check(change.Status(), Equals, state.DoneStatus)
  3645  	var conns map[string]interface{}
  3646  	err = s.state.Get("conns", &conns)
  3647  	c.Assert(err, IsNil)
  3648  	c.Check(conns, DeepEquals, map[string]interface{}{
  3649  		"consumer:plug producer:slot": map[string]interface{}{"interface": "test", "auto": true, "undesired": true},
  3650  	})
  3651  }
  3652  
  3653  func (s *interfaceManagerSuite) TestDisconnectByHotplug(c *C) {
  3654  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
  3655  	var consumerYaml = `
  3656  name: consumer
  3657  version: 1
  3658  plugs:
  3659   plug:
  3660    interface: test
  3661    attr: plug-attr
  3662  `
  3663  	consumerInfo := s.mockSnap(c, consumerYaml)
  3664  	s.mockSnap(c, coreSnapYaml)
  3665  
  3666  	s.state.Lock()
  3667  	s.state.Set("conns", map[string]interface{}{
  3668  		"consumer:plug core:hotplug-slot": map[string]interface{}{"interface": "test"},
  3669  		"consumer:plug core:slot2":        map[string]interface{}{"interface": "test"},
  3670  	})
  3671  	s.state.Set("hotplug-slots", map[string]interface{}{
  3672  		"hotplug-slot": map[string]interface{}{
  3673  			"name":        "hotplug-slot",
  3674  			"interface":   "test",
  3675  			"hotplug-key": "1234",
  3676  		}})
  3677  	s.state.Unlock()
  3678  
  3679  	s.manager(c)
  3680  
  3681  	s.state.Lock()
  3682  	conn := &interfaces.Connection{
  3683  		Plug: interfaces.NewConnectedPlug(consumerInfo.Plugs["plug"], nil, nil),
  3684  		Slot: interfaces.NewConnectedSlot(&snap.SlotInfo{Snap: &snap.Info{SuggestedName: "core"}, Name: "hotplug-slot"}, nil, nil),
  3685  	}
  3686  
  3687  	ts, err := ifacestate.DisconnectPriv(s.state, conn, ifacestate.NewDisconnectOptsWithByHotplugSet())
  3688  	c.Assert(err, IsNil)
  3689  
  3690  	change := s.state.NewChange("disconnect", "")
  3691  	change.AddAll(ts)
  3692  	s.state.Unlock()
  3693  
  3694  	s.settle(c)
  3695  
  3696  	s.state.Lock()
  3697  	defer s.state.Unlock()
  3698  
  3699  	c.Assert(change.Err(), IsNil)
  3700  	c.Check(change.Status(), Equals, state.DoneStatus)
  3701  
  3702  	var conns map[string]interface{}
  3703  	err = s.state.Get("conns", &conns)
  3704  	c.Assert(err, IsNil)
  3705  	c.Check(conns, DeepEquals, map[string]interface{}{
  3706  		"consumer:plug core:hotplug-slot": map[string]interface{}{
  3707  			"interface":    "test",
  3708  			"hotplug-gone": true,
  3709  		},
  3710  		"consumer:plug core:slot2": map[string]interface{}{
  3711  			"interface": "test",
  3712  		},
  3713  	})
  3714  }
  3715  
  3716  func (s *interfaceManagerSuite) TestManagerReloadsConnections(c *C) {
  3717  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  3718  	var consumerYaml = `
  3719  name: consumer
  3720  version: 1
  3721  plugs:
  3722   plug:
  3723    interface: content
  3724    content: foo
  3725    attr: plug-value
  3726  `
  3727  	var producerYaml = `
  3728  name: producer
  3729  version: 1
  3730  slots:
  3731   slot:
  3732    interface: content
  3733    content: foo
  3734    attr: slot-value
  3735  `
  3736  	s.mockSnap(c, consumerYaml)
  3737  	s.mockSnap(c, producerYaml)
  3738  
  3739  	s.state.Lock()
  3740  	s.state.Set("conns", map[string]interface{}{
  3741  		"consumer:plug producer:slot": map[string]interface{}{
  3742  			"interface": "content",
  3743  			"plug-static": map[string]interface{}{
  3744  				"content":    "foo",
  3745  				"attr":       "stored-plug-value",
  3746  				"other-attr": "irrelevant-value",
  3747  			},
  3748  			"slot-static": map[string]interface{}{
  3749  				"interface":  "content",
  3750  				"content":    "foo",
  3751  				"attr":       "stored-slot-value",
  3752  				"other-attr": "irrelevant-value",
  3753  			},
  3754  		},
  3755  	})
  3756  	s.state.Unlock()
  3757  
  3758  	mgr := s.manager(c)
  3759  	repo := mgr.Repository()
  3760  
  3761  	ifaces := repo.Interfaces()
  3762  	c.Assert(ifaces.Connections, HasLen, 1)
  3763  	cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}}
  3764  	c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{cref})
  3765  
  3766  	conn, err := repo.Connection(cref)
  3767  	c.Assert(err, IsNil)
  3768  	c.Assert(conn.Plug.Name(), Equals, "plug")
  3769  	c.Assert(conn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{
  3770  		"content": "foo",
  3771  		"attr":    "plug-value",
  3772  	})
  3773  	c.Assert(conn.Slot.Name(), Equals, "slot")
  3774  	c.Assert(conn.Slot.StaticAttrs(), DeepEquals, map[string]interface{}{
  3775  		"content": "foo",
  3776  		"attr":    "slot-value",
  3777  	})
  3778  }
  3779  
  3780  func (s *interfaceManagerSuite) TestManagerDoesntReloadUndesiredAutoconnections(c *C) {
  3781  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
  3782  	s.mockSnap(c, consumerYaml)
  3783  	s.mockSnap(c, producerYaml)
  3784  
  3785  	s.state.Lock()
  3786  	s.state.Set("conns", map[string]interface{}{
  3787  		"consumer:plug producer:slot": map[string]interface{}{
  3788  			"interface": "test",
  3789  			"auto":      true,
  3790  			"undesired": true,
  3791  		},
  3792  	})
  3793  	s.state.Unlock()
  3794  
  3795  	mgr := s.manager(c)
  3796  	c.Assert(mgr.Repository().Interfaces().Connections, HasLen, 0)
  3797  }
  3798  
  3799  func (s *interfaceManagerSuite) setupHotplugSlot(c *C) {
  3800  	s.mockIfaces(c, &ifacetest.TestHotplugInterface{TestInterface: ifacetest.TestInterface{InterfaceName: "test"}})
  3801  	s.mockSnap(c, consumerYaml)
  3802  	s.mockSnap(c, coreSnapYaml)
  3803  
  3804  	s.state.Lock()
  3805  	defer s.state.Unlock()
  3806  
  3807  	s.state.Set("hotplug-slots", map[string]interface{}{
  3808  		"slot": map[string]interface{}{
  3809  			"name":        "slot",
  3810  			"interface":   "test",
  3811  			"hotplug-key": "abcd",
  3812  		}})
  3813  }
  3814  
  3815  func (s *interfaceManagerSuite) TestManagerDoesntReloadHotlugGoneConnection(c *C) {
  3816  	s.setupHotplugSlot(c)
  3817  
  3818  	s.state.Lock()
  3819  	s.state.Set("conns", map[string]interface{}{
  3820  		"consumer:plug core:slot": map[string]interface{}{
  3821  			"interface":    "test",
  3822  			"hotplug-gone": true,
  3823  		}})
  3824  	s.state.Unlock()
  3825  
  3826  	mgr := s.manager(c)
  3827  	c.Assert(mgr.Repository().Interfaces().Connections, HasLen, 0)
  3828  }
  3829  
  3830  func (s *interfaceManagerSuite) TestManagerReloadsHotlugConnection(c *C) {
  3831  	s.setupHotplugSlot(c)
  3832  
  3833  	s.state.Lock()
  3834  	s.state.Set("conns", map[string]interface{}{
  3835  		"consumer:plug core:slot": map[string]interface{}{
  3836  			"interface":    "test",
  3837  			"hotplug-gone": false,
  3838  		}})
  3839  	s.state.Unlock()
  3840  
  3841  	mgr := s.manager(c)
  3842  	repo := mgr.Repository()
  3843  	c.Assert(repo.Interfaces().Connections, HasLen, 1)
  3844  	cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "core", Name: "slot"}}
  3845  	conn, err := repo.Connection(cref)
  3846  	c.Assert(err, IsNil)
  3847  	c.Assert(conn, NotNil)
  3848  }
  3849  
  3850  func (s *interfaceManagerSuite) TestSetupProfilesDevModeMultiple(c *C) {
  3851  	s.MockModel(c, nil)
  3852  
  3853  	mgr := s.manager(c)
  3854  	repo := mgr.Repository()
  3855  
  3856  	// setup two snaps that are connected
  3857  	siP := s.mockSnap(c, producerYaml)
  3858  	siC := s.mockSnap(c, consumerYaml)
  3859  	err := repo.AddInterface(&ifacetest.TestInterface{
  3860  		InterfaceName: "test",
  3861  	})
  3862  	c.Assert(err, IsNil)
  3863  	err = repo.AddInterface(&ifacetest.TestInterface{
  3864  		InterfaceName: "test2",
  3865  	})
  3866  	c.Assert(err, IsNil)
  3867  
  3868  	err = repo.AddSlot(&snap.SlotInfo{
  3869  		Snap:      siC,
  3870  		Name:      "slot",
  3871  		Interface: "test",
  3872  	})
  3873  	c.Assert(err, IsNil)
  3874  	err = repo.AddPlug(&snap.PlugInfo{
  3875  		Snap:      siP,
  3876  		Name:      "plug",
  3877  		Interface: "test",
  3878  	})
  3879  	c.Assert(err, IsNil)
  3880  	connRef := &interfaces.ConnRef{
  3881  		PlugRef: interfaces.PlugRef{Snap: siP.InstanceName(), Name: "plug"},
  3882  		SlotRef: interfaces.SlotRef{Snap: siC.InstanceName(), Name: "slot"},
  3883  	}
  3884  	_, err = repo.Connect(connRef, nil, nil, nil, nil, nil)
  3885  	c.Assert(err, IsNil)
  3886  
  3887  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  3888  		SideInfo: &snap.SideInfo{
  3889  			RealName: siC.SnapName(),
  3890  			Revision: siC.Revision,
  3891  		},
  3892  		Flags: snapstate.Flags{DevMode: true},
  3893  	})
  3894  	s.settle(c)
  3895  
  3896  	s.state.Lock()
  3897  	defer s.state.Unlock()
  3898  
  3899  	// Ensure that the task succeeded.
  3900  	c.Check(change.Err(), IsNil)
  3901  	c.Check(change.Status(), Equals, state.DoneStatus)
  3902  
  3903  	// The first snap is setup in devmode, the second is not
  3904  	c.Assert(s.secBackend.SetupCalls, HasLen, 2)
  3905  	c.Assert(s.secBackend.RemoveCalls, HasLen, 0)
  3906  	c.Check(s.secBackend.SetupCalls[0].SnapInfo.InstanceName(), Equals, siC.InstanceName())
  3907  	c.Check(s.secBackend.SetupCalls[0].Options, Equals, interfaces.ConfinementOptions{DevMode: true})
  3908  	c.Check(s.secBackend.SetupCalls[1].SnapInfo.InstanceName(), Equals, siP.InstanceName())
  3909  	c.Check(s.secBackend.SetupCalls[1].Options, Equals, interfaces.ConfinementOptions{})
  3910  }
  3911  
  3912  func (s *interfaceManagerSuite) TestCheckInterfacesDeny(c *C) {
  3913  	deviceCtx := s.TrivialDeviceContext(c, nil)
  3914  
  3915  	restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
  3916  type: base-declaration
  3917  authority-id: canonical
  3918  series: 16
  3919  slots:
  3920    test:
  3921      deny-installation: true
  3922  `))
  3923  	defer restore()
  3924  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  3925  
  3926  	s.MockSnapDecl(c, "producer", "producer-publisher", nil)
  3927  	snapInfo := s.mockSnap(c, producerYaml)
  3928  
  3929  	s.state.Lock()
  3930  	defer s.state.Unlock()
  3931  	c.Check(ifacestate.CheckInterfaces(s.state, snapInfo, deviceCtx), ErrorMatches, "installation denied.*")
  3932  }
  3933  
  3934  func (s *interfaceManagerSuite) TestCheckInterfacesNoDenyIfNoDecl(c *C) {
  3935  	deviceCtx := s.TrivialDeviceContext(c, nil)
  3936  	restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
  3937  type: base-declaration
  3938  authority-id: canonical
  3939  series: 16
  3940  slots:
  3941    test:
  3942      deny-installation: true
  3943  `))
  3944  	defer restore()
  3945  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  3946  
  3947  	// crucially, this test is missing this: s.mockSnapDecl(c, "producer", "producer-publisher", nil)
  3948  	snapInfo := s.mockSnap(c, producerYaml)
  3949  
  3950  	s.state.Lock()
  3951  	defer s.state.Unlock()
  3952  	c.Check(ifacestate.CheckInterfaces(s.state, snapInfo, deviceCtx), IsNil)
  3953  }
  3954  
  3955  func (s *interfaceManagerSuite) TestCheckInterfacesDisallowBasedOnSnapTypeNoSnapDecl(c *C) {
  3956  	deviceCtx := s.TrivialDeviceContext(c, nil)
  3957  
  3958  	restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
  3959  type: base-declaration
  3960  authority-id: canonical
  3961  series: 16
  3962  slots:
  3963    test:
  3964      allow-installation:
  3965        slot-snap-type:
  3966          - core
  3967  `))
  3968  	defer restore()
  3969  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  3970  
  3971  	// no snap decl
  3972  	snapInfo := s.mockSnap(c, producerYaml)
  3973  
  3974  	s.state.Lock()
  3975  	defer s.state.Unlock()
  3976  	c.Check(ifacestate.CheckInterfaces(s.state, snapInfo, deviceCtx), ErrorMatches, `installation not allowed by "slot" slot rule of interface "test"`)
  3977  }
  3978  
  3979  func (s *interfaceManagerSuite) TestCheckInterfacesAllowBasedOnSnapTypeNoSnapDecl(c *C) {
  3980  	deviceCtx := s.TrivialDeviceContext(c, nil)
  3981  
  3982  	restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
  3983  type: base-declaration
  3984  authority-id: canonical
  3985  series: 16
  3986  slots:
  3987    test:
  3988      allow-installation:
  3989        slot-snap-type:
  3990          - app
  3991  `))
  3992  	defer restore()
  3993  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  3994  
  3995  	// no snap decl
  3996  	snapInfo := s.mockSnap(c, producerYaml)
  3997  
  3998  	s.state.Lock()
  3999  	defer s.state.Unlock()
  4000  	c.Check(ifacestate.CheckInterfaces(s.state, snapInfo, deviceCtx), IsNil)
  4001  }
  4002  
  4003  func (s *interfaceManagerSuite) TestCheckInterfacesAllow(c *C) {
  4004  	deviceCtx := s.TrivialDeviceContext(c, nil)
  4005  
  4006  	restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
  4007  type: base-declaration
  4008  authority-id: canonical
  4009  series: 16
  4010  slots:
  4011    test:
  4012      deny-installation: true
  4013  `))
  4014  	defer restore()
  4015  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  4016  
  4017  	s.MockSnapDecl(c, "producer", "producer-publisher", map[string]interface{}{
  4018  		"format": "1",
  4019  		"slots": map[string]interface{}{
  4020  			"test": "true",
  4021  		},
  4022  	})
  4023  	snapInfo := s.mockSnap(c, producerYaml)
  4024  
  4025  	s.state.Lock()
  4026  	defer s.state.Unlock()
  4027  	c.Check(ifacestate.CheckInterfaces(s.state, snapInfo, deviceCtx), IsNil)
  4028  }
  4029  
  4030  func (s *interfaceManagerSuite) TestCheckInterfacesDeviceScopeRightStore(c *C) {
  4031  	deviceCtx := s.TrivialDeviceContext(c, map[string]interface{}{
  4032  		"store": "my-store",
  4033  	})
  4034  
  4035  	restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
  4036  type: base-declaration
  4037  authority-id: canonical
  4038  series: 16
  4039  slots:
  4040    test:
  4041      deny-installation: true
  4042  `))
  4043  	defer restore()
  4044  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  4045  
  4046  	s.MockSnapDecl(c, "producer", "producer-publisher", map[string]interface{}{
  4047  		"format": "3",
  4048  		"slots": map[string]interface{}{
  4049  			"test": map[string]interface{}{
  4050  				"allow-installation": map[string]interface{}{
  4051  					"on-store": []interface{}{"my-store"},
  4052  				},
  4053  			},
  4054  		},
  4055  	})
  4056  	snapInfo := s.mockSnap(c, producerYaml)
  4057  
  4058  	s.state.Lock()
  4059  	defer s.state.Unlock()
  4060  	c.Check(ifacestate.CheckInterfaces(s.state, snapInfo, deviceCtx), IsNil)
  4061  }
  4062  
  4063  func (s *interfaceManagerSuite) TestCheckInterfacesDeviceScopeNoStore(c *C) {
  4064  	deviceCtx := s.TrivialDeviceContext(c, nil)
  4065  
  4066  	restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
  4067  type: base-declaration
  4068  authority-id: canonical
  4069  series: 16
  4070  slots:
  4071    test:
  4072      deny-installation: true
  4073  `))
  4074  	defer restore()
  4075  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  4076  
  4077  	s.MockSnapDecl(c, "producer", "producer-publisher", map[string]interface{}{
  4078  		"format": "3",
  4079  		"slots": map[string]interface{}{
  4080  			"test": map[string]interface{}{
  4081  				"allow-installation": map[string]interface{}{
  4082  					"on-store": []interface{}{"my-store"},
  4083  				},
  4084  			},
  4085  		},
  4086  	})
  4087  	snapInfo := s.mockSnap(c, producerYaml)
  4088  
  4089  	s.state.Lock()
  4090  	defer s.state.Unlock()
  4091  	c.Check(ifacestate.CheckInterfaces(s.state, snapInfo, deviceCtx), ErrorMatches, `installation not allowed.*`)
  4092  }
  4093  
  4094  func (s *interfaceManagerSuite) TestCheckInterfacesDeviceScopeWrongStore(c *C) {
  4095  	deviceCtx := s.TrivialDeviceContext(c, map[string]interface{}{
  4096  		"store": "other-store",
  4097  	})
  4098  
  4099  	restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
  4100  type: base-declaration
  4101  authority-id: canonical
  4102  series: 16
  4103  slots:
  4104    test:
  4105      deny-installation: true
  4106  `))
  4107  	defer restore()
  4108  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  4109  
  4110  	s.MockSnapDecl(c, "producer", "producer-publisher", map[string]interface{}{
  4111  		"format": "3",
  4112  		"slots": map[string]interface{}{
  4113  			"test": map[string]interface{}{
  4114  				"allow-installation": map[string]interface{}{
  4115  					"on-store": []interface{}{"my-store"},
  4116  				},
  4117  			},
  4118  		},
  4119  	})
  4120  	snapInfo := s.mockSnap(c, producerYaml)
  4121  
  4122  	s.state.Lock()
  4123  	defer s.state.Unlock()
  4124  	c.Check(ifacestate.CheckInterfaces(s.state, snapInfo, deviceCtx), ErrorMatches, `installation not allowed.*`)
  4125  }
  4126  
  4127  func (s *interfaceManagerSuite) TestCheckInterfacesDeviceScopeRightFriendlyStore(c *C) {
  4128  	deviceCtx := s.TrivialDeviceContext(c, map[string]interface{}{
  4129  		"store": "my-substore",
  4130  	})
  4131  
  4132  	s.MockStore(c, s.state, "my-substore", map[string]interface{}{
  4133  		"friendly-stores": []interface{}{"my-store"},
  4134  	})
  4135  
  4136  	restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
  4137  type: base-declaration
  4138  authority-id: canonical
  4139  series: 16
  4140  slots:
  4141    test:
  4142      deny-installation: true
  4143  `))
  4144  	defer restore()
  4145  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  4146  
  4147  	s.MockSnapDecl(c, "producer", "producer-publisher", map[string]interface{}{
  4148  		"format": "3",
  4149  		"slots": map[string]interface{}{
  4150  			"test": map[string]interface{}{
  4151  				"allow-installation": map[string]interface{}{
  4152  					"on-store": []interface{}{"my-store"},
  4153  				},
  4154  			},
  4155  		},
  4156  	})
  4157  	snapInfo := s.mockSnap(c, producerYaml)
  4158  
  4159  	s.state.Lock()
  4160  	defer s.state.Unlock()
  4161  	c.Check(ifacestate.CheckInterfaces(s.state, snapInfo, deviceCtx), IsNil)
  4162  }
  4163  
  4164  func (s *interfaceManagerSuite) TestCheckInterfacesDeviceScopeWrongFriendlyStore(c *C) {
  4165  	deviceCtx := s.TrivialDeviceContext(c, map[string]interface{}{
  4166  		"store": "my-substore",
  4167  	})
  4168  
  4169  	s.MockStore(c, s.state, "my-substore", map[string]interface{}{
  4170  		"friendly-stores": []interface{}{"other-store"},
  4171  	})
  4172  
  4173  	restore := assertstest.MockBuiltinBaseDeclaration([]byte(`
  4174  type: base-declaration
  4175  authority-id: canonical
  4176  series: 16
  4177  slots:
  4178    test:
  4179      deny-installation: true
  4180  `))
  4181  	defer restore()
  4182  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  4183  
  4184  	s.MockSnapDecl(c, "producer", "producer-publisher", map[string]interface{}{
  4185  		"format": "3",
  4186  		"slots": map[string]interface{}{
  4187  			"test": map[string]interface{}{
  4188  				"allow-installation": map[string]interface{}{
  4189  					"on-store": []interface{}{"my-store"},
  4190  				},
  4191  			},
  4192  		},
  4193  	})
  4194  	snapInfo := s.mockSnap(c, producerYaml)
  4195  
  4196  	s.state.Lock()
  4197  	defer s.state.Unlock()
  4198  	c.Check(ifacestate.CheckInterfaces(s.state, snapInfo, deviceCtx), ErrorMatches, `installation not allowed.*`)
  4199  }
  4200  
  4201  func (s *interfaceManagerSuite) TestCheckInterfacesConsidersImplicitSlots(c *C) {
  4202  	deviceCtx := s.TrivialDeviceContext(c, nil)
  4203  	snapInfo := s.mockSnap(c, ubuntuCoreSnapYaml)
  4204  
  4205  	s.state.Lock()
  4206  	defer s.state.Unlock()
  4207  	c.Check(ifacestate.CheckInterfaces(s.state, snapInfo, deviceCtx), IsNil)
  4208  	c.Check(snapInfo.Slots["home"], NotNil)
  4209  }
  4210  
  4211  // Test that setup-snap-security gets undone correctly when a snap is installed
  4212  // but the installation fails (the security profiles are removed).
  4213  func (s *interfaceManagerSuite) TestUndoSetupProfilesOnInstall(c *C) {
  4214  	// Create the interface manager
  4215  	_ = s.manager(c)
  4216  
  4217  	// Mock a snap and remove the side info from the state (it is implicitly
  4218  	// added by mockSnap) so that we can emulate a undo during a fresh
  4219  	// install.
  4220  	snapInfo := s.mockSnap(c, sampleSnapYaml)
  4221  	s.state.Lock()
  4222  	snapstate.Set(s.state, snapInfo.InstanceName(), nil)
  4223  	s.state.Unlock()
  4224  
  4225  	// Add a change that undoes "setup-snap-security"
  4226  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  4227  		SideInfo: &snap.SideInfo{
  4228  			RealName: snapInfo.SnapName(),
  4229  			Revision: snapInfo.Revision,
  4230  		},
  4231  	})
  4232  	s.state.Lock()
  4233  	c.Assert(change.Tasks(), HasLen, 2)
  4234  	change.Tasks()[0].SetStatus(state.UndoStatus)
  4235  	change.Tasks()[1].SetStatus(state.UndoneStatus)
  4236  	s.state.Unlock()
  4237  
  4238  	// Turn the crank
  4239  	s.settle(c)
  4240  
  4241  	s.state.Lock()
  4242  	defer s.state.Unlock()
  4243  
  4244  	// Ensure that the change got undone.
  4245  	c.Assert(change.Err(), IsNil)
  4246  	c.Check(change.Status(), Equals, state.UndoneStatus)
  4247  
  4248  	// Ensure that since we had no prior revisions of this snap installed the
  4249  	// undo task removed the security profile from the system.
  4250  	c.Assert(s.secBackend.SetupCalls, HasLen, 0)
  4251  	c.Assert(s.secBackend.RemoveCalls, HasLen, 1)
  4252  	c.Check(s.secBackend.RemoveCalls, DeepEquals, []string{snapInfo.InstanceName()})
  4253  }
  4254  
  4255  // Test that setup-snap-security gets undone correctly when a snap is refreshed
  4256  // but the installation fails (the security profiles are restored to the old state).
  4257  func (s *interfaceManagerSuite) TestUndoSetupProfilesOnRefresh(c *C) {
  4258  	// Create the interface manager
  4259  	_ = s.manager(c)
  4260  
  4261  	// Mock a snap. The mockSnap call below also puts the side info into the
  4262  	// state so it seems like it was installed already.
  4263  	snapInfo := s.mockSnap(c, sampleSnapYaml)
  4264  
  4265  	// Add a change that undoes "setup-snap-security"
  4266  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  4267  		SideInfo: &snap.SideInfo{
  4268  			RealName: snapInfo.SnapName(),
  4269  			Revision: snapInfo.Revision,
  4270  		},
  4271  	})
  4272  	s.state.Lock()
  4273  	c.Assert(change.Tasks(), HasLen, 2)
  4274  	change.Tasks()[1].SetStatus(state.UndoStatus)
  4275  	s.state.Unlock()
  4276  
  4277  	// Turn the crank
  4278  	s.settle(c)
  4279  
  4280  	s.state.Lock()
  4281  	defer s.state.Unlock()
  4282  
  4283  	// Ensure that the change got undone.
  4284  	c.Assert(change.Err(), IsNil)
  4285  	c.Check(change.Status(), Equals, state.UndoneStatus)
  4286  
  4287  	// Ensure that since had a revision in the state the undo task actually
  4288  	// setup the security of the snap we had in the state.
  4289  	c.Assert(s.secBackend.SetupCalls, HasLen, 1)
  4290  	c.Assert(s.secBackend.RemoveCalls, HasLen, 0)
  4291  	c.Check(s.secBackend.SetupCalls[0].SnapInfo.InstanceName(), Equals, snapInfo.InstanceName())
  4292  	c.Check(s.secBackend.SetupCalls[0].SnapInfo.Revision, Equals, snapInfo.Revision)
  4293  	c.Check(s.secBackend.SetupCalls[0].Options, Equals, interfaces.ConfinementOptions{})
  4294  }
  4295  
  4296  func (s *interfaceManagerSuite) TestManagerTransitionConnectionsCore(c *C) {
  4297  	s.mockSnap(c, ubuntuCoreSnapYaml)
  4298  	s.mockSnap(c, coreSnapYaml)
  4299  	s.mockSnap(c, httpdSnapYaml)
  4300  
  4301  	s.manager(c)
  4302  
  4303  	s.state.Lock()
  4304  	defer s.state.Unlock()
  4305  	s.state.Set("conns", map[string]interface{}{
  4306  		"httpd:network ubuntu-core:network": map[string]interface{}{
  4307  			"interface": "network", "auto": true,
  4308  		},
  4309  	})
  4310  
  4311  	task := s.state.NewTask("transition-ubuntu-core", "...")
  4312  	task.Set("old-name", "ubuntu-core")
  4313  	task.Set("new-name", "core")
  4314  	change := s.state.NewChange("test-migrate", "")
  4315  	change.AddTask(task)
  4316  
  4317  	s.state.Unlock()
  4318  	s.se.Ensure()
  4319  	s.se.Wait()
  4320  	s.se.Stop()
  4321  	s.state.Lock()
  4322  
  4323  	c.Assert(change.Status(), Equals, state.DoneStatus)
  4324  	var conns map[string]interface{}
  4325  	err := s.state.Get("conns", &conns)
  4326  	c.Assert(err, IsNil)
  4327  	// ensure the connection went from "ubuntu-core" to "core"
  4328  	c.Check(conns, DeepEquals, map[string]interface{}{
  4329  		"httpd:network core:network": map[string]interface{}{
  4330  			"interface": "network", "auto": true,
  4331  		},
  4332  	})
  4333  }
  4334  
  4335  func (s *interfaceManagerSuite) TestManagerTransitionConnectionsCoreUndo(c *C) {
  4336  	s.mockSnap(c, ubuntuCoreSnapYaml)
  4337  	s.mockSnap(c, coreSnapYaml)
  4338  	s.mockSnap(c, httpdSnapYaml)
  4339  
  4340  	s.manager(c)
  4341  
  4342  	s.state.Lock()
  4343  	defer s.state.Unlock()
  4344  	s.state.Set("conns", map[string]interface{}{
  4345  		"httpd:network ubuntu-core:network": map[string]interface{}{
  4346  			"interface": "network", "auto": true,
  4347  		},
  4348  	})
  4349  
  4350  	t := s.state.NewTask("transition-ubuntu-core", "...")
  4351  	t.Set("old-name", "ubuntu-core")
  4352  	t.Set("new-name", "core")
  4353  	change := s.state.NewChange("test-migrate", "")
  4354  	change.AddTask(t)
  4355  	terr := s.state.NewTask("error-trigger", "provoking total undo")
  4356  	terr.WaitFor(t)
  4357  	change.AddTask(terr)
  4358  
  4359  	s.state.Unlock()
  4360  	for i := 0; i < 10; i++ {
  4361  		s.se.Ensure()
  4362  		s.se.Wait()
  4363  	}
  4364  	s.se.Stop()
  4365  	s.state.Lock()
  4366  
  4367  	c.Assert(change.Status(), Equals, state.ErrorStatus)
  4368  	c.Check(t.Status(), Equals, state.UndoneStatus)
  4369  
  4370  	var conns map[string]interface{}
  4371  	err := s.state.Get("conns", &conns)
  4372  	c.Assert(err, IsNil)
  4373  	// ensure the connection have not changed (still ubuntu-core)
  4374  	c.Check(conns, DeepEquals, map[string]interface{}{
  4375  		"httpd:network ubuntu-core:network": map[string]interface{}{
  4376  			"interface": "network", "auto": true,
  4377  		},
  4378  	})
  4379  }
  4380  
  4381  // Test "core-support" connections that loop back to core is
  4382  // renamed to match the rename of the plug.
  4383  func (s *interfaceManagerSuite) TestCoreConnectionsRenamed(c *C) {
  4384  	// Put state with old connection data.
  4385  	s.state.Lock()
  4386  	s.state.Set("conns", map[string]interface{}{
  4387  		"core:core-support core:core-support": map[string]interface{}{
  4388  			"interface": "core-support", "auto": true,
  4389  		},
  4390  		"snap:unrelated core:unrelated": map[string]interface{}{
  4391  			"interface": "unrelated", "auto": true,
  4392  		},
  4393  	})
  4394  	s.state.Unlock()
  4395  
  4396  	// mock both snaps, otherwise the manager will remove stale connections
  4397  	s.mockSnap(c, coreSnapYaml)
  4398  	s.mockSnap(c, sampleSnapYaml)
  4399  
  4400  	// Start the manager, this is where renames happen.
  4401  	s.manager(c)
  4402  
  4403  	// Check that "core-support" connection got renamed.
  4404  	s.state.Lock()
  4405  	var conns map[string]interface{}
  4406  	err := s.state.Get("conns", &conns)
  4407  	s.state.Unlock()
  4408  	c.Assert(err, IsNil)
  4409  	c.Assert(conns, DeepEquals, map[string]interface{}{
  4410  		"core:core-support-plug core:core-support": map[string]interface{}{
  4411  			"interface": "core-support", "auto": true,
  4412  		},
  4413  		"snap:unrelated core:unrelated": map[string]interface{}{
  4414  			"interface": "unrelated", "auto": true,
  4415  		},
  4416  	})
  4417  }
  4418  
  4419  // Test that "network-bind" and "core-support" plugs are renamed to
  4420  // "network-bind-plug" and "core-support-plug" in order not to clash with slots
  4421  // with the same names.
  4422  func (s *interfaceManagerSuite) TestAutomaticCorePlugsRenamed(c *C) {
  4423  	s.mockSnap(c, coreSnapYaml+`
  4424  plugs:
  4425    network-bind:
  4426    core-support:
  4427  `)
  4428  	mgr := s.manager(c)
  4429  
  4430  	// old plugs are gone
  4431  	c.Assert(mgr.Repository().Plug("core", "network-bind"), IsNil)
  4432  	c.Assert(mgr.Repository().Plug("core", "core-support"), IsNil)
  4433  	// new plugs are present
  4434  	c.Assert(mgr.Repository().Plug("core", "network-bind-plug"), Not(IsNil))
  4435  	c.Assert(mgr.Repository().Plug("core", "core-support-plug"), Not(IsNil))
  4436  	// slots are present and unchanged
  4437  	c.Assert(mgr.Repository().Slot("core", "network-bind"), Not(IsNil))
  4438  	c.Assert(mgr.Repository().Slot("core", "core-support"), Not(IsNil))
  4439  }
  4440  
  4441  func (s *interfaceManagerSuite) TestAutoConnectDuringCoreTransition(c *C) {
  4442  	s.MockModel(c, nil)
  4443  
  4444  	// Add both the old and new core snaps
  4445  	s.mockSnap(c, ubuntuCoreSnapYaml)
  4446  	s.mockSnap(c, coreSnapYaml)
  4447  
  4448  	// Initialize the manager. This registers both of the core snaps.
  4449  	mgr := s.manager(c)
  4450  
  4451  	// Add a sample snap with a "network" plug which should be auto-connected.
  4452  	// Normally it would not be auto connected because there are multiple
  4453  	// provides but we have special support for this case so the old
  4454  	// ubuntu-core snap is ignored and we pick the new core snap.
  4455  	snapInfo := s.mockSnap(c, sampleSnapYaml)
  4456  
  4457  	// Run the setup-snap-security task and let it finish.
  4458  	change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
  4459  		SideInfo: &snap.SideInfo{
  4460  			RealName: snapInfo.SnapName(),
  4461  			Revision: snapInfo.Revision,
  4462  		},
  4463  	})
  4464  
  4465  	s.settle(c)
  4466  
  4467  	s.state.Lock()
  4468  	defer s.state.Unlock()
  4469  
  4470  	// Ensure that the task succeeded.
  4471  	c.Assert(change.Status(), Equals, state.DoneStatus)
  4472  
  4473  	// Ensure that "network" is now saved in the state as auto-connected and
  4474  	// that it is connected to the new core snap rather than the old
  4475  	// ubuntu-core snap.
  4476  	var conns map[string]interface{}
  4477  	err := s.state.Get("conns", &conns)
  4478  	c.Assert(err, IsNil)
  4479  	c.Check(conns, DeepEquals, map[string]interface{}{
  4480  		"snap:network core:network": map[string]interface{}{
  4481  			"interface": "network", "auto": true,
  4482  		},
  4483  	})
  4484  
  4485  	// Ensure that "network" is really connected.
  4486  	repo := mgr.Repository()
  4487  	plug := repo.Plug("snap", "network")
  4488  	c.Assert(plug, Not(IsNil))
  4489  	ifaces := repo.Interfaces()
  4490  	c.Assert(ifaces.Connections, HasLen, 1)
  4491  	c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{
  4492  		PlugRef: interfaces.PlugRef{Snap: "snap", Name: "network"},
  4493  		SlotRef: interfaces.SlotRef{Snap: "core", Name: "network"}}})
  4494  }
  4495  
  4496  func makeAutoConnectChange(st *state.State, plugSnap, plug, slotSnap, slot string) *state.Change {
  4497  	chg := st.NewChange("connect...", "...")
  4498  
  4499  	t := st.NewTask("connect", "other connect task")
  4500  	t.Set("slot", interfaces.SlotRef{Snap: slotSnap, Name: slot})
  4501  	t.Set("plug", interfaces.PlugRef{Snap: plugSnap, Name: plug})
  4502  	var plugAttrs, slotAttrs map[string]interface{}
  4503  	t.Set("plug-dynamic", plugAttrs)
  4504  	t.Set("slot-dynamic", slotAttrs)
  4505  	t.Set("auto", true)
  4506  
  4507  	// two fake tasks for connect-plug-/slot- hooks
  4508  	hs1 := hookstate.HookSetup{
  4509  		Snap:     slotSnap,
  4510  		Optional: true,
  4511  		Hook:     "connect-slot-" + slot,
  4512  	}
  4513  	ht1 := hookstate.HookTask(st, "connect-slot hook", &hs1, nil)
  4514  	ht1.WaitFor(t)
  4515  	hs2 := hookstate.HookSetup{
  4516  		Snap:     plugSnap,
  4517  		Optional: true,
  4518  		Hook:     "connect-plug-" + plug,
  4519  	}
  4520  	ht2 := hookstate.HookTask(st, "connect-plug hook", &hs2, nil)
  4521  	ht2.WaitFor(ht1)
  4522  
  4523  	chg.AddTask(t)
  4524  	chg.AddTask(ht1)
  4525  	chg.AddTask(ht2)
  4526  
  4527  	return chg
  4528  }
  4529  
  4530  func (s *interfaceManagerSuite) TestUndoConnect(c *C) {
  4531  	s.MockModel(c, nil)
  4532  
  4533  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
  4534  	s.manager(c)
  4535  	producer := s.mockSnap(c, producerYaml)
  4536  	consumer := s.mockSnap(c, consumerYaml)
  4537  
  4538  	repo := s.manager(c).Repository()
  4539  	err := repo.AddPlug(&snap.PlugInfo{
  4540  		Snap:      consumer,
  4541  		Name:      "plug",
  4542  		Interface: "test",
  4543  	})
  4544  	c.Assert(err, IsNil)
  4545  	err = repo.AddSlot(&snap.SlotInfo{
  4546  		Snap:      producer,
  4547  		Name:      "slot",
  4548  		Interface: "test",
  4549  	})
  4550  	c.Assert(err, IsNil)
  4551  
  4552  	s.state.Lock()
  4553  
  4554  	// "consumer:plug producer:slot" wouldn't normally be present in conns when connecting because
  4555  	// ifacestate.Connect() checks for existing connection; it's used here to test removal on undo.
  4556  	s.state.Set("conns", map[string]interface{}{
  4557  		"snap1:plug snap2:slot":       map[string]interface{}{},
  4558  		"consumer:plug producer:slot": map[string]interface{}{},
  4559  	})
  4560  
  4561  	chg := makeAutoConnectChange(s.state, "consumer", "plug", "producer", "slot")
  4562  	terr := s.state.NewTask("error-trigger", "provoking undo")
  4563  	connTasks := chg.Tasks()
  4564  	terr.WaitAll(state.NewTaskSet(connTasks...))
  4565  	chg.AddTask(terr)
  4566  
  4567  	s.state.Unlock()
  4568  	s.settle(c)
  4569  	s.state.Lock()
  4570  	defer s.state.Unlock()
  4571  
  4572  	c.Assert(chg.Status().Ready(), Equals, true)
  4573  	for _, t := range connTasks {
  4574  		c.Assert(t.Status(), Equals, state.UndoneStatus)
  4575  	}
  4576  
  4577  	// connection is removed from conns, other connection is left intact
  4578  	var conns map[string]interface{}
  4579  	c.Assert(s.state.Get("conns", &conns), IsNil)
  4580  	c.Check(conns, DeepEquals, map[string]interface{}{
  4581  		"snap1:plug snap2:slot": map[string]interface{}{},
  4582  	})
  4583  }
  4584  
  4585  func (s *interfaceManagerSuite) TestConnectErrorMissingSlotSnapOnAutoConnect(c *C) {
  4586  	s.MockModel(c, nil)
  4587  
  4588  	_ = s.manager(c)
  4589  	s.mockSnap(c, producerYaml)
  4590  	s.mockSnap(c, consumerYaml)
  4591  
  4592  	s.state.Lock()
  4593  
  4594  	chg := makeAutoConnectChange(s.state, "consumer", "plug", "producer", "slot")
  4595  	// remove producer snap from the state, doConnect should complain
  4596  	snapstate.Set(s.state, "producer", nil)
  4597  
  4598  	s.state.Unlock()
  4599  
  4600  	s.settle(c)
  4601  
  4602  	s.state.Lock()
  4603  	defer s.state.Unlock()
  4604  
  4605  	c.Check(chg.Status(), Equals, state.ErrorStatus)
  4606  	c.Assert(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*snap "producer" is no longer available for auto-connecting.*`)
  4607  
  4608  	var conns map[string]interface{}
  4609  	c.Assert(s.state.Get("conns", &conns), Equals, state.ErrNoState)
  4610  }
  4611  
  4612  func (s *interfaceManagerSuite) TestConnectErrorMissingPlugSnapOnAutoConnect(c *C) {
  4613  	s.MockModel(c, nil)
  4614  
  4615  	_ = s.manager(c)
  4616  	s.mockSnap(c, producerYaml)
  4617  	s.mockSnap(c, consumerYaml)
  4618  
  4619  	s.state.Lock()
  4620  	chg := makeAutoConnectChange(s.state, "consumer", "plug", "producer", "slot")
  4621  	// remove consumer snap from the state, doConnect should complain
  4622  	snapstate.Set(s.state, "consumer", nil)
  4623  
  4624  	s.state.Unlock()
  4625  
  4626  	s.settle(c)
  4627  
  4628  	s.state.Lock()
  4629  	defer s.state.Unlock()
  4630  
  4631  	c.Assert(chg.Status(), Equals, state.ErrorStatus)
  4632  	c.Assert(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*snap "consumer" is no longer available for auto-connecting.*`)
  4633  
  4634  	var conns map[string]interface{}
  4635  	c.Assert(s.state.Get("conns", &conns), Equals, state.ErrNoState)
  4636  }
  4637  
  4638  func (s *interfaceManagerSuite) TestConnectErrorMissingPlugOnAutoConnect(c *C) {
  4639  	s.MockModel(c, nil)
  4640  
  4641  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
  4642  	_ = s.manager(c)
  4643  	producer := s.mockSnap(c, producerYaml)
  4644  	// consumer snap has no plug, doConnect should complain
  4645  	s.mockSnap(c, consumerYaml)
  4646  
  4647  	repo := s.manager(c).Repository()
  4648  	err := repo.AddSlot(&snap.SlotInfo{
  4649  		Snap:      producer,
  4650  		Name:      "slot",
  4651  		Interface: "test",
  4652  	})
  4653  	c.Assert(err, IsNil)
  4654  
  4655  	s.state.Lock()
  4656  
  4657  	chg := makeAutoConnectChange(s.state, "consumer", "plug", "producer", "slot")
  4658  	s.state.Unlock()
  4659  
  4660  	s.settle(c)
  4661  
  4662  	s.state.Lock()
  4663  	defer s.state.Unlock()
  4664  
  4665  	c.Assert(chg.Status(), Equals, state.ErrorStatus)
  4666  	c.Assert(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*snap "consumer" has no "plug" plug.*`)
  4667  
  4668  	var conns map[string]interface{}
  4669  	err = s.state.Get("conns", &conns)
  4670  	c.Assert(err, Equals, state.ErrNoState)
  4671  }
  4672  
  4673  func (s *interfaceManagerSuite) TestConnectErrorMissingSlotOnAutoConnect(c *C) {
  4674  	s.MockModel(c, nil)
  4675  
  4676  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
  4677  	_ = s.manager(c)
  4678  	// producer snap has no slot, doConnect should complain
  4679  	s.mockSnap(c, producerYaml)
  4680  	consumer := s.mockSnap(c, consumerYaml)
  4681  
  4682  	repo := s.manager(c).Repository()
  4683  	err := repo.AddPlug(&snap.PlugInfo{
  4684  		Snap:      consumer,
  4685  		Name:      "plug",
  4686  		Interface: "test",
  4687  	})
  4688  	c.Assert(err, IsNil)
  4689  
  4690  	s.state.Lock()
  4691  
  4692  	chg := makeAutoConnectChange(s.state, "consumer", "plug", "producer", "slot")
  4693  	s.state.Unlock()
  4694  
  4695  	s.settle(c)
  4696  
  4697  	s.state.Lock()
  4698  	defer s.state.Unlock()
  4699  
  4700  	c.Assert(chg.Status(), Equals, state.ErrorStatus)
  4701  	c.Assert(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*snap "producer" has no "slot" slot.*`)
  4702  
  4703  	var conns map[string]interface{}
  4704  	err = s.state.Get("conns", &conns)
  4705  	c.Assert(err, Equals, state.ErrNoState)
  4706  }
  4707  
  4708  func (s *interfaceManagerSuite) TestConnectHandlesAutoconnect(c *C) {
  4709  	s.MockModel(c, nil)
  4710  
  4711  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
  4712  	_ = s.manager(c)
  4713  	producer := s.mockSnap(c, producerYaml)
  4714  	consumer := s.mockSnap(c, consumerYaml)
  4715  
  4716  	repo := s.manager(c).Repository()
  4717  	err := repo.AddPlug(&snap.PlugInfo{
  4718  		Snap:      consumer,
  4719  		Name:      "plug",
  4720  		Interface: "test",
  4721  	})
  4722  	c.Assert(err, IsNil)
  4723  	err = repo.AddSlot(&snap.SlotInfo{
  4724  		Snap:      producer,
  4725  		Name:      "slot",
  4726  		Interface: "test",
  4727  	})
  4728  	c.Assert(err, IsNil)
  4729  
  4730  	s.state.Lock()
  4731  
  4732  	chg := makeAutoConnectChange(s.state, "consumer", "plug", "producer", "slot")
  4733  	s.state.Unlock()
  4734  
  4735  	s.settle(c)
  4736  
  4737  	s.state.Lock()
  4738  	defer s.state.Unlock()
  4739  
  4740  	task := chg.Tasks()[0]
  4741  	c.Assert(task.Status(), Equals, state.DoneStatus)
  4742  
  4743  	// Ensure that "slot" is now auto-connected.
  4744  	var conns map[string]interface{}
  4745  	err = s.state.Get("conns", &conns)
  4746  	c.Assert(err, IsNil)
  4747  	c.Check(conns, DeepEquals, map[string]interface{}{
  4748  		"consumer:plug producer:slot": map[string]interface{}{
  4749  			"interface": "test", "auto": true,
  4750  		},
  4751  	})
  4752  }
  4753  
  4754  func (s *interfaceManagerSuite) TestRegenerateAllSecurityProfilesWritesSystemKeyFile(c *C) {
  4755  	restore := interfaces.MockSystemKey(`{"core": "123"}`)
  4756  	defer restore()
  4757  
  4758  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  4759  	s.mockSnap(c, consumerYaml)
  4760  	c.Assert(osutil.FileExists(dirs.SnapSystemKeyFile), Equals, false)
  4761  
  4762  	_ = s.manager(c)
  4763  	c.Check(dirs.SnapSystemKeyFile, testutil.FileMatches, `{.*"build-id":.*`)
  4764  
  4765  	stat, err := os.Stat(dirs.SnapSystemKeyFile)
  4766  	c.Assert(err, IsNil)
  4767  
  4768  	// run manager again, but this time the snapsystemkey file should
  4769  	// not be rewriten as the systemKey inputs have not changed
  4770  	time.Sleep(20 * time.Millisecond)
  4771  	s.privateMgr = nil
  4772  	_ = s.manager(c)
  4773  	stat2, err := os.Stat(dirs.SnapSystemKeyFile)
  4774  	c.Assert(err, IsNil)
  4775  	c.Check(stat.ModTime(), DeepEquals, stat2.ModTime())
  4776  }
  4777  
  4778  func (s *interfaceManagerSuite) TestStartupTimings(c *C) {
  4779  	restore := interfaces.MockSystemKey(`{"core": "123"}`)
  4780  	defer restore()
  4781  
  4782  	s.extraBackends = []interfaces.SecurityBackend{&ifacetest.TestSecurityBackend{BackendName: "fake"}}
  4783  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  4784  	s.mockSnap(c, consumerYaml)
  4785  
  4786  	oldDurationThreshold := timings.DurationThreshold
  4787  	defer func() {
  4788  		timings.DurationThreshold = oldDurationThreshold
  4789  	}()
  4790  	timings.DurationThreshold = 0
  4791  
  4792  	_ = s.manager(c)
  4793  
  4794  	s.state.Lock()
  4795  	defer s.state.Unlock()
  4796  
  4797  	var allTimings []map[string]interface{}
  4798  	c.Assert(s.state.Get("timings", &allTimings), IsNil)
  4799  	c.Check(allTimings, HasLen, 1)
  4800  
  4801  	timings, ok := allTimings[0]["timings"]
  4802  	c.Assert(ok, Equals, true)
  4803  
  4804  	// one backed expected; the other fake backend from test setup doesn't have a name and is ignored by regenerateAllSecurityProfiles
  4805  	c.Assert(timings, HasLen, 1)
  4806  	timingsList, ok := timings.([]interface{})
  4807  	c.Assert(ok, Equals, true)
  4808  	tm := timingsList[0].(map[string]interface{})
  4809  	c.Check(tm["label"], Equals, "setup-security-backend")
  4810  	c.Check(tm["summary"], Matches, `setup security backend "fake" for snap "consumer"`)
  4811  
  4812  	tags, ok := allTimings[0]["tags"]
  4813  	c.Assert(ok, Equals, true)
  4814  	c.Check(tags, DeepEquals, map[string]interface{}{"startup": "ifacemgr"})
  4815  }
  4816  
  4817  func (s *interfaceManagerSuite) TestAutoconnectSelf(c *C) {
  4818  	s.MockModel(c, nil)
  4819  
  4820  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
  4821  	s.mockSnap(c, selfconnectSnapYaml)
  4822  	repo := s.manager(c).Repository()
  4823  	c.Assert(repo.Slots("producerconsumer"), HasLen, 1)
  4824  
  4825  	s.state.Lock()
  4826  
  4827  	sup := &snapstate.SnapSetup{
  4828  		SideInfo: &snap.SideInfo{
  4829  			Revision: snap.R(1),
  4830  			RealName: "producerconsumer"},
  4831  	}
  4832  
  4833  	chg := s.state.NewChange("install", "...")
  4834  	t := s.state.NewTask("auto-connect", "...")
  4835  	t.Set("snap-setup", sup)
  4836  	chg.AddTask(t)
  4837  
  4838  	s.state.Unlock()
  4839  
  4840  	s.settle(c)
  4841  
  4842  	s.state.Lock()
  4843  	defer s.state.Unlock()
  4844  
  4845  	hooktypes := make(map[string]int)
  4846  	for _, t := range s.state.Tasks() {
  4847  		if t.Kind() == "run-hook" {
  4848  			var hsup hookstate.HookSetup
  4849  			c.Assert(t.Get("hook-setup", &hsup), IsNil)
  4850  			count := hooktypes[hsup.Hook]
  4851  			hooktypes[hsup.Hook] = count + 1
  4852  		}
  4853  	}
  4854  
  4855  	// verify that every hook was run once
  4856  	for _, ht := range []string{"prepare-plug-plug", "prepare-slot-slot", "connect-slot-slot", "connect-plug-plug"} {
  4857  		c.Assert(hooktypes[ht], Equals, 1)
  4858  	}
  4859  }
  4860  
  4861  func (s *interfaceManagerSuite) TestAutoconnectForDefaultContentProvider(c *C) {
  4862  	s.MockModel(c, nil)
  4863  
  4864  	restore := ifacestate.MockContentLinkRetryTimeout(5 * time.Millisecond)
  4865  	defer restore()
  4866  
  4867  	s.mockSnap(c, `name: snap-content-plug
  4868  version: 1
  4869  plugs:
  4870   shared-content-plug:
  4871    interface: content
  4872    default-provider: snap-content-slot
  4873    content: shared-content
  4874  `)
  4875  	s.mockSnap(c, `name: snap-content-slot
  4876  version: 1
  4877  slots:
  4878   shared-content-slot:
  4879    interface: content
  4880    content: shared-content
  4881  `)
  4882  	s.manager(c)
  4883  
  4884  	s.state.Lock()
  4885  
  4886  	supContentPlug := &snapstate.SnapSetup{
  4887  		SideInfo: &snap.SideInfo{
  4888  			Revision: snap.R(1),
  4889  			RealName: "snap-content-plug"},
  4890  	}
  4891  	supContentSlot := &snapstate.SnapSetup{
  4892  		SideInfo: &snap.SideInfo{
  4893  			Revision: snap.R(1),
  4894  			RealName: "snap-content-slot"},
  4895  	}
  4896  	chg := s.state.NewChange("install", "...")
  4897  
  4898  	tInstPlug := s.state.NewTask("link-snap", "Install snap-content-plug")
  4899  	tInstPlug.Set("snap-setup", supContentPlug)
  4900  	chg.AddTask(tInstPlug)
  4901  
  4902  	tInstSlot := s.state.NewTask("link-snap", "Install snap-content-slot")
  4903  	tInstSlot.Set("snap-setup", supContentSlot)
  4904  	chg.AddTask(tInstSlot)
  4905  
  4906  	tConnectPlug := s.state.NewTask("auto-connect", "...")
  4907  	tConnectPlug.Set("snap-setup", supContentPlug)
  4908  	chg.AddTask(tConnectPlug)
  4909  
  4910  	tConnectSlot := s.state.NewTask("auto-connect", "...")
  4911  	tConnectSlot.Set("snap-setup", supContentSlot)
  4912  	chg.AddTask(tConnectSlot)
  4913  
  4914  	// run the change
  4915  	s.state.Unlock()
  4916  	for i := 0; i < 5; i++ {
  4917  		s.se.Ensure()
  4918  		s.se.Wait()
  4919  	}
  4920  
  4921  	// change did a retry
  4922  	s.state.Lock()
  4923  	c.Check(tConnectPlug.Status(), Equals, state.DoingStatus)
  4924  
  4925  	// pretend install of content slot is done
  4926  	tInstSlot.SetStatus(state.DoneStatus)
  4927  	// wait for contentLinkRetryTimeout
  4928  	time.Sleep(10 * time.Millisecond)
  4929  
  4930  	s.state.Unlock()
  4931  
  4932  	// run again
  4933  	for i := 0; i < 5; i++ {
  4934  		s.se.Ensure()
  4935  		s.se.Wait()
  4936  	}
  4937  
  4938  	// check that the connect plug task is now in done state
  4939  	s.state.Lock()
  4940  	defer s.state.Unlock()
  4941  	c.Check(tConnectPlug.Status(), Equals, state.DoneStatus)
  4942  }
  4943  
  4944  func (s *interfaceManagerSuite) TestAutoconnectForDefaultContentProviderWrongOrderWaitChain(c *C) {
  4945  	s.MockModel(c, nil)
  4946  
  4947  	restore := ifacestate.MockContentLinkRetryTimeout(5 * time.Millisecond)
  4948  	defer restore()
  4949  
  4950  	s.mockSnap(c, `name: snap-content-plug
  4951  version: 1
  4952  plugs:
  4953   shared-content-plug:
  4954    interface: content
  4955    default-provider: snap-content-slot
  4956    content: shared-content
  4957  `)
  4958  	s.mockSnap(c, `name: snap-content-slot
  4959  version: 1
  4960  slots:
  4961   shared-content-slot:
  4962    interface: content
  4963    content: shared-content
  4964  `)
  4965  	s.manager(c)
  4966  
  4967  	s.state.Lock()
  4968  
  4969  	supContentPlug := &snapstate.SnapSetup{
  4970  		SideInfo: &snap.SideInfo{
  4971  			Revision: snap.R(1),
  4972  			RealName: "snap-content-plug"},
  4973  	}
  4974  	supContentSlot := &snapstate.SnapSetup{
  4975  		SideInfo: &snap.SideInfo{
  4976  			Revision: snap.R(1),
  4977  			RealName: "snap-content-slot"},
  4978  	}
  4979  	chg := s.state.NewChange("install", "...")
  4980  
  4981  	// Setup a wait chain in the "wrong" order, i.e. pretend we seed
  4982  	// the consumer of the content interface before we seed the producer
  4983  	// (see LP:#1772844) for a real world example of this).
  4984  	tInstPlug := s.state.NewTask("link-snap", "Install snap-content-plug")
  4985  	tInstPlug.Set("snap-setup", supContentPlug)
  4986  	chg.AddTask(tInstPlug)
  4987  
  4988  	tConnectPlug := s.state.NewTask("auto-connect", "...plug")
  4989  	tConnectPlug.Set("snap-setup", supContentPlug)
  4990  	tConnectPlug.WaitFor(tInstPlug)
  4991  	chg.AddTask(tConnectPlug)
  4992  
  4993  	tInstSlot := s.state.NewTask("link-snap", "Install snap-content-slot")
  4994  	tInstSlot.Set("snap-setup", supContentSlot)
  4995  	tInstSlot.WaitFor(tInstPlug)
  4996  	tInstSlot.WaitFor(tConnectPlug)
  4997  	chg.AddTask(tInstSlot)
  4998  
  4999  	tConnectSlot := s.state.NewTask("auto-connect", "...slot")
  5000  	tConnectSlot.Set("snap-setup", supContentSlot)
  5001  	tConnectSlot.WaitFor(tInstPlug)
  5002  	tConnectSlot.WaitFor(tInstSlot)
  5003  	tConnectSlot.WaitFor(tConnectPlug)
  5004  	chg.AddTask(tConnectSlot)
  5005  
  5006  	// pretend plug install was done by snapstate
  5007  	tInstPlug.SetStatus(state.DoneStatus)
  5008  
  5009  	// run the change, this will trigger the auto-connect of the plug
  5010  	s.state.Unlock()
  5011  	for i := 0; i < 5; i++ {
  5012  		s.se.Ensure()
  5013  		s.se.Wait()
  5014  	}
  5015  
  5016  	// check that auto-connect did finish and not hang
  5017  	s.state.Lock()
  5018  	c.Check(tConnectPlug.Status(), Equals, state.DoneStatus)
  5019  	c.Check(tInstSlot.Status(), Equals, state.DoStatus)
  5020  	c.Check(tConnectSlot.Status(), Equals, state.DoStatus)
  5021  
  5022  	// pretend snapstate finished installing the slot
  5023  	tInstSlot.SetStatus(state.DoneStatus)
  5024  
  5025  	s.state.Unlock()
  5026  
  5027  	// run again
  5028  	for i := 0; i < 5; i++ {
  5029  		s.se.Ensure()
  5030  		s.se.Wait()
  5031  	}
  5032  
  5033  	// and now the slot side auto-connected
  5034  	s.state.Lock()
  5035  	defer s.state.Unlock()
  5036  	c.Check(tConnectSlot.Status(), Equals, state.DoneStatus)
  5037  }
  5038  
  5039  func (s *interfaceManagerSuite) TestSnapsWithSecurityProfiles(c *C) {
  5040  	s.state.Lock()
  5041  	defer s.state.Unlock()
  5042  
  5043  	si0 := &snap.SideInfo{
  5044  		RealName: "snap0",
  5045  		Revision: snap.R(10),
  5046  	}
  5047  	snaptest.MockSnap(c, `name: snap0`, si0)
  5048  	snapstate.Set(s.state, "snap0", &snapstate.SnapState{
  5049  		Active:   true,
  5050  		Sequence: []*snap.SideInfo{si0},
  5051  		Current:  si0.Revision,
  5052  	})
  5053  
  5054  	snaps := []struct {
  5055  		name        string
  5056  		setupStatus state.Status
  5057  		linkStatus  state.Status
  5058  	}{
  5059  		{"snap0", state.DoneStatus, state.DoneStatus},
  5060  		{"snap1", state.DoneStatus, state.DoStatus},
  5061  		{"snap2", state.DoneStatus, state.ErrorStatus},
  5062  		{"snap3", state.DoneStatus, state.UndoingStatus},
  5063  		{"snap4", state.DoingStatus, state.DoStatus},
  5064  		{"snap6", state.DoStatus, state.DoStatus},
  5065  	}
  5066  
  5067  	for i, snp := range snaps {
  5068  		var si *snap.SideInfo
  5069  
  5070  		if snp.name != "snap0" {
  5071  			si = &snap.SideInfo{
  5072  				RealName: snp.name,
  5073  				Revision: snap.R(i),
  5074  			}
  5075  			snaptest.MockSnap(c, "name: "+snp.name, si)
  5076  		}
  5077  
  5078  		chg := s.state.NewChange("linking", "linking 1")
  5079  		t1 := s.state.NewTask("setup-profiles", "setup profiles 1")
  5080  		t1.Set("snap-setup", &snapstate.SnapSetup{
  5081  			SideInfo: si,
  5082  		})
  5083  		t1.SetStatus(snp.setupStatus)
  5084  		t2 := s.state.NewTask("link-snap", "link snap 1")
  5085  		t2.Set("snap-setup", &snapstate.SnapSetup{
  5086  			SideInfo: si,
  5087  		})
  5088  		t2.WaitFor(t1)
  5089  		t2.SetStatus(snp.linkStatus)
  5090  		chg.AddTask(t1)
  5091  		chg.AddTask(t2)
  5092  	}
  5093  
  5094  	infos, err := ifacestate.SnapsWithSecurityProfiles(s.state)
  5095  	c.Assert(err, IsNil)
  5096  	c.Check(infos, HasLen, 3)
  5097  	got := make(map[string]snap.Revision)
  5098  	for _, info := range infos {
  5099  		got[info.InstanceName()] = info.Revision
  5100  	}
  5101  	c.Check(got, DeepEquals, map[string]snap.Revision{
  5102  		"snap0": snap.R(10),
  5103  		"snap1": snap.R(1),
  5104  		"snap3": snap.R(3),
  5105  	})
  5106  }
  5107  
  5108  func (s *interfaceManagerSuite) TestDisconnectInterfaces(c *C) {
  5109  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
  5110  	_ = s.manager(c)
  5111  
  5112  	consumerInfo := s.mockSnap(c, consumerYaml)
  5113  	producerInfo := s.mockSnap(c, producerYaml)
  5114  
  5115  	s.state.Lock()
  5116  
  5117  	sup := &snapstate.SnapSetup{
  5118  		SideInfo: &snap.SideInfo{
  5119  			RealName: "consumer"},
  5120  	}
  5121  
  5122  	repo := s.manager(c).Repository()
  5123  	c.Assert(repo.AddSnap(consumerInfo), IsNil)
  5124  	c.Assert(repo.AddSnap(producerInfo), IsNil)
  5125  
  5126  	plugDynAttrs := map[string]interface{}{
  5127  		"attr3": "value3",
  5128  	}
  5129  	slotDynAttrs := map[string]interface{}{
  5130  		"attr4": "value4",
  5131  	}
  5132  	repo.Connect(&interfaces.ConnRef{
  5133  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  5134  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
  5135  	}, nil, plugDynAttrs, nil, slotDynAttrs, nil)
  5136  
  5137  	chg := s.state.NewChange("install", "")
  5138  	t := s.state.NewTask("auto-disconnect", "")
  5139  	t.Set("snap-setup", sup)
  5140  	chg.AddTask(t)
  5141  
  5142  	s.state.Unlock()
  5143  
  5144  	s.se.Ensure()
  5145  	s.se.Wait()
  5146  
  5147  	s.state.Lock()
  5148  	defer s.state.Unlock()
  5149  
  5150  	ht := t.HaltTasks()
  5151  	c.Assert(ht, HasLen, 3)
  5152  
  5153  	c.Assert(ht[2].Kind(), Equals, "disconnect")
  5154  	var autoDisconnect bool
  5155  	c.Assert(ht[2].Get("auto-disconnect", &autoDisconnect), IsNil)
  5156  	c.Assert(autoDisconnect, Equals, true)
  5157  	var plugDynamic, slotDynamic, plugStatic, slotStatic map[string]interface{}
  5158  	c.Assert(ht[2].Get("plug-static", &plugStatic), IsNil)
  5159  	c.Assert(ht[2].Get("plug-dynamic", &plugDynamic), IsNil)
  5160  	c.Assert(ht[2].Get("slot-static", &slotStatic), IsNil)
  5161  	c.Assert(ht[2].Get("slot-dynamic", &slotDynamic), IsNil)
  5162  
  5163  	c.Assert(plugStatic, DeepEquals, map[string]interface{}{"attr1": "value1"})
  5164  	c.Assert(slotStatic, DeepEquals, map[string]interface{}{"attr2": "value2"})
  5165  	c.Assert(plugDynamic, DeepEquals, map[string]interface{}{"attr3": "value3"})
  5166  	c.Assert(slotDynamic, DeepEquals, map[string]interface{}{"attr4": "value4"})
  5167  
  5168  	var expectedHooks = []struct{ snap, hook string }{
  5169  		{snap: "producer", hook: "disconnect-slot-slot"},
  5170  		{snap: "consumer", hook: "disconnect-plug-plug"},
  5171  	}
  5172  
  5173  	for i := 0; i < 2; i++ {
  5174  		var hsup hookstate.HookSetup
  5175  		c.Assert(ht[i].Kind(), Equals, "run-hook")
  5176  		c.Assert(ht[i].Get("hook-setup", &hsup), IsNil)
  5177  
  5178  		c.Assert(hsup.Snap, Equals, expectedHooks[i].snap)
  5179  		c.Assert(hsup.Hook, Equals, expectedHooks[i].hook)
  5180  	}
  5181  }
  5182  
  5183  func (s *interfaceManagerSuite) testDisconnectInterfacesRetry(c *C, conflictingKind string) {
  5184  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
  5185  	_ = s.manager(c)
  5186  
  5187  	consumerInfo := s.mockSnap(c, consumerYaml)
  5188  	producerInfo := s.mockSnap(c, producerYaml)
  5189  
  5190  	supprod := &snapstate.SnapSetup{
  5191  		SideInfo: &snap.SideInfo{
  5192  			RealName: "producer"},
  5193  	}
  5194  
  5195  	s.state.Lock()
  5196  
  5197  	repo := s.manager(c).Repository()
  5198  	c.Assert(repo.AddSnap(consumerInfo), IsNil)
  5199  	c.Assert(repo.AddSnap(producerInfo), IsNil)
  5200  
  5201  	repo.Connect(&interfaces.ConnRef{
  5202  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  5203  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
  5204  	}, nil, nil, nil, nil, nil)
  5205  
  5206  	sup := &snapstate.SnapSetup{
  5207  		SideInfo: &snap.SideInfo{
  5208  			RealName: "consumer"},
  5209  	}
  5210  
  5211  	chg2 := s.state.NewChange("remove", "")
  5212  	t2 := s.state.NewTask("auto-disconnect", "")
  5213  	t2.Set("snap-setup", sup)
  5214  	chg2.AddTask(t2)
  5215  
  5216  	// create conflicting task
  5217  	chg1 := s.state.NewChange("conflicting change", "")
  5218  	t1 := s.state.NewTask(conflictingKind, "")
  5219  	t1.Set("snap-setup", supprod)
  5220  	chg1.AddTask(t1)
  5221  	t3 := s.state.NewTask("other", "")
  5222  	t1.WaitFor(t3)
  5223  	chg1.AddTask(t3)
  5224  	t3.SetStatus(state.HoldStatus)
  5225  
  5226  	s.state.Unlock()
  5227  	s.se.Ensure()
  5228  	s.se.Wait()
  5229  
  5230  	s.state.Lock()
  5231  	defer s.state.Unlock()
  5232  
  5233  	c.Assert(strings.Join(t2.Log(), ""), Matches, `.*Waiting for conflicting change in progress...`)
  5234  	c.Assert(t2.Status(), Equals, state.DoingStatus)
  5235  }
  5236  
  5237  func (s *interfaceManagerSuite) TestDisconnectInterfacesRetryLink(c *C) {
  5238  	s.testDisconnectInterfacesRetry(c, "link-snap")
  5239  }
  5240  
  5241  func (s *interfaceManagerSuite) TestDisconnectInterfacesRetrySetupProfiles(c *C) {
  5242  	s.testDisconnectInterfacesRetry(c, "setup-profiles")
  5243  }
  5244  
  5245  func (s *interfaceManagerSuite) setupGadgetConnect(c *C) {
  5246  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
  5247  	s.MockSnapDecl(c, "consumer", "publisher1", nil)
  5248  	s.mockSnap(c, consumerYaml)
  5249  	s.MockSnapDecl(c, "producer", "publisher2", nil)
  5250  	s.mockSnap(c, producerYaml)
  5251  
  5252  	gadgetInfo := s.mockSnap(c, `name: gadget
  5253  type: gadget
  5254  `)
  5255  
  5256  	gadgetYaml := []byte(`
  5257  connections:
  5258     - plug: consumeridididididididididididid:plug
  5259       slot: produceridididididididididididid:slot
  5260  
  5261  volumes:
  5262      volume-id:
  5263          bootloader: grub
  5264  `)
  5265  
  5266  	err := ioutil.WriteFile(filepath.Join(gadgetInfo.MountDir(), "meta", "gadget.yaml"), gadgetYaml, 0644)
  5267  	c.Assert(err, IsNil)
  5268  
  5269  	s.MockModel(c, nil)
  5270  }
  5271  
  5272  func (s *interfaceManagerSuite) TestGadgetConnect(c *C) {
  5273  	r1 := release.MockOnClassic(false)
  5274  	defer r1()
  5275  
  5276  	s.setupGadgetConnect(c)
  5277  	s.manager(c)
  5278  
  5279  	s.state.Lock()
  5280  	defer s.state.Unlock()
  5281  
  5282  	chg := s.state.NewChange("setting-up", "...")
  5283  	t := s.state.NewTask("gadget-connect", "gadget connections")
  5284  	chg.AddTask(t)
  5285  
  5286  	s.state.Unlock()
  5287  	s.se.Ensure()
  5288  	s.se.Wait()
  5289  	s.state.Lock()
  5290  
  5291  	c.Assert(chg.Err(), IsNil)
  5292  	tasks := chg.Tasks()
  5293  	c.Assert(tasks, HasLen, 6)
  5294  
  5295  	gotConnect := false
  5296  	for _, t := range tasks {
  5297  		switch t.Kind() {
  5298  		default:
  5299  			c.Fatalf("unexpected task kind: %s", t.Kind())
  5300  		case "gadget-connect":
  5301  		case "run-hook":
  5302  		case "connect":
  5303  			gotConnect = true
  5304  			var autoConnect, byGadget bool
  5305  			err := t.Get("auto", &autoConnect)
  5306  			c.Assert(err, IsNil)
  5307  			err = t.Get("by-gadget", &byGadget)
  5308  			c.Assert(err, IsNil)
  5309  			c.Check(autoConnect, Equals, true)
  5310  			c.Check(byGadget, Equals, true)
  5311  
  5312  			var plug interfaces.PlugRef
  5313  			err = t.Get("plug", &plug)
  5314  			c.Assert(err, IsNil)
  5315  			c.Assert(plug.Snap, Equals, "consumer")
  5316  			c.Assert(plug.Name, Equals, "plug")
  5317  			var slot interfaces.SlotRef
  5318  			err = t.Get("slot", &slot)
  5319  			c.Assert(err, IsNil)
  5320  			c.Assert(slot.Snap, Equals, "producer")
  5321  			c.Assert(slot.Name, Equals, "slot")
  5322  		}
  5323  	}
  5324  
  5325  	c.Assert(gotConnect, Equals, true)
  5326  }
  5327  
  5328  func (s *interfaceManagerSuite) TestGadgetConnectAlreadyConnected(c *C) {
  5329  	r1 := release.MockOnClassic(false)
  5330  	defer r1()
  5331  
  5332  	s.setupGadgetConnect(c)
  5333  	s.manager(c)
  5334  
  5335  	s.state.Lock()
  5336  	defer s.state.Unlock()
  5337  
  5338  	s.state.Set("conns", map[string]interface{}{
  5339  		"consumer:plug producer:slot": map[string]interface{}{
  5340  			"interface": "test", "auto": true,
  5341  		},
  5342  	})
  5343  
  5344  	chg := s.state.NewChange("setting-up", "...")
  5345  	t := s.state.NewTask("gadget-connect", "gadget connections")
  5346  	chg.AddTask(t)
  5347  
  5348  	s.state.Unlock()
  5349  	s.se.Ensure()
  5350  	s.se.Wait()
  5351  	s.state.Lock()
  5352  
  5353  	c.Assert(chg.Err(), IsNil)
  5354  	c.Check(chg.Status().Ready(), Equals, true)
  5355  	tasks := chg.Tasks()
  5356  	c.Assert(tasks, HasLen, 1)
  5357  }
  5358  
  5359  func (s *interfaceManagerSuite) TestGadgetConnectConflictRetry(c *C) {
  5360  	r1 := release.MockOnClassic(false)
  5361  	defer r1()
  5362  
  5363  	s.setupGadgetConnect(c)
  5364  	s.manager(c)
  5365  
  5366  	s.state.Lock()
  5367  	defer s.state.Unlock()
  5368  
  5369  	otherChg := s.state.NewChange("other-chg", "...")
  5370  	t := s.state.NewTask("link-snap", "...")
  5371  	t.Set("snap-setup", &snapstate.SnapSetup{
  5372  		SideInfo: &snap.SideInfo{
  5373  			RealName: "producer"},
  5374  	})
  5375  	otherChg.AddTask(t)
  5376  
  5377  	chg := s.state.NewChange("setting-up", "...")
  5378  	t = s.state.NewTask("gadget-connect", "gadget connections")
  5379  	chg.AddTask(t)
  5380  
  5381  	s.state.Unlock()
  5382  	s.se.Ensure()
  5383  	s.se.Wait()
  5384  	s.state.Lock()
  5385  
  5386  	c.Assert(chg.Err(), IsNil)
  5387  	c.Check(chg.Status().Ready(), Equals, false)
  5388  	tasks := chg.Tasks()
  5389  	c.Assert(tasks, HasLen, 1)
  5390  
  5391  	c.Check(t.Status(), Equals, state.DoingStatus)
  5392  	c.Check(t.Log()[0], Matches, `.*gadget connect will be retried: conflicting snap producer with task "link-snap"`)
  5393  }
  5394  
  5395  func (s *interfaceManagerSuite) TestGadgetConnectSkipUnknown(c *C) {
  5396  	r1 := release.MockOnClassic(false)
  5397  	defer r1()
  5398  
  5399  	s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
  5400  	s.MockSnapDecl(c, "consumer", "publisher1", nil)
  5401  	s.mockSnap(c, consumerYaml)
  5402  	s.MockSnapDecl(c, "producer", "publisher2", nil)
  5403  	s.mockSnap(c, producerYaml)
  5404  
  5405  	s.MockModel(c, nil)
  5406  
  5407  	s.manager(c)
  5408  
  5409  	gadgetInfo := s.mockSnap(c, `name: gadget
  5410  type: gadget
  5411  `)
  5412  
  5413  	gadgetYaml := []byte(`
  5414  connections:
  5415     - plug: consumeridididididididididididid:plug
  5416       slot: produceridididididididididididid:unknown
  5417     - plug: unknownididididididididididididi:plug
  5418       slot: produceridididididididididididid:slot
  5419  
  5420  volumes:
  5421      volume-id:
  5422          bootloader: grub
  5423  `)
  5424  
  5425  	err := ioutil.WriteFile(filepath.Join(gadgetInfo.MountDir(), "meta", "gadget.yaml"), gadgetYaml, 0644)
  5426  	c.Assert(err, IsNil)
  5427  
  5428  	s.state.Lock()
  5429  	defer s.state.Unlock()
  5430  
  5431  	chg := s.state.NewChange("setting-up", "...")
  5432  	t := s.state.NewTask("gadget-connect", "gadget connections")
  5433  	chg.AddTask(t)
  5434  
  5435  	s.state.Unlock()
  5436  	s.se.Ensure()
  5437  	s.se.Wait()
  5438  	s.state.Lock()
  5439  
  5440  	c.Assert(chg.Err(), IsNil)
  5441  	tasks := chg.Tasks()
  5442  	c.Assert(tasks, HasLen, 1)
  5443  
  5444  	logs := t.Log()
  5445  	c.Check(logs, HasLen, 2)
  5446  	c.Check(logs[0], Matches, `.*ignoring missing slot produceridididididididididididid:unknown`)
  5447  	c.Check(logs[1], Matches, `.* ignoring missing plug unknownididididididididididididi:plug`)
  5448  }
  5449  
  5450  func (s *interfaceManagerSuite) TestGadgetConnectHappyPolicyChecks(c *C) {
  5451  	// network-control does not auto-connect so this test also
  5452  	// checks that the right policy checker (for "*-connection"
  5453  	// rules) is used for gadget connections
  5454  	r1 := release.MockOnClassic(false)
  5455  	defer r1()
  5456  
  5457  	s.MockModel(c, nil)
  5458  
  5459  	s.mockSnap(c, coreSnapYaml)
  5460  
  5461  	s.MockSnapDecl(c, "foo", "publisher1", nil)
  5462  	s.mockSnap(c, `name: foo
  5463  version: 1.0
  5464  plugs:
  5465    network-control:
  5466  `)
  5467  
  5468  	s.manager(c)
  5469  
  5470  	gadgetInfo := s.mockSnap(c, `name: gadget
  5471  type: gadget
  5472  `)
  5473  
  5474  	gadgetYaml := []byte(`
  5475  connections:
  5476     - plug: fooididididididididididididididi:network-control
  5477  
  5478  volumes:
  5479      volume-id:
  5480          bootloader: grub
  5481  `)
  5482  
  5483  	err := ioutil.WriteFile(filepath.Join(gadgetInfo.MountDir(), "meta", "gadget.yaml"), gadgetYaml, 0644)
  5484  	c.Assert(err, IsNil)
  5485  
  5486  	s.state.Lock()
  5487  	defer s.state.Unlock()
  5488  
  5489  	chg := s.state.NewChange("setting-up", "...")
  5490  	t := s.state.NewTask("gadget-connect", "gadget connections")
  5491  	chg.AddTask(t)
  5492  
  5493  	s.state.Unlock()
  5494  	s.se.Ensure()
  5495  	s.se.Wait()
  5496  	s.state.Lock()
  5497  
  5498  	c.Assert(chg.Err(), IsNil)
  5499  	tasks := chg.Tasks()
  5500  	c.Assert(tasks, HasLen, 2)
  5501  	c.Assert(tasks[0].Kind(), Equals, "gadget-connect")
  5502  	c.Assert(tasks[1].Kind(), Equals, "connect")
  5503  
  5504  	s.state.Unlock()
  5505  	s.settle(c)
  5506  	s.state.Lock()
  5507  
  5508  	c.Assert(chg.Err(), IsNil)
  5509  	c.Assert(chg.Status().Ready(), Equals, true)
  5510  
  5511  	// check connection
  5512  	var conns map[string]interface{}
  5513  	err = s.state.Get("conns", &conns)
  5514  	c.Assert(err, IsNil)
  5515  	c.Check(conns, HasLen, 1)
  5516  	c.Check(conns, DeepEquals, map[string]interface{}{
  5517  		"foo:network-control core:network-control": map[string]interface{}{
  5518  			"interface": "network-control", "auto": true, "by-gadget": true,
  5519  		},
  5520  	})
  5521  }
  5522  
  5523  func (s *interfaceManagerSuite) testChangeConflict(c *C, kind string) {
  5524  	s.state.Lock()
  5525  	defer s.state.Unlock()
  5526  
  5527  	snapstate.Set(s.state, "producer", &snapstate.SnapState{
  5528  		Active:   true,
  5529  		Sequence: []*snap.SideInfo{{RealName: "producer", SnapID: "producer-id", Revision: snap.R(1)}},
  5530  		Current:  snap.R(1),
  5531  		SnapType: "app",
  5532  	})
  5533  	snapstate.Set(s.state, "consumer", &snapstate.SnapState{
  5534  		Active:   true,
  5535  		Sequence: []*snap.SideInfo{{RealName: "consumer", SnapID: "consumer-id", Revision: snap.R(1)}},
  5536  		Current:  snap.R(1),
  5537  		SnapType: "app",
  5538  	})
  5539  
  5540  	chg := s.state.NewChange("another change", "...")
  5541  	t := s.state.NewTask(kind, "...")
  5542  	t.Set("slot", interfaces.SlotRef{Snap: "producer", Name: "slot"})
  5543  	t.Set("plug", interfaces.PlugRef{Snap: "consumer", Name: "plug"})
  5544  	chg.AddTask(t)
  5545  
  5546  	_, err := snapstate.Disable(s.state, "producer")
  5547  	c.Assert(err, ErrorMatches, `snap "producer" has "another change" change in progress`)
  5548  
  5549  	_, err = snapstate.Disable(s.state, "consumer")
  5550  	c.Assert(err, ErrorMatches, `snap "consumer" has "another change" change in progress`)
  5551  }
  5552  
  5553  func (s *interfaceManagerSuite) TestSnapstateOpConflictWithConnect(c *C) {
  5554  	s.testChangeConflict(c, "connect")
  5555  }
  5556  
  5557  func (s *interfaceManagerSuite) TestSnapstateOpConflictWithDisconnect(c *C) {
  5558  	s.testChangeConflict(c, "disconnect")
  5559  }
  5560  
  5561  type udevMonitorMock struct {
  5562  	ConnectError, RunError                             error
  5563  	ConnectCalls, RunCalls, StopCalls, DisconnectCalls int
  5564  	AddDevice                                          udevmonitor.DeviceAddedFunc
  5565  	RemoveDevice                                       udevmonitor.DeviceRemovedFunc
  5566  	EnumerationDone                                    udevmonitor.EnumerationDoneFunc
  5567  }
  5568  
  5569  func (u *udevMonitorMock) Connect() error {
  5570  	u.ConnectCalls++
  5571  	return u.ConnectError
  5572  }
  5573  
  5574  func (u *udevMonitorMock) Disconnect() error {
  5575  	u.DisconnectCalls++
  5576  	return nil
  5577  }
  5578  
  5579  func (u *udevMonitorMock) Run() error {
  5580  	u.RunCalls++
  5581  	return u.RunError
  5582  }
  5583  
  5584  func (u *udevMonitorMock) Stop() error {
  5585  	u.StopCalls++
  5586  	return nil
  5587  }
  5588  
  5589  func (s *interfaceManagerSuite) TestUDevMonitorInit(c *C) {
  5590  	u := udevMonitorMock{}
  5591  	st := s.state
  5592  	st.Lock()
  5593  	snapstate.Set(s.state, "core", &snapstate.SnapState{
  5594  		Active: true,
  5595  		Sequence: []*snap.SideInfo{
  5596  			{RealName: "core", Revision: snap.R(1)},
  5597  		},
  5598  		Current:  snap.R(1),
  5599  		SnapType: "os",
  5600  	})
  5601  	st.Unlock()
  5602  
  5603  	restoreTimeout := ifacestate.MockUDevInitRetryTimeout(0 * time.Second)
  5604  	defer restoreTimeout()
  5605  
  5606  	restoreCreate := ifacestate.MockCreateUDevMonitor(func(udevmonitor.DeviceAddedFunc, udevmonitor.DeviceRemovedFunc, udevmonitor.EnumerationDoneFunc) udevmonitor.Interface {
  5607  		return &u
  5608  	})
  5609  	defer restoreCreate()
  5610  
  5611  	mgr, err := ifacestate.Manager(s.state, nil, s.o.TaskRunner(), nil, nil)
  5612  	c.Assert(err, IsNil)
  5613  	s.o.AddManager(mgr)
  5614  	c.Assert(s.o.StartUp(), IsNil)
  5615  
  5616  	// succesfull initialization should result in exactly 1 connect and run call
  5617  	for i := 0; i < 5; i++ {
  5618  		c.Assert(s.se.Ensure(), IsNil)
  5619  	}
  5620  	s.se.Stop()
  5621  
  5622  	c.Assert(u.ConnectCalls, Equals, 1)
  5623  	c.Assert(u.RunCalls, Equals, 1)
  5624  	c.Assert(u.StopCalls, Equals, 1)
  5625  }
  5626  
  5627  func (s *interfaceManagerSuite) TestUDevMonitorInitErrors(c *C) {
  5628  	u := udevMonitorMock{
  5629  		ConnectError: fmt.Errorf("Connect failed"),
  5630  	}
  5631  	st := s.state
  5632  	st.Lock()
  5633  	snapstate.Set(s.state, "core", &snapstate.SnapState{
  5634  		Active: true,
  5635  		Sequence: []*snap.SideInfo{
  5636  			{RealName: "core", Revision: snap.R(1)},
  5637  		},
  5638  		Current:  snap.R(1),
  5639  		SnapType: "os",
  5640  	})
  5641  	st.Unlock()
  5642  
  5643  	restoreTimeout := ifacestate.MockUDevInitRetryTimeout(0 * time.Second)
  5644  	defer restoreTimeout()
  5645  
  5646  	restoreCreate := ifacestate.MockCreateUDevMonitor(func(udevmonitor.DeviceAddedFunc, udevmonitor.DeviceRemovedFunc, udevmonitor.EnumerationDoneFunc) udevmonitor.Interface {
  5647  		return &u
  5648  	})
  5649  	defer restoreCreate()
  5650  
  5651  	mgr, err := ifacestate.Manager(s.state, nil, s.o.TaskRunner(), nil, nil)
  5652  	c.Assert(err, IsNil)
  5653  	s.o.AddManager(mgr)
  5654  	c.Assert(s.o.StartUp(), IsNil)
  5655  
  5656  	c.Assert(s.se.Ensure(), ErrorMatches, ".*Connect failed.*")
  5657  	c.Assert(u.ConnectCalls, Equals, 1)
  5658  	c.Assert(u.RunCalls, Equals, 0)
  5659  	c.Assert(u.StopCalls, Equals, 0)
  5660  
  5661  	u.ConnectError = nil
  5662  	u.RunError = fmt.Errorf("Run failed")
  5663  	c.Assert(s.se.Ensure(), ErrorMatches, ".*Run failed.*")
  5664  	c.Assert(u.ConnectCalls, Equals, 2)
  5665  	c.Assert(u.RunCalls, Equals, 1)
  5666  	c.Assert(u.StopCalls, Equals, 0)
  5667  	c.Assert(u.DisconnectCalls, Equals, 1)
  5668  
  5669  	u.RunError = nil
  5670  	c.Assert(s.se.Ensure(), IsNil)
  5671  
  5672  	s.se.Stop()
  5673  
  5674  	c.Assert(u.StopCalls, Equals, 1)
  5675  }
  5676  
  5677  func (s *interfaceManagerSuite) TestUDevMonitorInitWaitsForCore(c *C) {
  5678  	restoreTimeout := ifacestate.MockUDevInitRetryTimeout(0 * time.Second)
  5679  	defer restoreTimeout()
  5680  
  5681  	var udevMonitorCreated bool
  5682  	restoreCreate := ifacestate.MockCreateUDevMonitor(func(udevmonitor.DeviceAddedFunc, udevmonitor.DeviceRemovedFunc, udevmonitor.EnumerationDoneFunc) udevmonitor.Interface {
  5683  		udevMonitorCreated = true
  5684  		return &udevMonitorMock{}
  5685  	})
  5686  	defer restoreCreate()
  5687  
  5688  	mgr, err := ifacestate.Manager(s.state, nil, s.o.TaskRunner(), nil, nil)
  5689  	c.Assert(err, IsNil)
  5690  	s.o.AddManager(mgr)
  5691  	c.Assert(s.o.StartUp(), IsNil)
  5692  
  5693  	for i := 0; i < 5; i++ {
  5694  		c.Assert(s.se.Ensure(), IsNil)
  5695  		c.Assert(udevMonitorCreated, Equals, false)
  5696  	}
  5697  
  5698  	// core snap appears in the system
  5699  	st := s.state
  5700  	st.Lock()
  5701  	snapstate.Set(s.state, "core", &snapstate.SnapState{
  5702  		Active: true,
  5703  		Sequence: []*snap.SideInfo{
  5704  			{RealName: "core", Revision: snap.R(1)},
  5705  		},
  5706  		Current:  snap.R(1),
  5707  		SnapType: "os",
  5708  	})
  5709  	st.Unlock()
  5710  
  5711  	// and udev monitor is now created
  5712  	c.Assert(s.se.Ensure(), IsNil)
  5713  	c.Assert(udevMonitorCreated, Equals, true)
  5714  }
  5715  
  5716  func (s *interfaceManagerSuite) TestAttributesRestoredFromConns(c *C) {
  5717  	slotSnap := s.mockSnap(c, producer2Yaml)
  5718  	plugSnap := s.mockSnap(c, consumerYaml)
  5719  
  5720  	slot := slotSnap.Slots["slot"]
  5721  	c.Assert(slot, NotNil)
  5722  	plug := plugSnap.Plugs["plug"]
  5723  	c.Assert(plug, NotNil)
  5724  
  5725  	st := s.st
  5726  	st.Lock()
  5727  	defer st.Unlock()
  5728  
  5729  	conns, err := ifacestate.GetConns(st)
  5730  	c.Assert(err, IsNil)
  5731  
  5732  	// create connection in conns state
  5733  	dynamicAttrs := map[string]interface{}{"dynamic-number": 7}
  5734  	conn := &interfaces.Connection{
  5735  		Plug: interfaces.NewConnectedPlug(plug, nil, nil),
  5736  		Slot: interfaces.NewConnectedSlot(slot, nil, dynamicAttrs),
  5737  	}
  5738  
  5739  	var number, dynnumber int64
  5740  	c.Check(conn.Slot.Attr("number", &number), IsNil)
  5741  	c.Check(number, Equals, int64(1))
  5742  
  5743  	var isAuto, byGadget, isUndesired, hotplugGone bool
  5744  	ifacestate.UpdateConnectionInConnState(conns, conn, isAuto, byGadget, isUndesired, hotplugGone)
  5745  	ifacestate.SetConns(st, conns)
  5746  
  5747  	// restore connection from conns state
  5748  	newConns, err := ifacestate.GetConns(st)
  5749  	c.Assert(err, IsNil)
  5750  
  5751  	_, _, slotStaticAttrs, slotDynamicAttrs, ok := ifacestate.GetConnStateAttrs(newConns, "consumer:plug producer2:slot")
  5752  	c.Assert(ok, Equals, true)
  5753  
  5754  	restoredSlot := interfaces.NewConnectedSlot(slot, slotStaticAttrs, slotDynamicAttrs)
  5755  	c.Check(restoredSlot.Attr("number", &number), IsNil)
  5756  	c.Check(number, Equals, int64(1))
  5757  	c.Check(restoredSlot.Attr("dynamic-number", &dynnumber), IsNil)
  5758  }
  5759  
  5760  func (s *interfaceManagerSuite) setupHotplugConnectTestData(c *C) *state.Change {
  5761  	s.state.Unlock()
  5762  
  5763  	coreInfo := s.mockSnap(c, coreSnapYaml)
  5764  	repo := s.manager(c).Repository()
  5765  	c.Assert(repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "test"}), IsNil)
  5766  
  5767  	// mock hotplug slot in the repo and state
  5768  	err := repo.AddSlot(&snap.SlotInfo{
  5769  		Snap:       coreInfo,
  5770  		Name:       "hotplugslot",
  5771  		Interface:  "test",
  5772  		HotplugKey: "1234",
  5773  	})
  5774  	c.Assert(err, IsNil)
  5775  
  5776  	s.state.Lock()
  5777  	s.state.Set("hotplug-slots", map[string]interface{}{
  5778  		"hotplugslot": map[string]interface{}{
  5779  			"name":        "hotplugslot",
  5780  			"interface":   "test",
  5781  			"hotplug-key": "1234",
  5782  		}})
  5783  
  5784  	// mock the consumer
  5785  	si := &snap.SideInfo{RealName: "consumer", Revision: snap.R(1)}
  5786  	testSnap := snaptest.MockSnapInstance(c, "", consumerYaml, si)
  5787  	c.Assert(testSnap.Plugs["plug"], NotNil)
  5788  	c.Assert(repo.AddPlug(testSnap.Plugs["plug"]), IsNil)
  5789  	snapstate.Set(s.state, "consumer", &snapstate.SnapState{
  5790  		Active:   true,
  5791  		Sequence: []*snap.SideInfo{si},
  5792  		Current:  snap.R(1),
  5793  		SnapType: "app",
  5794  	})
  5795  
  5796  	chg := s.state.NewChange("hotplug change", "")
  5797  	t := s.state.NewTask("hotplug-connect", "")
  5798  	ifacestate.SetHotplugAttrs(t, "test", "1234")
  5799  	chg.AddTask(t)
  5800  
  5801  	return chg
  5802  }
  5803  
  5804  func (s *interfaceManagerSuite) TestHotplugConnect(c *C) {
  5805  	s.MockModel(c, nil)
  5806  
  5807  	s.state.Lock()
  5808  	defer s.state.Unlock()
  5809  	chg := s.setupHotplugConnectTestData(c)
  5810  
  5811  	// simulate a device that was known and connected before
  5812  	s.state.Set("conns", map[string]interface{}{
  5813  		"consumer:plug core:hotplugslot": map[string]interface{}{
  5814  			"interface":    "test",
  5815  			"hotplug-key":  "1234",
  5816  			"hotplug-gone": true,
  5817  		}})
  5818  
  5819  	s.state.Unlock()
  5820  	s.settle(c)
  5821  	s.state.Lock()
  5822  
  5823  	c.Assert(chg.Err(), IsNil)
  5824  
  5825  	var conns map[string]interface{}
  5826  	c.Assert(s.state.Get("conns", &conns), IsNil)
  5827  	c.Assert(conns, DeepEquals, map[string]interface{}{
  5828  		"consumer:plug core:hotplugslot": map[string]interface{}{
  5829  			"interface":   "test",
  5830  			"hotplug-key": "1234",
  5831  			"plug-static": map[string]interface{}{"attr1": "value1"},
  5832  		}})
  5833  }
  5834  
  5835  func (s *interfaceManagerSuite) TestHotplugConnectIgnoresUndesired(c *C) {
  5836  	s.MockModel(c, nil)
  5837  
  5838  	s.state.Lock()
  5839  	defer s.state.Unlock()
  5840  	chg := s.setupHotplugConnectTestData(c)
  5841  
  5842  	// simulate a device that was known and connected before
  5843  	s.state.Set("conns", map[string]interface{}{
  5844  		"consumer:plug core:hotplugslot": map[string]interface{}{
  5845  			"interface":   "test",
  5846  			"hotplug-key": "1234",
  5847  			"undesired":   true,
  5848  		}})
  5849  
  5850  	s.state.Unlock()
  5851  	s.settle(c)
  5852  	s.state.Lock()
  5853  
  5854  	// no connect task created
  5855  	c.Check(chg.Tasks(), HasLen, 1)
  5856  	c.Assert(chg.Err(), IsNil)
  5857  
  5858  	var conns map[string]interface{}
  5859  	c.Assert(s.state.Get("conns", &conns), IsNil)
  5860  	c.Assert(conns, DeepEquals, map[string]interface{}{
  5861  		"consumer:plug core:hotplugslot": map[string]interface{}{
  5862  			"interface":   "test",
  5863  			"hotplug-key": "1234",
  5864  			"undesired":   true,
  5865  		}})
  5866  }
  5867  
  5868  func (s *interfaceManagerSuite) TestHotplugConnectSlotMissing(c *C) {
  5869  	s.MockModel(c, nil)
  5870  
  5871  	repo := s.manager(c).Repository()
  5872  	coreInfo := s.mockSnap(c, coreSnapYaml)
  5873  	c.Assert(repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "test"}), IsNil)
  5874  	c.Assert(repo.AddSlot(&snap.SlotInfo{Snap: coreInfo, Name: "slot", Interface: "test", HotplugKey: "1"}), IsNil)
  5875  
  5876  	s.state.Lock()
  5877  	defer s.state.Unlock()
  5878  
  5879  	chg := s.state.NewChange("hotplug change", "")
  5880  	t := s.state.NewTask("hotplug-connect", "")
  5881  	ifacestate.SetHotplugAttrs(t, "test", "2")
  5882  	chg.AddTask(t)
  5883  
  5884  	s.state.Unlock()
  5885  	s.settle(c)
  5886  	s.state.Lock()
  5887  
  5888  	c.Assert(chg.Err(), ErrorMatches, `(?s).*cannot find hotplug slot for interface test and hotplug key "2".*`)
  5889  }
  5890  
  5891  func (s *interfaceManagerSuite) TestHotplugConnectNothingTodo(c *C) {
  5892  	s.MockModel(c, nil)
  5893  
  5894  	repo := s.manager(c).Repository()
  5895  	coreInfo := s.mockSnap(c, coreSnapYaml)
  5896  
  5897  	iface := &ifacetest.TestInterface{InterfaceName: "test", AutoConnectCallback: func(*snap.PlugInfo, *snap.SlotInfo) bool { return false }}
  5898  	c.Assert(repo.AddInterface(iface), IsNil)
  5899  	c.Assert(repo.AddSlot(&snap.SlotInfo{Snap: coreInfo, Name: "hotplugslot", Interface: "test", HotplugKey: "1"}), IsNil)
  5900  
  5901  	s.state.Lock()
  5902  	defer s.state.Unlock()
  5903  
  5904  	s.state.Set("hotplug-slots", map[string]interface{}{
  5905  		"hotplugslot": map[string]interface{}{
  5906  			"name":        "hotplugslot",
  5907  			"interface":   "test",
  5908  			"hotplug-key": "1",
  5909  		}})
  5910  
  5911  	chg := s.state.NewChange("hotplug change", "")
  5912  	t := s.state.NewTask("hotplug-connect", "")
  5913  	ifacestate.SetHotplugAttrs(t, "test", "1")
  5914  	chg.AddTask(t)
  5915  
  5916  	s.state.Unlock()
  5917  	s.settle(c)
  5918  	s.state.Lock()
  5919  
  5920  	// no connect tasks created
  5921  	c.Check(chg.Tasks(), HasLen, 1)
  5922  	c.Assert(chg.Err(), IsNil)
  5923  }
  5924  
  5925  func (s *interfaceManagerSuite) TestHotplugConnectConflictRetry(c *C) {
  5926  	s.MockModel(c, nil)
  5927  
  5928  	s.state.Lock()
  5929  	defer s.state.Unlock()
  5930  	chg := s.setupHotplugConnectTestData(c)
  5931  
  5932  	// simulate a device that was known and connected before
  5933  	s.state.Set("conns", map[string]interface{}{
  5934  		"consumer:plug core:hotplugslot": map[string]interface{}{
  5935  			"interface":    "test",
  5936  			"hotplug-key":  "1234",
  5937  			"hotplug-gone": true,
  5938  		}})
  5939  
  5940  	otherChg := s.state.NewChange("other-chg", "...")
  5941  	t := s.state.NewTask("link-snap", "...")
  5942  	t.Set("snap-setup", &snapstate.SnapSetup{SideInfo: &snap.SideInfo{RealName: "core"}})
  5943  	otherChg.AddTask(t)
  5944  
  5945  	s.state.Unlock()
  5946  	s.se.Ensure()
  5947  	s.se.Wait()
  5948  	s.state.Lock()
  5949  
  5950  	c.Assert(chg.Err(), IsNil)
  5951  	c.Check(chg.Status().Ready(), Equals, false)
  5952  	tasks := chg.Tasks()
  5953  	c.Assert(tasks, HasLen, 1)
  5954  
  5955  	hotplugConnectTask := tasks[0]
  5956  	c.Check(hotplugConnectTask.Status(), Equals, state.DoingStatus)
  5957  	c.Check(hotplugConnectTask.Log()[0], Matches, `.*hotplug connect will be retried: conflicting snap core with task "link-snap"`)
  5958  }
  5959  
  5960  func (s *interfaceManagerSuite) TestHotplugAutoconnect(c *C) {
  5961  	s.MockModel(c, nil)
  5962  
  5963  	s.state.Lock()
  5964  	defer s.state.Unlock()
  5965  	chg := s.setupHotplugConnectTestData(c)
  5966  
  5967  	s.state.Unlock()
  5968  	s.settle(c)
  5969  	s.state.Lock()
  5970  
  5971  	c.Assert(chg.Err(), IsNil)
  5972  
  5973  	var conns map[string]interface{}
  5974  	c.Assert(s.state.Get("conns", &conns), IsNil)
  5975  	c.Assert(conns, DeepEquals, map[string]interface{}{
  5976  		"consumer:plug core:hotplugslot": map[string]interface{}{
  5977  			"interface":   "test",
  5978  			"hotplug-key": "1234",
  5979  			"auto":        true,
  5980  			"plug-static": map[string]interface{}{"attr1": "value1"},
  5981  		}})
  5982  }
  5983  
  5984  func (s *interfaceManagerSuite) TestHotplugAutoconnectConflictRetry(c *C) {
  5985  	s.MockModel(c, nil)
  5986  
  5987  	s.state.Lock()
  5988  	defer s.state.Unlock()
  5989  	chg := s.setupHotplugConnectTestData(c)
  5990  
  5991  	otherChg := s.state.NewChange("other-chg", "...")
  5992  	t := s.state.NewTask("link-snap", "...")
  5993  	t.Set("snap-setup", &snapstate.SnapSetup{SideInfo: &snap.SideInfo{RealName: "core"}})
  5994  	otherChg.AddTask(t)
  5995  
  5996  	s.state.Unlock()
  5997  	s.se.Ensure()
  5998  	s.se.Wait()
  5999  	s.state.Lock()
  6000  
  6001  	c.Assert(chg.Err(), IsNil)
  6002  	c.Check(chg.Status().Ready(), Equals, false)
  6003  	tasks := chg.Tasks()
  6004  	c.Assert(tasks, HasLen, 1)
  6005  
  6006  	hotplugConnectTask := tasks[0]
  6007  	c.Check(hotplugConnectTask.Status(), Equals, state.DoingStatus)
  6008  	c.Check(hotplugConnectTask.Log()[0], Matches, `.*hotplug connect will be retried: conflicting snap core with task "link-snap"`)
  6009  }
  6010  
  6011  // mockConsumer mocks a consumer snap and its single plug in the repository
  6012  func mockConsumer(c *C, st *state.State, repo *interfaces.Repository, snapYaml, consumerSnapName, plugName string) {
  6013  	si := &snap.SideInfo{RealName: consumerSnapName, Revision: snap.R(1)}
  6014  	consumer := snaptest.MockSnapInstance(c, "", snapYaml, si)
  6015  	c.Assert(consumer.Plugs[plugName], NotNil)
  6016  	c.Assert(repo.AddPlug(consumer.Plugs[plugName]), IsNil)
  6017  	snapstate.Set(st, consumerSnapName, &snapstate.SnapState{
  6018  		Active:   true,
  6019  		Sequence: []*snap.SideInfo{si},
  6020  		Current:  snap.R(1),
  6021  		SnapType: "app",
  6022  	})
  6023  }
  6024  
  6025  func (s *interfaceManagerSuite) TestHotplugConnectAndAutoconnect(c *C) {
  6026  	s.MockModel(c, nil)
  6027  
  6028  	coreInfo := s.mockSnap(c, coreSnapYaml)
  6029  	repo := s.manager(c).Repository()
  6030  	c.Assert(repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "test"}), IsNil)
  6031  
  6032  	// mock hotplug slot in the repo and state
  6033  	c.Assert(repo.AddSlot(&snap.SlotInfo{Snap: coreInfo, Name: "hotplugslot", Interface: "test", HotplugKey: "1234"}), IsNil)
  6034  
  6035  	s.state.Lock()
  6036  	s.state.Set("hotplug-slots", map[string]interface{}{
  6037  		"hotplugslot": map[string]interface{}{"name": "hotplugslot", "interface": "test", "hotplug-key": "1234"},
  6038  	})
  6039  
  6040  	mockConsumer(c, s.state, repo, consumerYaml, "consumer", "plug")
  6041  	mockConsumer(c, s.state, repo, consumer2Yaml, "consumer2", "plug")
  6042  
  6043  	chg := s.state.NewChange("hotplug change", "")
  6044  	t := s.state.NewTask("hotplug-connect", "")
  6045  	ifacestate.SetHotplugAttrs(t, "test", "1234")
  6046  	chg.AddTask(t)
  6047  
  6048  	// simulate a device that was known and connected before to only one consumer, this connection will be restored
  6049  	s.state.Set("conns", map[string]interface{}{
  6050  		"consumer:plug core:hotplugslot": map[string]interface{}{
  6051  			"interface":    "test",
  6052  			"hotplug-key":  "1234",
  6053  			"hotplug-gone": true,
  6054  		}})
  6055  
  6056  	s.state.Unlock()
  6057  	s.settle(c)
  6058  	s.state.Lock()
  6059  
  6060  	c.Assert(chg.Err(), IsNil)
  6061  
  6062  	// two connections now present (restored one for consumer, and new one for consumer2)
  6063  	var conns map[string]interface{}
  6064  	c.Assert(s.state.Get("conns", &conns), IsNil)
  6065  	c.Assert(conns, DeepEquals, map[string]interface{}{
  6066  		"consumer:plug core:hotplugslot": map[string]interface{}{
  6067  			"interface":   "test",
  6068  			"hotplug-key": "1234",
  6069  			"plug-static": map[string]interface{}{"attr1": "value1"},
  6070  		},
  6071  		"consumer2:plug core:hotplugslot": map[string]interface{}{
  6072  			"interface":   "test",
  6073  			"hotplug-key": "1234",
  6074  			"auto":        true,
  6075  			"plug-static": map[string]interface{}{"attr1": "value1"},
  6076  		}})
  6077  }
  6078  
  6079  func (s *interfaceManagerSuite) TestHotplugDisconnect(c *C) {
  6080  	coreInfo := s.mockSnap(c, coreSnapYaml)
  6081  	repo := s.manager(c).Repository()
  6082  	err := repo.AddInterface(&ifacetest.TestInterface{
  6083  		InterfaceName: "test",
  6084  	})
  6085  	c.Assert(err, IsNil)
  6086  	err = repo.AddSlot(&snap.SlotInfo{
  6087  		Snap:       coreInfo,
  6088  		Name:       "hotplugslot",
  6089  		Interface:  "test",
  6090  		HotplugKey: "1234",
  6091  	})
  6092  	c.Assert(err, IsNil)
  6093  
  6094  	s.state.Lock()
  6095  	defer s.state.Unlock()
  6096  
  6097  	// mock the consumer
  6098  	si := &snap.SideInfo{RealName: "consumer", Revision: snap.R(1)}
  6099  	testSnap := snaptest.MockSnapInstance(c, "", consumerYaml, si)
  6100  	c.Assert(testSnap.Plugs["plug"], NotNil)
  6101  	c.Assert(repo.AddPlug(testSnap.Plugs["plug"]), IsNil)
  6102  	snapstate.Set(s.state, "consumer", &snapstate.SnapState{
  6103  		Active:   true,
  6104  		Sequence: []*snap.SideInfo{si},
  6105  		Current:  snap.R(1),
  6106  		SnapType: "app",
  6107  	})
  6108  
  6109  	s.state.Set("hotplug-slots", map[string]interface{}{
  6110  		"hotplugslot": map[string]interface{}{
  6111  			"name":        "hotplugslot",
  6112  			"interface":   "test",
  6113  			"hotplug-key": "1234",
  6114  		}})
  6115  	s.state.Set("conns", map[string]interface{}{
  6116  		"consumer:plug core:hotplugslot": map[string]interface{}{
  6117  			"interface":   "test",
  6118  			"hotplug-key": "1234",
  6119  		}})
  6120  	_, err = repo.Connect(&interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  6121  		SlotRef: interfaces.SlotRef{Snap: "core", Name: "hotplugslot"}},
  6122  		nil, nil, nil, nil, nil)
  6123  	c.Assert(err, IsNil)
  6124  
  6125  	chg := s.state.NewChange("hotplug change", "")
  6126  	t := s.state.NewTask("hotplug-disconnect", "")
  6127  	t.Set("hotplug-key", "1234")
  6128  	t.Set("interface", "test")
  6129  	chg.AddTask(t)
  6130  
  6131  	s.state.Unlock()
  6132  	for i := 0; i < 3; i++ {
  6133  		s.se.Ensure()
  6134  		s.se.Wait()
  6135  	}
  6136  	s.state.Lock()
  6137  	c.Assert(chg.Err(), IsNil)
  6138  
  6139  	var byHotplug bool
  6140  	for _, t := range s.state.Tasks() {
  6141  		// the 'disconnect' task created by hotplug-disconnect should have by-hotplug flag set
  6142  		if t.Kind() == "disconnect" {
  6143  			c.Assert(t.Get("by-hotplug", &byHotplug), IsNil)
  6144  		}
  6145  	}
  6146  	c.Assert(byHotplug, Equals, true)
  6147  
  6148  	// hotplug-gone flag on the connection is set
  6149  	var conns map[string]interface{}
  6150  	c.Assert(s.state.Get("conns", &conns), IsNil)
  6151  	c.Assert(conns, DeepEquals, map[string]interface{}{
  6152  		"consumer:plug core:hotplugslot": map[string]interface{}{
  6153  			"interface":    "test",
  6154  			"hotplug-key":  "1234",
  6155  			"hotplug-gone": true,
  6156  		}})
  6157  }
  6158  
  6159  func (s *interfaceManagerSuite) testHotplugDisconnectWaitsForCoreRefresh(c *C, taskKind string) {
  6160  	coreInfo := s.mockSnap(c, coreSnapYaml)
  6161  
  6162  	repo := s.manager(c).Repository()
  6163  	err := repo.AddInterface(&ifacetest.TestInterface{
  6164  		InterfaceName: "test",
  6165  	})
  6166  	c.Assert(err, IsNil)
  6167  	err = repo.AddSlot(&snap.SlotInfo{
  6168  		Snap:       coreInfo,
  6169  		Name:       "hotplugslot",
  6170  		Interface:  "test",
  6171  		HotplugKey: "1234",
  6172  	})
  6173  	c.Assert(err, IsNil)
  6174  
  6175  	s.state.Lock()
  6176  	defer s.state.Unlock()
  6177  
  6178  	// mock the consumer
  6179  	si := &snap.SideInfo{RealName: "consumer", Revision: snap.R(1)}
  6180  	testSnap := snaptest.MockSnapInstance(c, "", consumerYaml, si)
  6181  	c.Assert(testSnap.Plugs["plug"], NotNil)
  6182  	c.Assert(repo.AddPlug(testSnap.Plugs["plug"]), IsNil)
  6183  	snapstate.Set(s.state, "consumer", &snapstate.SnapState{
  6184  		Active:   true,
  6185  		Sequence: []*snap.SideInfo{si},
  6186  		Current:  snap.R(1),
  6187  		SnapType: "app",
  6188  	})
  6189  
  6190  	s.state.Set("hotplug-slots", map[string]interface{}{
  6191  		"hotplugslot": map[string]interface{}{
  6192  			"name":        "hotplugslot",
  6193  			"interface":   "test",
  6194  			"hotplug-key": "1234",
  6195  		}})
  6196  	s.state.Set("conns", map[string]interface{}{
  6197  		"consumer:plug core:hotplugslot": map[string]interface{}{
  6198  			"interface":   "test",
  6199  			"hotplug-key": "1234",
  6200  		}})
  6201  	_, err = repo.Connect(&interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  6202  		SlotRef: interfaces.SlotRef{Snap: "core", Name: "hotplugslot"}},
  6203  		nil, nil, nil, nil, nil)
  6204  	c.Assert(err, IsNil)
  6205  
  6206  	chg := s.state.NewChange("hotplug change", "")
  6207  	t := s.state.NewTask("hotplug-disconnect", "")
  6208  	ifacestate.SetHotplugAttrs(t, "test", "1234")
  6209  	chg.AddTask(t)
  6210  
  6211  	chg2 := s.state.NewChange("other-chg", "...")
  6212  	t2 := s.state.NewTask(taskKind, "...")
  6213  	t2.Set("snap-setup", &snapstate.SnapSetup{SideInfo: &snap.SideInfo{RealName: "core"}})
  6214  	chg2.AddTask(t2)
  6215  	t3 := s.state.NewTask("other", "")
  6216  	t2.WaitFor(t3)
  6217  	t3.SetStatus(state.HoldStatus)
  6218  	chg2.AddTask(t3)
  6219  
  6220  	s.state.Unlock()
  6221  	for i := 0; i < 3; i++ {
  6222  		s.se.Ensure()
  6223  		s.se.Wait()
  6224  	}
  6225  	s.state.Lock()
  6226  	c.Assert(chg.Err(), IsNil)
  6227  
  6228  	c.Assert(strings.Join(t.Log(), ""), Matches, `.*Waiting for conflicting change in progress:.*`)
  6229  	c.Assert(chg.Status(), Equals, state.DoingStatus)
  6230  
  6231  	t2.SetStatus(state.DoneStatus)
  6232  	t3.SetStatus(state.DoneStatus)
  6233  
  6234  	s.state.Unlock()
  6235  	for i := 0; i < 3; i++ {
  6236  		s.se.Ensure()
  6237  		s.se.Wait()
  6238  	}
  6239  	s.state.Lock()
  6240  
  6241  	c.Assert(chg.Err(), IsNil)
  6242  	c.Assert(chg.Status(), Equals, state.DoneStatus)
  6243  }
  6244  
  6245  func (s *interfaceManagerSuite) TestHotplugDisconnectWaitsForCoreSetupProfiles(c *C) {
  6246  	s.testHotplugDisconnectWaitsForCoreRefresh(c, "setup-profiles")
  6247  }
  6248  
  6249  func (s *interfaceManagerSuite) TestHotplugDisconnectWaitsForCoreLnkSnap(c *C) {
  6250  	s.testHotplugDisconnectWaitsForCoreRefresh(c, "link-snap")
  6251  }
  6252  
  6253  func (s *interfaceManagerSuite) TestHotplugDisconnectWaitsForCoreUnlinkSnap(c *C) {
  6254  	s.testHotplugDisconnectWaitsForCoreRefresh(c, "unlink-snap")
  6255  }
  6256  
  6257  func (s *interfaceManagerSuite) TestHotplugDisconnectWaitsForDisconnectPlug(c *C) {
  6258  	coreInfo := s.mockSnap(c, coreSnapYaml)
  6259  
  6260  	repo := s.manager(c).Repository()
  6261  	err := repo.AddInterface(&ifacetest.TestInterface{
  6262  		InterfaceName: "test",
  6263  	})
  6264  	c.Assert(err, IsNil)
  6265  	err = repo.AddSlot(&snap.SlotInfo{
  6266  		Snap:       coreInfo,
  6267  		Name:       "hotplugslot",
  6268  		Interface:  "test",
  6269  		HotplugKey: "1234",
  6270  	})
  6271  	c.Assert(err, IsNil)
  6272  
  6273  	s.state.Lock()
  6274  	defer s.state.Unlock()
  6275  
  6276  	// mock the consumer
  6277  	si := &snap.SideInfo{RealName: "consumer", Revision: snap.R(1)}
  6278  	testSnap := snaptest.MockSnapInstance(c, "", consumerYaml, si)
  6279  	c.Assert(testSnap.Plugs["plug"], NotNil)
  6280  	c.Assert(repo.AddPlug(testSnap.Plugs["plug"]), IsNil)
  6281  	snapstate.Set(s.state, "consumer", &snapstate.SnapState{
  6282  		Active:   true,
  6283  		Sequence: []*snap.SideInfo{si},
  6284  		Current:  snap.R(1),
  6285  		SnapType: "app",
  6286  	})
  6287  
  6288  	s.state.Set("hotplug-slots", map[string]interface{}{
  6289  		"hotplugslot": map[string]interface{}{
  6290  			"name":        "hotplugslot",
  6291  			"interface":   "test",
  6292  			"hotplug-key": "1234",
  6293  		}})
  6294  	s.state.Set("conns", map[string]interface{}{
  6295  		"consumer:plug core:hotplugslot": map[string]interface{}{
  6296  			"interface":   "test",
  6297  			"hotplug-key": "1234",
  6298  		}})
  6299  	conn, err := repo.Connect(&interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  6300  		SlotRef: interfaces.SlotRef{Snap: "core", Name: "hotplugslot"}},
  6301  		nil, nil, nil, nil, nil)
  6302  	c.Assert(err, IsNil)
  6303  
  6304  	hotplugChg := s.state.NewChange("hotplug change", "")
  6305  	hotplugDisconnect := s.state.NewTask("hotplug-disconnect", "")
  6306  	ifacestate.SetHotplugAttrs(hotplugDisconnect, "test", "1234")
  6307  	hotplugChg.AddTask(hotplugDisconnect)
  6308  
  6309  	disconnectChg := s.state.NewChange("disconnect change", "...")
  6310  	disconnectTs, err := ifacestate.Disconnect(s.state, conn)
  6311  	c.Assert(err, IsNil)
  6312  	disconnectChg.AddAll(disconnectTs)
  6313  
  6314  	holdingTask := s.state.NewTask("other", "")
  6315  	disconnectTs.WaitFor(holdingTask)
  6316  	holdingTask.SetStatus(state.HoldStatus)
  6317  	disconnectChg.AddTask(holdingTask)
  6318  
  6319  	s.state.Unlock()
  6320  	for i := 0; i < 3; i++ {
  6321  		s.se.Ensure()
  6322  		s.se.Wait()
  6323  	}
  6324  	s.state.Lock()
  6325  	c.Assert(hotplugChg.Err(), IsNil)
  6326  
  6327  	c.Assert(strings.Join(hotplugDisconnect.Log(), ""), Matches, `.*Waiting for conflicting change in progress: conflicting plug snap consumer.*`)
  6328  	c.Assert(hotplugChg.Status(), Equals, state.DoingStatus)
  6329  
  6330  	for _, t := range disconnectTs.Tasks() {
  6331  		t.SetStatus(state.DoneStatus)
  6332  	}
  6333  	holdingTask.SetStatus(state.DoneStatus)
  6334  
  6335  	s.state.Unlock()
  6336  	for i := 0; i < 3; i++ {
  6337  		s.se.Ensure()
  6338  		s.se.Wait()
  6339  	}
  6340  	s.state.Lock()
  6341  
  6342  	c.Assert(hotplugChg.Err(), IsNil)
  6343  	c.Assert(hotplugChg.Status(), Equals, state.DoneStatus)
  6344  }
  6345  
  6346  func (s *interfaceManagerSuite) testHotplugAddNewSlot(c *C, devData map[string]string, specName, expectedName string) {
  6347  	_ = s.mockSnap(c, coreSnapYaml)
  6348  	repo := s.manager(c).Repository()
  6349  	err := repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  6350  	c.Assert(err, IsNil)
  6351  
  6352  	s.state.Lock()
  6353  	defer s.state.Unlock()
  6354  
  6355  	chg := s.state.NewChange("hotplug change", "")
  6356  	t := s.state.NewTask("hotplug-add-slot", "")
  6357  	t.Set("hotplug-key", "1234")
  6358  	t.Set("interface", "test")
  6359  	proposedSlot := hotplug.ProposedSlot{Name: specName, Attrs: map[string]interface{}{"foo": "bar"}}
  6360  	t.Set("proposed-slot", proposedSlot)
  6361  	devinfo, _ := hotplug.NewHotplugDeviceInfo(devData)
  6362  	t.Set("device-info", devinfo)
  6363  	chg.AddTask(t)
  6364  
  6365  	s.state.Unlock()
  6366  	s.se.Ensure()
  6367  	s.se.Wait()
  6368  	s.state.Lock()
  6369  
  6370  	c.Assert(chg.Err(), IsNil)
  6371  
  6372  	// hotplugslot is created in the repository
  6373  	slot := repo.Slot("core", expectedName)
  6374  	c.Assert(slot, NotNil)
  6375  	c.Check(slot.Attrs, DeepEquals, map[string]interface{}{"foo": "bar"})
  6376  	c.Check(slot.HotplugKey, Equals, snap.HotplugKey("1234"))
  6377  
  6378  	var hotplugSlots map[string]interface{}
  6379  	c.Assert(s.state.Get("hotplug-slots", &hotplugSlots), IsNil)
  6380  	c.Assert(hotplugSlots, HasLen, 1)
  6381  	c.Check(hotplugSlots[expectedName], DeepEquals, map[string]interface{}{
  6382  		"name":         expectedName,
  6383  		"interface":    "test",
  6384  		"hotplug-key":  "1234",
  6385  		"static-attrs": map[string]interface{}{"foo": "bar"},
  6386  		"hotplug-gone": false,
  6387  	})
  6388  }
  6389  
  6390  func (s *interfaceManagerSuite) TestHotplugAddNewSlotWithNameFromSpec(c *C) {
  6391  	s.testHotplugAddNewSlot(c, map[string]string{"DEVPATH": "/a", "NAME": "hdcamera"}, "hotplugslot", "hotplugslot")
  6392  }
  6393  
  6394  func (s *interfaceManagerSuite) TestHotplugAddNewSlotWithNameFromDevice(c *C) {
  6395  	s.testHotplugAddNewSlot(c, map[string]string{"DEVPATH": "/a", "NAME": "hdcamera"}, "", "hdcamera")
  6396  }
  6397  
  6398  func (s *interfaceManagerSuite) TestHotplugAddNewSlotWithNameFromInterface(c *C) {
  6399  	s.testHotplugAddNewSlot(c, map[string]string{"DEVPATH": "/a"}, "", "test")
  6400  }
  6401  
  6402  func (s *interfaceManagerSuite) TestHotplugAddGoneSlot(c *C) {
  6403  	_ = s.mockSnap(c, coreSnapYaml)
  6404  	repo := s.manager(c).Repository()
  6405  	err := repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  6406  	c.Assert(err, IsNil)
  6407  
  6408  	s.state.Lock()
  6409  	defer s.state.Unlock()
  6410  
  6411  	s.state.Set("hotplug-slots", map[string]interface{}{
  6412  		"hotplugslot-old-name": map[string]interface{}{
  6413  			"name":         "hotplugslot-old-name",
  6414  			"interface":    "test",
  6415  			"static-attrs": map[string]interface{}{"foo": "old"},
  6416  			"hotplug-key":  "1234",
  6417  			"hotplug-gone": true,
  6418  		}})
  6419  
  6420  	chg := s.state.NewChange("hotplug change", "")
  6421  	t := s.state.NewTask("hotplug-add-slot", "")
  6422  	t.Set("hotplug-key", "1234")
  6423  	t.Set("interface", "test")
  6424  	proposedSlot := hotplug.ProposedSlot{Name: "hotplugslot", Label: "", Attrs: map[string]interface{}{"foo": "bar"}}
  6425  	t.Set("proposed-slot", proposedSlot)
  6426  	t.Set("device-info", map[string]string{"DEVPATH": "/a", "NAME": "hdcamera"})
  6427  	chg.AddTask(t)
  6428  
  6429  	s.state.Unlock()
  6430  	s.se.Ensure()
  6431  	s.se.Wait()
  6432  	s.state.Lock()
  6433  
  6434  	c.Assert(chg.Err(), IsNil)
  6435  
  6436  	// hotplugslot is re-created in the repository, reuses old name and has new attributes
  6437  	slot := repo.Slot("core", "hotplugslot-old-name")
  6438  	c.Assert(slot, NotNil)
  6439  	c.Check(slot.Attrs, DeepEquals, map[string]interface{}{"foo": "bar"})
  6440  	c.Check(slot.HotplugKey, DeepEquals, snap.HotplugKey("1234"))
  6441  
  6442  	var hotplugSlots map[string]interface{}
  6443  	c.Assert(s.state.Get("hotplug-slots", &hotplugSlots), IsNil)
  6444  	c.Check(hotplugSlots, DeepEquals, map[string]interface{}{
  6445  		"hotplugslot-old-name": map[string]interface{}{
  6446  			"name":         "hotplugslot-old-name",
  6447  			"interface":    "test",
  6448  			"hotplug-key":  "1234",
  6449  			"static-attrs": map[string]interface{}{"foo": "bar"},
  6450  			"hotplug-gone": false,
  6451  		}})
  6452  }
  6453  
  6454  func (s *interfaceManagerSuite) TestHotplugAddSlotWithChangedAttrs(c *C) {
  6455  	coreInfo := s.mockSnap(c, coreSnapYaml)
  6456  	repo := s.manager(c).Repository()
  6457  	err := repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  6458  	c.Assert(err, IsNil)
  6459  
  6460  	s.state.Lock()
  6461  	defer s.state.Unlock()
  6462  
  6463  	s.state.Set("hotplug-slots", map[string]interface{}{
  6464  		"hotplugslot": map[string]interface{}{
  6465  			"name":         "hotplugslot",
  6466  			"interface":    "test",
  6467  			"static-attrs": map[string]interface{}{"foo": "old"},
  6468  			"hotplug-key":  "1234",
  6469  		}})
  6470  	c.Assert(repo.AddSlot(&snap.SlotInfo{
  6471  		Snap:       coreInfo,
  6472  		Name:       "hotplugslot",
  6473  		Interface:  "test",
  6474  		Attrs:      map[string]interface{}{"foo": "oldfoo"},
  6475  		HotplugKey: "1234",
  6476  	}), IsNil)
  6477  
  6478  	chg := s.state.NewChange("hotplug change", "")
  6479  	t := s.state.NewTask("hotplug-add-slot", "")
  6480  	t.Set("hotplug-key", "1234")
  6481  	t.Set("interface", "test")
  6482  	proposedSlot := hotplug.ProposedSlot{Name: "hotplugslot", Label: "", Attrs: map[string]interface{}{"foo": "newfoo"}}
  6483  	t.Set("proposed-slot", proposedSlot)
  6484  	devinfo, _ := hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "/a"})
  6485  	t.Set("device-info", devinfo)
  6486  	chg.AddTask(t)
  6487  
  6488  	s.state.Unlock()
  6489  	for i := 0; i < 5; i++ {
  6490  		s.se.Ensure()
  6491  		s.se.Wait()
  6492  	}
  6493  	s.state.Lock()
  6494  	c.Assert(chg.Err(), IsNil)
  6495  	c.Assert(chg.Status(), Equals, state.DoneStatus)
  6496  
  6497  	// hotplugslot is re-created in the repository
  6498  	slot := repo.Slot("core", "hotplugslot")
  6499  	c.Assert(slot, NotNil)
  6500  	c.Check(slot.Attrs, DeepEquals, map[string]interface{}{"foo": "newfoo"})
  6501  	c.Check(slot.HotplugKey, DeepEquals, snap.HotplugKey("1234"))
  6502  
  6503  	var hotplugSlots map[string]interface{}
  6504  	c.Assert(s.state.Get("hotplug-slots", &hotplugSlots), IsNil)
  6505  	c.Check(hotplugSlots, DeepEquals, map[string]interface{}{
  6506  		"hotplugslot": map[string]interface{}{
  6507  			"name":         "hotplugslot",
  6508  			"interface":    "test",
  6509  			"hotplug-key":  "1234",
  6510  			"static-attrs": map[string]interface{}{"foo": "newfoo"},
  6511  			"hotplug-gone": false,
  6512  		}})
  6513  }
  6514  
  6515  func (s *interfaceManagerSuite) TestHotplugUpdateSlot(c *C) {
  6516  	coreInfo := s.mockSnap(c, coreSnapYaml)
  6517  	repo := s.manager(c).Repository()
  6518  	err := repo.AddInterface(&ifacetest.TestInterface{
  6519  		InterfaceName: "test",
  6520  	})
  6521  	c.Assert(err, IsNil)
  6522  	err = repo.AddSlot(&snap.SlotInfo{
  6523  		Snap:       coreInfo,
  6524  		Name:       "hotplugslot",
  6525  		Interface:  "test",
  6526  		HotplugKey: "1234",
  6527  	})
  6528  	c.Assert(err, IsNil)
  6529  
  6530  	// sanity check
  6531  	c.Assert(repo.Slot("core", "hotplugslot"), NotNil)
  6532  
  6533  	s.state.Lock()
  6534  	defer s.state.Unlock()
  6535  
  6536  	s.state.Set("hotplug-slots", map[string]interface{}{
  6537  		"hotplugslot": map[string]interface{}{
  6538  			"name":        "hotplugslot",
  6539  			"interface":   "test",
  6540  			"hotplug-key": "1234",
  6541  		}})
  6542  
  6543  	chg := s.state.NewChange("hotplug change", "")
  6544  	t := s.state.NewTask("hotplug-update-slot", "")
  6545  	t.Set("hotplug-key", "1234")
  6546  	t.Set("interface", "test")
  6547  	t.Set("slot-attrs", map[string]interface{}{"foo": "bar"})
  6548  	chg.AddTask(t)
  6549  
  6550  	s.state.Unlock()
  6551  	s.se.Ensure()
  6552  	s.se.Wait()
  6553  	s.state.Lock()
  6554  
  6555  	c.Assert(chg.Err(), IsNil)
  6556  
  6557  	// hotplugslot is updated in the repository
  6558  	slot := repo.Slot("core", "hotplugslot")
  6559  	c.Assert(slot, NotNil)
  6560  	c.Assert(slot.Attrs, DeepEquals, map[string]interface{}{"foo": "bar"})
  6561  
  6562  	var hotplugSlots map[string]interface{}
  6563  	c.Assert(s.state.Get("hotplug-slots", &hotplugSlots), IsNil)
  6564  	c.Assert(hotplugSlots, DeepEquals, map[string]interface{}{
  6565  		"hotplugslot": map[string]interface{}{
  6566  			"name":         "hotplugslot",
  6567  			"interface":    "test",
  6568  			"hotplug-key":  "1234",
  6569  			"static-attrs": map[string]interface{}{"foo": "bar"},
  6570  			"hotplug-gone": false,
  6571  		}})
  6572  }
  6573  
  6574  func (s *interfaceManagerSuite) TestHotplugUpdateSlotWhenConnected(c *C) {
  6575  	coreInfo := s.mockSnap(c, coreSnapYaml)
  6576  	consumer := s.mockSnap(c, consumerYaml)
  6577  	repo := s.manager(c).Repository()
  6578  	err := repo.AddInterface(&ifacetest.TestInterface{
  6579  		InterfaceName: "test",
  6580  	})
  6581  	c.Assert(err, IsNil)
  6582  	err = repo.AddSlot(&snap.SlotInfo{
  6583  		Snap:       coreInfo,
  6584  		Name:       "hotplugslot",
  6585  		Interface:  "test",
  6586  		HotplugKey: "1234",
  6587  	})
  6588  	c.Assert(err, IsNil)
  6589  	err = repo.AddPlug(consumer.Plugs["plug"])
  6590  	c.Assert(err, IsNil)
  6591  
  6592  	// sanity check
  6593  	c.Assert(repo.Slot("core", "hotplugslot"), NotNil)
  6594  
  6595  	s.state.Lock()
  6596  	defer s.state.Unlock()
  6597  
  6598  	s.state.Set("hotplug-slots", map[string]interface{}{
  6599  		"hotplugslot": map[string]interface{}{
  6600  			"name":        "hotplugslot",
  6601  			"interface":   "test",
  6602  			"hotplug-key": "1234",
  6603  		}})
  6604  	s.state.Set("conns", map[string]interface{}{
  6605  		"consumer:plug core:hotplugslot": map[string]interface{}{
  6606  			"interface":    "test",
  6607  			"hotplug-key":  "1234",
  6608  			"hotplug-gone": true,
  6609  		}})
  6610  	_, err = repo.Connect(&interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  6611  		SlotRef: interfaces.SlotRef{Snap: "core", Name: "hotplugslot"}},
  6612  		nil, nil, nil, nil, nil)
  6613  	c.Assert(err, IsNil)
  6614  
  6615  	chg := s.state.NewChange("hotplug change", "")
  6616  	t := s.state.NewTask("hotplug-update-slot", "")
  6617  	t.Set("hotplug-key", "1234")
  6618  	t.Set("interface", "test")
  6619  	t.Set("slot-attrs", map[string]interface{}{})
  6620  	chg.AddTask(t)
  6621  
  6622  	s.state.Unlock()
  6623  	s.se.Ensure()
  6624  	s.se.Wait()
  6625  	s.state.Lock()
  6626  
  6627  	c.Assert(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*internal error: cannot update slot hotplugslot while connected.*`)
  6628  
  6629  	// hotplugslot is not removed because of existing connection
  6630  	c.Assert(repo.Slot("core", "hotplugslot"), NotNil)
  6631  
  6632  	var hotplugSlots map[string]interface{}
  6633  	c.Assert(s.state.Get("hotplug-slots", &hotplugSlots), IsNil)
  6634  	c.Assert(hotplugSlots, DeepEquals, map[string]interface{}{
  6635  		"hotplugslot": map[string]interface{}{
  6636  			"name":        "hotplugslot",
  6637  			"interface":   "test",
  6638  			"hotplug-key": "1234",
  6639  		}})
  6640  }
  6641  
  6642  func (s *interfaceManagerSuite) TestHotplugRemoveSlot(c *C) {
  6643  	coreInfo := s.mockSnap(c, coreSnapYaml)
  6644  	repo := s.manager(c).Repository()
  6645  	err := repo.AddInterface(&ifacetest.TestInterface{
  6646  		InterfaceName: "test",
  6647  	})
  6648  	c.Assert(err, IsNil)
  6649  	err = repo.AddSlot(&snap.SlotInfo{
  6650  		Snap:       coreInfo,
  6651  		Name:       "hotplugslot",
  6652  		Interface:  "test",
  6653  		HotplugKey: "1234",
  6654  	})
  6655  	c.Assert(err, IsNil)
  6656  
  6657  	// sanity check
  6658  	c.Assert(repo.Slot("core", "hotplugslot"), NotNil)
  6659  
  6660  	s.state.Lock()
  6661  	defer s.state.Unlock()
  6662  
  6663  	s.state.Set("hotplug-slots", map[string]interface{}{
  6664  		"hotplugslot": map[string]interface{}{
  6665  			"name":        "hotplugslot",
  6666  			"interface":   "test",
  6667  			"hotplug-key": "1234",
  6668  		},
  6669  		"otherslot": map[string]interface{}{
  6670  			"name":        "otherslot",
  6671  			"interface":   "test",
  6672  			"hotplug-key": "5678",
  6673  		}})
  6674  
  6675  	chg := s.state.NewChange("hotplug change", "")
  6676  	t := s.state.NewTask("hotplug-remove-slot", "")
  6677  	t.Set("hotplug-key", "1234")
  6678  	t.Set("interface", "test")
  6679  	chg.AddTask(t)
  6680  
  6681  	s.state.Unlock()
  6682  	s.se.Ensure()
  6683  	s.se.Wait()
  6684  	s.state.Lock()
  6685  
  6686  	c.Assert(chg.Err(), IsNil)
  6687  
  6688  	// hotplugslot is removed from the repository and from the state
  6689  	c.Assert(repo.Slot("core", "hotplugslot"), IsNil)
  6690  	slot, err := repo.SlotForHotplugKey("test", "1234")
  6691  	c.Assert(err, IsNil)
  6692  	c.Assert(slot, IsNil)
  6693  
  6694  	var hotplugSlots map[string]interface{}
  6695  	c.Assert(s.state.Get("hotplug-slots", &hotplugSlots), IsNil)
  6696  	c.Assert(hotplugSlots, DeepEquals, map[string]interface{}{
  6697  		"otherslot": map[string]interface{}{
  6698  			"name":         "otherslot",
  6699  			"interface":    "test",
  6700  			"hotplug-key":  "5678",
  6701  			"hotplug-gone": false,
  6702  		}})
  6703  }
  6704  
  6705  func (s *interfaceManagerSuite) TestHotplugRemoveSlotWhenConnected(c *C) {
  6706  	coreInfo := s.mockSnap(c, coreSnapYaml)
  6707  	repo := s.manager(c).Repository()
  6708  	err := repo.AddInterface(&ifacetest.TestInterface{
  6709  		InterfaceName: "test",
  6710  	})
  6711  	c.Assert(err, IsNil)
  6712  	err = repo.AddSlot(&snap.SlotInfo{
  6713  		Snap:       coreInfo,
  6714  		Name:       "hotplugslot",
  6715  		Interface:  "test",
  6716  		HotplugKey: "1234",
  6717  	})
  6718  	c.Assert(err, IsNil)
  6719  
  6720  	// sanity check
  6721  	c.Assert(repo.Slot("core", "hotplugslot"), NotNil)
  6722  
  6723  	s.state.Lock()
  6724  	defer s.state.Unlock()
  6725  
  6726  	s.state.Set("hotplug-slots", map[string]interface{}{
  6727  		"hotplugslot": map[string]interface{}{
  6728  			"name":        "hotplugslot",
  6729  			"interface":   "test",
  6730  			"hotplug-key": "1234",
  6731  		}})
  6732  	s.state.Set("conns", map[string]interface{}{
  6733  		"consumer:plug core:hotplugslot": map[string]interface{}{
  6734  			"interface":    "test",
  6735  			"hotplug-key":  "1234",
  6736  			"hotplug-gone": true,
  6737  		}})
  6738  
  6739  	chg := s.state.NewChange("hotplug change", "")
  6740  	t := s.state.NewTask("hotplug-remove-slot", "")
  6741  	t.Set("hotplug-key", "1234")
  6742  	t.Set("interface", "test")
  6743  	chg.AddTask(t)
  6744  
  6745  	s.state.Unlock()
  6746  	s.se.Ensure()
  6747  	s.se.Wait()
  6748  	s.state.Lock()
  6749  
  6750  	c.Assert(chg.Err(), IsNil)
  6751  
  6752  	// hotplugslot is removed from the repository but not from the state, because of existing connection
  6753  	c.Assert(repo.Slot("core", "hotplugslot"), IsNil)
  6754  	slot, err := repo.SlotForHotplugKey("test", "1234")
  6755  	c.Assert(err, IsNil)
  6756  	c.Assert(slot, IsNil)
  6757  
  6758  	var hotplugSlots map[string]interface{}
  6759  	c.Assert(s.state.Get("hotplug-slots", &hotplugSlots), IsNil)
  6760  	c.Assert(hotplugSlots, DeepEquals, map[string]interface{}{
  6761  		"hotplugslot": map[string]interface{}{
  6762  			"name":         "hotplugslot",
  6763  			"interface":    "test",
  6764  			"hotplug-key":  "1234",
  6765  			"hotplug-gone": true,
  6766  		}})
  6767  }
  6768  
  6769  func (s *interfaceManagerSuite) TestHotplugSeqWaitTasks(c *C) {
  6770  	restore := ifacestate.MockHotplugRetryTimeout(5 * time.Millisecond)
  6771  	defer restore()
  6772  
  6773  	var order []int
  6774  	_ = s.manager(c)
  6775  	s.o.TaskRunner().AddHandler("witness", func(task *state.Task, tomb *tomb.Tomb) error {
  6776  		task.State().Lock()
  6777  		defer task.State().Unlock()
  6778  		var seq int
  6779  		c.Assert(task.Get("seq", &seq), IsNil)
  6780  		order = append(order, seq)
  6781  		return nil
  6782  	}, nil)
  6783  	s.st.Lock()
  6784  
  6785  	// create hotplug changes with witness task to track execution order
  6786  	for i := 10; i >= 1; i-- {
  6787  		chg := s.st.NewChange("hotplug-change", "")
  6788  		chg.Set("hotplug-key", "1234")
  6789  		chg.Set("hotplug-seq", i)
  6790  		t := s.st.NewTask("hotplug-seq-wait", "")
  6791  		witness := s.st.NewTask("witness", "")
  6792  		witness.Set("seq", i)
  6793  		witness.WaitFor(t)
  6794  		chg.AddTask(t)
  6795  		chg.AddTask(witness)
  6796  	}
  6797  
  6798  	s.st.Unlock()
  6799  
  6800  	s.settle(c)
  6801  
  6802  	s.st.Lock()
  6803  	defer s.st.Unlock()
  6804  
  6805  	c.Assert(order, DeepEquals, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10})
  6806  
  6807  	for _, chg := range s.st.Changes() {
  6808  		c.Assert(chg.Status(), Equals, state.DoneStatus)
  6809  	}
  6810  }
  6811  
  6812  func (s *interfaceManagerSuite) testConnectionStates(c *C, auto, byGadget, undesired, hotplugGone bool, expected map[string]ifacestate.ConnectionState) {
  6813  	slotSnap := s.mockSnap(c, producerYaml)
  6814  	plugSnap := s.mockSnap(c, consumerYaml)
  6815  
  6816  	mgr := s.manager(c)
  6817  
  6818  	conns, err := mgr.ConnectionStates()
  6819  	c.Assert(err, IsNil)
  6820  	c.Check(conns, HasLen, 0)
  6821  
  6822  	st := s.state
  6823  	st.Lock()
  6824  	sc, err := ifacestate.GetConns(st)
  6825  	c.Assert(err, IsNil)
  6826  
  6827  	slot := slotSnap.Slots["slot"]
  6828  	c.Assert(slot, NotNil)
  6829  	plug := plugSnap.Plugs["plug"]
  6830  	c.Assert(plug, NotNil)
  6831  	dynamicPlugAttrs := map[string]interface{}{"dynamic-number": 7}
  6832  	dynamicSlotAttrs := map[string]interface{}{"other-number": 9}
  6833  	// create connection in conns state
  6834  	conn := &interfaces.Connection{
  6835  		Plug: interfaces.NewConnectedPlug(plug, nil, dynamicPlugAttrs),
  6836  		Slot: interfaces.NewConnectedSlot(slot, nil, dynamicSlotAttrs),
  6837  	}
  6838  	ifacestate.UpdateConnectionInConnState(sc, conn, auto, byGadget, undesired, hotplugGone)
  6839  	ifacestate.SetConns(st, sc)
  6840  	st.Unlock()
  6841  
  6842  	conns, err = mgr.ConnectionStates()
  6843  	c.Assert(err, IsNil)
  6844  	c.Assert(conns, HasLen, 1)
  6845  	c.Check(conns, DeepEquals, expected)
  6846  }
  6847  
  6848  func (s *interfaceManagerSuite) TestConnectionStatesAutoManual(c *C) {
  6849  	var isAuto, byGadget, isUndesired, hotplugGone bool = true, false, false, false
  6850  	s.testConnectionStates(c, isAuto, byGadget, isUndesired, hotplugGone, map[string]ifacestate.ConnectionState{
  6851  		"consumer:plug producer:slot": {
  6852  			Interface: "test",
  6853  			Auto:      true,
  6854  			StaticPlugAttrs: map[string]interface{}{
  6855  				"attr1": "value1",
  6856  			},
  6857  			DynamicPlugAttrs: map[string]interface{}{
  6858  				"dynamic-number": int64(7),
  6859  			},
  6860  			StaticSlotAttrs: map[string]interface{}{
  6861  				"attr2": "value2",
  6862  			},
  6863  			DynamicSlotAttrs: map[string]interface{}{
  6864  				"other-number": int64(9),
  6865  			},
  6866  		}})
  6867  }
  6868  
  6869  func (s *interfaceManagerSuite) TestConnectionStatesGadget(c *C) {
  6870  	var isAuto, byGadget, isUndesired, hotplugGone bool = true, true, false, false
  6871  	s.testConnectionStates(c, isAuto, byGadget, isUndesired, hotplugGone, map[string]ifacestate.ConnectionState{
  6872  		"consumer:plug producer:slot": {
  6873  			Interface: "test",
  6874  			Auto:      true,
  6875  			ByGadget:  true,
  6876  			StaticPlugAttrs: map[string]interface{}{
  6877  				"attr1": "value1",
  6878  			},
  6879  			DynamicPlugAttrs: map[string]interface{}{
  6880  				"dynamic-number": int64(7),
  6881  			},
  6882  			StaticSlotAttrs: map[string]interface{}{
  6883  				"attr2": "value2",
  6884  			},
  6885  			DynamicSlotAttrs: map[string]interface{}{
  6886  				"other-number": int64(9),
  6887  			},
  6888  		}})
  6889  }
  6890  
  6891  func (s *interfaceManagerSuite) TestConnectionStatesUndesired(c *C) {
  6892  	var isAuto, byGadget, isUndesired, hotplugGone bool = true, false, true, false
  6893  	s.testConnectionStates(c, isAuto, byGadget, isUndesired, hotplugGone, map[string]ifacestate.ConnectionState{
  6894  		"consumer:plug producer:slot": {
  6895  			Interface: "test",
  6896  			Auto:      true,
  6897  			Undesired: true,
  6898  			StaticPlugAttrs: map[string]interface{}{
  6899  				"attr1": "value1",
  6900  			},
  6901  			DynamicPlugAttrs: map[string]interface{}{
  6902  				"dynamic-number": int64(7),
  6903  			},
  6904  			StaticSlotAttrs: map[string]interface{}{
  6905  				"attr2": "value2",
  6906  			},
  6907  			DynamicSlotAttrs: map[string]interface{}{
  6908  				"other-number": int64(9),
  6909  			},
  6910  		}})
  6911  }
  6912  
  6913  func (s *interfaceManagerSuite) TestConnectionStatesHotplugGone(c *C) {
  6914  	var isAuto, byGadget, isUndesired, hotplugGone bool = false, false, false, true
  6915  	s.testConnectionStates(c, isAuto, byGadget, isUndesired, hotplugGone, map[string]ifacestate.ConnectionState{
  6916  		"consumer:plug producer:slot": {
  6917  			Interface:   "test",
  6918  			HotplugGone: true,
  6919  			StaticPlugAttrs: map[string]interface{}{
  6920  				"attr1": "value1",
  6921  			},
  6922  			DynamicPlugAttrs: map[string]interface{}{
  6923  				"dynamic-number": int64(7),
  6924  			},
  6925  			StaticSlotAttrs: map[string]interface{}{
  6926  				"attr2": "value2",
  6927  			},
  6928  			DynamicSlotAttrs: map[string]interface{}{
  6929  				"other-number": int64(9),
  6930  			},
  6931  		}})
  6932  }
  6933  
  6934  const someSnapYaml = `name: some-snap
  6935  version: 1
  6936  plugs:
  6937    network:
  6938  `
  6939  
  6940  const ubuntucoreSnapYaml = `name: ubuntu-core
  6941  version: 1.0
  6942  type: os
  6943  slots:
  6944    network:
  6945  
  6946  `
  6947  const coreSnapYaml2 = `name: core
  6948  version: 1.0
  6949  type: os
  6950  slots:
  6951    network:
  6952  `
  6953  
  6954  func (s *interfaceManagerSuite) TestTransitionConnectionsCoreMigration(c *C) {
  6955  	mgr := s.manager(c)
  6956  
  6957  	st := s.st
  6958  	st.Lock()
  6959  	defer st.Unlock()
  6960  
  6961  	repo := mgr.Repository()
  6962  	snapstate.Set(st, "core", nil)
  6963  	snapstate.Set(st, "ubuntu-core", &snapstate.SnapState{
  6964  		Active:   true,
  6965  		Sequence: []*snap.SideInfo{{RealName: "ubuntu-core", SnapID: "ubuntu-core-snap-id", Revision: snap.R(1)}},
  6966  		Current:  snap.R(1),
  6967  		SnapType: "os",
  6968  		Channel:  "beta",
  6969  	})
  6970  
  6971  	si := snap.SideInfo{RealName: "some-snap", Revision: snap.R(-42)}
  6972  	someSnap := snaptest.MockSnap(c, someSnapYaml, &si)
  6973  	ubuntuCore := snaptest.MockSnap(c, ubuntucoreSnapYaml, &snap.SideInfo{
  6974  		RealName: "ubuntu-core",
  6975  		Revision: snap.R(1),
  6976  	})
  6977  	core := snaptest.MockSnap(c, coreSnapYaml2, &snap.SideInfo{
  6978  		RealName: "core",
  6979  		Revision: snap.R(1),
  6980  	})
  6981  
  6982  	c.Assert(repo.AddSnap(ubuntuCore), IsNil)
  6983  	c.Assert(repo.AddSnap(core), IsNil)
  6984  	c.Assert(repo.AddSnap(someSnap), IsNil)
  6985  
  6986  	_, err := repo.Connect(&interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "some-snap", Name: "network"}, SlotRef: interfaces.SlotRef{Snap: "ubuntu-core", Name: "network"}}, nil, nil, nil, nil, nil)
  6987  	c.Assert(err, IsNil)
  6988  	repoConns, err := repo.Connections("ubuntu-core")
  6989  	c.Assert(err, IsNil)
  6990  	c.Assert(repoConns, HasLen, 1)
  6991  
  6992  	st.Set("conns", map[string]interface{}{"some-snap:network ubuntu-core:network": map[string]interface{}{"interface": "network", "auto": true}})
  6993  
  6994  	c.Assert(mgr.TransitionConnectionsCoreMigration(st, "ubuntu-core", "core"), IsNil)
  6995  
  6996  	// check connections
  6997  	var conns map[string]interface{}
  6998  	st.Get("conns", &conns)
  6999  	c.Assert(conns, DeepEquals, map[string]interface{}{"some-snap:network core:network": map[string]interface{}{"interface": "network", "auto": true}})
  7000  
  7001  	repoConns, err = repo.Connections("ubuntu-core")
  7002  	c.Assert(err, IsNil)
  7003  	c.Assert(repoConns, HasLen, 0)
  7004  	repoConns, err = repo.Connections("core")
  7005  	c.Assert(err, IsNil)
  7006  	c.Assert(repoConns, HasLen, 1)
  7007  
  7008  	// migrate back (i.e. undo)
  7009  	c.Assert(mgr.TransitionConnectionsCoreMigration(st, "core", "ubuntu-core"), IsNil)
  7010  
  7011  	// check connections
  7012  	conns = nil
  7013  	st.Get("conns", &conns)
  7014  	c.Assert(conns, DeepEquals, map[string]interface{}{"some-snap:network ubuntu-core:network": map[string]interface{}{"interface": "network", "auto": true}})
  7015  	repoConns, err = repo.Connections("ubuntu-core")
  7016  	c.Assert(err, IsNil)
  7017  	c.Assert(repoConns, HasLen, 1)
  7018  	repoConns, err = repo.Connections("core")
  7019  	c.Assert(err, IsNil)
  7020  	c.Assert(repoConns, HasLen, 0)
  7021  }