Repository: ceph/go-ceph
Pull request #365 (2020-09-02 15:49:17)

rbd: add snapshot ID related functions
Added wrappers for 
1. rbd_snap_get_name()
2. rbd_snap_get_id()
3. rbd_snap_get_timestamp()
4. rbd_snap_set_by_id()

Signed-off-by: Mudit Agarwal <muagarwa@redhat.com>


Fixes: #354 

## Checklist
- [ ] Added tests for features and functional changes
- [ ] Public functions and types are documented
- [ ] Standard formatting is applied to Go code


agarwal-mudit: > A couple naming issues and the extension of the Snapshot type. The former should be straightforward but if you have questions about the latter and how we can add these features and still move away from the type just ask and I'll try to express myself in more detail. Thanks!

Sure, no problem. I can base the functions on Image instead of Snapshot. There shouldn't be any problem except we need to get the Snapshot object (in test cases) to call the existing functions which are tied to the Snapshot object. But that's no big deal.

I agree, lets move away from this and slowly deprecate the existing ones.
agarwal-mudit: > LGTM thanks.
> Sorry for the long delay, I had not realized you had pushed updates to this pr.

No problem, I was also on PTO last week. Thanks for the review.
phlogistonjohn: @agarwal-mudit PR now has conflicts. Can you please resolve? Thanks!
agarwal-mudit: > @agarwal-mudit PR now has conflicts. Can you please resolve? Thanks!

Rebased

Patch:
diff --git a/rbd/snapshot.go b/rbd/snapshot.go
index 1f020f7..9f10c63 100644
--- a/rbd/snapshot.go
+++ b/rbd/snapshot.go
@@ -7,6 +7,8 @@ import "C"
 
 import (
 	"unsafe"
+
+	ts "github.com/ceph/go-ceph/internal/timespec"
 )
 
 // Snapshot represents a snapshot on a particular rbd image.
@@ -160,3 +162,25 @@ func (snapshot *Snapshot) Set() error {
 
 	return getError(C.rbd_snap_set(snapshot.image.image, c_snapname))
 }
+
+// GetSnapTimestamp returns the timestamp of a snapshot for an image.
+// For a non-existing snap ID, GetSnapTimestamp() may trigger an assertion
+// and crash in the ceph library.
+// Check https://tracker.ceph.com/issues/47287 for details.
+//
+// Implements:
+//  int rbd_snap_get_timestamp(rbd_image_t image, uint64_t snap_id, struct timespec *timestamp)
+func (image *Image) GetSnapTimestamp(snapID uint64) (Timespec, error) {
+	if err := image.validate(imageIsOpen); err != nil {
+		return Timespec{}, err
+	}
+
+	var cts C.struct_timespec
+
+	ret := C.rbd_snap_get_timestamp(image.image, C.uint64_t(snapID), &cts)
+	if ret < 0 {
+		return Timespec{}, getError(ret)
+	}
+
+	return Timespec(ts.CStructToTimespec(ts.CTimespecPtr(&cts))), nil
+}
diff --git a/rbd/snapshot_nautilus.go b/rbd/snapshot_nautilus.go
index 3245457..7bccc6b 100644
--- a/rbd/snapshot_nautilus.go
+++ b/rbd/snapshot_nautilus.go
@@ -158,3 +158,17 @@ func (image *Image) ListChildren() (pools []string, images []string, err error)
 	}
 	return pools, images, nil
 }
