Skip to content

Commit 5882752

Browse files
tamilmani1989rbtr
andauthored
Reduce lock timeout and check pid same before cleaning up (#1035)
* lock fix changes * added test files * removing unused file * lint fixes * Update cni/plugin.go Co-authored-by: Evan Baker <[email protected]> * addressed comments * nit fix Co-authored-by: Evan Baker <[email protected]>
1 parent 7fc8768 commit 5882752

File tree

6 files changed

+160
-13
lines changed

6 files changed

+160
-13
lines changed

cni/plugin.go

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@ import (
2020
cniTypes "github.com/containernetworking/cni/pkg/types"
2121
cniTypesCurr "github.com/containernetworking/cni/pkg/types/current"
2222
cniVers "github.com/containernetworking/cni/pkg/version"
23+
"github.com/pkg/errors"
2324
)
2425

26+
var errEmptyContent = errors.New("read content is zero bytes")
27+
2528
// Plugin is the parent class for CNI plugins.
2629
type Plugin struct {
2730
*common.Plugin
@@ -197,26 +200,33 @@ func (plugin *Plugin) IsSafeToRemoveLock(processName string) (bool, error) {
197200
return false, cmdErr
198201
}
199202

200-
// Read pid from lockfile
201203
lockFileName := plugin.Store.GetLockFileName()
202-
content, err := ioutil.ReadFile(lockFileName)
204+
// Read pid from lockfile
205+
lockFilePid, err := plugin.readLockFile(lockFileName)
203206
if err != nil {
204-
log.Errorf("Failed to read lock file :%v, ", err)
205-
return false, err
206-
}
207-
208-
if len(content) <= 0 {
209-
log.Errorf("Num bytes read from lock file is 0")
210-
return false, fmt.Errorf("Num bytes read from lock file is 0")
207+
return false, errors.Wrap(err, "IsSafeToRemoveLock lockfile read failed")
211208
}
212209

213-
log.Printf("Read from Lock file:%s", content)
210+
log.Printf("Read from lockfile:%s", lockFilePid)
214211
// Get the process name if running and
215212
// check if that matches with our expected process
216-
pName, err := platform.GetProcessNameByID(string(content))
213+
// if it returns non-nil error then process is not running
214+
pName, err := platform.GetProcessNameByID(lockFilePid)
217215
if err != nil {
216+
var content string
217+
content, err = plugin.readLockFile(lockFileName)
218+
if err != nil {
219+
return false, errors.Wrap(err, "IsSafeToRemoveLock lockfile 2nd read failed")
220+
}
221+
222+
// pid in lockfile changed after getprocessnamebyid call. so some other process acquired lockfile in between.
223+
// so its not safe to remove lockfile
224+
if string(content) != lockFilePid {
225+
log.Printf("Lockfile content changed from %s to %s. So not safe to remove lockfile", lockFilePid, content)
226+
return false, nil
227+
}
228+
218229
return true, nil
219-
// if process id changed. read lockfile?
220230
}
221231

222232
log.Printf("[CNI] Process name is %s", pName)
@@ -229,3 +239,18 @@ func (plugin *Plugin) IsSafeToRemoveLock(processName string) (bool, error) {
229239
log.Errorf("Plugin store is nil")
230240
return false, fmt.Errorf("plugin store nil")
231241
}
242+
243+
func (plugin *Plugin) readLockFile(filename string) (string, error) {
244+
content, err := ioutil.ReadFile(filename)
245+
if err != nil {
246+
log.Errorf("Failed to read lockfile :%v", err)
247+
return "", fmt.Errorf("readLockFile error:%w", err)
248+
}
249+
250+
if len(content) == 0 {
251+
log.Errorf("Num bytes read from lockfile is 0")
252+
return "", errEmptyContent
253+
}
254+
255+
return string(content), nil
256+
}

cni/plugin_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package cni
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/Azure/azure-container-networking/common"
8+
"github.com/Azure/azure-container-networking/store"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestMain(m *testing.M) {
13+
// Run tests.
14+
exitCode := m.Run()
15+
os.Exit(exitCode)
16+
}
17+
18+
func TestPluginSafeToRemoveLock(t *testing.T) {
19+
tests := []struct {
20+
name string
21+
plugin Plugin
22+
processName string
23+
wantIsSafe bool
24+
wantErr bool
25+
}{
26+
{
27+
name: "Safe to remove lock-true. Process name does not match",
28+
plugin: Plugin{
29+
Plugin: &common.Plugin{
30+
Name: "cni",
31+
Version: "0.3.0",
32+
Store: store.NewMockStore("testfiles/processfound.lock"),
33+
},
34+
version: "0.3.0",
35+
},
36+
processName: "azure-vnet",
37+
wantIsSafe: true,
38+
wantErr: false,
39+
},
40+
{
41+
name: "Safe to remove lock-true. Process not running",
42+
plugin: Plugin{
43+
Plugin: &common.Plugin{
44+
Name: "cni",
45+
Version: "0.3.0",
46+
Store: store.NewMockStore("testfiles/processnotfound.lock"),
47+
},
48+
version: "0.3.0",
49+
},
50+
processName: "azure-vnet",
51+
wantIsSafe: true,
52+
wantErr: false,
53+
},
54+
}
55+
56+
for _, tt := range tests {
57+
tt := tt
58+
t.Run(tt.name, func(t *testing.T) {
59+
isSafe, err := tt.plugin.IsSafeToRemoveLock(tt.processName)
60+
if tt.wantErr {
61+
require.Error(t, err)
62+
require.Equal(t, tt.wantIsSafe, isSafe)
63+
} else {
64+
require.NoError(t, err)
65+
require.Equal(t, tt.wantIsSafe, isSafe)
66+
}
67+
})
68+
}
69+
}

cni/testfiles/processfound.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1

cni/testfiles/processnotfound.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-3

store/json.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const (
2525
lockExtension = ".lock"
2626

2727
// Maximum number of retries before failing a lock call.
28-
lockMaxRetries = 200
28+
lockMaxRetries = 100
2929

3030
// Delay between lock retries.
3131
lockRetryDelay = 100 * time.Millisecond

store/mockstore.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package store
2+
3+
import (
4+
"time"
5+
)
6+
7+
type mockStore struct {
8+
lockFilePath string
9+
}
10+
11+
// NewMockStore creates a new jsonFileStore object, accessed as a KeyValueStore.
12+
func NewMockStore(lockFilePath string) KeyValueStore {
13+
return &mockStore{
14+
lockFilePath: lockFilePath,
15+
}
16+
}
17+
18+
// Read restores the value for the given key from persistent store.
19+
func (ms *mockStore) Read(key string, value interface{}) error {
20+
return nil
21+
}
22+
23+
func (ms *mockStore) Write(key string, value interface{}) error {
24+
return nil
25+
}
26+
27+
func (ms *mockStore) Flush() error {
28+
return nil
29+
}
30+
31+
func (ms *mockStore) Lock(block bool) error {
32+
return nil
33+
}
34+
35+
func (ms *mockStore) Unlock(forceUnlock bool) error {
36+
return nil
37+
}
38+
39+
func (ms *mockStore) GetModificationTime() (time.Time, error) {
40+
return time.Time{}, nil
41+
}
42+
43+
func (ms *mockStore) GetLockFileModificationTime() (time.Time, error) {
44+
return time.Time{}, nil
45+
}
46+
47+
func (ms *mockStore) GetLockFileName() string {
48+
return ms.lockFilePath
49+
}
50+
51+
func (ms *mockStore) Remove() {}

0 commit comments

Comments
 (0)