| #!/usr/bin/env python |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| |
| ''' |
| gpsnap -- snapshot a gpdb array |
| |
| Usage: gpsnap [-Vvlfz?] [-u user] {-crdiI} snapshot_name |
| |
| -c: create a snapshot |
| -r: restore a snapshot |
| -d: delete a snapshot |
| -i: show information on snapshot |
| -I: show detailed info on snapshot |
| |
| -z: use zfs snapshot mechanism |
| -l: list snapshots |
| -v: verbose |
| -V: very verbose |
| -f: force |
| -?: print help |
| |
| ''' |
| |
| import os, sys |
| progname = os.path.split(sys.argv[0])[-1] |
| if sys.version_info < (2,5,0): |
| sys.exit( |
| '''Error %s is supported on Python version 2.5 or greater |
| Please upgrade python installed on this machine.''' % progname) |
| |
| import subprocess, time, datetime, threading, Queue, random, pickle |
| |
| |
| ############ |
| MASTER_DATA_DIRECTORY = os.getenv('MASTER_DATA_DIRECTORY') |
| if not MASTER_DATA_DIRECTORY: |
| sys.exit('MASTER_DATA_DIRECTORY env not defined') |
| |
| |
| |
| # we always connect to localhost for snapshot ... must run on master machine |
| os.putenv('PGHOST', '') |
| os.putenv("PGOPTIONS", '-c gp_session_role=utility') |
| os.putenv('PGDATABASE', 'template1') |
| |
| class __globals__: |
| opt = {} |
| for o in 'vVcrdiIflzu': opt['-' + o] = False |
| opt['-u'] = '' |
| snapname = '' |
| |
| |
| GV = __globals__() |
| |
| ############ |
| def usage(exitarg): |
| print __doc__ |
| sys.exit(exitarg) |
| |
| ############ |
| def humantime(td): |
| d = td.days > 0 and td.days or 0 |
| h = int(td.seconds / 60 / 60) |
| m = int(td.seconds / 60) % 60 |
| s = td.seconds % 60 |
| ret = '' |
| if d: ret = ret + '%dD ' % d |
| if h: ret = ret + '%dh ' % h |
| ret = ret + ('%dm %ds' % (m, s)) |
| return ret |
| |
| ############ |
| def tstamp(): |
| return datetime.datetime.now().strftime('[%Y-%m-%d %H:%M:%S]') |
| |
| |
| ############ |
| def msg(s): |
| print '%s %s' % (tstamp(), s) |
| |
| def vmsg(s): |
| if GV.opt['-v']: msg(s) |
| |
| def vvmsg(s): |
| if GV.opt['-V']: msg(s) |
| |
| ############ |
| def die(s): |
| sys.exit('%s ERROR: %s' % (tstamp(), s)) |
| |
| ############ |
| def strip_trailing_slash(dir): |
| return dir.rstrip('/') if dir else None |
| |
| ############ |
| def ssh(hostname, cmd, input=''): |
| proxy = ['ssh', '-o', 'BatchMode yes', '-o', 'StrictHostKeyChecking no', hostname, |
| '''bash -c 'exec python -c "import os,sys,pickle,subprocess; (prog, cmd, input) = pickle.load(sys.stdin); exec prog" ' '''] |
| prog = """ |
| p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) |
| p.stdin.write(input) |
| p.stdin.close() |
| sys.stdout.write(p.stdout.read()) |
| sys.exit(p.wait())""" |
| vvmsg(' - ssh ' + hostname + ' ' + str(cmd)) |
| p = subprocess.Popen(proxy, stdin=subprocess.PIPE, stdout=subprocess.PIPE) |
| if type(cmd) == type(''): |
| cmd = ['bash', '-c', cmd] |
| pickle.dump( (prog, cmd, input), p.stdin ) |
| p.stdin.close() |
| out = p.stdout.read() |
| rc = p.wait() |
| return (rc, out) |
| |
| |
| |
| ############ |
| def confirm(s): |
| if not GV.opt['-f'] and sys.stdin.isatty(): |
| ok = raw_input('%s\n ... proceed (y/n)? ' % s) |
| print |
| ok = ok.strip().lower() |
| return ok and ok[0] == 'y' |
| return True |
| |
| ############ |
| def parseCommandLine(): |
| import getopt |
| try: |
| (options, args) = getopt.getopt(sys.argv[1:], '?VvlcrdiIfzu:') |
| except Exception, e: |
| usage('Error: ' + str(e)) |
| |
| for (switch, val) in options: |
| if switch == '-?': usage(0) |
| elif switch[1] in 'VvlcrdiIfz': GV.opt[switch] = True |
| elif switch == '-u': GV.opt[switch] = val |
| |
| if GV.opt['-V']: GV.opt['-v'] = True |
| |
| if 1 == reduce(lambda x,y: x+y, [GV.opt['-'+s] and 1 or 0 for s in "crdiI"]): |
| pass |
| else: |
| if not GV.opt['-l']: |
| usage('Error: please specify one of -c / -r / -d / -i / -I') |
| |
| if 1 == len(args): |
| GV.snapname = args[0] |
| import re |
| if not re.match('\A[a-zA-Z0-9][a-zA-Z0-9\-\_\:]*\Z', GV.snapname): |
| usage("\n".join(["Error: invalid snapshot name", |
| "Hint: valid name like 'thursday_2009-03_19:00'"])) |
| else: |
| if not GV.opt['-l']: |
| usage('Error: missing snapshot_name') |
| |
| |
| |
| ############ |
| def run(cmd): |
| vvmsg(cmd) |
| p = os.popen(cmd) |
| out = p.readlines() |
| if GV.opt['-V']: |
| for line in out: vvmsg(line[:-1]) |
| rc = p.close() |
| return (rc, out) |
| |
| def runas(cmd): |
| if GV.opt['-u']: |
| x = ['su', '-', GV.opt['-u'], '-c', 'bash'] |
| vvmsg(" ".join(x)) |
| p = subprocess.Popen(x, |
| stdin=subprocess.PIPE, |
| stdout=subprocess.PIPE) |
| path = os.getenv('PATH') |
| ld_library_path = os.getenv('LD_LIBRARY_PATH') |
| dyld_library_path = os.getenv('DYLD_LIBRARY_PATH') |
| cmd1 = ('export PATH="%s"\n' % path + |
| 'export LD_LIBRARY_PATH="%s"\n' % ld_library_path + |
| 'export DYLD_LIBRARY_PATH="%s"\n' % dyld_library_path) |
| p.stdin.write(cmd1) |
| p.stdin.write(cmd) |
| p.stdin.close() |
| out = p.stdout.readlines() |
| if GV.opt['-V']: |
| for line in out: vvmsg(line[:-1]) |
| return (p.wait(), out) |
| else: |
| return run(cmd) |
| |
| |
| |
| def getZfsMountpoint(hostname, datapath): |
| |
| cmd = "echo hostname $(hostname); zfs list -t filesystem " + datapath |
| (rc, out) = ssh(hostname, cmd) |
| if rc: |
| return (None, None) |
| |
| (hname, mpoint) = (None, None) |
| for line in out.split('\n'): |
| t = map(lambda x: x.strip(), line.split()) |
| if not t: continue |
| if t[0] == 'hostname': hname = t[1]; continue |
| if len(t) != 5: break |
| if datapath.find(t[4]) == 0: mpoint = t[0]; continue |
| |
| return (hname, mpoint) |
| |
| |
| class Segment: |
| def __init__(self, line): |
| row = [x.strip() for x in line.split('|')] |
| (self.content, self.definedprimary, self.dbid, |
| self.isprimary, self.valid, self.hostname, |
| self.port, self.datapath) = row |
| (self.toppath, self.dbdir) = os.path.split(strip_trailing_slash(self.datapath)) |
| def __str__(self): |
| return "|".join([self.content, self.definedprimary, self.dbid, |
| self.isprimary, self.valid, self.hostname, |
| self.port, self.datapath]) |
| |
| |
| class SnapshotMethod: |
| def __init__(self, type, name, seg): |
| (self.type, self.name, self.seg) = (type, name, seg) |
| self.fullname = '' |
| |
| def set_fullname(self,fn): self.fullname = fn |
| def createSnapshot(self): return -1 |
| def deleteSnapshot(self): return -1 |
| def checkSnapshot(self): return (-1, None) |
| def restoreSnapshot(self): return -1 |
| |
| def type(self): return self.type |
| |
| class TgzSnapshot(SnapshotMethod): |
| |
| def __init__(self, name, seg): |
| SnapshotMethod.__init__(self, 'tgz', name, seg) |
| self.prefix = '%s@%s' % (seg.dbdir, self.name) |
| self.tgzname = self.prefix + '.tgz' |
| self.tgzpath = os.path.join(self.seg.toppath, self.tgzname) |
| self.set_fullname( (self.seg.hostname, self.tgzpath) ) |
| |
| def createSnapshot(self): |
| msg('Creating snapshot %s:%s' % self.fullname) |
| cmd = ('cd %s && ' % self.seg.toppath) |
| cmd = cmd + ('rm -f %s && ' % self.tgzname) |
| cmd = cmd + ('({ which gtar > /dev/null && TAR=gtar || TAR=tar; } ; $TAR cfz %s %s)' |
| % (self.tgzname, self.seg.dbdir)) |
| (rc, out) = ssh(self.seg.hostname, cmd) |
| if rc: |
| # some errors occurred ... clean up |
| self.deleteSnapshot() |
| else: |
| msg(' ... created %s:%s' % self.fullname) |
| return rc |
| |
| |
| def deleteSnapshot(self): |
| msg('Deleting snapshot %s:%s' % self.fullname) |
| cmd = 'rm -f %s' % (self.tgzpath) |
| (rc, out) = ssh(self.seg.hostname, cmd) |
| return rc |
| |
| |
| def checkSnapshot(self): |
| msg('Checking snapshot %s:%s' % self.fullname) |
| cmd = ('({ which gtar > /dev/null && TAR=gtar || TAR=tar; } ; $TAR tfz %s > /dev/null && ls -l %s) 2>&1' |
| % (self.tgzpath, self.tgzpath)) |
| (rc, out) = ssh(self.seg.hostname, cmd) |
| lines = out.split('\n') |
| return (rc, lines) |
| |
| def restoreSnapshot(self): |
| msg('Restoring snapshot %s:%s' % self.fullname) |
| cmd = ('rm -rf %s; cd %s && ({ which gtar > /dev/null && TAR=gtar || TAR=tar; } ; $TAR xfz %s) 2> /dev/null' |
| % (self.seg.datapath, self.seg.toppath, self.tgzname)) |
| (rc, out) = ssh(self.seg.hostname, cmd) |
| return rc |
| |
| |
| class ZfsSnapshot(SnapshotMethod): |
| '''Redefine the operations on segments using ZFS snapshots.''' |
| |
| def __init__(self, name, seg): |
| SnapshotMethod.__init__(self, 'zfs', name, seg) |
| (hname, mpoint) = getZfsMountpoint(self.seg.hostname, self.seg.datapath) |
| if not hname or not mpoint: |
| msg('Error: unable to find zfs mountpoint for %s:%s' |
| % (self.seg.hostname, self.seg.datapath)) |
| return None |
| |
| # note: hname is slightly different from self.seg.hostname(). |
| # hname is what the shell 'hostname' command returned. |
| self.set_fullname( (hname, mpoint + '@' + self.name) ) |
| |
| |
| def createSnapshot(self): |
| (hname, sname) = self.fullname |
| cmd = 'zfs snapshot ' + sname |
| (rc, out) = ssh(hname, cmd) |
| if not rc: |
| msg('Created snapshot %s:%s' % self.fullname) |
| return rc |
| |
| def deleteSnapshot(self): |
| (hname, sname) = self.fullname |
| msg('Deleting snapshot of %s:%s' % self.fullname) |
| cmd = 'zfs destroy %s' % (sname) |
| (rc, out) = ssh(hname, cmd) |
| return rc |
| |
| |
| def checkSnapshot(self): |
| (hname, sname) = self.fullname |
| msg('Checking snapshot of %s:%s' % self.fullname) |
| cmd = 'zfs list -t snapshot %s 2>&1' % sname |
| (rc, out) = ssh(hname, cmd) |
| return (rc, out.split('\n')) |
| |
| def restoreSnapshot(self): |
| (hname, sname) = self.fullname |
| msg('Restoring snapshot of %s:%s' % self.fullname) |
| cmd = 'zfs rollback ' + sname |
| (rc, out) = ssh(hname, cmd) |
| return rc |
| |
| |
| |
| ############ |
| def pmap(func, jlist, numThreads = 16): |
| if (numThreads > len(jlist)): |
| numThreads = len(jlist) |
| |
| inq = Queue.Queue(len(jlist)) |
| for i in jlist: inq.put(i) |
| |
| outq = Queue.Queue(len(jlist)) |
| def work(): |
| try: |
| while True: |
| outq.put((None, func(inq.get_nowait()))) |
| except Queue.Empty: pass |
| except: |
| outq.put( (sys.exc_info(), None) ) |
| # drain |
| try: |
| while True: inq.get_nowait() |
| except Queue.Empty: pass |
| |
| thread = [threading.Thread(target=work) for i in xrange(numThreads)] |
| for t in thread: t.start() |
| for t in thread: t.join() |
| |
| ret = [] |
| try: |
| while True: |
| (ex, result) = outq.get_nowait() |
| if ex: |
| raise ex[0], ex[1], ex[2] |
| ret.append(result) |
| except Queue.Empty: pass |
| return ret |
| |
| |
| def test_pmap(): |
| import random, time |
| def p(x): time.sleep(random.random()); print x; return x |
| jlist = [x for x in "abcdefghijklmnopqrstuvwxyz"] |
| return pmap(p, jlist) |
| |
| def ctlpath(dbdir, name): |
| home = "~%s" % GV.opt['-u'] |
| dir = ".gpsnap" |
| fname = "%s@%s.sn2" % (dbdir, name) |
| return (home, dir, fname) |
| |
| |
| ############ |
| class ControlInfo: |
| def __init__(self, name, snapshots, type, etime): |
| (self.name, self.snapshots, self.etime) = (name, snapshots, etime) |
| self.etime = self.etime.replace(microsecond=0) |
| self.type = type; |
| |
| def delete(self, master): |
| fpath = ctlpath(master.dbdir, self.name) |
| fpath = os.path.join(fpath[0], fpath[1], fpath[2]) |
| cmd = 'rm -f %s' % fpath |
| (rc, out) = ssh(master.hostname, cmd) |
| return rc |
| |
| def write(self, master): |
| (home, dir, fname) = ctlpath(master.dbdir, self.name) |
| cmd = ('cd %s && mkdir -p %s && cd %s && cat > %s' |
| % (home, dir, dir, fname)) |
| line = [] |
| line.append('name: ' + self.name) |
| line.append('type: ' + self.type) |
| line.append('etime: ' + str(self.etime)) |
| for i in xrange(len(self.snapshots)): |
| line.append('segment' + str(i) + ': ' + str(self.snapshots[i].seg)) |
| line.append('') |
| |
| (rc, out) = ssh(master.hostname, cmd, '\n'.join(line)) |
| if not rc: |
| msg('Control file at %s:%s/%s/%s' % (master.hostname, home, dir, fname)) |
| return rc |
| |
| @staticmethod |
| def parse(f): |
| dict = {} |
| for line in f: |
| line = line.strip() |
| if len(line) > 0 and line[0] == '#': continue |
| line = line.split(':', 1) |
| if len(line) != 2: continue |
| dict[line[0].strip()] = line[1].strip() |
| if not dict.get('name'): return None |
| if not dict.get('type'): return None |
| if not dict.get('etime'): return None |
| if not dict.get('segment0'): return None |
| segments = [] |
| i = 0 |
| while True: |
| n = dict.get('segment' + str(i)) |
| if not n: break |
| segments.append(Segment(n)) |
| i = i + 1 |
| |
| etime = datetime.datetime.strptime(dict['etime'], '%Y-%m-%d %H:%M:%S') |
| snapshots = mkSnapshots(segments, dict['name'], dict['type']) |
| return ControlInfo(dict['name'], snapshots, |
| dict['type'], etime) |
| |
| @staticmethod |
| def read(name): |
| (toppath, dbdir) = os.path.split(MASTER_DATA_DIRECTORY) |
| (home, dir, fname) = ctlpath(dbdir, name) |
| cmd = ('cd %s && test -d %s && cd %s && test -e %s && cat %s' |
| % (home, dir, dir, fname, fname)) |
| (rc, out) = ssh('localhost', cmd) |
| ctl = ControlInfo.parse(out.split('\n')) |
| return ctl |
| |
| |
| @staticmethod |
| def list(): |
| (toppath, dbdir) = os.path.split(MASTER_DATA_DIRECTORY) |
| fpath = ctlpath(dbdir, '') |
| fpath = os.path.join(fpath[0], fpath[1], '*@*.sn2') |
| p = os.popen('''bash -c 'ls -1 %s 2> /dev/null' ''' % fpath) |
| out = p.readlines() |
| p.close() |
| |
| ret = [] |
| for fpath in out: |
| fpath = fpath.strip() |
| f = None |
| try: |
| f = open(fpath); |
| x = ControlInfo.parse(f) |
| if x: ret.append(x) |
| except IOError: pass |
| finally: f and f.close() |
| |
| return ret |
| |
| |
| |
| def mkSnapshots(segments, name, type): |
| segments = segments[:] |
| random.shuffle(segments) |
| if type == 'zfs': |
| snapshots = pmap(lambda s: ZfsSnapshot(name, s), segments) |
| else: |
| snapshots = map(lambda s: TgzSnapshot(name, s), segments) |
| |
| htab = {} |
| for (fn, s) in map(lambda s: (s.fullname, s), snapshots): |
| htab[fn] = s |
| |
| return map(lambda k: htab[k], htab.keys()) |
| |
| |
| ############ |
| def pstop(segments): |
| def action(hostname): |
| cmd = "ps -ef | grep postgres | grep -v grep | awk '{print $2}' | xargs pstop" |
| (rc, out) = ssh(hostname, cmd) |
| return rc |
| |
| masters = filter(lambda s: s.content == '-1', segments) |
| segments = filter(lambda s: s.content != '-1', segments) |
| vmsg('Suspending Masters') |
| pmap(lambda s: action(s.hostname), masters) |
| vmsg('Suspending Segments') |
| pmap(lambda s: action(s.hostname), segments) |
| msg('GPDB suspended.') |
| |
| ############ |
| def prun(segments): |
| def action(hostname): |
| cmd = "ps -ef | grep postgres | grep -v grep | awk '{print $2}' | xargs prun" |
| (rc, out) = ssh(hostname, cmd) |
| return rc |
| |
| masters = filter(lambda s: s.content == '-1', segments) |
| segments = filter(lambda s: s.content != '-1', segments) |
| vmsg('Resuming Segments') |
| pmap(lambda s: action(s.hostname), segments) |
| vmsg('Resuming Masters') |
| pmap(lambda s: action(s.hostname), masters) |
| msg('GPDB resumed.') |
| |
| |
| ############ |
| def createSnapshot(name): |
| ctlInfo = ControlInfo.read(name) |
| if ctlInfo: |
| die("Snapshot '%s' exists." % name) |
| |
| # need gp_configuration |
| print 'Create a %s snapshot.' % (GV.opt['-z'] and 'zfs' or 'tgz') |
| print 'Retrieving gp_configuration ... ' |
| stime = datetime.datetime.now() |
| CMD = ("python %s -f -d %s %s" |
| % (os.path.join(sys.path[0], 'gpgetconfig.py'), |
| MASTER_DATA_DIRECTORY, |
| GV.opt['-u'] and '-u ' + GV.opt['-u'] or '')) |
| (rc, out) = runas(CMD) |
| if rc: |
| die("Unable to retrieve gp_configuration") |
| |
| vvmsg(out) |
| out = filter(lambda x: x.find('[gpgetconfig]') == 0, out) |
| segments = [Segment(line.split(']', 1)[1]) for line in out] |
| |
| # shut it down |
| suspend = GV.opt['-z'] |
| if suspend: |
| if not confirm('This will suspend the database.'): |
| die('Aborted by user.') |
| vmsg('Suspending gpdb ...') |
| pstop(segments) |
| else: |
| if not confirm('This will shutdown the database'): |
| die('Aborted by user.') |
| vmsg("Stopping gpdb ...") |
| runas('gpstop -af') |
| |
| try: |
| # make the snapshots objects |
| snapshots = mkSnapshots(segments, name, GV.opt['-z'] and 'zfs' or 'tgz') |
| |
| # take the snapshots in parallel |
| erows = filter(lambda (s, rc): rc, |
| pmap(lambda s: (s, s.createSnapshot()), snapshots)) |
| # if error -> rollback |
| if erows: |
| emsg = (["Create failed:"] + |
| [" unable to create %s:%s" % s.fullname for (s, rc) in erows]) |
| # delete the snapshots |
| map(lambda s: (s, s.deleteSnapshot()), snapshots) |
| #pmap(lambda s: (s, s.deleteSnapshot()), snapshots) |
| die("\n".join(emsg)) |
| |
| # create control file |
| etime = datetime.datetime.now() |
| ctlInfo = ControlInfo(name, snapshots, GV.opt['-z'] and 'zfs' or 'tgz', etime) |
| |
| # write the control file to the masters |
| masters = filter(lambda s: s.content == '-1', segments) |
| erows = filter(lambda (m, rc): rc, |
| [(m, ctlInfo.write(m)) for m in masters]) |
| # if error -> rollback |
| if erows: |
| emsg = (["Create failed:"] + |
| [" cannot create control file %s:%s" |
| % (m.hostname, "/".join(ctlpath(m.dbdir, name))) for (m, rc) in erows]) |
| # delete the snapshots |
| pmap(lambda s: (s, s.deleteSnapshot()), snapshots) |
| pmap(lambda m: (m, ctlInfo.delete(m)), masters) |
| die("\n".join(emsg)) |
| |
| msg("Created. Elapsed %s." % humantime(etime - stime)) |
| finally: |
| if suspend: |
| prun(segments) |
| |
| ############ |
| def restoreSnapshot(name): |
| ctlInfo = ControlInfo.read(name) |
| if not ctlInfo: |
| die("Unable to find snapshot '%s'" % name) |
| |
| if not confirm("Restore snapshot '%s'.\nThis will shutdown the database." % name): |
| die("Aborted by user.") |
| |
| stime = datetime.datetime.now() |
| |
| # shut it down |
| runas('gpstop -af') |
| |
| snapshots = ctlInfo.snapshots[:] |
| random.shuffle(snapshots) |
| erows = filter(lambda (s,(rc,lines)): rc, |
| pmap(lambda s: (s, s.checkSnapshot()), snapshots)) |
| |
| if erows: |
| emsg = (["Snapshot failure"] + |
| [" Bad/missing snapshot of %s:%s" % s.fullname for (s, rc) in erows]) |
| die("\n".join(emsg)) |
| |
| if not confirm("\n".join(["\nContinue to *DELETE* database and restore snapshot.", |
| "THIS IS THE POINT OF NO RETURN."])): |
| die("Aborted by user.") |
| |
| erows = filter(lambda (s,rc): rc, |
| pmap(lambda s: (s, s.restoreSnapshot()), snapshots)) |
| if erows: |
| emsg = (["Restore failed:"] + |
| [" unable to restore %s:%s" % s.fullname for (s, rc) in erows]) |
| die("\n".join(emsg)) |
| |
| etime = datetime.datetime.now() |
| msg("Restored. Elapsed %s." % humantime(etime - stime)) |
| |
| |
| ############ |
| def infoSnapshot(name): |
| ctlInfo = ControlInfo.read(name) |
| if not ctlInfo: |
| die("Unable to find snapshot '%s'" % name) |
| msg('Snapshot name: ' + ctlInfo.name) |
| msg('Created : ' + str(ctlInfo.etime)) |
| msg('Atoms : %s:%s' % ctlInfo.snapshots[0].fullname) |
| for s in ctlInfo.snapshots[1:]: msg(" %s:%s" % s.fullname) |
| return ctlInfo |
| |
| ############ |
| def infoSnapshotDetailed(name): |
| ctlInfo = infoSnapshot(name) |
| msg('') |
| msg('Obtaining for detailed info ...') |
| snapshots = ctlInfo.snapshots[:] |
| random.shuffle(snapshots) |
| |
| erows = [] |
| for (s, (rc, lines)) in pmap(lambda s: (s, s.checkSnapshot()), snapshots): |
| if rc: erows.append( (s, (rc, lines))) |
| else: |
| for x in lines: msg(' [%s:%s info] ' % s.fullname + x.strip()) |
| |
| for (s, (rc, lines)) in erows: |
| for x in lines: msg(' [%s:%s error] ' % s.fullname + x.strip()) |
| |
| if erows: |
| die("Error(s) detected. Snapshot is not valid.") |
| |
| |
| ############ |
| def deleteSnapshot(name): |
| ctlInfo = ControlInfo.read(name) |
| if not ctlInfo: |
| msg("Snapshot '%s' does not exist" % name) |
| return # assume there isn't any snapshots available |
| |
| if not confirm("Delete snapshot '%s'" % name): |
| die("Aborted by user.") |
| |
| stime = datetime.datetime.now() |
| |
| snapshots = ctlInfo.snapshots[:] |
| random.shuffle(snapshots) |
| |
| erows = filter(lambda (s,rc): rc, |
| pmap(lambda s: (s, s.deleteSnapshot()), snapshots)) |
| if erows: |
| emsg = (["Delete failed:"] + |
| [" unable to delete %s:%s" % s.fullname for (s, rc) in erows]) |
| die("\n".join(emsg)) |
| |
| master_snapshots = filter(lambda s: s.seg.content == '-1', snapshots) |
| pmap(lambda m: (m.seg, ctlInfo.delete(m.seg)), master_snapshots) |
| etime = datetime.datetime.now() |
| msg("Deleted. Elapsed %s." % humantime(etime - stime)) |
| |
| |
| ############ |
| def listSnapshots(name): |
| ctlInfoList = ControlInfo.list() |
| if not ctlInfoList: |
| msg("No snapshot exists") |
| return |
| ctlInfoList.sort(lambda x,y: x.etime > y.etime) |
| for i in ctlInfoList: |
| if not name or name == i.name: |
| print '%s %s %s' % (i.type, str(i.etime), i.name) |
| |
| |
| ############ |
| def main(): |
| global MASTER_DATA_DIRECTORY |
| MASTER_DATA_DIRECTORY = strip_trailing_slash(MASTER_DATA_DIRECTORY) |
| parseCommandLine() |
| if GV.opt['-c']: createSnapshot(GV.snapname) |
| elif GV.opt['-r']: restoreSnapshot(GV.snapname) |
| elif GV.opt['-d']: deleteSnapshot(GV.snapname) |
| elif GV.opt['-i']: infoSnapshot(GV.snapname) |
| elif GV.opt['-I']: infoSnapshotDetailed(GV.snapname) |
| elif GV.opt['-l']: listSnapshots(GV.snapname) |
| else: |
| usage('Error: invalid flags and/or arguments') |
| |
| if __name__ == '__main__': |
| main() |