+
+// SetSnapByID updates the rbd image (not the Snapshot) such that the snapshot
+// is the source of readable data.
+//
+// Implements:
+//  int rbd_snap_set_by_id(rbd_image_t image, uint64_t snap_id);
+func (image *Image) SetSnapByID(snapID uint64) error {
+	if err := image.validate(imageIsOpen); err != nil {
+		return err
+	}
+
+	ret := C.rbd_snap_set_by_id(image.image, C.uint64_t(snapID))
+	return getError(ret)
+}
diff --git a/rbd/snapshot_octopus.go b/rbd/snapshot_octopus.go
new file mode 100644
index 0000000..765eb92
--- /dev/null
+++ b/rbd/snapshot_octopus.go
@@ -0,0 +1,66 @@
+// +build octopus
+
+package rbd
+
+// #cgo LDFLAGS: -lrbd
+// #include <stdlib.h>
+// #include <rbd/librbd.h>
+import "C"
+
+import (
+	"unsafe"
+
+	"github.com/ceph/go-ceph/internal/retry"
+)
+
+// GetSnapID returns the snapshot ID for the given snapshot name.
+//
+// Implements:
+//  int rbd_snap_get_id(rbd_image_t image, const char *snapname, uint64_t *snap_id)
+func (image *Image) GetSnapID(snapName string) (uint64, error) {
+	var snapID C.uint64_t
+	if err := image.validate(imageIsOpen); err != nil {
+		return uint64(snapID), err
+	}
+	if snapName == "" {
+		return uint64(snapID), ErrSnapshotNoName
+	}
+
+	cSnapName := C.CString(snapName)
+	defer C.free(unsafe.Pointer(cSnapName))
+
+	ret := C.rbd_snap_get_id(image.image, cSnapName, &snapID)
+	return uint64(snapID), getError(ret)
+}
+
+// GetSnapByID returns the snapshot name for the given snapshot ID.
+//
+// Implements:
+//  int rbd_snap_get_name(rbd_image_t image, uint64_t snap_id, char *snapname, size_t *name_len)
+func (image *Image) GetSnapByID(snapID uint64) (string, error) {
+	if err := image.validate(imageIsOpen); err != nil {
+		return "", err
+	}
+
+	var (
+		buf []byte
+		err error
+	)
+	// range from 1k to 64KiB
+	retry.WithSizes(1024, 1<<16, func(len int) retry.Hint {
+		cLen := C.size_t(len)
+		buf = make([]byte, cLen)
+		ret := C.rbd_snap_get_name(
+			image.image,
+			(C.uint64_t)(snapID),
+			(*C.char)(unsafe.Pointer(&buf[0])),
+			&cLen)
+		err = getError(ret)
+		return retry.Size(int(cLen)).If(err == errRange)
+	})
+
+	if err != nil {
+		return "", err
+	}
+	return C.GoString((*C.char)(unsafe.Pointer(&buf[0]))), nil
+}


Test patch:
diff --git a/rbd/rbd_nautilus_test.go b/rbd/rbd_nautilus_test.go
index 39f3724..9bd5679 100644
--- a/rbd/rbd_nautilus_test.go
+++ b/rbd/rbd_nautilus_test.go
@@ -200,3 +200,103 @@ func TestGetParent(t *testing.T) {
 		assert.Error(t, err)
 	})
 }
