Skip to content

Commit ecc40df

Browse files
authored
Merge pull request #35 from adrianchiris/extend-sriovnet
Extend sriovnet with Functionality for Smart-NICs
2 parents 3129ae6 + e00d3f0 commit ecc40df

File tree

10 files changed

+306
-22
lines changed

10 files changed

+306
-22
lines changed

go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ go 1.13
44

55
require (
66
github.com/google/uuid v1.1.1
7-
github.com/spf13/afero v1.2.2
7+
github.com/spf13/afero v1.4.1
88
github.com/stretchr/testify v1.5.1
99
github.com/vishvananda/netlink v1.1.0
10-
golang.org/x/text v0.3.3 // indirect
1110
)

go.sum

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,26 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
22
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
33
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
44
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
5+
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
6+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
7+
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
58
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
69
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7-
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
8-
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
10+
github.com/spf13/afero v1.4.1 h1:asw9sl74539yqavKaglDM5hFpdJVK0Y5Dr/JOgQ89nQ=
11+
github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
912
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
13+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
1014
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
1115
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
1216
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
1317
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
1418
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
1519
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
20+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
21+
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
22+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
23+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
24+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1625
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444 h1:/d2cWp6PSamH4jDPFLyO150psQdqvtoNX8Zjg3AQ31g=
1726
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1827
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=

pkg/utils/filesystem/defaultfs.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ func (DefaultFs) Remove(name string) error {
4949
return os.Remove(name)
5050
}
5151

