Repository: ceph/go-ceph
Pull request #175 (2020-02-04 20:57:07)

rbd: add functions to open image by id
Add GetId wrapper for rbd_get_id.
Add OpenImageById and OpenImageByIdReadOnly wrappers for rbd_open_by_id and rbd_open_by_id_read_only.
Add tests for all. Coverage of these tests is a tad spotty as it currently seems impossible to test the buffer-too-small case in GetId as we're going to have small ids. In addition, ceph libs crash when asked to open an image by an invalid id so trivially testing the error handling is currently not possible. The tests exist but are set to always skip until we know a ceph version fixes this issue and then we will need to detect it to enable the tests.

## Checklist
- [x] Added tests for features and functional changes
- [x] Public functions and types are documented
- [x] Standard formatting is applied to Go code



Patch:
diff --git a/rbd/rbd.go b/rbd/rbd.go
index 8e2225f..eab4109 100644
--- a/rbd/rbd.go
+++ b/rbd/rbd.go
@@ -1125,6 +1125,32 @@ func (image *Image) RemoveMetadata(key string) error {
 	return nil
 }
 
+// GetId returns the internal image ID string.
+//
+// Implements:
+//  int rbd_get_id(rbd_image_t image, char *id, size_t id_len);
+func (image *Image) GetId() (string, error) {
+	if err := image.validate(imageIsOpen); err != nil {
+		return "", err
+	}
+	size := C.size_t(1024)
+	buf := make([]byte, size)
+	for {
+		ret := C.rbd_get_id(
+			image.image,
+			(*C.char)(unsafe.Pointer(&buf[0])),
+			size)
+		if ret == -C.ERANGE && size <= 8192 {
+			size *= 2
+			buf = make([]byte, size)
+		} else if ret < 0 {
+			return "", getError(ret)
+		}
+		id := C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
+		return id, nil
+	}
+}
+
 // int rbd_snap_remove(rbd_image_t image, const char *snapname);
 func (snapshot *Snapshot) Remove() error {
 	if err := snapshot.validate(snapshotNeedsName | imageIsOpen); err != nil {
@@ -1330,6 +1356,79 @@ func OpenImageReadOnly(ioctx *rados.IOContext, name, snapName string) (*Image, e
 	}, nil
 }
 
+// OpenImageById will open an existing rbd image by ID and snapshot name,
+// returning a new opened image. Pass the NoSnapshot sentinel value as the
+// snapName to explicitly indicate that no snapshot name is being provided.
+// Error handling will fail & segfault unless compiled with a version of ceph
+// that fixes https://tracker.ceph.com/issues/43178
+//
+// Implements:
+//  int rbd_open_by_id(rados_ioctx_t io, const char *id,
+//                     rbd_image_t *image, const char *snap_name);
+func OpenImageById(ioctx *rados.IOContext, id, snapName string) (*Image, error) {
+	cid := C.CString(id)
+	defer C.free(unsafe.Pointer(cid))
+
+	var cSnapName *C.char
+	if snapName != NoSnapshot {
+		cSnapName = C.CString(snapName)
+		defer C.free(unsafe.Pointer(cSnapName))
+	}
+
+	var cImage C.rbd_image_t
+	ret := C.rbd_open_by_id(
+		C.rados_ioctx_t(ioctx.Pointer()),
+		cid,
+		&cImage,
+		cSnapName)
+
+	if ret != 0 {
+		return nil, getError(ret)
+	}
+
+	return &Image{
+		ioctx: ioctx,
+		image: cImage,
+	}, nil
+}
+
+// OpenImageByIdReadOnly will open an existing rbd image by ID and snapshot
+// name, returning a new opened-for-read image. Pass the NoSnapshot sentinel
+// value as the snapName to explicitly indicate that no snapshot name is being
+// provided.
+// Error handling will fail & segfault unless compiled with a version of ceph
+// that fixes https://tracker.ceph.com/issues/43178
+//
+// Implements:
+//  int rbd_open_by_id_read_only(rados_ioctx_t io, const char *id,
+//                               rbd_image_t *image, const char *snap_name);
+func OpenImageByIdReadOnly(ioctx *rados.IOContext, id, snapName string) (*Image, error) {
+	cid := C.CString(id)
+	defer C.free(unsafe.Pointer(cid))
+
+	var cSnapName *C.char
+	if snapName != NoSnapshot {
+		cSnapName = C.CString(snapName)
+		defer C.free(unsafe.Pointer(cSnapName))
+	}
+
+	var cImage C.rbd_image_t
+	ret := C.rbd_open_by_id_read_only(
+		C.rados_ioctx_t(ioctx.Pointer()),
+		cid,
+		&cImage,
+		cSnapName)
+
+	if ret != 0 {
+		return nil, getError(ret)
+	}
+
+	return &Image{
+		ioctx: ioctx,
+		image: cImage,
+	}, nil
+}
+
 // CreateImage creates a new rbd image using provided image options.
 //
 // Implements:


Test patch:
diff --git a/rbd/rbd_test.go b/rbd/rbd_test.go
index ca4071c..a55eb5e 100644
--- a/rbd/rbd_test.go
+++ b/rbd/rbd_test.go
@@ -1425,3 +1425,139 @@ func quickCreate(ioctx *rados.IOContext, name string, size uint64, order int) er
 	}
 	return CreateImage(ioctx, name, size, options)
 }
