mogilefs-client 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +32 -0
- data/Manifest.txt +18 -0
- data/README +9 -0
- data/Rakefile +56 -0
- data/lib/mogilefs.rb +24 -0
- data/lib/mogilefs/admin.rb +274 -0
- data/lib/mogilefs/backend.rb +219 -0
- data/lib/mogilefs/client.rb +64 -0
- data/lib/mogilefs/httpfile.rb +144 -0
- data/lib/mogilefs/mogilefs.rb +214 -0
- data/lib/mogilefs/nfsfile.rb +81 -0
- data/lib/mogilefs/pool.rb +50 -0
- data/test/setup.rb +55 -0
- data/test/test_admin.rb +37 -0
- data/test/test_backend.rb +208 -0
- data/test/test_client.rb +49 -0
- data/test/test_mogilefs.rb +123 -0
- data/test/test_pool.rb +98 -0
- metadata +63 -0
data/LICENSE
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
Portions copyright 2004 David Heinemeier Hansson.
|
2
|
+
|
3
|
+
All original code copyright 2005 Eric Hodel, The Robot Co-op. All rights
|
4
|
+
reserved.
|
5
|
+
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
7
|
+
modification, are permitted provided that the following conditions
|
8
|
+
are met:
|
9
|
+
|
10
|
+
1. Redistributions of source code must retain the above copyright
|
11
|
+
notice, this list of conditions and the following disclaimer.
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright
|
13
|
+
notice, this list of conditions and the following disclaimer in the
|
14
|
+
documentation and/or other materials provided with the distribution.
|
15
|
+
3. Neither the names of the authors nor the names of their contributors
|
16
|
+
may be used to endorse or promote products derived from this software
|
17
|
+
without specific prior written permission.
|
18
|
+
4. Redistribution in Rails or any sub-projects of Rails is not allowed
|
19
|
+
until Rails runs without warnings with the ``-W2'' flag enabled.
|
20
|
+
|
21
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
|
22
|
+
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
23
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
24
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
|
25
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
26
|
+
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
27
|
+
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
28
|
+
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
29
|
+
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
30
|
+
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
31
|
+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
32
|
+
|
data/Manifest.txt
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
LICENSE
|
2
|
+
Manifest.txt
|
3
|
+
README
|
4
|
+
Rakefile
|
5
|
+
lib/mogilefs.rb
|
6
|
+
lib/mogilefs/admin.rb
|
7
|
+
lib/mogilefs/backend.rb
|
8
|
+
lib/mogilefs/client.rb
|
9
|
+
lib/mogilefs/httpfile.rb
|
10
|
+
lib/mogilefs/mogilefs.rb
|
11
|
+
lib/mogilefs/nfsfile.rb
|
12
|
+
lib/mogilefs/pool.rb
|
13
|
+
test/setup.rb
|
14
|
+
test/test_admin.rb
|
15
|
+
test/test_backend.rb
|
16
|
+
test/test_client.rb
|
17
|
+
test/test_mogilefs.rb
|
18
|
+
test/test_pool.rb
|
data/README
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
This is a client for Danga's MogileFS file store. For information on MogileFS
|
2
|
+
see:
|
3
|
+
|
4
|
+
http://danga.com/mogilefs/
|
5
|
+
|
6
|
+
This client is only known to work in NFS mode. HTTP mode is implemented but completely untested. If you find a bug, please report it in an email to:
|
7
|
+
|
8
|
+
eric@robotcoop.com.
|
9
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
|
7
|
+
$VERBOSE = nil
|
8
|
+
|
9
|
+
spec = Gem::Specification.new do |s|
|
10
|
+
s.name = 'mogilefs-client'
|
11
|
+
s.version = '1.0.1'
|
12
|
+
s.summary = 'A Ruby MogileFS client'
|
13
|
+
s.description = 'A Ruby MogileFS client. MogileFS is a distributed filesystem written by Danga Interactive. This client supports NFS mode and has untested support for HTTP mode.'
|
14
|
+
s.author = 'Eric Hodel'
|
15
|
+
s.email = 'eric@robotcoop.com'
|
16
|
+
|
17
|
+
s.has_rdoc = true
|
18
|
+
s.files = File.read('Manifest.txt').split($/)
|
19
|
+
s.require_path = 'lib'
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'Run tests'
|
23
|
+
task :default => [ :test ]
|
24
|
+
|
25
|
+
Rake::TestTask.new('test') do |t|
|
26
|
+
t.libs << 'test'
|
27
|
+
t.pattern = 'test/test_*.rb'
|
28
|
+
t.verbose = true
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'Update Manifest.txt'
|
32
|
+
task :update_manifest do
|
33
|
+
sh "find . -type f | sed -e 's%./%%' | egrep -v 'svn|swp|~' | egrep -v '^(doc|pkg)/' | sort > Manifest.txt"
|
34
|
+
end
|
35
|
+
|
36
|
+
desc 'Generate RDoc'
|
37
|
+
Rake::RDocTask.new :rdoc do |rd|
|
38
|
+
rd.rdoc_dir = 'doc'
|
39
|
+
rd.rdoc_files.add 'lib', 'README', 'LICENSE'
|
40
|
+
rd.main = 'README'
|
41
|
+
rd.options << '-d' if `which dot` =~ /\/dot/
|
42
|
+
end
|
43
|
+
|
44
|
+
desc 'Build Gem'
|
45
|
+
Rake::GemPackageTask.new spec do |pkg|
|
46
|
+
pkg.need_tar = true
|
47
|
+
end
|
48
|
+
|
49
|
+
desc 'Clean up'
|
50
|
+
task :clean => [ :clobber_rdoc, :clobber_package ]
|
51
|
+
|
52
|
+
desc 'Clean up'
|
53
|
+
task :clobber => [ :clean ]
|
54
|
+
|
55
|
+
# vim: syntax=Ruby
|
56
|
+
|
data/lib/mogilefs.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
##
|
2
|
+
# MogileFS is a Ruby client for Danga Interactive's open source distributed
|
3
|
+
# filesystem.
|
4
|
+
#
|
5
|
+
# To read more about Danga's MogileFS: http://danga.com/mogilefs/
|
6
|
+
|
7
|
+
module MogileFS
|
8
|
+
|
9
|
+
##
|
10
|
+
# Raised when a socket remains unreadable for too long.
|
11
|
+
|
12
|
+
class UnreadableSocketError < RuntimeError; end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'socket'
|
17
|
+
|
18
|
+
require 'mogilefs/backend'
|
19
|
+
require 'mogilefs/nfsfile'
|
20
|
+
require 'mogilefs/httpfile'
|
21
|
+
require 'mogilefs/client'
|
22
|
+
require 'mogilefs/mogilefs'
|
23
|
+
require 'mogilefs/admin'
|
24
|
+
|
@@ -0,0 +1,274 @@
|
|
1
|
+
require 'mogilefs/client'
|
2
|
+
|
3
|
+
##
|
4
|
+
# A MogileFS Administration Client
|
5
|
+
|
6
|
+
class MogileFS::Admin < MogileFS::Client
|
7
|
+
|
8
|
+
##
|
9
|
+
# Returns an Array of host status Hashes. If +hostid+ is given only that
|
10
|
+
# host is returned.
|
11
|
+
#
|
12
|
+
# admin.get_hosts 1
|
13
|
+
#
|
14
|
+
# Returns:
|
15
|
+
#
|
16
|
+
# [{"status"=>"alive",
|
17
|
+
# "http_get_port"=>"",
|
18
|
+
# "http_port"=>"",
|
19
|
+
# "hostid"=>"1",
|
20
|
+
# "hostip"=>"",
|
21
|
+
# "hostname"=>"rur-1",
|
22
|
+
# "remoteroot"=>"/mnt/mogilefs/rur-1",
|
23
|
+
# "altip"=>"",
|
24
|
+
# "altmask"=>""}]
|
25
|
+
|
26
|
+
def get_hosts(hostid = nil)
|
27
|
+
args = hostid ? { :hostid => hostid } : {}
|
28
|
+
res = @backend.get_hosts args
|
29
|
+
return clean('hosts', 'host', res)
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Returns an Array of device status Hashes. If devid is given only that
|
34
|
+
# device is returned.
|
35
|
+
#
|
36
|
+
# admin.get_devices 1
|
37
|
+
#
|
38
|
+
# Returns:
|
39
|
+
#
|
40
|
+
# [{"status"=>"alive",
|
41
|
+
# "mb_asof"=>"",
|
42
|
+
# "mb_free"=>"0",
|
43
|
+
# "devid"=>"1",
|
44
|
+
# "hostid"=>"1",
|
45
|
+
# "mb_used"=>"",
|
46
|
+
# "mb_total"=>""}]
|
47
|
+
|
48
|
+
def get_devices(devid = nil)
|
49
|
+
args = devid ? { :devid => devid } : {}
|
50
|
+
res = @backend.get_devices args
|
51
|
+
return clean('devices', 'dev', res)
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Returns an Array of fid Hashes from +from_fid+ to +to_fid+.
|
56
|
+
#
|
57
|
+
# admin.list_fids 0, 100
|
58
|
+
#
|
59
|
+
# Returns:
|
60
|
+
#
|
61
|
+
# [{"fid"=>"99",
|
62
|
+
# "class"=>"normal",
|
63
|
+
# "domain"=>"test",
|
64
|
+
# "devcount"=>"2",
|
65
|
+
# "length"=>"4",
|
66
|
+
# "key"=>"file_key"},
|
67
|
+
# {"fid"=>"82",
|
68
|
+
# "class"=>"normal",
|
69
|
+
# "devcount"=>"2",
|
70
|
+
# "domain"=>"test",
|
71
|
+
# "length"=>"9",
|
72
|
+
# "key"=>"new_new_key"}]
|
73
|
+
|
74
|
+
def list_fids(from_fid, to_fid)
|
75
|
+
res = @backend.list_fids :from => from_fid, :to => to_fid
|
76
|
+
return clean('fid_count', 'fid_', res)
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# Returns a statistics structure representing the state of mogilefs.
|
81
|
+
#
|
82
|
+
# admin.get_stats
|
83
|
+
#
|
84
|
+
# Returns:
|
85
|
+
#
|
86
|
+
# {"fids"=>{"max"=>"99", "count"=>"2"},
|
87
|
+
# "device"=>
|
88
|
+
# [{"status"=>"alive", "files"=>"2", "id"=>"1", "host"=>"rur-1"},
|
89
|
+
# {"status"=>"alive", "files"=>"2", "id"=>"2", "host"=>"rur-2"}],
|
90
|
+
# "replication"=>
|
91
|
+
# [{"files"=>"2", "class"=>"normal", "devcount"=>"2", "domain"=>"test"}],
|
92
|
+
# "file"=>[{"files"=>"2", "class"=>"normal", "domain"=>"test"}]}
|
93
|
+
|
94
|
+
def get_stats(type = 'all')
|
95
|
+
res = @backend.stats type => 1
|
96
|
+
stats = {}
|
97
|
+
|
98
|
+
stats['replication'] = clean 'replicationcount', 'replication', res, false
|
99
|
+
stats['file'] = clean 'filescount', 'files', res, false
|
100
|
+
stats['device'] = clean 'devicescount', 'devices', res, false
|
101
|
+
|
102
|
+
if res['fidmax'] or res['fidcount'] then
|
103
|
+
stats['fids'] = { 'max' => res['fidmax'], 'count' => res['fidcount'] }
|
104
|
+
end
|
105
|
+
|
106
|
+
return stats
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# Returns the domains present in the mogilefs.
|
111
|
+
#
|
112
|
+
# admin.get_domains
|
113
|
+
#
|
114
|
+
# Returns:
|
115
|
+
#
|
116
|
+
# {"test"=>{"normal"=>"2", "default"=>"2"}}
|
117
|
+
|
118
|
+
def get_domains
|
119
|
+
res = @backend.get_domains
|
120
|
+
|
121
|
+
domains = {}
|
122
|
+
(1..res['domains'].to_i).each do |i|
|
123
|
+
domain = clean "domain#{i}classes", "domain#{i}class", res, false
|
124
|
+
domains[res["domain#{i}"]] = Hash[*domain.map { |d| d.values}.flatten]
|
125
|
+
end
|
126
|
+
|
127
|
+
return domains
|
128
|
+
end
|
129
|
+
|
130
|
+
##
|
131
|
+
# Creates a new domain named +domain+. Returns nil if creation failed.
|
132
|
+
|
133
|
+
def create_domain(domain)
|
134
|
+
raise 'readonly mogilefs' if readonly?
|
135
|
+
res = @backend.create_domain :domain => domain
|
136
|
+
return res['domain'] unless res.nil?
|
137
|
+
end
|
138
|
+
|
139
|
+
##
|
140
|
+
# Deletes +domain+. Returns true if successful, false if not.
|
141
|
+
|
142
|
+
def delete_domain(domain)
|
143
|
+
raise 'readonly mogilefs' if readonly?
|
144
|
+
res = @backend.delete_domain :domain => domain
|
145
|
+
return !res.nil?
|
146
|
+
end
|
147
|
+
|
148
|
+
##
|
149
|
+
# Creates a new class in +domain+ named +klass+ with files replicated to
|
150
|
+
# +mindevcount+ devices. Returns nil on failure.
|
151
|
+
|
152
|
+
def create_class(domain, klass, mindevcount)
|
153
|
+
return modify_class(domain, klass, mindevcount, :create)
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Updates class +klass+ in +domain+ to be replicated to +mindevcount+
|
158
|
+
# devices. Returns nil on failure.
|
159
|
+
|
160
|
+
def update_class(domain, klass, mindevcount)
|
161
|
+
return modify_class(domain, klass, mindevcount, :update)
|
162
|
+
end
|
163
|
+
|
164
|
+
##
|
165
|
+
# Removes class +klass+ from +domain+. Returns true if successful, false if
|
166
|
+
# not.
|
167
|
+
|
168
|
+
def delete_class(domain, klass)
|
169
|
+
res = @backend.delete_class :domain => domain, :class => klass
|
170
|
+
return !res.nil?
|
171
|
+
end
|
172
|
+
|
173
|
+
##
|
174
|
+
# Creates a new host named +host+. +args+ must contain :ip and :port.
|
175
|
+
# Returns true if successful, false if not.
|
176
|
+
|
177
|
+
def create_host(host, args = {})
|
178
|
+
raise ArgumentError, "Must specify ip and port" unless \
|
179
|
+
args.include? :ip and args.include? :port
|
180
|
+
|
181
|
+
return modify_host(host, args, 'create')
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
# Updates +host+ with +args+. Returns true if successful, false if not.
|
186
|
+
|
187
|
+
def update_host(host, args = {})
|
188
|
+
return modify_host(host, args, 'update')
|
189
|
+
end
|
190
|
+
|
191
|
+
##
|
192
|
+
# Deletes host +host+. Returns nil on failure.
|
193
|
+
|
194
|
+
def delete_host(host)
|
195
|
+
raise 'readonly mogilefs' if readonly?
|
196
|
+
res = @backend.delete_host :host => host
|
197
|
+
return !res.nil?
|
198
|
+
end
|
199
|
+
|
200
|
+
##
|
201
|
+
# Changes the device status of +device+ on +host+ to +state+ which can be
|
202
|
+
# 'alive', 'down', or 'dead'.
|
203
|
+
|
204
|
+
def change_device_state(host, device, state)
|
205
|
+
raise 'readonly mogilefs' if readonly?
|
206
|
+
res = @backend.set_state :host => host, :device => device, :state => state
|
207
|
+
return !res.nil?
|
208
|
+
end
|
209
|
+
|
210
|
+
protected unless defined? $TESTING
|
211
|
+
|
212
|
+
##
|
213
|
+
# Modifies +klass+ on +domain+ to store files on +mindevcount+ devices via
|
214
|
+
# +action+. Returns the class name if successful, nil if not.
|
215
|
+
|
216
|
+
def modify_class(domain, klass, mindevcount, action)
|
217
|
+
raise 'readonly mogilefs' if readonly?
|
218
|
+
res = @backend.send("#{action}_class", :domain => domain, :class => klass,
|
219
|
+
:mindevcount => mindevcount)
|
220
|
+
|
221
|
+
return res['class'] unless res.nil?
|
222
|
+
end
|
223
|
+
|
224
|
+
##
|
225
|
+
# Modifies +host+ using +args+ via +action+. Returns true if successful,
|
226
|
+
# false if not.
|
227
|
+
|
228
|
+
def modify_host(host, args = {}, action = 'create')
|
229
|
+
args[:host] = host
|
230
|
+
res = @backend.send "#{action}_host", args
|
231
|
+
return !res.nil?
|
232
|
+
end
|
233
|
+
|
234
|
+
##
|
235
|
+
# Turns the response +res+ from the backend into an Array of Hashes from 1
|
236
|
+
# to res[+count+]. If +underscore+ is true then a '_' character is assumed
|
237
|
+
# between the prefix and the hash key value.
|
238
|
+
#
|
239
|
+
# res = {"host1_remoteroot"=>"/mnt/mogilefs/rur-1",
|
240
|
+
# "host1_hostname"=>"rur-1",
|
241
|
+
# "host1_hostid"=>"1",
|
242
|
+
# "host1_http_get_port"=>"",
|
243
|
+
# "host1_altip"=>"",
|
244
|
+
# "hosts"=>"1",
|
245
|
+
# "host1_hostip"=>"",
|
246
|
+
# "host1_http_port"=>"",
|
247
|
+
# "host1_status"=>"alive",
|
248
|
+
# "host1_altmask"=>""}
|
249
|
+
# admin.clean 'hosts', 'host', res
|
250
|
+
#
|
251
|
+
# Returns:
|
252
|
+
#
|
253
|
+
# [{"status"=>"alive",
|
254
|
+
# "http_get_port"=>"",
|
255
|
+
# "http_port"=>"",
|
256
|
+
# "hostid"=>"1",
|
257
|
+
# "hostip"=>"",
|
258
|
+
# "hostname"=>"rur-1",
|
259
|
+
# "remoteroot"=>"/mnt/mogilefs/rur-1",
|
260
|
+
# "altip"=>"",
|
261
|
+
# "altmask"=>""}]
|
262
|
+
|
263
|
+
def clean(count, prefix, res, underscore = true)
|
264
|
+
underscore = underscore ? '_' : ''
|
265
|
+
return (1..res[count].to_i).map do |i|
|
266
|
+
dev = res.select { |k,_| k =~ /^#{prefix}#{i}#{underscore}/ }.map do |k,v|
|
267
|
+
[k.sub(/^#{prefix}#{i}#{underscore}/, ''), v]
|
268
|
+
end
|
269
|
+
Hash[*dev.flatten]
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|
274
|
+
|
@@ -0,0 +1,219 @@
|
|
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
|
+
raise MogileFS::UnreadableSocketError if found.nil? or found.empty?
|
157
|
+
return true
|
158
|
+
end
|
159
|
+
|
160
|
+
##
|
161
|
+
# Returns a socket connected to a MogileFS tracker.
|
162
|
+
|
163
|
+
def socket
|
164
|
+
return @socket if @socket and not @socket.closed?
|
165
|
+
|
166
|
+
now = Time.now
|
167
|
+
|
168
|
+
@hosts.sort_by { rand(3) - 1 }.each do |host|
|
169
|
+
next if @dead.include? host and @dead[host] > now - 5
|
170
|
+
|
171
|
+
begin
|
172
|
+
@socket = connect_to(*host.split(':'))
|
173
|
+
rescue SystemCallError
|
174
|
+
@dead[host] = now
|
175
|
+
next
|
176
|
+
end
|
177
|
+
|
178
|
+
return @socket
|
179
|
+
end
|
180
|
+
|
181
|
+
raise "couldn't connect to mogilefsd backend"
|
182
|
+
end
|
183
|
+
|
184
|
+
##
|
185
|
+
# Turns a url params string into a Hash.
|
186
|
+
|
187
|
+
def url_decode(str)
|
188
|
+
pairs = str.split('&').map do |pair|
|
189
|
+
pair.split('=', 2).map { |v| url_unescape v }
|
190
|
+
end
|
191
|
+
|
192
|
+
return Hash[*pairs.flatten]
|
193
|
+
end
|
194
|
+
|
195
|
+
##
|
196
|
+
# Turns a Hash (or Array of pairs) into a url params string.
|
197
|
+
|
198
|
+
def url_encode(params)
|
199
|
+
return params.map do |k,v|
|
200
|
+
"#{url_escape k.to_s}=#{url_escape v.to_s}"
|
201
|
+
end.join("&")
|
202
|
+
end
|
203
|
+
|
204
|
+
##
|
205
|
+
# Escapes naughty URL characters.
|
206
|
+
|
207
|
+
def url_escape(str)
|
208
|
+
return str.gsub(/([^\w\,\-.\/\\\: ])/) { "%%%02x" % $1[0] }.tr(' ', '+')
|
209
|
+
end
|
210
|
+
|
211
|
+
##
|
212
|
+
# Unescapes naughty URL characters.
|
213
|
+
|
214
|
+
def url_unescape(str)
|
215
|
+
return str.gsub(/%([a-f0-9][a-f0-9])/i) { [$1.to_i(16)].pack 'C' }.tr('+', ' ')
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
|