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