github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/ifacestate/hotplug_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2018 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package ifacestate_test 21 22 import ( 23 "crypto/sha256" 24 "fmt" 25 "os" 26 "path/filepath" 27 "time" 28 29 "github.com/snapcore/snapd/dirs" 30 "github.com/snapcore/snapd/interfaces" 31 "github.com/snapcore/snapd/interfaces/builtin" 32 "github.com/snapcore/snapd/interfaces/hotplug" 33 "github.com/snapcore/snapd/interfaces/ifacetest" 34 "github.com/snapcore/snapd/osutil" 35 "github.com/snapcore/snapd/overlord" 36 "github.com/snapcore/snapd/overlord/configstate/config" 37 "github.com/snapcore/snapd/overlord/hookstate" 38 "github.com/snapcore/snapd/overlord/ifacestate" 39 "github.com/snapcore/snapd/overlord/ifacestate/udevmonitor" 40 "github.com/snapcore/snapd/overlord/snapstate" 41 "github.com/snapcore/snapd/overlord/state" 42 "github.com/snapcore/snapd/snap" 43 "github.com/snapcore/snapd/snap/snaptest" 44 "github.com/snapcore/snapd/testutil" 45 46 . "gopkg.in/check.v1" 47 ) 48 49 type hotplugSuite struct { 50 testutil.BaseTest 51 AssertsMock 52 53 o *overlord.Overlord 54 state *state.State 55 secBackend *ifacetest.TestSecurityBackend 56 mockSnapCmd *testutil.MockCmd 57 58 udevMon *udevMonitorMock 59 mgr *ifacestate.InterfaceManager 60 handledByGadgetCalled int 61 62 ifaceTestAAutoConnect bool 63 } 64 65 type hotplugTasksWitness struct { 66 seenHooks map[string]string 67 seenHotplugRemoveKeys map[snap.HotplugKey]string 68 seenHotplugConnectKeys map[snap.HotplugKey]string 69 seenHotplugUpdateKeys map[snap.HotplugKey]string 70 seenHotplugAddKeys map[snap.HotplugKey]string 71 seenTasks map[string]int 72 hotplugDisconnects map[snap.HotplugKey]string 73 connects []string 74 disconnects []string 75 } 76 77 func (w *hotplugTasksWitness) checkTasks(c *C, st *state.State) { 78 w.seenTasks = make(map[string]int) 79 w.seenHotplugRemoveKeys = make(map[snap.HotplugKey]string) 80 w.seenHotplugConnectKeys = make(map[snap.HotplugKey]string) 81 w.seenHotplugUpdateKeys = make(map[snap.HotplugKey]string) 82 w.seenHotplugAddKeys = make(map[snap.HotplugKey]string) 83 w.hotplugDisconnects = make(map[snap.HotplugKey]string) 84 w.seenHooks = make(map[string]string) 85 for _, t := range st.Tasks() { 86 c.Check(t.Status(), Equals, state.DoneStatus) 87 if t.Kind() == "run-hook" { 88 var hookSup hookstate.HookSetup 89 c.Assert(t.Get("hook-setup", &hookSup), IsNil) 90 _, ok := w.seenHooks[hookSup.Hook] 91 c.Assert(ok, Equals, false) 92 w.seenHooks[hookSup.Hook] = hookSup.Snap 93 continue 94 } 95 w.seenTasks[t.Kind()]++ 96 if t.Kind() == "connect" || t.Kind() == "disconnect" { 97 var plugRef interfaces.PlugRef 98 var slotRef interfaces.SlotRef 99 c.Assert(t.Get("plug", &plugRef), IsNil) 100 c.Assert(t.Get("slot", &slotRef), IsNil) 101 if t.Kind() == "connect" { 102 w.connects = append(w.connects, fmt.Sprintf("%s %s", plugRef, slotRef)) 103 } else { 104 testByHotplugTaskFlag(c, t) 105 w.disconnects = append(w.disconnects, fmt.Sprintf("%s %s", plugRef, slotRef)) 106 } 107 continue 108 } 109 110 if t.Kind() == "hotplug-seq-wait" { 111 continue 112 } 113 114 iface, key, err := ifacestate.GetHotplugAttrs(t) 115 c.Check(err, IsNil) 116 117 switch { 118 case t.Kind() == "hotplug-add-slot": 119 w.seenHotplugAddKeys[key] = iface 120 case t.Kind() == "hotplug-connect": 121 w.seenHotplugConnectKeys[key] = iface 122 case t.Kind() == "hotplug-update-slot": 123 w.seenHotplugUpdateKeys[key] = iface 124 case t.Kind() == "hotplug-remove-slot": 125 w.seenHotplugRemoveKeys[key] = iface 126 case t.Kind() == "hotplug-disconnect": 127 w.hotplugDisconnects[key] = iface 128 } 129 } 130 } 131 132 var _ = Suite(&hotplugSuite{}) 133 134 func (s *hotplugSuite) SetUpTest(c *C) { 135 s.BaseTest.SetUpTest(c) 136 s.secBackend = &ifacetest.TestSecurityBackend{} 137 s.BaseTest.AddCleanup(ifacestate.MockSecurityBackends([]interfaces.SecurityBackend{s.secBackend})) 138 139 dirs.SetRootDir(c.MkDir()) 140 c.Assert(os.MkdirAll(filepath.Dir(dirs.SnapSystemKeyFile), 0755), IsNil) 141 142 restore := osutil.MockMountInfo("") 143 s.AddCleanup(restore) 144 145 s.o = overlord.Mock() 146 s.state = s.o.State() 147 148 s.mockSnapCmd = testutil.MockCommand(c, "snap", "") 149 150 s.SetupAsserts(c, s.state, &s.BaseTest) 151 152 restoreTimeout := ifacestate.MockUDevInitRetryTimeout(0 * time.Second) 153 s.BaseTest.AddCleanup(restoreTimeout) 154 155 s.udevMon = &udevMonitorMock{} 156 restoreCreate := ifacestate.MockCreateUDevMonitor(func(add udevmonitor.DeviceAddedFunc, remove udevmonitor.DeviceRemovedFunc, done udevmonitor.EnumerationDoneFunc) udevmonitor.Interface { 157 s.udevMon.AddDevice = add 158 s.udevMon.RemoveDevice = remove 159 s.udevMon.EnumerationDone = done 160 return s.udevMon 161 }) 162 s.BaseTest.AddCleanup(restoreCreate) 163 164 // mock core snap 165 si := &snap.SideInfo{RealName: "core", Revision: snap.R(1)} 166 snaptest.MockSnapInstance(c, "", coreSnapYaml, si) 167 s.state.Lock() 168 snapstate.Set(s.state, "core", &snapstate.SnapState{ 169 Active: true, 170 Sequence: []*snap.SideInfo{si}, 171 Current: snap.R(1), 172 SnapType: "os", 173 }) 174 175 tr := config.NewTransaction(s.state) 176 tr.Set("core", "experimental.hotplug", true) 177 tr.Commit() 178 179 s.state.Unlock() 180 181 hookMgr, err := hookstate.Manager(s.state, s.o.TaskRunner()) 182 c.Assert(err, IsNil) 183 s.o.AddManager(hookMgr) 184 185 s.mgr, err = ifacestate.Manager(s.state, hookMgr, s.o.TaskRunner(), nil, nil) 186 c.Assert(err, IsNil) 187 188 s.o.AddManager(s.mgr) 189 s.o.AddManager(s.o.TaskRunner()) 190 191 // startup 192 err = s.o.StartUp() 193 c.Assert(err, IsNil) 194 195 autoConnectNo := func(*snap.PlugInfo, *snap.SlotInfo) bool { 196 return false 197 } 198 s.ifaceTestAAutoConnect = false 199 testAAutoConnect := func(*snap.PlugInfo, *snap.SlotInfo) bool { 200 return s.ifaceTestAAutoConnect 201 } 202 203 testIface1 := &ifacetest.TestHotplugInterface{ 204 TestInterface: ifacetest.TestInterface{ 205 InterfaceName: "test-a", 206 AutoConnectCallback: testAAutoConnect, 207 }, 208 HotplugKeyCallback: func(deviceInfo *hotplug.HotplugDeviceInfo) (snap.HotplugKey, error) { 209 return "key-1", nil 210 }, 211 HotplugDeviceDetectedCallback: func(deviceInfo *hotplug.HotplugDeviceInfo) (*hotplug.ProposedSlot, error) { 212 return &hotplug.ProposedSlot{ 213 Name: "hotplugslot-a", 214 Attrs: map[string]interface{}{ 215 "slot-a-attr1": "a", 216 "path": deviceInfo.DevicePath(), 217 }}, nil 218 }, 219 } 220 testIface2 := &ifacetest.TestHotplugInterface{ 221 TestInterface: ifacetest.TestInterface{ 222 InterfaceName: "test-b", 223 AutoConnectCallback: autoConnectNo, 224 }, 225 HotplugKeyCallback: func(deviceInfo *hotplug.HotplugDeviceInfo) (snap.HotplugKey, error) { 226 return "key-2", nil 227 }, 228 HotplugDeviceDetectedCallback: func(deviceInfo *hotplug.HotplugDeviceInfo) (*hotplug.ProposedSlot, error) { 229 return &hotplug.ProposedSlot{Name: "hotplugslot-b"}, nil 230 }, 231 HandledByGadgetCallback: func(di *hotplug.HotplugDeviceInfo, slot *snap.SlotInfo) bool { 232 s.handledByGadgetCalled++ 233 var path string 234 slot.Attr("path", &path) 235 return di.DeviceName() == path 236 }, 237 } 238 // 3rd hotplug interface doesn't create hotplug slot (to simulate a case where doesn't device is not supported) 239 testIface3 := &ifacetest.TestHotplugInterface{ 240 TestInterface: ifacetest.TestInterface{ 241 InterfaceName: "test-c", 242 AutoConnectCallback: autoConnectNo, 243 }, 244 HotplugKeyCallback: func(deviceInfo *hotplug.HotplugDeviceInfo) (snap.HotplugKey, error) { 245 return "key-3", nil 246 }, 247 HotplugDeviceDetectedCallback: func(deviceInfo *hotplug.HotplugDeviceInfo) (*hotplug.ProposedSlot, error) { 248 return nil, nil 249 }, 250 } 251 // 3rd hotplug interface will only create a slot if default hotplug key can be computed 252 testIface4 := &ifacetest.TestHotplugInterface{ 253 TestInterface: ifacetest.TestInterface{ 254 InterfaceName: "test-d", 255 AutoConnectCallback: autoConnectNo, 256 }, 257 HotplugDeviceDetectedCallback: func(deviceInfo *hotplug.HotplugDeviceInfo) (*hotplug.ProposedSlot, error) { 258 return &hotplug.ProposedSlot{Name: "hotplugslot-d"}, nil 259 }, 260 } 261 262 for _, iface := range []interfaces.Interface{testIface1, testIface2, testIface3, testIface4} { 263 c.Assert(s.mgr.Repository().AddInterface(iface), IsNil) 264 s.AddCleanup(builtin.MockInterface(iface)) 265 } 266 267 // single Ensure to have udev monitor created and wired up by interface manager 268 c.Assert(s.mgr.Ensure(), IsNil) 269 } 270 271 func (s *hotplugSuite) TearDownTest(c *C) { 272 s.BaseTest.TearDownTest(c) 273 dirs.SetRootDir("") 274 s.mockSnapCmd.Restore() 275 } 276 277 func testPlugSlotRefs(c *C, t *state.Task, plugSnap, plugName, slotSnap, slotName string) { 278 var plugRef interfaces.PlugRef 279 var slotRef interfaces.SlotRef 280 c.Assert(t.Get("plug", &plugRef), IsNil) 281 c.Assert(t.Get("slot", &slotRef), IsNil) 282 c.Assert(plugRef, DeepEquals, interfaces.PlugRef{Snap: plugSnap, Name: plugName}) 283 c.Assert(slotRef, DeepEquals, interfaces.SlotRef{Snap: slotSnap, Name: slotName}) 284 } 285 286 func testHotplugTaskAttrs(c *C, t *state.Task, ifaceName, hotplugKey string) { 287 iface, key, err := ifacestate.GetHotplugAttrs(t) 288 c.Assert(err, IsNil) 289 c.Assert(key, Equals, hotplugKey) 290 c.Assert(iface, Equals, ifaceName) 291 } 292 293 func testByHotplugTaskFlag(c *C, t *state.Task) { 294 var byHotplug bool 295 c.Assert(t.Get("by-hotplug", &byHotplug), IsNil) 296 c.Assert(byHotplug, Equals, true) 297 } 298 299 func (s *hotplugSuite) TestHotplugAddBasic(c *C) { 300 s.MockModel(c, nil) 301 302 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "a/path", "ACTION": "add", "SUBSYSTEM": "foo"}) 303 c.Assert(err, IsNil) 304 s.udevMon.AddDevice(di) 305 306 c.Assert(s.o.Settle(5*time.Second), IsNil) 307 308 st := s.state 309 st.Lock() 310 defer st.Unlock() 311 312 var hp hotplugTasksWitness 313 hp.checkTasks(c, st) 314 c.Check(hp.seenTasks, DeepEquals, map[string]int{"hotplug-seq-wait": 2, "hotplug-add-slot": 2, "hotplug-connect": 2}) 315 c.Check(hp.seenHotplugAddKeys, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a", "key-2": "test-b"}) 316 c.Check(hp.seenHotplugConnectKeys, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a", "key-2": "test-b"}) 317 c.Check(hp.seenHooks, HasLen, 0) 318 c.Check(hp.connects, HasLen, 0) 319 320 // make sure slots have been created in the repo 321 repo := s.mgr.Repository() 322 slot, err := repo.SlotForHotplugKey("test-a", "key-1") 323 c.Assert(err, IsNil) 324 c.Assert(slot, NotNil) 325 slots := repo.AllSlots("test-a") 326 c.Assert(slots, HasLen, 1) 327 c.Check(slots[0].Name, Equals, "hotplugslot-a") 328 c.Check(slots[0].Attrs, DeepEquals, map[string]interface{}{ 329 "path": di.DevicePath(), 330 "slot-a-attr1": "a"}) 331 c.Check(slots[0].HotplugKey, DeepEquals, snap.HotplugKey("key-1")) 332 333 slot, err = repo.SlotForHotplugKey("test-b", "key-2") 334 c.Assert(err, IsNil) 335 c.Assert(slot, NotNil) 336 337 slot, err = repo.SlotForHotplugKey("test-c", "key-3") 338 c.Assert(err, IsNil) 339 c.Assert(slot, IsNil) 340 341 c.Check(s.handledByGadgetCalled, Equals, 0) 342 } 343 344 func (s *hotplugSuite) TestHotplugConnectWithGadgetSlot(c *C) { 345 s.MockModel(c, map[string]interface{}{ 346 "gadget": "the-gadget", 347 }) 348 349 st := s.state 350 st.Lock() 351 defer st.Unlock() 352 353 gadgetSideInfo := &snap.SideInfo{RealName: "the-gadget", SnapID: "the-gadget-id", Revision: snap.R(1)} 354 gadgetInfo := snaptest.MockSnap(c, ` 355 name: the-gadget 356 type: gadget 357 version: 1.0 358 359 slots: 360 slot1: 361 interface: test-b 362 path: /dev/path 363 `, gadgetSideInfo) 364 snapstate.Set(s.state, "the-gadget", &snapstate.SnapState{ 365 Active: true, 366 Sequence: []*snap.SideInfo{&gadgetInfo.SideInfo}, 367 Current: snap.R(1), 368 SnapType: "gadget"}) 369 st.Unlock() 370 371 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{ 372 "DEVNAME": "/dev/path", 373 "DEVPATH": "a/path", 374 "ACTION": "add", 375 "SUBSYSTEM": "foo"}) 376 c.Assert(err, IsNil) 377 s.udevMon.AddDevice(di) 378 379 c.Assert(s.o.Settle(5*time.Second), IsNil) 380 st.Lock() 381 382 c.Check(s.handledByGadgetCalled, Equals, 1) 383 384 // make sure hotplug slot has been created in the repo 385 repo := s.mgr.Repository() 386 slot, err := repo.SlotForHotplugKey("test-a", "key-1") 387 c.Assert(err, IsNil) 388 c.Assert(slot, NotNil) 389 390 // but no hotplug slot has been created for the device path defined by gadget 391 slot, err = repo.SlotForHotplugKey("test-b", "key-2") 392 c.Assert(err, IsNil) 393 c.Assert(slot, IsNil) 394 } 395 396 func (s *hotplugSuite) TestHotplugAddWithDefaultKey(c *C) { 397 s.MockModel(c, nil) 398 399 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{ 400 "DEVPATH": "a/path", 401 "ACTION": "add", 402 "SUBSYSTEM": "foo", 403 "ID_VENDOR_ID": "vendor", 404 "ID_MODEL_ID": "model", 405 "ID_SERIAL_SHORT": "serial", 406 }) 407 c.Assert(err, IsNil) 408 s.udevMon.AddDevice(di) 409 410 c.Assert(s.o.Settle(5*time.Second), IsNil) 411 412 st := s.state 413 st.Lock() 414 defer st.Unlock() 415 416 var hp hotplugTasksWitness 417 hp.checkTasks(c, st) 418 c.Check(hp.seenTasks, DeepEquals, map[string]int{"hotplug-seq-wait": 3, "hotplug-add-slot": 3, "hotplug-connect": 3}) 419 c.Check(hp.seenHooks, HasLen, 0) 420 c.Check(hp.connects, HasLen, 0) 421 testIfaceDkey := keyHelper("ID_VENDOR_ID\x00vendor\x00ID_MODEL_ID\x00model\x00ID_SERIAL_SHORT\x00serial\x00") 422 c.Assert(hp.seenHotplugAddKeys, DeepEquals, map[snap.HotplugKey]string{ 423 "key-1": "test-a", 424 "key-2": "test-b", 425 testIfaceDkey: "test-d"}) 426 c.Assert(hp.seenHotplugConnectKeys, DeepEquals, map[snap.HotplugKey]string{ 427 "key-1": "test-a", 428 "key-2": "test-b", 429 testIfaceDkey: "test-d"}) 430 431 // make sure the slot has been created 432 repo := s.mgr.Repository() 433 slots := repo.AllSlots("test-d") 434 c.Assert(slots, HasLen, 1) 435 c.Check(slots[0].Name, Equals, "hotplugslot-d") 436 c.Check(slots[0].HotplugKey, Equals, testIfaceDkey) 437 } 438 439 func (s *hotplugSuite) TestHotplugAddWithAutoconnect(c *C) { 440 s.MockModel(c, nil) 441 442 s.ifaceTestAAutoConnect = true 443 444 repo := s.mgr.Repository() 445 st := s.state 446 447 st.Lock() 448 // mock the consumer snap/plug 449 si := &snap.SideInfo{RealName: "consumer", Revision: snap.R(1)} 450 testSnap := snaptest.MockSnapInstance(c, "", testSnapYaml, si) 451 c.Assert(testSnap.Plugs, HasLen, 1) 452 c.Assert(testSnap.Plugs["plug"], NotNil) 453 c.Assert(repo.AddPlug(testSnap.Plugs["plug"]), IsNil) 454 snapstate.Set(s.state, "consumer", &snapstate.SnapState{ 455 Active: true, 456 Sequence: []*snap.SideInfo{si}, 457 Current: snap.R(1), 458 SnapType: "app", 459 }) 460 st.Unlock() 461 462 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "a/path", "ACTION": "add", "SUBSYSTEM": "foo"}) 463 c.Assert(err, IsNil) 464 s.udevMon.AddDevice(di) 465 466 c.Assert(s.o.Settle(5*time.Second), IsNil) 467 st.Lock() 468 defer st.Unlock() 469 470 // verify hotplug tasks 471 var hp hotplugTasksWitness 472 hp.checkTasks(c, st) 473 c.Check(hp.seenTasks, DeepEquals, map[string]int{"hotplug-seq-wait": 2, "hotplug-add-slot": 2, "hotplug-connect": 2, "connect": 1}) 474 c.Check(hp.seenHooks, DeepEquals, map[string]string{"prepare-plug-plug": "consumer", "connect-plug-plug": "consumer"}) 475 c.Check(hp.seenHotplugAddKeys, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a", "key-2": "test-b"}) 476 c.Check(hp.seenHotplugConnectKeys, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a", "key-2": "test-b"}) 477 c.Check(hp.connects, DeepEquals, []string{"consumer:plug core:hotplugslot-a"}) 478 479 // make sure slots have been created in the repo 480 slot, err := repo.SlotForHotplugKey("test-a", "key-1") 481 c.Assert(err, IsNil) 482 c.Assert(slot, NotNil) 483 484 conn, err := repo.Connection(&interfaces.ConnRef{ 485 PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"}, 486 SlotRef: interfaces.SlotRef{Snap: "core", Name: "hotplugslot-a"}}) 487 c.Assert(err, IsNil) 488 c.Assert(conn, NotNil) 489 } 490 491 var testSnapYaml = ` 492 name: consumer 493 version: 1 494 plugs: 495 plug: 496 interface: test-a 497 hooks: 498 prepare-plug-plug: 499 connect-plug-plug: 500 disconnect-plug-plug: 501 ` 502 503 func (s *hotplugSuite) TestHotplugRemove(c *C) { 504 st := s.state 505 st.Lock() 506 507 st.Set("conns", map[string]interface{}{ 508 "consumer:plug core:hotplugslot": map[string]interface{}{ 509 "interface": "test-a", 510 "hotplug-key": "key-1", 511 "hotplug-gone": false}}) 512 st.Set("hotplug-slots", map[string]interface{}{ 513 "hotplugslot": map[string]interface{}{ 514 "name": "hotplugslot", 515 "interface": "test-a", 516 "hotplug-key": "key-1", 517 "hotplug-gone": false}}) 518 519 repo := s.mgr.Repository() 520 si := &snap.SideInfo{RealName: "consumer", Revision: snap.R(1)} 521 testSnap := snaptest.MockSnapInstance(c, "", testSnapYaml, si) 522 c.Assert(repo.AddPlug(&snap.PlugInfo{ 523 Interface: "test-a", 524 Name: "plug", 525 Attrs: map[string]interface{}{}, 526 Snap: testSnap, 527 }), IsNil) 528 snapstate.Set(s.state, "consumer", &snapstate.SnapState{ 529 Active: true, 530 Sequence: []*snap.SideInfo{si}, 531 Current: snap.R(1), 532 SnapType: "app", 533 }) 534 535 core, err := snapstate.CurrentInfo(s.state, "core") 536 c.Assert(err, IsNil) 537 c.Assert(repo.AddSlot(&snap.SlotInfo{ 538 Interface: "test-a", 539 Name: "hotplugslot", 540 Attrs: map[string]interface{}{}, 541 Snap: core, 542 HotplugKey: "key-1", 543 }), IsNil) 544 545 conn, err := repo.Connect(&interfaces.ConnRef{ 546 PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"}, 547 SlotRef: interfaces.SlotRef{Snap: "core", Name: "hotplugslot"}, 548 }, nil, nil, nil, nil, nil) 549 c.Assert(err, IsNil) 550 c.Assert(conn, NotNil) 551 552 restore := s.mgr.MockObservedDevicePath(filepath.Join(dirs.SysfsDir, "a/path"), "test-a", "key-1") 553 defer restore() 554 555 st.Unlock() 556 557 slot, _ := repo.SlotForHotplugKey("test-a", "key-1") 558 c.Assert(slot, NotNil) 559 560 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "a/path", "ACTION": "remove", "SUBSYSTEM": "foo"}) 561 c.Assert(err, IsNil) 562 s.udevMon.RemoveDevice(di) 563 564 c.Assert(s.o.Settle(5*time.Second), IsNil) 565 566 st.Lock() 567 defer st.Unlock() 568 569 // verify hotplug tasks 570 var hp hotplugTasksWitness 571 hp.checkTasks(c, st) 572 c.Check(hp.seenHooks, DeepEquals, map[string]string{"disconnect-plug-plug": "consumer"}) 573 c.Check(hp.seenHotplugRemoveKeys, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a"}) 574 c.Check(hp.hotplugDisconnects, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a"}) 575 c.Check(hp.seenTasks, DeepEquals, map[string]int{"hotplug-seq-wait": 1, "hotplug-disconnect": 1, "disconnect": 1, "hotplug-remove-slot": 1}) 576 c.Check(hp.disconnects, DeepEquals, []string{"consumer:plug core:hotplugslot"}) 577 578 slot, _ = repo.SlotForHotplugKey("test-a", "key-1") 579 c.Assert(slot, IsNil) 580 581 var newconns map[string]interface{} 582 c.Assert(st.Get("conns", &newconns), IsNil) 583 c.Assert(newconns, DeepEquals, map[string]interface{}{ 584 "consumer:plug core:hotplugslot": map[string]interface{}{ 585 "interface": "test-a", 586 "hotplug-key": "key-1", 587 "hotplug-gone": true}}) 588 } 589 590 func (s *hotplugSuite) TestHotplugEnumerationDone(c *C) { 591 s.MockModel(c, nil) 592 593 st := s.state 594 st.Lock() 595 596 // existing connection 597 st.Set("conns", map[string]interface{}{ 598 "consumer:plug core:hotplugslot": map[string]interface{}{ 599 "interface": "test-a", 600 "hotplug-key": "key-other-device", 601 "hotplug-gone": false}}) 602 603 repo := s.mgr.Repository() 604 605 si := &snap.SideInfo{RealName: "consumer", Revision: snap.R(1)} 606 testSnap := snaptest.MockSnapInstance(c, "", testSnapYaml, si) 607 c.Assert(repo.AddPlug(&snap.PlugInfo{ 608 Interface: "test-a", 609 Name: "plug", 610 Attrs: map[string]interface{}{}, 611 Snap: testSnap, 612 }), IsNil) 613 snapstate.Set(s.state, "consumer", &snapstate.SnapState{ 614 Active: true, 615 Sequence: []*snap.SideInfo{si}, 616 Current: snap.R(1), 617 SnapType: "app"}) 618 619 core, err := snapstate.CurrentInfo(s.state, "core") 620 c.Assert(err, IsNil) 621 c.Assert(repo.AddSlot(&snap.SlotInfo{ 622 Interface: "test-a", 623 Name: "hotplugslot", 624 Attrs: map[string]interface{}{}, 625 Snap: core, 626 HotplugKey: "key-other-device", 627 }), IsNil) 628 629 conn, err := repo.Connect(&interfaces.ConnRef{ 630 PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"}, 631 SlotRef: interfaces.SlotRef{Snap: "core", Name: "hotplugslot"}, 632 }, nil, nil, nil, nil, nil) 633 c.Assert(err, IsNil) 634 c.Assert(conn, NotNil) 635 636 st.Set("hotplug-slots", map[string]interface{}{ 637 "hotplugslot": map[string]interface{}{ 638 "name": "hotplugslot", 639 "interface": "test-a", 640 "hotplug-key": "key-other-device"}, 641 "anotherslot": map[string]interface{}{ 642 "name": "anotherslot", 643 "interface": "test-a", 644 "hotplug-key": "yet-another-device"}}) 645 646 // sanity 647 slot, _ := repo.SlotForHotplugKey("test-a", "key-other-device") 648 c.Assert(slot, NotNil) 649 650 st.Unlock() 651 652 // new device added; device for existing connection not present when enumeration is finished 653 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "a/path", "ACTION": "add", "SUBSYSTEM": "foo"}) 654 c.Assert(err, IsNil) 655 s.udevMon.AddDevice(di) 656 s.udevMon.EnumerationDone() 657 658 c.Assert(s.o.Settle(5*time.Second), IsNil) 659 660 s.state.Lock() 661 defer s.state.Unlock() 662 663 // make sure slots for new device have been created in the repo 664 hpslot, _ := repo.SlotForHotplugKey("test-a", "key-1") 665 c.Assert(hpslot, NotNil) 666 hpslot, _ = repo.SlotForHotplugKey("test-b", "key-2") 667 c.Assert(hpslot, NotNil) 668 669 // make sure slots for missing device got disconnected and removed 670 hpslot, _ = repo.SlotForHotplugKey("test-a", "key-other-device") 671 c.Assert(hpslot, IsNil) 672 673 // and the connection for missing device is marked with hotplug-gone: true; 674 // "anotherslot" is removed completely since there was no connection for it. 675 var newconns map[string]interface{} 676 c.Assert(st.Get("conns", &newconns), IsNil) 677 c.Check(newconns, DeepEquals, map[string]interface{}{ 678 "consumer:plug core:hotplugslot": map[string]interface{}{ 679 "hotplug-gone": true, 680 "hotplug-key": "key-other-device", 681 "interface": "test-a"}}) 682 683 var newHotplugSlots map[string]interface{} 684 c.Assert(st.Get("hotplug-slots", &newHotplugSlots), IsNil) 685 c.Check(newHotplugSlots, DeepEquals, map[string]interface{}{ 686 "hotplugslot-a": map[string]interface{}{ 687 "interface": "test-a", "hotplug-gone": false, "static-attrs": map[string]interface{}{"slot-a-attr1": "a", "path": di.DevicePath()}, "hotplug-key": "key-1", "name": "hotplugslot-a"}, 688 "hotplugslot-b": map[string]interface{}{ 689 "name": "hotplugslot-b", "hotplug-gone": false, "interface": "test-b", "hotplug-key": "key-2"}, 690 "hotplugslot": map[string]interface{}{"name": "hotplugslot", "hotplug-gone": true, "interface": "test-a", "hotplug-key": "key-other-device"}}) 691 } 692 693 func (s *hotplugSuite) TestHotplugDeviceUpdate(c *C) { 694 s.MockModel(c, nil) 695 st := s.state 696 st.Lock() 697 698 // existing connection 699 st.Set("conns", map[string]interface{}{ 700 "consumer:plug core:hotplugslot-a": map[string]interface{}{ 701 "interface": "test-a", 702 "hotplug-key": "key-1", 703 "hotplug-gone": false, 704 "slot-static": map[string]interface{}{"path": "/path-1"}}}) 705 706 repo := s.mgr.Repository() 707 si := &snap.SideInfo{RealName: "consumer", Revision: snap.R(1)} 708 testSnap := snaptest.MockSnapInstance(c, "", testSnapYaml, si) 709 c.Assert(repo.AddPlug(&snap.PlugInfo{ 710 Interface: "test-a", 711 Name: "plug", 712 Attrs: map[string]interface{}{}, 713 Snap: testSnap, 714 }), IsNil) 715 snapstate.Set(s.state, "consumer", &snapstate.SnapState{ 716 Active: true, 717 Sequence: []*snap.SideInfo{si}, 718 Current: snap.R(1), 719 SnapType: "app"}) 720 721 core, err := snapstate.CurrentInfo(s.state, "core") 722 c.Assert(err, IsNil) 723 c.Assert(repo.AddSlot(&snap.SlotInfo{ 724 Interface: "test-a", 725 Name: "hotplugslot-a", 726 Attrs: map[string]interface{}{"path": "/path-1"}, 727 Snap: core, 728 HotplugKey: "key-1", 729 }), IsNil) 730 731 conn, err := repo.Connect(&interfaces.ConnRef{ 732 PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"}, 733 SlotRef: interfaces.SlotRef{Snap: "core", Name: "hotplugslot-a"}, 734 }, nil, nil, nil, nil, nil) 735 c.Assert(err, IsNil) 736 c.Assert(conn, NotNil) 737 738 st.Set("hotplug-slots", map[string]interface{}{ 739 "hotplugslot-a": map[string]interface{}{ 740 "name": "hotplugslot-a", 741 "interface": "test-a", 742 "hotplug-key": "key-1", 743 "static-attrs": map[string]interface{}{"path": "/path-1"}}}) 744 st.Unlock() 745 746 // simulate device update 747 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "a/path", "ACTION": "add", "SUBSYSTEM": "foo"}) 748 c.Assert(err, IsNil) 749 s.udevMon.AddDevice(di) 750 s.udevMon.EnumerationDone() 751 752 c.Assert(s.o.Settle(5*time.Second), IsNil) 753 754 st.Lock() 755 defer st.Unlock() 756 757 // verify hotplug tasks 758 var hp hotplugTasksWitness 759 hp.checkTasks(c, s.state) 760 761 // we see 2 hotplug-connect tasks because of interface test-a and test-b (the latter does nothing as there is no change) 762 c.Check(hp.seenTasks, DeepEquals, map[string]int{"hotplug-seq-wait": 2, "hotplug-connect": 2, "hotplug-disconnect": 1, "connect": 1, "disconnect": 1, "hotplug-add-slot": 2, "hotplug-update-slot": 1}) 763 c.Check(hp.seenHooks, DeepEquals, map[string]string{ 764 "disconnect-plug-plug": "consumer", 765 "prepare-plug-plug": "consumer", 766 "connect-plug-plug": "consumer"}) 767 c.Check(hp.hotplugDisconnects, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a"}) 768 c.Check(hp.seenHotplugAddKeys, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a", "key-2": "test-b"}) 769 c.Check(hp.seenHotplugConnectKeys, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a", "key-2": "test-b"}) 770 c.Check(hp.seenHotplugUpdateKeys, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a"}) 771 c.Check(hp.connects, DeepEquals, []string{"consumer:plug core:hotplugslot-a"}) 772 c.Check(hp.disconnects, DeepEquals, []string{"consumer:plug core:hotplugslot-a"}) 773 774 // make sure slots for new device have been updated in the repo 775 slot, err := repo.SlotForHotplugKey("test-a", "key-1") 776 c.Assert(err, IsNil) 777 c.Check(slot.Attrs, DeepEquals, map[string]interface{}{"path": di.DevicePath(), "slot-a-attr1": "a"}) 778 779 // and the connection attributes have been updated 780 var newconns map[string]interface{} 781 c.Assert(st.Get("conns", &newconns), IsNil) 782 c.Check(newconns, DeepEquals, map[string]interface{}{ 783 "consumer:plug core:hotplugslot-a": map[string]interface{}{ 784 "hotplug-key": "key-1", 785 "interface": "test-a", 786 "slot-static": map[string]interface{}{"path": di.DevicePath(), "slot-a-attr1": "a"}, 787 }}) 788 789 var newHotplugSlots map[string]interface{} 790 c.Assert(st.Get("hotplug-slots", &newHotplugSlots), IsNil) 791 c.Check(newHotplugSlots["hotplugslot-a"], DeepEquals, map[string]interface{}{ 792 "interface": "test-a", 793 "static-attrs": map[string]interface{}{"slot-a-attr1": "a", "path": di.DevicePath()}, 794 "hotplug-key": "key-1", 795 "name": "hotplugslot-a", 796 "hotplug-gone": false}) 797 } 798 799 func keyHelper(input string) snap.HotplugKey { 800 return snap.HotplugKey(fmt.Sprintf("0%x", sha256.Sum256([]byte(input)))) 801 } 802 803 func (s *hotplugSuite) TestDefaultDeviceKey(c *C) { 804 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{ 805 "DEVPATH": "a/path", 806 "ACTION": "add", 807 "SUBSYSTEM": "foo", 808 "ID_V4L_PRODUCT": "v4lproduct", 809 "NAME": "name", 810 "ID_VENDOR_ID": "vendor", 811 "ID_MODEL_ID": "model", 812 "ID_SERIAL": "serial", 813 "ID_REVISION": "revision", 814 }) 815 c.Assert(err, IsNil) 816 key, err := ifacestate.DefaultDeviceKey(di, 0) 817 c.Assert(err, IsNil) 818 819 // sanity check 820 c.Check(key, HasLen, 65) 821 c.Check(key, Equals, snap.HotplugKey("08bcbdcda3fee3534c0288506d9b75d4e26fe3692a36a11e75d05eac9ebf5ca7d")) 822 c.Assert(key, Equals, keyHelper("ID_V4L_PRODUCT\x00v4lproduct\x00ID_VENDOR_ID\x00vendor\x00ID_MODEL_ID\x00model\x00ID_SERIAL\x00serial\x00")) 823 824 di, err = hotplug.NewHotplugDeviceInfo(map[string]string{ 825 "DEVPATH": "a/path", 826 "ACTION": "add", 827 "SUBSYSTEM": "foo", 828 "NAME": "name", 829 "ID_WWN": "wnn", 830 "ID_MODEL_ENC": "modelenc", 831 "ID_REVISION": "revision", 832 }) 833 c.Assert(err, IsNil) 834 key, err = ifacestate.DefaultDeviceKey(di, 0) 835 c.Assert(err, IsNil) 836 c.Assert(key, Equals, keyHelper("NAME\x00name\x00ID_WWN\x00wnn\x00ID_MODEL_ENC\x00modelenc\x00ID_REVISION\x00revision\x00")) 837 838 di, err = hotplug.NewHotplugDeviceInfo(map[string]string{ 839 "DEVPATH": "a/path", 840 "ACTION": "add", 841 "SUBSYSTEM": "foo", 842 "PCI_SLOT_NAME": "pcislot", 843 "ID_MODEL_ENC": "modelenc", 844 }) 845 c.Assert(err, IsNil) 846 key, err = ifacestate.DefaultDeviceKey(di, 0) 847 c.Assert(key, Equals, keyHelper("PCI_SLOT_NAME\x00pcislot\x00ID_MODEL_ENC\x00modelenc\x00")) 848 c.Assert(err, IsNil) 849 850 // real device #1 - Lime SDR device 851 di, err = hotplug.NewHotplugDeviceInfo(map[string]string{ 852 "DEVNAME": "/dev/bus/usb/002/002", 853 "DEVNUM": "002", 854 "DEVPATH": "/devices/pci0000:00/0000:00:14.0/usb2/2-3", 855 "DEVTYPE": "usb_device", 856 "DRIVER": "usb", 857 "ID_BUS": "usb", 858 "ID_MODEL": "LimeSDR-USB", 859 "ID_MODEL_ENC": "LimeSDR-USB", 860 "ID_MODEL_FROM_DATABASE": "Myriad-RF LimeSDR", 861 "ID_MODEL_ID": "6108", 862 "ID_REVISION": "0000", 863 "ID_SERIAL": "Myriad-RF_LimeSDR-USB_0009060B00492E2C", 864 "ID_SERIAL_SHORT": "0009060B00492E2C", 865 "ID_USB_INTERFACES": ":ff0000:", 866 "ID_VENDOR": "Myriad-RF", 867 "ID_VENDOR_ENC": "Myriad-RF", 868 "ID_VENDOR_FROM_DATABASE": "OpenMoko, Inc.", 869 "ID_VENDOR_ID": "1d50", 870 "MAJOR": "189", 871 "MINOR": "129", 872 "PRODUCT": "1d50/6108/0", 873 "SUBSYSTEM": "usb", 874 "TYPE": "0/0/0", 875 "USEC_INITIALIZED": "6125378086 ", 876 }) 877 c.Assert(err, IsNil) 878 key, err = ifacestate.DefaultDeviceKey(di, 0) 879 c.Assert(err, IsNil) 880 c.Assert(key, Equals, keyHelper("ID_VENDOR_ID\x001d50\x00ID_MODEL_ID\x006108\x00ID_SERIAL\x00Myriad-RF_LimeSDR-USB_0009060B00492E2C\x00")) 881 882 // real device #2 - usb-serial port adapter 883 di, err = hotplug.NewHotplugDeviceInfo(map[string]string{ 884 "DEVLINKS": "/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_AH06W0EQ-if00-port0 /dev/serial/by-path/pci-0000:00:14.0-usb-0:2:1.0-port0", 885 "DEVNAME": "/dev/ttyUSB0", 886 "DEVPATH": "/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.0/ttyUSB0/tty/ttyUSB0", 887 "ID_BUS": "usb", 888 "ID_MM_CANDIDATE": "1", 889 "ID_MODEL_ENC": "FT232R\x20USB\x20UART", 890 "MODEL_FROM_DATABASE": "FT232 Serial (UART) IC", 891 "ID_MODEL_ID": "6001", 892 "ID_PATH": "pci-0000:00:14.0-usb-0:2:1.0", 893 "ID_PATH_TAG": "pci-0000_00_14_0-usb-0_2_1_0", 894 "ID_PCI_CLASS_FROM_DATABASE": "Serial bus controller", 895 "ID_PCI_INTERFACE_FROM_DATABASE": "XHCI", 896 "ID_PCI_SUBCLASS_FROM_DATABASE": "USB controller", 897 "ID_REVISION": "0600", 898 "ID_SERIAL": "FTDI_FT232R_USB_UART_AH06W0EQ", 899 "ID_SERIAL_SHORT": "AH06W0EQ", 900 "ID_TYPE": "generic", 901 "ID_USB_DRIVER": "ftdi_sio", 902 "ID_USB_INTERFACES": ":ffffff:", 903 "ID_USB_INTERFACE_NUM": "00", 904 "ID_VENDOR": "FTDI", 905 "ID_VENDOR_ENC": "FTDI", 906 "ID_VENDOR_FROM_DATABASE": "Future Technology Devices International, Ltd", 907 "ID_VENDOR_ID": "0403", 908 "MAJOR": "188", 909 "MINOR": "0", 910 "SUBSYSTEM": "tty", 911 "TAGS": ":systemd:", 912 "USEC_INITIALIZED": "6571662103", 913 }) 914 c.Assert(err, IsNil) 915 key, err = ifacestate.DefaultDeviceKey(di, 0) 916 c.Assert(err, IsNil) 917 c.Assert(key, Equals, keyHelper("ID_VENDOR_ID\x000403\x00ID_MODEL_ID\x006001\x00ID_SERIAL\x00FTDI_FT232R_USB_UART_AH06W0EQ\x00")) 918 919 // real device #3 - integrated web camera 920 di, err = hotplug.NewHotplugDeviceInfo(map[string]string{ 921 "COLORD_DEVICE": "1", 922 "COLORD_KIND": "camera", 923 "DEVLINKS": "/dev/v4l/by-path/pci-0000:00:14.0-usb-0:11:1.0-video-index0 /dev/v4l/by-id/usb-CN0J8NNP7248766FA3H3A01_Integrated_Webcam_HD_200901010001-video-index0", 924 "DEVNAME": "/dev/video0", 925 "DEVPATH": "/devices/pci0000:00/0000:00:14.0/usb1/1-11/1-11:1.0/video4linux/video0", 926 "ID_BUS": "usb", 927 "ID_FOR_SEAT": "video4linux-pci-0000_00_14_0-usb-0_11_1_0", 928 "ID_MODEL": "Integrated_Webcam_HD", 929 "ID_MODEL_ENC": "Integrated_Webcam_HD", 930 "ID_MODEL_ID": "57c3", 931 "ID_PATH": "pci-0000:00:14.0-usb-0:11:1.0", 932 "ID_PATH_TAG": "pci-0000_00_14_0-usb-0_11_1_0", 933 "ID_REVISION": "5806", 934 "ID_SERIAL": "CN0J8NNP7248766FA3H3A01_Integrated_Webcam_HD_200901010001", 935 "ID_SERIAL_SHORT": "200901010001", 936 "ID_TYPE": "video", 937 "ID_USB_DRIVER": "uvcvideo", 938 "ID_USB_INTERFACES": ":0e0100:0e0200:", 939 "ID_USB_INTERFACE_NUM": "00", 940 "ID_V4L_CAPABILITIES": ":capture:", 941 "ID_V4L_PRODUCT": "Integrated_Webcam_HD: Integrate", 942 "ID_V4L_VERSION": "2", 943 "ID_VENDOR": "CN0J8NNP7248766FA3H3A01", 944 "ID_VENDOR_ENC": "CN0J8NNP7248766FA3H3A01", 945 "ID_VENDOR_ID": "0bda", 946 "MAJOR": "81", 947 "MINOR": "0", 948 "SUBSYSTEM": "video4linux", 949 "TAGS": ":uaccess:seat:", 950 "USEC_INITIALIZED": "3411321", 951 }) 952 c.Assert(err, IsNil) 953 key, err = ifacestate.DefaultDeviceKey(di, 0) 954 c.Assert(err, IsNil) 955 c.Assert(key, Equals, keyHelper("ID_V4L_PRODUCT\x00Integrated_Webcam_HD: Integrate\x00ID_VENDOR_ID\x000bda\x00ID_MODEL_ID\x0057c3\x00ID_SERIAL\x00CN0J8NNP7248766FA3H3A01_Integrated_Webcam_HD_200901010001\x00")) 956 957 // key cannot be computed - empty string 958 di, err = hotplug.NewHotplugDeviceInfo(map[string]string{ 959 "DEVPATH": "a/path", 960 "ACTION": "add", 961 "SUBSYSTEM": "foo", 962 }) 963 c.Assert(err, IsNil) 964 key, err = ifacestate.DefaultDeviceKey(di, 0) 965 c.Assert(err, IsNil) 966 c.Assert(key, Equals, snap.HotplugKey("")) 967 } 968 969 func (s *hotplugSuite) TestDefaultDeviceKeyError(c *C) { 970 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{ 971 "DEVPATH": "a/path", 972 "ACTION": "add", 973 "SUBSYSTEM": "foo", 974 "NAME": "name", 975 "ID_VENDOR_ID": "vendor", 976 "ID_MODEL_ID": "model", 977 "ID_SERIAL": "serial", 978 }) 979 c.Assert(err, IsNil) 980 _, err = ifacestate.DefaultDeviceKey(di, 16) 981 c.Assert(err, ErrorMatches, "internal error: invalid key version 16") 982 } 983 984 func (s *hotplugSuite) TestEnsureUniqueName(c *C) { 985 fakeRepositoryLookup := func(n string) bool { 986 reserved := map[string]bool{ 987 "slot1": true, 988 "slot": true, 989 "slot1234": true, 990 "slot-1": true, 991 } 992 return !reserved[n] 993 } 994 995 names := []struct{ proposedName, resultingName string }{ 996 {"foo", "foo"}, 997 {"slot", "slot-2"}, 998 {"slot1234", "slot1234-1"}, 999 {"slot-1", "slot-1-1"}, 1000 } 1001 1002 for _, name := range names { 1003 c.Assert(ifacestate.EnsureUniqueName(name.proposedName, fakeRepositoryLookup), Equals, name.resultingName) 1004 } 1005 } 1006 1007 func (s *hotplugSuite) TestMakeSlotName(c *C) { 1008 names := []struct{ proposedName, resultingName string }{ 1009 {"", ""}, 1010 {"-", ""}, 1011 {"slot1", "slot1"}, 1012 {"-slot1", "slot1"}, 1013 {"a--slot-1", "a-slot-1"}, 1014 {"(-slot", "slot"}, 1015 {"(--slot", "slot"}, 1016 {"slot-", "slot"}, 1017 {"slot---", "slot"}, 1018 {"slot-(", "slot"}, 1019 {"Integrated_Webcam_HD", "integratedwebcamhd"}, 1020 {"Xeon E3-1200 v5/E3-1500 v5/6th Gen Core Processor Host Bridge/DRAM Registers", "xeone3-1200v5e3-1500"}, 1021 } 1022 for _, name := range names { 1023 c.Assert(ifacestate.MakeSlotName(name.proposedName), Equals, name.resultingName) 1024 } 1025 } 1026 1027 func (s *hotplugSuite) TestSuggestedSlotName(c *C) { 1028 1029 events := []struct { 1030 eventData map[string]string 1031 outName string 1032 }{{ 1033 map[string]string{ 1034 "DEVPATH": "a/path", 1035 "ACTION": "add", 1036 "SUBSYSTEM": "foo", 1037 "NAME": "Name", 1038 "ID_MODEL": "Longer Name", 1039 "ID_MODEL_FROM_DATABASE": "Longest Name", 1040 }, 1041 "name", 1042 }, { 1043 map[string]string{ 1044 "DEVPATH": "a/path", 1045 "ACTION": "add", 1046 "SUBSYSTEM": "foo", 1047 "ID_MODEL": "Longer Name", 1048 "ID_MODEL_FROM_DATABASE": "Longest Name", 1049 }, 1050 "longername", 1051 }, { 1052 map[string]string{ 1053 "DEVPATH": "a/path", 1054 "ACTION": "add", 1055 "SUBSYSTEM": "foo", 1056 "ID_MODEL_FROM_DATABASE": "Longest Name", 1057 }, 1058 "longestname", 1059 }, { 1060 map[string]string{ 1061 "DEVPATH": "a/path", 1062 "ACTION": "add", 1063 "SUBSYSTEM": "foo", 1064 }, 1065 "fallbackname", 1066 }, 1067 } 1068 1069 for _, data := range events { 1070 di, err := hotplug.NewHotplugDeviceInfo(data.eventData) 1071 c.Assert(err, IsNil) 1072 1073 slotName := ifacestate.SuggestedSlotName(di, "fallbackname") 1074 c.Assert(slotName, Equals, data.outName) 1075 } 1076 } 1077 1078 func (s *hotplugSuite) TestHotplugSlotName(c *C) { 1079 st := state.New(nil) 1080 st.Lock() 1081 defer st.Unlock() 1082 1083 testData := []struct { 1084 slotSpecName string 1085 deviceData map[string]string 1086 expectedName string 1087 }{ 1088 // names dervied from slotSpecName 1089 {"hdcamera", map[string]string{"DEVPATH": "a", "NAME": "Video Camera"}, "hdcamera"}, 1090 {"hdcamera", map[string]string{"DEVPATH": "a", "NAME": "Video Camera"}, "hdcamera-1"}, 1091 {"ieee1394", map[string]string{"DEVPATH": "a"}, "ieee1394"}, 1092 {"ieee1394", map[string]string{"DEVPATH": "b"}, "ieee1394-1"}, 1093 {"ieee1394", map[string]string{"DEVPATH": "c"}, "ieee1394-2"}, 1094 // names derived from device attributes, since slotSpecName is empty 1095 {"", map[string]string{"DEVPATH": "a", "NAME": "Video Camera"}, "videocamera"}, 1096 {"", map[string]string{"DEVPATH": "b", "NAME": "Video Camera"}, "videocamera-1"}, 1097 {"", map[string]string{"DEVPATH": "b", "NAME": "Video Camera"}, "videocamera-2"}, 1098 // names derived from interface name, since slotSpecName and relevant device attributes are not present 1099 {"", map[string]string{"DEVPATH": "a"}, "ifacename"}, 1100 {"", map[string]string{"DEVPATH": "a"}, "ifacename-1"}, 1101 } 1102 1103 repo := interfaces.NewRepository() 1104 iface := &ifacetest.TestInterface{InterfaceName: "camera"} 1105 repo.AddInterface(iface) 1106 1107 stateSlots, err := ifacestate.GetHotplugSlots(st) 1108 c.Assert(err, IsNil) 1109 1110 for _, data := range testData { 1111 devinfo, err := hotplug.NewHotplugDeviceInfo(data.deviceData) 1112 c.Assert(err, IsNil) 1113 c.Check(ifacestate.HotplugSlotName("key", "core", data.slotSpecName, "ifacename", devinfo, repo, stateSlots), Equals, data.expectedName) 1114 // store the slot to affect ensureUniqueName 1115 stateSlots[data.expectedName] = &ifacestate.HotplugSlotInfo{} 1116 } 1117 } 1118 1119 func (s *hotplugSuite) TestUpdateDeviceTasks(c *C) { 1120 st := state.New(nil) 1121 st.Lock() 1122 defer st.Unlock() 1123 1124 tss := ifacestate.UpdateDevice(st, "interface", "key", map[string]interface{}{"attr": "value"}) 1125 c.Assert(tss, NotNil) 1126 c.Assert(tss.Tasks(), HasLen, 2) 1127 1128 task1 := tss.Tasks()[0] 1129 c.Assert(task1.Kind(), Equals, "hotplug-disconnect") 1130 1131 iface, key, err := ifacestate.GetHotplugAttrs(task1) 1132 c.Assert(err, IsNil) 1133 c.Assert(iface, Equals, "interface") 1134 c.Assert(key, DeepEquals, snap.HotplugKey("key")) 1135 1136 task2 := tss.Tasks()[1] 1137 c.Assert(task2.Kind(), Equals, "hotplug-update-slot") 1138 iface, key, err = ifacestate.GetHotplugAttrs(task2) 1139 c.Assert(err, IsNil) 1140 c.Assert(iface, Equals, "interface") 1141 c.Assert(key, DeepEquals, snap.HotplugKey("key")) 1142 var attrs map[string]interface{} 1143 c.Assert(task2.Get("slot-attrs", &attrs), IsNil) 1144 c.Assert(attrs, DeepEquals, map[string]interface{}{"attr": "value"}) 1145 1146 wt := task2.WaitTasks() 1147 c.Assert(wt, HasLen, 1) 1148 c.Assert(wt[0], DeepEquals, task1) 1149 } 1150 1151 func (s *hotplugSuite) TestRemoveDeviceTasks(c *C) { 1152 st := state.New(nil) 1153 st.Lock() 1154 defer st.Unlock() 1155 1156 tss := ifacestate.RemoveDevice(st, "interface", "key") 1157 c.Assert(tss, NotNil) 1158 c.Assert(tss.Tasks(), HasLen, 2) 1159 1160 task1 := tss.Tasks()[0] 1161 c.Assert(task1.Kind(), Equals, "hotplug-disconnect") 1162 1163 iface, key, err := ifacestate.GetHotplugAttrs(task1) 1164 c.Assert(err, IsNil) 1165 c.Assert(iface, Equals, "interface") 1166 c.Assert(key, DeepEquals, snap.HotplugKey("key")) 1167 1168 task2 := tss.Tasks()[1] 1169 c.Assert(task2.Kind(), Equals, "hotplug-remove-slot") 1170 iface, key, err = ifacestate.GetHotplugAttrs(task2) 1171 c.Assert(err, IsNil) 1172 c.Assert(iface, Equals, "interface") 1173 c.Assert(key, DeepEquals, snap.HotplugKey("key")) 1174 1175 wt := task2.WaitTasks() 1176 c.Assert(wt, HasLen, 1) 1177 c.Assert(wt[0], DeepEquals, task1) 1178 }