Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

interfaces/hotplug: add hotplug Specification and HotplugDeviceInfo #5416

Merged
merged 22 commits into from
Jul 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
275f7ad
Added hotplug Specification and HotplugDeviceInfo.
stolowski Jun 27, 2018
280ed48
Merge branch 'master' into hotplug-device-info
stolowski Jun 28, 2018
8163720
Merge branch 'master' into hotplug-device-info
stolowski Jun 28, 2018
1edbf70
Review feedback.
stolowski Jun 28, 2018
9a5f9da
Removed type HotplugDeviceHandler type definition for now.
stolowski Jun 28, 2018
63b33d0
Updated HotPlugSpec.
stolowski Jun 28, 2018
07adf32
Merge branch 'master' into hotplug-device-info
stolowski Jun 28, 2018
7dd7efe
Merge branch 'master' into hotplug-device-info
stolowski Jun 29, 2018
e408116
Merge branch 'master' into hotplug-device-info
stolowski Jun 29, 2018
e3adc77
Merge branch 'master' into hotplug-device-info
stolowski Jun 29, 2018
23e8ec6
Merge branch 'master' into hotplug-device-info
stolowski Jun 29, 2018
06eb95c
Sort slots.
stolowski Jun 29, 2018
4c637dd
Review comments.
stolowski Jun 29, 2018
4c23113
More comments.
stolowski Jun 29, 2018
ee9c8b6
Made data private, added generic Attribute getter.
stolowski Jun 29, 2018
dc6a005
Added Definer type.
stolowski Jul 2, 2018
4bff39a
Merge branch 'master' into hotplug-device-info
stolowski Jul 2, 2018
7ceb603
Merge branch 'master' into hotplug-device-info
stolowski Jul 4, 2018
9c027cf
Validate interface name in the hotplug spec.
stolowski Jul 4, 2018
8d1b22d
Drop Object attribute from HotplugDeviceInfo. Use snap.ValidateSlotName.
stolowski Jul 5, 2018
00276aa
Reverted the addition of ValidateName.
stolowski Jul 5, 2018
237c82b
Check that DEVPATH exists.
stolowski Jul 5, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions dirs/dirs.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ var (
SnapshotsDir string

ErrtrackerDbDir string
SysfsDir string
)

