package rados import ( "fmt" "io" "math/rand" "os" "sort" "strconv" "testing" "time" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" tsuite "github.com/stretchr/testify/suite" ) type RadosTestSuite struct { tsuite.Suite conn *Conn ioctx *IOContext pool string count int } func (suite *RadosTestSuite) SetupSuite() { conn, err := NewConn() require.NoError(suite.T(), err) defer conn.Shutdown() err = conn.ReadDefaultConfigFile() require.NoError(suite.T(), err) timeout := time.After(time.Second * 5) ch := make(chan error) go func(conn *Conn) { ch <- conn.Connect() }(conn) select { case err = <-ch: case <-timeout: err = fmt.Errorf("timed out waiting for connect") } if assert.NoError(suite.T(), err) { pool := uuid.Must(uuid.NewV4()).String() if err = conn.MakePool(pool); assert.NoError(suite.T(), err) { suite.pool = pool return } } suite.T().FailNow() } func (suite *RadosTestSuite) SetupTest() { suite.conn = nil suite.ioctx = nil suite.count = 0 conn, err := NewConn() require.NoError(suite.T(), err) suite.conn = conn err = suite.conn.ReadDefaultConfigFile() require.NoError(suite.T(), err) } func (suite *RadosTestSuite) SetupConnection() { if err := suite.conn.Connect(); assert.NoError(suite.T(), err) { ioctx, err := suite.conn.OpenIOContext(suite.pool) if assert.NoError(suite.T(), err) { suite.ioctx = ioctx return } } suite.conn.Shutdown() suite.T().FailNow() } func (suite *RadosTestSuite) GenObjectName() string { name := fmt.Sprintf("%s_%d", suite.T().Name(), suite.count) suite.count++ return name } func (suite *RadosTestSuite) RandomBytes(size int) []byte { bytes := make([]byte, size) n, err := rand.Read(bytes) require.Equal(suite.T(), n, size) require.NoError(suite.T(), err) return bytes } func (suite *RadosTestSuite) TearDownTest() { if suite.ioctx != nil { suite.ioctx.Destroy() } suite.conn.Shutdown() } func (suite *RadosTestSuite) TearDownSuite() { conn, err := NewConn() require.NoError(suite.T(), err) defer conn.Shutdown() err = conn.ReadDefaultConfigFile() require.NoError(suite.T(), err) if err = conn.Connect(); assert.NoError(suite.T(), err) { err = conn.DeletePool(suite.pool) assert.NoError(suite.T(), err) } } func TestVersion(t *testing.T) { major, minor, patch := Version() assert.False(t, major < 0 || major > 1000, "invalid major") assert.False(t, minor < 0 || minor > 1000, "invalid minor") assert.False(t, patch < 0 || patch > 1000, "invalid patch") } func (suite *RadosTestSuite) TestGetFSID() { fsid, err := suite.conn.GetFSID() assert.NoError(suite.T(), err) assert.NotEqual(suite.T(), fsid, "") } func (suite *RadosTestSuite) TestGetSetConfigOption() { // rejects invalid options err := suite.conn.SetConfigOption("___dne___", "value") assert.Error(suite.T(), err, "Invalid option") // check error for get invalid option _, err = suite.conn.GetConfigOption("__dne__") assert.Error(suite.T(), err) // verify SetConfigOption changes a values prevVal, err := suite.conn.GetConfigOption("log_file") assert.NoError(suite.T(), err, "Invalid option") err = suite.conn.SetConfigOption("log_file", "/dev/null") assert.NoError(suite.T(), err, "Invalid option") currVal, err := suite.conn.GetConfigOption("log_file") assert.NoError(suite.T(), err, "Invalid option") assert.NotEqual(suite.T(), prevVal, "/dev/null") assert.Equal(suite.T(), currVal, "/dev/null") } func (suite *RadosTestSuite) TestParseDefaultConfigEnv() { prevVal, err := suite.conn.GetConfigOption("log_file") assert.NoError(suite.T(), err, "Invalid option") err = os.Setenv("CEPH_ARGS", "--log-file /dev/null") assert.NoError(suite.T(), err) err = suite.conn.ParseDefaultConfigEnv() assert.NoError(suite.T(), err) currVal, err := suite.conn.GetConfigOption("log_file") assert.NoError(suite.T(), err, "Invalid option") assert.NotEqual(suite.T(), prevVal, "/dev/null") assert.Equal(suite.T(), currVal, "/dev/null") } func (suite *RadosTestSuite) TestParseConfigArgv() { prevVal, err := suite.conn.GetConfigOption("log_file") assert.NoError(suite.T(), err, "Invalid option") argv := []string{"rados.test", "--log_file", "/dev/null"} err = suite.conn.ParseConfigArgv(argv) assert.NoError(suite.T(), err) currVal, err := suite.conn.GetConfigOption("log_file") assert.NoError(suite.T(), err, "Invalid option") assert.NotEqual(suite.T(), prevVal, "/dev/null") assert.Equal(suite.T(), currVal, "/dev/null") // ensure that an empty slice triggers an error (not a crash) err = suite.conn.ParseConfigArgv([]string{}) assert.Error(suite.T(), err) // ensure we get an error for an invalid conn value badConn := &Conn{} err = badConn.ParseConfigArgv( []string{"cephfs.test", "--log_file", "/dev/null"}) assert.Error(suite.T(), err) } func (suite *RadosTestSuite) TestParseCmdLineArgs() { prevVal, err := suite.conn.GetConfigOption("log_file") assert.NoError(suite.T(), err, "Invalid option") args := []string{"--log_file", "/dev/null"} err = suite.conn.ParseCmdLineArgs(args) assert.NoError(suite.T(), err) currVal, err := suite.conn.GetConfigOption("log_file") assert.NoError(suite.T(), err, "Invalid option") assert.NotEqual(suite.T(), prevVal, "/dev/null") assert.Equal(suite.T(), currVal, "/dev/null") } func (suite *RadosTestSuite) TestReadConfigFile() { // check current log_file value prevStr, err := suite.conn.GetConfigOption("log_max_new") assert.NoError(suite.T(), err) prevVal, err := strconv.Atoi(prevStr) assert.NoError(suite.T(), err) // create conf file that changes log_file conf option file, err := os.CreateTemp("/tmp", "go-rados") require.NoError(suite.T(), err) defer func() { assert.NoError(suite.T(), file.Close()) assert.NoError(suite.T(), os.Remove(file.Name())) }() nextVal := prevVal + 1 conf := fmt.Sprintf("[global]\nlog_max_new = %d\n", nextVal) _, err = io.WriteString(file, conf) assert.NoError(suite.T(), err) // parse the config file err = suite.conn.ReadConfigFile(file.Name()) assert.NoError(suite.T(), err) // check current log_file value currStr, err := suite.conn.GetConfigOption("log_max_new") assert.NoError(suite.T(), err) currVal, err := strconv.Atoi(currStr) assert.NoError(suite.T(), err) assert.NotEqual(suite.T(), prevStr, currStr) assert.Equal(suite.T(), currVal, prevVal+1) } func (suite *RadosTestSuite) TestGetClusterStats() { suite.SetupConnection() // grab current stats prevStat, err := suite.conn.GetClusterStats() fmt.Printf("prevStat: %+v\n", prevStat) assert.NoError(suite.T(), err) // make some changes to the cluster buf := make([]byte, 1<<20) for i := 0; i < 10; i++ { objname := suite.GenObjectName() err = suite.ioctx.Write(objname, buf, 0) assert.NoError(suite.T(), err) } // wait a while for the stats to change for i := 0; i < 30; i++ { stat, err := suite.conn.GetClusterStats() assert.NoError(suite.T(), err) // wait for something to change if stat == prevStat { fmt.Printf("curr_stat: %+v (trying again...)\n", stat) time.Sleep(time.Second) } else { // success fmt.Printf("curr_stat: %+v (change detected)\n", stat) return } } suite.T().Error("Cluster stats aren't changing") } func (suite *RadosTestSuite) TestGetInstanceID() { suite.SetupConnection() id := suite.conn.GetInstanceID() assert.NotEqual(suite.T(), id, 0) } func (suite *RadosTestSuite) TestMakeDeletePool() { suite.SetupConnection() // check that new pool name is unique newName := uuid.Must(uuid.NewV4()).String() _, err := suite.conn.GetPoolByName(newName) if err == nil { suite.T().Error("Random pool name exists!") return } // create pool err = suite.conn.MakePool(newName) assert.NoError(suite.T(), err) // verify that the new pool name exists pool, err := suite.conn.GetPoolByName(newName) assert.NoError(suite.T(), err) if pool == 0 { suite.T().Error("Cannot find newly created pool") } // delete the pool err = suite.conn.DeletePool(newName) assert.NoError(suite.T(), err) // verify that it is gone pool, _ = suite.conn.GetPoolByName(newName) if pool != 0 { suite.T().Error("Deleted pool still exists") } } func (suite *RadosTestSuite) TestGetPoolByName() { suite.SetupConnection() // get current list of pool pools, err := suite.conn.ListPools() assert.NoError(suite.T(), err) // check that new pool name is unique newName := uuid.Must(uuid.NewV4()).String() require.NotContains( suite.T(), pools, newName, "Random pool name exists!") pool, _ := suite.conn.GetPoolByName(newName) assert.Equal(suite.T(), int64(0), pool, "Pool does not exist, but was found!") // create pool err = suite.conn.MakePool(newName) assert.NoError(suite.T(), err) // verify that the new pool name exists pools, err = suite.conn.ListPools() assert.NoError(suite.T(), err) assert.Contains( suite.T(), pools, newName, "Cannot find newly created pool") pool, err = suite.conn.GetPoolByName(newName) assert.NoError(suite.T(), err) assert.NotEqual(suite.T(), int64(0), pool, "Pool not found!") // delete the pool err = suite.conn.DeletePool(newName) assert.NoError(suite.T(), err) // verify that it is gone pools, err = suite.conn.ListPools() assert.NoError(suite.T(), err) assert.NotContains( suite.T(), pools, newName, "Deleted pool still exists") pool, err = suite.conn.GetPoolByName(newName) assert.Error(suite.T(), err) assert.Equal( suite.T(), int64(0), pool, "Pool should have been deleted, but was found!") } func (suite *RadosTestSuite) TestGetPoolByID() { suite.SetupConnection() // check that new pool name is unique newName := uuid.Must(uuid.NewV4()).String() pool, err := suite.conn.GetPoolByName(newName) if pool != 0 || err == nil { suite.T().Error("Random pool name exists!") return } // create pool err = suite.conn.MakePool(newName) assert.NoError(suite.T(), err) // verify that the new pool name exists id, err := suite.conn.GetPoolByName(newName) assert.NoError(suite.T(), err) if id == 0 { suite.T().Error("Cannot find newly created pool") } // get the name of the pool name, err := suite.conn.GetPoolByID(id) assert.NoError(suite.T(), err) if name == "" { suite.T().Error("Cannot find name of newly created pool") } // delete the pool err = suite.conn.DeletePool(newName) assert.NoError(suite.T(), err) // verify that it is gone name, err = suite.conn.GetPoolByID(id) if name != "" || err == nil { suite.T().Error("Deleted pool still exists") } } func (suite *RadosTestSuite) TestGetLargePoolList() { suite.SetupConnection() // get current list of pool pools, err := suite.conn.ListPools() assert.NoError(suite.T(), err) fill := func(r rune) string { b := make([]rune, 512) for i := range b { b[i] = r } return string(b) } // try to ensure we exceed the default 4096 byte initial buffer // size and make use of the increased buffer size code path names := []string{ fill('a'), fill('b'), fill('c'), fill('d'), fill('e'), fill('f'), fill('g'), fill('h'), fill('i'), fill('j'), } defer func(origPools []string) { for _, name := range names { err := suite.conn.DeletePool(name) assert.NoError(suite.T(), err) } cleanPools, err := suite.conn.ListPools() assert.NoError(suite.T(), err) assert.Equal(suite.T(), origPools, cleanPools) }(pools) for _, name := range names { err = suite.conn.MakePool(name) require.NoError(suite.T(), err) } pools, err = suite.conn.ListPools() assert.NoError(suite.T(), err) for _, name := range names { assert.Contains(suite.T(), pools, name) } } func (suite *RadosTestSuite) TestPingMonitor() { suite.T().SkipNow() // until #921 is fixed suite.SetupConnection() // mon id that should work with vstart.sh reply, err := suite.conn.PingMonitor("a") assert.NoError(suite.T(), err) assert.NotEqual(suite.T(), reply, "") // invalid mon id reply, err = suite.conn.PingMonitor("charlieB") assert.Error(suite.T(), err) assert.Equal(suite.T(), reply, "") } func (suite *RadosTestSuite) TestWaitForLatestOSDMap() { suite.SetupConnection() err := suite.conn.WaitForLatestOSDMap() assert.NoError(suite.T(), err) } func (suite *RadosTestSuite) TestCreate() { suite.SetupConnection() err := suite.ioctx.Create("unique", CreateExclusive) assert.NoError(suite.T(), err) err = suite.ioctx.Create("unique", CreateExclusive) assert.Error(suite.T(), err) assert.Equal(suite.T(), err, ErrObjectExists) err = suite.ioctx.Create("unique", CreateIdempotent) assert.NoError(suite.T(), err) } func (suite *RadosTestSuite) TestReadWrite() { suite.SetupConnection() bytesIn := []byte("input data") err := suite.ioctx.Write("obj", bytesIn, 0) assert.NoError(suite.T(), err) bytesOut := make([]byte, len(bytesIn)) numOut, err := suite.ioctx.Read("obj", bytesOut, 0) assert.NoError(suite.T(), err) assert.Equal(suite.T(), numOut, len(bytesIn)) assert.Equal(suite.T(), bytesIn, bytesOut) bytesIn = []byte("input another data") err = suite.ioctx.WriteFull("obj", bytesIn) assert.NoError(suite.T(), err) bytesOut = make([]byte, len(bytesIn)) numOut, err = suite.ioctx.Read("obj", bytesOut, 0) assert.NoError(suite.T(), err) assert.Equal(suite.T(), numOut, len(bytesIn)) assert.Equal(suite.T(), bytesIn, bytesOut) } func (suite *RadosTestSuite) TestAppend() { suite.SetupConnection() mirror := []byte{} oid := suite.GenObjectName() for i := 0; i < 3; i++ { // append random bytes bytes := suite.RandomBytes(33) err := suite.ioctx.Append(oid, bytes) assert.NoError(suite.T(), err) // what the object should contain mirror = append(mirror, bytes...) // check object contains what we expect buf := make([]byte, len(mirror)) n, err := suite.ioctx.Read(oid, buf, 0) assert.NoError(suite.T(), err) assert.Equal(suite.T(), n, len(buf)) assert.Equal(suite.T(), buf, mirror) } } func (suite *RadosTestSuite) TestReadNotFound() { suite.SetupConnection() var bytes []byte oid := suite.GenObjectName() _, err := suite.ioctx.Read(oid, bytes, 0) assert.Equal(suite.T(), err, ErrNotFound) } func (suite *RadosTestSuite) TestDeleteNotFound() { suite.SetupConnection() oid := suite.GenObjectName() err := suite.ioctx.Delete(oid) assert.Equal(suite.T(), err, ErrNotFound) } func (suite *RadosTestSuite) TestStatNotFound() { suite.SetupConnection() oid := suite.GenObjectName() _, err := suite.ioctx.Stat(oid) assert.Equal(suite.T(), err, ErrNotFound) } func (suite *RadosTestSuite) TestObjectStat() { suite.SetupConnection() oid := suite.GenObjectName() bytes := suite.RandomBytes(234) err := suite.ioctx.Write(oid, bytes, 0) assert.NoError(suite.T(), err) stat, err := suite.ioctx.Stat(oid) assert.NoError(suite.T(), err) assert.Equal(suite.T(), uint64(len(bytes)), stat.Size) assert.NotNil(suite.T(), stat.ModTime) } func (suite *RadosTestSuite) TestGetPoolStats() { suite.SetupConnection() // grab current stats prevStat, err := suite.ioctx.GetPoolStats() fmt.Printf("prevStat: %+v\n", prevStat) assert.NoError(suite.T(), err) // make some changes to the cluster buf := make([]byte, 1<<20) for i := 0; i < 10; i++ { oid := suite.GenObjectName() err = suite.ioctx.Write(oid, buf, 0) assert.NoError(suite.T(), err) } // wait a while for the stats to change for i := 0; i < 30; i++ { stat, err := suite.ioctx.GetPoolStats() assert.NoError(suite.T(), err) // wait for something to change if stat == prevStat { fmt.Printf("curr_stat: %+v (trying again...)\n", stat) time.Sleep(time.Second) } else { // success fmt.Printf("curr_stat: %+v (change detected)\n", stat) return } } 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() name, err := suite.ioctx.GetPoolName() assert.NoError(suite.T(), err) assert.Equal(suite.T(), name, suite.pool) } func (suite *RadosTestSuite) TestObjectListObjects() { suite.SetupConnection() // objects currently in pool prevObjectList := []string{} err := suite.ioctx.ListObjects(func(oid string) { prevObjectList = append(prevObjectList, oid) }) assert.NoError(suite.T(), err) // create some objects createdList := []string{} for i := 0; i < 10; i++ { oid := suite.GenObjectName() bytes := []byte("input data") err := suite.ioctx.Write(oid, bytes, 0) assert.NoError(suite.T(), err) createdList = append(createdList, oid) } // join the lists of objects expectedObjectList := prevObjectList expectedObjectList = append(expectedObjectList, createdList...) // now list the current set of objects in the pool currObjectList := []string{} err = suite.ioctx.ListObjects(func(oid string) { currObjectList = append(currObjectList, oid) }) assert.NoError(suite.T(), err) // lists should be equal sort.Strings(currObjectList) sort.Strings(expectedObjectList) assert.Equal(suite.T(), currObjectList, expectedObjectList) } func (suite *RadosTestSuite) TestObjectIterator() { suite.SetupConnection() // current objs in default namespace prevObjectList := []string{} iter, err := suite.ioctx.Iter() assert.NoError(suite.T(), err) for iter.Next() { prevObjectList = append(prevObjectList, iter.Value()) } iter.Close() assert.NoError(suite.T(), iter.Err()) // create an object in a different namespace to verify that // iteration within a namespace does not return it suite.ioctx.SetNamespace("ns1") bytesIn := []byte("input data") err = suite.ioctx.Write(suite.GenObjectName(), bytesIn, 0) assert.NoError(suite.T(), err) // create some objects in default namespace suite.ioctx.SetNamespace("") createdList := []string{} for i := 0; i < 10; i++ { oid := suite.GenObjectName() bytesIn := []byte("input data") err = suite.ioctx.Write(oid, bytesIn, 0) assert.NoError(suite.T(), err) createdList = append(createdList, oid) } // prev list plus new oids expectedObjectList := prevObjectList expectedObjectList = append(expectedObjectList, createdList...) currObjectList := []string{} iter, err = suite.ioctx.Iter() assert.NoError(suite.T(), err) for iter.Next() { currObjectList = append(currObjectList, iter.Value()) } iter.Close() assert.NoError(suite.T(), iter.Err()) // curr list doesn't include the obj in ns1 sort.Strings(expectedObjectList) sort.Strings(currObjectList) assert.Equal(suite.T(), currObjectList, expectedObjectList) } func (suite *RadosTestSuite) TestObjectIteratorAcrossNamespaces() { suite.SetupConnection() // tests use a shared pool so namespaces need to be unique across tests. // below ns1=nsX and ns2=nsY. ns1 is used elsewhere. objectListNS1 := []string{} objectListNS2 := []string{} // populate list of current objects suite.ioctx.SetNamespace(AllNamespaces) existingList := []string{} iter, err := suite.ioctx.Iter() assert.NoError(suite.T(), err) for iter.Next() { existingList = append(existingList, iter.Value()) } iter.Close() assert.NoError(suite.T(), iter.Err()) // create some new objects in namespace: nsX createdList := []string{} suite.ioctx.SetNamespace("nsX") for i := 0; i < 10; i++ { oid := suite.GenObjectName() bytesIn := []byte("input data") err = suite.ioctx.Write(oid, bytesIn, 0) assert.NoError(suite.T(), err) createdList = append(createdList, oid) } assert.True(suite.T(), len(createdList) == 10) // create some new objects in namespace: nsY suite.ioctx.SetNamespace("nsY") for i := 0; i < 10; i++ { oid := suite.GenObjectName() bytesIn := []byte("input data") err = suite.ioctx.Write(oid, bytesIn, 0) assert.NoError(suite.T(), err) createdList = append(createdList, oid) } assert.True(suite.T(), len(createdList) == 20) suite.ioctx.SetNamespace(AllNamespaces) iter, err = suite.ioctx.Iter() assert.NoError(suite.T(), err) rogueList := []string{} for iter.Next() { if iter.Namespace() == "nsX" { objectListNS1 = append(objectListNS1, iter.Value()) } else if iter.Namespace() == "nsY" { objectListNS2 = append(objectListNS2, iter.Value()) } else { rogueList = append(rogueList, iter.Value()) } } iter.Close() assert.NoError(suite.T(), iter.Err()) assert.Equal(suite.T(), len(existingList), len(rogueList)) assert.Equal(suite.T(), len(objectListNS1), 10) assert.Equal(suite.T(), len(objectListNS2), 10) objectList := []string{} objectList = append(objectList, objectListNS1...) objectList = append(objectList, objectListNS2...) sort.Strings(objectList) sort.Strings(createdList) assert.Equal(suite.T(), objectList, createdList) sort.Strings(rogueList) sort.Strings(existingList) assert.Equal(suite.T(), rogueList, existingList) } func (suite *RadosTestSuite) TestNewConnWithUser() { _, err := NewConnWithUser("admin") assert.Equal(suite.T(), err, nil) } func (suite *RadosTestSuite) TestNewConnWithClusterAndUser() { _, err := NewConnWithClusterAndUser("ceph", "client.admin") assert.Equal(suite.T(), err, nil) } func (suite *RadosTestSuite) TestReadWriteXattr() { suite.SetupConnection() oid := suite.GenObjectName() val := []byte("value") err := suite.ioctx.SetXattr(oid, "key", val) assert.NoError(suite.T(), err) out := make([]byte, len(val)) n, err := suite.ioctx.GetXattr(oid, "key", out) assert.NoError(suite.T(), err) assert.Equal(suite.T(), n, len(out)) assert.Equal(suite.T(), out, val) } func (suite *RadosTestSuite) TestListXattrs() { suite.SetupConnection() oid := suite.GenObjectName() xattrs := make(map[string][]byte) val := []byte("value") for i := 0; i < 10; i++ { name := fmt.Sprintf("key_%d", i) err := suite.ioctx.SetXattr(oid, name, val) assert.NoError(suite.T(), err) xattrs[name] = val } out, err := suite.ioctx.ListXattrs(oid) assert.NoError(suite.T(), err) assert.Equal(suite.T(), xattrs, out) } func (suite *RadosTestSuite) TestRmXattr() { suite.SetupConnection() oid := suite.GenObjectName() // 2 xattrs xattrs := make(map[string][]byte) xattrs["key1"] = []byte("val") xattrs["key2"] = []byte("val") assert.Equal(suite.T(), len(xattrs), 2) // add them to the object for key, value := range xattrs { err := suite.ioctx.SetXattr(oid, key, value) assert.NoError(suite.T(), err) } out, err := suite.ioctx.ListXattrs(oid) assert.NoError(suite.T(), err) assert.Equal(suite.T(), len(out), 2) assert.Equal(suite.T(), out, xattrs) // remove key1 err = suite.ioctx.RmXattr(oid, "key1") assert.NoError(suite.T(), err) delete(xattrs, "key1") // verify key1 is gone out, err = suite.ioctx.ListXattrs(oid) assert.NoError(suite.T(), err) assert.Equal(suite.T(), len(out), 1) assert.Equal(suite.T(), out, xattrs) // remove key2 err = suite.ioctx.RmXattr(oid, "key2") assert.NoError(suite.T(), err) delete(xattrs, "key2") // verify key2 is gone out, err = suite.ioctx.ListXattrs(oid) assert.NoError(suite.T(), err) assert.Equal(suite.T(), len(out), 0) assert.Equal(suite.T(), out, xattrs) } func (suite *RadosTestSuite) TestReadWriteOmap() { suite.SetupConnection() // set some key/value pairs on an object orig := map[string][]byte{ "key1": []byte("value1"), "key2": []byte("value2"), "prefixed-key3": []byte("value3"), "null\x00key4": []byte("value4"), "empty": []byte(""), } oid := suite.GenObjectName() err := suite.ioctx.SetOmap(oid, orig) assert.NoError(suite.T(), err) // verify that they can all be read back remaining := map[string][]byte{} for k, v := range orig { remaining[k] = v } err = suite.ioctx.ListOmapValues(oid, "", "", 5, func(key string, value []byte) { assert.Equal(suite.T(), remaining[key], value) delete(remaining, key) }) assert.NoError(suite.T(), err) assert.Equal(suite.T(), len(remaining), 0) // Get (with a fixed number of keys) fetched, err := suite.ioctx.GetOmapValues(oid, "", "", 5) assert.NoError(suite.T(), err) assert.Equal(suite.T(), orig, fetched) // Get All (with an iterator size bigger than the map size) fetched, err = suite.ioctx.GetAllOmapValues(oid, "", "", 100) assert.NoError(suite.T(), err) assert.Equal(suite.T(), orig, fetched) // Get All (with an iterator size smaller than the map size) fetched, err = suite.ioctx.GetAllOmapValues(oid, "", "", 2) assert.NoError(suite.T(), err) assert.Equal(suite.T(), orig, fetched) // Remove err = suite.ioctx.RmOmapKeys(oid, []string{"key1", "prefixed-key3", "null\x00key4"}) assert.NoError(suite.T(), err) fetched, err = suite.ioctx.GetOmapValues(oid, "", "", 5) assert.NoError(suite.T(), err) assert.Equal(suite.T(), map[string][]byte{ "key2": []byte("value2"), "empty": []byte(""), }, fetched) // Clear err = suite.ioctx.CleanOmap(oid) assert.NoError(suite.T(), err) fetched, err = suite.ioctx.GetOmapValues(oid, "", "", 4) assert.NoError(suite.T(), err) assert.Equal(suite.T(), map[string][]byte{}, fetched) } func (suite *RadosTestSuite) TestReadFilterOmap() { suite.SetupConnection() orig := map[string][]byte{ "key1": []byte("value1"), "prefixed-key3": []byte("value3"), "key2": []byte("value2"), } oid := suite.GenObjectName() err := suite.ioctx.SetOmap(oid, orig) assert.NoError(suite.T(), err) // filter by prefix fetched, err := suite.ioctx.GetOmapValues(oid, "", "prefixed", 4) assert.NoError(suite.T(), err) assert.Equal(suite.T(), map[string][]byte{ "prefixed-key3": []byte("value3"), }, fetched) // "start_after" a key fetched, err = suite.ioctx.GetOmapValues(oid, "key1", "", 4) assert.NoError(suite.T(), err) assert.Equal(suite.T(), map[string][]byte{ "prefixed-key3": []byte("value3"), "key2": []byte("value2"), }, fetched) // maxReturn fetched, err = suite.ioctx.GetOmapValues(oid, "", "key", 1) assert.NoError(suite.T(), err) assert.Equal(suite.T(), map[string][]byte{ "key1": []byte("value1"), }, fetched) } func (suite *RadosTestSuite) TestSetNamespace() { suite.SetupConnection() // create oid oid := suite.GenObjectName() bytesIn := []byte("input data") err := suite.ioctx.Write(oid, bytesIn, 0) assert.NoError(suite.T(), err) stat, err := suite.ioctx.Stat(oid) assert.NoError(suite.T(), err) assert.Equal(suite.T(), uint64(len(bytesIn)), stat.Size) assert.NotNil(suite.T(), stat.ModTime) // oid isn't seen in space1 ns suite.ioctx.SetNamespace("space1") stat, err = suite.ioctx.Stat(oid) assert.Equal(suite.T(), err, ErrNotFound) // create oid2 in space1 ns oid2 := suite.GenObjectName() bytesIn = []byte("input data") err = suite.ioctx.Write(oid2, bytesIn, 0) assert.NoError(suite.T(), err) suite.ioctx.SetNamespace("") stat, err = suite.ioctx.Stat(oid2) assert.Equal(suite.T(), err, ErrNotFound) stat, err = suite.ioctx.Stat(oid) assert.NoError(suite.T(), err) assert.Equal(suite.T(), uint64(len(bytesIn)), stat.Size) assert.NotNil(suite.T(), stat.ModTime) } func (suite *RadosTestSuite) TestListAcrossNamespaces() { suite.SetupConnection() // count objects in pool origObjects := 0 err := suite.ioctx.ListObjects(func(string) { origObjects++ }) assert.NoError(suite.T(), err) // create oid oid := suite.GenObjectName() bytesIn := []byte("input data") err = suite.ioctx.Write(oid, bytesIn, 0) assert.NoError(suite.T(), err) // create oid2 in space1 ns suite.ioctx.SetNamespace("space1") oid2 := suite.GenObjectName() bytesIn = []byte("input data") err = suite.ioctx.Write(oid2, bytesIn, 0) assert.NoError(suite.T(), err) // count objects in space1 ns nsFoundObjects := 0 err = suite.ioctx.ListObjects(func(string) { nsFoundObjects++ }) assert.NoError(suite.T(), err) assert.EqualValues(suite.T(), 1, nsFoundObjects) // count objects in pool suite.ioctx.SetNamespace(AllNamespaces) allFoundObjects := 0 err = suite.ioctx.ListObjects(func(_ string) { allFoundObjects++ }) assert.NoError(suite.T(), err) assert.EqualValues(suite.T(), (origObjects + 2), allFoundObjects) } func (suite *RadosTestSuite) TestLocking() { suite.SetupConnection() oid := suite.GenObjectName() // lock ex res, err := suite.ioctx.LockExclusive(oid, "myLock", "myCookie", "this is a test lock", 0, nil) assert.NoError(suite.T(), err) assert.Equal(suite.T(), 0, res) // verify lock ex info, err := suite.ioctx.ListLockers(oid, "myLock") assert.NoError(suite.T(), err) assert.Equal(suite.T(), 1, len(info.Clients)) assert.Equal(suite.T(), true, info.Exclusive) // fail to lock ex again res, err = suite.ioctx.LockExclusive(oid, "myLock", "myCookie", "this is a description", 0, nil) assert.NoError(suite.T(), err) assert.Equal(suite.T(), -17, res) // fail to lock sh res, err = suite.ioctx.LockShared(oid, "myLock", "myCookie", "", "a description", 0, nil) assert.NoError(suite.T(), err) assert.Equal(suite.T(), -17, res) // unlock res, err = suite.ioctx.Unlock(oid, "myLock", "myCookie") assert.NoError(suite.T(), err) assert.Equal(suite.T(), 0, res) // verify unlock info, err = suite.ioctx.ListLockers(oid, "myLock") assert.NoError(suite.T(), err) assert.Equal(suite.T(), 0, len(info.Clients)) // lock sh res, err = suite.ioctx.LockShared(oid, "myLock", "myCookie", "", "a description", 0, nil) assert.NoError(suite.T(), err) assert.Equal(suite.T(), 0, res) // verify lock sh info, err = suite.ioctx.ListLockers(oid, "myLock") assert.NoError(suite.T(), err) assert.Equal(suite.T(), 1, len(info.Clients)) assert.Equal(suite.T(), false, info.Exclusive) // fail to lock sh again res, err = suite.ioctx.LockExclusive(oid, "myLock", "myCookie", "a description", 0, nil) assert.NoError(suite.T(), err) assert.Equal(suite.T(), -17, res) // fail to lock ex res, err = suite.ioctx.LockExclusive(oid, "myLock", "myCookie", "this is a test lock", 0, nil) assert.NoError(suite.T(), err) assert.Equal(suite.T(), res, -17) // break the lock res, err = suite.ioctx.BreakLock(oid, "myLock", info.Clients[0], "myCookie") assert.NoError(suite.T(), err) assert.Equal(suite.T(), 0, res) // verify lock broken info, err = suite.ioctx.ListLockers(oid, "myLock") assert.NoError(suite.T(), err) assert.Equal(suite.T(), 0, len(info.Clients)) // lock sh with duration res, err = suite.ioctx.LockShared(oid, "myLock", "myCookie", "", "a description", time.Millisecond, nil) assert.NoError(suite.T(), err) assert.Equal(suite.T(), 0, res) // verify lock sh expired time.Sleep(time.Second) info, err = suite.ioctx.ListLockers(oid, "myLock") assert.NoError(suite.T(), err) assert.Equal(suite.T(), 0, len(info.Clients)) // lock sh with duration res, err = suite.ioctx.LockExclusive(oid, "myLock", "myCookie", "a description", time.Millisecond, nil) assert.NoError(suite.T(), err) assert.Equal(suite.T(), 0, res) // verify lock sh expired time.Sleep(time.Second) info, err = suite.ioctx.ListLockers(oid, "myLock") assert.NoError(suite.T(), err) assert.Equal(suite.T(), 0, len(info.Clients)) } func (suite *RadosTestSuite) TestOmapOnNonexistentObjectError() { suite.SetupConnection() oid := suite.GenObjectName() _, err := suite.ioctx.GetAllOmapValues(oid, "", "", 100) assert.Equal(suite.T(), err, ErrNotFound) } func (suite *RadosTestSuite) TestOpenIOContextInvalidPool() { ioctx, err := suite.conn.OpenIOContext("cmartel") require.Error(suite.T(), err) require.Nil(suite.T(), ioctx) } func (suite *RadosTestSuite) TestGetLastVersion() { suite.SetupConnection() // NOTE: Reusing the ioctx set up by SetupConnection seems to make this // test flakey. This is possibly be due to the value being "global" to the // ioctx. Thus we create a new ioctx for the subtests. suite.T().Run("write", func(t *testing.T) { ioctx, err := suite.conn.OpenIOContext(suite.pool) require.NoError(suite.T(), err) oid := suite.GenObjectName() defer func(oid string) { assert.NoError(t, ioctx.Delete(oid)) }(oid) v1, _ := ioctx.GetLastVersion() err = ioctx.Write(oid, []byte("something to write"), 0) assert.NoError(t, err) v2, _ := ioctx.GetLastVersion() assert.NotEqual(t, v1, v2) v3, _ := ioctx.GetLastVersion() assert.Equal(t, v2, v3) err = ioctx.Write(oid, []byte("something completely different"), 0) assert.NoError(t, err) v4, _ := ioctx.GetLastVersion() assert.NotEqual(t, v1, v4) assert.NotEqual(t, v2, v4) assert.NotEqual(t, v3, v4) }) suite.T().Run("writeAndRead", func(t *testing.T) { ioctx, err := suite.conn.OpenIOContext(suite.pool) require.NoError(suite.T(), err) oid := suite.GenObjectName() defer func(oid string) { assert.NoError(t, ioctx.Delete(oid)) }(oid) v1, _ := ioctx.GetLastVersion() err = ioctx.Write(oid, []byte("presto"), 0) assert.NoError(t, err) v2, _ := ioctx.GetLastVersion() assert.NotEqual(t, v1, v2) bytes := make([]byte, 1024) _, err = ioctx.Read(oid, bytes, 0) assert.NoError(t, err) v3, _ := ioctx.GetLastVersion() assert.Equal(t, v2, v3) err = ioctx.Write(oid, []byte("abracadabra"), 0) assert.NoError(t, err) v4, _ := ioctx.GetLastVersion() assert.NotEqual(t, v1, v4) assert.NotEqual(t, v2, v4) assert.NotEqual(t, v3, v4) _, err = ioctx.Read(oid, bytes, 0) assert.NoError(t, err) v5, _ := ioctx.GetLastVersion() assert.Equal(t, v4, v5) }) suite.T().Run("writeAndReadMultiple", func(t *testing.T) { ioctx, err := suite.conn.OpenIOContext(suite.pool) require.NoError(suite.T(), err) oids := make([]string, 5) vers := make([]uint64, 5) for i := 0; i < 5; i++ { oid := suite.GenObjectName() defer func(oid string) { assert.NoError(t, ioctx.Delete(oid)) }(oid) err = ioctx.Write(oid, []byte(oid), 0) assert.NoError(t, err) oids[i] = oid vers[i], _ = ioctx.GetLastVersion() } var v uint64 bytes := make([]byte, 1024) _, err = ioctx.Read(oids[0], bytes, 0) assert.NoError(t, err) v, _ = ioctx.GetLastVersion() assert.Equal(t, vers[0], v) _, err = ioctx.Read(oids[4], bytes, 0) assert.NoError(t, err) v, _ = ioctx.GetLastVersion() assert.Equal(t, vers[4], v) _, err = ioctx.Read(oids[1], bytes, 0) assert.NoError(t, err) v, _ = ioctx.GetLastVersion() assert.Equal(t, vers[1], v) }) suite.T().Run("invalidIOContext", func(t *testing.T) { ioctx := &IOContext{} _, err := ioctx.GetLastVersion() assert.Error(t, err) }) } func (suite *RadosTestSuite) TestSetGetNamespace() { suite.SetupConnection() suite.T().Run("validNS", func(t *testing.T) { suite.ioctx.SetNamespace("space1") ns, err := suite.ioctx.GetNamespace() assert.NoError(t, err) assert.Equal(t, "space1", ns) }) suite.T().Run("allNamespaces", func(_ *testing.T) { suite.ioctx.SetNamespace(AllNamespaces) ns, err := suite.ioctx.GetNamespace() assert.NoError(suite.T(), err) assert.Equal(suite.T(), AllNamespaces, ns) }) suite.T().Run("invalidIoctx", func(_ *testing.T) { i := &IOContext{} ns, err := i.GetNamespace() assert.Error(suite.T(), err) assert.Equal(suite.T(), "", ns) }) } func TestRadosTestSuite(t *testing.T) { tsuite.Run(t, new(RadosTestSuite)) }