nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: ceph/go-ceph
Pull request #159 (2020-01-23 20:06:18)

cephfs: Support creating mount from an existing rados connection
These changes wrap the rados and cephfs apis needed to use an existing rados connection to establish a cephfs mount.

## 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/cephfs/cephfs.go b/cephfs/cephfs.go
index 517cf57..b2adfb7 100644
--- a/cephfs/cephfs.go
+++ b/cephfs/cephfs.go
@@ -13,6 +13,7 @@ import (
 	"unsafe"
 
 	"github.com/ceph/go-ceph/errutil"
+	"github.com/ceph/go-ceph/rados"
 )
 
 type CephFSError int
@@ -47,6 +48,20 @@ func CreateMount() (*MountInfo, error) {
 	return mount, nil
 }
 
+// CreateFromRados creates a mount handle using an existing rados cluster
+// connection.
+//
+// Implements:
+//  int ceph_create_from_rados(struct ceph_mount_info **cmount, rados_t cluster);
+func CreateFromRados(conn *rados.Conn) (*MountInfo, error) {
+	mount := &MountInfo{}
+	ret := C.ceph_create_from_rados(&mount.mount, C.rados_t(conn.Cluster()))
+	if ret != 0 {
+		return nil, getError(ret)
+	}
+	return mount, nil
+}
+
 // ReadDefaultConfigFile loads the ceph configuration from the specified config file.
 func (mount *MountInfo) ReadDefaultConfigFile() error {
 	ret := C.ceph_conf_read_file(mount.mount, nil)
diff --git a/rados/conn.go b/rados/conn.go
index 0e1d098..78c3b26 100644
--- a/rados/conn.go
+++ b/rados/conn.go
@@ -30,6 +30,14 @@ type Conn struct {
 	connected bool
 }
 
+// ClusterRef represents a fundamental RADOS cluster connection.
+type ClusterRef C.rados_t
+
+// Cluster returns the underlying RADOS cluster reference for this Conn.
+func (c *Conn) Cluster() ClusterRef {
+	return ClusterRef(c.cluster)
+}
+
 // PingMonitor sends a ping to a monitor and returns the reply.
 func (c *Conn) PingMonitor(id string) (string, error) {
 	c_id := C.CString(id)


Test patch:
diff --git a/cephfs/cephfs_test.go b/cephfs/cephfs_test.go
index f108ad1..eb62e7b 100644
--- a/cephfs/cephfs_test.go
+++ b/cephfs/cephfs_test.go
@@ -7,6 +7,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/ceph/go-ceph/rados"
+
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -192,3 +194,30 @@ func TestCephFSError(t *testing.T) {
 	assert.Error(t, err)
 	assert.Equal(t, err.Error(), "cephfs: ret=345")
 }
+
+func radosConnect(t *testing.T) *rados.Conn {
+	conn, err := rados.NewConn()
+	require.NoError(t, err)
+	err = conn.ReadDefaultConfigFile()
+	require.NoError(t, err)
+
+	timeout := time.After(time.Second * 5)
+	ch := make(chan error)
+	go func(conn *rados.Conn) {
+		ch <- conn.Connect()
+	}(conn)
+	select {
+	case err = <-ch:
+	case <-timeout:
+		err = fmt.Errorf("timed out waiting for connect")
+	}
+	require.NoError(t, err)
+	return conn
+}
+
+func TestCreateFromRados(t *testing.T) {
+	conn := radosConnect(t)
+	mount, err := CreateFromRados(conn)
+	assert.NoError(t, err)
+	assert.NotNil(t, mount)
+}
Repository: ceph/go-ceph
Pull request #161 (2020-01-27 19:09:32)

cephfs: allow creating mount with an id
The ceph api function supports passing a string to identify the
client doing the mount. Expose that aspect of the api with a
new function CreateMountWithId.

Follow up PRs that add wrappers for mds command functions and more extensive verification of the feature to follow.

## 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/cephfs/cephfs.go b/cephfs/cephfs.go
index b2adfb7..ef12050 100644
--- a/cephfs/cephfs.go
+++ b/cephfs/cephfs.go
@@ -38,16 +38,28 @@ type MountInfo struct {
 	mount *C.struct_ceph_mount_info
 }
 
-// CreateMount creates a mount handle for interacting with Ceph.
-func CreateMount() (*MountInfo, error) {
+func createMount(id *C.char) (*MountInfo, error) {
 	mount := &MountInfo{}
-	ret := C.ceph_create(&mount.mount, nil)
+	ret := C.ceph_create(&mount.mount, id)
 	if ret != 0 {
 		return nil, getError(ret)
 	}
 	return mount, nil
 }
 
+// CreateMount creates a mount handle for interacting with Ceph.
+func CreateMount() (*MountInfo, error) {
+	return createMount(nil)
+}
+
+// CreateMountWithId creates a mount handle for interacting with Ceph.
+// The caller can specify a unique id that will identify this client.
+func CreateMountWithId(id string) (*MountInfo, error) {
+	cid := C.CString(id)
+	defer C.free(unsafe.Pointer(cid))
+	return createMount(cid)
+}
+
 // CreateFromRados creates a mount handle using an existing rados cluster
 // connection.
 //


Test patch:
diff --git a/cephfs/cephfs_test.go b/cephfs/cephfs_test.go
index eb62e7b..506102f 100644
--- a/cephfs/cephfs_test.go
+++ b/cephfs/cephfs_test.go
@@ -221,3 +221,18 @@ func TestCreateFromRados(t *testing.T) {
 	assert.NoError(t, err)
 	assert.NotNil(t, mount)
 }
+
+func TestCreateMountWithId(t *testing.T) {
+	// TODO: the entity_id is visible w/in the 'session ls' output
+	// of mds. Consider running the equivalent api call and checking
+	// that the string is set.
+	mount, err := CreateMountWithId("bob")
+	assert.NoError(t, err)
+	assert.NotNil(t, mount)
+
+	err = mount.ReadDefaultConfigFile()
+	assert.NoError(t, err)
+
+	err = mount.Mount()
+	assert.NoError(t, err)
+}
Repository: ceph/go-ceph
Pull request #190 (2020-03-04 19:32:23)

cephfs: add SetConfigOption and GetConfigOption functions
These are implemented similarly to the rados equivalents.

I'm putting a todo on myself to look into dealing with the ENAMETOOLONG case that existed in rados code and exists here now.

## 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/cephfs/cephfs.go b/cephfs/cephfs.go
index 9bfd2ea..708903c 100644
--- a/cephfs/cephfs.go
+++ b/cephfs/cephfs.go
@@ -82,6 +82,41 @@ func (mount *MountInfo) ReadDefaultConfigFile() error {
 	return getError(ret)
 }
 
+// SetConfigOption sets the value of the configuration option identified by
+// the given name.
+//
+// Implements:
+//  int ceph_conf_set(struct ceph_mount_info *cmount, const char *option, const char *value);
+func (mount *MountInfo) SetConfigOption(option, value string) error {
+	cOption := C.CString(option)
+	defer C.free(unsafe.Pointer(cOption))
+	cValue := C.CString(value)
+	defer C.free(unsafe.Pointer(cValue))
+	return getError(C.ceph_conf_set(mount.mount, cOption, cValue))
+}
+
+// GetConfigOption returns the value of the Ceph configuration option
+// identified by the given name.
+//
+// Implements:
+//  int ceph_conf_get(struct ceph_mount_info *cmount, const char *option, char *buf, size_t len);
+func (mount *MountInfo) GetConfigOption(option string) (string, error) {
+	cOption := C.CString(option)
+	defer C.free(unsafe.Pointer(cOption))
+	buf := make([]byte, 4096)
+	// TODO: handle ENAMETOOLONG cases. problem also exists in rados
+	ret := C.ceph_conf_get(
+		mount.mount,
+		cOption,
+		(*C.char)(unsafe.Pointer(&buf[0])),
+		C.size_t(len(buf)))
+	if ret < 0 {
+		return "", getError(ret)
+	}
+	value := C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
+	return value, nil
+}
+
 // Mount the file system, establishing a connection capable of I/O.
 //
 // Implements:


Test patch:
diff --git a/cephfs/cephfs_test.go b/cephfs/cephfs_test.go
index 6d3bf72..4b58b7a 100644
--- a/cephfs/cephfs_test.go
+++ b/cephfs/cephfs_test.go
@@ -328,3 +328,30 @@ func TestMountWithRoot(t *testing.T) {
 		assert.Error(t, err)
 	})
 }
+
+func TestGetSetConfigOption(t *testing.T) {
+	// we don't need an active connection for this, just the handle
+	mount, err := CreateMount()
+	require.NoError(t, err)
+	require.NotNil(t, mount)
+
+	err = mount.SetConfigOption("__dne__", "value")
+	assert.Error(t, err)
+	_, err = mount.GetConfigOption("__dne__")
+	assert.Error(t, err)
+
+	origVal, err := mount.GetConfigOption("log_file")
+	assert.NoError(t, err)
+
+	err = mount.SetConfigOption("log_file", "/dev/null")
+	assert.NoError(t, err)
+	currVal, err := mount.GetConfigOption("log_file")
+	assert.NoError(t, err)
+	assert.Equal(t, "/dev/null", currVal)
+
+	err = mount.SetConfigOption("log_file", origVal)
+	assert.NoError(t, err)
+	currVal, err = mount.GetConfigOption("log_file")
+	assert.NoError(t, err)
+	assert.Equal(t, origVal, currVal)
+}
Repository: ceph/go-ceph
Pull request #283 (2020-05-13 18:27:54)

cephfs: add Fstatx function to file type
Use the CephStatx type previously added to the library to implement a
wrapper for the ceph_fstatx function.

Fixes: #283 

## 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/cephfs/file.go b/cephfs/file.go
index 7ae2145..17dd2de 100644
--- a/cephfs/file.go
+++ b/cephfs/file.go
@@ -199,3 +199,27 @@ func (f *File) Fchown(user uint32, group uint32) error {
 	ret := C.ceph_fchown(f.mount.mount, f.fd, C.int(user), C.int(group))
 	return getError(ret)
 }
+
+// Fstatx returns information about an open file.
+//
+// Implements:
+//  int ceph_fstatx(struct ceph_mount_info *cmount, int fd, struct ceph_statx *stx,
+//                  unsigned int want, unsigned int flags);
+func (f *File) Fstatx(want StatxMask, flags AtFlags) (*CephStatx, error) {
+	if err := f.validate(); err != nil {
+		return nil, err
+	}
+
+	var stx C.struct_ceph_statx
+	ret := C.ceph_fstatx(
+		f.mount.mount,
+		f.fd,
+		&stx,
+		C.uint(want),
+		C.uint(flags),
+	)
+	if err := getError(ret); err != nil {
+		return nil, err
+	}
+	return cStructToCephStatx(stx), nil
+}


Test patch:
diff --git a/cephfs/file_test.go b/cephfs/file_test.go
index ae00f4f..9bc54ca 100644
--- a/cephfs/file_test.go
+++ b/cephfs/file_test.go
@@ -415,3 +415,63 @@ func TestFchown(t *testing.T) {
 	err = f2.Fchown(bob, bob)
 	assert.Error(t, err)
 }
+
+func TestFstatx(t *testing.T) {
+	fname := "test_fstatx.txt"
+
+	mount := fsConnect(t)
+	defer fsDisconnect(t, mount)
+
+	f, err := mount.Open(fname, os.O_RDWR|os.O_CREATE, 0600)
+	assert.NoError(t, err)
+	assert.NotNil(t, f)
+	assert.NoError(t, f.Close())
+	defer func() { assert.NoError(t, mount.Unlink(fname)) }()
+
+	t.Run("emptyFile", func(t *testing.T) {
+		f, err := mount.Open(fname, os.O_RDWR, 0600)
+		assert.NoError(t, err)
+		assert.NotNil(t, f)
+
+		st, err := f.Fstatx(StatxBasicStats, 0)
+		assert.NoError(t, err)
+		assert.NotNil(t, f)
+
+		assert.Equal(t, uint16(0600), st.Mode&0600)
+		assert.Equal(t, uint64(0), st.Size)
+	})
+
+	t.Run("populateFile", func(t *testing.T) {
+		f, err := mount.Open(fname, os.O_RDWR|os.O_CREATE, 0600)
+		assert.NoError(t, err)
+		assert.NotNil(t, f)
+		defer func() { assert.NoError(t, f.Close()) }()
+
+		_, err = f.Write([]byte("See spot run.\nSee spot jump.\n"))
+		assert.NoError(t, err)
+
+		st, err := f.Fstatx(StatxBasicStats, 0)
+		assert.NoError(t, err)
+		assert.NotNil(t, f)
+
+		assert.Equal(t, uint16(0600), st.Mode&0600)
+		assert.Equal(t, uint64(29), st.Size)
+	})
+
+	t.Run("closedFile", func(t *testing.T) {
+		f, err := mount.Open(fname, os.O_RDWR|os.O_CREATE, 0600)
+		assert.NoError(t, err)
+		assert.NotNil(t, f)
+		assert.NoError(t, f.Close())
+
+		st, err := f.Fstatx(StatxBasicStats, 0)
+		assert.Error(t, err)
+		assert.Nil(t, st)
+	})
+
+	t.Run("invalidFile", func(t *testing.T) {
+		f := &File{}
+		_, err := f.Fstatx(StatxBasicStats, 0)
+		assert.Error(t, err)
+	})
+}
Repository: ceph/go-ceph
Pull request #303 (2020-06-11 19:28:16)

cephfs: add Rename function
Rename, it does what it says on the tin. Tests included.

## 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/cephfs/path.go b/cephfs/path.go
index e58221c..c01e68d 100644
--- a/cephfs/path.go
+++ b/cephfs/path.go
@@ -156,3 +156,20 @@ func (mount *MountInfo) Statx(path string, want StatxMask, flags AtFlags) (*Ceph
 	}
 	return cStructToCephStatx(stx), nil
 }
