12 base="/srv/static.debian.org"
13 serialname = '.serial'
17 with open('/etc/static-clients.conf') as f:
20 if line == "": continue
21 if line.startswith('#'): continue
25 t = time.strftime("[%Y-%m-%d %H:%M:%S]", time.gmtime())
28 def stage1(pipes, status):
32 line = p.stdout.readline()
38 log("%s: failed with returncode %d"%(c,p.returncode))
42 log("%s >> %s"%(c, line))
43 if not line.startswith('[MSM]'): continue
44 kw = string.split(line, ' ', 2)[1]
46 if kw == 'ALREADY-CURRENT':
47 pipes[c].stdout.close()
48 pipes[c].stdin.close()
51 log("%s: already current"%(c,))
54 log("%s: said ALREADY-CURRENT but returncode %d"%(c,p.returncode))
57 elif kw == 'STAGE1-DONE':
58 log("%s: waiting"%(c,))
61 elif kw in ['STAGE1-START']:
64 log("%s: ignoring unknown line"%(c,))
66 def count_statuses(status):
70 if v not in cnt: cnt[v] = 1
74 def stage2(pipes, status, command):
76 if status[c] != 'waiting': continue
77 log("%s << %s"%(c, command))
78 pipes[c].stdin.write("%s\n"%(command,))
81 if status[c] != 'waiting': continue
84 (o, dummy) = p.communicate('')
85 for l in string.split(o, "\n"):
86 log("%s >> %s"%(c, l))
87 log("%s: returned %d"%(c, p.returncode))
89 def callout(component, serial):
90 log("Calling clients...")
94 args = ['ssh', '-o', 'BatchMode=yes', c, 'mirror', component, "%d"%(serial,)]
95 p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
97 status[c] = 'in-progress'
100 stage1(pipes, status)
102 cnt = count_statuses(status)
104 if 'failed' in cnt and cnt['failed'] >= 2:
105 log("%d clients failed, aborting..."%(cnt['failed'],))
106 stage2(pipes, status, 'abort')
109 failedmirrorsfile = os.path.join(base, 'master', component + "-failedmirrors")
111 log("WARNING: %d clients failed! Continuing anyway!"%(cnt['failed'],))
114 f = open(failedmirrorsfile, "w")
116 if status[c] == 'failed': f.write(c+"\n")
119 if os.path.exists(failedmirrorsfile): os.unlink(failedmirrorsfile)
123 stage2(pipes, status, 'go')
126 log("All clients up to date.")
131 def run_mirror(component):
133 basemaster = os.path.join(base, 'master')
134 componentdir = os.path.join(basemaster, component)
135 cur = componentdir + '-current-push'
136 live = componentdir + '-current-live'
137 tmpdir_new = tempfile.mkdtemp(prefix=component+'-live.new-', dir=basemaster); cleanup_dirs.append(tmpdir_new);
138 tmpdir_old = tempfile.mkdtemp(prefix=component+'-live.old-', dir=basemaster); cleanup_dirs.append(tmpdir_old);
139 os.chmod(tmpdir_new, 0755)
142 for p in (componentdir, live, tmpdir_new):
143 if not os.path.exists(p): os.mkdir(p, 0755)
144 fd = os.open(p, os.O_RDONLY)
145 log("Acquiring lock for %s(%d)."%(p,fd))
146 fcntl.flock(fd, fcntl.LOCK_EX)
148 log("All locks acquired.")
150 serialfile = os.path.join(componentdir, serialname)
152 with open(serialfile) as f: serial = int(f.read())
154 serial = int(time.time())
155 with open(serialfile, "w") as f: f.write("%d\n"%(serial,))
156 log("Serial is %s."%(serial,))
158 log("Populating %s."%(tmpdir_new,))
159 subprocess.check_call(['cp', '-al', os.path.join(componentdir, '.'), tmpdir_new])
161 if os.path.exists(cur):
162 log("Removing existing %s."%(cur,))
165 log("Renaming %s to %s."%(tmpdir_new, cur))
166 os.rename(tmpdir_new, cur)
168 proceed = callout(component, serial)
171 log("Moving %s aside."%(live,))
172 os.rename(live, os.path.join(tmpdir_old, 'old'))
173 log("Renaming %s to %s."%(cur, live))
176 shutil.rmtree(tmpdir_old)
177 if had_warnings: log("Done, with warnings.")
190 if len(sys.argv) != 2:
191 print >> sys.stderr, "Usage: %s <component>"%(sys.argv[0],)
193 component = sys.argv[1]
197 ok = run_mirror(component)
199 for p in cleanup_dirs:
200 if os.path.exists(p): shutil.rmtree(p)
206 # vim:set shiftwidth=2: