-
Notifications
You must be signed in to change notification settings - Fork 465
MCD: Add node-ip subcommand #1564
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "flag" | ||
| "fmt" | ||
| "net" | ||
| "os" | ||
| "path/filepath" | ||
|
|
||
| // Enable sha256 in container image references | ||
| _ "crypto/sha256" | ||
|
|
||
| "github.com/golang/glog" | ||
| "github.com/spf13/cobra" | ||
| "github.com/spf13/pflag" | ||
|
|
||
| "github.com/openshift/machine-config-operator/pkg/daemon/nodenet" | ||
| ) | ||
|
|
||
| const ( | ||
| kubeletSvcOverridePath = "/etc/systemd/system/kubelet.service.d/20-nodenet.conf" | ||
| crioSvcOverridePath = "/etc/systemd/system/crio.service.d/20-nodenet.conf" | ||
| ) | ||
|
|
||
| var nodeIPCmd = &cobra.Command{ | ||
| Use: "node-ip", | ||
| DisableFlagsInUseLine: true, | ||
| Short: "Node IP tools", | ||
| Long: "Node IP has tools that aid in the configuration of nodes in platforms that use Virtual IPs", | ||
| } | ||
|
|
||
| var nodeIPShowCmd = &cobra.Command{ | ||
| Use: "show", | ||
| DisableFlagsInUseLine: true, | ||
| Short: "Show a configured IP address that directly routes to the given Virtual IPs", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not just a configured IP address, it's the right one (for some definition of "right") |
||
| Args: cobra.MinimumNArgs(1), | ||
| Run: func(cmd *cobra.Command, args []string) { | ||
| err := show(cmd, args) | ||
| if err != nil { | ||
| glog.Exitf("error in node-ip show: %v\n", err) | ||
| } | ||
| }, | ||
| } | ||
|
|
||
| var nodeIPSetCmd = &cobra.Command{ | ||
| Use: "set", | ||
| DisableFlagsInUseLine: true, | ||
| Short: "Sets container runtime services to bind to a configured IP address that directly routes to the given virtual IPs", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I assume the usage here is that you pass it the IPs of the other masters, in order to find an IP on the interface that routes to those other IPs, rather than an IP on a provisioning/storage network? In that case, the IPs you pass would be non-virtual IPs, right?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You give it the virtual IPs so that then it can find which non virtual IPs are on the virtual IPs network, which will rule out non virtual IPs in other networks (like a storage network). |
||
| Args: cobra.MinimumNArgs(1), | ||
| Run: func(cmd *cobra.Command, args []string) { | ||
| err := set(cmd, args) | ||
| if err != nil { | ||
| glog.Exitf("error in node-ip set: %v\n", err) | ||
| } | ||
| }, | ||
| } | ||
|
|
||
| // init executes upon import | ||
| func init() { | ||
| rootCmd.AddCommand(nodeIPCmd) | ||
| nodeIPCmd.AddCommand(nodeIPShowCmd) | ||
| nodeIPCmd.AddCommand(nodeIPSetCmd) | ||
| pflag.CommandLine.AddGoFlagSet(flag.CommandLine) | ||
| flag.Set("logtostderr", "true") | ||
| flag.Parse() | ||
| } | ||
|
|
||
| func show(_ *cobra.Command, args []string) error { | ||
| vips := make([]net.IP, len(args)) | ||
| for i, arg := range args { | ||
| vips[i] = net.ParseIP(arg) | ||
| if vips[i] == nil { | ||
| return fmt.Errorf("Failed to parse IP address %s", arg) | ||
| } | ||
| glog.V(3).Infof("Parsed Virtual IP %s", vips[i]) | ||
| } | ||
|
|
||
| nodeAddrs, err := nodenet.AddressesRouting(vips, nodenet.NonDeprecatedAddress, nodenet.NonDefaultRoute) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| if len(nodeAddrs) > 0 { | ||
| fmt.Println(nodeAddrs[0]) | ||
| } else { | ||
| return fmt.Errorf("Failed to find node IP") | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func set(_ *cobra.Command, args []string) error { | ||
| vips := make([]net.IP, len(args)) | ||
| for i, arg := range args { | ||
| vips[i] = net.ParseIP(arg) | ||
| if vips[i] == nil { | ||
| return fmt.Errorf("Failed to parse IP address %s", arg) | ||
| } | ||
| glog.V(3).Infof("Parsed Virtual IP %s", vips[i]) | ||
| } | ||
|
|
||
| nodeAddrs, err := nodenet.AddressesRouting(vips, nodenet.NonDeprecatedAddress, nodenet.NonDefaultRoute) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| var chosenAddress net.IP | ||
| if len(nodeAddrs) > 0 { | ||
| chosenAddress = nodeAddrs[0] | ||
| } else { | ||
| return fmt.Errorf("Failed to find node IP") | ||
| } | ||
| glog.Infof("Chosen Node IP %s", chosenAddress) | ||
|
|
||
| // Kubelet | ||
| glog.V(2).Infof("Opening Kubelet service override path %s", kubeletSvcOverridePath) | ||
| kOverride, err := os.Create(kubeletSvcOverridePath) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| defer kOverride.Close() | ||
|
|
||
| kOverrideContent := fmt.Sprintf("[Service]\nEnvironment=\"KUBELET_NODE_IP=%s\"\n", chosenAddress) | ||
| glog.V(3).Infof("Writing Kubelet service override with content %s", kOverrideContent) | ||
| _, err = kOverride.WriteString(kOverrideContent) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| // CRI-O | ||
| crioOverrideDir := filepath.Dir(crioSvcOverridePath) | ||
| err = os.MkdirAll(crioOverrideDir, 0755) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| glog.V(2).Infof("Opening CRI-O service override path %s", crioSvcOverridePath) | ||
| cOverride, err := os.Create(crioSvcOverridePath) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| defer cOverride.Close() | ||
|
|
||
| cOverrideContent := fmt.Sprintf("[Service]\nEnvironment=\"CONTAINER_STREAM_ADDRESS=%s\"\n", chosenAddress) | ||
| glog.V(3).Infof("Writing CRI-O service override with content %s", cOverrideContent) | ||
| _, err = cOverride.WriteString(cOverrideContent) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| package nodenet | ||
|
|
||
| import ( | ||
| "net" | ||
|
|
||
| "github.com/golang/glog" | ||
| "github.com/vishvananda/netlink" | ||
| "golang.org/x/sys/unix" | ||
| ) | ||
|
|
||
| // AddressFilter is a function type to filter addresses | ||
| type AddressFilter func(netlink.Addr) bool | ||
|
|
||
| // RouteFilter is a function type to filter routes | ||
| type RouteFilter func(netlink.Route) bool | ||
|
|
||
| func getAddrs() (addrMap map[netlink.Link][]netlink.Addr, err error) { | ||
| nlHandle, err := netlink.NewHandle() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| defer nlHandle.Delete() | ||
|
|
||
| links, err := nlHandle.LinkList() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| addrMap = make(map[netlink.Link][]netlink.Addr) | ||
| for _, link := range links { | ||
| addresses, err := nlHandle.AddrList(link, netlink.FAMILY_ALL) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| for _, address := range addresses { | ||
| if _, ok := addrMap[link]; ok { | ||
| addrMap[link] = append(addrMap[link], address) | ||
| } else { | ||
| addrMap[link] = []netlink.Addr{address} | ||
| } | ||
| } | ||
| } | ||
| glog.V(7).Infof("retrieved Address map %+v", addrMap) | ||
| return addrMap, nil | ||
| } | ||
|
|
||
| func getRouteMap() (routeMap map[int][]netlink.Route, err error) { | ||
| nlHandle, err := netlink.NewHandle() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| defer nlHandle.Delete() | ||
|
|
||
| routes, err := nlHandle.RouteList(nil, netlink.FAMILY_V6) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| routeMap = make(map[int][]netlink.Route) | ||
| for _, route := range routes { | ||
| if route.Protocol != unix.RTPROT_RA { | ||
| glog.V(4).Infof("Ignoring route non Router advertisement route %+v", route) | ||
| continue | ||
| } | ||
| if _, ok := routeMap[route.LinkIndex]; ok { | ||
| routeMap[route.LinkIndex] = append(routeMap[route.LinkIndex], route) | ||
| } else { | ||
| routeMap[route.LinkIndex] = []netlink.Route{route} | ||
| } | ||
| } | ||
|
|
||
| glog.V(7).Infof("Retrieved IPv6 route map %+v", routeMap) | ||
|
|
||
| return routeMap, nil | ||
| } | ||
|
|
||
| // NonDeprecatedAddress returns true if the address is IPv6 and has a preferred lifetime of 0 | ||
| func NonDeprecatedAddress(address netlink.Addr) bool { | ||
| return !(net.IPv6len == len(address.IP) && address.PreferedLft == 0) | ||
| } | ||
|
|
||
| // NonDefaultRoute returns whether the passed Route is the default | ||
| func NonDefaultRoute(route netlink.Route) bool { | ||
| return route.Dst != nil | ||
| } | ||
|
|
||
| // AddressesRouting takes a slice of Virtual IPs and returns a slice of configured addresses in the current network namespace that directly route to those vips. You can optionally pass an AddressFilter and/or RouteFilter to further filter down which addresses are considered | ||
| func AddressesRouting(vips []net.IP, af AddressFilter, rf RouteFilter) ([]net.IP, error) { | ||
| addrMap, err := getAddrs() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| var routeMap map[int][]netlink.Route | ||
| matches := make([]net.IP, 0) | ||
| for link, addresses := range addrMap { | ||
| for _, address := range addresses { | ||
| maskPrefix, maskBits := address.Mask.Size() | ||
| if !af(address) { | ||
| continue | ||
| } | ||
| if net.IPv6len == len(address.IP) && maskPrefix == maskBits { | ||
| if routeMap == nil { | ||
| routeMap, err = getRouteMap() | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
| } | ||
| if routes, ok := routeMap[link.Attrs().Index]; ok { | ||
| for _, route := range routes { | ||
| if !rf(route) { | ||
| continue | ||
| } | ||
| routePrefix, _ := route.Dst.Mask.Size() | ||
| glog.V(4).Infof("Checking route %+v (mask %s) for address %+v", route, route.Dst.Mask, address) | ||
| if routePrefix != 0 { | ||
| containmentNet := net.IPNet{IP: address.IP, Mask: route.Dst.Mask} | ||
| for _, vip := range vips { | ||
| glog.V(3).Infof("Checking whether address %s with route %s contains VIP %s", address, route, vip) | ||
| if containmentNet.Contains(vip) { | ||
| glog.V(2).Infof("Address %s with route %s contains VIP %s", address, route, vip) | ||
| matches = append(matches, address.IP) | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| for _, vip := range vips { | ||
| glog.V(3).Infof("Checking whether address %s contains VIP %s", address, vip) | ||
| if address.Contains(vip) { | ||
| glog.V(2).Infof("Address %s contains VIP %s", address, vip) | ||
| matches = append(matches, address.IP) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| } | ||
| return matches, 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.
It's not really clear in this context what "virtual" means. It might be better to just say "multiple IPs"?