+
+// Rename a file or directory.
+//
+// Implements:
+//  int ceph_rename(struct ceph_mount_info *cmount, const char *from, const char *to);
+func (mount *MountInfo) Rename(from, to string) error {
+	if err := mount.validate(); err != nil {
+		return err
+	}
+	cFrom := C.CString(from)
+	defer C.free(unsafe.Pointer(cFrom))
+	cTo := C.CString(to)
+	defer C.free(unsafe.Pointer(cTo))
+
+	ret := C.ceph_rename(mount.mount, cFrom, cTo)
+	return getError(ret)
+}


Test patch:
diff --git a/cephfs/path_test.go b/cephfs/path_test.go
index 9065df2..127c820 100644
--- a/cephfs/path_test.go
+++ b/cephfs/path_test.go
@@ -377,3 +377,25 @@ func TestStatx(t *testing.T) {
 	assert.Nil(t, st)
 	assert.Equal(t, errNoEntry, err)
 }
+
+func TestRename(t *testing.T) {
+	t.Run("invalidMount", func(t *testing.T) {
+		m := &MountInfo{}
+		err := m.Rename("foo", "bar")
+		assert.Error(t, err)
+	})
+
+	t.Run("renameDir", func(t *testing.T) {
+		mount := fsConnect(t)
+		defer fsDisconnect(t, mount)
+
+		n1 := "new_amsterdam"
+		n2 := "new_york"
+		assert.NoError(t, mount.MakeDir(n1, 0755))
+
+		err := mount.Rename(n1, n2)
+		assert.NoError(t, err)
+
+		assert.NoError(t, mount.RemoveDir(n2))
+	})
+}
Repository: ceph/go-ceph
Pull request #334 (2020-07-21 06:42:10)

