├── Rakefile ├── lib ├── ceph-ruby │ ├── version.rb │ ├── cluster.rb │ ├── pool.rb │ ├── lib │ │ ├── rados.rb │ │ └── rbd.rb │ ├── rados_object.rb │ └── rados_block_device.rb └── ceph-ruby.rb ├── Gemfile ├── .gitignore ├── ceph-ruby.gemspec ├── LICENSE.txt └── README.md /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /lib/ceph-ruby/version.rb: -------------------------------------------------------------------------------- 1 | module CephRuby 2 | VERSION = "1.1" 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in ceph-ruby.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /lib/ceph-ruby.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/module/delegation" 2 | require "active_support/core_ext/module/attribute_accessors" 3 | 4 | require "ffi" 5 | 6 | require "ceph-ruby/lib/rados" 7 | require "ceph-ruby/lib/rbd" 8 | 9 | require "ceph-ruby/version" 10 | require "ceph-ruby/cluster" 11 | require "ceph-ruby/pool" 12 | require "ceph-ruby/rados_block_device" 13 | require "ceph-ruby/rados_object" 14 | 15 | module CephRuby 16 | mattr_accessor :logger 17 | 18 | def self.log(message) 19 | return unless logger 20 | logger.info("CephRuby: #{message}") 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /ceph-ruby.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'ceph-ruby/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "ceph-ruby" 8 | gem.version = CephRuby::VERSION 9 | gem.authors = ["Netskin GmbH", "Corin Langosch"] 10 | gem.email = ["info@netskin.com", "info@corinlangosch.com"] 11 | gem.description = %q{Easy management of Ceph} 12 | gem.summary = %q{Easy management of Ceph Distributed Storage System using ruby} 13 | gem.homepage = "https://github.com/ceph/ceph-ruby" 14 | gem.license = "MIT" 15 | 16 | gem.files = `git ls-files`.split($/) 17 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 18 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 19 | gem.require_paths = ["lib"] 20 | 21 | gem.add_dependency('ffi', '~> 1.1.5') 22 | gem.add_dependency('activesupport', '>= 3.0.0') 23 | end 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 - 2013 Netskin GmbH 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /lib/ceph-ruby/cluster.rb: -------------------------------------------------------------------------------- 1 | module CephRuby 2 | class Cluster 3 | attr_accessor :handle 4 | 5 | def initialize(config_path = "/etc/ceph/ceph.conf") 6 | log("init lib rados #{Lib::Rados.version_string}, lib rbd #{Lib::Rbd.version_string}") 7 | 8 | handle_p = FFI::MemoryPointer.new(:pointer) 9 | ret = Lib::Rados.rados_create(handle_p, nil) 10 | raise SystemCallError.new("open of cluster failed", -ret) if ret < 0 11 | self.handle = handle_p.get_pointer(0) 12 | 13 | setup_using_file(config_path) 14 | 15 | connect 16 | 17 | if block_given? 18 | begin 19 | yield(self) 20 | ensure 21 | close 22 | end 23 | end 24 | end 25 | 26 | def close 27 | return unless handle 28 | log("close") 29 | Lib::Rados.rados_shutdown(handle) 30 | self.handle = nil 31 | end 32 | 33 | def pool(name, &block) 34 | Pool.new(self, name, &block) 35 | end 36 | 37 | # helper methods below 38 | 39 | def connect 40 | log("connect") 41 | ret = Lib::Rados.rados_connect(handle) 42 | raise SystemCallError.new("connect to cluster failed", -ret) if ret < 0 43 | end 44 | 45 | def setup_using_file(path) 46 | log("setup_using_file #{path}") 47 | ret = Lib::Rados.rados_conf_read_file(handle, path) 48 | raise SystemCallError.new("setup of cluster from config file '#{path}' failed", -ret) if ret < 0 49 | end 50 | 51 | def log(message) 52 | CephRuby.log("cluster #{message}") 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/ceph-ruby/pool.rb: -------------------------------------------------------------------------------- 1 | module CephRuby 2 | class Pool 3 | attr_accessor :cluster, :name, :handle 4 | 5 | def initialize(cluster, name) 6 | self.cluster = cluster 7 | self.name = name 8 | if block_given? 9 | begin 10 | yield(self) 11 | ensure 12 | close 13 | end 14 | end 15 | end 16 | 17 | def exists? 18 | log("exists?") 19 | ret = Lib::Rados.rados_pool_lookup(cluster.handle, name) 20 | return true if ret >= 0 21 | return false if ret == -Errno::ENOENT::Errno 22 | raise SystemCallError.new("lookup of '#{name}' failed", -ret) if ret < 0 23 | end 24 | 25 | def open 26 | return if open? 27 | log("open") 28 | handle_p = FFI::MemoryPointer.new(:pointer) 29 | ret = Lib::Rados.rados_ioctx_create(cluster.handle, name, handle_p) 30 | raise SystemCallError.new("creation of io context for '#{name}' failed", -ret) if ret < 0 31 | self.handle = handle_p.get_pointer(0) 32 | end 33 | 34 | def close 35 | return unless open? 36 | log("close") 37 | Lib::Rados.rados_ioctx_destroy(handle) 38 | self.handle = nil 39 | end 40 | 41 | def rados_object(name, &block) 42 | ensure_open 43 | RadosObject.new(self, name, &block) 44 | end 45 | 46 | def rados_block_device(name, &block) 47 | ensure_open 48 | RadosBlockDevice.new(self, name, &block) 49 | end 50 | 51 | # helper methods below 52 | 53 | def open? 54 | !!handle 55 | end 56 | 57 | def ensure_open 58 | return if open? 59 | open 60 | end 61 | 62 | def log(message) 63 | CephRuby.log("pool #{name} #{message}") 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ceph::Ruby 2 | 3 | Easy management of Ceph Distributed Storage System (rbd, images, rados objects) using ruby. 4 | 5 | 6 | ## Installation 7 | 8 | Add this line to your application's Gemfile: 9 | 10 | gem 'ceph-ruby' 11 | 12 | And then execute: 13 | 14 | $ bundle 15 | 16 | Or install it yourself as: 17 | 18 | $ gem install ceph-ruby 19 | 20 | 21 | ## Usage 22 | 23 | require "ceph-ruby" 24 | 25 | # version information 26 | puts CephRuby::Lib::Rados.version_string 27 | puts CephRuby::Lib::Rbd.version_string 28 | 29 | # connect to cluster and open a pool 30 | cluster = CephRuby::Cluster.new 31 | pool = cluster.pool("my-pool-xyz") 32 | pool.open 33 | 34 | # simple example for using rados objects 35 | object = pool.rados_object("my-object-xyz") 36 | object.write(0, "This is a Test!") 37 | puts object.size 38 | 39 | # simple example for using rbd images 40 | image = pool.rados_block_device("my-image-xyz") 41 | puts image.exists? 42 | image.create(10.gigabytes) 43 | puts image.exists? 44 | puts image.size 45 | image.write(0, "This is a Test!") 46 | pp image.stat 47 | image.close 48 | 49 | # clean up 50 | pool.close 51 | cluster.shutdown 52 | 53 | 54 | ## Known bugs 55 | 56 | * Many features provided by ceph are not implemented yet. Please contribute! 57 | 58 | 59 | ## Contributing 60 | 61 | 1. Fork it 62 | 2. Create your feature branch (`git checkout -b my-new-feature`) 63 | 3. Commit your changes (`git commit -am 'Add some feature'`) 64 | 4. Push to the branch (`git push origin my-new-feature`) 65 | 5. Create new Pull Request 66 | 67 | 68 | ## Copyright 69 | 70 | Copyright (c) 2012 - 2013 [Netskin GmbH](http://www.netskin.com). Released unter the MIT license. 71 | -------------------------------------------------------------------------------- /lib/ceph-ruby/lib/rados.rb: -------------------------------------------------------------------------------- 1 | require "ffi" 2 | 3 | # see https://github.com/ceph/ceph/blob/v0.48.2argonaut/src/pybind/rados.py 4 | 5 | module CephRuby 6 | module Lib 7 | module Rados 8 | extend FFI::Library 9 | 10 | ffi_lib ['rados', 'librados.so.2'] 11 | 12 | attach_function 'rados_version', [:pointer, :pointer, :pointer], :void 13 | 14 | attach_function 'rados_create', [:pointer, :string], :int 15 | attach_function 'rados_connect', [:pointer], :int 16 | attach_function 'rados_conf_read_file', [:pointer, :string], :int 17 | attach_function 'rados_shutdown', [:pointer], :void 18 | 19 | attach_function 'rados_pool_lookup', [:pointer, :string], :int 20 | 21 | attach_function 'rados_ioctx_create', [:pointer, :string, :pointer], :int 22 | attach_function 'rados_ioctx_destroy', [:pointer], :void 23 | 24 | attach_function 'rados_write', [:pointer, :string, :buffer_in, :size_t, :off_t], :int 25 | attach_function 'rados_read', [:pointer, :string, :buffer_out, :size_t, :off_t], :int 26 | attach_function 'rados_remove', [:pointer, :string], :int 27 | attach_function 'rados_trunc', [:pointer, :string, :size_t], :int 28 | attach_function 'rados_stat', [:pointer, :string, :pointer, :pointer], :int 29 | 30 | def self.version 31 | major = FFI::MemoryPointer.new(:int) 32 | minor= FFI::MemoryPointer.new(:int) 33 | extra = FFI::MemoryPointer.new(:int) 34 | rados_version(major, minor, extra) 35 | { 36 | :major => major.get_int(0), 37 | :minor => minor.get_int(0), 38 | :extra => extra.get_int(0), 39 | } 40 | end 41 | 42 | def self.version_string 43 | "#{version[:major]}.#{version[:minor]}.#{version[:extra]}" 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/ceph-ruby/rados_object.rb: -------------------------------------------------------------------------------- 1 | module CephRuby 2 | class RadosObject 3 | attr_accessor :pool, :name 4 | 5 | def initialize(pool, name) 6 | self.pool = pool 7 | self.name = name 8 | if block_given? 9 | yield(self) 10 | end 11 | end 12 | 13 | def exists? 14 | log("exists?") 15 | !!stat 16 | rescue SystemCallError => e 17 | return false if e.errno == Errno::ENOENT::Errno 18 | raise e 19 | end 20 | 21 | def write(offset, data) 22 | size = data.bytesize 23 | log("write offset #{offset}, size #{size}") 24 | ret = Lib::Rados.rados_write(pool.handle, name, data, size, offset) 25 | raise SystemCallError.new("write of #{size} bytes to '#{name}' at #{offset} failed", -ret) if ret < 0 26 | raise Errno::EIO.new("wrote only #{ret} of #{size} bytes to '#{name}' at #{offset}") if ret < size 27 | end 28 | 29 | def read(offset, size) 30 | log("read offset #{offset}, size #{size}") 31 | data_p = FFI::MemoryPointer.new(:char, size) 32 | ret = Lib::Rados.rados_read(pool.handle, name, data_p, size, offset) 33 | raise SystemCallError.new("read of #{size} bytes from '#{name}' at #{offset} failed", -ret) if ret < 0 34 | data_p.get_bytes(0, ret) 35 | end 36 | 37 | def destroy 38 | log("destroy") 39 | ret = Lib::Rados.rados_remove(pool.handle, name) 40 | raise SystemCallError.new("destroy of '#{name}' failed", -ret) if ret < 0 41 | end 42 | 43 | def resize(size) 44 | log("resize size #{size}") 45 | ret = Lib::Rados.rados_trunc(pool.handle, name, size) 46 | raise SystemCallError.new("resize of '#{name}' to #{size} failed", -ret) if ret < 0 47 | end 48 | 49 | def stat 50 | log("stat") 51 | size_p = FFI::MemoryPointer.new(:uint64) 52 | mtime_p = FFI::MemoryPointer.new(:uint64) 53 | ret = Lib::Rados.rados_stat(pool.handle, name, size_p, mtime_p) 54 | raise SystemCallError.new("stat of '#{name}' failed", -ret) if ret < 0 55 | { 56 | :size => size_p.get_uint64(0), 57 | :mtime => Time.at(mtime_p.get_uint64(0)), 58 | } 59 | end 60 | 61 | def size 62 | stat[:size] 63 | end 64 | 65 | # helper methods below 66 | 67 | def log(message) 68 | CephRuby.log("rados object #{pool.name}/#{name} #{message}") 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/ceph-ruby/lib/rbd.rb: -------------------------------------------------------------------------------- 1 | require "ffi" 2 | 3 | # see https://github.com/ceph/ceph/blob/v0.48.2argonaut/src/pybind/rbd.py 4 | 5 | module CephRuby 6 | module Lib 7 | module Rbd 8 | extend FFI::Library 9 | 10 | ffi_lib ['rbd', 'librbd.so.1'] 11 | 12 | attach_function 'rbd_version', [:pointer, :pointer, :pointer], :void 13 | 14 | attach_function 'rbd_create2', [:pointer, :string, :size_t, :uint64, :pointer], :int 15 | attach_function 'rbd_remove', [:pointer, :string], :int 16 | 17 | attach_function 'rbd_open', [:pointer, :string, :pointer, :string], :int 18 | attach_function 'rbd_close', [:pointer], :void 19 | 20 | attach_function 'rbd_write', [:pointer, :off_t, :size_t, :buffer_in], :int 21 | attach_function 'rbd_read', [:pointer, :off_t, :size_t, :buffer_out], :int 22 | attach_function 'rbd_stat', [:pointer, :pointer, :size_t], :int 23 | attach_function 'rbd_resize', [:pointer, :size_t], :int 24 | attach_function 'rbd_get_size', [:pointer, :pointer], :int 25 | attach_function 'rbd_get_features', [:pointer, :pointer], :int 26 | 27 | attach_function 'rbd_copy', [:pointer, :pointer, :string], :int 28 | attach_function 'rbd_copy_with_progress', [:pointer, :pointer, :string, :pointer, :pointer], :int 29 | 30 | attach_function 'rbd_snap_create', [:pointer, :string], :int 31 | attach_function 'rbd_snap_remove', [:pointer, :string], :int 32 | attach_function 'rbd_snap_protect', [:pointer, :string], :int 33 | attach_function 'rbd_snap_unprotect', [:pointer, :string], :int 34 | attach_function 'rbd_snap_set', [:pointer, :string], :int 35 | 36 | attach_function 'rbd_clone', [:pointer, :string, :string, :pointer, :string, :uint64, :pointer], :int 37 | attach_function 'rbd_flatten', [:pointer], :int 38 | 39 | class StatStruct < FFI::Struct 40 | layout :size, :uint64, 41 | :obj_size, :uint64, 42 | :num_objs, :uint64, 43 | :order, :int, 44 | :block_name_prefix, [:char, 24], 45 | :parent_pool, :int, # deprecated 46 | :parent_name, [:char, 96] # deprecated 47 | end 48 | 49 | def self.version 50 | major = FFI::MemoryPointer.new(:int) 51 | minor= FFI::MemoryPointer.new(:int) 52 | extra = FFI::MemoryPointer.new(:int) 53 | rbd_version(major, minor, extra) 54 | { 55 | :major => major.get_int(0), 56 | :minor => minor.get_int(0), 57 | :extra => extra.get_int(0), 58 | } 59 | end 60 | 61 | def self.version_string 62 | "#{version[:major]}.#{version[:minor]}.#{version[:extra]}" 63 | end 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/ceph-ruby/rados_block_device.rb: -------------------------------------------------------------------------------- 1 | module CephRuby 2 | class RadosBlockDevice 3 | FEATURE_LAYERING = 1 4 | FEATURE_STRIPING_V2 = 2 5 | FEATURE_EXCLUSIVE_LOCK = 4 6 | FEATURE_OBJECT_MAP = 8 7 | 8 | attr_accessor :pool, :name, :handle 9 | 10 | delegate :cluster, :to => :pool 11 | 12 | def initialize(pool, name) 13 | self.pool = pool 14 | self.name = name 15 | if block_given? 16 | begin 17 | yield(self) 18 | ensure 19 | close 20 | end 21 | end 22 | end 23 | 24 | def exists? 25 | log("exists?") 26 | handle_p = FFI::MemoryPointer.new(:pointer) 27 | ret = Lib::Rbd.rbd_open(pool.handle, name, handle_p, nil) 28 | case ret 29 | when 0 30 | handle = handle_p.get_pointer(0) 31 | Lib::Rbd.rbd_close(handle) 32 | true 33 | when -Errno::ENOENT::Errno 34 | false 35 | else 36 | raise SystemCallError.new("open of '#{name}' failed", -ret) if ret < 0 37 | end 38 | end 39 | 40 | def create(size, features = 0, order = 0) 41 | log("create size #{size}, features #{features}, order #{order}") 42 | order_p = FFI::MemoryPointer.new(:int) 43 | order_p.put_int(0, order) 44 | ret = Lib::Rbd.rbd_create2(pool.handle, name, size, features, order_p) 45 | raise SystemCallError.new("creation of '#{name}' failed", -ret) if ret < 0 46 | end 47 | 48 | def open 49 | return if open? 50 | log("open") 51 | handle_p = FFI::MemoryPointer.new(:pointer) 52 | ret = Lib::Rbd.rbd_open(pool.handle, name, handle_p, nil) 53 | raise SystemCallError.new("open of '#{name}' failed", -ret) if ret < 0 54 | self.handle = handle_p.get_pointer(0) 55 | end 56 | 57 | def close 58 | return unless open? 59 | log("close") 60 | Lib::Rbd.rbd_close(handle) 61 | self.handle = nil 62 | end 63 | 64 | def destroy 65 | close if open? 66 | log("destroy") 67 | ret = Lib::Rbd.rbd_remove(pool.handle, name) 68 | raise SystemCallError.new("destroy of '#{name}' failed", -ret) if ret < 0 69 | end 70 | 71 | def write(offset, data) 72 | ensure_open 73 | size = data.bytesize 74 | log("write offset #{offset}, size #{size}") 75 | ret = Lib::Rbd.rbd_write(handle, offset, size, data) 76 | raise SystemCallError.new("write of #{size} bytes to '#{name}' at #{offset} failed", -ret) if ret < 0 77 | raise Errno::EIO.new("wrote only #{ret} of #{size} bytes to '#{name}' at #{offset}") if ret < size 78 | end 79 | 80 | def read(offset, size) 81 | ensure_open 82 | log("read offset #{offset}, size #{size}") 83 | data_p = FFI::MemoryPointer.new(:char, size) 84 | ret = Lib::Rbd.rbd_read(handle, offset, size, data_p) 85 | raise SystemCallError.new("read of #{size} bytes from '#{name}' at #{offset} failed", -ret) if ret < 0 86 | data_p.get_bytes(0, ret) 87 | end 88 | 89 | def stat 90 | ensure_open 91 | log("stat") 92 | stat = Lib::Rbd::StatStruct.new 93 | ret = Lib::Rbd.rbd_stat(handle, stat, stat.size) 94 | raise SystemCallError.new("stat of '#{name}' failed", -ret) if ret < 0 95 | Hash[[:size, :obj_size, :num_objs, :order].map{ |k| [k, stat[k]] }].tap do |hash| 96 | hash[:block_name_prefix] = stat[:block_name_prefix].to_ptr.read_string 97 | end 98 | end 99 | 100 | def size 101 | ensure_open 102 | log("size") 103 | size_p = FFI::MemoryPointer.new(:uint64) 104 | ret = Lib::Rbd.rbd_get_size(handle, size_p) 105 | raise SystemCallError.new("size of '#{name}' failed", -ret) if ret < 0 106 | size_p.read_uint64 107 | end 108 | 109 | def features 110 | ensure_open 111 | log("features") 112 | features_p = FFI::MemoryPointer.new(:uint64) 113 | ret = Lib::Rbd.rbd_get_features(handle, features_p) 114 | raise SystemCallError.new("features of '#{name}' failed", -ret) if ret < 0 115 | features_p.read_uint64 116 | end 117 | 118 | def resize(size) 119 | ensure_open 120 | log("resize size #{size}") 121 | ret = Lib::Rbd.rbd_resize(handle, size) 122 | raise SystemCallError.new("resize of '#{name}' to #{size} failed", -ret) if ret < 0 123 | end 124 | 125 | def copy_to(dst_name, dst_pool = nil) 126 | ensure_open 127 | case dst_pool 128 | when String 129 | dst_pool = cluster.pool(dst_pool) 130 | when nil 131 | dst_pool = pool 132 | end 133 | dst_pool.ensure_open 134 | log("copy_to #{dst_pool.name}/#{dst_name}") 135 | ret = Lib::Rbd.rbd_copy(handle, dst_pool.handle, dst_name) 136 | raise SystemCallError.new("copy of '#{name}' to '#{dst_pool.name}/#{dst_name}' failed", -ret) if ret < 0 137 | end 138 | 139 | def snapshot_create(name) 140 | log("snapshot_create #{name}") 141 | ensure_open 142 | ret = Lib::Rbd.rbd_snap_create(handle, name) 143 | raise SystemCallError.new("snapshot create '#{name}' of '#{self.name}' failed", -ret) if ret < 0 144 | end 145 | 146 | def snapshot_destroy(name) 147 | log("snapshot_destroy #{name}") 148 | ensure_open 149 | ret = Lib::Rbd.rbd_snap_remove(handle, name) 150 | raise SystemCallError.new("snapshot destroy '#{name}' of '#{self.name}' failed", -ret) if ret < 0 151 | end 152 | 153 | def snapshot_protect(name) 154 | log("snapshot_protect #{name}") 155 | ensure_open 156 | ret = Lib::Rbd.rbd_snap_protect(handle, name) 157 | raise SystemCallError.new("snapshot protect '#{name}' of '#{self.name}' failed", -ret) if ret < 0 158 | end 159 | 160 | def snapshot_unprotect(name) 161 | log("snapshot_unprotect #{name}") 162 | ensure_open 163 | ret = Lib::Rbd.rbd_snap_unprotect(handle, name) 164 | raise SystemCallError.new("snapshot unprotect '#{name}' of '#{self.name}' failed", -ret) if ret < 0 165 | end 166 | 167 | def snapshot_activate(name) 168 | log("snapshot_activate #{name}") 169 | ensure_open 170 | ret = Lib::Rbd.rbd_snap_set(handle, name) 171 | raise SystemCallError.new("activate snapshot '#{name}' of '#{self.name}' failed", -ret) if ret < 0 172 | end 173 | 174 | def clone(snapshot, dst_name, dst_pool = nil, features = 0, order = 0) 175 | ensure_open 176 | case dst_pool 177 | when String 178 | dst_pool = cluster.pool(dst_pool) 179 | when nil 180 | dst_pool = pool 181 | end 182 | dst_pool.ensure_open 183 | log("clone snapshot #{snapshot} to #{dst_pool.name}/#{dst_name} features #{features} order #{order}") 184 | order_p = FFI::MemoryPointer.new(:int) 185 | order_p.put_int(0, order) 186 | ret = Lib::Rbd.rbd_clone(pool.handle, name, snapshot, dst_pool.handle, dst_name, features, order_p) 187 | raise SystemCallError.new("clone of '#{name}@#{snapshot}' to '#{dst_pool.name}/#{dst_name}' failed", -ret) if ret < 0 188 | end 189 | 190 | def flatten 191 | log("flatten") 192 | ensure_open 193 | ret = Lib::Rbd.rbd_flatten(handle) 194 | raise SystemCallError.new("flatten of '#{name}' failed", -ret) if ret < 0 195 | end 196 | 197 | # helper methods below 198 | 199 | def open? 200 | !!handle 201 | end 202 | 203 | def ensure_open 204 | return if open? 205 | open 206 | end 207 | 208 | def log(message) 209 | CephRuby.log("rbd image #{pool.name}/#{name} #{message}") 210 | end 211 | end 212 | end 213 | --------------------------------------------------------------------------------