52+
// Readlink via os.Readlink
53+
func (DefaultFs) Readlink(name string) (string, error) {
54+
return os.Readlink(name)
55+
}
56+
57+
// Symlink via os.Symlink
58+
func (DefaultFs) Symlink(oldname, newname string) error {
59+
return os.Symlink(oldname, newname)
60+
}
61+
5262
// ReadFile via ioutil.ReadFile
5363
func (DefaultFs) ReadFile(filename string) ([]byte, error) {
5464
return ioutil.ReadFile(filename)

pkg/utils/filesystem/fakefs.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
//nolint:gomnd
12
package filesystem
23

34
import (
5+
"fmt"
46
"os"
57
"path/filepath"
68
"time"
@@ -13,9 +15,31 @@ type fakeFs struct {
1315
a afero.Afero
1416
}
1517

16-
// NewFakeFs returns a fake Filesystem that exists in-memory, useful for unit tests
17-
func NewFakeFs() Filesystem {
18-
return &fakeFs{a: afero.Afero{Fs: afero.NewMemMapFs()}}
18+
// NewFakeFs returns a fake Filesystem that exists at fakeFsRoot as its base path, useful for unit tests.
19+
// Returns: Filesystem interface, teardown method (cleanup of provided root path) and error.
20+
// teardown method should be called at the end of each test to ensure environment is left clean.
21+
func NewFakeFs(fakeFsRoot string) (Filesystem, func(), error) {
22+
_, err := os.Stat(fakeFsRoot)
23+
// if fakeFsRoot dir exists remove it.
24+
if err == nil {
25+
err = os.RemoveAll(fakeFsRoot)
26+
if err != nil {
27+
return nil, nil, fmt.Errorf("failed to cleanup fake root dir %s. %s", fakeFsRoot, err)
28+
}
29+
} else if !os.IsNotExist(err) {
30+
return nil, nil, fmt.Errorf("failed to lstat fake root dir %s. %s", fakeFsRoot, err)
31+
}
32+
33+
// create fakeFsRoot dir
34+
if err = os.MkdirAll(fakeFsRoot, os.FileMode(0755)); err != nil {
35+
return nil, nil, fmt.Errorf("failed to create fake root dir: %s. %s", fakeFsRoot, err)
36+
}
37+
38+
return &fakeFs{a: afero.Afero{Fs: afero.NewBasePathFs(afero.NewOsFs(), fakeFsRoot)}},
39+
func() {
40+
os.RemoveAll(fakeFsRoot)
41+
},
42+
nil
1943
}
2044

2145
// Stat via afero.Fs.Stat
@@ -86,6 +110,16 @@ func (fs *fakeFs) Remove(name string) error {
86110
return fs.a.Remove(name)
87111
}
88112

113+
// Readlink via afero.ReadlinkIfPossible
114+
func (fs *fakeFs) Readlink(name string) (string, error) {
115+
return fs.a.Fs.(afero.Symlinker).ReadlinkIfPossible(name)
116+
}
117+
118+
// Symlink via afero.FS.(Symlinker).SymlinkIfPossible
119+
func (fs *fakeFs) Symlink(oldname, newname string) error {
120+
return fs.a.Fs.(afero.Symlinker).SymlinkIfPossible(oldname, newname)
121+
}
122+
89123
// fakeFile implements File; for use with fakeFs
90124
type fakeFile struct {
91125
file afero.File

pkg/utils/filesystem/filesystem.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ type Filesystem interface {
1818
Chtimes(name string, atime time.Time, mtime time.Time) error
1919
RemoveAll(path string) error
2020
Remove(name string) error
21+
Readlink(name string) (string, error)
22+
Symlink(oldname, newname string) error
2123

2224
// from "io/ioutil"
2325
ReadFile(filename string) ([]byte, error)

sriovnet.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"log"
66
"net"
77
"os"
8+
"path"
89
"path/filepath"
910
"regexp"
1011
"strconv"
@@ -437,3 +438,18 @@ func GetNetDevicesFromPci(pciAddress string) ([]string, error) {
437438
}
438439
return netDevices, nil
439440
}
441+
442+
// GetPfPciFromVfPci retrieves the parent PF PCI address of the provided VF PCI address in D:B:D.f format
443+
func GetPfPciFromVfPci(vfPciAddress string) (string, error) {
444+
pfPath := filepath.Join(PciSysDir, vfPciAddress, "physfn")
445+
pciDevDir, err := utilfs.Fs.Readlink(pfPath)
446+
if err != nil {
447+
return "", fmt.Errorf("failed to read physfn link, provided address may not be a VF. %v", err)
448+
}
449+
450+
pf := path.Base(pciDevDir)
451+
if pf == "" {
452+
return pf, fmt.Errorf("could not find PF PCI Address")
453+
}
454+
return pf, err
455+
}

sriovnet_integration_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
// +build integration
22

3+
/*
4+
NOTE:
5+
====
6+
This test although not usable as is, since netdev names and configuration differs from one setup to the next
7+
is useful for testing your changes on a real setup.
8+
9+
for new functionality in sriovnet package add a new integration test case.
10+
to run an existing test modify the netdev/VF in the set to fit your setup and execute the test.
11+
12+
Build and run integration test:
13+
==============================
14+
# go test --tags integration -v -run <TestName>
15+
*/
16+
317
package sriovnet
418

519
import (
@@ -201,3 +215,25 @@ func TestGetVfNetdevName(t *testing.T) {
201215
}
202216
}
203217
}
218+
219+
func TestIntegrationGetPfPciFromVfPci(t *testing.T) {
220+
vf := "0000:05:00.6"
221+
pf, err := GetPfPciFromVfPci(vf)
222+
if err != nil {
223+
t.Log("GetPfPciFromVfPci", "VF PCI: ", vf, "Error: ", err)
224+
t.Fatal()
225+
}
226+
t.Log("VF: ", vf, "PF: ", pf)
227+
}
228+
229+
func TestIntegrationGetVfRepresentorSmartNIC(t *testing.T) {
230+
pfID := "0"
231+
vfIdx := "2"
232+
t.Log("GetVfRepresentorSmartNIC ", "PF ID: ", pfID, "VF Index: ", vfIdx)
233+
rep, err := GetVfRepresentorSmartNIC(pfID, vfIdx)
234+
if err != nil {
235+
t.Log("GetVfRepresentorSmartNIC ", "Error: ", err)
236+
t.Fatal()
237+
}
238+
t.Log("VF Representor: ", rep)
239+
}

sriovnet_switchdev.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,47 @@ func getNetDevPhysPortName(netDev string) (string, error) {
129129
}
130130
return strings.TrimSpace(string(physPortName)), nil
131131
}
132+
133+
// GetVfRepresentorSmartNIC returns VF representor on Smart-NIC for a host VF identified by pfID and vfIndex
134+
func GetVfRepresentorSmartNIC(pfID, vfIndex string) (string, error) {
135+
// TODO(Adrianc): This method should change to get switchID and vfIndex as input, then common logic can
136+
// be shared with GetVfRepresentor, backward compatibility should be preserved when this happens.
137+
138+
// pfID should be 0 or 1
139+
if pfID != "0" && pfID != "1" {
140+
return "", fmt.Errorf("unexpected pfID(%s). It should be 0 or 1", pfID)
141+
}
142+
143+
// vfIndex should be an unsinged integer provided as a decimal number
144+
if _, err := strconv.ParseUint(vfIndex, 10, 32); err != nil {
145+
return "", fmt.Errorf("unexpected vfIndex(%s). It should be an unsigned decimal number", vfIndex)
146+
}
147+
148+
netdevs, err := utilfs.Fs.ReadDir(NetSysDir)
149+
if err != nil {
150+
return "", err
151+
}
152+
153+
// map for easy search of expected VF rep port name.
154+
// Note: no supoport for Multi-Chassis Smart-NICs
155+
expectedPhysPortNames := map[string]interface{}{
156+
fmt.Sprintf("pf%svf%s", pfID, vfIndex): nil,
157+
fmt.Sprintf("c0pf%svf%s", pfID, vfIndex): nil,
158+
}
159+
160+
// iterate all net devs and get phys port name
161+
// if phys port name == pf<pfIndex>vf<vfIndex> or c0pf<pfIndex>vf<vfIndex> we have a match
162+
for _, netdev := range netdevs {
163+
// find matching VF representor
164+
netdevName := netdev.Name()
165+
portName, err := getNetDevPhysPortName(netdevName)
166+
if err != nil {
167+
// skip
168+
continue
169+
}
170+
if _, ok := expectedPhysPortNames[portName]; ok {
171+
return netdevName, nil
172+
}
173+
}
174+
return "", fmt.Errorf("vf representor for pfID:%s, vfIndex: %s not found", pfID, vfIndex)
175+
}

sriovnet_switchdev_test.go

Lines changed: 89 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@ type repContext struct {
1818
}
1919

2020
func setUpRepresentorLayout(vfPciAddress string, rep *repContext) error {
21-
path := filepath.Join(PciSysDir, vfPciAddress, "physfn/net", rep.Name)
22-
err := utilfs.Fs.MkdirAll(path, os.FileMode(0755))
23-
if err != nil {
24-
return err
21+
if vfPciAddress != "" {
22+
path := filepath.Join(PciSysDir, vfPciAddress, "physfn/net", rep.Name)
23+
err := utilfs.Fs.MkdirAll(path, os.FileMode(0755))
24+
if err != nil {
25+
return err
26+
}
2527
}
2628

27-
path = filepath.Join(NetSysDir, rep.Name)
28-
err = utilfs.Fs.MkdirAll(path, os.FileMode(0755))
29+
path := filepath.Join(NetSysDir, rep.Name)
30+
err := utilfs.Fs.MkdirAll(path, os.FileMode(0755))
2931
if err != nil {
3032
return err
3133
}
@@ -54,19 +56,38 @@ func setUpRepresentorLayout(vfPciAddress string, rep *repContext) error {
5456
//nolint:unparam
5557
func setupUplinkRepresentorEnv(t *testing.T, uplink *repContext, vfPciAddress string, vfReps []*repContext) func() {
5658
var err error
57-
utilfs.Fs = utilfs.NewFakeFs()
59+
teardown := setupRepresentorEnv(t, vfPciAddress, vfReps)
5860
defer func() {
5961
if err != nil {
62+
teardown()
6063
t.Errorf("setupUplinkRepresentorEnv, got %v", err)
6164
}
6265
}()
63-
66+
// Setup uplink
6467
err = setUpRepresentorLayout(vfPciAddress, uplink)
68+
if err != nil {
69+
return nil
70+
}
71+
72+
return teardown
73+
}
74+
75+
func setupRepresentorEnv(t *testing.T, vfPciAddress string, vfReps []*repContext) func() {
76+
var err error
77+
teardown := setupFakeFs(t)
78+
79+
defer func() {
80+
if err != nil {
81+
teardown()
82+
t.Errorf("setupRepresentorEnv, got %v", err)
83+
}
84+
}()
85+
6586
for _, rep := range vfReps {
6687
err = setUpRepresentorLayout(vfPciAddress, rep)
6788
}
6889

69-
return func() { utilfs.Fs.RemoveAll("/") } //nolint:errcheck
90+
return teardown
7091
}
7192

7293
func TestGetUplinkRepresentorWithPhysPortNameSuccess(t *testing.T) {
@@ -153,3 +174,62 @@ func TestGetUplinkRepresentorErrorMissingUplink(t *testing.T) {
153174
assert.Equal(t, "", uplinkNetdev)
154175
assert.Contains(t, err.Error(), expectedError)
155176
}
177+
178+
func TestGetVfRepresentorSmartNIC(t *testing.T) {
179+
vfReps := []*repContext{
180+
{
181+
Name: "eth0",
182+
PhysPortName: "pf0vf0",
183+
PhysSwitchID: "c2cfc60003a1420c",
184+
},
185+
{
186+
Name: "eth1",
187+
PhysPortName: "pf0vf1",
188+
PhysSwitchID: "c2cfc60003a1420c",
189+
},
190+
{
191+
Name: "eth2",
192+
PhysPortName: "pf0vf2",
193+
PhysSwitchID: "c2cfc60003a1420c",
194+
},
195+
}
196+
teardown := setupRepresentorEnv(t, "", vfReps)
197+
defer teardown()
198+
199+
vfRep, err := GetVfRepresentorSmartNIC("0", "2")
200+
assert.NoError(t, err)
201+
assert.Equal(t, "eth2", vfRep)
202+
}
203+
204+
func TestGetVfRepresentorSmartNICNoRep(t *testing.T) {
205+
vfReps := []*repContext{
206+
{
207+
Name: "eth0",
208+
PhysPortName: "pf0vf0",
209+
PhysSwitchID: "c2cfc60003a1420c",
210+
},
211+
{
212+
Name: "eth1",
213+
PhysPortName: "pf0vf1",
214+
PhysSwitchID: "c2cfc60003a1420c",
215+
},
216+
}
217+
teardown := setupRepresentorEnv(t, "", vfReps)
218+
defer teardown()
219+
220+
vfRep, err := GetVfRepresentorSmartNIC("1", "2")
221+
assert.Error(t, err)
222+
assert.Equal(t, "", vfRep)
223+
}
224+
225+
func TestGetVfRepresentorSmartNICInvalidPfID(t *testing.T) {
226+
vfRep, err := GetVfRepresentorSmartNIC("invalid", "2")
227+
assert.Error(t, err)
228+
assert.Equal(t, "", vfRep)
229+
}
230+
231+
func TestGetVfRepresentorSmartNICInvalidVfIndex(t *testing.T) {
232+
vfRep, err := GetVfRepresentorSmartNIC("1", "invalid")
233+
assert.Error(t, err)
234+
assert.Equal(t, "", vfRep)
235+
}

0 commit comments

Comments
 (0)