1#!/usr/bin/perl 2package MogileFS::Client; 3 4=head1 NAME 5 6MogileFS::Client - Client library for the MogileFS distributed file system. 7 8=head1 SYNOPSIS 9 10 use MogileFS::Client; 11 12 # create client object w/ server-configured namespace 13 # and IPs of trackers 14 $mogc = MogileFS::Client->new(domain => "foo.com::my_namespace", 15 hosts => ['10.0.0.2:7001', '10.0.0.3:7001']); 16 17 # create a file 18 # mogile is a flat namespace. no paths. 19 $key = "image_of_userid:$userid"; 20 # must be configured on server 21 $class = "user_images"; 22 $fh = $mogc->new_file($key, $class); 23 24 print $fh $data; 25 26 unless ($fh->close) { 27 die "Error writing file: " . $mogc->errcode . ": " . $mogc->errstr; 28 } 29 30 # Find the URLs that the file was replicated to. 31 # May change over time. 32 @urls = $mogc->get_paths($key); 33 34 # no longer want it? 35 $mogc->delete($key); 36 37=head1 DESCRIPTION 38 39This module is a client library for the MogileFS distributed file system. The class method 'new' creates a client object against a 40particular mogilefs tracker and domain. This object may then be used to store and retrieve content easily from MogileFS. 41 42=cut 43 44use strict; 45use Carp; 46use IO::WrapTie; 47use LWP::UserAgent; 48use fields ( 49 'domain', # scalar: the MogileFS domain (namespace). 50 'backend', # MogileFS::Backend object 51 'readonly', # bool: if set, client won't permit write actions/etc. just reads. 52 'hooks', # hash: hookname -> coderef 53 ); 54use Time::HiRes (); 55use MogileFS::Backend; 56use MogileFS::NewHTTPFile; 57use MogileFS::ClientHTTPFile; 58 59our $VERSION = '1.17'; 60 61our $AUTOLOAD; 62 63=head1 METHODS 64 65=head2 new 66 67 $client = MogileFS::Client->new( %OPTIONS ); 68 69Creates a new MogileFS::Client object. 70 71Returns MogileFS::Client object on success, or dies on failure. 72 73OPTIONS: 74 75=over 76 77=item hosts 78 79Arrayref of 'host:port' strings to connect to as backend trackers in this client. 80 81=item domain 82 83String representing the mogile domain which this MogileFS client is associated with. (All create/delete/fetch operations 84will be performed against this mogile domain). See the mogadm shell command and its 'domain' category of operations for 85information on manipulating the list of possible domains on a MogileFS system. 86 87=back 88 89=cut 90 91sub new { 92 my MogileFS::Client $self = shift; 93 $self = fields::new($self) unless ref $self; 94 95 return $self->_init(@_); 96} 97 98=head2 reload 99 100 $mogc->reload( %OPTIONS ) 101 102Re-init the object, like you'd just reconstructed it with 'new', but change it in-place instead. Useful if you have a system which reloads a config file, and you want to update a singleton $mogc handle's config value. 103 104=cut 105 106sub reload { 107 my MogileFS::Client $self = shift; 108 return undef unless $self; 109 110 return $self->_init(@_); 111} 112 113sub _init { 114 my MogileFS::Client $self = shift; 115 116 my %args = @_; 117 118 # FIXME: add actual validation 119 { 120 # by default, set readonly off 121 $self->{readonly} = $args{readonly} ? 1 : 0; 122 123 # get domain (required) 124 $self->{domain} = $args{domain} or 125 _fail("constructor requires parameter 'domain'"); 126 127 # create a new backend object if there's not one already, 128 # otherwise call a reload on the existing one 129 if ($self->{backend}) { 130 $self->{backend}->reload( hosts => $args{hosts} ); 131 } else { 132 $self->{backend} = MogileFS::Backend->new( hosts => $args{hosts}, 133 timeout => $args{timeout}, 134 ); 135 } 136 _fail("cannot instantiate MogileFS::Backend") unless $self->{backend}; 137 } 138 139 _debug("MogileFS object: [$self]", $self); 140 141 return $self; 142} 143 144=head2 last_tracker 145 146Returns a scalar of form "ip:port", representing the last mogilefsd 147'tracker' server which was talked to. 148 149=cut 150 151sub last_tracker { 152 my MogileFS::Client $self = shift; 153 return $self->{backend}->last_tracker; 154} 155 156=head2 errstr 157 158Returns string representation of the last error that occurred. It 159includes the error code (same as method 'errcode') and a space before 160the optional English error message. 161 162This isn't necessarily guaranteed to reset after a successful 163operation. Only call it after another operation returns an error. 164 165 166=cut 167 168sub errstr { 169 my MogileFS::Client $self = shift; 170 return $self->{backend}->errstr; 171} 172 173=head2 errcode 174 175Returns an error code. Not a number, but a string identifier 176(e.g. "no_domain") which is stable for use in error handling logic. 177 178This isn't necessarily guaranteed to reset after a successful 179operation. Only call it after another operation returns an error. 180 181=cut 182 183sub errcode { 184 my MogileFS::Client $self = shift; 185 return $self->{backend}->errcode; 186} 187 188=head2 force_disconnect 189 190Forces the client to disconnect from the tracker, causing it to reconnect 191when the next request is made. It will reconnect to a different tracker if 192possible. A paranoid application may wish to do to this before retrying a 193failed command, on the off chance that another tracker may be working better. 194 195=cut 196 197sub force_disconnect { 198 my MogileFS::Client $self = shift; 199 return $self->{backend}->force_disconnect(); 200} 201 202=head2 readonly 203 204 $is_readonly = $mogc->readonly 205 $mogc->readonly(1) 206 207Getter/setter to mark this client object as read-only. Purely a local 208operation/restriction, doesn't do a network operation to the mogilefsd 209server. 210 211=cut 212 213sub readonly { 214 my MogileFS::Client $self = shift; 215 return $self->{readonly} = $_[0] ? 1 : 0 if @_; 216 return $self->{readonly}; 217} 218 219=head2 new_file 220 221 $mogc->new_file($key) 222 $mogc->new_file($key, $class) 223 $mogc->new_file($key, $class, $content_length) 224 $mogc->new_file($key, $class, $content_length , $opts_hashref) 225 226Start creating a new filehandle with the given key, and option given 227class and options. 228 229Returns a filehandle you should then print to, and later close to 230complete the operation. B<NOTE:> check the return value from close! 231If your close didn't succeed, the file didn't get saved! 232 233$opts_hashref can contain keys: 234 235=over 236 237=item fid 238 239Explicitly specify the fid number to use, rather than it being automatically allocated. 240 241=item create_open_args 242 243Hashref of extra key/value pairs to send to mogilefsd in create_open phase. 244 245=item create_close_args 246 247Hashref of extra key/value pairs to send to mogilefsd in create_close phase. 248 249=item largefile 250 251Use MogileFS::ClientHTTPFile which will not load the entire file into memory 252like the default MogileFS::NewHTTPFile but requires that the storage node 253HTTP servers support the Content-Range header in PUT requests and is a little 254slower. 255 256=back 257 258=cut 259 260# returns MogileFS::NewHTTPFile object, or undef if no device 261# available for writing 262# ARGS: ( key, class, bytes?, opts? ) 263# where bytes is optional and the length of the file and opts is also optional 264# and is a hashref of options. supported options: fid = unique file id to use 265# instead of just picking one in the database. 266sub new_file { 267 my MogileFS::Client $self = shift; 268 return undef if $self->{readonly}; 269 270 my ($key, $class, $bytes, $opts) = @_; 271 $bytes += 0; 272 $opts ||= {}; 273 274 # Extra args to be passed along with the create_open and create_close commands. 275 # Any internally generated args of the same name will overwrite supplied ones in 276 # these hashes. 277 my $create_open_args = $opts->{create_open_args} || {}; 278 my $create_close_args = $opts->{create_close_args} || {}; 279 280 $self->run_hook('new_file_start', $self, $key, $class, $opts); 281 282 my $res = $self->{backend}->do_request 283 ("create_open", { 284 %$create_open_args, 285 domain => $self->{domain}, 286 class => $class, 287 key => $key, 288 fid => $opts->{fid} || 0, # fid should be specified, or pass 0 meaning to auto-generate one 289 multi_dest => 1, 290 }) or return undef; 291 292 my $dests = []; # [ [devid,path], [devid,path], ... ] 293 294 # determine old vs. new format to populate destinations 295 unless (exists $res->{dev_count}) { 296 push @$dests, [ $res->{devid}, $res->{path} ]; 297 } else { 298 for my $i (1..$res->{dev_count}) { 299 push @$dests, [ $res->{"devid_$i"}, $res->{"path_$i"} ]; 300 } 301 } 302 303 my $main_dest = shift @$dests; 304 my ($main_devid, $main_path) = ($main_dest->[0], $main_dest->[1]); 305 306 # create a MogileFS::NewHTTPFile object, based off of IO::File 307 unless ($main_path =~ m!^http://!) { 308 Carp::croak("This version of MogileFS::Client no longer supports non-http storage URLs.\n"); 309 } 310 311 $self->run_hook('new_file_end', $self, $key, $class, $opts); 312 313 return IO::WrapTie::wraptie( ( $opts->{largefile} 314 ? 'MogileFS::ClientHTTPFile' 315 : 'MogileFS::NewHTTPFile' ), 316 mg => $self, 317 fid => $res->{fid}, 318 path => $main_path, 319 devid => $main_devid, 320 backup_dests => $dests, 321 class => $class, 322 key => $key, 323 content_length => $bytes+0, 324 create_close_args => $create_close_args, 325 overwrite => 1, 326 ); 327} 328 329=head2 edit_file 330 331 $mogc->edit_file($key, $opts_hashref) 332 333Edit the file with the the given key. 334 335 336B<NOTE:> edit_file is currently EXPERIMENTAL and not recommended for 337production use. MogileFS is primarily designed for storing files 338for later retrieval, rather than editing. Use of this function may lead to 339poor performance and, until it has been proven mature, should be 340considered to also potentially cause data loss. 341 342B<NOTE:> use of this function requires support for the DAV 'MOVE' 343verb and partial PUT (i.e. Content-Range in PUT) on the back-end 344storage servers (e.g. apache with mod_dav). 345 346Returns a seekable filehandle you can read/write to. Calling this 347function may invalidate some or all URLs you currently have for this 348key, so you should call ->get_paths again afterwards if you need 349them. 350 351On close of the filehandle, the new file contents will replace the 352previous contents (and again invalidate any existing URLs). 353 354By default, the file contents are preserved on open, but you may 355specify the overwrite option to zero the file first. The seek position 356is at the beginning of the file, but you may seek to the end to append. 357 358$opts_hashref can contain keys: 359 360=over 361 362=item overwrite 363 364The edit will overwrite the file, equivalent to opening with '>'. 365Default: false. 366 367=back 368 369=cut 370 371sub edit_file { 372 my MogileFS::Client $self = shift; 373 return undef if $self->{readonly}; 374 375 my($key, $opts) = @_; 376 377 my $res = $self->{backend}->do_request 378 ("edit_file", { 379 domain => $self->{domain}, 380 key => $key, 381 }) or return undef; 382 383 my $moveReq = HTTP::Request->new('MOVE', $res->{oldpath}); 384 $moveReq->header(Destination => $res->{newpath}); 385 my $ua = LWP::UserAgent->new; 386 my $resp = $ua->request($moveReq); 387 unless ($resp->is_success) { 388 warn "Failed to MOVE $res->{oldpath} to $res->{newpath}"; 389 return undef; 390 } 391 392 return IO::WrapTie::wraptie('MogileFS::ClientHTTPFile', 393 mg => $self, 394 fid => $res->{fid}, 395 path => $res->{newpath}, 396 devid => $res->{devid}, 397 class => $res->{class}, 398 key => $key, 399 overwrite => $opts->{overwrite}, 400 ); 401} 402 403=head2 read_file 404 405 $mogc->read_file($key) 406 407Read the file with the the given key. 408 409Returns a seekable filehandle you can read() from. Note that you cannot 410read line by line using <$fh> notation. 411 412Takes the same options as get_paths (which is called internally to get 413the URIs to read from). 414 415=cut 416 417sub read_file { 418 my MogileFS::Client $self = shift; 419 420 my @paths = $self->get_paths(@_); 421 422 my $path = shift @paths; 423 424 return if !$path; 425 426 my @backup_dests = map { [ undef, $_ ] } @paths; 427 428 return IO::WrapTie::wraptie('MogileFS::ClientHTTPFile', 429 path => $path, 430 backup_dests => \@backup_dests, 431 readonly => 1, 432 ); 433} 434 435=head2 store_file 436 437 $mogc->store_file($key, $class, $fh_or_filename[, $opts_hashref]) 438 439Wrapper around new_file, print, and close. 440 441Given a key, class, and a filehandle or filename, stores the file 442contents in MogileFS. Returns the number of bytes stored on success, 443undef on failure. 444 445$opts_hashref can contain keys for new_file, and also the following: 446 447=over 448 449=item chunk_size 450 451Number of bytes to read and write and write at once out of the larger file. 452Defaults to 8192 bytes. Increasing this can increase performance at the cost 453of more memory used while uploading the file. 454Note that this mostly helps when using largefile => 1 455 456=back 457 458=cut 459 460sub store_file { 461 my MogileFS::Client $self = shift; 462 return undef if $self->{readonly}; 463 464 my($key, $class, $file, $opts) = @_; 465 $self->run_hook('store_file_start', $self, $key, $class, $opts); 466 467 my $chunk_size = $opts->{chunk_size} || 8192; 468 my $fh = $self->new_file($key, $class, undef, $opts) or return; 469 my $fh_from; 470 if (ref($file)) { 471 $fh_from = $file; 472 } else { 473 open $fh_from, $file or return; 474 } 475 my $bytes; 476 while (my $len = read $fh_from, my($chunk), $chunk_size) { 477 $fh->print($chunk); 478 $bytes += $len; 479 } 480 481 $self->run_hook('store_file_end', $self, $key, $class, $opts); 482 483 close $fh_from unless ref $file; 484 $fh->close or return; 485 $bytes; 486} 487 488=head2 store_content 489 490 $mogc->store_content($key, $class, $content[, $opts]); 491 492Wrapper around new_file, print, and close. Given a key, class, and 493file contents (scalar or scalarref), stores the file contents in 494MogileFS. Returns the number of bytes stored on success, undef on 495failure. 496 497=cut 498 499sub store_content { 500 my MogileFS::Client $self = shift; 501 return undef if $self->{readonly}; 502 503 my($key, $class, $content, $opts) = @_; 504 505 $self->run_hook('store_content_start', $self, $key, $class, $opts); 506 507 my $fh = $self->new_file($key, $class, undef, $opts) or return; 508 $content = ref($content) eq 'SCALAR' ? $$content : $content; 509 $fh->print($content); 510 511 $self->run_hook('store_content_end', $self, $key, $class, $opts); 512 513 $fh->close or return; 514 length($content); 515} 516 517=head2 get_paths 518 519 @paths = $mogc->get_paths($key) 520 @paths = $mogc->get_paths($key, $no_verify_bool); # old way 521 @paths = $mogc->get_paths($key, { noverify => $bool }); # new way 522 523Given a key, returns an array of all the locations (HTTP URLs) that 524the file has been replicated to. 525 526=over 527 528=item noverify 529 530If the "no verify" option is set, the mogilefsd tracker doesn't verify 531that the first item returned in the list is up/alive. Skipping that 532check is faster, so use "noverify" if your application can do it 533faster/smarter. For instance, when giving L<Perlbal> a list of URLs 534to reproxy to, Perlbal can intelligently find one that's alive, so use 535noverify and get out of mod_perl or whatever as soon as possible. 536 537=item zone 538 539If the zone option is set to 'alt', the mogilefsd tracker will use the 540alternative IP for each host if available, while constructing the paths. 541 542=item pathcount 543 544If the pathcount option is set to a positive integer greater than 2, the 545mogilefsd tracker will attempt to return that many different paths (if 546available) to the same file. If not present or out of range, this value 547defaults to 2. 548 549=back 550 551=cut 552 553# old style calling: 554# get_paths(key, noverify) 555# new style calling: 556# get_paths(key, { noverify => 0/1, zone => "alt", pathcount => 2..N }); 557# but with both, second parameter is optional 558# 559# returns list of URLs that key can be found at, or the empty 560# list on either error or no paths 561sub get_paths { 562 my MogileFS::Client $self = shift; 563 my ($key, $opts) = @_; 564 565 # handle parameters, if any 566 my ($noverify, $zone); 567 unless (ref $opts) { 568 $opts = { noverify => $opts }; 569 } 570 my %extra_args; 571 572 $noverify = 1 if $opts->{noverify}; 573 $zone = $opts->{zone}; 574 575 if (my $pathcount = delete $opts->{pathcount}) { 576 $extra_args{pathcount} = $pathcount; 577 } 578 579 $self->run_hook('get_paths_start', $self, $key, $opts); 580 581 my $res = $self->{backend}->do_request 582 ("get_paths", { 583 domain => $self->{domain}, 584 key => $key, 585 noverify => $noverify ? 1 : 0, 586 zone => $zone, 587 %extra_args, 588 }) or return (); 589 590 my @paths = map { $res->{"path$_"} } (1..$res->{paths}); 591 592 $self->run_hook('get_paths_end', $self, $key, $opts); 593 594 return @paths; 595} 596 597=head2 get_file_data 598 599 $dataref = $mogc->get_file_data($key) 600 601Wrapper around get_paths & LWP, which returns scalarref of file 602contents in a scalarref. 603 604Don't use for large data, as it all comes back to you in one string. 605 606=cut 607 608# given a key, returns a scalar reference pointing at a string containing 609# the contents of the file. takes one parameter; a scalar key to get the 610# data for the file. 611sub get_file_data { 612 # given a key, load some paths and get data 613 my MogileFS::Client $self = $_[0]; 614 my ($key, $timeout) = ($_[1], $_[2]); 615 616 my @paths = $self->get_paths($key, 1); 617 return undef unless @paths; 618 619 # iterate over each 620 foreach my $path (@paths) { 621 next unless defined $path; 622 if ($path =~ m!^http://!) { 623 # try via HTTP 624 my $ua = new LWP::UserAgent; 625 $ua->timeout($timeout || 10); 626 627 my $res = $ua->get($path); 628 if ($res->is_success) { 629 my $contents = $res->content; 630 return \$contents; 631 } 632 633 } else { 634 # open the file from disk and just grab it all 635 open FILE, "<$path" or next; 636 my $contents; 637 { local $/ = undef; $contents = <FILE>; } 638 close FILE; 639 return \$contents if $contents; 640 } 641 } 642 return undef; 643} 644 645=head2 delete 646 647 $mogc->delete($key); 648 649Delete a key from MogileFS. 650 651=cut 652 653# this method returns undef only on a fatal error such as inability to actually 654# delete a resource and inability to contact the server. attempting to delete 655# something that doesn't exist counts as success, as it doesn't exist. 656sub delete { 657 my MogileFS::Client $self = shift; 658 return undef if $self->{readonly}; 659 660 my $key = shift; 661 662 my $rv = $self->{backend}->do_request 663 ("delete", { 664 domain => $self->{domain}, 665 key => $key, 666 }); 667 668 # if it's unknown_key, not an error 669 return undef unless defined $rv || 670 $self->{backend}->{lasterr} eq 'unknown_key'; 671 672 return 1; 673} 674 675=head2 rename 676 677 $mogc->rename($oldkey, $newkey); 678 679Rename file (key) in MogileFS from oldkey to newkey. Returns true on 680success, failure otherwise. 681 682=cut 683 684# this method renames a file. it returns an undef on error (only a fatal error 685# is considered as undef; "file didn't exist" isn't an error). 686sub rename { 687 my MogileFS::Client $self = shift; 688 return undef if $self->{readonly}; 689 690 my ($fkey, $tkey) = @_; 691 692 my $rv = $self->{backend}->do_request 693 ("rename", { 694 domain => $self->{domain}, 695 from_key => $fkey, 696 to_key => $tkey, 697 }); 698 699 # if it's unknown_key, not an error 700 return undef unless defined $rv || 701 $self->{backend}->{lasterr} eq 'unknown_key'; 702 703 return 1; 704} 705 706=head2 file_debug 707 708 my $info_gob = $mogc->file_debug(fid => $fid); 709 ... or ... 710 my $info_gob = $mogc->file_debug(key => $key); 711 712Thoroughly search for any database notes about a particular fid. Searchable by 713raw fidid, or by domain and key. B<Use sparingly>. Command hits the master 714database numerous times, and if you're using it in production something is 715likely very wrong. 716 717To be used with troubleshooting broken/odd files and errors from mogilefsd. 718 719=cut 720 721sub file_debug { 722 my MogileFS::Client $self = shift; 723 my %opts = @_; 724 $opts{domain} = $self->{domain} unless exists $opts{domain}; 725 726 my $res = $self->{backend}->do_request 727 ("file_debug", { 728 %opts, 729 }) or return undef; 730 return $res; 731} 732 733=head2 file_info 734 735 my $fid = $mogc->file_info($key, { devices => 0 }); 736 737Used to return metadata about a file. Returns the domain, class, expected 738length, devcount, etc. Optionally device ids (not paths) can be returned as 739well. 740 741Should be used for informational purposes, and not usually for dynamically 742serving files. 743 744=cut 745 746sub file_info { 747 my MogileFS::Client $self = shift; 748 my ($key, $opts) = @_; 749 750 my %extra = (); 751 $extra{devices} = delete $opts->{devices}; 752 die "Unknown arguments: " . join(', ', keys %$opts) if keys %$opts; 753 754 my $res = $self->{backend}->do_request 755 ("file_info", { 756 domain => $self->{domain}, 757 key => $key, 758 %extra, 759 }) or return undef; 760 return $res; 761} 762 763=head2 list_keys 764 765 $keys = $mogc->list_keys($prefix, $after[, $limit]); 766 ($after, $keys) = $mogc->list_keys($prefix, $after[, $limit]); 767 768Used to get a list of keys matching a certain prefix. 769 770$prefix specifies what you want to get a list of. $after is the item 771specified as a return value from this function last time you called 772it. $limit is optional and defaults to 1000 keys returned. 773 774In list context, returns ($after, $keys). In scalar context, returns 775arrayref of keys. The value $after is to be used as $after when you 776call this function again. 777 778When there are no more keys in the list, you will get back undef or 779an empty list. 780 781=cut 782 783sub list_keys { 784 my MogileFS::Client $self = shift; 785 my ($prefix, $after, $limit) = @_; 786 787 my $res = $self->{backend}->do_request 788 ("list_keys", { 789 domain => $self->{domain}, 790 prefix => $prefix, 791 after => $after, 792 limit => $limit, 793 }) or return undef; 794 795 # construct our list of keys and the new after value 796 my $resafter = $res->{next_after}; 797 my $reslist = []; 798 for (my $i = 1; $i <= $res->{key_count}+0; $i++) { 799 push @$reslist, $res->{"key_$i"}; 800 } 801 return wantarray ? ($resafter, $reslist) : $reslist; 802} 803 804=head2 foreach_key 805 806 $mogc->foreach_key( %OPTIONS, sub { my $key = shift; ... } ); 807 $mogc->foreach_key( prefix => "foo:", sub { my $key = shift; ... } ); 808 809 810Functional interface/wrapper around list_keys. 811 812Given some %OPTIONS (currently only one, "prefix"), calls your callback 813for each key matching the provided prefix. 814 815=cut 816 817sub foreach_key { 818 my MogileFS::Client $self = shift; 819 my $callback = pop; 820 Carp::croak("Last parameter not a subref") unless ref $callback eq "CODE"; 821 my %opts = @_; 822 my $prefix = delete $opts{prefix}; 823 Carp::croak("Unknown option(s): " . join(", ", keys %opts)) if %opts; 824 825 my $last = ""; 826 my $max = 1000; 827 my $count = $max; 828 829 while ($count == $max) { 830 my $res = $self->{backend}->do_request 831 ("list_keys", { 832 domain => $self->{domain}, 833 prefix => $prefix, 834 after => $last, 835 limit => $max, 836 }) or return undef; 837 $count = $res->{key_count}+0; 838 for (my $i = 1; $i <= $count; $i++) { 839 $callback->($res->{"key_$i"}); 840 } 841 $last = $res->{"key_$count"}; 842 } 843 return 1; 844} 845 846# just makes some sleeping happen. first and only argument is number of 847# seconds to instruct backend thread to sleep for. 848sub sleep { 849 my MogileFS::Client $self = shift; 850 my $duration = shift; 851 852 $self->{backend}->do_request("sleep", { duration => $duration + 0 }) 853 or return undef; 854 855 return 1; 856} 857 858=head2 update_class 859 860 $mogc->update_class($key, $newclass); 861 862Update the replication class of a pre-existing file, causing 863the file to become more or less replicated. 864 865=cut 866 867sub update_class { 868 my MogileFS::Client $self = shift; 869 my ($key, $class) = @_; 870 my $res = $self->{backend}->do_request 871 ("updateclass", { 872 domain => $self->{domain}, 873 key => $key, 874 class => $class, 875 }) or return undef; 876 return $res; 877} 878 879=head2 set_pref_ip 880 881 $mogc->set_pref_ip({ "10.0.0.2" => "10.2.0.2" }); 882 883Weird option for old, weird network architecture. Sets a mapping 884table of preferred alternate IPs, if reachable. For instance, if 885trying to connect to 10.0.0.2 in the above example, the module would 886instead try to connect to 10.2.0.2 quickly first, then then fall back 887to 10.0.0.2 if 10.2.0.2 wasn't reachable. 888 889=cut 890 891# expects as argument a hashref of "standard-ip" => "preferred-ip" 892sub set_pref_ip { 893 my MogileFS::Client $self = shift; 894 $self->{backend}->set_pref_ip(shift) 895 if $self->{backend}; 896} 897 898=head1 PLUGIN METHODS 899 900WRITEME 901 902=cut 903 904# used to support plugins that have modified the server, this builds things into 905# an argument list and passes them back to the server 906# TODO: there is no readonly protection here? does it matter? should we check 907# with the server to see what methods they support? (and if they should be disallowed 908# when the client is in readonly mode?) 909sub AUTOLOAD { 910 # remove everything up to the last colon, so we only have the method name left 911 my $method = $AUTOLOAD; 912 $method =~ s/^.*://; 913 914 return if $method eq 'DESTROY'; 915 916 # let this work 917 no strict 'refs'; 918 919 # create a method to pass this on back 920 *{$AUTOLOAD} = sub { 921 my MogileFS::Client $self = shift; 922 # pre-assemble the arguments into a hashref 923 my $ct = 0; 924 my $args = {}; 925 $args->{"arg" . ++$ct} = shift() while @_; 926 $args->{"argcount"} = $ct; 927 928 # now put in standard args 929 $args->{"domain"} = $self->{domain}; 930 931 # now call and return whatever we get back from the backend 932 return $self->{backend}->do_request("plugin_$method", $args); 933 }; 934 935 # now pass through 936 goto &$AUTOLOAD; 937} 938 939=head1 HOOKS 940 941=head2 add_hook 942 943WRITEME 944 945=cut 946 947sub add_hook { 948 my MogileFS::Client $self = shift; 949 my $hookname = shift || return; 950 951 if (@_) { 952 $self->{hooks}->{$hookname} = shift; 953 } else { 954 delete $self->{hooks}->{$hookname}; 955 } 956} 957 958sub run_hook { 959 my MogileFS::Client $self = shift; 960 my $hookname = shift || return; 961 962 my $hook = $self->{hooks}->{$hookname}; 963 return unless $hook; 964 965 eval { $hook->(@_) }; 966 967 warn "MogileFS::Client hook '$hookname' threw error: $@\n" if $@; 968} 969 970=head2 add_backend_hook 971 972WRITEME 973 974=cut 975 976sub add_backend_hook { 977 my MogileFS::Client $self = shift; 978 my $backend = $self->{backend}; 979 980 $backend->add_hook(@_); 981} 982 983 984################################################################################ 985# MogileFS class methods 986# 987 988sub _fail { 989 croak "MogileFS: $_[0]"; 990} 991 992sub _debug { 993 return 1 unless $MogileFS::DEBUG; 994 995 my $msg = shift; 996 my $ref = shift; 997 chomp $msg; 998 999 eval "use Data::Dumper;"; 1000 print STDERR "$msg\n" . Dumper($ref) . "\n"; 1001 return 1; 1002} 1003 1004 10051; 1006__END__ 1007 1008=head1 SEE ALSO 1009 1010L<http://www.danga.com/mogilefs/> 1011 1012=head1 COPYRIGHT 1013 1014This module is Copyright 2003-2004 Brad Fitzpatrick, 1015and copyright 2005-2007 Six Apart, Ltd. 1016 1017All rights reserved. 1018 1019You may distribute under the terms of either the GNU General Public 1020License or the Artistic License, as specified in the Perl README file. 1021 1022=head1 WARRANTY 1023 1024This is free software. IT COMES WITHOUT WARRANTY OF ANY KIND. 1025 1026=head1 AUTHORS 1027 1028Brad Fitzpatrick <brad@danga.com> 1029 1030Brad Whitaker <whitaker@danga.com> 1031 1032Mark Smith <marksmith@danga.com> 1033 1034