1 | """ |
---|
2 | Tests for the grid manager CLI. |
---|
3 | """ |
---|
4 | |
---|
5 | import os |
---|
6 | from io import ( |
---|
7 | BytesIO, |
---|
8 | ) |
---|
9 | from unittest import ( |
---|
10 | skipIf, |
---|
11 | ) |
---|
12 | |
---|
13 | from twisted.trial.unittest import ( |
---|
14 | TestCase, |
---|
15 | ) |
---|
16 | from allmydata.cli.grid_manager import ( |
---|
17 | grid_manager, |
---|
18 | ) |
---|
19 | |
---|
20 | import click.testing |
---|
21 | |
---|
22 | # these imports support the tests for `tahoe *` subcommands |
---|
23 | from ..common_util import ( |
---|
24 | run_cli, |
---|
25 | ) |
---|
26 | from ..common import ( |
---|
27 | superuser, |
---|
28 | ) |
---|
29 | from twisted.internet.defer import ( |
---|
30 | inlineCallbacks, |
---|
31 | ) |
---|
32 | from twisted.python.filepath import ( |
---|
33 | FilePath, |
---|
34 | ) |
---|
35 | from twisted.python.runtime import ( |
---|
36 | platform, |
---|
37 | ) |
---|
38 | from allmydata.util import jsonbytes as json |
---|
39 | |
---|
40 | class GridManagerCommandLine(TestCase): |
---|
41 | """ |
---|
42 | Test the mechanics of the `grid-manager` command |
---|
43 | """ |
---|
44 | |
---|
45 | def setUp(self): |
---|
46 | self.runner = click.testing.CliRunner() |
---|
47 | super(GridManagerCommandLine, self).setUp() |
---|
48 | |
---|
49 | def invoke_and_check(self, *args, **kwargs): |
---|
50 | """Invoke a command with the runner and ensure it succeeded.""" |
---|
51 | result = self.runner.invoke(*args, **kwargs) |
---|
52 | if result.exception is not None: |
---|
53 | raise result.exc_info[1].with_traceback(result.exc_info[2]) |
---|
54 | self.assertEqual(result.exit_code, 0, result) |
---|
55 | return result |
---|
56 | |
---|
57 | def test_create(self): |
---|
58 | """ |
---|
59 | Create a new grid-manager |
---|
60 | """ |
---|
61 | with self.runner.isolated_filesystem(): |
---|
62 | result = self.invoke_and_check(grid_manager, ["--config", "foo", "create"]) |
---|
63 | self.assertEqual(["foo"], os.listdir(".")) |
---|
64 | self.assertEqual(["config.json"], os.listdir("./foo")) |
---|
65 | result = self.invoke_and_check(grid_manager, ["--config", "foo", "public-identity"]) |
---|
66 | self.assertTrue(result.output.startswith("pub-v0-")) |
---|
67 | |
---|
68 | def test_load_invalid(self): |
---|
69 | """ |
---|
70 | An invalid config is reported to the user |
---|
71 | """ |
---|
72 | with self.runner.isolated_filesystem(): |
---|
73 | with open("config.json", "wb") as f: |
---|
74 | f.write(json.dumps_bytes({"not": "valid"})) |
---|
75 | result = self.runner.invoke(grid_manager, ["--config", ".", "public-identity"]) |
---|
76 | self.assertNotEqual(result.exit_code, 0) |
---|
77 | self.assertIn( |
---|
78 | "Error loading Grid Manager", |
---|
79 | result.output, |
---|
80 | ) |
---|
81 | |
---|
82 | def test_create_already(self): |
---|
83 | """ |
---|
84 | It's an error to create a new grid-manager in an existing |
---|
85 | directory. |
---|
86 | """ |
---|
87 | with self.runner.isolated_filesystem(): |
---|
88 | result = self.invoke_and_check(grid_manager, ["--config", "foo", "create"]) |
---|
89 | result = self.runner.invoke(grid_manager, ["--config", "foo", "create"]) |
---|
90 | self.assertEqual(1, result.exit_code) |
---|
91 | self.assertIn( |
---|
92 | "Can't create", |
---|
93 | result.stdout, |
---|
94 | ) |
---|
95 | |
---|
96 | def test_create_stdout(self): |
---|
97 | """ |
---|
98 | Create a new grid-manager with no files |
---|
99 | """ |
---|
100 | with self.runner.isolated_filesystem(): |
---|
101 | result = self.invoke_and_check(grid_manager, ["--config", "-", "create"]) |
---|
102 | self.assertEqual([], os.listdir(".")) |
---|
103 | config = json.loads(result.output) |
---|
104 | self.assertEqual( |
---|
105 | {"private_key", "grid_manager_config_version"}, |
---|
106 | set(config.keys()), |
---|
107 | ) |
---|
108 | |
---|
109 | def test_list_stdout(self): |
---|
110 | """ |
---|
111 | Load Grid Manager without files (using 'list' subcommand, but any will do) |
---|
112 | """ |
---|
113 | config = { |
---|
114 | "storage_servers": { |
---|
115 | "storage0": { |
---|
116 | "public_key": "pub-v0-cbq6hcf3pxcz6ouoafrbktmkixkeuywpcpbcomzd3lqbkq4nmfga" |
---|
117 | } |
---|
118 | }, |
---|
119 | "private_key": "priv-v0-6uinzyaxy3zvscwgsps5pxcfezhrkfb43kvnrbrhhfzyduyqnniq", |
---|
120 | "grid_manager_config_version": 0 |
---|
121 | } |
---|
122 | result = self.invoke_and_check( |
---|
123 | grid_manager, ["--config", "-", "list"], |
---|
124 | input=BytesIO(json.dumps_bytes(config)), |
---|
125 | ) |
---|
126 | self.assertEqual(result.exit_code, 0) |
---|
127 | self.assertEqual( |
---|
128 | "storage0: pub-v0-cbq6hcf3pxcz6ouoafrbktmkixkeuywpcpbcomzd3lqbkq4nmfga\n", |
---|
129 | result.output, |
---|
130 | ) |
---|
131 | |
---|
132 | def test_add_and_sign(self): |
---|
133 | """ |
---|
134 | Add a new storage-server and sign a certificate for it |
---|
135 | """ |
---|
136 | pubkey = "pub-v0-cbq6hcf3pxcz6ouoafrbktmkixkeuywpcpbcomzd3lqbkq4nmfga" |
---|
137 | with self.runner.isolated_filesystem(): |
---|
138 | self.invoke_and_check(grid_manager, ["--config", "foo", "create"]) |
---|
139 | self.invoke_and_check(grid_manager, ["--config", "foo", "add", "storage0", pubkey]) |
---|
140 | result = self.invoke_and_check(grid_manager, ["--config", "foo", "sign", "storage0", "10"]) |
---|
141 | sigcert = json.loads(result.output) |
---|
142 | self.assertEqual({"certificate", "signature"}, set(sigcert.keys())) |
---|
143 | cert = json.loads(sigcert['certificate']) |
---|
144 | self.assertEqual(cert["public_key"], pubkey) |
---|
145 | |
---|
146 | def test_add_and_sign_second_cert(self): |
---|
147 | """ |
---|
148 | Add a new storage-server and sign two certificates. |
---|
149 | """ |
---|
150 | pubkey = "pub-v0-cbq6hcf3pxcz6ouoafrbktmkixkeuywpcpbcomzd3lqbkq4nmfga" |
---|
151 | with self.runner.isolated_filesystem(): |
---|
152 | self.invoke_and_check(grid_manager, ["--config", "foo", "create"]) |
---|
153 | self.invoke_and_check(grid_manager, ["--config", "foo", "add", "storage0", pubkey]) |
---|
154 | self.invoke_and_check(grid_manager, ["--config", "foo", "sign", "storage0", "10"]) |
---|
155 | self.invoke_and_check(grid_manager, ["--config", "foo", "sign", "storage0", "10"]) |
---|
156 | # we should now have two certificates stored |
---|
157 | self.assertEqual( |
---|
158 | set(FilePath("foo").listdir()), |
---|
159 | {'storage0.cert.1', 'storage0.cert.0', 'config.json'}, |
---|
160 | ) |
---|
161 | |
---|
162 | def test_add_twice(self): |
---|
163 | """ |
---|
164 | An error is reported trying to add an existing server |
---|
165 | """ |
---|
166 | pubkey0 = "pub-v0-cbq6hcf3pxcz6ouoafrbktmkixkeuywpcpbcomzd3lqbkq4nmfga" |
---|
167 | pubkey1 = "pub-v0-5ysc55trfvfvg466v46j4zmfyltgus3y2gdejifctv7h4zkuyveq" |
---|
168 | with self.runner.isolated_filesystem(): |
---|
169 | self.invoke_and_check(grid_manager, ["--config", "foo", "create"]) |
---|
170 | self.invoke_and_check(grid_manager, ["--config", "foo", "add", "storage0", pubkey0]) |
---|
171 | result = self.runner.invoke(grid_manager, ["--config", "foo", "add", "storage0", pubkey1]) |
---|
172 | self.assertNotEquals(result.exit_code, 0) |
---|
173 | self.assertIn( |
---|
174 | "A storage-server called 'storage0' already exists", |
---|
175 | result.output, |
---|
176 | ) |
---|
177 | |
---|
178 | def test_add_list_remove(self): |
---|
179 | """ |
---|
180 | Add a storage server, list it, remove it. |
---|
181 | """ |
---|
182 | pubkey = "pub-v0-cbq6hcf3pxcz6ouoafrbktmkixkeuywpcpbcomzd3lqbkq4nmfga" |
---|
183 | with self.runner.isolated_filesystem(): |
---|
184 | self.invoke_and_check(grid_manager, ["--config", "foo", "create"]) |
---|
185 | self.invoke_and_check(grid_manager, ["--config", "foo", "add", "storage0", pubkey]) |
---|
186 | self.invoke_and_check(grid_manager, ["--config", "foo", "sign", "storage0", "1"]) |
---|
187 | |
---|
188 | result = self.invoke_and_check(grid_manager, ["--config", "foo", "list"]) |
---|
189 | names = [ |
---|
190 | line.split(':')[0] |
---|
191 | for line in result.output.strip().split('\n') |
---|
192 | if not line.startswith(" ") # "cert" lines start with whitespace |
---|
193 | ] |
---|
194 | self.assertEqual(names, ["storage0"]) |
---|
195 | |
---|
196 | self.invoke_and_check(grid_manager, ["--config", "foo", "remove", "storage0"]) |
---|
197 | |
---|
198 | result = self.invoke_and_check(grid_manager, ["--config", "foo", "list"]) |
---|
199 | self.assertEqual(result.output.strip(), "") |
---|
200 | |
---|
201 | def test_remove_missing(self): |
---|
202 | """ |
---|
203 | Error reported when removing non-existant server |
---|
204 | """ |
---|
205 | with self.runner.isolated_filesystem(): |
---|
206 | self.invoke_and_check(grid_manager, ["--config", "foo", "create"]) |
---|
207 | result = self.runner.invoke(grid_manager, ["--config", "foo", "remove", "storage0"]) |
---|
208 | self.assertNotEquals(result.exit_code, 0) |
---|
209 | self.assertIn( |
---|
210 | "No storage-server called 'storage0' exists", |
---|
211 | result.output, |
---|
212 | ) |
---|
213 | |
---|
214 | def test_sign_missing(self): |
---|
215 | """ |
---|
216 | Error reported when signing non-existant server |
---|
217 | """ |
---|
218 | with self.runner.isolated_filesystem(): |
---|
219 | self.invoke_and_check(grid_manager, ["--config", "foo", "create"]) |
---|
220 | result = self.runner.invoke(grid_manager, ["--config", "foo", "sign", "storage0", "42"]) |
---|
221 | self.assertNotEquals(result.exit_code, 0) |
---|
222 | self.assertIn( |
---|
223 | "No storage-server called 'storage0' exists", |
---|
224 | result.output, |
---|
225 | ) |
---|
226 | |
---|
227 | @skipIf(platform.isWindows(), "We don't know how to set permissions on Windows.") |
---|
228 | @skipIf(superuser, "cannot test as superuser with all permissions") |
---|
229 | def test_sign_bad_perms(self): |
---|
230 | """ |
---|
231 | Error reported if we can't create certificate file |
---|
232 | """ |
---|
233 | pubkey = "pub-v0-cbq6hcf3pxcz6ouoafrbktmkixkeuywpcpbcomzd3lqbkq4nmfga" |
---|
234 | with self.runner.isolated_filesystem(): |
---|
235 | self.invoke_and_check(grid_manager, ["--config", "foo", "create"]) |
---|
236 | self.invoke_and_check(grid_manager, ["--config", "foo", "add", "storage0", pubkey]) |
---|
237 | # make the directory un-writable (so we can't create a new cert) |
---|
238 | os.chmod("foo", 0o550) |
---|
239 | result = self.runner.invoke(grid_manager, ["--config", "foo", "sign", "storage0", "42"]) |
---|
240 | self.assertEquals(result.exit_code, 1) |
---|
241 | self.assertIn( |
---|
242 | "Permission denied", |
---|
243 | result.output, |
---|
244 | ) |
---|
245 | |
---|
246 | |
---|
247 | class TahoeAddGridManagerCert(TestCase): |
---|
248 | """ |
---|
249 | Test `tahoe admin add-grid-manager-cert` subcommand |
---|
250 | """ |
---|
251 | |
---|
252 | @inlineCallbacks |
---|
253 | def test_help(self): |
---|
254 | """ |
---|
255 | some kind of help is printed |
---|
256 | """ |
---|
257 | code, out, err = yield run_cli("admin", "add-grid-manager-cert") |
---|
258 | self.assertEqual(err, "") |
---|
259 | self.assertNotEqual(0, code) |
---|
260 | |
---|
261 | @inlineCallbacks |
---|
262 | def test_no_name(self): |
---|
263 | """ |
---|
264 | error to miss --name option |
---|
265 | """ |
---|
266 | code, out, err = yield run_cli( |
---|
267 | "admin", "add-grid-manager-cert", "--filename", "-", |
---|
268 | stdin=b"the cert", |
---|
269 | ) |
---|
270 | self.assertIn( |
---|
271 | "Must provide --name", |
---|
272 | out |
---|
273 | ) |
---|
274 | |
---|
275 | @inlineCallbacks |
---|
276 | def test_no_filename(self): |
---|
277 | """ |
---|
278 | error to miss --name option |
---|
279 | """ |
---|
280 | code, out, err = yield run_cli( |
---|
281 | "admin", "add-grid-manager-cert", "--name", "foo", |
---|
282 | stdin=b"the cert", |
---|
283 | ) |
---|
284 | self.assertIn( |
---|
285 | "Must provide --filename", |
---|
286 | out |
---|
287 | ) |
---|
288 | |
---|
289 | @inlineCallbacks |
---|
290 | def test_add_one(self): |
---|
291 | """ |
---|
292 | we can add a certificate |
---|
293 | """ |
---|
294 | nodedir = self.mktemp() |
---|
295 | fake_cert = b"""{"certificate": "", "signature": ""}""" |
---|
296 | |
---|
297 | code, out, err = yield run_cli( |
---|
298 | "--node-directory", nodedir, |
---|
299 | "admin", "add-grid-manager-cert", "-f", "-", "--name", "foo", |
---|
300 | stdin=fake_cert, |
---|
301 | ignore_stderr=True, |
---|
302 | ) |
---|
303 | nodepath = FilePath(nodedir) |
---|
304 | with nodepath.child("tahoe.cfg").open("r") as f: |
---|
305 | config_data = f.read() |
---|
306 | |
---|
307 | self.assertIn("tahoe.cfg", nodepath.listdir()) |
---|
308 | self.assertIn( |
---|
309 | b"foo = foo.cert", |
---|
310 | config_data, |
---|
311 | ) |
---|
312 | self.assertIn("foo.cert", nodepath.listdir()) |
---|
313 | with nodepath.child("foo.cert").open("r") as f: |
---|
314 | self.assertEqual( |
---|
315 | json.load(f), |
---|
316 | json.loads(fake_cert) |
---|
317 | ) |
---|