File Coverage

blib/lib/MogileFS/ReplicationPolicy/MultipleHosts.pm
Criterion Covered Total %
statement 73 73 100.0
branch 22 28 78.5
condition 15 27 55.5
subroutine 10 10 100.0
pod 0 6 0.0
total 120 144 83.3


line stmt bran cond sub pod time code
1             package MogileFS::ReplicationPolicy::MultipleHosts;
2 21     21   60236 use strict;
  21         937  
  21         579  
3 21     21   101 use base 'MogileFS::ReplicationPolicy';
  21         35  
  21         7065  
4 21     21   431 use MogileFS::Util qw(weighted_list);
  21         36  
  21         791  
5 21     21   406 use MogileFS::ReplicationRequest qw(ALL_GOOD TOO_GOOD TEMP_NO_ANSWER);
  21         37  
  21         12691  
6              
7             sub new {
8 36     36 0 2102 my ($class, $mindevcount) = @_;
9 36         126 return bless {
10             mindevcount => $mindevcount,
11             }, $class;
12             }
13              
14             sub new_from_policy_args {
15 16     16 0 31 my ($class, $argref) = @_;
16             # Note: "MultipleHosts()" is okay, in which case the 'mindevcount'
17             # on the class is used. (see below)
18 16 50       87 $$argref =~ s/^\s* \( \s* (\d*) \s* \) \s*//x
19             or die "$class failed to parse args: $$argref";
20 16         38 return $class->new($1)
21             }
22              
23 1     1 0 524 sub mindevcount { $_[0]{mindevcount} }
24              
25             sub replicate_to {
26 20     20 0 284 my ($self, %args) = @_;
27              
28 20         39 my $fid = delete $args{fid}; # fid scalar to copy
29 20         35 my $on_devs = delete $args{on_devs}; # arrayref of device objects
30 20         28 my $all_devs = delete $args{all_devs}; # hashref of { devid => MogileFS::Device }
31 20         29 my $failed = delete $args{failed}; # hashref of { devid => 1 } of failed attempts this round
32              
33             # this is the per-class mindevcount (the old way), which is passed in automatically
34             # from the replication worker. but if we have our own configured mindevcount
35             # in class.replpolicy, like "MultipleHosts(3)", then we use the explicit one. otherwise,
36             # if blank, or zero, like "MultipleHosts()", then we use the builtin on
37 20         31 my $min = delete $args{min};
38 20   33     68 $min = $self->{mindevcount} || $min;
39              
40 20 50       43 warn "Unknown parameters: " . join(", ", sort keys %args) if %args;
41 20 50 33     106 die "Missing parameters" unless $on_devs && $all_devs && $failed && $fid;
      33        
      33        
42              
43             # number of devices we currently live on
44 20         29 my $already_on = @$on_devs;
45              
46             # a silly special case, bail out early.
47 20 50 33     53 return ALL_GOOD if $min == 1 && $already_on == 1;
48              
49             # total disks available which are candidates for having files on them
50 20         49 my $total_disks = scalar grep { $_->dstate->should_have_files } values %$all_devs;
  125         198  
51              
52             # see which and how many unique hosts we're already on.
53 20         33 my %on_dev;
54             my %on_host;
55 20         34 foreach my $dev (@$on_devs) {
56 43         85 $on_host{$dev->hostid} = 1;
57 43         100 $on_dev{$dev->id} = 1;
58             }
59 20         46 my $uniq_hosts_on = scalar keys %on_host;
60 20         31 my $total_uniq_hosts = unique_hosts($all_devs);
61              
62             # if we are on two hosts but 10 devices, you want to weaken the number of
63             # devices you're on until you're on the right number of hosts with the
64             # right number of devices.
65 20 100       47 return TOO_GOOD if $uniq_hosts_on > $min;
66 18 100       39 return TOO_GOOD if $already_on > $min;
67 16 100       36 return ALL_GOOD if $uniq_hosts_on == $min;
68 13 100 100     37 return ALL_GOOD if $uniq_hosts_on >= $total_uniq_hosts && $already_on >= $min;
69              
70             # if we have two copies and that's all the disks there are
71             # anywhere, be happy enough, even if mindevcount is higher. in
72             # that case, when they add more disks later, they'll need to fsck
73             # to make files replicate more.
74             # this is here instead of above in case an over replication error causes
75             # the file to be on all disks (where more than necessary)
76 12 50 66     36 return ALL_GOOD if $already_on >= 2 && $already_on == $total_disks;
77              
78             # if there are more hosts we're not on yet, we want to exclude devices we're already
79             # on from our applicable host search.
80 12         17 my %skip_host; # hostid => 1
81 12 100       23 if ($uniq_hosts_on < $total_uniq_hosts) {
82 11         28 %skip_host = %on_host;
83             }
84              
85             my @all_dests = sort {
86 33         109 $b->percent_free <=> $a->percent_free
87             } grep {
88 12         24 ! $on_dev{$_->devid} &&
89 76 100 66     296 ! $failed->{$_->devid} &&
90             $_->should_get_replicated_files
91             } values %$all_devs;
92              
93 12 50       37 return TEMP_NO_ANSWER unless @all_dests;
94              
95 12         18 my @ideal = grep { ! $skip_host{$_->hostid} } @all_dests;
  35         110  
96 12         26 my @desp = grep { $skip_host{$_->hostid} } @all_dests;
  35         70  
97              
98 12 100 100     44 return TEMP_NO_ANSWER if $already_on >= $min && @ideal == 0;
99              
100 11         29 $self->sort_devices(\@ideal, \@desp, $fid);
101              
102 11         41 return MogileFS::ReplicationRequest->new(
103             ideal => \@ideal,
104             desperate => \@desp,
105             );
106             }
107              
108             sub unique_hosts {
109 20     20 0 33 my $devs = shift;
110 20         25 my %host; # hostid -> 1
111 20         46 foreach my $devid (keys %$devs) {
112 125         237 my $dev = $devs->{$devid};
113 125 100       197 next unless $dev->dstate->should_get_repl_files;
114 122         194 $host{$dev->hostid}++;
115             }
116 20         53 return scalar keys %host;
117             }
118              
119             sub sort_devices {
120 11     11 0 22 my ($self, $ideal, $desp, $fid) = @_;
121              
122             # Do this little dance to only weight-shuffle the top end of empty devices
123             # to save CPU.
124              
125 11         20 @{ $ideal } = weighted_list(map { [$_, 100 * $_->percent_free] }
  20         46  
126 11         18 splice(@{ $ideal }, 0, 20));
  11         24  
127              
128 11         18 @{ $desp } = weighted_list(map { [$_, 100 * $_->percent_free] }
  12         28  
129 11         23 splice(@{ $desp }, 0, 20));
  11         25  
130              
131 11         21 return;
132             }
133              
134             1;
135              
136             # Local Variables:
137             # mode: perl
138             # c-basic-indent: 4
139             # indent-tabs-mode: nil
140             # End:
141              
142             __END__