From 8939cb421d77e7469160f38381cf2106974e5ba8 Mon Sep 17 00:00:00 2001 From: "Edward Z. Yang" Date: Mon, 12 Oct 2009 16:32:50 -0400 Subject: [PATCH] More bugfixes from running live. * Add a few more wgCacheEpoch resolutions * Make prepareMerge work for \r\r\r\n * Add flush and fails to Report. This needs further work. * Require reason for wizard blacklist * Cleanup /dev/shm/wizard at the start of mass runs * Special case a few more errors * Report reason for blacklisting in stdout. * Add global finally for removing /dev/shm directories * Add QuotaParseError Signed-off-by: Edward Z. Yang --- wizard/app/mediawiki.py | 29 +++- wizard/command/__init__.py | 26 ++- wizard/command/blacklist.py | 11 +- wizard/command/mass_upgrade.py | 39 +++-- wizard/command/upgrade.py | 286 +++++++++++++++++---------------- wizard/scripts.py | 16 +- 6 files changed, 241 insertions(+), 166 deletions(-) diff --git a/wizard/app/mediawiki.py b/wizard/app/mediawiki.py index ceae53a..600eccd 100644 --- a/wizard/app/mediawiki.py +++ b/wizard/app/mediawiki.py @@ -73,6 +73,25 @@ $wgUseTeX = false; # order of these rules is important (""" <<<<<<< +$configdate = gmdate( 'YmdHis', @filemtime( __FILE__ ) ); +$wgCacheEpoch = max( $wgCacheEpoch, $configdate ); +***1*** +?> +======= +$wgCacheEpoch = max( $wgCacheEpoch, gmdate( 'YmdHis', @filemtime( __FILE__ ) ) ); +>>>>>>> +""", [0, 1]), + (""" +<<<<<<< +$configdate = gmdate( 'YmdHis', @filemtime( __FILE__ ) ); +$wgCacheEpoch = max( $wgCacheEpoch, $configdate ); +***1*** +======= +$wgCacheEpoch = max( $wgCacheEpoch, gmdate( 'YmdHis', @filemtime( __FILE__ ) ) ); +>>>>>>> +""", [0, 1]), + (""" +<<<<<<< ?> ======= # When you make changes to this configuration file, this will make @@ -138,9 +157,13 @@ class Application(deploy.Application): def prepareMerge(self, dir): with util.ChangeDirectory(dir): # XXX: this should be factored out - contents = open("LocalSettings.php", "r").read() - new_contents = "\n".join(contents.splitlines()) - if contents != new_contents: + old_contents = open("LocalSettings.php", "r").read() + contents = old_contents + while "\r\n" in contents: + contents = contents.replace("\r\n", "\n") + contents = contents.replace("\r", "\n") + if contents != old_contents: + logging.info("Converted LocalSettings.php to UNIX file endings") open("LocalSettings.php", "w").write(contents) def resolveConflicts(self, dir): # XXX: this is pretty generic diff --git a/wizard/command/__init__.py b/wizard/command/__init__.py index e4de0cf..c020922 100644 --- a/wizard/command/__init__.py +++ b/wizard/command/__init__.py @@ -131,10 +131,19 @@ def create_logdir(log_dir): class Report(object): #: Set of indices that should be skipped skip = None - def __init__(self, names, fobjs, skip): + #: Dict of append names to counts. You should manually increment these as necessary + fails = None + #: Names of the files objects + names = None + def __init__(self, names, fobjs, skip, fails): self.skip = skip + self.names = names + self.fails = fails for name, fobj in zip(names, fobjs): setattr(self, name, fobj) + def flush(self): + for n in self.names: + getattr(self, n).flush() def report_files(log_dir, names): return [os.path.join(os.path.join(log_dir, "%s.txt" % x)) for x in names] @@ -144,7 +153,7 @@ def read_reports(log_dir, names): Reads a number of reports files. The return value is a :class:`Report` object with attributes that are open file objects that correspond to ``names``. """ - return Report(names, [(os.path.exists(f) and open(f, "r") or cStringIO.StringIO()) for f in report_files(log_dir, names)], set()) + return Report(names, [(os.path.exists(f) and open(f, "r") or cStringIO.StringIO()) for f in report_files(log_dir, names)], set(), {}) def open_reports(log_dir, names=('warnings', 'errors'), redo=False, append_names=()): """ @@ -158,15 +167,20 @@ def open_reports(log_dir, names=('warnings', 'errors'), redo=False, append_names should be skipped. """ skip = set() + fails = {} if not redo: rr = read_reports(log_dir, append_names) - def build_set(skip, fobj): - skip |= set(int(l[1:5]) for l in fobj.read().splitlines()) + def build_set(skip, fails, name, fobj): + lines = fobj.read().strip().splitlines() + skip |= set(int(l[1:5]) for l in lines) + fails[name] = len(lines) fobj.close() for name in append_names: - build_set(skip, getattr(rr, name)) + build_set(skip, fails, name, getattr(rr, name)) else: names += append_names + for name in append_names: + fails[name] = 0 append_names = () files = report_files(log_dir, names) append_files = report_files(log_dir, append_names) @@ -183,7 +197,7 @@ def open_reports(log_dir, names=('warnings', 'errors'), redo=False, append_names for f in append_files: if os.path.exists(f): shutil.copy(f, rundir) - return Report(names + append_names, [open(f, "w") for f in files] + [open(f, "a") for f in append_files], skip) + return Report(names + append_names, [open(f, "w") for f in files] + [open(f, "a") for f in append_files], skip, fails) class NullLogHandler(logging.Handler): """Log handler that doesn't do anything""" diff --git a/wizard/command/blacklist.py b/wizard/command/blacklist.py index 36b2a98..bfed964 100644 --- a/wizard/command/blacklist.py +++ b/wizard/command/blacklist.py @@ -8,23 +8,22 @@ from wizard import command, deploy, git, shell, util def main(argv, baton): options, args = parse_args(argv, baton) - if not args: - reason = "" - else: - reason = args[0] + reason = args[0] sh = shell.Shell() if os.path.exists(".git/WIZARD_REPO"): util.chdir(sh.eval('git', 'config', 'remote.origin.url')) open('.scripts/blacklisted', 'w').write(reason + "\n") def parse_args(argv, baton): - usage = """usage: %prog blacklist [ARGS] [REASON] + usage = """usage: %prog blacklist [ARGS] REASON Touches .scripts/blacklisted so that we don't attempt to upgrade the script in the future.""" parser = command.WizardOptionParser(usage) options, args = parser.parse_all(argv) - if len(args) > 1: + if len(args) > 2: parser.error("too many arguments") + if len(args) < 1: + parser.error("must specify reason") return options, args diff --git a/wizard/command/mass_upgrade.py b/wizard/command/mass_upgrade.py index 80dd2a6..c39a852 100644 --- a/wizard/command/mass_upgrade.py +++ b/wizard/command/mass_upgrade.py @@ -8,6 +8,7 @@ import errno import time import itertools import sys +import shutil import wizard from wizard import deploy, util, scripts, shell, sset, command @@ -20,19 +21,22 @@ def main(argv, baton): command.create_logdir(options.log_dir) seen = sset.make(options.seen) is_root = not os.getuid() - report = command.open_reports(options.log_dir, ('lookup', 'warnings', 'errors'), options.redo, ('merge', 'verify')) + report = command.open_reports(options.log_dir, ('lookup', 'warnings', 'errors'), + options.redo, ('merge', 'verify', 'backup_failure', 'blacklisted')) # loop stuff errors = {} i = 0 - fails = { - "merge": 0, - "verify": 0, - } deploys = deploy.parse_install_lines(app, options.versions_path, user=options.user) requested_deploys = itertools.islice(deploys, options.limit) + # clean up /dev/shm/wizard + if os.path.exists("/dev/shm/wizard"): + shutil.rmtree("/dev/shm/wizard") + os.mkdir("/dev/shm/wizard") + os.chmod("/dev/shm/wizard", 0o777) try: for i, d in enumerate(requested_deploys, 1): report.lookup.write("%04d %s\n" % (i, d.location)) + report.flush() # check if we want to punt due to --limit if d.location in seen: continue @@ -57,6 +61,7 @@ def main(argv, baton): report.lookup.write("[%04d] %s\n" % (i, d.location)) logging.warning("[%04d] Warnings at [%s]:\n%s" % (i, d.location, stderr)) seen.add(d.location) + report.flush() def on_error(e): if e.name == "AlreadyUpgraded": seen.add(d.location) @@ -66,7 +71,14 @@ def main(argv, baton): conflicts, _, tmpdir = e.stdout.rstrip().partition(" ") logging.warning("[%04d] Conflicts in %d files: resolve at [%s], source at [%s]" % (i, int(conflicts), tmpdir, d.location)) report.merge.write("[%04d] %s %d %s\n" % (i, tmpdir, int(conflicts), d.location)) - fails['merge'] += 1 + report.fails['merge'] += 1 + elif e.name == "BlacklistedError": + _, _, reason = e.stdout.rstrip().partition(" ") + reason = reason.replace("\n", " ") + shortmsg = "[%04d] %s %s\n" % (i, d.location, reason) + report.blacklisted.write(shortmsg) + report.fails['blacklisted'] += 1 + logging.warning("[%04d] Blacklisted because of '%s' at %s" % (i, reason, d.location)) else: name = e.name if name == "WebVerificationError": @@ -79,13 +91,18 @@ def main(argv, baton): # it's a really common error logging.info("[%04d] Could not verify application at %s" % (i, url)) report.verify.write("[%04d] %s\n" % (i, url)) - fails['verify'] += 1 + report.fails['verify'] += 1 else: if name not in errors: errors[name] = [] errors[name].append(d) msg = "[%04d] %s in %s" % (i, name, d.location) logging.error(msg) report.errors.write(msg + "\n") + shortmsg = "[%04d] %s\n" % (i, d.location) + if name == "BackupFailure": + report.backup_failure.write(shortmsg) + report.fails['backup_failure'] += 1 + report.flush() return (on_success, on_error) on_success, on_error = make_on_pair(d, i) sh.call("wizard", "upgrade", d.location, *child_args, @@ -97,10 +114,10 @@ def main(argv, baton): logging.warning("%s from %d installs" % (name, len(deploys))) def printPercent(description, number, total): logging.warning("%d out of %d installs (%.1f%%) had %s" % (number, total, float(number)/total*100, description)) - if fails['merge']: - printPercent("merge conflicts", fails['merge'], i) - if fails['verify']: - printPercent("web verification failure", fails['verify'], i) + if report.fails['merge']: + printPercent("merge conflicts", report.fails['merge'], i) + if report.fails['verify']: + printPercent("web verification failure", report.fails['verify'], i) def parse_args(argv, baton): usage = """usage: %prog mass-upgrade [ARGS] APPLICATION diff --git a/wizard/command/upgrade.py b/wizard/command/upgrade.py index f56d00b..8f25920 100644 --- a/wizard/command/upgrade.py +++ b/wizard/command/upgrade.py @@ -21,136 +21,137 @@ def main(argv, baton): shell.drop_priviledges(dir, options.log_file) util.chdir(dir) if os.path.exists(".scripts/blacklisted"): - raise BlacklistedError() + reason = open(".scripts/blacklisted").read() + print "-1 " + reason + raise BlacklistedError(reason) sh = shell.Shell() util.set_git_env() use_shm = False # if you are running --continue, this is guaranteed to be False - if options.continue_: - temp_wc_dir = os.getcwd() - user_commit, next_commit = open(".git/WIZARD_PARENTS", "r").read().split() - repo = open(".git/WIZARD_REPO", "r").read() - version = open(".git/WIZARD_UPGRADE_VERSION", "r").read() - if not options.log_file and os.path.exists(".git/WIZARD_LOG_FILE"): - options.log_file = open(".git/WIZARD_LOG_FILE", "r").read() - # reload logging - command.addFileLogger(options.log_file, options.debug) - logging.info("Continuing upgrade...") - util.chdir(sh.eval("git", "config", "remote.origin.url")) - d = deploy.Deployment(".") - try: - sh.call("git", "status") - raise LocalChangesError() - except shell.CallError: - pass - else: - d = deploy.Deployment(".") - d.verify() - d.verifyTag(options.srv_path) - d.verifyGit(options.srv_path) - d.verifyConfigured() - d.verifyVersion() - if not options.dry_run: - d.verifyWeb() - repo = d.application.repository(options.srv_path) - version = calculate_newest_version(sh, repo) - if version == d.app_version.scripts_tag and not options.force: - # don't log this error - # XXX: maybe we should build this in as a flag to add - # to exceptions w/ our exception handler - sys.stderr.write("Traceback:\n (n/a)\nAlreadyUpgraded\n") - sys.exit(1) - logging.info("Upgrading %s" % os.getcwd()) - kib_usage, kib_limit = scripts.get_quota_usage_and_limit() - if kib_limit is not None and (kib_limit - kib_usage) < kib_buffer: # 10 mebibytes - raise QuotaTooLow - if not options.dry_run: - perform_pre_commit(sh) - # If /dev/shm exists, it's a tmpfs and we can use it - # to do a fast git merge. Don't forget to move it to - # /tmp if it fails. - # XXX: git merge-tree is another possibility for - # reducing filesystem interactions, and also probably - # works better with extra merging functionality. However, - # I don't know how I would put the results back in the - # working tree. - use_shm = os.path.exists("/dev/shm") - temp_dir, temp_wc_dir = perform_tmp_clone(sh, use_shm) - with util.ChangeDirectory(temp_wc_dir): - sh.call("git", "remote", "add", "scripts", repo) - sh.call("git", "fetch", "-q", "scripts") - user_commit, next_commit = calculate_parents(sh, version) - # save variables so that --continue will work - # yeah yeah no trailing newline whatever - open(".git/WIZARD_REPO", "w").write(repo) - open(".git/WIZARD_UPGRADE_VERSION", "w").write(version) - open(".git/WIZARD_PARENTS", "w").write("%s\n%s" % (user_commit, next_commit)) - open(".git/WIZARD_SIZE", "w").write(str(scripts.get_disk_usage())) - if options.log_file: - open(".git/WIZARD_LOG_FILE", "w").write(options.log_file) - perform_merge(sh, repo, d, version, use_shm, kib_limit and kib_limit - kib_usage or None) - # variables: version, user_commit, next_commit, temp_wc_dir - with util.ChangeDirectory(temp_wc_dir): - try: - sh.call("git", "status") - sh.call("git", "commit", "-m", "throw-away commit") - except shell.CallError: - pass - message = make_commit_message(version) - new_tree = sh.eval("git", "rev-parse", "HEAD^{tree}") - final_commit = sh.eval("git", "commit-tree", new_tree, - "-p", user_commit, "-p", next_commit, input=message, log=True) - # a master branch may not necessarily exist if the user - # was manually installed to an earlier version - try: - sh.call("git", "checkout", "-q", "-b", "master", "--") - except shell.CallError: - sh.call("git", "checkout", "-q", "master", "--") - sh.call("git", "reset", "-q", "--hard", final_commit) - # This is a quick sanity check to make sure we didn't completely - # mess up the merge - d.verifyVersion() - # Till now, all of our operations were in a tmp sandbox. - if options.dry_run: - logging.info("Dry run, bailing. See results at %s" % temp_wc_dir) - return - # Ok, now we have to do a crazy complicated dance to see if we're - # going to have enough quota to finish what we need - pre_size = int(open(os.path.join(temp_wc_dir, ".git/WIZARD_SIZE"), "r").read()) - post_size = scripts.get_disk_usage(temp_wc_dir) - kib_usage, kib_limit = scripts.get_quota_usage_and_limit() - backup = d.backup(options) - if kib_limit is not None and (kib_limit - kib_usage) - (post_size - pre_size) / 1024 < kib_buffer: - shutil.rmtree(os.path.join(".scripts/backups", sh.eval("wizard", "restore").splitlines()[0])) - raise QuotaTooLow - # XXX: frob .htaccess to make site inaccessible - with util.IgnoreKeyboardInterrupts(): - with util.LockDirectory(".scripts-upgrade-lock"): - # git merge (which performs a fast forward) - sh.call("git", "pull", "-q", temp_wc_dir, "master") - # after the pull is successful, the directory now - # has the objects for this commit, so we can safely - # nuke the shm directory. We refrain from nuking the - # tmp directory in case we messed up the merge resolution - # and want to be able to use it again. - if use_shm: - shutil.rmtree(temp_dir) - version_obj = distutils.version.LooseVersion(version.partition('-')[2]) + temp_dir = None + try: + if options.continue_: + temp_wc_dir = os.getcwd() + user_commit, next_commit = open(".git/WIZARD_PARENTS", "r").read().split() + repo = open(".git/WIZARD_REPO", "r").read() + version = open(".git/WIZARD_UPGRADE_VERSION", "r").read() + if not options.log_file and os.path.exists(".git/WIZARD_LOG_FILE"): + options.log_file = open(".git/WIZARD_LOG_FILE", "r").read() + # reload logging + command.addFileLogger(options.log_file, options.debug) + logging.info("Continuing upgrade...") + util.chdir(sh.eval("git", "config", "remote.origin.url")) + d = deploy.Deployment(".") try: - # run update script - d.upgrade(version_obj, options) + sh.call("git", "status") + raise LocalChangesError() + except shell.CallError: + pass + else: + d = deploy.Deployment(".") + d.verify() + d.verifyTag(options.srv_path) + d.verifyGit(options.srv_path) + d.verifyConfigured() + d.verifyVersion() + if not options.dry_run: d.verifyWeb() - except app.UpgradeFailure: - logging.warning("Upgrade failed: rolling back") - perform_restore(d, backup) - raise - except deploy.WebVerificationError as e: - logging.warning("Web verification failed: rolling back") - perform_restore(d, backup) - raise app.UpgradeVerificationFailure(e.contents) - # XXX: frob .htaccess to make site accessible - # to do this, check if .htaccess changed, first. Upgrade - # process might have frobbed it. Don't be - # particularly worried if the segment disappeared + repo = d.application.repository(options.srv_path) + version = calculate_newest_version(sh, repo) + if version == d.app_version.scripts_tag and not options.force: + # don't log this error + # XXX: maybe we should build this in as a flag to add + # to exceptions w/ our exception handler + sys.stderr.write("Traceback:\n (n/a)\nAlreadyUpgraded\n") + sys.exit(1) + logging.info("Upgrading %s" % os.getcwd()) + kib_usage, kib_limit = scripts.get_quota_usage_and_limit() + if kib_limit is not None and (kib_limit - kib_usage) < kib_buffer: # 10 mebibytes + raise QuotaTooLow + if not options.dry_run: + perform_pre_commit(sh) + # If /dev/shm exists, it's a tmpfs and we can use it + # to do a fast git merge. Don't forget to move it to + # /tmp if it fails. + # XXX: git merge-tree is another possibility for + # reducing filesystem interactions, and also probably + # works better with extra merging functionality. However, + # I don't know how I would put the results back in the + # working tree. + if not options.dry_run: + use_shm = os.path.exists("/dev/shm") + temp_dir, temp_wc_dir = perform_tmp_clone(sh, use_shm) + with util.ChangeDirectory(temp_wc_dir): + sh.call("git", "remote", "add", "scripts", repo) + sh.call("git", "fetch", "-q", "scripts") + user_commit, next_commit = calculate_parents(sh, version) + # save variables so that --continue will work + # yeah yeah no trailing newline whatever + open(".git/WIZARD_REPO", "w").write(repo) + open(".git/WIZARD_UPGRADE_VERSION", "w").write(version) + open(".git/WIZARD_PARENTS", "w").write("%s\n%s" % (user_commit, next_commit)) + open(".git/WIZARD_SIZE", "w").write(str(scripts.get_disk_usage())) + if options.log_file: + open(".git/WIZARD_LOG_FILE", "w").write(options.log_file) + perform_merge(sh, repo, d, version, use_shm, kib_limit and kib_limit - kib_usage or None) + # variables: version, user_commit, next_commit, temp_wc_dir + with util.ChangeDirectory(temp_wc_dir): + try: + sh.call("git", "status") + sh.call("git", "commit", "-m", "throw-away commit") + except shell.CallError: + pass + message = make_commit_message(version) + new_tree = sh.eval("git", "rev-parse", "HEAD^{tree}") + final_commit = sh.eval("git", "commit-tree", new_tree, + "-p", user_commit, "-p", next_commit, input=message, log=True) + # a master branch may not necessarily exist if the user + # was manually installed to an earlier version + try: + sh.call("git", "checkout", "-q", "-b", "master", "--") + except shell.CallError: + sh.call("git", "checkout", "-q", "master", "--") + sh.call("git", "reset", "-q", "--hard", final_commit) + # This is a quick sanity check to make sure we didn't completely + # mess up the merge + d.verifyVersion() + # Till now, all of our operations were in a tmp sandbox. + if options.dry_run: + logging.info("Dry run, bailing. See results at %s" % temp_wc_dir) + return + # Ok, now we have to do a crazy complicated dance to see if we're + # going to have enough quota to finish what we need + pre_size = int(open(os.path.join(temp_wc_dir, ".git/WIZARD_SIZE"), "r").read()) + post_size = scripts.get_disk_usage(temp_wc_dir) + kib_usage, kib_limit = scripts.get_quota_usage_and_limit() + backup = d.backup(options) + if kib_limit is not None and (kib_limit - kib_usage) - (post_size - pre_size) / 1024 < kib_buffer: + shutil.rmtree(os.path.join(".scripts/backups", sh.eval("wizard", "restore").splitlines()[0])) + raise QuotaTooLow + # XXX: frob .htaccess to make site inaccessible + with util.IgnoreKeyboardInterrupts(): + with util.LockDirectory(".scripts-upgrade-lock"): + # git merge (which performs a fast forward) + sh.call("git", "pull", "-q", temp_wc_dir, "master") + version_obj = distutils.version.LooseVersion(version.partition('-')[2]) + try: + # run update script + d.upgrade(version_obj, options) + d.verifyWeb() + except app.UpgradeFailure: + logging.warning("Upgrade failed: rolling back") + perform_restore(d, backup) + raise + except deploy.WebVerificationError as e: + logging.warning("Web verification failed: rolling back") + perform_restore(d, backup) + raise app.UpgradeVerificationFailure(e.contents) + # XXX: frob .htaccess to make site accessible + # to do this, check if .htaccess changed, first. Upgrade + # process might have frobbed it. Don't be + # particularly worried if the segment disappeared + finally: + if use_shm and temp_dir and os.path.exists(temp_dir): + shutil.rmtree(temp_dir) def perform_restore(d, backup): # You don't want d.restore() because it doesn't perform @@ -245,21 +246,24 @@ def perform_merge(sh, repo, d, version, use_shm, kib_avail): return # XXX: Maybe should recalculate conflicts logging.info("Conflict info:\n" + sh.eval("git", "diff")) - if use_shm: - # Keeping all of our autoinstalls in shared memory is - # a recipe for disaster, so let's move them to slightly - # less volatile storage (a temporary directory) - os.chdir(tempfile.gettempdir()) - newdir = tempfile.mkdtemp(prefix="wizard") - # shutil, not os; at least on Ubuntu os.move fails - # with "[Errno 18] Invalid cross-device link" - shutil.move(curdir, newdir) - shutil.rmtree(os.path.dirname(curdir)) - curdir = os.path.join(newdir, "repo") - os.chdir(curdir) + curdir = mv_shm_to_tmp(curdir, use_shm) print "%d %s" % (conflicts, curdir) raise MergeFailed +def mv_shm_to_tmp(curdir, use_shm): + if not use_shm: return curdir + # Keeping all of our autoinstalls in shared memory is + # a recipe for disaster, so let's move them to slightly + # less volatile storage (a temporary directory) + os.chdir(tempfile.gettempdir()) + newdir = tempfile.mkdtemp(prefix="wizard") + # shutil, not os; at least on Ubuntu os.move fails + # with "[Errno 18] Invalid cross-device link" + shutil.move(curdir, newdir) + shutil.rmtree(os.path.dirname(curdir)) + curdir = os.path.join(newdir, "repo") + return curdir + def parse_args(argv, baton): usage = """usage: %prog upgrade [ARGS] [DIR] @@ -320,9 +324,15 @@ with git rerere to remember merge resolutions (XXX: not sure if this actually works).""" class BlacklistedError(Error): + #: Reason why the autoinstall was blacklisted + reason = None + def __init__(self, reason): + self.reason = reason def __str__(self): return """ ERROR: This autoinstall was manually blacklisted against errors; if the user has not been notified of this, please send them -mail.""" +mail. + +The reason was: %s""" % self.reason diff --git a/wizard/scripts.py b/wizard/scripts.py index 4e0aadd..a81f539 100644 --- a/wizard/scripts.py +++ b/wizard/scripts.py @@ -1,6 +1,7 @@ import os import shlex +import wizard from wizard import shell, util def get_sql_credentials(vars=None): @@ -71,8 +72,11 @@ def get_quota_usage_and_limit(dir=None): result = sh.eval("vos", "examine", "-id", volume, "-cell", cell).splitlines() except shell.CallError: return (0, None) - usage = int(result[0].split()[3]) - limit = int(result[3].split()[1]) # XXX: FRAGILE + try: + usage = int(result[0].split()[3]) + limit = int(result[3].split()[1]) # XXX: FRAGILE + except ValueError: + raise QuotaParseError("vos examine output was:\n\n" + "\n".join(result)) return (usage, limit) # XXX: Possibly in the wrong module @@ -84,3 +88,11 @@ def get_disk_usage(dir=None): if dir is None: dir = os.getcwd() return int(shell.Shell().eval("du", "--exclude=.git", "-s", dir).split()[0]) +class QuotaParseError(wizard.Error): + def __init__(self, msg): + self.msg = msg + def __str__(self): + return """ + +ERROR: Could not parse quota. %s +""" % self.msg -- 2.45.0