]> scripts.mit.edu Git - wizard.git/blob - wizard/command/massmigrate.py
Refactor get*() to .() with property decorator.
[wizard.git] / wizard / command / massmigrate.py
1 import optparse
2 import logging
3 import os
4 import os.path
5 import pwd
6
7 import wizard
8 from wizard import deploy
9 from wizard import util
10 from wizard import shell
11 from wizard import sset
12 from wizard.command import _command
13 from wizard.command import migrate
14
15 def main(argv, baton):
16     options, args = parse_args(argv, baton)
17     app = args[0]
18     base_args = calculate_base_args(options)
19     sh = make_shell(options)
20     seen = make_serialized_set(options)
21     my_uid = os.getuid() # to see if we have root
22     # loop stuff
23     errors = {}
24     uid = None
25     for line in deploy.getInstallLines(options.versions_path):
26         # validate and filter the deployments
27         try:
28             d = deploy.Deployment.parse(line)
29         except deploy.DeploymentParseError, deploy.NoSuchApplication:
30             continue
31         name = d.application.name
32         if name != app: continue
33         if d.location in seen:
34             continue
35         # security check: see if the user's directory is the prefix of what
36         if not my_uid:
37             uid = util.get_dir_uid(d.location)
38             real = os.path.realpath(d.location)
39             if not real.startswith(pwd.getpwuid(uid).pw_dir + "/"):
40                 logging.error("Security check failed, owner of deployment and owner of home directory mismatch for %s" % d.location)
41                 continue
42         # actual meat
43         def make_on_pair(d):
44             def on_success(stdout, stderr):
45                 seen.add(d.location)
46             def on_error(e):
47                 if e.name == "wizard.command.migrate.AlreadyMigratedError":
48                     seen.add(d.location)
49                     logging.info("Skipped already migrated %s" % d.location)
50                 else:
51                     name = e.name
52                     if name not in errors: errors[name] = []
53                     errors[name].append(d)
54                     logging.error("%s in %s" % (name, d.location))
55             return (on_success, on_error)
56         on_success, on_error = make_on_pair(d)
57         sh.wait() # wait for a parallel processing slot to be available
58         sh.callAsUser(shell.wizard, "migrate", d.location, *base_args,
59                       uid=uid, on_success=on_success, on_error=on_error)
60     sh.join()
61     for name, deploys in errors.items():
62         logging.warning("%s from %d installs" % (name, len(deploys)))
63
64 def parse_args(argv, baton):
65     usage = """usage: %prog massmigrate [ARGS] APPLICATION
66
67 Mass migrates an application to the new repository format.
68 Essentially equivalent to running '%prog migrate' on all
69 autoinstalls for a particular application found by parallel-find,
70 but with advanced reporting.
71
72 When doing an actual run, it is recommended to use --seen to
73 be able to resume gracefully (without it, massmigrate must
74 stat every install to find out if it migrated it yet).
75
76 This command is intended to be run as root on a server with
77 the scripts AFS patch.  You may run it as an unpriviledged
78 user for testing purposes, but then you MUST NOT run this on
79 untrusted repositories."""
80     parser = _command.WizardOptionParser(usage)
81     parser.add_option("--no-parallelize", dest="no_parallelize", action="store_true",
82             default=False, help="Turn off parallelization")
83     parser.add_option("--dry-run", dest="dry_run", action="store_true",
84             default=False, help="Print commands that would be run. Implies --no-parallelize")
85     parser.add_option("--max", dest="max",
86             default=10, help="Maximum subprocesses to run concurrently")
87     parser.add_option("--seen", dest="seen",
88             default=None, help="File to read/write paths of already processed installs. These will be skipped.")
89     baton.push(parser, "versions_path")
90     options, args, = parser.parse_all(argv)
91     if len(args) > 1:
92         parser.error("too many arguments")
93     elif not args:
94         parser.error("must specify application to migrate")
95     if options.dry_run:
96         options.no_parallelize = True
97     return options, args
98
99 def calculate_base_args(options):
100     base_args = _command.makeBaseArgs(options, dry_run="--dry-run")
101     if not options.debug:
102         base_args.append("--quiet")
103     return base_args
104
105 def make_shell(options):
106     if options.no_parallelize:
107         sh = shell.DummyParallelShell()
108     else:
109         sh = shell.ParallelShell(max=int(options.max))
110     return sh
111
112 def make_serialized_set(options):
113     if options.seen:
114         seen = sset.SerializedSet(options.seen)
115     else:
116         seen = sset.DummySerializedSet()
117     return seen