rados: add wrapper for the function rados_ioctx_get_id()
Implements GetPoolID() and test case around the same.

Signed-off-by: Mudit Agarwal <muagarwa@redhat.com>

Fixes: #266 

## Checklist
- [ ] Added tests for features and functional changes
- [ ] Public functions and types are documented
- [ ] Standard formatting is applied to Go code



Patch:
diff --git a/rados/ioctx.go b/rados/ioctx.go
index c6f4ac6..d1cfd3a 100644
--- a/rados/ioctx.go
+++ b/rados/ioctx.go
@@ -249,6 +249,15 @@ func (ioctx *IOContext) GetPoolStats() (stat PoolStat, err error) {
 	}, nil
 }
 
+// GetPoolID returns the pool ID associated with the I/O context.
+//
+// Implements:
+//  int64_t rados_ioctx_get_id(rados_ioctx_t io)
+func (ioctx *IOContext) GetPoolID() int64 {
+	ret := C.rados_ioctx_get_id(ioctx.ioctx)
+	return int64(ret)
+}
+
 // GetPoolName returns the name of the pool associated with the I/O context.
 func (ioctx *IOContext) GetPoolName() (name string, err error) {
 	var (


Test patch:
diff --git a/rados/rados_test.go b/rados/rados_test.go
index 0ce57c8..7de0ab1 100644
--- a/rados/rados_test.go
+++ b/rados/rados_test.go
@@ -610,6 +610,17 @@ func (suite *RadosTestSuite) TestGetPoolStats() {
 	suite.T().Error("Pool stats aren't changing")
 }
 
+func (suite *RadosTestSuite) TestGetPoolID() {
+	suite.SetupConnection()
+
+	poolIDByName, err := suite.conn.GetPoolByName(suite.pool)
+	assert.NoError(suite.T(), err)
+	assert.NotEqual(suite.T(), int64(0), poolIDByName)
+
+	poolID := suite.ioctx.GetPoolID()
+	assert.Equal(suite.T(), poolIDByName, poolID)
+}
+
 func (suite *RadosTestSuite) TestGetPoolName() {
 	suite.SetupConnection()
Repository: ceph/go-ceph
Pull request #349 (2020-08-20 18:42:46)

rbd: add Sparsify implementing rbd_sparsify
The sparsify function can be used to make an image sparse by replacing
runs of zeros with holes. This change adds a wrapper for the basic
rbd_sparsify function and accompanying tests.

Fixes: #280

## 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_nautilus.go b/rbd/rbd_nautilus.go
index 3ae2a48..37cb405 100644
--- a/rbd/rbd_nautilus.go
+++ b/rbd/rbd_nautilus.go
@@ -101,3 +101,17 @@ func (image *Image) GetModifyTimestamp() (Timespec, error) {
 
 	return Timespec(ts.CStructToTimespec(ts.CTimespecPtr(&cts))), nil
 }
+
+// Sparsify makes an image sparse by deallocating runs of zeros.
+// The sparseSize value will be used to find runs of zeros and must be
+// a power of two no less than 4096 and no larger than the image size.
+//
+// Implements:
+//  int rbd_sparsify(rbd_image_t image, size_t sparse_size);
+func (image *Image) Sparsify(sparseSize uint) error {
+	if err := image.validate(imageIsOpen); err != nil {
+		return err
+	}
+
+	return getError(C.rbd_sparsify(image.image, C.size_t(sparseSize)))
+}


Test patch:
diff --git a/rbd/rbd_nautilus_test.go b/rbd/rbd_nautilus_test.go
index d2f2123..4e1cc12 100644
--- a/rbd/rbd_nautilus_test.go
+++ b/rbd/rbd_nautilus_test.go
@@ -77,3 +77,49 @@ func TestClosedImageNautilus(t *testing.T) {
 	_, err = image.GetModifyTimestamp()
 	assert.Error(t, err)
 }
+
+func TestSparsify(t *testing.T) {
+	conn := radosConnect(t)
+	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()
+	err = quickCreate(ioctx, name, testImageSize, testImageOrder)
+	require.NoError(t, err)
+	defer func() { assert.NoError(t, RemoveImage(ioctx, name)) }()
+
+	t.Run("valid", func(t *testing.T) {
+		img, err := OpenImage(ioctx, name, NoSnapshot)
+		assert.NoError(t, err)
+		defer func() { assert.NoError(t, img.Close()) }()
+
+		err = img.Sparsify(4096)
+		assert.NoError(t, err)
+	})
+
+	t.Run("invalidValue", func(t *testing.T) {
+		img, err := OpenImage(ioctx, name, NoSnapshot)
+		assert.NoError(t, err)
+		defer func() { assert.NoError(t, img.Close()) }()
+
+		err = img.Sparsify(1024)
+		assert.Error(t, err)
+	})
+
+	t.Run("closedImage", func(t *testing.T) {
+		img, err := OpenImage(ioctx, name, NoSnapshot)
+		assert.NoError(t, err)
+		assert.NoError(t, img.Close())
+
+		err = img.Sparsify(1024)
+		assert.Error(t, err)
+	})
+}
Repository: ceph/go-ceph
Pull request #436 (2021-01-18 15:28:52)

rbd: test that cephIoctx helper panics when fed invalid inputs
As per go-ceph issue #427, and a discussion in #399 this change and
test ensure that the cephIoctx helper function, that extracts the
C.rados_ioctx_t from a Go rados.IOContext, never returns a nil/null
C type.
As these are always "programming errors" panicking is reasonable
behavior for this case.

## Checklist
- [x] Added tests for features and functional changes
- [x] Public functions and types are documented
- [x] Standard formatting is applied to Go code


phlogistonjohn: @mergifyio rebase
mergify[bot]: **Command `rebase`: success**
> **Branch has been successfully rebased**
> 

ansiwen: @Mergifyio rebase
mergify[bot]: **Command `rebase`: success**
> **Branch already up to date**
> 

phlogistonjohn: @nixpanic  or @agarwal-mudit  ping. I hope this would be a fairly simple one to review.

Patch:
diff --git a/rbd/rbd.go b/rbd/rbd.go
index 7eb4bb8..c94fdd4 100644
--- a/rbd/rbd.go
+++ b/rbd/rbd.go
@@ -95,7 +95,11 @@ type TrashInfo struct {
 
 // cephIoctx returns a ceph rados_ioctx_t given a go-ceph rados IOContext.
 func cephIoctx(radosIoctx *rados.IOContext) C.rados_ioctx_t {
-	return C.rados_ioctx_t(radosIoctx.Pointer())
+	p := radosIoctx.Pointer()
+	if p == nil {
+		panic("invalid IOContext pointer")
+	}
+	return C.rados_ioctx_t(p)
 }
 
 // test if a bit is set in the given value


Test patch:
diff --git a/rbd/rbd_test.go b/rbd/rbd_test.go
index f095142..781bb99 100644
--- a/rbd/rbd_test.go
+++ b/rbd/rbd_test.go
@@ -1652,3 +1652,16 @@ func TestOpenImageById(t *testing.T) {
 	conn.DeletePool(poolname)
 	conn.Shutdown()
 }
+
+func TestRadosIoctxInvalid(t *testing.T) {
+	// ensure cephIoctx panics if passed a nil pointer
+	assert.Panics(t, func() {
+		_ = cephIoctx(nil)
+	})
+	// test that an invalid/incomplete (no actual backing C ioctx) will
+	// trigger a panic in cephIoctx when called.
+	ioctx := &rados.IOContext{}
+	assert.Panics(t, func() {
+		_ = cephIoctx(ioctx)
+	})
+}
Repository: ceph/go-ceph
Pull request #473 (2021-03-17 20:12:00)

rbd: add String methods to some mirroring constants
Make some of the types we added to support constants for the mirroring meet the Stringer interface.
This adds string conversion for MirrorMode, ImageMirrorMode, and MirrorImageState.

Fixes: #470 

## 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/mirror.go b/rbd/mirror.go
index 1087494..2d67105 100644
--- a/rbd/mirror.go
+++ b/rbd/mirror.go
@@ -31,6 +31,20 @@ const (
 	MirrorModePool = MirrorMode(C.RBD_MIRROR_MODE_POOL)
 )
 
+// String representation of MirrorMode.
+func (m MirrorMode) String() string {
+	switch m {
+	case MirrorModeDisabled:
+		return "disabled"
+	case MirrorModeImage:
+		return "image"
+	case MirrorModePool:
+		return "pool"
+	default:
+		return "<unknown>"
+	}
+}
+
 // ImageMirrorMode is used to indicate the mirroring approach for an RBD image.
 type ImageMirrorMode int64
 
@@ -43,6 +57,18 @@ const (
 	ImageMirrorModeSnapshot = ImageMirrorMode(C.RBD_MIRROR_IMAGE_MODE_SNAPSHOT)
 )
 
+// String representation of ImageMirrorMode.
+func (imm ImageMirrorMode) String() string {
+	switch imm {
+	case ImageMirrorModeJournal:
+		return "journal"
+	case ImageMirrorModeSnapshot:
+		return "snapshot"
+	default:
+		return "<unknown>"
+	}
+}
+
 // SetMirrorMode is used to enable or disable pool level mirroring with either
 // an automatic or per-image behavior.
 //
@@ -181,6 +207,20 @@ const (
 	MirrorImageDisabled = MirrorImageState(C.RBD_MIRROR_IMAGE_DISABLED)
 )
 
+// String representation of MirrorImageState.
+func (mis MirrorImageState) String() string {
+	switch mis {
+	case MirrorImageDisabling:
+		return "disabling"
+	case MirrorImageEnabled:
+		return "enabled"
+	case MirrorImageDisabled:
+		return "disabled"
+	default:
+		return "<unknown>"
+	}
+}
+
 // MirrorImageInfo represents the mirroring status information of a RBD image.
 type MirrorImageInfo struct {
 	GlobalID string


Test patch:
diff --git a/rbd/mirror_test.go b/rbd/mirror_test.go
index a8eb8e0..8a77a72 100644
--- a/rbd/mirror_test.go
+++ b/rbd/mirror_test.go
@@ -8,6 +8,7 @@
 package rbd
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -239,3 +240,25 @@ func TestGetMirrorImageInfo(t *testing.T) {
 		assert.Equal(t, mii.Primary, true)
 	})
 }
+
+func TestMirrorConstantStrings(t *testing.T) {
+	x := []struct {
+		s fmt.Stringer
+		t string
+	}{
+		{MirrorModeDisabled, "disabled"},
+		{MirrorModeImage, "image"},
+		{MirrorModePool, "pool"},
+		{MirrorMode(9999), "<unknown>"},
+		{ImageMirrorModeJournal, "journal"},
+		{ImageMirrorModeSnapshot, "snapshot"},
+		{ImageMirrorMode(9999), "<unknown>"},
+		{MirrorImageDisabling, "disabling"},
+		{MirrorImageEnabled, "enabled"},
+		{MirrorImageDisabled, "disabled"},
+		{MirrorImageState(9999), "<unknown>"},
+	}
+	for _, v := range x {
+		assert.Equal(t, v.s.String(), v.t)
+	}
+}