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