Repository: ceph/go-ceph
Pull request #289 (2020-05-19 04:49:38)

cephfs: add ceph_flock() function
Added ceph_flock() function which applies or removes an advisory lock on an open file.

Fixes: https://github.com/ceph/go-ceph/issues/248

Signed-off-by: Mudit Agarwal muagarwa@redhat.com

- [ ] Added tests for features and functional changes
- [ ] Public functions and types are documented
- [ ] Standard formatting is applied to Go code


agarwal-mudit: Have raised as a draft/WIP because UT covers basic tests as of now.

ceph has thread based UTs for ceph_flock(), wanted to check that whether we need same here or not using go routines?

@phlogistonjohn 
phlogistonjohn: Sorry but I don't know what "UT" stands for and I can't figure it out from context.
agarwal-mudit: > Sorry but I don't know what "UT" stands for and I can't figure it out from context.

Sorry, my bad. UT == unit tests (tests)
agarwal-mudit: > > Sorry but I don't know what "UT" stands for and I can't figure it out from context.
> 
> Sorry, my bad. UT == unit tests (file_test.go)

Comments??
agarwal-mudit: @phlogistonjohn PTAL

Patch:
diff --git a/cephfs/file.go b/cephfs/file.go
index 60f4bfa..bd3e2f9 100644
--- a/cephfs/file.go
+++ b/cephfs/file.go
@@ -255,3 +255,41 @@ func (f *File) Fallocate(mode FallocFlags, offset, length int64) error {
 	ret := C.ceph_fallocate(f.mount.mount, f.fd, C.int(mode), C.int64_t(offset), C.int64_t(length))
 	return getError(ret)
 }
+
+// LockOp determines operations/type of locks which can be applied on a file.
+type LockOp int
+
+const (
+	// LockSH places a shared lock.
+	// More than one process may hold a shared lock for a given file at a given time.
+	LockSH = LockOp(C.LOCK_SH)
+	// LockEX places an exclusive lock.
+	// Only one process may hold an exclusive lock for a given file at a given time.
+	LockEX = LockOp(C.LOCK_EX)
+	// LockUN removes and existing lock held by this process.
+	LockUN = LockOp(C.LOCK_UN)
+	// LockNB can be ORed with any of the above to make a nonblocking call.
+	LockNB = LockOp(C.LOCK_NB)
+)
+
+// Flock applies or removes an advisory lock on an open file.
+// Param owner is the user-supplied identifier for the owner of the
+// lock, must be an arbitrary integer.
+//
+// Implements:
+//  int ceph_flock(struct ceph_mount_info *cmount, int fd, int operation, uint64_t owner);
+func (f *File) Flock(operation LockOp, owner uint64) error {
+	if err := f.validate(); err != nil {
+		return err
+	}
+
+	// validate the operation values before passing it on.
+	switch operation &^ LockNB {
+	case LockSH, LockEX, LockUN:
+	default:
+		return errInvalid
+	}
+
+	ret := C.ceph_flock(f.mount.mount, f.fd, C.int(operation), C.uint64_t(owner))
+	return getError(ret)
+}


Test patch:
diff --git a/cephfs/file_test.go b/cephfs/file_test.go
index 836d7df..d930b68 100644
--- a/cephfs/file_test.go
+++ b/cephfs/file_test.go
@@ -615,3 +615,99 @@ func TestFallocate(t *testing.T) {
 		assert.Error(t, err)
 	})
 }
+
+func TestFlock(t *testing.T) {
+	mount := fsConnect(t)
+	defer fsDisconnect(t, mount)
+
+	t.Run("validate", func(t *testing.T) {
+		f := &File{}
+		err := f.Flock(LockSH, 1010)
+		assert.Error(t, err)
+	})
+
+	t.Run("validateOperation", func(t *testing.T) {
+		fname := "Flockfile.txt"
+		f, err := mount.Open(fname, os.O_RDWR|os.O_CREATE, 0666)
+		assert.NoError(t, err)
+		assert.NotNil(t, f)
+		defer func() {
+			assert.NoError(t, f.Close())
+			assert.NoError(t, mount.Unlink(fname))
+		}()
+		err = f.Flock(LockSH|LockEX, 1010)
+		assert.Error(t, err)
+	})
+
+	t.Run("basicLocking", func(t *testing.T) {
+		const (
+			anna  = 42
+			bob   = 43
+			chris = 44
+		)
+		fname1 := "Flockfile1.txt"
+		f1, err := mount.Open(fname1, os.O_RDWR|os.O_CREATE, 0666)
+		assert.NoError(t, err)
+		assert.NotNil(t, f1)
+		defer func() {
+			assert.NoError(t, f1.Close())
+			assert.NoError(t, mount.Unlink(fname1))
+		}()
+		// Lock exclusively twice.
+		t.Run("exclusiveTwiceBlock", func(t *testing.T) {
+			err := f1.Flock(LockEX, anna)
+			assert.NoError(t, err)
+			defer func() {
+				assert.NoError(t, f1.Flock(LockUN, anna))
+			}()
+			err = f1.Flock(LockEX|LockNB, bob)
+			assert.Error(t, err)
+		})
+		t.Run("exclusiveTwiceNonBlock", func(t *testing.T) {
+			err := f1.Flock(LockEX|LockNB, anna)
+			assert.NoError(t, err)
+			defer func() {
+				assert.NoError(t, f1.Flock(LockUN, anna))
+			}()
+			err = f1.Flock(LockEX|LockNB, bob)
+			assert.Error(t, err)
+		})
+
+		// Lock shared.
+		t.Run("sharedLock", func(t *testing.T) {
+			err := f1.Flock(LockSH, anna)
+			assert.NoError(t, err)
+			err = f1.Flock(LockSH, bob)
+			assert.NoError(t, err)
+			defer func() {
+				assert.NoError(t, f1.Flock(LockUN, anna))
+				assert.NoError(t, f1.Flock(LockUN, bob))
+			}()
+			// Now try to take exclusive lock.
+			err = f1.Flock(LockEX|LockNB, chris)
+			assert.Error(t, err)
+		})
+
+		// Lock shared with upgrade to exclusive.
+		t.Run("sharedLockUpExclusive", func(t *testing.T) {
+			err := f1.Flock(LockSH, bob)
+			assert.NoError(t, err)
+			defer func() {
+				assert.NoError(t, f1.Flock(LockUN, bob))
+			}()
+			err = f1.Flock(LockEX, bob)
+			assert.NoError(t, err)
+		})
+
+		// Lock exclusive with downgrade to shared.
+		t.Run("exclusiveLockDownShared", func(t *testing.T) {
+			err := f1.Flock(LockEX, bob)
+			assert.NoError(t, err)
+			defer func() {
+				assert.NoError(t, f1.Flock(LockUN, bob))
+			}()
+			err = f1.Flock(LockSH, bob)
+			assert.NoError(t, err)
+		})
+	})
+}