]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - misc/coding_tools/make-canary-files.py
misc/coding_tools/make-canary-files.py: fix a suspicious capture reported by check...
[tahoe-lafs/tahoe-lafs.git] / misc / coding_tools / make-canary-files.py
1 #!/usr/bin/env python
2
3 """
4 Given a list of nodeids and a 'convergence' file, create a bunch of files
5 that will (when encoded at k=1,N=1) be uploaded to specific nodeids.
6
7 Run this as follows:
8
9  make-canary-files.py -c PATH/TO/convergence -n PATH/TO/nodeids -k 1 -N 1
10
11 It will create a directory named 'canaries', with one file per nodeid named
12 '$NODEID-$NICKNAME.txt', that contains some random text.
13
14 The 'nodeids' file should contain one base32 nodeid per line, followed by the
15 optional nickname, like:
16
17 ---
18 5yyqu2hbvbh3rgtsgxrmmg4g77b6p3yo  server12
19 vb7vm2mneyid5jbyvcbk2wb5icdhwtun  server13
20 ...
21 ---
22
23 The resulting 'canaries/5yyqu2hbvbh3rgtsgxrmmg4g77b6p3yo-server12.txt' file
24 will, when uploaded with the given (convergence,k,N) pair, have its first
25 share placed on the 5yyq/server12 storage server. If N>1, the other shares
26 will be placed elsewhere, of course.
27
28 This tool can be useful to construct a set of 'canary' files, which can then
29 be uploaded to storage servers, and later downloaded to test a grid's health.
30 If you are able to download the canary for server12 via some tahoe node X,
31 then the following properties are known to be true:
32
33  node X is running, and has established a connection to server12
34  server12 is running, and returning data for at least the given file
35
36 Using k=1/N=1 creates a separate test for each server. The test process is
37 then to download the whole directory of files (perhaps with a t=deep-check
38 operation).
39
40 Alternatively, you could upload with the usual k=3/N=10 and then move/delete
41 shares to put all N shares on a single server.
42
43 Note that any changes to the nodeid list will affect the placement of shares.
44 Shares should be uploaded with the same nodeid list as this tool used when
45 constructing the files.
46
47 Also note that this tool uses the Tahoe codebase, so it should be run on a
48 system where Tahoe is installed, or in a source tree with setup.py like this:
49
50  setup.py run_with_pythonpath -p -c 'misc/make-canary-files.py ARGS..'
51 """
52
53 import os, sha
54 from twisted.python import usage
55 from allmydata.immutable import upload
56 from allmydata.util import base32
57
58 class Options(usage.Options):
59     optParameters = [
60         ("convergence", "c", None, "path to NODEDIR/private/convergence"),
61         ("nodeids", "n", None, "path to file with one base32 nodeid per line"),
62         ("k", "k", 1, "number of necessary shares, defaults to 1", int),
63         ("N", "N", 1, "number of total shares, defaults to 1", int),
64         ]
65     optFlags = [
66         ("verbose", "v", "Be noisy"),
67         ]
68
69 opts = Options()
70 opts.parseOptions()
71
72 verbose = bool(opts["verbose"])
73
74 nodes = {}
75 for line in open(opts["nodeids"], "r").readlines():
76     line = line.strip()
77     if not line or line.startswith("#"):
78         continue
79     pieces = line.split(None, 1)
80     if len(pieces) == 2:
81         nodeid_s, nickname = pieces
82     else:
83         nodeid_s = pieces[0]
84         nickname = None
85     nodeid = base32.a2b(nodeid_s)
86     nodes[nodeid] = nickname
87
88 if opts["k"] != 3 or opts["N"] != 10:
89     print "note: using non-default k/N requires patching the Tahoe code"
90     print "src/allmydata/client.py line 55, DEFAULT_ENCODING_PARAMETERS"
91
92 convergence_file = os.path.expanduser(opts["convergence"])
93 convergence_s = open(convergence_file, "rb").read().strip()
94 convergence = base32.a2b(convergence_s)
95
96 def get_permuted_peers(key):
97     results = []
98     for nodeid in nodes:
99         permuted = sha.new(key + nodeid).digest()
100         results.append((permuted, nodeid))
101     results.sort(lambda a,b: cmp(a[0], b[0]))
102     return [ r[1] for r in results ]
103
104 def find_share_for_target(target):
105     target_s = base32.b2a(target)
106     prefix = "The first share of this file will be placed on " + target_s + "\n"
107     prefix += "This data is random: "
108     attempts = 0
109     while True:
110         attempts += 1
111         suffix = base32.b2a(os.urandom(10))
112         if verbose: print " trying", suffix,
113         data = prefix + suffix + "\n"
114         assert len(data) > 55  # no LIT files
115         # now, what storage index will this get?
116         u = upload.Data(data, convergence)
117         eu = upload.EncryptAnUploadable(u)
118         d = eu.get_storage_index() # this happens to run synchronously
119         def _got_si(si, data=data):
120             if verbose: print "SI", base32.b2a(si),
121             peerlist = get_permuted_peers(si)
122             if peerlist[0] == target:
123                 # great!
124                 if verbose: print "  yay!"
125                 fn = base32.b2a(target)
126                 if nodes[target]:
127                     nickname = nodes[target].replace("/", "_")
128                     fn += "-" + nickname
129                 fn += ".txt"
130                 fn = os.path.join("canaries", fn)
131                 open(fn, "w").write(data)
132                 return True
133             # nope, must try again
134             if verbose: print "  boo"
135             return False
136         d.addCallback(_got_si)
137         # get sneaky and look inside the Deferred for the synchronous result
138         if d.result:
139             return attempts
140
141 os.mkdir("canaries")
142 attempts = []
143 for target in nodes:
144     target_s = base32.b2a(target)
145     print "working on", target_s
146     attempts.append(find_share_for_target(target))
147 print "done"
148 print "%d attempts total, avg %d per target, max %d" % \
149       (sum(attempts), 1.0* sum(attempts) / len(nodes), max(attempts))
150
151