1 package MogileFS
::Device
;
6 use MogileFS
::Util
qw(throw);
7 use MogileFS
::Util
qw(okay_args device_state error);
11 MogileFS::Device - device class
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/,
25 my ($class, $args, $host_factory) = @_;
27 host_factory
=> $host_factory,
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)$/;
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
} }
51 return $self->{host_factory
}->get_by_id($self->{hostid
});
54 # returns 0 if not known, else [0,1]
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]
64 return undef unless $self->{mb_total
} && defined $self->{mb_used
};
65 return $self->{mb_used
} / $self->{mb_total
};
68 # FIXME: $self->mb_free?
71 my @tofetch = @_ ?
@_ : @fields;
72 my $ret = { (map { $_ => $self->{$_} } @tofetch),
73 'mb_free' => $self->mb_free };
78 return $_[0]->fields(@observed_fields);
81 sub observed_utilization
{
85 my $weight_varname = 'T_FAKE_IO_DEV' . $self->id;
86 return $ENV{$weight_varname} if defined $ENV{$weight_varname};
89 return $self->{utilization
};
93 my $host = $_[0]->host;
94 return ($host && $host->observed_reachable);
97 sub observed_writeable
{
99 return 0 unless $self->host_ok;
100 return $self->{observed_state
} && $self->{observed_state
} eq 'writeable';
103 sub observed_readable
{
105 return 0 unless $self->host_ok;
106 return $self->{observed_state
} && $self->{observed_state
} eq 'readable';
109 sub observed_unreachable
{
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?
118 my $ds = device_state
($_[0]->status);
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
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
{
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;
157 return $self->{mb_total
} - $self->{mb_used
}
158 if $self->{mb_total
} && defined $self->{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;
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;
185 # returns 1 on success, 0 on failure
186 sub create_directory
{
187 my ($self, $uri) = @_;
188 return 1 if $self->doesnt_know_mkcol || MogileFS
::Config
->server_setting_cached('skip_mkcol');
190 # rfc2518 says we "should" use a trailing slash. Some servers
191 # (nginx) appears to require it.
192 $uri .= '/' unless $uri =~ m
/\
/$/;
194 return 1 if $dir_made{$uri};
196 my $hostid = $self->hostid;
197 my $host = $self->host;
198 my $hostip = $host->ip or return 0;
199 my $port = $host->http_port or return 0;
200 my $peer = "$hostip:$port";
202 my $sock = IO
::Socket
::INET
->new(PeerAddr
=> $peer, Timeout
=> 1)
205 print $sock "MKCOL $uri HTTP/1.0\r\n".
206 "Content-Length: 0\r\n\r\n";
210 # if they don't support this method, remember that
211 if ($ans && $ans =~ m!HTTP/1\.[01] (400|501)!) {
212 $self->{no_mkcol
} = 1;
213 # TODO: move this into method in *monitor* worker
217 return 0 unless $ans && $ans =~ m!^HTTP/1.[01] 2\d\d!;
220 $dir_made{$uri} = $now;
222 # cleanup %dir_made occasionally.
223 my $clean_interval = 300; # every 5 minutes.
224 if ($dir_made_lastclean < $now - $clean_interval) {
225 $dir_made_lastclean = $now;
226 foreach my $k (keys %dir_made) {
227 delete $dir_made{$k} if $dir_made{$k} < $now - 3600;
234 my ($self, %opts) = @_;
235 my $limit = delete $opts{limit
};
236 croak
("No limit specified") unless $limit && $limit =~ /^\d+$/;
237 croak
("Unknown options to fid_list") if %opts;
239 my $sto = Mgd
::get_store
();
240 my $fidids = $sto->get_fidids_by_device($self->devid, $limit);
242 MogileFS
::FID
->new($_)
247 my ($self, %opts) = @_;
249 my $sto = Mgd
::get_store
();
250 # storage function does validation.
251 my $fidids = $sto->get_fidid_chunks_by_device(devid
=> $self->devid, %opts);
253 MogileFS
::FID
->new($_)
258 my ($self, $fid) = @_;
259 Mgd
::get_store
()->remove_fidid_from_devid($fid->id, $self->id);
265 my $host = $self->host;
266 my $get_port = $host->http_get_port;
267 my $hostip = $host->ip;
268 return "http://$hostip:$get_port/dev$self->{devid}/usage";
271 sub can_change_to_state
{
272 my ($self, $newstate) = @_;
273 # don't allow dead -> alive transitions. (yes, still possible
274 # to go dead -> readonly -> alive to bypass this, but this is
275 # all more of a user-education thing than an absolute policy)
276 return 0 if $self->dstate->is_perm_dead && $newstate eq 'alive';
280 sub vivify_directories
{
281 my ($self, $path) = @_;
283 # $path is something like:
284 # http://10.0.0.26:7500/dev2/0/000/148/0000148056.fid
286 # three directories we'll want to make:
287 # http://10.0.0.26:7500/dev2/0
288 # http://10.0.0.26:7500/dev2/0/000
289 # http://10.0.0.26:7500/dev2/0/000/148
291 croak
"non-HTTP mode no longer supported" unless $path =~ /^http/;
292 return 0 unless $path =~ m!/dev(\d+)/(\d+)/(\d\d\d)/(\d\d\d)/\d+\.fid$!;
293 my ($devid, $p1, $p2, $p3) = ($1, $2, $3, $4);
295 die "devid mismatch" unless $self->id == $devid;
297 $self->create_directory("/dev$devid/$p1");
298 $self->create_directory("/dev$devid/$p1/$p2");
299 $self->create_directory("/dev$devid/$p1/$p2/$p3");
302 # FIXME: Remove this once vestigial code is removed.
303 sub set_observed_utilization
{
307 # Compatibility interface since this old routine is unfortunately called
308 # internally within plugins. This data should be passed into any hooks which
310 # Currently an issue with MogileFS::Network + ZoneLocal
311 # Remove this in 2012.
313 return Mgd
::device_factory
()->get_all;
317 return $_[0]->{reject_bad_md5
};