Changeset 89
- Timestamp:
- 08/15/06 18:22:35 (2 years ago)
- Files:
-
- trunk/RebuildQueue/README.txt (modified) (1 diff)
- trunk/RebuildQueue/plugins/RebuildQueue/RebuildQueue.pl (modified) (4 diffs)
- trunk/RebuildQueue/plugins/RebuildQueue/lib/RebuildQueue/Daemon.pm (modified) (9 diffs)
- trunk/RebuildQueue/plugins/RebuildQueue/lib/RebuildQueue/Plugin.pm (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/RebuildQueue/README.txt
r86 r89 17 17 Requirements 18 18 ============ 19 20 Movable Type version 3.3 or later (or Movable Type Enterprise). 19 21 20 22 The 'rsync' command if using remote server synchronization. trunk/RebuildQueue/plugins/RebuildQueue/RebuildQueue.pl
r85 r89 27 27 my $help = 0; 28 28 my %throttle; 29 my $worker = 1; 29 my @worker; 30 my $worker = ''; 30 31 my $sync = 0; 31 32 my $rsync_opt = ''; … … 37 38 "help|?" => \$help, 38 39 "throttle=i" => \%throttle, 39 "worker= i" => \$worker,40 "worker=s" => \$worker, 40 41 "sync" => \$sync, 41 42 "to|target=s" => \@target, … … 63 64 } 64 65 } 66 if ($worker =~ m/,/) { 67 @worker = split(/,/, $worker); 68 } else { 69 @worker = ($worker) if $worker =~ m/^\d+$/; 70 } 65 71 66 72 require RebuildQueue::Daemon; 67 73 if ($sync) { 68 74 RebuildQueue::Daemon->new->sync( 69 worker => $worker, 75 daemonize => $daemonize, 76 'sleep' => $sleep, 77 worker => \@worker, 70 78 target => \@target, 71 79 rsync_opt => $rsync_opt, … … 76 84 'sleep' => $sleep, 77 85 throttle => \%throttle, 78 worker => $worker,86 worker => \@worker, 79 87 ); 80 88 } trunk/RebuildQueue/plugins/RebuildQueue/lib/RebuildQueue/Daemon.pm
r85 r89 39 39 my (%opt) = @_; 40 40 41 my $worker = $opt{worker} || 1; 41 my $nap_time = $opt{'sleep'} || 5; 42 my $daemonize = $opt{daemonize} || 0; 43 my $worker = $opt{worker} || []; 42 44 my $targets = $opt{target} || []; 43 45 my $rsync_opt = $opt{rsync_opt} || '-a'; 44 46 45 unless ($unlock = $mt->_lock("sync-$worker")) { 47 # We need the plugin for getting settings, but not for anything 48 # else... 49 require RebuildQueue::Plugin; 50 my $plugin = RebuildQueue::Plugin->instance; 51 $plugin->disable; 52 53 my $sync_support = $plugin->sync_support; 54 unless ($sync_support) { 55 print "Synchronization is not enabled.\n"; 46 56 exit 1; 47 57 } 48 58 49 local $SIG{INT} = sub { die }; 50 51 print "RebuildQueue sync running...\n"; 52 my $sync_start = [gettimeofday]; 53 54 my $iter = RebuildQueue::File->load_iter({ 55 sync_me => 1, worker => $worker 56 }, { 'sort' => 'priority', direction => 'ascend' }); 57 my @rqf; 58 my @files; 59 while (my $rqf = $iter->()) { 60 my $fi = $rqf->fileinfo; 61 push @files, $fi->file_path if $fi && (-f $fi->file_path); 62 push @rqf, $rqf; 63 } 64 my $synced = 0; 65 if (@files) { 66 $synced = scalar @files; 67 require File::Spec; 68 my $file = File::Spec->catfile($mt->config('TempDir'), "rebuildq-rsync-$$.lst"); 69 open FOUT, ">$file"; 70 print FOUT join("\n", @files) . "\n"; 71 close FOUT; 72 foreach my $target (@$targets) { 73 my $cmd = "rsync $rsync_opt --files-from=\"$file\" / \"$target\""; 74 my $res = system $cmd; 75 my $exit = $? >> 8; 76 if ($exit != 0) { 77 print STDERR "Error during rsync of files in $file...\n"; 78 print STDERR "Command: $cmd\n"; 79 print STDERR $res; 80 exit 1; 81 } 82 } 83 unlink $file; 84 # clear sync flags... 85 $_->remove foreach @rqf; 86 } 87 if ($synced) { 88 print "RebuildQueue sync finished. ($synced files in " . sprintf("%0.02f", tv_interval($sync_start)) . " seconds)\n"; 89 } else { 90 print "No files available to sync.\n"; 91 } 59 my $worker_id = join ',', sort @$worker; 60 $worker_id = 'all' unless $worker_id; 61 unless ($unlock = $mt->_lock("sync-$worker_id")) { 62 exit 1; 63 } 64 65 my $stop = 0; 66 local $SIG{INT} = sub { $stop = 1 }; 67 local $SIG{QUIT} = sub { $stop = 1 }; 68 69 $| = 1; 70 71 print "RebuildQueue sync daemon running...\n" if $daemonize; 72 73 while (!$stop) { 74 my $sync_set = [gettimeofday]; 75 my $iter = RebuildQueue::File->load_iter({ 76 sync_me => 1, (@$worker ? ( worker => $worker ) : ()), 77 }, { 'sort' => 'priority', direction => 'ascend' }); 78 my @rqf; 79 my @files; 80 my @static_fileinfo; 81 while (my $rqf = $iter->()) { 82 my $fi = $rqf->fileinfo; 83 push @files, $fi->file_path if $fi && (-f $fi->file_path); 84 push @rqf, $rqf; 85 unless ($fi->template_id) { 86 # static file 87 push @static_fileinfo, $fi; 88 } 89 } 90 my $synced = 0; 91 if (@files) { 92 $synced = scalar @files; 93 require File::Spec; 94 my $file = File::Spec->catfile($mt->config('TempDir'), "rebuildq-rsync-$$.lst"); 95 open FOUT, ">$file"; 96 print FOUT join("\n", @files) . "\n"; 97 close FOUT; 98 foreach my $target (@$targets) { 99 my $cmd = "rsync $rsync_opt --files-from=\"$file\" / \"$target\""; 100 my $res = system $cmd; 101 my $exit = $? >> 8; 102 if ($exit != 0) { 103 # TBD: notification to administrator 104 # At the very least, log to MT activity log. 105 print STDERR "Error during rsync of files in $file...\n"; 106 print STDERR "Command: $cmd\n"; 107 print STDERR $res; 108 exit 1; 109 } 110 } 111 unlink $file; 112 # clear sync flags... 113 $_->remove foreach @rqf; 114 $_->remove foreach @static_fileinfo; 115 } 116 if ($synced) { 117 print "RebuildQueue sync finished. ($synced files in " . sprintf("%0.02f", tv_interval($sync_set)) . " seconds)\n"; 118 } else { 119 print "No files available to sync.\n" unless $daemonize; 120 } 121 if (!$daemonize) { 122 last; 123 } 124 sleep $nap_time; 125 } 126 print "\nShutting down RebuildQueue...\n" if $daemonize; 92 127 } 93 128 … … 96 131 my (%opt) = @_; 97 132 98 my $nap_time = $opt{'sleep'} || 5; 99 my $daemonize = $opt{daemonize}; 100 my $throttles = $opt{throttle} || {}; 101 my $worker = $opt{worker} || 1; 102 103 unless ($unlock = $mt->_lock("daemon-$worker")) { 133 my $nap_time = $opt{'sleep'} || 5; 134 my $daemonize = $opt{daemonize} || 0; 135 my $throttles = $opt{throttle} || {}; 136 my $worker = $opt{worker} || []; 137 138 my $worker_id = join ',', sort @$worker; 139 $worker_id = 'all' unless $worker_id; 140 unless ($unlock = $mt->_lock("daemon-$worker_id")) { 104 141 # an existing daemon is running with this worker id. don't 105 142 # allow a second to run. … … 107 144 } 108 145 146 # We need the plugin for getting settings, but not for anything 147 # else... 148 require RebuildQueue::Plugin; 149 my $plugin = RebuildQueue::Plugin->instance; 150 $plugin->disable; 151 152 my $sync_support = $plugin->sync_support; 153 109 154 my $stop = 0; 110 local $SIG{INT} = sub { $stop = 1 };155 local $SIG{INT} = sub { $stop = 1 }; 111 156 local $SIG{QUIT} = sub { $stop = 1 }; 112 157 … … 115 160 $mt->cleanup_rebuild_queue; 116 161 117 print "RebuildQueue daemon running...\n" if $daemonize;162 print "RebuildQueue build daemon running...\n" if $daemonize; 118 163 119 164 my $pub = $mt->publisher; … … 121 166 while (!$stop) { 122 167 my $iter = RebuildQueue::File->load_iter({ 123 rebuild_me => 1, worker => $worker,168 rebuild_me => 1, (@$worker ? ( worker => $worker ) : ()), 124 169 build_time => [undef, time], 125 170 }, { … … 168 213 if ($mtime != $mtime2) { 169 214 # file was updated; mark for syncing 170 $rqf->sync_me(1); 171 $rqf->rebuild_me(0); 172 $rqf->save; 215 if ($sync_support) { 216 $rqf->sync_me(1); 217 $rqf->rebuild_me(0); 218 $rqf->build_time(time); 219 $rqf->save; 220 } else { 221 $rqf->remove; 222 } 173 223 } else { 174 224 # touch file to help throttle mechanism … … 195 245 } 196 246 247 # Method to remove any RebuildQueue::File objects that 248 # are no longer requiring rebuild or sync operations. 197 249 sub cleanup_rebuild_queue { 198 250 my $mt = shift; … … 204 256 } 205 257 258 # Summarizes the current queue item for display in the log. 206 259 sub _summary { 207 260 my $fi = shift; … … 214 267 $file =~ s/^\Q$root\E//; 215 268 $file =~ s/^\///; 269 # TBD: Handle this situation more gracefully. 216 270 die "failed to load template " . $fi->template_id unless $tmpl; 217 271 # Output summary trunk/RebuildQueue/plugins/RebuildQueue/lib/RebuildQueue/Plugin.pm
r87 r89 15 15 use base 'MT::Plugin'; 16 16 17 our $VERSION = '1.0 ';17 our $VERSION = '1.01'; 18 18 our $SCHEMA_VERSION = '1.01'; 19 20 my $plugin; 19 our $ENABLED = 1; 20 21 my $plugin = new RebuildQueue::Plugin({ 22 name => "Rebuild Queue", 23 description => "Queues rebuild operations for offline building.", 24 object_classes => ['RebuildQueue::File'], 25 version => $VERSION, 26 schema_version => $SCHEMA_VERSION, 27 author_name => "Six Apart, Ltd.", 28 author_link => "http://www.sixapart.com/", 29 plugin_link => "http://code.sixapart.com/", 30 icon => "rebuildq.gif", 31 system_config_template => \&system_config_template, 32 blog_config_template => \&blog_config_template, 33 settings => new MT::PluginSettings([ 34 ['workers', { Default => 1, Scope => 'system' }], 35 ['rebuildq_sync', { Default => 0, Scope => 'system' }], 36 ['rebuildq_mode', { Default => 0, Scope => 'blog' }], 37 ['worker', { Default => undef, Scope => 'blog' }], 38 ]), 39 callbacks => { 40 'BuildFileFilter' => { 41 priority => 10, 42 code => \&build_file_filter, 43 }, 44 'BuildFile' => { 45 priority => 10, 46 code => \&build_file, 47 }, 48 'MT::FileInfo::post_remove' => { 49 priority => 1, 50 code => \&fileinfo_post_remove, 51 }, 52 'MT::FileInfo::post_remove_all' => { 53 priority => 1, 54 code => \&fileinfo_post_remove_all, 55 }, 56 'CMSUploadFile' => { 57 priority => 1, 58 code => \&cms_upload_file, 59 }, 60 }, 61 }); 21 62 22 63 if (!MT->instance->isa('RebuildQueue::Daemon')) { 23 $plugin = new RebuildQueue::Plugin({24 name => "Rebuild Queue",25 description => "Queues rebuild operations for offline building.",26 object_classes => ['RebuildQueue::File'],27 version => $VERSION,28 schema_version => $SCHEMA_VERSION,29 author_name => "Six Apart, Ltd.",30 author_link => "http://www.sixapart.com/",31 plugin_link => "http://code.sixapart.com/",32 icon => "rebuildq.gif",33 system_config_template => \&system_config_template,34 blog_config_template => \&blog_config_template,35 settings => new MT::PluginSettings([36 ['workers', { Default => 1, Scope => 'system' }],37 ['rebuildq_mode', { Default => 0, Scope => 'blog' }],38 ['worker', { Default => undef, Scope => 'blog' }],39 ]),40 callbacks => {41 'BuildFileFilter' => {42 priority => 10,43 code => \&build_file_filter,44 },45 }46 });47 64 MT->add_plugin($plugin); 48 65 } 49 66 67 sub instance { 68 $plugin; 69 } 70 71 sub enable { 72 $ENABLED = 1; 73 } 74 75 sub disable { 76 $ENABLED = 0; 77 } 78 79 sub enabled { 80 $ENABLED; 81 } 82 83 sub init_request { 84 my $plugin = shift; 85 my ($app) = @_; 86 87 $plugin->enable; 88 if ($app->isa('MT::App::CMS')) { 89 my $mode = $app->mode; 90 $plugin->disable if $mode =~ m/^rebuild/; 91 } 92 93 $plugin->SUPER::init_request(@_); 94 } 95 96 # The RebuildQueue can synchronize built pages, but in order to handle 97 # files uploaded through the interface, we need to manage our own 98 # FileInfo records. Upon synchronization of these, they can be removed. 99 sub cms_upload_file { 100 my ($cb, %args) = @_; 101 102 my $url = $args{Url}; 103 my $file = $args{File}; 104 return unless -f $file; 105 106 return unless $plugin->sync_support; 107 108 my $blog = $args{Blog}; 109 my $blog_id = $blog->id; 110 return unless $plugin->blog_enabled($blog_id); 111 112 require MT::FileInfo; 113 my $base_url = $url; 114 $base_url =~ s!^https?://[^/]+!!; 115 my $fi = MT::FileInfo->load({ blog_id => $blog_id, url => $base_url }); 116 if (!$fi) { 117 $fi = new MT::FileInfo; 118 $fi->blog_id($blog_id); 119 $fi->url($base_url); 120 $fi->file_path($file); 121 } else { 122 $fi->file_path($file); 123 } 124 $fi->save; 125 126 require RebuildQueue::File; 127 my $rqf = RebuildQueue::File->load($fi->id); 128 if (!$rqf) { 129 $rqf = new RebuildQueue::File; 130 $rqf->id($fi->id); 131 } 132 $rqf->worker($plugin->blog_worker($blog_id)); 133 $rqf->sync_me(1); 134 $rqf->save; 135 } 136 137 sub fileinfo_post_remove { 138 my ($cb, $fi) = @_; 139 require RebuildQueue::File; 140 if (my $rqf = RebuildQueue::File->load($fi->id)) { 141 $rqf->remove; 142 } 143 } 144 145 sub fileinfo_post_remove_all { 146 my ($cb, $fi) = @_; 147 require RebuildQueue::File; 148 RebuildQueue::File->remove_all; 149 } 150 50 151 sub system_config_template { 152 my $workers = $plugin->get_config_value('workers', 'system'); 153 154 my $max = 10; 155 $max = $workers + 10 if $workers >= $max; 156 157 my $worker_html = ''; 158 for (my $i = 1; $i <= $max; $i++) { 159 $worker_html .= qq{ 160 <option value="$i" <TMPL_IF NAME=WORKERS_$i>selected="selected"</TMPL_IF>>$i</option>}; 161 } 162 51 163 return <<HTML; 52 164 <div class="setting"> 53 165 <div class="label"><MT_TRANS phrase="Number of Workers:"></div> 54 166 <div class="field"><ul><li><select name="workers"> 55 <option value="1" <TMPL_IF NAME=WORKERS_1>selected="selected"</TMPL_IF>>1</option> 56 <option value="2" <TMPL_IF NAME=WORKERS_2>selected="selected"</TMPL_IF>>2</option> 57 <option value="3" <TMPL_IF NAME=WORKERS_3>selected="selected"</TMPL_IF>>3</option> 58 <option value="4" <TMPL_IF NAME=WORKERS_4>selected="selected"</TMPL_IF>>4</option> 59 <option value="5" <TMPL_IF NAME=WORKERS_5>selected="selected"</TMPL_IF>>5</option> 60 <option value="6" <TMPL_IF NAME=WORKERS_6>selected="selected"</TMPL_IF>>6</option> 61 <option value="7" <TMPL_IF NAME=WORKERS_7>selected="selected"</TMPL_IF>>7</option> 62 <option value="8" <TMPL_IF NAME=WORKERS_8>selected="selected"</TMPL_IF>>8</option> 63 <option value="9" <TMPL_IF NAME=WORKERS_9>selected="selected"</TMPL_IF>>9</option> 64 <option value="10" <TMPL_IF NAME=WORKERS_10>selected="selected"</TMPL_IF>>10</option> 167 $worker_html 65 168 </select></li></ul> 169 </div> 170 </div> 171 172 <div class="setting"> 173 <div class="label"><MT_TRANS phrase="Synchronize:"></div> 174 <div class="field"><ul><input name="rebuildq_sync" type="checkbox" <TMPL_IF NAME=REBUILDQ_SYNC_1>checked="checked"</TMPL_IF> value="1" /> 175 <MT_TRANS phrase="Check to support synchronization of queued items with other servers."> 176 </li></ul> 66 177 </div> 67 178 </div> … … 121 232 } 122 233 234 sub sync_support { 235 $plugin->get_config_value('rebuildq_sync') ? 1 : 0; 236 } 237 238 sub blog_enabled { 239 my $plugin = shift; 240 my ($blog_id) = @_; 241 $plugin->get_config_value('rebuildq_mode', 'blog:'.$blog_id) ? 1 : 0; 242 } 243 244 sub blog_worker { 245 my $plugin = shift; 246 my ($blog_id) = @_; 247 my $workers = $plugin->get_config_value('workers') || 1; 248 my $worker = $plugin->get_config_value('workers', 'blog:'.$blog_id); 249 $worker = 1 if (defined $worker) && ($worker > $workers); 250 defined $worker ? $worker : int(rand($workers)) + 1; 251 } 252 253 # Adds an element to the rebuild queue when the plugin is enabled. 123 254 sub build_file_filter { 124 255 my ($cb, %args) = @_; 256 return 1 unless $ENABLED; 125 257 126 258 my $fi = $args{FileInfo}; 127 259 return 1 unless $fi; 128 260 129 my $mode = $plugin->get_config_value('rebuildq_mode', 'blog:'.$fi->blog_id); 130 return 1 unless $mode; 261 return 1 unless $plugin->blog_enabled($fi->blog_id); 131 262 132 263 require RebuildQueue::File; … … 138 269 $rqf->id($fi->id); 139 270 } 140 my $workers = $plugin->get_config_value('workers') || 1;141 my $worker = $plugin->get_config_value('workers', 'blog:'.$fi->blog_id);142 $worker = 1 if (defined $worker) && ($worker > $workers);143 271 144 272 $rqf->rebuild_me(1); 145 273 $rqf->sync_me(0); 146 $rqf->worker(defined $worker ? $worker : int(rand($workers))+1); 274 $rqf->build_time(0); 275 $rqf->worker($plugin->blog_worker($fi->blog_id)); 147 276 148 277 my $at = $fi->archive_type || ''; … … 178 307 } 179 308 309 # Something was built while the queue itself was disabled; make sure 310 # the item is marked for syncing. 311 sub build_file { 312 my ($cb, %args) = @_; 313 314 return unless $plugin->sync_support; 315 316 my $fi = $args{FileInfo}; 317 my $blog = $args{Blog}; 318 if (!$fi) { 319 # FileInfo may still be there due to a bug in MT::WeblogPublisher 320 # passing an undef FileInfo element... 321 require MT::FileInfo; 322 $fi = MT::FileInfo->load({ blog_id => $blog->id, file_path => $args{File} }); 323 } 324 return unless $fi; 325 return unless $plugin->blog_enabled($blog->id); 326 327 require RebuildQueue::File; 328 my $rqf = RebuildQueue::File->load($fi->id); 329 if (!$rqf) { 330 $rqf = new RebuildQueue::File; 331 $rqf->id($fi->id); 332 $rqf->build_time(time); 333 $rqf->worker($plugin->blog_worker($blog->id)); 334 $rqf->sync_me(1); 335 $rqf->save; 336 } else { 337 if (!$rqf->rebuild_me) { 338 if (!$rqf->sync_me) { 339 $rqf->sync_me(1); 340 $rqf->build_time(time); 341 $rqf->worker($plugin->blog_worker($blog->id)); 342 $rqf->save; 343 } 344 } 345 } 346 } 347 180 348 1; 349 350 __END__ 351 352 =head1 NAME 353 354 RebuildQueue::Plugin - Movable Type plugin for distributed rebuilding. 355 356 =head1 SYNOPSIS 357 358 =head1 TWEAKING 359 360 You may wish to further tune the rebuild queue items. To do so, you 361 should hook into the RebuildQueue::File::pre_save MT callback. This 362 gives you a chance to modify the priority and build_time properties 363 of a RebuildQueue::File record before it is saved. Here's an example: 364 365 Within a separate plugin, register for the callback: 366 367 MT->add_callback('RebuildQueue::File', 5, undef, \&rqf_fix); 368 369 Then, define your callback routine: 370 371 sub rqf_fix { 372 my ($cb, $obj, $orig) = @_; 373 my $fi = $obj->fileinfo; 374 375 # only tweak when being saved to the rebuild queue 376 return unless $obj->rebuild_me; 377 378 # Sidebar elements can be built last. 379 if ($fi->file_path =~ m!/sidebar/!) { 380 $obj->priority(1000); # really low priority 381 } 382 383 # Forces pages built for nagios to run on a particular 384 # worker that also has access to nagios local data. 385 if ($fi->file_path =~ m!/nagios/!) { 386 $obj->worker(3); 387 } 388 } 389 390 As you can see, it's possible to really fine-tune your rebuild 391 queue. 392 393 =head1 AUTHORS 394 395 Brad Choate and Jay Allen 396 397 =head1 COPYRIGHT 398 399 Copyright (c) 2006, Brad Choate and Jay Allen. This is free software. 400 It may be distributed and modified under the same terms as Perl itself. 401 402 =head1 AVAILABILITY 403 404 http://code.sixapart.com/ 405 406 =cut