+
+func TestSetSnapByID(t *testing.T) {
+	conn := radosConnect(t)
+	poolName := GetUUID()
+	err := conn.MakePool(poolName)
+	assert.NoError(t, err)
+	ioctx, err := conn.OpenIOContext(poolName)
+	assert.NoError(t, err)
+
+	defer func() {
+		ioctx.Destroy()
+		assert.NoError(t, conn.DeletePool(poolName))
+		conn.Shutdown()
+	}()
+
+	t.Run("happyPath", func(t *testing.T) {
+		imgName := "Hogwarts"
+		img, err := Create(ioctx, imgName, testImageSize, testImageOrder, 1)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, img.Remove())
+		}()
+
+		img, err = OpenImage(ioctx, imgName, NoSnapshot)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, img.Close())
+		}()
+		bytesIn := []byte("Gryffindor")
+		in, err := img.Write(bytesIn)
+		assert.NoError(t, err)
+		assert.Equal(t, in, len(bytesIn))
+
+		snapName := "myPensieve"
+		snapshot, err := img.CreateSnapshot(snapName)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, snapshot.Remove())
+		}()
+
+		// overwrite
+		_, err = img.Seek(0, SeekSet)
+		assert.NoError(t, err)
+		bytesOver := []byte("Slytherin")
+		over, err := img.Write(bytesOver)
+		assert.NoError(t, err)
+		assert.Equal(t, over, len(bytesOver))
+
+		snapInfo, err := img.GetSnapshotNames()
+		assert.NoError(t, err)
+		snapID := snapInfo[0].Id
+		assert.Equal(t, snapName, snapInfo[0].Name)
+
+		err = img.SetSnapByID(snapID)
+		assert.NoError(t, err)
+
+		// read
+		_, err = img.Seek(0, SeekSet)
+		assert.NoError(t, err)
+		bytesOut := make([]byte, len(bytesIn))
+		out, err := img.Read(bytesOut)
+		assert.NoError(t, err)
+		assert.Equal(t, out, len(bytesOut))
+		assert.Equal(t, bytesIn, bytesOut)
+	})
+
+	t.Run("ClosedImage", func(t *testing.T) {
+		closedImg, err := Create(ioctx, "someImage", testImageSize, testImageOrder, 1)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, closedImg.Remove())
+		}()
+
+		var snapID uint64
+		snapID = 22
+		err = closedImg.SetSnapByID(snapID)
+		assert.Error(t, err)
+		assert.Equal(t, err, ErrImageNotOpen)
+	})
+
+	t.Run("invalidSnapID", func(t *testing.T) {
+		imgName := "someImage"
+		img, err := Create(ioctx, imgName, testImageSize, testImageOrder, 1)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, img.Remove())
+		}()
+
+		img, err = OpenImage(ioctx, imgName, NoSnapshot)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, img.Close())
+		}()
+
+		var snapID uint64
+		snapID = 22
+		err = img.SetSnapByID(snapID)
+		assert.Error(t, err)
+	})
+}
diff --git a/rbd/snapshot_octopus_test.go b/rbd/snapshot_octopus_test.go
new file mode 100644
index 0000000..71044cc
--- /dev/null
+++ b/rbd/snapshot_octopus_test.go
@@ -0,0 +1,113 @@
+// +build octopus
+
+package rbd
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSnapIDFunctions(t *testing.T) {
+	conn := radosConnect(t)
+	poolName := GetUUID()
+	err := conn.MakePool(poolName)
+	assert.NoError(t, err)
+	ioctx, err := conn.OpenIOContext(poolName)
+	assert.NoError(t, err)
+
+	defer func() {
+		ioctx.Destroy()
+		assert.NoError(t, conn.DeletePool(poolName))
+		conn.Shutdown()
+	}()
+
+	t.Run("happyPath", func(t *testing.T) {
+		imgName := "myImage"
+		img, err := Create(ioctx, imgName, testImageSize, testImageOrder, 1)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, img.Remove())
+		}()
+
+		img, err = OpenImage(ioctx, imgName, NoSnapshot)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, img.Close())
+		}()
+
+		snapName := "mySnap"
+		snapshot, err := img.CreateSnapshot(snapName)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, snapshot.Remove())
+		}()
+
+		snapID, err := img.GetSnapID(snapshot.name)
+		assert.NoError(t, err)
+		snapNameByID, err := img.GetSnapByID(snapID)
+		assert.NoError(t, err)
+		assert.Equal(t, snapshot.name, snapNameByID)
+	})
+
+	t.Run("closedImage", func(t *testing.T) {
+		imgName := "myImage"
+		closedImg, err := Create(ioctx, imgName, testImageSize, testImageOrder, 1)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, closedImg.Remove())
+		}()
+
+		img, err := OpenImage(ioctx, imgName, NoSnapshot)
+		assert.NoError(t, err)
+		snapName := "mySnap"
+		snapshot, err := img.CreateSnapshot(snapName)
+		assert.NoError(t, err)
+
+		snapID, err := img.GetSnapID(snapshot.name)
+		assert.NoError(t, err)
+
+		// close the image
+		assert.NoError(t, img.Close())
+
+		// try to get the ID again
+		_, err = img.GetSnapID(snapshot.name)
+		assert.Error(t, err)
+
+		_, err = img.GetSnapByID(snapID)
+		assert.Error(t, err)
+
+		// Open the image for snapshot removal
+		img, err = OpenImage(ioctx, imgName, NoSnapshot)
+		assert.NoError(t, err)
+		snapshot = img.GetSnapshot(snapName)
+		assert.NoError(t, snapshot.Remove())
+		assert.NoError(t, img.Close())
+	})
+
+	t.Run("invalidOptions", func(t *testing.T) {
+		imgName := "someImage"
+		img, err := Create(ioctx, imgName, testImageSize, testImageOrder, 1)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, img.Remove())
+		}()
+
+		img, err = OpenImage(ioctx, imgName, NoSnapshot)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, img.Close())
+		}()
+
+		_, err = img.GetSnapID("")
+		assert.Error(t, err)
+
+		_, err = img.GetSnapID("someSnap")
+		assert.Error(t, err)
+
+		var snapID uint64
+		snapID = 22
+		_, err = img.GetSnapByID(snapID)
+		assert.Error(t, err)
+	})
+}
diff --git a/rbd/snapshot_test.go b/rbd/snapshot_test.go
index 01fc559..be8e4b4 100644
--- a/rbd/snapshot_test.go
+++ b/rbd/snapshot_test.go
@@ -105,3 +105,82 @@ func TestErrorSnapshotNoName(t *testing.T) {
 	conn.DeletePool(poolname)
 	conn.Shutdown()
 }
