diff --git a/.travis.yml b/.travis.yml index 73a64de..a4d6541 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,14 @@ sudo: required dist: trusty env: - - TARGET=iqn.2016-09.com.gotgt.gostor:example_tgt_0 + global: + - TARGET=iqn.2016-09.com.gotgt.gostor:example_tgt_0 + matrix: + - TGT_CFG='{"storages":[{"deviceID":1000,"path":"file:/var/tmp/disk.img","online":true}],"iscsiportals":[{"id":0,"portal":"127.0.0.1:3260"}],"iscsitargets":{"iqn.2016-09.com.gotgt.gostor:example_tgt_0":{"tpgts":{"1":[0]},"luns":{"0":1000}}}}' + - TGT_CFG='{"storages":[{"deviceID":1000,"path":"ceph-rbd:iscsi_pool/lun0","online":true}],"iscsiportals":[{"id":0,"portal":"127.0.0.1:3260"}],"iscsitargets":{"iqn.2016-09.com.gotgt.gostor:example_tgt_0":{"tpgts":{"1":[0]},"luns":{"0":1000}}}}' language: go + go: - 1.6 - 1.7 @@ -13,22 +18,28 @@ install: - true before_script: - - go get github.com/kr/godep + - echo ${TGT_CFG} + - echo ${TARGET} - sudo apt-get update + - ci/ceph_install.sh + - bash ci/ceph_micro-osd.sh /tmp/micro-ceph + - export CEPH_CONF=/tmp/micro-ceph/ceph.conf + - ceph status + - go get github.com/kr/godep - sudo apt-get install -y libcunit1 libcunit1-doc libcunit1-dev - sudo apt-get install -y open-iscsi script: - cd ${TRAVIS_BUILD_DIR} - ./autogen.sh - - ./configure + - ./configure - make - hack/verify-gofmt.sh - export GOPATH=`pwd`/Godeps/_workspace/:$GOPATH - go test -v ./pkg/... - dd if=/dev/zero of=/var/tmp/disk.img bs=1024 count=102400 - mkdir ${HOME}/.gotgt - - echo '{"storages":[{"deviceID":1000,"path":"file:/var/tmp/disk.img","online":true}],"iscsiportals":[{"id":0,"portal":"127.0.0.1:3260"}],"iscsitargets":{"iqn.2016-09.com.gotgt.gostor:example_tgt_0":{"tpgts":{"1":[0]},"luns":{"0":1000}}}}' > ${HOME}/.gotgt/config.json + - echo ${TGT_CFG} > ${HOME}/.gotgt/config.json - ./gotgt daemon --log debug 1>/dev/null 2>&1 & # libiscsi test - mkdir ${HOME}/libiscsi diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 0bc17c9..569e679 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -58,6 +58,11 @@ { "ImportPath": "golang.org/x/net/proxy", "Rev": "d9558e5c97f85372afee28cf2b6059d7d3818919" + }, + { + "ImportPath": "github.com/ceph/go-ceph", + "Rev": "bd5bc6d4cb3e3d3441f2ec4e9f89899178edfc71" } + ] } diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/.travis.yml b/Godeps/_workspace/src/github.com/ceph/go-ceph/.travis.yml new file mode 100644 index 0000000..4872c41 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/.travis.yml @@ -0,0 +1,26 @@ +dist: trusty +sudo: required + +language: go + +branches: + except: + - gh-pages + +matrix: + include: + - env: CEPH_RELEASE=jewel + - env: CEPH_RELEASE=kraken + +before_install: + - sudo apt-get update + - ci/before_install.sh + - bash ci/micro-osd.sh /tmp/micro-ceph + - export CEPH_CONF=/tmp/micro-ceph/ceph.conf + - ceph status + +script: + - go get -t -v ./... + - go list ./... + - go test -v $(go list ./... | grep -v cephfs) + - go fmt ./... diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/Dockerfile b/Godeps/_workspace/src/github.com/ceph/go-ceph/Dockerfile new file mode 100644 index 0000000..f0201aa --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/Dockerfile @@ -0,0 +1,26 @@ +FROM golang:1.7.1 +MAINTAINER Abhishek Lekshmanan "abhishek.lekshmanan@gmail.com" + +ENV CEPH_VERSION jewel + +RUN echo deb http://download.ceph.com/debian-$CEPH_VERSION/ jessie main | tee /etc/apt/sources.list.d/ceph-$CEPH_VERSION.list + +# Running wget with no certificate checks, alternatively ssl-cert package should be installed +RUN wget --no-check-certificate -q -O- 'https://ceph.com/git/?p=ceph.git;a=blob_plain;f=keys/release.asc' | apt-key add - \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + ceph \ + ceph-mds \ + librados-dev \ + librbd-dev \ + libcephfs-dev \ + uuid-runtime \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean + +VOLUME /go/src/github.com/ceph/go-ceph + +COPY ./ci/entrypoint.sh /tmp/entrypoint.sh + +ENTRYPOINT ["/tmp/entrypoint.sh", "/tmp/micro-ceph"] + diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/LICENSE b/Godeps/_workspace/src/github.com/ceph/go-ceph/LICENSE new file mode 100644 index 0000000..08d70bf --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Noah Watkins + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/README.md b/Godeps/_workspace/src/github.com/ceph/go-ceph/README.md new file mode 100644 index 0000000..c773281 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/README.md @@ -0,0 +1,107 @@ +# go-ceph - Go bindings for Ceph APIs + +[![Build Status](https://travis-ci.org/ceph/go-ceph.svg)](https://travis-ci.org/ceph/go-ceph) [![Godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/ceph/go-ceph) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/ceph/go-ceph/master/LICENSE) + +## Installation + + go get github.com/ceph/go-ceph + +The native RADOS library and development headers are expected to be installed. + +## Documentation + +Detailed documentation is available at +. + +### Connecting to a cluster + +Connect to a Ceph cluster using a configuration file located in the default +search paths. + +```go +conn, _ := rados.NewConn() +conn.ReadDefaultConfigFile() +conn.Connect() +``` + +A connection can be shutdown by calling the `Shutdown` method on the +connection object (e.g. `conn.Shutdown()`). There are also other methods for +configuring the connection. Specific configuration options can be set: + +```go +conn.SetConfigOption("log_file", "/dev/null") +``` + +and command line options can also be used using the `ParseCmdLineArgs` method. + +```go +args := []string{ "--mon-host", "1.1.1.1" } +err := conn.ParseCmdLineArgs(args) +``` + +For other configuration options see the full documentation. + +### Object I/O + +Object in RADOS can be written to and read from with through an interface very +similar to a standard file I/O interface: + +```go +// open a pool handle +ioctx, err := conn.OpenIOContext("mypool") + +// write some data +bytes_in := []byte("input data") +err = ioctx.Write("obj", bytes_in, 0) + +// read the data back out +bytes_out := make([]byte, len(bytes_in)) +n_out, err := ioctx.Read("obj", bytes_out, 0) + +if bytes_in != bytes_out { + fmt.Println("Output is not input!") +} +``` + +### Pool maintenance + +The list of pools in a cluster can be retreived using the `ListPools` method +on the connection object. On a new cluster the following code snippet: + +```go +pools, _ := conn.ListPools() +fmt.Println(pools) +``` + +will produce the output `[data metadata rbd]`, along with any other pools that +might exist in your cluster. Pools can also be created and destroyed. The +following creates a new, empty pool with default settings. + +```go +conn.MakePool("new_pool") +``` + +Deleting a pool is also easy. Call `DeletePool(name string)` on a connection object to +delete a pool with the given name. The following will delete the pool named +`new_pool` and remove all of the pool's data. + +```go +conn.DeletePool("new_pool") +``` + +## Contributing + +Contributions are welcome & greatly appreciated, every little bit helps. Make code changes via Github pull requests: + +- Fork the repo and create a topic branch for every feature/fix. Avoid + making changes directly on master branch. +- All incoming features should be accompanied with tests. +- Make sure that you run `go fmt` before submitting a change + set. Alternatively the Makefile has a flag for this, so you can call + `make fmt` as well. +- The integration tests can be run in a docker container, for this run: + +``` +make test-docker +``` + diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/cephfs/cephfs.go b/Godeps/_workspace/src/github.com/ceph/go-ceph/cephfs/cephfs.go new file mode 100644 index 0000000..8bab2c4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/cephfs/cephfs.go @@ -0,0 +1,89 @@ +package cephfs + +/* +#cgo LDFLAGS: -lcephfs +#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64 +#include +#include +*/ +import "C" +import "fmt" +import "unsafe" + +// +type CephError int + +func (e CephError) Error() string { + return fmt.Sprintf("cephfs: ret=%d", e) +} + +// +type MountInfo struct { + mount *C.struct_ceph_mount_info +} + +func CreateMount() (*MountInfo, error) { + mount := &MountInfo{} + ret := C.ceph_create(&mount.mount, nil) + if ret == 0 { + return mount, nil + } else { + return nil, CephError(ret) + } +} + +func (mount *MountInfo) ReadDefaultConfigFile() error { + ret := C.ceph_conf_read_file(mount.mount, nil) + if ret == 0 { + return nil + } else { + return CephError(ret) + } +} + +func (mount *MountInfo) Mount() error { + ret := C.ceph_mount(mount.mount, nil) + if ret == 0 { + return nil + } else { + return CephError(ret) + } +} + +func (mount *MountInfo) SyncFs() error { + ret := C.ceph_sync_fs(mount.mount) + if ret == 0 { + return nil + } else { + return CephError(ret) + } +} + +func (mount *MountInfo) CurrentDir() string { + c_dir := C.ceph_getcwd(mount.mount) + return C.GoString(c_dir) +} + +func (mount *MountInfo) ChangeDir(path string) error { + c_path := C.CString(path) + defer C.free(unsafe.Pointer(c_path)) + + ret := C.ceph_chdir(mount.mount, c_path) + if ret == 0 { + return nil + } else { + return CephError(ret) + } +} + +func (mount *MountInfo) MakeDir(path string, mode uint32) error { + c_path := C.CString(path) + defer C.free(unsafe.Pointer(c_path)) + + ret := C.ceph_mkdir(mount.mount, c_path, C.mode_t(mode)) + if ret == 0 { + return nil + } else { + return CephError(ret) + } +} diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/cephfs/cephfs_test.go b/Godeps/_workspace/src/github.com/ceph/go-ceph/cephfs/cephfs_test.go new file mode 100644 index 0000000..3745b35 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/cephfs/cephfs_test.go @@ -0,0 +1,66 @@ +package cephfs_test + +import "testing" +import "github.com/ceph/go-ceph/cephfs" +import "github.com/stretchr/testify/assert" + +func TestCreateMount(t *testing.T) { + mount, err := cephfs.CreateMount() + assert.NoError(t, err) + assert.NotNil(t, mount) +} + +func TestMountRoot(t *testing.T) { + mount, err := cephfs.CreateMount() + assert.NoError(t, err) + assert.NotNil(t, mount) + + err = mount.ReadDefaultConfigFile() + assert.NoError(t, err) + + err = mount.Mount() + assert.NoError(t, err) +} + +func TestSyncFs(t *testing.T) { + mount, err := cephfs.CreateMount() + assert.NoError(t, err) + assert.NotNil(t, mount) + + err = mount.ReadDefaultConfigFile() + assert.NoError(t, err) + + err = mount.Mount() + assert.NoError(t, err) + + err = mount.SyncFs() + assert.NoError(t, err) +} + +func TestChangeDir(t *testing.T) { + mount, err := cephfs.CreateMount() + assert.NoError(t, err) + assert.NotNil(t, mount) + + err = mount.ReadDefaultConfigFile() + assert.NoError(t, err) + + err = mount.Mount() + assert.NoError(t, err) + + dir1 := mount.CurrentDir() + assert.NotNil(t, dir1) + + err = mount.MakeDir("/asdf", 0755) + assert.NoError(t, err) + + err = mount.ChangeDir("/asdf") + assert.NoError(t, err) + + dir2 := mount.CurrentDir() + assert.NotNil(t, dir2) + + assert.NotEqual(t, dir1, dir2) + assert.Equal(t, dir1, "/") + assert.Equal(t, dir2, "/asdf") +} diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/ci/before_install.sh b/Godeps/_workspace/src/github.com/ceph/go-ceph/ci/before_install.sh new file mode 100755 index 0000000..1823499 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/ci/before_install.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -e +set -x + +sudo apt-get install -y python-virtualenv + +# ceph-deploy and ceph + +WORKDIR=$HOME/workdir +mkdir $WORKDIR +pushd $WORKDIR + +ssh-keygen -f $HOME/.ssh/id_rsa -t rsa -N '' +cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys +chmod 600 ~/.ssh/authorized_keys + +git clone git://github.com/ceph/ceph-deploy +pushd ceph-deploy +./bootstrap +./ceph-deploy install --release ${CEPH_RELEASE} `hostname` +./ceph-deploy pkg --install librados-dev `hostname` +./ceph-deploy pkg --install librbd-dev `hostname` +./ceph-deploy pkg --install libcephfs-dev `hostname` +popd # ceph-deploy + +popd # workdir diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/ci/entrypoint.sh b/Godeps/_workspace/src/github.com/ceph/go-ceph/ci/entrypoint.sh new file mode 100755 index 0000000..59f604b --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/ci/entrypoint.sh @@ -0,0 +1,114 @@ +#!/bin/bash +# +# Copyright (C) 2013,2014 Loic Dachary +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +set -e +set -u +DIR=$1 + +#if ! dpkg -l ceph ; then +# wget -q -O- 'https://ceph.com/git/?p=ceph.git;a=blob_plain;f=keys/release.asc' | sudo apt-key add - +# echo deb http://ceph.com/debian-dumpling/ $(lsb_release -sc) main | sudo tee /etc/apt/sources.list.d/ceph.list +# sudo apt-get update +# sudo apt-get --yes install ceph ceph-common +#fi + +# get rid of process and directories leftovers +pkill ceph-mon || true +pkill ceph-osd || true +rm -fr $DIR + +# cluster wide parameters +mkdir -p ${DIR}/log +cat >> $DIR/ceph.conf <> $DIR/ceph.conf <> $DIR/ceph.conf <> $DIR/ceph.conf < +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +set -e +set -u + +DIR=$1 + +#if ! dpkg -l ceph ; then +# wget -q -O- 'https://ceph.com/git/?p=ceph.git;a=blob_plain;f=keys/release.asc' | sudo apt-key add - +# echo deb http://ceph.com/debian-dumpling/ $(lsb_release -sc) main | sudo tee /etc/apt/sources.list.d/ceph.list +# sudo apt-get update +# sudo apt-get --yes install ceph ceph-common +#fi + +# get rid of process and directories leftovers +pkill ceph-mon || true +pkill ceph-osd || true +rm -fr $DIR + +# cluster wide parameters +mkdir -p ${DIR}/log +cat >> $DIR/ceph.conf <> $DIR/ceph.conf <> $DIR/ceph.conf <> $DIR/ceph.conf < /dev/null; then + break + fi + fi + sleep 1 +done + +# check that it works +rados --pool rbd put group /etc/group +rados --pool rbd get group ${DIR}/group +diff /etc/group ${DIR}/group +ceph osd tree diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/doc.go b/Godeps/_workspace/src/github.com/ceph/go-ceph/doc.go new file mode 100644 index 0000000..cc935dc --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/doc.go @@ -0,0 +1,9 @@ +/* +Set of wrappers around Ceph APIs. +*/ +package rados + +import ( + _ "github.com/ceph/go-ceph/rados" + _ "github.com/ceph/go-ceph/rbd" +) diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/package_test.go b/Godeps/_workspace/src/github.com/ceph/go-ceph/package_test.go new file mode 100644 index 0000000..73ef3e0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/package_test.go @@ -0,0 +1,12 @@ +package rados + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestImports(t *testing.T) { + if assert.Equal(t, 1, 1) != true { + t.Error("Something is wrong.") + } +} diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/rados/conn.go b/Godeps/_workspace/src/github.com/ceph/go-ceph/rados/conn.go new file mode 100644 index 0000000..381fa4c --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/rados/conn.go @@ -0,0 +1,308 @@ +package rados + +// #cgo LDFLAGS: -lrados +// #include +// #include +import "C" + +import "unsafe" +import "bytes" + +// ClusterStat represents Ceph cluster statistics. +type ClusterStat struct { + 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 +} + +// 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)) + + 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) + + 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)) + } +} + +// Shutdown disconnects from the cluster. +func (c *Conn) Shutdown() { + 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)) + } +} + +// 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)) + } +} + +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)) + } +} + +// 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)) + } + + 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) + } + } + + 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 + } +} + +// 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) + } +} + +// 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 + } +} + +// 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 + } +} + +// 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) + + // 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])) + } + + 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)) + } +} + +// 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)) + } +} + +// 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)) +} + +// 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) + } +} + +// 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) + } +} + +// MonCommand sends a command to one of the monitors +func (c *Conn) MonCommand(args []byte) (buffer []byte, info string, err error) { + return c.monCommand(args, nil) +} + +// MonCommand sends a command to one of the monitors, with an input buffer +func (c *Conn) MonCommandWithInputBuffer(args, inputBuffer []byte) (buffer []byte, info string, err error) { + return c.monCommand(args, inputBuffer) +} + +func (c *Conn) monCommand(args, inputBuffer []byte) (buffer []byte, info string, err error) { + argv := C.CString(string(args)) + defer C.free(unsafe.Pointer(argv)) + + var ( + outs, outbuf *C.char + outslen, outbuflen C.size_t + ) + inbuf := C.CString(string(inputBuffer)) + inbufLen := len(inputBuffer) + defer C.free(unsafe.Pointer(inbuf)) + + ret := C.rados_mon_command(c.cluster, + &argv, 1, + inbuf, // bulk input (e.g. crush map) + C.size_t(inbufLen), // length inbuf + &outbuf, // buffer + &outbuflen, // buffer length + &outs, // status string + &outslen) + + if outslen > 0 { + info = C.GoStringN(outs, C.int(outslen)) + C.free(unsafe.Pointer(outs)) + } + if outbuflen > 0 { + buffer = C.GoBytes(unsafe.Pointer(outbuf), C.int(outbuflen)) + C.free(unsafe.Pointer(outbuf)) + } + if ret != 0 { + err = RadosError(int(ret)) + return nil, info, err + } + + return +} diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/rados/doc.go b/Godeps/_workspace/src/github.com/ceph/go-ceph/rados/doc.go new file mode 100644 index 0000000..14babe9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/rados/doc.go @@ -0,0 +1,4 @@ +/* +Set of wrappers around librados API. +*/ +package rados diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/rados/ioctx.go b/Godeps/_workspace/src/github.com/ceph/go-ceph/rados/ioctx.go new file mode 100644 index 0000000..cf2cab0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/rados/ioctx.go @@ -0,0 +1,872 @@ +package rados + +// #cgo LDFLAGS: -lrados +// #include +// #include +// #include +// +// char* nextChunk(char **idx) { +// char *copy; +// copy = strdup(*idx); +// *idx += strlen(*idx) + 1; +// return copy; +// } +// +// #if __APPLE__ +// #define ceph_time_t __darwin_time_t +// #define ceph_suseconds_t __darwin_suseconds_t +// #elif __GLIBC__ +// #define ceph_time_t __time_t +// #define ceph_suseconds_t __suseconds_t +// #else +// #define ceph_time_t time_t +// #define ceph_suseconds_t suseconds_t +// #endif +import "C" + +import ( + "syscall" + "time" + "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 +} + +// ObjectStat represents an object stat information +type ObjectStat struct { + // current length in bytes + Size uint64 + // last modification time + ModTime time.Time +} + +// LockInfo represents information on a current Ceph lock +type LockInfo struct { + NumLockers int + Exclusive bool + Tag string + Clients []string + Cookies []string + Addrs []string +} + +// IOContext represents a context for performing I/O within a pool. +type IOContext struct { + ioctx C.rados_ioctx_t +} + +// Pointer returns a uintptr representation of the IOContext. +func (ioctx *IOContext) Pointer() uintptr { + return uintptr(ioctx.ioctx) +} + +// SetNamespace sets the namespace for objects within this IO context (pool). +// Setting namespace to a empty or zero length string sets the pool to the default namespace. +func (ioctx *IOContext) SetNamespace(namespace string) { + var c_ns *C.char + if len(namespace) > 0 { + c_ns = C.CString(namespace) + defer C.free(unsafe.Pointer(c_ns)) + } + C.rados_ioctx_set_namespace(ioctx.ioctx, c_ns) +} + +// 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)) + + ret := C.rados_write(ioctx.ioctx, c_oid, + (*C.char)(unsafe.Pointer(&data[0])), + (C.size_t)(len(data)), + (C.uint64_t)(offset)) + + return GetRadosError(int(ret)) +} + +// WriteFull writes len(data) bytes to the object with key oid. +// The object is filled with the provided data. If the object exists, +// it is atomically truncated and then written. It returns an error, if any. +func (ioctx *IOContext) WriteFull(oid string, data []byte) error { + c_oid := C.CString(oid) + defer C.free(unsafe.Pointer(c_oid)) + + ret := C.rados_write_full(ioctx.ioctx, c_oid, + (*C.char)(unsafe.Pointer(&data[0])), + (C.size_t)(len(data))) + return GetRadosError(int(ret)) +} + +// Append appends len(data) bytes to the object with key oid. +// The object is appended with the provided data. If the object exists, +// it is atomically appended to. It returns an error, if any. +func (ioctx *IOContext) Append(oid string, data []byte) error { + c_oid := C.CString(oid) + defer C.free(unsafe.Pointer(c_oid)) + + ret := C.rados_append(ioctx.ioctx, c_oid, + (*C.char)(unsafe.Pointer(&data[0])), + (C.size_t)(len(data))) + return GetRadosError(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 + } + + 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)) + + if ret >= 0 { + return int(ret), nil + } else { + return 0, GetRadosError(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)) + + return GetRadosError(int(C.rados_remove(ioctx.ioctx, c_oid))) +} + +// Truncate resizes the object with key oid to size size. If the operation +// enlarges the object, the new area is logically filled with zeroes. If the +// 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)) + + return GetRadosError(int(C.rados_trunc(ioctx.ioctx, c_oid, (C.uint64_t)(size)))) +} + +// 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) +} + +// 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{}, GetRadosError(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 "", GetRadosError(int(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 +// by ListObjects. +type ObjectListFunc func(oid string) + +// ListObjects lists all of the objects in the pool associated with the I/O +// 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_nobjects_list_open(ioctx.ioctx, &ctx) + if ret < 0 { + return GetRadosError(int(ret)) + } + defer func() { C.rados_nobjects_list_close(ctx) }() + + for { + var c_entry *C.char + ret := C.rados_nobjects_list_next(ctx, &c_entry, nil, nil) + if ret == -2 { // FIXME + return nil + } else if ret < 0 { + return GetRadosError(int(ret)) + } + listFn(C.GoString(c_entry)) + } + + panic("invalid state") +} + +// Stat returns the size of the object and its last modification time +func (ioctx *IOContext) Stat(object string) (stat ObjectStat, err error) { + var c_psize C.uint64_t + var c_pmtime C.time_t + c_object := C.CString(object) + defer C.free(unsafe.Pointer(c_object)) + + ret := C.rados_stat( + ioctx.ioctx, + c_object, + &c_psize, + &c_pmtime) + + if ret < 0 { + return ObjectStat{}, GetRadosError(int(ret)) + } else { + return ObjectStat{ + Size: uint64(c_psize), + ModTime: time.Unix(int64(c_pmtime), 0), + }, nil + } +} + +// GetXattr gets an xattr with key `name`, it returns the length of +// the key read or an error if not successful +func (ioctx *IOContext) GetXattr(object string, name string, data []byte) (int, error) { + c_object := C.CString(object) + c_name := C.CString(name) + defer C.free(unsafe.Pointer(c_object)) + defer C.free(unsafe.Pointer(c_name)) + + ret := C.rados_getxattr( + ioctx.ioctx, + c_object, + c_name, + (*C.char)(unsafe.Pointer(&data[0])), + (C.size_t)(len(data))) + + if ret >= 0 { + return int(ret), nil + } else { + return 0, GetRadosError(int(ret)) + } +} + +// Sets an xattr for an object with key `name` with value as `data` +func (ioctx *IOContext) SetXattr(object string, name string, data []byte) error { + c_object := C.CString(object) + c_name := C.CString(name) + defer C.free(unsafe.Pointer(c_object)) + defer C.free(unsafe.Pointer(c_name)) + + ret := C.rados_setxattr( + ioctx.ioctx, + c_object, + c_name, + (*C.char)(unsafe.Pointer(&data[0])), + (C.size_t)(len(data))) + + return GetRadosError(int(ret)) +} + +// function that lists all the xattrs for an object, since xattrs are +// a k-v pair, this function returns a map of k-v pairs on +// success, error code on failure +func (ioctx *IOContext) ListXattrs(oid string) (map[string][]byte, error) { + c_oid := C.CString(oid) + defer C.free(unsafe.Pointer(c_oid)) + + var it C.rados_xattrs_iter_t + + ret := C.rados_getxattrs(ioctx.ioctx, c_oid, &it) + if ret < 0 { + return nil, GetRadosError(int(ret)) + } + defer func() { C.rados_getxattrs_end(it) }() + m := make(map[string][]byte) + for { + var c_name, c_val *C.char + var c_len C.size_t + defer C.free(unsafe.Pointer(c_name)) + defer C.free(unsafe.Pointer(c_val)) + + ret := C.rados_getxattrs_next(it, &c_name, &c_val, &c_len) + if ret < 0 { + return nil, GetRadosError(int(ret)) + } + // rados api returns a null name,val & 0-length upon + // end of iteration + if c_name == nil { + return m, nil // stop iteration + } + m[C.GoString(c_name)] = C.GoBytes(unsafe.Pointer(c_val), (C.int)(c_len)) + } +} + +// Remove an xattr with key `name` from object `oid` +func (ioctx *IOContext) RmXattr(oid string, name string) error { + c_oid := C.CString(oid) + c_name := C.CString(name) + defer C.free(unsafe.Pointer(c_oid)) + defer C.free(unsafe.Pointer(c_name)) + + ret := C.rados_rmxattr( + ioctx.ioctx, + c_oid, + c_name) + + return GetRadosError(int(ret)) +} + +// Append the map `pairs` to the omap `oid` +func (ioctx *IOContext) SetOmap(oid string, pairs map[string][]byte) error { + c_oid := C.CString(oid) + defer C.free(unsafe.Pointer(c_oid)) + + var s C.size_t + var c *C.char + ptrSize := unsafe.Sizeof(c) + + c_keys := C.malloc(C.size_t(len(pairs)) * C.size_t(ptrSize)) + c_values := C.malloc(C.size_t(len(pairs)) * C.size_t(ptrSize)) + c_lengths := C.malloc(C.size_t(len(pairs)) * C.size_t(unsafe.Sizeof(s))) + + defer C.free(unsafe.Pointer(c_keys)) + defer C.free(unsafe.Pointer(c_values)) + defer C.free(unsafe.Pointer(c_lengths)) + + i := 0 + for key, value := range pairs { + // key + c_key_ptr := (**C.char)(unsafe.Pointer(uintptr(c_keys) + uintptr(i)*ptrSize)) + *c_key_ptr = C.CString(key) + defer C.free(unsafe.Pointer(*c_key_ptr)) + + // value and its length + c_value_ptr := (**C.char)(unsafe.Pointer(uintptr(c_values) + uintptr(i)*ptrSize)) + + var c_length C.size_t + if len(value) > 0 { + *c_value_ptr = (*C.char)(unsafe.Pointer(&value[0])) + c_length = C.size_t(len(value)) + } else { + *c_value_ptr = nil + c_length = C.size_t(0) + } + + c_length_ptr := (*C.size_t)(unsafe.Pointer(uintptr(c_lengths) + uintptr(i)*ptrSize)) + *c_length_ptr = c_length + + i++ + } + + op := C.rados_create_write_op() + C.rados_write_op_omap_set( + op, + (**C.char)(c_keys), + (**C.char)(c_values), + (*C.size_t)(c_lengths), + C.size_t(len(pairs))) + + ret := C.rados_write_op_operate(op, ioctx.ioctx, c_oid, nil, 0) + C.rados_release_write_op(op) + + return GetRadosError(int(ret)) +} + +// OmapListFunc is the type of the function called for each omap key +// visited by ListOmapValues +type OmapListFunc func(key string, value []byte) + +// Iterate on a set of keys and their values from an omap +// `startAfter`: iterate only on the keys after this specified one +// `filterPrefix`: iterate only on the keys beginning with this prefix +// `maxReturn`: iterate no more than `maxReturn` key/value pairs +// `listFn`: the function called at each iteration +func (ioctx *IOContext) ListOmapValues(oid string, startAfter string, filterPrefix string, maxReturn int64, listFn OmapListFunc) error { + c_oid := C.CString(oid) + c_start_after := C.CString(startAfter) + c_filter_prefix := C.CString(filterPrefix) + c_max_return := C.uint64_t(maxReturn) + + defer C.free(unsafe.Pointer(c_oid)) + defer C.free(unsafe.Pointer(c_start_after)) + defer C.free(unsafe.Pointer(c_filter_prefix)) + + op := C.rados_create_read_op() + + var c_iter C.rados_omap_iter_t + var c_prval C.int + C.rados_read_op_omap_get_vals( + op, + c_start_after, + c_filter_prefix, + c_max_return, + &c_iter, + &c_prval, + ) + + ret := C.rados_read_op_operate(op, ioctx.ioctx, c_oid, 0) + + if int(ret) != 0 { + return GetRadosError(int(ret)) + } else if int(c_prval) != 0 { + return RadosError(int(c_prval)) + } + + for { + var c_key *C.char + var c_val *C.char + var c_len C.size_t + + ret = C.rados_omap_get_next(c_iter, &c_key, &c_val, &c_len) + + if int(ret) != 0 { + return GetRadosError(int(ret)) + } + + if c_key == nil { + break + } + + listFn(C.GoString(c_key), C.GoBytes(unsafe.Pointer(c_val), C.int(c_len))) + } + + C.rados_omap_get_end(c_iter) + C.rados_release_read_op(op) + + return nil +} + +// Fetch a set of keys and their values from an omap and returns then as a map +// `startAfter`: retrieve only the keys after this specified one +// `filterPrefix`: retrieve only the keys beginning with this prefix +// `maxReturn`: retrieve no more than `maxReturn` key/value pairs +func (ioctx *IOContext) GetOmapValues(oid string, startAfter string, filterPrefix string, maxReturn int64) (map[string][]byte, error) { + omap := map[string][]byte{} + + err := ioctx.ListOmapValues( + oid, startAfter, filterPrefix, maxReturn, + func(key string, value []byte) { + omap[key] = value + }, + ) + + return omap, err +} + +// Fetch all the keys and their values from an omap and returns then as a map +// `startAfter`: retrieve only the keys after this specified one +// `filterPrefix`: retrieve only the keys beginning with this prefix +// `iteratorSize`: internal number of keys to fetch during a read operation +func (ioctx *IOContext) GetAllOmapValues(oid string, startAfter string, filterPrefix string, iteratorSize int64) (map[string][]byte, error) { + omap := map[string][]byte{} + omapSize := 0 + + for { + err := ioctx.ListOmapValues( + oid, startAfter, filterPrefix, iteratorSize, + func(key string, value []byte) { + omap[key] = value + startAfter = key + }, + ) + + if err != nil { + return omap, err + } + + // End of omap + if len(omap) == omapSize { + break + } + + omapSize = len(omap) + } + + return omap, nil +} + +// Remove the specified `keys` from the omap `oid` +func (ioctx *IOContext) RmOmapKeys(oid string, keys []string) error { + c_oid := C.CString(oid) + defer C.free(unsafe.Pointer(c_oid)) + + var c *C.char + ptrSize := unsafe.Sizeof(c) + + c_keys := C.malloc(C.size_t(len(keys)) * C.size_t(ptrSize)) + defer C.free(unsafe.Pointer(c_keys)) + + i := 0 + for _, key := range keys { + c_key_ptr := (**C.char)(unsafe.Pointer(uintptr(c_keys) + uintptr(i)*ptrSize)) + *c_key_ptr = C.CString(key) + defer C.free(unsafe.Pointer(*c_key_ptr)) + i++ + } + + op := C.rados_create_write_op() + C.rados_write_op_omap_rm_keys( + op, + (**C.char)(c_keys), + C.size_t(len(keys))) + + ret := C.rados_write_op_operate(op, ioctx.ioctx, c_oid, nil, 0) + C.rados_release_write_op(op) + + return GetRadosError(int(ret)) +} + +// Clear the omap `oid` +func (ioctx *IOContext) CleanOmap(oid string) error { + c_oid := C.CString(oid) + defer C.free(unsafe.Pointer(c_oid)) + + op := C.rados_create_write_op() + C.rados_write_op_omap_clear(op) + + ret := C.rados_write_op_operate(op, ioctx.ioctx, c_oid, nil, 0) + C.rados_release_write_op(op) + + return GetRadosError(int(ret)) +} + +type Iter struct { + ctx C.rados_list_ctx_t + err error + entry string +} + +type IterToken uint32 + +// Return a Iterator object that can be used to list the object names in the current pool +func (ioctx *IOContext) Iter() (*Iter, error) { + iter := Iter{} + if cerr := C.rados_nobjects_list_open(ioctx.ioctx, &iter.ctx); cerr < 0 { + return nil, GetRadosError(int(cerr)) + } + return &iter, nil +} + +// Returns a token marking the current position of the iterator. To be used in combination with Iter.Seek() +func (iter *Iter) Token() IterToken { + return IterToken(C.rados_nobjects_list_get_pg_hash_position(iter.ctx)) +} + +func (iter *Iter) Seek(token IterToken) { + C.rados_nobjects_list_seek(iter.ctx, C.uint32_t(token)) +} + +// Next retrieves the next object name in the pool/namespace iterator. +// Upon a successful invocation (return value of true), the Value method should +// be used to obtain the name of the retrieved object name. When the iterator is +// exhausted, Next returns false. The Err method should used to verify whether the +// end of the iterator was reached, or the iterator received an error. +// +// Example: +// iter := pool.Iter() +// defer iter.Close() +// for iter.Next() { +// fmt.Printf("%v\n", iter.Value()) +// } +// return iter.Err() +// +func (iter *Iter) Next() bool { + var c_entry *C.char + if cerr := C.rados_nobjects_list_next(iter.ctx, &c_entry, nil, nil); cerr < 0 { + iter.err = GetRadosError(int(cerr)) + return false + } + iter.entry = C.GoString(c_entry) + return true +} + +// Returns the current value of the iterator (object name), after a successful call to Next. +func (iter *Iter) Value() string { + if iter.err != nil { + return "" + } + return iter.entry +} + +// Checks whether the iterator has encountered an error. +func (iter *Iter) Err() error { + if iter.err == RadosErrorNotFound { + return nil + } + return iter.err +} + +// Closes the iterator cursor on the server. Be aware that iterators are not closed automatically +// at the end of iteration. +func (iter *Iter) Close() { + C.rados_nobjects_list_close(iter.ctx) +} + +// Take an exclusive lock on an object. +func (ioctx *IOContext) LockExclusive(oid, name, cookie, desc string, duration time.Duration, flags *byte) (int, error) { + c_oid := C.CString(oid) + c_name := C.CString(name) + c_cookie := C.CString(cookie) + c_desc := C.CString(desc) + + var c_duration C.struct_timeval + if duration != 0 { + tv := syscall.NsecToTimeval(duration.Nanoseconds()) + c_duration = C.struct_timeval{tv_sec: C.ceph_time_t(tv.Sec), tv_usec: C.ceph_suseconds_t(tv.Usec)} + } + + var c_flags C.uint8_t + if flags != nil { + c_flags = C.uint8_t(*flags) + } + + defer C.free(unsafe.Pointer(c_oid)) + defer C.free(unsafe.Pointer(c_name)) + defer C.free(unsafe.Pointer(c_cookie)) + defer C.free(unsafe.Pointer(c_desc)) + + ret := C.rados_lock_exclusive( + ioctx.ioctx, + c_oid, + c_name, + c_cookie, + c_desc, + &c_duration, + c_flags) + + // 0 on success, negative error code on failure + // -EBUSY if the lock is already held by another (client, cookie) pair + // -EEXIST if the lock is already held by the same (client, cookie) pair + + switch ret { + case 0: + return int(ret), nil + case -16: // EBUSY + return int(ret), nil + case -17: // EEXIST + return int(ret), nil + default: + return int(ret), RadosError(int(ret)) + } +} + +// Take a shared lock on an object. +func (ioctx *IOContext) LockShared(oid, name, cookie, tag, desc string, duration time.Duration, flags *byte) (int, error) { + c_oid := C.CString(oid) + c_name := C.CString(name) + c_cookie := C.CString(cookie) + c_tag := C.CString(tag) + c_desc := C.CString(desc) + + var c_duration C.struct_timeval + if duration != 0 { + tv := syscall.NsecToTimeval(duration.Nanoseconds()) + c_duration = C.struct_timeval{tv_sec: C.ceph_time_t(tv.Sec), tv_usec: C.ceph_suseconds_t(tv.Usec)} + } + + var c_flags C.uint8_t + if flags != nil { + c_flags = C.uint8_t(*flags) + } + + defer C.free(unsafe.Pointer(c_oid)) + defer C.free(unsafe.Pointer(c_name)) + defer C.free(unsafe.Pointer(c_cookie)) + defer C.free(unsafe.Pointer(c_tag)) + defer C.free(unsafe.Pointer(c_desc)) + + ret := C.rados_lock_shared( + ioctx.ioctx, + c_oid, + c_name, + c_cookie, + c_tag, + c_desc, + &c_duration, + c_flags) + + // 0 on success, negative error code on failure + // -EBUSY if the lock is already held by another (client, cookie) pair + // -EEXIST if the lock is already held by the same (client, cookie) pair + + switch ret { + case 0: + return int(ret), nil + case -16: // EBUSY + return int(ret), nil + case -17: // EEXIST + return int(ret), nil + default: + return int(ret), RadosError(int(ret)) + } +} + +// Release a shared or exclusive lock on an object. +func (ioctx *IOContext) Unlock(oid, name, cookie string) (int, error) { + c_oid := C.CString(oid) + c_name := C.CString(name) + c_cookie := C.CString(cookie) + + defer C.free(unsafe.Pointer(c_oid)) + defer C.free(unsafe.Pointer(c_name)) + defer C.free(unsafe.Pointer(c_cookie)) + + // 0 on success, negative error code on failure + // -ENOENT if the lock is not held by the specified (client, cookie) pair + + ret := C.rados_unlock( + ioctx.ioctx, + c_oid, + c_name, + c_cookie) + + switch ret { + case 0: + return int(ret), nil + case -2: // -ENOENT + return int(ret), nil + default: + return int(ret), RadosError(int(ret)) + } +} + +// List clients that have locked the named object lock and information about the lock. +// The number of bytes required in each buffer is put in the corresponding size out parameter. +// If any of the provided buffers are too short, -ERANGE is returned after these sizes are filled in. +func (ioctx *IOContext) ListLockers(oid, name string) (*LockInfo, error) { + c_oid := C.CString(oid) + c_name := C.CString(name) + + c_tag := (*C.char)(C.malloc(C.size_t(1024))) + c_clients := (*C.char)(C.malloc(C.size_t(1024))) + c_cookies := (*C.char)(C.malloc(C.size_t(1024))) + c_addrs := (*C.char)(C.malloc(C.size_t(1024))) + + var c_exclusive C.int + c_tag_len := C.size_t(1024) + c_clients_len := C.size_t(1024) + c_cookies_len := C.size_t(1024) + c_addrs_len := C.size_t(1024) + + defer C.free(unsafe.Pointer(c_oid)) + defer C.free(unsafe.Pointer(c_name)) + defer C.free(unsafe.Pointer(c_tag)) + defer C.free(unsafe.Pointer(c_clients)) + defer C.free(unsafe.Pointer(c_cookies)) + defer C.free(unsafe.Pointer(c_addrs)) + + ret := C.rados_list_lockers( + ioctx.ioctx, + c_oid, + c_name, + &c_exclusive, + c_tag, + &c_tag_len, + c_clients, + &c_clients_len, + c_cookies, + &c_cookies_len, + c_addrs, + &c_addrs_len) + + splitCString := func(items *C.char, itemsLen C.size_t) []string { + currLen := 0 + clients := []string{} + for currLen < int(itemsLen) { + client := C.GoString(C.nextChunk(&items)) + clients = append(clients, client) + currLen += len(client) + 1 + } + return clients + } + + if ret < 0 { + return nil, RadosError(int(ret)) + } else { + return &LockInfo{int(ret), c_exclusive == 1, C.GoString(c_tag), splitCString(c_clients, c_clients_len), splitCString(c_cookies, c_cookies_len), splitCString(c_addrs, c_addrs_len)}, nil + } +} + +// Releases a shared or exclusive lock on an object, which was taken by the specified client. +func (ioctx *IOContext) BreakLock(oid, name, client, cookie string) (int, error) { + c_oid := C.CString(oid) + c_name := C.CString(name) + c_client := C.CString(client) + c_cookie := C.CString(cookie) + + defer C.free(unsafe.Pointer(c_oid)) + defer C.free(unsafe.Pointer(c_name)) + defer C.free(unsafe.Pointer(c_client)) + defer C.free(unsafe.Pointer(c_cookie)) + + // 0 on success, negative error code on failure + // -ENOENT if the lock is not held by the specified (client, cookie) pair + // -EINVAL if the client cannot be parsed + + ret := C.rados_break_lock( + ioctx.ioctx, + c_oid, + c_name, + c_client, + c_cookie) + + switch ret { + case 0: + return int(ret), nil + case -2: // -ENOENT + return int(ret), nil + case -22: // -EINVAL + return int(ret), nil + default: + return int(ret), RadosError(int(ret)) + } +} diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/rados/rados.go b/Godeps/_workspace/src/github.com/ceph/go-ceph/rados/rados.go new file mode 100644 index 0000000..944e642 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/rados/rados.go @@ -0,0 +1,83 @@ +package rados + +// #cgo LDFLAGS: -lrados +// #include +// #include +// #include +import "C" + +import ( + "fmt" + "unsafe" +) + +type RadosError int + +func (e RadosError) Error() string { + return fmt.Sprintf("rados: %s", C.GoString(C.strerror(C.int(-e)))) +} + +var RadosErrorNotFound = RadosError(-C.ENOENT) +var RadosErrorPermissionDenied = RadosError(-C.EPERM) + +func GetRadosError(err int) error { + if err == 0 { + return nil + } + return RadosError(err) +} + +// 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) +} + +// 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) + + if ret == 0 { + return conn, nil + } else { + return nil, RadosError(int(ret)) + } +} + +// NewConnWithUser creates a new connection object with a custom username. +// It returns the connection and an error, if any. +func NewConnWithUser(user string) (*Conn, error) { + c_user := C.CString(user) + defer C.free(unsafe.Pointer(c_user)) + + conn := &Conn{} + ret := C.rados_create(&conn.cluster, c_user) + + if ret == 0 { + return conn, nil + } else { + return nil, RadosError(int(ret)) + } +} + +// NewConnWithClusterAndUser creates a new connection object for a specific cluster and username. +// It returns the connection and an error, if any. +func NewConnWithClusterAndUser(clusterName string, userName string) (*Conn, error) { + c_cluster_name := C.CString(clusterName) + defer C.free(unsafe.Pointer(c_cluster_name)) + + c_name := C.CString(userName) + defer C.free(unsafe.Pointer(c_name)) + + conn := &Conn{} + ret := C.rados_create2(&conn.cluster, c_cluster_name, c_name, 0) + if ret == 0 { + return conn, nil + } else { + return nil, RadosError(int(ret)) + } +} diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/rados/rados_test.go b/Godeps/_workspace/src/github.com/ceph/go-ceph/rados/rados_test.go new file mode 100644 index 0000000..dcd636e --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/rados/rados_test.go @@ -0,0 +1,1022 @@ +package rados_test + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net" + "os" + "os/exec" + "sort" + "testing" + "time" + + "github.com/ceph/go-ceph/rados" + "github.com/stretchr/testify/assert" +) + +func GetUUID() string { + 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") +} + +func TestGetSetConfigOption(t *testing.T) { + conn, _ := rados.NewConn() + + // 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") + + 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") +} + +func TestParseDefaultConfigEnv(t *testing.T) { + conn, _ := rados.NewConn() + + 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 = conn.ParseDefaultConfigEnv() + assert.NoError(t, err) + + 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() + + 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) + + 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() + + poolname := GetUUID() + err := conn.MakePool(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) + + // 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 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") +} + +func TestGetFSID(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + fsid, err := conn.GetFSID() + assert.NoError(t, err) + assert.NotEqual(t, fsid, "") + + conn.Shutdown() +} + +func TestGetInstanceID(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + id := conn.GetInstanceID() + assert.NotEqual(t, id, 0) + + conn.Shutdown() +} + +func TestMakeDeletePool(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + // 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 + } + } + + // create pool + err = conn.MakePool(new_name) + 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 + } + } + + if !found { + t.Error("Cannot find newly created pool") + } + + // delete the pool + err = conn.DeletePool(new_name) + assert.NoError(t, err) + + // verify that it is gone + + // 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 + } + } + + if found { + t.Error("Deleted pool still exists") + } + + conn.Shutdown() +} + +func TestPingMonitor(t *testing.T) { + 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 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 + } + } + + t.Error("Could not find a valid monitor id") + + conn.Shutdown() +} + +func TestReadConfigFile(t *testing.T) { + 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") + + // 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) + + // 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") + + // cleanup + file.Close() + os.Remove(file.Name()) +} + +func TestWaitForLatestOSDMap(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + err := conn.WaitForLatestOSDMap() + assert.NoError(t, err) + + conn.Shutdown() +} + +func TestReadWrite(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + // make pool + pool_name := GetUUID() + err := conn.MakePool(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_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) + + bytes_in = []byte("input another data") + err = pool.WriteFull("obj", bytes_in) + assert.NoError(t, err) + + 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) + + pool.Destroy() + conn.Shutdown() +} + +func TestAppend(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + // make pool + pool_name := GetUUID() + err := conn.MakePool(pool_name) + assert.NoError(t, err) + + pool, err := conn.OpenIOContext(pool_name) + assert.NoError(t, err) + + bytes_accum := []byte{} + for _, str_in := range []string{"input", " ", "another", " ", "data"} { + bytes_in := []byte(str_in) + err = pool.Append("obj", bytes_in) + assert.NoError(t, err) + + bytes_accum = append(bytes_accum, bytes_in...) + bytes_out := make([]byte, len(bytes_accum)) + n_out, err := pool.Read("obj", bytes_out, 0) + + assert.NoError(t, err) + assert.Equal(t, n_out, len(bytes_accum)) + assert.Equal(t, bytes_accum, bytes_out) + } + + pool.Destroy() + conn.Shutdown() +} + +func TestNotFound(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + // make pool + pool_name := GetUUID() + err := conn.MakePool(pool_name) + assert.NoError(t, err) + + pool, err := conn.OpenIOContext(pool_name) + assert.NoError(t, err) + + size := 128 + bytes_out := make([]byte, size) + _, err = pool.Read("obj", bytes_out, 0) + assert.Equal(t, err, rados.RadosErrorNotFound) + + err = pool.Delete("obj") + assert.Equal(t, err, rados.RadosErrorNotFound) + + pool.Destroy() + conn.Shutdown() +} + +func TestObjectStat(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + pool_name := GetUUID() + err := conn.MakePool(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) + + stat, err := pool.Stat("obj") + assert.Equal(t, uint64(len(bytes_in)), stat.Size) + assert.NotNil(t, stat.ModTime) + + _, err = pool.Stat("notfound") + assert.Equal(t, err, rados.RadosErrorNotFound) + + pool.Destroy() + conn.Shutdown() +} + +func TestGetPoolStats(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + poolname := GetUUID() + err := conn.MakePool(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) + + // 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 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") +} + +func TestGetPoolName(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + poolname := GetUUID() + err := conn.MakePool(poolname) + assert.NoError(t, err) + + ioctx, err := conn.OpenIOContext(poolname) + assert.NoError(t, err) + + poolname_ret, err := ioctx.GetPoolName() + assert.NoError(t, err) + + assert.Equal(t, poolname, poolname_ret) + + ioctx.Destroy() + conn.Shutdown() +} + +func TestMonCommand(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + defer conn.Shutdown() + + command, err := json.Marshal(map[string]string{"prefix": "df", "format": "json"}) + assert.NoError(t, err) + + buf, info, err := conn.MonCommand(command) + assert.NoError(t, err) + assert.Equal(t, info, "") + + var message map[string]interface{} + err = json.Unmarshal(buf, &message) + assert.NoError(t, err) +} + +func TestMonCommandWithInputBuffer(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + defer conn.Shutdown() + + // first add the new test user, specifying its key in the input buffer + command, err := json.Marshal(map[string]interface{}{ + "prefix": "auth add", + "format": "json", + "entity": "client.testMonCommandUser", + }) + assert.NoError(t, err) + + inbuf := []byte(`[client.testMonCommandUser] +key = AQD4PGNXBZJNHhAA582iUgxe9DsN+MqFN4Z6Jw== +`) + + buf, info, err := conn.MonCommandWithInputBuffer(command, inbuf) + assert.NoError(t, err) + assert.Equal(t, "added key for client.testMonCommandUser", info) + assert.Equal(t, "", string(buf[:])) + + // now get the key, and verify it is equal to the key we specified in the input buffer for "auth add" + command, err = json.Marshal(map[string]interface{}{ + "prefix": "auth get-key", + "format": "json", + "entity": "client.testMonCommandUser", + }) + assert.NoError(t, err) + + buf, info, err = conn.MonCommand(command) + assert.NoError(t, err) + assert.Equal(t, "", info) + assert.Equal(t, `{"key":"AQD4PGNXBZJNHhAA582iUgxe9DsN+MqFN4Z6Jw=="}`, string(buf[:])) +} + +func TestObjectListObjects(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + poolname := GetUUID() + err := conn.MakePool(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) + + 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)) + + sort.Strings(objectList) + sort.Strings(createdList) + + assert.Equal(t, objectList, createdList) +} + +func TestObjectIterator(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + poolname := GetUUID() + err := conn.MakePool(poolname) + assert.NoError(t, err) + + ioctx, err := conn.OpenIOContext(poolname) + assert.NoError(t, err) + + objectList := []string{} + iter, err := ioctx.Iter() + assert.NoError(t, err) + for iter.Next() { + objectList = append(objectList, iter.Value()) + } + iter.Close() + assert.NoError(t, iter.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) + + iter, err = ioctx.Iter() + assert.NoError(t, err) + for iter.Next() { + objectList = append(objectList, iter.Value()) + } + iter.Close() + assert.NoError(t, iter.Err()) + assert.Equal(t, len(objectList), len(createdList)) + + sort.Strings(objectList) + sort.Strings(createdList) + + assert.Equal(t, objectList, createdList) +} + +func TestNewConnWithUser(t *testing.T) { + _, err := rados.NewConnWithUser("admin") + assert.Equal(t, err, nil) +} + +func TestNewConnWithClusterAndUser(t *testing.T) { + _, err := rados.NewConnWithClusterAndUser("ceph", "client.admin") + assert.Equal(t, err, nil) +} + +func TestReadWriteXattr(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + // make pool + pool_name := GetUUID() + err := conn.MakePool(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) + + my_xattr_in := []byte("my_value") + err = pool.SetXattr("obj", "my_key", my_xattr_in) + assert.NoError(t, err) + + my_xattr_out := make([]byte, len(my_xattr_in)) + n_out, err := pool.GetXattr("obj", "my_key", my_xattr_out) + + assert.Equal(t, n_out, len(my_xattr_in)) + assert.Equal(t, my_xattr_in, my_xattr_out) + + pool.Destroy() +} + +func TestListXattrs(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + // make pool + pool_name := GetUUID() + err := conn.MakePool(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) + + input_xattrs := make(map[string][]byte) + for i := 0; i < 200; i++ { + name := fmt.Sprintf("key_%d", i) + data := []byte(GetUUID()) + err = pool.SetXattr("obj", name, data) + assert.NoError(t, err) + input_xattrs[name] = data + } + + output_xattrs := make(map[string][]byte) + output_xattrs, err = pool.ListXattrs("obj") + assert.NoError(t, err) + assert.Equal(t, len(input_xattrs), len(output_xattrs)) + assert.Equal(t, input_xattrs, output_xattrs) + + pool.Destroy() +} + +func TestRmXattr(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + pool_name := GetUUID() + err := conn.MakePool(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) + + key := "key1" + val := []byte("val1") + err = pool.SetXattr("obj", key, val) + assert.NoError(t, err) + + key = "key2" + val = []byte("val2") + err = pool.SetXattr("obj", key, val) + assert.NoError(t, err) + + xattr_list := make(map[string][]byte) + xattr_list, err = pool.ListXattrs("obj") + assert.NoError(t, err) + assert.Equal(t, len(xattr_list), 2) + + pool.RmXattr("obj", "key2") + xattr_list, err = pool.ListXattrs("obj") + assert.NoError(t, err) + assert.Equal(t, len(xattr_list), 1) + + found := false + for key, _ = range xattr_list { + if key == "key2" { + found = true + } + + } + + if found { + t.Error("Deleted pool still exists") + } + + pool.Destroy() +} + +func TestReadWriteOmap(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + pool_name := GetUUID() + err := conn.MakePool(pool_name) + assert.NoError(t, err) + + pool, err := conn.OpenIOContext(pool_name) + assert.NoError(t, err) + + // Set + orig := map[string][]byte{ + "key1": []byte("value1"), + "key2": []byte("value2"), + "prefixed-key3": []byte("value3"), + "empty": []byte(""), + } + + err = pool.SetOmap("obj", orig) + assert.NoError(t, err) + + // List + remaining := map[string][]byte{} + for k, v := range orig { + remaining[k] = v + } + + err = pool.ListOmapValues("obj", "", "", 4, func(key string, value []byte) { + assert.Equal(t, remaining[key], value) + delete(remaining, key) + }) + assert.NoError(t, err) + assert.Equal(t, 0, len(remaining)) + + // Get (with a fixed number of keys) + fetched, err := pool.GetOmapValues("obj", "", "", 4) + assert.NoError(t, err) + assert.Equal(t, orig, fetched) + + // Get All (with an iterator size bigger than the map size) + fetched, err = pool.GetAllOmapValues("obj", "", "", 100) + assert.NoError(t, err) + assert.Equal(t, orig, fetched) + + // Get All (with an iterator size smaller than the map size) + fetched, err = pool.GetAllOmapValues("obj", "", "", 1) + assert.NoError(t, err) + assert.Equal(t, orig, fetched) + + // Remove + err = pool.RmOmapKeys("obj", []string{"key1", "prefixed-key3"}) + assert.NoError(t, err) + + fetched, err = pool.GetOmapValues("obj", "", "", 4) + assert.NoError(t, err) + assert.Equal(t, map[string][]byte{ + "key2": []byte("value2"), + "empty": []byte(""), + }, fetched) + + // Clear + err = pool.CleanOmap("obj") + assert.NoError(t, err) + + fetched, err = pool.GetOmapValues("obj", "", "", 4) + assert.NoError(t, err) + assert.Equal(t, map[string][]byte{}, fetched) + + pool.Destroy() +} + +func TestReadFilterOmap(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + pool_name := GetUUID() + err := conn.MakePool(pool_name) + assert.NoError(t, err) + + pool, err := conn.OpenIOContext(pool_name) + assert.NoError(t, err) + + orig := map[string][]byte{ + "key1": []byte("value1"), + "prefixed-key3": []byte("value3"), + "key2": []byte("value2"), + } + + err = pool.SetOmap("obj", orig) + assert.NoError(t, err) + + // filter by prefix + fetched, err := pool.GetOmapValues("obj", "", "prefixed", 4) + assert.NoError(t, err) + assert.Equal(t, map[string][]byte{ + "prefixed-key3": []byte("value3"), + }, fetched) + + // "start_after" a key + fetched, err = pool.GetOmapValues("obj", "key1", "", 4) + assert.NoError(t, err) + assert.Equal(t, map[string][]byte{ + "prefixed-key3": []byte("value3"), + "key2": []byte("value2"), + }, fetched) + + // maxReturn + fetched, err = pool.GetOmapValues("obj", "", "key", 1) + assert.NoError(t, err) + assert.Equal(t, map[string][]byte{ + "key1": []byte("value1"), + }, fetched) + + pool.Destroy() +} + +func TestSetNamespace(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + pool_name := GetUUID() + err := conn.MakePool(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) + + stat, err := pool.Stat("obj") + assert.Equal(t, uint64(len(bytes_in)), stat.Size) + assert.NotNil(t, stat.ModTime) + + pool.SetNamespace("space1") + stat, err = pool.Stat("obj") + assert.Equal(t, err, rados.RadosErrorNotFound) + + bytes_in = []byte("input data") + err = pool.Write("obj2", bytes_in, 0) + assert.NoError(t, err) + + pool.SetNamespace("") + + stat, err = pool.Stat("obj2") + assert.Equal(t, err, rados.RadosErrorNotFound) + + stat, err = pool.Stat("obj") + assert.Equal(t, uint64(len(bytes_in)), stat.Size) + assert.NotNil(t, stat.ModTime) + + pool.Destroy() + conn.Shutdown() +} + +func TestLocking(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + pool_name := GetUUID() + err := conn.MakePool(pool_name) + assert.NoError(t, err) + + pool, err := conn.OpenIOContext(pool_name) + assert.NoError(t, err) + + // lock ex + res, err := pool.LockExclusive("obj", "myLock", "myCookie", "this is a test lock", 0, nil) + assert.NoError(t, err) + assert.Equal(t, 0, res) + + // verify lock ex + info, err := pool.ListLockers("obj", "myLock") + assert.NoError(t, err) + assert.Equal(t, 1, len(info.Clients)) + assert.Equal(t, true, info.Exclusive) + + // fail to lock ex again + res, err = pool.LockExclusive("obj", "myLock", "myCookie", "this is a description", 0, nil) + assert.NoError(t, err) + assert.Equal(t, -17, res) + + // fail to lock sh + res, err = pool.LockShared("obj", "myLock", "myCookie", "", "a description", 0, nil) + assert.NoError(t, err) + assert.Equal(t, -17, res) + + // unlock + res, err = pool.Unlock("obj", "myLock", "myCookie") + assert.NoError(t, err) + assert.Equal(t, 0, res) + + // verify unlock + info, err = pool.ListLockers("obj", "myLock") + assert.NoError(t, err) + assert.Equal(t, 0, len(info.Clients)) + + // lock sh + res, err = pool.LockShared("obj", "myLock", "myCookie", "", "a description", 0, nil) + assert.NoError(t, err) + assert.Equal(t, 0, res) + + // verify lock sh + info, err = pool.ListLockers("obj", "myLock") + assert.NoError(t, err) + assert.Equal(t, 1, len(info.Clients)) + assert.Equal(t, false, info.Exclusive) + + // fail to lock sh again + res, err = pool.LockExclusive("obj", "myLock", "myCookie", "a description", 0, nil) + assert.NoError(t, err) + assert.Equal(t, -17, res) + + // fail to lock ex + res, err = pool.LockExclusive("obj", "myLock", "myCookie", "this is a test lock", 0, nil) + assert.NoError(t, err) + assert.Equal(t, res, -17) + + // break the lock + res, err = pool.BreakLock("obj", "myLock", info.Clients[0], "myCookie") + assert.NoError(t, err) + assert.Equal(t, 0, res) + + // verify lock broken + info, err = pool.ListLockers("obj", "myLock") + assert.NoError(t, err) + assert.Equal(t, 0, len(info.Clients)) + + // lock sh with duration + res, err = pool.LockShared("obj", "myLock", "myCookie", "", "a description", time.Millisecond, nil) + assert.NoError(t, err) + assert.Equal(t, 0, res) + + // verify lock sh expired + time.Sleep(time.Second) + info, err = pool.ListLockers("obj", "myLock") + assert.NoError(t, err) + assert.Equal(t, 0, len(info.Clients)) + + // lock sh with duration + res, err = pool.LockExclusive("obj", "myLock", "myCookie", "a description", time.Millisecond, nil) + assert.NoError(t, err) + assert.Equal(t, 0, res) + + // verify lock sh expired + time.Sleep(time.Second) + info, err = pool.ListLockers("obj", "myLock") + assert.NoError(t, err) + assert.Equal(t, 0, len(info.Clients)) + + pool.Destroy() + conn.Shutdown() +} + +func TestOmapOnNonexistentObjectError(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + pool_name := GetUUID() + err := conn.MakePool(pool_name) + assert.NoError(t, err) + + pool, err := conn.OpenIOContext(pool_name) + assert.NoError(t, err) + + //This object does not exist + objname := GetUUID() + + _, err = pool.GetAllOmapValues(objname, "", "", 100) + assert.Equal(t, err, rados.RadosErrorNotFound) +} diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/rbd/doc.go b/Godeps/_workspace/src/github.com/ceph/go-ceph/rbd/doc.go new file mode 100644 index 0000000..70e70f3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/rbd/doc.go @@ -0,0 +1,4 @@ +/* +Wrappers around librbd. +*/ +package rbd diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/rbd/rbd.go b/Godeps/_workspace/src/github.com/ceph/go-ceph/rbd/rbd.go new file mode 100644 index 0000000..8e3fd81 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/rbd/rbd.go @@ -0,0 +1,874 @@ +package rbd + +// #cgo LDFLAGS: -lrbd +// #include +// #include +// #include +// #include +import "C" + +import ( + "bytes" + "errors" + "fmt" + "github.com/ceph/go-ceph/rados" + "io" + "unsafe" +) + +// +type RBDError int + +var RbdErrorImageNotOpen = errors.New("RBD image not open") +var RbdErrorNotFound = errors.New("RBD image not found") + +//Rdb feature +var RbdFeatureLayering = uint64(1 << 0) +var RbdFeatureStripingV2 = uint64(1 << 1) + +// +type ImageInfo struct { + Size uint64 + Obj_size uint64 + Num_objs uint64 + Order int + Block_name_prefix string + Parent_pool int64 + Parent_name string +} + +// +type SnapInfo struct { + Id uint64 + Size uint64 + Name string +} + +// +type Locker struct { + Client string + Cookie string + Addr string +} + +// +type Image struct { + io.Reader + io.Writer + io.Seeker + io.ReaderAt + io.WriterAt + name string + offset int64 + ioctx *rados.IOContext + image C.rbd_image_t +} + +// +type Snapshot struct { + image *Image + name string +} + +// +func split(buf []byte) (values []string) { + tmp := bytes.Split(buf[:len(buf)-1], []byte{0}) + for _, s := range tmp { + if len(s) > 0 { + go_s := C.GoString((*C.char)(unsafe.Pointer(&s[0]))) + values = append(values, go_s) + } + } + return values +} + +// +func (e RBDError) Error() string { + return fmt.Sprintf("rbd: ret=%d", e) +} + +// +func GetError(err C.int) error { + if err != 0 { + if err == -C.ENOENT { + return RbdErrorNotFound + } + return RBDError(err) + } else { + return nil + } +} + +// +func Version() (int, int, int) { + var c_major, c_minor, c_patch C.int + C.rbd_version(&c_major, &c_minor, &c_patch) + return int(c_major), int(c_minor), int(c_patch) +} + +// GetImageNames returns the list of current RBD images. +func GetImageNames(ioctx *rados.IOContext) (names []string, err error) { + buf := make([]byte, 4096) + for { + size := C.size_t(len(buf)) + ret := C.rbd_list(C.rados_ioctx_t(ioctx.Pointer()), + (*C.char)(unsafe.Pointer(&buf[0])), &size) + if ret == -34 { // FIXME + buf = make([]byte, size) + continue + } else if ret < 0 { + return nil, RBDError(ret) + } + tmp := bytes.Split(buf[:size-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 + } +} + +// +func GetImage(ioctx *rados.IOContext, name string) *Image { + return &Image{ + ioctx: ioctx, + name: name, + } +} + +// int rbd_create(rados_ioctx_t io, const char *name, uint64_t size, int *order); +// int rbd_create2(rados_ioctx_t io, const char *name, uint64_t size, +// uint64_t features, int *order); +// int rbd_create3(rados_ioctx_t io, const char *name, uint64_t size, +// uint64_t features, int *order, +// uint64_t stripe_unit, uint64_t stripe_count); +func Create(ioctx *rados.IOContext, name string, size uint64, order int, + args ...uint64) (image *Image, err error) { + var ret C.int + var c_order C.int = C.int(order) + var c_name *C.char = C.CString(name) + defer C.free(unsafe.Pointer(c_name)) + + switch len(args) { + case 2: + ret = C.rbd_create3(C.rados_ioctx_t(ioctx.Pointer()), + c_name, C.uint64_t(size), + C.uint64_t(args[0]), &c_order, + C.uint64_t(args[1]), C.uint64_t(args[2])) + case 1: + ret = C.rbd_create2(C.rados_ioctx_t(ioctx.Pointer()), + c_name, C.uint64_t(size), + C.uint64_t(args[0]), &c_order) + case 0: + ret = C.rbd_create(C.rados_ioctx_t(ioctx.Pointer()), + c_name, C.uint64_t(size), &c_order) + default: + return nil, errors.New("Wrong number of argument") + } + + if ret < 0 { + return nil, RBDError(int(ret)) + } + + return &Image{ + ioctx: ioctx, + name: name, + }, nil +} + +// int rbd_clone(rados_ioctx_t p_ioctx, const char *p_name, +// const char *p_snapname, rados_ioctx_t c_ioctx, +// const char *c_name, uint64_t features, int *c_order); +// int rbd_clone2(rados_ioctx_t p_ioctx, const char *p_name, +// const char *p_snapname, rados_ioctx_t c_ioctx, +// const char *c_name, uint64_t features, int *c_order, +// uint64_t stripe_unit, int stripe_count); +func (image *Image) Clone(snapname string, c_ioctx *rados.IOContext, c_name string, features uint64, order int) (*Image, error) { + var c_order C.int = C.int(order) + var c_p_name *C.char = C.CString(image.name) + var c_p_snapname *C.char = C.CString(snapname) + var c_c_name *C.char = C.CString(c_name) + defer C.free(unsafe.Pointer(c_p_name)) + defer C.free(unsafe.Pointer(c_p_snapname)) + defer C.free(unsafe.Pointer(c_c_name)) + + ret := C.rbd_clone(C.rados_ioctx_t(image.ioctx.Pointer()), + c_p_name, c_p_snapname, + C.rados_ioctx_t(c_ioctx.Pointer()), + c_c_name, C.uint64_t(features), &c_order) + if ret < 0 { + return nil, RBDError(int(ret)) + } + + return &Image{ + ioctx: c_ioctx, + name: c_name, + }, nil +} + +// int rbd_remove(rados_ioctx_t io, const char *name); +// int rbd_remove_with_progress(rados_ioctx_t io, const char *name, +// librbd_progress_fn_t cb, void *cbdata); +func (image *Image) Remove() error { + var c_name *C.char = C.CString(image.name) + defer C.free(unsafe.Pointer(c_name)) + return GetError(C.rbd_remove(C.rados_ioctx_t(image.ioctx.Pointer()), c_name)) +} + +// int rbd_rename(rados_ioctx_t src_io_ctx, const char *srcname, const char *destname); +func (image *Image) Rename(destname string) error { + var c_srcname *C.char = C.CString(image.name) + var c_destname *C.char = C.CString(destname) + defer C.free(unsafe.Pointer(c_srcname)) + defer C.free(unsafe.Pointer(c_destname)) + err := RBDError(C.rbd_rename(C.rados_ioctx_t(image.ioctx.Pointer()), + c_srcname, c_destname)) + if err == 0 { + image.name = destname + return nil + } + return err +} + +// int rbd_open(rados_ioctx_t io, const char *name, rbd_image_t *image, const char *snap_name); +// int rbd_open_read_only(rados_ioctx_t io, const char *name, rbd_image_t *image, +// const char *snap_name); +func (image *Image) Open(args ...interface{}) error { + var c_image C.rbd_image_t + var c_name *C.char = C.CString(image.name) + var c_snap_name *C.char + var ret C.int + var read_only bool = false + + defer C.free(unsafe.Pointer(c_name)) + for _, arg := range args { + switch t := arg.(type) { + case string: + if t != "" { + c_snap_name = C.CString(t) + defer C.free(unsafe.Pointer(c_snap_name)) + } + case bool: + read_only = t + default: + return errors.New("Unexpected argument") + } + } + + if read_only { + ret = C.rbd_open_read_only(C.rados_ioctx_t(image.ioctx.Pointer()), c_name, + &c_image, c_snap_name) + } else { + ret = C.rbd_open(C.rados_ioctx_t(image.ioctx.Pointer()), c_name, + &c_image, c_snap_name) + } + + image.image = c_image + + return GetError(ret) +} + +// int rbd_close(rbd_image_t image); +func (image *Image) Close() error { + if image.image == nil { + return RbdErrorImageNotOpen + } + + ret := C.rbd_close(image.image) + if ret != 0 { + return RBDError(ret) + } + image.image = nil + return nil +} + +// int rbd_resize(rbd_image_t image, uint64_t size); +func (image *Image) Resize(size uint64) error { + if image.image == nil { + return RbdErrorImageNotOpen + } + + return GetError(C.rbd_resize(image.image, C.uint64_t(size))) +} + +// int rbd_stat(rbd_image_t image, rbd_image_info_t *info, size_t infosize); +func (image *Image) Stat() (info *ImageInfo, err error) { + if image.image == nil { + return nil, RbdErrorImageNotOpen + } + + var c_stat C.rbd_image_info_t + ret := C.rbd_stat(image.image, + &c_stat, C.size_t(unsafe.Sizeof(info))) + if ret < 0 { + return info, RBDError(int(ret)) + } + + return &ImageInfo{ + Size: uint64(c_stat.size), + Obj_size: uint64(c_stat.obj_size), + Num_objs: uint64(c_stat.num_objs), + Order: int(c_stat.order), + Block_name_prefix: C.GoString((*C.char)(&c_stat.block_name_prefix[0])), + Parent_pool: int64(c_stat.parent_pool), + Parent_name: C.GoString((*C.char)(&c_stat.parent_name[0]))}, nil +} + +// int rbd_get_old_format(rbd_image_t image, uint8_t *old); +func (image *Image) IsOldFormat() (old_format bool, err error) { + if image.image == nil { + return false, RbdErrorImageNotOpen + } + + var c_old_format C.uint8_t + ret := C.rbd_get_old_format(image.image, + &c_old_format) + if ret < 0 { + return false, RBDError(int(ret)) + } + + return c_old_format != 0, nil +} + +// int rbd_size(rbd_image_t image, uint64_t *size); +func (image *Image) GetSize() (size uint64, err error) { + if image.image == nil { + return 0, RbdErrorImageNotOpen + } + + ret := C.rbd_get_size(image.image, + (*C.uint64_t)(&size)) + if ret < 0 { + return 0, RBDError(int(ret)) + } + + return size, nil +} + +// int rbd_get_features(rbd_image_t image, uint64_t *features); +func (image *Image) GetFeatures() (features uint64, err error) { + if image.image == nil { + return 0, RbdErrorImageNotOpen + } + + ret := C.rbd_get_features(image.image, + (*C.uint64_t)(&features)) + if ret < 0 { + return 0, RBDError(int(ret)) + } + + return features, nil +} + +// int rbd_get_stripe_unit(rbd_image_t image, uint64_t *stripe_unit); +func (image *Image) GetStripeUnit() (stripe_unit uint64, err error) { + if image.image == nil { + return 0, RbdErrorImageNotOpen + } + + ret := C.rbd_get_stripe_unit(image.image, (*C.uint64_t)(&stripe_unit)) + if ret < 0 { + return 0, RBDError(int(ret)) + } + + return stripe_unit, nil +} + +// int rbd_get_stripe_count(rbd_image_t image, uint64_t *stripe_count); +func (image *Image) GetStripeCount() (stripe_count uint64, err error) { + if image.image == nil { + return 0, RbdErrorImageNotOpen + } + + ret := C.rbd_get_stripe_count(image.image, (*C.uint64_t)(&stripe_count)) + if ret < 0 { + return 0, RBDError(int(ret)) + } + + return stripe_count, nil +} + +// int rbd_get_overlap(rbd_image_t image, uint64_t *overlap); +func (image *Image) GetOverlap() (overlap uint64, err error) { + if image.image == nil { + return 0, RbdErrorImageNotOpen + } + + ret := C.rbd_get_overlap(image.image, (*C.uint64_t)(&overlap)) + if ret < 0 { + return overlap, RBDError(int(ret)) + } + + return overlap, nil +} + +// int rbd_copy(rbd_image_t image, rados_ioctx_t dest_io_ctx, const char *destname); +// int rbd_copy2(rbd_image_t src, rbd_image_t dest); +// int rbd_copy_with_progress(rbd_image_t image, rados_ioctx_t dest_p, const char *destname, +// librbd_progress_fn_t cb, void *cbdata); +// int rbd_copy_with_progress2(rbd_image_t src, rbd_image_t dest, +// librbd_progress_fn_t cb, void *cbdata); +func (image *Image) Copy(args ...interface{}) error { + if image.image == nil { + return RbdErrorImageNotOpen + } + + switch t := args[0].(type) { + case rados.IOContext: + switch t2 := args[1].(type) { + case string: + var c_destname *C.char = C.CString(t2) + defer C.free(unsafe.Pointer(c_destname)) + return RBDError(C.rbd_copy(image.image, + C.rados_ioctx_t(t.Pointer()), + c_destname)) + default: + return errors.New("Must specify destname") + } + case Image: + var dest Image = t + if dest.image == nil { + return errors.New(fmt.Sprintf("RBD image %s is not open", dest.name)) + } + return GetError(C.rbd_copy2(image.image, + dest.image)) + default: + return errors.New("Must specify either destination pool " + + "or destination image") + } +} + +// int rbd_flatten(rbd_image_t image); +func (image *Image) Flatten() error { + if image.image == nil { + return errors.New(fmt.Sprintf("RBD image %s is not open", image.name)) + } + + return GetError(C.rbd_flatten(image.image)) +} + +// ssize_t rbd_list_children(rbd_image_t image, char *pools, size_t *pools_len, +// char *images, size_t *images_len); +func (image *Image) ListChildren() (pools []string, images []string, err error) { + if image.image == nil { + return nil, nil, RbdErrorImageNotOpen + } + + var c_pools_len, c_images_len C.size_t + + ret := C.rbd_list_children(image.image, + nil, &c_pools_len, + nil, &c_images_len) + if ret == 0 { + return nil, nil, nil + } + if ret < 0 && ret != -C.ERANGE { + return nil, nil, RBDError(int(ret)) + } + + pools_buf := make([]byte, c_pools_len) + images_buf := make([]byte, c_images_len) + + ret = C.rbd_list_children(image.image, + (*C.char)(unsafe.Pointer(&pools_buf[0])), + &c_pools_len, + (*C.char)(unsafe.Pointer(&images_buf[0])), + &c_images_len) + if ret < 0 { + return nil, nil, RBDError(int(ret)) + } + + tmp := bytes.Split(pools_buf[:c_pools_len-1], []byte{0}) + for _, s := range tmp { + if len(s) > 0 { + name := C.GoString((*C.char)(unsafe.Pointer(&s[0]))) + pools = append(pools, name) + } + } + + tmp = bytes.Split(images_buf[:c_images_len-1], []byte{0}) + for _, s := range tmp { + if len(s) > 0 { + name := C.GoString((*C.char)(unsafe.Pointer(&s[0]))) + images = append(images, name) + } + } + + return pools, images, nil +} + +// ssize_t rbd_list_lockers(rbd_image_t image, int *exclusive, +// char *tag, size_t *tag_len, +// char *clients, size_t *clients_len, +// char *cookies, size_t *cookies_len, +// char *addrs, size_t *addrs_len); +func (image *Image) ListLockers() (tag string, lockers []Locker, err error) { + if image.image == nil { + return "", nil, RbdErrorImageNotOpen + } + + var c_exclusive C.int + var c_tag_len, c_clients_len, c_cookies_len, c_addrs_len C.size_t + var c_locker_cnt C.ssize_t + + C.rbd_list_lockers(image.image, &c_exclusive, + nil, (*C.size_t)(&c_tag_len), + nil, (*C.size_t)(&c_clients_len), + nil, (*C.size_t)(&c_cookies_len), + nil, (*C.size_t)(&c_addrs_len)) + + // no locker held on rbd image when either c_clients_len, + // c_cookies_len or c_addrs_len is *0*, so just quickly returned + if int(c_clients_len) == 0 || int(c_cookies_len) == 0 || + int(c_addrs_len) ==0 { + lockers = make([]Locker, 0) + return "", lockers, nil + } + + tag_buf := make([]byte, c_tag_len) + clients_buf := make([]byte, c_clients_len) + cookies_buf := make([]byte, c_cookies_len) + addrs_buf := make([]byte, c_addrs_len) + + c_locker_cnt = C.rbd_list_lockers(image.image, &c_exclusive, + (*C.char)(unsafe.Pointer(&tag_buf[0])), (*C.size_t)(&c_tag_len), + (*C.char)(unsafe.Pointer(&clients_buf[0])), (*C.size_t)(&c_clients_len), + (*C.char)(unsafe.Pointer(&cookies_buf[0])), (*C.size_t)(&c_cookies_len), + (*C.char)(unsafe.Pointer(&addrs_buf[0])), (*C.size_t)(&c_addrs_len)) + + // rbd_list_lockers returns negative value for errors + // and *0* means no locker held on rbd image. + // but *0* is unexpected here because first rbd_list_lockers already + // dealt with no locker case + if int(c_locker_cnt) <= 0 { + return "", nil, RBDError(int(c_locker_cnt)) + } + + clients := split(clients_buf) + cookies := split(cookies_buf) + addrs := split(addrs_buf) + + lockers = make([]Locker, c_locker_cnt) + for i := 0; i < int(c_locker_cnt); i++ { + lockers[i] = Locker{Client: clients[i], + Cookie: cookies[i], + Addr: addrs[i]} + } + + return string(tag_buf), lockers, nil +} + +// int rbd_lock_exclusive(rbd_image_t image, const char *cookie); +func (image *Image) LockExclusive(cookie string) error { + if image.image == nil { + return RbdErrorImageNotOpen + } + + var c_cookie *C.char = C.CString(cookie) + defer C.free(unsafe.Pointer(c_cookie)) + + return GetError(C.rbd_lock_exclusive(image.image, c_cookie)) +} + +// int rbd_lock_shared(rbd_image_t image, const char *cookie, const char *tag); +func (image *Image) LockShared(cookie string, tag string) error { + if image.image == nil { + return RbdErrorImageNotOpen + } + + var c_cookie *C.char = C.CString(cookie) + var c_tag *C.char = C.CString(tag) + defer C.free(unsafe.Pointer(c_cookie)) + defer C.free(unsafe.Pointer(c_tag)) + + return GetError(C.rbd_lock_shared(image.image, c_cookie, c_tag)) +} + +// int rbd_lock_shared(rbd_image_t image, const char *cookie, const char *tag); +func (image *Image) Unlock(cookie string) error { + if image.image == nil { + return RbdErrorImageNotOpen + } + + var c_cookie *C.char = C.CString(cookie) + defer C.free(unsafe.Pointer(c_cookie)) + + return GetError(C.rbd_unlock(image.image, c_cookie)) +} + +// int rbd_break_lock(rbd_image_t image, const char *client, const char *cookie); +func (image *Image) BreakLock(client string, cookie string) error { + if image.image == nil { + return RbdErrorImageNotOpen + } + + var c_client *C.char = C.CString(client) + var c_cookie *C.char = C.CString(cookie) + defer C.free(unsafe.Pointer(c_client)) + defer C.free(unsafe.Pointer(c_cookie)) + + return GetError(C.rbd_break_lock(image.image, c_client, c_cookie)) +} + +// ssize_t rbd_read(rbd_image_t image, uint64_t ofs, size_t len, char *buf); +// TODO: int64_t rbd_read_iterate(rbd_image_t image, uint64_t ofs, size_t len, +// int (*cb)(uint64_t, size_t, const char *, void *), void *arg); +// TODO: int rbd_read_iterate2(rbd_image_t image, uint64_t ofs, uint64_t len, +// int (*cb)(uint64_t, size_t, const char *, void *), void *arg); +// TODO: int rbd_diff_iterate(rbd_image_t image, +// const char *fromsnapname, +// uint64_t ofs, uint64_t len, +// int (*cb)(uint64_t, size_t, int, void *), void *arg); +func (image *Image) Read(data []byte) (n int, err error) { + if image.image == nil { + return 0, RbdErrorImageNotOpen + } + + if len(data) == 0 { + return 0, nil + } + + ret := int(C.rbd_read( + image.image, + (C.uint64_t)(image.offset), + (C.size_t)(len(data)), + (*C.char)(unsafe.Pointer(&data[0])))) + + if ret < 0 { + return 0, RBDError(ret) + } + + image.offset += int64(ret) + if ret < n { + return ret, io.EOF + } + + return ret, nil +} + +// ssize_t rbd_write(rbd_image_t image, uint64_t ofs, size_t len, const char *buf); +func (image *Image) Write(data []byte) (n int, err error) { + ret := int(C.rbd_write(image.image, C.uint64_t(image.offset), + C.size_t(len(data)), (*C.char)(unsafe.Pointer(&data[0])))) + + if ret >= 0 { + image.offset += int64(ret) + } + + if ret != len(data) { + err = RBDError(-1) + } + + return ret, err +} + +func (image *Image) Seek(offset int64, whence int) (int64, error) { + switch whence { + case 0: + image.offset = offset + case 1: + image.offset += offset + case 2: + stats, err := image.Stat() + if err != nil { + return 0, err + } + image.offset = int64(stats.Size) - offset + default: + return 0, errors.New("Wrong value for whence") + } + return image.offset, nil +} + +// int rbd_discard(rbd_image_t image, uint64_t ofs, uint64_t len); +func (image *Image) Discard(ofs uint64, length uint64) error { + return RBDError(C.rbd_discard(image.image, C.uint64_t(ofs), + C.uint64_t(length))) +} + +func (image *Image) ReadAt(data []byte, off int64) (n int, err error) { + if image.image == nil { + return 0, RbdErrorImageNotOpen + } + + if len(data) == 0 { + return 0, nil + } + + ret := int(C.rbd_read( + image.image, + (C.uint64_t)(off), + (C.size_t)(len(data)), + (*C.char)(unsafe.Pointer(&data[0])))) + + if ret < 0 { + return 0, RBDError(ret) + } + + if ret < n { + return ret, io.EOF + } + + return ret, nil +} + +func (image *Image) WriteAt(data []byte, off int64) (n int, err error) { + if image.image == nil { + return 0, RbdErrorImageNotOpen + } + + if len(data) == 0 { + return 0, nil + } + + ret := int(C.rbd_write(image.image, C.uint64_t(off), + C.size_t(len(data)), (*C.char)(unsafe.Pointer(&data[0])))) + + if ret != len(data) { + err = RBDError(-1) + } + + return ret, err +} + +// int rbd_flush(rbd_image_t image); +func (image *Image) Flush() error { + return GetError(C.rbd_flush(image.image)) +} + +// int rbd_snap_list(rbd_image_t image, rbd_snap_info_t *snaps, int *max_snaps); +// void rbd_snap_list_end(rbd_snap_info_t *snaps); +func (image *Image) GetSnapshotNames() (snaps []SnapInfo, err error) { + if image.image == nil { + return nil, RbdErrorImageNotOpen + } + + var c_max_snaps C.int = 0 + + ret := C.rbd_snap_list(image.image, nil, &c_max_snaps) + + c_snaps := make([]C.rbd_snap_info_t, c_max_snaps) + snaps = make([]SnapInfo, c_max_snaps) + + ret = C.rbd_snap_list(image.image, + &c_snaps[0], &c_max_snaps) + if ret < 0 { + return nil, RBDError(int(ret)) + } + + for i, s := range c_snaps { + snaps[i] = SnapInfo{Id: uint64(s.id), + Size: uint64(s.size), + Name: C.GoString(s.name)} + } + + C.rbd_snap_list_end(&c_snaps[0]) + return snaps[:len(snaps)-1], nil +} + +// int rbd_snap_create(rbd_image_t image, const char *snapname); +func (image *Image) CreateSnapshot(snapname string) (*Snapshot, error) { + if image.image == nil { + return nil, RbdErrorImageNotOpen + } + + var c_snapname *C.char = C.CString(snapname) + defer C.free(unsafe.Pointer(c_snapname)) + + ret := C.rbd_snap_create(image.image, c_snapname) + if ret < 0 { + return nil, RBDError(int(ret)) + } + + return &Snapshot{ + image: image, + name: snapname, + }, nil +} + +// +func (image *Image) GetSnapshot(snapname string) *Snapshot { + return &Snapshot{ + image: image, + name: snapname, + } +} + +// int rbd_get_parent_info(rbd_image_t image, +// char *parent_pool_name, size_t ppool_namelen, char *parent_name, +// size_t pnamelen, char *parent_snap_name, size_t psnap_namelen) +func (image *Image) GetParentInfo(p_pool, p_name, p_snapname []byte) error { + ret := C.rbd_get_parent_info( + image.image, + (*C.char)(unsafe.Pointer(&p_pool[0])), + (C.size_t)(len(p_pool)), + (*C.char)(unsafe.Pointer(&p_name[0])), + (C.size_t)(len(p_name)), + (*C.char)(unsafe.Pointer(&p_snapname[0])), + (C.size_t)(len(p_snapname))) + if ret == 0 { + return nil + } else { + return RBDError(int(ret)) + } +} + +// int rbd_snap_remove(rbd_image_t image, const char *snapname); +func (snapshot *Snapshot) Remove() error { + var c_snapname *C.char = C.CString(snapshot.name) + defer C.free(unsafe.Pointer(c_snapname)) + + return GetError(C.rbd_snap_remove(snapshot.image.image, c_snapname)) +} + +// int rbd_snap_rollback(rbd_image_t image, const char *snapname); +// int rbd_snap_rollback_with_progress(rbd_image_t image, const char *snapname, +// librbd_progress_fn_t cb, void *cbdata); +func (snapshot *Snapshot) Rollback() error { + var c_snapname *C.char = C.CString(snapshot.name) + defer C.free(unsafe.Pointer(c_snapname)) + + return GetError(C.rbd_snap_rollback(snapshot.image.image, c_snapname)) +} + +// int rbd_snap_protect(rbd_image_t image, const char *snap_name); +func (snapshot *Snapshot) Protect() error { + var c_snapname *C.char = C.CString(snapshot.name) + defer C.free(unsafe.Pointer(c_snapname)) + + return GetError(C.rbd_snap_protect(snapshot.image.image, c_snapname)) +} + +// int rbd_snap_unprotect(rbd_image_t image, const char *snap_name); +func (snapshot *Snapshot) Unprotect() error { + var c_snapname *C.char = C.CString(snapshot.name) + defer C.free(unsafe.Pointer(c_snapname)) + + return GetError(C.rbd_snap_unprotect(snapshot.image.image, c_snapname)) +} + +// int rbd_snap_is_protected(rbd_image_t image, const char *snap_name, +// int *is_protected); +func (snapshot *Snapshot) IsProtected() (bool, error) { + var c_is_protected C.int + var c_snapname *C.char = C.CString(snapshot.name) + defer C.free(unsafe.Pointer(c_snapname)) + + ret := C.rbd_snap_is_protected(snapshot.image.image, c_snapname, + &c_is_protected) + if ret < 0 { + return false, RBDError(int(ret)) + } + + return c_is_protected != 0, nil +} + +// int rbd_snap_set(rbd_image_t image, const char *snapname); +func (snapshot *Snapshot) Set() error { + var c_snapname *C.char = C.CString(snapshot.name) + defer C.free(unsafe.Pointer(c_snapname)) + + return GetError(C.rbd_snap_set(snapshot.image.image, c_snapname)) +} diff --git a/Godeps/_workspace/src/github.com/ceph/go-ceph/rbd/rbd_test.go b/Godeps/_workspace/src/github.com/ceph/go-ceph/rbd/rbd_test.go new file mode 100644 index 0000000..ce67b28 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ceph/go-ceph/rbd/rbd_test.go @@ -0,0 +1,277 @@ +package rbd_test + +import ( + "bytes" + "encoding/json" + "github.com/ceph/go-ceph/rados" + "github.com/ceph/go-ceph/rbd" + "github.com/stretchr/testify/assert" + "os/exec" + "sort" + "testing" +) + +func GetUUID() string { + 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") +} + +func TestGetImageNames(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + poolname := GetUUID() + err := conn.MakePool(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, 22) + assert.NoError(t, err) + createdList = append(createdList, name) + } + + imageNames, err := rbd.GetImageNames(ioctx) + assert.NoError(t, err) + + 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) + } + + ioctx.Destroy() + conn.DeletePool(poolname) + conn.Shutdown() +} + +func TestIOReaderWriter(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + poolname := GetUUID() + err := conn.MakePool(poolname) + assert.NoError(t, err) + + ioctx, err := conn.OpenIOContext(poolname) + assert.NoError(t, err) + + name := GetUUID() + img, err := rbd.Create(ioctx, name, 1<<22, 22) + assert.NoError(t, err) + + err = img.Open() + assert.NoError(t, err) + + stats, err := img.Stat() + assert.NoError(t, err) + + encoder := json.NewEncoder(img) + encoder.Encode(stats) + + err = img.Flush() + assert.NoError(t, err) + + _, err = img.Seek(0, 0) + assert.NoError(t, err) + + var stats2 *rbd.ImageInfo + decoder := json.NewDecoder(img) + decoder.Decode(&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) + assert.NoError(t, err) + + 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) + + err = img.Close() + assert.NoError(t, err) + + img.Remove() + assert.NoError(t, err) + + ioctx.Destroy() + conn.DeletePool(poolname) + conn.Shutdown() +} + +func TestCreateSnapshot(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + poolname := GetUUID() + err := conn.MakePool(poolname) + assert.NoError(t, err) + + ioctx, err := conn.OpenIOContext(poolname) + assert.NoError(t, err) + + name := GetUUID() + img, err := rbd.Create(ioctx, name, 1<<22, 22) + assert.NoError(t, err) + + err = img.Open() + assert.NoError(t, err) + + snapshot, err := img.CreateSnapshot("mysnap") + assert.NoError(t, err) + + err = img.Close() + err = img.Open("mysnap") + assert.NoError(t, err) + + snapshot.Remove() + assert.NoError(t, err) + + err = img.Close() + assert.NoError(t, err) + + img.Remove() + assert.NoError(t, err) + + ioctx.Destroy() + conn.DeletePool(poolname) + conn.Shutdown() +} + +func TestParentInfo(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + poolname := GetUUID() + err := conn.MakePool(poolname) + assert.NoError(t, err) + + ioctx, err := conn.OpenIOContext(poolname) + assert.NoError(t, err) + + name := "parent" + img, err := rbd.Create(ioctx, name, 1<<22, 22, 1) + assert.NoError(t, err) + + err = img.Open() + assert.NoError(t, err) + + snapshot, err := img.CreateSnapshot("mysnap") + assert.NoError(t, err) + + err = snapshot.Protect() + assert.NoError(t, err) + + // create an image context with the parent+snapshot + snapImg := rbd.GetImage(ioctx, "parent") + err = snapImg.Open("mysnap") + assert.NoError(t, err) + + // ensure no children prior to clone + pools, images, err := snapImg.ListChildren() + assert.NoError(t, err) + assert.Equal(t, len(pools), 0, "pools equal") + assert.Equal(t, len(images), 0, "children length equal") + + imgNew, err := img.Clone("mysnap", ioctx, "child", 1, 22) + assert.NoError(t, err) + + err = imgNew.Open() + assert.NoError(t, err) + parentPool := make([]byte, 128) + parentName := make([]byte, 128) + parentSnapname := make([]byte, 128) + + err = imgNew.GetParentInfo(parentPool, parentName, parentSnapname) + assert.NoError(t, err) + + n := bytes.Index(parentName, []byte{0}) + pName := string(parentName[:n]) + + n = bytes.Index(parentSnapname, []byte{0}) + pSnapname := string(parentSnapname[:n]) + assert.Equal(t, pName, "parent", "they should be equal") + assert.Equal(t, pSnapname, "mysnap", "they should be equal") + + pools, images, err = snapImg.ListChildren() + assert.NoError(t, err) + assert.Equal(t, len(pools), 1, "pools equal") + assert.Equal(t, len(images), 1, "children length equal") + + err = imgNew.Close() + assert.NoError(t, err) + + err = imgNew.Remove() + assert.NoError(t, err) + + err = snapshot.Unprotect() + assert.NoError(t, err) + + err = snapshot.Remove() + assert.NoError(t, err) + + err = img.Close() + assert.NoError(t, err) + + err = snapImg.Close() + assert.NoError(t, err) + + err = img.Remove() + assert.NoError(t, err) + + ioctx.Destroy() + conn.DeletePool(poolname) + conn.Shutdown() +} + +func TestNotFound(t *testing.T) { + conn, _ := rados.NewConn() + conn.ReadDefaultConfigFile() + conn.Connect() + + poolname := GetUUID() + err := conn.MakePool(poolname) + assert.NoError(t, err) + + ioctx, err := conn.OpenIOContext(poolname) + assert.NoError(t, err) + + name := GetUUID() + + img := rbd.GetImage(ioctx, name) + err = img.Open() + assert.Equal(t, err, rbd.RbdErrorNotFound) + + img.Remove() + assert.Equal(t, err, rbd.RbdErrorNotFound) + + ioctx.Destroy() + conn.DeletePool(poolname) + conn.Shutdown() +} diff --git a/ci/ceph_entrypoint.sh b/ci/ceph_entrypoint.sh new file mode 100755 index 0000000..59f604b --- /dev/null +++ b/ci/ceph_entrypoint.sh @@ -0,0 +1,114 @@ +#!/bin/bash +# +# Copyright (C) 2013,2014 Loic Dachary +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +set -e +set -u +DIR=$1 + +#if ! dpkg -l ceph ; then +# wget -q -O- 'https://ceph.com/git/?p=ceph.git;a=blob_plain;f=keys/release.asc' | sudo apt-key add - +# echo deb http://ceph.com/debian-dumpling/ $(lsb_release -sc) main | sudo tee /etc/apt/sources.list.d/ceph.list +# sudo apt-get update +# sudo apt-get --yes install ceph ceph-common +#fi + +# get rid of process and directories leftovers +pkill ceph-mon || true +pkill ceph-osd || true +rm -fr $DIR + +# cluster wide parameters +mkdir -p ${DIR}/log +cat >> $DIR/ceph.conf <> $DIR/ceph.conf <> $DIR/ceph.conf <> $DIR/ceph.conf <> ~/.ssh/authorized_keys +chmod 600 ~/.ssh/authorized_keys + +git clone git://github.com/ceph/ceph-deploy +pushd ceph-deploy +./bootstrap +./ceph-deploy install --release ${CEPH_RELEASE} `hostname` +./ceph-deploy pkg --install librados-dev `hostname` +./ceph-deploy pkg --install librbd-dev `hostname` +./ceph-deploy pkg --install libcephfs-dev `hostname` +popd # ceph-deploy + +popd # workdir diff --git a/ci/ceph_micro-osd.sh b/ci/ceph_micro-osd.sh new file mode 100644 index 0000000..3846062 --- /dev/null +++ b/ci/ceph_micro-osd.sh @@ -0,0 +1,123 @@ +# +# Copyright (C) 2013,2014 Loic Dachary +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +set -e +set -u + +DIR=$1 + +#if ! dpkg -l ceph ; then +# wget -q -O- 'https://ceph.com/git/?p=ceph.git;a=blob_plain;f=keys/release.asc' | sudo apt-key add - +# echo deb http://ceph.com/debian-dumpling/ $(lsb_release -sc) main | sudo tee /etc/apt/sources.list.d/ceph.list +# sudo apt-get update +# sudo apt-get --yes install ceph ceph-common +#fi + +# get rid of process and directories leftovers +pkill ceph-mon || true +pkill ceph-osd || true +rm -fr $DIR + +# cluster wide parameters +mkdir -p ${DIR}/log +cat >> $DIR/ceph.conf <> $DIR/ceph.conf <> $DIR/ceph.conf <> $DIR/ceph.conf < /dev/null; then + break + fi + fi + sleep 1 +done + +# check that it works +rados --pool rbd put group /etc/group +rados --pool rbd get group ${DIR}/group +diff /etc/group ${DIR}/group +ceph osd tree diff --git a/pkg/scsi/backingstore/cephstore_linux.go b/pkg/scsi/backingstore/cephstore_linux.go new file mode 100644 index 0000000..291283a --- /dev/null +++ b/pkg/scsi/backingstore/cephstore_linux.go @@ -0,0 +1,151 @@ +/* +Copyright 2018 The GoStor Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package backingstore + +import ( + "fmt" + "strings" + + "github.com/gostor/gotgt/pkg/api" + "github.com/gostor/gotgt/pkg/scsi" + + log "github.com/Sirupsen/logrus" + "github.com/ceph/go-ceph/rados" + "github.com/ceph/go-ceph/rbd" +) + +// This ceph-rbd plugin is only for linux +// path format ceph-rbd:poolname/imagename +const ( + CephBackingStorage = "ceph-rbd" +) + +func init() { + scsi.RegisterBackingStore(CephBackingStorage, newCeph) +} + +type CephBackingStore struct { + scsi.BaseBackingStore + poolName string + imageName string + conn *rados.Conn + ioctx *rados.IOContext + image *rbd.Image +} + +func newCeph() (api.BackingStore, error) { + return &CephBackingStore{ + BaseBackingStore: scsi.BaseBackingStore{ + Name: CephBackingStorage, + DataSize: 0, + OflagsSupported: 0, + }, + }, nil +} + +func (bs *CephBackingStore) Open(dev *api.SCSILu, path string) error { + + pathinfo := strings.SplitN(path, "/", 2) + if len(pathinfo) != 2 { + return fmt.Errorf("invalid device path string:%s", path) + } + poolName := pathinfo[0] + imageName := pathinfo[1] + log.Debugf("ceph path = %s", path) + if conn, err := rados.NewConn(); err != nil { + log.Error(err) + return err + } else { + bs.conn = conn + } + if err := bs.conn.ReadDefaultConfigFile(); err != nil { + log.Error(err) + return err + } + + if err := bs.conn.Connect(); err != nil { + log.Error(err) + return err + } + + if ioctx, err := bs.conn.OpenIOContext(poolName); err != nil { + bs.conn.Shutdown() + log.Error(err) + return err + } else { + bs.ioctx = ioctx + } + + if image := rbd.GetImage(bs.ioctx, imageName); image == nil { + err := fmt.Errorf("rbdGetImage failed:poolName:%s,imageName:%s", + poolName, imageName) + log.Error(err) + } else { + bs.image = image + } + + if err := bs.image.Open(); err != nil { + log.Error(err) + return err + } + + if dataSize, err := bs.image.GetSize(); err != nil { + log.Error(err) + return err + } else { + bs.DataSize = dataSize + } + return nil +} + +func (bs *CephBackingStore) Close(dev *api.SCSILu) error { + err := bs.image.Close() + bs.ioctx.Destroy() + bs.conn.Shutdown() + return err +} + +func (bs *CephBackingStore) Init(dev *api.SCSILu, Opts string) error { + return nil +} + +func (bs *CephBackingStore) Exit(dev *api.SCSILu) error { + return nil +} + +func (bs *CephBackingStore) Size(dev *api.SCSILu) uint64 { + return bs.DataSize +} + +func (bs *CephBackingStore) Read(offset, tl int64) ([]byte, error) { + tmpbuf := make([]byte, tl) + _, err := bs.image.ReadAt(tmpbuf, offset) + return tmpbuf, err +} + +func (bs *CephBackingStore) Write(wbuf []byte, offset int64) error { + _, err := bs.image.WriteAt(wbuf, offset) + return err +} + +func (bs *CephBackingStore) DataSync() error { + err := bs.image.Flush() + return err +} + +func (bs *CephBackingStore) DataAdvise(offset, length int64, advise uint32) error { + return nil +}