mirror of
https://github.com/ceph/go-ceph
synced 2024-12-25 23:52:27 +00:00
Merge pull request #10 from giganteous/gofmt
style: run gofmt on all sources Signed-off-by: Noah Watkins <noahwatkins@gmail.com>
This commit is contained in:
commit
e703215356
4
doc.go
4
doc.go
@ -4,6 +4,6 @@ Set of wrappers around Ceph APIs.
|
||||
package rados
|
||||
|
||||
import (
|
||||
_ "github.com/noahdesu/go-ceph/rados"
|
||||
_ "github.com/noahdesu/go-ceph/rbd"
|
||||
_ "github.com/noahdesu/go-ceph/rados"
|
||||
_ "github.com/noahdesu/go-ceph/rbd"
|
||||
)
|
||||
|
@ -1,12 +1,12 @@
|
||||
package rados
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestImports(t *testing.T) {
|
||||
if assert.Equal(t, 1, 1) != true {
|
||||
t.Error("Something is wrong.")
|
||||
}
|
||||
if assert.Equal(t, 1, 1) != true {
|
||||
t.Error("Something is wrong.")
|
||||
}
|
||||
}
|
||||
|
318
rados/conn.go
318
rados/conn.go
@ -10,254 +10,254 @@ import "bytes"
|
||||
|
||||
// ClusterStat represents Ceph cluster statistics.
|
||||
type ClusterStat struct {
|
||||
Kb uint64
|
||||
Kb_used uint64
|
||||
Kb_avail uint64
|
||||
Num_objects uint64
|
||||
Kb uint64
|
||||
Kb_used uint64
|
||||
Kb_avail uint64
|
||||
Num_objects uint64
|
||||
}
|
||||
|
||||
// Conn is a connection handle to a Ceph cluster.
|
||||
type Conn struct {
|
||||
cluster C.rados_t
|
||||
cluster C.rados_t
|
||||
}
|
||||
|
||||
// PingMonitor sends a ping to a monitor and returns the reply.
|
||||
func (c *Conn) PingMonitor(id string) (string, error) {
|
||||
c_id := C.CString(id)
|
||||
defer C.free(unsafe.Pointer(c_id))
|
||||
c_id := C.CString(id)
|
||||
defer C.free(unsafe.Pointer(c_id))
|
||||
|
||||
var strlen C.size_t
|
||||
var strout *C.char
|
||||
var strlen C.size_t
|
||||
var strout *C.char
|
||||
|
||||
ret := C.rados_ping_monitor(c.cluster, c_id, &strout, &strlen)
|
||||
defer C.rados_buffer_free(strout)
|
||||
ret := C.rados_ping_monitor(c.cluster, c_id, &strout, &strlen)
|
||||
defer C.rados_buffer_free(strout)
|
||||
|
||||
if ret == 0 {
|
||||
reply := C.GoStringN(strout, (C.int)(strlen))
|
||||
return reply, nil
|
||||
} else {
|
||||
return "", RadosError(int(ret))
|
||||
}
|
||||
if ret == 0 {
|
||||
reply := C.GoStringN(strout, (C.int)(strlen))
|
||||
return reply, nil
|
||||
} else {
|
||||
return "", RadosError(int(ret))
|
||||
}
|
||||
}
|
||||
|
||||
// Connect establishes a connection to a RADOS cluster. It returns an error,
|
||||
// if any.
|
||||
func (c *Conn) Connect() error {
|
||||
ret := C.rados_connect(c.cluster)
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(int(ret))
|
||||
}
|
||||
ret := C.rados_connect(c.cluster)
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(int(ret))
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown disconnects from the cluster.
|
||||
func (c *Conn) Shutdown() {
|
||||
C.rados_shutdown(c.cluster)
|
||||
C.rados_shutdown(c.cluster)
|
||||
}
|
||||
|
||||
// ReadConfigFile configures the connection using a Ceph configuration file.
|
||||
func (c *Conn) ReadConfigFile(path string) error {
|
||||
c_path := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(c_path))
|
||||
ret := C.rados_conf_read_file(c.cluster, c_path)
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(int(ret))
|
||||
}
|
||||
c_path := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(c_path))
|
||||
ret := C.rados_conf_read_file(c.cluster, c_path)
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(int(ret))
|
||||
}
|
||||
}
|
||||
|
||||
// ReadDefaultConfigFile configures the connection using a Ceph configuration
|
||||
// file located at default locations.
|
||||
func (c *Conn) ReadDefaultConfigFile() error {
|
||||
ret := C.rados_conf_read_file(c.cluster, nil)
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(int(ret))
|
||||
}
|
||||
ret := C.rados_conf_read_file(c.cluster, nil)
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(int(ret))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) OpenIOContext(pool string) (*IOContext, error) {
|
||||
c_pool := C.CString(pool)
|
||||
defer C.free(unsafe.Pointer(c_pool))
|
||||
ioctx := &IOContext{}
|
||||
ret := C.rados_ioctx_create(c.cluster, c_pool, &ioctx.ioctx)
|
||||
if ret == 0 {
|
||||
return ioctx, nil
|
||||
} else {
|
||||
return nil, RadosError(int(ret))
|
||||
}
|
||||
c_pool := C.CString(pool)
|
||||
defer C.free(unsafe.Pointer(c_pool))
|
||||
ioctx := &IOContext{}
|
||||
ret := C.rados_ioctx_create(c.cluster, c_pool, &ioctx.ioctx)
|
||||
if ret == 0 {
|
||||
return ioctx, nil
|
||||
} else {
|
||||
return nil, RadosError(int(ret))
|
||||
}
|
||||
}
|
||||
|
||||
// ListPools returns the names of all existing pools.
|
||||
func (c *Conn) ListPools() (names []string, err error) {
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
ret := int(C.rados_pool_list(c.cluster,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
|
||||
if ret < 0 {
|
||||
return nil, RadosError(int(ret))
|
||||
}
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
ret := int(C.rados_pool_list(c.cluster,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
|
||||
if ret < 0 {
|
||||
return nil, RadosError(int(ret))
|
||||
}
|
||||
|
||||
if ret > len(buf) {
|
||||
buf = make([]byte, ret)
|
||||
continue
|
||||
}
|
||||
if ret > len(buf) {
|
||||
buf = make([]byte, ret)
|
||||
continue
|
||||
}
|
||||
|
||||
tmp := bytes.SplitAfter(buf[:ret-1], []byte{0})
|
||||
for _, s := range tmp {
|
||||
if len(s) > 0 {
|
||||
name := C.GoString((*C.char)(unsafe.Pointer(&s[0])))
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
tmp := bytes.SplitAfter(buf[:ret-1], []byte{0})
|
||||
for _, s := range tmp {
|
||||
if len(s) > 0 {
|
||||
name := C.GoString((*C.char)(unsafe.Pointer(&s[0])))
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
|
||||
return names, nil
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
}
|
||||
|
||||
// SetConfigOption sets the value of the configuration option identified by
|
||||
// the given name.
|
||||
func (c *Conn) SetConfigOption(option, value string) error {
|
||||
c_opt, c_val := C.CString(option), C.CString(value)
|
||||
defer C.free(unsafe.Pointer(c_opt))
|
||||
defer C.free(unsafe.Pointer(c_val))
|
||||
ret := C.rados_conf_set(c.cluster, c_opt, c_val)
|
||||
if ret < 0 {
|
||||
return RadosError(int(ret))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
c_opt, c_val := C.CString(option), C.CString(value)
|
||||
defer C.free(unsafe.Pointer(c_opt))
|
||||
defer C.free(unsafe.Pointer(c_val))
|
||||
ret := C.rados_conf_set(c.cluster, c_opt, c_val)
|
||||
if ret < 0 {
|
||||
return RadosError(int(ret))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetConfigOption returns the value of the Ceph configuration option
|
||||
// identified by the given name.
|
||||
func (c *Conn) GetConfigOption(name string) (value string, err error) {
|
||||
buf := make([]byte, 4096)
|
||||
c_name := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(c_name))
|
||||
ret := int(C.rados_conf_get(c.cluster, c_name,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
|
||||
// FIXME: ret may be -ENAMETOOLONG if the buffer is not large enough. We
|
||||
// can handle this case, but we need a reliable way to test for
|
||||
// -ENAMETOOLONG constant. Will the syscall/Errno stuff in Go help?
|
||||
if ret == 0 {
|
||||
value = C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
|
||||
return value, nil
|
||||
} else {
|
||||
return "", RadosError(ret)
|
||||
}
|
||||
buf := make([]byte, 4096)
|
||||
c_name := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(c_name))
|
||||
ret := int(C.rados_conf_get(c.cluster, c_name,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
|
||||
// FIXME: ret may be -ENAMETOOLONG if the buffer is not large enough. We
|
||||
// can handle this case, but we need a reliable way to test for
|
||||
// -ENAMETOOLONG constant. Will the syscall/Errno stuff in Go help?
|
||||
if ret == 0 {
|
||||
value = C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
|
||||
return value, nil
|
||||
} else {
|
||||
return "", RadosError(ret)
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForLatestOSDMap blocks the caller until the latest OSD map has been
|
||||
// retrieved.
|
||||
func (c *Conn) WaitForLatestOSDMap() error {
|
||||
ret := C.rados_wait_for_latest_osdmap(c.cluster)
|
||||
if ret < 0 {
|
||||
return RadosError(int(ret))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
ret := C.rados_wait_for_latest_osdmap(c.cluster)
|
||||
if ret < 0 {
|
||||
return RadosError(int(ret))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetClusterStat returns statistics about the cluster associated with the
|
||||
// connection.
|
||||
func (c *Conn) GetClusterStats() (stat ClusterStat, err error) {
|
||||
c_stat := C.struct_rados_cluster_stat_t{}
|
||||
ret := C.rados_cluster_stat(c.cluster, &c_stat)
|
||||
if ret < 0 {
|
||||
return ClusterStat{}, RadosError(int(ret))
|
||||
} else {
|
||||
return ClusterStat{
|
||||
Kb: uint64(c_stat.kb),
|
||||
Kb_used: uint64(c_stat.kb_used),
|
||||
Kb_avail: uint64(c_stat.kb_avail),
|
||||
Num_objects: uint64(c_stat.num_objects),
|
||||
}, nil
|
||||
}
|
||||
c_stat := C.struct_rados_cluster_stat_t{}
|
||||
ret := C.rados_cluster_stat(c.cluster, &c_stat)
|
||||
if ret < 0 {
|
||||
return ClusterStat{}, RadosError(int(ret))
|
||||
} else {
|
||||
return ClusterStat{
|
||||
Kb: uint64(c_stat.kb),
|
||||
Kb_used: uint64(c_stat.kb_used),
|
||||
Kb_avail: uint64(c_stat.kb_avail),
|
||||
Num_objects: uint64(c_stat.num_objects),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ParseCmdLineArgs configures the connection from command line arguments.
|
||||
func (c *Conn) ParseCmdLineArgs(args []string) error {
|
||||
// add an empty element 0 -- Ceph treats the array as the actual contents
|
||||
// of argv and skips the first element (the executable name)
|
||||
argc := C.int(len(args) + 1)
|
||||
argv := make([]*C.char, argc)
|
||||
// add an empty element 0 -- Ceph treats the array as the actual contents
|
||||
// of argv and skips the first element (the executable name)
|
||||
argc := C.int(len(args) + 1)
|
||||
argv := make([]*C.char, argc)
|
||||
|
||||
// make the first element a string just in case it is ever examined
|
||||
argv[0] = C.CString("placeholder")
|
||||
defer C.free(unsafe.Pointer(argv[0]))
|
||||
// make the first element a string just in case it is ever examined
|
||||
argv[0] = C.CString("placeholder")
|
||||
defer C.free(unsafe.Pointer(argv[0]))
|
||||
|
||||
for i, arg := range args {
|
||||
argv[i+1] = C.CString(arg)
|
||||
defer C.free(unsafe.Pointer(argv[i+1]))
|
||||
}
|
||||
for i, arg := range args {
|
||||
argv[i+1] = C.CString(arg)
|
||||
defer C.free(unsafe.Pointer(argv[i+1]))
|
||||
}
|
||||
|
||||
ret := C.rados_conf_parse_argv(c.cluster, argc, &argv[0])
|
||||
if ret < 0 {
|
||||
return RadosError(int(ret))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
ret := C.rados_conf_parse_argv(c.cluster, argc, &argv[0])
|
||||
if ret < 0 {
|
||||
return RadosError(int(ret))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ParseDefaultConfigEnv configures the connection from the default Ceph
|
||||
// environment variable(s).
|
||||
func (c *Conn) ParseDefaultConfigEnv() error {
|
||||
ret := C.rados_conf_parse_env(c.cluster, nil)
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(int(ret))
|
||||
}
|
||||
ret := C.rados_conf_parse_env(c.cluster, nil)
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(int(ret))
|
||||
}
|
||||
}
|
||||
|
||||
// GetFSID returns the fsid of the cluster as a hexadecimal string. The fsid
|
||||
// is a unique identifier of an entire Ceph cluster.
|
||||
func (c *Conn) GetFSID() (fsid string, err error) {
|
||||
buf := make([]byte, 37)
|
||||
ret := int(C.rados_cluster_fsid(c.cluster,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
|
||||
// FIXME: the success case isn't documented correctly in librados.h
|
||||
if ret == 36 {
|
||||
fsid = C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
|
||||
return fsid, nil
|
||||
} else {
|
||||
return "", RadosError(int(ret))
|
||||
}
|
||||
buf := make([]byte, 37)
|
||||
ret := int(C.rados_cluster_fsid(c.cluster,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))))
|
||||
// FIXME: the success case isn't documented correctly in librados.h
|
||||
if ret == 36 {
|
||||
fsid = C.GoString((*C.char)(unsafe.Pointer(&buf[0])))
|
||||
return fsid, nil
|
||||
} else {
|
||||
return "", RadosError(int(ret))
|
||||
}
|
||||
}
|
||||
|
||||
// GetInstanceID returns a globally unique identifier for the cluster
|
||||
// connection instance.
|
||||
func (c *Conn) GetInstanceID() uint64 {
|
||||
// FIXME: are there any error cases for this?
|
||||
return uint64(C.rados_get_instance_id(c.cluster))
|
||||
// FIXME: are there any error cases for this?
|
||||
return uint64(C.rados_get_instance_id(c.cluster))
|
||||
}
|
||||
|
||||
// MakePool creates a new pool with default settings.
|
||||
func (c *Conn) MakePool(name string) error {
|
||||
c_name :=C.CString(name)
|
||||
defer C.free(unsafe.Pointer(c_name))
|
||||
ret := int(C.rados_pool_create(c.cluster, c_name))
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(ret)
|
||||
}
|
||||
c_name := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(c_name))
|
||||
ret := int(C.rados_pool_create(c.cluster, c_name))
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(ret)
|
||||
}
|
||||
}
|
||||
|
||||
// DeletePool deletes a pool and all the data inside the pool.
|
||||
func (c *Conn) DeletePool(name string) error {
|
||||
c_name :=C.CString(name)
|
||||
defer C.free(unsafe.Pointer(c_name))
|
||||
ret := int(C.rados_pool_delete(c.cluster, c_name))
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(ret)
|
||||
}
|
||||
c_name := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(c_name))
|
||||
ret := int(C.rados_pool_delete(c.cluster, c_name))
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(ret)
|
||||
}
|
||||
}
|
||||
|
||||
// MonCommand sends a command to one of the monitors
|
||||
|
232
rados/ioctx.go
232
rados/ioctx.go
@ -9,92 +9,92 @@ import "unsafe"
|
||||
|
||||
// PoolStat represents Ceph pool statistics.
|
||||
type PoolStat struct {
|
||||
// space used in bytes
|
||||
Num_bytes uint64
|
||||
// space used in KB
|
||||
Num_kb uint64
|
||||
// number of objects in the pool
|
||||
Num_objects uint64
|
||||
// number of clones of objects
|
||||
Num_object_clones uint64
|
||||
// num_objects * num_replicas
|
||||
Num_object_copies uint64
|
||||
Num_objects_missing_on_primary uint64
|
||||
// number of objects found on no OSDs
|
||||
Num_objects_unfound uint64
|
||||
// number of objects replicated fewer times than they should be
|
||||
// (but found on at least one OSD)
|
||||
Num_objects_degraded uint64
|
||||
Num_rd uint64
|
||||
Num_rd_kb uint64
|
||||
Num_wr uint64
|
||||
Num_wr_kb uint64
|
||||
// space used in bytes
|
||||
Num_bytes uint64
|
||||
// space used in KB
|
||||
Num_kb uint64
|
||||
// number of objects in the pool
|
||||
Num_objects uint64
|
||||
// number of clones of objects
|
||||
Num_object_clones uint64
|
||||
// num_objects * num_replicas
|
||||
Num_object_copies uint64
|
||||
Num_objects_missing_on_primary uint64
|
||||
// number of objects found on no OSDs
|
||||
Num_objects_unfound uint64
|
||||
// number of objects replicated fewer times than they should be
|
||||
// (but found on at least one OSD)
|
||||
Num_objects_degraded uint64
|
||||
Num_rd uint64
|
||||
Num_rd_kb uint64
|
||||
Num_wr uint64
|
||||
Num_wr_kb uint64
|
||||
}
|
||||
|
||||
// IOContext represents a context for performing I/O within a pool.
|
||||
type IOContext struct {
|
||||
ioctx C.rados_ioctx_t
|
||||
ioctx C.rados_ioctx_t
|
||||
}
|
||||
|
||||
// Pointer returns a uintptr representation of the IOContext.
|
||||
func (ioctx *IOContext) Pointer() uintptr {
|
||||
return uintptr(ioctx.ioctx)
|
||||
return uintptr(ioctx.ioctx)
|
||||
}
|
||||
|
||||
// Write writes len(data) bytes to the object with key oid starting at byte
|
||||
// offset offset. It returns an error, if any.
|
||||
func (ioctx *IOContext) Write(oid string, data []byte, offset uint64) error {
|
||||
c_oid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(c_oid))
|
||||
c_oid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(c_oid))
|
||||
|
||||
ret := C.rados_write(ioctx.ioctx, c_oid,
|
||||
(*C.char)(unsafe.Pointer(&data[0])),
|
||||
(C.size_t)(len(data)),
|
||||
(C.uint64_t)(offset))
|
||||
ret := C.rados_write(ioctx.ioctx, c_oid,
|
||||
(*C.char)(unsafe.Pointer(&data[0])),
|
||||
(C.size_t)(len(data)),
|
||||
(C.uint64_t)(offset))
|
||||
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(int(ret))
|
||||
}
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(int(ret))
|
||||
}
|
||||
}
|
||||
|
||||
// Read reads up to len(data) bytes from the object with key oid starting at byte
|
||||
// offset offset. It returns the number of bytes read and an error, if any.
|
||||
func (ioctx *IOContext) Read(oid string, data []byte, offset uint64) (int, error) {
|
||||
if len(data) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
c_oid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(c_oid))
|
||||
c_oid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(c_oid))
|
||||
|
||||
ret := C.rados_read(
|
||||
ioctx.ioctx,
|
||||
c_oid,
|
||||
(*C.char)(unsafe.Pointer(&data[0])),
|
||||
(C.size_t)(len(data)),
|
||||
(C.uint64_t)(offset))
|
||||
ret := C.rados_read(
|
||||
ioctx.ioctx,
|
||||
c_oid,
|
||||
(*C.char)(unsafe.Pointer(&data[0])),
|
||||
(C.size_t)(len(data)),
|
||||
(C.uint64_t)(offset))
|
||||
|
||||
if ret >= 0 {
|
||||
return int(ret), nil
|
||||
} else {
|
||||
return 0, RadosError(int(ret))
|
||||
}
|
||||
if ret >= 0 {
|
||||
return int(ret), nil
|
||||
} else {
|
||||
return 0, RadosError(int(ret))
|
||||
}
|
||||
}
|
||||
|
||||
// Delete deletes the object with key oid. It returns an error, if any.
|
||||
func (ioctx *IOContext) Delete(oid string) error {
|
||||
c_oid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(c_oid))
|
||||
c_oid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(c_oid))
|
||||
|
||||
ret := C.rados_remove(ioctx.ioctx, c_oid)
|
||||
ret := C.rados_remove(ioctx.ioctx, c_oid)
|
||||
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(int(ret))
|
||||
}
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(int(ret))
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate resizes the object with key oid to size size. If the operation
|
||||
@ -102,65 +102,65 @@ func (ioctx *IOContext) Delete(oid string) error {
|
||||
// operation shrinks the object, the excess data is removed. It returns an
|
||||
// error, if any.
|
||||
func (ioctx *IOContext) Truncate(oid string, size uint64) error {
|
||||
c_oid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(c_oid))
|
||||
c_oid := C.CString(oid)
|
||||
defer C.free(unsafe.Pointer(c_oid))
|
||||
|
||||
ret := C.rados_trunc(ioctx.ioctx, c_oid, (C.uint64_t)(size))
|
||||
ret := C.rados_trunc(ioctx.ioctx, c_oid, (C.uint64_t)(size))
|
||||
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(int(ret))
|
||||
}
|
||||
if ret == 0 {
|
||||
return nil
|
||||
} else {
|
||||
return RadosError(int(ret))
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy informs librados that the I/O context is no longer in use.
|
||||
// Resources associated with the context may not be freed immediately, and the
|
||||
// context should not be used again after calling this method.
|
||||
func (ioctx *IOContext) Destroy() {
|
||||
C.rados_ioctx_destroy(ioctx.ioctx)
|
||||
C.rados_ioctx_destroy(ioctx.ioctx)
|
||||
}
|
||||
|
||||
// Stat returns a set of statistics about the pool associated with this I/O
|
||||
// context.
|
||||
func (ioctx *IOContext) GetPoolStats() (stat PoolStat, err error) {
|
||||
c_stat := C.struct_rados_pool_stat_t{}
|
||||
ret := C.rados_ioctx_pool_stat(ioctx.ioctx, &c_stat)
|
||||
if ret < 0 {
|
||||
return PoolStat{}, RadosError(int(ret))
|
||||
} else {
|
||||
return PoolStat{
|
||||
Num_bytes: uint64(c_stat.num_bytes),
|
||||
Num_kb: uint64(c_stat.num_kb),
|
||||
Num_objects: uint64(c_stat.num_objects),
|
||||
Num_object_clones: uint64(c_stat.num_object_clones),
|
||||
Num_object_copies: uint64(c_stat.num_object_copies),
|
||||
Num_objects_missing_on_primary: uint64(c_stat.num_objects_missing_on_primary),
|
||||
Num_objects_unfound: uint64(c_stat.num_objects_unfound),
|
||||
Num_objects_degraded: uint64(c_stat.num_objects_degraded),
|
||||
Num_rd: uint64(c_stat.num_rd),
|
||||
Num_rd_kb: uint64(c_stat.num_rd_kb),
|
||||
Num_wr: uint64(c_stat.num_wr),
|
||||
Num_wr_kb: uint64(c_stat.num_wr_kb),
|
||||
}, nil
|
||||
}
|
||||
c_stat := C.struct_rados_pool_stat_t{}
|
||||
ret := C.rados_ioctx_pool_stat(ioctx.ioctx, &c_stat)
|
||||
if ret < 0 {
|
||||
return PoolStat{}, RadosError(int(ret))
|
||||
} else {
|
||||
return PoolStat{
|
||||
Num_bytes: uint64(c_stat.num_bytes),
|
||||
Num_kb: uint64(c_stat.num_kb),
|
||||
Num_objects: uint64(c_stat.num_objects),
|
||||
Num_object_clones: uint64(c_stat.num_object_clones),
|
||||
Num_object_copies: uint64(c_stat.num_object_copies),
|
||||
Num_objects_missing_on_primary: uint64(c_stat.num_objects_missing_on_primary),
|
||||
Num_objects_unfound: uint64(c_stat.num_objects_unfound),
|
||||
Num_objects_degraded: uint64(c_stat.num_objects_degraded),
|
||||
Num_rd: uint64(c_stat.num_rd),
|
||||
Num_rd_kb: uint64(c_stat.num_rd_kb),
|
||||
Num_wr: uint64(c_stat.num_wr),
|
||||
Num_wr_kb: uint64(c_stat.num_wr_kb),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetPoolName returns the name of the pool associated with the I/O context.
|
||||
func (ioctx *IOContext) GetPoolName() (name string, err error) {
|
||||
buf := make([]byte, 128)
|
||||
for {
|
||||
ret := C.rados_ioctx_get_pool_name(ioctx.ioctx,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])), C.unsigned(len(buf)))
|
||||
if ret == -34 { // FIXME
|
||||
buf = make([]byte, len(buf)*2)
|
||||
continue
|
||||
} else if ret < 0 {
|
||||
return "", RadosError(ret)
|
||||
}
|
||||
name = C.GoStringN((*C.char)(unsafe.Pointer(&buf[0])), ret)
|
||||
return name, nil
|
||||
}
|
||||
buf := make([]byte, 128)
|
||||
for {
|
||||
ret := C.rados_ioctx_get_pool_name(ioctx.ioctx,
|
||||
(*C.char)(unsafe.Pointer(&buf[0])), C.unsigned(len(buf)))
|
||||
if ret == -34 { // FIXME
|
||||
buf = make([]byte, len(buf)*2)
|
||||
continue
|
||||
} else if ret < 0 {
|
||||
return "", RadosError(ret)
|
||||
}
|
||||
name = C.GoStringN((*C.char)(unsafe.Pointer(&buf[0])), ret)
|
||||
return name, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ObjectListFunc is the type of the function called for each object visited
|
||||
@ -171,23 +171,23 @@ type ObjectListFunc func(oid string)
|
||||
// context, and called the provided listFn function for each object, passing
|
||||
// to the function the name of the object.
|
||||
func (ioctx *IOContext) ListObjects(listFn ObjectListFunc) error {
|
||||
var ctx C.rados_list_ctx_t
|
||||
ret := C.rados_objects_list_open(ioctx.ioctx, &ctx)
|
||||
if ret < 0 {
|
||||
return RadosError(ret)
|
||||
}
|
||||
defer func() { C.rados_objects_list_close(ctx) }()
|
||||
var ctx C.rados_list_ctx_t
|
||||
ret := C.rados_objects_list_open(ioctx.ioctx, &ctx)
|
||||
if ret < 0 {
|
||||
return RadosError(ret)
|
||||
}
|
||||
defer func() { C.rados_objects_list_close(ctx) }()
|
||||
|
||||
for {
|
||||
var c_entry *C.char
|
||||
ret := C.rados_objects_list_next(ctx, &c_entry, nil)
|
||||
if ret == -2 { // FIXME
|
||||
return nil
|
||||
} else if ret < 0 {
|
||||
return RadosError(ret)
|
||||
}
|
||||
listFn(C.GoString(c_entry))
|
||||
}
|
||||
for {
|
||||
var c_entry *C.char
|
||||
ret := C.rados_objects_list_next(ctx, &c_entry, nil)
|
||||
if ret == -2 { // FIXME
|
||||
return nil
|
||||
} else if ret < 0 {
|
||||
return RadosError(ret)
|
||||
}
|
||||
listFn(C.GoString(c_entry))
|
||||
}
|
||||
|
||||
panic("invalid state")
|
||||
panic("invalid state")
|
||||
}
|
||||
|
@ -6,32 +6,32 @@ package rados
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type RadosError int
|
||||
|
||||
func (e RadosError) Error() string {
|
||||
return fmt.Sprintf("rados: ret=%d", e)
|
||||
return fmt.Sprintf("rados: ret=%d", e)
|
||||
}
|
||||
|
||||
// Version returns the major, minor, and patch components of the version of
|
||||
// the RADOS library linked against.
|
||||
func Version() (int, int, int) {
|
||||
var c_major, c_minor, c_patch C.int
|
||||
C.rados_version(&c_major, &c_minor, &c_patch)
|
||||
return int(c_major), int(c_minor), int(c_patch)
|
||||
var c_major, c_minor, c_patch C.int
|
||||
C.rados_version(&c_major, &c_minor, &c_patch)
|
||||
return int(c_major), int(c_minor), int(c_patch)
|
||||
}
|
||||
|
||||
// NewConn creates a new connection object. It returns the connection and an
|
||||
// error, if any.
|
||||
func NewConn() (*Conn, error) {
|
||||
conn := &Conn{}
|
||||
ret := C.rados_create(&conn.cluster, nil)
|
||||
conn := &Conn{}
|
||||
ret := C.rados_create(&conn.cluster, nil)
|
||||
|
||||
if ret == 0 {
|
||||
return conn, nil
|
||||
} else {
|
||||
return nil, RadosError(int(ret))
|
||||
}
|
||||
if ret == 0 {
|
||||
return conn, nil
|
||||
} else {
|
||||
return nil, RadosError(int(ret))
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package rados_test
|
||||
|
||||
import "testing"
|
||||
|
||||
//import "bytes"
|
||||
import "github.com/noahdesu/go-ceph/rados"
|
||||
import "github.com/stretchr/testify/assert"
|
||||
@ -15,371 +16,371 @@ import "sort"
|
||||
import "encoding/json"
|
||||
|
||||
func GetUUID() string {
|
||||
out, _ := exec.Command("uuidgen").Output()
|
||||
return string(out[:36])
|
||||
out, _ := exec.Command("uuidgen").Output()
|
||||
return string(out[:36])
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
var major, minor, patch = rados.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")
|
||||
var major, minor, patch = rados.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 TestGetSetConfigOption(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn, _ := rados.NewConn()
|
||||
|
||||
// rejects invalid options
|
||||
err := conn.SetConfigOption("wefoijweojfiw", "welfkwjelkfj")
|
||||
assert.Error(t, err, "Invalid option")
|
||||
// rejects invalid options
|
||||
err := conn.SetConfigOption("wefoijweojfiw", "welfkwjelkfj")
|
||||
assert.Error(t, err, "Invalid option")
|
||||
|
||||
// verify SetConfigOption changes a values
|
||||
log_file_val, err := conn.GetConfigOption("log_file")
|
||||
assert.NotEqual(t, log_file_val, "/dev/null")
|
||||
// verify SetConfigOption changes a values
|
||||
log_file_val, err := conn.GetConfigOption("log_file")
|
||||
assert.NotEqual(t, log_file_val, "/dev/null")
|
||||
|
||||
err = conn.SetConfigOption("log_file", "/dev/null")
|
||||
assert.NoError(t, err, "Invalid option")
|
||||
err = conn.SetConfigOption("log_file", "/dev/null")
|
||||
assert.NoError(t, err, "Invalid option")
|
||||
|
||||
log_file_val, err = conn.GetConfigOption("log_file")
|
||||
assert.Equal(t, log_file_val, "/dev/null")
|
||||
log_file_val, err = conn.GetConfigOption("log_file")
|
||||
assert.Equal(t, log_file_val, "/dev/null")
|
||||
}
|
||||
|
||||
func TestParseDefaultConfigEnv(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn, _ := rados.NewConn()
|
||||
|
||||
log_file_val, _ := conn.GetConfigOption("log_file")
|
||||
assert.NotEqual(t, log_file_val, "/dev/null")
|
||||
log_file_val, _ := conn.GetConfigOption("log_file")
|
||||
assert.NotEqual(t, log_file_val, "/dev/null")
|
||||
|
||||
err := os.Setenv("CEPH_ARGS", "--log-file /dev/null")
|
||||
assert.NoError(t, err)
|
||||
err := os.Setenv("CEPH_ARGS", "--log-file /dev/null")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = conn.ParseDefaultConfigEnv()
|
||||
assert.NoError(t, err)
|
||||
err = conn.ParseDefaultConfigEnv()
|
||||
assert.NoError(t, err)
|
||||
|
||||
log_file_val, _ = conn.GetConfigOption("log_file")
|
||||
assert.Equal(t, log_file_val, "/dev/null")
|
||||
log_file_val, _ = conn.GetConfigOption("log_file")
|
||||
assert.Equal(t, log_file_val, "/dev/null")
|
||||
}
|
||||
|
||||
func TestParseCmdLineArgs(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
|
||||
mon_host_val, _ := conn.GetConfigOption("mon_host")
|
||||
assert.NotEqual(t, mon_host_val, "1.1.1.1")
|
||||
mon_host_val, _ := conn.GetConfigOption("mon_host")
|
||||
assert.NotEqual(t, mon_host_val, "1.1.1.1")
|
||||
|
||||
args := []string{ "--mon-host", "1.1.1.1" }
|
||||
err := conn.ParseCmdLineArgs(args)
|
||||
assert.NoError(t, err)
|
||||
args := []string{"--mon-host", "1.1.1.1"}
|
||||
err := conn.ParseCmdLineArgs(args)
|
||||
assert.NoError(t, err)
|
||||
|
||||
mon_host_val, _ = conn.GetConfigOption("mon_host")
|
||||
assert.Equal(t, mon_host_val, "1.1.1.1")
|
||||
mon_host_val, _ = conn.GetConfigOption("mon_host")
|
||||
assert.Equal(t, mon_host_val, "1.1.1.1")
|
||||
}
|
||||
|
||||
func TestGetClusterStats(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
|
||||
poolname := GetUUID()
|
||||
err := conn.MakePool(poolname)
|
||||
assert.NoError(t, err)
|
||||
poolname := GetUUID()
|
||||
err := conn.MakePool(poolname)
|
||||
assert.NoError(t, err)
|
||||
|
||||
pool, err := conn.OpenIOContext(poolname)
|
||||
assert.NoError(t, err)
|
||||
pool, err := conn.OpenIOContext(poolname)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// grab current stats
|
||||
prev_stat, err := conn.GetClusterStats()
|
||||
fmt.Printf("prev_stat: %+v\n", prev_stat)
|
||||
assert.NoError(t, err)
|
||||
// grab current stats
|
||||
prev_stat, err := conn.GetClusterStats()
|
||||
fmt.Printf("prev_stat: %+v\n", prev_stat)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// make some changes to the cluster
|
||||
buf := make([]byte, 1<<20)
|
||||
for i := 0; i < 10; i++ {
|
||||
objname := GetUUID()
|
||||
pool.Write(objname, buf, 0)
|
||||
}
|
||||
// make some changes to the cluster
|
||||
buf := make([]byte, 1<<20)
|
||||
for i := 0; i < 10; i++ {
|
||||
objname := GetUUID()
|
||||
pool.Write(objname, buf, 0)
|
||||
}
|
||||
|
||||
// wait a while for the stats to change
|
||||
for i := 0; i < 30; i++ {
|
||||
stat, err := conn.GetClusterStats()
|
||||
assert.NoError(t, err)
|
||||
// wait a while for the stats to change
|
||||
for i := 0; i < 30; i++ {
|
||||
stat, err := conn.GetClusterStats()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// wait for something to change
|
||||
if stat == prev_stat {
|
||||
fmt.Printf("curr_stat: %+v (trying again...)\n", stat)
|
||||
time.Sleep(time.Second)
|
||||
} else {
|
||||
// success
|
||||
fmt.Printf("curr_stat: %+v (change detected)\n", stat)
|
||||
conn.Shutdown()
|
||||
return
|
||||
}
|
||||
}
|
||||
// wait for something to change
|
||||
if stat == prev_stat {
|
||||
fmt.Printf("curr_stat: %+v (trying again...)\n", stat)
|
||||
time.Sleep(time.Second)
|
||||
} else {
|
||||
// success
|
||||
fmt.Printf("curr_stat: %+v (change detected)\n", stat)
|
||||
conn.Shutdown()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pool.Destroy()
|
||||
conn.Shutdown()
|
||||
t.Error("Cluster stats aren't changing")
|
||||
pool.Destroy()
|
||||
conn.Shutdown()
|
||||
t.Error("Cluster stats aren't changing")
|
||||
}
|
||||
|
||||
func TestGetFSID(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
|
||||
fsid, err := conn.GetFSID()
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, fsid, "")
|
||||
fsid, err := conn.GetFSID()
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, fsid, "")
|
||||
|
||||
conn.Shutdown()
|
||||
conn.Shutdown()
|
||||
}
|
||||
|
||||
func TestGetInstanceID(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
|
||||
id := conn.GetInstanceID()
|
||||
assert.NotEqual(t, id, 0)
|
||||
id := conn.GetInstanceID()
|
||||
assert.NotEqual(t, id, 0)
|
||||
|
||||
conn.Shutdown()
|
||||
conn.Shutdown()
|
||||
}
|
||||
|
||||
func TestMakeDeletePool(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
|
||||
// get current list of pool
|
||||
pools, err := conn.ListPools()
|
||||
assert.NoError(t, err)
|
||||
// get current list of pool
|
||||
pools, err := conn.ListPools()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check that new pool name is unique
|
||||
new_name := GetUUID()
|
||||
for _, poolname := range pools {
|
||||
if new_name == poolname {
|
||||
t.Error("Random pool name exists!")
|
||||
return
|
||||
}
|
||||
}
|
||||
// check that new pool name is unique
|
||||
new_name := GetUUID()
|
||||
for _, poolname := range pools {
|
||||
if new_name == poolname {
|
||||
t.Error("Random pool name exists!")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// create pool
|
||||
err = conn.MakePool(new_name)
|
||||
assert.NoError(t, err)
|
||||
// create pool
|
||||
err = conn.MakePool(new_name)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// get updated list of pools
|
||||
pools, err = conn.ListPools()
|
||||
assert.NoError(t, err)
|
||||
// get updated list of pools
|
||||
pools, err = conn.ListPools()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// verify that the new pool name exists
|
||||
found := false
|
||||
for _, poolname := range pools {
|
||||
if new_name == poolname {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
// verify that the new pool name exists
|
||||
found := false
|
||||
for _, poolname := range pools {
|
||||
if new_name == poolname {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Error("Cannot find newly created pool")
|
||||
}
|
||||
if !found {
|
||||
t.Error("Cannot find newly created pool")
|
||||
}
|
||||
|
||||
// delete the pool
|
||||
err = conn.DeletePool(new_name)
|
||||
assert.NoError(t, err)
|
||||
// delete the pool
|
||||
err = conn.DeletePool(new_name)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// verify that it is gone
|
||||
// verify that it is gone
|
||||
|
||||
// get updated list of pools
|
||||
pools, err = conn.ListPools()
|
||||
assert.NoError(t, err)
|
||||
// get updated list of pools
|
||||
pools, err = conn.ListPools()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// verify that the new pool name exists
|
||||
found = false
|
||||
for _, poolname := range pools {
|
||||
if new_name == poolname {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
// verify that the new pool name exists
|
||||
found = false
|
||||
for _, poolname := range pools {
|
||||
if new_name == poolname {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
t.Error("Deleted pool still exists")
|
||||
}
|
||||
if found {
|
||||
t.Error("Deleted pool still exists")
|
||||
}
|
||||
|
||||
conn.Shutdown()
|
||||
conn.Shutdown()
|
||||
}
|
||||
|
||||
func TestPingMonitor(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
|
||||
// mon id that should work with vstart.sh
|
||||
reply, err := conn.PingMonitor("a")
|
||||
if err == nil {
|
||||
assert.NotEqual(t, reply, "")
|
||||
return
|
||||
}
|
||||
// mon id that should work with vstart.sh
|
||||
reply, err := conn.PingMonitor("a")
|
||||
if err == nil {
|
||||
assert.NotEqual(t, reply, "")
|
||||
return
|
||||
}
|
||||
|
||||
// mon id that should work with micro-osd.sh
|
||||
reply, err = conn.PingMonitor("0")
|
||||
if err == nil {
|
||||
assert.NotEqual(t, reply, "")
|
||||
return
|
||||
}
|
||||
// mon id that should work with micro-osd.sh
|
||||
reply, err = conn.PingMonitor("0")
|
||||
if err == nil {
|
||||
assert.NotEqual(t, reply, "")
|
||||
return
|
||||
}
|
||||
|
||||
// try to use a hostname as the monitor id
|
||||
mon_addr, _ := conn.GetConfigOption("mon_host")
|
||||
hosts, _ := net.LookupAddr(mon_addr)
|
||||
for _, host := range hosts {
|
||||
reply, err := conn.PingMonitor(host)
|
||||
if err == nil {
|
||||
assert.NotEqual(t, reply, "")
|
||||
return
|
||||
}
|
||||
}
|
||||
// try to use a hostname as the monitor id
|
||||
mon_addr, _ := conn.GetConfigOption("mon_host")
|
||||
hosts, _ := net.LookupAddr(mon_addr)
|
||||
for _, host := range hosts {
|
||||
reply, err := conn.PingMonitor(host)
|
||||
if err == nil {
|
||||
assert.NotEqual(t, reply, "")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
t.Error("Could not find a valid monitor id")
|
||||
t.Error("Could not find a valid monitor id")
|
||||
|
||||
conn.Shutdown()
|
||||
conn.Shutdown()
|
||||
}
|
||||
|
||||
func TestReadConfigFile(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn, _ := rados.NewConn()
|
||||
|
||||
// check current log_file value
|
||||
log_file_val, err := conn.GetConfigOption("log_file")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, log_file_val, "/dev/null")
|
||||
// check current log_file value
|
||||
log_file_val, err := conn.GetConfigOption("log_file")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, log_file_val, "/dev/null")
|
||||
|
||||
// create a temporary ceph.conf file that changes the log_file conf
|
||||
// option.
|
||||
file, err := ioutil.TempFile("/tmp", "go-rados")
|
||||
assert.NoError(t, err)
|
||||
// create a temporary ceph.conf file that changes the log_file conf
|
||||
// option.
|
||||
file, err := ioutil.TempFile("/tmp", "go-rados")
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = io.WriteString(file, "[global]\nlog_file = /dev/null\n")
|
||||
assert.NoError(t, err)
|
||||
_, err = io.WriteString(file, "[global]\nlog_file = /dev/null\n")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// parse the config file
|
||||
err = conn.ReadConfigFile(file.Name())
|
||||
assert.NoError(t, err)
|
||||
// parse the config file
|
||||
err = conn.ReadConfigFile(file.Name())
|
||||
assert.NoError(t, err)
|
||||
|
||||
// check current log_file value
|
||||
log_file_val, err = conn.GetConfigOption("log_file")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, log_file_val, "/dev/null")
|
||||
// check current log_file value
|
||||
log_file_val, err = conn.GetConfigOption("log_file")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, log_file_val, "/dev/null")
|
||||
|
||||
// cleanup
|
||||
file.Close()
|
||||
os.Remove(file.Name())
|
||||
// cleanup
|
||||
file.Close()
|
||||
os.Remove(file.Name())
|
||||
}
|
||||
|
||||
func TestWaitForLatestOSDMap(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
|
||||
err := conn.WaitForLatestOSDMap()
|
||||
assert.NoError(t, err)
|
||||
err := conn.WaitForLatestOSDMap()
|
||||
assert.NoError(t, err)
|
||||
|
||||
conn.Shutdown()
|
||||
conn.Shutdown()
|
||||
}
|
||||
|
||||
func TestReadWrite(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
|
||||
// make pool
|
||||
pool_name := GetUUID()
|
||||
err := conn.MakePool(pool_name)
|
||||
assert.NoError(t, err)
|
||||
// make pool
|
||||
pool_name := GetUUID()
|
||||
err := conn.MakePool(pool_name)
|
||||
assert.NoError(t, err)
|
||||
|
||||
pool, err := conn.OpenIOContext(pool_name)
|
||||
assert.NoError(t, err)
|
||||
pool, err := conn.OpenIOContext(pool_name)
|
||||
assert.NoError(t, err)
|
||||
|
||||
bytes_in := []byte("input data")
|
||||
err = pool.Write("obj", bytes_in, 0)
|
||||
assert.NoError(t, err)
|
||||
bytes_in := []byte("input data")
|
||||
err = pool.Write("obj", bytes_in, 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
bytes_out := make([]byte, len(bytes_in))
|
||||
n_out, err := pool.Read("obj", bytes_out, 0)
|
||||
bytes_out := make([]byte, len(bytes_in))
|
||||
n_out, err := pool.Read("obj", bytes_out, 0)
|
||||
|
||||
assert.Equal(t, n_out, len(bytes_in))
|
||||
assert.Equal(t, bytes_in, bytes_out)
|
||||
assert.Equal(t, n_out, len(bytes_in))
|
||||
assert.Equal(t, bytes_in, bytes_out)
|
||||
|
||||
pool.Destroy()
|
||||
pool.Destroy()
|
||||
}
|
||||
|
||||
func TestGetPoolStats(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
|
||||
poolname := GetUUID()
|
||||
err := conn.MakePool(poolname)
|
||||
assert.NoError(t, err)
|
||||
poolname := GetUUID()
|
||||
err := conn.MakePool(poolname)
|
||||
assert.NoError(t, err)
|
||||
|
||||
pool, err := conn.OpenIOContext(poolname)
|
||||
assert.NoError(t, err)
|
||||
pool, err := conn.OpenIOContext(poolname)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// grab current stats
|
||||
prev_stat, err := pool.GetPoolStats()
|
||||
fmt.Printf("prev_stat: %+v\n", prev_stat)
|
||||
assert.NoError(t, err)
|
||||
// grab current stats
|
||||
prev_stat, err := pool.GetPoolStats()
|
||||
fmt.Printf("prev_stat: %+v\n", prev_stat)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// make some changes to the cluster
|
||||
buf := make([]byte, 1<<20)
|
||||
for i := 0; i < 10; i++ {
|
||||
objname := GetUUID()
|
||||
pool.Write(objname, buf, 0)
|
||||
}
|
||||
// make some changes to the cluster
|
||||
buf := make([]byte, 1<<20)
|
||||
for i := 0; i < 10; i++ {
|
||||
objname := GetUUID()
|
||||
pool.Write(objname, buf, 0)
|
||||
}
|
||||
|
||||
// wait a while for the stats to change
|
||||
for i := 0; i < 30; i++ {
|
||||
stat, err := pool.GetPoolStats()
|
||||
assert.NoError(t, err)
|
||||
// wait a while for the stats to change
|
||||
for i := 0; i < 30; i++ {
|
||||
stat, err := pool.GetPoolStats()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// wait for something to change
|
||||
if stat == prev_stat {
|
||||
fmt.Printf("curr_stat: %+v (trying again...)\n", stat)
|
||||
time.Sleep(time.Second)
|
||||
} else {
|
||||
// success
|
||||
fmt.Printf("curr_stat: %+v (change detected)\n", stat)
|
||||
conn.Shutdown()
|
||||
return
|
||||
}
|
||||
}
|
||||
// wait for something to change
|
||||
if stat == prev_stat {
|
||||
fmt.Printf("curr_stat: %+v (trying again...)\n", stat)
|
||||
time.Sleep(time.Second)
|
||||
} else {
|
||||
// success
|
||||
fmt.Printf("curr_stat: %+v (change detected)\n", stat)
|
||||
conn.Shutdown()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
pool.Destroy()
|
||||
conn.Shutdown()
|
||||
t.Error("Pool stats aren't changing")
|
||||
pool.Destroy()
|
||||
conn.Shutdown()
|
||||
t.Error("Pool stats aren't changing")
|
||||
}
|
||||
|
||||
func TestGetPoolName(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
|
||||
poolname := GetUUID()
|
||||
err := conn.MakePool(poolname)
|
||||
assert.NoError(t, err)
|
||||
poolname := GetUUID()
|
||||
err := conn.MakePool(poolname)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ioctx, err := conn.OpenIOContext(poolname)
|
||||
assert.NoError(t, err)
|
||||
ioctx, err := conn.OpenIOContext(poolname)
|
||||
assert.NoError(t, err)
|
||||
|
||||
poolname_ret, err := ioctx.GetPoolName()
|
||||
assert.NoError(t, err)
|
||||
poolname_ret, err := ioctx.GetPoolName()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, poolname, poolname_ret)
|
||||
assert.Equal(t, poolname, poolname_ret)
|
||||
|
||||
ioctx.Destroy()
|
||||
conn.Shutdown()
|
||||
ioctx.Destroy()
|
||||
conn.Shutdown()
|
||||
}
|
||||
|
||||
func TestMonCommand(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
|
||||
command, err := json.Marshal(map[string]string{"prefix": "df", "format": "json"})
|
||||
assert.NoError(t, err)
|
||||
@ -388,50 +389,50 @@ func TestMonCommand(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, info, "")
|
||||
|
||||
var message map[string]interface{}
|
||||
var message map[string]interface{}
|
||||
err = json.Unmarshal(buf, &message)
|
||||
assert.NoError(t, err)
|
||||
|
||||
conn.Shutdown()
|
||||
conn.Shutdown()
|
||||
}
|
||||
|
||||
func TestObjectIterator(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
|
||||
poolname := GetUUID()
|
||||
err := conn.MakePool(poolname)
|
||||
assert.NoError(t, err)
|
||||
poolname := GetUUID()
|
||||
err := conn.MakePool(poolname)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ioctx, err := conn.OpenIOContext(poolname)
|
||||
assert.NoError(t, err)
|
||||
ioctx, err := conn.OpenIOContext(poolname)
|
||||
assert.NoError(t, err)
|
||||
|
||||
objectList := []string{}
|
||||
err = ioctx.ListObjects(func(oid string) {
|
||||
objectList = append(objectList, oid)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, len(objectList) == 0)
|
||||
objectList := []string{}
|
||||
err = ioctx.ListObjects(func(oid string) {
|
||||
objectList = append(objectList, oid)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, len(objectList) == 0)
|
||||
|
||||
createdList := []string{}
|
||||
for i := 0; i < 200; i++ {
|
||||
oid := GetUUID()
|
||||
bytes_in := []byte("input data")
|
||||
err = ioctx.Write(oid, bytes_in, 0)
|
||||
assert.NoError(t, err)
|
||||
createdList = append(createdList, oid)
|
||||
}
|
||||
assert.True(t, len(createdList) == 200)
|
||||
createdList := []string{}
|
||||
for i := 0; i < 200; i++ {
|
||||
oid := GetUUID()
|
||||
bytes_in := []byte("input data")
|
||||
err = ioctx.Write(oid, bytes_in, 0)
|
||||
assert.NoError(t, err)
|
||||
createdList = append(createdList, oid)
|
||||
}
|
||||
assert.True(t, len(createdList) == 200)
|
||||
|
||||
err = ioctx.ListObjects(func(oid string) {
|
||||
objectList = append(objectList, oid)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(objectList), len(createdList))
|
||||
err = ioctx.ListObjects(func(oid string) {
|
||||
objectList = append(objectList, oid)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(objectList), len(createdList))
|
||||
|
||||
sort.Strings(objectList)
|
||||
sort.Strings(createdList)
|
||||
sort.Strings(objectList)
|
||||
sort.Strings(createdList)
|
||||
|
||||
assert.Equal(t, objectList, createdList)
|
||||
assert.Equal(t, objectList, createdList)
|
||||
}
|
||||
|
965
rbd/rbd.go
965
rbd/rbd.go
File diff suppressed because it is too large
Load Diff
220
rbd/rbd_test.go
220
rbd/rbd_test.go
@ -1,163 +1,163 @@
|
||||
package rbd_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"github.com/noahdesu/go-ceph/rados"
|
||||
"github.com/noahdesu/go-ceph/rbd"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"encoding/json"
|
||||
"encoding/json"
|
||||
"github.com/noahdesu/go-ceph/rados"
|
||||
"github.com/noahdesu/go-ceph/rbd"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func GetUUID() string {
|
||||
out, _ := exec.Command("uuidgen").Output()
|
||||
return string(out[:36])
|
||||
out, _ := exec.Command("uuidgen").Output()
|
||||
return string(out[:36])
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
var major, minor, patch = rbd.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")
|
||||
var major, minor, patch = rbd.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 TestGetImageNames(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
|
||||
poolname := GetUUID()
|
||||
err := conn.MakePool(poolname)
|
||||
assert.NoError(t, err)
|
||||
poolname := GetUUID()
|
||||
err := conn.MakePool(poolname)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ioctx, err := conn.OpenIOContext(poolname)
|
||||
assert.NoError(t, err)
|
||||
ioctx, err := conn.OpenIOContext(poolname)
|
||||
assert.NoError(t, err)
|
||||
|
||||
createdList := []string{}
|
||||
for i := 0; i < 10; i++ {
|
||||
name := GetUUID()
|
||||
_, err := rbd.Create(ioctx, name, 1<<22)
|
||||
assert.NoError(t, err)
|
||||
createdList = append(createdList, name)
|
||||
}
|
||||
createdList := []string{}
|
||||
for i := 0; i < 10; i++ {
|
||||
name := GetUUID()
|
||||
_, err := rbd.Create(ioctx, name, 1<<22)
|
||||
assert.NoError(t, err)
|
||||
createdList = append(createdList, name)
|
||||
}
|
||||
|
||||
imageNames, err := rbd.GetImageNames(ioctx)
|
||||
assert.NoError(t, err)
|
||||
imageNames, err := rbd.GetImageNames(ioctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
sort.Strings(createdList)
|
||||
sort.Strings(imageNames)
|
||||
assert.Equal(t, createdList, imageNames)
|
||||
sort.Strings(createdList)
|
||||
sort.Strings(imageNames)
|
||||
assert.Equal(t, createdList, imageNames)
|
||||
|
||||
for _, name := range(createdList) {
|
||||
img := rbd.GetImage(ioctx, name)
|
||||
err := img.Remove()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
for _, name := range createdList {
|
||||
img := rbd.GetImage(ioctx, name)
|
||||
err := img.Remove()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
ioctx.Destroy()
|
||||
conn.DeletePool(poolname)
|
||||
conn.Shutdown()
|
||||
ioctx.Destroy()
|
||||
conn.DeletePool(poolname)
|
||||
conn.Shutdown()
|
||||
}
|
||||
|
||||
func TestIOReaderWriter(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
|
||||
poolname := GetUUID()
|
||||
err := conn.MakePool(poolname)
|
||||
assert.NoError(t, err)
|
||||
poolname := GetUUID()
|
||||
err := conn.MakePool(poolname)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ioctx, err := conn.OpenIOContext(poolname)
|
||||
assert.NoError(t, err)
|
||||
ioctx, err := conn.OpenIOContext(poolname)
|
||||
assert.NoError(t, err)
|
||||
|
||||
name := GetUUID()
|
||||
img, err := rbd.Create(ioctx, name, 1<<22)
|
||||
assert.NoError(t, err)
|
||||
name := GetUUID()
|
||||
img, err := rbd.Create(ioctx, name, 1<<22)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = img.Open()
|
||||
assert.NoError(t, err)
|
||||
err = img.Open()
|
||||
assert.NoError(t, err)
|
||||
|
||||
stats, err := img.Stat()
|
||||
assert.NoError(t, err)
|
||||
stats, err := img.Stat()
|
||||
assert.NoError(t, err)
|
||||
|
||||
encoder := json.NewEncoder(img)
|
||||
encoder.Encode(stats)
|
||||
encoder := json.NewEncoder(img)
|
||||
encoder.Encode(stats)
|
||||
|
||||
err = img.Flush()
|
||||
assert.NoError(t, err)
|
||||
err = img.Flush()
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = img.Seek(0, 0)
|
||||
assert.NoError(t, err)
|
||||
_, err = img.Seek(0, 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var stats2 *rbd.ImageInfo
|
||||
decoder := json.NewDecoder(img)
|
||||
decoder.Decode(&stats2)
|
||||
var stats2 *rbd.ImageInfo
|
||||
decoder := json.NewDecoder(img)
|
||||
decoder.Decode(&stats2)
|
||||
|
||||
assert.Equal(t, &stats, &stats2)
|
||||
assert.Equal(t, &stats, &stats2)
|
||||
|
||||
_, err = img.Seek(0, 0)
|
||||
bytes_in := []byte("input data")
|
||||
_, err = img.Write(bytes_in)
|
||||
assert.NoError(t, err)
|
||||
_, err = img.Seek(0, 0)
|
||||
bytes_in := []byte("input data")
|
||||
_, err = img.Write(bytes_in)
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = img.Seek(0, 0)
|
||||
assert.NoError(t, err)
|
||||
_, err = img.Seek(0, 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
bytes_out := make([]byte, len(bytes_in))
|
||||
n_out, err := img.Read(bytes_out)
|
||||
bytes_out := make([]byte, len(bytes_in))
|
||||
n_out, err := img.Read(bytes_out)
|
||||
|
||||
assert.Equal(t, n_out, len(bytes_in))
|
||||
assert.Equal(t, bytes_in, bytes_out)
|
||||
assert.Equal(t, n_out, len(bytes_in))
|
||||
assert.Equal(t, bytes_in, bytes_out)
|
||||
|
||||
err = img.Close()
|
||||
assert.NoError(t, err)
|
||||
err = img.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
img.Remove()
|
||||
assert.NoError(t, err)
|
||||
img.Remove()
|
||||
assert.NoError(t, err)
|
||||
|
||||
ioctx.Destroy()
|
||||
conn.DeletePool(poolname)
|
||||
conn.Shutdown()
|
||||
ioctx.Destroy()
|
||||
conn.DeletePool(poolname)
|
||||
conn.Shutdown()
|
||||
}
|
||||
|
||||
func TestCreateSnapshot(t *testing.T) {
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
conn, _ := rados.NewConn()
|
||||
conn.ReadDefaultConfigFile()
|
||||
conn.Connect()
|
||||
|
||||
poolname := GetUUID()
|
||||
err := conn.MakePool(poolname)
|
||||
assert.NoError(t, err)
|
||||
poolname := GetUUID()
|
||||
err := conn.MakePool(poolname)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ioctx, err := conn.OpenIOContext(poolname)
|
||||
assert.NoError(t, err)
|
||||
ioctx, err := conn.OpenIOContext(poolname)
|
||||
assert.NoError(t, err)
|
||||
|
||||
name := GetUUID()
|
||||
img, err := rbd.Create(ioctx, name, 1<<22)
|
||||
assert.NoError(t, err)
|
||||
name := GetUUID()
|
||||
img, err := rbd.Create(ioctx, name, 1<<22)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = img.Open()
|
||||
assert.NoError(t, err)
|
||||
err = img.Open()
|
||||
assert.NoError(t, err)
|
||||
|
||||
snapshot, err := img.CreateSnapshot("mysnap")
|
||||
assert.NoError(t, err)
|
||||
snapshot, err := img.CreateSnapshot("mysnap")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = img.Close()
|
||||
err = img.Open("mysnap")
|
||||
assert.NoError(t, err)
|
||||
err = img.Close()
|
||||
err = img.Open("mysnap")
|
||||
assert.NoError(t, err)
|
||||
|
||||
snapshot.Remove()
|
||||
assert.NoError(t, err)
|
||||
snapshot.Remove()
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = img.Close()
|
||||
assert.NoError(t, err)
|
||||
err = img.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
img.Remove()
|
||||
assert.NoError(t, err)
|
||||
img.Remove()
|
||||
assert.NoError(t, err)
|
||||
|
||||
ioctx.Destroy()
|
||||
conn.DeletePool(poolname)
|
||||
conn.Shutdown()
|
||||
ioctx.Destroy()
|
||||
conn.DeletePool(poolname)
|
||||
conn.Shutdown()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user