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   35098 use strict;
  21         49  
  21         869  
3 21     21   128 use base 'MogileFS::ReplicationPolicy';
  21         116  
  21         22578  
4 21     21   807 use MogileFS::Util qw(weighted_list);
  21         55  
  21         1510  
5 21     21   609 use MogileFS::ReplicationRequest qw(ALL_GOOD TOO_GOOD TEMP_NO_ANSWER);
  21         55  
  21         18611  
6              
7             sub new {
8 36     36 0 1762 my ($class, $mindevcount) = @_;
9 36         196 return bless {
10             mindevcount => $mindevcount,
11             }, $class;
12             }
13              
14             sub new_from_policy_args {
15 16     16 0 29 my ($class, $argref) = @_;
16             # Note: "MultipleHosts()" is okay, in which case the 'mindevcount'
17             # on the class is used. (see below)
18 16 50       91 $$argref =~ s/^\s* \( \s* (\d*) \s* \) \s*//x
19             or die "$class failed to parse args: $$argref";
20 16         46 return $class->new($1)
21             }
22              
23 1     1 0 598 sub mindevcount { $_[0]{mindevcount} }
24              
25             sub replicate_to {
26 20     20 0 327 my ($self, %args) = @_;
27              
28 20         38 my $fid = delete $args{fid}; # fid scalar to copy
29 20         41 my $on_devs = delete $args{on_devs}; # arrayref of device objects
30 20         34 my $all_devs = delete $args{all_devs}; # hashref of { devid => MogileFS::Device }
31 20         35 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         34 my $min = delete $args{min};
38 20   33     89 $min = $self->{mindevcount} || $min;
39              
40 20 50       54 warn "Unknown parameters: " . join(", ", sort keys %args) if %args;
41 20 50 33     173 die "Missing parameters" unless $on_devs && $all_devs && $failed && $fid;
      33        
      33        
42              
43             # number of devices we currently live on
44 20         30 my $already_on = @$on_devs;
45              
46             # a silly special case, bail out early.
47 20 50 33     62 return ALL_GOOD if $min == 1 && $already_on == 1;
48              
49             # total disks available which are candidates for having files on them
50 20         53 my $total_disks = scalar grep { $_->dstate->should_have_files } values %$all_devs;
  125         260  
51              
52             # see which and how many unique hosts we're already on.
53 20         27 my %on_dev;
54             my %on_host;
55 20         37 foreach my $dev (@$on_devs) {
56 43         131 $on_host{$dev->hostid} = 1;
57 43         141 $on_dev{$dev->id} = 1;
58             }
59 20         54 my $uniq_hosts_on = scalar keys %on_host;
60 20         46 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       56 return TOO_GOOD if $uniq_hosts_on > $min;
66 18 100       61 return TOO_GOOD if $already_on > $min;
67 16 100       50 return ALL_GOOD if $uniq_hosts_on == $min;
68 13 100 100     49 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     51 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         20 my %skip_host; # hostid => 1
81 12 100       26 if ($uniq_hosts_on < $total_uniq_hosts) {
82 11         34 %skip_host = %on_host;
83             }
84              
85 33         155 my @all_dests = sort {
86 76 100 66     495 $b->percent_free <=> $a->percent_free
87             } grep {
88 12         31 ! $on_dev{$_->devid} &&
89             ! $failed->{$_->devid} &&
90             $_->should_get_replicated_files
91             } values %$all_devs;
92              
93 12 50       56 return TEMP_NO_ANSWER unless @all_dests;
94              
95 12         24 my @ideal = grep { ! $skip_host{$_->hostid} } @all_dests;
  35         107  
96 12         27 my @desp = grep { $skip_host{$_->hostid} } @all_dests;
  35         97  
97              
98 12 100 100     60 return TEMP_NO_ANSWER if $already_on >= $min && @ideal == 0;
99              
100 11         41 $self->sort_devices(\@ideal, \@desp, $fid);
101              
102 11         64 return MogileFS::ReplicationRequest->new(
103             ideal => \@ideal,
104             desperate => \@desp,
105             );
106             }
107              
108             sub unique_hosts {
109 20     20 0 27 my $devs = shift;
110 20         24 my %host; # hostid -> 1
111 20         62 foreach my $devid (keys %$devs) {
112 125         300 my $dev = $devs->{$devid};
113 125 100       251 next unless $dev->dstate->should_get_repl_files;
114 122         314 $host{$dev->hostid}++;
115             }
116 20         82 return scalar keys %host;
117             }
118              
119             sub sort_devices {
120 11     11 0 44 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         25 @{ $ideal } = weighted_list(map { [$_, 100 * $_->percent_free] }
  20         63  
  11         37  
126 11         18 splice(@{ $ideal }, 0, 20));
127              
128 11         20 @{ $desp } = weighted_list(map { [$_, 100 * $_->percent_free] }
  12         45  
  11         32  
129 11         28 splice(@{ $desp }, 0, 20));
130              
131 11         27 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__