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