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