device: reuse HTTP connections for MKCOL
[MogileFS-Server.git] / lib / MogileFS / Device.pm
blob3374aa91ec019d4b6666931044660ebb9c827ba2
1 package MogileFS::Device;
2 use strict;
3 use warnings;
4 use Carp qw/croak/;
5 use MogileFS::Server;
6 use MogileFS::Util qw(throw);
7 use MogileFS::Util qw(okay_args device_state error);
9 =head1
11 MogileFS::Device - device class
13 =cut
15 BEGIN {
16 my $testing = $ENV{TESTING} ? 1 : 0;
17 eval "sub TESTING () { $testing }";
20 my @observed_fields = qw/observed_state utilization reject_bad_md5/;
21 my @fields = (qw/hostid status weight mb_total mb_used mb_asof devid/,
22 @observed_fields);
24 sub new_from_args {
25 my ($class, $args, $host_factory) = @_;
26 my $self = bless {
27 host_factory => $host_factory,
28 %{$args},
29 }, $class;
31 # FIXME: No guarantee (as of now?) that hosts get loaded before devs.
32 #$self->host || die "No host for $self->{devid} (host $self->{hostid})";
34 croak "invalid device observed state '$self->{observed_state}', valid: writeable, readable, unreachable"
35 if $self->{observed_state} && $self->{observed_state} !~ /^(?:writeable|readable|unreachable)$/;
37 return $self;
40 # Instance methods
42 sub id { return $_[0]{devid} }
43 sub devid { return $_[0]{devid} }
44 sub name { return $_[0]{devid} }
45 sub status { return $_[0]{status} }
46 sub weight { return $_[0]{weight} }
47 sub hostid { return $_[0]{hostid} }
49 sub host {
50 my $self = shift;
51 return $self->{host_factory}->get_by_id($self->{hostid});
54 # returns 0 if not known, else [0,1]
55 sub percent_free {
56 my $self = shift;
57 return 0 unless $self->{mb_total} && defined $self->{mb_used};
58 return 1 - ($self->{mb_used} / $self->{mb_total});
61 # returns undef if not known, else [0,1]
62 sub percent_full {
63 my $self = shift;
64 return undef unless $self->{mb_total} && defined $self->{mb_used};
65 return $self->{mb_used} / $self->{mb_total};
68 # FIXME: $self->mb_free?
69 sub fields {
70 my $self = shift;
71 my @tofetch = @_ ? @_ : @fields;
72 my $ret = { (map { $_ => $self->{$_} } @tofetch),
73 'mb_free' => $self->mb_free };
74 return $ret;
77 sub observed_fields {
78 return $_[0]->fields(@observed_fields);
81 sub observed_utilization {
82 my $self = shift;
84 if (TESTING) {
85 my $weight_varname = 'T_FAKE_IO_DEV' . $self->id;
86 return $ENV{$weight_varname} if defined $ENV{$weight_varname};
89 return $self->{utilization};
92 sub host_ok {
93 my $host = $_[0]->host;
94 return ($host && $host->observed_reachable);
97 sub observed_writeable {
98 my $self = shift;
99 return 0 unless $self->host_ok;
100 return $self->{observed_state} && $self->{observed_state} eq 'writeable';
103 sub observed_readable {
104 my $self = shift;
105 return 0 unless $self->host_ok;
106 return $self->{observed_state} && $self->{observed_state} eq 'readable';
109 sub observed_unreachable {
110 my $self = shift;
111 # host is unreachability implies device unreachability
112 return 1 unless $self->host_ok;
113 return $self->{observed_state} && $self->{observed_state} eq 'unreachable';
116 # FIXME: This pattern is weird. Store the object on new?
117 sub dstate {
118 my $ds = device_state($_[0]->status);
119 return $ds if $ds;
120 error("dev$_[0]->{devid} has bogus status '$_[0]->{status}', pretending 'down'");
121 return device_state("down");
124 sub can_delete_from {
125 return $_[0]->dstate->can_delete_from;
128 # this method is for Monitor, other workers should use should_read_from
129 sub can_read_from {
130 return $_[0]->host->alive && $_[0]->dstate->can_read_from;
133 # this is the only method a worker should call for checking for readability
134 sub should_read_from {
135 return $_[0]->can_read_from && ($_[0]->observed_readable || $_[0]->observed_writeable);
138 # FIXME: Is there a (unrelated to this code) bug where new files aren't tested
139 # against the free space limit before being stored or replicated somewhere?
140 sub should_get_new_files {
141 my $self = shift;
142 my $dstate = $self->dstate;
144 return 0 unless $dstate->should_get_new_files;
145 return 0 unless $self->observed_writeable;
146 return 0 unless $self->host->alive;
147 # have enough disk space? (default: 100MB)
148 my $min_free = MogileFS->config("min_free_space");
149 return 0 if $self->{mb_total} &&
150 $self->mb_free < $min_free;
152 return 1;
155 sub mb_free {
156 my $self = shift;
157 return $self->{mb_total} - $self->{mb_used}
158 if $self->{mb_total} && defined $self->{mb_used};
161 sub mb_used {
162 return $_[0]->{mb_used};
165 # currently the same policy, but leaving it open for differences later.
166 sub should_get_replicated_files {
167 return $_[0]->should_get_new_files;
170 sub not_on_hosts {
171 my ($self, @hosts) = @_;
172 my @hostids = map { ref($_) ? $_->id : $_ } @hosts;
173 my $my_hostid = $self->id;
174 return (grep { $my_hostid == $_ } @hostids) ? 0 : 1;
177 # "cached" by nature of the monitor worker testing this.
178 sub doesnt_know_mkcol {
179 return $_[0]->{no_mkcol};
182 # Gross class-based singleton cache.
183 my %dir_made; # /dev<n>/path -> $time
184 my $dir_made_lastclean = 0;
186 # if no callback is supplied: returns 1 on success, 0 on failure
187 # if a callback is supplied, the return value will be passed to the callback
188 # upon completion.
189 sub create_directory {
190 my ($self, $uri, $cb) = @_;
191 if ($self->doesnt_know_mkcol || MogileFS::Config->server_setting_cached('skip_mkcol')) {
192 return $cb ? $cb->(1) : 1;
195 # rfc2518 says we "should" use a trailing slash. Some servers
196 # (nginx) appears to require it.
197 $uri .= '/' unless $uri =~ m/\/$/;
199 if ($dir_made{$uri}) {
200 return $cb ? $cb->(1) : 1;
203 my $res;
204 my $on_mkcol_response = sub {
205 if ($res->is_success) {
206 my $now = time();
207 $dir_made{$uri} = $now;
209 # cleanup %dir_made occasionally.
210 my $clean_interval = 300; # every 5 minutes.
211 if ($dir_made_lastclean < $now - $clean_interval) {
212 $dir_made_lastclean = $now;
213 foreach my $k (keys %dir_made) {
214 delete $dir_made{$k} if $dir_made{$k} < $now - 3600;
217 return 1;
218 } elsif ($res->code =~ /\A(?:400|501)\z/) {
219 # if they don't support this method, remember that
220 # TODO: move this into method in *monitor* worker
221 $self->{no_mkcol} = 1;
222 return 1;
223 } else {
224 return 0;
228 my %opts = ( headers => { "Content-Length" => "0" } );
229 $self->host->http("MKCOL", $uri, \%opts, sub {
230 ($res) = @_;
231 $cb->($on_mkcol_response->()) if $cb;
234 return if $cb;
236 Danga::Socket->SetPostLoopCallback(sub { !defined $res });
237 Danga::Socket->EventLoop;
238 return $on_mkcol_response->();
241 sub fid_list {
242 my ($self, %opts) = @_;
243 my $limit = delete $opts{limit};
244 croak("No limit specified") unless $limit && $limit =~ /^\d+$/;
245 croak("Unknown options to fid_list") if %opts;
247 my $sto = Mgd::get_store();
248 my $fidids = $sto->get_fidids_by_device($self->devid, $limit);
249 return map {
250 MogileFS::FID->new($_)
251 } @{$fidids || []};
254 sub fid_chunks {
255 my ($self, %opts) = @_;
257 my $sto = Mgd::get_store();
258 # storage function does validation.
259 my $fidids = $sto->get_fidid_chunks_by_device(devid => $self->devid, %opts);
260 return map {
261 MogileFS::FID->new($_)
262 } @{$fidids || []};
265 sub forget_about {
266 my ($self, $fid) = @_;
267 Mgd::get_store()->remove_fidid_from_devid($fid->id, $self->id);
268 return 1;
271 sub usage_url {
272 my $self = shift;
273 my $host = $self->host;
274 my $get_port = $host->http_get_port;
275 my $hostip = $host->ip;
276 return "http://$hostip:$get_port/dev$self->{devid}/usage";
279 sub can_change_to_state {
280 my ($self, $newstate) = @_;
281 # don't allow dead -> alive transitions. (yes, still possible
282 # to go dead -> readonly -> alive to bypass this, but this is
283 # all more of a user-education thing than an absolute policy)
284 return 0 if $self->dstate->is_perm_dead && $newstate eq 'alive';
285 return 1;
288 sub vivify_directories {
289 my ($self, $path) = @_;
291 # $path is something like:
292 # http://10.0.0.26:7500/dev2/0/000/148/0000148056.fid
294 # three directories we'll want to make:
295 # http://10.0.0.26:7500/dev2/0
296 # http://10.0.0.26:7500/dev2/0/000
297 # http://10.0.0.26:7500/dev2/0/000/148
299 croak "non-HTTP mode no longer supported" unless $path =~ /^http/;
300 return 0 unless $path =~ m!/dev(\d+)/(\d+)/(\d\d\d)/(\d\d\d)/\d+\.fid$!;
301 my ($devid, $p1, $p2, $p3) = ($1, $2, $3, $4);
303 die "devid mismatch" unless $self->id == $devid;
305 $self->create_directory("/dev$devid/$p1");
306 $self->create_directory("/dev$devid/$p1/$p2");
307 $self->create_directory("/dev$devid/$p1/$p2/$p3");
310 # Compatibility interface since this old routine is unfortunately called
311 # internally within plugins. This data should be passed into any hooks which
312 # may need it?
313 # Currently an issue with MogileFS::Network + ZoneLocal
314 # Remove this in 2012.
315 sub devices {
316 return Mgd::device_factory()->get_all;
319 sub reject_bad_md5 {
320 return $_[0]->{reject_bad_md5};