const (
Expand Down Expand Up @@ -267,4 +268,5 @@ func SetRootDir(rootdir string) {
SnapshotsDir = filepath.Join(rootdir, snappyDir, "snapshots")

ErrtrackerDbDir = filepath.Join(rootdir, snappyDir, "errtracker.db")
SysfsDir = filepath.Join(rootdir, "/sys")
}
36 changes: 6 additions & 30 deletions interfaces/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func getAttribute(snapName string, ifaceName string, staticAttrs map[string]inte
func NewConnectedSlot(slot *snap.SlotInfo, dynamicAttrs map[string]interface{}) *ConnectedSlot {
return &ConnectedSlot{
slotInfo: slot,
staticAttrs: copyAttributes(slot.Attrs),
staticAttrs: utils.CopyAttributes(slot.Attrs),
dynamicAttrs: utils.NormalizeInterfaceAttributes(dynamicAttrs).(map[string]interface{}),
}
}
Expand All @@ -115,7 +115,7 @@ func NewConnectedSlot(slot *snap.SlotInfo, dynamicAttrs map[string]interface{})
func NewConnectedPlug(plug *snap.PlugInfo, dynamicAttrs map[string]interface{}) *ConnectedPlug {
return &ConnectedPlug{
plugInfo: plug,
staticAttrs: copyAttributes(plug.Attrs),
staticAttrs: utils.CopyAttributes(plug.Attrs),
dynamicAttrs: utils.NormalizeInterfaceAttributes(dynamicAttrs).(map[string]interface{}),
}
}
Expand Down Expand Up @@ -157,12 +157,12 @@ func (plug *ConnectedPlug) StaticAttr(key string, val interface{}) error {

// StaticAttrs returns all static attributes.
func (plug *ConnectedPlug) StaticAttrs() map[string]interface{} {
return copyAttributes(plug.staticAttrs)
return utils.CopyAttributes(plug.staticAttrs)
}

// DynamicAttrs returns all dynamic attributes.
func (plug *ConnectedPlug) DynamicAttrs() map[string]interface{} {
return copyAttributes(plug.dynamicAttrs)
return utils.CopyAttributes(plug.dynamicAttrs)
}

// Attr returns a dynamic attribute with the given name. It falls back to returning static
Expand Down Expand Up @@ -230,12 +230,12 @@ func (slot *ConnectedSlot) StaticAttr(key string, val interface{}) error {

// StaticAttrs returns all static attributes.
func (slot *ConnectedSlot) StaticAttrs() map[string]interface{} {
return copyAttributes(slot.staticAttrs)
return utils.CopyAttributes(slot.staticAttrs)
}

// DynamicAttrs returns all dynamic attributes.
func (slot *ConnectedSlot) DynamicAttrs() map[string]interface{} {
return copyAttributes(slot.dynamicAttrs)
return utils.CopyAttributes(slot.dynamicAttrs)
}

// Attr returns a dynamic attribute with the given name. It falls back to returning static
Expand Down Expand Up @@ -270,27 +270,3 @@ func (slot *ConnectedSlot) Ref() *SlotRef {
func (conn *Connection) Interface() string {
return conn.Plug.plugInfo.Interface
}

func copyAttributes(value map[string]interface{}) map[string]interface{} {
return copyRecursive(value).(map[string]interface{})
}

func copyRecursive(value interface{}) interface{} {
// note: ensure all the mutable types (or types that need a conversion)
// are handled here.
switch v := value.(type) {
case []interface{}:
arr := make([]interface{}, len(v))
for i, el := range v {
arr[i] = copyRecursive(el)
}
return arr
case map[string]interface{}:
mp := make(map[string]interface{}, len(v))
for key, item := range v {
mp[key] = copyRecursive(item)
}
return mp
}
return value
}
3 changes: 2 additions & 1 deletion interfaces/connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package interfaces
import (
. "gopkg.in/check.v1"

"github.com/snapcore/snapd/interfaces/utils"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
"github.com/snapcore/snapd/testutil"
Expand Down Expand Up @@ -310,7 +311,7 @@ func (s *connSuite) TestCopyAttributes(c *C) {
"e": map[string]interface{}{"e1": "E1"},
}

cpy := CopyAttributes(orig)
cpy := utils.CopyAttributes(orig)
c.Check(cpy, DeepEquals, orig)

cpy["d"].([]interface{})[0] = 999
Expand Down
3 changes: 1 addition & 2 deletions interfaces/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ func (c BySlotInfo) Swap(i, j int) { bySlotInfo(c).Swap(i, j) }
func (c BySlotInfo) Less(i, j int) bool { return bySlotInfo(c).Less(i, j) }

var (
CopyAttributes = copyAttributes
FindSnapdPath = findSnapdPath
FindSnapdPath = findSnapdPath
)

// MockIsHomeUsingNFS mocks the real implementation of osutil.IsHomeUsingNFS
Expand Down
87 changes: 87 additions & 0 deletions interfaces/hotplug/deviceinfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package hotplug

import (
"fmt"
"path/filepath"

"github.com/snapcore/snapd/dirs"
)

// HotplugDeviceInfo carries information about added/removed device detected at runtime.
type HotplugDeviceInfo struct {
// map of all attributes returned for given uevent.
data map[string]string
}

// NewHotplugDeviceInfo creates HotplugDeviceInfo structure related to udev add or remove event.
func NewHotplugDeviceInfo(env map[string]string) (*HotplugDeviceInfo, error) {
if _, ok := env["DEVPATH"]; !ok {
return nil, fmt.Errorf("missing device path attribute")
}
return &HotplugDeviceInfo{
data: env,
}, nil
}

// Returns the value of "SUBSYSTEM" attribute of the udev event associated with the device, e.g. "usb".
// Subsystem value is always present.
func (h *HotplugDeviceInfo) Subsystem() string {
return h.data["SUBSYSTEM"]
}

// Returns full device path under /sysfs, e.g /sys/devices/pci0000:00/0000:00:14.0/usb1/1-2.
// The path is derived from DEVPATH attribute of the udev event.
func (h *HotplugDeviceInfo) DevicePath() string {
// DEVPATH is guaranteed to exist (checked in the ctor).
path, _ := h.Attribute("DEVPATH")
return filepath.Join(dirs.SysfsDir, path)
}

// Returns the value of "MINOR" attribute of the udev event associated with the device.
// The Minor value may be empty.
func (h *HotplugDeviceInfo) Minor() string {
return h.data["MINOR"]
}

// Returns the value of "MAJOR" attribute of the udev event associated with the device.
// The Major value may be empty.
func (h *HotplugDeviceInfo) Major() string {
return h.data["MAJOR"]
}

// Returns the value of "DEVNAME" attribute of the udev event associated with the device, e.g. "ttyUSB0".
// The DeviceName value may be empty.
func (h *HotplugDeviceInfo) DeviceName() string {
return h.data["DEVNAME"]
}

// Returns the value of "DEVTYPE" attribute of the udev event associated with the device, e.g. "usb_device".
// The DeviceType value may be empty.
func (h *HotplugDeviceInfo) DeviceType() string {
return h.data["DEVTYPE"]
}

// Generic method for getting arbitrary attribute from the uevent data.
func (h *HotplugDeviceInfo) Attribute(name string) (string, bool) {
val, ok := h.data[name]
return val, ok
}
98 changes: 98 additions & 0 deletions interfaces/hotplug/deviceinfo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package hotplug

import (
"path/filepath"
"testing"

. "gopkg.in/check.v1"

"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/testutil"
)

func Test(t *testing.T) { TestingT(t) }

type hotplugSuite struct {
testutil.BaseTest
}

var _ = Suite(&hotplugSuite{})

func (s *hotplugSuite) SetUpTest(c *C) {
s.BaseTest.SetUpTest(c)
dirs.SetRootDir(c.MkDir())
}

func (s *hotplugSuite) TearDownTest(c *C) {
s.BaseTest.TearDownTest(c)
dirs.SetRootDir("")
}

func (s *hotplugSuite) TestBasicProperties(c *C) {
env := map[string]string{
"DEVPATH": "/devices/pci0000:00/0000:00:14.0/usb2/2-3", "DEVNAME": "bus/usb/002/003",
"DEVTYPE": "usb_device",
"PRODUCT": "1d50/6108/0", "DEVNUM": "003",
"SEQNUM": "4053",
"ACTION": "add", "SUBSYSTEM": "usb",
"MAJOR": "189", "MINOR": "130",
"TYPE": "0/0/0", "BUSNUM": "002",
}

di, err := NewHotplugDeviceInfo(env)
c.Assert(err, IsNil)

c.Assert(di.DeviceName(), Equals, "bus/usb/002/003")
c.Assert(di.DeviceType(), Equals, "usb_device")
c.Assert(di.DevicePath(), Equals, filepath.Join(dirs.SysfsDir, "/devices/pci0000:00/0000:00:14.0/usb2/2-3"))
c.Assert(di.Subsystem(), Equals, "usb")
c.Assert(di.Major(), Equals, "189")
c.Assert(di.Minor(), Equals, "130")

minor, ok := di.Attribute("MINOR")
c.Assert(ok, Equals, true)
c.Assert(minor, Equals, "130")

_, ok = di.Attribute("FOO")
c.Assert(ok, Equals, false)
}

func (s *hotplugSuite) TestPropertiesMissing(c *C) {
env := map[string]string{
"DEVPATH": "/devices/pci0000:00/0000:00:14.0/usb2/2-3",
"ACTION": "add", "SUBSYSTEM": "usb",
}

di, err := NewHotplugDeviceInfo(map[string]string{})
c.Assert(err, NotNil)
c.Assert(err, ErrorMatches, `missing device path attribute`)

di, err = NewHotplugDeviceInfo(env)
c.Assert(err, IsNil)

c.Assert(di.DeviceName(), Equals, "")
c.Assert(di.DeviceType(), Equals, "")
c.Assert(di.DevicePath(), Equals, filepath.Join(dirs.SysfsDir, "/devices/pci0000:00/0000:00:14.0/usb2/2-3"))
c.Assert(di.Subsystem(), Equals, "usb")
c.Assert(di.Major(), Equals, "")
c.Assert(di.Minor(), Equals, "")
}
93 changes: 93 additions & 0 deletions interfaces/hotplug/spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package hotplug

import (
"fmt"
"sort"

"github.com/snapcore/snapd/interfaces/utils"
"github.com/snapcore/snapd/snap"
)

// Definer can be implemented by interfaces that need to create slots in response to hotplug events
type Definer interface {
HotplugDeviceDetected(di *HotplugDeviceInfo, spec *Specification) error
}

// SlotSpec is a definition of the slot to create in response to udev event.
type SlotSpec struct {
// XXX: Name is the name the interface wants to give to the slot; we
// might want to mediate this though (e.g. generate automatically), so this
// may change/go away.
Name string
Label string
Attrs map[string]interface{}
}

// Specification contains data about all slots that a hotplug interface wants to have created in response to uevent.
type Specification struct {
// slots are indexed by slot name to ensure unique names
slots map[string]*SlotSpec
}

// NewSpecification creates an empty hotplug Specification.
func NewSpecification() *Specification {
return &Specification{
slots: make(map[string]*SlotSpec),
}
}

// AddSlot adds a specification of a slot.
func (h *Specification) AddSlot(slotSpec *SlotSpec) error {
if _, ok := h.slots[slotSpec.Name]; ok {
return fmt.Errorf("slot %q already exists", slotSpec.Name)
}
if err := snap.ValidateSlotName(slotSpec.Name); err != nil {
return err
}
attrs := slotSpec.Attrs
if attrs == nil {
attrs = make(map[string]interface{})
} else {
attrs = utils.CopyAttributes(slotSpec.Attrs)
}
h.slots[slotSpec.Name] = &SlotSpec{
Name: slotSpec.Name,
Label: slotSpec.Label,
Attrs: utils.NormalizeInterfaceAttributes(attrs).(map[string]interface{}),
}
return nil
}

// Slots returns specifications of all slots created by given interface.
func (h *Specification) Slots() []*SlotSpec {
keys := make([]string, 0, len(h.slots))
for k := range h.slots {
keys = append(keys, k)
}
sort.Strings(keys)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do these need to be sorted or is it just to get a stable output for the tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's about making it test friendly only.


slots := make([]*SlotSpec, 0, len(h.slots))
for _, k := range keys {
slots = append(slots, h.slots[k])
}
return slots
}