+
+func TestGetId(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()
+	assert.NoError(t,
+		options.SetUint64(RbdImageOptionOrder, uint64(testImageOrder)))
+	err = CreateImage(ioctx, name, testImageSize, options)
+	assert.NoError(t, err)
+
+	image, err := OpenImage(ioctx, name, NoSnapshot)
+	assert.NoError(t, err)
+	id, err := image.GetId()
+	assert.NoError(t, err)
+	assert.Greater(t, len(id), 8)
+
+	err = image.Close()
+	assert.NoError(t, err)
+
+	id, err = image.GetId()
+	assert.Error(t, err)
+	assert.Equal(t, "", id)
+
+	err = image.Remove()
+	assert.NoError(t, err)
+
+	ioctx.Destroy()
+	conn.DeletePool(poolname)
+	conn.Shutdown()
+}
+
+func TestOpenImageById(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()
+	assert.NoError(t,
+		options.SetUint64(RbdImageOptionOrder, uint64(testImageOrder)))
+	err = CreateImage(ioctx, name, testImageSize, options)
+	assert.NoError(t, err)
+
+	workingImage, err := OpenImage(ioctx, name, NoSnapshot)
+	assert.NoError(t, err)
+	id, err := workingImage.GetId()
+	assert.NoError(t, err)
+	err = workingImage.Close()
+	assert.NoError(t, err)
+
+	t.Run("ReadWriteBadId", func(t *testing.T) {
+		t.Skip("segfaults due to https://tracker.ceph.com/issues/43178")
+		// phony id
+		img, err := OpenImageById(ioctx, "102f00aaabbbccd", NoSnapshot)
+		require.Error(t, err)
+		require.Nil(t, img)
+	})
+	t.Run("ReadOnlyBadId", func(t *testing.T) {
+		t.Skip("segfaults due to https://tracker.ceph.com/issues/43178")
+		// phony id
+		img, err := OpenImageByIdReadOnly(ioctx, "blubb", NoSnapshot)
+		require.Error(t, err)
+		require.Nil(t, img)
+	})
+	t.Run("ReadWrite", func(t *testing.T) {
+		img, err := OpenImageById(ioctx, id, NoSnapshot)
+		require.NoError(t, err)
+		require.NotNil(t, img)
+		defer func() { assert.NoError(t, img.Close()) }()
+
+		data := []byte("input data")
+		_, err = img.Write(data)
+		assert.NoError(t, err)
+	})
+	t.Run("ReadOnly", func(t *testing.T) {
+		img, err := OpenImageByIdReadOnly(ioctx, id, NoSnapshot)
+		require.NoError(t, err)
+		require.NotNil(t, img)
+		defer func() { assert.NoError(t, img.Close()) }()
+
+		data := []byte("input data")
+		_, err = img.Write(data)
+		// writing should fail in read-only mode
+		assert.Error(t, err)
+	})
+	t.Run("Snapshot", func(t *testing.T) {
+		img, err := OpenImageById(ioctx, id, NoSnapshot)
+		require.NoError(t, err)
+		require.NotNil(t, img)
+		defer func() { assert.NoError(t, img.Close()) }()
+
+		snapshot, err := img.CreateSnapshot("snerpshort")
+		assert.NoError(t, err)
+		defer func() { assert.NoError(t, snapshot.Remove()) }()
+
+		snapImage, err := OpenImageById(ioctx, id, "snerpshort")
+		require.NoError(t, err)
+		require.NotNil(t, snapImage)
+		assert.NoError(t, snapImage.Close())
+	})
+	t.Run("ReadOnlySnapshot", func(t *testing.T) {
+		img, err := OpenImageById(ioctx, id, NoSnapshot)
+		require.NoError(t, err)
+		require.NotNil(t, img)
+		defer func() { assert.NoError(t, img.Close()) }()
+
+		snapshot, err := img.CreateSnapshot("snerpshort2")
+		assert.NoError(t, err)
+		defer func() { assert.NoError(t, snapshot.Remove()) }()
+
+		snapImage, err := OpenImageByIdReadOnly(ioctx, id, "snerpshort2")
+		require.NoError(t, err)
+		require.NotNil(t, snapImage)
+		assert.NoError(t, snapImage.Close())
+	})
+
+	err = workingImage.Remove()
+	assert.NoError(t, err)
+
+	ioctx.Destroy()
+	conn.DeletePool(poolname)
+	conn.Shutdown()
+}