- Timestamp:
- Jun 25, 2009, 2:00:56 PM (17 years ago)
- Location:
- branches/eam_branches/20090522
- Files:
-
- 3 edited
-
. (modified) (1 prop)
-
Nebulous-Server (modified) (1 prop)
-
Nebulous-Server/lib/Nebulous/Server.pm (modified) (109 diffs)
Legend:
- Unmodified
- Added
- Removed
-
branches/eam_branches/20090522
- Property svn:mergeinfo changed
-
branches/eam_branches/20090522/Nebulous-Server
- Property svn:mergeinfo changed
-
branches/eam_branches/20090522/Nebulous-Server/lib/Nebulous/Server.pm
r23932 r24557 13 13 use base qw( Class::Accessor::Fast ); 14 14 15 use Cache::Memcached; 15 16 use DBI; 16 17 use Digest::SHA1 qw( sha1_hex ); 18 use Fcntl ':mode'; 17 19 use File::Basename qw( basename dirname fileparse ); 18 20 use File::ExtAttr qw( setfattr ); … … 27 29 use URI::file; 28 30 29 __PACKAGE__->mk_accessors(qw( log sql config )); 30 31 use constant SUBPATH_DEPTH => 2; 32 use constant NFS_RETRIES => 100; 31 __PACKAGE__->mk_accessors(qw( log sql config cache )); 32 33 use constant SUBPATH_DEPTH => 2; 34 use constant NFS_RETRIES => 100; 35 use constant NFS_RETRY_WAIT => 1; 36 use constant TRANS_RETRY_WAIT => 1; 37 38 # transaction restart/retry regex 39 my $trans_regex = qr/Deadlock Found|Lock wait timeout exceeded|try restarting transaction|Can't connect to MySQL server/i; 33 40 34 41 sub new … … 45 52 sub new_from_config 46 53 { 47 my ($class, $config) = @_; 54 my $class = shift; 55 56 my ($config) = @_; 48 57 49 58 # log4perl is not avaliable until we call init() … … 51 60 my $log = Log::Log4perl::get_logger( "Nebulous::Server" ); 52 61 $log->level($config->trace); 62 $log->debug( "entered - @_" ); 53 63 54 64 my $sql = Nebulous::Server::SQL->new; 55 56 $log->debug( "entered - @_" );57 65 58 66 my $self = bless {}, ref $class || $class; … … 60 68 $self->sql($sql); 61 69 $self->config($config); 70 $self->cache( 71 Cache::Memcached->new({ 72 servers => $config->memcached_servers, 73 }) 74 ); 75 # $self->cache->set("foo", "bar") or die "set failed"; 76 # $self->cache->get("foo") or die "get failed"; 77 $log->logdie("at least one database must be defined") unless $config->n_db; 78 79 # cause a db session to be started 80 $self->_db_for_index(0); 62 81 63 82 $log->debug( "leaving" ); … … 70 89 sub _db_index_for_key 71 90 { 72 my ($self, $key) = @_; 91 my $self = shift; 92 93 my $log = $self->log; 94 $log->debug( "entered - @_" ); 95 96 my ($key) = @_; 73 97 74 98 my $config = $self->config; 75 99 76 100 my $db_index = 0; 77 die "key not defined"unless defined $key;101 $log->logdie("key not defined") unless defined $key; 78 102 79 103 # hash the key to select the correct database instance … … 85 109 $db_index = unpack("h8", sha1_hex($path)) % $config->n_db; 86 110 111 $log->debug("index is $db_index"); 112 $log->debug("leaving"); 113 87 114 return $db_index; 88 115 } … … 90 117 sub _db_for_index 91 118 { 92 my ($self, $db_index) = @_;119 my $self = shift; 93 120 94 121 my $log = $self->log; 122 $log->debug( "entered - @_" ); 123 124 my ($db_index) = @_; 125 95 126 my $sql = $self->sql; 96 127 my $config = $self->config; … … 108 139 # lookup database info 109 140 my $db_config = $config->db($db_index); 110 die "can't find database configuration info for database # $db_index"141 $log->logdie("can't find database configuration info for db # $db_index") 111 142 unless $db_config; 112 143 … … 116 147 # processes and the database might have gone away on us. Apache::DBI will 117 148 # take care of getting a valid dbh back. 118 eval { 119 $dbh = DBI->connect_cached( 120 $db_config->dsn, 121 $db_config->dbuser, 122 $db_config->dbpasswd, 123 { 124 RaiseError => 1, 125 PrintError => 0, 126 AutoCommit => 0, 127 }, 128 ); 129 130 $dbh->do( $sql->set_transaction_model ); 131 $log->debug( "connected to database: ", sub { $dbh->data_sources; } ); 132 $dbh->commit; 133 $log->debug("commit"); 134 }; 135 if ( $@ ) { 136 $dbh->rollback if $dbh; 137 $log->debug("rollback"); 138 $log->logdie( "database error: $@" ); 149 TRANS: while (1) { 150 eval { 151 $dbh = DBI->connect_cached( 152 $db_config->dsn, 153 $db_config->dbuser, 154 $db_config->dbpasswd, 155 { 156 RaiseError => 1, 157 PrintError => 0, 158 AutoCommit => 0, 159 }, 160 ); 161 162 $dbh->do( $sql->set_transaction_model ); 163 $log->debug( "connected to database: ", sub { $dbh->data_sources; } ); 164 $dbh->commit; 165 $log->debug("commit"); 166 }; 167 if ($@) { 168 $dbh->rollback if $dbh; 169 $log->debug("rollback") if $dbh; 170 if ($@ =~ qr/Can't connect to MySQL server/) { 171 $log->warn("database error, retrying transaction: $@"); 172 sleep TRANS_RETRY_WAIT; 173 redo TRANS; 174 } 175 $log->logdie( "database error: $@" ); 176 } 177 last; 139 178 } 140 179 141 180 $self->{dbs}[$db_index] = $dbh; 142 181 182 $log->debug("leaving"); 183 143 184 return $dbh; 144 185 } … … 148 189 my $self = shift; 149 190 191 my $log = $self->log; 192 $log->debug( "entered - @_" ); 193 150 194 my ($key) = validate_pos(@_, 151 195 { … … 154 198 ); 155 199 156 my $log = $self->log;157 200 my $sql = $self->sql; 158 201 my $config = $self->config; 159 202 160 die "key not defined"unless defined $key;203 $log->logdie("key not defined") unless defined $key; 161 204 my $db_index = $self->_db_index_for_key($key); 162 205 163 206 my $dbh = $self->_db_for_index($db_index); 207 164 208 return $dbh; 165 209 } … … 168 212 { 169 213 my $self = shift; 214 215 my $log = $self->log; 216 $log->debug( "entered - @_" ); 170 217 171 218 my ($key, $vol_name) = validate_pos(@_, … … 186 233 ); 187 234 235 my $sql = $self->sql; 236 188 237 # vol_name overrides the key implied volume 189 $key = parse_neb_key($key, $vol_name); 238 eval { 239 $key = parse_neb_key($key, $vol_name); 240 }; 241 $log->logdie("$@") if $@; 190 242 $vol_name = $key->volume; 191 243 192 my $log = $self->log;193 my $sql = $self->sql;194 244 my $db = $self->db($key); 195 196 $log->debug( "entered - @_" );197 245 198 246 # the key's volume can't be validiated on input for this method so we have … … 200 248 if (defined $vol_name 201 249 and not $self->_is_valid_volume_name($key, $key->volume)) { 202 if ($key->soft_volume) {250 unless ($key->hard_volume) { 203 251 $log->warn( "$vol_name is not a known volume name" ); 204 252 $vol_name = undef; 205 253 } else { 206 die "$vol_name is not a valid volume name"254 $log->logdie("$vol_name is not a valid volume name"); 207 255 } 208 256 } 209 257 210 258 my ($vol_id, $vol_host, $vol_path, $vol_xattr) 211 = $self->_get_storage_volume($key, $vol_name, $key-> soft_volume);259 = $self->_get_storage_volume($key, $vol_name, $key->hard_volume); 212 260 213 261 my $parent_id = $self->_resolve_dir_parent_id(key => $key, create => 1); … … 282 330 $db->rollback; 283 331 $log->debug("rollback"); 284 if ($@ =~ /Deadlock found/) { 285 $log->warn("database deadlock retrying transaction: $@"); 332 if ($@ =~ $trans_regex) { 333 $log->warn("database error, retrying transaction: $@"); 334 sleep TRANS_RETRY_WAIT; 286 335 redo TRANS; 287 336 } … … 291 340 } 292 341 342 # add new key to the cache 343 $self->cache->set($key->path, 1) if defined $self->cache; 344 $log->debug( "key added to cache" ); 345 293 346 $log->debug("leaving"); 294 347 … … 300 353 { 301 354 my $self = shift; 355 356 my $log = $self->log; 357 $log->debug( "entered - @_" ); 302 358 303 359 my %p = validate(@_, … … 322 378 my $key = $p{key}; 323 379 324 my $log = $self->log;325 380 my $sql = $self->sql; 326 381 my $db = $self->db($key); 327 328 $log->debug( "entered - @_" );329 382 330 383 # no path means '/', which has a dir_id & parent_id of 1 … … 394 447 $query->finish; 395 448 } 396 $log->logdie("failed to get LAST_INSERT_ID()")449 die("failed to get LAST_INSERT_ID()") 397 450 unless $parent_id; 398 451 399 $db->commit;452 # $db->commit; 400 453 } 401 454 }; … … 403 456 $db->rollback; 404 457 $log->debug("rollback"); 405 if ($@ =~ /Deadlock found/) { 406 $log->warn("database deadlock retrying transaction: $@"); 458 if ($@ =~ $trans_regex) { 459 $log->warn("database error, retrying transaction: $@"); 460 $parent_id = 1; 461 sleep TRANS_RETRY_WAIT; 407 462 redo TRANS; 408 463 } 464 if ($@ =~ qr/Duplicate entry/) { 465 $log->warn("Duplicate database entry, retrying transaction: $@"); 466 $parent_id = 1; 467 sleep TRANS_RETRY_WAIT; 468 redo TRANS; 469 } 409 470 $log->logdie("error: $@"); 410 471 } … … 421 482 { 422 483 my $self = shift; 484 485 my $log = $self->log; 486 $log->debug("entered - @_"); 423 487 424 488 my ($key, $newkey) = validate_pos(@_, … … 438 502 ); 439 503 504 my $sql = $self->sql; 505 440 506 # ignore volumes 441 $key = parse_neb_key($key); 442 $newkey = parse_neb_key($newkey); 443 444 my $log = $self->log; 445 my $sql = $self->sql; 507 eval { 508 $key = parse_neb_key($key); 509 }; 510 $log->logdie("$@") if $@; 511 eval { 512 $newkey = parse_neb_key($newkey); 513 }; 514 $log->logdie("$@") if $@; 515 446 516 my $db = $self->db($key); 447 448 $log->debug("entered - @_");449 517 450 518 # XXX this may require database migration in the future … … 464 532 unless ($rows == 1) { 465 533 $query->finish; 466 $log->logdie("affected row count is $rows instead of 1"); 467 } 534 die("affected row count is $rows instead of 1"); 535 } 536 537 $self->cache->delete($key->path) if defined $self->cache; 538 $self->cache->set($newkey->path, 1) if defined $self->cache; 468 539 469 540 $db->commit; … … 473 544 $db->rollback; 474 545 $log->debug("rollback"); 475 if ($@ =~ /Deadlock found/) { 476 $log->warn("database deadlock retrying transaction: $@"); 546 if ($@ =~ $trans_regex) { 547 $log->warn("database error, retrying transaction: $@"); 548 sleep TRANS_RETRY_WAIT; 477 549 redo TRANS; 478 550 } … … 491 563 my $self = shift; 492 564 565 my $log = $self->log; 566 $log->debug("entered - @_"); 567 493 568 my ($key1, $key2) = validate_pos(@_, 494 569 { … … 506 581 ); 507 582 583 my $sql = $self->sql; 584 508 585 # ignore volumes 509 $key1 = parse_neb_key($key1); 510 $key2 = parse_neb_key($key2); 511 512 my $log = $self->log; 513 my $sql = $self->sql; 586 eval { 587 $key1 = parse_neb_key($key1); 588 }; 589 $log->logdie("$@") if $@; 590 eval { 591 $key2 = parse_neb_key($key2); 592 }; 593 $log->logdie("$@") if $@; 514 594 515 595 my $dbidx1 = $self->_db_index_for_key($key1); 516 596 my $dbidx2 = $self->_db_index_for_key($key2); 517 die "cannot swap keys not stored on the same database" unless ($dbidx1 == $dbidx2); 597 $log->logdie("cannot swap keys not stored on the same database") 598 unless ($dbidx1 == $dbidx2); 518 599 519 600 my $dbh1 = $self->_db_for_index($dbidx1); 520 601 my $dbh2 = $self->_db_for_index($dbidx2); 521 die "different db handles for the same db?" unless ($dbh1 == $dbh2); 522 523 $log->debug("entered - @_"); 602 $log->logdie("different db handles for the same db?") 603 unless ($dbh1 == $dbh2); 524 604 525 605 # order of operations for the swap with a single db is: … … 543 623 unless ($rows == 1) { 544 624 $query->finish; 545 $log->logdie("affected row count is $rows instead of 1");625 die("affected row count is $rows instead of 1"); 546 626 } 547 627 } … … 556 636 unless ($rows == 1) { 557 637 $query->finish; 558 $log->logdie("affected row count is $rows instead of 1");638 die("affected row count is $rows instead of 1"); 559 639 } 560 640 } … … 569 649 unless ($rows == 1) { 570 650 $query->finish; 571 $log->logdie("affected row count is $rows instead of 1");651 die("affected row count is $rows instead of 1"); 572 652 } 573 653 } … … 579 659 $db->rollback; 580 660 $log->debug("rollback"); 581 if ($@ =~ /Deadlock found/) { 582 $log->warn("database deadlock retrying transaction: $@"); 661 if ($@ =~ $trans_regex) { 662 $log->warn("database error, retrying transaction: $@"); 663 sleep TRANS_RETRY_WAIT; 583 664 redo TRANS; 584 665 } … … 612 693 my $self = shift; 613 694 695 my $log = $self->log; 696 $log->debug("entered - @_"); 697 614 698 my ($key, $vol_name) = validate_pos(@_, 615 699 { … … 633 717 ); 634 718 719 my $sql = $self->sql; 720 635 721 # if a volume name is explicity specified then we should make the 636 722 # replication onto that volume (even if there is alread an instance on that … … 642 728 643 729 # vol_name overrides the key implied volume 644 $key = parse_neb_key($key, $vol_name); 730 eval { 731 $key = parse_neb_key($key, $vol_name); 732 }; 733 $log->logdie("$@") if $@; 645 734 $vol_name = $key->volume; 646 735 647 my $log = $self->log;648 my $sql = $self->sql;649 736 my $db = $self->db($key); 650 651 $log->debug("entered - @_");652 737 653 738 if (defined $vol_name 654 739 and not $self->_is_valid_volume_name($key, $key->volume)) { 655 if ($key->soft_volume) {740 unless ($key->hard_volume) { 656 741 $log->warn( "$vol_name is not a known volume name" ); 657 742 $vol_name = undef; 658 743 } else { 659 die "$vol_name is not a valid volume name"744 $log->logdie("$vol_name is not a valid volume name"); 660 745 } 661 746 } … … 681 766 unless ( $rows > 0 ) { 682 767 $query->finish; 683 $log->logdie( "storage object does not exist" );768 die( "storage object does not exist" ); 684 769 } 685 770 … … 725 810 $db->rollback; 726 811 # handle soft volumes 727 if (defined $vol_name and defined $key->soft_volume) {812 if (defined $vol_name and not defined $key->hard_volume) { 728 813 $log->debug("retrying with 'any' volume"); 729 814 return $self->replicate_object($key->path, 'any'); 730 815 } 731 816 $log->debug("rollback"); 732 if ($@ =~ /Deadlock found/) { 733 $log->warn("database deadlock retrying transaction: $@"); 817 if ($@ =~ $trans_regex) { 818 $log->warn("database error, retrying transaction: $@"); 819 sleep TRANS_RETRY_WAIT; 734 820 redo TRANS; 735 821 } … … 739 825 } 740 826 827 # check to see if the user.mode xattr exists 828 eval { 829 my $mode = $self->getxattr_object("$key", 'user.mode'); 830 if (defined $mode) { 831 $self->chmod_object("$key", $mode); 832 } 833 }; 834 if ($@) { 835 unless ($@ =~ qr/user.mode does not exist/) { 836 $log->logdie("error: $@"); 837 } 838 } 839 741 840 $log->debug("leaving"); 742 841 … … 748 847 { 749 848 my $self = shift; 849 850 my $log = $self->log; 851 $log->debug( "entered - @_" ); 750 852 751 853 my ( $key, $type ) = validate_pos( @_, … … 764 866 ); 765 867 868 my $sql = $self->sql; 869 766 870 # ignore volume 767 $key = parse_neb_key($key); 768 769 my $log = $self->log; 770 my $sql = $self->sql; 871 eval { 872 $key = parse_neb_key($key); 873 }; 874 $log->logdie("$@") if $@; 875 771 876 my $db = $self->db($key); 772 773 $log->debug( "entered - @_" );774 877 775 878 my $so_id; … … 785 888 unless ( $rows == 1 ) { 786 889 $query->finish; 787 $log->logdie( "storage object does not exist" );890 die( "storage object does not exist" ); 788 891 } 789 892 … … 800 903 # can't set a write lock if there are read locks 801 904 if ($write_lock) { 802 $log->logdie("can not write lock twice -- retry");905 die("can not write lock twice -- retry"); 803 906 } 804 907 805 908 if ($read_lock > 0) { 806 $log->logdie("can not write lock after read lock -- retry");909 die("can not write lock after read lock -- retry"); 807 910 } 808 911 … … 813 916 # if we affected more then one row something very bad has happened. 814 917 unless ($rows == 1) { 815 $log->logdie("affected row count is $rows instead of 1");918 die("affected row count is $rows instead of 1"); 816 919 } 817 920 … … 820 923 # can't set a read lock if there's a write lock 821 924 if ($write_lock) { 822 $log->logdie("can not read lock after write lock -- retry");925 die("can not read lock after write lock -- retry"); 823 926 } 824 927 … … 829 932 # if we affected more then one row something very bad has happened. 830 933 unless ($rows == 1) { 831 $log->logdie("affected row count is $rows instead of 1");934 die("affected row count is $rows instead of 1"); 832 935 } 833 936 } … … 840 943 $db->rollback; 841 944 $log->debug("rollback"); 842 if ($@ =~ /Deadlock found/) { 843 $log->warn("database deadlock retrying transaction: $@"); 945 if ($@ =~ $trans_regex) { 946 $log->warn("database error, retrying transaction: $@"); 947 sleep TRANS_RETRY_WAIT; 844 948 redo TRANS; 845 949 } … … 858 962 { 859 963 my $self = shift; 964 965 my $log = $self->log; 966 $log->debug( "entered - @_" ); 860 967 861 968 my ( $key, $type ) = validate_pos( @_, … … 874 981 ); 875 982 983 my $sql = $self->sql; 984 876 985 # ignore volume 877 $key = parse_neb_key($key); 878 879 my $log = $self->log; 880 my $sql = $self->sql; 986 eval { 987 $key = parse_neb_key($key); 988 }; 989 $log->logdie("$@") if $@; 990 881 991 my $db = $self->db($key); 882 883 $log->debug( "entered - @_" );884 992 885 993 my $so_id; … … 895 1003 unless ($rows == 1) { 896 1004 $query->finish; 897 $log->logdie("storage object does not exist");1005 die("storage object does not exist"); 898 1006 } 899 1007 … … 909 1017 # can't remove a write lock if it doesn't exist 910 1018 if ($read_lock) { 911 $log->logdie("can not have a write lock under a read lock");1019 die("can not have a write lock under a read lock"); 912 1020 } 913 1021 914 1022 unless ($write_lock) { 915 $log->logdie("can not remove non-existant write lock");1023 die("can not remove non-existant write lock"); 916 1024 } 917 1025 … … 922 1030 # if we affected more then one row something very bad has happened. 923 1031 unless ($rows == 1) { 924 $log->logdie("affected row count is $rows instead of 1");1032 die("affected row count is $rows instead of 1"); 925 1033 } 926 1034 } … … 929 1037 # can't remove a read lock if there aren't any 930 1038 if ($write_lock) { 931 $log->logdie("can not have a read lock under a write lock");1039 die("can not have a read lock under a write lock"); 932 1040 } 933 1041 934 1042 if ($read_lock == 0) { 935 $log->logdie("can not remove non-existant read lock");1043 die("can not remove non-existant read lock"); 936 1044 } 937 1045 … … 942 1050 # if we affected more then one row something very bad has happened. 943 1051 unless ($rows == 1) { 944 $log->logdie("affected row count is $rows instead of 1");1052 die("affected row count is $rows instead of 1"); 945 1053 } 946 1054 … … 953 1061 $db->rollback; 954 1062 $log->debug("rollback"); 955 if ($@ =~ /Deadlock found/) { 956 $log->warn("database deadlock retrying transaction: $@"); 1063 if ($@ =~ $trans_regex) { 1064 $log->warn("database error, retrying transaction: $@"); 1065 sleep TRANS_RETRY_WAIT; 957 1066 redo TRANS; 958 1067 } … … 971 1080 { 972 1081 my $self = shift; 1082 1083 my $log = $self->log; 1084 $log->debug("entered - @_"); 973 1085 974 1086 my ($key, $name, $value, $flags) = validate_pos(@_, … … 981 1093 { 982 1094 type => SCALAR, 1095 callbacks => { 1096 'xattr is in user. namespace' 1097 => sub { ($_[0]) =~ qr/^user\./ }, 1098 }, 983 1099 }, 984 1100 { … … 993 1109 ); 994 1110 1111 my $sql = $self->sql; 1112 995 1113 # ignore volume 996 $key = parse_neb_key($key); 997 998 my $log = $self->log; 999 my $sql = $self->sql; 1114 eval { 1115 $key = parse_neb_key($key); 1116 }; 1117 $log->logdie("$@") if $@; 1118 1000 1119 my $db = $self->db($key); 1001 1002 $log->debug("entered - @_");1003 1120 1004 1121 TRANS: while (1) { … … 1020 1137 if ($flags eq 'create') { 1021 1138 unless ($rows == 1) { 1022 $log->logdie( "affected row count is $rows instead of 1" );1139 die( "affected row count is $rows instead of 1" ); 1023 1140 } 1024 1141 } else { … … 1026 1143 # the case of a replace and 1 if the xattr didn't already exist. 1027 1144 unless ($rows == 1 or $rows == 2) { 1028 $log->logdie( "affected row count is $rows instead of 2" );1145 die( "affected row count is $rows instead of 2" ); 1029 1146 } 1030 1147 } … … 1036 1153 $db->rollback; 1037 1154 $log->debug("rollback"); 1038 if ($@ =~ /Deadlock found/) { 1039 $log->warn("database deadlock retrying transaction: $@"); 1155 if ($@ =~ $trans_regex) { 1156 $log->warn("database error, retrying transaction: $@"); 1157 sleep TRANS_RETRY_WAIT; 1040 1158 redo TRANS; 1041 1159 } … … 1054 1172 { 1055 1173 my $self = shift; 1174 1175 my $log = $self->log; 1176 $log->debug("entered - @_"); 1056 1177 1057 1178 my ($key, $name) = validate_pos(@_, … … 1064 1185 { 1065 1186 type => SCALAR, 1187 callbacks => { 1188 'xattr is in user. namespace' 1189 => sub { ($_[0]) =~ qr/^user\./ }, 1190 }, 1066 1191 }, 1067 1192 ); 1068 1193 1194 my $sql = $self->sql; 1195 1069 1196 # ignore volume 1070 $key = parse_neb_key($key); 1071 1072 my $log = $self->log; 1073 my $sql = $self->sql; 1197 eval { 1198 $key = parse_neb_key($key); 1199 }; 1200 $log->logdie("$@") if $@; 1201 1074 1202 my $db = $self->db($key); 1075 1076 $log->debug("entered - @_");1077 1203 1078 1204 my $value; … … 1085 1211 if ($rows == 0) { 1086 1212 $query->finish; 1087 $log->logdie( "xattr $key:$name does not exist" );1213 die( "xattr $key:$name does not exist" ); 1088 1214 } 1089 1215 # if we go more then one row bad something very bad has happened. 1090 1216 unless ($rows == 1) { 1091 1217 $query->finish; 1092 $log->logdie( "affected row count is $rows instead of 1" );1218 die( "affected row count is $rows instead of 1" ); 1093 1219 } 1094 1220 … … 1099 1225 $value = $row->{ 'value' }; 1100 1226 }; 1101 $log->logdie("database error: $@") if $@; 1227 if ($@) { 1228 if ($@ =~ /user\..*? does not exist/) { 1229 # do not log xattr does not exist messages 1230 die $@; 1231 } 1232 $log->logdie("database error: $@") if $@; 1233 } 1102 1234 1103 1235 $log->debug("leaving"); … … 1110 1242 { 1111 1243 my $self = shift; 1244 1245 my $log = $self->log; 1246 $log->debug("entered - @_"); 1112 1247 1113 1248 my ($key) = validate_pos(@_, … … 1120 1255 ); 1121 1256 1257 my $sql = $self->sql; 1258 1122 1259 # ignore volume 1123 $key = parse_neb_key($key); 1124 1125 my $log = $self->log; 1126 my $sql = $self->sql; 1260 eval { 1261 $key = parse_neb_key($key); 1262 }; 1263 $log->logdie("$@") if $@; 1264 1127 1265 my $db = $self->db($key); 1128 1129 $log->debug("entered - @_");1130 1266 1131 1267 my @xattrs; … … 1151 1287 my $self = shift; 1152 1288 1289 my $log = $self->log; 1290 $log->debug("entered - @_"); 1291 1153 1292 my ($key, $name) = validate_pos(@_, 1154 1293 { … … 1160 1299 { 1161 1300 type => SCALAR, 1301 callbacks => { 1302 'xattr is in user. namespace' 1303 => sub { ($_[0]) =~ qr/^user\./ }, 1304 }, 1162 1305 }, 1163 1306 ); 1164 1307 1308 my $sql = $self->sql; 1309 1165 1310 # ignore volume 1166 $key = parse_neb_key($key); 1167 1168 my $log = $self->log; 1169 my $sql = $self->sql; 1311 eval { 1312 $key = parse_neb_key($key); 1313 }; 1314 $log->logdie("$@") if $@; 1315 1170 1316 my $db = $self->db($key); 1171 1172 $log->debug("entered - @_");1173 1317 1174 1318 TRANS: while (1) { … … 1179 1323 $query->finish; 1180 1324 1325 # no rows affected means the xattr did not exist 1326 if ($rows == 0) { 1327 die( "xattr $key:$name does not exist" ); 1328 } 1329 1181 1330 # if we affected more then one row something very bad has happened. 1182 unless ($rows ==1) {1183 $log->logdie( "affected row count is $rows instead of 1" );1331 if ($rows > 1) { 1332 die( "affected row count is $rows instead of 1" ); 1184 1333 } 1185 1334 … … 1190 1339 $db->rollback; 1191 1340 $log->debug("rollback"); 1192 if ($@ =~ /Deadlock found/) { 1193 $log->warn("database deadlock retrying transaction: $@"); 1341 if ($@ =~ $trans_regex) { 1342 $log->warn("database error, retrying transaction: $@"); 1343 sleep TRANS_RETRY_WAIT; 1194 1344 redo TRANS; 1195 1345 } … … 1209 1359 my $self = shift; 1210 1360 1361 my $log = $self->log; 1362 $log->debug( "entered - @_" ); 1363 1211 1364 my ($pattern) = validate_pos( @_, 1212 1365 { … … 1216 1369 ); 1217 1370 1218 $pattern = parse_neb_key($pattern); 1219 1220 my $log = $self->log;1221 1222 $log-> debug( "entered - @_" );1371 1372 eval { 1373 $pattern = parse_neb_key($pattern) if defined $pattern; 1374 }; 1375 $log->logdie("$@") if $@; 1223 1376 1224 1377 unless (defined $pattern) { … … 1235 1388 $log->logdie("no keys found") unless ( scalar @keys ); 1236 1389 1390 if (defined $self->cache) { 1391 foreach my $path (@keys) { 1392 $self->cache->set($path, 1); 1393 $log->debug("key added to cache as: $path"); 1394 } 1395 } 1396 1237 1397 $log->debug( "leaving" ); 1238 1398 … … 1245 1405 1246 1406 my $self = shift; 1407 1408 my $log = $self->log; 1409 $log->debug( "entered - @_" ); 1410 1247 1411 my $index = shift; 1248 1412 my $key = shift; 1249 1413 1250 my $log = $self->log;1251 1414 my $sql = $self->sql; 1252 1415 my $db = $self->_db_for_index($index); 1253 1416 1254 $log->debug( "entered - @_" );1255 1417 1256 1418 # first check to see if the key is an exact match … … 1310 1472 my $self = shift; 1311 1473 1474 my $log = $self->log; 1475 $log->debug("entered - @_"); 1476 1312 1477 my ($key, $vol_name) = validate_pos(@_, 1313 1478 { … … 1330 1495 ); 1331 1496 1497 my $sql = $self->sql; 1498 1499 # unless ($key) { 1500 # $log->warn("key was undefined after validate_pos(), trying again..."); 1501 # return $self->find_instances(@_); 1502 # } 1503 1332 1504 # vol_name overrides the key implied volume 1333 $key = parse_neb_key($key, $vol_name); 1505 eval { 1506 $key = parse_neb_key($key, $vol_name); 1507 }; 1508 $log->logdie("$@") if $@; 1334 1509 $vol_name = $key->volume; 1335 1510 1336 my $log = $self->log;1337 my $sql = $self->sql;1338 1511 my $db = $self->db($key); 1339 1340 $log->debug("entered - @_");1341 1512 1342 1513 # the key's volume can't be validiated on input for this method so we have … … 1344 1515 if (defined $vol_name 1345 1516 and not $self->_is_valid_volume_name($key, $key->volume)) { 1346 if ($key->soft_volume) { 1517 if ($key->hard_volume) { 1518 $log->logdie("$vol_name is not a valid volume name"); 1519 } else { 1347 1520 $log->warn( "$vol_name is not a known volume name" ); 1348 1521 $vol_name = undef; 1349 } else {1350 die "$vol_name is not a valid volume name"1351 1522 } 1352 1523 } … … 1361 1532 unless ($rows > 0) { 1362 1533 $query->finish; 1363 $log->logdie("no instances on storage volume or volume is not avaiable for key: $key volume: $vol_name");1534 die("no instances on storage volume or volume is not avaiable for key: $key volume: $vol_name"); 1364 1535 } 1365 1536 } else { … … 1369 1540 unless ($rows > 0) { 1370 1541 $query->finish; 1371 $log->logdie("no instances available for key: $key");1542 die("no instances available for key: $key"); 1372 1543 } 1373 1544 } … … 1381 1552 $db->rollback; 1382 1553 # handle soft volumes 1383 if (defined $vol_name and defined $key->soft_volume) {1554 if (defined $vol_name and not defined $key->hard_volume) { 1384 1555 $log->debug("retrying with 'any' volume"); 1385 1556 return $self->find_instances($key->path, 'any'); … … 1403 1574 my $self = shift; 1404 1575 1576 my $log = $self->log; 1577 $log->debug( "entered - @_" ); 1578 1405 1579 my ($key, $uri) = validate_pos( @_, 1406 1580 { … … 1415 1589 ); 1416 1590 1591 my $sql = $self->sql; 1592 1417 1593 # ignore volume 1418 $key = parse_neb_key($key); 1419 1420 my $log = $self->log; 1421 my $sql = $self->sql; 1594 eval { 1595 $key = parse_neb_key($key); 1596 }; 1597 $log->logdie("$@") if $@; 1598 1422 1599 my $db = $self->db($key); 1423 1424 $log->debug( "entered - @_" );1425 1600 1426 1601 TRANS: while (1) { 1427 1602 eval { 1603 my $instances; 1428 1604 my $so_id; 1429 my $ins tances;1430 # get so_id1605 my $ins_id; 1606 # find so_id for key and get count of instances 1431 1607 { 1432 my $query = $db->prepare_cached( $sql->get_object_from_uri ); 1433 my $rows = $query->execute( $uri ); 1434 1608 my $query = $db->prepare_cached( $sql->get_instance_count_by_ext_id ); 1609 my $rows = $query->execute($key->path); 1435 1610 unless ( $rows > 0 ) { 1436 1611 $query->finish; 1437 $log->logdie( "no instance is associated with uri" ); 1438 } 1439 1440 $so_id = $query->fetchrow_hashref->{ 'so_id' }; 1612 die( "$key has no associated instances - this should not happen" ); 1613 } 1614 1615 my $record = $query->fetchrow_hashref; 1616 $so_id = $record->{ 'so_id' }; 1617 $instances = $record->{ 'count(ins_id)' }; 1441 1618 $query->finish; 1442 1443 } 1444 1619 } 1620 1621 # find ins_id for uri 1445 1622 { 1446 my $query = $db->prepare_cached( $sql->get_instance_count ); 1447 $query->execute( $so_id ); 1448 1449 $instances = $query->fetchrow_hashref->{ 'count(ins_id)' }; 1623 my $query = $db->prepare_cached( $sql->get_instance_by_uri ); 1624 my $rows = $query->execute($so_id, $uri); 1625 unless ( $rows > 0) { 1626 $query->finish; 1627 die( "no instance is associated with uri" ); 1628 } 1629 1630 $ins_id = $query->fetchrow_hashref->{ 'ins_id' }; 1450 1631 $query->finish; 1451 1632 } … … 1453 1634 # remove instance 1454 1635 { 1455 my $query = $db->prepare_cached( $sql->delete_instance );1456 my $rows = $query->execute( $ uri);1636 my $query = $db->prepare_cached( $sql->delete_instance_by_ins_id ); 1637 my $rows = $query->execute( $ins_id ); 1457 1638 $query->finish; 1458 1639 1459 # if we affected something other then two rows something very bad1460 # has happened1640 # if we affected something other then one row something very 1641 # bad has happened 1461 1642 unless ( $rows == 1 ) { 1462 $log->logdie( "affected row count is $rows instead of 1" );1643 die( "affected row count is $rows instead of 1" ); 1463 1644 } 1464 1645 } … … 1467 1648 # remove it too 1468 1649 if ( $instances == 1 ) { 1650 # remove key from cache 1651 $self->cache->delete($key->path) if defined $self->cache; 1652 1469 1653 # we just removed the last instance 1470 1654 my $query = $db->prepare_cached( $sql->delete_object ); … … 1474 1658 # TODO: this will have to be changed in order to support hardlinks 1475 1659 unless ( $rows == 1 ) { 1476 $log->logdie( "affected row count is $rows instead of 2" );1660 die( "affected row count is $rows instead of 2" ); 1477 1661 } 1478 1662 } … … 1484 1668 $db->rollback; 1485 1669 $log->debug("rollback"); 1486 if ($@ =~ /Deadlock found/) { 1487 $log->warn("database deadlock retrying transaction: $@"); 1670 if ($@ =~ $trans_regex) { 1671 $log->warn("database error, retrying transaction: $@"); 1672 sleep TRANS_RETRY_WAIT; 1488 1673 redo TRANS; 1489 1674 } … … 1502 1687 { 1503 1688 my $self = shift; 1689 1690 my $log = $self->log; 1691 $log->debug("entered - @_"); 1504 1692 1505 1693 my ( $key ) = validate_pos( @_, … … 1512 1700 ); 1513 1701 1702 my $sql = $self->sql; 1703 1514 1704 # ignore volume 1515 $key = parse_neb_key($key); 1516 1517 my $log = $self->log; 1518 my $sql = $self->sql; 1705 eval { 1706 $key = parse_neb_key($key); 1707 }; 1708 $log->logdie("$@") if $@; 1709 1519 1710 my $db = $self->db($key); 1520 1521 $log->debug("entered - @_");1522 1711 1523 1712 my $stat; … … 1527 1716 1528 1717 unless ($rows == 1) { 1529 $log->logdie("no storage object found");1718 die("no storage object found"); 1530 1719 } 1531 1720 … … 1548 1737 my $self = shift; 1549 1738 1739 my $log = $self->log; 1740 $log->debug("entered - @_"); 1741 1550 1742 validate_pos(@_); 1551 1743 1552 my $log = $self->log;1553 1744 my $sql = $self->sql; 1554 1745 my $db = $self->_db_for_index(0); # XXX fix as above 1555 1746 1556 $log->debug("entered - @_");1557 1558 1747 my $stats; 1559 1748 my $query; … … 1576 1765 } 1577 1766 1767 sub chmod_object 1768 { 1769 my $self = shift; 1770 1771 my $log = $self->log; 1772 $log->debug("entered - @_"); 1773 1774 my ($key, $mode) = validate_pos( @_, 1775 { 1776 type => SCALAR, 1777 callbacks => { 1778 'is valid object key' => sub { $self->_is_valid_object_key($_[0]) }, 1779 }, 1780 }, 1781 { 1782 type => SCALAR, 1783 regex => qr/\d{3,4}/, 1784 callbacks => { 1785 'is allowable mode' => sub { 1786 $_[0] == (S_IRUSR | S_IRGRP) 1787 }, 1788 }, 1789 }, 1790 ); 1791 1792 my $sql = $self->sql; 1793 1794 # ignore volume 1795 eval { 1796 $key = parse_neb_key($key); 1797 }; 1798 $log->logdie("$@") if $@; 1799 1800 my $db = $self->db($key); 1801 1802 # find all instances of this object 1803 my $locations; 1804 eval { 1805 $locations = $self->find_instances("$key"); 1806 }; 1807 $log->logdie("error: $@") if $@; 1808 1809 # update each instances 1810 foreach my $inst (@$locations) { 1811 my $path = URI->new($inst)->path; 1812 1813 $self->_retry(sub { chmod($mode, $path) }) 1814 or $log->logdie("can not chmod() $path: $!"); 1815 1816 # XXX I'm assuming that it's OK to fsync() a filehandle that's only 1817 # open for reading? Opening as w/rw here can fail if the chmod removes 1818 # write permissions. 1819 my $fh; 1820 $self->_retry(sub { open($fh, '<', $path) }) 1821 or $log->logdie("can not open() $path: $!"); 1822 1823 # fsync(3c) 1824 $self->_retry(sub { $fh->sync() }) 1825 or $log->logdie("can not sync() $path: $!"); 1826 1827 $self->_retry(sub { close($fh) }) 1828 or $log->logdie("can not close() $path: $!"); 1829 } 1830 1831 # stick an xattr on this object with the mode 1832 # XXX this would probably be better as a field in the storage_object_attr 1833 # table but since we're not planning to use this for very many objects (as 1834 # a %) it may not be worth adding the extra field at this time. 1835 eval { 1836 $self->setxattr_object("$key", 'user.mode', $mode, 'replace'); 1837 }; 1838 $log->logdie("error: $@") if $@; 1839 1840 $log->debug("leaving"); 1841 1842 return $mode; 1843 } 1578 1844 1579 1845 sub _get_storage_volume … … 1581 1847 my $self = shift; 1582 1848 1583 my ($key, $name, $soft_volume) = @_; 1584 1585 my $log = $self->log; 1586 my $sql = $self->sql; 1587 my $db = $self->db($key); 1588 1849 my $log = $self->log; 1589 1850 no warnings qw( uninitialized ); 1590 1851 $log->debug( "entered - @_" ); 1591 1852 use warnings; 1853 1854 my ($key, $name, $hard_volume) = @_; 1855 1856 my $sql = $self->sql; 1857 my $db = $self->db($key); 1592 1858 1593 1859 my ($vol_id, $vol_host, $vol_path, $xattr); … … 1605 1871 # if a volume name was specified, and is soft, and we failed to 1606 1872 # find it, fall back to any volume 1607 if ($soft_volume) {1873 unless ($hard_volume) { 1608 1874 ($vol_id, $vol_host, $vol_path, $xattr) = $self->_get_storage_volume($key); 1609 1875 return; # this just returns out of the eval not from the subroutine 1610 1876 } 1611 $log->logdie("storage volume: $name is not available");1877 die("storage volume: $name is not available"); 1612 1878 } 1613 1879 # when matching by name we shouldn't ever match more than once 1614 1880 if ($rows > 1) { 1615 1881 $query->finish; 1616 $log->logdie("affected row count is $rows instead of 1");1882 die("affected row count is $rows instead of 1"); 1617 1883 } 1618 1884 } else { … … 1623 1889 unless ($rows > 0) { 1624 1890 $query->finish; 1625 $log->logdie("no storage volume is available");1891 die("no storage volume is available for key: $key volume: $name hard_volume: $hard_volume"); 1626 1892 } 1627 1893 } … … 1631 1897 $query->finish; 1632 1898 }; 1633 $log->logdie("database error: $@") if $@; 1899 if ($@) { 1900 if ($@ =~ qr/no storage volume is available/) { 1901 # this should not happen unless all volumes are full 1902 $log->error($@); 1903 $log->debug("retrying..."); 1904 return $self->_get_storage_volume(@_); 1905 } 1906 # else 1907 $log->logdie("database error: $@"); 1908 } 1634 1909 1635 1910 $log->logdie("failed to find a suitable volume" ) … … 1646 1921 my $self = shift; 1647 1922 1648 my $key = shift; 1649 1650 my $log = $self->log; 1651 my $sql = $self->sql; 1652 my $db = $self->db($key); 1653 1923 my $log = $self->log; 1654 1924 no warnings qw( uninitialized ); 1655 1925 $log->debug( "entered - @_" ); 1656 1926 use warnings; 1927 1928 my $key = shift; 1929 1930 my $sql = $self->sql; 1931 my $db = $self->db($key); 1657 1932 1658 1933 my ($vol_id, $vol_host, $vol_path, $xattr); … … 1665 1940 unless ($rows > 0) { 1666 1941 $query->finish; 1667 $log->logdie("can't find a suitable storage volume to replicate $key to");1942 die("can't find a suitable storage volume to replicate $key to"); 1668 1943 } 1669 1944 # when matching by name we shouldn't ever match more than once 1670 1945 if ($rows > 1) { 1671 1946 $query->finish; 1672 $log->logdie("affected row count is $rows instead of 1");1947 die("affected row count is $rows instead of 1"); 1673 1948 } 1674 1949 … … 1690 1965 sub _is_valid_object_key 1691 1966 { 1692 my ($self, $key) = @_; 1693 1694 $key = parse_neb_key($key); 1695 1696 my $log = $self->log; 1967 my $self = shift; 1968 1969 my $log = $self->log; 1970 $log->debug( "entered - @_" ); 1971 1972 my ($key) = @_; 1973 1697 1974 my $sql = $self->sql; 1975 1976 eval { 1977 $key = parse_neb_key($key); 1978 }; 1979 $log->logdie("$@") if $@; 1980 1981 # check cache first 1982 my $cached = $self->cache->get($key->path) if defined $self->cache; 1983 if (defined $cached) { 1984 $log->debug( "key $key found in cache as ", $key->path ); 1985 $log->debug( "leaving" ); 1986 return 1; 1987 } else { 1988 $log->debug( "key $key not found in cache" ); 1989 } 1990 1698 1991 my $db = $self->db($key); 1699 1992 … … 1712 2005 1713 2006 if (defined $ext_id) { 2007 $log->debug( "key found in db" ); 2008 # add key to cache 2009 $self->cache->set($key->path, 1) if defined $self->cache; 2010 $log->debug( "key added to cache as ", $key->path ); 2011 $log->debug( "leaving" ); 1714 2012 return 1; 1715 2013 } 1716 2014 2015 $log->debug( "key not found in db" ); 2016 $log->debug( "leaving" ); 2017 1717 2018 return; 1718 2019 } … … 1721 2022 sub _is_valid_volume_name 1722 2023 { 1723 my ($self, $key, $vol_name) = @_; 1724 1725 $key = parse_neb_key($key); 2024 my $self = shift; 2025 2026 my $log = $self->log; 2027 $log->debug( "entered - @_" ); 2028 2029 my ($key, $vol_name) = @_; 2030 2031 my $sql = $self->sql; 2032 2033 # the volume name implied by the key is ignored. $key is only needed to 2034 # select a database connection 2035 eval { 2036 $key = parse_neb_key($key); 2037 }; 2038 $log->logdie("$@") if $@; 1726 2039 my $volume_info = parse_neb_volume($vol_name); 1727 2040 1728 my $log = $self->log;1729 my $sql = $self->sql;1730 2041 my $db = $self->db($key); 1731 2042 … … 1734 2045 # handle "any" volume 1735 2046 if ($vol_name eq 'any') { 2047 $log->debug( "found volume name $vol_name" ); 2048 $log->debug( "leaving" ); 1736 2049 return 1; 1737 2050 } … … 1748 2061 1749 2062 if (defined $vol_id and defined $vol_path) { 2063 $log->debug( "found volume name $vol_name" ); 2064 $log->debug( "leaving" ); 1750 2065 return 1; 1751 2066 } 1752 2067 2068 $log->debug( "volume name $vol_name not found" ); 2069 $log->debug( "leaving" ); 2070 1753 2071 return; 1754 2072 } … … 1759 2077 my $self = shift; 1760 2078 2079 my $log = $self->log; 2080 $log->debug( "entered - @_" ); 2081 1761 2082 my ($key, $so_id, $ins_id, $vol_path, $xattr) = @_; 1762 2083 1763 my $log = $self->log;1764 2084 my $sql = $self->sql; 1765 2085 my $db = $self->db($key); … … 1770 2090 my $storage_filename = $self->_generate_storage_filename($key->path, $ins_id); 1771 2091 unless (-d $storage_path) { 1772 _retry(sub { mkpath(@_) }, $storage_path, 0, 0775)1773 or die "can't create storage path: $storage_path";2092 $self->_retry(sub { mkpath([$storage_path], 0, 0775) }) 2093 or die("can't create storage path: $storage_path"); 1774 2094 } 1775 2095 # check to make sure at least the parent directory has the proper 1776 2096 # permissions 1777 my $mode = [ _retry(sub { stat($storage_path) } )]->[2] & 07777;2097 my $mode = [$self->_retry(sub { stat($storage_path) } )]->[2] & 07777; 1778 2098 unless ($mode == 0775) { 1779 $log->error("$storage_path has the wrong permissions of: %04x", $mode); 1780 _retry(sub { chmod(0775, $storage_path) }) or die "can't chmod $storage_path: $!"; 2099 # XXX: this problem is so common that it's flooding the logs 2100 $log->debug("$storage_path has the wrong permissions of: 0", sprintf("%o", $mode)); 2101 $self->_retry(sub { chmod(0775, $storage_path) }) 2102 or die("can not chmod() $storage_path: $!"); 1781 2103 } 1782 2104 … … 1787 2109 }; 1788 2110 if ($@) { 2111 if (defined $uri and -e $uri->file) { 2112 unlink($uri->file) 2113 or $log->error("failed to unlink() $uri: $!"); 2114 } 1789 2115 $log->logdie($@); 1790 2116 } … … 1792 2118 if ($xattr) { 1793 2119 my $path = $uri->file; 1794 die "can not set xattr on $path: $!"2120 $log->logdie("can not set xattr on $path: $!") 1795 2121 unless (setfattr($path, 'user.nebulous_key', $key->path)); 1796 2122 } … … 1804 2130 my $self = shift; 1805 2131 2132 my $log = $self->log; 2133 $log->debug( "entered - @_" ); 2134 1806 2135 my ($path) = @_; 1807 2136 1808 2137 # perl's open() can't do an O_CREAT | O_EXCL 1809 die "file $path already exists" if (-e $path); 2138 # XXX is it possible to tell if this system call failed? 2139 -e $path 2140 and $log->logdie("file $path already exists"); 1810 2141 1811 2142 my $fh; 1812 die "can not open $path: $!"1813 unless (_retry(sub { open($fh, '>', $path) }));2143 $self->_retry(sub { open($fh, '>', $path) }) 2144 or $log->logdie("can not open() $path: $!"); 1814 2145 1815 2146 # chmod before fsync() to make sure the changed perms hit the disk too 1816 die "can not chmod $path: $!" 1817 unless (_retry(sub { chmod(0664, $path) })); 1818 1819 die "can not flush $path: $!" 1820 unless ($fh->flush); 1821 1822 die "can not sync $path: $!" 1823 unless ($fh->sync); 1824 1825 die "can not close $path: $!" 1826 unless (_retry(sub { close($fh) })); 2147 $self->_retry(sub { chmod(0664, $path) }) 2148 or $log->logdie("can not chmod() $path: $!"); 2149 2150 $self->_retry(sub { $fh->sync() }) 2151 or $log->logdie("can not sync() $path: $!"); 2152 2153 $self->_retry(sub { close($fh) }) 2154 or $log->logdie("can not close() $path: $!"); 1827 2155 1828 2156 return $path; … … 1861 2189 sub _retry 1862 2190 { 2191 my $self = shift; 2192 2193 my $log = $self->log; 2194 $log->debug( "entered - @_" ); 2195 1863 2196 my $func = shift; 1864 2197 … … 1869 2202 }; 1870 2203 if ($@) { 1871 die $@;1872 sleep 1;2204 $log->logdie($@); 2205 sleep NFS_RETRY_WAIT; 1873 2206 next; 1874 2207 } … … 1879 2212 # if the loop ended and $@ is set, rethrow the error 1880 2213 if ($@) { 1881 die $@;2214 $log->logdie($@); 1882 2215 } 1883 2216
Note:
See TracChangeset
for help on using the changeset viewer.
