-
Notifications
You must be signed in to change notification settings - Fork 601
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
Conversation
This is missing some stuff
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First pass, -1 because of the issue with make
, please ping me for 2nd pass.
dirs/dirs.go
Outdated
@@ -106,6 +106,7 @@ var ( | |||
SnapshotsDir string | |||
|
|||
ErrtrackerDbDir string | |||
SysDir string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick, to avoid any shade of confusion could you please call this SysfsDir
instead?
interfaces/hotplug/deviceinfo.go
Outdated
"github.com/snapcore/snapd/dirs" | ||
) | ||
|
||
type HotplugDeviceInfo struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document this somehow.
HotplugDeviceInfo stores information about devices added dynamically at runtime.
interfaces/hotplug/deviceinfo.go
Outdated
type HotplugDeviceInfo struct { | ||
object string | ||
Data map[string]string | ||
idVendor string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm somewhat -0.5 on keeping those explicit. Instead we could have methods that fish them out of Data
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note, these attributes are not present in Data, they were read lazily on first access from the filesystem. However, removed all the idVendor/idProduct/serial aspects for now per our discussion.
interfaces/hotplug/deviceinfo.go
Outdated
) | ||
|
||
type HotplugDeviceInfo struct { | ||
object string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document what you mean by object
here. Is this is a sysfs path?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a comment to Object() getter.
interfaces/hotplug/deviceinfo.go
Outdated
serial string | ||
} | ||
|
||
func NewHotplugDeviceInfo(obj string, env map[string]string) *HotplugDeviceInfo { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add docs for everything.
interfaces/hotplug/deviceinfo.go
Outdated
HotplugDeviceDetected(di *HotplugDeviceInfo, spec *Specification) error | ||
} | ||
|
||
// HotplugDeviceInfo can be implemented by interfaces that need to provide a non-standard device key for hotplug devices |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a chunk of text right at the top of the file that documents the key concepts of hot plug in snapd. Please specifically document the notion of device key.
interfaces/hotplug/spec.go
Outdated
} | ||
} | ||
|
||
func (h *Specification) AddSlot(name, label string, attrs map[string]interface{}) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this take a SlotInfo instead (and ensure some things are not wired, i.e. no snap.Info). This is more future proof than a function with three arguments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could, my only concern is there is actually more stuff in SlotInfo that should not be touched/initialized by the interface implementation. We can of course make sure it's not wired (error out or ignore/reset snap.Info, hooks, apps etc.) but it feels like somewhat confusing API. How about something like:
AddSlot(slot SlotSpec)
so this is stable and future-proof, SlotSpec
has only the attributes/setters that are relevant and we have clear way of mediating.
interfaces/hotplug/spec.go
Outdated
"github.com/snapcore/snapd/interfaces/utils" | ||
) | ||
|
||
type SlotSpec struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document public things please. This helps you and your reviewers alike.
interfaces/hotplug/spec.go
Outdated
} | ||
|
||
type Specification struct { | ||
slots map[string]SlotSpec |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would make this map[string]*SlotSpec
as there are maps inside SlotSpec
and making all of this by-value feels bad.
interfaces/hotplug/spec.go
Outdated
} | ||
|
||
func (h *Specification) Slots() []SlotSpec { | ||
slots := make([]SlotSpec, len(h.slots)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong, you meant make([]SlotSpec, 0, len(h.slots))
Probably implies this is not tested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zyga covered the rest
interfaces/hotplug/deviceinfo.go
Outdated
|
||
func (h *HotplugDeviceInfo) readOnceMaybe(fileName string, out *string) string { | ||
if *out == "" { | ||
data, err := ioutil.ReadFile(filepath.Join(h.Path(), fileName)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be converted to a public ReadAttribute() (string, error)
helper
Codecov Report
@@ Coverage Diff @@
## master #5416 +/- ##
==========================================
+ Coverage 78.92% 79.01% +0.08%
==========================================
Files 509 512 +3
Lines 38445 38672 +227
==========================================
+ Hits 30343 30555 +212
- Misses 5655 5664 +9
- Partials 2447 2453 +6
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more pass with some comments. I think this needs some more tweaks before landing (at least in my eyes)
interfaces/hotplug/deviceinfo.go
Outdated
|
||
// HotplugDeviceInfo carries information about added/removed device detected at runtime. | ||
type HotplugDeviceInfo struct { | ||
object string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you just call this devpath
please, here and everywhere in the hot plug code. My desire here is to limit the vocabulary used so that we don't invent names for existing kernel constructs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, I could, however this corresponds to KObj (kernel object?) value associated with the udev event reported by go-udev (what I get from go-udev is kobj string and map of extra data such as DEVPATH etc). While there seems to be direct relation between kobj and DEVPATH, I'm not 100% sure it's always the case, so perhaps it's ok to leave the doors open in case we find both devpath and object values useful?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either devpath
and kobjsomething
are better than object
which is as generic as one can get. Whatever you do please explain what the value is in a comment.
interfaces/hotplug/deviceinfo.go
Outdated
} | ||
|
||
// Returns the value of "SUBSYSTEM" attribute of the udev event associated with the device, e.g. "usb". | ||
func (h *HotplugDeviceInfo) Subsystem() string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is worth to indicate that SUBSYSTEM is always present. while pretty much everything else is entirely optional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Plugging a device in results in multiple add
events with generic events first, followed by more concrete ones where you actually got more interesting attributes present. I expect interfaces to filter out (ignore) some events and only react on those that actually cary all important attributes.
Point taken thouhg, I added comments to all attributes.
interfaces/hotplug/deviceinfo.go
Outdated
return h.Data["SUBSYSTEM"] | ||
} | ||
|
||
// Returns the value of "MINOR" attribute of the udev event associated with the device. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please indicate that this and everything below is optional and may return an empty string.
I also have a gut feeling Data should be private but we can see how this works.
EDIT: I have a feeling we should just expose an attribute getter and not have APIs for things that are optional (and by looking at udev database, mostly absent).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added comments. Made data
private and added a generic attribute getter. I'm not sure about removing getters for optional attributes, I think these are actually the attributes that are the most interesting for interfaces (even if optional), so having exposed helpers for them feels handy and I'd personally prefer to read di.Minor() than di.Attribute("MINOR").
} | ||
|
||
di := NewHotplugDeviceInfo("/devices/pci0000:00/0000:00:14.0/usb2/2-3", env) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a test with all of the optional things absent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.
interfaces/hotplug/spec.go
Outdated
} | ||
|
||
type Specification struct { | ||
slots map[string]*SlotSpec |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document what the key in the map is. Is it DEVPATH?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documented.
interfaces/hotplug/spec.go
Outdated
} | ||
} | ||
|
||
func (h *Specification) AddSlot(slotSpec *SlotSpec) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This must absolutely associate with a sysfs device. How is this being ensured?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is going to happen outside in the hotplug event processing loop. Hotplug event handler will iterate over all hotplug interfaces, determine device key, create a Specification per-device. Interface fills in the Specification by adding slot(s), the outher code knows device key for given set of slots.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add this response as a comment please.
func (s *hotplugSpecSuite) TestAddSlot(c *C) { | ||
spec := NewSpecification() | ||
c.Assert(spec.AddSlot(&SlotSpec{Name: "slot1", Label: "A slot", Attrs: map[string]interface{}{"foo": "bar"}}), IsNil) | ||
c.Assert(spec.AddSlot(&SlotSpec{Name: "slot2", Label: "A slot", Attrs: map[string]interface{}{"baz": "booze"}}), IsNil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
foo bar froz bot AFAIK ;-)
for k := range h.slots { | ||
keys = append(keys, k) | ||
} | ||
sort.Strings(keys) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
interfaces/hotplug/deviceinfo.go
Outdated
// 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) Path() string { | ||
path, ok := h.Attribute("DEVPATH") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The check doesn't do any harm, but Isn't DEVPATH one of the attributes that's supposed to be there always?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we have Path and Object as separate functions if they must always return the exact same value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more pass, let's chat on IRC
interfaces/hotplug/deviceinfo.go
Outdated
|
||
// Returns object path, i.e. the sysfs path of the device, e.g. /devices/pci0000:00/0000:00:14.0/usb1/1-2. | ||
// It is expected to be equal to DEVPATH attribute. | ||
func (h *HotplugDeviceInfo) Object() string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really think this should be just DevPath
or Devpath
and that we should not use the un-decorated word object
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, removed object (and Object getter) alltogether for now, per IRC discussion.
interfaces/hotplug/deviceinfo.go
Outdated
// 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) Path() string { | ||
path, ok := h.Attribute("DEVPATH") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we have Path and Object as separate functions if they must always return the exact same value?
interfaces/utils/utils.go
Outdated
var validName = regexp.MustCompile("^[a-z](?:-?[a-z0-9])*$") | ||
|
||
// ValidateName checks if a string can be used as a plug or slot name. | ||
func ValidateName(name string) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please drop this and look at snap.Validate{Plug,Slot}Name
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea, thanks. Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with one last comment
interfaces/hotplug/deviceinfo.go
Outdated
|
||
// NewHotplugDeviceInfo creates HotplugDeviceInfo structure related to udev add or remove event. | ||
func NewHotplugDeviceInfo(env map[string]string) *HotplugDeviceInfo { | ||
return &HotplugDeviceInfo{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a sanity check this should ensure that env
contains DEVPATH
.
Introduce two structures related to hotplug:
Specification
will be passed to interfaces that implementHotplugDeviceDetected(..)
method, it's a way for interfaces to add slot definitions on hot plug add event.HotplugDeviceInfo
wraps some uevent data, it will be passed from udev monitor to interface manager callback on hotplug add/remove events.