github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/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 const testConsumerInvalidSlotNameYaml = ` 1553 name: consumer 1554 version: 0 1555 slots: 1556 ttyS5: 1557 interface: iface 1558 apps: 1559 app: 1560 slots: [iface] 1561 ` 1562 1563 const testConsumerInvalidPlugNameYaml = ` 1564 name: consumer 1565 version: 0 1566 plugs: 1567 ttyS3: 1568 interface: iface 1569 apps: 1570 app: 1571 plugs: [iface] 1572 ` 1573 1574 func (s *AddRemoveSuite) addSnap(c *C, yaml string) (*snap.Info, error) { 1575 snapInfo := snaptest.MockInfo(c, yaml, nil) 1576 return snapInfo, s.repo.AddSnap(snapInfo) 1577 } 1578 1579 func (s *AddRemoveSuite) TestAddSnapAddsPlugs(c *C) { 1580 _, err := s.addSnap(c, testConsumerYaml) 1581 c.Assert(err, IsNil) 1582 // The plug was added 1583 c.Assert(s.repo.Plug("consumer", "iface"), Not(IsNil)) 1584 } 1585 1586 func (s *AddRemoveSuite) TestAddSnapErrorsOnExistingSnapPlugs(c *C) { 1587 _, err := s.addSnap(c, testConsumerYaml) 1588 c.Assert(err, IsNil) 1589 _, err = s.addSnap(c, testConsumerYaml) 1590 c.Assert(err, ErrorMatches, `cannot register interfaces for snap "consumer" more than once`) 1591 } 1592 1593 func (s *AddRemoveSuite) TestAddSnapAddsSlots(c *C) { 1594 _, err := s.addSnap(c, testProducerYaml) 1595 c.Assert(err, IsNil) 1596 // The slot was added 1597 c.Assert(s.repo.Slot("producer", "iface"), Not(IsNil)) 1598 } 1599 1600 func (s *AddRemoveSuite) TestAddSnapErrorsOnExistingSnapSlots(c *C) { 1601 _, err := s.addSnap(c, testProducerYaml) 1602 c.Assert(err, IsNil) 1603 _, err = s.addSnap(c, testProducerYaml) 1604 c.Assert(err, ErrorMatches, `cannot register interfaces for snap "producer" more than once`) 1605 } 1606 1607 func (s *AddRemoveSuite) TestAddSnapSkipsUnknownInterfaces(c *C) { 1608 info, err := s.addSnap(c, ` 1609 name: bogus 1610 version: 0 1611 plugs: 1612 bogus-plug: 1613 slots: 1614 bogus-slot: 1615 `) 1616 c.Assert(err, IsNil) 1617 // the snap knowns about the bogus plug and slot 1618 c.Assert(info.Plugs["bogus-plug"], NotNil) 1619 c.Assert(info.Slots["bogus-slot"], NotNil) 1620 // but the repository ignores them 1621 c.Assert(s.repo.Plug("bogus", "bogus-plug"), IsNil) 1622 c.Assert(s.repo.Slot("bogus", "bogus-slot"), IsNil) 1623 } 1624 1625 func (s AddRemoveSuite) TestRemoveRemovesPlugs(c *C) { 1626 _, err := s.addSnap(c, testConsumerYaml) 1627 c.Assert(err, IsNil) 1628 s.repo.RemoveSnap("consumer") 1629 c.Assert(s.repo.Plug("consumer", "iface"), IsNil) 1630 } 1631 1632 func (s AddRemoveSuite) TestRemoveRemovesSlots(c *C) { 1633 _, err := s.addSnap(c, testProducerYaml) 1634 c.Assert(err, IsNil) 1635 s.repo.RemoveSnap("producer") 1636 c.Assert(s.repo.Plug("producer", "iface"), IsNil) 1637 } 1638 1639 func (s *AddRemoveSuite) TestRemoveSnapErrorsOnStillConnectedPlug(c *C) { 1640 _, err := s.addSnap(c, testConsumerYaml) 1641 c.Assert(err, IsNil) 1642 _, err = s.addSnap(c, testProducerYaml) 1643 c.Assert(err, IsNil) 1644 connRef := &ConnRef{PlugRef: PlugRef{Snap: "consumer", Name: "iface"}, SlotRef: SlotRef{Snap: "producer", Name: "iface"}} 1645 _, err = s.repo.Connect(connRef, nil, nil, nil, nil, nil) 1646 c.Assert(err, IsNil) 1647 err = s.repo.RemoveSnap("consumer") 1648 c.Assert(err, ErrorMatches, "cannot remove connected plug consumer.iface") 1649 } 1650 1651 func (s *AddRemoveSuite) TestRemoveSnapErrorsOnStillConnectedSlot(c *C) { 1652 _, err := s.addSnap(c, testConsumerYaml) 1653 c.Assert(err, IsNil) 1654 _, err = s.addSnap(c, testProducerYaml) 1655 c.Assert(err, IsNil) 1656 connRef := &ConnRef{PlugRef: PlugRef{Snap: "consumer", Name: "iface"}, SlotRef: SlotRef{Snap: "producer", Name: "iface"}} 1657 _, err = s.repo.Connect(connRef, nil, nil, nil, nil, nil) 1658 c.Assert(err, IsNil) 1659 err = s.repo.RemoveSnap("producer") 1660 c.Assert(err, ErrorMatches, "cannot remove connected slot producer.iface") 1661 } 1662 1663 type DisconnectSnapSuite struct { 1664 testutil.BaseTest 1665 repo *Repository 1666 s1, s2, s2Instance *snap.Info 1667 } 1668 1669 var _ = Suite(&DisconnectSnapSuite{}) 1670 1671 func (s *DisconnectSnapSuite) SetUpTest(c *C) { 1672 s.BaseTest.SetUpTest(c) 1673 s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})) 1674 1675 s.repo = NewRepository() 1676 1677 err := s.repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "iface-a"}) 1678 c.Assert(err, IsNil) 1679 err = s.repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "iface-b"}) 1680 c.Assert(err, IsNil) 1681 1682 s.s1 = snaptest.MockInfo(c, ` 1683 name: s1 1684 version: 0 1685 plugs: 1686 iface-a: 1687 slots: 1688 iface-b: 1689 `, nil) 1690 err = s.repo.AddSnap(s.s1) 1691 c.Assert(err, IsNil) 1692 1693 s.s2 = snaptest.MockInfo(c, ` 1694 name: s2 1695 version: 0 1696 plugs: 1697 iface-b: 1698 slots: 1699 iface-a: 1700 `, nil) 1701 c.Assert(err, IsNil) 1702 err = s.repo.AddSnap(s.s2) 1703 c.Assert(err, IsNil) 1704 s.s2Instance = snaptest.MockInfo(c, ` 1705 name: s2 1706 version: 0 1707 plugs: 1708 iface-b: 1709 slots: 1710 iface-a: 1711 `, nil) 1712 s.s2Instance.InstanceKey = "instance" 1713 c.Assert(err, IsNil) 1714 err = s.repo.AddSnap(s.s2Instance) 1715 c.Assert(err, IsNil) 1716 } 1717 1718 func (s *DisconnectSnapSuite) TearDownTest(c *C) { 1719 s.BaseTest.TearDownTest(c) 1720 } 1721 1722 func (s *DisconnectSnapSuite) TestNotConnected(c *C) { 1723 affected, err := s.repo.DisconnectSnap("s1") 1724 c.Assert(err, IsNil) 1725 c.Check(affected, HasLen, 0) 1726 } 1727 1728 func (s *DisconnectSnapSuite) TestOutgoingConnection(c *C) { 1729 connRef := &ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "iface-a"}, SlotRef: SlotRef{Snap: "s2", Name: "iface-a"}} 1730 _, err := s.repo.Connect(connRef, nil, nil, nil, nil, nil) 1731 c.Assert(err, IsNil) 1732 // Disconnect s1 with which has an outgoing connection to s2 1733 affected, err := s.repo.DisconnectSnap("s1") 1734 c.Assert(err, IsNil) 1735 c.Check(affected, testutil.Contains, "s1") 1736 c.Check(affected, testutil.Contains, "s2") 1737 } 1738 1739 func (s *DisconnectSnapSuite) TestIncomingConnection(c *C) { 1740 connRef := &ConnRef{PlugRef: PlugRef{Snap: "s2", Name: "iface-b"}, SlotRef: SlotRef{Snap: "s1", Name: "iface-b"}} 1741 _, err := s.repo.Connect(connRef, nil, nil, nil, nil, nil) 1742 c.Assert(err, IsNil) 1743 // Disconnect s1 with which has an incoming connection from s2 1744 affected, err := s.repo.DisconnectSnap("s1") 1745 c.Assert(err, IsNil) 1746 c.Check(affected, testutil.Contains, "s1") 1747 c.Check(affected, testutil.Contains, "s2") 1748 } 1749 1750 func (s *DisconnectSnapSuite) TestCrossConnection(c *C) { 1751 // This test is symmetric wrt s1 <-> s2 connections 1752 for _, snapName := range []string{"s1", "s2"} { 1753 connRef1 := &ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "iface-a"}, SlotRef: SlotRef{Snap: "s2", Name: "iface-a"}} 1754 _, err := s.repo.Connect(connRef1, nil, nil, nil, nil, nil) 1755 c.Assert(err, IsNil) 1756 connRef2 := &ConnRef{PlugRef: PlugRef{Snap: "s2", Name: "iface-b"}, SlotRef: SlotRef{Snap: "s1", Name: "iface-b"}} 1757 _, err = s.repo.Connect(connRef2, nil, nil, nil, nil, nil) 1758 c.Assert(err, IsNil) 1759 affected, err := s.repo.DisconnectSnap(snapName) 1760 c.Assert(err, IsNil) 1761 c.Check(affected, testutil.Contains, "s1") 1762 c.Check(affected, testutil.Contains, "s2") 1763 } 1764 } 1765 1766 func (s *DisconnectSnapSuite) TestParallelInstances(c *C) { 1767 _, 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) 1768 c.Assert(err, IsNil) 1769 affected, err := s.repo.DisconnectSnap("s1") 1770 c.Assert(err, IsNil) 1771 c.Check(affected, testutil.Contains, "s1") 1772 c.Check(affected, testutil.Contains, "s2_instance") 1773 1774 _, 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) 1775 c.Assert(err, IsNil) 1776 affected, err = s.repo.DisconnectSnap("s1") 1777 c.Assert(err, IsNil) 1778 c.Check(affected, testutil.Contains, "s1") 1779 c.Check(affected, testutil.Contains, "s2_instance") 1780 } 1781 1782 func contentPolicyCheck(plug *ConnectedPlug, slot *ConnectedSlot) (bool, SideArity, error) { 1783 return plug.Snap().Publisher.ID == slot.Snap().Publisher.ID, nil, nil 1784 } 1785 1786 func contentAutoConnect(plug *snap.PlugInfo, slot *snap.SlotInfo) bool { 1787 return plug.Attrs["content"] == slot.Attrs["content"] 1788 } 1789 1790 // internal helper that creates a new repository with two snaps, one 1791 // is a content plug and one a content slot 1792 func makeContentConnectionTestSnaps(c *C, plugContentToken, slotContentToken string) (*Repository, *snap.Info, *snap.Info) { 1793 repo := NewRepository() 1794 err := repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "content", AutoConnectCallback: contentAutoConnect}) 1795 c.Assert(err, IsNil) 1796 1797 plugSnap := snaptest.MockInfo(c, fmt.Sprintf(` 1798 name: content-plug-snap 1799 version: 0 1800 plugs: 1801 imported-content: 1802 interface: content 1803 content: %s 1804 `, plugContentToken), nil) 1805 slotSnap := snaptest.MockInfo(c, fmt.Sprintf(` 1806 name: content-slot-snap 1807 version: 0 1808 slots: 1809 exported-content: 1810 interface: content 1811 content: %s 1812 `, slotContentToken), nil) 1813 1814 err = repo.AddSnap(plugSnap) 1815 c.Assert(err, IsNil) 1816 err = repo.AddSnap(slotSnap) 1817 c.Assert(err, IsNil) 1818 1819 return repo, plugSnap, slotSnap 1820 } 1821 1822 func (s *RepositorySuite) TestAutoConnectContentInterfaceSimple(c *C) { 1823 repo, _, _ := makeContentConnectionTestSnaps(c, "mylib", "mylib") 1824 candidateSlots, _ := repo.AutoConnectCandidateSlots("content-plug-snap", "imported-content", contentPolicyCheck) 1825 c.Assert(candidateSlots, HasLen, 1) 1826 c.Check(candidateSlots[0].Name, Equals, "exported-content") 1827 candidatePlugs := repo.AutoConnectCandidatePlugs("content-slot-snap", "exported-content", contentPolicyCheck) 1828 c.Assert(candidatePlugs, HasLen, 1) 1829 c.Check(candidatePlugs[0].Name, Equals, "imported-content") 1830 } 1831 1832 func (s *RepositorySuite) TestAutoConnectContentInterfaceOSWorksCorrectly(c *C) { 1833 repo, _, slotSnap := makeContentConnectionTestSnaps(c, "mylib", "otherlib") 1834 slotSnap.SnapType = snap.TypeOS 1835 1836 candidateSlots, _ := repo.AutoConnectCandidateSlots("content-plug-snap", "imported-content", contentPolicyCheck) 1837 c.Check(candidateSlots, HasLen, 0) 1838 candidatePlugs := repo.AutoConnectCandidatePlugs("content-slot-snap", "exported-content", contentPolicyCheck) 1839 c.Assert(candidatePlugs, HasLen, 0) 1840 } 1841 1842 func (s *RepositorySuite) TestAutoConnectContentInterfaceNoMatchingContent(c *C) { 1843 repo, _, _ := makeContentConnectionTestSnaps(c, "mylib", "otherlib") 1844 candidateSlots, _ := repo.AutoConnectCandidateSlots("content-plug-snap", "imported-content", contentPolicyCheck) 1845 c.Check(candidateSlots, HasLen, 0) 1846 candidatePlugs := repo.AutoConnectCandidatePlugs("content-slot-snap", "exported-content", contentPolicyCheck) 1847 c.Assert(candidatePlugs, HasLen, 0) 1848 } 1849 1850 func (s *RepositorySuite) TestAutoConnectContentInterfaceNoMatchingDeveloper(c *C) { 1851 repo, plugSnap, slotSnap := makeContentConnectionTestSnaps(c, "mylib", "mylib") 1852 // real code will use the assertions, this is just for emulation 1853 plugSnap.Publisher.ID = "fooid" 1854 slotSnap.Publisher.ID = "barid" 1855 1856 candidateSlots, _ := repo.AutoConnectCandidateSlots("content-plug-snap", "imported-content", contentPolicyCheck) 1857 c.Check(candidateSlots, HasLen, 0) 1858 candidatePlugs := repo.AutoConnectCandidatePlugs("content-slot-snap", "exported-content", contentPolicyCheck) 1859 c.Assert(candidatePlugs, HasLen, 0) 1860 } 1861 1862 func (s *RepositorySuite) TestInfo(c *C) { 1863 r := s.emptyRepo 1864 1865 // Add some test interfaces. 1866 i1 := &ifacetest.TestInterface{InterfaceName: "i1", InterfaceStaticInfo: StaticInfo{Summary: "i1 summary", DocURL: "http://example.com/i1"}} 1867 i2 := &ifacetest.TestInterface{InterfaceName: "i2", InterfaceStaticInfo: StaticInfo{Summary: "i2 summary", DocURL: "http://example.com/i2"}} 1868 i3 := &ifacetest.TestInterface{InterfaceName: "i3", InterfaceStaticInfo: StaticInfo{Summary: "i3 summary", DocURL: "http://example.com/i3"}} 1869 c.Assert(r.AddInterface(i1), IsNil) 1870 c.Assert(r.AddInterface(i2), IsNil) 1871 c.Assert(r.AddInterface(i3), IsNil) 1872 1873 // Add some test snaps. 1874 s1 := snaptest.MockInfo(c, ` 1875 name: s1 1876 version: 0 1877 apps: 1878 s1: 1879 plugs: [i1, i2] 1880 `, nil) 1881 c.Assert(r.AddSnap(s1), IsNil) 1882 1883 s2 := snaptest.MockInfo(c, ` 1884 name: s2 1885 version: 0 1886 apps: 1887 s2: 1888 slots: [i1, i3] 1889 `, nil) 1890 c.Assert(r.AddSnap(s2), IsNil) 1891 1892 s3 := snaptest.MockInfo(c, ` 1893 name: s3 1894 version: 0 1895 type: os 1896 slots: 1897 i2: 1898 `, nil) 1899 c.Assert(r.AddSnap(s3), IsNil) 1900 s3Instance := snaptest.MockInfo(c, ` 1901 name: s3 1902 version: 0 1903 type: os 1904 slots: 1905 i2: 1906 `, nil) 1907 s3Instance.InstanceKey = "instance" 1908 c.Assert(r.AddSnap(s3Instance), IsNil) 1909 s4 := snaptest.MockInfo(c, ` 1910 name: s4 1911 version: 0 1912 apps: 1913 s1: 1914 plugs: [i2] 1915 `, nil) 1916 c.Assert(r.AddSnap(s4), IsNil) 1917 1918 // Connect a few things for the tests below. 1919 _, err := r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i1"}, SlotRef: SlotRef{Snap: "s2", Name: "i1"}}, nil, nil, nil, nil, nil) 1920 c.Assert(err, IsNil) 1921 _, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i1"}, SlotRef: SlotRef{Snap: "s2", Name: "i1"}}, nil, nil, nil, nil, nil) 1922 c.Assert(err, IsNil) 1923 _, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i2"}, SlotRef: SlotRef{Snap: "s3", Name: "i2"}}, nil, nil, nil, nil, nil) 1924 c.Assert(err, IsNil) 1925 _, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s4", Name: "i2"}, SlotRef: SlotRef{Snap: "s3_instance", Name: "i2"}}, nil, nil, nil, nil, nil) 1926 c.Assert(err, IsNil) 1927 1928 // Without any names or options we get the summary of all the interfaces. 1929 infos := r.Info(nil) 1930 c.Assert(infos, DeepEquals, []*Info{ 1931 {Name: "i1", Summary: "i1 summary"}, 1932 {Name: "i2", Summary: "i2 summary"}, 1933 {Name: "i3", Summary: "i3 summary"}, 1934 }) 1935 1936 // We can choose specific interfaces, unknown names are just skipped. 1937 infos = r.Info(&InfoOptions{Names: []string{"i2", "i4"}}) 1938 c.Assert(infos, DeepEquals, []*Info{ 1939 {Name: "i2", Summary: "i2 summary"}, 1940 }) 1941 1942 // We can ask for documentation. 1943 infos = r.Info(&InfoOptions{Names: []string{"i2"}, Doc: true}) 1944 c.Assert(infos, DeepEquals, []*Info{ 1945 {Name: "i2", Summary: "i2 summary", DocURL: "http://example.com/i2"}, 1946 }) 1947 1948 // We can ask for a list of plugs. 1949 infos = r.Info(&InfoOptions{Names: []string{"i2"}, Plugs: true}) 1950 c.Assert(infos, DeepEquals, []*Info{ 1951 {Name: "i2", Summary: "i2 summary", Plugs: []*snap.PlugInfo{s1.Plugs["i2"], s4.Plugs["i2"]}}, 1952 }) 1953 1954 // We can ask for a list of slots too. 1955 infos = r.Info(&InfoOptions{Names: []string{"i2"}, Slots: true}) 1956 c.Assert(infos, DeepEquals, []*Info{ 1957 {Name: "i2", Summary: "i2 summary", Slots: []*snap.SlotInfo{s3.Slots["i2"], s3Instance.Slots["i2"]}}, 1958 }) 1959 1960 // We can also ask for only those interfaces that have connected plugs or slots. 1961 infos = r.Info(&InfoOptions{Connected: true}) 1962 c.Assert(infos, DeepEquals, []*Info{ 1963 {Name: "i1", Summary: "i1 summary"}, 1964 {Name: "i2", Summary: "i2 summary"}, 1965 }) 1966 } 1967 1968 const ifacehooksSnap1 = ` 1969 name: s1 1970 version: 0 1971 plugs: 1972 consumer: 1973 interface: iface2 1974 attr0: val0 1975 ` 1976 1977 const ifacehooksSnap2 = ` 1978 name: s2 1979 version: 0 1980 slots: 1981 producer: 1982 interface: iface2 1983 attr0: val0 1984 ` 1985 1986 func (s *RepositorySuite) TestBeforeConnectValidation(c *C) { 1987 err := s.emptyRepo.AddInterface(&ifacetest.TestInterface{ 1988 InterfaceName: "iface2", 1989 BeforeConnectSlotCallback: func(slot *ConnectedSlot) error { 1990 var val string 1991 if err := slot.Attr("attr1", &val); err != nil { 1992 return err 1993 } 1994 return slot.SetAttr("attr1", fmt.Sprintf("%s-validated", val)) 1995 }, 1996 BeforeConnectPlugCallback: func(plug *ConnectedPlug) error { 1997 var val string 1998 if err := plug.Attr("attr1", &val); err != nil { 1999 return err 2000 } 2001 return plug.SetAttr("attr1", fmt.Sprintf("%s-validated", val)) 2002 }, 2003 }) 2004 c.Assert(err, IsNil) 2005 2006 s1 := snaptest.MockInfo(c, ifacehooksSnap1, nil) 2007 c.Assert(s.emptyRepo.AddSnap(s1), IsNil) 2008 s2 := snaptest.MockInfo(c, ifacehooksSnap2, nil) 2009 c.Assert(s.emptyRepo.AddSnap(s2), IsNil) 2010 2011 plugDynAttrs := map[string]interface{}{"attr1": "val1"} 2012 slotDynAttrs := map[string]interface{}{"attr1": "val1"} 2013 2014 policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, error) { return true, nil } 2015 conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, nil, plugDynAttrs, nil, slotDynAttrs, policyCheck) 2016 c.Assert(err, IsNil) 2017 c.Assert(conn, NotNil) 2018 2019 c.Assert(conn.Plug, NotNil) 2020 c.Assert(conn.Slot, NotNil) 2021 2022 c.Assert(conn.Plug.StaticAttrs(), DeepEquals, map[string]interface{}{"attr0": "val0"}) 2023 c.Assert(conn.Plug.DynamicAttrs(), DeepEquals, map[string]interface{}{"attr1": "val1-validated"}) 2024 c.Assert(conn.Slot.StaticAttrs(), DeepEquals, map[string]interface{}{"attr0": "val0"}) 2025 c.Assert(conn.Slot.DynamicAttrs(), DeepEquals, map[string]interface{}{"attr1": "val1-validated"}) 2026 } 2027 2028 func (s *RepositorySuite) TestBeforeConnectValidationFailure(c *C) { 2029 err := s.emptyRepo.AddInterface(&ifacetest.TestInterface{ 2030 InterfaceName: "iface2", 2031 BeforeConnectSlotCallback: func(slot *ConnectedSlot) error { 2032 return fmt.Errorf("invalid slot") 2033 }, 2034 BeforeConnectPlugCallback: func(plug *ConnectedPlug) error { 2035 return fmt.Errorf("invalid plug") 2036 }, 2037 }) 2038 c.Assert(err, IsNil) 2039 2040 s1 := snaptest.MockInfo(c, ifacehooksSnap1, nil) 2041 c.Assert(s.emptyRepo.AddSnap(s1), IsNil) 2042 s2 := snaptest.MockInfo(c, ifacehooksSnap2, nil) 2043 c.Assert(s.emptyRepo.AddSnap(s2), IsNil) 2044 2045 plugDynAttrs := map[string]interface{}{"attr1": "val1"} 2046 slotDynAttrs := map[string]interface{}{"attr1": "val1"} 2047 2048 policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, error) { return true, nil } 2049 2050 conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, nil, plugDynAttrs, nil, slotDynAttrs, policyCheck) 2051 c.Assert(err, NotNil) 2052 c.Assert(err, ErrorMatches, `cannot connect plug "consumer" of snap "s1": invalid plug`) 2053 c.Assert(conn, IsNil) 2054 } 2055 2056 func (s *RepositorySuite) TestBeforeConnectValidationPolicyCheckFailure(c *C) { 2057 err := s.emptyRepo.AddInterface(&ifacetest.TestInterface{ 2058 InterfaceName: "iface2", 2059 BeforeConnectSlotCallback: func(slot *ConnectedSlot) error { return nil }, 2060 BeforeConnectPlugCallback: func(plug *ConnectedPlug) error { return nil }, 2061 }) 2062 c.Assert(err, IsNil) 2063 2064 s1 := snaptest.MockInfo(c, ifacehooksSnap1, nil) 2065 c.Assert(s.emptyRepo.AddSnap(s1), IsNil) 2066 s2 := snaptest.MockInfo(c, ifacehooksSnap2, nil) 2067 c.Assert(s.emptyRepo.AddSnap(s2), IsNil) 2068 2069 plugDynAttrs := map[string]interface{}{"attr1": "val1"} 2070 slotDynAttrs := map[string]interface{}{"attr1": "val1"} 2071 2072 policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, error) { 2073 return false, fmt.Errorf("policy check failed") 2074 } 2075 2076 conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, nil, plugDynAttrs, nil, slotDynAttrs, policyCheck) 2077 c.Assert(err, NotNil) 2078 c.Assert(err, ErrorMatches, `policy check failed`) 2079 c.Assert(conn, IsNil) 2080 } 2081 2082 func (s *RepositorySuite) TestConnection(c *C) { 2083 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 2084 c.Assert(s.testRepo.AddSlot(s.slot), IsNil) 2085 2086 connRef := NewConnRef(s.plug, s.slot) 2087 2088 _, err := s.testRepo.Connection(connRef) 2089 c.Assert(err, ErrorMatches, `no connection from consumer:plug to producer:slot`) 2090 2091 _, err = s.testRepo.Connect(connRef, nil, nil, nil, nil, nil) 2092 c.Assert(err, IsNil) 2093 2094 conn, err := s.testRepo.Connection(connRef) 2095 c.Assert(err, IsNil) 2096 c.Assert(conn.Plug.Name(), Equals, "plug") 2097 c.Assert(conn.Slot.Name(), Equals, "slot") 2098 2099 conn, err = s.testRepo.Connection(&ConnRef{PlugRef: PlugRef{Snap: "a", Name: "b"}, SlotRef: SlotRef{Snap: "producer", Name: "slot"}}) 2100 c.Assert(err, ErrorMatches, `snap "a" has no plug named "b"`) 2101 e, _ := err.(*NoPlugOrSlotError) 2102 c.Check(e, NotNil) 2103 2104 conn, err = s.testRepo.Connection(&ConnRef{PlugRef: PlugRef{Snap: "consumer", Name: "plug"}, SlotRef: SlotRef{Snap: "a", Name: "b"}}) 2105 c.Assert(err, ErrorMatches, `snap "a" has no slot named "b"`) 2106 e, _ = err.(*NoPlugOrSlotError) 2107 c.Check(e, NotNil) 2108 } 2109 2110 func (s *RepositorySuite) TestConnectWithStaticAttrs(c *C) { 2111 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 2112 c.Assert(s.testRepo.AddSlot(s.slot), IsNil) 2113 2114 connRef := NewConnRef(s.plug, s.slot) 2115 2116 plugAttrs := map[string]interface{}{"foo": "bar"} 2117 slotAttrs := map[string]interface{}{"boo": "baz"} 2118 _, err := s.testRepo.Connect(connRef, plugAttrs, nil, slotAttrs, nil, nil) 2119 c.Assert(err, IsNil) 2120 2121 conn, err := s.testRepo.Connection(connRef) 2122 c.Assert(err, IsNil) 2123 c.Assert(conn.Plug.Name(), Equals, "plug") 2124 c.Assert(conn.Slot.Name(), Equals, "slot") 2125 c.Assert(conn.Plug.StaticAttrs(), DeepEquals, plugAttrs) 2126 c.Assert(conn.Slot.StaticAttrs(), DeepEquals, slotAttrs) 2127 } 2128 2129 func (s *RepositorySuite) TestAllHotplugInterfaces(c *C) { 2130 repo := NewRepository() 2131 c.Assert(repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "iface1"}), IsNil) 2132 c.Assert(repo.AddInterface(&ifacetest.TestHotplugInterface{TestInterface: ifacetest.TestInterface{InterfaceName: "iface2"}}), IsNil) 2133 c.Assert(repo.AddInterface(&ifacetest.TestHotplugInterface{TestInterface: ifacetest.TestInterface{InterfaceName: "iface3"}}), IsNil) 2134 2135 hi := repo.AllHotplugInterfaces() 2136 c.Assert(hi, HasLen, 2) 2137 c.Assert(hi["iface2"], DeepEquals, &ifacetest.TestHotplugInterface{TestInterface: ifacetest.TestInterface{InterfaceName: "iface2"}}) 2138 c.Assert(hi["iface3"], DeepEquals, &ifacetest.TestHotplugInterface{TestInterface: ifacetest.TestInterface{InterfaceName: "iface3"}}) 2139 } 2140 2141 func (s *RepositorySuite) TestHotplugMethods(c *C) { 2142 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 2143 2144 coreSlot := &snap.SlotInfo{ 2145 Snap: s.coreSnap, 2146 Name: "dummy-slot", 2147 Interface: "interface", 2148 HotplugKey: "1234", 2149 } 2150 c.Assert(s.testRepo.AddSlot(coreSlot), IsNil) 2151 2152 slotInfo, err := s.testRepo.SlotForHotplugKey("interface", "1234") 2153 c.Assert(err, IsNil) 2154 c.Check(slotInfo, DeepEquals, coreSlot) 2155 2156 // no slot for device key 9999 2157 slotInfo, err = s.testRepo.SlotForHotplugKey("interface", "9999") 2158 c.Assert(err, IsNil) 2159 c.Check(slotInfo, IsNil) 2160 2161 _, err = s.testRepo.Connect(NewConnRef(s.plug, coreSlot), nil, nil, nil, nil, nil) 2162 c.Assert(err, IsNil) 2163 2164 conns, err := s.testRepo.ConnectionsForHotplugKey("interface", "1234") 2165 c.Assert(err, IsNil) 2166 c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, coreSlot)}) 2167 2168 // no connections for device 9999 2169 conns, err = s.testRepo.ConnectionsForHotplugKey("interface", "9999") 2170 c.Assert(err, IsNil) 2171 c.Check(conns, HasLen, 0) 2172 } 2173 2174 func (s *RepositorySuite) TestUpdateHotplugSlotAttrs(c *C) { 2175 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 2176 coreSlot := &snap.SlotInfo{ 2177 Snap: s.coreSnap, 2178 Name: "dummy-slot", 2179 Interface: "interface", 2180 HotplugKey: "1234", 2181 Attrs: map[string]interface{}{"a": "b"}, 2182 } 2183 c.Assert(s.testRepo.AddSlot(coreSlot), IsNil) 2184 2185 slot, err := s.testRepo.UpdateHotplugSlotAttrs("interface", "unknownkey", nil) 2186 c.Assert(err, ErrorMatches, `cannot find hotplug slot for interface interface and hotplug key "unknownkey"`) 2187 c.Assert(slot, IsNil) 2188 2189 newAttrs := map[string]interface{}{"c": "d"} 2190 slot, err = s.testRepo.UpdateHotplugSlotAttrs("interface", "1234", newAttrs) 2191 // attributes are copied, so this change shouldn't be visible 2192 newAttrs["c"] = "tainted" 2193 c.Assert(err, IsNil) 2194 c.Assert(slot, NotNil) 2195 c.Assert(slot.Attrs, DeepEquals, map[string]interface{}{"c": "d"}) 2196 c.Assert(coreSlot.Attrs, DeepEquals, map[string]interface{}{"c": "d"}) 2197 } 2198 2199 func (s *RepositorySuite) TestUpdateHotplugSlotAttrsConnectedError(c *C) { 2200 c.Assert(s.testRepo.AddPlug(s.plug), IsNil) 2201 coreSlot := &snap.SlotInfo{ 2202 Snap: s.coreSnap, 2203 Name: "dummy-slot", 2204 Interface: "interface", 2205 HotplugKey: "1234", 2206 } 2207 c.Assert(s.testRepo.AddSlot(coreSlot), IsNil) 2208 2209 _, err := s.testRepo.Connect(NewConnRef(s.plug, coreSlot), nil, nil, nil, nil, nil) 2210 c.Assert(err, IsNil) 2211 2212 slot, err := s.testRepo.UpdateHotplugSlotAttrs("interface", "1234", map[string]interface{}{"c": "d"}) 2213 c.Assert(err, ErrorMatches, `internal error: cannot update slot dummy-slot while connected`) 2214 c.Assert(slot, IsNil) 2215 }