Repository: ceph/go-ceph
Pull request #402 (2020-11-09 11:01:22)

Add rbd_writesame()
In order to provide the ability to do thick provisioning of RBD images,
Image.WriteSame() can be used. The `rbd` commandline utility calls the
rbd_writesame() function when an image is created with the
`--thick-provision` option.

See-also: ceph/ceph-csi#1675

## Checklist
- [x] Added tests for features and functional changes
- [x] Public functions and types are documented
- [x] Standard formatting is applied to Go code


agarwal-mudit: @nixpanic What data one should pass so that it works like the cli with --thick-image option?
nixpanic: > @nixpanic What data one should pass so that it works like the cli with --thick-image option?
> Would it be good to write a test case explaining the same (with proper offset and data) just for the users of the API?

Maybe, not sure. The `rbd` commandline just fills the image with `0`s when `--thick-provision` is passed.

Patch:
diff --git a/rados/rados.go b/rados/rados.go
index d1f000a..b18fe08 100644
--- a/rados/rados.go
+++ b/rados/rados.go
@@ -25,6 +25,33 @@ const (
 	RadosAllNamespaces = AllNamespaces
 )
 
+// OpFlags are flags that can be set on a per-op basis.
+type OpFlags uint
+
+const (
+	// OpFlagNone can be use to not set any flags.
+	OpFlagNone = OpFlags(0)
+	// OpFlagExcl marks an op to fail a create operation if the object
+	// already exists.
+	OpFlagExcl = OpFlags(C.LIBRADOS_OP_FLAG_EXCL)
+	// OpFlagFailOk allows the transaction to succeed even if the flagged
+	// op fails.
+	OpFlagFailOk = OpFlags(C.LIBRADOS_OP_FLAG_FAILOK)
+	// OpFlagFAdviseRandom indicates read/write op random.
+	OpFlagFAdviseRandom = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_RANDOM)
+	// OpFlagFAdviseSequential indicates read/write op sequential.
+	OpFlagFAdviseSequential = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL)
+	// OpFlagFAdviseWillNeed indicates read/write data will be accessed in
+	// the near future (by someone).
+	OpFlagFAdviseWillNeed = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_WILLNEED)
+	// OpFlagFAdviseDontNeed indicates read/write data will not accessed in
+	// the near future (by anyone).
+	OpFlagFAdviseDontNeed = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_DONTNEED)
+	// OpFlagFAdviseNoCache indicates read/write data will not accessed
+	// again (by *this* client).
+	OpFlagFAdviseNoCache = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_NOCACHE)
+)
+
 // Version returns the major, minor, and patch components of the version of
 // the RADOS library linked against.
 func Version() (int, int, int) {
diff --git a/rados/rados_nautilus.go b/rados/rados_nautilus.go
new file mode 100644
index 0000000..e12e62a
--- /dev/null
+++ b/rados/rados_nautilus.go
@@ -0,0 +1,12 @@
+// +build !mimic
+
+package rados
+
+// #include <rados/librados.h>
+import "C"
+
+const (
+	// OpFlagFAdviseFUA optionally support FUA (force unit access) on write
+	// requests.
+	OpFlagFAdviseFUA = OpFlags(C.LIBRADOS_OP_FLAG_FADVISE_FUA)
+)
diff --git a/rbd/rbd.go b/rbd/rbd.go
index 7e9ae23..7eb4bb8 100644
--- a/rbd/rbd.go
+++ b/rbd/rbd.go
@@ -808,6 +808,36 @@ func (image *Image) WriteAt(data []byte, off int64) (n int, err error) {
 	return ret, err
 }
 
+// WriteSame repeats writing data from starting point ofs until n bytes have
+// been written.
+//
+// Implements:
+//  ssize_t rbd_writesame(rbd_image_t image, uint64_t ofs, size_t len,
+//                        const char *buf, size_t data_len, int op_flags);
+func (image *Image) WriteSame(ofs, n uint64, data []byte, flags rados.OpFlags) (int64, error) {
+	var err error
+
+	if err = image.validate(imageIsOpen); err != nil {
+		return 0, err
+	}
+
+	if len(data) == 0 {
+		return 0, nil
+	}
+
+	ret := C.rbd_writesame(image.image,
+		C.uint64_t(ofs),
+		C.uint64_t(n),
+		(*C.char)(unsafe.Pointer(&data[0])),
+		C.size_t(len(data)),
+		C.int(flags))
+	if ret < 0 {
+		err = getError(C.int(ret))
+	}
+
+	return int64(ret), err
+}
+
 // Flush all cached writes to storage.
 //
 // Implements:


Test patch:
diff --git a/rbd/rbd_test.go b/rbd/rbd_test.go
index 6ca0906..b06a520 100644
--- a/rbd/rbd_test.go
+++ b/rbd/rbd_test.go
@@ -521,6 +521,86 @@ func TestImageDiscard(t *testing.T) {
 	conn.Shutdown()
 }
 
+func TestWriteSame(t *testing.T) {
+	conn := radosConnect(t)
+
+	poolname := GetUUID()
+	err := conn.MakePool(poolname)
+	assert.NoError(t, err)
+
+	ioctx, err := conn.OpenIOContext(poolname)
+	require.NoError(t, err)
+
+	name := GetUUID()
+	options := NewRbdImageOptions()
+	defer options.Destroy()
+	err = options.SetUint64(ImageOptionOrder, uint64(testImageOrder))
+	assert.NoError(t, err)
+	err = CreateImage(ioctx, name, testImageSize, options)
+	require.NoError(t, err)
+
+	img, err := OpenImage(ioctx, name, NoSnapshot)
+	assert.NoError(t, err)
+
+	data_out := []byte("this is a string of 28 bytes")
+
+	t.Run("writeAndRead", func(t *testing.T) {
+		// write some bytes at the start of the image
+		n_out, err := img.WriteSame(0, uint64(4*len(data_out)), data_out, rados.OpFlagNone)
+		assert.Equal(t, int64(4*len(data_out)), n_out)
+		assert.NoError(t, err)
+
+		// the same string should be read from byte 0
+		data_in := make([]byte, len(data_out))
+		n_in, err := img.ReadAt(data_in, 0)
+		assert.Equal(t, len(data_out), n_in)
+		assert.Equal(t, data_out, data_in)
+		assert.NoError(t, err)
+
+		// the same string should be read from byte 28
+		data_in = make([]byte, len(data_out))
+		n_in, err = img.ReadAt(data_in, 28)
+		assert.Equal(t, len(data_out), n_in)
+		assert.Equal(t, data_out, data_in)
+		assert.NoError(t, err)
+	})
+
+	t.Run("writePartialData", func(t *testing.T) {
+		// writing a non-multiple of data_out len will fail
+		_, err = img.WriteSame(0, 64, data_out, rados.OpFlagNone)
+		assert.Error(t, err)
+	})
+
+	t.Run("writeNoData", func(t *testing.T) {
+		// writing empty data should succeed
+		n_in, err := img.WriteSame(0, 64, []byte(""), rados.OpFlagNone)
+		assert.NoError(t, err)
+		assert.Equal(t, int64(0), n_in)
+	})
+
+	err = img.Close()
+	assert.NoError(t, err)
+
+	t.Run("writeSameReadOnly", func(t *testing.T) {
+		// writing to a read-only image should fail
+		img, err = OpenImageReadOnly(ioctx, name, NoSnapshot)
+		assert.NoError(t, err)
+
+		_, err = img.WriteSame(96, 32, data_out, rados.OpFlagNone)
+		assert.Error(t, err)
+
+		err = img.Close()
+		assert.NoError(t, err)
+	})
+
+	err = img.Remove()
+	assert.NoError(t, err)
+
+	ioctx.Destroy()
+	conn.DeletePool(poolname)
+	conn.Shutdown()
+}
+
 func TestIOReaderWriter(t *testing.T) {
 	conn := radosConnect(t)
 
@@ -1016,6 +1096,9 @@ func TestErrorImageNotOpen(t *testing.T) {
 	_, err = image.WriteAt(nil, 0)
 	assert.Equal(t, err, ErrImageNotOpen)
 
+	_, err = image.WriteSame(64, 128, []byte("not opened"), rados.OpFlagNone)
+	assert.Equal(t, err, ErrImageNotOpen)
+
 	err = image.Flush()
 	assert.Equal(t, err, ErrImageNotOpen)
 }