github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/ifacestate/helpers_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 "errors" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "strings" 29 30 . "gopkg.in/check.v1" 31 32 "github.com/snapcore/snapd/dirs" 33 "github.com/snapcore/snapd/interfaces" 34 "github.com/snapcore/snapd/interfaces/ifacetest" 35 "github.com/snapcore/snapd/logger" 36 "github.com/snapcore/snapd/osutil" 37 "github.com/snapcore/snapd/overlord" 38 "github.com/snapcore/snapd/overlord/ifacestate" 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 46 type helpersSuite struct { 47 st *state.State 48 } 49 50 var _ = Suite(&helpersSuite{}) 51 52 func (s *helpersSuite) SetUpTest(c *C) { 53 s.st = state.New(nil) 54 dirs.SetRootDir(c.MkDir()) 55 } 56 57 func (s *helpersSuite) TearDownTest(c *C) { 58 dirs.SetRootDir("") 59 } 60 61 func (s *helpersSuite) TestIdentityMapper(c *C) { 62 var m ifacestate.SnapMapper = &ifacestate.IdentityMapper{} 63 64 // Nothing is altered. 65 c.Assert(m.RemapSnapFromState("example"), Equals, "example") 66 c.Assert(m.RemapSnapToState("example"), Equals, "example") 67 c.Assert(m.RemapSnapFromRequest("example"), Equals, "example") 68 69 c.Assert(m.SystemSnapName(), Equals, "unknown") 70 } 71 72 func (s *helpersSuite) TestCoreCoreSystemMapper(c *C) { 73 var m ifacestate.SnapMapper = &ifacestate.CoreCoreSystemMapper{} 74 75 // Snaps are not renamed when interacting with the state. 76 c.Assert(m.RemapSnapFromState("core"), Equals, "core") 77 c.Assert(m.RemapSnapToState("core"), Equals, "core") 78 79 // The "core" snap is renamed to the "system" in API response 80 // and back in the API requests. 81 c.Assert(m.RemapSnapFromRequest("system"), Equals, "core") 82 83 // Other snap names are unchanged. 84 c.Assert(m.RemapSnapFromState("potato"), Equals, "potato") 85 c.Assert(m.RemapSnapToState("potato"), Equals, "potato") 86 c.Assert(m.RemapSnapFromRequest("potato"), Equals, "potato") 87 88 c.Assert(m.SystemSnapName(), Equals, "core") 89 } 90 91 func (s *helpersSuite) TestCoreSnapdSystemMapper(c *C) { 92 var m ifacestate.SnapMapper = &ifacestate.CoreSnapdSystemMapper{} 93 94 // The "snapd" snap is renamed to the "core" in when saving the state 95 // and back when loading the state. 96 c.Assert(m.RemapSnapFromState("core"), Equals, "snapd") 97 c.Assert(m.RemapSnapToState("snapd"), Equals, "core") 98 99 // The "snapd" snap is renamed to the "system" in API response and back in 100 // the API requests. 101 c.Assert(m.RemapSnapFromRequest("system"), Equals, "snapd") 102 103 // The "core" snap is also renamed to "snapd" in API requests, for 104 // compatibility. 105 c.Assert(m.RemapSnapFromRequest("core"), Equals, "snapd") 106 107 // Other snap names are unchanged. 108 c.Assert(m.RemapSnapFromState("potato"), Equals, "potato") 109 c.Assert(m.RemapSnapToState("potato"), Equals, "potato") 110 c.Assert(m.RemapSnapFromRequest("potato"), Equals, "potato") 111 112 c.Assert(m.SystemSnapName(), Equals, "snapd") 113 } 114 115 // caseMapper implements SnapMapper to use upper case internally and lower case externally. 116 type caseMapper struct{} 117 118 func (m *caseMapper) RemapSnapFromState(snapName string) string { 119 return strings.ToUpper(snapName) 120 } 121 122 func (m *caseMapper) RemapSnapToState(snapName string) string { 123 return strings.ToLower(snapName) 124 } 125 126 func (m *caseMapper) RemapSnapFromRequest(snapName string) string { 127 return strings.ToUpper(snapName) 128 } 129 130 func (m *caseMapper) SystemSnapName() string { 131 return "unknown" 132 } 133 134 func (s *helpersSuite) TestMappingFunctions(c *C) { 135 restore := ifacestate.MockSnapMapper(&caseMapper{}) 136 defer restore() 137 138 c.Assert(ifacestate.RemapSnapFromState("example"), Equals, "EXAMPLE") 139 c.Assert(ifacestate.RemapSnapToState("EXAMPLE"), Equals, "example") 140 c.Assert(ifacestate.RemapSnapFromRequest("example"), Equals, "EXAMPLE") 141 c.Assert(ifacestate.SystemSnapName(), Equals, "unknown") 142 } 143 144 func (s *helpersSuite) TestGetConns(c *C) { 145 s.st.Lock() 146 defer s.st.Unlock() 147 s.st.Set("conns", map[string]interface{}{ 148 "app:network core:network": map[string]interface{}{ 149 "auto": true, 150 "interface": "network", 151 "slot-static": map[string]interface{}{ 152 "number": int(78), 153 }, 154 }, 155 }) 156 157 restore := ifacestate.MockSnapMapper(&caseMapper{}) 158 defer restore() 159 160 conns, err := ifacestate.GetConns(s.st) 161 c.Assert(err, IsNil) 162 for id, connState := range conns { 163 c.Assert(id, Equals, "APP:network CORE:network") 164 c.Assert(connState.Auto, Equals, true) 165 c.Assert(connState.Interface, Equals, "network") 166 c.Assert(connState.StaticSlotAttrs["number"], Equals, int64(78)) 167 } 168 } 169 170 func (s *helpersSuite) TestSetConns(c *C) { 171 s.st.Lock() 172 defer s.st.Unlock() 173 174 restore := ifacestate.MockSnapMapper(&caseMapper{}) 175 defer restore() 176 177 // This has upper-case data internally, see export_test.go 178 ifacestate.SetConns(s.st, ifacestate.UpperCaseConnState()) 179 var conns map[string]interface{} 180 err := s.st.Get("conns", &conns) 181 c.Assert(err, IsNil) 182 c.Assert(conns, DeepEquals, map[string]interface{}{ 183 "app:network core:network": map[string]interface{}{ 184 "auto": true, 185 "interface": "network", 186 }}) 187 } 188 189 func (s *helpersSuite) TestHotplugTaskHelpers(c *C) { 190 s.st.Lock() 191 defer s.st.Unlock() 192 193 t := s.st.NewTask("foo", "") 194 _, _, err := ifacestate.GetHotplugAttrs(t) 195 c.Assert(err, ErrorMatches, `internal error: cannot get interface name from hotplug task: no state entry for key`) 196 197 t.Set("interface", "x") 198 _, _, err = ifacestate.GetHotplugAttrs(t) 199 c.Assert(err, ErrorMatches, `internal error: cannot get hotplug key from hotplug task: no state entry for key`) 200 201 ifacestate.SetHotplugAttrs(t, "iface", "key") 202 203 var iface string 204 var key snap.HotplugKey 205 c.Assert(t.Get("hotplug-key", &key), IsNil) 206 c.Assert(key, DeepEquals, snap.HotplugKey("key")) 207 208 c.Assert(t.Get("interface", &iface), IsNil) 209 c.Assert(iface, Equals, "iface") 210 211 iface, key, err = ifacestate.GetHotplugAttrs(t) 212 c.Assert(err, IsNil) 213 c.Assert(key, DeepEquals, snap.HotplugKey("key")) 214 c.Assert(iface, Equals, "iface") 215 } 216 217 func (s *helpersSuite) TestHotplugSlotInfo(c *C) { 218 s.st.Lock() 219 defer s.st.Unlock() 220 221 slots, err := ifacestate.GetHotplugSlots(s.st) 222 c.Assert(err, IsNil) 223 c.Assert(slots, HasLen, 0) 224 225 defs := map[string]*ifacestate.HotplugSlotInfo{} 226 defs["foo"] = &ifacestate.HotplugSlotInfo{ 227 Name: "foo", 228 Interface: "iface", 229 StaticAttrs: map[string]interface{}{"attr": "value"}, 230 HotplugKey: "key", 231 } 232 ifacestate.SetHotplugSlots(s.st, defs) 233 234 var data map[string]interface{} 235 c.Assert(s.st.Get("hotplug-slots", &data), IsNil) 236 c.Assert(data, DeepEquals, map[string]interface{}{ 237 "foo": map[string]interface{}{ 238 "name": "foo", 239 "interface": "iface", 240 "static-attrs": map[string]interface{}{"attr": "value"}, 241 "hotplug-key": "key", 242 "hotplug-gone": false, 243 }}) 244 245 slots, err = ifacestate.GetHotplugSlots(s.st) 246 c.Assert(err, IsNil) 247 c.Assert(slots, DeepEquals, defs) 248 } 249 250 func (s *helpersSuite) TestFindConnsForHotplugKey(c *C) { 251 st := s.st 252 st.Lock() 253 defer st.Unlock() 254 255 // Set conns in the state and get them via GetConns to avoid having to 256 // know the internals of connState struct. 257 st.Set("conns", map[string]interface{}{ 258 "snap1:plug1 core:slot1": map[string]interface{}{ 259 "interface": "iface1", 260 "hotplug-key": "key1", 261 }, 262 "snap1:plug2 core:slot2": map[string]interface{}{ 263 "interface": "iface2", 264 "hotplug-key": "key1", 265 }, 266 "snap1:plug3 core:slot3": map[string]interface{}{ 267 "interface": "iface2", 268 "hotplug-key": "key2", 269 }, 270 "snap2:plug1 core:slot1": map[string]interface{}{ 271 "interface": "iface2", 272 "hotplug-key": "key2", 273 }, 274 }) 275 276 conns, err := ifacestate.GetConns(st) 277 c.Assert(err, IsNil) 278 279 hotplugConns := ifacestate.FindConnsForHotplugKey(conns, "iface1", "key1") 280 c.Assert(hotplugConns, DeepEquals, []string{"snap1:plug1 core:slot1"}) 281 282 hotplugConns = ifacestate.FindConnsForHotplugKey(conns, "iface2", "key2") 283 c.Assert(hotplugConns, DeepEquals, []string{"snap1:plug3 core:slot3", "snap2:plug1 core:slot1"}) 284 285 hotplugConns = ifacestate.FindConnsForHotplugKey(conns, "unknown", "key1") 286 c.Assert(hotplugConns, HasLen, 0) 287 } 288 289 func (s *helpersSuite) TestCheckIsSystemSnapPresentWithCore(c *C) { 290 restore := ifacestate.MockSnapMapper(&ifacestate.CoreCoreSystemMapper{}) 291 defer restore() 292 293 // no core snap yet 294 c.Assert(ifacestate.CheckSystemSnapIsPresent(s.st), Equals, false) 295 296 s.st.Lock() 297 298 // add "core" snap 299 sideInfo := &snap.SideInfo{Revision: snap.R(1)} 300 snapInfo := snaptest.MockSnapInstance(c, "", coreSnapYaml, sideInfo) 301 sideInfo.RealName = snapInfo.SnapName() 302 303 snapstate.Set(s.st, snapInfo.InstanceName(), &snapstate.SnapState{ 304 Active: true, 305 Sequence: []*snap.SideInfo{sideInfo}, 306 Current: sideInfo.Revision, 307 SnapType: string(snapInfo.GetType()), 308 InstanceKey: snapInfo.InstanceKey, 309 }) 310 s.st.Unlock() 311 312 c.Assert(ifacestate.CheckSystemSnapIsPresent(s.st), Equals, true) 313 } 314 315 var snapdYaml = `name: snapd 316 version: 1.0 317 ` 318 319 func (s *helpersSuite) TestCheckIsSystemSnapPresentWithSnapd(c *C) { 320 restore := ifacestate.MockSnapMapper(&ifacestate.CoreSnapdSystemMapper{}) 321 defer restore() 322 323 // no snapd snap yet 324 c.Assert(ifacestate.CheckSystemSnapIsPresent(s.st), Equals, false) 325 326 s.st.Lock() 327 328 // "snapd" snap 329 sideInfo := &snap.SideInfo{Revision: snap.R(1)} 330 snapInfo := snaptest.MockSnapInstance(c, "", snapdYaml, sideInfo) 331 sideInfo.RealName = snapInfo.SnapName() 332 333 snapstate.Set(s.st, snapInfo.InstanceName(), &snapstate.SnapState{ 334 Active: true, 335 Sequence: []*snap.SideInfo{sideInfo}, 336 Current: sideInfo.Revision, 337 SnapType: string(snapInfo.GetType()), 338 InstanceKey: snapInfo.InstanceKey, 339 }) 340 341 inf, err := ifacestate.SystemSnapInfo(s.st) 342 c.Assert(err, IsNil) 343 c.Assert(inf.InstanceName(), Equals, "snapd") 344 345 s.st.Unlock() 346 347 c.Assert(ifacestate.CheckSystemSnapIsPresent(s.st), Equals, true) 348 } 349 350 // Check what happens with system-key when security profile regeneration fails. 351 func (s *helpersSuite) TestSystemKeyAndFailingProfileRegeneration(c *C) { 352 dirs.SetRootDir(c.MkDir()) 353 defer dirs.SetRootDir("") 354 355 // Create a fake security backend with failing Setup method and mock all 356 // security backends away so that we only use this special one. Note that 357 // the backend is given a non-empty name as the interface manager skips 358 // test backends with empty name for convenience. 359 backend := &ifacetest.TestSecurityBackend{ 360 BackendName: "BROKEN", 361 SetupCallback: func(snapInfo *snap.Info, opts interfaces.ConfinementOptions, repo *interfaces.Repository) error { 362 return errors.New("cannot setup security profile") 363 }, 364 } 365 restore := ifacestate.MockSecurityBackends([]interfaces.SecurityBackend{backend}) 366 defer restore() 367 368 // Create a mock overlord, mainly to have state. 369 ovld := overlord.Mock() 370 st := ovld.State() 371 372 // Put a fake snap in the state, we need to setup security for at least one 373 // snap to give the fake security backend a chance to fail. 374 yamlText := ` 375 name: test-snapd-canary 376 version: 1 377 apps: 378 test-snapd-canary: 379 command: bin/canary 380 ` 381 si := &snap.SideInfo{Revision: snap.R(1), RealName: "test-snapd-canary"} 382 snapInfo := snaptest.MockSnap(c, yamlText, si) 383 st.Lock() 384 snapst := &snapstate.SnapState{ 385 SnapType: string(snap.TypeApp), 386 Sequence: []*snap.SideInfo{si}, 387 Active: true, 388 Current: snap.R(1), 389 } 390 snapstate.Set(st, snapInfo.InstanceName(), snapst) 391 st.Unlock() 392 393 // Pretend that security profiles are out of date and mock the 394 // function that writes the new system key with one always panics. 395 restore = ifacestate.MockProfilesNeedRegeneration(func() bool { return true }) 396 defer restore() 397 restore = ifacestate.MockWriteSystemKey(func() error { panic("should not attempt to write system key") }) 398 defer restore() 399 // Put a fake system key in place, we just want to see that file being removed. 400 err := os.MkdirAll(filepath.Dir(dirs.SnapSystemKeyFile), 0755) 401 c.Assert(err, IsNil) 402 err = ioutil.WriteFile(dirs.SnapSystemKeyFile, []byte("system-key"), 0755) 403 c.Assert(err, IsNil) 404 405 // Put up a fake logger to capture logged messages. 406 log, restore := logger.MockLogger() 407 defer restore() 408 409 // Construct and start up the interface manager. 410 mgr, err := ifacestate.Manager(st, nil, ovld.TaskRunner(), nil, nil) 411 c.Assert(err, IsNil) 412 err = mgr.StartUp() 413 c.Assert(err, IsNil) 414 415 // Check that system key is not on disk. 416 c.Check(log.String(), testutil.Contains, `cannot regenerate BROKEN profile for snap "test-snapd-canary": cannot setup security profile`) 417 c.Check(osutil.FileExists(dirs.SnapSystemKeyFile), Equals, false) 418 } 419 420 func (s *helpersSuite) TestIsHotplugChange(c *C) { 421 s.st.Lock() 422 defer s.st.Unlock() 423 424 chg := s.st.NewChange("foo", "") 425 c.Assert(ifacestate.IsHotplugChange(chg), Equals, false) 426 427 chg = s.st.NewChange("hotplugfoo", "") 428 c.Assert(ifacestate.IsHotplugChange(chg), Equals, false) 429 430 chg = s.st.NewChange("hotplug-foo", "") 431 c.Assert(ifacestate.IsHotplugChange(chg), Equals, true) 432 } 433 434 func (s *helpersSuite) TestGetHotplugChangeAttrs(c *C) { 435 s.st.Lock() 436 defer s.st.Unlock() 437 438 chg := s.st.NewChange("none-set", "") 439 _, _, err := ifacestate.GetHotplugChangeAttrs(chg) 440 c.Assert(err, ErrorMatches, `internal error: hotplug-key not set on change "none-set"`) 441 442 chg = s.st.NewChange("foo", "") 443 chg.Set("hotplug-seq", 1) 444 _, _, err = ifacestate.GetHotplugChangeAttrs(chg) 445 c.Assert(err, ErrorMatches, `internal error: hotplug-key not set on change "foo"`) 446 447 chg = s.st.NewChange("bar", "") 448 chg.Set("hotplug-key", "2222") 449 _, _, err = ifacestate.GetHotplugChangeAttrs(chg) 450 c.Assert(err, ErrorMatches, `internal error: hotplug-seq not set on change "bar"`) 451 452 chg = s.st.NewChange("baz", "") 453 chg.Set("hotplug-key", "1234") 454 chg.Set("hotplug-seq", 7) 455 456 seq, key, err := ifacestate.GetHotplugChangeAttrs(chg) 457 c.Assert(err, IsNil) 458 c.Check(key, DeepEquals, snap.HotplugKey("1234")) 459 c.Check(seq, Equals, 7) 460 } 461 462 func (s *helpersSuite) TestSetHotplugChangeAttrs(c *C) { 463 s.st.Lock() 464 defer s.st.Unlock() 465 466 chg := s.st.NewChange("foo", "") 467 ifacestate.SetHotplugChangeAttrs(chg, 12, "abcd") 468 469 var seq int 470 var hotplugKey string 471 c.Assert(chg.Get("hotplug-seq", &seq), IsNil) 472 c.Assert(chg.Get("hotplug-key", &hotplugKey), IsNil) 473 c.Check(seq, Equals, 12) 474 c.Check(hotplugKey, Equals, "abcd") 475 } 476 477 func (s *helpersSuite) TestAllocHotplugSeq(c *C) { 478 s.st.Lock() 479 defer s.st.Unlock() 480 481 var stateSeq int 482 483 // sanity 484 c.Assert(s.st.Get("hotplug-seq", &stateSeq), Equals, state.ErrNoState) 485 486 seq, err := ifacestate.AllocHotplugSeq(s.st) 487 c.Assert(err, IsNil) 488 c.Assert(seq, Equals, 1) 489 490 seq, err = ifacestate.AllocHotplugSeq(s.st) 491 c.Assert(err, IsNil) 492 c.Assert(seq, Equals, 2) 493 494 c.Assert(s.st.Get("hotplug-seq", &stateSeq), IsNil) 495 c.Check(stateSeq, Equals, 2) 496 } 497 498 func (s *helpersSuite) TestAddHotplugSeqWaitTask(c *C) { 499 s.st.Lock() 500 defer s.st.Unlock() 501 502 chg := s.st.NewChange("foo", "") 503 t1 := s.st.NewTask("task1", "") 504 t2 := s.st.NewTask("task2", "") 505 chg.AddTask(t1) 506 chg.AddTask(t2) 507 508 ifacestate.AddHotplugSeqWaitTask(chg, "1234", 1) 509 // hotplug change got an extra task 510 c.Assert(chg.Tasks(), HasLen, 3) 511 seq, key, err := ifacestate.GetHotplugChangeAttrs(chg) 512 c.Assert(err, IsNil) 513 c.Check(seq, Equals, 1) 514 c.Check(key, DeepEquals, snap.HotplugKey("1234")) 515 516 var seqTask *state.Task 517 for _, t := range chg.Tasks() { 518 if t.Kind() == "hotplug-seq-wait" { 519 seqTask = t 520 break 521 } 522 } 523 c.Assert(seqTask, NotNil) 524 525 // existing tasks wait for the hotplug-seq-wait task 526 c.Assert(t1.WaitTasks(), HasLen, 1) 527 c.Assert(t1.WaitTasks()[0].ID(), Equals, seqTask.ID()) 528 c.Assert(t2.WaitTasks(), HasLen, 1) 529 c.Assert(t2.WaitTasks()[0].ID(), Equals, seqTask.ID()) 530 } 531 532 func (s *helpersSuite) TestAddHotplugSlot(c *C) { 533 s.st.Lock() 534 defer s.st.Unlock() 535 536 var beforePrepareSlotCalled int 537 repo := interfaces.NewRepository() 538 iface := &ifacetest.TestInterface{ 539 InterfaceName: "test", 540 BeforePrepareSlotCallback: func(*snap.SlotInfo) error { 541 beforePrepareSlotCalled += 1 542 return nil 543 }, 544 } 545 repo.AddInterface(iface) 546 547 stateSlots, err := ifacestate.GetHotplugSlots(s.st) 548 c.Assert(err, IsNil) 549 c.Check(stateSlots, HasLen, 0) 550 551 si := &snap.SideInfo{Revision: snap.R(1)} 552 coreInfo := snaptest.MockSnap(c, coreSnapYaml, si) 553 554 slot := &snap.SlotInfo{ 555 Name: "slot", 556 Label: "label", 557 Snap: coreInfo, 558 Interface: "test", 559 Attrs: map[string]interface{}{"foo": "bar"}, 560 HotplugKey: "key", 561 } 562 c.Assert(ifacestate.AddHotplugSlot(s.st, repo, stateSlots, iface, slot), IsNil) 563 c.Assert(beforePrepareSlotCalled, Equals, 1) 564 565 // same slot cannot be re-added to repo 566 c.Assert(ifacestate.AddHotplugSlot(s.st, repo, stateSlots, iface, slot), ErrorMatches, `cannot add hotplug slot "slot" for interface test: snap "core" has slots conflicting on name "slot"`) 567 568 stateSlots, err = ifacestate.GetHotplugSlots(s.st) 569 c.Assert(err, IsNil) 570 c.Assert(stateSlots, HasLen, 1) 571 572 stateSlot := stateSlots["slot"] 573 c.Assert(stateSlot, NotNil) 574 c.Check(stateSlot, DeepEquals, &ifacestate.HotplugSlotInfo{ 575 Name: "slot", 576 Interface: "test", 577 StaticAttrs: map[string]interface{}{"foo": "bar"}, 578 HotplugKey: "key", 579 HotplugGone: false}) 580 } 581 582 func (s *helpersSuite) TestAddHotplugSlotValidationErrors(c *C) { 583 s.st.Lock() 584 defer s.st.Unlock() 585 586 repo := interfaces.NewRepository() 587 iface := &ifacetest.TestInterface{ 588 InterfaceName: "test", 589 BeforePrepareSlotCallback: func(slot *snap.SlotInfo) error { return fmt.Errorf("fail") }, 590 } 591 repo.AddInterface(iface) 592 593 stateSlots, err := ifacestate.GetHotplugSlots(s.st) 594 c.Assert(err, IsNil) 595 c.Check(stateSlots, HasLen, 0) 596 597 si := &snap.SideInfo{Revision: snap.R(1)} 598 coreInfo := snaptest.MockSnap(c, coreSnapYaml, si) 599 600 slot := &snap.SlotInfo{ 601 Name: "slot", 602 Snap: coreInfo, 603 Interface: "test", 604 } 605 // hotplug key missing 606 c.Assert(ifacestate.AddHotplugSlot(s.st, repo, stateSlots, iface, slot), ErrorMatches, `internal error: cannot store slot "slot", not a hotplug slot`) 607 slot.HotplugKey = "key" 608 609 // sanitization failure 610 c.Assert(ifacestate.AddHotplugSlot(s.st, repo, stateSlots, iface, slot), ErrorMatches, `cannot sanitize hotplug slot \"slot\" for interface test: fail`) 611 }