+
+func TestGetSnapTimestamp(t *testing.T) {
+	conn := radosConnect(t)
+	poolName := GetUUID()
+	err := conn.MakePool(poolName)
+	assert.NoError(t, err)
+	ioctx, err := conn.OpenIOContext(poolName)
+	assert.NoError(t, err)
+
+	defer func() {
+		ioctx.Destroy()
+		assert.NoError(t, conn.DeletePool(poolName))
+		conn.Shutdown()
+	}()
+
+	t.Run("ClosedImage", func(t *testing.T) {
+		imgName := "someImage"
+		img, err := Create(ioctx, imgName, testImageSize, testImageOrder, 1)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, img.Remove())
+		}()
+		var snapID uint64
+		snapID = 22
+		_, err = img.GetSnapTimestamp(snapID)
+		assert.Error(t, err)
+		assert.Equal(t, err, ErrImageNotOpen)
+	})
+
+	t.Run("invalidSnapID", func(t *testing.T) {
+		t.Skip("hits assert due to https://tracker.ceph.com/issues/47287")
+		imgName := "someImage"
+		img, err := Create(ioctx, imgName, testImageSize, testImageOrder, 1)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, img.Remove())
+		}()
+
+		img, err = OpenImage(ioctx, imgName, NoSnapshot)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, img.Close())
+		}()
+
+		var snapID uint64
+		snapID = 22
+		_, err = img.GetSnapTimestamp(snapID)
+		assert.Error(t, err)
+	})
+
+	t.Run("happyPath", func(t *testing.T) {
+		imgName := "someImage"
+		img, err := Create(ioctx, imgName, testImageSize, testImageOrder, 1)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, img.Remove())
+		}()
+
+		img, err = OpenImage(ioctx, imgName, NoSnapshot)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, img.Close())
+		}()
+
+		snapName := "mysnap"
+		snapshot, err := img.CreateSnapshot(snapName)
+		assert.NoError(t, err)
+		defer func() {
+			assert.NoError(t, snapshot.Remove())
+		}()
+
+		snapInfo, err := img.GetSnapshotNames()
+		assert.NoError(t, err)
+		assert.Equal(t, snapName, snapInfo[0].Name)
+		snapID := snapInfo[0].Id
+		_, err = img.GetSnapTimestamp(snapID)
+		assert.NoError(t, err)
+	})
+}