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

Add SR-IOV CNI plugin #259

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions Godeps/Godeps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

69 changes: 69 additions & 0 deletions plugins/main/sriov/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# SR-IOV CNI plugin

Copy link
Member

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?

Copy link
Author

Choose a reason for hiding this comment

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

Introduction added:)

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
Copy link
Member

@steveej steveej Jul 11, 2016

Choose a reason for hiding this comment

The 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?

Copy link
Author

Choose a reason for hiding this comment

The 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)
```
235 changes: 235 additions & 0 deletions plugins/main/sriov/sriov.go
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)
}
27 changes: 27 additions & 0 deletions plugins/main/sriov/sriov_suite_test.go
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")
}
Loading