12 serialname = '.serial'
15 conffile = '/etc/staticsync.conf'
18 with open(conffile) as f:
21 if not line or line.startswith("#"): continue
22 (name, value) = line.split("=")
27 raise Exception("Configuration element '%s' not found in config file %s", key, conffile)
30 with open('/etc/static-clients.conf') as f:
33 if line == "": continue
34 if line.startswith('#'): continue
38 t = time.strftime("[%Y-%m-%d %H:%M:%S]", time.gmtime())
41 def stage1(pipes, status, clients):
45 line = p.stdout.readline()
51 log("%s: failed with returncode %d"%(c,p.returncode))
55 log("%s >> %s"%(c, line))
56 if not line.startswith('[MSM]'): continue
57 kw = string.split(line, ' ', 2)[1]
59 if kw == 'ALREADY-CURRENT':
60 pipes[c].stdout.close()
61 pipes[c].stdin.close()
64 log("%s: already current"%(c,))
67 log("%s: said ALREADY-CURRENT but returncode %d"%(c,p.returncode))
70 elif kw == 'STAGE1-DONE':
71 log("%s: waiting"%(c,))
74 elif kw in ['STAGE1-START']:
77 log("%s: ignoring unknown line"%(c,))
79 def count_statuses(status):
83 if v not in cnt: cnt[v] = 1
87 def stage2(pipes, status, command, clients):
89 if status[c] != 'waiting': continue
90 log("%s << %s"%(c, command))
91 pipes[c].stdin.write("%s\n"%(command,))
94 if status[c] != 'waiting': continue
97 (o, dummy) = p.communicate('')
98 for l in string.split(o, "\n"):
99 log("%s >> %s"%(c, l))
100 log("%s: returned %d"%(c, p.returncode))
102 def callout(component, serial, clients):
103 log("Calling clients...")
107 args = ['ssh', '-o', 'BatchMode=yes', c, 'mirror', component, "%d"%(serial,)]
108 p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
110 status[c] = 'in-progress'
113 stage1(pipes, status, clients)
115 cnt = count_statuses(status)
117 if 'failed' in cnt and cnt['failed'] >= 2:
118 log("%d clients failed, aborting..."%(cnt['failed'],))
119 stage2(pipes, status, 'abort', clients)
122 failedmirrorsfile = os.path.join(config['base'], 'master', component + "-failedmirrors")
124 log("WARNING: %d clients failed! Continuing anyway!"%(cnt['failed'],))
127 f = open(failedmirrorsfile, "w")
129 if status[c] == 'failed': f.write(c+"\n")
132 if os.path.exists(failedmirrorsfile): os.unlink(failedmirrorsfile)
136 stage2(pipes, status, 'go', clients)
139 log("All clients up to date.")
142 def load_component_info(component):
143 with open('/etc/static-components.conf') as f:
145 if line.startswith('#'): continue
146 field = line.strip().split()
147 if len(field) < 4: continue
148 if field[1] != component: continue
150 meta['master'] = field[0]
151 meta['sourcehost'] = field[2]
152 meta['sourcedir'] = field[3]
153 meta['extrapushhosts'] = set(field[4].split(',')) if len(field) > 4 else set()
154 meta['extraignoreclients'] = set(field[5].split(',')) if len(field) > 5 else set()
160 def run_mirror(component):
161 meta = load_component_info(component)
163 log("Component %s not found."%(component,))
165 clients = allclients - meta['extraignoreclients']
168 basemaster = os.path.join(config['base'], 'master')
169 componentdir = os.path.join(basemaster, component)
170 cur = componentdir + '-current-push'
171 live = componentdir + '-current-live'
172 tmpdir_new = tempfile.mkdtemp(prefix=component+'-live.new-', dir=basemaster); cleanup_dirs.append(tmpdir_new);
173 tmpdir_old = tempfile.mkdtemp(prefix=component+'-live.old-', dir=basemaster); cleanup_dirs.append(tmpdir_old);
174 os.chmod(tmpdir_new, 0755)
177 for p in (componentdir, live, tmpdir_new):
178 if not os.path.exists(p): os.mkdir(p, 0755)
179 fd = os.open(p, os.O_RDONLY)
180 log("Acquiring lock for %s(%d)."%(p,fd))
181 fcntl.flock(fd, fcntl.LOCK_EX)
183 log("All locks acquired.")
185 serialfile = os.path.join(componentdir, serialname)
187 with open(serialfile) as f: serial = int(f.read())
189 serial = int(time.time())
190 with open(serialfile, "w") as f: f.write("%d\n"%(serial,))
191 log("Serial is %s."%(serial,))
193 log("Populating %s."%(tmpdir_new,))
194 subprocess.check_call(['cp', '-al', os.path.join(componentdir, '.'), tmpdir_new])
196 if os.path.exists(cur):
197 log("Removing existing %s."%(cur,))
200 log("Renaming %s to %s."%(tmpdir_new, cur))
201 os.rename(tmpdir_new, cur)
203 proceed = callout(component, serial, clients)
206 log("Moving %s aside."%(live,))
207 os.rename(live, os.path.join(tmpdir_old, 'old'))
208 log("Renaming %s to %s."%(cur, live))
211 shutil.rmtree(tmpdir_old)
212 if had_warnings: log("Done, with warnings.")
225 if len(sys.argv) != 2:
226 print >> sys.stderr, "Usage: %s <component>"%(sys.argv[0],)
228 component = sys.argv[1]
232 ok = run_mirror(component)
234 for p in cleanup_dirs:
235 if os.path.exists(p): shutil.rmtree(p)
241 # vim:set shiftwidth=2: