1 | diff -rN -u old-tahoe/contrib/fuse/impl_c/blackmatch.py new-tahoe/contrib/fuse/impl_c/blackmatch.py |
---|
2 | --- old-tahoe/contrib/fuse/impl_c/blackmatch.py 2010-01-24 05:51:58.956000000 +0000 |
---|
3 | +++ new-tahoe/contrib/fuse/impl_c/blackmatch.py 2010-01-24 05:52:03.173000000 +0000 |
---|
4 | @@ -1,7 +1,7 @@ |
---|
5 | #!/usr/bin/env python |
---|
6 | |
---|
7 | #----------------------------------------------------------------------------------------------- |
---|
8 | -from allmydata.uri import CHKFileURI, DirectoryURI, LiteralFileURI |
---|
9 | +from allmydata.uri import CHKFileURI, DirectoryURI, LiteralFileURI, is_literal_file_uri |
---|
10 | from allmydata.scripts.common_http import do_http as do_http_req |
---|
11 | from allmydata.util.hashutil import tagged_hash |
---|
12 | from allmydata.util.assertutil import precondition |
---|
13 | @@ -335,7 +335,7 @@ |
---|
14 | self.fname = self.tfs.cache.tmp_file(os.urandom(20)) |
---|
15 | if self.fnode is None: |
---|
16 | log('TFF: [%s] open() for write: no file node, creating new File %s' % (self.name, self.fname, )) |
---|
17 | - self.fnode = File(0, 'URI:LIT:') |
---|
18 | + self.fnode = File(0, LiteralFileURI.BASE_STRING) |
---|
19 | self.fnode.tmp_fname = self.fname # XXX kill this |
---|
20 | self.parent.add_child(self.name, self.fnode, {}) |
---|
21 | elif hasattr(self.fnode, 'tmp_fname'): |
---|
22 | @@ -362,7 +362,7 @@ |
---|
23 | self.fname = self.fnode.tmp_fname |
---|
24 | log('TFF: reopening(%s) for reading' % self.fname) |
---|
25 | else: |
---|
26 | - if uri.startswith("URI:LIT") or not self.tfs.async: |
---|
27 | + if is_literal_file_uri(uri) or not self.tfs.async: |
---|
28 | log('TFF: synchronously fetching file from cache for reading') |
---|
29 | self.fname = self.tfs.cache.get_file(uri) |
---|
30 | else: |
---|
31 | @@ -906,7 +906,7 @@ |
---|
32 | |
---|
33 | class TStat(fuse.Stat): |
---|
34 | # in fuse 0.2, these are set by fuse.Stat.__init__ |
---|
35 | - # in fuse 0.2-pre3 (hardy) they are not. badness unsues if they're missing |
---|
36 | + # in fuse 0.2-pre3 (hardy) they are not. badness ensues if they're missing |
---|
37 | st_mode = None |
---|
38 | st_ino = 0 |
---|
39 | st_dev = 0 |
---|
40 | @@ -1237,7 +1237,7 @@ |
---|
41 | |
---|
42 | def get_file(self, uri): |
---|
43 | self.log('get_file(%s)' % (uri,)) |
---|
44 | - if uri.startswith("URI:LIT"): |
---|
45 | + if is_literal_file_uri(uri): |
---|
46 | return self.get_literal(uri) |
---|
47 | else: |
---|
48 | return self.get_chk(uri, async=False) |
---|
49 | diff -rN -u old-tahoe/docs/frontends/webapi.txt new-tahoe/docs/frontends/webapi.txt |
---|
50 | --- old-tahoe/docs/frontends/webapi.txt 2010-01-24 05:51:59.214000000 +0000 |
---|
51 | +++ new-tahoe/docs/frontends/webapi.txt 2010-01-24 05:52:03.375000000 +0000 |
---|
52 | @@ -70,20 +70,20 @@ |
---|
53 | these tasks. In general, everything that can be done with a PUT or DELETE can |
---|
54 | also be done with a POST. |
---|
55 | |
---|
56 | -Tahoe's web API is designed for two different consumers. The first is a |
---|
57 | -program that needs to manipulate the virtual file system. Such programs are |
---|
58 | +Tahoe's web API is designed for two different kinds of consumer. The first is |
---|
59 | +a program that needs to manipulate the virtual file system. Such programs are |
---|
60 | expected to use the RESTful interface described above. The second is a human |
---|
61 | using a standard web browser to work with the filesystem. This user is given |
---|
62 | a series of HTML pages with links to download files, and forms that use POST |
---|
63 | actions to upload, rename, and delete files. |
---|
64 | |
---|
65 | When an error occurs, the HTTP response code will be set to an appropriate |
---|
66 | -400-series code (like 404 for an unknown childname, or 400 Gone when a file |
---|
67 | -is unrecoverable due to insufficient shares), and the HTTP response body will |
---|
68 | -usually contain a few lines of explanation as to the cause of the error and |
---|
69 | -possible responses. Unusual exceptions may result in a 500 Internal Server |
---|
70 | -Error as a catch-all, with a default response body will contain a |
---|
71 | -Nevow-generated HTML-ized representation of the Python exception stack trace |
---|
72 | +400-series code (like 404 Not Found for an unknown childname, or 400 Bad Request |
---|
73 | +when the parameters to a webapi operation are invalid), and the HTTP response |
---|
74 | +body will usually contain a few lines of explanation as to the cause of the |
---|
75 | +error and possible responses. Unusual exceptions may result in a |
---|
76 | +500 Internal Server Error as a catch-all, with a default response body containing |
---|
77 | +a Nevow-generated HTML-ized representation of the Python exception stack trace |
---|
78 | that caused the problem. CLI programs which want to copy the response body to |
---|
79 | stderr should provide an "Accept: text/plain" header to their requests to get |
---|
80 | a plain text stack trace instead. If the Accept header contains */*, or |
---|
81 | @@ -108,9 +108,9 @@ |
---|
82 | read- and write- caps, which start with "URI:SSK", and give access to mutable |
---|
83 | files. |
---|
84 | |
---|
85 | -(later versions of Tahoe will make these strings shorter, and will remove the |
---|
86 | +(Later versions of Tahoe will make these strings shorter, and will remove the |
---|
87 | unfortunate colons, which must be escaped when these caps are embedded in |
---|
88 | -URLs). |
---|
89 | +URLs.) |
---|
90 | |
---|
91 | To refer to any Tahoe object through the web API, you simply need to combine |
---|
92 | a prefix (which indicates the HTTP server to use) with the cap (which |
---|
93 | @@ -150,8 +150,12 @@ |
---|
94 | |
---|
95 | === Child Lookup === |
---|
96 | |
---|
97 | -Tahoe directories contain named children, just like directories in a regular |
---|
98 | -local filesystem. These children can be either files or subdirectories. |
---|
99 | +Tahoe directories contain named child entries, just like directories in a regular |
---|
100 | +local filesystem. These child entries, called "dirnodes", consist of a name, |
---|
101 | +metadata, a write slot, and a read slot. The write and read slots normally contain |
---|
102 | +a writecap and readcap referring to the same object, which can be either a file |
---|
103 | +or a subdirectory. The write slot may be empty (actually, both may be empty, |
---|
104 | +but that is unusual). |
---|
105 | |
---|
106 | If you have a Tahoe URL that refers to a directory, and want to reference a |
---|
107 | named child inside it, just append the child name to the URL. For example, if |
---|
108 | @@ -195,9 +199,9 @@ |
---|
109 | representable as such. |
---|
110 | |
---|
111 | All Tahoe operations that refer to existing files or directories must include |
---|
112 | -a suitable read- or write- cap in the URL: the wapi server won't add one |
---|
113 | +a suitable read- or write- cap in the URL: the webapi server won't add one |
---|
114 | for you. If you don't know the cap, you can't access the file. This allows |
---|
115 | -the security properties of Tahoe caps to be extended across the wapi |
---|
116 | +the security properties of Tahoe caps to be extended across the webapi |
---|
117 | interface. |
---|
118 | |
---|
119 | == Slow Operations, Progress, and Cancelling == |
---|
120 | @@ -271,7 +275,7 @@ |
---|
121 | since the operation completed) will remain valid for ten minutes. |
---|
122 | |
---|
123 | Many "slow" operations can begin to use unacceptable amounts of memory when |
---|
124 | -operation on large directory structures. The memory usage increases when the |
---|
125 | +operating on large directory structures. The memory usage increases when the |
---|
126 | ophandle is polled, as the results must be copied into a JSON string, sent |
---|
127 | over the wire, then parsed by a client. So, as an alternative, many "slow" |
---|
128 | operations have streaming equivalents. These equivalents do not use operation |
---|
129 | @@ -314,7 +318,7 @@ |
---|
130 | To use the /uri/$FILECAP form, $FILECAP be a write-cap for a mutable file. |
---|
131 | |
---|
132 | In the /uri/$DIRCAP/[SUBDIRS../]FILENAME form, if the target file is a |
---|
133 | - writable mutable file, that files contents will be overwritten in-place. If |
---|
134 | + writable mutable file, that file's contents will be overwritten in-place. If |
---|
135 | it is a read-cap for a mutable file, an error will occur. If it is an |
---|
136 | immutable file, the old file will be discarded, and a new one will be put in |
---|
137 | its place. |
---|
138 | @@ -333,7 +337,7 @@ |
---|
139 | PUT /uri |
---|
140 | |
---|
141 | This uploads a file, and produces a file-cap for the contents, but does not |
---|
142 | - attach the file into the virtual drive. No directories will be modified by |
---|
143 | + attach the file into the filesystem. No directories will be modified by |
---|
144 | this operation. The file-cap is returned as the body of the HTTP response. |
---|
145 | |
---|
146 | If "mutable=true" is in the query arguments, the operation will create a |
---|
147 | @@ -347,7 +351,7 @@ |
---|
148 | |
---|
149 | Create a new empty directory and return its write-cap as the HTTP response |
---|
150 | body. This does not make the newly created directory visible from the |
---|
151 | - virtual drive. The "PUT" operation is provided for backwards compatibility: |
---|
152 | + filesystem. The "PUT" operation is provided for backwards compatibility: |
---|
153 | new code should use POST. |
---|
154 | |
---|
155 | POST /uri?t=mkdir-with-children |
---|
156 | @@ -388,8 +392,29 @@ |
---|
157 | "linkcrtime": 1202777696.7564139, |
---|
158 | "linkmotime": 1202777696.7564139, |
---|
159 | } } } ] |
---|
160 | - } |
---|
161 | + } |
---|
162 | |
---|
163 | + For forward-compatibility, a mutable directory can also contain caps in |
---|
164 | + a format that is unknown to the webapi server. When such caps are retrieved |
---|
165 | + from a mutable directory in a "ro_uri" field, they will be prefixed with |
---|
166 | + the string "ro.", indicating that they must not be decoded without |
---|
167 | + checking that they are read-only. The "ro." prefix must not be stripped |
---|
168 | + off without performing this check. (Future versions of the webapi server |
---|
169 | + will perform it where necessary.) |
---|
170 | + |
---|
171 | + If both the "rw_uri" and "ro_uri" fields are present in a given PROPDICT, |
---|
172 | + and the webapi server recognizes the rw_uri as a write cap, then it will |
---|
173 | + reset the ro_uri to the corresponding read cap and discard the original |
---|
174 | + contents of ro_uri (in order to ensure that the two caps correspond to the |
---|
175 | + same object and that the ro_uri is in fact read-only). However this may not |
---|
176 | + happen for caps in a format unknown to the webapi server. Therefore, when |
---|
177 | + writing a directory the webapi client should ensure that the contents |
---|
178 | + of "rw_uri" and "ro_uri" for a given PROPDICT are a consistent |
---|
179 | + (write cap, read cap) pair if possible. If the webapi client only has |
---|
180 | + one cap and does not know whether it is a write cap or read cap, then |
---|
181 | + it is acceptable to set "rw_uri" to that cap and omit "ro_uri". The |
---|
182 | + client must not put a write cap into a "ro_uri" field. |
---|
183 | + |
---|
184 | Note that the webapi-using client application must not provide the |
---|
185 | "Content-Type: multipart/form-data" header that usually accompanies HTML |
---|
186 | form submissions, since the body is not formatted this way. Doing so will |
---|
187 | @@ -404,59 +429,95 @@ |
---|
188 | |
---|
189 | Like t=mkdir-with-children above, but the new directory will be |
---|
190 | deep-immutable. This means that the directory itself is immutable, and that |
---|
191 | - it can only contain deep-immutable objects, like immutable files, literal |
---|
192 | - files, and deep-immutable directories. A non-empty request body is |
---|
193 | - mandatory, since after the directory is created, it will not be possible to |
---|
194 | - add more children to it. |
---|
195 | + it can only contain objects that are treated as being deep-immutable, like |
---|
196 | + immutable files, literal files, and deep-immutable directories. |
---|
197 | + |
---|
198 | + For forward-compatibility, a deep-immutable directory can also contain caps |
---|
199 | + in a format that is unknown to the webapi server. When such caps are retrieved |
---|
200 | + from a deep-immutable directory in a "ro_uri" field, they will be prefixed |
---|
201 | + with the string "imm.", indicating that they must not be decoded without |
---|
202 | + checking that they are immutable. The "imm." prefix must not be stripped |
---|
203 | + off without performing this check. (Future versions of the webapi server |
---|
204 | + will perform it where necessary.) |
---|
205 | + |
---|
206 | + The cap for each child may be given either in the "rw_uri" or "ro_uri" |
---|
207 | + field of the PROPDICT (not both). If a cap is given in the "rw_uri" field, |
---|
208 | + then the webapi server will check that it is an immutable readcap of a |
---|
209 | + *known* format, and give an error if it is not. If a cap is given in the |
---|
210 | + "ro_uri" field, then the webapi server will still check whether known |
---|
211 | + caps are immutable, but for unknown caps it will simply assume that the |
---|
212 | + cap can be stored, as described above. Note that an attacker would be |
---|
213 | + able to store any cap in an immutable directory, so this check when |
---|
214 | + creating the directory is only to help non-malicious clients to avoid |
---|
215 | + accidentally giving away more authority than intended. |
---|
216 | + |
---|
217 | + A non-empty request body is mandatory, since after the directory is created, |
---|
218 | + it will not be possible to add more children to it. |
---|
219 | |
---|
220 | POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir |
---|
221 | PUT /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir |
---|
222 | |
---|
223 | Create new directories as necessary to make sure that the named target |
---|
224 | ($DIRCAP/SUBDIRS../SUBDIR) is a directory. This will create additional |
---|
225 | - intermediate directories as necessary. If the named target directory already |
---|
226 | - exists, this will make no changes to it. |
---|
227 | + intermediate mutable directories as necessary. If the named target directory |
---|
228 | + already exists, this will make no changes to it. |
---|
229 | |
---|
230 | If the final directory is created, it will be empty. |
---|
231 | |
---|
232 | - This will return an error if a blocking file is present at any of the parent |
---|
233 | - names, preventing the server from creating the necessary parent directory. |
---|
234 | + This operation will return an error if a blocking file is present at any of |
---|
235 | + the parent names, preventing the server from creating the necessary parent |
---|
236 | + directory, or if it would require changing an immutable directory. |
---|
237 | |
---|
238 | The write-cap of the new directory will be returned as the HTTP response |
---|
239 | body. |
---|
240 | |
---|
241 | POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir-with-children |
---|
242 | |
---|
243 | - Like above, but if the final directory is created, it will be populated with |
---|
244 | - initial children from the POST request body, as described above in the |
---|
245 | - /uri?t=mkdir-with-children operation. |
---|
246 | + Like /uri?t=mkdir-with-children, but the final directory is created as a |
---|
247 | + child of an existing mutable directory. This will create additional |
---|
248 | + intermediate mutable directories as necessary. If the final directory is |
---|
249 | + created, it will be populated with initial children from the POST request |
---|
250 | + body, as described above. |
---|
251 | |
---|
252 | POST /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir-immutable |
---|
253 | |
---|
254 | - Like above, but the final directory will be deep-immutable, with the |
---|
255 | - children specified as a JSON dictionary in the POST request body. |
---|
256 | + Like /uri?t=mkdir-immutable, but the final directory is created as a child |
---|
257 | + of an existing mutable directory. The final directory will be deep-immutable, |
---|
258 | + and will be populated with the children specified as a JSON dictionary in |
---|
259 | + the POST request body. |
---|
260 | + |
---|
261 | + In Tahoe 1.6 this operation creates intermediate mutable directories if |
---|
262 | + necessary, but that behaviour should not be relied on; see ticket #920. |
---|
263 | |
---|
264 | POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=NAME |
---|
265 | |
---|
266 | - Create a new empty directory and attach it to the given existing directory. |
---|
267 | - This will create additional intermediate directories as necessary. |
---|
268 | + Create a new empty mutable directory and attach it to the given existing |
---|
269 | + directory. This will create additional intermediate directories as necessary. |
---|
270 | |
---|
271 | - The URL of this form points to the parent of the bottom-most new directory, |
---|
272 | - whereas the previous form has a URL that points directly to the bottom-most |
---|
273 | - new directory. |
---|
274 | + This operation will return an error if a blocking file is present at any of |
---|
275 | + the parent names, preventing the server from creating the necessary parent |
---|
276 | + directory, or if it would require changing any immutable directory. |
---|
277 | + |
---|
278 | + The URL of this operation points to the parent of the bottommost new directory, |
---|
279 | + whereas the /uri/$DIRCAP/[SUBDIRS../]SUBDIR?t=mkdir operation above has a URL |
---|
280 | + that points directly to the bottommost new directory. |
---|
281 | |
---|
282 | POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-with-children&name=NAME |
---|
283 | |
---|
284 | - As above, but the new directory will be populated with initial children via |
---|
285 | - the POST request body, as described in /uri?t=mkdir-with-children above. |
---|
286 | + Like /uri/$DIRCAP/[SUBDIRS../]?t=mkdir&name=NAME, but the new directory will |
---|
287 | + be populated with initial children via the POST request body. |
---|
288 | Note that the name= argument must be passed as a queryarg, because the POST |
---|
289 | request body is used for the initial children JSON. |
---|
290 | |
---|
291 | POST /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-immutable&name=NAME |
---|
292 | |
---|
293 | - As above, but the new directory will be deep-immutable, with the children |
---|
294 | - specified as a JSON dictionary in the POST request body. Again, the name= |
---|
295 | - argument must be passed as a queryarg. |
---|
296 | + Like /uri/$DIRCAP/[SUBDIRS../]?t=mkdir-with-children&name=NAME, but the |
---|
297 | + final directory will be deep-immutable. The children are specified as a |
---|
298 | + JSON dictionary in the POST request body. Again, the name= argument must be |
---|
299 | + passed as a queryarg. |
---|
300 | + |
---|
301 | + In Tahoe 1.6 this operation creates intermediate mutable directories if |
---|
302 | + necessary, but that behaviour should not be relied on; see ticket #920. |
---|
303 | |
---|
304 | === Get Information About A File Or Directory (as JSON) === |
---|
305 | |
---|
306 | @@ -546,7 +607,7 @@ |
---|
307 | |
---|
308 | Then the rw_uri field will be present in the information about a directory |
---|
309 | if and only if you have read-write access to that directory. The verify_uri |
---|
310 | - field will be presend if and only if the object has a verify-cap |
---|
311 | + field will be present if and only if the object has a verify-cap |
---|
312 | (non-distributed LIT files do not have verify-caps). |
---|
313 | |
---|
314 | ==== About the metadata ==== |
---|
315 | @@ -622,11 +683,11 @@ |
---|
316 | link points. |
---|
317 | |
---|
318 | 4. Also, quite apart from Tahoe, you might be confused about the meaning of |
---|
319 | - the 'ctime' in unix local filesystems, which people sometimes think means |
---|
320 | - file creation time, but which actually means, in unix local filesystems, the |
---|
321 | + the 'ctime' in UNIX local filesystems, which people sometimes think means |
---|
322 | + file creation time, but which actually means, in UNIX local filesystems, the |
---|
323 | most recent time that the file contents or the file metadata (such as owner, |
---|
324 | permission bits, extended attributes, etc.) has changed. Note that although |
---|
325 | - 'ctime' does not mean file creation time in Unix, it does mean link creation |
---|
326 | + 'ctime' does not mean file creation time in UNIX, it does mean link creation |
---|
327 | time in Tahoe, unless the "tahoe backup" command has been used on that link, |
---|
328 | in which case it means something about the local filesystem file which |
---|
329 | corresponds to the Tahoe file which is pointed at by the link. It means |
---|
330 | @@ -634,7 +695,6 @@ |
---|
331 | Windows) or file-contents-or-metadata-update-time of the local file (if |
---|
332 | "tahoe backup" was run on a different operating system). |
---|
333 | |
---|
334 | - |
---|
335 | === Attaching an existing File or Directory by its read- or write- cap === |
---|
336 | |
---|
337 | PUT /uri/$DIRCAP/[SUBDIRS../]CHILDNAME?t=uri |
---|
338 | @@ -658,10 +718,15 @@ |
---|
339 | if there is already an object at the given location, rather than |
---|
340 | overwriting the existing object. To allow the operation to overwrite a |
---|
341 | file, but return an error when trying to overwrite a directory, use |
---|
342 | - "replace=only-files" (this behavior is closer to the traditional unix "mv" |
---|
343 | + "replace=only-files" (this behavior is closer to the traditional UNIX "mv" |
---|
344 | command). Note that "true", "t", and "1" are all synonyms for "True", and |
---|
345 | "false", "f", and "0" are synonyms for "False", and the parameter is |
---|
346 | case-insensitive. |
---|
347 | + |
---|
348 | + Note that this operation does not take its child cap in the form of |
---|
349 | + separate "rw_uri" and "ro_uri" fields. Therefore, it cannot accept a |
---|
350 | + child cap in a format unknown to the webapi server, because the server |
---|
351 | + is not able to attenuate an unknown write cap to a read cap. |
---|
352 | |
---|
353 | === Adding multiple files or directories to a parent directory at once === |
---|
354 | |
---|
355 | @@ -679,7 +744,9 @@ |
---|
356 | "childinfo" is a dictionary that contains "rw_uri", "ro_uri", and |
---|
357 | "metadata" keys. You can take the output of "GET /uri/$DIRCAP1?t=json" and |
---|
358 | use it as the input to "POST /uri/$DIRCAP2?t=set_children" to make DIR2 |
---|
359 | - look very much like DIR1. |
---|
360 | + look very much like DIR1 (except for any existing children of DIR2 that |
---|
361 | + were not overwritten, and any existing "tahoe" metadata keys as described |
---|
362 | + below). |
---|
363 | |
---|
364 | When the set_children request contains a child name that already exists in |
---|
365 | the target directory, this command defaults to overwriting that child with |
---|
366 | @@ -721,7 +788,7 @@ |
---|
367 | The object will only become completely unreachable once 1: there are no |
---|
368 | reachable directories that reference it, and 2: nobody is holding a read- |
---|
369 | or write- cap to the object. (This behavior is very similar to the way |
---|
370 | - hardlinks and anonymous files work in traditional unix filesystems). |
---|
371 | + hardlinks and anonymous files work in traditional UNIX filesystems). |
---|
372 | |
---|
373 | This operation will not modify more than a single directory. Intermediate |
---|
374 | directories which were implicitly created by PUT or POST methods will *not* |
---|
375 | @@ -850,7 +917,7 @@ |
---|
376 | POST /uri?t=upload |
---|
377 | |
---|
378 | This uploads a file, and produces a file-cap for the contents, but does not |
---|
379 | - attach the file into the virtual drive. No directories will be modified by |
---|
380 | + attach the file into the filesystem. No directories will be modified by |
---|
381 | this operation. |
---|
382 | |
---|
383 | The file must be provided as the "file" field of an HTML encoded form body, |
---|
384 | @@ -880,9 +947,9 @@ |
---|
385 | |
---|
386 | POST /uri/$DIRCAP/[SUBDIRS../]?t=upload |
---|
387 | |
---|
388 | - This uploads a file, and attaches it as a new child of the given directory. |
---|
389 | - The file must be provided as the "file" field of an HTML encoded form body, |
---|
390 | - produced in response to an HTML form like this: |
---|
391 | + This uploads a file, and attaches it as a new child of the given directory, |
---|
392 | + which must be mutable. The file must be provided as the "file" field of an |
---|
393 | + HTML-encoded form body, produced in response to an HTML form like this: |
---|
394 | <form action="." method="POST" enctype="multipart/form-data"> |
---|
395 | <input type="hidden" name="t" value="upload" /> |
---|
396 | <input type="file" name="file" /> |
---|
397 | @@ -925,9 +992,10 @@ |
---|
398 | POST /uri/$DIRCAP/[SUBDIRS../]FILENAME?t=upload |
---|
399 | |
---|
400 | This also uploads a file and attaches it as a new child of the given |
---|
401 | - directory. It is a slight variant of the previous operation, as the URL |
---|
402 | - refers to the target file rather than the parent directory. It is otherwise |
---|
403 | - identical: this accepts mutable= and when_done= arguments too. |
---|
404 | + directory, which must be mutable. It is a slight variant of the previous |
---|
405 | + operation, as the URL refers to the target file rather than the parent |
---|
406 | + directory. It is otherwise identical: this accepts mutable= and when_done= |
---|
407 | + arguments too. |
---|
408 | |
---|
409 | POST /uri/$FILECAP?t=upload |
---|
410 | |
---|
411 | @@ -955,20 +1023,21 @@ |
---|
412 | |
---|
413 | POST /uri/$DIRCAP/[SUBDIRS../]?t=delete&name=CHILDNAME |
---|
414 | |
---|
415 | - This instructs the node to delete a child object (file or subdirectory) from |
---|
416 | - the given directory. Note that the entire subtree is removed. This is |
---|
417 | - somewhat like "rm -rf" (from the point of view of the parent), but other |
---|
418 | - references into the subtree will see that the child subdirectories are not |
---|
419 | - modified by this operation. Only the link from the given directory to its |
---|
420 | - child is severed. |
---|
421 | + This instructs the node to remove a child object (file or subdirectory) from |
---|
422 | + the given directory, which must be mutable. Note that the entire subtree is |
---|
423 | + unlinked from the parent. Unlike deleting a subdirectory in a UNIX local |
---|
424 | + filesystem, the subtree need not be empty; if it isn't, then other references |
---|
425 | + into the subtree will see that the child subdirectories are not modified by |
---|
426 | + this operation. Only the link from the given directory to its child is severed. |
---|
427 | |
---|
428 | === Renaming A Child === |
---|
429 | |
---|
430 | POST /uri/$DIRCAP/[SUBDIRS../]?t=rename&from_name=OLD&to_name=NEW |
---|
431 | |
---|
432 | - This instructs the node to rename a child of the given directory. This is |
---|
433 | - exactly the same as removing the child, then adding the same child-cap under |
---|
434 | - the new name. This operation cannot move the child to a different directory. |
---|
435 | + This instructs the node to rename a child of the given directory, which must |
---|
436 | + be mutable. This has a similar effect to removing the child, then adding the |
---|
437 | + same child-cap under the new name, except that it preserves metadata. This |
---|
438 | + operation cannot move the child to a different directory. |
---|
439 | |
---|
440 | This operation will replace any existing child of the new name, making it |
---|
441 | behave like the UNIX "mv -f" command. |
---|
442 | @@ -1590,7 +1659,7 @@ |
---|
443 | |
---|
444 | == Static Files in /public_html == |
---|
445 | |
---|
446 | -The wapi server will take any request for a URL that starts with /static |
---|
447 | +The webapi server will take any request for a URL that starts with /static |
---|
448 | and serve it from a configurable directory which defaults to |
---|
449 | $BASEDIR/public_html . This is configured by setting the "[node]web.static" |
---|
450 | value in $BASEDIR/tahoe.cfg . If this is left at the default value of |
---|
451 | @@ -1598,10 +1667,10 @@ |
---|
452 | served with the contents of the file $BASEDIR/public_html/subdir/foo.html . |
---|
453 | |
---|
454 | This can be useful to serve a javascript application which provides a |
---|
455 | -prettier front-end to the rest of the Tahoe wapi. |
---|
456 | +prettier front-end to the rest of the Tahoe webapi. |
---|
457 | |
---|
458 | |
---|
459 | -== safety and security issues -- names vs. URIs == |
---|
460 | +== Safety and security issues -- names vs. URIs == |
---|
461 | |
---|
462 | Summary: use explicit file- and dir- caps whenever possible, to reduce the |
---|
463 | potential for surprises when the filesystem structure is changed. |
---|
464 | @@ -1641,6 +1710,17 @@ |
---|
465 | child's name and the child's URI are included in the results of listing the |
---|
466 | parent directory, so it isn't any harder to use the URI for this purpose. |
---|
467 | |
---|
468 | +The read and write caps in a given directory node are separate URIs, and |
---|
469 | +can't be assumed to point to the same object even if they were retrieved in |
---|
470 | +the same operation (although the webapi server attempts to ensure this |
---|
471 | +in most cases). If you need to rely on that property, you should explicitly |
---|
472 | +verify it. More generally, you should not make assumptions about the |
---|
473 | +internal consistency of the contents of mutable directories. As a result |
---|
474 | +of the signatures on mutable object versions, it is guaranteed that a given |
---|
475 | +version was written in a single update, but -- as in the case of a file -- |
---|
476 | +the contents may have been chosen by a malicious writer in a way that is |
---|
477 | +designed to confuse applications that rely on their consistency. |
---|
478 | + |
---|
479 | In general, use names if you want "whatever object (whether file or |
---|
480 | directory) is found by following this name (or sequence of names) when my |
---|
481 | request reaches the server". Use URIs if you want "this particular object". |
---|
482 | @@ -1676,7 +1756,7 @@ |
---|
483 | |
---|
484 | Tahoe nodes implement internal serialization to make sure that a single Tahoe |
---|
485 | node cannot conflict with itself. For example, it is safe to issue two |
---|
486 | -directory modification requests to a single tahoe node's wapi server at the |
---|
487 | +directory modification requests to a single tahoe node's webapi server at the |
---|
488 | same time, because the Tahoe node will internally delay one of them until |
---|
489 | after the other has finished being applied. (This feature was introduced in |
---|
490 | Tahoe-1.1; back with Tahoe-1.0 the web client was responsible for serializing |
---|
491 | diff -rN -u old-tahoe/docs/logging.txt new-tahoe/docs/logging.txt |
---|
492 | --- old-tahoe/docs/logging.txt 2010-01-24 05:51:59.330000000 +0000 |
---|
493 | +++ new-tahoe/docs/logging.txt 2010-01-24 05:52:03.452000000 +0000 |
---|
494 | @@ -224,6 +224,9 @@ |
---|
495 | |
---|
496 | == Log Messages During Unit Tests == |
---|
497 | |
---|
498 | +*** WARNING: setting the environment variables below may cause some tests to *** |
---|
499 | +*** fail spuriously. See ticket #923 for the status of a fix for this problem. *** |
---|
500 | + |
---|
501 | If a test is failing and you aren't sure why, start by enabling |
---|
502 | FLOGTOTWISTED=1 like this: |
---|
503 | |
---|
504 | diff -rN -u old-tahoe/relnotes.txt new-tahoe/relnotes.txt |
---|
505 | --- old-tahoe/relnotes.txt 2010-01-24 05:52:00.882000000 +0000 |
---|
506 | +++ new-tahoe/relnotes.txt 2010-01-24 05:52:05.267000000 +0000 |
---|
507 | @@ -1,7 +1,7 @@ |
---|
508 | -ANNOUNCING Tahoe, the Lofty-Atmospheric Filesystem, v1.5 |
---|
509 | +ANNOUNCING Tahoe, the Lofty-Atmospheric Filesystem, v1.6 |
---|
510 | |
---|
511 | The Tahoe-LAFS team is pleased to announce the immediate |
---|
512 | -availability of version 1.5 of Tahoe, the Lofty Atmospheric |
---|
513 | +availability of version 1.6 of Tahoe, the Lofty Atmospheric |
---|
514 | File System. |
---|
515 | |
---|
516 | Tahoe-LAFS is the first cloud storage technology which offers |
---|
517 | @@ -29,15 +29,20 @@ |
---|
518 | |
---|
519 | COMPATIBILITY |
---|
520 | |
---|
521 | -Version 1.5 is fully compatible with the version 1 series of |
---|
522 | -Tahoe-LAFS. Files written by v1.5 clients can be read by |
---|
523 | -clients of all versions back to v1.0. v1.5 clients can read |
---|
524 | -files produced by clients of all versions since v1.0. v1.5 |
---|
525 | -servers can serve clients of all versions back to v1.0 and v1.5 |
---|
526 | +Version 1.6 is fully compatible with the version 1 series of |
---|
527 | +Tahoe-LAFS. Files written by v1.6 clients can be read by |
---|
528 | +clients of all versions back to v1.0. v1.6 clients can read |
---|
529 | +files produced by clients of all versions since v1.0. v1.6 |
---|
530 | +servers can serve clients of all versions back to v1.0 and v1.6 |
---|
531 | clients can use servers of all versions back to v1.0. |
---|
532 | |
---|
533 | -This is the sixth release in the version 1 series. The version |
---|
534 | -1 series of Tahoe-LAFS will be actively supported and |
---|
535 | +In addition, version 1.6 improves forward-compatibility with |
---|
536 | +planned future cap formats, allowing updates to a directory |
---|
537 | +containing both current and future caps, without loss of |
---|
538 | +information. |
---|
539 | + |
---|
540 | +This is the seventh major release in the version 1 series. The |
---|
541 | +version 1 series of Tahoe-LAFS will be actively supported and |
---|
542 | maintained for the forseeable future, and future versions of |
---|
543 | Tahoe-LAFS will retain the ability to read and write files |
---|
544 | compatible with Tahoe-LAFS v1. |
---|
545 | diff -rN -u old-tahoe/src/allmydata/client.py new-tahoe/src/allmydata/client.py |
---|
546 | --- old-tahoe/src/allmydata/client.py 2010-01-24 05:52:00.924000000 +0000 |
---|
547 | +++ new-tahoe/src/allmydata/client.py 2010-01-24 05:52:05.334000000 +0000 |
---|
548 | @@ -471,13 +471,16 @@ |
---|
549 | # dirnodes. The first takes a URI and produces a filenode or (new-style) |
---|
550 | # dirnode. The other three create brand-new filenodes/dirnodes. |
---|
551 | |
---|
552 | - def create_node_from_uri(self, writecap, readcap=None): |
---|
553 | - # this returns synchronously. |
---|
554 | - return self.nodemaker.create_from_cap(writecap, readcap) |
---|
555 | + def create_node_from_uri(self, write_uri, read_uri=None, deep_immutable=False, name="<unknown name>"): |
---|
556 | + # This returns synchronously. |
---|
557 | + # Note that it does *not* validate the write_uri and read_uri; instead we |
---|
558 | + # may get an opaque node if there were any problems. |
---|
559 | + return self.nodemaker.create_from_cap(write_uri, read_uri, deep_immutable=deep_immutable, name=name) |
---|
560 | |
---|
561 | def create_dirnode(self, initial_children={}): |
---|
562 | d = self.nodemaker.create_new_mutable_directory(initial_children) |
---|
563 | return d |
---|
564 | + |
---|
565 | def create_immutable_dirnode(self, children, convergence=None): |
---|
566 | return self.nodemaker.create_immutable_directory(children, convergence) |
---|
567 | |
---|
568 | diff -rN -u old-tahoe/src/allmydata/control.py new-tahoe/src/allmydata/control.py |
---|
569 | --- old-tahoe/src/allmydata/control.py 2010-01-24 05:52:00.936000000 +0000 |
---|
570 | +++ new-tahoe/src/allmydata/control.py 2010-01-24 05:52:05.351000000 +0000 |
---|
571 | @@ -5,7 +5,7 @@ |
---|
572 | from twisted.internet import defer |
---|
573 | from twisted.internet.interfaces import IConsumer |
---|
574 | from foolscap.api import Referenceable |
---|
575 | -from allmydata.interfaces import RIControlClient |
---|
576 | +from allmydata.interfaces import RIControlClient, IFileNode |
---|
577 | from allmydata.util import fileutil, mathutil |
---|
578 | from allmydata.immutable import upload |
---|
579 | from twisted.python import log |
---|
580 | @@ -67,7 +67,9 @@ |
---|
581 | return d |
---|
582 | |
---|
583 | def remote_download_from_uri_to_file(self, uri, filename): |
---|
584 | - filenode = self.parent.create_node_from_uri(uri) |
---|
585 | + filenode = self.parent.create_node_from_uri(uri, name=filename) |
---|
586 | + if not IFileNode.providedBy(filenode): |
---|
587 | + raise AssertionError("The URI does not reference a file.") |
---|
588 | c = FileWritingConsumer(filename) |
---|
589 | d = filenode.read(c) |
---|
590 | d.addCallback(lambda res: filename) |
---|
591 | @@ -199,6 +201,8 @@ |
---|
592 | if i >= self.count: |
---|
593 | return |
---|
594 | n = self.parent.create_node_from_uri(self.uris[i]) |
---|
595 | + if not IFileNode.providedBy(n): |
---|
596 | + raise AssertionError("The URI does not reference a file.") |
---|
597 | if n.is_mutable(): |
---|
598 | d1 = n.download_best_version() |
---|
599 | else: |
---|
600 | diff -rN -u old-tahoe/src/allmydata/dirnode.py new-tahoe/src/allmydata/dirnode.py |
---|
601 | --- old-tahoe/src/allmydata/dirnode.py 2010-01-24 05:52:00.946000000 +0000 |
---|
602 | +++ new-tahoe/src/allmydata/dirnode.py 2010-01-24 05:52:05.362000000 +0000 |
---|
603 | @@ -5,13 +5,13 @@ |
---|
604 | from twisted.internet import defer |
---|
605 | from foolscap.api import fireEventually |
---|
606 | import simplejson |
---|
607 | -from allmydata.mutable.common import NotMutableError |
---|
608 | +from allmydata.mutable.common import NotWriteableError |
---|
609 | from allmydata.mutable.filenode import MutableFileNode |
---|
610 | -from allmydata.unknown import UnknownNode |
---|
611 | +from allmydata.unknown import UnknownNode, strip_prefix_for_ro |
---|
612 | from allmydata.interfaces import IFilesystemNode, IDirectoryNode, IFileNode, \ |
---|
613 | IImmutableFileNode, IMutableFileNode, \ |
---|
614 | ExistingChildError, NoSuchChildError, ICheckable, IDeepCheckable, \ |
---|
615 | - CannotPackUnknownNodeError |
---|
616 | + MustBeDeepImmutableError, CapConstraintError |
---|
617 | from allmydata.check_results import DeepCheckResults, \ |
---|
618 | DeepCheckAndRepairResults |
---|
619 | from allmydata.monitor import Monitor |
---|
620 | @@ -23,6 +23,11 @@ |
---|
621 | from pycryptopp.cipher.aes import AES |
---|
622 | from allmydata.util.dictutil import AuxValueDict |
---|
623 | |
---|
624 | + |
---|
625 | +# TODO: {Deleter,MetadataSetter,Adder}.modify all start by unpacking the |
---|
626 | +# contents and end by repacking them. It might be better to apply them to |
---|
627 | +# the unpacked contents. |
---|
628 | + |
---|
629 | class Deleter: |
---|
630 | def __init__(self, node, name, must_exist=True): |
---|
631 | self.node = node |
---|
632 | @@ -40,6 +45,7 @@ |
---|
633 | new_contents = self.node._pack_contents(children) |
---|
634 | return new_contents |
---|
635 | |
---|
636 | + |
---|
637 | class MetadataSetter: |
---|
638 | def __init__(self, node, name, metadata): |
---|
639 | self.node = node |
---|
640 | @@ -75,6 +81,11 @@ |
---|
641 | for (name, (child, new_metadata)) in self.entries.iteritems(): |
---|
642 | precondition(isinstance(name, unicode), name) |
---|
643 | precondition(IFilesystemNode.providedBy(child), child) |
---|
644 | + |
---|
645 | + # Strictly speaking this is redundant because we would raise the |
---|
646 | + # error again in pack_children. |
---|
647 | + child.raise_error() |
---|
648 | + |
---|
649 | if name in children: |
---|
650 | if not self.overwrite: |
---|
651 | raise ExistingChildError("child '%s' already exists" % name) |
---|
652 | @@ -123,25 +134,21 @@ |
---|
653 | new_contents = self.node._pack_contents(children) |
---|
654 | return new_contents |
---|
655 | |
---|
656 | -def _encrypt_rwcap(filenode, rwcap): |
---|
657 | - assert isinstance(rwcap, str) |
---|
658 | +def _encrypt_rw_uri(filenode, rw_uri): |
---|
659 | + assert isinstance(rw_uri, str) |
---|
660 | writekey = filenode.get_writekey() |
---|
661 | if not writekey: |
---|
662 | return "" |
---|
663 | - salt = hashutil.mutable_rwcap_salt_hash(rwcap) |
---|
664 | + salt = hashutil.mutable_rwcap_salt_hash(rw_uri) |
---|
665 | key = hashutil.mutable_rwcap_key_hash(salt, writekey) |
---|
666 | cryptor = AES(key) |
---|
667 | - crypttext = cryptor.process(rwcap) |
---|
668 | + crypttext = cryptor.process(rw_uri) |
---|
669 | mac = hashutil.hmac(key, salt + crypttext) |
---|
670 | assert len(mac) == 32 |
---|
671 | return salt + crypttext + mac |
---|
672 | # The MAC is not checked by readers in Tahoe >= 1.3.0, but we still |
---|
673 | # produce it for the sake of older readers. |
---|
674 | |
---|
675 | -class MustBeDeepImmutable(Exception): |
---|
676 | - """You tried to add a non-deep-immutable node to a deep-immutable |
---|
677 | - directory.""" |
---|
678 | - |
---|
679 | def pack_children(filenode, children, deep_immutable=False): |
---|
680 | """Take a dict that maps: |
---|
681 | children[unicode_name] = (IFileSystemNode, metadata_dict) |
---|
682 | @@ -152,7 +159,7 @@ |
---|
683 | time. |
---|
684 | |
---|
685 | If deep_immutable is True, I will require that all my children are deeply |
---|
686 | - immutable, and will raise a MustBeDeepImmutable exception if not. |
---|
687 | + immutable, and will raise a MustBeDeepImmutableError if not. |
---|
688 | """ |
---|
689 | |
---|
690 | has_aux = isinstance(children, AuxValueDict) |
---|
691 | @@ -161,25 +168,25 @@ |
---|
692 | assert isinstance(name, unicode) |
---|
693 | entry = None |
---|
694 | (child, metadata) = children[name] |
---|
695 | - if deep_immutable and child.is_mutable(): |
---|
696 | - # TODO: consider adding IFileSystemNode.is_deep_immutable() |
---|
697 | - raise MustBeDeepImmutable("child '%s' is mutable" % (name,)) |
---|
698 | + child.raise_error() |
---|
699 | + if deep_immutable and not child.is_allowed_in_immutable_directory(): |
---|
700 | + raise MustBeDeepImmutableError("child '%s' is not allowed in an immutable directory" % (name,), name) |
---|
701 | if has_aux: |
---|
702 | entry = children.get_aux(name) |
---|
703 | if not entry: |
---|
704 | assert IFilesystemNode.providedBy(child), (name,child) |
---|
705 | assert isinstance(metadata, dict) |
---|
706 | - rwcap = child.get_uri() # might be RO if the child is not writeable |
---|
707 | - if rwcap is None: |
---|
708 | - rwcap = "" |
---|
709 | - assert isinstance(rwcap, str), rwcap |
---|
710 | - rocap = child.get_readonly_uri() |
---|
711 | - if rocap is None: |
---|
712 | - rocap = "" |
---|
713 | - assert isinstance(rocap, str), rocap |
---|
714 | + rw_uri = child.get_write_uri() |
---|
715 | + if rw_uri is None: |
---|
716 | + rw_uri = "" |
---|
717 | + assert isinstance(rw_uri, str), rw_uri |
---|
718 | + ro_uri = child.get_readonly_uri() |
---|
719 | + if ro_uri is None: |
---|
720 | + ro_uri = "" |
---|
721 | + assert isinstance(ro_uri, str), ro_uri |
---|
722 | entry = "".join([netstring(name.encode("utf-8")), |
---|
723 | - netstring(rocap), |
---|
724 | - netstring(_encrypt_rwcap(filenode, rwcap)), |
---|
725 | + netstring(strip_prefix_for_ro(ro_uri, deep_immutable)), |
---|
726 | + netstring(_encrypt_rw_uri(filenode, rw_uri)), |
---|
727 | netstring(simplejson.dumps(metadata))]) |
---|
728 | entries.append(netstring(entry)) |
---|
729 | return "".join(entries) |
---|
730 | @@ -230,38 +237,66 @@ |
---|
731 | plaintext = cryptor.process(crypttext) |
---|
732 | return plaintext |
---|
733 | |
---|
734 | - def _create_node(self, rwcap, rocap): |
---|
735 | - return self._nodemaker.create_from_cap(rwcap, rocap) |
---|
736 | + def _create_and_validate_node(self, rw_uri, ro_uri, name): |
---|
737 | + #print "mutable? %r\n" % self.is_mutable() |
---|
738 | + #print "_create_and_validate_node(rw_uri=%r, ro_uri=%r, name=%r)\n" % (rw_uri, ro_uri, name) |
---|
739 | + node = self._nodemaker.create_from_cap(rw_uri, ro_uri, |
---|
740 | + deep_immutable=not self.is_mutable(), |
---|
741 | + name=name) |
---|
742 | + node.raise_error() |
---|
743 | + return node |
---|
744 | |
---|
745 | def _unpack_contents(self, data): |
---|
746 | # the directory is serialized as a list of netstrings, one per child. |
---|
747 | - # Each child is serialized as a list of four netstrings: (name, |
---|
748 | - # rocap, rwcap, metadata), in which the name,rocap,metadata are in |
---|
749 | - # cleartext. The 'name' is UTF-8 encoded. The rwcap is formatted as: |
---|
750 | - # pack("16ss32s", iv, AES(H(writekey+iv), plaintextrwcap), mac) |
---|
751 | + # Each child is serialized as a list of four netstrings: (name, ro_uri, |
---|
752 | + # rwcapdata, metadata), in which the name, ro_uri, metadata are in |
---|
753 | + # cleartext. The 'name' is UTF-8 encoded. The rwcapdata is formatted as: |
---|
754 | + # pack("16ss32s", iv, AES(H(writekey+iv), plaintext_rw_uri), mac) |
---|
755 | assert isinstance(data, str), (repr(data), type(data)) |
---|
756 | # an empty directory is serialized as an empty string |
---|
757 | if data == "": |
---|
758 | return AuxValueDict() |
---|
759 | writeable = not self.is_readonly() |
---|
760 | + mutable = self.is_mutable() |
---|
761 | children = AuxValueDict() |
---|
762 | position = 0 |
---|
763 | while position < len(data): |
---|
764 | entries, position = split_netstring(data, 1, position) |
---|
765 | entry = entries[0] |
---|
766 | - (name, rocap, rwcapdata, metadata_s), subpos = split_netstring(entry, 4) |
---|
767 | + (name, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4) |
---|
768 | name = name.decode("utf-8") |
---|
769 | - rwcap = None |
---|
770 | + rw_uri = "" |
---|
771 | if writeable: |
---|
772 | - rwcap = self._decrypt_rwcapdata(rwcapdata) |
---|
773 | - if not rwcap: |
---|
774 | - rwcap = None # rwcap is None or a non-empty string |
---|
775 | - if not rocap: |
---|
776 | - rocap = None # rocap is None or a non-empty string |
---|
777 | - child = self._create_node(rwcap, rocap) |
---|
778 | - metadata = simplejson.loads(metadata_s) |
---|
779 | - assert isinstance(metadata, dict) |
---|
780 | - children.set_with_aux(name, (child, metadata), auxilliary=entry) |
---|
781 | + rw_uri = self._decrypt_rwcapdata(rwcapdata) |
---|
782 | + #print "mutable=%r, writeable=%r, rw_uri=%r, ro_uri=%r, name=%r" % (mutable, writeable, rw_uri, ro_uri, name) |
---|
783 | + |
---|
784 | + # Since the encryption uses CTR mode, it currently leaks the length of the |
---|
785 | + # plaintext rw_uri -- and therefore whether it is present, i.e. whether the |
---|
786 | + # dirnode is writable (ticket #925). By stripping spaces in Tahoe >= 1.6.0, |
---|
787 | + # we may make it easier for future versions to plug this leak. |
---|
788 | + rw_uri = rw_uri.strip(' ') |
---|
789 | + if not rw_uri: |
---|
790 | + rw_uri = None # rw_uri is None or a non-empty string |
---|
791 | + |
---|
792 | + # Treat ro_uri in the same way for consistency. |
---|
793 | + ro_uri = ro_uri.strip(' ') |
---|
794 | + if not ro_uri: |
---|
795 | + ro_uri = None # ro_uri is None or a non-empty string |
---|
796 | + |
---|
797 | + try: |
---|
798 | + child = self._create_and_validate_node(rw_uri, ro_uri, name) |
---|
799 | + #print "%r.is_allowed_in_immutable_directory() = %r" % (child, child.is_allowed_in_immutable_directory()) |
---|
800 | + if mutable or child.is_allowed_in_immutable_directory(): |
---|
801 | + metadata = simplejson.loads(metadata_s) |
---|
802 | + assert isinstance(metadata, dict) |
---|
803 | + children[name] = (child, metadata) |
---|
804 | + children.set_with_aux(name, (child, metadata), auxilliary=entry) |
---|
805 | + except CapConstraintError, e: |
---|
806 | + #print "unmet constraint: (%s, %s)" % (e.args[0], e.args[1].encode("utf-8")) |
---|
807 | + log.msg(format="unmet constraint on cap for child '%(name)s' unpacked from a directory:\n" |
---|
808 | + "%(message)s", message=e.args[0], name=e.args[1].encode("utf-8"), |
---|
809 | + facility="tahoe.webish", level=log.UNUSUAL) |
---|
810 | + |
---|
811 | return children |
---|
812 | |
---|
813 | def _pack_contents(self, children): |
---|
814 | @@ -270,21 +305,39 @@ |
---|
815 | |
---|
816 | def is_readonly(self): |
---|
817 | return self._node.is_readonly() |
---|
818 | + |
---|
819 | def is_mutable(self): |
---|
820 | return self._node.is_mutable() |
---|
821 | |
---|
822 | + def is_unknown(self): |
---|
823 | + return False |
---|
824 | + |
---|
825 | + def is_allowed_in_immutable_directory(self): |
---|
826 | + return not self._node.is_mutable() |
---|
827 | + |
---|
828 | + def raise_error(self): |
---|
829 | + pass |
---|
830 | + |
---|
831 | def get_uri(self): |
---|
832 | return self._uri.to_string() |
---|
833 | |
---|
834 | + def get_write_uri(self): |
---|
835 | + if self.is_readonly(): |
---|
836 | + return None |
---|
837 | + return self._uri.to_string() |
---|
838 | + |
---|
839 | def get_readonly_uri(self): |
---|
840 | return self._uri.get_readonly().to_string() |
---|
841 | |
---|
842 | def get_cap(self): |
---|
843 | return self._uri |
---|
844 | + |
---|
845 | def get_readcap(self): |
---|
846 | return self._uri.get_readonly() |
---|
847 | + |
---|
848 | def get_verify_cap(self): |
---|
849 | return self._uri.get_verify_cap() |
---|
850 | + |
---|
851 | def get_repair_cap(self): |
---|
852 | if self._node.is_readonly(): |
---|
853 | return None # readonly (mutable) dirnodes are not yet repairable |
---|
854 | @@ -350,7 +403,7 @@ |
---|
855 | def set_metadata_for(self, name, metadata): |
---|
856 | assert isinstance(name, unicode) |
---|
857 | if self.is_readonly(): |
---|
858 | - return defer.fail(NotMutableError()) |
---|
859 | + return defer.fail(NotWriteableError()) |
---|
860 | assert isinstance(metadata, dict) |
---|
861 | s = MetadataSetter(self, name, metadata) |
---|
862 | d = self._node.modify(s.modify) |
---|
863 | @@ -398,14 +451,10 @@ |
---|
864 | precondition(isinstance(name, unicode), name) |
---|
865 | precondition(isinstance(writecap, (str,type(None))), writecap) |
---|
866 | precondition(isinstance(readcap, (str,type(None))), readcap) |
---|
867 | - child_node = self._create_node(writecap, readcap) |
---|
868 | - if isinstance(child_node, UnknownNode): |
---|
869 | - # don't be willing to pack unknown nodes: we might accidentally |
---|
870 | - # put some write-authority into the rocap slot because we don't |
---|
871 | - # know how to diminish the URI they gave us. We don't even know |
---|
872 | - # if they gave us a readcap or a writecap. |
---|
873 | - msg = "cannot pack unknown node as child %s" % str(name) |
---|
874 | - raise CannotPackUnknownNodeError(msg) |
---|
875 | + |
---|
876 | + # We now allow packing unknown nodes, provided they are valid |
---|
877 | + # for this type of directory. |
---|
878 | + child_node = self._create_and_validate_node(writecap, readcap, name) |
---|
879 | d = self.set_node(name, child_node, metadata, overwrite) |
---|
880 | d.addCallback(lambda res: child_node) |
---|
881 | return d |
---|
882 | @@ -423,10 +472,10 @@ |
---|
883 | writecap, readcap, metadata = e |
---|
884 | precondition(isinstance(writecap, (str,type(None))), writecap) |
---|
885 | precondition(isinstance(readcap, (str,type(None))), readcap) |
---|
886 | - child_node = self._create_node(writecap, readcap) |
---|
887 | - if isinstance(child_node, UnknownNode): |
---|
888 | - msg = "cannot pack unknown node as child %s" % str(name) |
---|
889 | - raise CannotPackUnknownNodeError(msg) |
---|
890 | + |
---|
891 | + # We now allow packing unknown nodes, provided they are valid |
---|
892 | + # for this type of directory. |
---|
893 | + child_node = self._create_and_validate_node(writecap, readcap, name) |
---|
894 | a.set_node(name, child_node, metadata) |
---|
895 | d = self._node.modify(a.modify) |
---|
896 | d.addCallback(lambda ign: self) |
---|
897 | @@ -439,12 +488,12 @@ |
---|
898 | same name. |
---|
899 | |
---|
900 | If this directory node is read-only, the Deferred will errback with a |
---|
901 | - NotMutableError.""" |
---|
902 | + NotWriteableError.""" |
---|
903 | |
---|
904 | precondition(IFilesystemNode.providedBy(child), child) |
---|
905 | |
---|
906 | if self.is_readonly(): |
---|
907 | - return defer.fail(NotMutableError()) |
---|
908 | + return defer.fail(NotWriteableError()) |
---|
909 | assert isinstance(name, unicode) |
---|
910 | assert IFilesystemNode.providedBy(child), child |
---|
911 | a = Adder(self, overwrite=overwrite) |
---|
912 | @@ -456,7 +505,7 @@ |
---|
913 | def set_nodes(self, entries, overwrite=True): |
---|
914 | precondition(isinstance(entries, dict), entries) |
---|
915 | if self.is_readonly(): |
---|
916 | - return defer.fail(NotMutableError()) |
---|
917 | + return defer.fail(NotWriteableError()) |
---|
918 | a = Adder(self, entries, overwrite=overwrite) |
---|
919 | d = self._node.modify(a.modify) |
---|
920 | d.addCallback(lambda res: self) |
---|
921 | @@ -470,10 +519,10 @@ |
---|
922 | the operation completes.""" |
---|
923 | assert isinstance(name, unicode) |
---|
924 | if self.is_readonly(): |
---|
925 | - return defer.fail(NotMutableError()) |
---|
926 | + return defer.fail(NotWriteableError()) |
---|
927 | d = self._uploader.upload(uploadable) |
---|
928 | - d.addCallback(lambda results: results.uri) |
---|
929 | - d.addCallback(self._nodemaker.create_from_cap) |
---|
930 | + d.addCallback(lambda results: |
---|
931 | + self._create_and_validate_node(results.uri, None, name)) |
---|
932 | d.addCallback(lambda node: |
---|
933 | self.set_node(name, node, metadata, overwrite)) |
---|
934 | return d |
---|
935 | @@ -483,7 +532,7 @@ |
---|
936 | fires (with the node just removed) when the operation finishes.""" |
---|
937 | assert isinstance(name, unicode) |
---|
938 | if self.is_readonly(): |
---|
939 | - return defer.fail(NotMutableError()) |
---|
940 | + return defer.fail(NotWriteableError()) |
---|
941 | deleter = Deleter(self, name) |
---|
942 | d = self._node.modify(deleter.modify) |
---|
943 | d.addCallback(lambda res: deleter.old_child) |
---|
944 | @@ -493,7 +542,7 @@ |
---|
945 | mutable=True): |
---|
946 | assert isinstance(name, unicode) |
---|
947 | if self.is_readonly(): |
---|
948 | - return defer.fail(NotMutableError()) |
---|
949 | + return defer.fail(NotWriteableError()) |
---|
950 | if mutable: |
---|
951 | d = self._nodemaker.create_new_mutable_directory(initial_children) |
---|
952 | else: |
---|
953 | @@ -515,7 +564,7 @@ |
---|
954 | Deferred that fires when the operation finishes.""" |
---|
955 | assert isinstance(current_child_name, unicode) |
---|
956 | if self.is_readonly() or new_parent.is_readonly(): |
---|
957 | - return defer.fail(NotMutableError()) |
---|
958 | + return defer.fail(NotWriteableError()) |
---|
959 | if new_child_name is None: |
---|
960 | new_child_name = current_child_name |
---|
961 | assert isinstance(new_child_name, unicode) |
---|
962 | diff -rN -u old-tahoe/src/allmydata/immutable/filenode.py new-tahoe/src/allmydata/immutable/filenode.py |
---|
963 | --- old-tahoe/src/allmydata/immutable/filenode.py 2010-01-24 05:52:01.109000000 +0000 |
---|
964 | +++ new-tahoe/src/allmydata/immutable/filenode.py 2010-01-24 05:52:05.564000000 +0000 |
---|
965 | @@ -17,6 +17,9 @@ |
---|
966 | class _ImmutableFileNodeBase(object): |
---|
967 | implements(IImmutableFileNode, ICheckable) |
---|
968 | |
---|
969 | + def get_write_uri(self): |
---|
970 | + return None |
---|
971 | + |
---|
972 | def get_readonly_uri(self): |
---|
973 | return self.get_uri() |
---|
974 | |
---|
975 | @@ -26,6 +29,15 @@ |
---|
976 | def is_readonly(self): |
---|
977 | return True |
---|
978 | |
---|
979 | + def is_unknown(self): |
---|
980 | + return False |
---|
981 | + |
---|
982 | + def is_allowed_in_immutable_directory(self): |
---|
983 | + return True |
---|
984 | + |
---|
985 | + def raise_error(self): |
---|
986 | + pass |
---|
987 | + |
---|
988 | def __hash__(self): |
---|
989 | return self.u.__hash__() |
---|
990 | def __eq__(self, other): |
---|
991 | diff -rN -u old-tahoe/src/allmydata/interfaces.py new-tahoe/src/allmydata/interfaces.py |
---|
992 | --- old-tahoe/src/allmydata/interfaces.py 2010-01-24 05:52:01.138000000 +0000 |
---|
993 | +++ new-tahoe/src/allmydata/interfaces.py 2010-01-24 05:52:05.593000000 +0000 |
---|
994 | @@ -426,6 +426,7 @@ |
---|
995 | """Return True if the data can be modified by *somebody* (perhaps |
---|
996 | someone who has a more powerful URI than this one).""" |
---|
997 | |
---|
998 | + # TODO: rename to get_read_cap() |
---|
999 | def get_readonly(): |
---|
1000 | """Return another IURI instance, which represents a read-only form of |
---|
1001 | this one. If is_readonly() is True, this returns self.""" |
---|
1002 | @@ -456,7 +457,6 @@ |
---|
1003 | class IDirnodeURI(Interface): |
---|
1004 | """I am a URI which represents a dirnode.""" |
---|
1005 | |
---|
1006 | - |
---|
1007 | class IFileURI(Interface): |
---|
1008 | """I am a URI which represents a filenode.""" |
---|
1009 | def get_size(): |
---|
1010 | @@ -467,21 +467,28 @@ |
---|
1011 | |
---|
1012 | class IMutableFileURI(Interface): |
---|
1013 | """I am a URI which represents a mutable filenode.""" |
---|
1014 | + |
---|
1015 | class IDirectoryURI(Interface): |
---|
1016 | pass |
---|
1017 | + |
---|
1018 | class IReadonlyDirectoryURI(Interface): |
---|
1019 | pass |
---|
1020 | |
---|
1021 | -class CannotPackUnknownNodeError(Exception): |
---|
1022 | - """UnknownNodes (using filecaps from the future that we don't understand) |
---|
1023 | - cannot yet be copied safely, so I refuse to copy them.""" |
---|
1024 | - |
---|
1025 | -class UnhandledCapTypeError(Exception): |
---|
1026 | - """I recognize the cap/URI, but I cannot create an IFilesystemNode for |
---|
1027 | - it.""" |
---|
1028 | +class CapConstraintError(Exception): |
---|
1029 | + """A constraint on a cap was violated.""" |
---|
1030 | |
---|
1031 | -class NotDeepImmutableError(Exception): |
---|
1032 | - """Deep-immutable directories can only contain deep-immutable children""" |
---|
1033 | +class MustBeDeepImmutableError(CapConstraintError): |
---|
1034 | + """Mutable children cannot be added to an immutable directory. |
---|
1035 | + Also, caps obtained from an immutable directory can trigger this error |
---|
1036 | + if they are later found to refer to a mutable object and then used.""" |
---|
1037 | + |
---|
1038 | +class MustBeReadonlyError(CapConstraintError): |
---|
1039 | + """Known write caps cannot be specified in a ro_uri field. Also, |
---|
1040 | + caps obtained from a ro_uri field can trigger this error if they |
---|
1041 | + are later found to be write caps and then used.""" |
---|
1042 | + |
---|
1043 | +class MustNotBeUnknownRWError(CapConstraintError): |
---|
1044 | + """Cannot add an unknown child cap specified in a rw_uri field.""" |
---|
1045 | |
---|
1046 | # The hierarchy looks like this: |
---|
1047 | # IFilesystemNode |
---|
1048 | @@ -518,9 +525,8 @@ |
---|
1049 | """ |
---|
1050 | |
---|
1051 | def get_uri(): |
---|
1052 | - """ |
---|
1053 | - Return the URI string that can be used by others to get access to |
---|
1054 | - this node. If this node is read-only, the URI will only offer |
---|
1055 | + """Return the URI string corresponding to the strongest cap associated |
---|
1056 | + with this node. If this node is read-only, the URI will only offer |
---|
1057 | read-only access. If this node is read-write, the URI will offer |
---|
1058 | read-write access. |
---|
1059 | |
---|
1060 | @@ -528,6 +534,11 @@ |
---|
1061 | read-only access with others, use get_readonly_uri(). |
---|
1062 | """ |
---|
1063 | |
---|
1064 | + def get_write_uri(n): |
---|
1065 | + """Return the URI string that can be used by others to get write |
---|
1066 | + access to this node, if it is writeable. If this is a read-only node, |
---|
1067 | + return None.""" |
---|
1068 | + |
---|
1069 | def get_readonly_uri(): |
---|
1070 | """Return the URI string that can be used by others to get read-only |
---|
1071 | access to this node. The result is a read-only URI, regardless of |
---|
1072 | @@ -557,6 +568,18 @@ |
---|
1073 | file. |
---|
1074 | """ |
---|
1075 | |
---|
1076 | + def is_unknown(): |
---|
1077 | + """Return True if this is an unknown node.""" |
---|
1078 | + |
---|
1079 | + def is_allowed_in_immutable_directory(): |
---|
1080 | + """Return True if this node is allowed as a child of a deep-immutable |
---|
1081 | + directory. This is true if either the node is of a known-immutable type, |
---|
1082 | + or it is unknown and read-only. |
---|
1083 | + """ |
---|
1084 | + |
---|
1085 | + def raise_error(): |
---|
1086 | + """Raise any error associated with this node.""" |
---|
1087 | + |
---|
1088 | def get_size(): |
---|
1089 | """Return the length (in bytes) of the data this node represents. For |
---|
1090 | directory nodes, I return the size of the backing store. I return |
---|
1091 | @@ -902,7 +925,7 @@ |
---|
1092 | ctime/mtime semantics of traditional filesystems. |
---|
1093 | |
---|
1094 | If this directory node is read-only, the Deferred will errback with a |
---|
1095 | - NotMutableError.""" |
---|
1096 | + NotWriteableError.""" |
---|
1097 | |
---|
1098 | def set_children(entries, overwrite=True): |
---|
1099 | """Add multiple children (by writecap+readcap) to a directory node. |
---|
1100 | @@ -928,7 +951,7 @@ |
---|
1101 | ctime/mtime semantics of traditional filesystems. |
---|
1102 | |
---|
1103 | If this directory node is read-only, the Deferred will errback with a |
---|
1104 | - NotMutableError.""" |
---|
1105 | + NotWriteableError.""" |
---|
1106 | |
---|
1107 | def set_nodes(entries, overwrite=True): |
---|
1108 | """Add multiple children to a directory node. Takes a dict mapping |
---|
1109 | @@ -2074,7 +2097,7 @@ |
---|
1110 | Tahoe process will typically have a single NodeMaker, but unit tests may |
---|
1111 | create simplified/mocked forms for testing purposes. |
---|
1112 | """ |
---|
1113 | - def create_from_cap(writecap, readcap=None): |
---|
1114 | + def create_from_cap(writecap, readcap=None, **kwargs): |
---|
1115 | """I create an IFilesystemNode from the given writecap/readcap. I can |
---|
1116 | only provide nodes for existing file/directory objects: use my other |
---|
1117 | methods to create new objects. I return synchronously.""" |
---|
1118 | diff -rN -u old-tahoe/src/allmydata/mutable/common.py new-tahoe/src/allmydata/mutable/common.py |
---|
1119 | --- old-tahoe/src/allmydata/mutable/common.py 2010-01-24 05:52:01.186000000 +0000 |
---|
1120 | +++ new-tahoe/src/allmydata/mutable/common.py 2010-01-24 05:52:05.658000000 +0000 |
---|
1121 | @@ -8,7 +8,7 @@ |
---|
1122 | # creation |
---|
1123 | MODE_READ = "MODE_READ" |
---|
1124 | |
---|
1125 | -class NotMutableError(Exception): |
---|
1126 | +class NotWriteableError(Exception): |
---|
1127 | pass |
---|
1128 | |
---|
1129 | class NeedMoreDataError(Exception): |
---|
1130 | diff -rN -u old-tahoe/src/allmydata/mutable/filenode.py new-tahoe/src/allmydata/mutable/filenode.py |
---|
1131 | --- old-tahoe/src/allmydata/mutable/filenode.py 2010-01-24 05:52:01.191000000 +0000 |
---|
1132 | +++ new-tahoe/src/allmydata/mutable/filenode.py 2010-01-24 05:52:05.665000000 +0000 |
---|
1133 | @@ -214,6 +214,12 @@ |
---|
1134 | |
---|
1135 | def get_uri(self): |
---|
1136 | return self._uri.to_string() |
---|
1137 | + |
---|
1138 | + def get_write_uri(self): |
---|
1139 | + if self.is_readonly(): |
---|
1140 | + return None |
---|
1141 | + return self._uri.to_string() |
---|
1142 | + |
---|
1143 | def get_readonly_uri(self): |
---|
1144 | return self._uri.get_readonly().to_string() |
---|
1145 | |
---|
1146 | @@ -227,9 +233,19 @@ |
---|
1147 | |
---|
1148 | def is_mutable(self): |
---|
1149 | return self._uri.is_mutable() |
---|
1150 | + |
---|
1151 | def is_readonly(self): |
---|
1152 | return self._uri.is_readonly() |
---|
1153 | |
---|
1154 | + def is_unknown(self): |
---|
1155 | + return False |
---|
1156 | + |
---|
1157 | + def is_allowed_in_immutable_directory(self): |
---|
1158 | + return not self._uri.is_mutable() |
---|
1159 | + |
---|
1160 | + def raise_error(self): |
---|
1161 | + pass |
---|
1162 | + |
---|
1163 | def __hash__(self): |
---|
1164 | return hash((self.__class__, self._uri)) |
---|
1165 | def __cmp__(self, them): |
---|
1166 | diff -rN -u old-tahoe/src/allmydata/nodemaker.py new-tahoe/src/allmydata/nodemaker.py |
---|
1167 | --- old-tahoe/src/allmydata/nodemaker.py 2010-01-24 05:52:01.249000000 +0000 |
---|
1168 | +++ new-tahoe/src/allmydata/nodemaker.py 2010-01-24 05:52:05.708000000 +0000 |
---|
1169 | @@ -1,7 +1,7 @@ |
---|
1170 | import weakref |
---|
1171 | from zope.interface import implements |
---|
1172 | from allmydata.util.assertutil import precondition |
---|
1173 | -from allmydata.interfaces import INodeMaker, NotDeepImmutableError |
---|
1174 | +from allmydata.interfaces import INodeMaker, MustBeDeepImmutableError |
---|
1175 | from allmydata.immutable.filenode import ImmutableFileNode, LiteralFileNode |
---|
1176 | from allmydata.immutable.upload import Data |
---|
1177 | from allmydata.mutable.filenode import MutableFileNode |
---|
1178 | @@ -44,28 +44,36 @@ |
---|
1179 | def _create_dirnode(self, filenode): |
---|
1180 | return DirectoryNode(filenode, self, self.uploader) |
---|
1181 | |
---|
1182 | - def create_from_cap(self, writecap, readcap=None): |
---|
1183 | + def create_from_cap(self, writecap, readcap=None, deep_immutable=False, name=u"<unknown name>"): |
---|
1184 | # this returns synchronously. It starts with a "cap string". |
---|
1185 | assert isinstance(writecap, (str, type(None))), type(writecap) |
---|
1186 | assert isinstance(readcap, (str, type(None))), type(readcap) |
---|
1187 | + #import traceback |
---|
1188 | + #traceback.print_stack() |
---|
1189 | + #print '%r.create_from_cap(%r, %r, %r)' % (self, writecap, readcap, kwargs) |
---|
1190 | + |
---|
1191 | bigcap = writecap or readcap |
---|
1192 | if not bigcap: |
---|
1193 | # maybe the writecap was hidden because we're in a readonly |
---|
1194 | # directory, and the future cap format doesn't have a readcap, or |
---|
1195 | # something. |
---|
1196 | - return UnknownNode(writecap, readcap) |
---|
1197 | - if bigcap in self._node_cache: |
---|
1198 | - return self._node_cache[bigcap] |
---|
1199 | - cap = uri.from_string(bigcap) |
---|
1200 | - node = self._create_from_cap(cap) |
---|
1201 | + return UnknownNode(None, None) # deep_immutable and name not needed |
---|
1202 | + |
---|
1203 | + # The name doesn't matter for caching since it's only used in the error |
---|
1204 | + # attribute of an UnknownNode, and we don't cache those. |
---|
1205 | + memokey = ("I" if deep_immutable else "M") + bigcap |
---|
1206 | + if memokey in self._node_cache: |
---|
1207 | + return self._node_cache[memokey] |
---|
1208 | + cap = uri.from_string(bigcap, deep_immutable=deep_immutable, name=name) |
---|
1209 | + node = self._create_from_single_cap(cap) |
---|
1210 | if node: |
---|
1211 | - self._node_cache[bigcap] = node # note: WeakValueDictionary |
---|
1212 | + self._node_cache[memokey] = node # note: WeakValueDictionary |
---|
1213 | else: |
---|
1214 | - node = UnknownNode(writecap, readcap) # don't cache UnknownNode |
---|
1215 | + # don't cache UnknownNode |
---|
1216 | + node = UnknownNode(writecap, readcap, deep_immutable=deep_immutable, name=name) |
---|
1217 | return node |
---|
1218 | |
---|
1219 | - def _create_from_cap(self, cap): |
---|
1220 | - # This starts with a "cap instance" |
---|
1221 | + def _create_from_single_cap(self, cap): |
---|
1222 | if isinstance(cap, uri.LiteralFileURI): |
---|
1223 | return self._create_lit(cap) |
---|
1224 | if isinstance(cap, uri.CHKFileURI): |
---|
1225 | @@ -76,7 +84,7 @@ |
---|
1226 | uri.ReadonlyDirectoryURI, |
---|
1227 | uri.ImmutableDirectoryURI, |
---|
1228 | uri.LiteralDirectoryURI)): |
---|
1229 | - filenode = self._create_from_cap(cap.get_filenode_cap()) |
---|
1230 | + filenode = self._create_from_single_cap(cap.get_filenode_cap()) |
---|
1231 | return self._create_dirnode(filenode) |
---|
1232 | return None |
---|
1233 | |
---|
1234 | @@ -89,13 +97,11 @@ |
---|
1235 | return d |
---|
1236 | |
---|
1237 | def create_new_mutable_directory(self, initial_children={}): |
---|
1238 | - # initial_children must have metadata (i.e. {} instead of None), and |
---|
1239 | - # should not contain UnknownNodes |
---|
1240 | + # initial_children must have metadata (i.e. {} instead of None) |
---|
1241 | for (name, (node, metadata)) in initial_children.iteritems(): |
---|
1242 | - precondition(not isinstance(node, UnknownNode), |
---|
1243 | - "create_new_mutable_directory does not accept UnknownNode", node) |
---|
1244 | precondition(isinstance(metadata, dict), |
---|
1245 | "create_new_mutable_directory requires metadata to be a dict, not None", metadata) |
---|
1246 | + node.raise_error() |
---|
1247 | d = self.create_mutable_file(lambda n: |
---|
1248 | pack_children(n, initial_children)) |
---|
1249 | d.addCallback(self._create_dirnode) |
---|
1250 | @@ -105,19 +111,15 @@ |
---|
1251 | if convergence is None: |
---|
1252 | convergence = self.secret_holder.get_convergence_secret() |
---|
1253 | for (name, (node, metadata)) in children.iteritems(): |
---|
1254 | - precondition(not isinstance(node, UnknownNode), |
---|
1255 | - "create_immutable_directory does not accept UnknownNode", node) |
---|
1256 | precondition(isinstance(metadata, dict), |
---|
1257 | "create_immutable_directory requires metadata to be a dict, not None", metadata) |
---|
1258 | - if node.is_mutable(): |
---|
1259 | - raise NotDeepImmutableError("%s is not immutable" % (node,)) |
---|
1260 | + node.raise_error() |
---|
1261 | + if not node.is_allowed_in_immutable_directory(): |
---|
1262 | + raise MustBeDeepImmutableError("%s is not immutable" % (node,), name) |
---|
1263 | n = DummyImmutableFileNode() # writekey=None |
---|
1264 | packed = pack_children(n, children) |
---|
1265 | uploadable = Data(packed, convergence) |
---|
1266 | d = self.uploader.upload(uploadable, history=self.history) |
---|
1267 | - def _uploaded(results): |
---|
1268 | - filecap = self.create_from_cap(results.uri) |
---|
1269 | - return filecap |
---|
1270 | - d.addCallback(_uploaded) |
---|
1271 | + d.addCallback(lambda results: self.create_from_cap(None, results.uri)) |
---|
1272 | d.addCallback(self._create_dirnode) |
---|
1273 | return d |
---|
1274 | diff -rN -u old-tahoe/src/allmydata/scripts/common.py new-tahoe/src/allmydata/scripts/common.py |
---|
1275 | --- old-tahoe/src/allmydata/scripts/common.py 2010-01-24 05:52:01.296000000 +0000 |
---|
1276 | +++ new-tahoe/src/allmydata/scripts/common.py 2010-01-24 05:52:05.785000000 +0000 |
---|
1277 | @@ -128,12 +128,14 @@ |
---|
1278 | pass |
---|
1279 | |
---|
1280 | def get_alias(aliases, path, default): |
---|
1281 | + from allmydata import uri |
---|
1282 | # transform "work:path/filename" into (aliases["work"], "path/filename"). |
---|
1283 | # If default=None, then an empty alias is indicated by returning |
---|
1284 | - # DefaultAliasMarker. We special-case "URI:" to make it easy to access |
---|
1285 | - # specific files/directories by their read-cap. |
---|
1286 | + # DefaultAliasMarker. We special-case strings with a recognized cap URI |
---|
1287 | + # prefix, to make it easy to access specific files/directories by their |
---|
1288 | + # caps. |
---|
1289 | path = path.strip() |
---|
1290 | - if path.startswith("URI:"): |
---|
1291 | + if uri.has_uri_prefix(path): |
---|
1292 | # The only way to get a sub-path is to use URI:blah:./foo, and we |
---|
1293 | # strip out the :./ sequence. |
---|
1294 | sep = path.find(":./") |
---|
1295 | diff -rN -u old-tahoe/src/allmydata/scripts/tahoe_cp.py new-tahoe/src/allmydata/scripts/tahoe_cp.py |
---|
1296 | --- old-tahoe/src/allmydata/scripts/tahoe_cp.py 2010-01-24 05:52:01.358000000 +0000 |
---|
1297 | +++ new-tahoe/src/allmydata/scripts/tahoe_cp.py 2010-01-24 05:52:05.845000000 +0000 |
---|
1298 | @@ -258,8 +258,7 @@ |
---|
1299 | readcap = ascii_or_none(data[1].get("ro_uri")) |
---|
1300 | self.children[name] = TahoeFileSource(self.nodeurl, mutable, |
---|
1301 | writecap, readcap) |
---|
1302 | - else: |
---|
1303 | - assert data[0] == "dirnode" |
---|
1304 | + elif data[0] == "dirnode": |
---|
1305 | writecap = ascii_or_none(data[1].get("rw_uri")) |
---|
1306 | readcap = ascii_or_none(data[1].get("ro_uri")) |
---|
1307 | if writecap and writecap in self.cache: |
---|
1308 | @@ -277,6 +276,11 @@ |
---|
1309 | if recurse: |
---|
1310 | child.populate(True) |
---|
1311 | self.children[name] = child |
---|
1312 | + else: |
---|
1313 | + # TODO: there should be an option to skip unknown nodes. |
---|
1314 | + raise TahoeError("Cannot copy unknown nodes (ticket #839). " |
---|
1315 | + "You probably need to use a later version of " |
---|
1316 | + "Tahoe-LAFS to copy this directory.") |
---|
1317 | |
---|
1318 | class TahoeMissingTarget: |
---|
1319 | def __init__(self, url): |
---|
1320 | @@ -353,8 +357,7 @@ |
---|
1321 | urllib.quote(name.encode('utf-8'))]) |
---|
1322 | self.children[name] = TahoeFileTarget(self.nodeurl, mutable, |
---|
1323 | writecap, readcap, url) |
---|
1324 | - else: |
---|
1325 | - assert data[0] == "dirnode" |
---|
1326 | + elif data[0] == "dirnode": |
---|
1327 | writecap = ascii_or_none(data[1].get("rw_uri")) |
---|
1328 | readcap = ascii_or_none(data[1].get("ro_uri")) |
---|
1329 | if writecap and writecap in self.cache: |
---|
1330 | @@ -372,6 +375,11 @@ |
---|
1331 | if recurse: |
---|
1332 | child.populate(True) |
---|
1333 | self.children[name] = child |
---|
1334 | + else: |
---|
1335 | + # TODO: there should be an option to skip unknown nodes. |
---|
1336 | + raise TahoeError("Cannot copy unknown nodes (ticket #839). " |
---|
1337 | + "You probably need to use a later version of " |
---|
1338 | + "Tahoe-LAFS to copy this directory.") |
---|
1339 | |
---|
1340 | def get_child_target(self, name): |
---|
1341 | # return a new target for a named subdirectory of this dir |
---|
1342 | @@ -407,9 +415,11 @@ |
---|
1343 | set_data = {} |
---|
1344 | for (name, filecap) in self.new_children.items(): |
---|
1345 | # it just so happens that ?t=set_children will accept both file |
---|
1346 | - # read-caps and write-caps as ['rw_uri'], and will handle eithe |
---|
1347 | + # read-caps and write-caps as ['rw_uri'], and will handle either |
---|
1348 | # correctly. So don't bother trying to figure out whether the one |
---|
1349 | # we have is read-only or read-write. |
---|
1350 | + # TODO: think about how this affects forward-compatibility for |
---|
1351 | + # unknown caps |
---|
1352 | set_data[name] = ["filenode", {"rw_uri": filecap}] |
---|
1353 | body = simplejson.dumps(set_data) |
---|
1354 | POST(url, body) |
---|
1355 | @@ -770,6 +780,7 @@ |
---|
1356 | # local-file-in-the-way |
---|
1357 | # touch proposed |
---|
1358 | # tahoe cp -r my:docs/proposed/denver.txt proposed/denver.txt |
---|
1359 | +# handling of unknown nodes |
---|
1360 | |
---|
1361 | # things that maybe should be errors but aren't |
---|
1362 | # local-dir-in-the-way |
---|
1363 | diff -rN -u old-tahoe/src/allmydata/scripts/tahoe_put.py new-tahoe/src/allmydata/scripts/tahoe_put.py |
---|
1364 | --- old-tahoe/src/allmydata/scripts/tahoe_put.py 2010-01-24 05:52:01.384000000 +0000 |
---|
1365 | +++ new-tahoe/src/allmydata/scripts/tahoe_put.py 2010-01-24 05:52:05.867000000 +0000 |
---|
1366 | @@ -40,6 +40,7 @@ |
---|
1367 | # DIRCAP:./subdir/foo : DIRCAP/subdir/foo |
---|
1368 | # MUTABLE-FILE-WRITECAP : filecap |
---|
1369 | |
---|
1370 | + # FIXME: this shouldn't rely on a particular prefix. |
---|
1371 | if to_file.startswith("URI:SSK:"): |
---|
1372 | url = nodeurl + "uri/%s" % urllib.quote(to_file) |
---|
1373 | else: |
---|
1374 | diff -rN -u old-tahoe/src/allmydata/test/common.py new-tahoe/src/allmydata/test/common.py |
---|
1375 | --- old-tahoe/src/allmydata/test/common.py 2010-01-24 05:52:01.532000000 +0000 |
---|
1376 | +++ new-tahoe/src/allmydata/test/common.py 2010-01-24 05:52:06.003000000 +0000 |
---|
1377 | @@ -51,6 +51,8 @@ |
---|
1378 | |
---|
1379 | def get_uri(self): |
---|
1380 | return self.my_uri.to_string() |
---|
1381 | + def get_write_uri(self): |
---|
1382 | + return None |
---|
1383 | def get_readonly_uri(self): |
---|
1384 | return self.my_uri.to_string() |
---|
1385 | def get_cap(self): |
---|
1386 | @@ -103,6 +105,12 @@ |
---|
1387 | return False |
---|
1388 | def is_readonly(self): |
---|
1389 | return True |
---|
1390 | + def is_unknown(self): |
---|
1391 | + return False |
---|
1392 | + def is_allowed_in_immutable_directory(self): |
---|
1393 | + return True |
---|
1394 | + def raise_error(self): |
---|
1395 | + pass |
---|
1396 | |
---|
1397 | def get_size(self): |
---|
1398 | try: |
---|
1399 | @@ -190,6 +198,10 @@ |
---|
1400 | return self.my_uri.get_readonly() |
---|
1401 | def get_uri(self): |
---|
1402 | return self.my_uri.to_string() |
---|
1403 | + def get_write_uri(self): |
---|
1404 | + if self.is_readonly(): |
---|
1405 | + return None |
---|
1406 | + return self.my_uri.to_string() |
---|
1407 | def get_readonly(self): |
---|
1408 | return self.my_uri.get_readonly() |
---|
1409 | def get_readonly_uri(self): |
---|
1410 | @@ -200,6 +212,12 @@ |
---|
1411 | return self.my_uri.is_readonly() |
---|
1412 | def is_mutable(self): |
---|
1413 | return self.my_uri.is_mutable() |
---|
1414 | + def is_unknown(self): |
---|
1415 | + return False |
---|
1416 | + def is_allowed_in_immutable_directory(self): |
---|
1417 | + return not self.my_uri.is_mutable() |
---|
1418 | + def raise_error(self): |
---|
1419 | + pass |
---|
1420 | def get_writekey(self): |
---|
1421 | return "\x00"*16 |
---|
1422 | def get_size(self): |
---|
1423 | diff -rN -u old-tahoe/src/allmydata/test/test_client.py new-tahoe/src/allmydata/test/test_client.py |
---|
1424 | --- old-tahoe/src/allmydata/test/test_client.py 2010-01-24 05:52:01.647000000 +0000 |
---|
1425 | +++ new-tahoe/src/allmydata/test/test_client.py 2010-01-24 05:52:06.135000000 +0000 |
---|
1426 | @@ -288,11 +288,14 @@ |
---|
1427 | self.failUnless(n.is_readonly()) |
---|
1428 | self.failUnless(n.is_mutable()) |
---|
1429 | |
---|
1430 | - future = "x-tahoe-crazy://future_cap_format." |
---|
1431 | - n = c.create_node_from_uri(future) |
---|
1432 | + unknown_rw = "lafs://from_the_future" |
---|
1433 | + unknown_ro = "lafs://readonly_from_the_future" |
---|
1434 | + n = c.create_node_from_uri(unknown_rw, unknown_ro) |
---|
1435 | self.failUnless(IFilesystemNode.providedBy(n)) |
---|
1436 | self.failIf(IFileNode.providedBy(n)) |
---|
1437 | self.failIf(IImmutableFileNode.providedBy(n)) |
---|
1438 | self.failIf(IMutableFileNode.providedBy(n)) |
---|
1439 | self.failIf(IDirectoryNode.providedBy(n)) |
---|
1440 | - self.failUnlessEqual(n.get_uri(), future) |
---|
1441 | + self.failUnless(n.is_unknown()) |
---|
1442 | + self.failUnlessEqual(n.get_uri(), unknown_rw) |
---|
1443 | + self.failUnlessEqual(n.get_readonly_uri(), "ro." + unknown_ro) |
---|
1444 | diff -rN -u old-tahoe/src/allmydata/test/test_dirnode.py new-tahoe/src/allmydata/test/test_dirnode.py |
---|
1445 | --- old-tahoe/src/allmydata/test/test_dirnode.py 2010-01-24 05:52:01.672000000 +0000 |
---|
1446 | +++ new-tahoe/src/allmydata/test/test_dirnode.py 2010-01-24 05:52:06.160000000 +0000 |
---|
1447 | @@ -7,8 +7,8 @@ |
---|
1448 | from allmydata.client import Client |
---|
1449 | from allmydata.immutable import upload |
---|
1450 | from allmydata.interfaces import IImmutableFileNode, IMutableFileNode, \ |
---|
1451 | - ExistingChildError, NoSuchChildError, NotDeepImmutableError, \ |
---|
1452 | - IDeepCheckResults, IDeepCheckAndRepairResults, CannotPackUnknownNodeError |
---|
1453 | + ExistingChildError, NoSuchChildError, MustBeDeepImmutableError, \ |
---|
1454 | + IDeepCheckResults, IDeepCheckAndRepairResults, MustNotBeUnknownRWError |
---|
1455 | from allmydata.mutable.filenode import MutableFileNode |
---|
1456 | from allmydata.mutable.common import UncoordinatedWriteError |
---|
1457 | from allmydata.util import hashutil, base32 |
---|
1458 | @@ -32,6 +32,11 @@ |
---|
1459 | d = c.create_dirnode() |
---|
1460 | def _done(res): |
---|
1461 | self.failUnless(isinstance(res, dirnode.DirectoryNode)) |
---|
1462 | + self.failUnless(res.is_mutable()) |
---|
1463 | + self.failIf(res.is_readonly()) |
---|
1464 | + self.failIf(res.is_unknown()) |
---|
1465 | + self.failIf(res.is_allowed_in_immutable_directory()) |
---|
1466 | + res.raise_error() |
---|
1467 | rep = str(res) |
---|
1468 | self.failUnless("RW-MUT" in rep) |
---|
1469 | d.addCallback(_done) |
---|
1470 | @@ -44,36 +49,74 @@ |
---|
1471 | nm = c.nodemaker |
---|
1472 | setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861" |
---|
1473 | one_uri = "URI:LIT:n5xgk" # LIT for "one" |
---|
1474 | + mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq" |
---|
1475 | + mut_read_uri = "URI:SSK-RO:jf6wkflosyvntwxqcdo7a54jvm:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq" |
---|
1476 | + future_write_uri = "x-tahoe-crazy://I_am_from_the_future." |
---|
1477 | + future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
1478 | kids = {u"one": (nm.create_from_cap(one_uri), {}), |
---|
1479 | u"two": (nm.create_from_cap(setup_py_uri), |
---|
1480 | {"metakey": "metavalue"}), |
---|
1481 | + u"mut": (nm.create_from_cap(mut_write_uri, mut_read_uri), {}), |
---|
1482 | + u"fut": (nm.create_from_cap(future_write_uri, future_read_uri), {}), |
---|
1483 | + u"fro": (nm.create_from_cap(None, future_read_uri), {}), |
---|
1484 | } |
---|
1485 | d = c.create_dirnode(kids) |
---|
1486 | + |
---|
1487 | def _created(dn): |
---|
1488 | self.failUnless(isinstance(dn, dirnode.DirectoryNode)) |
---|
1489 | + self.failUnless(dn.is_mutable()) |
---|
1490 | + self.failIf(dn.is_readonly()) |
---|
1491 | + self.failIf(dn.is_unknown()) |
---|
1492 | + self.failIf(dn.is_allowed_in_immutable_directory()) |
---|
1493 | + dn.raise_error() |
---|
1494 | rep = str(dn) |
---|
1495 | self.failUnless("RW-MUT" in rep) |
---|
1496 | return dn.list() |
---|
1497 | d.addCallback(_created) |
---|
1498 | + |
---|
1499 | def _check_kids(children): |
---|
1500 | - self.failUnlessEqual(sorted(children.keys()), [u"one", u"two"]) |
---|
1501 | + self.failUnlessEqual(sorted(children.keys()), |
---|
1502 | + [u"fro", u"fut", u"mut", u"one", u"two"]) |
---|
1503 | one_node, one_metadata = children[u"one"] |
---|
1504 | two_node, two_metadata = children[u"two"] |
---|
1505 | + mut_node, mut_metadata = children[u"mut"] |
---|
1506 | + fut_node, fut_metadata = children[u"fut"] |
---|
1507 | + fro_node, fro_metadata = children[u"fro"] |
---|
1508 | + |
---|
1509 | self.failUnlessEqual(one_node.get_size(), 3) |
---|
1510 | - self.failUnlessEqual(two_node.get_size(), 14861) |
---|
1511 | + self.failUnlessEqual(one_node.get_uri(), one_uri) |
---|
1512 | + self.failUnlessEqual(one_node.get_readonly_uri(), one_uri) |
---|
1513 | self.failUnless(isinstance(one_metadata, dict), one_metadata) |
---|
1514 | + |
---|
1515 | + self.failUnlessEqual(two_node.get_size(), 14861) |
---|
1516 | + self.failUnlessEqual(two_node.get_uri(), setup_py_uri) |
---|
1517 | + self.failUnlessEqual(two_node.get_readonly_uri(), setup_py_uri) |
---|
1518 | self.failUnlessEqual(two_metadata["metakey"], "metavalue") |
---|
1519 | + |
---|
1520 | + self.failUnlessEqual(mut_node.get_uri(), mut_write_uri) |
---|
1521 | + self.failUnlessEqual(mut_node.get_readonly_uri(), mut_read_uri) |
---|
1522 | + self.failUnless(isinstance(mut_metadata, dict), mut_metadata) |
---|
1523 | + |
---|
1524 | + self.failUnless(fut_node.is_unknown()) |
---|
1525 | + self.failUnlessEqual(fut_node.get_uri(), future_write_uri) |
---|
1526 | + self.failUnlessEqual(fut_node.get_readonly_uri(), "ro." + future_read_uri) |
---|
1527 | + self.failUnless(isinstance(fut_metadata, dict), fut_metadata) |
---|
1528 | + |
---|
1529 | + self.failUnless(fro_node.is_unknown()) |
---|
1530 | + self.failUnlessEqual(fro_node.get_uri(), "ro." + future_read_uri) |
---|
1531 | + self.failUnlessEqual(fut_node.get_readonly_uri(), "ro." + future_read_uri) |
---|
1532 | + self.failUnless(isinstance(fro_metadata, dict), fro_metadata) |
---|
1533 | d.addCallback(_check_kids) |
---|
1534 | + |
---|
1535 | d.addCallback(lambda ign: nm.create_new_mutable_directory(kids)) |
---|
1536 | d.addCallback(lambda dn: dn.list()) |
---|
1537 | d.addCallback(_check_kids) |
---|
1538 | - future_writecap = "x-tahoe-crazy://I_am_from_the_future." |
---|
1539 | - future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
1540 | - future_node = UnknownNode(future_writecap, future_readcap) |
---|
1541 | - bad_kids1 = {u"one": (future_node, {})} |
---|
1542 | + |
---|
1543 | + bad_future_node = UnknownNode(future_write_uri, None) |
---|
1544 | + bad_kids1 = {u"one": (bad_future_node, {})} |
---|
1545 | d.addCallback(lambda ign: |
---|
1546 | - self.shouldFail(AssertionError, "bad_kids1", |
---|
1547 | - "does not accept UnknownNode", |
---|
1548 | + self.shouldFail(MustNotBeUnknownRWError, "bad_kids1", |
---|
1549 | + "cannot attach unknown", |
---|
1550 | nm.create_new_mutable_directory, |
---|
1551 | bad_kids1)) |
---|
1552 | bad_kids2 = {u"one": (nm.create_from_cap(one_uri), None)} |
---|
1553 | @@ -91,17 +134,24 @@ |
---|
1554 | nm = c.nodemaker |
---|
1555 | setup_py_uri = "URI:CHK:n7r3m6wmomelk4sep3kw5cvduq:os7ijw5c3maek7pg65e5254k2fzjflavtpejjyhshpsxuqzhcwwq:3:20:14861" |
---|
1556 | one_uri = "URI:LIT:n5xgk" # LIT for "one" |
---|
1557 | - mut_readcap = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q" |
---|
1558 | - mut_writecap = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq" |
---|
1559 | + mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq" |
---|
1560 | + mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q" |
---|
1561 | + future_write_uri = "x-tahoe-crazy://I_am_from_the_future." |
---|
1562 | + future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
1563 | kids = {u"one": (nm.create_from_cap(one_uri), {}), |
---|
1564 | u"two": (nm.create_from_cap(setup_py_uri), |
---|
1565 | {"metakey": "metavalue"}), |
---|
1566 | + u"fut": (nm.create_from_cap(None, future_read_uri), {}), |
---|
1567 | } |
---|
1568 | d = c.create_immutable_dirnode(kids) |
---|
1569 | + |
---|
1570 | def _created(dn): |
---|
1571 | self.failUnless(isinstance(dn, dirnode.DirectoryNode)) |
---|
1572 | self.failIf(dn.is_mutable()) |
---|
1573 | self.failUnless(dn.is_readonly()) |
---|
1574 | + self.failIf(dn.is_unknown()) |
---|
1575 | + self.failUnless(dn.is_allowed_in_immutable_directory()) |
---|
1576 | + dn.raise_error() |
---|
1577 | rep = str(dn) |
---|
1578 | self.failUnless("RO-IMM" in rep) |
---|
1579 | cap = dn.get_cap() |
---|
1580 | @@ -109,50 +159,73 @@ |
---|
1581 | self.cap = cap |
---|
1582 | return dn.list() |
---|
1583 | d.addCallback(_created) |
---|
1584 | + |
---|
1585 | def _check_kids(children): |
---|
1586 | - self.failUnlessEqual(sorted(children.keys()), [u"one", u"two"]) |
---|
1587 | + self.failUnlessEqual(sorted(children.keys()), [u"fut", u"one", u"two"]) |
---|
1588 | one_node, one_metadata = children[u"one"] |
---|
1589 | two_node, two_metadata = children[u"two"] |
---|
1590 | + fut_node, fut_metadata = children[u"fut"] |
---|
1591 | + |
---|
1592 | self.failUnlessEqual(one_node.get_size(), 3) |
---|
1593 | - self.failUnlessEqual(two_node.get_size(), 14861) |
---|
1594 | + self.failUnlessEqual(one_node.get_uri(), one_uri) |
---|
1595 | + self.failUnlessEqual(one_node.get_readonly_uri(), one_uri) |
---|
1596 | self.failUnless(isinstance(one_metadata, dict), one_metadata) |
---|
1597 | + |
---|
1598 | + self.failUnlessEqual(two_node.get_size(), 14861) |
---|
1599 | + self.failUnlessEqual(two_node.get_uri(), setup_py_uri) |
---|
1600 | + self.failUnlessEqual(two_node.get_readonly_uri(), setup_py_uri) |
---|
1601 | self.failUnlessEqual(two_metadata["metakey"], "metavalue") |
---|
1602 | + |
---|
1603 | + self.failUnless(fut_node.is_unknown()) |
---|
1604 | + self.failUnlessEqual(fut_node.get_uri(), "imm." + future_read_uri) |
---|
1605 | + self.failUnlessEqual(fut_node.get_readonly_uri(), "imm." + future_read_uri) |
---|
1606 | + self.failUnless(isinstance(fut_metadata, dict), fut_metadata) |
---|
1607 | d.addCallback(_check_kids) |
---|
1608 | + |
---|
1609 | d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string())) |
---|
1610 | d.addCallback(lambda dn: dn.list()) |
---|
1611 | d.addCallback(_check_kids) |
---|
1612 | - future_writecap = "x-tahoe-crazy://I_am_from_the_future." |
---|
1613 | - future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
1614 | - future_node = UnknownNode(future_writecap, future_readcap) |
---|
1615 | - bad_kids1 = {u"one": (future_node, {})} |
---|
1616 | + |
---|
1617 | + bad_future_node1 = UnknownNode(future_write_uri, None) |
---|
1618 | + bad_kids1 = {u"one": (bad_future_node1, {})} |
---|
1619 | d.addCallback(lambda ign: |
---|
1620 | - self.shouldFail(AssertionError, "bad_kids1", |
---|
1621 | - "does not accept UnknownNode", |
---|
1622 | + self.shouldFail(MustNotBeUnknownRWError, "bad_kids1", |
---|
1623 | + "cannot attach unknown", |
---|
1624 | c.create_immutable_dirnode, |
---|
1625 | bad_kids1)) |
---|
1626 | - bad_kids2 = {u"one": (nm.create_from_cap(one_uri), None)} |
---|
1627 | + bad_future_node2 = UnknownNode(future_write_uri, future_read_uri) |
---|
1628 | + bad_kids2 = {u"one": (bad_future_node2, {})} |
---|
1629 | d.addCallback(lambda ign: |
---|
1630 | - self.shouldFail(AssertionError, "bad_kids2", |
---|
1631 | - "requires metadata to be a dict", |
---|
1632 | + self.shouldFail(MustBeDeepImmutableError, "bad_kids2", |
---|
1633 | + "is not immutable", |
---|
1634 | c.create_immutable_dirnode, |
---|
1635 | bad_kids2)) |
---|
1636 | - bad_kids3 = {u"one": (nm.create_from_cap(mut_writecap), {})} |
---|
1637 | + bad_kids3 = {u"one": (nm.create_from_cap(one_uri), None)} |
---|
1638 | d.addCallback(lambda ign: |
---|
1639 | - self.shouldFail(NotDeepImmutableError, "bad_kids3", |
---|
1640 | - "is not immutable", |
---|
1641 | + self.shouldFail(AssertionError, "bad_kids3", |
---|
1642 | + "requires metadata to be a dict", |
---|
1643 | c.create_immutable_dirnode, |
---|
1644 | bad_kids3)) |
---|
1645 | - bad_kids4 = {u"one": (nm.create_from_cap(mut_readcap), {})} |
---|
1646 | + bad_kids4 = {u"one": (nm.create_from_cap(mut_write_uri), {})} |
---|
1647 | d.addCallback(lambda ign: |
---|
1648 | - self.shouldFail(NotDeepImmutableError, "bad_kids4", |
---|
1649 | + self.shouldFail(MustBeDeepImmutableError, "bad_kids4", |
---|
1650 | "is not immutable", |
---|
1651 | c.create_immutable_dirnode, |
---|
1652 | bad_kids4)) |
---|
1653 | + bad_kids5 = {u"one": (nm.create_from_cap(mut_read_uri), {})} |
---|
1654 | + d.addCallback(lambda ign: |
---|
1655 | + self.shouldFail(MustBeDeepImmutableError, "bad_kids5", |
---|
1656 | + "is not immutable", |
---|
1657 | + c.create_immutable_dirnode, |
---|
1658 | + bad_kids5)) |
---|
1659 | d.addCallback(lambda ign: c.create_immutable_dirnode({})) |
---|
1660 | def _created_empty(dn): |
---|
1661 | self.failUnless(isinstance(dn, dirnode.DirectoryNode)) |
---|
1662 | self.failIf(dn.is_mutable()) |
---|
1663 | self.failUnless(dn.is_readonly()) |
---|
1664 | + self.failIf(dn.is_unknown()) |
---|
1665 | + self.failUnless(dn.is_allowed_in_immutable_directory()) |
---|
1666 | + dn.raise_error() |
---|
1667 | rep = str(dn) |
---|
1668 | self.failUnless("RO-IMM" in rep) |
---|
1669 | cap = dn.get_cap() |
---|
1670 | @@ -168,6 +241,9 @@ |
---|
1671 | self.failUnless(isinstance(dn, dirnode.DirectoryNode)) |
---|
1672 | self.failIf(dn.is_mutable()) |
---|
1673 | self.failUnless(dn.is_readonly()) |
---|
1674 | + self.failIf(dn.is_unknown()) |
---|
1675 | + self.failUnless(dn.is_allowed_in_immutable_directory()) |
---|
1676 | + dn.raise_error() |
---|
1677 | rep = str(dn) |
---|
1678 | self.failUnless("RO-IMM" in rep) |
---|
1679 | cap = dn.get_cap() |
---|
1680 | @@ -193,9 +269,9 @@ |
---|
1681 | d.addCallback(_check_kids) |
---|
1682 | d.addCallback(lambda ign: n.get(u"subdir")) |
---|
1683 | d.addCallback(lambda sd: self.failIf(sd.is_mutable())) |
---|
1684 | - bad_kids = {u"one": (nm.create_from_cap(mut_writecap), {})} |
---|
1685 | + bad_kids = {u"one": (nm.create_from_cap(mut_write_uri), {})} |
---|
1686 | d.addCallback(lambda ign: |
---|
1687 | - self.shouldFail(NotDeepImmutableError, "YZ", |
---|
1688 | + self.shouldFail(MustBeDeepImmutableError, "YZ", |
---|
1689 | "is not immutable", |
---|
1690 | n.create_subdirectory, |
---|
1691 | u"sub2", bad_kids, mutable=False)) |
---|
1692 | @@ -203,7 +279,6 @@ |
---|
1693 | d.addCallback(_made_parent) |
---|
1694 | return d |
---|
1695 | |
---|
1696 | - |
---|
1697 | def test_check(self): |
---|
1698 | self.basedir = "dirnode/Dirnode/test_check" |
---|
1699 | self.set_up_grid() |
---|
1700 | @@ -337,24 +412,27 @@ |
---|
1701 | ro_dn = c.create_node_from_uri(ro_uri) |
---|
1702 | self.failUnless(ro_dn.is_readonly()) |
---|
1703 | self.failUnless(ro_dn.is_mutable()) |
---|
1704 | + self.failIf(ro_dn.is_unknown()) |
---|
1705 | + self.failIf(ro_dn.is_allowed_in_immutable_directory()) |
---|
1706 | + ro_dn.raise_error() |
---|
1707 | |
---|
1708 | - self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, |
---|
1709 | + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, |
---|
1710 | ro_dn.set_uri, u"newchild", filecap, filecap) |
---|
1711 | - self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, |
---|
1712 | + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, |
---|
1713 | ro_dn.set_node, u"newchild", filenode) |
---|
1714 | - self.shouldFail(dirnode.NotMutableError, "set_nodes ro", None, |
---|
1715 | + self.shouldFail(dirnode.NotWriteableError, "set_nodes ro", None, |
---|
1716 | ro_dn.set_nodes, { u"newchild": (filenode, None) }) |
---|
1717 | - self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, |
---|
1718 | + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, |
---|
1719 | ro_dn.add_file, u"newchild", uploadable) |
---|
1720 | - self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, |
---|
1721 | + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, |
---|
1722 | ro_dn.delete, u"child") |
---|
1723 | - self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, |
---|
1724 | + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, |
---|
1725 | ro_dn.create_subdirectory, u"newchild") |
---|
1726 | - self.shouldFail(dirnode.NotMutableError, "set_metadata_for ro", None, |
---|
1727 | + self.shouldFail(dirnode.NotWriteableError, "set_metadata_for ro", None, |
---|
1728 | ro_dn.set_metadata_for, u"child", {}) |
---|
1729 | - self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, |
---|
1730 | + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, |
---|
1731 | ro_dn.move_child_to, u"child", rw_dn) |
---|
1732 | - self.shouldFail(dirnode.NotMutableError, "set_uri ro", None, |
---|
1733 | + self.shouldFail(dirnode.NotWriteableError, "set_uri ro", None, |
---|
1734 | rw_dn.move_child_to, u"child", ro_dn) |
---|
1735 | return ro_dn.list() |
---|
1736 | d.addCallback(_ready) |
---|
1737 | @@ -901,8 +979,8 @@ |
---|
1738 | nodemaker = NodeMaker(None, None, None, |
---|
1739 | None, None, None, |
---|
1740 | {"k": 3, "n": 10}, None) |
---|
1741 | - writecap = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q" |
---|
1742 | - filenode = nodemaker.create_from_cap(writecap) |
---|
1743 | + write_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q" |
---|
1744 | + filenode = nodemaker.create_from_cap(write_uri) |
---|
1745 | node = dirnode.DirectoryNode(filenode, nodemaker, None) |
---|
1746 | children = node._unpack_contents(known_tree) |
---|
1747 | self._check_children(children) |
---|
1748 | @@ -975,23 +1053,23 @@ |
---|
1749 | self.failUnlessIn("lit", packed) |
---|
1750 | |
---|
1751 | kids = self._make_kids(nm, ["imm", "lit", "write"]) |
---|
1752 | - self.failUnlessRaises(dirnode.MustBeDeepImmutable, |
---|
1753 | + self.failUnlessRaises(dirnode.MustBeDeepImmutableError, |
---|
1754 | dirnode.pack_children, |
---|
1755 | fn, kids, deep_immutable=True) |
---|
1756 | |
---|
1757 | # read-only is not enough: all children must be immutable |
---|
1758 | kids = self._make_kids(nm, ["imm", "lit", "read"]) |
---|
1759 | - self.failUnlessRaises(dirnode.MustBeDeepImmutable, |
---|
1760 | + self.failUnlessRaises(dirnode.MustBeDeepImmutableError, |
---|
1761 | dirnode.pack_children, |
---|
1762 | fn, kids, deep_immutable=True) |
---|
1763 | |
---|
1764 | kids = self._make_kids(nm, ["imm", "lit", "dirwrite"]) |
---|
1765 | - self.failUnlessRaises(dirnode.MustBeDeepImmutable, |
---|
1766 | + self.failUnlessRaises(dirnode.MustBeDeepImmutableError, |
---|
1767 | dirnode.pack_children, |
---|
1768 | fn, kids, deep_immutable=True) |
---|
1769 | |
---|
1770 | kids = self._make_kids(nm, ["imm", "lit", "dirread"]) |
---|
1771 | - self.failUnlessRaises(dirnode.MustBeDeepImmutable, |
---|
1772 | + self.failUnlessRaises(dirnode.MustBeDeepImmutableError, |
---|
1773 | dirnode.pack_children, |
---|
1774 | fn, kids, deep_immutable=True) |
---|
1775 | |
---|
1776 | @@ -1017,16 +1095,31 @@ |
---|
1777 | |
---|
1778 | def get_cap(self): |
---|
1779 | return self.uri |
---|
1780 | + |
---|
1781 | def get_uri(self): |
---|
1782 | return self.uri.to_string() |
---|
1783 | + |
---|
1784 | + def get_write_uri(self): |
---|
1785 | + return self.uri.to_string() |
---|
1786 | + |
---|
1787 | def download_best_version(self): |
---|
1788 | return defer.succeed(self.data) |
---|
1789 | + |
---|
1790 | def get_writekey(self): |
---|
1791 | return "writekey" |
---|
1792 | + |
---|
1793 | def is_readonly(self): |
---|
1794 | return False |
---|
1795 | + |
---|
1796 | def is_mutable(self): |
---|
1797 | return True |
---|
1798 | + |
---|
1799 | + def is_unknown(self): |
---|
1800 | + return False |
---|
1801 | + |
---|
1802 | + def is_allowed_in_immutable_directory(self): |
---|
1803 | + return False |
---|
1804 | + |
---|
1805 | def modify(self, modifier): |
---|
1806 | self.data = modifier(self.data, None, True) |
---|
1807 | return defer.succeed(None) |
---|
1808 | @@ -1050,47 +1143,59 @@ |
---|
1809 | |
---|
1810 | def test_from_future(self): |
---|
1811 | # create a dirnode that contains unknown URI types, and make sure we |
---|
1812 | - # tolerate them properly. Since dirnodes aren't allowed to add |
---|
1813 | - # unknown node types, we have to be tricky. |
---|
1814 | + # tolerate them properly. |
---|
1815 | d = self.nodemaker.create_new_mutable_directory() |
---|
1816 | - future_writecap = "x-tahoe-crazy://I_am_from_the_future." |
---|
1817 | - future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
1818 | - future_node = UnknownNode(future_writecap, future_readcap) |
---|
1819 | + future_write_uri = "x-tahoe-crazy://I_am_from_the_future." |
---|
1820 | + future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
1821 | + future_node = UnknownNode(future_write_uri, future_read_uri) |
---|
1822 | def _then(n): |
---|
1823 | self._node = n |
---|
1824 | return n.set_node(u"future", future_node) |
---|
1825 | d.addCallback(_then) |
---|
1826 | |
---|
1827 | - # we should be prohibited from adding an unknown URI to a directory, |
---|
1828 | - # since we don't know how to diminish the cap to a readcap (for the |
---|
1829 | - # dirnode's rocap slot), and we don't want to accidentally grant |
---|
1830 | - # write access to a holder of the dirnode's readcap. |
---|
1831 | + # We should be prohibited from adding an unknown URI to a directory |
---|
1832 | + # just in the rw_uri slot, since we don't know how to diminish the cap |
---|
1833 | + # to a readcap (for the ro_uri slot). |
---|
1834 | d.addCallback(lambda ign: |
---|
1835 | - self.shouldFail(CannotPackUnknownNodeError, |
---|
1836 | + self.shouldFail(MustNotBeUnknownRWError, |
---|
1837 | "copy unknown", |
---|
1838 | - "cannot pack unknown node as child add", |
---|
1839 | + "cannot attach unknown rw cap as child", |
---|
1840 | self._node.set_uri, u"add", |
---|
1841 | - future_writecap, future_readcap)) |
---|
1842 | + future_write_uri, None)) |
---|
1843 | + |
---|
1844 | + # However, we should be able to add both rw_uri and ro_uri as a pair of |
---|
1845 | + # unknown URIs. |
---|
1846 | + d.addCallback(lambda ign: self._node.set_uri(u"add-pair", |
---|
1847 | + future_write_uri, future_read_uri)) |
---|
1848 | + |
---|
1849 | d.addCallback(lambda ign: self._node.list()) |
---|
1850 | def _check(children): |
---|
1851 | - self.failUnlessEqual(len(children), 1) |
---|
1852 | + self.failUnlessEqual(len(children), 2) |
---|
1853 | (fn, metadata) = children[u"future"] |
---|
1854 | self.failUnless(isinstance(fn, UnknownNode), fn) |
---|
1855 | - self.failUnlessEqual(fn.get_uri(), future_writecap) |
---|
1856 | - self.failUnlessEqual(fn.get_readonly_uri(), future_readcap) |
---|
1857 | - # but we *should* be allowed to copy this node, because the |
---|
1858 | + self.failUnlessEqual(fn.get_uri(), future_write_uri) |
---|
1859 | + self.failUnlessEqual(fn.get_readonly_uri(), "ro." + future_read_uri) |
---|
1860 | + |
---|
1861 | + (fn2, metadata2) = children[u"add-pair"] |
---|
1862 | + self.failUnless(isinstance(fn2, UnknownNode), fn2) |
---|
1863 | + self.failUnlessEqual(fn2.get_uri(), future_write_uri) |
---|
1864 | + self.failUnlessEqual(fn2.get_readonly_uri(), "ro." + future_read_uri) |
---|
1865 | + |
---|
1866 | + # we should also be allowed to copy this node, because the |
---|
1867 | # UnknownNode contains all the information that was in the |
---|
1868 | # original directory (readcap and writecap), so we're preserving |
---|
1869 | # everything. |
---|
1870 | return self._node.set_node(u"copy", fn) |
---|
1871 | d.addCallback(_check) |
---|
1872 | + |
---|
1873 | d.addCallback(lambda ign: self._node.list()) |
---|
1874 | def _check2(children): |
---|
1875 | - self.failUnlessEqual(len(children), 2) |
---|
1876 | + self.failUnlessEqual(len(children), 3) |
---|
1877 | (fn, metadata) = children[u"copy"] |
---|
1878 | self.failUnless(isinstance(fn, UnknownNode), fn) |
---|
1879 | - self.failUnlessEqual(fn.get_uri(), future_writecap) |
---|
1880 | - self.failUnlessEqual(fn.get_readonly_uri(), future_readcap) |
---|
1881 | + self.failUnlessEqual(fn.get_uri(), future_write_uri) |
---|
1882 | + self.failUnlessEqual(fn.get_readonly_uri(), "ro." + future_read_uri) |
---|
1883 | + d.addCallback(_check2) |
---|
1884 | return d |
---|
1885 | |
---|
1886 | class DeepStats(unittest.TestCase): |
---|
1887 | diff -rN -u old-tahoe/src/allmydata/test/test_filenode.py new-tahoe/src/allmydata/test/test_filenode.py |
---|
1888 | --- old-tahoe/src/allmydata/test/test_filenode.py 2010-01-24 05:52:01.687000000 +0000 |
---|
1889 | +++ new-tahoe/src/allmydata/test/test_filenode.py 2010-01-24 05:52:06.173000000 +0000 |
---|
1890 | @@ -41,14 +41,21 @@ |
---|
1891 | self.failUnlessEqual(fn1.get_readcap(), u) |
---|
1892 | self.failUnlessEqual(fn1.is_readonly(), True) |
---|
1893 | self.failUnlessEqual(fn1.is_mutable(), False) |
---|
1894 | + self.failUnlessEqual(fn1.is_unknown(), False) |
---|
1895 | + self.failUnlessEqual(fn1.is_allowed_in_immutable_directory(), True) |
---|
1896 | + self.failUnlessEqual(fn1.get_write_uri(), None) |
---|
1897 | self.failUnlessEqual(fn1.get_readonly_uri(), u.to_string()) |
---|
1898 | self.failUnlessEqual(fn1.get_size(), 1000) |
---|
1899 | self.failUnlessEqual(fn1.get_storage_index(), u.storage_index) |
---|
1900 | + fn1.raise_error() |
---|
1901 | + fn2.raise_error() |
---|
1902 | d = {} |
---|
1903 | d[fn1] = 1 # exercise __hash__ |
---|
1904 | v = fn1.get_verify_cap() |
---|
1905 | self.failUnless(isinstance(v, uri.CHKFileVerifierURI)) |
---|
1906 | self.failUnlessEqual(fn1.get_repair_cap(), v) |
---|
1907 | + self.failUnlessEqual(v.is_readonly(), True) |
---|
1908 | + self.failUnlessEqual(v.is_mutable(), False) |
---|
1909 | |
---|
1910 | |
---|
1911 | def test_literal_filenode(self): |
---|
1912 | @@ -64,9 +71,14 @@ |
---|
1913 | self.failUnlessEqual(fn1.get_readcap(), u) |
---|
1914 | self.failUnlessEqual(fn1.is_readonly(), True) |
---|
1915 | self.failUnlessEqual(fn1.is_mutable(), False) |
---|
1916 | + self.failUnlessEqual(fn1.is_unknown(), False) |
---|
1917 | + self.failUnlessEqual(fn1.is_allowed_in_immutable_directory(), True) |
---|
1918 | + self.failUnlessEqual(fn1.get_write_uri(), None) |
---|
1919 | self.failUnlessEqual(fn1.get_readonly_uri(), u.to_string()) |
---|
1920 | self.failUnlessEqual(fn1.get_size(), len(DATA)) |
---|
1921 | self.failUnlessEqual(fn1.get_storage_index(), None) |
---|
1922 | + fn1.raise_error() |
---|
1923 | + fn2.raise_error() |
---|
1924 | d = {} |
---|
1925 | d[fn1] = 1 # exercise __hash__ |
---|
1926 | |
---|
1927 | @@ -99,24 +111,29 @@ |
---|
1928 | self.failUnlessEqual(n.get_writekey(), wk) |
---|
1929 | self.failUnlessEqual(n.get_readkey(), rk) |
---|
1930 | self.failUnlessEqual(n.get_storage_index(), si) |
---|
1931 | - # these itmes are populated on first read (or create), so until that |
---|
1932 | + # these items are populated on first read (or create), so until that |
---|
1933 | # happens they'll be None |
---|
1934 | self.failUnlessEqual(n.get_privkey(), None) |
---|
1935 | self.failUnlessEqual(n.get_encprivkey(), None) |
---|
1936 | self.failUnlessEqual(n.get_pubkey(), None) |
---|
1937 | |
---|
1938 | self.failUnlessEqual(n.get_uri(), u.to_string()) |
---|
1939 | + self.failUnlessEqual(n.get_write_uri(), u.to_string()) |
---|
1940 | self.failUnlessEqual(n.get_readonly_uri(), u.get_readonly().to_string()) |
---|
1941 | self.failUnlessEqual(n.get_cap(), u) |
---|
1942 | self.failUnlessEqual(n.get_readcap(), u.get_readonly()) |
---|
1943 | self.failUnlessEqual(n.is_mutable(), True) |
---|
1944 | self.failUnlessEqual(n.is_readonly(), False) |
---|
1945 | + self.failUnlessEqual(n.is_unknown(), False) |
---|
1946 | + self.failUnlessEqual(n.is_allowed_in_immutable_directory(), False) |
---|
1947 | + n.raise_error() |
---|
1948 | |
---|
1949 | n2 = MutableFileNode(None, None, client.get_encoding_parameters(), |
---|
1950 | None).init_from_cap(u) |
---|
1951 | self.failUnlessEqual(n, n2) |
---|
1952 | self.failIfEqual(n, "not even the right type") |
---|
1953 | self.failIfEqual(n, u) # not the right class |
---|
1954 | + n.raise_error() |
---|
1955 | d = {n: "can these be used as dictionary keys?"} |
---|
1956 | d[n2] = "replace the old one" |
---|
1957 | self.failUnlessEqual(len(d), 1) |
---|
1958 | @@ -127,12 +144,16 @@ |
---|
1959 | self.failUnlessEqual(nro.get_readonly(), nro) |
---|
1960 | self.failUnlessEqual(nro.get_cap(), u.get_readonly()) |
---|
1961 | self.failUnlessEqual(nro.get_readcap(), u.get_readonly()) |
---|
1962 | + self.failUnlessEqual(nro.is_mutable(), True) |
---|
1963 | + self.failUnlessEqual(nro.is_readonly(), True) |
---|
1964 | + self.failUnlessEqual(nro.is_unknown(), False) |
---|
1965 | + self.failUnlessEqual(nro.is_allowed_in_immutable_directory(), False) |
---|
1966 | nro_u = nro.get_uri() |
---|
1967 | self.failUnlessEqual(nro_u, nro.get_readonly_uri()) |
---|
1968 | self.failUnlessEqual(nro_u, u.get_readonly().to_string()) |
---|
1969 | - self.failUnlessEqual(nro.is_mutable(), True) |
---|
1970 | - self.failUnlessEqual(nro.is_readonly(), True) |
---|
1971 | + self.failUnlessEqual(nro.get_write_uri(), None) |
---|
1972 | self.failUnlessEqual(nro.get_repair_cap(), None) # RSAmut needs writecap |
---|
1973 | + nro.raise_error() |
---|
1974 | |
---|
1975 | v = n.get_verify_cap() |
---|
1976 | self.failUnless(isinstance(v, uri.SSKVerifierURI)) |
---|
1977 | diff -rN -u old-tahoe/src/allmydata/test/test_system.py new-tahoe/src/allmydata/test/test_system.py |
---|
1978 | --- old-tahoe/src/allmydata/test/test_system.py 2010-01-24 05:52:01.855000000 +0000 |
---|
1979 | +++ new-tahoe/src/allmydata/test/test_system.py 2010-01-24 05:52:06.340000000 +0000 |
---|
1980 | @@ -17,7 +17,7 @@ |
---|
1981 | from allmydata.interfaces import IDirectoryNode, IFileNode, \ |
---|
1982 | NoSuchChildError, NoSharesError |
---|
1983 | from allmydata.monitor import Monitor |
---|
1984 | -from allmydata.mutable.common import NotMutableError |
---|
1985 | +from allmydata.mutable.common import NotWriteableError |
---|
1986 | from allmydata.mutable import layout as mutable_layout |
---|
1987 | from foolscap.api import DeadReferenceError |
---|
1988 | from twisted.python.failure import Failure |
---|
1989 | @@ -890,11 +890,11 @@ |
---|
1990 | d1.addCallback(lambda res: dirnode.list()) |
---|
1991 | d1.addCallback(self.log, "dirnode.list") |
---|
1992 | |
---|
1993 | - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mkdir(nope)", None, dirnode.create_subdirectory, u"nope")) |
---|
1994 | + d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mkdir(nope)", None, dirnode.create_subdirectory, u"nope")) |
---|
1995 | |
---|
1996 | d1.addCallback(self.log, "doing add_file(ro)") |
---|
1997 | ut = upload.Data("I will disappear, unrecorded and unobserved. The tragedy of my demise is made more poignant by its silence, but this beauty is not for you to ever know.", convergence="99i-p1x4-xd4-18yc-ywt-87uu-msu-zo -- completely and totally unguessable string (unless you read this)") |
---|
1998 | - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "add_file(nope)", None, dirnode.add_file, u"hope", ut)) |
---|
1999 | + d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "add_file(nope)", None, dirnode.add_file, u"hope", ut)) |
---|
2000 | |
---|
2001 | d1.addCallback(self.log, "doing get(ro)") |
---|
2002 | d1.addCallback(lambda res: dirnode.get(u"mydata992")) |
---|
2003 | @@ -902,17 +902,17 @@ |
---|
2004 | self.failUnless(IFileNode.providedBy(filenode))) |
---|
2005 | |
---|
2006 | d1.addCallback(self.log, "doing delete(ro)") |
---|
2007 | - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "delete(nope)", None, dirnode.delete, u"mydata992")) |
---|
2008 | + d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "delete(nope)", None, dirnode.delete, u"mydata992")) |
---|
2009 | |
---|
2010 | - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "set_uri(nope)", None, dirnode.set_uri, u"hopeless", self.uri, self.uri)) |
---|
2011 | + d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "set_uri(nope)", None, dirnode.set_uri, u"hopeless", self.uri, self.uri)) |
---|
2012 | |
---|
2013 | d1.addCallback(lambda res: self.shouldFail2(NoSuchChildError, "get(missing)", "missing", dirnode.get, u"missing")) |
---|
2014 | |
---|
2015 | personal = self._personal_node |
---|
2016 | - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mv from readonly", None, dirnode.move_child_to, u"mydata992", personal, u"nope")) |
---|
2017 | + d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mv from readonly", None, dirnode.move_child_to, u"mydata992", personal, u"nope")) |
---|
2018 | |
---|
2019 | d1.addCallback(self.log, "doing move_child_to(ro)2") |
---|
2020 | - d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mv to readonly", None, personal.move_child_to, u"sekrit data", dirnode, u"nope")) |
---|
2021 | + d1.addCallback(lambda res: self.shouldFail2(NotWriteableError, "mv to readonly", None, personal.move_child_to, u"sekrit data", dirnode, u"nope")) |
---|
2022 | |
---|
2023 | d1.addCallback(self.log, "finished with _got_s2ro") |
---|
2024 | return d1 |
---|
2025 | diff -rN -u old-tahoe/src/allmydata/test/test_uri.py new-tahoe/src/allmydata/test/test_uri.py |
---|
2026 | --- old-tahoe/src/allmydata/test/test_uri.py 2010-01-24 05:52:01.867000000 +0000 |
---|
2027 | +++ new-tahoe/src/allmydata/test/test_uri.py 2010-01-24 05:52:06.351000000 +0000 |
---|
2028 | @@ -3,7 +3,7 @@ |
---|
2029 | from allmydata import uri |
---|
2030 | from allmydata.util import hashutil, base32 |
---|
2031 | from allmydata.interfaces import IURI, IFileURI, IDirnodeURI, IMutableFileURI, \ |
---|
2032 | - IVerifierURI |
---|
2033 | + IVerifierURI, CapConstraintError |
---|
2034 | |
---|
2035 | class Literal(unittest.TestCase): |
---|
2036 | def _help_test(self, data): |
---|
2037 | @@ -22,8 +22,16 @@ |
---|
2038 | self.failIf(IDirnodeURI.providedBy(u2)) |
---|
2039 | self.failUnlessEqual(u2.data, data) |
---|
2040 | self.failUnlessEqual(u2.get_size(), len(data)) |
---|
2041 | - self.failUnless(u.is_readonly()) |
---|
2042 | - self.failIf(u.is_mutable()) |
---|
2043 | + self.failUnless(u2.is_readonly()) |
---|
2044 | + self.failIf(u2.is_mutable()) |
---|
2045 | + |
---|
2046 | + u2i = uri.from_string(u.to_string(), deep_immutable=True) |
---|
2047 | + self.failUnless(IFileURI.providedBy(u2i)) |
---|
2048 | + self.failIf(IDirnodeURI.providedBy(u2i)) |
---|
2049 | + self.failUnlessEqual(u2i.data, data) |
---|
2050 | + self.failUnlessEqual(u2i.get_size(), len(data)) |
---|
2051 | + self.failUnless(u2i.is_readonly()) |
---|
2052 | + self.failIf(u2i.is_mutable()) |
---|
2053 | |
---|
2054 | u3 = u.get_readonly() |
---|
2055 | self.failUnlessIdentical(u, u3) |
---|
2056 | @@ -51,18 +59,36 @@ |
---|
2057 | fileURI = 'URI:CHK:f5ahxa25t4qkktywz6teyfvcx4:opuioq7tj2y6idzfp6cazehtmgs5fdcebcz3cygrxyydvcozrmeq:3:10:345834' |
---|
2058 | chk1 = uri.CHKFileURI.init_from_string(fileURI) |
---|
2059 | chk2 = uri.CHKFileURI.init_from_string(fileURI) |
---|
2060 | + unk = uri.UnknownURI("lafs://from_the_future") |
---|
2061 | self.failIfEqual(lit1, chk1) |
---|
2062 | self.failUnlessEqual(chk1, chk2) |
---|
2063 | self.failIfEqual(chk1, "not actually a URI") |
---|
2064 | # these should be hashable too |
---|
2065 | - s = set([lit1, chk1, chk2]) |
---|
2066 | - self.failUnlessEqual(len(s), 2) # since chk1==chk2 |
---|
2067 | + s = set([lit1, chk1, chk2, unk]) |
---|
2068 | + self.failUnlessEqual(len(s), 3) # since chk1==chk2 |
---|
2069 | |
---|
2070 | def test_is_uri(self): |
---|
2071 | lit1 = uri.LiteralFileURI("some data").to_string() |
---|
2072 | self.failUnless(uri.is_uri(lit1)) |
---|
2073 | self.failIf(uri.is_uri(None)) |
---|
2074 | |
---|
2075 | + def test_is_literal_file_uri(self): |
---|
2076 | + lit1 = uri.LiteralFileURI("some data").to_string() |
---|
2077 | + self.failUnless(uri.is_literal_file_uri(lit1)) |
---|
2078 | + self.failIf(uri.is_literal_file_uri(None)) |
---|
2079 | + self.failIf(uri.is_literal_file_uri("foo")) |
---|
2080 | + self.failIf(uri.is_literal_file_uri("ro.foo")) |
---|
2081 | + self.failIf(uri.is_literal_file_uri("URI:LITfoo")) |
---|
2082 | + self.failUnless(uri.is_literal_file_uri("ro.URI:LIT:foo")) |
---|
2083 | + self.failUnless(uri.is_literal_file_uri("imm.URI:LIT:foo")) |
---|
2084 | + |
---|
2085 | + def test_has_uri_prefix(self): |
---|
2086 | + self.failUnless(uri.has_uri_prefix("URI:foo")) |
---|
2087 | + self.failUnless(uri.has_uri_prefix("ro.URI:foo")) |
---|
2088 | + self.failUnless(uri.has_uri_prefix("imm.URI:foo")) |
---|
2089 | + self.failIf(uri.has_uri_prefix(None)) |
---|
2090 | + self.failIf(uri.has_uri_prefix("foo")) |
---|
2091 | + |
---|
2092 | class CHKFile(unittest.TestCase): |
---|
2093 | def test_pack(self): |
---|
2094 | key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" |
---|
2095 | @@ -88,8 +114,7 @@ |
---|
2096 | self.failUnless(IFileURI.providedBy(u)) |
---|
2097 | self.failIf(IDirnodeURI.providedBy(u)) |
---|
2098 | self.failUnlessEqual(u.get_size(), 1234) |
---|
2099 | - self.failUnless(u.is_readonly()) |
---|
2100 | - self.failIf(u.is_mutable()) |
---|
2101 | + |
---|
2102 | u_ro = u.get_readonly() |
---|
2103 | self.failUnlessIdentical(u, u_ro) |
---|
2104 | he = u.to_human_encoding() |
---|
2105 | @@ -109,11 +134,19 @@ |
---|
2106 | self.failUnless(IFileURI.providedBy(u2)) |
---|
2107 | self.failIf(IDirnodeURI.providedBy(u2)) |
---|
2108 | self.failUnlessEqual(u2.get_size(), 1234) |
---|
2109 | - self.failUnless(u2.is_readonly()) |
---|
2110 | - self.failIf(u2.is_mutable()) |
---|
2111 | + |
---|
2112 | + u2i = uri.from_string(u.to_string(), deep_immutable=True) |
---|
2113 | + self.failUnlessEqual(u.to_string(), u2i.to_string()) |
---|
2114 | + u2ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u.to_string()) |
---|
2115 | + self.failUnlessEqual(u.to_string(), u2ro.to_string()) |
---|
2116 | + u2imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u.to_string()) |
---|
2117 | + self.failUnlessEqual(u.to_string(), u2imm.to_string()) |
---|
2118 | |
---|
2119 | v = u.get_verify_cap() |
---|
2120 | self.failUnless(isinstance(v.to_string(), str)) |
---|
2121 | + self.failUnless(v.is_readonly()) |
---|
2122 | + self.failIf(v.is_mutable()) |
---|
2123 | + |
---|
2124 | v2 = uri.from_string(v.to_string()) |
---|
2125 | self.failUnlessEqual(v, v2) |
---|
2126 | he = v.to_human_encoding() |
---|
2127 | @@ -126,6 +159,8 @@ |
---|
2128 | total_shares=10, |
---|
2129 | size=1234) |
---|
2130 | self.failUnless(isinstance(v3.to_string(), str)) |
---|
2131 | + self.failUnless(v3.is_readonly()) |
---|
2132 | + self.failIf(v3.is_mutable()) |
---|
2133 | |
---|
2134 | def test_pack_badly(self): |
---|
2135 | key = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" |
---|
2136 | @@ -179,13 +214,20 @@ |
---|
2137 | self.failUnlessEqual(readable["UEB_hash"], |
---|
2138 | base32.b2a(hashutil.uri_extension_hash(ext))) |
---|
2139 | |
---|
2140 | -class Invalid(unittest.TestCase): |
---|
2141 | +class Unknown(unittest.TestCase): |
---|
2142 | def test_from_future(self): |
---|
2143 | # any URI type that we don't recognize should be treated as unknown |
---|
2144 | future_uri = "I am a URI from the future. Whatever you do, don't " |
---|
2145 | u = uri.from_string(future_uri) |
---|
2146 | self.failUnless(isinstance(u, uri.UnknownURI)) |
---|
2147 | self.failUnlessEqual(u.to_string(), future_uri) |
---|
2148 | + self.failUnless(u.get_readonly() is None) |
---|
2149 | + self.failUnless(u.get_error() is None) |
---|
2150 | + |
---|
2151 | + u2 = uri.UnknownURI(future_uri, error=CapConstraintError("...")) |
---|
2152 | + self.failUnlessEqual(u.to_string(), future_uri) |
---|
2153 | + self.failUnless(u2.get_readonly() is None) |
---|
2154 | + self.failUnless(isinstance(u2.get_error(), CapConstraintError)) |
---|
2155 | |
---|
2156 | class Constraint(unittest.TestCase): |
---|
2157 | def test_constraint(self): |
---|
2158 | @@ -226,6 +268,13 @@ |
---|
2159 | self.failUnless(IMutableFileURI.providedBy(u2)) |
---|
2160 | self.failIf(IDirnodeURI.providedBy(u2)) |
---|
2161 | |
---|
2162 | + u2i = uri.from_string(u.to_string(), deep_immutable=True) |
---|
2163 | + self.failUnless(isinstance(u2i, uri.UnknownURI), u2i) |
---|
2164 | + u2ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u.to_string()) |
---|
2165 | + self.failUnless(isinstance(u2ro, uri.UnknownURI), u2ro) |
---|
2166 | + u2imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u.to_string()) |
---|
2167 | + self.failUnless(isinstance(u2imm, uri.UnknownURI), u2imm) |
---|
2168 | + |
---|
2169 | u3 = u2.get_readonly() |
---|
2170 | readkey = hashutil.ssk_readkey_hash(writekey) |
---|
2171 | self.failUnlessEqual(u3.fingerprint, fingerprint) |
---|
2172 | @@ -236,6 +285,13 @@ |
---|
2173 | self.failUnless(IMutableFileURI.providedBy(u3)) |
---|
2174 | self.failIf(IDirnodeURI.providedBy(u3)) |
---|
2175 | |
---|
2176 | + u3i = uri.from_string(u3.to_string(), deep_immutable=True) |
---|
2177 | + self.failUnless(isinstance(u3i, uri.UnknownURI), u3i) |
---|
2178 | + u3ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u3.to_string()) |
---|
2179 | + self.failUnlessEqual(u3.to_string(), u3ro.to_string()) |
---|
2180 | + u3imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u3.to_string()) |
---|
2181 | + self.failUnless(isinstance(u3imm, uri.UnknownURI), u3imm) |
---|
2182 | + |
---|
2183 | he = u3.to_human_encoding() |
---|
2184 | u3_h = uri.ReadonlySSKFileURI.init_from_human_encoding(he) |
---|
2185 | self.failUnlessEqual(u3, u3_h) |
---|
2186 | @@ -249,6 +305,13 @@ |
---|
2187 | self.failUnless(IMutableFileURI.providedBy(u4)) |
---|
2188 | self.failIf(IDirnodeURI.providedBy(u4)) |
---|
2189 | |
---|
2190 | + u4i = uri.from_string(u4.to_string(), deep_immutable=True) |
---|
2191 | + self.failUnless(isinstance(u4i, uri.UnknownURI), u4i) |
---|
2192 | + u4ro = uri.from_string(uri.ALLEGED_READONLY_PREFIX + u4.to_string()) |
---|
2193 | + self.failUnlessEqual(u4.to_string(), u4ro.to_string()) |
---|
2194 | + u4imm = uri.from_string(uri.ALLEGED_IMMUTABLE_PREFIX + u4.to_string()) |
---|
2195 | + self.failUnless(isinstance(u4imm, uri.UnknownURI), u4imm) |
---|
2196 | + |
---|
2197 | u4a = uri.from_string(u4.to_string()) |
---|
2198 | self.failUnlessEqual(u4a, u4) |
---|
2199 | self.failUnless("ReadonlySSKFileURI" in str(u4a)) |
---|
2200 | @@ -291,12 +354,19 @@ |
---|
2201 | self.failIf(IFileURI.providedBy(u2)) |
---|
2202 | self.failUnless(IDirnodeURI.providedBy(u2)) |
---|
2203 | |
---|
2204 | + u2i = uri.from_string(u1.to_string(), deep_immutable=True) |
---|
2205 | + self.failUnless(isinstance(u2i, uri.UnknownURI)) |
---|
2206 | + |
---|
2207 | u3 = u2.get_readonly() |
---|
2208 | self.failUnless(u3.is_readonly()) |
---|
2209 | self.failUnless(u3.is_mutable()) |
---|
2210 | self.failUnless(IURI.providedBy(u3)) |
---|
2211 | self.failIf(IFileURI.providedBy(u3)) |
---|
2212 | self.failUnless(IDirnodeURI.providedBy(u3)) |
---|
2213 | + |
---|
2214 | + u3i = uri.from_string(u2.to_string(), deep_immutable=True) |
---|
2215 | + self.failUnless(isinstance(u3i, uri.UnknownURI)) |
---|
2216 | + |
---|
2217 | u3n = u3._filenode_uri |
---|
2218 | self.failUnless(u3n.is_readonly()) |
---|
2219 | self.failUnless(u3n.is_mutable()) |
---|
2220 | @@ -363,10 +433,16 @@ |
---|
2221 | self.failIf(IFileURI.providedBy(u2)) |
---|
2222 | self.failUnless(IDirnodeURI.providedBy(u2)) |
---|
2223 | |
---|
2224 | + u2i = uri.from_string(u1.to_string(), deep_immutable=True) |
---|
2225 | + self.failUnlessEqual(u1.to_string(), u2i.to_string()) |
---|
2226 | + |
---|
2227 | u3 = u2.get_readonly() |
---|
2228 | self.failUnlessEqual(u3.to_string(), u2.to_string()) |
---|
2229 | self.failUnless(str(u3)) |
---|
2230 | |
---|
2231 | + u3i = uri.from_string(u2.to_string(), deep_immutable=True) |
---|
2232 | + self.failUnlessEqual(u2.to_string(), u3i.to_string()) |
---|
2233 | + |
---|
2234 | u2_verifier = u2.get_verify_cap() |
---|
2235 | self.failUnless(isinstance(u2_verifier, |
---|
2236 | uri.ImmutableDirectoryURIVerifier), |
---|
2237 | diff -rN -u old-tahoe/src/allmydata/test/test_web.py new-tahoe/src/allmydata/test/test_web.py |
---|
2238 | --- old-tahoe/src/allmydata/test/test_web.py 2010-01-24 05:52:01.885000000 +0000 |
---|
2239 | +++ new-tahoe/src/allmydata/test/test_web.py 2010-01-24 05:52:06.361000000 +0000 |
---|
2240 | @@ -7,7 +7,7 @@ |
---|
2241 | from twisted.web import client, error, http |
---|
2242 | from twisted.python import failure, log |
---|
2243 | from nevow import rend |
---|
2244 | -from allmydata import interfaces, uri, webish |
---|
2245 | +from allmydata import interfaces, uri, webish, dirnode |
---|
2246 | from allmydata.storage.shares import get_share_file |
---|
2247 | from allmydata.storage_client import StorageFarmBroker |
---|
2248 | from allmydata.immutable import upload, download |
---|
2249 | @@ -18,6 +18,7 @@ |
---|
2250 | from allmydata.scripts.debug import CorruptShareOptions, corrupt_share |
---|
2251 | from allmydata.util import fileutil, base32 |
---|
2252 | from allmydata.util.consumer import download_to_data |
---|
2253 | +from allmydata.util.netstring import split_netstring |
---|
2254 | from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \ |
---|
2255 | create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri |
---|
2256 | from allmydata.interfaces import IMutableFileNode |
---|
2257 | @@ -366,25 +367,101 @@ |
---|
2258 | self.fail("%s was supposed to Error(404), not get '%s'" % |
---|
2259 | (which, res)) |
---|
2260 | |
---|
2261 | + def _dump_res(self, res): |
---|
2262 | + import traceback |
---|
2263 | + s = "%r\n" % (res,) |
---|
2264 | + if hasattr(res, 'tb_frame'): |
---|
2265 | + s += "Traceback:\n%s\n" % (traceback.format_tb(res),) |
---|
2266 | + if hasattr(res, 'value'): |
---|
2267 | + s += "%r\n" % (res.value,) |
---|
2268 | + if hasattr(res.value, 'tb_frame'): |
---|
2269 | + s += "Traceback:\n%s\n" % (res, res.value, traceback.format_tb(res)) |
---|
2270 | + if hasattr(res.value, 'response'): |
---|
2271 | + s += "Response body:\n%s\n" % (res.value.response,) |
---|
2272 | + return s |
---|
2273 | + |
---|
2274 | + def shouldSucceedGET(self, urlpath, followRedirect=False, |
---|
2275 | + expected_statuscode=http.OK, return_response=False, **kwargs): |
---|
2276 | + d = self.GET(urlpath, followRedirect=followRedirect, return_response=True, **kwargs) |
---|
2277 | + def done((res, statuscode, headers)): |
---|
2278 | + if isinstance(res, failure.Failure): |
---|
2279 | + self.fail(("'GET %s' with kwargs %r was supposed to succeed with statuscode %s, " |
---|
2280 | + "but it failed with statuscode %s instead.\n" |
---|
2281 | + "%s\nThe response headers were:\n%s") % ( |
---|
2282 | + urlpath, kwargs, expected_statuscode, statuscode, |
---|
2283 | + self._dump_res(res), headers)) |
---|
2284 | + if str(statuscode) != str(expected_statuscode): |
---|
2285 | + self.fail(("'GET %s' with kwargs %r was supposed to succeed with statuscode %s, " |
---|
2286 | + "but it succeeded with statuscode %s instead.\n" |
---|
2287 | + "The response headers were:\n%s\n\n" |
---|
2288 | + "The response body was:\n%s") % ( |
---|
2289 | + urlpath, kwargs, expected_statuscode, statuscode, headers, res)) |
---|
2290 | + if return_response: |
---|
2291 | + return (res, statuscode, headers) |
---|
2292 | + else: |
---|
2293 | + return res |
---|
2294 | + d.addBoth(done) |
---|
2295 | + return d |
---|
2296 | + |
---|
2297 | + def shouldSucceedHEAD(self, urlpath, expected_statuscode=http.OK, |
---|
2298 | + return_response=False, **kwargs): |
---|
2299 | + d = self.HEAD(urlpath, return_response=True, **kwargs) |
---|
2300 | + def done((res, statuscode, headers)): |
---|
2301 | + if isinstance(res, failure.Failure): |
---|
2302 | + self.fail(("'HEAD %s' with kwargs %r was supposed to succeed with statuscode %s, " |
---|
2303 | + "but it failed with statuscode %s instead.\n" |
---|
2304 | + "%s\nThe response headers were:\n%s") % ( |
---|
2305 | + urlpath, kwargs, expected_statuscode, statuscode, |
---|
2306 | + self._dump_res(res), headers)) |
---|
2307 | + if str(statuscode) != str(expected_statuscode): |
---|
2308 | + self.fail(("'HEAD %s' with kwargs %r was supposed to succeed with statuscode %s, " |
---|
2309 | + "but it succeeded with statuscode %s instead.\n" |
---|
2310 | + "The response headers were:\n%s\n\n" |
---|
2311 | + "The response body was:\n%s") % ( |
---|
2312 | + urlpath, kwargs, expected_statuscode, statuscode, headers, res)) |
---|
2313 | + if return_response: |
---|
2314 | + return (res, statuscode, headers) |
---|
2315 | + else: |
---|
2316 | + return res |
---|
2317 | + d.addBoth(done) |
---|
2318 | + return d |
---|
2319 | + |
---|
2320 | + def shouldSucceed(self, which, expected_statuscode, callable, *args, **kwargs): |
---|
2321 | + d = defer.maybeDeferred(callable, *args, **kwargs) |
---|
2322 | + def done(res): |
---|
2323 | + if isinstance(res, failure.Failure): |
---|
2324 | + self.fail(("%s:\nAn HTTP op with args %r and kwargs %r was supposed to " |
---|
2325 | + "succeed with statuscode %s, but it failed:\n%s") % ( |
---|
2326 | + which, args, kwargs, expected_statuscode, |
---|
2327 | + self._dump_res(res))) |
---|
2328 | + #if str(statuscode) != str(expected_statuscode): |
---|
2329 | + # self.fail(("%s:\nAn HTTP op with args %r and kwargs %r was supposed to " |
---|
2330 | + # "succeed with statuscode %s, but it succeeded with statuscode %s instead.\n" |
---|
2331 | + # "The response body was:\n%s") % ( |
---|
2332 | + # which, args, kwargs, expected_statuscode, statuscode, res)) |
---|
2333 | + return res |
---|
2334 | + d.addBoth(done) |
---|
2335 | + return d |
---|
2336 | + |
---|
2337 | |
---|
2338 | class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase): |
---|
2339 | def test_create(self): |
---|
2340 | pass |
---|
2341 | |
---|
2342 | def test_welcome(self): |
---|
2343 | - d = self.GET("/") |
---|
2344 | + d = self.shouldSucceedGET("/") |
---|
2345 | def _check(res): |
---|
2346 | self.failUnless('Welcome To Tahoe-LAFS' in res, res) |
---|
2347 | |
---|
2348 | self.s.basedir = 'web/test_welcome' |
---|
2349 | fileutil.make_dirs("web/test_welcome") |
---|
2350 | fileutil.make_dirs("web/test_welcome/private") |
---|
2351 | - return self.GET("/") |
---|
2352 | + return self.shouldSucceedGET("/") |
---|
2353 | d.addCallback(_check) |
---|
2354 | return d |
---|
2355 | |
---|
2356 | def test_provisioning(self): |
---|
2357 | - d = self.GET("/provisioning/") |
---|
2358 | + d = self.shouldSucceedGET("/provisioning/") |
---|
2359 | def _check(res): |
---|
2360 | self.failUnless('Tahoe Provisioning Tool' in res) |
---|
2361 | fields = {'filled': True, |
---|
2362 | @@ -400,9 +477,10 @@ |
---|
2363 | "delete_rate": 10, |
---|
2364 | "lease_timer": 7, |
---|
2365 | } |
---|
2366 | - return self.POST("/provisioning/", **fields) |
---|
2367 | - |
---|
2368 | + return self.shouldSucceed("POST_provisioning-1", http.OK, self.POST, |
---|
2369 | + "/provisioning/", **fields) |
---|
2370 | d.addCallback(_check) |
---|
2371 | + |
---|
2372 | def _check2(res): |
---|
2373 | self.failUnless('Tahoe Provisioning Tool' in res) |
---|
2374 | self.failUnless("Share space consumed: 167.01TB" in res) |
---|
2375 | @@ -422,13 +500,17 @@ |
---|
2376 | "delete_rate": 100, |
---|
2377 | "lease_timer": 7, |
---|
2378 | } |
---|
2379 | - return self.POST("/provisioning/", **fields) |
---|
2380 | + return self.shouldSucceed("POST_provisioning-2", http.OK, self.POST, |
---|
2381 | + "/provisioning/", **fields) |
---|
2382 | d.addCallback(_check2) |
---|
2383 | + |
---|
2384 | def _check3(res): |
---|
2385 | self.failUnless("Share space consumed: huge!" in res) |
---|
2386 | fields = {'filled': True} |
---|
2387 | - return self.POST("/provisioning/", **fields) |
---|
2388 | + return self.shouldSucceed("POST_provisioning-3", http.OK, self.POST, |
---|
2389 | + "/provisioning/", **fields) |
---|
2390 | d.addCallback(_check3) |
---|
2391 | + |
---|
2392 | def _check4(res): |
---|
2393 | self.failUnless("Share space consumed:" in res) |
---|
2394 | d.addCallback(_check4) |
---|
2395 | @@ -442,7 +524,7 @@ |
---|
2396 | except: |
---|
2397 | raise unittest.SkipTest("reliability tool requires NumPy") |
---|
2398 | |
---|
2399 | - d = self.GET("/reliability/") |
---|
2400 | + d = self.shouldSucceedGET("/reliability/") |
---|
2401 | def _check(res): |
---|
2402 | self.failUnless('Tahoe Reliability Tool' in res) |
---|
2403 | fields = {'drive_lifetime': "8Y", |
---|
2404 | @@ -471,7 +553,7 @@ |
---|
2405 | mu_num = h.list_all_mapupdate_statuses()[0].get_counter() |
---|
2406 | pub_num = h.list_all_publish_statuses()[0].get_counter() |
---|
2407 | ret_num = h.list_all_retrieve_statuses()[0].get_counter() |
---|
2408 | - d = self.GET("/status", followRedirect=True) |
---|
2409 | + d = self.shouldSucceedGET("/status", followRedirect=True) |
---|
2410 | def _check(res): |
---|
2411 | self.failUnless('Upload and Download Status' in res, res) |
---|
2412 | self.failUnless('"down-%d"' % dl_num in res, res) |
---|
2413 | @@ -480,7 +562,7 @@ |
---|
2414 | self.failUnless('"publish-%d"' % pub_num in res, res) |
---|
2415 | self.failUnless('"retrieve-%d"' % ret_num in res, res) |
---|
2416 | d.addCallback(_check) |
---|
2417 | - d.addCallback(lambda res: self.GET("/status/?t=json")) |
---|
2418 | + d.addCallback(lambda res: self.shouldSucceedGET("/status/?t=json")) |
---|
2419 | def _check_json(res): |
---|
2420 | data = simplejson.loads(res) |
---|
2421 | self.failUnless(isinstance(data, dict)) |
---|
2422 | @@ -489,23 +571,23 @@ |
---|
2423 | # here. |
---|
2424 | d.addCallback(_check_json) |
---|
2425 | |
---|
2426 | - d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num)) |
---|
2427 | + d.addCallback(lambda res: self.shouldSucceedGET("/status/down-%d" % dl_num)) |
---|
2428 | def _check_dl(res): |
---|
2429 | self.failUnless("File Download Status" in res, res) |
---|
2430 | d.addCallback(_check_dl) |
---|
2431 | - d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num)) |
---|
2432 | + d.addCallback(lambda res: self.shouldSucceedGET("/status/up-%d" % ul_num)) |
---|
2433 | def _check_ul(res): |
---|
2434 | self.failUnless("File Upload Status" in res, res) |
---|
2435 | d.addCallback(_check_ul) |
---|
2436 | - d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num)) |
---|
2437 | + d.addCallback(lambda res: self.shouldSucceedGET("/status/mapupdate-%d" % mu_num)) |
---|
2438 | def _check_mapupdate(res): |
---|
2439 | self.failUnless("Mutable File Servermap Update Status" in res, res) |
---|
2440 | d.addCallback(_check_mapupdate) |
---|
2441 | - d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num)) |
---|
2442 | + d.addCallback(lambda res: self.shouldSucceedGET("/status/publish-%d" % pub_num)) |
---|
2443 | def _check_publish(res): |
---|
2444 | self.failUnless("Mutable File Publish Status" in res, res) |
---|
2445 | d.addCallback(_check_publish) |
---|
2446 | - d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num)) |
---|
2447 | + d.addCallback(lambda res: self.shouldSucceedGET("/status/retrieve-%d" % ret_num)) |
---|
2448 | def _check_retrieve(res): |
---|
2449 | self.failUnless("Mutable File Retrieve Status" in res, res) |
---|
2450 | d.addCallback(_check_retrieve) |
---|
2451 | @@ -536,16 +618,15 @@ |
---|
2452 | self.failUnlessEqual(urrm.render_rate(None, 123), "123Bps") |
---|
2453 | |
---|
2454 | def test_GET_FILEURL(self): |
---|
2455 | - d = self.GET(self.public_url + "/foo/bar.txt") |
---|
2456 | + d = self.shouldSucceedGET(self.public_url + "/foo/bar.txt") |
---|
2457 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
2458 | return d |
---|
2459 | |
---|
2460 | def test_GET_FILEURL_range(self): |
---|
2461 | headers = {"range": "bytes=1-10"} |
---|
2462 | - d = self.GET(self.public_url + "/foo/bar.txt", headers=headers, |
---|
2463 | - return_response=True) |
---|
2464 | - def _got((res, status, headers)): |
---|
2465 | - self.failUnlessEqual(int(status), 206) |
---|
2466 | + d = self.shouldSucceedGET(self.public_url + "/foo/bar.txt", headers=headers, |
---|
2467 | + expected_statuscode=http.PARTIAL_CONTENT, return_response=True) |
---|
2468 | + def _got((res, statuscode, headers)): |
---|
2469 | self.failUnless(headers.has_key("content-range")) |
---|
2470 | self.failUnlessEqual(headers["content-range"][0], |
---|
2471 | "bytes 1-10/%d" % len(self.BAR_CONTENTS)) |
---|
2472 | @@ -556,10 +637,9 @@ |
---|
2473 | def test_GET_FILEURL_partial_range(self): |
---|
2474 | headers = {"range": "bytes=5-"} |
---|
2475 | length = len(self.BAR_CONTENTS) |
---|
2476 | - d = self.GET(self.public_url + "/foo/bar.txt", headers=headers, |
---|
2477 | - return_response=True) |
---|
2478 | - def _got((res, status, headers)): |
---|
2479 | - self.failUnlessEqual(int(status), 206) |
---|
2480 | + d = self.shouldSucceedGET(self.public_url + "/foo/bar.txt", headers=headers, |
---|
2481 | + expected_statuscode=http.PARTIAL_CONTENT, return_response=True) |
---|
2482 | + def _got((res, statuscode, headers)): |
---|
2483 | self.failUnless(headers.has_key("content-range")) |
---|
2484 | self.failUnlessEqual(headers["content-range"][0], |
---|
2485 | "bytes 5-%d/%d" % (length-1, length)) |
---|
2486 | @@ -569,11 +649,10 @@ |
---|
2487 | |
---|
2488 | def test_HEAD_FILEURL_range(self): |
---|
2489 | headers = {"range": "bytes=1-10"} |
---|
2490 | - d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers, |
---|
2491 | - return_response=True) |
---|
2492 | - def _got((res, status, headers)): |
---|
2493 | + d = self.shouldSucceedHEAD(self.public_url + "/foo/bar.txt", headers=headers, |
---|
2494 | + expected_statuscode=http.PARTIAL_CONTENT, return_response=True) |
---|
2495 | + def _got((res, statuscode, headers)): |
---|
2496 | self.failUnlessEqual(res, "") |
---|
2497 | - self.failUnlessEqual(int(status), 206) |
---|
2498 | self.failUnless(headers.has_key("content-range")) |
---|
2499 | self.failUnlessEqual(headers["content-range"][0], |
---|
2500 | "bytes 1-10/%d" % len(self.BAR_CONTENTS)) |
---|
2501 | @@ -583,10 +662,9 @@ |
---|
2502 | def test_HEAD_FILEURL_partial_range(self): |
---|
2503 | headers = {"range": "bytes=5-"} |
---|
2504 | length = len(self.BAR_CONTENTS) |
---|
2505 | - d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers, |
---|
2506 | - return_response=True) |
---|
2507 | - def _got((res, status, headers)): |
---|
2508 | - self.failUnlessEqual(int(status), 206) |
---|
2509 | + d = self.shouldSucceedHEAD(self.public_url + "/foo/bar.txt", headers=headers, |
---|
2510 | + expected_statuscode=http.PARTIAL_CONTENT, return_response=True) |
---|
2511 | + def _got((res, statuscode, headers)): |
---|
2512 | self.failUnless(headers.has_key("content-range")) |
---|
2513 | self.failUnlessEqual(headers["content-range"][0], |
---|
2514 | "bytes 5-%d/%d" % (length-1, length)) |
---|
2515 | @@ -595,7 +673,7 @@ |
---|
2516 | |
---|
2517 | def test_GET_FILEURL_range_bad(self): |
---|
2518 | headers = {"range": "BOGUS=fizbop-quarnak"} |
---|
2519 | - d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_bad", |
---|
2520 | + d = self.shouldFail2(error.Error, "GET_FILEURL_range_bad", |
---|
2521 | "400 Bad Request", |
---|
2522 | "Syntactically invalid http range header", |
---|
2523 | self.GET, self.public_url + "/foo/bar.txt", |
---|
2524 | @@ -603,8 +681,9 @@ |
---|
2525 | return d |
---|
2526 | |
---|
2527 | def test_HEAD_FILEURL(self): |
---|
2528 | - d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True) |
---|
2529 | - def _got((res, status, headers)): |
---|
2530 | + d = self.shouldSucceedHEAD(self.public_url + "/foo/bar.txt", |
---|
2531 | + expected_statuscode=http.OK, return_response=True) |
---|
2532 | + def _got((res, statuscode, headers)): |
---|
2533 | self.failUnlessEqual(res, "") |
---|
2534 | self.failUnlessEqual(headers["content-length"][0], |
---|
2535 | str(len(self.BAR_CONTENTS))) |
---|
2536 | @@ -615,27 +694,27 @@ |
---|
2537 | def test_GET_FILEURL_named(self): |
---|
2538 | base = "/file/%s" % urllib.quote(self._bar_txt_uri) |
---|
2539 | base2 = "/named/%s" % urllib.quote(self._bar_txt_uri) |
---|
2540 | - d = self.GET(base + "/@@name=/blah.txt") |
---|
2541 | + d = self.shouldSucceedGET(base + "/@@name=/blah.txt") |
---|
2542 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
2543 | - d.addCallback(lambda res: self.GET(base + "/blah.txt")) |
---|
2544 | + d.addCallback(lambda res: self.shouldSucceedGET(base + "/blah.txt")) |
---|
2545 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
2546 | - d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt")) |
---|
2547 | + d.addCallback(lambda res: self.shouldSucceedGET(base + "/ignore/lots/blah.txt")) |
---|
2548 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
2549 | - d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt")) |
---|
2550 | + d.addCallback(lambda res: self.shouldSucceedGET(base2 + "/@@name=/blah.txt")) |
---|
2551 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
2552 | save_url = base + "?save=true&filename=blah.txt" |
---|
2553 | - d.addCallback(lambda res: self.GET(save_url)) |
---|
2554 | + d.addCallback(lambda res: self.shouldSucceedGET(save_url)) |
---|
2555 | d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers |
---|
2556 | u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t |
---|
2557 | u_fn_e = urllib.quote(u_filename.encode("utf-8")) |
---|
2558 | u_url = base + "?save=true&filename=" + u_fn_e |
---|
2559 | - d.addCallback(lambda res: self.GET(u_url)) |
---|
2560 | + d.addCallback(lambda res: self.shouldSucceedGET(u_url)) |
---|
2561 | d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers |
---|
2562 | return d |
---|
2563 | |
---|
2564 | def test_PUT_FILEURL_named_bad(self): |
---|
2565 | base = "/file/%s" % urllib.quote(self._bar_txt_uri) |
---|
2566 | - d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad", |
---|
2567 | + d = self.shouldFail2(error.Error, "PUT_FILEURL_named_bad", |
---|
2568 | "400 Bad Request", |
---|
2569 | "/file can only be used with GET or HEAD", |
---|
2570 | self.PUT, base + "/@@name=/blah.txt", "") |
---|
2571 | @@ -643,14 +722,14 @@ |
---|
2572 | |
---|
2573 | def test_GET_DIRURL_named_bad(self): |
---|
2574 | base = "/file/%s" % urllib.quote(self._foo_uri) |
---|
2575 | - d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad", |
---|
2576 | + d = self.shouldFail2(error.Error, "PUT_DIRURL_named_bad", |
---|
2577 | "400 Bad Request", |
---|
2578 | "is not a file-cap", |
---|
2579 | self.GET, base + "/@@name=/blah.txt") |
---|
2580 | return d |
---|
2581 | |
---|
2582 | def test_GET_slash_file_bad(self): |
---|
2583 | - d = self.shouldFail2(error.Error, "test_GET_slash_file_bad", |
---|
2584 | + d = self.shouldFail2(error.Error, "GET_slash_file_bad", |
---|
2585 | "404 Not Found", |
---|
2586 | "/file must be followed by a file-cap and a name", |
---|
2587 | self.GET, "/file") |
---|
2588 | @@ -671,7 +750,7 @@ |
---|
2589 | verifier_cap = n.get_verify_cap().to_string() |
---|
2590 | base = "/uri/%s" % urllib.quote(verifier_cap) |
---|
2591 | # client.create_node_from_uri() can't handle verify-caps |
---|
2592 | - d = self.shouldFail2(error.Error, "test_GET_unhandled_URI", |
---|
2593 | + d = self.shouldFail2(error.Error, "GET_unhandled_URI", |
---|
2594 | "400 Bad Request", |
---|
2595 | "GET unknown URI type: can only do t=info", |
---|
2596 | self.GET, base) |
---|
2597 | @@ -679,14 +758,14 @@ |
---|
2598 | |
---|
2599 | def test_GET_FILE_URI(self): |
---|
2600 | base = "/uri/%s" % urllib.quote(self._bar_txt_uri) |
---|
2601 | - d = self.GET(base) |
---|
2602 | + d = self.shouldSucceedGET(base) |
---|
2603 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
2604 | return d |
---|
2605 | |
---|
2606 | def test_GET_FILE_URI_badchild(self): |
---|
2607 | base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri) |
---|
2608 | errmsg = "Files have no children, certainly not named 'boguschild'" |
---|
2609 | - d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild", |
---|
2610 | + d = self.shouldFail2(error.Error, "GET_FILE_URI_badchild", |
---|
2611 | "400 Bad Request", errmsg, |
---|
2612 | self.GET, base) |
---|
2613 | return d |
---|
2614 | @@ -694,35 +773,42 @@ |
---|
2615 | def test_PUT_FILE_URI_badchild(self): |
---|
2616 | base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri) |
---|
2617 | errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory" |
---|
2618 | - d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild", |
---|
2619 | + d = self.shouldFail2(error.Error, "GET_FILE_URI_badchild", |
---|
2620 | "400 Bad Request", errmsg, |
---|
2621 | self.PUT, base, "") |
---|
2622 | return d |
---|
2623 | |
---|
2624 | + # TODO: version of this with a Unicode filename |
---|
2625 | def test_GET_FILEURL_save(self): |
---|
2626 | - d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true") |
---|
2627 | - # TODO: look at the headers, expect a Content-Disposition: attachment |
---|
2628 | - # header. |
---|
2629 | - d.addCallback(self.failUnlessIsBarDotTxt) |
---|
2630 | + d = self.shouldSucceedGET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true", |
---|
2631 | + return_response=True) |
---|
2632 | + def _got((res, statuscode, headers)): |
---|
2633 | + content_disposition = headers["content-disposition"][0] |
---|
2634 | + self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition) |
---|
2635 | + self.failUnlessIsBarDotTxt(res) |
---|
2636 | + d.addCallback(_got) |
---|
2637 | return d |
---|
2638 | |
---|
2639 | def test_GET_FILEURL_missing(self): |
---|
2640 | d = self.GET(self.public_url + "/foo/missing") |
---|
2641 | - d.addBoth(self.should404, "test_GET_FILEURL_missing") |
---|
2642 | + d.addBoth(self.should404, "GET_FILEURL_missing") |
---|
2643 | return d |
---|
2644 | |
---|
2645 | def test_PUT_overwrite_only_files(self): |
---|
2646 | # create a directory, put a file in that directory. |
---|
2647 | contents, n, filecap = self.makefile(8) |
---|
2648 | - d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "") |
---|
2649 | + d = self.shouldSucceed("PUT_overwrite_only_files_1", http.OK, self.PUT, |
---|
2650 | + self.public_url + "/foo/dir?t=mkdir", "") |
---|
2651 | d.addCallback(lambda res: |
---|
2652 | - self.PUT(self.public_url + "/foo/dir/file1.txt", |
---|
2653 | - self.NEWFILE_CONTENTS)) |
---|
2654 | + self.shouldSucceed("PUT_overwrite_only_files_2", http.OK, self.PUT, |
---|
2655 | + self.public_url + "/foo/dir/file1.txt", |
---|
2656 | + self.NEWFILE_CONTENTS)) |
---|
2657 | # try to overwrite the file with replace=only-files |
---|
2658 | # (this should work) |
---|
2659 | d.addCallback(lambda res: |
---|
2660 | - self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files", |
---|
2661 | - filecap)) |
---|
2662 | + self.shouldSucceed("PUT_overwrite_only_files_3", http.OK, self.PUT, |
---|
2663 | + self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files", |
---|
2664 | + filecap)) |
---|
2665 | d.addCallback(lambda res: |
---|
2666 | self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict", |
---|
2667 | "There was already a child by that name, and you asked me " |
---|
2668 | @@ -732,21 +818,19 @@ |
---|
2669 | return d |
---|
2670 | |
---|
2671 | def test_PUT_NEWFILEURL(self): |
---|
2672 | - d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS) |
---|
2673 | - # TODO: we lose the response code, so we can't check this |
---|
2674 | - #self.failUnlessEqual(responsecode, 201) |
---|
2675 | - d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt") |
---|
2676 | + d = self.shouldSucceed("PUT_NEWFILEURL", http.CREATED, self.PUT, |
---|
2677 | + self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS) |
---|
2678 | + d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt") |
---|
2679 | d.addCallback(lambda res: |
---|
2680 | self.failUnlessChildContentsAre(self._foo_node, u"new.txt", |
---|
2681 | self.NEWFILE_CONTENTS)) |
---|
2682 | return d |
---|
2683 | |
---|
2684 | def test_PUT_NEWFILEURL_not_mutable(self): |
---|
2685 | - d = self.PUT(self.public_url + "/foo/new.txt?mutable=false", |
---|
2686 | - self.NEWFILE_CONTENTS) |
---|
2687 | - # TODO: we lose the response code, so we can't check this |
---|
2688 | - #self.failUnlessEqual(responsecode, 201) |
---|
2689 | - d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt") |
---|
2690 | + d = self.shouldSucceed("PUT_NEWFILEURL_not_mutable", http.CREATED, self.PUT, |
---|
2691 | + self.public_url + "/foo/new.txt?mutable=false", |
---|
2692 | + self.NEWFILE_CONTENTS) |
---|
2693 | + d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt") |
---|
2694 | d.addCallback(lambda res: |
---|
2695 | self.failUnlessChildContentsAre(self._foo_node, u"new.txt", |
---|
2696 | self.NEWFILE_CONTENTS)) |
---|
2697 | @@ -755,7 +839,7 @@ |
---|
2698 | def test_PUT_NEWFILEURL_range_bad(self): |
---|
2699 | headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)} |
---|
2700 | target = self.public_url + "/foo/new.txt" |
---|
2701 | - d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad", |
---|
2702 | + d = self.shouldFail2(error.Error, "PUT_NEWFILEURL_range_bad", |
---|
2703 | "501 Not Implemented", |
---|
2704 | "Content-Range in PUT not yet supported", |
---|
2705 | # (and certainly not for immutable files) |
---|
2706 | @@ -766,17 +850,16 @@ |
---|
2707 | return d |
---|
2708 | |
---|
2709 | def test_PUT_NEWFILEURL_mutable(self): |
---|
2710 | - d = self.PUT(self.public_url + "/foo/new.txt?mutable=true", |
---|
2711 | - self.NEWFILE_CONTENTS) |
---|
2712 | - # TODO: we lose the response code, so we can't check this |
---|
2713 | - #self.failUnlessEqual(responsecode, 201) |
---|
2714 | + d = self.shouldSucceed("PUT_NEWFILEURL_mutable", http.CREATED, self.PUT, |
---|
2715 | + self.public_url + "/foo/new.txt?mutable=true", |
---|
2716 | + self.NEWFILE_CONTENTS) |
---|
2717 | def _check_uri(res): |
---|
2718 | u = uri.from_string_mutable_filenode(res) |
---|
2719 | self.failUnless(u.is_mutable()) |
---|
2720 | self.failIf(u.is_readonly()) |
---|
2721 | return res |
---|
2722 | d.addCallback(_check_uri) |
---|
2723 | - d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt") |
---|
2724 | + d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt") |
---|
2725 | d.addCallback(lambda res: |
---|
2726 | self.failUnlessMutableChildContentsAre(self._foo_node, |
---|
2727 | u"new.txt", |
---|
2728 | @@ -784,7 +867,7 @@ |
---|
2729 | return d |
---|
2730 | |
---|
2731 | def test_PUT_NEWFILEURL_mutable_toobig(self): |
---|
2732 | - d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_mutable_toobig", |
---|
2733 | + d = self.shouldFail2(error.Error, "PUT_NEWFILEURL_mutable_toobig", |
---|
2734 | "413 Request Entity Too Large", |
---|
2735 | "SDMF is limited to one segment, and 10001 > 10000", |
---|
2736 | self.PUT, |
---|
2737 | @@ -793,10 +876,9 @@ |
---|
2738 | return d |
---|
2739 | |
---|
2740 | def test_PUT_NEWFILEURL_replace(self): |
---|
2741 | - d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS) |
---|
2742 | - # TODO: we lose the response code, so we can't check this |
---|
2743 | - #self.failUnlessEqual(responsecode, 200) |
---|
2744 | - d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt") |
---|
2745 | + d = self.shouldSucceed("PUT_NEWFILEURL_replace", http.OK, self.PUT, |
---|
2746 | + self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS) |
---|
2747 | + d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt") |
---|
2748 | d.addCallback(lambda res: |
---|
2749 | self.failUnlessChildContentsAre(self._foo_node, u"bar.txt", |
---|
2750 | self.NEWFILE_CONTENTS)) |
---|
2751 | @@ -819,9 +901,11 @@ |
---|
2752 | return d |
---|
2753 | |
---|
2754 | def test_PUT_NEWFILEURL_mkdirs(self): |
---|
2755 | - d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS) |
---|
2756 | + d = self.shouldSucceed("PUT_NEWFILEURL_mkdirs", http.OK, self.PUT, |
---|
2757 | + self.public_url + "/foo/newdir/new.txt", |
---|
2758 | + self.NEWFILE_CONTENTS) |
---|
2759 | fn = self._foo_node |
---|
2760 | - d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt") |
---|
2761 | + d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt") |
---|
2762 | d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt")) |
---|
2763 | d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir")) |
---|
2764 | d.addCallback(lambda res: |
---|
2765 | @@ -839,26 +923,27 @@ |
---|
2766 | |
---|
2767 | def test_PUT_NEWFILEURL_emptyname(self): |
---|
2768 | # an empty pathname component (i.e. a double-slash) is disallowed |
---|
2769 | - d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname", |
---|
2770 | + d = self.shouldFail2(error.Error, "PUT_NEWFILEURL_emptyname", |
---|
2771 | "400 Bad Request", |
---|
2772 | "The webapi does not allow empty pathname components", |
---|
2773 | self.PUT, self.public_url + "/foo//new.txt", "") |
---|
2774 | return d |
---|
2775 | |
---|
2776 | def test_DELETE_FILEURL(self): |
---|
2777 | - d = self.DELETE(self.public_url + "/foo/bar.txt") |
---|
2778 | + d = self.shouldSucceed("DELETE_FILEURL", http.OK, self.DELETE, |
---|
2779 | + self.public_url + "/foo/bar.txt") |
---|
2780 | d.addCallback(lambda res: |
---|
2781 | self.failIfNodeHasChild(self._foo_node, u"bar.txt")) |
---|
2782 | return d |
---|
2783 | |
---|
2784 | def test_DELETE_FILEURL_missing(self): |
---|
2785 | d = self.DELETE(self.public_url + "/foo/missing") |
---|
2786 | - d.addBoth(self.should404, "test_DELETE_FILEURL_missing") |
---|
2787 | + d.addBoth(self.should404, "DELETE_FILEURL_missing") |
---|
2788 | return d |
---|
2789 | |
---|
2790 | def test_DELETE_FILEURL_missing2(self): |
---|
2791 | d = self.DELETE(self.public_url + "/missing/missing") |
---|
2792 | - d.addBoth(self.should404, "test_DELETE_FILEURL_missing2") |
---|
2793 | + d.addBoth(self.should404, "DELETE_FILEURL_missing2") |
---|
2794 | return d |
---|
2795 | |
---|
2796 | def failUnlessHasBarDotTxtMetadata(self, res): |
---|
2797 | @@ -875,7 +960,7 @@ |
---|
2798 | # I can't do "GET /path?json", I have to do "GET /path/t=json" |
---|
2799 | # instead. This may make it tricky to emulate the S3 interface |
---|
2800 | # completely. |
---|
2801 | - d = self.GET(self.public_url + "/foo/bar.txt?t=json") |
---|
2802 | + d = self.shouldSucceedGET(self.public_url + "/foo/bar.txt?t=json") |
---|
2803 | def _check1(data): |
---|
2804 | self.failUnlessIsBarJSON(data) |
---|
2805 | self.failUnlessHasBarDotTxtMetadata(data) |
---|
2806 | @@ -885,16 +970,16 @@ |
---|
2807 | |
---|
2808 | def test_GET_FILEURL_json_missing(self): |
---|
2809 | d = self.GET(self.public_url + "/foo/missing?json") |
---|
2810 | - d.addBoth(self.should404, "test_GET_FILEURL_json_missing") |
---|
2811 | + d.addBoth(self.should404, "GET_FILEURL_json_missing") |
---|
2812 | return d |
---|
2813 | |
---|
2814 | def test_GET_FILEURL_uri(self): |
---|
2815 | - d = self.GET(self.public_url + "/foo/bar.txt?t=uri") |
---|
2816 | + d = self.shouldSucceedGET(self.public_url + "/foo/bar.txt?t=uri") |
---|
2817 | def _check(res): |
---|
2818 | self.failUnlessEqual(res, self._bar_txt_uri) |
---|
2819 | d.addCallback(_check) |
---|
2820 | d.addCallback(lambda res: |
---|
2821 | - self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri")) |
---|
2822 | + self.shouldSucceedGET(self.public_url + "/foo/bar.txt?t=readonly-uri")) |
---|
2823 | def _check2(res): |
---|
2824 | # for now, for files, uris and readonly-uris are the same |
---|
2825 | self.failUnlessEqual(res, self._bar_txt_uri) |
---|
2826 | @@ -910,14 +995,14 @@ |
---|
2827 | |
---|
2828 | def test_GET_FILEURL_uri_missing(self): |
---|
2829 | d = self.GET(self.public_url + "/foo/missing?t=uri") |
---|
2830 | - d.addBoth(self.should404, "test_GET_FILEURL_uri_missing") |
---|
2831 | + d.addBoth(self.should404, "GET_FILEURL_uri_missing") |
---|
2832 | return d |
---|
2833 | |
---|
2834 | def test_GET_DIRURL(self): |
---|
2835 | # the addSlash means we get a redirect here |
---|
2836 | # from /uri/$URI/foo/ , we need ../../../ to get back to the root |
---|
2837 | ROOT = "../../.." |
---|
2838 | - d = self.GET(self.public_url + "/foo", followRedirect=True) |
---|
2839 | + d = self.shouldSucceedGET(self.public_url + "/foo", followRedirect=True) |
---|
2840 | def _check(res): |
---|
2841 | self.failUnless(('<a href="%s">Return to Welcome page' % ROOT) |
---|
2842 | in res, res) |
---|
2843 | @@ -954,9 +1039,9 @@ |
---|
2844 | self.failUnless(re.search(get_sub, res), res) |
---|
2845 | d.addCallback(_check) |
---|
2846 | |
---|
2847 | - # look at a directory which is readonly |
---|
2848 | + # look at a readonly directory |
---|
2849 | d.addCallback(lambda res: |
---|
2850 | - self.GET(self.public_url + "/reedownlee", followRedirect=True)) |
---|
2851 | + self.shouldSucceedGET(self.public_url + "/reedownlee", followRedirect=True)) |
---|
2852 | def _check2(res): |
---|
2853 | self.failUnless("(read-only)" in res, res) |
---|
2854 | self.failIf("Upload a file" in res, res) |
---|
2855 | @@ -964,14 +1049,14 @@ |
---|
2856 | |
---|
2857 | # and at a directory that contains a readonly directory |
---|
2858 | d.addCallback(lambda res: |
---|
2859 | - self.GET(self.public_url, followRedirect=True)) |
---|
2860 | + self.shouldSucceedGET(self.public_url, followRedirect=True)) |
---|
2861 | def _check3(res): |
---|
2862 | self.failUnless(re.search('<td>DIR-RO</td>' |
---|
2863 | r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res) |
---|
2864 | d.addCallback(_check3) |
---|
2865 | |
---|
2866 | # and an empty directory |
---|
2867 | - d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/")) |
---|
2868 | + d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/empty/")) |
---|
2869 | def _check4(res): |
---|
2870 | self.failUnless("directory is empty" in res, res) |
---|
2871 | MKDIR_BUTTON_RE=re.compile('<input type="hidden" name="t" value="mkdir" />.*<legend class="freeform-form-label">Create a new directory in this directory</legend>.*<input type="submit" value="Create" />', re.I) |
---|
2872 | @@ -981,7 +1066,7 @@ |
---|
2873 | return d |
---|
2874 | |
---|
2875 | def test_GET_DIRURL_badtype(self): |
---|
2876 | - d = self.shouldHTTPError("test_GET_DIRURL_badtype", |
---|
2877 | + d = self.shouldHTTPError("GET_DIRURL_badtype", |
---|
2878 | 400, "Bad Request", |
---|
2879 | "bad t=bogus", |
---|
2880 | self.GET, |
---|
2881 | @@ -989,14 +1074,14 @@ |
---|
2882 | return d |
---|
2883 | |
---|
2884 | def test_GET_DIRURL_json(self): |
---|
2885 | - d = self.GET(self.public_url + "/foo?t=json") |
---|
2886 | + d = self.shouldSucceedGET(self.public_url + "/foo?t=json") |
---|
2887 | d.addCallback(self.failUnlessIsFooJSON) |
---|
2888 | return d |
---|
2889 | |
---|
2890 | |
---|
2891 | def test_POST_DIRURL_manifest_no_ophandle(self): |
---|
2892 | d = self.shouldFail2(error.Error, |
---|
2893 | - "test_POST_DIRURL_manifest_no_ophandle", |
---|
2894 | + "POST_DIRURL_manifest_no_ophandle", |
---|
2895 | "400 Bad Request", |
---|
2896 | "slow operation requires ophandle=", |
---|
2897 | self.POST, self.public_url, t="start-manifest") |
---|
2898 | @@ -1005,8 +1090,9 @@ |
---|
2899 | def test_POST_DIRURL_manifest(self): |
---|
2900 | d = defer.succeed(None) |
---|
2901 | def getman(ignored, output): |
---|
2902 | - d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125", |
---|
2903 | - followRedirect=True) |
---|
2904 | + d = self.shouldSucceed("POST_DIRURL_manifest", http.OK, self.POST, |
---|
2905 | + self.public_url + "/foo/?t=start-manifest&ophandle=125", |
---|
2906 | + followRedirect=True) |
---|
2907 | d.addCallback(self.wait_for_operation, "125") |
---|
2908 | d.addCallback(self.get_operation_results, "125", output) |
---|
2909 | return d |
---|
2910 | @@ -1019,7 +1105,7 @@ |
---|
2911 | d.addCallback(_got_html) |
---|
2912 | |
---|
2913 | # both t=status and unadorned GET should be identical |
---|
2914 | - d.addCallback(lambda res: self.GET("/operations/125")) |
---|
2915 | + d.addCallback(lambda res: self.shouldSucceedGET("/operations/125")) |
---|
2916 | d.addCallback(_got_html) |
---|
2917 | |
---|
2918 | d.addCallback(getman, "html") |
---|
2919 | @@ -1047,15 +1133,16 @@ |
---|
2920 | |
---|
2921 | def test_POST_DIRURL_deepsize_no_ophandle(self): |
---|
2922 | d = self.shouldFail2(error.Error, |
---|
2923 | - "test_POST_DIRURL_deepsize_no_ophandle", |
---|
2924 | + "POST_DIRURL_deepsize_no_ophandle", |
---|
2925 | "400 Bad Request", |
---|
2926 | "slow operation requires ophandle=", |
---|
2927 | self.POST, self.public_url, t="start-deep-size") |
---|
2928 | return d |
---|
2929 | |
---|
2930 | def test_POST_DIRURL_deepsize(self): |
---|
2931 | - d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126", |
---|
2932 | - followRedirect=True) |
---|
2933 | + d = self.shouldSucceed("POST_DIRURL_deepsize", http.OK, self.POST, |
---|
2934 | + self.public_url + "/foo/?t=start-deep-size&ophandle=126", |
---|
2935 | + followRedirect=True) |
---|
2936 | d.addCallback(self.wait_for_operation, "126") |
---|
2937 | d.addCallback(self.get_operation_results, "126", "json") |
---|
2938 | def _got_json(data): |
---|
2939 | @@ -1075,15 +1162,16 @@ |
---|
2940 | |
---|
2941 | def test_POST_DIRURL_deepstats_no_ophandle(self): |
---|
2942 | d = self.shouldFail2(error.Error, |
---|
2943 | - "test_POST_DIRURL_deepstats_no_ophandle", |
---|
2944 | + "POST_DIRURL_deepstats_no_ophandle", |
---|
2945 | "400 Bad Request", |
---|
2946 | "slow operation requires ophandle=", |
---|
2947 | self.POST, self.public_url, t="start-deep-stats") |
---|
2948 | return d |
---|
2949 | |
---|
2950 | def test_POST_DIRURL_deepstats(self): |
---|
2951 | - d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127", |
---|
2952 | - followRedirect=True) |
---|
2953 | + d = self.shouldSucceed("POST_DIRURL_deepstats", http.OK, self.POST, |
---|
2954 | + self.public_url + "/foo/?t=start-deep-stats&ophandle=127", |
---|
2955 | + followRedirect=True) |
---|
2956 | d.addCallback(self.wait_for_operation, "127") |
---|
2957 | d.addCallback(self.get_operation_results, "127", "json") |
---|
2958 | def _got_json(stats): |
---|
2959 | @@ -1109,7 +1197,8 @@ |
---|
2960 | return d |
---|
2961 | |
---|
2962 | def test_POST_DIRURL_stream_manifest(self): |
---|
2963 | - d = self.POST(self.public_url + "/foo/?t=stream-manifest") |
---|
2964 | + d = self.shouldSucceed("POST_DIRURL_stream_manifest", http.OK, self.POST, |
---|
2965 | + self.public_url + "/foo/?t=stream-manifest") |
---|
2966 | def _check(res): |
---|
2967 | self.failUnless(res.endswith("\n")) |
---|
2968 | units = [simplejson.loads(t) for t in res[:-1].split("\n")] |
---|
2969 | @@ -1129,21 +1218,22 @@ |
---|
2970 | return d |
---|
2971 | |
---|
2972 | def test_GET_DIRURL_uri(self): |
---|
2973 | - d = self.GET(self.public_url + "/foo?t=uri") |
---|
2974 | + d = self.shouldSucceedGET(self.public_url + "/foo?t=uri") |
---|
2975 | def _check(res): |
---|
2976 | self.failUnlessEqual(res, self._foo_uri) |
---|
2977 | d.addCallback(_check) |
---|
2978 | return d |
---|
2979 | |
---|
2980 | def test_GET_DIRURL_readonly_uri(self): |
---|
2981 | - d = self.GET(self.public_url + "/foo?t=readonly-uri") |
---|
2982 | + d = self.shouldSucceedGET(self.public_url + "/foo?t=readonly-uri") |
---|
2983 | def _check(res): |
---|
2984 | self.failUnlessEqual(res, self._foo_readonly_uri) |
---|
2985 | d.addCallback(_check) |
---|
2986 | return d |
---|
2987 | |
---|
2988 | def test_PUT_NEWDIRURL(self): |
---|
2989 | - d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "") |
---|
2990 | + d = self.shouldSucceed("PUT_NEWDIRURL", http.OK, self.PUT, |
---|
2991 | + self.public_url + "/foo/newdir?t=mkdir", "") |
---|
2992 | d.addCallback(lambda res: |
---|
2993 | self.failUnlessNodeHasChild(self._foo_node, u"newdir")) |
---|
2994 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
2995 | @@ -1151,7 +1241,8 @@ |
---|
2996 | return d |
---|
2997 | |
---|
2998 | def test_POST_NEWDIRURL(self): |
---|
2999 | - d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "") |
---|
3000 | + d = self.shouldSucceed("POST_NEWDIRURL", http.OK, self.POST2, |
---|
3001 | + self.public_url + "/foo/newdir?t=mkdir", "") |
---|
3002 | d.addCallback(lambda res: |
---|
3003 | self.failUnlessNodeHasChild(self._foo_node, u"newdir")) |
---|
3004 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
3005 | @@ -1160,30 +1251,41 @@ |
---|
3006 | |
---|
3007 | def test_POST_NEWDIRURL_emptyname(self): |
---|
3008 | # an empty pathname component (i.e. a double-slash) is disallowed |
---|
3009 | - d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_emptyname", |
---|
3010 | + d = self.shouldFail2(error.Error, "POST_NEWDIRURL_emptyname", |
---|
3011 | "400 Bad Request", |
---|
3012 | "The webapi does not allow empty pathname components, i.e. a double slash", |
---|
3013 | self.POST, self.public_url + "//?t=mkdir") |
---|
3014 | return d |
---|
3015 | |
---|
3016 | def test_POST_NEWDIRURL_initial_children(self): |
---|
3017 | - (newkids, filecap1, filecap2, filecap3, |
---|
3018 | - dircap) = self._create_initial_children() |
---|
3019 | - d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-with-children", |
---|
3020 | - simplejson.dumps(newkids)) |
---|
3021 | + (newkids, caps) = self._create_initial_children() |
---|
3022 | + d = self.shouldSucceed("POST_NEWDIRURL_initial_children", http.OK, self.POST2, |
---|
3023 | + self.public_url + "/foo/newdir?t=mkdir-with-children", |
---|
3024 | + simplejson.dumps(newkids)) |
---|
3025 | def _check(uri): |
---|
3026 | n = self.s.create_node_from_uri(uri.strip()) |
---|
3027 | d2 = self.failUnlessNodeKeysAre(n, newkids.keys()) |
---|
3028 | d2.addCallback(lambda ign: |
---|
3029 | - self.failUnlessChildURIIs(n, u"child-imm", filecap1)) |
---|
3030 | + self.failUnlessROChildURIIs(n, u"child-imm", |
---|
3031 | + caps['filecap1'])) |
---|
3032 | d2.addCallback(lambda ign: |
---|
3033 | - self.failUnlessChildURIIs(n, u"child-mutable", |
---|
3034 | - filecap2)) |
---|
3035 | + self.failUnlessRWChildURIIs(n, u"child-mutable", |
---|
3036 | + caps['filecap2'])) |
---|
3037 | d2.addCallback(lambda ign: |
---|
3038 | - self.failUnlessChildURIIs(n, u"child-mutable-ro", |
---|
3039 | - filecap3)) |
---|
3040 | + self.failUnlessROChildURIIs(n, u"child-mutable-ro", |
---|
3041 | + caps['filecap3'])) |
---|
3042 | d2.addCallback(lambda ign: |
---|
3043 | - self.failUnlessChildURIIs(n, u"dirchild", dircap)) |
---|
3044 | + self.failUnlessROChildURIIs(n, u"unknownchild-ro", |
---|
3045 | + caps['unknown_rocap'])) |
---|
3046 | + d2.addCallback(lambda ign: |
---|
3047 | + self.failUnlessRWChildURIIs(n, u"unknownchild-rw", |
---|
3048 | + caps['unknown_rwcap'])) |
---|
3049 | + d2.addCallback(lambda ign: |
---|
3050 | + self.failUnlessROChildURIIs(n, u"unknownchild-imm", |
---|
3051 | + caps['unknown_immcap'])) |
---|
3052 | + d2.addCallback(lambda ign: |
---|
3053 | + self.failUnlessRWChildURIIs(n, u"dirchild", |
---|
3054 | + caps['dircap'])) |
---|
3055 | return d2 |
---|
3056 | d.addCallback(_check) |
---|
3057 | d.addCallback(lambda res: |
---|
3058 | @@ -1191,21 +1293,26 @@ |
---|
3059 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
3060 | d.addCallback(self.failUnlessNodeKeysAre, newkids.keys()) |
---|
3061 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
3062 | - d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1) |
---|
3063 | + d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1']) |
---|
3064 | return d |
---|
3065 | |
---|
3066 | def test_POST_NEWDIRURL_immutable(self): |
---|
3067 | - (newkids, filecap1, immdircap) = self._create_immutable_children() |
---|
3068 | - d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable", |
---|
3069 | - simplejson.dumps(newkids)) |
---|
3070 | + (newkids, caps) = self._create_immutable_children() |
---|
3071 | + d = self.shouldSucceed("POST_NEWDIRURL_immutable", http.OK, self.POST2, |
---|
3072 | + self.public_url + "/foo/newdir?t=mkdir-immutable", |
---|
3073 | + simplejson.dumps(newkids)) |
---|
3074 | def _check(uri): |
---|
3075 | n = self.s.create_node_from_uri(uri.strip()) |
---|
3076 | d2 = self.failUnlessNodeKeysAre(n, newkids.keys()) |
---|
3077 | d2.addCallback(lambda ign: |
---|
3078 | - self.failUnlessChildURIIs(n, u"child-imm", filecap1)) |
---|
3079 | + self.failUnlessROChildURIIs(n, u"child-imm", |
---|
3080 | + caps['filecap1'])) |
---|
3081 | + d2.addCallback(lambda ign: |
---|
3082 | + self.failUnlessROChildURIIs(n, u"unknownchild-imm", |
---|
3083 | + caps['unknown_immcap'])) |
---|
3084 | d2.addCallback(lambda ign: |
---|
3085 | - self.failUnlessChildURIIs(n, u"dirchild-imm", |
---|
3086 | - immdircap)) |
---|
3087 | + self.failUnlessROChildURIIs(n, u"dirchild-imm", |
---|
3088 | + caps['immdircap'])) |
---|
3089 | return d2 |
---|
3090 | d.addCallback(_check) |
---|
3091 | d.addCallback(lambda res: |
---|
3092 | @@ -1213,25 +1320,27 @@ |
---|
3093 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
3094 | d.addCallback(self.failUnlessNodeKeysAre, newkids.keys()) |
---|
3095 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
3096 | - d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1) |
---|
3097 | + d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1']) |
---|
3098 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
3099 | - d.addCallback(self.failUnlessChildURIIs, u"dirchild-imm", immdircap) |
---|
3100 | + d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap']) |
---|
3101 | + d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
3102 | + d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap']) |
---|
3103 | d.addErrback(self.explain_web_error) |
---|
3104 | return d |
---|
3105 | |
---|
3106 | def test_POST_NEWDIRURL_immutable_bad(self): |
---|
3107 | - (newkids, filecap1, filecap2, filecap3, |
---|
3108 | - dircap) = self._create_initial_children() |
---|
3109 | - d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad", |
---|
3110 | + (newkids, caps) = self._create_initial_children() |
---|
3111 | + d = self.shouldFail2(error.Error, "POST_NEWDIRURL_immutable_bad", |
---|
3112 | "400 Bad Request", |
---|
3113 | - "a mkdir-immutable operation was given a child that was not itself immutable", |
---|
3114 | + "needed to be immutable but was not", |
---|
3115 | self.POST2, |
---|
3116 | self.public_url + "/foo/newdir?t=mkdir-immutable", |
---|
3117 | simplejson.dumps(newkids)) |
---|
3118 | return d |
---|
3119 | |
---|
3120 | def test_PUT_NEWDIRURL_exists(self): |
---|
3121 | - d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "") |
---|
3122 | + d = self.shouldSucceed("PUT_NEWDIRURL_exists", http.OK, self.PUT, |
---|
3123 | + self.public_url + "/foo/sub?t=mkdir", "") |
---|
3124 | d.addCallback(lambda res: |
---|
3125 | self.failUnlessNodeHasChild(self._foo_node, u"sub")) |
---|
3126 | d.addCallback(lambda res: self._foo_node.get(u"sub")) |
---|
3127 | @@ -1249,18 +1358,21 @@ |
---|
3128 | d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"]) |
---|
3129 | return d |
---|
3130 | |
---|
3131 | - def test_PUT_NEWDIRURL_mkdir_p(self): |
---|
3132 | + def test_POST_NEWDIRURL_mkdir_p(self): |
---|
3133 | d = defer.succeed(None) |
---|
3134 | - d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp')) |
---|
3135 | + d.addCallback(lambda res: self.shouldSucceed("POST_NEWDIRURL_mkdir_p-1", http.OK, self.POST, |
---|
3136 | + self.public_url + "/foo", t='mkdir', name='mkp')) |
---|
3137 | d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp")) |
---|
3138 | d.addCallback(lambda res: self._foo_node.get(u"mkp")) |
---|
3139 | def mkdir_p(mkpnode): |
---|
3140 | url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri()) |
---|
3141 | - d = self.POST(url) |
---|
3142 | + d = self.shouldSucceed("POST_NEWDIRURL_mkdir_p-2", http.OK, self.POST, |
---|
3143 | + url) |
---|
3144 | def made_subsub(ssuri): |
---|
3145 | d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2") |
---|
3146 | d.addCallback(lambda ssnode: self.failUnlessEqual(ssnode.get_uri(), ssuri)) |
---|
3147 | - d = self.POST(url) |
---|
3148 | + d = self.shouldSucceed("POST_NEWDIRURL_mkdir_p-3", http.OK, self.POST, |
---|
3149 | + url) |
---|
3150 | d.addCallback(lambda uri2: self.failUnlessEqual(uri2, ssuri)) |
---|
3151 | return d |
---|
3152 | d.addCallback(made_subsub) |
---|
3153 | @@ -1269,7 +1381,8 @@ |
---|
3154 | return d |
---|
3155 | |
---|
3156 | def test_PUT_NEWDIRURL_mkdirs(self): |
---|
3157 | - d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "") |
---|
3158 | + d = self.shouldSucceed("PUT_NEWDIRURL_mkdirs", http.OK, self.PUT, |
---|
3159 | + self.public_url + "/foo/subdir/newdir?t=mkdir", "") |
---|
3160 | d.addCallback(lambda res: |
---|
3161 | self.failIfNodeHasChild(self._foo_node, u"newdir")) |
---|
3162 | d.addCallback(lambda res: |
---|
3163 | @@ -1280,21 +1393,22 @@ |
---|
3164 | return d |
---|
3165 | |
---|
3166 | def test_DELETE_DIRURL(self): |
---|
3167 | - d = self.DELETE(self.public_url + "/foo") |
---|
3168 | + d = self.shouldSucceed("DELETE_DIRURL", http.OK, self.DELETE, |
---|
3169 | + self.public_url + "/foo") |
---|
3170 | d.addCallback(lambda res: |
---|
3171 | self.failIfNodeHasChild(self.public_root, u"foo")) |
---|
3172 | return d |
---|
3173 | |
---|
3174 | def test_DELETE_DIRURL_missing(self): |
---|
3175 | d = self.DELETE(self.public_url + "/foo/missing") |
---|
3176 | - d.addBoth(self.should404, "test_DELETE_DIRURL_missing") |
---|
3177 | + d.addBoth(self.should404, "DELETE_DIRURL_missing") |
---|
3178 | d.addCallback(lambda res: |
---|
3179 | self.failUnlessNodeHasChild(self.public_root, u"foo")) |
---|
3180 | return d |
---|
3181 | |
---|
3182 | def test_DELETE_DIRURL_missing2(self): |
---|
3183 | d = self.DELETE(self.public_url + "/missing") |
---|
3184 | - d.addBoth(self.should404, "test_DELETE_DIRURL_missing2") |
---|
3185 | + d.addBoth(self.should404, "DELETE_DIRURL_missing2") |
---|
3186 | return d |
---|
3187 | |
---|
3188 | def dump_root(self): |
---|
3189 | @@ -1346,18 +1460,44 @@ |
---|
3190 | d.addCallback(_check) |
---|
3191 | return d |
---|
3192 | |
---|
3193 | - def failUnlessChildURIIs(self, node, name, expected_uri): |
---|
3194 | + def failUnlessRWChildURIIs(self, node, name, expected_uri): |
---|
3195 | + assert isinstance(name, unicode) |
---|
3196 | + d = node.get_child_at_path(name) |
---|
3197 | + def _check(child): |
---|
3198 | + self.failUnless(child.is_unknown() or not child.is_readonly()) |
---|
3199 | + self.failUnlessEqual(child.get_uri(), expected_uri.strip()) |
---|
3200 | + expected_ro_uri = self._make_readonly(expected_uri) |
---|
3201 | + if expected_ro_uri: |
---|
3202 | + self.failUnlessEqual(child.get_readonly_uri(), expected_ro_uri.strip()) |
---|
3203 | + d.addCallback(_check) |
---|
3204 | + return d |
---|
3205 | + |
---|
3206 | + def failUnlessROChildURIIs(self, node, name, expected_uri): |
---|
3207 | assert isinstance(name, unicode) |
---|
3208 | d = node.get_child_at_path(name) |
---|
3209 | def _check(child): |
---|
3210 | + self.failUnless(child.is_unknown() or child.is_readonly()) |
---|
3211 | self.failUnlessEqual(child.get_uri(), expected_uri.strip()) |
---|
3212 | d.addCallback(_check) |
---|
3213 | return d |
---|
3214 | |
---|
3215 | - def failUnlessURIMatchesChild(self, got_uri, node, name): |
---|
3216 | + def failUnlessURIMatchesRWChild(self, got_uri, node, name): |
---|
3217 | + assert isinstance(name, unicode) |
---|
3218 | + d = node.get_child_at_path(name) |
---|
3219 | + def _check(child): |
---|
3220 | + self.failUnless(child.is_unknown() or not child.is_readonly()) |
---|
3221 | + self.failUnlessEqual(child.get_uri(), got_uri.strip()) |
---|
3222 | + expected_ro_uri = self._make_readonly(got_uri) |
---|
3223 | + if expected_ro_uri: |
---|
3224 | + self.failUnlessEqual(child.get_readonly_uri(), expected_ro_uri.strip()) |
---|
3225 | + d.addCallback(_check) |
---|
3226 | + return d |
---|
3227 | + |
---|
3228 | + def failUnlessURIMatchesROChild(self, got_uri, node, name): |
---|
3229 | assert isinstance(name, unicode) |
---|
3230 | d = node.get_child_at_path(name) |
---|
3231 | def _check(child): |
---|
3232 | + self.failUnless(child.is_unknown() or child.is_readonly()) |
---|
3233 | self.failUnlessEqual(got_uri.strip(), child.get_uri()) |
---|
3234 | d.addCallback(_check) |
---|
3235 | return d |
---|
3236 | @@ -1366,10 +1506,11 @@ |
---|
3237 | self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents) |
---|
3238 | |
---|
3239 | def test_POST_upload(self): |
---|
3240 | - d = self.POST(self.public_url + "/foo", t="upload", |
---|
3241 | - file=("new.txt", self.NEWFILE_CONTENTS)) |
---|
3242 | + d = self.shouldSucceed("POST_upload", http.OK, self.POST, |
---|
3243 | + self.public_url + "/foo", t="upload", |
---|
3244 | + file=("new.txt", self.NEWFILE_CONTENTS)) |
---|
3245 | fn = self._foo_node |
---|
3246 | - d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt") |
---|
3247 | + d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt") |
---|
3248 | d.addCallback(lambda res: |
---|
3249 | self.failUnlessChildContentsAre(fn, u"new.txt", |
---|
3250 | self.NEWFILE_CONTENTS)) |
---|
3251 | @@ -1377,15 +1518,16 @@ |
---|
3252 | |
---|
3253 | def test_POST_upload_unicode(self): |
---|
3254 | filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t |
---|
3255 | - d = self.POST(self.public_url + "/foo", t="upload", |
---|
3256 | - file=(filename, self.NEWFILE_CONTENTS)) |
---|
3257 | + d = self.shouldSucceed("POST_upload_unicode", http.OK, self.POST, |
---|
3258 | + self.public_url + "/foo", t="upload", |
---|
3259 | + file=(filename, self.NEWFILE_CONTENTS)) |
---|
3260 | fn = self._foo_node |
---|
3261 | - d.addCallback(self.failUnlessURIMatchesChild, fn, filename) |
---|
3262 | + d.addCallback(self.failUnlessURIMatchesROChild, fn, filename) |
---|
3263 | d.addCallback(lambda res: |
---|
3264 | self.failUnlessChildContentsAre(fn, filename, |
---|
3265 | self.NEWFILE_CONTENTS)) |
---|
3266 | target_url = self.public_url + "/foo/" + filename.encode("utf-8") |
---|
3267 | - d.addCallback(lambda res: self.GET(target_url)) |
---|
3268 | + d.addCallback(lambda res: self.shouldSucceedGET(target_url)) |
---|
3269 | d.addCallback(lambda contents: self.failUnlessEqual(contents, |
---|
3270 | self.NEWFILE_CONTENTS, |
---|
3271 | contents)) |
---|
3272 | @@ -1393,24 +1535,26 @@ |
---|
3273 | |
---|
3274 | def test_POST_upload_unicode_named(self): |
---|
3275 | filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t |
---|
3276 | - d = self.POST(self.public_url + "/foo", t="upload", |
---|
3277 | - name=filename, |
---|
3278 | - file=("overridden", self.NEWFILE_CONTENTS)) |
---|
3279 | + d = self.shouldSucceed("POST_upload_unicode_named", http.OK, self.POST, |
---|
3280 | + self.public_url + "/foo", t="upload", |
---|
3281 | + name=filename, |
---|
3282 | + file=("overridden", self.NEWFILE_CONTENTS)) |
---|
3283 | fn = self._foo_node |
---|
3284 | - d.addCallback(self.failUnlessURIMatchesChild, fn, filename) |
---|
3285 | + d.addCallback(self.failUnlessURIMatchesROChild, fn, filename) |
---|
3286 | d.addCallback(lambda res: |
---|
3287 | self.failUnlessChildContentsAre(fn, filename, |
---|
3288 | self.NEWFILE_CONTENTS)) |
---|
3289 | target_url = self.public_url + "/foo/" + filename.encode("utf-8") |
---|
3290 | - d.addCallback(lambda res: self.GET(target_url)) |
---|
3291 | + d.addCallback(lambda res: self.shouldSucceedGET(target_url)) |
---|
3292 | d.addCallback(lambda contents: self.failUnlessEqual(contents, |
---|
3293 | self.NEWFILE_CONTENTS, |
---|
3294 | contents)) |
---|
3295 | return d |
---|
3296 | |
---|
3297 | def test_POST_upload_no_link(self): |
---|
3298 | - d = self.POST("/uri", t="upload", |
---|
3299 | - file=("new.txt", self.NEWFILE_CONTENTS)) |
---|
3300 | + d = self.shouldSucceed("POST_upload_no_link", http.OK, self.POST, |
---|
3301 | + "/uri", t="upload", |
---|
3302 | + file=("new.txt", self.NEWFILE_CONTENTS)) |
---|
3303 | def _check_upload_results(page): |
---|
3304 | # this should be a page which describes the results of the upload |
---|
3305 | # that just finished. |
---|
3306 | @@ -1449,7 +1593,7 @@ |
---|
3307 | self.failUnlessEqual(statuscode, str(http.FOUND)) |
---|
3308 | self.failUnless(target.startswith(self.webish_url), target) |
---|
3309 | return client.getPage(target, method="GET") |
---|
3310 | - d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results", |
---|
3311 | + d = self.shouldRedirect2("POST_upload_no_link_whendone_results", |
---|
3312 | check, |
---|
3313 | self.POST, "/uri", t="upload", |
---|
3314 | when_done="/uri/%(uri)s", |
---|
3315 | @@ -1459,8 +1603,9 @@ |
---|
3316 | return d |
---|
3317 | |
---|
3318 | def test_POST_upload_no_link_mutable(self): |
---|
3319 | - d = self.POST("/uri", t="upload", mutable="true", |
---|
3320 | - file=("new.txt", self.NEWFILE_CONTENTS)) |
---|
3321 | + d = self.shouldSucceed("POST_upload_no_link_mutable", http.OK, self.POST, |
---|
3322 | + "/uri", t="upload", mutable="true", |
---|
3323 | + file=("new.txt", self.NEWFILE_CONTENTS)) |
---|
3324 | def _check(filecap): |
---|
3325 | filecap = filecap.strip() |
---|
3326 | self.failUnless(filecap.startswith("URI:SSK:"), filecap) |
---|
3327 | @@ -1472,11 +1617,11 @@ |
---|
3328 | d.addCallback(_check) |
---|
3329 | def _check2(data): |
---|
3330 | self.failUnlessEqual(data, self.NEWFILE_CONTENTS) |
---|
3331 | - return self.GET("/uri/%s" % urllib.quote(self.filecap)) |
---|
3332 | + return self.shouldSucceedGET("/uri/%s" % urllib.quote(self.filecap)) |
---|
3333 | d.addCallback(_check2) |
---|
3334 | def _check3(data): |
---|
3335 | self.failUnlessEqual(data, self.NEWFILE_CONTENTS) |
---|
3336 | - return self.GET("/file/%s" % urllib.quote(self.filecap)) |
---|
3337 | + return self.shouldSucceedGET("/file/%s" % urllib.quote(self.filecap)) |
---|
3338 | d.addCallback(_check3) |
---|
3339 | def _check4(data): |
---|
3340 | self.failUnlessEqual(data, self.NEWFILE_CONTENTS) |
---|
3341 | @@ -1485,7 +1630,7 @@ |
---|
3342 | |
---|
3343 | def test_POST_upload_no_link_mutable_toobig(self): |
---|
3344 | d = self.shouldFail2(error.Error, |
---|
3345 | - "test_POST_upload_no_link_mutable_toobig", |
---|
3346 | + "POST_upload_no_link_mutable_toobig", |
---|
3347 | "413 Request Entity Too Large", |
---|
3348 | "SDMF is limited to one segment, and 10001 > 10000", |
---|
3349 | self.POST, |
---|
3350 | @@ -1496,10 +1641,11 @@ |
---|
3351 | |
---|
3352 | def test_POST_upload_mutable(self): |
---|
3353 | # this creates a mutable file |
---|
3354 | - d = self.POST(self.public_url + "/foo", t="upload", mutable="true", |
---|
3355 | - file=("new.txt", self.NEWFILE_CONTENTS)) |
---|
3356 | + d = self.shouldSucceed("POST_upload_mutable", http.OK, self.POST, |
---|
3357 | + self.public_url + "/foo", t="upload", mutable="true", |
---|
3358 | + file=("new.txt", self.NEWFILE_CONTENTS)) |
---|
3359 | fn = self._foo_node |
---|
3360 | - d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt") |
---|
3361 | + d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt") |
---|
3362 | d.addCallback(lambda res: |
---|
3363 | self.failUnlessMutableChildContentsAre(fn, u"new.txt", |
---|
3364 | self.NEWFILE_CONTENTS)) |
---|
3365 | @@ -1515,10 +1661,11 @@ |
---|
3366 | # now upload it again and make sure that the URI doesn't change |
---|
3367 | NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n" |
---|
3368 | d.addCallback(lambda res: |
---|
3369 | - self.POST(self.public_url + "/foo", t="upload", |
---|
3370 | - mutable="true", |
---|
3371 | - file=("new.txt", NEWER_CONTENTS))) |
---|
3372 | - d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt") |
---|
3373 | + self.shouldSucceed("POST_upload_mutable-again", http.OK, self.POST, |
---|
3374 | + self.public_url + "/foo", t="upload", |
---|
3375 | + mutable="true", |
---|
3376 | + file=("new.txt", NEWER_CONTENTS))) |
---|
3377 | + d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt") |
---|
3378 | d.addCallback(lambda res: |
---|
3379 | self.failUnlessMutableChildContentsAre(fn, u"new.txt", |
---|
3380 | NEWER_CONTENTS)) |
---|
3381 | @@ -1533,8 +1680,9 @@ |
---|
3382 | # upload a second time, using PUT instead of POST |
---|
3383 | NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n" |
---|
3384 | d.addCallback(lambda res: |
---|
3385 | - self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS)) |
---|
3386 | - d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt") |
---|
3387 | + self.shouldSucceed("POST_upload_mutable-again-with-PUT", http.OK, self.PUT, |
---|
3388 | + self.public_url + "/foo/new.txt", NEW2_CONTENTS)) |
---|
3389 | + d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt") |
---|
3390 | d.addCallback(lambda res: |
---|
3391 | self.failUnlessMutableChildContentsAre(fn, u"new.txt", |
---|
3392 | NEW2_CONTENTS)) |
---|
3393 | @@ -1543,8 +1691,8 @@ |
---|
3394 | # slightly differently |
---|
3395 | |
---|
3396 | d.addCallback(lambda res: |
---|
3397 | - self.GET(self.public_url + "/foo/", |
---|
3398 | - followRedirect=True)) |
---|
3399 | + self.shouldSucceedGET(self.public_url + "/foo/", |
---|
3400 | + followRedirect=True)) |
---|
3401 | def _check_page(res): |
---|
3402 | # TODO: assert more about the contents |
---|
3403 | self.failUnless("SSK" in res) |
---|
3404 | @@ -1561,8 +1709,8 @@ |
---|
3405 | |
---|
3406 | # look at the JSON form of the enclosing directory |
---|
3407 | d.addCallback(lambda res: |
---|
3408 | - self.GET(self.public_url + "/foo/?t=json", |
---|
3409 | - followRedirect=True)) |
---|
3410 | + self.shouldSucceedGET(self.public_url + "/foo/?t=json", |
---|
3411 | + followRedirect=True)) |
---|
3412 | def _check_page_json(res): |
---|
3413 | parsed = simplejson.loads(res) |
---|
3414 | self.failUnlessEqual(parsed[0], "dirnode") |
---|
3415 | @@ -1580,7 +1728,7 @@ |
---|
3416 | |
---|
3417 | # and the JSON form of the file |
---|
3418 | d.addCallback(lambda res: |
---|
3419 | - self.GET(self.public_url + "/foo/new.txt?t=json")) |
---|
3420 | + self.shouldSucceedGET(self.public_url + "/foo/new.txt?t=json")) |
---|
3421 | def _check_file_json(res): |
---|
3422 | parsed = simplejson.loads(res) |
---|
3423 | self.failUnlessEqual(parsed[0], "filenode") |
---|
3424 | @@ -1592,10 +1740,10 @@ |
---|
3425 | |
---|
3426 | # and look at t=uri and t=readonly-uri |
---|
3427 | d.addCallback(lambda res: |
---|
3428 | - self.GET(self.public_url + "/foo/new.txt?t=uri")) |
---|
3429 | + self.shouldSucceedGET(self.public_url + "/foo/new.txt?t=uri")) |
---|
3430 | d.addCallback(lambda res: self.failUnlessEqual(res, self._mutable_uri)) |
---|
3431 | d.addCallback(lambda res: |
---|
3432 | - self.GET(self.public_url + "/foo/new.txt?t=readonly-uri")) |
---|
3433 | + self.shouldSucceedGET(self.public_url + "/foo/new.txt?t=readonly-uri")) |
---|
3434 | def _check_ro_uri(res): |
---|
3435 | ro_uri = unicode(self._mutable_node.get_readonly().to_string()) |
---|
3436 | self.failUnlessEqual(res, ro_uri) |
---|
3437 | @@ -1603,15 +1751,15 @@ |
---|
3438 | |
---|
3439 | # make sure we can get to it from /uri/URI |
---|
3440 | d.addCallback(lambda res: |
---|
3441 | - self.GET("/uri/%s" % urllib.quote(self._mutable_uri))) |
---|
3442 | + self.shouldSucceedGET("/uri/%s" % urllib.quote(self._mutable_uri))) |
---|
3443 | d.addCallback(lambda res: |
---|
3444 | self.failUnlessEqual(res, NEW2_CONTENTS)) |
---|
3445 | |
---|
3446 | # and that HEAD computes the size correctly |
---|
3447 | d.addCallback(lambda res: |
---|
3448 | - self.HEAD(self.public_url + "/foo/new.txt", |
---|
3449 | - return_response=True)) |
---|
3450 | - def _got_headers((res, status, headers)): |
---|
3451 | + self.shouldSucceedHEAD(self.public_url + "/foo/new.txt", |
---|
3452 | + return_response=True)) |
---|
3453 | + def _got_headers((res, statuscode, headers)): |
---|
3454 | self.failUnlessEqual(res, "") |
---|
3455 | self.failUnlessEqual(headers["content-length"][0], |
---|
3456 | str(len(NEW2_CONTENTS))) |
---|
3457 | @@ -1621,7 +1769,7 @@ |
---|
3458 | # make sure that size errors are displayed correctly for overwrite |
---|
3459 | d.addCallback(lambda res: |
---|
3460 | self.shouldFail2(error.Error, |
---|
3461 | - "test_POST_upload_mutable-toobig", |
---|
3462 | + "POST_upload_mutable-toobig", |
---|
3463 | "413 Request Entity Too Large", |
---|
3464 | "SDMF is limited to one segment, and 10001 > 10000", |
---|
3465 | self.POST, |
---|
3466 | @@ -1636,7 +1784,7 @@ |
---|
3467 | |
---|
3468 | def test_POST_upload_mutable_toobig(self): |
---|
3469 | d = self.shouldFail2(error.Error, |
---|
3470 | - "test_POST_upload_mutable_toobig", |
---|
3471 | + "POST_upload_mutable_toobig", |
---|
3472 | "413 Request Entity Too Large", |
---|
3473 | "SDMF is limited to one segment, and 10001 > 10000", |
---|
3474 | self.POST, |
---|
3475 | @@ -1660,19 +1808,21 @@ |
---|
3476 | return f |
---|
3477 | |
---|
3478 | def test_POST_upload_replace(self): |
---|
3479 | - d = self.POST(self.public_url + "/foo", t="upload", |
---|
3480 | - file=("bar.txt", self.NEWFILE_CONTENTS)) |
---|
3481 | + d = self.shouldSucceed("POST_upload_replace", http.OK, self.POST, |
---|
3482 | + self.public_url + "/foo", t="upload", |
---|
3483 | + file=("bar.txt", self.NEWFILE_CONTENTS)) |
---|
3484 | fn = self._foo_node |
---|
3485 | - d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt") |
---|
3486 | + d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt") |
---|
3487 | d.addCallback(lambda res: |
---|
3488 | self.failUnlessChildContentsAre(fn, u"bar.txt", |
---|
3489 | self.NEWFILE_CONTENTS)) |
---|
3490 | return d |
---|
3491 | |
---|
3492 | def test_POST_upload_no_replace_ok(self): |
---|
3493 | - d = self.POST(self.public_url + "/foo?replace=false", t="upload", |
---|
3494 | - file=("new.txt", self.NEWFILE_CONTENTS)) |
---|
3495 | - d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt")) |
---|
3496 | + d = self.shouldSucceed("POST_upload_no_replace_ok", http.OK, self.POST, |
---|
3497 | + self.public_url + "/foo?replace=false", t="upload", |
---|
3498 | + file=("new.txt", self.NEWFILE_CONTENTS)) |
---|
3499 | + d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/new.txt")) |
---|
3500 | d.addCallback(lambda res: self.failUnlessEqual(res, |
---|
3501 | self.NEWFILE_CONTENTS)) |
---|
3502 | return d |
---|
3503 | @@ -1685,7 +1835,7 @@ |
---|
3504 | "409 Conflict", |
---|
3505 | "There was already a child by that name, and you asked me " |
---|
3506 | "to not replace it") |
---|
3507 | - d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt")) |
---|
3508 | + d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/bar.txt")) |
---|
3509 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
3510 | return d |
---|
3511 | |
---|
3512 | @@ -1696,7 +1846,7 @@ |
---|
3513 | "409 Conflict", |
---|
3514 | "There was already a child by that name, and you asked me " |
---|
3515 | "to not replace it") |
---|
3516 | - d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt")) |
---|
3517 | + d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/bar.txt")) |
---|
3518 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
3519 | return d |
---|
3520 | |
---|
3521 | @@ -1712,9 +1862,10 @@ |
---|
3522 | |
---|
3523 | def test_POST_upload_named(self): |
---|
3524 | fn = self._foo_node |
---|
3525 | - d = self.POST(self.public_url + "/foo", t="upload", |
---|
3526 | - name="new.txt", file=self.NEWFILE_CONTENTS) |
---|
3527 | - d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt") |
---|
3528 | + d = self.shouldSucceed("POST_upload_named", http.OK, self.POST, |
---|
3529 | + self.public_url + "/foo", t="upload", |
---|
3530 | + name="new.txt", file=self.NEWFILE_CONTENTS) |
---|
3531 | + d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt") |
---|
3532 | d.addCallback(lambda res: |
---|
3533 | self.failUnlessChildContentsAre(fn, u"new.txt", |
---|
3534 | self.NEWFILE_CONTENTS)) |
---|
3535 | @@ -1724,7 +1875,7 @@ |
---|
3536 | d = self.POST(self.public_url + "/foo", t="upload", |
---|
3537 | name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS) |
---|
3538 | d.addBoth(self.shouldFail, error.Error, |
---|
3539 | - "test_POST_upload_named_badfilename", |
---|
3540 | + "POST_upload_named_badfilename", |
---|
3541 | "400 Bad Request", |
---|
3542 | "name= may not contain a slash", |
---|
3543 | ) |
---|
3544 | @@ -1738,7 +1889,8 @@ |
---|
3545 | |
---|
3546 | def test_POST_FILEURL_check(self): |
---|
3547 | bar_url = self.public_url + "/foo/bar.txt" |
---|
3548 | - d = self.POST(bar_url, t="check") |
---|
3549 | + d = self.shouldSucceed("POST_FILEURL_check-1", http.OK, self.POST, |
---|
3550 | + bar_url, t="check") |
---|
3551 | def _check(res): |
---|
3552 | self.failUnless("Healthy :" in res) |
---|
3553 | d.addCallback(_check) |
---|
3554 | @@ -1747,13 +1899,14 @@ |
---|
3555 | self.failUnlessEqual(statuscode, str(http.FOUND)) |
---|
3556 | self.failUnlessEqual(target, redir_url) |
---|
3557 | d.addCallback(lambda res: |
---|
3558 | - self.shouldRedirect2("test_POST_FILEURL_check", |
---|
3559 | + self.shouldRedirect2("POST_FILEURL_check-2", |
---|
3560 | _check2, |
---|
3561 | self.POST, bar_url, |
---|
3562 | t="check", |
---|
3563 | when_done=redir_url)) |
---|
3564 | d.addCallback(lambda res: |
---|
3565 | - self.POST(bar_url, t="check", return_to=redir_url)) |
---|
3566 | + self.shouldSucceed("POST_FILEURL_check-3", http.OK, self.POST, |
---|
3567 | + bar_url, t="check", return_to=redir_url)) |
---|
3568 | def _check3(res): |
---|
3569 | self.failUnless("Healthy :" in res) |
---|
3570 | self.failUnless("Return to file" in res) |
---|
3571 | @@ -1761,7 +1914,8 @@ |
---|
3572 | d.addCallback(_check3) |
---|
3573 | |
---|
3574 | d.addCallback(lambda res: |
---|
3575 | - self.POST(bar_url, t="check", output="JSON")) |
---|
3576 | + self.shouldSucceed("POST_FILEURL_check-4", http.OK, self.POST, |
---|
3577 | + bar_url, t="check", output="JSON")) |
---|
3578 | def _check_json(res): |
---|
3579 | data = simplejson.loads(res) |
---|
3580 | self.failUnless("storage-index" in data) |
---|
3581 | @@ -1772,7 +1926,8 @@ |
---|
3582 | |
---|
3583 | def test_POST_FILEURL_check_and_repair(self): |
---|
3584 | bar_url = self.public_url + "/foo/bar.txt" |
---|
3585 | - d = self.POST(bar_url, t="check", repair="true") |
---|
3586 | + d = self.shouldSucceed("POST_FILEURL_check_and_repair-1", http.OK, self.POST, |
---|
3587 | + bar_url, t="check", repair="true") |
---|
3588 | def _check(res): |
---|
3589 | self.failUnless("Healthy :" in res) |
---|
3590 | d.addCallback(_check) |
---|
3591 | @@ -1781,13 +1936,14 @@ |
---|
3592 | self.failUnlessEqual(statuscode, str(http.FOUND)) |
---|
3593 | self.failUnlessEqual(target, redir_url) |
---|
3594 | d.addCallback(lambda res: |
---|
3595 | - self.shouldRedirect2("test_POST_FILEURL_check_and_repair", |
---|
3596 | + self.shouldRedirect2("POST_FILEURL_check_and_repair-2", |
---|
3597 | _check2, |
---|
3598 | self.POST, bar_url, |
---|
3599 | t="check", repair="true", |
---|
3600 | when_done=redir_url)) |
---|
3601 | d.addCallback(lambda res: |
---|
3602 | - self.POST(bar_url, t="check", return_to=redir_url)) |
---|
3603 | + self.shouldSucceed("POST_FILEURL_check_and_repair-3", http.OK, self.POST, |
---|
3604 | + bar_url, t="check", return_to=redir_url)) |
---|
3605 | def _check3(res): |
---|
3606 | self.failUnless("Healthy :" in res) |
---|
3607 | self.failUnless("Return to file" in res) |
---|
3608 | @@ -1797,7 +1953,8 @@ |
---|
3609 | |
---|
3610 | def test_POST_DIRURL_check(self): |
---|
3611 | foo_url = self.public_url + "/foo/" |
---|
3612 | - d = self.POST(foo_url, t="check") |
---|
3613 | + d = self.shouldSucceed("POST_DIRURL_check-1", http.OK, self.POST, |
---|
3614 | + foo_url, t="check") |
---|
3615 | def _check(res): |
---|
3616 | self.failUnless("Healthy :" in res, res) |
---|
3617 | d.addCallback(_check) |
---|
3618 | @@ -1806,13 +1963,14 @@ |
---|
3619 | self.failUnlessEqual(statuscode, str(http.FOUND)) |
---|
3620 | self.failUnlessEqual(target, redir_url) |
---|
3621 | d.addCallback(lambda res: |
---|
3622 | - self.shouldRedirect2("test_POST_DIRURL_check", |
---|
3623 | + self.shouldRedirect2("POST_DIRURL_check-2", |
---|
3624 | _check2, |
---|
3625 | self.POST, foo_url, |
---|
3626 | t="check", |
---|
3627 | when_done=redir_url)) |
---|
3628 | d.addCallback(lambda res: |
---|
3629 | - self.POST(foo_url, t="check", return_to=redir_url)) |
---|
3630 | + self.shouldSucceed("POST_DIRURL_check-3", http.OK, self.POST, |
---|
3631 | + foo_url, t="check", return_to=redir_url)) |
---|
3632 | def _check3(res): |
---|
3633 | self.failUnless("Healthy :" in res, res) |
---|
3634 | self.failUnless("Return to file/directory" in res) |
---|
3635 | @@ -1820,7 +1978,8 @@ |
---|
3636 | d.addCallback(_check3) |
---|
3637 | |
---|
3638 | d.addCallback(lambda res: |
---|
3639 | - self.POST(foo_url, t="check", output="JSON")) |
---|
3640 | + self.shouldSucceed("POST_DIRURL_check-4", http.OK, self.POST, |
---|
3641 | + foo_url, t="check", output="JSON")) |
---|
3642 | def _check_json(res): |
---|
3643 | data = simplejson.loads(res) |
---|
3644 | self.failUnless("storage-index" in data) |
---|
3645 | @@ -1831,7 +1990,8 @@ |
---|
3646 | |
---|
3647 | def test_POST_DIRURL_check_and_repair(self): |
---|
3648 | foo_url = self.public_url + "/foo/" |
---|
3649 | - d = self.POST(foo_url, t="check", repair="true") |
---|
3650 | + d = self.shouldSucceed("POST_DIRURL_check_and_repair-1", http.OK, self.POST, |
---|
3651 | + foo_url, t="check", repair="true") |
---|
3652 | def _check(res): |
---|
3653 | self.failUnless("Healthy :" in res, res) |
---|
3654 | d.addCallback(_check) |
---|
3655 | @@ -1840,13 +2000,14 @@ |
---|
3656 | self.failUnlessEqual(statuscode, str(http.FOUND)) |
---|
3657 | self.failUnlessEqual(target, redir_url) |
---|
3658 | d.addCallback(lambda res: |
---|
3659 | - self.shouldRedirect2("test_POST_DIRURL_check_and_repair", |
---|
3660 | + self.shouldRedirect2("POST_DIRURL_check_and_repair-2", |
---|
3661 | _check2, |
---|
3662 | self.POST, foo_url, |
---|
3663 | t="check", repair="true", |
---|
3664 | when_done=redir_url)) |
---|
3665 | d.addCallback(lambda res: |
---|
3666 | - self.POST(foo_url, t="check", return_to=redir_url)) |
---|
3667 | + self.shouldSucceed("POST_DIRURL_check_and_repair-3", http.OK, self.POST, |
---|
3668 | + foo_url, t="check", return_to=redir_url)) |
---|
3669 | def _check3(res): |
---|
3670 | self.failUnless("Healthy :" in res) |
---|
3671 | self.failUnless("Return to file/directory" in res) |
---|
3672 | @@ -1857,7 +2018,7 @@ |
---|
3673 | def wait_for_operation(self, ignored, ophandle): |
---|
3674 | url = "/operations/" + ophandle |
---|
3675 | url += "?t=status&output=JSON" |
---|
3676 | - d = self.GET(url) |
---|
3677 | + d = self.shouldSucceedGET(url) |
---|
3678 | def _got(res): |
---|
3679 | data = simplejson.loads(res) |
---|
3680 | if not data["finished"]: |
---|
3681 | @@ -1873,7 +2034,7 @@ |
---|
3682 | url += "?t=status" |
---|
3683 | if output: |
---|
3684 | url += "&output=" + output |
---|
3685 | - d = self.GET(url) |
---|
3686 | + d = self.shouldSucceedGET(url) |
---|
3687 | def _got(res): |
---|
3688 | if output and output.lower() == "json": |
---|
3689 | return simplejson.loads(res) |
---|
3690 | @@ -1883,7 +2044,7 @@ |
---|
3691 | |
---|
3692 | def test_POST_DIRURL_deepcheck_no_ophandle(self): |
---|
3693 | d = self.shouldFail2(error.Error, |
---|
3694 | - "test_POST_DIRURL_deepcheck_no_ophandle", |
---|
3695 | + "POST_DIRURL_deepcheck_no_ophandle", |
---|
3696 | "400 Bad Request", |
---|
3697 | "slow operation requires ophandle=", |
---|
3698 | self.POST, self.public_url, t="start-deep-check") |
---|
3699 | @@ -1893,7 +2054,7 @@ |
---|
3700 | def _check_redirect(statuscode, target): |
---|
3701 | self.failUnlessEqual(statuscode, str(http.FOUND)) |
---|
3702 | self.failUnless(target.endswith("/operations/123")) |
---|
3703 | - d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect, |
---|
3704 | + d = self.shouldRedirect2("POST_DIRURL_deepcheck", _check_redirect, |
---|
3705 | self.POST, self.public_url, |
---|
3706 | t="start-deep-check", ophandle="123") |
---|
3707 | d.addCallback(self.wait_for_operation, "123") |
---|
3708 | @@ -1909,7 +2070,7 @@ |
---|
3709 | d.addCallback(_check_html) |
---|
3710 | |
---|
3711 | d.addCallback(lambda res: |
---|
3712 | - self.GET("/operations/123/")) |
---|
3713 | + self.shouldSucceedGET("/operations/123/")) |
---|
3714 | d.addCallback(_check_html) # should be the same as without the slash |
---|
3715 | |
---|
3716 | d.addCallback(lambda res: |
---|
3717 | @@ -1920,7 +2081,7 @@ |
---|
3718 | foo_si = self._foo_node.get_storage_index() |
---|
3719 | foo_si_s = base32.b2a(foo_si) |
---|
3720 | d.addCallback(lambda res: |
---|
3721 | - self.GET("/operations/123/%s?output=JSON" % foo_si_s)) |
---|
3722 | + self.shouldSucceedGET("/operations/123/%s?output=JSON" % foo_si_s)) |
---|
3723 | def _check_foo_json(res): |
---|
3724 | data = simplejson.loads(res) |
---|
3725 | self.failUnlessEqual(data["storage-index"], foo_si_s) |
---|
3726 | @@ -1929,8 +2090,9 @@ |
---|
3727 | return d |
---|
3728 | |
---|
3729 | def test_POST_DIRURL_deepcheck_and_repair(self): |
---|
3730 | - d = self.POST(self.public_url, t="start-deep-check", repair="true", |
---|
3731 | - ophandle="124", output="json", followRedirect=True) |
---|
3732 | + d = self.shouldSucceed("POST_DIRURL_deepcheck_and_repair", http.OK, self.POST, |
---|
3733 | + self.public_url, t="start-deep-check", repair="true", |
---|
3734 | + ophandle="124", output="json", followRedirect=True) |
---|
3735 | d.addCallback(self.wait_for_operation, "124") |
---|
3736 | def _check_json(data): |
---|
3737 | self.failUnlessEqual(data["finished"], True) |
---|
3738 | @@ -1971,45 +2133,47 @@ |
---|
3739 | return d |
---|
3740 | |
---|
3741 | def test_POST_mkdir(self): # return value? |
---|
3742 | - d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir") |
---|
3743 | + d = self.shouldSucceed("POST_mkdir", http.OK, self.POST, |
---|
3744 | + self.public_url + "/foo", t="mkdir", name="newdir") |
---|
3745 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
3746 | d.addCallback(self.failUnlessNodeKeysAre, []) |
---|
3747 | return d |
---|
3748 | |
---|
3749 | def test_POST_mkdir_initial_children(self): |
---|
3750 | - newkids, filecap1, ign, ign, ign = self._create_initial_children() |
---|
3751 | - d = self.POST2(self.public_url + |
---|
3752 | - "/foo?t=mkdir-with-children&name=newdir", |
---|
3753 | - simplejson.dumps(newkids)) |
---|
3754 | + (newkids, caps) = self._create_initial_children() |
---|
3755 | + d = self.shouldSucceed("POST_mkdir_initial_children", http.OK, self.POST2, |
---|
3756 | + self.public_url + "/foo?t=mkdir-with-children&name=newdir", |
---|
3757 | + simplejson.dumps(newkids)) |
---|
3758 | d.addCallback(lambda res: |
---|
3759 | self.failUnlessNodeHasChild(self._foo_node, u"newdir")) |
---|
3760 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
3761 | d.addCallback(self.failUnlessNodeKeysAre, newkids.keys()) |
---|
3762 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
3763 | - d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1) |
---|
3764 | + d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1']) |
---|
3765 | return d |
---|
3766 | |
---|
3767 | def test_POST_mkdir_immutable(self): |
---|
3768 | - (newkids, filecap1, immdircap) = self._create_immutable_children() |
---|
3769 | - d = self.POST2(self.public_url + |
---|
3770 | - "/foo?t=mkdir-immutable&name=newdir", |
---|
3771 | - simplejson.dumps(newkids)) |
---|
3772 | + (newkids, caps) = self._create_immutable_children() |
---|
3773 | + d = self.shouldSucceed("POST_mkdir_immutable", http.OK, self.POST2, |
---|
3774 | + self.public_url + "/foo?t=mkdir-immutable&name=newdir", |
---|
3775 | + simplejson.dumps(newkids)) |
---|
3776 | d.addCallback(lambda res: |
---|
3777 | self.failUnlessNodeHasChild(self._foo_node, u"newdir")) |
---|
3778 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
3779 | d.addCallback(self.failUnlessNodeKeysAre, newkids.keys()) |
---|
3780 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
3781 | - d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1) |
---|
3782 | + d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1']) |
---|
3783 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
3784 | - d.addCallback(self.failUnlessChildURIIs, u"dirchild-imm", immdircap) |
---|
3785 | + d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap']) |
---|
3786 | + d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
3787 | + d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap']) |
---|
3788 | return d |
---|
3789 | |
---|
3790 | def test_POST_mkdir_immutable_bad(self): |
---|
3791 | - (newkids, filecap1, filecap2, filecap3, |
---|
3792 | - dircap) = self._create_initial_children() |
---|
3793 | - d = self.shouldFail2(error.Error, "test_POST_mkdir_immutable_bad", |
---|
3794 | + (newkids, caps) = self._create_initial_children() |
---|
3795 | + d = self.shouldFail2(error.Error, "POST_mkdir_immutable_bad", |
---|
3796 | "400 Bad Request", |
---|
3797 | - "a mkdir-immutable operation was given a child that was not itself immutable", |
---|
3798 | + "needed to be immutable but was not", |
---|
3799 | self.POST2, |
---|
3800 | self.public_url + |
---|
3801 | "/foo?t=mkdir-immutable&name=newdir", |
---|
3802 | @@ -2017,7 +2181,8 @@ |
---|
3803 | return d |
---|
3804 | |
---|
3805 | def test_POST_mkdir_2(self): |
---|
3806 | - d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "") |
---|
3807 | + d = self.shouldSucceed("POST_mkdir_2", http.OK, self.POST, |
---|
3808 | + self.public_url + "/foo/newdir?t=mkdir", "") |
---|
3809 | d.addCallback(lambda res: |
---|
3810 | self.failUnlessNodeHasChild(self._foo_node, u"newdir")) |
---|
3811 | d.addCallback(lambda res: self._foo_node.get(u"newdir")) |
---|
3812 | @@ -2025,7 +2190,8 @@ |
---|
3813 | return d |
---|
3814 | |
---|
3815 | def test_POST_mkdirs_2(self): |
---|
3816 | - d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "") |
---|
3817 | + d = self.shouldSucceed("POST_mkdirs_2", http.OK, self.POST, |
---|
3818 | + self.public_url + "/foo/bardir/newdir?t=mkdir", "") |
---|
3819 | d.addCallback(lambda res: |
---|
3820 | self.failUnlessNodeHasChild(self._foo_node, u"bardir")) |
---|
3821 | d.addCallback(lambda res: self._foo_node.get(u"bardir")) |
---|
3822 | @@ -2034,7 +2200,8 @@ |
---|
3823 | return d |
---|
3824 | |
---|
3825 | def test_POST_mkdir_no_parentdir_noredirect(self): |
---|
3826 | - d = self.POST("/uri?t=mkdir") |
---|
3827 | + d = self.shouldSucceed("POST_mkdir_no_parentdir_noredirect", http.OK, self.POST, |
---|
3828 | + "/uri?t=mkdir") |
---|
3829 | def _after_mkdir(res): |
---|
3830 | uri.DirectoryURI.init_from_string(res) |
---|
3831 | d.addCallback(_after_mkdir) |
---|
3832 | @@ -2049,21 +2216,43 @@ |
---|
3833 | d.addCallback(_check_target) |
---|
3834 | return d |
---|
3835 | |
---|
3836 | + def _make_readonly(self, u): |
---|
3837 | + ro_uri = uri.from_string(u).get_readonly() |
---|
3838 | + if ro_uri is None: |
---|
3839 | + return None |
---|
3840 | + return ro_uri.to_string() |
---|
3841 | + |
---|
3842 | def _create_initial_children(self): |
---|
3843 | contents, n, filecap1 = self.makefile(12) |
---|
3844 | md1 = {"metakey1": "metavalue1"} |
---|
3845 | filecap2 = make_mutable_file_uri() |
---|
3846 | node3 = self.s.create_node_from_uri(make_mutable_file_uri()) |
---|
3847 | filecap3 = node3.get_readonly_uri() |
---|
3848 | + unknown_rwcap = "lafs://from_the_future" |
---|
3849 | + unknown_rocap = "ro.lafs://readonly_from_the_future" |
---|
3850 | + unknown_immcap = "imm.lafs://immutable_from_the_future" |
---|
3851 | node4 = self.s.create_node_from_uri(make_mutable_file_uri()) |
---|
3852 | dircap = DirectoryNode(node4, None, None).get_uri() |
---|
3853 | - newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1, |
---|
3854 | - "metadata": md1, }], |
---|
3855 | - u"child-mutable": ["filenode", {"rw_uri": filecap2}], |
---|
3856 | + newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1, |
---|
3857 | + "ro_uri": self._make_readonly(filecap1), |
---|
3858 | + "metadata": md1, }], |
---|
3859 | + u"child-mutable": ["filenode", {"rw_uri": filecap2, |
---|
3860 | + "ro_uri": self._make_readonly(filecap2)}], |
---|
3861 | u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}], |
---|
3862 | - u"dirchild": ["dirnode", {"rw_uri": dircap}], |
---|
3863 | + u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap, |
---|
3864 | + "ro_uri": unknown_rocap}], |
---|
3865 | + u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}], |
---|
3866 | + u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}], |
---|
3867 | + u"dirchild": ["dirnode", {"rw_uri": dircap, |
---|
3868 | + "ro_uri": self._make_readonly(dircap)}], |
---|
3869 | } |
---|
3870 | - return newkids, filecap1, filecap2, filecap3, dircap |
---|
3871 | + return newkids, {'filecap1': filecap1, |
---|
3872 | + 'filecap2': filecap2, |
---|
3873 | + 'filecap3': filecap3, |
---|
3874 | + 'unknown_rwcap': unknown_rwcap, |
---|
3875 | + 'unknown_rocap': unknown_rocap, |
---|
3876 | + 'unknown_immcap': unknown_immcap, |
---|
3877 | + 'dircap': dircap} |
---|
3878 | |
---|
3879 | def _create_immutable_children(self): |
---|
3880 | contents, n, filecap1 = self.makefile(12) |
---|
3881 | @@ -2071,31 +2260,46 @@ |
---|
3882 | tnode = create_chk_filenode("immutable directory contents\n"*10) |
---|
3883 | dnode = DirectoryNode(tnode, None, None) |
---|
3884 | assert not dnode.is_mutable() |
---|
3885 | + unknown_immcap = "imm.lafs://immutable_from_the_future" |
---|
3886 | immdircap = dnode.get_uri() |
---|
3887 | - newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1, |
---|
3888 | - "metadata": md1, }], |
---|
3889 | - u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}], |
---|
3890 | + newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1, |
---|
3891 | + "metadata": md1, }], |
---|
3892 | + u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}], |
---|
3893 | + u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}], |
---|
3894 | } |
---|
3895 | - return newkids, filecap1, immdircap |
---|
3896 | + return newkids, {'filecap1': filecap1, |
---|
3897 | + 'unknown_immcap': unknown_immcap, |
---|
3898 | + 'immdircap': immdircap} |
---|
3899 | |
---|
3900 | def test_POST_mkdir_no_parentdir_initial_children(self): |
---|
3901 | - (newkids, filecap1, filecap2, filecap3, |
---|
3902 | - dircap) = self._create_initial_children() |
---|
3903 | - d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids)) |
---|
3904 | + (newkids, caps) = self._create_initial_children() |
---|
3905 | + d = self.shouldSucceed("POST_mkdir_no_parentdir_initial_children", http.OK, self.POST2, |
---|
3906 | + "/uri?t=mkdir-with-children", simplejson.dumps(newkids)) |
---|
3907 | def _after_mkdir(res): |
---|
3908 | self.failUnless(res.startswith("URI:DIR"), res) |
---|
3909 | n = self.s.create_node_from_uri(res) |
---|
3910 | d2 = self.failUnlessNodeKeysAre(n, newkids.keys()) |
---|
3911 | d2.addCallback(lambda ign: |
---|
3912 | - self.failUnlessChildURIIs(n, u"child-imm", filecap1)) |
---|
3913 | + self.failUnlessROChildURIIs(n, u"child-imm", |
---|
3914 | + caps['filecap1'])) |
---|
3915 | + d2.addCallback(lambda ign: |
---|
3916 | + self.failUnlessRWChildURIIs(n, u"child-mutable", |
---|
3917 | + caps['filecap2'])) |
---|
3918 | + d2.addCallback(lambda ign: |
---|
3919 | + self.failUnlessROChildURIIs(n, u"child-mutable-ro", |
---|
3920 | + caps['filecap3'])) |
---|
3921 | + d2.addCallback(lambda ign: |
---|
3922 | + self.failUnlessRWChildURIIs(n, u"unknownchild-rw", |
---|
3923 | + caps['unknown_rwcap'])) |
---|
3924 | d2.addCallback(lambda ign: |
---|
3925 | - self.failUnlessChildURIIs(n, u"child-mutable", |
---|
3926 | - filecap2)) |
---|
3927 | + self.failUnlessROChildURIIs(n, u"unknownchild-ro", |
---|
3928 | + caps['unknown_rocap'])) |
---|
3929 | d2.addCallback(lambda ign: |
---|
3930 | - self.failUnlessChildURIIs(n, u"child-mutable-ro", |
---|
3931 | - filecap3)) |
---|
3932 | + self.failUnlessROChildURIIs(n, u"unknownchild-imm", |
---|
3933 | + caps['unknown_immcap'])) |
---|
3934 | d2.addCallback(lambda ign: |
---|
3935 | - self.failUnlessChildURIIs(n, u"dirchild", dircap)) |
---|
3936 | + self.failUnlessRWChildURIIs(n, u"dirchild", |
---|
3937 | + caps['dircap'])) |
---|
3938 | return d2 |
---|
3939 | d.addCallback(_after_mkdir) |
---|
3940 | return d |
---|
3941 | @@ -2103,8 +2307,7 @@ |
---|
3942 | def test_POST_mkdir_no_parentdir_unexpected_children(self): |
---|
3943 | # the regular /uri?t=mkdir operation is specified to ignore its body. |
---|
3944 | # Only t=mkdir-with-children pays attention to it. |
---|
3945 | - (newkids, filecap1, filecap2, filecap3, |
---|
3946 | - dircap) = self._create_initial_children() |
---|
3947 | + (newkids, caps) = self._create_initial_children() |
---|
3948 | d = self.shouldHTTPError("POST t=mkdir unexpected children", |
---|
3949 | 400, "Bad Request", |
---|
3950 | "t=mkdir does not accept children=, " |
---|
3951 | @@ -2121,28 +2324,32 @@ |
---|
3952 | return d |
---|
3953 | |
---|
3954 | def test_POST_mkdir_no_parentdir_immutable(self): |
---|
3955 | - (newkids, filecap1, immdircap) = self._create_immutable_children() |
---|
3956 | - d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids)) |
---|
3957 | + (newkids, caps) = self._create_immutable_children() |
---|
3958 | + d = self.shouldSucceed("POST_mkdir_no_parentdir_immutable", http.OK, self.POST2, |
---|
3959 | + "/uri?t=mkdir-immutable", simplejson.dumps(newkids)) |
---|
3960 | def _after_mkdir(res): |
---|
3961 | self.failUnless(res.startswith("URI:DIR"), res) |
---|
3962 | n = self.s.create_node_from_uri(res) |
---|
3963 | d2 = self.failUnlessNodeKeysAre(n, newkids.keys()) |
---|
3964 | d2.addCallback(lambda ign: |
---|
3965 | - self.failUnlessChildURIIs(n, u"child-imm", filecap1)) |
---|
3966 | + self.failUnlessROChildURIIs(n, u"child-imm", |
---|
3967 | + caps['filecap1'])) |
---|
3968 | d2.addCallback(lambda ign: |
---|
3969 | - self.failUnlessChildURIIs(n, u"dirchild-imm", |
---|
3970 | - immdircap)) |
---|
3971 | + self.failUnlessROChildURIIs(n, u"unknownchild-imm", |
---|
3972 | + caps['unknown_immcap'])) |
---|
3973 | + d2.addCallback(lambda ign: |
---|
3974 | + self.failUnlessROChildURIIs(n, u"dirchild-imm", |
---|
3975 | + caps['immdircap'])) |
---|
3976 | return d2 |
---|
3977 | d.addCallback(_after_mkdir) |
---|
3978 | return d |
---|
3979 | |
---|
3980 | def test_POST_mkdir_no_parentdir_immutable_bad(self): |
---|
3981 | - (newkids, filecap1, filecap2, filecap3, |
---|
3982 | - dircap) = self._create_initial_children() |
---|
3983 | + (newkids, caps) = self._create_initial_children() |
---|
3984 | d = self.shouldFail2(error.Error, |
---|
3985 | - "test_POST_mkdir_no_parentdir_immutable_bad", |
---|
3986 | + "POST_mkdir_no_parentdir_immutable_bad", |
---|
3987 | "400 Bad Request", |
---|
3988 | - "a mkdir-immutable operation was given a child that was not itself immutable", |
---|
3989 | + "needed to be immutable but was not", |
---|
3990 | self.POST2, |
---|
3991 | "/uri?t=mkdir-immutable", |
---|
3992 | simplejson.dumps(newkids)) |
---|
3993 | @@ -2150,9 +2357,14 @@ |
---|
3994 | |
---|
3995 | def test_welcome_page_mkdir_button(self): |
---|
3996 | # Fetch the welcome page. |
---|
3997 | - d = self.GET("/") |
---|
3998 | + d = self.shouldSucceedGET("/") |
---|
3999 | def _after_get_welcome_page(res): |
---|
4000 | - MKDIR_BUTTON_RE=re.compile('<form action="([^"]*)" method="post".*?<input type="hidden" name="t" value="([^"]*)" /><input type="hidden" name="([^"]*)" value="([^"]*)" /><input type="submit" value="Create a directory" />', re.I) |
---|
4001 | + MKDIR_BUTTON_RE = re.compile( |
---|
4002 | + '<form action="([^"]*)" method="post".*?' |
---|
4003 | + '<input type="hidden" name="t" value="([^"]*)" />' |
---|
4004 | + '<input type="hidden" name="([^"]*)" value="([^"]*)" />' |
---|
4005 | + '<input type="submit" value="Create a directory" />', |
---|
4006 | + re.I) |
---|
4007 | mo = MKDIR_BUTTON_RE.search(res) |
---|
4008 | formaction = mo.group(1) |
---|
4009 | formt = mo.group(2) |
---|
4010 | @@ -2168,7 +2380,8 @@ |
---|
4011 | return d |
---|
4012 | |
---|
4013 | def test_POST_mkdir_replace(self): # return value? |
---|
4014 | - d = self.POST(self.public_url + "/foo", t="mkdir", name="sub") |
---|
4015 | + d = self.shouldSucceed("POST_mkdir_replace", http.OK, self.POST, |
---|
4016 | + self.public_url + "/foo", t="mkdir", name="sub") |
---|
4017 | d.addCallback(lambda res: self._foo_node.get(u"sub")) |
---|
4018 | d.addCallback(self.failUnlessNodeKeysAre, []) |
---|
4019 | return d |
---|
4020 | @@ -2250,9 +2463,9 @@ |
---|
4021 | |
---|
4022 | d = client.getPage(url, method="POST", postdata=reqbody) |
---|
4023 | def _then(res): |
---|
4024 | - self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1") |
---|
4025 | - self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2") |
---|
4026 | - self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3") |
---|
4027 | + self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1") |
---|
4028 | + self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2") |
---|
4029 | + self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3") |
---|
4030 | |
---|
4031 | d.addCallback(_then) |
---|
4032 | d.addErrback(self.dump_error) |
---|
4033 | @@ -2263,8 +2476,9 @@ |
---|
4034 | |
---|
4035 | def test_POST_put_uri(self): |
---|
4036 | contents, n, newuri = self.makefile(8) |
---|
4037 | - d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri) |
---|
4038 | - d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt") |
---|
4039 | + d = self.shouldSucceed("POST_put_uri", http.OK, self.POST, |
---|
4040 | + self.public_url + "/foo", t="uri", name="new.txt", uri=newuri) |
---|
4041 | + d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt") |
---|
4042 | d.addCallback(lambda res: |
---|
4043 | self.failUnlessChildContentsAre(self._foo_node, u"new.txt", |
---|
4044 | contents)) |
---|
4045 | @@ -2272,8 +2486,9 @@ |
---|
4046 | |
---|
4047 | def test_POST_put_uri_replace(self): |
---|
4048 | contents, n, newuri = self.makefile(8) |
---|
4049 | - d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri) |
---|
4050 | - d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt") |
---|
4051 | + d = self.shouldSucceed("POST_put_uri_replace", http.OK, self.POST, |
---|
4052 | + self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri) |
---|
4053 | + d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt") |
---|
4054 | d.addCallback(lambda res: |
---|
4055 | self.failUnlessChildContentsAre(self._foo_node, u"bar.txt", |
---|
4056 | contents)) |
---|
4057 | @@ -2288,7 +2503,7 @@ |
---|
4058 | "409 Conflict", |
---|
4059 | "There was already a child by that name, and you asked me " |
---|
4060 | "to not replace it") |
---|
4061 | - d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt")) |
---|
4062 | + d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/bar.txt")) |
---|
4063 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
4064 | return d |
---|
4065 | |
---|
4066 | @@ -2301,12 +2516,13 @@ |
---|
4067 | "409 Conflict", |
---|
4068 | "There was already a child by that name, and you asked me " |
---|
4069 | "to not replace it") |
---|
4070 | - d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt")) |
---|
4071 | + d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/bar.txt")) |
---|
4072 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
4073 | return d |
---|
4074 | |
---|
4075 | def test_POST_delete(self): |
---|
4076 | - d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt") |
---|
4077 | + d = self.shouldSucceed("POST_delete", http.OK, self.POST, |
---|
4078 | + self.public_url + "/foo", t="delete", name="bar.txt") |
---|
4079 | d.addCallback(lambda res: self._foo_node.list()) |
---|
4080 | def _check(children): |
---|
4081 | self.failIf(u"bar.txt" in children) |
---|
4082 | @@ -2314,40 +2530,43 @@ |
---|
4083 | return d |
---|
4084 | |
---|
4085 | def test_POST_rename_file(self): |
---|
4086 | - d = self.POST(self.public_url + "/foo", t="rename", |
---|
4087 | - from_name="bar.txt", to_name='wibble.txt') |
---|
4088 | + d = self.shouldSucceed("POST_rename_file", http.OK, self.POST, |
---|
4089 | + self.public_url + "/foo", t="rename", |
---|
4090 | + from_name="bar.txt", to_name='wibble.txt') |
---|
4091 | d.addCallback(lambda res: |
---|
4092 | self.failIfNodeHasChild(self._foo_node, u"bar.txt")) |
---|
4093 | d.addCallback(lambda res: |
---|
4094 | self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt")) |
---|
4095 | - d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt")) |
---|
4096 | + d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/wibble.txt")) |
---|
4097 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
4098 | - d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json")) |
---|
4099 | + d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/wibble.txt?t=json")) |
---|
4100 | d.addCallback(self.failUnlessIsBarJSON) |
---|
4101 | return d |
---|
4102 | |
---|
4103 | def test_POST_rename_file_redundant(self): |
---|
4104 | - d = self.POST(self.public_url + "/foo", t="rename", |
---|
4105 | - from_name="bar.txt", to_name='bar.txt') |
---|
4106 | + d = self.shouldSucceed("POST_rename_file_redundant", http.OK, self.POST, |
---|
4107 | + self.public_url + "/foo", t="rename", |
---|
4108 | + from_name="bar.txt", to_name='bar.txt') |
---|
4109 | d.addCallback(lambda res: |
---|
4110 | self.failUnlessNodeHasChild(self._foo_node, u"bar.txt")) |
---|
4111 | - d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt")) |
---|
4112 | + d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/bar.txt")) |
---|
4113 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
4114 | - d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json")) |
---|
4115 | + d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/bar.txt?t=json")) |
---|
4116 | d.addCallback(self.failUnlessIsBarJSON) |
---|
4117 | return d |
---|
4118 | |
---|
4119 | def test_POST_rename_file_replace(self): |
---|
4120 | # rename a file and replace a directory with it |
---|
4121 | - d = self.POST(self.public_url + "/foo", t="rename", |
---|
4122 | - from_name="bar.txt", to_name='empty') |
---|
4123 | + d = self.shouldSucceed("POST_rename_file_replace", http.OK, self.POST, |
---|
4124 | + self.public_url + "/foo", t="rename", |
---|
4125 | + from_name="bar.txt", to_name='empty') |
---|
4126 | d.addCallback(lambda res: |
---|
4127 | self.failIfNodeHasChild(self._foo_node, u"bar.txt")) |
---|
4128 | d.addCallback(lambda res: |
---|
4129 | self.failUnlessNodeHasChild(self._foo_node, u"empty")) |
---|
4130 | - d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty")) |
---|
4131 | + d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/empty")) |
---|
4132 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
4133 | - d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json")) |
---|
4134 | + d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/empty?t=json")) |
---|
4135 | d.addCallback(self.failUnlessIsBarJSON) |
---|
4136 | return d |
---|
4137 | |
---|
4138 | @@ -2360,7 +2579,7 @@ |
---|
4139 | "409 Conflict", |
---|
4140 | "There was already a child by that name, and you asked me " |
---|
4141 | "to not replace it") |
---|
4142 | - d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json")) |
---|
4143 | + d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/empty?t=json")) |
---|
4144 | d.addCallback(self.failUnlessIsEmptyJSON) |
---|
4145 | return d |
---|
4146 | |
---|
4147 | @@ -2373,7 +2592,7 @@ |
---|
4148 | "409 Conflict", |
---|
4149 | "There was already a child by that name, and you asked me " |
---|
4150 | "to not replace it") |
---|
4151 | - d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json")) |
---|
4152 | + d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/foo/empty?t=json")) |
---|
4153 | d.addCallback(self.failUnlessIsEmptyJSON) |
---|
4154 | return d |
---|
4155 | |
---|
4156 | @@ -2386,7 +2605,7 @@ |
---|
4157 | d = self.POST(self.public_url + "/foo", t="rename", |
---|
4158 | from_name="bar.txt", to_name='kirk/spock.txt') |
---|
4159 | d.addBoth(self.shouldFail, error.Error, |
---|
4160 | - "test_POST_rename_file_slash_fail", |
---|
4161 | + "POST_rename_file_slash_fail", |
---|
4162 | "400 Bad Request", |
---|
4163 | "to_name= may not contain a slash", |
---|
4164 | ) |
---|
4165 | @@ -2395,13 +2614,14 @@ |
---|
4166 | return d |
---|
4167 | |
---|
4168 | def test_POST_rename_dir(self): |
---|
4169 | - d = self.POST(self.public_url, t="rename", |
---|
4170 | - from_name="foo", to_name='plunk') |
---|
4171 | + d = self.shouldSucceed("POST_rename_dir", http.OK, self.POST, |
---|
4172 | + self.public_url, t="rename", |
---|
4173 | + from_name="foo", to_name='plunk') |
---|
4174 | d.addCallback(lambda res: |
---|
4175 | self.failIfNodeHasChild(self.public_root, u"foo")) |
---|
4176 | d.addCallback(lambda res: |
---|
4177 | self.failUnlessNodeHasChild(self.public_root, u"plunk")) |
---|
4178 | - d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json")) |
---|
4179 | + d.addCallback(lambda res: self.shouldSucceedGET(self.public_url + "/plunk?t=json")) |
---|
4180 | d.addCallback(self.failUnlessIsFooJSON) |
---|
4181 | return d |
---|
4182 | |
---|
4183 | @@ -2436,24 +2656,24 @@ |
---|
4184 | d.addCallback(lambda res: self.GET(base+"&t=json")) |
---|
4185 | d.addBoth(self.shouldRedirect, targetbase+"?t=json") |
---|
4186 | d.addCallback(self.log, "about to get file by uri") |
---|
4187 | - d.addCallback(lambda res: self.GET(base, followRedirect=True)) |
---|
4188 | + d.addCallback(lambda res: self.shouldSucceedGET(base, followRedirect=True)) |
---|
4189 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
4190 | d.addCallback(self.log, "got file by uri, about to get dir by uri") |
---|
4191 | - d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri, |
---|
4192 | - followRedirect=True)) |
---|
4193 | + d.addCallback(lambda res: self.shouldSucceedGET("/uri?uri=%s&t=json" % self._foo_uri, |
---|
4194 | + followRedirect=True)) |
---|
4195 | d.addCallback(self.failUnlessIsFooJSON) |
---|
4196 | d.addCallback(self.log, "got dir by uri") |
---|
4197 | |
---|
4198 | return d |
---|
4199 | |
---|
4200 | def test_GET_URI_form_bad(self): |
---|
4201 | - d = self.shouldFail2(error.Error, "test_GET_URI_form_bad", |
---|
4202 | + d = self.shouldFail2(error.Error, "GET_URI_form_bad", |
---|
4203 | "400 Bad Request", "GET /uri requires uri=", |
---|
4204 | self.GET, "/uri") |
---|
4205 | return d |
---|
4206 | |
---|
4207 | def test_GET_rename_form(self): |
---|
4208 | - d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt", |
---|
4209 | + d = self.shouldSucceedGET(self.public_url + "/foo?t=rename-form&name=bar.txt", |
---|
4210 | followRedirect=True) |
---|
4211 | def _check(res): |
---|
4212 | self.failUnless('name="when_done" value="."' in res, res) |
---|
4213 | @@ -2468,23 +2688,23 @@ |
---|
4214 | |
---|
4215 | def test_GET_URI_URL(self): |
---|
4216 | base = "/uri/%s" % self._bar_txt_uri |
---|
4217 | - d = self.GET(base) |
---|
4218 | + d = self.shouldSucceedGET(base) |
---|
4219 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
4220 | - d.addCallback(lambda res: self.GET(base+"?filename=bar.txt")) |
---|
4221 | + d.addCallback(lambda res: self.shouldSucceedGET(base+"?filename=bar.txt")) |
---|
4222 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
4223 | - d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true")) |
---|
4224 | + d.addCallback(lambda res: self.shouldSucceedGET(base+"?filename=bar.txt&save=true")) |
---|
4225 | d.addCallback(self.failUnlessIsBarDotTxt) |
---|
4226 | return d |
---|
4227 | |
---|
4228 | def test_GET_URI_URL_dir(self): |
---|
4229 | base = "/uri/%s?t=json" % self._foo_uri |
---|
4230 | - d = self.GET(base) |
---|
4231 | + d = self.shouldSucceedGET(base) |
---|
4232 | d.addCallback(self.failUnlessIsFooJSON) |
---|
4233 | return d |
---|
4234 | |
---|
4235 | def test_GET_URI_URL_missing(self): |
---|
4236 | base = "/uri/%s" % self._bad_file_uri |
---|
4237 | - d = self.shouldHTTPError("test_GET_URI_URL_missing", |
---|
4238 | + d = self.shouldHTTPError("GET_URI_URL_missing", |
---|
4239 | http.GONE, None, "NotEnoughSharesError", |
---|
4240 | self.GET, base) |
---|
4241 | # TODO: how can we exercise both sides of WebDownloadTarget.fail |
---|
4242 | @@ -2502,9 +2722,9 @@ |
---|
4243 | d.addCallback(lambda res: |
---|
4244 | self.failUnlessEqual(res.strip(), new_uri)) |
---|
4245 | d.addCallback(lambda res: |
---|
4246 | - self.failUnlessChildURIIs(self.public_root, |
---|
4247 | - u"foo", |
---|
4248 | - new_uri)) |
---|
4249 | + self.failUnlessRWChildURIIs(self.public_root, |
---|
4250 | + u"foo", |
---|
4251 | + new_uri)) |
---|
4252 | return d |
---|
4253 | d.addCallback(_made_dir) |
---|
4254 | return d |
---|
4255 | @@ -2515,32 +2735,33 @@ |
---|
4256 | new_uri = dn.get_uri() |
---|
4257 | # replace /foo with a new (empty) directory, but ask that |
---|
4258 | # replace=false, so it should fail |
---|
4259 | - d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace", |
---|
4260 | + d = self.shouldFail2(error.Error, "PUT_DIRURL_uri_noreplace", |
---|
4261 | "409 Conflict", "There was already a child by that name, and you asked me to not replace it", |
---|
4262 | self.PUT, |
---|
4263 | self.public_url + "/foo?t=uri&replace=false", |
---|
4264 | new_uri) |
---|
4265 | d.addCallback(lambda res: |
---|
4266 | - self.failUnlessChildURIIs(self.public_root, |
---|
4267 | - u"foo", |
---|
4268 | - self._foo_uri)) |
---|
4269 | + self.failUnlessRWChildURIIs(self.public_root, |
---|
4270 | + u"foo", |
---|
4271 | + self._foo_uri)) |
---|
4272 | return d |
---|
4273 | d.addCallback(_made_dir) |
---|
4274 | return d |
---|
4275 | |
---|
4276 | def test_PUT_DIRURL_bad_t(self): |
---|
4277 | - d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t", |
---|
4278 | + d = self.shouldFail2(error.Error, "PUT_DIRURL_bad_t", |
---|
4279 | "400 Bad Request", "PUT to a directory", |
---|
4280 | self.PUT, self.public_url + "/foo?t=BOGUS", "") |
---|
4281 | d.addCallback(lambda res: |
---|
4282 | - self.failUnlessChildURIIs(self.public_root, |
---|
4283 | - u"foo", |
---|
4284 | - self._foo_uri)) |
---|
4285 | + self.failUnlessRWChildURIIs(self.public_root, |
---|
4286 | + u"foo", |
---|
4287 | + self._foo_uri)) |
---|
4288 | return d |
---|
4289 | |
---|
4290 | def test_PUT_NEWFILEURL_uri(self): |
---|
4291 | contents, n, new_uri = self.makefile(8) |
---|
4292 | - d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri) |
---|
4293 | + d = self.shouldSucceed("PUT_NEWFILEURL_uri", http.OK, self.PUT, |
---|
4294 | + self.public_url + "/foo/new.txt?t=uri", new_uri) |
---|
4295 | d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri)) |
---|
4296 | d.addCallback(lambda res: |
---|
4297 | self.failUnlessChildContentsAre(self._foo_node, u"new.txt", |
---|
4298 | @@ -2567,13 +2788,14 @@ |
---|
4299 | |
---|
4300 | def test_PUT_NEWFILE_URI(self): |
---|
4301 | file_contents = "New file contents here\n" |
---|
4302 | - d = self.PUT("/uri", file_contents) |
---|
4303 | + d = self.shouldSucceed("PUT_NEWFILE_URI", http.OK, self.PUT, |
---|
4304 | + "/uri", file_contents) |
---|
4305 | def _check(uri): |
---|
4306 | assert isinstance(uri, str), uri |
---|
4307 | self.failUnless(uri in FakeCHKFileNode.all_contents) |
---|
4308 | self.failUnlessEqual(FakeCHKFileNode.all_contents[uri], |
---|
4309 | file_contents) |
---|
4310 | - return self.GET("/uri/%s" % uri) |
---|
4311 | + return self.shouldSucceedGET("/uri/%s" % uri) |
---|
4312 | d.addCallback(_check) |
---|
4313 | def _check2(res): |
---|
4314 | self.failUnlessEqual(res, file_contents) |
---|
4315 | @@ -2582,13 +2804,14 @@ |
---|
4316 | |
---|
4317 | def test_PUT_NEWFILE_URI_not_mutable(self): |
---|
4318 | file_contents = "New file contents here\n" |
---|
4319 | - d = self.PUT("/uri?mutable=false", file_contents) |
---|
4320 | + d = self.shouldSucceed("PUT_NEWFILE_URI_not_mutable", http.OK, self.PUT, |
---|
4321 | + "/uri?mutable=false", file_contents) |
---|
4322 | def _check(uri): |
---|
4323 | assert isinstance(uri, str), uri |
---|
4324 | self.failUnless(uri in FakeCHKFileNode.all_contents) |
---|
4325 | self.failUnlessEqual(FakeCHKFileNode.all_contents[uri], |
---|
4326 | file_contents) |
---|
4327 | - return self.GET("/uri/%s" % uri) |
---|
4328 | + return self.shouldSucceedGET("/uri/%s" % uri) |
---|
4329 | d.addCallback(_check) |
---|
4330 | def _check2(res): |
---|
4331 | self.failUnlessEqual(res, file_contents) |
---|
4332 | @@ -2605,7 +2828,8 @@ |
---|
4333 | |
---|
4334 | def test_PUT_NEWFILE_URI_mutable(self): |
---|
4335 | file_contents = "New file contents here\n" |
---|
4336 | - d = self.PUT("/uri?mutable=true", file_contents) |
---|
4337 | + d = self.shouldSucceed("PUT_NEWFILE_URI_mutable", http.OK, self.PUT, |
---|
4338 | + "/uri?mutable=true", file_contents) |
---|
4339 | def _check1(filecap): |
---|
4340 | filecap = filecap.strip() |
---|
4341 | self.failUnless(filecap.startswith("URI:SSK:"), filecap) |
---|
4342 | @@ -2617,7 +2841,7 @@ |
---|
4343 | d.addCallback(_check1) |
---|
4344 | def _check2(data): |
---|
4345 | self.failUnlessEqual(data, file_contents) |
---|
4346 | - return self.GET("/uri/%s" % urllib.quote(self.filecap)) |
---|
4347 | + return self.shouldSucceedGET("/uri/%s" % urllib.quote(self.filecap)) |
---|
4348 | d.addCallback(_check2) |
---|
4349 | def _check3(res): |
---|
4350 | self.failUnlessEqual(res, file_contents) |
---|
4351 | @@ -2625,19 +2849,21 @@ |
---|
4352 | return d |
---|
4353 | |
---|
4354 | def test_PUT_mkdir(self): |
---|
4355 | - d = self.PUT("/uri?t=mkdir", "") |
---|
4356 | + d = self.shouldSucceed("PUT_mkdir", http.OK, self.PUT, |
---|
4357 | + "/uri?t=mkdir", "") |
---|
4358 | def _check(uri): |
---|
4359 | n = self.s.create_node_from_uri(uri.strip()) |
---|
4360 | d2 = self.failUnlessNodeKeysAre(n, []) |
---|
4361 | d2.addCallback(lambda res: |
---|
4362 | - self.GET("/uri/%s?t=json" % uri)) |
---|
4363 | + self.shouldSucceedGET("/uri/%s?t=json" % uri)) |
---|
4364 | return d2 |
---|
4365 | d.addCallback(_check) |
---|
4366 | d.addCallback(self.failUnlessIsEmptyJSON) |
---|
4367 | return d |
---|
4368 | |
---|
4369 | def test_POST_check(self): |
---|
4370 | - d = self.POST(self.public_url + "/foo", t="check", name="bar.txt") |
---|
4371 | + d = self.shouldSucceed("POST_check", http.OK, self.POST, |
---|
4372 | + self.public_url + "/foo", t="check", name="bar.txt") |
---|
4373 | def _done(res): |
---|
4374 | # this returns a string form of the results, which are probably |
---|
4375 | # None since we're using fake filenodes. |
---|
4376 | @@ -2650,7 +2876,7 @@ |
---|
4377 | |
---|
4378 | def test_bad_method(self): |
---|
4379 | url = self.webish_url + self.public_url + "/foo/bar.txt" |
---|
4380 | - d = self.shouldHTTPError("test_bad_method", |
---|
4381 | + d = self.shouldHTTPError("bad_method", |
---|
4382 | 501, "Not Implemented", |
---|
4383 | "I don't know how to treat a BOGUS request.", |
---|
4384 | client.getPage, url, method="BOGUS") |
---|
4385 | @@ -2658,28 +2884,30 @@ |
---|
4386 | |
---|
4387 | def test_short_url(self): |
---|
4388 | url = self.webish_url + "/uri" |
---|
4389 | - d = self.shouldHTTPError("test_short_url", 501, "Not Implemented", |
---|
4390 | + d = self.shouldHTTPError("short_url", 501, "Not Implemented", |
---|
4391 | "I don't know how to treat a DELETE request.", |
---|
4392 | client.getPage, url, method="DELETE") |
---|
4393 | return d |
---|
4394 | |
---|
4395 | def test_ophandle_bad(self): |
---|
4396 | url = self.webish_url + "/operations/bogus?t=status" |
---|
4397 | - d = self.shouldHTTPError("test_ophandle_bad", 404, "404 Not Found", |
---|
4398 | + d = self.shouldHTTPError("ophandle_bad", 404, "404 Not Found", |
---|
4399 | "unknown/expired handle 'bogus'", |
---|
4400 | client.getPage, url) |
---|
4401 | return d |
---|
4402 | |
---|
4403 | def test_ophandle_cancel(self): |
---|
4404 | - d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128", |
---|
4405 | - followRedirect=True) |
---|
4406 | + d = self.shouldSucceed("ophandle_cancel-1", http.OK, self.POST, |
---|
4407 | + self.public_url + "/foo/?t=start-manifest&ophandle=128", |
---|
4408 | + followRedirect=True) |
---|
4409 | d.addCallback(lambda ignored: |
---|
4410 | - self.GET("/operations/128?t=status&output=JSON")) |
---|
4411 | + self.shouldSucceedGET("/operations/128?t=status&output=JSON")) |
---|
4412 | def _check1(res): |
---|
4413 | data = simplejson.loads(res) |
---|
4414 | self.failUnless("finished" in data, res) |
---|
4415 | monitor = self.ws.root.child_operations.handles["128"][0] |
---|
4416 | - d = self.POST("/operations/128?t=cancel&output=JSON") |
---|
4417 | + d = self.shouldSucceed("ophandle_cancel-2", http.OK, self.POST, |
---|
4418 | + "/operations/128?t=cancel&output=JSON") |
---|
4419 | def _check2(res): |
---|
4420 | data = simplejson.loads(res) |
---|
4421 | self.failUnless("finished" in data, res) |
---|
4422 | @@ -2689,7 +2917,7 @@ |
---|
4423 | return d |
---|
4424 | d.addCallback(_check1) |
---|
4425 | d.addCallback(lambda ignored: |
---|
4426 | - self.shouldHTTPError("test_ophandle_cancel", |
---|
4427 | + self.shouldHTTPError("ophandle_cancel", |
---|
4428 | 404, "404 Not Found", |
---|
4429 | "unknown/expired handle '128'", |
---|
4430 | self.GET, |
---|
4431 | @@ -2700,7 +2928,7 @@ |
---|
4432 | d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60", |
---|
4433 | followRedirect=True) |
---|
4434 | d.addCallback(lambda ignored: |
---|
4435 | - self.GET("/operations/129?t=status&output=JSON&retain-for=0")) |
---|
4436 | + self.shouldSucceedGET("/operations/129?t=status&output=JSON&retain-for=0")) |
---|
4437 | def _check1(res): |
---|
4438 | data = simplejson.loads(res) |
---|
4439 | self.failUnless("finished" in data, res) |
---|
4440 | @@ -2708,7 +2936,7 @@ |
---|
4441 | # the retain-for=0 will cause the handle to be expired very soon |
---|
4442 | d.addCallback(self.stall, 2.0) |
---|
4443 | d.addCallback(lambda ignored: |
---|
4444 | - self.shouldHTTPError("test_ophandle_retainfor", |
---|
4445 | + self.shouldHTTPError("ophandle_retainfor", |
---|
4446 | 404, "404 Not Found", |
---|
4447 | "unknown/expired handle '129'", |
---|
4448 | self.GET, |
---|
4449 | @@ -2716,14 +2944,15 @@ |
---|
4450 | return d |
---|
4451 | |
---|
4452 | def test_ophandle_release_after_complete(self): |
---|
4453 | - d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130", |
---|
4454 | - followRedirect=True) |
---|
4455 | + d = self.shouldSucceed("ophandle_release_after_complete", http.OK, self.POST, |
---|
4456 | + self.public_url + "/foo/?t=start-manifest&ophandle=130", |
---|
4457 | + followRedirect=True) |
---|
4458 | d.addCallback(self.wait_for_operation, "130") |
---|
4459 | d.addCallback(lambda ignored: |
---|
4460 | - self.GET("/operations/130?t=status&output=JSON&release-after-complete=true")) |
---|
4461 | + self.shouldSucceedGET("/operations/130?t=status&output=JSON&release-after-complete=true")) |
---|
4462 | # the release-after-complete=true will cause the handle to be expired |
---|
4463 | d.addCallback(lambda ignored: |
---|
4464 | - self.shouldHTTPError("test_ophandle_release_after_complete", |
---|
4465 | + self.shouldHTTPError("ophandle_release_after_complete", |
---|
4466 | 404, "404 Not Found", |
---|
4467 | "unknown/expired handle '130'", |
---|
4468 | self.GET, |
---|
4469 | @@ -2731,7 +2960,8 @@ |
---|
4470 | return d |
---|
4471 | |
---|
4472 | def test_incident(self): |
---|
4473 | - d = self.POST("/report_incident", details="eek") |
---|
4474 | + d = self.shouldSucceed("incident", http.OK, self.POST, |
---|
4475 | + "/report_incident", details="eek") |
---|
4476 | def _done(res): |
---|
4477 | self.failUnless("Thank you for your report!" in res, res) |
---|
4478 | d.addCallback(_done) |
---|
4479 | @@ -2744,7 +2974,7 @@ |
---|
4480 | f.write("hello") |
---|
4481 | f.close() |
---|
4482 | |
---|
4483 | - d = self.GET("/static/subdir/hello.txt") |
---|
4484 | + d = self.shouldSucceedGET("/static/subdir/hello.txt") |
---|
4485 | def _check(res): |
---|
4486 | self.failUnlessEqual(res, "hello") |
---|
4487 | d.addCallback(_check) |
---|
4488 | @@ -2757,7 +2987,7 @@ |
---|
4489 | self.failUnlessEqual(common.parse_replace_arg("false"), False) |
---|
4490 | self.failUnlessEqual(common.parse_replace_arg("only-files"), |
---|
4491 | "only-files") |
---|
4492 | - self.shouldFail(AssertionError, "test_parse_replace_arg", "", |
---|
4493 | + self.shouldFail(AssertionError, "parse_replace_arg", "", |
---|
4494 | common.parse_replace_arg, "only_fles") |
---|
4495 | |
---|
4496 | def test_abbreviate_time(self): |
---|
4497 | @@ -3062,71 +3292,246 @@ |
---|
4498 | d.addErrback(self.explain_web_error) |
---|
4499 | return d |
---|
4500 | |
---|
4501 | - def test_unknown(self): |
---|
4502 | + def test_unknown(self, immutable=False): |
---|
4503 | self.basedir = "web/Grid/unknown" |
---|
4504 | + if immutable: |
---|
4505 | + self.basedir = "web/Grid/unknown-immutable" |
---|
4506 | + |
---|
4507 | self.set_up_grid() |
---|
4508 | c0 = self.g.clients[0] |
---|
4509 | self.uris = {} |
---|
4510 | self.fileurls = {} |
---|
4511 | |
---|
4512 | - future_writecap = "x-tahoe-crazy://I_am_from_the_future." |
---|
4513 | - future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
4514 | + future_write_uri = "x-tahoe-crazy://I_am_from_the_future." |
---|
4515 | + future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
4516 | # the future cap format may contain slashes, which must be tolerated |
---|
4517 | - expected_info_url = "uri/%s?t=info" % urllib.quote(future_writecap, |
---|
4518 | + expected_info_url = "uri/%s?t=info" % urllib.quote(future_write_uri, |
---|
4519 | safe="") |
---|
4520 | - future_node = UnknownNode(future_writecap, future_readcap) |
---|
4521 | |
---|
4522 | - d = c0.create_dirnode() |
---|
4523 | + if immutable: |
---|
4524 | + name = u"future-imm" |
---|
4525 | + future_node = UnknownNode(None, future_read_uri, deep_immutable=True) |
---|
4526 | + d = c0.create_immutable_dirnode({name: (future_node, {})}) |
---|
4527 | + else: |
---|
4528 | + name = u"future" |
---|
4529 | + future_node = UnknownNode(future_write_uri, future_read_uri) |
---|
4530 | + d = c0.create_dirnode() |
---|
4531 | + |
---|
4532 | def _stash_root_and_create_file(n): |
---|
4533 | self.rootnode = n |
---|
4534 | self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/" |
---|
4535 | self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/" |
---|
4536 | - return self.rootnode.set_node(u"future", future_node) |
---|
4537 | + if not immutable: |
---|
4538 | + return self.rootnode.set_node(name, future_node) |
---|
4539 | d.addCallback(_stash_root_and_create_file) |
---|
4540 | + |
---|
4541 | # make sure directory listing tolerates unknown nodes |
---|
4542 | d.addCallback(lambda ign: self.GET(self.rooturl)) |
---|
4543 | - def _check_html(res): |
---|
4544 | - self.failUnlessIn("<td>future</td>", res) |
---|
4545 | - # find the More Info link for "future", should be relative |
---|
4546 | + def _check_directory_html(res): |
---|
4547 | + self.failUnlessIn("<td>%s</td>" % (str(name),), res) |
---|
4548 | + # find the More Info link for name, should be relative |
---|
4549 | mo = re.search(r'<a href="([^"]+)">More Info</a>', res) |
---|
4550 | info_url = mo.group(1) |
---|
4551 | - self.failUnlessEqual(info_url, "future?t=info") |
---|
4552 | + self.failUnlessEqual(info_url, "%s?t=info" % (str(name),)) |
---|
4553 | + d.addCallback(_check_directory_html) |
---|
4554 | |
---|
4555 | - d.addCallback(_check_html) |
---|
4556 | d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json")) |
---|
4557 | - def _check_json(res, expect_writecap): |
---|
4558 | + def _check_directory_json(res, expect_rw_uri): |
---|
4559 | data = simplejson.loads(res) |
---|
4560 | self.failUnlessEqual(data[0], "dirnode") |
---|
4561 | - f = data[1]["children"]["future"] |
---|
4562 | + f = data[1]["children"][name] |
---|
4563 | self.failUnlessEqual(f[0], "unknown") |
---|
4564 | - if expect_writecap: |
---|
4565 | - self.failUnlessEqual(f[1]["rw_uri"], future_writecap) |
---|
4566 | + if expect_rw_uri: |
---|
4567 | + self.failUnlessEqual(f[1]["rw_uri"], future_write_uri) |
---|
4568 | else: |
---|
4569 | self.failIfIn("rw_uri", f[1]) |
---|
4570 | - self.failUnlessEqual(f[1]["ro_uri"], future_readcap) |
---|
4571 | + self.failUnlessEqual(f[1]["ro_uri"], |
---|
4572 | + ("imm." if immutable else "ro.") + future_read_uri) |
---|
4573 | self.failUnless("metadata" in f[1]) |
---|
4574 | - d.addCallback(_check_json, expect_writecap=True) |
---|
4575 | - d.addCallback(lambda ign: self.GET(expected_info_url)) |
---|
4576 | - def _check_info(res, expect_readcap): |
---|
4577 | + d.addCallback(_check_directory_json, expect_rw_uri=not immutable) |
---|
4578 | + |
---|
4579 | + def _check_info(res, expect_rw_uri, expect_ro_uri): |
---|
4580 | self.failUnlessIn("Object Type: <span>unknown</span>", res) |
---|
4581 | - self.failUnlessIn(future_writecap, res) |
---|
4582 | - if expect_readcap: |
---|
4583 | - self.failUnlessIn(future_readcap, res) |
---|
4584 | + if expect_rw_uri: |
---|
4585 | + self.failUnlessIn(future_write_uri, res) |
---|
4586 | + if expect_ro_uri: |
---|
4587 | + self.failUnlessIn(future_read_uri, res) |
---|
4588 | + else: |
---|
4589 | + self.failIfIn(future_read_uri, res) |
---|
4590 | self.failIfIn("Raw data as", res) |
---|
4591 | self.failIfIn("Directory writecap", res) |
---|
4592 | self.failIfIn("Checker Operations", res) |
---|
4593 | self.failIfIn("Mutable File Operations", res) |
---|
4594 | self.failIfIn("Directory Operations", res) |
---|
4595 | - d.addCallback(_check_info, expect_readcap=False) |
---|
4596 | - d.addCallback(lambda ign: self.GET(self.rooturl+"future?t=info")) |
---|
4597 | - d.addCallback(_check_info, expect_readcap=True) |
---|
4598 | + |
---|
4599 | + # FIXME: these should have expect_rw_uri=not immutable; I don't know |
---|
4600 | + # why they fail. Possibly related to ticket #922. |
---|
4601 | + |
---|
4602 | + d.addCallback(lambda ign: self.GET(expected_info_url)) |
---|
4603 | + d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False) |
---|
4604 | + d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name)))) |
---|
4605 | + d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True) |
---|
4606 | + |
---|
4607 | + def _check_json(res, expect_rw_uri): |
---|
4608 | + data = simplejson.loads(res) |
---|
4609 | + self.failUnlessEqual(data[0], "unknown") |
---|
4610 | + if expect_rw_uri: |
---|
4611 | + self.failUnlessEqual(data[1]["rw_uri"], future_write_uri) |
---|
4612 | + else: |
---|
4613 | + self.failIfIn("rw_uri", data[1]) |
---|
4614 | + self.failUnlessEqual(data[1]["ro_uri"], |
---|
4615 | + ("imm." if immutable else "ro.") + future_read_uri) |
---|
4616 | + # TODO: check metadata contents |
---|
4617 | + self.failUnless("metadata" in data[1]) |
---|
4618 | + |
---|
4619 | + d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name)))) |
---|
4620 | + d.addCallback(_check_json, expect_rw_uri=not immutable) |
---|
4621 | |
---|
4622 | # and make sure that a read-only version of the directory can be |
---|
4623 | - # rendered too. This version will not have future_writecap |
---|
4624 | + # rendered too. This version will not have future_write_uri, whether |
---|
4625 | + # or not future_node was immutable. |
---|
4626 | d.addCallback(lambda ign: self.GET(self.rourl)) |
---|
4627 | - d.addCallback(_check_html) |
---|
4628 | + d.addCallback(_check_directory_html) |
---|
4629 | d.addCallback(lambda ign: self.GET(self.rourl+"?t=json")) |
---|
4630 | - d.addCallback(_check_json, expect_writecap=False) |
---|
4631 | + d.addCallback(_check_directory_json, expect_rw_uri=False) |
---|
4632 | + |
---|
4633 | + d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name)))) |
---|
4634 | + d.addCallback(_check_json, expect_rw_uri=False) |
---|
4635 | + |
---|
4636 | + # TODO: check that getting t=info from the Info link in the ro directory |
---|
4637 | + # works, and does not include the writecap URI. |
---|
4638 | + return d |
---|
4639 | + |
---|
4640 | + def test_immutable_unknown(self): |
---|
4641 | + return self.test_unknown(immutable=True) |
---|
4642 | + |
---|
4643 | + def test_mutant_dirnodes_are_omitted(self): |
---|
4644 | + self.basedir = "web/Grid/mutant_dirnodes_are_omitted" |
---|
4645 | + |
---|
4646 | + self.set_up_grid() |
---|
4647 | + c = self.g.clients[0] |
---|
4648 | + nm = c.nodemaker |
---|
4649 | + self.uris = {} |
---|
4650 | + self.fileurls = {} |
---|
4651 | + |
---|
4652 | + lonely_uri = "URI:LIT:n5xgk" # LIT for "one" |
---|
4653 | + mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq" |
---|
4654 | + mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q" |
---|
4655 | + |
---|
4656 | + # This method tests mainly dirnode, but we'd have to duplicate code in order to |
---|
4657 | + # test the dirnode and web layers separately. |
---|
4658 | + |
---|
4659 | + # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap, |
---|
4660 | + # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field. |
---|
4661 | + # When the directory is read, the mutants should be silently disposed of, leaving |
---|
4662 | + # their lonely sibling. |
---|
4663 | + # We don't test the case of a retrieving a cap from the encrypted rw_uri field, |
---|
4664 | + # because immutable directories don't have a writecap and therefore that field |
---|
4665 | + # isn't (and can't be) decrypted. |
---|
4666 | + # TODO: The field still exists in the netstring. Technically we should check what |
---|
4667 | + # happens if something is put there (it should be ignored), but that can wait. |
---|
4668 | + |
---|
4669 | + lonely_child = nm.create_from_cap(lonely_uri) |
---|
4670 | + mutant_ro_child = nm.create_from_cap(mut_read_uri) |
---|
4671 | + mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri) |
---|
4672 | + |
---|
4673 | + def _by_hook_or_by_crook(): |
---|
4674 | + return True |
---|
4675 | + for n in [mutant_ro_child, mutant_write_in_ro_child]: |
---|
4676 | + n.is_allowed_in_immutable_directory = _by_hook_or_by_crook |
---|
4677 | + |
---|
4678 | + mutant_write_in_ro_child.get_write_uri = lambda: None |
---|
4679 | + mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri |
---|
4680 | + |
---|
4681 | + kids = {u"lonely": (lonely_child, {}), |
---|
4682 | + u"ro": (mutant_ro_child, {}), |
---|
4683 | + u"write-in-ro": (mutant_write_in_ro_child, {}), |
---|
4684 | + } |
---|
4685 | + d = c.create_immutable_dirnode(kids) |
---|
4686 | + |
---|
4687 | + def _created(dn): |
---|
4688 | + self.failUnless(isinstance(dn, dirnode.DirectoryNode)) |
---|
4689 | + self.failIf(dn.is_mutable()) |
---|
4690 | + self.failUnless(dn.is_readonly()) |
---|
4691 | + # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail. |
---|
4692 | + self.failIf(hasattr(dn._node, 'get_writekey')) |
---|
4693 | + rep = str(dn) |
---|
4694 | + self.failUnless("RO-IMM" in rep) |
---|
4695 | + cap = dn.get_cap() |
---|
4696 | + self.failUnlessIn("CHK", cap.to_string()) |
---|
4697 | + self.cap = cap |
---|
4698 | + self.rootnode = dn |
---|
4699 | + self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/" |
---|
4700 | + return download_to_data(dn._node) |
---|
4701 | + d.addCallback(_created) |
---|
4702 | + |
---|
4703 | + def _check_data(data): |
---|
4704 | + # Decode the netstring representation of the directory to check that all children |
---|
4705 | + # are present. This is a bit of an abstraction violation, but there's not really |
---|
4706 | + # any other way to do it given that the real DirectoryNode._unpack_contents would |
---|
4707 | + # strip the mutant children out (which is what we're trying to test, later). |
---|
4708 | + position = 0 |
---|
4709 | + numkids = 0 |
---|
4710 | + while position < len(data): |
---|
4711 | + entries, position = split_netstring(data, 1, position) |
---|
4712 | + entry = entries[0] |
---|
4713 | + (name, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4) |
---|
4714 | + name = name.decode("utf-8") |
---|
4715 | + self.failUnless(rwcapdata == "") |
---|
4716 | + ro_uri = ro_uri.strip() |
---|
4717 | + if name in kids: |
---|
4718 | + self.failIfEqual(ro_uri, "") |
---|
4719 | + (expected_child, ign) = kids[name] |
---|
4720 | + self.failUnlessEqual(ro_uri, expected_child.get_readonly_uri()) |
---|
4721 | + numkids += 1 |
---|
4722 | + |
---|
4723 | + self.failUnlessEqual(numkids, 3) |
---|
4724 | + return self.rootnode.list() |
---|
4725 | + d.addCallback(_check_data) |
---|
4726 | + |
---|
4727 | + # Now when we use the real directory listing code, the mutants should be absent. |
---|
4728 | + def _check_kids(children): |
---|
4729 | + self.failUnlessEqual(sorted(children.keys()), [u"lonely"]) |
---|
4730 | + lonely_node, lonely_metadata = children[u"lonely"] |
---|
4731 | + |
---|
4732 | + self.failUnlessEqual(lonely_node.get_write_uri(), None) |
---|
4733 | + self.failUnlessEqual(lonely_node.get_readonly_uri(), lonely_uri) |
---|
4734 | + d.addCallback(_check_kids) |
---|
4735 | + |
---|
4736 | + d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string())) |
---|
4737 | + d.addCallback(lambda n: n.list()) |
---|
4738 | + d.addCallback(_check_kids) # again with dirnode recreated from cap |
---|
4739 | + |
---|
4740 | + # Make sure the lonely child can be listed in HTML... |
---|
4741 | + d.addCallback(lambda ign: self.GET(self.rooturl)) |
---|
4742 | + def _check_html(res): |
---|
4743 | + self.failIfIn("URI:SSK", res) |
---|
4744 | + get_lonely = "".join([r'<td>FILE</td>', |
---|
4745 | + r'\s+<td>', |
---|
4746 | + r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),), |
---|
4747 | + r'</td>', |
---|
4748 | + r'\s+<td>%d</td>' % len("one"), |
---|
4749 | + ]) |
---|
4750 | + self.failUnless(re.search(get_lonely, res), res) |
---|
4751 | + |
---|
4752 | + # find the More Info link for name, should be relative |
---|
4753 | + mo = re.search(r'<a href="([^"]+)">More Info</a>', res) |
---|
4754 | + info_url = mo.group(1) |
---|
4755 | + self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url) |
---|
4756 | + d.addCallback(_check_html) |
---|
4757 | + |
---|
4758 | + # ... and in JSON. |
---|
4759 | + d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json")) |
---|
4760 | + def _check_json(res): |
---|
4761 | + data = simplejson.loads(res) |
---|
4762 | + self.failUnlessEqual(data[0], "dirnode") |
---|
4763 | + listed_children = data[1]["children"] |
---|
4764 | + self.failUnlessEqual(sorted(listed_children.keys()), [u"lonely"]) |
---|
4765 | + ll_type, ll_data = listed_children[u"lonely"] |
---|
4766 | + self.failUnlessEqual(ll_type, "filenode") |
---|
4767 | + self.failIf("rw_uri" in ll_data) |
---|
4768 | + self.failUnlessEqual(ll_data["ro_uri"], lonely_uri) |
---|
4769 | + d.addCallback(_check_json) |
---|
4770 | return d |
---|
4771 | |
---|
4772 | def test_deep_check(self): |
---|
4773 | @@ -3159,10 +3564,10 @@ |
---|
4774 | |
---|
4775 | # this tests that deep-check and stream-manifest will ignore |
---|
4776 | # UnknownNode instances. Hopefully this will also cover deep-stats. |
---|
4777 | - future_writecap = "x-tahoe-crazy://I_am_from_the_future." |
---|
4778 | - future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
4779 | - future_node = UnknownNode(future_writecap, future_readcap) |
---|
4780 | - d.addCallback(lambda ign: self.rootnode.set_node(u"future",future_node)) |
---|
4781 | + future_write_uri = "x-tahoe-crazy://I_am_from_the_future." |
---|
4782 | + future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future." |
---|
4783 | + future_node = UnknownNode(future_write_uri, future_read_uri) |
---|
4784 | + d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node)) |
---|
4785 | |
---|
4786 | def _clobber_shares(ignored): |
---|
4787 | self.delete_shares_numbered(self.uris["sick"], [0,1]) |
---|
4788 | diff -rN -u old-tahoe/src/allmydata/unknown.py new-tahoe/src/allmydata/unknown.py |
---|
4789 | --- old-tahoe/src/allmydata/unknown.py 2010-01-24 05:52:01.896000000 +0000 |
---|
4790 | +++ new-tahoe/src/allmydata/unknown.py 2010-01-24 05:52:06.373000000 +0000 |
---|
4791 | @@ -1,29 +1,146 @@ |
---|
4792 | + |
---|
4793 | from zope.interface import implements |
---|
4794 | from twisted.internet import defer |
---|
4795 | -from allmydata.interfaces import IFilesystemNode |
---|
4796 | +from allmydata.interfaces import IFilesystemNode, MustNotBeUnknownRWError |
---|
4797 | +from allmydata import uri |
---|
4798 | +from allmydata.uri import ALLEGED_READONLY_PREFIX, ALLEGED_IMMUTABLE_PREFIX |
---|
4799 | + |
---|
4800 | + |
---|
4801 | +# See ticket #833 for design rationale of UnknownNodes. |
---|
4802 | + |
---|
4803 | +"""Strip prefixes when storing an URI in a ro_uri field.""" |
---|
4804 | +def strip_prefix_for_ro(ro_uri, deep_immutable): |
---|
4805 | + # It is possible for an alleged-immutable URI to be put into a |
---|
4806 | + # mutable directory. In that case the ALLEGED_IMMUTABLE_PREFIX |
---|
4807 | + # should not be stripped. In other cases, the prefix can safely |
---|
4808 | + # be stripped because it is implied by the context. |
---|
4809 | + |
---|
4810 | + if ro_uri.startswith(ALLEGED_IMMUTABLE_PREFIX): |
---|
4811 | + if not deep_immutable: |
---|
4812 | + return ro_uri |
---|
4813 | + return ro_uri[len(ALLEGED_IMMUTABLE_PREFIX):] |
---|
4814 | + elif ro_uri.startswith(ALLEGED_READONLY_PREFIX): |
---|
4815 | + return ro_uri[len(ALLEGED_READONLY_PREFIX):] |
---|
4816 | + else: |
---|
4817 | + return ro_uri |
---|
4818 | |
---|
4819 | class UnknownNode: |
---|
4820 | implements(IFilesystemNode) |
---|
4821 | - def __init__(self, writecap, readcap): |
---|
4822 | - assert writecap is None or isinstance(writecap, str) |
---|
4823 | - self.writecap = writecap |
---|
4824 | - assert readcap is None or isinstance(readcap, str) |
---|
4825 | - self.readcap = readcap |
---|
4826 | + |
---|
4827 | + def __init__(self, rw_uri, ro_uri, deep_immutable=False, |
---|
4828 | + name=u"<unknown name>"): |
---|
4829 | + #traceback.print_stack() |
---|
4830 | + #print '%r.__init__(%r, %r, deep_immutable=%r, name=%r)' % (self, rw_uri, ro_uri, deep_immutable, name) |
---|
4831 | + assert rw_uri is None or isinstance(rw_uri, str) |
---|
4832 | + assert ro_uri is None or isinstance(ro_uri, str) |
---|
4833 | + |
---|
4834 | + # We don't raise errors when creating an UnknownNode; we instead create an |
---|
4835 | + # opaque node that records the error. This avoids breaking operations that |
---|
4836 | + # never store the opaque node. |
---|
4837 | + # Note that this means that if a stored dirnode has only a rw_uri, it |
---|
4838 | + # might be dropped. Any future "write-only" cap formats should have a dummy |
---|
4839 | + # unusable read cap to stop that from happening. |
---|
4840 | + |
---|
4841 | + self.error = None |
---|
4842 | + self.rw_uri = self.ro_uri = None |
---|
4843 | + if rw_uri is not None: |
---|
4844 | + if deep_immutable: |
---|
4845 | + self.error = MustNotBeUnknownRWError("cannot attach unknown rw cap as immutable child", |
---|
4846 | + name, True) |
---|
4847 | + return |
---|
4848 | + elif ro_uri is None: |
---|
4849 | + # If we have a single unknown cap (specified as a single cap |
---|
4850 | + # argument, or from a rw_uri slot when ro_uri has been omitted), |
---|
4851 | + # then we cannot tell whether it is a rw_uri, and we cannot |
---|
4852 | + # diminish it to a ro_uri. Prefixing it with ALLEGED_READONLY_PREFIX |
---|
4853 | + # would not be sufficient because we have no reason to believe |
---|
4854 | + # that it is a ro_uri, so that might grant excess authority. |
---|
4855 | + self.error = MustNotBeUnknownRWError("cannot attach unknown rw cap as child", |
---|
4856 | + name, False) |
---|
4857 | + return |
---|
4858 | + |
---|
4859 | + # If ro_uri definitely fails the constraint, it should be treated as opaque. |
---|
4860 | + if ro_uri is not None: |
---|
4861 | + read_cap = uri.from_string(ro_uri, deep_immutable=deep_immutable, name=name) |
---|
4862 | + if isinstance(read_cap, uri.UnknownURI): |
---|
4863 | + self.error = read_cap.get_error() |
---|
4864 | + if self.error: |
---|
4865 | + return |
---|
4866 | + |
---|
4867 | + if deep_immutable: |
---|
4868 | + # strengthen ro_uri to have ALLEGED_IMMUTABLE_PREFIX |
---|
4869 | + if ro_uri is not None: |
---|
4870 | + if ro_uri.startswith(ALLEGED_IMMUTABLE_PREFIX): |
---|
4871 | + self.ro_uri = ro_uri |
---|
4872 | + elif ro_uri.startswith(ALLEGED_READONLY_PREFIX): |
---|
4873 | + self.ro_uri = ALLEGED_IMMUTABLE_PREFIX + ro_uri[len(ALLEGED_READONLY_PREFIX):] |
---|
4874 | + else: |
---|
4875 | + self.ro_uri = ALLEGED_IMMUTABLE_PREFIX + ro_uri |
---|
4876 | + else: |
---|
4877 | + self.rw_uri = rw_uri |
---|
4878 | + # strengthen ro_uri to have ALLEGED_READONLY_PREFIX |
---|
4879 | + if ro_uri is not None: |
---|
4880 | + if (ro_uri.startswith(ALLEGED_READONLY_PREFIX) or |
---|
4881 | + ro_uri.startswith(ALLEGED_IMMUTABLE_PREFIX)): |
---|
4882 | + self.ro_uri = ro_uri |
---|
4883 | + else: |
---|
4884 | + self.ro_uri = ALLEGED_READONLY_PREFIX + ro_uri |
---|
4885 | + |
---|
4886 | + #print 'self.(error, rw_uri, ro_uri) = (%r, %r, %r)' % (self.error, self.rw_uri, self.ro_uri) |
---|
4887 | + |
---|
4888 | + def get_cap(self): |
---|
4889 | + return uri.UnknownURI(self.rw_uri or self.ro_uri) |
---|
4890 | + |
---|
4891 | + def get_readcap(self): |
---|
4892 | + return uri.UnknownURI(self.ro_uri) |
---|
4893 | + |
---|
4894 | + def is_readonly(self): |
---|
4895 | + raise AssertionError("an UnknownNode might be either read-only or " |
---|
4896 | + "read/write, so we shouldn't be calling is_readonly") |
---|
4897 | + |
---|
4898 | + def is_mutable(self): |
---|
4899 | + raise AssertionError("an UnknownNode might be either mutable or immutable, " |
---|
4900 | + "so we shouldn't be calling is_mutable") |
---|
4901 | + |
---|
4902 | + def is_unknown(self): |
---|
4903 | + return True |
---|
4904 | + |
---|
4905 | + def is_allowed_in_immutable_directory(self): |
---|
4906 | + # An UnknownNode consisting only of a ro_uri is allowed in an |
---|
4907 | + # immutable directory, even though we do not know that it is |
---|
4908 | + # immutable (or even read-only), provided that no error was detected. |
---|
4909 | + return not self.error and not self.rw_uri |
---|
4910 | + |
---|
4911 | + def raise_error(self): |
---|
4912 | + if self.error is not None: |
---|
4913 | + raise self.error |
---|
4914 | + |
---|
4915 | def get_uri(self): |
---|
4916 | - return self.writecap |
---|
4917 | + return self.rw_uri or self.ro_uri |
---|
4918 | + |
---|
4919 | + def get_write_uri(self): |
---|
4920 | + return self.rw_uri |
---|
4921 | + |
---|
4922 | def get_readonly_uri(self): |
---|
4923 | - return self.readcap |
---|
4924 | + return self.ro_uri |
---|
4925 | + |
---|
4926 | def get_storage_index(self): |
---|
4927 | return None |
---|
4928 | + |
---|
4929 | def get_verify_cap(self): |
---|
4930 | return None |
---|
4931 | + |
---|
4932 | def get_repair_cap(self): |
---|
4933 | return None |
---|
4934 | + |
---|
4935 | def get_size(self): |
---|
4936 | return None |
---|
4937 | + |
---|
4938 | def get_current_size(self): |
---|
4939 | return defer.succeed(None) |
---|
4940 | + |
---|
4941 | def check(self, monitor, verify, add_lease): |
---|
4942 | return defer.succeed(None) |
---|
4943 | + |
---|
4944 | def check_and_repair(self, monitor, verify, add_lease): |
---|
4945 | return defer.succeed(None) |
---|
4946 | diff -rN -u old-tahoe/src/allmydata/uri.py new-tahoe/src/allmydata/uri.py |
---|
4947 | --- old-tahoe/src/allmydata/uri.py 2010-01-24 05:52:01.901000000 +0000 |
---|
4948 | +++ new-tahoe/src/allmydata/uri.py 2010-01-24 05:52:06.378000000 +0000 |
---|
4949 | @@ -5,14 +5,16 @@ |
---|
4950 | from allmydata.storage.server import si_a2b, si_b2a |
---|
4951 | from allmydata.util import base32, hashutil |
---|
4952 | from allmydata.interfaces import IURI, IDirnodeURI, IFileURI, IImmutableFileURI, \ |
---|
4953 | - IVerifierURI, IMutableFileURI, IDirectoryURI, IReadonlyDirectoryURI |
---|
4954 | + IVerifierURI, IMutableFileURI, IDirectoryURI, IReadonlyDirectoryURI, \ |
---|
4955 | + MustBeDeepImmutableError, MustBeReadonlyError |
---|
4956 | |
---|
4957 | class BadURIError(Exception): |
---|
4958 | pass |
---|
4959 | |
---|
4960 | -# the URI shall be an ascii representation of the file. It shall contain |
---|
4961 | -# enough information to retrieve and validate the contents. It shall be |
---|
4962 | -# expressed in a limited character set (namely [TODO]). |
---|
4963 | +# The URI shall be an ASCII representation of a reference to the file/directory. |
---|
4964 | +# It shall contain enough information to retrieve and validate the contents. |
---|
4965 | +# It shall be expressed in a limited character set (currently base32 plus ':' and |
---|
4966 | +# capital letters, but future URIs might use a larger charset). |
---|
4967 | |
---|
4968 | BASE32STR_128bits = '(%s{25}%s)' % (base32.BASE32CHAR, base32.BASE32CHAR_3bits) |
---|
4969 | BASE32STR_256bits = '(%s{51}%s)' % (base32.BASE32CHAR, base32.BASE32CHAR_1bits) |
---|
4970 | @@ -39,6 +41,10 @@ |
---|
4971 | return self.to_string() != them.to_string() |
---|
4972 | else: |
---|
4973 | return True |
---|
4974 | + |
---|
4975 | + def is_unknown(self): |
---|
4976 | + return False |
---|
4977 | + |
---|
4978 | def to_human_encoding(self): |
---|
4979 | return 'http://127.0.0.1:3456/uri/'+self.to_string() |
---|
4980 | |
---|
4981 | @@ -97,8 +103,10 @@ |
---|
4982 | |
---|
4983 | def is_readonly(self): |
---|
4984 | return True |
---|
4985 | + |
---|
4986 | def is_mutable(self): |
---|
4987 | return False |
---|
4988 | + |
---|
4989 | def get_readonly(self): |
---|
4990 | return self |
---|
4991 | |
---|
4992 | @@ -157,6 +165,18 @@ |
---|
4993 | self.total_shares, |
---|
4994 | self.size)) |
---|
4995 | |
---|
4996 | + def is_readonly(self): |
---|
4997 | + return True |
---|
4998 | + |
---|
4999 | + def is_mutable(self): |
---|
5000 | + return False |
---|
5001 | + |
---|
5002 | + def get_readonly(self): |
---|
5003 | + return self |
---|
5004 | + |
---|
5005 | + def get_verify_cap(self): |
---|
5006 | + return self |
---|
5007 | + |
---|
5008 | |
---|
5009 | class LiteralFileURI(_BaseURI): |
---|
5010 | implements(IURI, IImmutableFileURI) |
---|
5011 | @@ -297,10 +317,13 @@ |
---|
5012 | |
---|
5013 | def is_readonly(self): |
---|
5014 | return True |
---|
5015 | + |
---|
5016 | def is_mutable(self): |
---|
5017 | return True |
---|
5018 | + |
---|
5019 | def get_readonly(self): |
---|
5020 | return self |
---|
5021 | + |
---|
5022 | def get_verify_cap(self): |
---|
5023 | return SSKVerifierURI(self.storage_index, self.fingerprint) |
---|
5024 | |
---|
5025 | @@ -334,6 +357,15 @@ |
---|
5026 | return 'URI:SSK-Verifier:%s:%s' % (si_b2a(self.storage_index), |
---|
5027 | base32.b2a(self.fingerprint)) |
---|
5028 | |
---|
5029 | + def is_readonly(self): |
---|
5030 | + return True |
---|
5031 | + def is_mutable(self): |
---|
5032 | + return False |
---|
5033 | + def get_readonly(self): |
---|
5034 | + return self |
---|
5035 | + def get_verify_cap(self): |
---|
5036 | + return self |
---|
5037 | + |
---|
5038 | class _DirectoryBaseURI(_BaseURI): |
---|
5039 | implements(IURI, IDirnodeURI) |
---|
5040 | def __init__(self, filenode_uri=None): |
---|
5041 | @@ -376,12 +408,12 @@ |
---|
5042 | def abbrev_si(self): |
---|
5043 | return base32.b2a(self._filenode_uri.storage_index)[:5] |
---|
5044 | |
---|
5045 | - def get_filenode_cap(self): |
---|
5046 | - return self._filenode_uri |
---|
5047 | - |
---|
5048 | def is_mutable(self): |
---|
5049 | return True |
---|
5050 | |
---|
5051 | + def get_filenode_cap(self): |
---|
5052 | + return self._filenode_uri |
---|
5053 | + |
---|
5054 | def get_verify_cap(self): |
---|
5055 | return DirectoryURIVerifier(self._filenode_uri.get_verify_cap()) |
---|
5056 | |
---|
5057 | @@ -432,12 +464,12 @@ |
---|
5058 | assert isinstance(filenode_uri, self.INNER_URI_CLASS), filenode_uri |
---|
5059 | _DirectoryBaseURI.__init__(self, filenode_uri) |
---|
5060 | |
---|
5061 | - def is_mutable(self): |
---|
5062 | - return False |
---|
5063 | - |
---|
5064 | def is_readonly(self): |
---|
5065 | return True |
---|
5066 | |
---|
5067 | + def is_mutable(self): |
---|
5068 | + return False |
---|
5069 | + |
---|
5070 | def get_readonly(self): |
---|
5071 | return self |
---|
5072 | |
---|
5073 | @@ -460,6 +492,7 @@ |
---|
5074 | # LIT caps have no verifier, since they aren't distributed |
---|
5075 | return None |
---|
5076 | |
---|
5077 | + |
---|
5078 | def wrap_dirnode_cap(filecap): |
---|
5079 | if isinstance(filecap, WriteableSSKFileURI): |
---|
5080 | return DirectoryURI(filecap) |
---|
5081 | @@ -469,7 +502,8 @@ |
---|
5082 | return ImmutableDirectoryURI(filecap) |
---|
5083 | if isinstance(filecap, LiteralFileURI): |
---|
5084 | return LiteralDirectoryURI(filecap) |
---|
5085 | - assert False, "cannot wrap a dirnode around %s" % filecap.__class__ |
---|
5086 | + assert False, "cannot interpret as a directory cap: %s" % filecap.__class__ |
---|
5087 | + |
---|
5088 | |
---|
5089 | class DirectoryURIVerifier(_DirectoryBaseURI): |
---|
5090 | implements(IVerifierURI) |
---|
5091 | @@ -487,6 +521,10 @@ |
---|
5092 | def get_filenode_cap(self): |
---|
5093 | return self._filenode_uri |
---|
5094 | |
---|
5095 | + def is_mutable(self): |
---|
5096 | + return False |
---|
5097 | + |
---|
5098 | + |
---|
5099 | class ImmutableDirectoryURIVerifier(DirectoryURIVerifier): |
---|
5100 | implements(IVerifierURI) |
---|
5101 | BASE_STRING='URI:DIR2-CHK-Verifier:' |
---|
5102 | @@ -494,68 +532,133 @@ |
---|
5103 | BASE_HUMAN_RE=re.compile('^'+OPTIONALHTTPLEAD+'URI'+SEP+'DIR2-CHK-VERIFIER'+SEP) |
---|
5104 | INNER_URI_CLASS=CHKFileVerifierURI |
---|
5105 | |
---|
5106 | + |
---|
5107 | class UnknownURI: |
---|
5108 | - def __init__(self, uri): |
---|
5109 | + def __init__(self, uri, error=None): |
---|
5110 | self._uri = uri |
---|
5111 | + self._error = error |
---|
5112 | + |
---|
5113 | def to_string(self): |
---|
5114 | return self._uri |
---|
5115 | |
---|
5116 | -def from_string(s): |
---|
5117 | - if not isinstance(s, str): |
---|
5118 | - raise TypeError("unknown URI type: %s.." % str(s)[:100]) |
---|
5119 | - elif s.startswith('URI:CHK:'): |
---|
5120 | + def get_readonly(self): |
---|
5121 | + return None |
---|
5122 | + |
---|
5123 | + def get_error(self): |
---|
5124 | + return self._error |
---|
5125 | + |
---|
5126 | + |
---|
5127 | +ALLEGED_READONLY_PREFIX = 'ro.' |
---|
5128 | +ALLEGED_IMMUTABLE_PREFIX = 'imm.' |
---|
5129 | + |
---|
5130 | +def from_string(u, deep_immutable=False, name=u"<unknown name>"): |
---|
5131 | + if not isinstance(u, str): |
---|
5132 | + raise TypeError("unknown URI type: %s.." % str(u)[:100]) |
---|
5133 | + |
---|
5134 | + # We allow and check ALLEGED_READONLY_PREFIX or ALLEGED_IMMUTABLE_PREFIX |
---|
5135 | + # on all URIs, even though we would only strictly need to do so for caps of |
---|
5136 | + # new formats (post Tahoe-LAFS 1.6). URIs that are not consistent with their |
---|
5137 | + # prefix are treated as unknown. This should be revisited when we add the |
---|
5138 | + # new cap formats. See <http://allmydata.org/trac/tahoe/ticket/833#comment:31>. |
---|
5139 | + s = u |
---|
5140 | + can_be_mutable = can_be_writeable = not deep_immutable |
---|
5141 | + if s.startswith(ALLEGED_IMMUTABLE_PREFIX): |
---|
5142 | + can_be_mutable = can_be_writeable = False |
---|
5143 | + s = s[len(ALLEGED_IMMUTABLE_PREFIX):] |
---|
5144 | + elif s.startswith(ALLEGED_READONLY_PREFIX): |
---|
5145 | + can_be_writeable = False |
---|
5146 | + s = s[len(ALLEGED_READONLY_PREFIX):] |
---|
5147 | + |
---|
5148 | + error = None |
---|
5149 | + if s.startswith('URI:CHK:'): |
---|
5150 | return CHKFileURI.init_from_string(s) |
---|
5151 | elif s.startswith('URI:CHK-Verifier:'): |
---|
5152 | return CHKFileVerifierURI.init_from_string(s) |
---|
5153 | elif s.startswith('URI:LIT:'): |
---|
5154 | return LiteralFileURI.init_from_string(s) |
---|
5155 | elif s.startswith('URI:SSK:'): |
---|
5156 | - return WriteableSSKFileURI.init_from_string(s) |
---|
5157 | + if can_be_writeable: |
---|
5158 | + return WriteableSSKFileURI.init_from_string(s) |
---|
5159 | + error = MustBeReadonlyError("URI:SSK file writecap used in a read-only context", |
---|
5160 | + name) |
---|
5161 | elif s.startswith('URI:SSK-RO:'): |
---|
5162 | - return ReadonlySSKFileURI.init_from_string(s) |
---|
5163 | + if can_be_mutable: |
---|
5164 | + return ReadonlySSKFileURI.init_from_string(s) |
---|
5165 | + error = MustBeDeepImmutableError("URI:SSK-RO readcap to a mutable file used in an immutable context", |
---|
5166 | + name) |
---|
5167 | elif s.startswith('URI:SSK-Verifier:'): |
---|
5168 | return SSKVerifierURI.init_from_string(s) |
---|
5169 | elif s.startswith('URI:DIR2:'): |
---|
5170 | - return DirectoryURI.init_from_string(s) |
---|
5171 | + if can_be_writeable: |
---|
5172 | + return DirectoryURI.init_from_string(s) |
---|
5173 | + error = MustBeReadonlyError("URI:DIR2 directory writecap used in a read-only context", |
---|
5174 | + name) |
---|
5175 | elif s.startswith('URI:DIR2-RO:'): |
---|
5176 | - return ReadonlyDirectoryURI.init_from_string(s) |
---|
5177 | + if can_be_mutable: |
---|
5178 | + return ReadonlyDirectoryURI.init_from_string(s) |
---|
5179 | + error = MustBeDeepImmutableError("URI:DIR2-RO readcap to a mutable directory used in an immutable context", |
---|
5180 | + name) |
---|
5181 | elif s.startswith('URI:DIR2-Verifier:'): |
---|
5182 | return DirectoryURIVerifier.init_from_string(s) |
---|
5183 | elif s.startswith('URI:DIR2-CHK:'): |
---|
5184 | return ImmutableDirectoryURI.init_from_string(s) |
---|
5185 | elif s.startswith('URI:DIR2-LIT:'): |
---|
5186 | return LiteralDirectoryURI.init_from_string(s) |
---|
5187 | - return UnknownURI(s) |
---|
5188 | + elif s.startswith('x-tahoe-future-test-writeable:') and not can_be_writeable: |
---|
5189 | + # For testing how future writeable caps would behave in read-only contexts. |
---|
5190 | + error = MustBeReadonlyError("x-tahoe-future-test-writeable: testing cap used in a read-only context", |
---|
5191 | + name) |
---|
5192 | + elif s.startswith('x-tahoe-future-test-mutable:') and not can_be_mutable: |
---|
5193 | + # For testing how future mutable readcaps would behave in immutable contexts. |
---|
5194 | + error = MustBeDeepImmutableError("x-tahoe-future-test-mutable: testing cap used in an immutable context", |
---|
5195 | + name) |
---|
5196 | + |
---|
5197 | + #if error: print error |
---|
5198 | + return UnknownURI(u, error=error) |
---|
5199 | |
---|
5200 | def is_uri(s): |
---|
5201 | try: |
---|
5202 | - from_string(s) |
---|
5203 | + from_string(s, deep_immutable=False) |
---|
5204 | return True |
---|
5205 | except (TypeError, AssertionError): |
---|
5206 | return False |
---|
5207 | |
---|
5208 | -def from_string_dirnode(s): |
---|
5209 | - u = from_string(s) |
---|
5210 | +def is_literal_file_uri(s): |
---|
5211 | + if not isinstance(s, str): |
---|
5212 | + return False |
---|
5213 | + return (s.startswith('URI:LIT:') or |
---|
5214 | + s.startswith(ALLEGED_READONLY_PREFIX + 'URI:LIT:') or |
---|
5215 | + s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:LIT:')) |
---|
5216 | + |
---|
5217 | +def has_uri_prefix(s): |
---|
5218 | + if not isinstance(s, str): |
---|
5219 | + return False |
---|
5220 | + return (s.startswith("URI:") or |
---|
5221 | + s.startswith(ALLEGED_READONLY_PREFIX + 'URI:') or |
---|
5222 | + s.startswith(ALLEGED_IMMUTABLE_PREFIX + 'URI:')) |
---|
5223 | + |
---|
5224 | +def from_string_dirnode(s, **kwargs): |
---|
5225 | + u = from_string(s, **kwargs) |
---|
5226 | assert IDirnodeURI.providedBy(u) |
---|
5227 | return u |
---|
5228 | |
---|
5229 | registerAdapter(from_string_dirnode, str, IDirnodeURI) |
---|
5230 | |
---|
5231 | -def from_string_filenode(s): |
---|
5232 | - u = from_string(s) |
---|
5233 | +def from_string_filenode(s, **kwargs): |
---|
5234 | + u = from_string(s, **kwargs) |
---|
5235 | assert IFileURI.providedBy(u) |
---|
5236 | return u |
---|
5237 | |
---|
5238 | registerAdapter(from_string_filenode, str, IFileURI) |
---|
5239 | |
---|
5240 | -def from_string_mutable_filenode(s): |
---|
5241 | - u = from_string(s) |
---|
5242 | +def from_string_mutable_filenode(s, **kwargs): |
---|
5243 | + u = from_string(s, **kwargs) |
---|
5244 | assert IMutableFileURI.providedBy(u) |
---|
5245 | return u |
---|
5246 | registerAdapter(from_string_mutable_filenode, str, IMutableFileURI) |
---|
5247 | |
---|
5248 | -def from_string_verifier(s): |
---|
5249 | - u = from_string(s) |
---|
5250 | +def from_string_verifier(s, **kwargs): |
---|
5251 | + u = from_string(s, **kwargs) |
---|
5252 | assert IVerifierURI.providedBy(u) |
---|
5253 | return u |
---|
5254 | registerAdapter(from_string_verifier, str, IVerifierURI) |
---|
5255 | diff -rN -u old-tahoe/src/allmydata/web/common.py new-tahoe/src/allmydata/web/common.py |
---|
5256 | --- old-tahoe/src/allmydata/web/common.py 2010-01-24 05:52:02.392000000 +0000 |
---|
5257 | +++ new-tahoe/src/allmydata/web/common.py 2010-01-24 05:52:06.593000000 +0000 |
---|
5258 | @@ -8,7 +8,8 @@ |
---|
5259 | from nevow.util import resource_filename |
---|
5260 | from allmydata.interfaces import ExistingChildError, NoSuchChildError, \ |
---|
5261 | FileTooLargeError, NotEnoughSharesError, NoSharesError, \ |
---|
5262 | - NotDeepImmutableError, EmptyPathnameComponentError |
---|
5263 | + EmptyPathnameComponentError, MustBeDeepImmutableError, \ |
---|
5264 | + MustBeReadonlyError, MustNotBeUnknownRWError |
---|
5265 | from allmydata.mutable.common import UnrecoverableFileError |
---|
5266 | from allmydata.util import abbreviate # TODO: consolidate |
---|
5267 | |
---|
5268 | @@ -181,9 +182,42 @@ |
---|
5269 | "failure, or disk corruption. You should perform a filecheck on " |
---|
5270 | "this object to learn more.") |
---|
5271 | return (t, http.GONE) |
---|
5272 | - if f.check(NotDeepImmutableError): |
---|
5273 | - t = ("NotDeepImmutableError: a mkdir-immutable operation was given " |
---|
5274 | - "a child that was not itself immutable: %s" % (f.value,)) |
---|
5275 | + if f.check(MustNotBeUnknownRWError): |
---|
5276 | + name = f.value.args[1] |
---|
5277 | + immutable = f.value.args[2] |
---|
5278 | + if immutable: |
---|
5279 | + t = ("MustNotBeUnknownRWError: an operation to add a child named " |
---|
5280 | + "'%s' to a directory was given an unknown cap in a write slot.\n" |
---|
5281 | + "If the cap is actually an immutable readcap, then using a " |
---|
5282 | + "webapi server that supports a later version of Tahoe may help.\n\n" |
---|
5283 | + "If you are using the webapi directly, then specifying an immutable " |
---|
5284 | + "readcap in the read slot (ro_uri) of the JSON PROPDICT, and " |
---|
5285 | + "omitting the write slot (rw_uri), would also work in this " |
---|
5286 | + "case.") % name.encode("utf-8") |
---|
5287 | + else: |
---|
5288 | + t = ("MustNotBeUnknownRWError: an operation to add a child named " |
---|
5289 | + "'%s' to a directory was given an unknown cap in a write slot.\n" |
---|
5290 | + "Using a webapi server that supports a later version of Tahoe " |
---|
5291 | + "may help.\n\n" |
---|
5292 | + "If you are using the webapi directly, specifying a readcap in " |
---|
5293 | + "the read slot (ro_uri) of the JSON PROPDICT, as well as a " |
---|
5294 | + "writecap in the write slot if desired, would also work in this " |
---|
5295 | + "case.") % name.encode("utf-8") |
---|
5296 | + return (t, http.BAD_REQUEST) |
---|
5297 | + if f.check(MustBeDeepImmutableError): |
---|
5298 | + name = f.value.args[1] |
---|
5299 | + t = ("MustBeDeepImmutableError: a cap passed to this operation for " |
---|
5300 | + "the child named '%s', needed to be immutable but was not. Either " |
---|
5301 | + "the cap is being added to an immutable directory, or it was " |
---|
5302 | + "originally retrieved from an immutable directory as an unknown " |
---|
5303 | + "cap." % name.encode("utf-8")) |
---|
5304 | + return (t, http.BAD_REQUEST) |
---|
5305 | + if f.check(MustBeReadonlyError): |
---|
5306 | + name = f.value.args[1] |
---|
5307 | + t = ("MustBeReadonlyError: a cap passed to this operation for " |
---|
5308 | + "the child named '%s', needed to be read-only but was not. " |
---|
5309 | + "The cap is being passed in a read slot (ro_uri), or was retrieved " |
---|
5310 | + "from a read slot as an unknown cap." % name.encode("utf-8")) |
---|
5311 | return (t, http.BAD_REQUEST) |
---|
5312 | if f.check(WebError): |
---|
5313 | return (f.value.text, f.value.code) |
---|
5314 | diff -rN -u old-tahoe/src/allmydata/web/directory.py new-tahoe/src/allmydata/web/directory.py |
---|
5315 | --- old-tahoe/src/allmydata/web/directory.py 2010-01-24 05:52:02.456000000 +0000 |
---|
5316 | +++ new-tahoe/src/allmydata/web/directory.py 2010-01-24 05:52:06.612000000 +0000 |
---|
5317 | @@ -351,7 +351,12 @@ |
---|
5318 | charset = get_arg(req, "_charset", "utf-8") |
---|
5319 | name = name.decode(charset) |
---|
5320 | replace = boolean_of_arg(get_arg(req, "replace", "true")) |
---|
5321 | - d = self.node.set_uri(name, childcap, childcap, overwrite=replace) |
---|
5322 | + |
---|
5323 | + # We mustn't pass childcap for the readcap argument because we don't |
---|
5324 | + # know whether it is a read cap. Passing a read cap as the writecap |
---|
5325 | + # argument will work (it ends up calling NodeMaker.create_from_cap, |
---|
5326 | + # which derives a readcap if necessary and possible). |
---|
5327 | + d = self.node.set_uri(name, childcap, None, overwrite=replace) |
---|
5328 | d.addCallback(lambda res: childcap) |
---|
5329 | return d |
---|
5330 | |
---|
5331 | @@ -362,9 +367,9 @@ |
---|
5332 | # won't show up in the resulting encoded form.. the 'name' |
---|
5333 | # field is completely missing. So to allow deletion of an |
---|
5334 | # empty file, we have to pretend that None means ''. The only |
---|
5335 | - # downide of this is a slightly confusing error message if |
---|
5336 | + # downside of this is a slightly confusing error message if |
---|
5337 | # someone does a POST without a name= field. For our own HTML |
---|
5338 | - # thisn't a big deal, because we create the 'delete' POST |
---|
5339 | + # this isn't a big deal, because we create the 'delete' POST |
---|
5340 | # buttons ourselves. |
---|
5341 | name = '' |
---|
5342 | charset = get_arg(req, "_charset", "utf-8") |
---|
5343 | @@ -584,7 +589,11 @@ |
---|
5344 | def render_title(self, ctx, data): |
---|
5345 | si_s = abbreviated_dirnode(self.node) |
---|
5346 | header = ["Tahoe-LAFS - Directory SI=%s" % si_s] |
---|
5347 | - if self.node.is_readonly(): |
---|
5348 | + if self.node.is_unknown(): |
---|
5349 | + header.append(" (unknown)") |
---|
5350 | + elif not self.node.is_mutable(): |
---|
5351 | + header.append(" (immutable)") |
---|
5352 | + elif self.node.is_readonly(): |
---|
5353 | header.append(" (read-only)") |
---|
5354 | else: |
---|
5355 | header.append(" (modifiable)") |
---|
5356 | @@ -593,7 +602,11 @@ |
---|
5357 | def render_header(self, ctx, data): |
---|
5358 | si_s = abbreviated_dirnode(self.node) |
---|
5359 | header = ["Tahoe-LAFS Directory SI=", T.span(class_="data-chars")[si_s]] |
---|
5360 | - if self.node.is_readonly(): |
---|
5361 | + if self.node.is_unknown(): |
---|
5362 | + header.append(" (unknown)") |
---|
5363 | + elif not self.node.is_mutable(): |
---|
5364 | + header.append(" (immutable)") |
---|
5365 | + elif self.node.is_readonly(): |
---|
5366 | header.append(" (read-only)") |
---|
5367 | return ctx.tag[header] |
---|
5368 | |
---|
5369 | @@ -602,7 +615,7 @@ |
---|
5370 | return T.div[T.a(href=link)["Return to Welcome page"]] |
---|
5371 | |
---|
5372 | def render_show_readonly(self, ctx, data): |
---|
5373 | - if self.node.is_readonly(): |
---|
5374 | + if self.node.is_unknown() or self.node.is_readonly(): |
---|
5375 | return "" |
---|
5376 | rocap = self.node.get_readonly_uri() |
---|
5377 | root = get_root(ctx) |
---|
5378 | @@ -629,7 +642,7 @@ |
---|
5379 | |
---|
5380 | root = get_root(ctx) |
---|
5381 | here = "%s/uri/%s/" % (root, urllib.quote(self.node.get_uri())) |
---|
5382 | - if self.node.is_readonly(): |
---|
5383 | + if self.node.is_unknown() or self.node.is_readonly(): |
---|
5384 | delete = "-" |
---|
5385 | rename = "-" |
---|
5386 | else: |
---|
5387 | @@ -677,8 +690,8 @@ |
---|
5388 | ctx.fillSlots("times", times) |
---|
5389 | |
---|
5390 | assert IFilesystemNode.providedBy(target), target |
---|
5391 | - writecap = target.get_uri() or "" |
---|
5392 | - quoted_uri = urllib.quote(writecap, safe="") # escape slashes too |
---|
5393 | + target_uri = target.get_uri() or "" |
---|
5394 | + quoted_uri = urllib.quote(target_uri, safe="") # escape slashes too |
---|
5395 | |
---|
5396 | if IMutableFileNode.providedBy(target): |
---|
5397 | # to prevent javascript in displayed .html files from stealing a |
---|
5398 | @@ -707,7 +720,7 @@ |
---|
5399 | |
---|
5400 | elif IDirectoryNode.providedBy(target): |
---|
5401 | # directory |
---|
5402 | - uri_link = "%s/uri/%s/" % (root, urllib.quote(writecap)) |
---|
5403 | + uri_link = "%s/uri/%s/" % (root, urllib.quote(target_uri)) |
---|
5404 | ctx.fillSlots("filename", |
---|
5405 | T.a(href=uri_link)[html.escape(name)]) |
---|
5406 | if not target.is_mutable(): |
---|
5407 | @@ -794,35 +807,30 @@ |
---|
5408 | kids = {} |
---|
5409 | for name, (childnode, metadata) in children.iteritems(): |
---|
5410 | assert IFilesystemNode.providedBy(childnode), childnode |
---|
5411 | - rw_uri = childnode.get_uri() |
---|
5412 | + rw_uri = childnode.get_write_uri() |
---|
5413 | ro_uri = childnode.get_readonly_uri() |
---|
5414 | if IFileNode.providedBy(childnode): |
---|
5415 | - if childnode.is_readonly(): |
---|
5416 | - rw_uri = None |
---|
5417 | kiddata = ("filenode", {'size': childnode.get_size(), |
---|
5418 | 'mutable': childnode.is_mutable(), |
---|
5419 | }) |
---|
5420 | elif IDirectoryNode.providedBy(childnode): |
---|
5421 | - if childnode.is_readonly(): |
---|
5422 | - rw_uri = None |
---|
5423 | kiddata = ("dirnode", {'mutable': childnode.is_mutable()}) |
---|
5424 | else: |
---|
5425 | kiddata = ("unknown", {}) |
---|
5426 | + |
---|
5427 | kiddata[1]["metadata"] = metadata |
---|
5428 | - if ro_uri: |
---|
5429 | - kiddata[1]["ro_uri"] = ro_uri |
---|
5430 | if rw_uri: |
---|
5431 | kiddata[1]["rw_uri"] = rw_uri |
---|
5432 | + if ro_uri: |
---|
5433 | + kiddata[1]["ro_uri"] = ro_uri |
---|
5434 | verifycap = childnode.get_verify_cap() |
---|
5435 | if verifycap: |
---|
5436 | kiddata[1]['verify_uri'] = verifycap.to_string() |
---|
5437 | + |
---|
5438 | kids[name] = kiddata |
---|
5439 | - if dirnode.is_readonly(): |
---|
5440 | - drw_uri = None |
---|
5441 | - dro_uri = dirnode.get_uri() |
---|
5442 | - else: |
---|
5443 | - drw_uri = dirnode.get_uri() |
---|
5444 | - dro_uri = dirnode.get_readonly_uri() |
---|
5445 | + |
---|
5446 | + drw_uri = dirnode.get_write_uri() |
---|
5447 | + dro_uri = dirnode.get_readonly_uri() |
---|
5448 | contents = { 'children': kids } |
---|
5449 | if dro_uri: |
---|
5450 | contents['ro_uri'] = dro_uri |
---|
5451 | @@ -833,13 +841,14 @@ |
---|
5452 | contents['verify_uri'] = verifycap.to_string() |
---|
5453 | contents['mutable'] = dirnode.is_mutable() |
---|
5454 | data = ("dirnode", contents) |
---|
5455 | - return simplejson.dumps(data, indent=1) + "\n" |
---|
5456 | + json = simplejson.dumps(data, indent=1) + "\n" |
---|
5457 | + #print json |
---|
5458 | + return json |
---|
5459 | d.addCallback(_got) |
---|
5460 | d.addCallback(text_plain, ctx) |
---|
5461 | return d |
---|
5462 | |
---|
5463 | |
---|
5464 | - |
---|
5465 | def DirectoryURI(ctx, dirnode): |
---|
5466 | return text_plain(dirnode.get_uri(), ctx) |
---|
5467 | |
---|
5468 | @@ -1131,18 +1140,39 @@ |
---|
5469 | self.req.write(j+"\n") |
---|
5470 | return "" |
---|
5471 | |
---|
5472 | -class UnknownNodeHandler(RenderMixin, rend.Page): |
---|
5473 | |
---|
5474 | +class UnknownNodeHandler(RenderMixin, rend.Page): |
---|
5475 | def __init__(self, client, node, parentnode=None, name=None): |
---|
5476 | rend.Page.__init__(self) |
---|
5477 | assert node |
---|
5478 | self.node = node |
---|
5479 | + self.parentnode = parentnode |
---|
5480 | + self.name = name |
---|
5481 | |
---|
5482 | def render_GET(self, ctx): |
---|
5483 | req = IRequest(ctx) |
---|
5484 | t = get_arg(req, "t", "").strip() |
---|
5485 | if t == "info": |
---|
5486 | return MoreInfo(self.node) |
---|
5487 | - raise WebError("GET unknown URI type: can only do t=info, not t=%s" % t) |
---|
5488 | - |
---|
5489 | - |
---|
5490 | + if t == "json": |
---|
5491 | + if self.parentnode and self.name: |
---|
5492 | + d = self.parentnode.get_metadata_for(self.name) |
---|
5493 | + else: |
---|
5494 | + d = defer.succeed(None) |
---|
5495 | + d.addCallback(lambda md: UnknownJSONMetadata(ctx, self.node, md)) |
---|
5496 | + return d |
---|
5497 | + raise WebError("GET unknown URI type: can only do t=info and t=json, not t=%s.\n" |
---|
5498 | + "Using a webapi server that supports a later version of Tahoe " |
---|
5499 | + "may help." % t) |
---|
5500 | + |
---|
5501 | +def UnknownJSONMetadata(ctx, filenode, edge_metadata): |
---|
5502 | + rw_uri = filenode.get_write_uri() |
---|
5503 | + ro_uri = filenode.get_readonly_uri() |
---|
5504 | + data = ("unknown", {}) |
---|
5505 | + if ro_uri: |
---|
5506 | + data[1]['ro_uri'] = ro_uri |
---|
5507 | + if rw_uri: |
---|
5508 | + data[1]['rw_uri'] = rw_uri |
---|
5509 | + if edge_metadata is not None: |
---|
5510 | + data[1]['metadata'] = edge_metadata |
---|
5511 | + return text_plain(simplejson.dumps(data, indent=1) + "\n", ctx) |
---|
5512 | diff -rN -u old-tahoe/src/allmydata/web/filenode.py new-tahoe/src/allmydata/web/filenode.py |
---|
5513 | --- old-tahoe/src/allmydata/web/filenode.py 2010-01-24 05:52:02.484000000 +0000 |
---|
5514 | +++ new-tahoe/src/allmydata/web/filenode.py 2010-01-24 05:52:06.628000000 +0000 |
---|
5515 | @@ -6,10 +6,9 @@ |
---|
5516 | from nevow import url, rend |
---|
5517 | from nevow.inevow import IRequest |
---|
5518 | |
---|
5519 | -from allmydata.interfaces import ExistingChildError, CannotPackUnknownNodeError |
---|
5520 | +from allmydata.interfaces import ExistingChildError |
---|
5521 | from allmydata.monitor import Monitor |
---|
5522 | from allmydata.immutable.upload import FileHandle |
---|
5523 | -from allmydata.unknown import UnknownNode |
---|
5524 | from allmydata.util import log, base32 |
---|
5525 | |
---|
5526 | from allmydata.web.common import text_plain, WebError, RenderMixin, \ |
---|
5527 | @@ -20,7 +19,6 @@ |
---|
5528 | from allmydata.web.info import MoreInfo |
---|
5529 | |
---|
5530 | class ReplaceMeMixin: |
---|
5531 | - |
---|
5532 | def replace_me_with_a_child(self, req, client, replace): |
---|
5533 | # a new file is being uploaded in our place. |
---|
5534 | mutable = boolean_of_arg(get_arg(req, "mutable", "false")) |
---|
5535 | @@ -55,14 +53,7 @@ |
---|
5536 | def replace_me_with_a_childcap(self, req, client, replace): |
---|
5537 | req.content.seek(0) |
---|
5538 | childcap = req.content.read() |
---|
5539 | - childnode = client.create_node_from_uri(childcap, childcap+"readonly") |
---|
5540 | - if isinstance(childnode, UnknownNode): |
---|
5541 | - # don't be willing to pack unknown nodes: we might accidentally |
---|
5542 | - # put some write-authority into the rocap slot because we don't |
---|
5543 | - # know how to diminish the URI they gave us. We don't even know |
---|
5544 | - # if they gave us a readcap or a writecap. |
---|
5545 | - msg = "cannot attach unknown node as child %s" % str(self.name) |
---|
5546 | - raise CannotPackUnknownNodeError(msg) |
---|
5547 | + childnode = client.create_node_from_uri(childcap, None, name=self.name) |
---|
5548 | d = self.parentnode.set_node(self.name, childnode, overwrite=replace) |
---|
5549 | d.addCallback(lambda res: childnode.get_uri()) |
---|
5550 | return d |
---|
5551 | @@ -426,12 +417,8 @@ |
---|
5552 | |
---|
5553 | |
---|
5554 | def FileJSONMetadata(ctx, filenode, edge_metadata): |
---|
5555 | - if filenode.is_readonly(): |
---|
5556 | - rw_uri = None |
---|
5557 | - ro_uri = filenode.get_uri() |
---|
5558 | - else: |
---|
5559 | - rw_uri = filenode.get_uri() |
---|
5560 | - ro_uri = filenode.get_readonly_uri() |
---|
5561 | + rw_uri = filenode.get_write_uri() |
---|
5562 | + ro_uri = filenode.get_readonly_uri() |
---|
5563 | data = ("filenode", {}) |
---|
5564 | data[1]['size'] = filenode.get_size() |
---|
5565 | if ro_uri: |
---|
5566 | diff -rN -u old-tahoe/src/allmydata/web/info.py new-tahoe/src/allmydata/web/info.py |
---|
5567 | --- old-tahoe/src/allmydata/web/info.py 2010-01-24 05:52:02.499000000 +0000 |
---|
5568 | +++ new-tahoe/src/allmydata/web/info.py 2010-01-24 05:52:06.636000000 +0000 |
---|
5569 | @@ -21,6 +21,8 @@ |
---|
5570 | def get_type(self): |
---|
5571 | node = self.original |
---|
5572 | if IDirectoryNode.providedBy(node): |
---|
5573 | + if not node.is_mutable(): |
---|
5574 | + return "immutable directory" |
---|
5575 | return "directory" |
---|
5576 | if IFileNode.providedBy(node): |
---|
5577 | si = node.get_storage_index() |
---|
5578 | @@ -28,7 +30,7 @@ |
---|
5579 | if node.is_mutable(): |
---|
5580 | return "mutable file" |
---|
5581 | return "immutable file" |
---|
5582 | - return "LIT file" |
---|
5583 | + return "immutable LIT file" |
---|
5584 | return "unknown" |
---|
5585 | |
---|
5586 | def render_title(self, ctx, data): |
---|
5587 | @@ -68,10 +70,10 @@ |
---|
5588 | |
---|
5589 | def render_directory_writecap(self, ctx, data): |
---|
5590 | node = self.original |
---|
5591 | - if node.is_readonly(): |
---|
5592 | - return "" |
---|
5593 | if not IDirectoryNode.providedBy(node): |
---|
5594 | return "" |
---|
5595 | + if node.is_readonly(): |
---|
5596 | + return "" |
---|
5597 | return ctx.tag[node.get_uri()] |
---|
5598 | |
---|
5599 | def render_directory_readcap(self, ctx, data): |
---|
5600 | @@ -86,27 +88,24 @@ |
---|
5601 | return "" |
---|
5602 | return ctx.tag[node.get_verify_cap().to_string()] |
---|
5603 | |
---|
5604 | - |
---|
5605 | def render_file_writecap(self, ctx, data): |
---|
5606 | node = self.original |
---|
5607 | if IDirectoryNode.providedBy(node): |
---|
5608 | node = node._node |
---|
5609 | - if ((IDirectoryNode.providedBy(node) or IFileNode.providedBy(node)) |
---|
5610 | - and node.is_readonly()): |
---|
5611 | - return "" |
---|
5612 | - writecap = node.get_uri() |
---|
5613 | - if not writecap: |
---|
5614 | + write_uri = node.get_write_uri() |
---|
5615 | + #print "write_uri = %r, node = %r" % (write_uri, node) |
---|
5616 | + if not write_uri: |
---|
5617 | return "" |
---|
5618 | - return ctx.tag[writecap] |
---|
5619 | + return ctx.tag[write_uri] |
---|
5620 | |
---|
5621 | def render_file_readcap(self, ctx, data): |
---|
5622 | node = self.original |
---|
5623 | if IDirectoryNode.providedBy(node): |
---|
5624 | node = node._node |
---|
5625 | - readcap = node.get_readonly_uri() |
---|
5626 | - if not readcap: |
---|
5627 | + read_uri = node.get_readonly_uri() |
---|
5628 | + if not read_uri: |
---|
5629 | return "" |
---|
5630 | - return ctx.tag[readcap] |
---|
5631 | + return ctx.tag[read_uri] |
---|
5632 | |
---|
5633 | def render_file_verifycap(self, ctx, data): |
---|
5634 | node = self.original |
---|
5635 | diff -rN -u old-tahoe/src/allmydata/web/root.py new-tahoe/src/allmydata/web/root.py |
---|
5636 | --- old-tahoe/src/allmydata/web/root.py 2010-01-24 05:52:02.625000000 +0000 |
---|
5637 | +++ new-tahoe/src/allmydata/web/root.py 2010-01-24 05:52:06.710000000 +0000 |
---|
5638 | @@ -12,7 +12,7 @@ |
---|
5639 | from allmydata import get_package_versions_string |
---|
5640 | from allmydata import provisioning |
---|
5641 | from allmydata.util import idlib, log |
---|
5642 | -from allmydata.interfaces import IFileNode, UnhandledCapTypeError |
---|
5643 | +from allmydata.interfaces import IFileNode |
---|
5644 | from allmydata.web import filenode, directory, unlinked, status, operations |
---|
5645 | from allmydata.web import reliability, storage |
---|
5646 | from allmydata.web.common import abbreviate_size, getxmlfile, WebError, \ |
---|
5647 | @@ -85,7 +85,7 @@ |
---|
5648 | try: |
---|
5649 | node = self.client.create_node_from_uri(name) |
---|
5650 | return directory.make_handler_for(node, self.client) |
---|
5651 | - except (TypeError, UnhandledCapTypeError, AssertionError): |
---|
5652 | + except (TypeError, AssertionError): |
---|
5653 | raise WebError("'%s' is not a valid file- or directory- cap" |
---|
5654 | % name) |
---|
5655 | |
---|
5656 | @@ -104,7 +104,7 @@ |
---|
5657 | # 'name' must be a file URI |
---|
5658 | try: |
---|
5659 | node = self.client.create_node_from_uri(name) |
---|
5660 | - except (TypeError, UnhandledCapTypeError, AssertionError): |
---|
5661 | + except (TypeError, AssertionError): |
---|
5662 | # I think this can no longer be reached |
---|
5663 | raise WebError("'%s' is not a valid file- or directory- cap" |
---|
5664 | % name) |
---|