gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/interfaces/repo_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 interfaces_test 21 22 import ( 23 "fmt" 24 "strings" 25 26 . "gopkg.in/check.v1" 27 28 . "github.com/snapcore/snapd/interfaces" 29 "github.com/snapcore/snapd/interfaces/ifacetest" 30 "github.com/snapcore/snapd/snap" 31 "github.com/snapcore/snapd/snap/snaptest" 32 "github.com/snapcore/snapd/testutil" 33 ) 34 35 type RepositorySuite struct { 36 testutil.BaseTest 37 iface Interface 38 plug *snap.PlugInfo 39 plugSelf *snap.PlugInfo 40 slot *snap.SlotInfo 41 emptyRepo *Repository 42 // Repository pre-populated with s.iface 43 testRepo *Repository 44 45 // "Core"-like snaps with the same set of interfaces. 46 coreSnap *snap.Info 47 ubuntuCoreSnap *snap.Info 48 snapdSnap *snap.Info 49 } 50 51 var _ = Suite(&RepositorySuite{ 52 iface: &ifacetest.TestInterface{ 53 InterfaceName: "interface", 54 }, 55 }) 56 57 const consumerYaml = ` 58 name: consumer 59 version: 0 60 apps: 61 app: 62 hooks: 63 configure: 64 plugs: 65 plug: 66 interface: interface 67 label: label 68 attr: value 69 ` 70 71 const producerYaml = ` 72 name: producer 73 version: 0 74 apps: 75 app: 76 hooks: 77 configure: 78 slots: 79 slot: 80 interface: interface 81 label: label 82 attr: value 83 plugs: 84 self: 85 interface: interface 86 label: label 87 ` 88 89 func (s *RepositorySuite) SetUpTest(c *C) { 90 s.BaseTest.SetUpTest(c) 91 s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})) 92 93 consumer := snaptest.MockInfo(c, consumerYaml, nil) 94 s.plug = consumer.Plugs["plug"] 95 producer := snaptest.MockInfo(c, producerYaml, nil) 96 s.slot = producer.Slots["slot"] 97 s.plugSelf = producer.Plugs["self"] 98 // NOTE: Each of the snaps below have one slot so that they can be picked 99 // up by the repository. Some tests rename the "slot" slot as appropriate. 100 s.ubuntuCoreSnap = snaptest.MockInfo(c, ` 101 name: ubuntu-core 102 version: 0 103 type: os 104 slots: 105 slot: 106 interface: interface 107 `, nil) 108 // NOTE: The core snap has a slot so that it shows up in the 109 // repository. The repository doesn't record snaps unless they 110 // have at least one interface. 111 s.coreSnap = snaptest.MockInfo(c, ` 112 name: core 113 version: 0 114 type: os 115 slots: 116 slot: 117 interface: interface 118 `, nil) 119 s.snapdSnap = snaptest.MockInfo(c, ` 120 name: snapd 121 version: 0 122 type: app 123 slots: 124 slot: 125 interface: interface 126 `, nil) 127 128 s.emptyRepo = NewRepository() 129 s.testRepo = NewRepository() 130 err := s.testRepo.AddInterface(s.iface) 131 c.Assert(err, IsNil) 132 } 133 134 func (s *RepositorySuite) TearDownTest(c *C) { 135 s.BaseTest.TearDownTest(c) 136 } 137 138 type instanceNameAndYaml struct { 139 Name string 140 Yaml string 141 } 142 143 func addPlugsSlotsFromInstances(c *C, repo *Repository, iys []instanceNameAndYaml) []*snap.Info { 144 result := make([]*snap.Info, 0, len(iys)) 145 for _, iy := range iys { 146 info := snaptest.MockInfo(c, iy.Yaml, nil) 147 if iy.Name != "" { 148 instanceName := iy.Name 149 c.Assert(snap.ValidateInstanceName(instanceName), IsNil) 150 _, info.InstanceKey = snap.SplitInstanceName(instanceName) 151 } 152 153 result = append(result, info) 154 for _, plugInfo := range info.Plugs { 155 err := repo.AddPlug(plugInfo) 156 c.Assert(err, IsNil) 157 } 158 for _, slotInfo := range info.Slots { 159 err := repo.AddSlot(slotInfo) 160 c.Assert(err, IsNil) 161 } 162 } 163 return result 164 } 165 166 // Tests for Repository.AddInterface() 167 168 func (s *RepositorySuite) TestAddInterface(c *C) { 169 // Adding a valid interfaces works 170 err := s.emptyRepo.AddInterface(s.iface) 171 c.Assert(err, IsNil) 172 c.Assert(s.emptyRepo.Interface(s.iface.Name()), Equals, s.iface) 173 } 174 175 func (s *RepositorySuite) TestAddInterfaceClash(c *C) { 176 iface1 := &ifacetest.TestInterface{InterfaceName: "iface"} 177 iface2 := &ifacetest.TestInterface{InterfaceName: "iface"} 178 err := s.emptyRepo.AddInterface(iface1) 179 c.Assert(err, IsNil) 180 // Adding an interface with the same name as another interface is not allowed 181 err = s.emptyRepo.AddInterface(iface2) 182 c.Assert(err, ErrorMatches, `cannot add interface: "iface", interface name is in use`) 183 c.Assert(s.emptyRepo.Interface(iface1.Name()), Equals, iface1) 184 } 185 186 func (s *RepositorySuite) TestAddInterfaceInvalidName(c *C) { 187 iface := &ifacetest.TestInterface{InterfaceName: "bad-name-"} 188 // Adding an interface with invalid name is not allowed 189 err := s.emptyRepo.AddInterface(iface) 190 c.Assert(err, ErrorMatches, `invalid interface name: "bad-name-"`) 191 c.Assert(s.emptyRepo.Interface(iface.Name()), IsNil) 192 } 193 194 // Tests for Repository.AllInterfaces() 195 196 func (s *RepositorySuite) TestAllInterfaces(c *C) { 197 c.Assert(s.emptyRepo.AllInterfaces(), HasLen, 0) 198 c.Assert(s.testRepo.AllInterfaces(), DeepEquals, []Interface{s.iface}) 199 200 // Add three interfaces in some non-sorted order. 201 i1 := &ifacetest.TestInterface{InterfaceName: "i1"} 202 i2 := &ifacetest.TestInterface{InterfaceName: "i2"} 203 i3 := &ifacetest.TestInterface{InterfaceName: "i3"} 204 c.Assert(s.emptyRepo.AddInterface(i3), IsNil) 205 c.Assert(s.emptyRepo.AddInterface(i1), IsNil) 206 c.Assert(s.emptyRepo.AddInterface(i2), IsNil) 207 208 // The result is always sorted. 209 c.Assert(s.emptyRepo.AllInterfaces(), DeepEquals, []Interface{i1, i2, i3}) 210 211 } 212 213 func (s *RepositorySuite) TestAddBackend(c *C) { 214 backend := &ifacetest.TestSecurityBackend{BackendName: "test"} 215 c.Assert(s.emptyRepo.AddBackend(backend), IsNil) 216 err := s.emptyRepo.AddBackend(backend) 217 c.Assert(err, ErrorMatches, `cannot add backend "test", security system name is in use`) 218 } 219 220 func (s *RepositorySuite) TestBackends(c *C) { 221 b1 := &ifacetest.TestSecurityBackend{BackendName: "b1"} 222 b2 := &ifacetest.TestSecurityBackend{BackendName: "b2"} 223 c.Assert(s.emptyRepo.AddBackend(b2), IsNil) 224 c.Assert(s.emptyRepo.AddBackend(b1), IsNil) 225 // The order of insertion is retained. 226 c.Assert(s.emptyRepo.Backends(), DeepEquals, []SecurityBackend{b2, b1}) 227 } 228 229 // Tests for Repository.Interface() 230 231 func (s *RepositorySuite) TestInterface(c *C) { 232 // Interface returns nil when it cannot be found 233 iface := s.emptyRepo.Interface(s.iface.Name()) 234 c.Assert(iface, IsNil) 235 c.Assert(s.emptyRepo.Interface(s.iface.Name()), IsNil) 236 err := s.emptyRepo.AddInterface(s.iface) 237 c.Assert(err, IsNil) 238 // Interface returns the found interface 239 iface = s.emptyRepo.Interface(s.iface.Name()) 240 c.Assert(iface, Equals, s.iface) 241 } 242 243 func (s *RepositorySuite) TestInterfaceSearch(c *C) { 244 ifaceA := &ifacetest.TestInterface{InterfaceName: "a"} 245 ifaceB := &ifacetest.TestInterface{InterfaceName: "b"} 246 ifaceC := &ifacetest.TestInterface{InterfaceName: "c"} 247 err := s.emptyRepo.AddInterface(ifaceA) 248 c.Assert(err, IsNil) 249 err = s.emptyRepo.AddInterface(ifaceB) 250 c.Assert(err, IsNil) 251 err = s.emptyRepo.AddInterface(ifaceC) 252 c.Assert(err, IsNil) 253 // Interface correctly finds interfaces 254 c.Assert(s.emptyRepo.Interface("a"), Equals, ifaceA) 255 c.Assert(s.emptyRepo.Interface("b"), Equals, ifaceB) 256 c.Assert(s.emptyRepo.Interface("c"), Equals, ifaceC) 257 } 258 259 // Tests for Repository.AddPlug() 260 261 func (s *RepositorySuite) TestAddPlug(c *C) { 262 c.Assert(s.testRepo.AllPlugs(""), HasLen, 0) 263 err := s.testRepo.AddPlug(s.plug) 264 c.Assert(err, IsNil) 265 c.Assert(s.testRepo.AllPlugs(""), HasLen, 1) 266 c.Assert(s.testRepo.Plug(s.plug.Snap.InstanceName(), s.plug.Name), DeepEquals, s.plug) 267 } 268 269 func (s *RepositorySuite) TestAddPlugClashingPlug(c *C) { 270 err := s.testRepo.AddPlug(s.plug) 271 c.Assert(err, IsNil) 272 err = s.testRepo.AddPlug(s.plug) 273 c.Assert(err, ErrorMatches, `snap "consumer" has plugs conflicting on name "plug"`) 274 c.Assert(s.testRepo.AllPlugs(""), HasLen, 1) 275 c.Assert(s.testRepo.Plug(s.plug.Snap.InstanceName(), s.plug.Name), DeepEquals, s.plug) 276 } 277 278 func (s *RepositorySuite) TestAddPlugClashingSlot(c *C) { 279 snapInfo := &snap.Info{SuggestedName: "snap"} 280 plug := &snap.PlugInfo{ 281 Snap: snapInfo, 282 Name: "clashing", 283 Interface: "interface", 284 } 285 slot := &snap.SlotInfo{ 286 Snap: snapInfo, 287 Name: "clashing", 288 Interface: "interface", 289 } 290 err := s.testRepo.AddSlot(slot) 291 c.Assert(err, IsNil) 292 err = s.testRepo.AddPlug(plug) 293 c.Assert(err, ErrorMatches, `snap "snap" has plug and slot conflicting on name "clashing"`) 294 c.Assert(s.testRepo.AllSlots(""), HasLen, 1) 295 c.Assert(s.testRepo.Slot(slot.Snap.InstanceName(), slot.Name), DeepEquals, slot) 296 } 297 298 func (s *RepositorySuite) TestAddPlugFailsWithInvalidSnapName(c *C) { 299 plug := &snap.PlugInfo{ 300 Snap: &snap.Info{SuggestedName: "bad-snap-"}, 301 Name: "interface", 302 Interface: "interface", 303 } 304 err := s.testRepo.AddPlug(plug) 305 c.Assert(err, ErrorMatches, `invalid snap name: "bad-snap-"`) 306 c.Assert(s.testRepo.AllPlugs(""), HasLen, 0) 307 } 308 309 func (s *RepositorySuite) TestAddPlugFailsWithInvalidPlugName(c *C) { 310 plug := &snap.PlugInfo{ 311 Snap: &snap.Info{SuggestedName: "snap"}, 312 Name: "bad-name-", 313 Interface: "interface", 314 } 315 err := s.testRepo.AddPlug(plug) 316 c.Assert(err, ErrorMatches, `invalid plug name: "bad-name-"`) 317 c.Assert(s.testRepo.AllPlugs(""), HasLen, 0) 318 } 319 320 func (s *RepositorySuite) TestAddPlugFailsWithUnknownInterface(c *C) { 321 err := s.emptyRepo.AddPlug(s.plug) 322 c.Assert(err, ErrorMatches, `cannot add plug, interface "interface" is not known`) 323 c.Assert(s.emptyRepo.AllPlugs(""), HasLen, 0) 324 } 325 326 func (s *RepositorySuite) TestAddPlugParallelInstance(c *C) { 327 c.Assert(s.testRepo.AllPlugs(""), HasLen, 0) 328 329 err := s.testRepo.AddPlug(s.plug) 330 c.Assert(err, IsNil) 331 c.Assert(s.testRepo.AllPlugs(""), HasLen, 1) 332 333 consumer := snaptest.MockInfo(c, consumerYaml, nil) 334 consumer.InstanceKey = "instance" 335 err = s.testRepo.AddPlug(consumer.Plugs["plug"]) 336 c.Assert(err, IsNil) 337 c.Assert(s.testRepo.AllPlugs(""), HasLen, 2) 338 339 c.Assert(s.testRepo.Plug(s.plug.Snap.InstanceName(), s.plug.Name), DeepEquals, s.plug) 340 c.Assert(s.testRepo.Plug(consumer.InstanceName(), "plug"), DeepEquals, consumer.Plugs["plug"]) 341 } 342 343 // Tests for Repository.Plug() 344 345 func (s *RepositorySuite) TestPlug(c *C) { 346 err := s.testRepo.AddPlug(s.plug) 347 c.Assert(err, IsNil) 348 c.Assert(s.emptyRepo.Plug(s.plug.Snap.InstanceName(), s.plug.Name), IsNil) 349 c.Assert(s.testRepo.Plug(s.plug.Snap.InstanceName(), s.plug.Name), DeepEquals, s.plug) 350 } 351 352 func (s *RepositorySuite) TestPlugSearch(c *C) { 353 addPlugsSlotsFromInstances(c, s.testRepo, []instanceNameAndYaml{ 354 {Name: "xx", Yaml: ` 355 name: xx 356 version: 0 357 plugs: 358 a: interface 359 b: interface 360 c: interface 361 `}, 362 {Name: "yy", Yaml: ` 363 name: yy 364 version: 0 365 plugs: 366 a: interface 367 b: interface 368 c: interface 369 `}, 370 {Name: "zz_instance", Yaml: ` 371 name: zz 372 version: 0 373 plugs: 374 a: interface 375 b: interface 376 c: interface 377 `}, 378 }) 379 // Plug() correctly finds plugs 380 c.Assert(s.testRepo.Plug("xx", "a"), Not(IsNil)) 381 c.Assert(s.testRepo.Plug("xx", "b"), Not(IsNil)) 382 c.Assert(s.testRepo.Plug("xx", "c"), Not(IsNil)) 383 c.Assert(s.testRepo.Plug("yy", "a"), Not(IsNil)) 384 c.Assert(s.testRepo.Plug("yy", "b"), Not(IsNil)) 385 c.Assert(s.testRepo.Plug("yy", "c"), Not(IsNil)) 386 c.Assert(s.testRepo.Plug("zz_instance", "a"), Not(IsNil)) 387 c.Assert(s.testRepo.Plug("zz_instance", "b"), Not(IsNil)) 388 c.Assert(s.testRepo.Plug("zz_instance", "c"), Not(IsNil)) 389 } 390 391 // Tests for Repository.RemovePlug() 392 393 func (s *RepositorySuite) TestRemovePlugSucceedsWhenPlugExistsAndDisconnected(c *C) { 394 err := s.testRepo.AddPlug(s.plug) 395 c.Assert(err, IsNil) 396 err = s.testRepo.RemovePlug(s.plug.Snap.InstanceName(), s.plug.Name) 397 c.Assert(err, IsNil) 398 c.Assert(s.testRepo.AllPlugs(""), HasLen, 0) 399 } 400 401 func (s *RepositorySuite) TestRemovePlugFailsWhenPlugDoesntExist(c *C) { 402 err := s.emptyRepo.RemovePlug(s.plug.Snap.InstanceName(), s.plug.Name) 403 c.Assert(err, ErrorMatches, `cannot remove plug "plug" from snap "consumer", no such plug`) 404 } 405 406 func (s *RepositorySuite) TestRemovePlugFailsWhenPlugIsConnected(c *C) { 407 err := s.testRepo.AddPlug(s.plug) 408 c.Assert(err, IsNil) 409 err = s.testRepo.AddSlot(s.slot) 410 c.Assert(err, IsNil) 411 connRef := NewConnRef(s.plug, s.slot) 412 _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil) 413 c.Assert(err, IsNil) 414 // Removing a plug used by a slot returns an appropriate error 415 err = s.testRepo.RemovePlug(s.plug.Snap.InstanceName(), s.plug.Name) 416 c.Assert(err, ErrorMatches, `cannot remove plug "plug" from snap "consumer", it is still connected`) 417 // The plug is still there 418 slot := s.testRepo.Plug(s.plug.Snap.InstanceName(), s.plug.Name) 419 c.Assert(slot, Not(IsNil)) 420 } 421 422 // Tests for Repository.AllPlugs() 423 424 func (s *RepositorySuite) TestAllPlugsWithoutInterfaceName(c *C) { 425 snaps := addPlugsSlotsFromInstances(c, s.testRepo, []instanceNameAndYaml{ 426 {Name: "snap-a", Yaml: ` 427 name: snap-a 428 version: 0 429 plugs: 430 name-a: interface 431 `}, 432 {Name: "snap-b", Yaml: ` 433 name: snap-b 434 version: 0 435 plugs: 436 name-a: interface 437 name-b: interface 438 name-c: interface 439 `}, 440 {Name: "snap-b_instance", Yaml: ` 441 name: snap-b 442 version: 0 443 plugs: 444 name-a: interface 445 name-b: interface 446 name-c: interface 447 `}, 448 }) 449 c.Assert(snaps, HasLen, 3) 450 // The result is sorted by snap and name 451 c.Assert(s.testRepo.AllPlugs(""), DeepEquals, []*snap.PlugInfo{ 452 snaps[0].Plugs["name-a"], 453 snaps[1].Plugs["name-a"], 454 snaps[1].Plugs["name-b"], 455 snaps[1].Plugs["name-c"], 456 snaps[2].Plugs["name-a"], 457 snaps[2].Plugs["name-b"], 458 snaps[2].Plugs["name-c"], 459 }) 460 } 461 462 func (s *RepositorySuite) TestAllPlugsWithInterfaceName(c *C) { 463 // Add another interface so that we can look for it 464 err := s.testRepo.AddInterface(&ifacetest.TestInterface{InterfaceName: "other-interface"}) 465 c.Assert(err, IsNil) 466 snaps := addPlugsSlotsFromInstances(c, s.testRepo, []instanceNameAndYaml{ 467 {Name: "snap-a", Yaml: ` 468 name: snap-a 469 version: 0 470 plugs: 471 name-a: interface 472 `}, 473 {Name: "snap-b", Yaml: ` 474 name: snap-b 475 version: 0 476 plugs: 477 name-a: interface 478 name-b: other-interface 479 name-c: interface 480 `}, 481 {Name: "snap-b_instance", Yaml: ` 482 name: snap-b 483 version: 0 484 plugs: 485 name-a: interface 486 name-b: other-interface 487 name-c: interface 488 `}, 489 }) 490 c.Assert(snaps, HasLen, 3) 491 c.Assert(s.testRepo.AllPlugs("other-interface"), DeepEquals, []*snap.PlugInfo{ 492 snaps[1].Plugs["name-b"], 493 snaps[2].Plugs["name-b"], 494 }) 495 } 496 497 // Tests for Repository.Plugs() 498 499 func (s *RepositorySuite) TestPlugs(c *C) { 500 snaps := addPlugsSlotsFromInstances(c, s.testRepo, []instanceNameAndYaml{ 501 {Name: "snap-a", Yaml: ` 502 name: snap-a 503 version: 0 504 plugs: 505 name-a: interface 506 `}, 507 {Name: "snap-b", Yaml: ` 508 name: snap-b 509 version: 0 510 plugs: 511 name-a: interface 512 name-b: interface 513 name-c: interface 514 `}, 515 {Name: "snap-b_instance", Yaml: ` 516 name: snap-b 517 version: 0 518 plugs: 519 name-a: interface 520 name-b: interface 521 name-c: interface 522 `}, 523 }) 524 c.Assert(snaps, HasLen, 3) 525 // The result is sorted by snap and name 526 c.Assert(s.testRepo.Plugs("snap-b"), DeepEquals, []*snap.PlugInfo{ 527 snaps[1].Plugs["name-a"], 528 snaps[1].Plugs["name-b"], 529 snaps[1].Plugs["name-c"], 530 }) 531 c.Assert(s.testRepo.Plugs("snap-b_instance"), DeepEquals, []*snap.PlugInfo{ 532 snaps[2].Plugs["name-a"], 533 snaps[2].Plugs["name-b"], 534 snaps[2].Plugs["name-c"], 535 }) 536 // The result is empty if the snap is not known 537 c.Assert(s.testRepo.Plugs("snap-x"), HasLen, 0) 538 c.Assert(s.testRepo.Plugs("snap-b_other"), HasLen, 0) 539 } 540 541 // Tests for Repository.AllSlots() 542 543 func (s *RepositorySuite) TestAllSlots(c *C) { 544 err := s.testRepo.AddInterface(&ifacetest.TestInterface{InterfaceName: "other-interface"}) 545 c.Assert(err, IsNil) 546 snaps := addPlugsSlotsFromInstances(c, s.testRepo, []instanceNameAndYaml{ 547 {Name: "snap-a", Yaml: ` 548 name: snap-a 549 version: 0 550 slots: 551 name-a: interface 552 name-b: interface 553 `}, 554 {Name: "snap-b", Yaml: ` 555 name: snap-b 556 version: 0 557 slots: 558 name-a: other-interface 559 `}, 560 {Name: "snap-b_instance", Yaml: ` 561 name: snap-b 562 version: 0 563 slots: 564 name-a: other-interface 565 `}, 566 }) 567 c.Assert(snaps, HasLen, 3) 568 // AllSlots("") returns all slots, sorted by snap and slot name 569 c.Assert(s.testRepo.AllSlots(""), DeepEquals, []*snap.SlotInfo{ 570 snaps[0].Slots["name-a"], 571 snaps[0].Slots["name-b"], 572 snaps[1].Slots["name-a"], 573 snaps[2].Slots["name-a"], 574 }) 575 // AllSlots("") returns all slots, sorted by snap and slot name 576 c.Assert(s.testRepo.AllSlots("other-interface"), DeepEquals, []*snap.SlotInfo{ 577 snaps[1].Slots["name-a"], 578 snaps[2].Slots["name-a"], 579 }) 580 } 581 582 // Tests for Repository.Slots() 583 584 func (s *RepositorySuite) TestSlots(c *C) { 585 snaps := addPlugsSlotsFromInstances(c, s.testRepo, []instanceNameAndYaml{ 586 {Name: "snap-a", Yaml: ` 587 name: snap-a 588 version: 0 589 slots: 590 name-a: interface 591 name-b: interface 592 `}, 593 {Name: "snap-b", Yaml: ` 594 name: snap-b 595 version: 0 596 slots: 597 name-a: interface 598 `}, 599 {Name: "snap-b_instance", Yaml: ` 600 name: snap-b 601 version: 0 602 slots: 603 name-a: interface 604 `}, 605 }) 606 // Slots("snap-a") returns slots present in that snap 607 c.Assert(s.testRepo.Slots("snap-a"), DeepEquals, []*snap.SlotInfo{ 608 snaps[0].Slots["name-a"], 609 snaps[0].Slots["name-b"], 610 }) 611 // Slots("snap-b") returns slots present in that snap 612 c.Assert(s.testRepo.Slots("snap-b"), DeepEquals, []*snap.SlotInfo{ 613 snaps[1].Slots["name-a"], 614 }) 615 // Slots("snap-b_instance") returns slots present in that snap 616 c.Assert(s.testRepo.Slots("snap-b_instance"), DeepEquals, []*snap.SlotInfo{ 617 snaps[2].Slots["name-a"], 618 }) 619 // Slots("snap-c") returns no slots (because that snap doesn't exist) 620 c.Assert(s.testRepo.Slots("snap-c"), HasLen, 0) 621 // Slots("snap-b_other") returns no slots (the snap does not exist) 622 c.Assert(s.testRepo.Slots("snap-b_other"), HasLen, 0) 623 // Slots("") returns no slots 624 c.Assert(s.testRepo.Slots(""), HasLen, 0) 625 } 626 627 // Tests for Repository.Slot() 628 629 func (s *RepositorySuite) TestSlotSucceedsWhenSlotExists(c *C) { 630 err := s.testRepo.AddSlot(s.slot) 631 c.Assert(err, IsNil) 632 slot := s.testRepo.Slot(s.slot.Snap.InstanceName(), s.slot.Name) 633 c.Assert(slot, DeepEquals, s.slot) 634 } 635 636 func (s *RepositorySuite) TestSlotFailsWhenSlotDoesntExist(c *C) { 637 slot := s.testRepo.Slot(s.slot.Snap.InstanceName(), s.slot.Name) 638 c.Assert(slot, IsNil) 639 } 640 641 // Tests for Repository.AddSlot() 642 643 func (s *RepositorySuite) TestAddSlotFailsWhenInterfaceIsUnknown(c *C) { 644 err := s.emptyRepo.AddSlot(s.slot) 645 c.Assert(err, ErrorMatches, `cannot add slot, interface "interface" is not known`) 646 } 647 648 func (s *RepositorySuite) TestAddSlotFailsWhenSlotNameIsInvalid(c *C) { 649 slot := &snap.SlotInfo{ 650 Snap: &snap.Info{SuggestedName: "snap"}, 651 Name: "bad-name-", 652 Interface: "interface", 653 } 654 err := s.emptyRepo.AddSlot(slot) 655 c.Assert(err, ErrorMatches, `invalid slot name: "bad-name-"`) 656 c.Assert(s.emptyRepo.AllSlots(""), HasLen, 0) 657 } 658 659 func (s *RepositorySuite) TestAddSlotFailsWithInvalidSnapName(c *C) { 660 slot := &snap.SlotInfo{ 661 Snap: &snap.Info{SuggestedName: "bad-snap-"}, 662 Name: "slot", 663 Interface: "interface", 664 } 665 err := s.emptyRepo.AddSlot(slot) 666 c.Assert(err, ErrorMatches, `invalid snap name: "bad-snap-"`) 667 c.Assert(s.emptyRepo.AllSlots(""), HasLen, 0) 668 } 669 670 func (s *RepositorySuite) TestAddSlotClashingSlot(c *C) { 671 // Adding the first slot succeeds 672 err := s.testRepo.AddSlot(s.slot) 673 c.Assert(err, IsNil) 674 // Adding the slot again fails with appropriate error 675 err = s.testRepo.AddSlot(s.slot) 676 c.Assert(err, ErrorMatches, `snap "producer" has slots conflicting on name "slot"`) 677 } 678 679 func (s *RepositorySuite) TestAddSlotClashingPlug(c *C) { 680 snapInfo := &snap.Info{SuggestedName: "snap"} 681 plug := &snap.PlugInfo{ 682 Snap: snapInfo, 683 Name: "clashing", 684 Interface: "interface", 685 } 686 slot := &snap.SlotInfo{ 687 Snap: snapInfo, 688 Name: "clashing", 689 Interface: "interface", 690 } 691 err := s.testRepo.AddPlug(plug) 692 c.Assert(err, IsNil) 693 err = s.testRepo.AddSlot(slot) 694 c.Assert(err, ErrorMatches, `snap "snap" has plug and slot conflicting on name "clashing"`) 695 c.Assert(s.testRepo.AllPlugs(""), HasLen, 1) 696 c.Assert(s.testRepo.Plug(plug.Snap.InstanceName(), plug.Name), DeepEquals, plug) 697 } 698 699 func (s *RepositorySuite) TestAddSlotStoresCorrectData(c *C) { 700 err := s.testRepo.AddSlot(s.slot) 701 c.Assert(err, IsNil) 702 slot := s.testRepo.Slot(s.slot.Snap.InstanceName(), s.slot.Name) 703 // The added slot has the same data 704 c.Assert(slot, DeepEquals, s.slot) 705 } 706 707 func (s *RepositorySuite) TestAddSlotParallelInstance(c *C) { 708 c.Assert(s.testRepo.AllSlots(""), HasLen, 0) 709 710 err := s.testRepo.AddSlot(s.slot) 711 c.Assert(err, IsNil) 712 c.Assert(s.testRepo.AllSlots(""), HasLen, 1) 713 714 producer := snaptest.MockInfo(c, producerYaml, nil) 715 producer.InstanceKey = "instance" 716 err = s.testRepo.AddSlot(producer.Slots["slot"]) 717 c.Assert(err, IsNil) 718 c.Assert(s.testRepo.AllSlots(""), HasLen, 2) 719 720 c.Assert(s.testRepo.Slot(s.slot.Snap.InstanceName(), s.slot.Name), DeepEquals, s.slot) 721 c.Assert(s.testRepo.Slot(producer.InstanceName(), "slot"), DeepEquals, producer.Slots["slot"]) 722 } 723 724 // Tests for Repository.RemoveSlot() 725 726 func (s *RepositorySuite) TestRemoveSlotSuccedsWhenSlotExistsAndDisconnected(c *C) { 727 err := s.testRepo.AddSlot(s.slot) 728 c.Assert(err, IsNil) 729 // Removing a vacant slot simply works 730 err = s.testRepo.RemoveSlot(s.slot.Snap.InstanceName(), s.slot.Name) 731 c.Assert(err, IsNil) 732 // The slot is gone now 733 slot := s.testRepo.Slot(s.slot.Snap.InstanceName(), s.slot.Name) 734 c.Assert(slot, IsNil) 735 } 736 737 func (s *RepositorySuite) TestRemoveSlotFailsWhenSlotDoesntExist(c *C) { 738 // Removing a slot that doesn't exist returns an appropriate error 739 err := s.testRepo.RemoveSlot(s.slot.Snap.InstanceName(), s.slot.Name) 740 c.Assert(err, Not(IsNil)) 741 c.Assert(err, ErrorMatches, `cannot remove slot "slot" from snap "producer", no such slot`) 742 } 743 744 func (s *RepositorySuite) TestRemoveSlotFailsWhenSlotIsConnected(c *C) { 745 err := s.testRepo.AddPlug(s.plug) 746 c.Assert(err, IsNil) 747 err = s.testRepo.AddSlot(s.slot) 748 c.Assert(err, IsNil) 749 connRef := NewConnRef(s.plug, s.slot) 750 _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil) 751 c.Assert(err, IsNil) 752 // Removing a slot occupied by a plug returns an appropriate error 753 err = s.testRepo.RemoveSlot(s.slot.Snap.InstanceName(), s.slot.Name) 754 c.Assert(err, ErrorMatches, `cannot remove slot "slot" from snap "producer", it is still connected`) 755 // The slot is still there 756 slot := s.testRepo.Slot(s.slot.Snap.InstanceName(), s.slot.Name) 757 c.Assert(slot, Not(IsNil)) 758 } 759 760 // Tests for Repository.ResolveConnect() 761 762 func (s *RepositorySuite) TestResolveConnectExplicit(c *C) { 763 c.Assert(s.testRepo.AddSlot(s.slot), IsNil) 764 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 765 conn, err := s.testRepo.ResolveConnect("consumer", "plug", "producer", "slot") 766 c.Check(err, IsNil) 767 c.Check(conn, DeepEquals, &ConnRef{ 768 PlugRef: PlugRef{Snap: "consumer", Name: "plug"}, 769 SlotRef: SlotRef{Snap: "producer", Name: "slot"}, 770 }) 771 } 772 773 // ResolveConnect uses the "snapd" snap when slot snap name is empty 774 func (s *RepositorySuite) TestResolveConnectImplicitSnapdSlot(c *C) { 775 c.Assert(s.testRepo.AddSnap(s.snapdSnap), IsNil) 776 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 777 conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "slot") 778 c.Check(err, IsNil) 779 c.Check(conn, DeepEquals, &ConnRef{ 780 PlugRef: PlugRef{Snap: "consumer", Name: "plug"}, 781 SlotRef: SlotRef{Snap: "snapd", Name: "slot"}, 782 }) 783 } 784 785 // ResolveConnect uses the "core" snap when slot snap name is empty 786 func (s *RepositorySuite) TestResolveConnectImplicitCoreSlot(c *C) { 787 c.Assert(s.testRepo.AddSnap(s.coreSnap), IsNil) 788 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 789 conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "slot") 790 c.Check(err, IsNil) 791 c.Check(conn, DeepEquals, &ConnRef{ 792 PlugRef: PlugRef{Snap: "consumer", Name: "plug"}, 793 SlotRef: SlotRef{Snap: "core", Name: "slot"}, 794 }) 795 } 796 797 // ResolveConnect uses the "ubuntu-core" snap when slot snap name is empty 798 func (s *RepositorySuite) TestResolveConnectImplicitUbuntuCoreSlot(c *C) { 799 c.Assert(s.testRepo.AddSnap(s.ubuntuCoreSnap), IsNil) 800 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 801 conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "slot") 802 c.Check(err, IsNil) 803 c.Check(conn, DeepEquals, &ConnRef{ 804 PlugRef: PlugRef{Snap: "consumer", Name: "plug"}, 805 SlotRef: SlotRef{Snap: "ubuntu-core", Name: "slot"}, 806 }) 807 } 808 809 // ResolveConnect prefers the "snapd" snap if "snapd" and "core" are available 810 func (s *RepositorySuite) TestResolveConnectImplicitSlotPrefersSnapdOverCore(c *C) { 811 c.Assert(s.testRepo.AddSnap(s.snapdSnap), IsNil) 812 c.Assert(s.testRepo.AddSnap(s.coreSnap), IsNil) 813 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 814 conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "slot") 815 c.Check(err, IsNil) 816 c.Check(conn.SlotRef.Snap, Equals, "snapd") 817 } 818 819 // ResolveConnect prefers the "core" snap if "core" and "ubuntu-core" are available 820 func (s *RepositorySuite) TestResolveConnectImplicitSlotPrefersCoreOverUbuntuCore(c *C) { 821 c.Assert(s.testRepo.AddSnap(s.coreSnap), IsNil) 822 c.Assert(s.testRepo.AddSnap(s.ubuntuCoreSnap), IsNil) 823 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 824 conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "slot") 825 c.Check(err, IsNil) 826 c.Check(conn.SlotRef.Snap, Equals, "core") 827 } 828 829 // ResolveConnect detects lack of candidates 830 func (s *RepositorySuite) TestResolveConnectNoImplicitCandidates(c *C) { 831 err := s.testRepo.AddInterface(&ifacetest.TestInterface{InterfaceName: "other-interface"}) 832 c.Assert(err, IsNil) 833 // Tweak the "slot" slot so that it has an incompatible interface type. 834 s.coreSnap.Slots["slot"].Interface = "other-interface" 835 c.Assert(s.testRepo.AddSnap(s.coreSnap), IsNil) 836 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 837 conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "") 838 c.Check(err, ErrorMatches, `snap "core" has no "interface" interface slots`) 839 c.Check(conn, IsNil) 840 } 841 842 // ResolveConnect detects ambiguities when slot snap name is empty 843 func (s *RepositorySuite) TestResolveConnectAmbiguity(c *C) { 844 coreSnap := snaptest.MockInfo(c, ` 845 name: core 846 version: 0 847 type: os 848 slots: 849 slot-a: 850 interface: interface 851 slot-b: 852 interface: interface 853 `, nil) 854 c.Assert(s.testRepo.AddSnap(coreSnap), IsNil) 855 c.Assert(s.testRepo.AddSlot(s.slot), IsNil) 856 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 857 conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "") 858 c.Check(err, ErrorMatches, `snap "core" has multiple "interface" interface slots: slot-a, slot-b`) 859 c.Check(conn, IsNil) 860 } 861 862 // Pug snap name cannot be empty 863 func (s *RepositorySuite) TestResolveConnectEmptyPlugSnapName(c *C) { 864 conn, err := s.testRepo.ResolveConnect("", "plug", "producer", "slot") 865 c.Check(err, ErrorMatches, "cannot resolve connection, plug snap name is empty") 866 c.Check(conn, IsNil) 867 } 868 869 // Plug name cannot be empty 870 func (s *RepositorySuite) TestResolveConnectEmptyPlugName(c *C) { 871 conn, err := s.testRepo.ResolveConnect("consumer", "", "producer", "slot") 872 c.Check(err, ErrorMatches, "cannot resolve connection, plug name is empty") 873 c.Check(conn, IsNil) 874 } 875 876 // Plug must exist 877 func (s *RepositorySuite) TestResolveNoSuchPlug(c *C) { 878 conn, err := s.testRepo.ResolveConnect("consumer", "plug", "consumer", "slot") 879 c.Check(err, ErrorMatches, `snap "consumer" has no plug named "plug"`) 880 e, _ := err.(*NoPlugOrSlotError) 881 c.Check(e, NotNil) 882 c.Check(conn, IsNil) 883 } 884 885 // Slot snap name cannot be empty if there's no core snap around 886 func (s *RepositorySuite) TestResolveConnectEmptySlotSnapName(c *C) { 887 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 888 conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "slot") 889 c.Check(err, ErrorMatches, "cannot resolve connection, slot snap name is empty") 890 c.Check(conn, IsNil) 891 } 892 893 // Slot name cannot be empty if there's no core snap around 894 func (s *RepositorySuite) TestResolveConnectEmptySlotName(c *C) { 895 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 896 conn, err := s.testRepo.ResolveConnect("consumer", "plug", "producer", "") 897 c.Check(err, ErrorMatches, `snap "producer" has no "interface" interface slots`) 898 c.Check(conn, IsNil) 899 } 900 901 // Slot must exists 902 func (s *RepositorySuite) TestResolveNoSuchSlot(c *C) { 903 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 904 conn, err := s.testRepo.ResolveConnect("consumer", "plug", "producer", "slot") 905 c.Check(err, ErrorMatches, `snap "producer" has no slot named "slot"`) 906 e, _ := err.(*NoPlugOrSlotError) 907 c.Check(e, NotNil) 908 c.Check(conn, IsNil) 909 } 910 911 // Plug and slot must have matching types 912 func (s *RepositorySuite) TestResolveIncompatibleTypes(c *C) { 913 c.Assert(s.testRepo.AddInterface(&ifacetest.TestInterface{InterfaceName: "other-interface"}), IsNil) 914 plug := &snap.PlugInfo{ 915 Snap: &snap.Info{SuggestedName: "consumer"}, 916 Name: "plug", 917 Interface: "other-interface", 918 } 919 c.Assert(s.testRepo.AddPlug(plug), IsNil) 920 c.Assert(s.testRepo.AddSlot(s.slot), IsNil) 921 // Connecting a plug to an incompatible slot fails with an appropriate error 922 conn, err := s.testRepo.ResolveConnect("consumer", "plug", "producer", "slot") 923 c.Check(err, ErrorMatches, 924 `cannot connect consumer:plug \("other-interface" interface\) to producer:slot \("interface" interface\)`) 925 c.Check(conn, IsNil) 926 } 927 928 // Tests for Repository.Connect() 929 930 func (s *RepositorySuite) TestConnectFailsWhenPlugDoesNotExist(c *C) { 931 err := s.testRepo.AddSlot(s.slot) 932 c.Assert(err, IsNil) 933 // Connecting an unknown plug returns an appropriate error 934 connRef := NewConnRef(s.plug, s.slot) 935 _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil) 936 c.Assert(err, ErrorMatches, `cannot connect plug "plug" from snap "consumer": no such plug`) 937 e, _ := err.(*NoPlugOrSlotError) 938 c.Check(e, NotNil) 939 } 940 941 func (s *RepositorySuite) TestConnectFailsWhenSlotDoesNotExist(c *C) { 942 err := s.testRepo.AddPlug(s.plug) 943 c.Assert(err, IsNil) 944 // Connecting to an unknown slot returns an error 945 connRef := NewConnRef(s.plug, s.slot) 946 _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil) 947 c.Assert(err, ErrorMatches, `cannot connect slot "slot" from snap "producer": no such slot`) 948 e, _ := err.(*NoPlugOrSlotError) 949 c.Check(e, NotNil) 950 } 951 952 func (s *RepositorySuite) TestConnectSucceedsWhenIdenticalConnectExists(c *C) { 953 err := s.testRepo.AddPlug(s.plug) 954 c.Assert(err, IsNil) 955 err = s.testRepo.AddSlot(s.slot) 956 c.Assert(err, IsNil) 957 connRef := NewConnRef(s.plug, s.slot) 958 conn, err := s.testRepo.Connect(connRef, nil, nil, nil, nil, nil) 959 c.Assert(err, IsNil) 960 c.Assert(conn, NotNil) 961 c.Assert(conn.Plug, NotNil) 962 c.Assert(conn.Slot, NotNil) 963 c.Assert(conn.Plug.Name(), Equals, "plug") 964 c.Assert(conn.Slot.Name(), Equals, "slot") 965 // Connecting exactly the same thing twice succeeds without an error but does nothing. 966 _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil) 967 c.Assert(err, IsNil) 968 // Only one connection is actually present. 969 c.Assert(s.testRepo.Interfaces(), DeepEquals, &Interfaces{ 970 Plugs: []*snap.PlugInfo{s.plug}, 971 Slots: []*snap.SlotInfo{s.slot}, 972 Connections: []*ConnRef{NewConnRef(s.plug, s.slot)}, 973 }) 974 } 975 976 func (s *RepositorySuite) TestConnectFailsWhenSlotAndPlugAreIncompatible(c *C) { 977 otherInterface := &ifacetest.TestInterface{InterfaceName: "other-interface"} 978 err := s.testRepo.AddInterface(otherInterface) 979 plug := &snap.PlugInfo{ 980 Snap: &snap.Info{SuggestedName: "consumer"}, 981 Name: "plug", 982 Interface: "other-interface", 983 } 984 c.Assert(err, IsNil) 985 err = s.testRepo.AddPlug(plug) 986 c.Assert(err, IsNil) 987 err = s.testRepo.AddSlot(s.slot) 988 c.Assert(err, IsNil) 989 // Connecting a plug to an incompatible slot fails with an appropriate error 990 connRef := NewConnRef(s.plug, s.slot) 991 _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil) 992 c.Assert(err, ErrorMatches, `cannot connect plug "consumer:plug" \(interface "other-interface"\) to "producer:slot" \(interface "interface"\)`) 993 } 994 995 func (s *RepositorySuite) TestConnectSucceeds(c *C) { 996 err := s.testRepo.AddPlug(s.plug) 997 c.Assert(err, IsNil) 998 err = s.testRepo.AddSlot(s.slot) 999 c.Assert(err, IsNil) 1000 // Connecting a plug works okay 1001 connRef := NewConnRef(s.plug, s.slot) 1002 _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil) 1003 c.Assert(err, IsNil) 1004 } 1005 1006 // Tests for Repository.Disconnect() and DisconnectAll() 1007 1008 // Disconnect fails if any argument is empty 1009 func (s *RepositorySuite) TestDisconnectFailsOnEmptyArgs(c *C) { 1010 err1 := s.testRepo.Disconnect(s.plug.Snap.InstanceName(), s.plug.Name, s.slot.Snap.InstanceName(), "") 1011 err2 := s.testRepo.Disconnect(s.plug.Snap.InstanceName(), s.plug.Name, "", s.slot.Name) 1012 err3 := s.testRepo.Disconnect(s.plug.Snap.InstanceName(), "", s.slot.Snap.InstanceName(), s.slot.Name) 1013 err4 := s.testRepo.Disconnect("", s.plug.Name, s.slot.Snap.InstanceName(), s.slot.Name) 1014 c.Assert(err1, ErrorMatches, `cannot disconnect, slot name is empty`) 1015 c.Assert(err2, ErrorMatches, `cannot disconnect, slot snap name is empty`) 1016 c.Assert(err3, ErrorMatches, `cannot disconnect, plug name is empty`) 1017 c.Assert(err4, ErrorMatches, `cannot disconnect, plug snap name is empty`) 1018 } 1019 1020 // Disconnect fails if plug doesn't exist 1021 func (s *RepositorySuite) TestDisconnectFailsWithoutPlug(c *C) { 1022 c.Assert(s.testRepo.AddSlot(s.slot), IsNil) 1023 err := s.testRepo.Disconnect(s.plug.Snap.InstanceName(), s.plug.Name, s.slot.Snap.InstanceName(), s.slot.Name) 1024 c.Assert(err, ErrorMatches, `snap "consumer" has no plug named "plug"`) 1025 e, _ := err.(*NoPlugOrSlotError) 1026 c.Check(e, NotNil) 1027 } 1028 1029 // Disconnect fails if slot doesn't exist 1030 func (s *RepositorySuite) TestDisconnectFailsWithutSlot(c *C) { 1031 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 1032 err := s.testRepo.Disconnect(s.plug.Snap.InstanceName(), s.plug.Name, s.slot.Snap.InstanceName(), s.slot.Name) 1033 c.Assert(err, ErrorMatches, `snap "producer" has no slot named "slot"`) 1034 e, _ := err.(*NoPlugOrSlotError) 1035 c.Check(e, NotNil) 1036 } 1037 1038 // Disconnect fails if there's no connection to disconnect 1039 func (s *RepositorySuite) TestDisconnectFailsWhenNotConnected(c *C) { 1040 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 1041 c.Assert(s.testRepo.AddSlot(s.slot), IsNil) 1042 err := s.testRepo.Disconnect(s.plug.Snap.InstanceName(), s.plug.Name, s.slot.Snap.InstanceName(), s.slot.Name) 1043 c.Assert(err, ErrorMatches, `cannot disconnect consumer:plug from producer:slot, it is not connected`) 1044 e, _ := err.(*NotConnectedError) 1045 c.Check(e, NotNil) 1046 } 1047 1048 // Disconnect works when plug and slot exist and are connected 1049 func (s *RepositorySuite) TestDisconnectSucceeds(c *C) { 1050 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 1051 c.Assert(s.testRepo.AddSlot(s.slot), IsNil) 1052 _, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil, nil, nil) 1053 c.Assert(err, IsNil) 1054 _, err = s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil, nil, nil) 1055 c.Assert(err, IsNil) 1056 err = s.testRepo.Disconnect(s.plug.Snap.InstanceName(), s.plug.Name, s.slot.Snap.InstanceName(), s.slot.Name) 1057 c.Assert(err, IsNil) 1058 c.Assert(s.testRepo.Interfaces(), DeepEquals, &Interfaces{ 1059 Plugs: []*snap.PlugInfo{s.plug}, 1060 Slots: []*snap.SlotInfo{s.slot}, 1061 }) 1062 } 1063 1064 // Tests for Repository.Connected 1065 1066 // Connected fails if snap name is empty and there's no core snap around 1067 func (s *RepositorySuite) TestConnectedFailsWithEmptySnapName(c *C) { 1068 _, err := s.testRepo.Connected("", s.plug.Name) 1069 c.Check(err, ErrorMatches, "internal error: cannot obtain core snap name while computing connections") 1070 } 1071 1072 // Connected fails if plug or slot name is empty 1073 func (s *RepositorySuite) TestConnectedFailsWithEmptyPlugSlotName(c *C) { 1074 _, err := s.testRepo.Connected(s.plug.Snap.InstanceName(), "") 1075 c.Check(err, ErrorMatches, "plug or slot name is empty") 1076 } 1077 1078 // Connected fails if plug or slot doesn't exist 1079 func (s *RepositorySuite) TestConnectedFailsWithoutPlugOrSlot(c *C) { 1080 _, err1 := s.testRepo.Connected(s.plug.Snap.InstanceName(), s.plug.Name) 1081 _, err2 := s.testRepo.Connected(s.slot.Snap.InstanceName(), s.slot.Name) 1082 c.Check(err1, ErrorMatches, `snap "consumer" has no plug or slot named "plug"`) 1083 e, _ := err1.(*NoPlugOrSlotError) 1084 c.Check(e, NotNil) 1085 c.Check(err2, ErrorMatches, `snap "producer" has no plug or slot named "slot"`) 1086 e, _ = err1.(*NoPlugOrSlotError) 1087 c.Check(e, NotNil) 1088 } 1089 1090 // Connected finds connections when asked from plug or from slot side 1091 func (s *RepositorySuite) TestConnectedFindsConnections(c *C) { 1092 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 1093 c.Assert(s.testRepo.AddSlot(s.slot), IsNil) 1094 _, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil, nil, nil) 1095 c.Assert(err, IsNil) 1096 1097 conns, err := s.testRepo.Connected(s.plug.Snap.InstanceName(), s.plug.Name) 1098 c.Assert(err, IsNil) 1099 c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, s.slot)}) 1100 1101 conns, err = s.testRepo.Connected(s.slot.Snap.InstanceName(), s.slot.Name) 1102 c.Assert(err, IsNil) 1103 c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, s.slot)}) 1104 } 1105 1106 // Connected uses the core snap if snap name is empty 1107 func (s *RepositorySuite) TestConnectedFindsCoreSnap(c *C) { 1108 slot := &snap.SlotInfo{ 1109 Snap: &snap.Info{SuggestedName: "core", SnapType: snap.TypeOS}, 1110 Name: "slot", 1111 Interface: "interface", 1112 } 1113 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 1114 c.Assert(s.testRepo.AddSlot(slot), IsNil) 1115 _, err := s.testRepo.Connect(NewConnRef(s.plug, slot), nil, nil, nil, nil, nil) 1116 c.Assert(err, IsNil) 1117 1118 conns, err := s.testRepo.Connected("", s.slot.Name) 1119 c.Assert(err, IsNil) 1120 c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, slot)}) 1121 } 1122 1123 // Connected finds connections when asked from plug or from slot side 1124 func (s *RepositorySuite) TestConnections(c *C) { 1125 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 1126 c.Assert(s.testRepo.AddSlot(s.slot), IsNil) 1127 _, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil, nil, nil) 1128 c.Assert(err, IsNil) 1129 1130 conns, err := s.testRepo.Connections(s.plug.Snap.InstanceName()) 1131 c.Assert(err, IsNil) 1132 c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, s.slot)}) 1133 1134 conns, err = s.testRepo.Connections(s.slot.Snap.InstanceName()) 1135 c.Assert(err, IsNil) 1136 c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, s.slot)}) 1137 1138 conns, err = s.testRepo.Connections("abc") 1139 c.Assert(err, IsNil) 1140 c.Assert(conns, HasLen, 0) 1141 } 1142 1143 func (s *RepositorySuite) TestConnectionsWithSelfConnected(c *C) { 1144 c.Assert(s.testRepo.AddPlug(s.plugSelf), IsNil) 1145 c.Assert(s.testRepo.AddSlot(s.slot), IsNil) 1146 _, err := s.testRepo.Connect(NewConnRef(s.plugSelf, s.slot), nil, nil, nil, nil, nil) 1147 c.Assert(err, IsNil) 1148 1149 conns, err := s.testRepo.Connections(s.plugSelf.Snap.InstanceName()) 1150 c.Assert(err, IsNil) 1151 c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plugSelf, s.slot)}) 1152 1153 conns, err = s.testRepo.Connections(s.slot.Snap.InstanceName()) 1154 c.Assert(err, IsNil) 1155 c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plugSelf, s.slot)}) 1156 } 1157 1158 // Tests for Repository.DisconnectAll() 1159 1160 func (s *RepositorySuite) TestDisconnectAll(c *C) { 1161 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 1162 c.Assert(s.testRepo.AddSlot(s.slot), IsNil) 1163 _, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil, nil, nil) 1164 c.Assert(err, IsNil) 1165 1166 conns := []*ConnRef{NewConnRef(s.plug, s.slot)} 1167 s.testRepo.DisconnectAll(conns) 1168 c.Assert(s.testRepo.Interfaces(), DeepEquals, &Interfaces{ 1169 Plugs: []*snap.PlugInfo{s.plug}, 1170 Slots: []*snap.SlotInfo{s.slot}, 1171 }) 1172 } 1173 1174 // Tests for Repository.Interfaces() 1175 1176 func (s *RepositorySuite) TestInterfacesSmokeTest(c *C) { 1177 err := s.testRepo.AddPlug(s.plug) 1178 c.Assert(err, IsNil) 1179 err = s.testRepo.AddSlot(s.slot) 1180 c.Assert(err, IsNil) 1181 // After connecting the result is as expected 1182 connRef := NewConnRef(s.plug, s.slot) 1183 _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil) 1184 c.Assert(err, IsNil) 1185 ifaces := s.testRepo.Interfaces() 1186 c.Assert(ifaces, DeepEquals, &Interfaces{ 1187 Plugs: []*snap.PlugInfo{s.plug}, 1188 Slots: []*snap.SlotInfo{s.slot}, 1189 Connections: []*ConnRef{NewConnRef(s.plug, s.slot)}, 1190 }) 1191 // After disconnecting the connections become empty 1192 err = s.testRepo.Disconnect(s.plug.Snap.InstanceName(), s.plug.Name, s.slot.Snap.InstanceName(), s.slot.Name) 1193 c.Assert(err, IsNil) 1194 ifaces = s.testRepo.Interfaces() 1195 c.Assert(ifaces, DeepEquals, &Interfaces{ 1196 Plugs: []*snap.PlugInfo{s.plug}, 1197 Slots: []*snap.SlotInfo{s.slot}, 1198 }) 1199 } 1200 1201 // Tests for Repository.SnapSpecification 1202 1203 const testSecurity SecuritySystem = "test" 1204 1205 var testInterface = &ifacetest.TestInterface{ 1206 InterfaceName: "interface", 1207 TestPermanentPlugCallback: func(spec *ifacetest.Specification, plug *snap.PlugInfo) error { 1208 spec.AddSnippet("static plug snippet") 1209 return nil 1210 }, 1211 TestConnectedPlugCallback: func(spec *ifacetest.Specification, plug *ConnectedPlug, slot *ConnectedSlot) error { 1212 spec.AddSnippet("connection-specific plug snippet") 1213 return nil 1214 }, 1215 TestPermanentSlotCallback: func(spec *ifacetest.Specification, slot *snap.SlotInfo) error { 1216 spec.AddSnippet("static slot snippet") 1217 return nil 1218 }, 1219 TestConnectedSlotCallback: func(spec *ifacetest.Specification, plug *ConnectedPlug, slot *ConnectedSlot) error { 1220 spec.AddSnippet("connection-specific slot snippet") 1221 return nil 1222 }, 1223 } 1224 1225 func (s *RepositorySuite) TestSnapSpecification(c *C) { 1226 repo := s.emptyRepo 1227 backend := &ifacetest.TestSecurityBackend{BackendName: testSecurity} 1228 c.Assert(repo.AddBackend(backend), IsNil) 1229 c.Assert(repo.AddInterface(testInterface), IsNil) 1230 c.Assert(repo.AddPlug(s.plug), IsNil) 1231 c.Assert(repo.AddSlot(s.slot), IsNil) 1232 1233 // Snaps should get static security now 1234 spec, err := repo.SnapSpecification(testSecurity, s.plug.Snap.InstanceName()) 1235 c.Assert(err, IsNil) 1236 c.Check(spec.(*ifacetest.Specification).Snippets, DeepEquals, []string{"static plug snippet"}) 1237 1238 spec, err = repo.SnapSpecification(testSecurity, s.slot.Snap.InstanceName()) 1239 c.Assert(err, IsNil) 1240 c.Check(spec.(*ifacetest.Specification).Snippets, DeepEquals, []string{"static slot snippet"}) 1241 1242 // Establish connection between plug and slot 1243 connRef := NewConnRef(s.plug, s.slot) 1244 _, err = repo.Connect(connRef, nil, nil, nil, nil, nil) 1245 c.Assert(err, IsNil) 1246 1247 // Snaps should get static and connection-specific security now 1248 spec, err = repo.SnapSpecification(testSecurity, s.plug.Snap.InstanceName()) 1249 c.Assert(err, IsNil) 1250 c.Check(spec.(*ifacetest.Specification).Snippets, DeepEquals, []string{ 1251 "static plug snippet", 1252 "connection-specific plug snippet", 1253 }) 1254 1255 spec, err = repo.SnapSpecification(testSecurity, s.slot.Snap.InstanceName()) 1256 c.Assert(err, IsNil) 1257 c.Check(spec.(*ifacetest.Specification).Snippets, DeepEquals, []string{ 1258 "static slot snippet", 1259 "connection-specific slot snippet", 1260 }) 1261 } 1262 1263 func (s *RepositorySuite) TestSnapSpecificationFailureWithConnectionSnippets(c *C) { 1264 var testSecurity SecuritySystem = "security" 1265 backend := &ifacetest.TestSecurityBackend{BackendName: testSecurity} 1266 iface := &ifacetest.TestInterface{ 1267 InterfaceName: "interface", 1268 TestConnectedSlotCallback: func(spec *ifacetest.Specification, plug *ConnectedPlug, slot *ConnectedSlot) error { 1269 return fmt.Errorf("cannot compute snippet for provider") 1270 }, 1271 TestConnectedPlugCallback: func(spec *ifacetest.Specification, plug *ConnectedPlug, slot *ConnectedSlot) error { 1272 return fmt.Errorf("cannot compute snippet for consumer") 1273 }, 1274 } 1275 repo := s.emptyRepo 1276 1277 c.Assert(repo.AddBackend(backend), IsNil) 1278 c.Assert(repo.AddInterface(iface), IsNil) 1279 c.Assert(repo.AddPlug(s.plug), IsNil) 1280 c.Assert(repo.AddSlot(s.slot), IsNil) 1281 connRef := NewConnRef(s.plug, s.slot) 1282 _, err := repo.Connect(connRef, nil, nil, nil, nil, nil) 1283 c.Assert(err, IsNil) 1284 1285 spec, err := repo.SnapSpecification(testSecurity, s.plug.Snap.InstanceName()) 1286 c.Assert(err, ErrorMatches, "cannot compute snippet for consumer") 1287 c.Assert(spec, IsNil) 1288 1289 spec, err = repo.SnapSpecification(testSecurity, s.slot.Snap.InstanceName()) 1290 c.Assert(err, ErrorMatches, "cannot compute snippet for provider") 1291 c.Assert(spec, IsNil) 1292 } 1293 1294 func (s *RepositorySuite) TestSnapSpecificationFailureWithPermanentSnippets(c *C) { 1295 var testSecurity SecuritySystem = "security" 1296 iface := &ifacetest.TestInterface{ 1297 InterfaceName: "interface", 1298 TestPermanentSlotCallback: func(spec *ifacetest.Specification, slot *snap.SlotInfo) error { 1299 return fmt.Errorf("cannot compute snippet for provider") 1300 }, 1301 TestPermanentPlugCallback: func(spec *ifacetest.Specification, plug *snap.PlugInfo) error { 1302 return fmt.Errorf("cannot compute snippet for consumer") 1303 }, 1304 } 1305 backend := &ifacetest.TestSecurityBackend{BackendName: testSecurity} 1306 repo := s.emptyRepo 1307 c.Assert(repo.AddBackend(backend), IsNil) 1308 c.Assert(repo.AddInterface(iface), IsNil) 1309 c.Assert(repo.AddPlug(s.plug), IsNil) 1310 c.Assert(repo.AddSlot(s.slot), IsNil) 1311 connRef := NewConnRef(s.plug, s.slot) 1312 _, err := repo.Connect(connRef, nil, nil, nil, nil, nil) 1313 c.Assert(err, IsNil) 1314 1315 spec, err := repo.SnapSpecification(testSecurity, s.plug.Snap.InstanceName()) 1316 c.Assert(err, ErrorMatches, "cannot compute snippet for consumer") 1317 c.Assert(spec, IsNil) 1318 1319 spec, err = repo.SnapSpecification(testSecurity, s.slot.Snap.InstanceName()) 1320 c.Assert(err, ErrorMatches, "cannot compute snippet for provider") 1321 c.Assert(spec, IsNil) 1322 } 1323 1324 type testSideArity struct { 1325 sideSnapName string 1326 } 1327 1328 func (a *testSideArity) SlotsPerPlugAny() bool { 1329 return strings.HasSuffix(a.sideSnapName, "2") 1330 } 1331 1332 func (s *RepositorySuite) TestAutoConnectCandidatePlugsAndSlots(c *C) { 1333 // Add two interfaces, one with automatic connections, one with manual 1334 repo := s.emptyRepo 1335 err := repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "auto"}) 1336 c.Assert(err, IsNil) 1337 err = repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "manual"}) 1338 c.Assert(err, IsNil) 1339 1340 policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, SideArity, error) { 1341 return slot.Interface() == "auto", &testSideArity{plug.Snap().InstanceName()}, nil 1342 } 1343 1344 // Add a pair of snaps with plugs/slots using those two interfaces 1345 consumer := snaptest.MockInfo(c, ` 1346 name: consumer 1347 version: 0 1348 plugs: 1349 auto: 1350 manual: 1351 `, nil) 1352 producer := snaptest.MockInfo(c, ` 1353 name: producer 1354 version: 0 1355 type: os 1356 slots: 1357 auto: 1358 manual: 1359 `, nil) 1360 err = repo.AddSnap(producer) 1361 c.Assert(err, IsNil) 1362 err = repo.AddSnap(consumer) 1363 c.Assert(err, IsNil) 1364 1365 candidateSlots, arities := repo.AutoConnectCandidateSlots("consumer", "auto", policyCheck) 1366 c.Assert(candidateSlots, HasLen, 1) 1367 c.Check(candidateSlots[0].Snap.InstanceName(), Equals, "producer") 1368 c.Check(candidateSlots[0].Interface, Equals, "auto") 1369 c.Check(candidateSlots[0].Name, Equals, "auto") 1370 c.Assert(arities, HasLen, 1) 1371 c.Check(arities[0].SlotsPerPlugAny(), Equals, false) 1372 1373 candidatePlugs := repo.AutoConnectCandidatePlugs("producer", "auto", policyCheck) 1374 c.Assert(candidatePlugs, HasLen, 1) 1375 c.Check(candidatePlugs[0].Snap.InstanceName(), Equals, "consumer") 1376 c.Check(candidatePlugs[0].Interface, Equals, "auto") 1377 c.Check(candidatePlugs[0].Name, Equals, "auto") 1378 } 1379 1380 func (s *RepositorySuite) TestAutoConnectCandidatePlugsAndSlotsSymmetry(c *C) { 1381 repo := s.emptyRepo 1382 // Add a "auto" interface 1383 err := repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "auto"}) 1384 c.Assert(err, IsNil) 1385 1386 policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, SideArity, error) { 1387 return slot.Interface() == "auto", &testSideArity{plug.Snap().InstanceName()}, nil 1388 } 1389 1390 // Add a producer snap for "auto" 1391 producer := snaptest.MockInfo(c, ` 1392 name: producer 1393 version: 0 1394 type: os 1395 slots: 1396 auto: 1397 `, nil) 1398 err = repo.AddSnap(producer) 1399 c.Assert(err, IsNil) 1400 1401 // Add two consumers snaps for "auto" 1402 consumer1 := snaptest.MockInfo(c, ` 1403 name: consumer1 1404 version: 0 1405 plugs: 1406 auto: 1407 `, nil) 1408 1409 err = repo.AddSnap(consumer1) 1410 c.Assert(err, IsNil) 1411 1412 // Add two consumers snaps for "auto" 1413 consumer2 := snaptest.MockInfo(c, ` 1414 name: consumer2 1415 version: 0 1416 plugs: 1417 auto: 1418 `, nil) 1419 1420 err = repo.AddSnap(consumer2) 1421 c.Assert(err, IsNil) 1422 1423 // Both can auto-connect 1424 candidateSlots, arities := repo.AutoConnectCandidateSlots("consumer1", "auto", policyCheck) 1425 c.Assert(candidateSlots, HasLen, 1) 1426 c.Check(candidateSlots[0].Snap.InstanceName(), Equals, "producer") 1427 c.Check(candidateSlots[0].Interface, Equals, "auto") 1428 c.Check(candidateSlots[0].Name, Equals, "auto") 1429 c.Assert(arities, HasLen, 1) 1430 c.Check(arities[0].SlotsPerPlugAny(), Equals, false) 1431 1432 candidateSlots, arities = repo.AutoConnectCandidateSlots("consumer2", "auto", policyCheck) 1433 c.Assert(candidateSlots, HasLen, 1) 1434 c.Check(candidateSlots[0].Snap.InstanceName(), Equals, "producer") 1435 c.Check(candidateSlots[0].Interface, Equals, "auto") 1436 c.Check(candidateSlots[0].Name, Equals, "auto") 1437 c.Assert(arities, HasLen, 1) 1438 c.Check(arities[0].SlotsPerPlugAny(), Equals, true) 1439 1440 // Plugs candidates seen from the producer (for example if 1441 // it's installed after) should be the same 1442 candidatePlugs := repo.AutoConnectCandidatePlugs("producer", "auto", policyCheck) 1443 c.Assert(candidatePlugs, HasLen, 2) 1444 } 1445 1446 func (s *RepositorySuite) TestAutoConnectCandidateSlotsSideArity(c *C) { 1447 repo := s.emptyRepo 1448 // Add a "auto" interface 1449 err := repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "auto"}) 1450 c.Assert(err, IsNil) 1451 1452 policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, SideArity, error) { 1453 return slot.Interface() == "auto", &testSideArity{slot.Snap().InstanceName()}, nil 1454 } 1455 1456 // Add two producer snaps for "auto" 1457 producer1 := snaptest.MockInfo(c, ` 1458 name: producer1 1459 version: 0 1460 slots: 1461 auto: 1462 `, nil) 1463 err = repo.AddSnap(producer1) 1464 c.Assert(err, IsNil) 1465 1466 producer2 := snaptest.MockInfo(c, ` 1467 name: producer2 1468 version: 0 1469 slots: 1470 auto: 1471 `, nil) 1472 err = repo.AddSnap(producer2) 1473 c.Assert(err, IsNil) 1474 1475 // Add a consumer snap for "auto" 1476 consumer := snaptest.MockInfo(c, ` 1477 name: consumer 1478 version: 0 1479 plugs: 1480 auto: 1481 `, nil) 1482 err = repo.AddSnap(consumer) 1483 c.Assert(err, IsNil) 1484 1485 // Both slots could auto-connect 1486 seenProducers := make(map[string]bool) 1487 candidateSlots, arities := repo.AutoConnectCandidateSlots("consumer", "auto", policyCheck) 1488 c.Assert(candidateSlots, HasLen, 2) 1489 c.Assert(arities, HasLen, 2) 1490 for i, candSlot := range candidateSlots { 1491 c.Check(candSlot.Interface, Equals, "auto") 1492 c.Check(candSlot.Name, Equals, "auto") 1493 producerName := candSlot.Snap.InstanceName() 1494 // SideArities match 1495 switch producerName { 1496 case "producer1": 1497 c.Check(arities[i].SlotsPerPlugAny(), Equals, false) 1498 case "producer2": 1499 c.Check(arities[i].SlotsPerPlugAny(), Equals, true) 1500 } 1501 seenProducers[producerName] = true 1502 } 1503 c.Check(seenProducers, DeepEquals, map[string]bool{ 1504 "producer1": true, 1505 "producer2": true, 1506 }) 1507 } 1508 1509 // Tests for AddSnap and RemoveSnap 1510 1511 type AddRemoveSuite struct { 1512 testutil.BaseTest 1513 repo *Repository 1514 } 1515 1516 var _ = Suite(&AddRemoveSuite{}) 1517 1518 func (s *AddRemoveSuite) SetUpTest(c *C) { 1519 s.BaseTest.SetUpTest(c) 1520 s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})) 1521 1522 s.repo = NewRepository() 1523 err := s.repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "iface"}) 1524 c.Assert(err, IsNil) 1525 err = s.repo.AddInterface(&ifacetest.TestInterface{ 1526 InterfaceName: "invalid", 1527 BeforePreparePlugCallback: func(plug *snap.PlugInfo) error { return fmt.Errorf("plug is invalid") }, 1528 BeforePrepareSlotCallback: func(slot *snap.SlotInfo) error { return fmt.Errorf("slot is invalid") }, 1529 }) 1530 c.Assert(err, IsNil) 1531 } 1532 1533 func (s *AddRemoveSuite) TearDownTest(c *C) { 1534 s.BaseTest.TearDownTest(c) 1535 } 1536 1537 const testConsumerYaml = ` 1538 name: consumer 1539 version: 0 1540 apps: 1541 app: 1542 plugs: [iface] 1543 ` 1544 const testProducerYaml = ` 1545 name: producer 1546 version: 0 1547 apps: 1548 app: 1549 slots: [iface] 1550 ` 1551 1552 func (s *AddRemoveSuite) addSnap(c *C, yaml string) (*snap.Info, error) { 1553 snapInfo := snaptest.MockInfo(c, yaml, nil) 1554 return snapInfo, s.repo.AddSnap(snapInfo) 1555 } 1556 1557 func (s *AddRemoveSuite) TestAddSnapAddsPlugs(c *C) { 1558 _, err := s.addSnap(c, testConsumerYaml) 1559 c.Assert(err, IsNil) 1560 // The plug was added 1561 c.Assert(s.repo.Plug("consumer", "iface"), Not(IsNil)) 1562 } 1563 1564 func (s *AddRemoveSuite) TestAddSnapErrorsOnExistingSnapPlugs(c *C) { 1565 _, err := s.addSnap(c, testConsumerYaml) 1566 c.Assert(err, IsNil) 1567 _, err = s.addSnap(c, testConsumerYaml) 1568 c.Assert(err, ErrorMatches, `cannot register interfaces for snap "consumer" more than once`) 1569 } 1570 1571 func (s *AddRemoveSuite) TestAddSnapAddsSlots(c *C) { 1572 _, err := s.addSnap(c, testProducerYaml) 1573 c.Assert(err, IsNil) 1574 // The slot was added 1575 c.Assert(s.repo.Slot("producer", "iface"), Not(IsNil)) 1576 } 1577 1578 func (s *AddRemoveSuite) TestAddSnapErrorsOnExistingSnapSlots(c *C) { 1579 _, err := s.addSnap(c, testProducerYaml) 1580 c.Assert(err, IsNil) 1581 _, err = s.addSnap(c, testProducerYaml) 1582 c.Assert(err, ErrorMatches, `cannot register interfaces for snap "producer" more than once`) 1583 } 1584 1585 func (s *AddRemoveSuite) TestAddSnapSkipsUnknownInterfaces(c *C) { 1586 info, err := s.addSnap(c, ` 1587 name: bogus 1588 version: 0 1589 plugs: 1590 bogus-plug: 1591 slots: 1592 bogus-slot: 1593 `) 1594 c.Assert(err, IsNil) 1595 // the snap knowns about the bogus plug and slot 1596 c.Assert(info.Plugs["bogus-plug"], NotNil) 1597 c.Assert(info.Slots["bogus-slot"], NotNil) 1598 // but the repository ignores them 1599 c.Assert(s.repo.Plug("bogus", "bogus-plug"), IsNil) 1600 c.Assert(s.repo.Slot("bogus", "bogus-slot"), IsNil) 1601 } 1602 1603 func (s AddRemoveSuite) TestRemoveRemovesPlugs(c *C) { 1604 _, err := s.addSnap(c, testConsumerYaml) 1605 c.Assert(err, IsNil) 1606 s.repo.RemoveSnap("consumer") 1607 c.Assert(s.repo.Plug("consumer", "iface"), IsNil) 1608 } 1609 1610 func (s AddRemoveSuite) TestRemoveRemovesSlots(c *C) { 1611 _, err := s.addSnap(c, testProducerYaml) 1612 c.Assert(err, IsNil) 1613 s.repo.RemoveSnap("producer") 1614 c.Assert(s.repo.Plug("producer", "iface"), IsNil) 1615 } 1616 1617 func (s *AddRemoveSuite) TestRemoveSnapErrorsOnStillConnectedPlug(c *C) { 1618 _, err := s.addSnap(c, testConsumerYaml) 1619 c.Assert(err, IsNil) 1620 _, err = s.addSnap(c, testProducerYaml) 1621 c.Assert(err, IsNil) 1622 connRef := &ConnRef{PlugRef: PlugRef{Snap: "consumer", Name: "iface"}, SlotRef: SlotRef{Snap: "producer", Name: "iface"}} 1623 _, err = s.repo.Connect(connRef, nil, nil, nil, nil, nil) 1624 c.Assert(err, IsNil) 1625 err = s.repo.RemoveSnap("consumer") 1626 c.Assert(err, ErrorMatches, "cannot remove connected plug consumer.iface") 1627 } 1628 1629 func (s *AddRemoveSuite) TestRemoveSnapErrorsOnStillConnectedSlot(c *C) { 1630 _, err := s.addSnap(c, testConsumerYaml) 1631 c.Assert(err, IsNil) 1632 _, err = s.addSnap(c, testProducerYaml) 1633 c.Assert(err, IsNil) 1634 connRef := &ConnRef{PlugRef: PlugRef{Snap: "consumer", Name: "iface"}, SlotRef: SlotRef{Snap: "producer", Name: "iface"}} 1635 _, err = s.repo.Connect(connRef, nil, nil, nil, nil, nil) 1636 c.Assert(err, IsNil) 1637 err = s.repo.RemoveSnap("producer") 1638 c.Assert(err, ErrorMatches, "cannot remove connected slot producer.iface") 1639 } 1640 1641 type DisconnectSnapSuite struct { 1642 testutil.BaseTest 1643 repo *Repository 1644 s1, s2, s2Instance *snap.Info 1645 } 1646 1647 var _ = Suite(&DisconnectSnapSuite{}) 1648 1649 func (s *DisconnectSnapSuite) SetUpTest(c *C) { 1650 s.BaseTest.SetUpTest(c) 1651 s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})) 1652 1653 s.repo = NewRepository() 1654 1655 err := s.repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "iface-a"}) 1656 c.Assert(err, IsNil) 1657 err = s.repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "iface-b"}) 1658 c.Assert(err, IsNil) 1659 1660 s.s1 = snaptest.MockInfo(c, ` 1661 name: s1 1662 version: 0 1663 plugs: 1664 iface-a: 1665 slots: 1666 iface-b: 1667 `, nil) 1668 err = s.repo.AddSnap(s.s1) 1669 c.Assert(err, IsNil) 1670 1671 s.s2 = snaptest.MockInfo(c, ` 1672 name: s2 1673 version: 0 1674 plugs: 1675 iface-b: 1676 slots: 1677 iface-a: 1678 `, nil) 1679 c.Assert(err, IsNil) 1680 err = s.repo.AddSnap(s.s2) 1681 c.Assert(err, IsNil) 1682 s.s2Instance = snaptest.MockInfo(c, ` 1683 name: s2 1684 version: 0 1685 plugs: 1686 iface-b: 1687 slots: 1688 iface-a: 1689 `, nil) 1690 s.s2Instance.InstanceKey = "instance" 1691 c.Assert(err, IsNil) 1692 err = s.repo.AddSnap(s.s2Instance) 1693 c.Assert(err, IsNil) 1694 } 1695 1696 func (s *DisconnectSnapSuite) TearDownTest(c *C) { 1697 s.BaseTest.TearDownTest(c) 1698 } 1699 1700 func (s *DisconnectSnapSuite) TestNotConnected(c *C) { 1701 affected, err := s.repo.DisconnectSnap("s1") 1702 c.Assert(err, IsNil) 1703 c.Check(affected, HasLen, 0) 1704 } 1705 1706 func (s *DisconnectSnapSuite) TestOutgoingConnection(c *C) { 1707 connRef := &ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "iface-a"}, SlotRef: SlotRef{Snap: "s2", Name: "iface-a"}} 1708 _, err := s.repo.Connect(connRef, nil, nil, nil, nil, nil) 1709 c.Assert(err, IsNil) 1710 // Disconnect s1 with which has an outgoing connection to s2 1711 affected, err := s.repo.DisconnectSnap("s1") 1712 c.Assert(err, IsNil) 1713 c.Check(affected, testutil.Contains, "s1") 1714 c.Check(affected, testutil.Contains, "s2") 1715 } 1716 1717 func (s *DisconnectSnapSuite) TestIncomingConnection(c *C) { 1718 connRef := &ConnRef{PlugRef: PlugRef{Snap: "s2", Name: "iface-b"}, SlotRef: SlotRef{Snap: "s1", Name: "iface-b"}} 1719 _, err := s.repo.Connect(connRef, nil, nil, nil, nil, nil) 1720 c.Assert(err, IsNil) 1721 // Disconnect s1 with which has an incoming connection from s2 1722 affected, err := s.repo.DisconnectSnap("s1") 1723 c.Assert(err, IsNil) 1724 c.Check(affected, testutil.Contains, "s1") 1725 c.Check(affected, testutil.Contains, "s2") 1726 } 1727 1728 func (s *DisconnectSnapSuite) TestCrossConnection(c *C) { 1729 // This test is symmetric wrt s1 <-> s2 connections 1730 for _, snapName := range []string{"s1", "s2"} { 1731 connRef1 := &ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "iface-a"}, SlotRef: SlotRef{Snap: "s2", Name: "iface-a"}} 1732 _, err := s.repo.Connect(connRef1, nil, nil, nil, nil, nil) 1733 c.Assert(err, IsNil) 1734 connRef2 := &ConnRef{PlugRef: PlugRef{Snap: "s2", Name: "iface-b"}, SlotRef: SlotRef{Snap: "s1", Name: "iface-b"}} 1735 _, err = s.repo.Connect(connRef2, nil, nil, nil, nil, nil) 1736 c.Assert(err, IsNil) 1737 affected, err := s.repo.DisconnectSnap(snapName) 1738 c.Assert(err, IsNil) 1739 c.Check(affected, testutil.Contains, "s1") 1740 c.Check(affected, testutil.Contains, "s2") 1741 } 1742 } 1743 1744 func (s *DisconnectSnapSuite) TestParallelInstances(c *C) { 1745 _, err := s.repo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "iface-a"}, SlotRef: SlotRef{Snap: "s2_instance", Name: "iface-a"}}, nil, nil, nil, nil, nil) 1746 c.Assert(err, IsNil) 1747 affected, err := s.repo.DisconnectSnap("s1") 1748 c.Assert(err, IsNil) 1749 c.Check(affected, testutil.Contains, "s1") 1750 c.Check(affected, testutil.Contains, "s2_instance") 1751 1752 _, err = s.repo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s2_instance", Name: "iface-b"}, SlotRef: SlotRef{Snap: "s1", Name: "iface-b"}}, nil, nil, nil, nil, nil) 1753 c.Assert(err, IsNil) 1754 affected, err = s.repo.DisconnectSnap("s1") 1755 c.Assert(err, IsNil) 1756 c.Check(affected, testutil.Contains, "s1") 1757 c.Check(affected, testutil.Contains, "s2_instance") 1758 } 1759 1760 func contentPolicyCheck(plug *ConnectedPlug, slot *ConnectedSlot) (bool, SideArity, error) { 1761 return plug.Snap().Publisher.ID == slot.Snap().Publisher.ID, nil, nil 1762 } 1763 1764 func contentAutoConnect(plug *snap.PlugInfo, slot *snap.SlotInfo) bool { 1765 return plug.Attrs["content"] == slot.Attrs["content"] 1766 } 1767 1768 // internal helper that creates a new repository with two snaps, one 1769 // is a content plug and one a content slot 1770 func makeContentConnectionTestSnaps(c *C, plugContentToken, slotContentToken string) (*Repository, *snap.Info, *snap.Info) { 1771 repo := NewRepository() 1772 err := repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "content", AutoConnectCallback: contentAutoConnect}) 1773 c.Assert(err, IsNil) 1774 1775 plugSnap := snaptest.MockInfo(c, fmt.Sprintf(` 1776 name: content-plug-snap 1777 version: 0 1778 plugs: 1779 imported-content: 1780 interface: content 1781 content: %s 1782 `, plugContentToken), nil) 1783 slotSnap := snaptest.MockInfo(c, fmt.Sprintf(` 1784 name: content-slot-snap 1785 version: 0 1786 slots: 1787 exported-content: 1788 interface: content 1789 content: %s 1790 `, slotContentToken), nil) 1791 1792 err = repo.AddSnap(plugSnap) 1793 c.Assert(err, IsNil) 1794 err = repo.AddSnap(slotSnap) 1795 c.Assert(err, IsNil) 1796 1797 return repo, plugSnap, slotSnap 1798 } 1799 1800 func (s *RepositorySuite) TestAutoConnectContentInterfaceSimple(c *C) { 1801 repo, _, _ := makeContentConnectionTestSnaps(c, "mylib", "mylib") 1802 candidateSlots, _ := repo.AutoConnectCandidateSlots("content-plug-snap", "imported-content", contentPolicyCheck) 1803 c.Assert(candidateSlots, HasLen, 1) 1804 c.Check(candidateSlots[0].Name, Equals, "exported-content") 1805 candidatePlugs := repo.AutoConnectCandidatePlugs("content-slot-snap", "exported-content", contentPolicyCheck) 1806 c.Assert(candidatePlugs, HasLen, 1) 1807 c.Check(candidatePlugs[0].Name, Equals, "imported-content") 1808 } 1809 1810 func (s *RepositorySuite) TestAutoConnectContentInterfaceOSWorksCorrectly(c *C) { 1811 repo, _, slotSnap := makeContentConnectionTestSnaps(c, "mylib", "otherlib") 1812 slotSnap.SnapType = snap.TypeOS 1813 1814 candidateSlots, _ := repo.AutoConnectCandidateSlots("content-plug-snap", "imported-content", contentPolicyCheck) 1815 c.Check(candidateSlots, HasLen, 0) 1816 candidatePlugs := repo.AutoConnectCandidatePlugs("content-slot-snap", "exported-content", contentPolicyCheck) 1817 c.Assert(candidatePlugs, HasLen, 0) 1818 } 1819 1820 func (s *RepositorySuite) TestAutoConnectContentInterfaceNoMatchingContent(c *C) { 1821 repo, _, _ := makeContentConnectionTestSnaps(c, "mylib", "otherlib") 1822 candidateSlots, _ := repo.AutoConnectCandidateSlots("content-plug-snap", "imported-content", contentPolicyCheck) 1823 c.Check(candidateSlots, HasLen, 0) 1824 candidatePlugs := repo.AutoConnectCandidatePlugs("content-slot-snap", "exported-content", contentPolicyCheck) 1825 c.Assert(candidatePlugs, HasLen, 0) 1826 } 1827 1828 func (s *RepositorySuite) TestAutoConnectContentInterfaceNoMatchingDeveloper(c *C) { 1829 repo, plugSnap, slotSnap := makeContentConnectionTestSnaps(c, "mylib", "mylib") 1830 // real code will use the assertions, this is just for emulation 1831 plugSnap.Publisher.ID = "fooid" 1832 slotSnap.Publisher.ID = "barid" 1833 1834 candidateSlots, _ := repo.AutoConnectCandidateSlots("content-plug-snap", "imported-content", contentPolicyCheck) 1835 c.Check(candidateSlots, HasLen, 0) 1836 candidatePlugs := repo.AutoConnectCandidatePlugs("content-slot-snap", "exported-content", contentPolicyCheck) 1837 c.Assert(candidatePlugs, HasLen, 0) 1838 } 1839 1840 func (s *RepositorySuite) TestInfo(c *C) { 1841 r := s.emptyRepo 1842 1843 // Add some test interfaces. 1844 i1 := &ifacetest.TestInterface{InterfaceName: "i1", InterfaceStaticInfo: StaticInfo{Summary: "i1 summary", DocURL: "http://example.com/i1"}} 1845 i2 := &ifacetest.TestInterface{InterfaceName: "i2", InterfaceStaticInfo: StaticInfo{Summary: "i2 summary", DocURL: "http://example.com/i2"}} 1846 i3 := &ifacetest.TestInterface{InterfaceName: "i3", InterfaceStaticInfo: StaticInfo{Summary: "i3 summary", DocURL: "http://example.com/i3"}} 1847 c.Assert(r.AddInterface(i1), IsNil) 1848 c.Assert(r.AddInterface(i2), IsNil) 1849 c.Assert(r.AddInterface(i3), IsNil) 1850 1851 // Add some test snaps. 1852 s1 := snaptest.MockInfo(c, ` 1853 name: s1 1854 version: 0 1855 apps: 1856 s1: 1857 plugs: [i1, i2] 1858 `, nil) 1859 c.Assert(r.AddSnap(s1), IsNil) 1860 1861 s2 := snaptest.MockInfo(c, ` 1862 name: s2 1863 version: 0 1864 apps: 1865 s2: 1866 slots: [i1, i3] 1867 `, nil) 1868 c.Assert(r.AddSnap(s2), IsNil) 1869 1870 s3 := snaptest.MockInfo(c, ` 1871 name: s3 1872 version: 0 1873 type: os 1874 slots: 1875 i2: 1876 `, nil) 1877 c.Assert(r.AddSnap(s3), IsNil) 1878 s3Instance := snaptest.MockInfo(c, ` 1879 name: s3 1880 version: 0 1881 type: os 1882 slots: 1883 i2: 1884 `, nil) 1885 s3Instance.InstanceKey = "instance" 1886 c.Assert(r.AddSnap(s3Instance), IsNil) 1887 s4 := snaptest.MockInfo(c, ` 1888 name: s4 1889 version: 0 1890 apps: 1891 s1: 1892 plugs: [i2] 1893 `, nil) 1894 c.Assert(r.AddSnap(s4), IsNil) 1895 1896 // Connect a few things for the tests below. 1897 _, err := r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i1"}, SlotRef: SlotRef{Snap: "s2", Name: "i1"}}, nil, nil, nil, nil, nil) 1898 c.Assert(err, IsNil) 1899 _, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i1"}, SlotRef: SlotRef{Snap: "s2", Name: "i1"}}, nil, nil, nil, nil, nil) 1900 c.Assert(err, IsNil) 1901 _, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i2"}, SlotRef: SlotRef{Snap: "s3", Name: "i2"}}, nil, nil, nil, nil, nil) 1902 c.Assert(err, IsNil) 1903 _, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s4", Name: "i2"}, SlotRef: SlotRef{Snap: "s3_instance", Name: "i2"}}, nil, nil, nil, nil, nil) 1904 c.Assert(err, IsNil) 1905 1906 // Without any names or options we get the summary of all the interfaces. 1907 infos := r.Info(nil) 1908 c.Assert(infos, DeepEquals, []*Info{ 1909 {Name: "i1", Summary: "i1 summary"}, 1910 {Name: "i2", Summary: "i2 summary"}, 1911 {Name: "i3", Summary: "i3 summary"}, 1912 }) 1913 1914 // We can choose specific interfaces, unknown names are just skipped. 1915 infos = r.Info(&InfoOptions{Names: []string{"i2", "i4"}}) 1916 c.Assert(infos, DeepEquals, []*Info{ 1917 {Name: "i2", Summary: "i2 summary"}, 1918 }) 1919 1920 // We can ask for documentation. 1921 infos = r.Info(&InfoOptions{Names: []string{"i2"}, Doc: true}) 1922 c.Assert(infos, DeepEquals, []*Info{ 1923 {Name: "i2", Summary: "i2 summary", DocURL: "http://example.com/i2"}, 1924 }) 1925 1926 // We can ask for a list of plugs. 1927 infos = r.Info(&InfoOptions{Names: []string{"i2"}, Plugs: true}) 1928 c.Assert(infos, DeepEquals, []*Info{ 1929 {Name: "i2", Summary: "i2 summary", Plugs: []*snap.PlugInfo{s1.Plugs["i2"], s4.Plugs["i2"]}}, 1930 }) 1931 1932 // We can ask for a list of slots too. 1933 infos = r.Info(&InfoOptions{Names: []string{"i2"}, Slots: true}) 1934 c.Assert(infos, DeepEquals, []*Info{ 1935 {Name: "i2", Summary: "i2 summary", Slots: []*snap.SlotInfo{s3.Slots["i2"], s3Instance.Slots["i2"]}}, 1936 }) 1937 1938 // We can also ask for only those interfaces that have connected plugs or slots. 1939 infos = r.Info(&InfoOptions{Connected: true}) 1940 c.Assert(infos, DeepEquals, []*Info{ 1941 {Name: "i1", Summary: "i1 summary"}, 1942 {Name: "i2", Summary: "i2 summary"}, 1943 }) 1944 } 1945 1946 const ifacehooksSnap1 = ` 1947 name: s1 1948 version: 0 1949 plugs: 1950 consumer: 1951 interface: iface2 1952 attr0: val0 1953 ` 1954 1955 const ifacehooksSnap2 = ` 1956 name: s2 1957 version: 0 1958 slots: 1959 producer: 1960 interface: iface2 1961 attr0: val0 1962 ` 1963 1964 func (s *RepositorySuite) TestBeforeConnectValidation(c *C) { 1965 err := s.emptyRepo.AddInterface(&ifacetest.TestInterface{ 1966 InterfaceName: "iface2", 1967 BeforeConnectSlotCallback: func(slot *ConnectedSlot) error { 1968 var val string 1969 if err := slot.Attr("attr1", &val); err != nil { 1970 return err 1971 } 1972 return slot.SetAttr("attr1", fmt.Sprintf("%s-validated", val)) 1973 }, 1974 BeforeConnectPlugCallback: func(plug *ConnectedPlug) error { 1975 var val string 1976 if err := plug.Attr("attr1", &val); err != nil { 1977 return err 1978 } 1979 return plug.SetAttr("attr1", fmt.Sprintf("%s-validated", val)) 1980 }, 1981 }) 1982 c.Assert(err, IsNil) 1983 1984 s1 := snaptest.MockInfo(c, ifacehooksSnap1, nil) 1985 c.Assert(s.emptyRepo.AddSnap(s1), IsNil) 1986 s2 := snaptest.MockInfo(c, ifacehooksSnap2, nil) 1987 c.Assert(s.emptyRepo.AddSnap(s2), IsNil) 1988 1989 plugDynAttrs := map[string]interface{}{"attr1": "val1"} 1990 slotDynAttrs := map[string]interface{}{"attr1": "val1"} 1991 1992 policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, error) { return true, nil } 1993 conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, nil, plugDynAttrs, nil, slotDynAttrs, policyCheck) 1994 c.Assert(err, IsNil) 1995 c.Assert(conn, NotNil) 1996 1997 c.Assert(conn.Plug, NotNil) 1998 c.Assert(conn.Slot, NotNil) 1999 2000 c.Assert(conn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{"attr0": "val0"}) 2001 c.Assert(conn.Plug.DynamicAttrs(), DeepEquals, map[string]interface{}{"attr1": "val1-validated"}) 2002 c.Assert(conn.Slot.StaticAttrs(), DeepEquals, map[string]interface{}{"attr0": "val0"}) 2003 c.Assert(conn.Slot.DynamicAttrs(), DeepEquals, map[string]interface{}{"attr1": "val1-validated"}) 2004 } 2005 2006 func (s *RepositorySuite) TestBeforeConnectValidationFailure(c *C) { 2007 err := s.emptyRepo.AddInterface(&ifacetest.TestInterface{ 2008 InterfaceName: "iface2", 2009 BeforeConnectSlotCallback: func(slot *ConnectedSlot) error { 2010 return fmt.Errorf("invalid slot") 2011 }, 2012 BeforeConnectPlugCallback: func(plug *ConnectedPlug) error { 2013 return fmt.Errorf("invalid plug") 2014 }, 2015 }) 2016 c.Assert(err, IsNil) 2017 2018 s1 := snaptest.MockInfo(c, ifacehooksSnap1, nil) 2019 c.Assert(s.emptyRepo.AddSnap(s1), IsNil) 2020 s2 := snaptest.MockInfo(c, ifacehooksSnap2, nil) 2021 c.Assert(s.emptyRepo.AddSnap(s2), IsNil) 2022 2023 plugDynAttrs := map[string]interface{}{"attr1": "val1"} 2024 slotDynAttrs := map[string]interface{}{"attr1": "val1"} 2025 2026 policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, error) { return true, nil } 2027 2028 conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, nil, plugDynAttrs, nil, slotDynAttrs, policyCheck) 2029 c.Assert(err, NotNil) 2030 c.Assert(err, ErrorMatches, `cannot connect plug "consumer" of snap "s1": invalid plug`) 2031 c.Assert(conn, IsNil) 2032 } 2033 2034 func (s *RepositorySuite) TestBeforeConnectValidationPolicyCheckFailure(c *C) { 2035 err := s.emptyRepo.AddInterface(&ifacetest.TestInterface{ 2036 InterfaceName: "iface2", 2037 BeforeConnectSlotCallback: func(slot *ConnectedSlot) error { return nil }, 2038 BeforeConnectPlugCallback: func(plug *ConnectedPlug) error { return nil }, 2039 }) 2040 c.Assert(err, IsNil) 2041 2042 s1 := snaptest.MockInfo(c, ifacehooksSnap1, nil) 2043 c.Assert(s.emptyRepo.AddSnap(s1), IsNil) 2044 s2 := snaptest.MockInfo(c, ifacehooksSnap2, nil) 2045 c.Assert(s.emptyRepo.AddSnap(s2), IsNil) 2046 2047 plugDynAttrs := map[string]interface{}{"attr1": "val1"} 2048 slotDynAttrs := map[string]interface{}{"attr1": "val1"} 2049 2050 policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, error) { 2051 return false, fmt.Errorf("policy check failed") 2052 } 2053 2054 conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, nil, plugDynAttrs, nil, slotDynAttrs, policyCheck) 2055 c.Assert(err, NotNil) 2056 c.Assert(err, ErrorMatches, `policy check failed`) 2057 c.Assert(conn, IsNil) 2058 } 2059 2060 func (s *RepositorySuite) TestConnection(c *C) { 2061 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 2062 c.Assert(s.testRepo.AddSlot(s.slot), IsNil) 2063 2064 connRef := NewConnRef(s.plug, s.slot) 2065 2066 _, err := s.testRepo.Connection(connRef) 2067 c.Assert(err, ErrorMatches, `no connection from consumer:plug to producer:slot`) 2068 2069 _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil) 2070 c.Assert(err, IsNil) 2071 2072 conn, err := s.testRepo.Connection(connRef) 2073 c.Assert(err, IsNil) 2074 c.Assert(conn.Plug.Name(), Equals, "plug") 2075 c.Assert(conn.Slot.Name(), Equals, "slot") 2076 2077 conn, err = s.testRepo.Connection(&ConnRef{PlugRef: PlugRef{Snap: "a", Name: "b"}, SlotRef: SlotRef{Snap: "producer", Name: "slot"}}) 2078 c.Assert(err, ErrorMatches, `snap "a" has no plug named "b"`) 2079 e, _ := err.(*NoPlugOrSlotError) 2080 c.Check(e, NotNil) 2081 2082 conn, err = s.testRepo.Connection(&ConnRef{PlugRef: PlugRef{Snap: "consumer", Name: "plug"}, SlotRef: SlotRef{Snap: "a", Name: "b"}}) 2083 c.Assert(err, ErrorMatches, `snap "a" has no slot named "b"`) 2084 e, _ = err.(*NoPlugOrSlotError) 2085 c.Check(e, NotNil) 2086 } 2087 2088 func (s *RepositorySuite) TestConnectWithStaticAttrs(c *C) { 2089 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 2090 c.Assert(s.testRepo.AddSlot(s.slot), IsNil) 2091 2092 connRef := NewConnRef(s.plug, s.slot) 2093 2094 plugAttrs := map[string]interface{}{"foo": "bar"} 2095 slotAttrs := map[string]interface{}{"boo": "baz"} 2096 _, err := s.testRepo.Connect(connRef, plugAttrs, nil, slotAttrs, nil, nil) 2097 c.Assert(err, IsNil) 2098 2099 conn, err := s.testRepo.Connection(connRef) 2100 c.Assert(err, IsNil) 2101 c.Assert(conn.Plug.Name(), Equals, "plug") 2102 c.Assert(conn.Slot.Name(), Equals, "slot") 2103 c.Assert(conn.Plug.StaticAttrs(), DeepEquals, plugAttrs) 2104 c.Assert(conn.Slot.StaticAttrs(), DeepEquals, slotAttrs) 2105 } 2106 2107 func (s *RepositorySuite) TestAllHotplugInterfaces(c *C) { 2108 repo := NewRepository() 2109 c.Assert(repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "iface1"}), IsNil) 2110 c.Assert(repo.AddInterface(&ifacetest.TestHotplugInterface{TestInterface: ifacetest.TestInterface{InterfaceName: "iface2"}}), IsNil) 2111 c.Assert(repo.AddInterface(&ifacetest.TestHotplugInterface{TestInterface: ifacetest.TestInterface{InterfaceName: "iface3"}}), IsNil) 2112 2113 hi := repo.AllHotplugInterfaces() 2114 c.Assert(hi, HasLen, 2) 2115 c.Assert(hi["iface2"], DeepEquals, &ifacetest.TestHotplugInterface{TestInterface: ifacetest.TestInterface{InterfaceName: "iface2"}}) 2116 c.Assert(hi["iface3"], DeepEquals, &ifacetest.TestHotplugInterface{TestInterface: ifacetest.TestInterface{InterfaceName: "iface3"}}) 2117 } 2118 2119 func (s *RepositorySuite) TestHotplugMethods(c *C) { 2120 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 2121 2122 coreSlot := &snap.SlotInfo{ 2123 Snap: s.coreSnap, 2124 Name: "dummy-slot", 2125 Interface: "interface", 2126 HotplugKey: "1234", 2127 } 2128 c.Assert(s.testRepo.AddSlot(coreSlot), IsNil) 2129 2130 slotInfo, err := s.testRepo.SlotForHotplugKey("interface", "1234") 2131 c.Assert(err, IsNil) 2132 c.Check(slotInfo, DeepEquals, coreSlot) 2133 2134 // no slot for device key 9999 2135 slotInfo, err = s.testRepo.SlotForHotplugKey("interface", "9999") 2136 c.Assert(err, IsNil) 2137 c.Check(slotInfo, IsNil) 2138 2139 _, err = s.testRepo.Connect(NewConnRef(s.plug, coreSlot), nil, nil, nil, nil, nil) 2140 c.Assert(err, IsNil) 2141 2142 conns, err := s.testRepo.ConnectionsForHotplugKey("interface", "1234") 2143 c.Assert(err, IsNil) 2144 c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, coreSlot)}) 2145 2146 // no connections for device 9999 2147 conns, err = s.testRepo.ConnectionsForHotplugKey("interface", "9999") 2148 c.Assert(err, IsNil) 2149 c.Check(conns, HasLen, 0) 2150 } 2151 2152 func (s *RepositorySuite) TestUpdateHotplugSlotAttrs(c *C) { 2153 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 2154 coreSlot := &snap.SlotInfo{ 2155 Snap: s.coreSnap, 2156 Name: "dummy-slot", 2157 Interface: "interface", 2158 HotplugKey: "1234", 2159 Attrs: map[string]interface{}{"a": "b"}, 2160 } 2161 c.Assert(s.testRepo.AddSlot(coreSlot), IsNil) 2162 2163 slot, err := s.testRepo.UpdateHotplugSlotAttrs("interface", "unknownkey", nil) 2164 c.Assert(err, ErrorMatches, `cannot find hotplug slot for interface interface and hotplug key "unknownkey"`) 2165 c.Assert(slot, IsNil) 2166 2167 newAttrs := map[string]interface{}{"c": "d"} 2168 slot, err = s.testRepo.UpdateHotplugSlotAttrs("interface", "1234", newAttrs) 2169 // attributes are copied, so this change shouldn't be visible 2170 newAttrs["c"] = "tainted" 2171 c.Assert(err, IsNil) 2172 c.Assert(slot, NotNil) 2173 c.Assert(slot.Attrs, DeepEquals, map[string]interface{}{"c": "d"}) 2174 c.Assert(coreSlot.Attrs, DeepEquals, map[string]interface{}{"c": "d"}) 2175 } 2176 2177 func (s *RepositorySuite) TestUpdateHotplugSlotAttrsConnectedError(c *C) { 2178 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 2179 coreSlot := &snap.SlotInfo{ 2180 Snap: s.coreSnap, 2181 Name: "dummy-slot", 2182 Interface: "interface", 2183 HotplugKey: "1234", 2184 } 2185 c.Assert(s.testRepo.AddSlot(coreSlot), IsNil) 2186 2187 _, err := s.testRepo.Connect(NewConnRef(s.plug, coreSlot), nil, nil, nil, nil, nil) 2188 c.Assert(err, IsNil) 2189 2190 slot, err := s.testRepo.UpdateHotplugSlotAttrs("interface", "1234", map[string]interface{}{"c": "d"}) 2191 c.Assert(err, ErrorMatches, `internal error: cannot update slot dummy-slot while connected`) 2192 c.Assert(slot, IsNil) 2193 }