github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/dirs" 32 "github.com/snapcore/snapd/interfaces" 33 "github.com/snapcore/snapd/interfaces/builtin" 34 "github.com/snapcore/snapd/interfaces/hotplug" 35 "github.com/snapcore/snapd/interfaces/ifacetest" 36 "github.com/snapcore/snapd/osutil" 37 "github.com/snapcore/snapd/overlord" 38 "github.com/snapcore/snapd/overlord/configstate/config" 39 "github.com/snapcore/snapd/overlord/hookstate" 40 "github.com/snapcore/snapd/overlord/ifacestate" 41 "github.com/snapcore/snapd/overlord/ifacestate/udevmonitor" 42 "github.com/snapcore/snapd/overlord/snapstate" 43 "github.com/snapcore/snapd/overlord/state" 44 "github.com/snapcore/snapd/snap" 45 "github.com/snapcore/snapd/snap/snaptest" 46 "github.com/snapcore/snapd/testutil" 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 testHotplugTaskAttrs(c *C, t *state.Task, ifaceName, hotplugKey string) { 278 iface, key, err := ifacestate.GetHotplugAttrs(t) 279 c.Assert(err, IsNil) 280 c.Assert(key, Equals, snap.HotplugKey(hotplugKey)) 281 c.Assert(iface, Equals, ifaceName) 282 } 283 284 func testByHotplugTaskFlag(c *C, t *state.Task) { 285 var byHotplug bool 286 c.Assert(t.Get("by-hotplug", &byHotplug), IsNil) 287 c.Assert(byHotplug, Equals, true) 288 } 289 290 func (s *hotplugSuite) TestHotplugAddBasic(c *C) { 291 s.MockModel(c, nil) 292 293 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "a/path", "ACTION": "add", "SUBSYSTEM": "foo"}) 294 c.Assert(err, IsNil) 295 s.udevMon.AddDevice(di) 296 297 c.Assert(s.o.Settle(5*time.Second), IsNil) 298 299 st := s.state 300 st.Lock() 301 defer st.Unlock() 302 303 var hp hotplugTasksWitness 304 hp.checkTasks(c, st) 305 c.Check(hp.seenTasks, DeepEquals, map[string]int{"hotplug-seq-wait": 2, "hotplug-add-slot": 2, "hotplug-connect": 2}) 306 c.Check(hp.seenHotplugAddKeys, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a", "key-2": "test-b"}) 307 c.Check(hp.seenHotplugConnectKeys, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a", "key-2": "test-b"}) 308 c.Check(hp.seenHooks, HasLen, 0) 309 c.Check(hp.connects, HasLen, 0) 310 311 // make sure slots have been created in the repo 312 repo := s.mgr.Repository() 313 slot, err := repo.SlotForHotplugKey("test-a", "key-1") 314 c.Assert(err, IsNil) 315 c.Assert(slot, NotNil) 316 slots := repo.AllSlots("test-a") 317 c.Assert(slots, HasLen, 1) 318 c.Check(slots[0].Name, Equals, "hotplugslot-a") 319 c.Check(slots[0].Attrs, DeepEquals, map[string]interface{}{ 320 "path": di.DevicePath(), 321 "slot-a-attr1": "a"}) 322 c.Check(slots[0].HotplugKey, DeepEquals, snap.HotplugKey("key-1")) 323 324 slot, err = repo.SlotForHotplugKey("test-b", "key-2") 325 c.Assert(err, IsNil) 326 c.Assert(slot, NotNil) 327 328 slot, err = repo.SlotForHotplugKey("test-c", "key-3") 329 c.Assert(err, IsNil) 330 c.Assert(slot, IsNil) 331 332 c.Check(s.handledByGadgetCalled, Equals, 0) 333 } 334 335 func (s *hotplugSuite) TestHotplugConnectWithGadgetSlot(c *C) { 336 s.MockModel(c, map[string]interface{}{ 337 "gadget": "the-gadget", 338 }) 339 340 st := s.state 341 st.Lock() 342 defer st.Unlock() 343 344 gadgetSideInfo := &snap.SideInfo{RealName: "the-gadget", SnapID: "the-gadget-id", Revision: snap.R(1)} 345 gadgetInfo := snaptest.MockSnap(c, ` 346 name: the-gadget 347 type: gadget 348 version: 1.0 349 350 slots: 351 slot1: 352 interface: test-b 353 path: /dev/path 354 `, gadgetSideInfo) 355 snapstate.Set(s.state, "the-gadget", &snapstate.SnapState{ 356 Active: true, 357 Sequence: []*snap.SideInfo{&gadgetInfo.SideInfo}, 358 Current: snap.R(1), 359 SnapType: "gadget"}) 360 st.Unlock() 361 362 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{ 363 "DEVNAME": "/dev/path", 364 "DEVPATH": "a/path", 365 "ACTION": "add", 366 "SUBSYSTEM": "foo"}) 367 c.Assert(err, IsNil) 368 s.udevMon.AddDevice(di) 369 370 c.Assert(s.o.Settle(5*time.Second), IsNil) 371 st.Lock() 372 373 c.Check(s.handledByGadgetCalled, Equals, 1) 374 375 // make sure hotplug slot has been created in the repo 376 repo := s.mgr.Repository() 377 slot, err := repo.SlotForHotplugKey("test-a", "key-1") 378 c.Assert(err, IsNil) 379 c.Assert(slot, NotNil) 380 381 // but no hotplug slot has been created for the device path defined by gadget 382 slot, err = repo.SlotForHotplugKey("test-b", "key-2") 383 c.Assert(err, IsNil) 384 c.Assert(slot, IsNil) 385 } 386 387 func (s *hotplugSuite) TestHotplugAddWithDefaultKey(c *C) { 388 s.MockModel(c, nil) 389 390 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{ 391 "DEVPATH": "a/path", 392 "ACTION": "add", 393 "SUBSYSTEM": "foo", 394 "ID_VENDOR_ID": "vendor", 395 "ID_MODEL_ID": "model", 396 "ID_SERIAL_SHORT": "serial", 397 }) 398 c.Assert(err, IsNil) 399 s.udevMon.AddDevice(di) 400 401 c.Assert(s.o.Settle(5*time.Second), IsNil) 402 403 st := s.state 404 st.Lock() 405 defer st.Unlock() 406 407 var hp hotplugTasksWitness 408 hp.checkTasks(c, st) 409 c.Check(hp.seenTasks, DeepEquals, map[string]int{"hotplug-seq-wait": 3, "hotplug-add-slot": 3, "hotplug-connect": 3}) 410 c.Check(hp.seenHooks, HasLen, 0) 411 c.Check(hp.connects, HasLen, 0) 412 testIfaceDkey := keyHelper("ID_VENDOR_ID\x00vendor\x00ID_MODEL_ID\x00model\x00ID_SERIAL_SHORT\x00serial\x00") 413 c.Assert(hp.seenHotplugAddKeys, DeepEquals, map[snap.HotplugKey]string{ 414 "key-1": "test-a", 415 "key-2": "test-b", 416 testIfaceDkey: "test-d"}) 417 c.Assert(hp.seenHotplugConnectKeys, DeepEquals, map[snap.HotplugKey]string{ 418 "key-1": "test-a", 419 "key-2": "test-b", 420 testIfaceDkey: "test-d"}) 421 422 // make sure the slot has been created 423 repo := s.mgr.Repository() 424 slots := repo.AllSlots("test-d") 425 c.Assert(slots, HasLen, 1) 426 c.Check(slots[0].Name, Equals, "hotplugslot-d") 427 c.Check(slots[0].HotplugKey, Equals, testIfaceDkey) 428 } 429 430 func (s *hotplugSuite) TestHotplugAddWithAutoconnect(c *C) { 431 s.MockModel(c, nil) 432 433 s.ifaceTestAAutoConnect = true 434 435 repo := s.mgr.Repository() 436 st := s.state 437 438 st.Lock() 439 // mock the consumer snap/plug 440 si := &snap.SideInfo{RealName: "consumer", Revision: snap.R(1)} 441 testSnap := snaptest.MockSnapInstance(c, "", testSnapYaml, si) 442 c.Assert(testSnap.Plugs, HasLen, 1) 443 c.Assert(testSnap.Plugs["plug"], NotNil) 444 c.Assert(repo.AddPlug(testSnap.Plugs["plug"]), IsNil) 445 snapstate.Set(s.state, "consumer", &snapstate.SnapState{ 446 Active: true, 447 Sequence: []*snap.SideInfo{si}, 448 Current: snap.R(1), 449 SnapType: "app", 450 }) 451 st.Unlock() 452 453 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "a/path", "ACTION": "add", "SUBSYSTEM": "foo"}) 454 c.Assert(err, IsNil) 455 s.udevMon.AddDevice(di) 456 457 c.Assert(s.o.Settle(5*time.Second), IsNil) 458 st.Lock() 459 defer st.Unlock() 460 461 // verify hotplug tasks 462 var hp hotplugTasksWitness 463 hp.checkTasks(c, st) 464 c.Check(hp.seenTasks, DeepEquals, map[string]int{"hotplug-seq-wait": 2, "hotplug-add-slot": 2, "hotplug-connect": 2, "connect": 1}) 465 c.Check(hp.seenHooks, DeepEquals, map[string]string{"prepare-plug-plug": "consumer", "connect-plug-plug": "consumer"}) 466 c.Check(hp.seenHotplugAddKeys, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a", "key-2": "test-b"}) 467 c.Check(hp.seenHotplugConnectKeys, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a", "key-2": "test-b"}) 468 c.Check(hp.connects, DeepEquals, []string{"consumer:plug core:hotplugslot-a"}) 469 470 // make sure slots have been created in the repo 471 slot, err := repo.SlotForHotplugKey("test-a", "key-1") 472 c.Assert(err, IsNil) 473 c.Assert(slot, NotNil) 474 475 conn, err := repo.Connection(&interfaces.ConnRef{ 476 PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"}, 477 SlotRef: interfaces.SlotRef{Snap: "core", Name: "hotplugslot-a"}}) 478 c.Assert(err, IsNil) 479 c.Assert(conn, NotNil) 480 } 481 482 var testSnapYaml = ` 483 name: consumer 484 version: 1 485 plugs: 486 plug: 487 interface: test-a 488 hooks: 489 prepare-plug-plug: 490 connect-plug-plug: 491 disconnect-plug-plug: 492 ` 493 494 func (s *hotplugSuite) TestHotplugRemove(c *C) { 495 st := s.state 496 st.Lock() 497 498 st.Set("conns", map[string]interface{}{ 499 "consumer:plug core:hotplugslot": map[string]interface{}{ 500 "interface": "test-a", 501 "hotplug-key": "key-1", 502 "hotplug-gone": false}}) 503 st.Set("hotplug-slots", map[string]interface{}{ 504 "hotplugslot": map[string]interface{}{ 505 "name": "hotplugslot", 506 "interface": "test-a", 507 "hotplug-key": "key-1", 508 "hotplug-gone": false}}) 509 510 repo := s.mgr.Repository() 511 si := &snap.SideInfo{RealName: "consumer", Revision: snap.R(1)} 512 testSnap := snaptest.MockSnapInstance(c, "", testSnapYaml, si) 513 c.Assert(repo.AddPlug(&snap.PlugInfo{ 514 Interface: "test-a", 515 Name: "plug", 516 Attrs: map[string]interface{}{}, 517 Snap: testSnap, 518 }), IsNil) 519 snapstate.Set(s.state, "consumer", &snapstate.SnapState{ 520 Active: true, 521 Sequence: []*snap.SideInfo{si}, 522 Current: snap.R(1), 523 SnapType: "app", 524 }) 525 526 core, err := snapstate.CurrentInfo(s.state, "core") 527 c.Assert(err, IsNil) 528 c.Assert(repo.AddSlot(&snap.SlotInfo{ 529 Interface: "test-a", 530 Name: "hotplugslot", 531 Attrs: map[string]interface{}{}, 532 Snap: core, 533 HotplugKey: "key-1", 534 }), IsNil) 535 536 conn, err := repo.Connect(&interfaces.ConnRef{ 537 PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"}, 538 SlotRef: interfaces.SlotRef{Snap: "core", Name: "hotplugslot"}, 539 }, nil, nil, nil, nil, nil) 540 c.Assert(err, IsNil) 541 c.Assert(conn, NotNil) 542 543 restore := s.mgr.MockObservedDevicePath(filepath.Join(dirs.SysfsDir, "a/path"), "test-a", "key-1") 544 defer restore() 545 546 st.Unlock() 547 548 slot, _ := repo.SlotForHotplugKey("test-a", "key-1") 549 c.Assert(slot, NotNil) 550 551 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "a/path", "ACTION": "remove", "SUBSYSTEM": "foo"}) 552 c.Assert(err, IsNil) 553 s.udevMon.RemoveDevice(di) 554 555 c.Assert(s.o.Settle(5*time.Second), IsNil) 556 557 st.Lock() 558 defer st.Unlock() 559 560 // verify hotplug tasks 561 var hp hotplugTasksWitness 562 hp.checkTasks(c, st) 563 c.Check(hp.seenHooks, DeepEquals, map[string]string{"disconnect-plug-plug": "consumer"}) 564 c.Check(hp.seenHotplugRemoveKeys, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a"}) 565 c.Check(hp.hotplugDisconnects, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a"}) 566 c.Check(hp.seenTasks, DeepEquals, map[string]int{"hotplug-seq-wait": 1, "hotplug-disconnect": 1, "disconnect": 1, "hotplug-remove-slot": 1}) 567 c.Check(hp.disconnects, DeepEquals, []string{"consumer:plug core:hotplugslot"}) 568 569 slot, _ = repo.SlotForHotplugKey("test-a", "key-1") 570 c.Assert(slot, IsNil) 571 572 var newconns map[string]interface{} 573 c.Assert(st.Get("conns", &newconns), IsNil) 574 c.Assert(newconns, DeepEquals, map[string]interface{}{ 575 "consumer:plug core:hotplugslot": map[string]interface{}{ 576 "interface": "test-a", 577 "hotplug-key": "key-1", 578 "hotplug-gone": true}}) 579 } 580 581 func (s *hotplugSuite) TestHotplugEnumerationDone(c *C) { 582 s.MockModel(c, nil) 583 584 st := s.state 585 st.Lock() 586 587 // existing connection 588 st.Set("conns", map[string]interface{}{ 589 "consumer:plug core:hotplugslot": map[string]interface{}{ 590 "interface": "test-a", 591 "hotplug-key": "key-other-device", 592 "hotplug-gone": false}}) 593 594 repo := s.mgr.Repository() 595 596 si := &snap.SideInfo{RealName: "consumer", Revision: snap.R(1)} 597 testSnap := snaptest.MockSnapInstance(c, "", testSnapYaml, si) 598 c.Assert(repo.AddPlug(&snap.PlugInfo{ 599 Interface: "test-a", 600 Name: "plug", 601 Attrs: map[string]interface{}{}, 602 Snap: testSnap, 603 }), IsNil) 604 snapstate.Set(s.state, "consumer", &snapstate.SnapState{ 605 Active: true, 606 Sequence: []*snap.SideInfo{si}, 607 Current: snap.R(1), 608 SnapType: "app"}) 609 610 core, err := snapstate.CurrentInfo(s.state, "core") 611 c.Assert(err, IsNil) 612 c.Assert(repo.AddSlot(&snap.SlotInfo{ 613 Interface: "test-a", 614 Name: "hotplugslot", 615 Attrs: map[string]interface{}{}, 616 Snap: core, 617 HotplugKey: "key-other-device", 618 }), IsNil) 619 620 conn, err := repo.Connect(&interfaces.ConnRef{ 621 PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"}, 622 SlotRef: interfaces.SlotRef{Snap: "core", Name: "hotplugslot"}, 623 }, nil, nil, nil, nil, nil) 624 c.Assert(err, IsNil) 625 c.Assert(conn, NotNil) 626 627 st.Set("hotplug-slots", map[string]interface{}{ 628 "hotplugslot": map[string]interface{}{ 629 "name": "hotplugslot", 630 "interface": "test-a", 631 "hotplug-key": "key-other-device"}, 632 "anotherslot": map[string]interface{}{ 633 "name": "anotherslot", 634 "interface": "test-a", 635 "hotplug-key": "yet-another-device"}}) 636 637 // sanity 638 slot, _ := repo.SlotForHotplugKey("test-a", "key-other-device") 639 c.Assert(slot, NotNil) 640 641 st.Unlock() 642 643 // new device added; device for existing connection not present when enumeration is finished 644 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "a/path", "ACTION": "add", "SUBSYSTEM": "foo"}) 645 c.Assert(err, IsNil) 646 s.udevMon.AddDevice(di) 647 s.udevMon.EnumerationDone() 648 649 c.Assert(s.o.Settle(5*time.Second), IsNil) 650 651 s.state.Lock() 652 defer s.state.Unlock() 653 654 // make sure slots for new device have been created in the repo 655 hpslot, _ := repo.SlotForHotplugKey("test-a", "key-1") 656 c.Assert(hpslot, NotNil) 657 hpslot, _ = repo.SlotForHotplugKey("test-b", "key-2") 658 c.Assert(hpslot, NotNil) 659 660 // make sure slots for missing device got disconnected and removed 661 hpslot, _ = repo.SlotForHotplugKey("test-a", "key-other-device") 662 c.Assert(hpslot, IsNil) 663 664 // and the connection for missing device is marked with hotplug-gone: true; 665 // "anotherslot" is removed completely since there was no connection for it. 666 var newconns map[string]interface{} 667 c.Assert(st.Get("conns", &newconns), IsNil) 668 c.Check(newconns, DeepEquals, map[string]interface{}{ 669 "consumer:plug core:hotplugslot": map[string]interface{}{ 670 "hotplug-gone": true, 671 "hotplug-key": "key-other-device", 672 "interface": "test-a"}}) 673 674 var newHotplugSlots map[string]interface{} 675 c.Assert(st.Get("hotplug-slots", &newHotplugSlots), IsNil) 676 c.Check(newHotplugSlots, DeepEquals, map[string]interface{}{ 677 "hotplugslot-a": map[string]interface{}{ 678 "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"}, 679 "hotplugslot-b": map[string]interface{}{ 680 "name": "hotplugslot-b", "hotplug-gone": false, "interface": "test-b", "hotplug-key": "key-2"}, 681 "hotplugslot": map[string]interface{}{"name": "hotplugslot", "hotplug-gone": true, "interface": "test-a", "hotplug-key": "key-other-device"}}) 682 } 683 684 func (s *hotplugSuite) TestHotplugDeviceUpdate(c *C) { 685 s.MockModel(c, nil) 686 st := s.state 687 st.Lock() 688 689 // existing connection 690 st.Set("conns", map[string]interface{}{ 691 "consumer:plug core:hotplugslot-a": map[string]interface{}{ 692 "interface": "test-a", 693 "hotplug-key": "key-1", 694 "hotplug-gone": false, 695 "slot-static": map[string]interface{}{"path": "/path-1"}}}) 696 697 repo := s.mgr.Repository() 698 si := &snap.SideInfo{RealName: "consumer", Revision: snap.R(1)} 699 testSnap := snaptest.MockSnapInstance(c, "", testSnapYaml, si) 700 c.Assert(repo.AddPlug(&snap.PlugInfo{ 701 Interface: "test-a", 702 Name: "plug", 703 Attrs: map[string]interface{}{}, 704 Snap: testSnap, 705 }), IsNil) 706 snapstate.Set(s.state, "consumer", &snapstate.SnapState{ 707 Active: true, 708 Sequence: []*snap.SideInfo{si}, 709 Current: snap.R(1), 710 SnapType: "app"}) 711 712 core, err := snapstate.CurrentInfo(s.state, "core") 713 c.Assert(err, IsNil) 714 c.Assert(repo.AddSlot(&snap.SlotInfo{ 715 Interface: "test-a", 716 Name: "hotplugslot-a", 717 Attrs: map[string]interface{}{"path": "/path-1"}, 718 Snap: core, 719 HotplugKey: "key-1", 720 }), IsNil) 721 722 conn, err := repo.Connect(&interfaces.ConnRef{ 723 PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"}, 724 SlotRef: interfaces.SlotRef{Snap: "core", Name: "hotplugslot-a"}, 725 }, nil, nil, nil, nil, nil) 726 c.Assert(err, IsNil) 727 c.Assert(conn, NotNil) 728 729 st.Set("hotplug-slots", map[string]interface{}{ 730 "hotplugslot-a": map[string]interface{}{ 731 "name": "hotplugslot-a", 732 "interface": "test-a", 733 "hotplug-key": "key-1", 734 "static-attrs": map[string]interface{}{"path": "/path-1"}}}) 735 st.Unlock() 736 737 // simulate device update 738 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{"DEVPATH": "a/path", "ACTION": "add", "SUBSYSTEM": "foo"}) 739 c.Assert(err, IsNil) 740 s.udevMon.AddDevice(di) 741 s.udevMon.EnumerationDone() 742 743 c.Assert(s.o.Settle(5*time.Second), IsNil) 744 745 st.Lock() 746 defer st.Unlock() 747 748 // verify hotplug tasks 749 var hp hotplugTasksWitness 750 hp.checkTasks(c, s.state) 751 752 // we see 2 hotplug-connect tasks because of interface test-a and test-b (the latter does nothing as there is no change) 753 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}) 754 c.Check(hp.seenHooks, DeepEquals, map[string]string{ 755 "disconnect-plug-plug": "consumer", 756 "prepare-plug-plug": "consumer", 757 "connect-plug-plug": "consumer"}) 758 c.Check(hp.hotplugDisconnects, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a"}) 759 c.Check(hp.seenHotplugAddKeys, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a", "key-2": "test-b"}) 760 c.Check(hp.seenHotplugConnectKeys, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a", "key-2": "test-b"}) 761 c.Check(hp.seenHotplugUpdateKeys, DeepEquals, map[snap.HotplugKey]string{"key-1": "test-a"}) 762 c.Check(hp.connects, DeepEquals, []string{"consumer:plug core:hotplugslot-a"}) 763 c.Check(hp.disconnects, DeepEquals, []string{"consumer:plug core:hotplugslot-a"}) 764 765 // make sure slots for new device have been updated in the repo 766 slot, err := repo.SlotForHotplugKey("test-a", "key-1") 767 c.Assert(err, IsNil) 768 c.Check(slot.Attrs, DeepEquals, map[string]interface{}{"path": di.DevicePath(), "slot-a-attr1": "a"}) 769 770 // and the connection attributes have been updated 771 var newconns map[string]interface{} 772 c.Assert(st.Get("conns", &newconns), IsNil) 773 c.Check(newconns, DeepEquals, map[string]interface{}{ 774 "consumer:plug core:hotplugslot-a": map[string]interface{}{ 775 "hotplug-key": "key-1", 776 "interface": "test-a", 777 "slot-static": map[string]interface{}{"path": di.DevicePath(), "slot-a-attr1": "a"}, 778 }}) 779 780 var newHotplugSlots map[string]interface{} 781 c.Assert(st.Get("hotplug-slots", &newHotplugSlots), IsNil) 782 c.Check(newHotplugSlots["hotplugslot-a"], DeepEquals, map[string]interface{}{ 783 "interface": "test-a", 784 "static-attrs": map[string]interface{}{"slot-a-attr1": "a", "path": di.DevicePath()}, 785 "hotplug-key": "key-1", 786 "name": "hotplugslot-a", 787 "hotplug-gone": false}) 788 } 789 790 func keyHelper(input string) snap.HotplugKey { 791 return snap.HotplugKey(fmt.Sprintf("0%x", sha256.Sum256([]byte(input)))) 792 } 793 794 func (s *hotplugSuite) TestDefaultDeviceKey(c *C) { 795 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{ 796 "DEVPATH": "a/path", 797 "ACTION": "add", 798 "SUBSYSTEM": "foo", 799 "ID_V4L_PRODUCT": "v4lproduct", 800 "NAME": "name", 801 "ID_VENDOR_ID": "vendor", 802 "ID_MODEL_ID": "model", 803 "ID_SERIAL": "serial", 804 "ID_REVISION": "revision", 805 }) 806 c.Assert(err, IsNil) 807 key, err := ifacestate.DefaultDeviceKey(di, 0) 808 c.Assert(err, IsNil) 809 810 // sanity check 811 c.Check(key, HasLen, 65) 812 c.Check(key, Equals, snap.HotplugKey("08bcbdcda3fee3534c0288506d9b75d4e26fe3692a36a11e75d05eac9ebf5ca7d")) 813 c.Assert(key, Equals, keyHelper("ID_V4L_PRODUCT\x00v4lproduct\x00ID_VENDOR_ID\x00vendor\x00ID_MODEL_ID\x00model\x00ID_SERIAL\x00serial\x00")) 814 815 di, err = hotplug.NewHotplugDeviceInfo(map[string]string{ 816 "DEVPATH": "a/path", 817 "ACTION": "add", 818 "SUBSYSTEM": "foo", 819 "NAME": "name", 820 "ID_WWN": "wnn", 821 "ID_MODEL_ENC": "modelenc", 822 "ID_REVISION": "revision", 823 }) 824 c.Assert(err, IsNil) 825 key, err = ifacestate.DefaultDeviceKey(di, 0) 826 c.Assert(err, IsNil) 827 c.Assert(key, Equals, keyHelper("NAME\x00name\x00ID_WWN\x00wnn\x00ID_MODEL_ENC\x00modelenc\x00ID_REVISION\x00revision\x00")) 828 829 di, err = hotplug.NewHotplugDeviceInfo(map[string]string{ 830 "DEVPATH": "a/path", 831 "ACTION": "add", 832 "SUBSYSTEM": "foo", 833 "PCI_SLOT_NAME": "pcislot", 834 "ID_MODEL_ENC": "modelenc", 835 }) 836 c.Assert(err, IsNil) 837 key, err = ifacestate.DefaultDeviceKey(di, 0) 838 c.Assert(key, Equals, keyHelper("PCI_SLOT_NAME\x00pcislot\x00ID_MODEL_ENC\x00modelenc\x00")) 839 c.Assert(err, IsNil) 840 841 // real device #1 - Lime SDR device 842 di, err = hotplug.NewHotplugDeviceInfo(map[string]string{ 843 "DEVNAME": "/dev/bus/usb/002/002", 844 "DEVNUM": "002", 845 "DEVPATH": "/devices/pci0000:00/0000:00:14.0/usb2/2-3", 846 "DEVTYPE": "usb_device", 847 "DRIVER": "usb", 848 "ID_BUS": "usb", 849 "ID_MODEL": "LimeSDR-USB", 850 "ID_MODEL_ENC": "LimeSDR-USB", 851 "ID_MODEL_FROM_DATABASE": "Myriad-RF LimeSDR", 852 "ID_MODEL_ID": "6108", 853 "ID_REVISION": "0000", 854 "ID_SERIAL": "Myriad-RF_LimeSDR-USB_0009060B00492E2C", 855 "ID_SERIAL_SHORT": "0009060B00492E2C", 856 "ID_USB_INTERFACES": ":ff0000:", 857 "ID_VENDOR": "Myriad-RF", 858 "ID_VENDOR_ENC": "Myriad-RF", 859 "ID_VENDOR_FROM_DATABASE": "OpenMoko, Inc.", 860 "ID_VENDOR_ID": "1d50", 861 "MAJOR": "189", 862 "MINOR": "129", 863 "PRODUCT": "1d50/6108/0", 864 "SUBSYSTEM": "usb", 865 "TYPE": "0/0/0", 866 "USEC_INITIALIZED": "6125378086 ", 867 }) 868 c.Assert(err, IsNil) 869 key, err = ifacestate.DefaultDeviceKey(di, 0) 870 c.Assert(err, IsNil) 871 c.Assert(key, Equals, keyHelper("ID_VENDOR_ID\x001d50\x00ID_MODEL_ID\x006108\x00ID_SERIAL\x00Myriad-RF_LimeSDR-USB_0009060B00492E2C\x00")) 872 873 // real device #2 - usb-serial port adapter 874 di, err = hotplug.NewHotplugDeviceInfo(map[string]string{ 875 "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", 876 "DEVNAME": "/dev/ttyUSB0", 877 "DEVPATH": "/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.0/ttyUSB0/tty/ttyUSB0", 878 "ID_BUS": "usb", 879 "ID_MM_CANDIDATE": "1", 880 "ID_MODEL_ENC": "FT232R\x20USB\x20UART", 881 "MODEL_FROM_DATABASE": "FT232 Serial (UART) IC", 882 "ID_MODEL_ID": "6001", 883 "ID_PATH": "pci-0000:00:14.0-usb-0:2:1.0", 884 "ID_PATH_TAG": "pci-0000_00_14_0-usb-0_2_1_0", 885 "ID_PCI_CLASS_FROM_DATABASE": "Serial bus controller", 886 "ID_PCI_INTERFACE_FROM_DATABASE": "XHCI", 887 "ID_PCI_SUBCLASS_FROM_DATABASE": "USB controller", 888 "ID_REVISION": "0600", 889 "ID_SERIAL": "FTDI_FT232R_USB_UART_AH06W0EQ", 890 "ID_SERIAL_SHORT": "AH06W0EQ", 891 "ID_TYPE": "generic", 892 "ID_USB_DRIVER": "ftdi_sio", 893 "ID_USB_INTERFACES": ":ffffff:", 894 "ID_USB_INTERFACE_NUM": "00", 895 "ID_VENDOR": "FTDI", 896 "ID_VENDOR_ENC": "FTDI", 897 "ID_VENDOR_FROM_DATABASE": "Future Technology Devices International, Ltd", 898 "ID_VENDOR_ID": "0403", 899 "MAJOR": "188", 900 "MINOR": "0", 901 "SUBSYSTEM": "tty", 902 "TAGS": ":systemd:", 903 "USEC_INITIALIZED": "6571662103", 904 }) 905 c.Assert(err, IsNil) 906 key, err = ifacestate.DefaultDeviceKey(di, 0) 907 c.Assert(err, IsNil) 908 c.Assert(key, Equals, keyHelper("ID_VENDOR_ID\x000403\x00ID_MODEL_ID\x006001\x00ID_SERIAL\x00FTDI_FT232R_USB_UART_AH06W0EQ\x00")) 909 910 // real device #3 - integrated web camera 911 di, err = hotplug.NewHotplugDeviceInfo(map[string]string{ 912 "COLORD_DEVICE": "1", 913 "COLORD_KIND": "camera", 914 "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", 915 "DEVNAME": "/dev/video0", 916 "DEVPATH": "/devices/pci0000:00/0000:00:14.0/usb1/1-11/1-11:1.0/video4linux/video0", 917 "ID_BUS": "usb", 918 "ID_FOR_SEAT": "video4linux-pci-0000_00_14_0-usb-0_11_1_0", 919 "ID_MODEL": "Integrated_Webcam_HD", 920 "ID_MODEL_ENC": "Integrated_Webcam_HD", 921 "ID_MODEL_ID": "57c3", 922 "ID_PATH": "pci-0000:00:14.0-usb-0:11:1.0", 923 "ID_PATH_TAG": "pci-0000_00_14_0-usb-0_11_1_0", 924 "ID_REVISION": "5806", 925 "ID_SERIAL": "CN0J8NNP7248766FA3H3A01_Integrated_Webcam_HD_200901010001", 926 "ID_SERIAL_SHORT": "200901010001", 927 "ID_TYPE": "video", 928 "ID_USB_DRIVER": "uvcvideo", 929 "ID_USB_INTERFACES": ":0e0100:0e0200:", 930 "ID_USB_INTERFACE_NUM": "00", 931 "ID_V4L_CAPABILITIES": ":capture:", 932 "ID_V4L_PRODUCT": "Integrated_Webcam_HD: Integrate", 933 "ID_V4L_VERSION": "2", 934 "ID_VENDOR": "CN0J8NNP7248766FA3H3A01", 935 "ID_VENDOR_ENC": "CN0J8NNP7248766FA3H3A01", 936 "ID_VENDOR_ID": "0bda", 937 "MAJOR": "81", 938 "MINOR": "0", 939 "SUBSYSTEM": "video4linux", 940 "TAGS": ":uaccess:seat:", 941 "USEC_INITIALIZED": "3411321", 942 }) 943 c.Assert(err, IsNil) 944 key, err = ifacestate.DefaultDeviceKey(di, 0) 945 c.Assert(err, IsNil) 946 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")) 947 948 // key cannot be computed - empty string 949 di, err = hotplug.NewHotplugDeviceInfo(map[string]string{ 950 "DEVPATH": "a/path", 951 "ACTION": "add", 952 "SUBSYSTEM": "foo", 953 }) 954 c.Assert(err, IsNil) 955 key, err = ifacestate.DefaultDeviceKey(di, 0) 956 c.Assert(err, IsNil) 957 c.Assert(key, Equals, snap.HotplugKey("")) 958 } 959 960 func (s *hotplugSuite) TestDefaultDeviceKeyError(c *C) { 961 di, err := hotplug.NewHotplugDeviceInfo(map[string]string{ 962 "DEVPATH": "a/path", 963 "ACTION": "add", 964 "SUBSYSTEM": "foo", 965 "NAME": "name", 966 "ID_VENDOR_ID": "vendor", 967 "ID_MODEL_ID": "model", 968 "ID_SERIAL": "serial", 969 }) 970 c.Assert(err, IsNil) 971 _, err = ifacestate.DefaultDeviceKey(di, 16) 972 c.Assert(err, ErrorMatches, "internal error: invalid key version 16") 973 } 974 975 func (s *hotplugSuite) TestEnsureUniqueName(c *C) { 976 fakeRepositoryLookup := func(n string) bool { 977 reserved := map[string]bool{ 978 "slot1": true, 979 "slot": true, 980 "slot1234": true, 981 "slot-1": true, 982 } 983 return !reserved[n] 984 } 985 986 names := []struct{ proposedName, resultingName string }{ 987 {"foo", "foo"}, 988 {"slot", "slot-2"}, 989 {"slot1234", "slot1234-1"}, 990 {"slot-1", "slot-1-1"}, 991 } 992 993 for _, name := range names { 994 c.Assert(ifacestate.EnsureUniqueName(name.proposedName, fakeRepositoryLookup), Equals, name.resultingName) 995 } 996 } 997 998 func (s *hotplugSuite) TestMakeSlotName(c *C) { 999 names := []struct{ proposedName, resultingName string }{ 1000 {"", ""}, 1001 {"-", ""}, 1002 {"slot1", "slot1"}, 1003 {"-slot1", "slot1"}, 1004 {"a--slot-1", "a-slot-1"}, 1005 {"(-slot", "slot"}, 1006 {"(--slot", "slot"}, 1007 {"slot-", "slot"}, 1008 {"slot---", "slot"}, 1009 {"slot-(", "slot"}, 1010 {"Integrated_Webcam_HD", "integratedwebcamhd"}, 1011 {"Xeon E3-1200 v5/E3-1500 v5/6th Gen Core Processor Host Bridge/DRAM Registers", "xeone3-1200v5e3-1500"}, 1012 } 1013 for _, name := range names { 1014 c.Assert(ifacestate.MakeSlotName(name.proposedName), Equals, name.resultingName) 1015 } 1016 } 1017 1018 func (s *hotplugSuite) TestSuggestedSlotName(c *C) { 1019 1020 events := []struct { 1021 eventData map[string]string 1022 outName string 1023 }{{ 1024 map[string]string{ 1025 "DEVPATH": "a/path", 1026 "ACTION": "add", 1027 "SUBSYSTEM": "foo", 1028 "NAME": "Name", 1029 "ID_MODEL": "Longer Name", 1030 "ID_MODEL_FROM_DATABASE": "Longest Name", 1031 }, 1032 "name", 1033 }, { 1034 map[string]string{ 1035 "DEVPATH": "a/path", 1036 "ACTION": "add", 1037 "SUBSYSTEM": "foo", 1038 "ID_MODEL": "Longer Name", 1039 "ID_MODEL_FROM_DATABASE": "Longest Name", 1040 }, 1041 "longername", 1042 }, { 1043 map[string]string{ 1044 "DEVPATH": "a/path", 1045 "ACTION": "add", 1046 "SUBSYSTEM": "foo", 1047 "ID_MODEL_FROM_DATABASE": "Longest Name", 1048 }, 1049 "longestname", 1050 }, { 1051 map[string]string{ 1052 "DEVPATH": "a/path", 1053 "ACTION": "add", 1054 "SUBSYSTEM": "foo", 1055 }, 1056 "fallbackname", 1057 }, 1058 } 1059 1060 for _, data := range events { 1061 di, err := hotplug.NewHotplugDeviceInfo(data.eventData) 1062 c.Assert(err, IsNil) 1063 1064 slotName := ifacestate.SuggestedSlotName(di, "fallbackname") 1065 c.Assert(slotName, Equals, data.outName) 1066 } 1067 } 1068 1069 func (s *hotplugSuite) TestHotplugSlotName(c *C) { 1070 st := state.New(nil) 1071 st.Lock() 1072 defer st.Unlock() 1073 1074 testData := []struct { 1075 slotSpecName string 1076 deviceData map[string]string 1077 expectedName string 1078 }{ 1079 // names dervied from slotSpecName 1080 {"hdcamera", map[string]string{"DEVPATH": "a", "NAME": "Video Camera"}, "hdcamera"}, 1081 {"hdcamera", map[string]string{"DEVPATH": "a", "NAME": "Video Camera"}, "hdcamera-1"}, 1082 {"ieee1394", map[string]string{"DEVPATH": "a"}, "ieee1394"}, 1083 {"ieee1394", map[string]string{"DEVPATH": "b"}, "ieee1394-1"}, 1084 {"ieee1394", map[string]string{"DEVPATH": "c"}, "ieee1394-2"}, 1085 // names derived from device attributes, since slotSpecName is empty 1086 {"", map[string]string{"DEVPATH": "a", "NAME": "Video Camera"}, "videocamera"}, 1087 {"", map[string]string{"DEVPATH": "b", "NAME": "Video Camera"}, "videocamera-1"}, 1088 {"", map[string]string{"DEVPATH": "b", "NAME": "Video Camera"}, "videocamera-2"}, 1089 // names derived from interface name, since slotSpecName and relevant device attributes are not present 1090 {"", map[string]string{"DEVPATH": "a"}, "ifacename"}, 1091 {"", map[string]string{"DEVPATH": "a"}, "ifacename-1"}, 1092 } 1093 1094 repo := interfaces.NewRepository() 1095 iface := &ifacetest.TestInterface{InterfaceName: "camera"} 1096 repo.AddInterface(iface) 1097 1098 stateSlots, err := ifacestate.GetHotplugSlots(st) 1099 c.Assert(err, IsNil) 1100 1101 for _, data := range testData { 1102 devinfo, err := hotplug.NewHotplugDeviceInfo(data.deviceData) 1103 c.Assert(err, IsNil) 1104 c.Check(ifacestate.HotplugSlotName("key", "core", data.slotSpecName, "ifacename", devinfo, repo, stateSlots), Equals, data.expectedName) 1105 // store the slot to affect ensureUniqueName 1106 stateSlots[data.expectedName] = &ifacestate.HotplugSlotInfo{} 1107 } 1108 } 1109 1110 func (s *hotplugSuite) TestUpdateDeviceTasks(c *C) { 1111 st := state.New(nil) 1112 st.Lock() 1113 defer st.Unlock() 1114 1115 tss := ifacestate.UpdateDevice(st, "interface", "key", map[string]interface{}{"attr": "value"}) 1116 c.Assert(tss, NotNil) 1117 c.Assert(tss.Tasks(), HasLen, 2) 1118 1119 task1 := tss.Tasks()[0] 1120 c.Assert(task1.Kind(), Equals, "hotplug-disconnect") 1121 testHotplugTaskAttrs(c, task1, "interface", "key") 1122 1123 task2 := tss.Tasks()[1] 1124 c.Assert(task2.Kind(), Equals, "hotplug-update-slot") 1125 testHotplugTaskAttrs(c, task2, "interface", "key") 1126 1127 var attrs map[string]interface{} 1128 c.Assert(task2.Get("slot-attrs", &attrs), IsNil) 1129 c.Assert(attrs, DeepEquals, map[string]interface{}{"attr": "value"}) 1130 1131 wt := task2.WaitTasks() 1132 c.Assert(wt, HasLen, 1) 1133 c.Assert(wt[0], DeepEquals, task1) 1134 } 1135 1136 func (s *hotplugSuite) TestRemoveDeviceTasks(c *C) { 1137 st := state.New(nil) 1138 st.Lock() 1139 defer st.Unlock() 1140 1141 tss := ifacestate.RemoveDevice(st, "interface", "key") 1142 c.Assert(tss, NotNil) 1143 c.Assert(tss.Tasks(), HasLen, 2) 1144 1145 task1 := tss.Tasks()[0] 1146 c.Assert(task1.Kind(), Equals, "hotplug-disconnect") 1147 testHotplugTaskAttrs(c, task1, "interface", "key") 1148 1149 task2 := tss.Tasks()[1] 1150 c.Assert(task2.Kind(), Equals, "hotplug-remove-slot") 1151 testHotplugTaskAttrs(c, task2, "interface", "key") 1152 1153 wt := task2.WaitTasks() 1154 c.Assert(wt, HasLen, 1) 1155 c.Assert(wt[0], DeepEquals, task1) 1156 }