-
-
Notifications
You must be signed in to change notification settings - Fork 322
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
resources: net: Add net resource #289
Conversation
@purpleidea Still needs tests (but the code was written with testing in mind,) and I want to chat with you about whether or not to pull out the dbus properties eavesdropping code into a helper function with the signature |
resources/net.go
Outdated
} | ||
if obj.State == "up" && ifaceUp { | ||
// error if we lose the connection and softfail is false | ||
if !obj.SoftFail && obj.oState == dbus.MakeVariant("no-carrier") { |
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 a bug. If we haven't yet received an event from the NIC, obj.oState will be nil. I'll figure out an alternative.
examples/net1.yaml
Outdated
@@ -0,0 +1,9 @@ | |||
--- | |||
graph: mygraph |
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 realize these examples are pretty specific to my environment. Let's chat about generalizing ("consistent network device naming" doesn't really apply across systems :/)
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.
Yeah :P -- There is some good news:
The consistently named devices are actually useful for automation when you have more than one network card. But annoying when you just have one. The good news: we could easily add a function in the language that returns the eth device name if there's only one :) -- More on that in February :)
For this patch, let's just name them all eth0
so users aren't confused about what they are. This will also work best in vms.
resources/net.go
Outdated
return false, errwrap.Wrapf(err, "error getting addrs") | ||
} | ||
if obj.State == "up" && ifaceUp { | ||
// error if we lose the connection and softfail is false |
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'd like to discuss this with you at some point, maybe before you review. This doesn't really work like it should. Instead I should be getting the /org/freedesktop/network1/link/$foo
OperationalState
property so I can detect when it's no-carrier
to know when we've lost connectivity. But, I can't figure out how to get the property with the dbus api (need to input a systemd unit, nothing I've tried works, but I can get it in d-feet)
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.
resources/net.go
Outdated
// until the timeout expires. If the connection comes back, continue | ||
// as if nothing happened. Error if the timeout expires (or isn't set.) | ||
if obj.OpState == dbus.MakeVariant(OpStateNoCarrier) { | ||
for i := 0; i < int(obj.Timeout); i++ { |
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 need some mentoring on making this exit cleanly. I'll try to figure it out in the meantime.
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.
Happy to-- let's discuss why we need a timeout at all... I think explaining this in the struct will be the best starting point, although yes, I agree having a blocking time.Sleep
in the code is not ideal.
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.
My apologies for the enormous number of comments and the delay in getting it done. I think this is pretty great, so thank you!
The two major structural things are the ip
list, and whether we need to use ip link set
command or not.
Thank you again!
PS: While Gateway could be a list, let's keep it as a single string for now.
examples/net1.yaml
Outdated
@@ -0,0 +1,9 @@ | |||
--- | |||
graph: mygraph |
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.
Yeah :P -- There is some good news:
The consistently named devices are actually useful for automation when you have more than one network card. But annoying when you just have one. The good news: we could easily add a function in the language that returns the eth device name if there's only one :) -- More on that in February :)
For this patch, let's just name them all eth0
so users aren't confused about what they are. This will also work best in vms.
examples/net1.yaml
Outdated
net: | ||
- name: enp4s0 | ||
state: up | ||
ipv4: 192.168.2.55/24 |
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.
nit: how about: 192.168.42.13/24
and 192.168.42.1 please? ;)
resources/net.go
Outdated
|
||
// ActiveState is the systemd-networkd service's active state. | ||
ActiveState = "ActiveState" | ||
// ActiveStateActive is the active service state. |
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.
These and a few other comments on these consts are in the form: foo is the foo of the whatever
. While I've certainly been guilt of this kind of thing before, if you could update a few of them so that they're a little bit more self descriptive for those that don't know what the internal networkd API is all about. Remember, these comments get displayed in godoc.
The fact that you have all these const's is probably good! Let's just make them useful in the docs if someone would ever need them, or private if they should never be used externally.
Why should they be public or private? If a third party wants to predict what we might be doing, knowing the prefix (eg mgmt-
) is something we need public. Also if a consumer of this resource when using mgmt as a lib wants to know something that it should be able to gleam from this, that's useful too.
resources/net.go
Outdated
// NetRes is an systemd-networkd resource. | ||
type NetRes struct { | ||
BaseRes `yaml:",inline"` | ||
State string `yaml:"state"` // up or down |
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.
Should we allow this to be empty so that it doesn't change the state? (Eg: maybe it only send's the IPV4 address if one exists?)
resources/net.go
Outdated
IPv4 string `yaml:"ipv4"` // ipv4 address in cidr format | ||
IPv6 string `yaml:"ipv6"` // ipv6 address in cidr format | ||
Gateway string `yaml:"gateway"` // gateway address | ||
Timeout time.Duration `yaml:"timeout"` // connection timeout in seconds |
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.
Could you better document what this does for the godoc string please? A multi-line above it might be appropriate.
resources/net.go
Outdated
// enable or disable the interface | ||
cmd := exec.Command("ip", "link", "set", obj.GetName(), obj.State) | ||
|
||
// make sure we run the command as root |
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.
If mgmt is not running as root, does this error appropriately? Is it possible that something like CAP_SYS_ADMIN or similar would give us this without root?
resources/net.go
Outdated
} | ||
} | ||
// build the unit file contents from the definition | ||
contents := unitFileContents(obj.GetName(), obj.IPv4, obj.IPv6, obj.Gateway) |
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 probably okay to just grab these values off the struct directly and have unitFileContents
be defined as part of the struct, so you don't have to pass them in.
resources/net.go
Outdated
return false, nil | ||
} | ||
|
||
// ipCheckApply checks the network device's ip addresses and performs the |
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.
What?
resources/net.go
Outdated
if svcState.Value != dbus.MakeVariant(ActiveStateReloading) { | ||
return false, nil | ||
} | ||
// restart networkd to re-initialize the interface |
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.
Is this what needs doing? Is there a reload
instead?
resources/net.go
Outdated
} | ||
for _, addr := range addrs { | ||
// ipv4 | ||
if strings.Contains(addr.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.
Cute-- Is this the way to check for ipv4 vs. v6?
In any case, as discussed if IP is an array, this just becomes easier, and we don't need to distinguish :)
If need be someone could write an isIPv4
func for the language.
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.
My first pass used this. https://golang.org/pkg/net/#IP.To4 but ACK on the array (slice?)
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.
Oh! Cool--- Why not use that?
@purpleidea No worries re: number of comments. :) Maybe we can hangout this weekend to nail down the unknowns. My pace is a little slower than usual this week, so no rush. |
@jonathangold I happened to notice that you updated this and it passed... Is it ready for another review and possible merge, or are you still hacking on it? |
@purpleidea still a few things to iron out. I should be able to finish it tomorrow. |
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 shaping up!
This isn't a final review, but if you can address each item and a bunch of XXX's, we'll be in good shape.
Take your time, good code takes polish, and you're 80% done. The last 20% will take 80% of the time, but it's the most fun. Leave lots of source comments (even full paragraphs with full sentences) if you want to document everything that you learn.
Thanks!
examples/yaml/net1.yaml
Outdated
@@ -0,0 +1,9 @@ | |||
--- | |||
graph: mygraph |
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.
All the examples are good, but do you mind converting them to mcl instead, and just ensuring that this works please?
// it is in the process of configuring. | ||
adminStateConfiguring = "configuring" | ||
|
||
// eventSources is the number of event sources in watch. |
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.
We should probably talk about this. AIUI it shouldn't be necessary.
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.
@purpleidea I could use some guidance on keeping track of which channels are open/closed with a map. Even just a hint like the types in the map I should be using would be helpful. ;)
BaseRes `yaml:",inline"` | ||
State string `yaml:"state"` // up or down or empty | ||
DHCP bool `yaml:"dhcp"` // enable dhcp | ||
Addrs []string `yaml:"addrs"` // list of addresses in cidr format |
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.
Public API in struct looks perfect.
resources/net.go
Outdated
var err error | ||
// get the network link's dbus path | ||
obj.ifacePath, err = dbusIfacePath(obj.GetName()) | ||
if err != nil { |
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.
nit, but fwiw when you're doing an =
instead of an :=
you can prefix this, eg:
if obj.ifacePath, err = dbusIfacePath(obj.GetName()); err != nil {
...
}
In general I like to do those as much as possible, unless the line is extremely long.
resources/net.go
Outdated
// Add a DBus rule to watch the "PropertiesChanged" signal and get messages | ||
// from the network link's dbus path. | ||
args := fmt.Sprintf(dBusEavesdropSprint, dBusPropertiesInterface, obj.ifacePath) | ||
call := obj.gdbus.BusObject().Call(dBusAddMatch, 0, args) |
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.
same as above if call is not used elsewhere. inline with if
resources/net.go
Outdated
objAddrs := obj.Addrs | ||
sort.Strings(objAddrs) | ||
sort.Strings(addrStrings) | ||
if len(objAddrs) == len(addrStrings) { |
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 sort of deep nesting either needs inline-ing, or made into a helper function please
if err := restartSystemdUnit(obj.sdbus, networkdService, dBusFailMode); err != nil { | ||
return false, errwrap.Wrapf(err, "error restarting networkd service") | ||
} | ||
|
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 we need to wait here? what happens with the restartSystemdUnit stuff?
please include your findings about how it works as a comment :)
resources/net.go
Outdated
if len(obj.Addrs) != len(res.Addrs) { | ||
return false | ||
} | ||
objAddrs := obj.Addrs |
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.
If we had this compare as a helper func, then it could be re-used here :)
resources/net.go
Outdated
// Close cleans up some resources so we can exit cleanly. | ||
func (obj *NetRes) Close() error { | ||
// close the dbus connection | ||
obj.sdbus.Close() |
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.
What about these errors that are returned...
return errwrap.Wrapf(err, "failed to restart unit") | ||
} | ||
|
||
// XXX: will this ever hang? |
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.
you tell me :)
unfortunately, sometimes these sorts of things are difficult to know and occasionally involve reading the upstream code a little. it's good practice, but don't dive too long. if you can't find it in 20 min, move on or ask someone.
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.
@purpleidea Made most of the changes. Still want to discuss the close map and a few other things before it's ready for another review. Maybe we can hangout later. :)
|
||
// Add a DBus rule to watch the "PropertiesChanged" signal and get messages | ||
// from the network link's dbus path. | ||
args := fmt.Sprintf(dBusEavesdropSprint, dBusPropertiesInterface, obj.ifacePath) |
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.
dBusEavesdropSprint is the format string. Maybe a more descriptive name for the const, or a comment?
Edit: Maybe dBusEavesdropFmtString
resources/net.go
Outdated
log.Printf("%s: ifaceCheckApply(%t)", obj, apply) | ||
|
||
// enable or disable the interface | ||
cmd := exec.Command("ip", "link", "set", obj.GetName(), obj.State) |
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.
replaced with netlink
resources/net.go
Outdated
if err := cmd.Wait(); err != nil { | ||
return false, errwrap.Wrapf(err, "%s", slurp) | ||
} | ||
// XXX: wait for state? |
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.
Yes. It may "reload" the unit file, but the ip doesn't change until the service is restarted. tried systemctl daemon-reload
and dbus.Conn.Reload() but the ip does not change without restarting the service.
This patch adds a net resource for managing nework interfaces, based around systemd-networkd.
Closing this since #371 is merged. I'll keep the branch in my repo for networkd reference. |
#28