diff --git a/rbd/watchers_mimic.go b/rbd/watchers_mimic.go new file mode 100644 index 0000000..6f6bfbd --- /dev/null +++ b/rbd/watchers_mimic.go @@ -0,0 +1,57 @@ +// +build !luminous +// +// Ceph Mimic is the first version that supports watchers through librbd. + +package rbd + +// #cgo LDFLAGS: -lrbd +// #include +// #include +import "C" + +// ImageWatcher is a representation of the rbd_image_watcher_t from librbd.h +type ImageWatcher struct { + Addr string + Id int64 + Cookie uint64 +} + +// ListWatchers returns the watchers on an RBD image. In case of an error, nil +// and an error are returned. +// +// Note: +// Only supported in Ceph Mimic and newer. +// +// Implements: +// int rbd_watchers_list(rbd_image_t image, +// rbd_image_watcher_t *watchers, size_t *max_watchers) +func (image *Image) ListWatchers() ([]ImageWatcher, error) { + if err := image.validate(imageIsOpen); err != nil { + return nil, err + } + + count := C.ulong(0) + ret := C.rbd_watchers_list(image.image, nil, &count) + if ret != 0 && ret != -C.ERANGE { + return nil, getError(ret) + } + if ret == 0 && count == 0 { + return nil, nil + } + + watchers := make([]C.rbd_image_watcher_t, count) + ret = C.rbd_watchers_list(image.image, &watchers[0], &count) + if ret != 0 && ret != -C.ERANGE { + return nil, getError(ret) + } + defer C.rbd_watchers_list_cleanup(&watchers[0], count) + + imageWatchers := make([]ImageWatcher, len(watchers)) + for i, watcher := range watchers { + imageWatchers[i].Addr = C.GoString(watcher.addr) + imageWatchers[i].Id = int64(watcher.id) + imageWatchers[i].Cookie = uint64(watcher.cookie) + } + + return imageWatchers, nil +} diff --git a/rbd/watchers_mimic_test.go b/rbd/watchers_mimic_test.go new file mode 100644 index 0000000..451cfef --- /dev/null +++ b/rbd/watchers_mimic_test.go @@ -0,0 +1,117 @@ +// +build !luminous +// +// Ceph Mimic is the first version that supports watchers through librbd. + +package rbd + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestListWatchers(t *testing.T) { + conn := radosConnect(t) + require.NotNil(t, conn) + defer conn.Shutdown() + + poolname := GetUUID() + err := conn.MakePool(poolname) + require.NoError(t, err) + defer conn.DeletePool(poolname) + + ioctx, err := conn.OpenIOContext(poolname) + require.NoError(t, err) + defer ioctx.Destroy() + + name := GetUUID() + options := NewRbdImageOptions() + err = CreateImage(ioctx, name, 1<<22, options) + require.NoError(t, err) + defer func() { assert.NoError(t, RemoveImage(ioctx, name)) }() + + t.Run("imageNotOpen", func(t *testing.T) { + image, err := OpenImageReadOnly(ioctx, name, NoSnapshot) + require.NoError(t, err) + require.NotNil(t, image) + + err = image.Close() + require.NoError(t, err) + + _, err = image.ListWatchers() + assert.Equal(t, ErrImageNotOpen, err) + }) + + t.Run("noWatchers", func(t *testing.T) { + // open image read-only, as OpenImage() automatically adds a watcher + image, err := OpenImageReadOnly(ioctx, name, NoSnapshot) + require.NoError(t, err) + require.NotNil(t, image) + defer func() { assert.NoError(t, image.Close()) }() + + watchers, err := image.ListWatchers() + assert.NoError(t, err) + assert.Equal(t, 0, len(watchers)) + }) + + t.Run("addWatchers", func(t *testing.T) { + // open image read-only, as OpenImage() automatically adds a watcher + image, err := OpenImageReadOnly(ioctx, name, NoSnapshot) + require.NoError(t, err) + require.NotNil(t, image) + defer func() { assert.NoError(t, image.Close()) }() + + watchers, err := image.ListWatchers() + assert.NoError(t, err) + assert.Equal(t, 0, len(watchers)) + + // opening an image writable adds a watcher automatically + image2, err := OpenImage(ioctx, name, NoSnapshot) + require.NoError(t, err) + require.NotNil(t, image2) + + watchers, err = image.ListWatchers() + assert.NoError(t, err) + assert.Equal(t, 1, len(watchers)) + + watchers, err = image2.ListWatchers() + assert.NoError(t, err) + assert.Equal(t, 1, len(watchers)) + + image3, err := OpenImage(ioctx, name, NoSnapshot) + require.NoError(t, err) + require.NotNil(t, image3) + + watchers, err = image.ListWatchers() + assert.NoError(t, err) + assert.Equal(t, 2, len(watchers)) + + watchers, err = image2.ListWatchers() + assert.NoError(t, err) + assert.Equal(t, 2, len(watchers)) + + watchers, err = image3.ListWatchers() + assert.NoError(t, err) + assert.Equal(t, 2, len(watchers)) + + // closing an image removes the watchers + err = image3.Close() + require.NoError(t, err) + + watchers, err = image.ListWatchers() + assert.NoError(t, err) + assert.Equal(t, 1, len(watchers)) + + watchers, err = image2.ListWatchers() + assert.NoError(t, err) + assert.Equal(t, 1, len(watchers)) + + err = image2.Close() + require.NoError(t, err) + + watchers, err = image.ListWatchers() + assert.NoError(t, err) + assert.Equal(t, 0, len(watchers)) + }) +}