pager-mogilefs-client 1.2.1.20080519
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +11 -0
- data/LICENSE.txt +27 -0
- data/Manifest.txt +19 -0
- data/README.txt +66 -0
- data/Rakefile +18 -0
- data/lib/mogilefs.rb +26 -0
- data/lib/mogilefs/admin.rb +298 -0
- data/lib/mogilefs/backend.rb +222 -0
- data/lib/mogilefs/client.rb +65 -0
- data/lib/mogilefs/httpfile.rb +118 -0
- data/lib/mogilefs/mogilefs.rb +237 -0
- data/lib/mogilefs/nfsfile.rb +81 -0
- data/lib/mogilefs/pool.rb +50 -0
- data/test/setup.rb +54 -0
- data/test/test_admin.rb +174 -0
- data/test/test_backend.rb +220 -0
- data/test/test_client.rb +53 -0
- data/test/test_mogilefs.rb +160 -0
- data/test/test_pool.rb +98 -0
- metadata +96 -0
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'thread'
|
3
|
+
require 'mogilefs'
|
4
|
+
|
5
|
+
##
|
6
|
+
# MogileFS::Backend communicates with the MogileFS trackers.
|
7
|
+
|
8
|
+
class MogileFS::Backend
|
9
|
+
|
10
|
+
##
|
11
|
+
# Adds MogileFS commands +names+.
|
12
|
+
|
13
|
+
def self.add_command(*names)
|
14
|
+
names.each do |name|
|
15
|
+
define_method name do |*args|
|
16
|
+
do_request name, args.first || {}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# The last error
|
23
|
+
#--
|
24
|
+
# TODO Use Exceptions
|
25
|
+
|
26
|
+
attr_reader :lasterr
|
27
|
+
|
28
|
+
##
|
29
|
+
# The string attached to the last error
|
30
|
+
#--
|
31
|
+
# TODO Use Exceptions
|
32
|
+
|
33
|
+
attr_reader :lasterrstr
|
34
|
+
|
35
|
+
##
|
36
|
+
# Creates a new MogileFS::Backend.
|
37
|
+
#
|
38
|
+
# :hosts is a required argument and must be an Array containing one or more
|
39
|
+
# 'hostname:port' pairs as Strings.
|
40
|
+
#
|
41
|
+
# :timeout adjusts the request timeout before an error is returned.
|
42
|
+
|
43
|
+
def initialize(args)
|
44
|
+
@hosts = args[:hosts]
|
45
|
+
raise ArgumentError, "must specify at least one host" unless @hosts
|
46
|
+
raise ArgumentError, "must specify at least one host" if @hosts.empty?
|
47
|
+
unless @hosts == @hosts.select { |h| h =~ /:\d+$/ } then
|
48
|
+
raise ArgumentError, ":hosts must be in 'host:port' form"
|
49
|
+
end
|
50
|
+
|
51
|
+
@mutex = Mutex.new
|
52
|
+
@timeout = args[:timeout] || 3
|
53
|
+
@socket = nil
|
54
|
+
@lasterr = nil
|
55
|
+
@lasterrstr = nil
|
56
|
+
|
57
|
+
@dead = {}
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Closes this backend's socket.
|
62
|
+
|
63
|
+
def shutdown
|
64
|
+
@socket.close unless @socket.nil? or @socket.closed?
|
65
|
+
@socket = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# MogileFS::MogileFS commands
|
69
|
+
|
70
|
+
add_command :create_open
|
71
|
+
add_command :create_close
|
72
|
+
add_command :get_paths
|
73
|
+
add_command :delete
|
74
|
+
add_command :sleep
|
75
|
+
add_command :rename
|
76
|
+
add_command :list_keys
|
77
|
+
|
78
|
+
# MogileFS::Backend commands
|
79
|
+
|
80
|
+
add_command :get_hosts
|
81
|
+
add_command :get_devices
|
82
|
+
add_command :list_fids
|
83
|
+
add_command :stats
|
84
|
+
add_command :get_domains
|
85
|
+
add_command :create_domain
|
86
|
+
add_command :delete_domain
|
87
|
+
add_command :create_class
|
88
|
+
add_command :update_class
|
89
|
+
add_command :delete_class
|
90
|
+
add_command :create_host
|
91
|
+
add_command :update_host
|
92
|
+
add_command :delete_host
|
93
|
+
add_command :set_state
|
94
|
+
|
95
|
+
private unless defined? $TESTING
|
96
|
+
|
97
|
+
##
|
98
|
+
# Returns a new TCPSocket connected to +port+ on +host+.
|
99
|
+
|
100
|
+
def connect_to(host, port)
|
101
|
+
return TCPSocket.new(host, port)
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Performs the +cmd+ request with +args+.
|
106
|
+
|
107
|
+
def do_request(cmd, args)
|
108
|
+
@mutex.synchronize do
|
109
|
+
request = make_request cmd, args
|
110
|
+
|
111
|
+
begin
|
112
|
+
bytes_sent = socket.send request, 0
|
113
|
+
rescue SystemCallError
|
114
|
+
@socket = nil
|
115
|
+
raise "couldn't connect to mogilefsd backend"
|
116
|
+
end
|
117
|
+
|
118
|
+
unless bytes_sent == request.length then
|
119
|
+
raise "request truncated (sent #{bytes_sent} expected #{request.length})"
|
120
|
+
end
|
121
|
+
|
122
|
+
readable?
|
123
|
+
|
124
|
+
return parse_response(socket.gets)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# Makes a new request string for +cmd+ and +args+.
|
130
|
+
|
131
|
+
def make_request(cmd, args)
|
132
|
+
return "#{cmd} #{url_encode args}\r\n"
|
133
|
+
end
|
134
|
+
|
135
|
+
##
|
136
|
+
# Turns the +line+ response from the server into a Hash of options, an
|
137
|
+
# error, or raises, as appropriate.
|
138
|
+
|
139
|
+
def parse_response(line)
|
140
|
+
if line =~ /^ERR\s+(\w+)\s*(.*)/ then
|
141
|
+
@lasterr = $1
|
142
|
+
@lasterrstr = $2 ? url_unescape($2) : nil
|
143
|
+
return nil
|
144
|
+
end
|
145
|
+
|
146
|
+
return url_decode($1) if line =~ /^OK\s+\d*\s*(\S*)/
|
147
|
+
|
148
|
+
raise "Invalid response from server: #{line.inspect}"
|
149
|
+
end
|
150
|
+
|
151
|
+
##
|
152
|
+
# Raises if the socket does not become readable in +@timeout+ seconds.
|
153
|
+
|
154
|
+
def readable?
|
155
|
+
found = select [socket], nil, nil, @timeout
|
156
|
+
if found.nil? or found.empty? then
|
157
|
+
peer = (@socket ? "#{@socket.peeraddr[3]}:#{@socket.peeraddr[1]} " : nil)
|
158
|
+
raise MogileFS::UnreadableSocketError, "#{peer}never became readable"
|
159
|
+
end
|
160
|
+
return true
|
161
|
+
end
|
162
|
+
|
163
|
+
##
|
164
|
+
# Returns a socket connected to a MogileFS tracker.
|
165
|
+
|
166
|
+
def socket
|
167
|
+
return @socket if @socket and not @socket.closed?
|
168
|
+
|
169
|
+
now = Time.now
|
170
|
+
|
171
|
+
@hosts.sort_by { rand(3) - 1 }.each do |host|
|
172
|
+
next if @dead.include? host and @dead[host] > now - 5
|
173
|
+
|
174
|
+
begin
|
175
|
+
@socket = connect_to(*host.split(':'))
|
176
|
+
rescue SystemCallError
|
177
|
+
@dead[host] = now
|
178
|
+
next
|
179
|
+
end
|
180
|
+
|
181
|
+
return @socket
|
182
|
+
end
|
183
|
+
|
184
|
+
raise "couldn't connect to mogilefsd backend"
|
185
|
+
end
|
186
|
+
|
187
|
+
##
|
188
|
+
# Turns a url params string into a Hash.
|
189
|
+
|
190
|
+
def url_decode(str)
|
191
|
+
pairs = str.split('&').map do |pair|
|
192
|
+
pair.split('=', 2).map { |v| url_unescape v }
|
193
|
+
end
|
194
|
+
|
195
|
+
return Hash[*pairs.flatten]
|
196
|
+
end
|
197
|
+
|
198
|
+
##
|
199
|
+
# Turns a Hash (or Array of pairs) into a url params string.
|
200
|
+
|
201
|
+
def url_encode(params)
|
202
|
+
return params.map do |k,v|
|
203
|
+
"#{url_escape k.to_s}=#{url_escape v.to_s}"
|
204
|
+
end.join("&")
|
205
|
+
end
|
206
|
+
|
207
|
+
##
|
208
|
+
# Escapes naughty URL characters.
|
209
|
+
|
210
|
+
def url_escape(str)
|
211
|
+
return str.gsub(/([^\w\,\-.\/\\\: ])/) { "%%%02x" % $1[0] }.tr(' ', '+')
|
212
|
+
end
|
213
|
+
|
214
|
+
##
|
215
|
+
# Unescapes naughty URL characters.
|
216
|
+
|
217
|
+
def url_unescape(str)
|
218
|
+
return str.gsub(/%([a-f0-9][a-f0-9])/i) { [$1.to_i(16)].pack 'C' }.tr('+', ' ')
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'mogilefs/backend'
|
2
|
+
|
3
|
+
##
|
4
|
+
# MogileFS::Client is the MogileFS client base class. Concrete clients like
|
5
|
+
# MogileFS::MogileFS and MogileFS::Admin are implemented atop this one to do
|
6
|
+
# real work.
|
7
|
+
|
8
|
+
class MogileFS::Client
|
9
|
+
|
10
|
+
##
|
11
|
+
# The backend connection for this client
|
12
|
+
|
13
|
+
attr_reader :backend
|
14
|
+
|
15
|
+
attr_accessor :hosts if defined? $TESTING
|
16
|
+
|
17
|
+
##
|
18
|
+
# Creates a new Client. See MogileFS::Backend#initialize for how to specify
|
19
|
+
# hosts. If :readonly is set to true, the client will not modify anything
|
20
|
+
# on the server.
|
21
|
+
#
|
22
|
+
# MogileFS::Client.new :hosts => ['kaa:6001', 'ziz:6001'], :readonly => true
|
23
|
+
|
24
|
+
def initialize(args)
|
25
|
+
@hosts = args[:hosts]
|
26
|
+
@readonly = args[:readonly] ? true : false
|
27
|
+
@timeout = args[:timeout]
|
28
|
+
|
29
|
+
reload
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Creates a new MogileFS::Backend.
|
34
|
+
|
35
|
+
def reload
|
36
|
+
@backend = MogileFS::Backend.new :hosts => @hosts, :timeout => @timeout
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# The last error reported by the backend.
|
41
|
+
#--
|
42
|
+
# TODO use Exceptions
|
43
|
+
|
44
|
+
def err
|
45
|
+
@backend.lasterr
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# The last error message reported by the backend.
|
50
|
+
#--
|
51
|
+
# TODO use Exceptions
|
52
|
+
|
53
|
+
def errstr
|
54
|
+
@backend.lasterrstr
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Is this a read-only client?
|
59
|
+
|
60
|
+
def readonly?
|
61
|
+
return @readonly
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'fcntl'
|
2
|
+
require 'socket'
|
3
|
+
require 'stringio'
|
4
|
+
require 'uri'
|
5
|
+
require 'mogilefs/backend'
|
6
|
+
|
7
|
+
##
|
8
|
+
# HTTPFile wraps up the new file operations for storing files onto an HTTP
|
9
|
+
# storage node.
|
10
|
+
#
|
11
|
+
# You really don't want to create an HTTPFile by hand. Instead you want to
|
12
|
+
# create a new file using MogileFS::MogileFS.new_file.
|
13
|
+
#
|
14
|
+
# WARNING! HTTP mode is completely untested as I cannot make it work on
|
15
|
+
# FreeBSD. Please send patches/tests if you find bugs.
|
16
|
+
#--
|
17
|
+
# TODO dup'd content in MogileFS::NFSFile
|
18
|
+
|
19
|
+
class MogileFS::HTTPFile < StringIO
|
20
|
+
|
21
|
+
##
|
22
|
+
# The path this file will be stored to.
|
23
|
+
|
24
|
+
attr_reader :path
|
25
|
+
|
26
|
+
##
|
27
|
+
# The key for this file. This key won't represent a real file until you've
|
28
|
+
# called #close.
|
29
|
+
|
30
|
+
attr_reader :key
|
31
|
+
|
32
|
+
##
|
33
|
+
# The class of this file.
|
34
|
+
|
35
|
+
attr_reader :class
|
36
|
+
|
37
|
+
##
|
38
|
+
# Works like File.open. Use MogileFS::MogileFS#new_file instead of this
|
39
|
+
# method.
|
40
|
+
|
41
|
+
def self.open(*args)
|
42
|
+
fp = new(*args)
|
43
|
+
|
44
|
+
return fp unless block_given?
|
45
|
+
|
46
|
+
begin
|
47
|
+
yield fp
|
48
|
+
ensure
|
49
|
+
fp.close
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Creates a new HTTPFile with MogileFS-specific data. Use
|
55
|
+
# MogileFS::MogileFS#new_file instead of this method.
|
56
|
+
|
57
|
+
def initialize(mg, fid, path, devid, klass, key, dests, content_length)
|
58
|
+
super ''
|
59
|
+
@mg = mg
|
60
|
+
@fid = fid
|
61
|
+
@path = path
|
62
|
+
@devid = devid
|
63
|
+
@klass = klass
|
64
|
+
@key = key
|
65
|
+
|
66
|
+
@dests = dests.map { |(_,u)| URI.parse u }
|
67
|
+
@tried = {}
|
68
|
+
|
69
|
+
@socket = nil
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Closes the file handle and marks it as closed in MogileFS.
|
74
|
+
|
75
|
+
def close
|
76
|
+
connect_client
|
77
|
+
|
78
|
+
resp = @client.put(@path.request_uri, :body => string)
|
79
|
+
raise "HTTP response status from upload: #{resp.http_status}" unless resp.http_status.to_i == 200
|
80
|
+
|
81
|
+
@mg.backend.create_close(:fid => @fid, :devid => @devid,
|
82
|
+
:domain => @mg.domain, :key => @key,
|
83
|
+
:path => @path, :size => length)
|
84
|
+
return nil
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def connected?
|
90
|
+
return !(@client.nil?)
|
91
|
+
end
|
92
|
+
|
93
|
+
def connect_client
|
94
|
+
return @client if connected?
|
95
|
+
|
96
|
+
next_path
|
97
|
+
|
98
|
+
if @path.nil? then
|
99
|
+
@tried.clear
|
100
|
+
next_path
|
101
|
+
raise 'Unable to open socket to storage node' if @path.nil?
|
102
|
+
end
|
103
|
+
|
104
|
+
@client = RFuzz::HttpClient.new(@path.host, @path.port)
|
105
|
+
end
|
106
|
+
|
107
|
+
def next_path
|
108
|
+
@path = nil
|
109
|
+
@dests.each do |dest|
|
110
|
+
unless @tried.include? dest then
|
111
|
+
@path = dest
|
112
|
+
return
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
@@ -0,0 +1,237 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
require 'rfuzz/client'
|
5
|
+
|
6
|
+
require 'mogilefs/client'
|
7
|
+
require 'mogilefs/nfsfile'
|
8
|
+
|
9
|
+
##
|
10
|
+
# Timeout error class.
|
11
|
+
|
12
|
+
class MogileFS::Timeout < Timeout::Error; end
|
13
|
+
|
14
|
+
##
|
15
|
+
# MogileFS File manipulation client.
|
16
|
+
|
17
|
+
class MogileFS::MogileFS < MogileFS::Client
|
18
|
+
|
19
|
+
##
|
20
|
+
# The path to the local MogileFS mount point if you are using NFS mode.
|
21
|
+
|
22
|
+
attr_reader :root
|
23
|
+
|
24
|
+
##
|
25
|
+
# The domain of keys for this MogileFS client.
|
26
|
+
|
27
|
+
attr_reader :domain
|
28
|
+
|
29
|
+
##
|
30
|
+
# Creates a new MogileFS::MogileFS instance. +args+ must include a key
|
31
|
+
# :domain specifying the domain of this client. A key :root will be used to
|
32
|
+
# specify the root of the NFS file system.
|
33
|
+
|
34
|
+
def initialize(args = {})
|
35
|
+
@domain = args[:domain]
|
36
|
+
@root = args[:root]
|
37
|
+
|
38
|
+
raise ArgumentError, "you must specify a domain" unless @domain
|
39
|
+
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Enumerates keys starting with +key+.
|
45
|
+
|
46
|
+
def each_key(prefix)
|
47
|
+
after = nil
|
48
|
+
|
49
|
+
keys, after = list_keys prefix
|
50
|
+
|
51
|
+
until keys.empty? do
|
52
|
+
keys.each { |k| yield k }
|
53
|
+
keys, after = list_keys prefix, after
|
54
|
+
end
|
55
|
+
|
56
|
+
return nil
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Retrieves the contents of +key+.
|
61
|
+
|
62
|
+
def get_file_data(key)
|
63
|
+
paths = get_paths key
|
64
|
+
|
65
|
+
return nil unless paths
|
66
|
+
|
67
|
+
paths.each do |path|
|
68
|
+
next unless path
|
69
|
+
case path
|
70
|
+
when /^http:\/\// then
|
71
|
+
begin
|
72
|
+
path = URI.parse path
|
73
|
+
data = timeout(5, MogileFS::Timeout) {
|
74
|
+
RFuzz::HttpClient.new(path.host, path.port).get(path).http_body
|
75
|
+
}
|
76
|
+
return data
|
77
|
+
rescue MogileFS::Timeout, RFuzz::HttpClientError, RFuzz::HttpClientParserError
|
78
|
+
next
|
79
|
+
end
|
80
|
+
else
|
81
|
+
next unless File.exist? path
|
82
|
+
return File.read(path)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
return nil
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Get the paths for +key+.
|
91
|
+
|
92
|
+
def get_paths(key, noverify = true, zone = nil)
|
93
|
+
noverify = noverify ? 1 : 0
|
94
|
+
res = @backend.get_paths(:domain => @domain, :key => key,
|
95
|
+
:noverify => noverify, :zone => zone)
|
96
|
+
|
97
|
+
return nil if res.nil? and @backend.lasterr == 'unknown_key'
|
98
|
+
paths = (1..res['paths'].to_i).map { |i| res["path#{i}"] }
|
99
|
+
return paths if paths.empty?
|
100
|
+
return paths if paths.first =~ /^http:\/\//
|
101
|
+
return paths.map { |path| File.join @root, path }
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Creates a new file +key+ in +klass+. +bytes+ is currently unused.
|
106
|
+
#
|
107
|
+
# The +block+ operates like File.open.
|
108
|
+
|
109
|
+
def new_file(key, klass, bytes = 0, &block) # :yields: file
|
110
|
+
raise 'readonly mogilefs' if readonly?
|
111
|
+
|
112
|
+
res = @backend.create_open(:domain => @domain, :class => klass,
|
113
|
+
:key => key, :multi_dest => 1)
|
114
|
+
|
115
|
+
raise "#{@backend.lasterr}: #{@backend.lasterrstr}" if res.nil? # HACK
|
116
|
+
|
117
|
+
dests = nil
|
118
|
+
|
119
|
+
if res.include? 'dev_count' then # HACK HUH?
|
120
|
+
dests = (1..res['dev_count'].to_i).map do |i|
|
121
|
+
[res["devid_#{i}"], res["path_#{i}"]]
|
122
|
+
end
|
123
|
+
else
|
124
|
+
# 0x0040: d0e4 4f4b 2064 6576 6964 3d31 2666 6964 ..OK.devid=1&fid
|
125
|
+
# 0x0050: 3d33 2670 6174 683d 6874 7470 3a2f 2f31 =3&path=http://1
|
126
|
+
# 0x0060: 3932 2e31 3638 2e31 2e37 323a 3735 3030 92.168.1.72:7500
|
127
|
+
# 0x0070: 2f64 6576 312f 302f 3030 302f 3030 302f /dev1/0/000/000/
|
128
|
+
# 0x0080: 3030 3030 3030 3030 3033 2e66 6964 0d0a 0000000003.fid..
|
129
|
+
|
130
|
+
dests = [[res['devid'], res['path']]]
|
131
|
+
end
|
132
|
+
|
133
|
+
dest = dests.first
|
134
|
+
devid, path = dest
|
135
|
+
|
136
|
+
case path
|
137
|
+
when /^http:\/\// then
|
138
|
+
MogileFS::HTTPFile.open(self, res['fid'], path, devid, klass, key,
|
139
|
+
dests, bytes, &block)
|
140
|
+
else
|
141
|
+
MogileFS::NFSFile.open(self, res['fid'], path, devid, klass, key, &block)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Copies the contents of +file+ into +key+ in class +klass+. +file+ can be
|
147
|
+
# either a file name or an object that responds to #read.
|
148
|
+
|
149
|
+
def store_file(key, klass, file)
|
150
|
+
raise 'readonly mogilefs' if readonly?
|
151
|
+
|
152
|
+
new_file key, klass do |mfp|
|
153
|
+
if file.respond_to? :read then
|
154
|
+
return copy(file, mfp)
|
155
|
+
else
|
156
|
+
return File.open(file) { |fp| copy(fp, mfp) }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Stores +content+ into +key+ in class +klass+.
|
163
|
+
|
164
|
+
def store_content(key, klass, content)
|
165
|
+
raise 'readonly mogilefs' if readonly?
|
166
|
+
|
167
|
+
new_file key, klass do |mfp|
|
168
|
+
mfp << content
|
169
|
+
end
|
170
|
+
|
171
|
+
return content.length
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# Removes +key+.
|
176
|
+
|
177
|
+
def delete(key)
|
178
|
+
raise 'readonly mogilefs' if readonly?
|
179
|
+
|
180
|
+
res = @backend.delete :domain => @domain, :key => key
|
181
|
+
|
182
|
+
if res.nil? and @backend.lasterr != 'unknown_key' then
|
183
|
+
raise "unable to delete #{key}: #{@backend.lasterr}"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
##
|
188
|
+
# Sleeps +duration+.
|
189
|
+
|
190
|
+
def sleep(duration)
|
191
|
+
@backend.sleep :duration => duration
|
192
|
+
end
|
193
|
+
|
194
|
+
##
|
195
|
+
# Renames a key +from+ to key +to+.
|
196
|
+
|
197
|
+
def rename(from, to)
|
198
|
+
raise 'readonly mogilefs' if readonly?
|
199
|
+
|
200
|
+
res = @backend.rename :domain => @domain, :from_key => from, :to_key => to
|
201
|
+
|
202
|
+
if res.nil? and @backend.lasterr != 'unknown_key' then
|
203
|
+
raise "unable to rename #{from_key} to #{to_key}: #{@backend.lasterr}"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
##
|
208
|
+
# Lists keys starting with +prefix+ follwing +after+ up to +limit+. If
|
209
|
+
# +after+ is nil the list starts at the beginning.
|
210
|
+
|
211
|
+
def list_keys(prefix, after = nil, limit = 1000)
|
212
|
+
res = @backend.list_keys(:domain => domain, :prefix => prefix,
|
213
|
+
:after => after, :limit => limit)
|
214
|
+
|
215
|
+
return nil if res.nil?
|
216
|
+
|
217
|
+
keys = (1..res['key_count'].to_i).map { |i| res["key_#{i}"] }
|
218
|
+
|
219
|
+
return keys, res['next_after']
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
def copy(from, to) # HACK use FileUtils
|
225
|
+
bytes = 0
|
226
|
+
|
227
|
+
until from.eof? do
|
228
|
+
chunk = from.read 8192
|
229
|
+
to.write chunk
|
230
|
+
bytes += chunk.length
|
231
|
+
end
|
232
|
+
|
233
|
+
return bytes
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
|