source: trunk/integration/test_grid_manager.py

Last change on this file was 45898ff8, checked in by meejah <meejah@…>, at 2023-07-25T02:08:41Z

refactor: make sftp tests (etc) work with 'grid' refactoring

  • Property mode set to 100644
File size: 11.4 KB
Line 
1import sys
2import json
3from os.path import join
4
5from cryptography.hazmat.primitives.serialization import (
6    Encoding,
7    PublicFormat,
8)
9
10from twisted.internet.utils import (
11    getProcessOutputAndValue,
12)
13from twisted.internet.defer import (
14    inlineCallbacks,
15    returnValue,
16)
17
18from allmydata.crypto import ed25519
19from allmydata.util import base32
20from allmydata.util import configutil
21
22from . import util
23from .grid import (
24    create_grid,
25)
26
27import pytest_twisted
28
29
30@inlineCallbacks
31def _run_gm(reactor, request, *args, **kwargs):
32    """
33    Run the grid-manager process, passing all arguments as extra CLI
34    args.
35
36    :returns: all process output
37    """
38    if request.config.getoption('coverage'):
39        base_args = ("-b", "-m", "coverage", "run", "-m", "allmydata.cli.grid_manager")
40    else:
41        base_args = ("-m", "allmydata.cli.grid_manager")
42
43    output, errput, exit_code = yield getProcessOutputAndValue(
44        sys.executable,
45        base_args + args,
46        reactor=reactor,
47        **kwargs
48    )
49    if exit_code != 0:
50        raise util.ProcessFailed(
51            RuntimeError("Exit code {}".format(exit_code)),
52            output + errput,
53        )
54    returnValue(output)
55
56
57@pytest_twisted.inlineCallbacks
58def test_create_certificate(reactor, request):
59    """
60    The Grid Manager produces a valid, correctly-signed certificate.
61    """
62    gm_config = yield _run_gm(reactor, request, "--config", "-", "create")
63    privkey_bytes = json.loads(gm_config)['private_key'].encode('ascii')
64    privkey, pubkey = ed25519.signing_keypair_from_string(privkey_bytes)
65
66    # Note that zara + her key here are arbitrary and don't match any
67    # "actual" clients in the test-grid; we're just checking that the
68    # Grid Manager signs this properly.
69    gm_config = yield _run_gm(
70        reactor, request, "--config", "-", "add",
71        "zara", "pub-v0-kzug3ut2m7ziihf3ndpqlquuxeie4foyl36wn54myqc4wmiwe4ga",
72        stdinBytes=gm_config,
73    )
74    zara_cert_bytes = yield _run_gm(
75        reactor,  request, "--config", "-", "sign", "zara", "1",
76        stdinBytes=gm_config,
77    )
78    zara_cert = json.loads(zara_cert_bytes)
79
80    # confirm that zara's certificate is made by the Grid Manager
81    # (.verify returns None on success, raises exception on error)
82    pubkey.verify(
83        base32.a2b(zara_cert['signature'].encode('ascii')),
84        zara_cert['certificate'].encode('ascii'),
85    )
86
87
88@pytest_twisted.inlineCallbacks
89def test_remove_client(reactor, request):
90    """
91    A Grid Manager can add and successfully remove a client
92    """
93    gm_config = yield _run_gm(
94        reactor, request, "--config", "-", "create",
95    )
96
97    gm_config = yield _run_gm(
98        reactor, request, "--config", "-", "add",
99        "zara", "pub-v0-kzug3ut2m7ziihf3ndpqlquuxeie4foyl36wn54myqc4wmiwe4ga",
100        stdinBytes=gm_config,
101    )
102    gm_config = yield _run_gm(
103        reactor, request, "--config", "-", "add",
104        "yakov", "pub-v0-kvxhb3nexybmipkrar2ztfrwp4uxxsmrjzkpzafit3ket4u5yldq",
105        stdinBytes=gm_config,
106    )
107    assert "zara" in json.loads(gm_config)['storage_servers']
108    assert "yakov" in json.loads(gm_config)['storage_servers']
109
110    gm_config = yield _run_gm(
111        reactor, request, "--config", "-", "remove",
112        "zara",
113        stdinBytes=gm_config,
114    )
115    assert "zara" not in json.loads(gm_config)['storage_servers']
116    assert "yakov" in json.loads(gm_config)['storage_servers']
117
118
119@pytest_twisted.inlineCallbacks
120def test_remove_last_client(reactor, request):
121    """
122    A Grid Manager can remove all clients
123    """
124    gm_config = yield _run_gm(
125        reactor, request, "--config", "-", "create",
126    )
127
128    gm_config = yield _run_gm(
129        reactor, request, "--config", "-", "add",
130        "zara", "pub-v0-kzug3ut2m7ziihf3ndpqlquuxeie4foyl36wn54myqc4wmiwe4ga",
131        stdinBytes=gm_config,
132    )
133    assert "zara" in json.loads(gm_config)['storage_servers']
134
135    gm_config = yield _run_gm(
136        reactor, request, "--config", "-", "remove",
137        "zara",
138        stdinBytes=gm_config,
139    )
140    # there are no storage servers left at all now
141    assert "storage_servers" not in json.loads(gm_config)
142
143
144@pytest_twisted.inlineCallbacks
145def test_add_remove_client_file(reactor, request, temp_dir):
146    """
147    A Grid Manager can add and successfully remove a client (when
148    keeping data on disk)
149    """
150    gmconfig = join(temp_dir, "gmtest")
151    gmconfig_file = join(temp_dir, "gmtest", "config.json")
152    yield _run_gm(
153        reactor, request, "--config", gmconfig, "create",
154    )
155
156    yield _run_gm(
157        reactor, request, "--config", gmconfig, "add",
158        "zara", "pub-v0-kzug3ut2m7ziihf3ndpqlquuxeie4foyl36wn54myqc4wmiwe4ga",
159    )
160    yield _run_gm(
161        reactor, request, "--config", gmconfig, "add",
162        "yakov", "pub-v0-kvxhb3nexybmipkrar2ztfrwp4uxxsmrjzkpzafit3ket4u5yldq",
163    )
164    assert "zara" in json.load(open(gmconfig_file, "r"))['storage_servers']
165    assert "yakov" in json.load(open(gmconfig_file, "r"))['storage_servers']
166
167    yield _run_gm(
168        reactor, request, "--config", gmconfig, "remove",
169        "zara",
170    )
171    assert "zara" not in json.load(open(gmconfig_file, "r"))['storage_servers']
172    assert "yakov" in json.load(open(gmconfig_file, "r"))['storage_servers']
173
174
175@pytest_twisted.inlineCallbacks
176def _test_reject_storage_server(reactor, request, temp_dir, flog_gatherer, port_allocator):
177    """
178    A client with happines=2 fails to upload to a Grid when it is
179    using Grid Manager and there is only 1 storage server with a valid
180    certificate.
181    """
182    grid = yield create_grid(reactor, request, temp_dir, flog_gatherer, port_allocator)
183    storage0 = yield grid.add_storage_node()
184    _ = yield grid.add_storage_node()
185
186    gm_config = yield _run_gm(
187        reactor, request, "--config", "-", "create",
188    )
189    gm_privkey_bytes = json.loads(gm_config)['private_key'].encode('ascii')
190    gm_privkey, gm_pubkey = ed25519.signing_keypair_from_string(gm_privkey_bytes)
191
192    # create certificate for the first storage-server
193    pubkey_fname = join(storage0.process.node_dir, "node.pubkey")
194    with open(pubkey_fname, 'r') as f:
195        pubkey_str = f.read().strip()
196
197    gm_config = yield _run_gm(
198        reactor, request, "--config", "-", "add",
199        "storage0", pubkey_str,
200        stdinBytes=gm_config,
201    )
202    assert json.loads(gm_config)['storage_servers'].keys() == {'storage0'}
203
204    print("inserting certificate")
205    cert = yield _run_gm(
206        reactor, request, "--config", "-", "sign", "storage0", "1",
207        stdinBytes=gm_config,
208    )
209    print(cert)
210
211    yield util.run_tahoe(
212        reactor, request, "--node-directory", storage0.process.node_dir,
213        "admin", "add-grid-manager-cert",
214        "--name", "default",
215        "--filename", "-",
216        stdin=cert,
217    )
218
219    # re-start this storage server
220    yield storage0.restart(reactor, request)
221
222    # now only one storage-server has the certificate .. configure
223    # diana to have the grid-manager certificate
224
225    diana = yield grid.add_client("diana", needed=2, happy=2, total=2)
226
227    config = configutil.get_config(join(diana.process.node_dir, "tahoe.cfg"))
228    config.add_section("grid_managers")
229    config.set("grid_managers", "test", str(ed25519.string_from_verifying_key(gm_pubkey), "ascii"))
230    with open(join(diana.process.node_dir, "tahoe.cfg"), "w") as f:
231        config.write(f)
232
233    yield diana.restart(reactor, request, servers=2)
234
235    # try to put something into the grid, which should fail (because
236    # diana has happy=2 but should only find storage0 to be acceptable
237    # to upload to)
238
239    try:
240        yield util.run_tahoe(
241            reactor, request, "--node-directory", diana.process.node_dir,
242            "put", "-",
243            stdin=b"some content\n" * 200,
244        )
245        assert False, "Should get a failure"
246    except util.ProcessFailed as e:
247        if b'UploadUnhappinessError' in e.output:
248            # We're done! We've succeeded.
249            return
250
251    assert False, "Failed to see one of out of two servers"
252
253
254@pytest_twisted.inlineCallbacks
255def _test_accept_storage_server(reactor, request, temp_dir, flog_gatherer, port_allocator):
256    """
257    Successfully upload to a Grid Manager enabled Grid.
258    """
259    grid = yield create_grid(reactor, request, temp_dir, flog_gatherer, port_allocator)
260    happy0 = yield grid.add_storage_node()
261    happy1 = yield grid.add_storage_node()
262
263    gm_config = yield _run_gm(
264        reactor, request, "--config", "-", "create",
265    )
266    gm_privkey_bytes = json.loads(gm_config)['private_key'].encode('ascii')
267    gm_privkey, gm_pubkey = ed25519.signing_keypair_from_string(gm_privkey_bytes)
268
269    # create certificates for all storage-servers
270    servers = (
271        ("happy0", happy0),
272        ("happy1", happy1),
273    )
274    for st_name, st in servers:
275        pubkey_fname = join(st.process.node_dir, "node.pubkey")
276        with open(pubkey_fname, 'r') as f:
277            pubkey_str = f.read().strip()
278
279        gm_config = yield _run_gm(
280            reactor, request, "--config", "-", "add",
281            st_name, pubkey_str,
282            stdinBytes=gm_config,
283        )
284    assert json.loads(gm_config)['storage_servers'].keys() == {'happy0', 'happy1'}
285
286    # add the certificates from the grid-manager to the storage servers
287    print("inserting storage-server certificates")
288    for st_name, st in servers:
289        cert = yield _run_gm(
290            reactor, request, "--config", "-", "sign", st_name, "1",
291            stdinBytes=gm_config,
292        )
293
294        yield util.run_tahoe(
295            reactor, request, "--node-directory", st.process.node_dir,
296            "admin", "add-grid-manager-cert",
297            "--name", "default",
298            "--filename", "-",
299            stdin=cert,
300        )
301
302    # re-start the storage servers
303    yield happy0.restart(reactor, request)
304    yield happy1.restart(reactor, request)
305
306    # configure freya (a client) to have the grid-manager certificate
307    freya = yield grid.add_client("freya", needed=2, happy=2, total=2)
308
309    config = configutil.get_config(join(freya.process.node_dir, "tahoe.cfg"))
310    config.add_section("grid_managers")
311    config.set("grid_managers", "test", str(ed25519.string_from_verifying_key(gm_pubkey), "ascii"))
312    with open(join(freya.process.node_dir, "tahoe.cfg"), "w") as f:
313        config.write(f)
314
315    yield freya.restart(reactor, request, servers=2)
316
317    # confirm that Freya will upload to the GridManager-enabled Grid
318    yield util.run_tahoe(
319        reactor, request, "--node-directory", freya.process.node_dir,
320        "put", "-",
321        stdin=b"some content\n" * 200,
322    )
323
324
325@pytest_twisted.inlineCallbacks
326def test_identity(reactor, request, temp_dir):
327    """
328    Dump public key to CLI
329    """
330    gm_config = join(temp_dir, "test_identity")
331    yield _run_gm(
332        reactor, request, "--config", gm_config, "create",
333    )
334
335    # ask the CLI for the grid-manager pubkey
336    pubkey = yield _run_gm(
337        reactor, request, "--config", gm_config, "public-identity",
338    )
339    alleged_pubkey = ed25519.verifying_key_from_string(pubkey.strip())
340
341    # load the grid-manager pubkey "ourselves"
342    with open(join(gm_config, "config.json"), "r") as f:
343        real_config = json.load(f)
344    real_privkey, real_pubkey = ed25519.signing_keypair_from_string(
345        real_config["private_key"].encode("ascii"),
346    )
347
348    # confirm the CLI told us the correct thing
349    alleged_bytes = alleged_pubkey.public_bytes(Encoding.Raw, PublicFormat.Raw)
350    real_bytes = real_pubkey.public_bytes(Encoding.Raw, PublicFormat.Raw)
351    assert alleged_bytes == real_bytes, "Keys don't match"
Note: See TracBrowser for help on using the repository browser.