-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Add SR-IOV CNI plugin #259
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# SR-IOV CNI plugin | ||
|
||
NIC with [SR-IOV](http://blog.scottlowe.org/2009/12/02/what-is-sr-iov/) capabilities works by introducing the idea of physical functions (PFs) and virtual functions (VFs). | ||
|
||
PF is used by host.Each VFs can be treated as a separate physical NIC and assigned to one container, and configured with separate MAC, VLAN and IP, etc. | ||
|
||
## Enable SR-IOV | ||
|
||
Given Intel ixgbe NIC on CentOS, Fedora or RHEL: | ||
|
||
``` | ||
# vi /etc/modprobe.conf | ||
options ixgbe max_vfs=8,8 | ||
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. is this a requirement for the plugin to work or just an example? 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 requirement. SR-IOV plugin need NIC enable SR-IOV capability, which not enabled by default. |
||
``` | ||
|
||
## Network configuration reference | ||
|
||
* `name` (string, required): the name of the network | ||
* `type` (string, required): "sriov" | ||
* `master` (string, required): name of the PF | ||
* `vf` (int, optional): VF index, default value is 0 | ||
* `vlan` (int, optional): VLAN ID for VF device | ||
* `mac` (string, optional): mac address for VF device | ||
* `ipam` (dictionary, required): IPAM configuration to be used for this network. | ||
|
||
## Usage | ||
|
||
Given the following network configuration: | ||
|
||
``` | ||
# cat > /etc/cni/net.d/10-mynet.conf <<EOF | ||
{ | ||
"name": "mynet", | ||
"type": "sriov", | ||
"master": "eth1", | ||
"vf": 1, | ||
"mac": "66:d8:02:77:aa:aa", | ||
"ipam": { | ||
"type": "host-local", | ||
"subnet": "10.55.206.0/26", | ||
"routes": [ | ||
{ "dst": "0.0.0.0/0" } | ||
], | ||
"gateway": "10.55.206.1" | ||
} | ||
} | ||
EOF | ||
``` | ||
|
||
``` | ||
# CNI_PATH=$CNI_PATH CNI_ARGS="IP=10.55.206.46" ./priv-net-run.sh ifconfig | ||
eth0 Link encap:Ethernet HWaddr 66:D8:02:77:AA:AA | ||
inet addr:10.55.206.46 Bcast:0.0.0.0 Mask:255.255.255.192 | ||
inet6 addr: fe80::64d8:2ff:fe77:aaaa/64 Scope:Link | ||
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 | ||
RX packets:7 errors:0 dropped:0 overruns:0 frame:0 | ||
TX packets:14 errors:0 dropped:0 overruns:0 carrier:0 | ||
collisions:0 txqueuelen:1000 | ||
RX bytes:530 (530.0 b) TX bytes:988 (988.0 b) | ||
|
||
lo Link encap:Local Loopback | ||
inet addr:127.0.0.1 Mask:255.0.0.0 | ||
inet6 addr: ::1/128 Scope:Host | ||
UP LOOPBACK RUNNING MTU:65536 Metric:1 | ||
RX packets:0 errors:0 dropped:0 overruns:0 frame:0 | ||
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 | ||
collisions:0 txqueuelen:0 | ||
RX bytes:0 (0.0 b) TX bytes:0 (0.0 b) | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
// Copyright 2015 CNI authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io/ioutil" | ||
"net" | ||
"os" | ||
"runtime" | ||
|
||
"github.com/containernetworking/cni/pkg/ipam" | ||
"github.com/containernetworking/cni/pkg/ns" | ||
"github.com/containernetworking/cni/pkg/skel" | ||
"github.com/containernetworking/cni/pkg/types" | ||
"github.com/vishvananda/netlink" | ||
) | ||
|
||
type NetConf struct { | ||
types.NetConf | ||
Master string `json:"master"` | ||
MAC string `json:"mac"` | ||
VF int `json:"vf"` | ||
Vlan int `json:"vlan"` | ||
} | ||
|
||
func init() { | ||
// this ensures that main runs only on main thread (thread group leader). | ||
// since namespace ops (unshare, setns) are done for a single thread, we | ||
// must ensure that the goroutine does not jump from OS thread to thread | ||
runtime.LockOSThread() | ||
} | ||
|
||
func loadConf(bytes []byte) (*NetConf, error) { | ||
n := &NetConf{} | ||
if err := json.Unmarshal(bytes, n); err != nil { | ||
return nil, fmt.Errorf("failed to load netconf: %v", err) | ||
} | ||
if n.Master == "" { | ||
return nil, fmt.Errorf(`"master" field is required. It specifies the host interface name to virtualize`) | ||
} | ||
return n, nil | ||
} | ||
|
||
func setupVF(conf *NetConf, ifName string, netns ns.NetNS) error { | ||
|
||
masterName := conf.Master | ||
vfIdx := conf.VF | ||
|
||
m, err := netlink.LinkByName(masterName) | ||
if err != nil { | ||
return fmt.Errorf("failed to lookup master %q: %v", conf.Master, err) | ||
} | ||
|
||
vfDir := fmt.Sprintf("/sys/class/net/%s/device/virtfn%d/net", masterName, vfIdx) | ||
if _, err := os.Lstat(vfDir); err != nil { | ||
return err | ||
} | ||
|
||
infos, err := ioutil.ReadDir(vfDir) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if len(infos) != 1 { | ||
return fmt.Errorf("Mutiple network devices in directory %s", vfDir) | ||
} | ||
|
||
// VF NIC name | ||
vfDevName := infos[0].Name() | ||
vfDev, err := netlink.LinkByName(vfDevName) | ||
if err != nil { | ||
return fmt.Errorf("failed to lookup vf device %q: %v", vfDevName, err) | ||
} | ||
|
||
// set hardware address | ||
if conf.MAC != "" { | ||
macAddr, err := net.ParseMAC(conf.MAC) | ||
if err != nil { | ||
return err | ||
} | ||
if err = netlink.LinkSetVfHardwareAddr(m, conf.VF, macAddr); err != nil { | ||
return fmt.Errorf("failed to set vf %d macaddress: %v", conf.VF, err) | ||
} | ||
} | ||
|
||
if conf.Vlan != 0 { | ||
if err = netlink.LinkSetVfVlan(m, conf.VF, conf.Vlan); err != nil { | ||
return fmt.Errorf("failed to set vf %d vlan: %v", conf.VF, err) | ||
} | ||
} | ||
|
||
if err = netlink.LinkSetUp(vfDev); err != nil { | ||
return fmt.Errorf("failed to setup vf %d device: %v", conf.VF, err) | ||
} | ||
|
||
// move VF device to ns | ||
if err = netlink.LinkSetNsFd(vfDev, int(netns.Fd())); err != nil { | ||
return fmt.Errorf("failed to move vf %d to netns: %v", conf.VF, err) | ||
} | ||
|
||
return netns.Do(func(_ ns.NetNS) error { | ||
err := renameLink(vfDevName, ifName) | ||
if err != nil { | ||
return fmt.Errorf("failed to rename vf %d device %q to %q: %v", conf.VF, vfDevName, ifName, err) | ||
} | ||
return nil | ||
}) | ||
} | ||
|
||
func releaseVF(conf *NetConf, ifName string, netns ns.NetNS) error { | ||
initns, err := ns.GetCurrentNS() | ||
if err != nil { | ||
return fmt.Errorf("failed to get init netns: %v", err) | ||
} | ||
|
||
if err = netns.Set(); err != nil { | ||
return fmt.Errorf("failed to enter netns %q: %v", netns, err) | ||
} | ||
|
||
// get VF device | ||
vfDev, err := netlink.LinkByName(ifName) | ||
if err != nil { | ||
return fmt.Errorf("failed to lookup vf %d device %q: %v", conf.VF, ifName, err) | ||
} | ||
|
||
// device name in init netns | ||
index := vfDev.Attrs().Index | ||
devName := fmt.Sprintf("dev%d", index) | ||
|
||
// shutdown VF device | ||
if err = netlink.LinkSetDown(vfDev); err != nil { | ||
return fmt.Errorf("failed to down vf % device: %v", conf.VF, err) | ||
} | ||
|
||
// rename VF device | ||
err = renameLink(ifName, devName) | ||
if err != nil { | ||
return fmt.Errorf("failed to rename vf %d evice %q to %q: %v", conf.VF, ifName, devName, err) | ||
} | ||
|
||
// move VF device to init netns | ||
if err = netlink.LinkSetNsFd(vfDev, int(initns.Fd())); err != nil { | ||
return fmt.Errorf("failed to move vf %d to init netns: %v", conf.VF, err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func cmdAdd(args *skel.CmdArgs) error { | ||
n, err := loadConf(args.StdinData) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
netns, err := ns.GetNS(args.Netns) | ||
if err != nil { | ||
return fmt.Errorf("failed to open netns %q: %v", netns, err) | ||
} | ||
defer netns.Close() | ||
|
||
if err = setupVF(n, args.IfName, netns); err != nil { | ||
return err | ||
} | ||
|
||
// run the IPAM plugin and get back the config to apply | ||
result, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) | ||
if err != nil { | ||
return err | ||
} | ||
if result.IP4 == nil { | ||
return errors.New("IPAM plugin returned missing IPv4 config") | ||
} | ||
|
||
err = netns.Do(func(_ ns.NetNS) error { | ||
return ipam.ConfigureIface(args.IfName, result) | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
result.DNS = n.DNS | ||
return result.Print() | ||
} | ||
|
||
func cmdDel(args *skel.CmdArgs) error { | ||
n, err := loadConf(args.StdinData) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
netns, err := ns.GetNS(args.Netns) | ||
if err != nil { | ||
return fmt.Errorf("failed to open netns %q: %v", netns, err) | ||
} | ||
defer netns.Close() | ||
|
||
if err = releaseVF(n, args.IfName, netns); err != nil { | ||
return err | ||
} | ||
|
||
err = ipam.ExecDel(n.IPAM.Type, args.StdinData) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func renameLink(curName, newName string) error { | ||
link, err := netlink.LinkByName(curName) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return netlink.LinkSetName(link, newName) | ||
} | ||
|
||
func main() { | ||
skel.PluginMain(cmdAdd, cmdDel) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Copyright 2016 CNI authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package main | ||
|
||
import ( | ||
. "github.com/onsi/ginkgo" | ||
. "github.com/onsi/gomega" | ||
|
||
"testing" | ||
) | ||
|
||
func TestMacvlan(t *testing.T) { | ||
RegisterFailHandler(Fail) | ||
RunSpecs(t, "sriov Suite") | ||
} |
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 seems a bit empty here! Could you add an introduction to this plugin that explains what it does and what it's used for?
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.
Introduction added:)