#!/usr/local/bin/perl5 # -*-Fundamental-*- # $Id: //guest/richard_geiger/utils/snap_checkpoint/snap_checkpoint#4 $ # # Original Author: Richard Geiger, Network Appliance, Inc. # use Carp; use strict; $| = 1; my $Myname; ($Myname = $0) =~ s%^.*/%%; # Configuration Settings # # REVIEW THESE SETTINGS, AND ADJUST THEM AS NECESSARY FOR USE IN YOUR # ENVIRONMENT: # # $P4PORT for the server you wish to checkpoint; # my $P4PORT = "p4netapp:1678"; # $P4ROOT for the server you wish to checkpoint # my $P4ROOT = "/u/p4/root.$P4PORT"; # The path to the "p4" client to be used # my $P4 = "/u/p4/dist/r99.2/bin.osf/p4"; # The path to the "p4d" server to be used # my $P4D = "/u/p4/dist/r99.2/bin.osf/p4d"; # The path to the directory where the checkpoint should be written # my $P4CHECKPOINT = "/u/p4/checkpoint.$P4PORT"; # The path to the journal file # my $P4JOURNAL = "/u/p4/checkpoint.$P4PORT/journal"; # The name of the NetApp filer that holds the volume where # $P4ROOT is stored # # my $FILER = "maglite"; # The volume name of the volume where $P4ROOT is stored # my $VOLUME = "perforce"; # Path to the host's "rsh" command # my $RSH = "/bin/rsh"; # Path to the host's "gzip" command # my $GZIP = "/usr/local/bin/gzip"; # The locking order of the db.* files, as of r99.1 & r99.2, per information # supplied by Perforce Software. # # ***** You should confirm the correct order for any other version # ***** of the Perforce server; if the locking order is not correct, # ***** it is possible to get into a deadlock situation!) # my $dbfiles = <<EOL; db.counters db.user db.group db.depot db.domain db.view db.review db.have db.integ db.locks db.rev db.revcx db.working db.change db.desc db.job db.jobpend db.jobdesc db.fix db.fixrev db.boddate db.bodtext db.ixdate db.ixtext db.protect db.trigger EOL my @dbfiles = split(/\n/, $dbfiles); sub dirname { my ($dir) = @_; $dir =~ s%^$%.%; $dir = "$dir/"; if ($dir =~ m%^/[^/]*//*$%) { return "/"; } if ($dir =~ m%^.*[^/]//*[^/][^/]*//*$%) { $dir =~ s%^(.*[^/])//*[^/][^/]*//*$%$1%; { return $dir; } } return "."; } use Fcntl ':flock'; # import LOCK_* constants sub p4d_lock { no strict 'refs'; my($sleep) = @_; foreach my $file (@dbfiles) { my $handle = $file; $handle =~ s/^db\.//; $handle =~ tr/a-z/A-Z/; if (! open($handle, "<$P4ROOT/$file")) { &msg("$Myname: can't open \"$P4ROOT/$file\": $!\n", undef, undef, "p4"); exit 1; } if (! flock($handle, LOCK_EX)) { &msg("$Myname: can't lock \"$P4ROOT/$file\": $!\n", undef, undef, "p4"); exit 1; } } use strict 'refs'; &msg("$Myname: $P4ROOT locked.\n"); if ($sleep) { while (1) { sleep 60*60*24; } } } sub p4d_unlock { no strict 'refs'; foreach my $file (reverse(@dbfiles)) { my $handle = $file; $handle =~ s/^db\.//; $handle =~ tr/a-z/A-Z/; close $handle; } use strict 'refs'; &msg("$Myname: $P4ROOT unlocked.\n"); } # Run a command, returning status and output; terminate # on any error. # sub s { my ($cmd) = @_; my ($sts, $output); print("> $cmd\n"); if (! open(CMD, "$cmd 2>&1 |")) { die "can't open \"$cmd 2>&1 |\": $!"; } while (<CMD>) { print(": $_"); $output .= $_; } close CMD; if ($sts = $?) { my $sig = $sts & 0x0f; $sts = $sts >> 8; die "\"$cmd\" exited with signal $sig status $sts"; } return ($sts, $output); } sub ts { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); return sprintf("%04d%02d%02d%02d%02d%02d", 1900+$year, $mon+1, $mday, $hour, $min, $sec); } # The following function is taken from the NetApp "p4d_admin" script. # I've decided to try and keep the function the same, verbatim, as it # is there, so things might be done a bit differently than they # otherwise would. In perticular, I've stubbed out the "&msg" # function with a dummy version that ignore the later argsm which are # used n the context of p4d_admin, but not here. # # sub msg { my ($msg) = @_; print STDERR $msg; } sub p4d_snap_checkpoint { # First, delete the previous snapshot (if it exists). (We keep the # old one around, just in case, until we need to make a new one. # my ($sts, $output) = &s("$RSH maglite snap delete perforce checkpoint 2>&1"); chop $output; if ($sts || $output !~ /^(deleting snapshot\.+|No such snapshot.)$/) { &msg("$Myname: couldn't delete snapshot.\n", undef, undef, "p4"); exit 1; } # Next, look up the journal sequence number counter... Logically, # we'd prefer to do this with the database locked, but the danger of # a rouge checkpoint -jc happening seems tolerable... # ($sts, $output) = &s("$P4 -p $P4PORT counters"); my $journal_counter = ""; foreach $_ (split(/\n/, $output)) { if (/^journal = (\d+)/) { $journal_counter = $1; } } if ($journal_counter eq "") { &msg("$Myname: can't get journal counter, nothing done.\n", undef, undef, "p4"); exit 1; } # Now increment the counter # my $new_journal_counter = $journal_counter + 1; ($sts, $output) = &s("$P4 -p $P4PORT counter journal $new_journal_counter"); chop $output; if ($output !~ /^Counter journal set\.$/) { &msg("$Myname: couldn't increment journal counter:\n$output\n", undef, undef, "p4"); exit 1; } # Next, we lock down the entire database # &p4d_lock(0); my $tstamp = &ts; # Now, we snapshot the database filesystem... # ($sts, $output) = &s("$RSH maglite snap create perforce checkpoint 2>&1"); chop $output; if ($sts || $output !~ /^creating snapshot\.+$/) { &msg("$Myname: couldn't create snapshot.\n", undef, undef, "p4"); exit 1; } # OK, now we are confident that we have a good snapshot. We can # proceed on the assumption that that the "off line" operations of # copying the journal and "p4 -jd" will work. All we need to do here # is to truncate the journal, and put the server back on-line by # unlocking it... # Truncate the journal: # if (! open(J, ">$P4JOURNAL")) { &msg("$Myname: couldn't truncate \"$P4JOURNAL\": $!\n", undef, undef, "p4"); exit 1; } close J; &msg("$Myname: \"$P4JOURNAL\" truncated.\n"); # Now we can release the lock... # &p4d_unlock; # At this point, the syetem is online for users. # Copy the journal (from the snapshot). # my $journaln = "$P4CHECKPOINT/$tstamp.jnl.$journal_counter"; my $P4JOURNALDIR = &dirname($P4JOURNAL); my $P4JOURNALFILE; ($P4JOURNALFILE = $P4JOURNAL) =~ s%^.*/%%; ($sts, $output) = &s("/bin/cp -p $P4JOURNALDIR/.snapshot/checkpoint/$P4JOURNALFILE $journaln"); if ($sts) { &msg("$Myname: couldn't copy \"$P4JOURNALDIR/.snapshot/checkpoint/$P4JOURNALFILE\".\n", undef, undef, "p4"); exit 1; } # Compress the saved journal segment... # (Ignore errors - they can be dealt with later) # &s("/usr/local/bin/gzip $journaln"); # Diddle $P4ROOT so that this checkpoint is done from the snapshot # we just took... # my $P4ROOT_sav = $P4ROOT; $P4ROOT .= "/.snapshot/checkpoint"; $ENV{"P4ROOT"} = $P4ROOT; # And then run the checkpoint from the snapshot... # my ($sts, $output) = &s("$P4D -r $P4ROOT -p $P4PORT -z -jd $P4CHECKPOINT/$tstamp.ckp.$new_journal_counter.gz"); if ($sts || $output !~ /^Dumping to $P4CHECKPOINT\/$tstamp\.ckp\.$new_journal_counter\.gz\.\.\.$/) { &msg("$Myname: checkpoint failed.\n", undef, undef, "p4"); exit 1; } # Mom taught me to put things back where I'd got 'em. # $P4ROOT = $P4ROOT_sav; $ENV{"P4ROOT"} = $P4ROOT; return $sts; } exit (&p4d_snap_checkpoint());
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#9 | 1544 | Richard Geiger |
Update to reflect changes in p4d 2002.1: a) The change in the locking order, due to db.changex b) The fix for job006497 |
||
#8 | 942 | Richard Geiger | Use $VOLUME, too! | ||
#7 | 941 | Richard Geiger | Use the $GZIP variable instead of the literal path. | ||
#6 | 920 | Richard Geiger |
add 2001.1 locking order; correct open mode ("+<") for Solaris; some notes; and the "lockcheck" option. |
||
#5 | 437 | Richard Geiger |
Hack to handle r2000.1's newfound reluctance to do "p4 counter journal NNNN". |
||
#4 | 248 | Richard Geiger |
The main change here is to move the copying of the journal file to done from the checkpoint, outside of the region where the server is locked. This can make the whole thing go much faster when the journal is sizable enought that the copy takes a significant amoutn of time to happen. |
||
#3 | 246 | Richard Geiger |
Update the script such that we use, verbatim, the p4d_snap_checkpoint function from "p4d_admin", which the version we're finally really deploying. This should make it much easier to maintain in the future. Also update the html doc to match. |
||
#2 | 239 | Richard Geiger |
- Use LOCK_SH when locking the database - Use ALL CAPS when shunning all responsibility for the thing (Warranty disclaimer) |
||
#1 | 238 | Richard Geiger |
Sample script illustrating how to use Data ONTAP snapshots for a "fast checkpoint", plus accompanying notes |