Repository: ceph/go-ceph
Pull request #290 (2020-05-20 18:20:47)

rados: add support for remaining missing "command" funcs
This series of changes do a little clean up before wrapping rados_osd_command & rados_mon_command_target and tests.

Fixes: #272 
Fixes: #270 

## 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: Something very strange is happening with MonCommandTarget function on octopus. This will need additional debugging.
phlogistonjohn: There's still some hinky behavior with MonCommandTarget & family on octopus. However, I did enough investigating to find a JSON string that will work will all the versions.
I will file an issue here for us to track this and that will probably lead me to send a mail or file an issue in ceph. But for now our wrappers look OK to me. I don't think the issue I saw is related to how we use the function in Go.
phlogistonjohn: See #291 for issue tracking the behavioral differences.

Patch:
diff --git a/rados/command.go b/rados/command.go
index 1ec0422..7404d31 100644
--- a/rados/command.go
+++ b/rados/command.go
@@ -17,15 +17,11 @@ func radosBufferFree(p unsafe.Pointer) {
 
 // MonCommand sends a command to one of the monitors
 func (c *Conn) MonCommand(args []byte) ([]byte, string, error) {
-	return c.monCommand(args, nil)
+	return c.MonCommandWithInputBuffer(args, nil)
 }
 
 // MonCommandWithInputBuffer sends a command to one of the monitors, with an input buffer
 func (c *Conn) MonCommandWithInputBuffer(args, inputBuffer []byte) ([]byte, string, error) {
-	return c.monCommand(args, inputBuffer)
-}
-
-func (c *Conn) monCommand(args, inputBuffer []byte) ([]byte, string, error) {
 	ci := cutil.NewCommandInput([][]byte{args}, inputBuffer)
 	defer ci.Free()
 	co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
@@ -54,7 +50,7 @@ func (c *Conn) monCommand(args, inputBuffer []byte) ([]byte, string, error) {
 //                       char **outbuf, size_t *outbuflen,
 //                       char **outs, size_t *outslen);
 func (c *Conn) PGCommand(pgid []byte, args [][]byte) ([]byte, string, error) {
-	return c.pgCommand(pgid, args, nil)
+	return c.PGCommandWithInputBuffer(pgid, args, nil)
 }
 
 // PGCommandWithInputBuffer sends a command to one of the PGs, with an input buffer
@@ -66,10 +62,6 @@ func (c *Conn) PGCommand(pgid []byte, args [][]byte) ([]byte, string, error) {
 //                       char **outbuf, size_t *outbuflen,
 //                       char **outs, size_t *outslen);
 func (c *Conn) PGCommandWithInputBuffer(pgid []byte, args [][]byte, inputBuffer []byte) ([]byte, string, error) {
-	return c.pgCommand(pgid, args, inputBuffer)
-}
-
-func (c *Conn) pgCommand(pgid []byte, args [][]byte, inputBuffer []byte) ([]byte, string, error) {
 	name := C.CString(string(pgid))
 	defer C.free(unsafe.Pointer(name))
 	ci := cutil.NewCommandInput(args, inputBuffer)
@@ -94,21 +86,18 @@ func (c *Conn) pgCommand(pgid []byte, args [][]byte, inputBuffer []byte) ([]byte
 
 // MgrCommand sends a command to a ceph-mgr.
 func (c *Conn) MgrCommand(args [][]byte) ([]byte, string, error) {
-	return c.mgrCommand(args, nil)
+	return c.MgrCommandWithInputBuffer(args, nil)
 }
 
 // MgrCommandWithInputBuffer sends a command, with an input buffer, to a ceph-mgr.
-func (c *Conn) MgrCommandWithInputBuffer(args [][]byte, inputBuffer []byte) ([]byte, string, error) {
-	return c.mgrCommand(args, inputBuffer)
-}
-
+//
 // Implements:
 //  int rados_mgr_command(rados_t cluster, const char **cmd,
 //                         size_t cmdlen, const char *inbuf,
 //                         size_t inbuflen, char **outbuf,
 //                         size_t *outbuflen, char **outs,
 //                          size_t *outslen);
-func (c *Conn) mgrCommand(args [][]byte, inputBuffer []byte) ([]byte, string, error) {
+func (c *Conn) MgrCommandWithInputBuffer(args [][]byte, inputBuffer []byte) ([]byte, string, error) {
 	ci := cutil.NewCommandInput(args, inputBuffer)
 	defer ci.Free()
 	co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
@@ -127,3 +116,78 @@ func (c *Conn) mgrCommand(args [][]byte, inputBuffer []byte) ([]byte, string, er
 	buf, status := co.GoValues()
 	return buf, status, getError(ret)
 }
+
+// OsdCommand sends a command to the specified ceph OSD.
+func (c *Conn) OsdCommand(osd int, args [][]byte) ([]byte, string, error) {
+	return c.OsdCommandWithInputBuffer(osd, args, nil)
+}
+
+// OsdCommandWithInputBuffer sends a command, with an input buffer, to the
+// specified ceph OSD.
+//
+// Implements:
+//  int rados_osd_command(rados_t cluster, int osdid,
+//                                       const char **cmd, size_t cmdlen,
+//                                       const char *inbuf, size_t inbuflen,
+//                                       char **outbuf, size_t *outbuflen,
+//                                       char **outs, size_t *outslen);
+func (c *Conn) OsdCommandWithInputBuffer(
+	osd int, args [][]byte, inputBuffer []byte) ([]byte, string, error) {
+
+	ci := cutil.NewCommandInput(args, inputBuffer)
+	defer ci.Free()
+	co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
+	defer co.Free()
+
+	ret := C.rados_osd_command(
+		c.cluster,
+		C.int(osd),
+		(**C.char)(ci.Cmd()),
+		C.size_t(ci.CmdLen()),
+		(*C.char)(ci.InBuf()),
+		C.size_t(ci.InBufLen()),
+		(**C.char)(co.OutBuf()),
+		(*C.size_t)(co.OutBufLen()),
+		(**C.char)(co.Outs()),
+		(*C.size_t)(co.OutsLen()))
+	buf, status := co.GoValues()
+	return buf, status, getError(ret)
+}
+
+// MonCommandTarget sends a command to a specified monitor.
+func (c *Conn) MonCommandTarget(name string, args [][]byte) ([]byte, string, error) {
+	return c.MonCommandTargetWithInputBuffer(name, args, nil)
+}
+
+// MonCommandTargetWithInputBuffer sends a command, with an input buffer, to a specified monitor.
+//
+// Implements:
+//  int rados_mon_command_target(rados_t cluster, const char *name,
+//                               const char **cmd, size_t cmdlen,
+//                               const char *inbuf, size_t inbuflen,
+//                               char **outbuf, size_t *outbuflen,
+//                               char **outs, size_t *outslen);
+func (c *Conn) MonCommandTargetWithInputBuffer(
+	name string, args [][]byte, inputBuffer []byte) ([]byte, string, error) {
+
+	cName := C.CString(name)
+	defer C.free(unsafe.Pointer(cName))
+	ci := cutil.NewCommandInput(args, inputBuffer)
+	defer ci.Free()
+	co := cutil.NewCommandOutput().SetFreeFunc(radosBufferFree)
+	defer co.Free()
+
+	ret := C.rados_mon_command_target(
+		c.cluster,
+		cName,
+		(**C.char)(ci.Cmd()),
+		C.size_t(ci.CmdLen()),
+		(*C.char)(ci.InBuf()),
+		C.size_t(ci.InBufLen()),
+		(**C.char)(co.OutBuf()),
+		(*C.size_t)(co.OutBufLen()),
+		(**C.char)(co.Outs()),
+		(*C.size_t)(co.OutsLen()))
+	buf, status := co.GoValues()
+	return buf, status, getError(ret)
+}


Test patch:
diff --git a/rados/command_test.go b/rados/command_test.go
index 33f0f40..223b890 100644
--- a/rados/command_test.go
+++ b/rados/command_test.go
@@ -8,6 +8,11 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+var (
+	osdNumber = 0
+	monName   = "a"
+)
+
 func (suite *RadosTestSuite) TestMonCommand() {
 	suite.SetupConnection()
 
@@ -129,3 +134,87 @@ func (suite *RadosTestSuite) TestMgrCommandMalformedCommand() {
 	assert.NotEqual(suite.T(), info, "")
 	assert.Len(suite.T(), buf, 0)
 }
+
+func (suite *RadosTestSuite) TestOsdCommandDescriptions() {
+	suite.SetupConnection()
+
+	command, err := json.Marshal(
+		map[string]string{"prefix": "get_command_descriptions", "format": "json"})
+	assert.NoError(suite.T(), err)
+
+	buf, info, err := suite.conn.OsdCommand(osdNumber, [][]byte{command})
+	assert.NoError(suite.T(), err)
+	assert.Equal(suite.T(), info, "")
+
+	var message map[string]interface{}
+	err = json.Unmarshal(buf, &message)
+	assert.NoError(suite.T(), err)
+}
+
+func (suite *RadosTestSuite) TestOsdCommand() {
+	suite.SetupConnection()
+
+	command, err := json.Marshal(
+		map[string]string{"prefix": "version", "format": "json"})
+	assert.NoError(suite.T(), err)
+
+	buf, info, err := suite.conn.OsdCommand(osdNumber, [][]byte{command})
+	assert.NoError(suite.T(), err)
+	assert.Equal(suite.T(), info, "")
+
+	var message map[string]interface{}
+	err = json.Unmarshal(buf, &message)
+	assert.NoError(suite.T(), err)
+}
+
+func (suite *RadosTestSuite) TestOsdCommandMalformedCommand() {
+	suite.SetupConnection()
+
+	command := []byte("JUNK!")
+	buf, info, err := suite.conn.OsdCommand(osdNumber, [][]byte{command})
+	assert.Error(suite.T(), err)
+	assert.NotEqual(suite.T(), info, "")
+	assert.Len(suite.T(), buf, 0)
+}
+
+func (suite *RadosTestSuite) TestMonCommandTargetDescriptions() {
+	suite.SetupConnection()
+
+	command, err := json.Marshal(
+		map[string]string{"prefix": "get_command_descriptions", "format": "json"})
+	assert.NoError(suite.T(), err)
+
+	buf, info, err := suite.conn.MonCommandTarget(monName, [][]byte{command})
+	assert.NoError(suite.T(), err)
+	assert.Equal(suite.T(), info, "")
+
+	var message map[string]interface{}
+	err = json.Unmarshal(buf, &message)
+	assert.NoError(suite.T(), err)
+}
+
+func (suite *RadosTestSuite) TestMonCommandTarget() {
+	suite.SetupConnection()
+
+	command, err := json.Marshal(
+		map[string]string{"prefix": "mon_status"})
+	assert.NoError(suite.T(), err)
+
+	buf, info, err := suite.conn.MonCommandTarget(monName, [][]byte{command})
+	assert.NoError(suite.T(), err)
+	assert.Equal(suite.T(), info, "")
+
+	var message map[string]interface{}
+	err = json.Unmarshal(buf, &message)
+	assert.NoError(suite.T(), err)
+}
+
+func (suite *RadosTestSuite) TestMonCommandTargetMalformedCommand() {
+	suite.SetupConnection()
+
+	command := []byte("JUNK!")
+	buf, info, err := suite.conn.MonCommandTarget(monName, [][]byte{command})
+	assert.Error(suite.T(), err)
+	assert.NotEqual(suite.T(), info, "")
+	assert.Len(suite.T(), buf, 0)
+}