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