Skip to content

Commit d5232e6

Browse files
committed
Lock PCI allocated file
When running on heavy a load, sriov-cni might be invoked multiple times by the kubelet or the container runtime. In these situations, it might happen that a `cmdAdd` or a `cmdDel` run on the same device that is still handled by a `cmdDel` instance (e.g. when the IPAM plugin takes a long time to finish). This commit adds a Lock mechanism around the `/var/lib/cni/sriov/<PCI>.lock` file, to avoid two or more instances handling the same the device. The lock is based on `flock` [1] using the `O_CLOEXEC` flag, which garantees that the lock is released when the process finishes. [1] https://linux.die.net/man/2/flock Signed-off-by: Andrea Panattoni <[email protected]>
1 parent b811f7a commit d5232e6

File tree

4 files changed

+67
-2
lines changed

4 files changed

+67
-2
lines changed

cmd/sriov/main.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,17 @@ func cmdDel(args *skel.CmdArgs) error {
245245
return nil
246246
}
247247

248+
allocator := utils.NewPCIAllocator(config.DefaultCNIDir)
249+
250+
err = allocator.Lock(netConf.DeviceID)
251+
if err != nil {
252+
return fmt.Errorf("cmdDel() error obtaining lock for device [%s]: %w", netConf.DeviceID, err)
253+
}
254+
255+
logging.Debug("Acquired device lock",
256+
"func", "cmdDel",
257+
"DeviceID", netConf.DeviceID)
258+
248259
defer func() {
249260
if err == nil && cRefPath != "" {
250261
_ = utils.CleanCachedNetConf(cRefPath)
@@ -317,7 +328,6 @@ func cmdDel(args *skel.CmdArgs) error {
317328
"func", "cmdDel",
318329
"config.DefaultCNIDir", config.DefaultCNIDir,
319330
"netConf.DeviceID", netConf.DeviceID)
320-
allocator := utils.NewPCIAllocator(config.DefaultCNIDir)
321331
if err = allocator.DeleteAllocatedPCI(netConf.DeviceID); err != nil {
322332
return fmt.Errorf("error cleaning the pci allocation for vf pci address %s: %v", netConf.DeviceID, err)
323333
}

pkg/config/config.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ func LoadConf(bytes []byte) (*sriovtypes.NetConf, error) {
4848
return nil, fmt.Errorf("LoadConf(): VF pci addr is required")
4949
}
5050

51+
allocator := utils.NewPCIAllocator(DefaultCNIDir)
52+
err := allocator.Lock(n.DeviceID)
53+
if err != nil {
54+
return nil, err
55+
}
56+
logging.Debug("Acquired device lock",
57+
"func", "LoadConf",
58+
"DeviceID", n.DeviceID)
59+
5160
// Check if the device is already allocated.
5261
// This is to prevent issues where kubelet request to delete a pod and in the same time a new pod using the same
5362
// vf is started. we can have an issue where the cmdDel of the old pod is called AFTER the cmdAdd of the new one
@@ -56,7 +65,6 @@ func LoadConf(bytes []byte) (*sriovtypes.NetConf, error) {
5665
"func", "LoadConf",
5766
"DefaultCNIDir", DefaultCNIDir,
5867
"n.DeviceID", n.DeviceID)
59-
allocator := utils.NewPCIAllocator(DefaultCNIDir)
6068
isAllocated, err := allocator.IsAllocated(n.DeviceID)
6169
if err != nil {
6270
return n, err

pkg/config/config_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ import (
1313
)
1414

1515
var _ = Describe("Config", func() {
16+
BeforeEach(func() {
17+
DeferCleanup(func(x string) { DefaultCNIDir = x }, DefaultCNIDir)
18+
DefaultCNIDir = GinkgoT().TempDir()
19+
})
20+
1621
Context("Checking LoadConf function", func() {
1722
It("Assuming correct config file - existing DeviceID", func() {
1823
conf := []byte(`{
@@ -80,6 +85,7 @@ var _ = Describe("Config", func() {
8085
invalidProto := "802"
8186
DescribeTable("Vlan ID, QoS and Proto",
8287
func(vlanID *int, vlanQoS *int, vlanProto *string, failure bool) {
88+
8389
s := `{
8490
"name": "mynet",
8591
"type": "sriov",

pkg/utils/pci_allocator.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@ package utils
33
import (
44
"fmt"
55
"os"
6+
"path"
67
"path/filepath"
8+
"time"
79

810
"github.com/containernetworking/plugins/pkg/ns"
911
"github.com/k8snetworkplumbingwg/sriov-cni/pkg/logging"
12+
13+
"golang.org/x/sys/unix"
1014
)
1115

16+
const pciLockAcquireTimeout = 60 * time.Second
17+
1218
type PCIAllocation interface {
1319
SaveAllocatedPCI(string, string) error
1420
DeleteAllocatedPCI(string) error
@@ -25,6 +31,41 @@ func NewPCIAllocator(dataDir string) *PCIAllocator {
2531
return &PCIAllocator{dataDir: filepath.Join(dataDir, "pci")}
2632
}
2733

34+
// Lock gets an exclusive lock on the given PCI address, ensuring there is no other process configuring / or de-configuring the same device.
35+
func (p *PCIAllocator) Lock(pciAddress string) error {
36+
lockDir := path.Join(p.dataDir, "vf_lock")
37+
if err := os.MkdirAll(lockDir, 0600); err != nil {
38+
return fmt.Errorf("failed to create the sriov lock directory(%q): %v", lockDir, err)
39+
}
40+
41+
path := filepath.Join(lockDir, fmt.Sprintf("%s.lock", pciAddress))
42+
43+
// unix.O_CREAT - Create the file if it doesn't exist
44+
// unix.O_RDONLY - Open the file for read
45+
// unix.O_CLOEXEC - Automatically close the file on exit. This is useful to keep the flock until the process exits
46+
fd, err := unix.Open(path, unix.O_CREAT|unix.O_RDONLY|unix.O_CLOEXEC, 0600)
47+
if err != nil {
48+
return fmt.Errorf("failed to open PCI file [%s] for locking: %w", path, err)
49+
}
50+
51+
errCh := make(chan error)
52+
go func() {
53+
// unix.LOCK_EX - Exclusive lock
54+
errCh <- unix.Flock(fd, unix.LOCK_EX)
55+
}()
56+
57+
select {
58+
case err = <-errCh:
59+
if err != nil {
60+
return fmt.Errorf("failed to flock PCI file [%s]: %w", path, err)
61+
}
62+
return nil
63+
64+
case <-time.After(pciLockAcquireTimeout):
65+
return fmt.Errorf("time out while waiting to acquire exclusive lock on [%s]", path)
66+
}
67+
}
68+
2869
// SaveAllocatedPCI creates a file with the pci address as a name and the network namespace as the content
2970
// return error if the file was not created
3071
func (p *PCIAllocator) SaveAllocatedPCI(pciAddress, ns string) error {

0 commit comments

Comments
 (0)