#!/usr/bin/perl # # bin/upgrading/migrate-mogilefs.pl # # Move files out of a MogileFS cluster and into the current BlobStore # primary storage. # # Authors: # Mark Smith # # Copyright (c) 2017 by Dreamwidth Studios, LLC. # # This program is free software; you may redistribute it and/or modify it under # the same terms as Perl itself. For a copy of the license, please reference # 'perldoc perlartistic' or 'perldoc perlgpl'. # use v5.10; use strict; BEGIN { require "$ENV{LJHOME}/cgi-bin/ljlib.pl"; } use Carp qw/ croak /; use DBI; use Getopt::Long; use MogileFS::Client; use DW::BlobStore; use constant BLOCK_SIZE => 1_000; my ( $startfid, $endfid, $max_workers, $conf ); GetOptions( 'start-fid=i' => \$startfid, 'end-fid=i' => \$endfid, 'num-workers=i' => \$max_workers, 'mogilefs-config=s' => \$conf, ); $startfid ||= 0; $endfid ||= 1_000_000_000; $max_workers ||= 10; die "Must provide valid --mogilefs-config=FILEPATH argument.\n" unless $conf && -e $conf; my $mogc = get_mogilefs_client(); my $queue = []; my $cur_workers = 0; my $num_workers = 0; my $enqueued = 0; while ( $startfid <= $endfid ) { my $files = get_files( $startfid, $startfid + ( BLOCK_SIZE - 1 ) ); $startfid += BLOCK_SIZE; next unless $files; foreach my $fid ( keys %$files ) { $enqueued++; push @$queue, $files->{$fid}; next if scalar @$queue < BLOCK_SIZE; $0 = sprintf( 'migrate-mogilefs: enqueued %d, workers %d', $enqueued, $num_workers ); if ( $cur_workers >= $max_workers ) { wait; $cur_workers--; } make_worker($queue); $num_workers++; $cur_workers++; $queue = []; } } if ( $queue && @$queue ) { make_worker($queue); $cur_workers++; } while ( $cur_workers > 0 ) { wait; $cur_workers--; } say "All done."; sub make_worker { my $queue = $_[0]; if ( my $pid = fork ) { return; } my $pos = 0; my $llen = scalar @$queue; foreach my $file (@$queue) { $pos++; $0 = sprintf( 'migrate-mogilefs [%d/%d] = %0.2f%%', $pos, $llen, 100 * ( $pos / $llen ) ); # Quick check to make sure this file isn't already in BlobStore if ( DW::BlobStore->exists( $file->{class} => $file->{key} ) ) { say "$file->{fid}: OK EXISTS"; next; } my $data = $mogc->get_file_data( $file->{key} ); unless ($data) { say "$file->{fid}: ERR NODATA"; next; } my $size = length $$data; unless ( $size == $file->{size} ) { say "$file->{fid}: ERR WRONGSIZE $size $file->{size}"; next; } # It's a file and the length is write, let's store it to BlobStore with the # right data... my $rv = DW::BlobStore->store( $file->{class} => $file->{key}, $data ); if ($rv) { say "$file->{fid}: OK STORE"; } else { say "$file->{fid}: ERR STORE"; } } exit; } sub get_files { my ( $start_fid, $end_fid ) = @_; my $dbh = get_mogilefs_dbh(); my $rows = $dbh->selectall_arrayref( q{SELECT f.fid, f.dkey, f.length, d.namespace, c.classname FROM file f, domain d, class c WHERE f.fid >= ? AND f.fid <= ? AND d.dmid = f.dmid AND c.dmid = f.dmid AND c.classid = f.classid }, undef, $start_fid, $end_fid, ); return undef unless $rows && scalar @$rows; my $out = {}; foreach my $row (@$rows) { $out->{ $row->[0] } = { fid => $row->[0], key => $row->[1], size => $row->[2], domain => $row->[3], class => $row->[4], }; } return $out; } sub get_mogilefs_dbh { my %config; open FILE, "<$conf" or die; foreach my $line () { my ( $key, $val ) = ( $1, $2 ) if $line =~ /^db_(\w+)\s*=\s*(.+?)$/; next unless $key && $val; $config{$key} = $val; } close FILE; my $dbh = DBI->connect( $config{dsn}, $config{user}, $config{pass} ) or die "Failed to connect to MogileFS.\n"; return $dbh; } sub get_mogilefs_client { my $mogclient = MogileFS::Client->new( domain => $LJ::MOGILEFS_CONFIG{domain}, root => $LJ::MOGILEFS_CONFIG{root}, hosts => $LJ::MOGILEFS_CONFIG{hosts}, timeout => $LJ::MOGILEFS_CONFIG{timeout}, ) or die "Failed to create MogileFS::Client!\n"; return $mogclient; }