1 | """ |
---|
2 | Integration tests for I2P support. |
---|
3 | """ |
---|
4 | |
---|
5 | import sys |
---|
6 | from os.path import join, exists |
---|
7 | from os import mkdir, environ |
---|
8 | from time import sleep |
---|
9 | from shutil import which |
---|
10 | |
---|
11 | from eliot import log_call |
---|
12 | |
---|
13 | import pytest |
---|
14 | import pytest_twisted |
---|
15 | |
---|
16 | from . import util |
---|
17 | |
---|
18 | from twisted.python.filepath import ( |
---|
19 | FilePath, |
---|
20 | ) |
---|
21 | from twisted.internet.error import ProcessExitedAlready |
---|
22 | |
---|
23 | from allmydata.test.common import ( |
---|
24 | write_introducer, |
---|
25 | ) |
---|
26 | from allmydata.node import read_config |
---|
27 | from allmydata.util.iputil import allocate_tcp_port |
---|
28 | |
---|
29 | |
---|
30 | if which("docker") is None: |
---|
31 | pytest.skip('Skipping I2P tests since Docker is unavailable', allow_module_level=True) |
---|
32 | # Docker on Windows machines sometimes expects Windows-y Docker images, so just |
---|
33 | # don't bother. |
---|
34 | if sys.platform.startswith('win'): |
---|
35 | pytest.skip('Skipping I2P tests on Windows', allow_module_level=True) |
---|
36 | |
---|
37 | |
---|
38 | @pytest.fixture |
---|
39 | def i2p_network(reactor, temp_dir, request): |
---|
40 | """Fixture to start up local i2pd.""" |
---|
41 | proto = util._MagicTextProtocol("ephemeral keys", "i2pd") |
---|
42 | reactor.spawnProcess( |
---|
43 | proto, |
---|
44 | which("docker"), |
---|
45 | ( |
---|
46 | "docker", "run", "-p", "7656:7656", "purplei2p/i2pd:release-2.45.1", |
---|
47 | # Bad URL for reseeds, so it can't talk to other routers. |
---|
48 | "--reseed.urls", "http://localhost:1/", |
---|
49 | # Make sure we see the "ephemeral keys message" |
---|
50 | "--log=stdout", |
---|
51 | "--loglevel=info" |
---|
52 | ), |
---|
53 | env=environ, |
---|
54 | ) |
---|
55 | |
---|
56 | def cleanup(): |
---|
57 | try: |
---|
58 | proto.transport.signalProcess("INT") |
---|
59 | util.block_with_timeout(proto.exited, reactor) |
---|
60 | except ProcessExitedAlready: |
---|
61 | pass |
---|
62 | request.addfinalizer(cleanup) |
---|
63 | |
---|
64 | util.block_with_timeout(proto.magic_seen, reactor, timeout=30) |
---|
65 | |
---|
66 | |
---|
67 | @pytest.fixture |
---|
68 | @log_call( |
---|
69 | action_type=u"integration:i2p:introducer", |
---|
70 | include_args=["temp_dir", "flog_gatherer"], |
---|
71 | include_result=False, |
---|
72 | ) |
---|
73 | def i2p_introducer(reactor, temp_dir, flog_gatherer, request): |
---|
74 | intro_dir = join(temp_dir, 'introducer_i2p') |
---|
75 | print("making introducer", intro_dir) |
---|
76 | |
---|
77 | if not exists(intro_dir): |
---|
78 | mkdir(intro_dir) |
---|
79 | done_proto = util._ProcessExitedProtocol() |
---|
80 | util._tahoe_runner_optional_coverage( |
---|
81 | done_proto, |
---|
82 | reactor, |
---|
83 | request, |
---|
84 | ( |
---|
85 | 'create-introducer', |
---|
86 | '--listen=i2p', |
---|
87 | intro_dir, |
---|
88 | ), |
---|
89 | ) |
---|
90 | pytest_twisted.blockon(done_proto.done) |
---|
91 | |
---|
92 | # over-write the config file with our stuff |
---|
93 | config = read_config(intro_dir, "tub.port") |
---|
94 | config.set_config("node", "nickname", "introducer_i2p") |
---|
95 | config.set_config("node", "web.port", "4563") |
---|
96 | config.set_config("node", "log_gatherer.furl", flog_gatherer) |
---|
97 | |
---|
98 | # "tahoe run" is consistent across Linux/macOS/Windows, unlike the old |
---|
99 | # "start" command. |
---|
100 | protocol = util._MagicTextProtocol('introducer running', "introducer") |
---|
101 | transport = util._tahoe_runner_optional_coverage( |
---|
102 | protocol, |
---|
103 | reactor, |
---|
104 | request, |
---|
105 | ( |
---|
106 | 'run', |
---|
107 | intro_dir, |
---|
108 | ), |
---|
109 | ) |
---|
110 | |
---|
111 | def cleanup(): |
---|
112 | try: |
---|
113 | transport.signalProcess('TERM') |
---|
114 | util.block_with_timeout(protocol.exited, reactor) |
---|
115 | except ProcessExitedAlready: |
---|
116 | pass |
---|
117 | request.addfinalizer(cleanup) |
---|
118 | |
---|
119 | pytest_twisted.blockon(protocol.magic_seen) |
---|
120 | return transport |
---|
121 | |
---|
122 | |
---|
123 | @pytest.fixture |
---|
124 | def i2p_introducer_furl(i2p_introducer, temp_dir): |
---|
125 | furl_fname = join(temp_dir, 'introducer_i2p', 'private', 'introducer.furl') |
---|
126 | while not exists(furl_fname): |
---|
127 | print("Don't see {} yet".format(furl_fname)) |
---|
128 | sleep(.1) |
---|
129 | furl = open(furl_fname, 'r').read() |
---|
130 | return furl |
---|
131 | |
---|
132 | |
---|
133 | @pytest_twisted.inlineCallbacks |
---|
134 | @pytest.mark.skip("I2P tests are not functioning at all, for unknown reasons") |
---|
135 | def test_i2p_service_storage(reactor, request, temp_dir, flog_gatherer, i2p_network, i2p_introducer_furl): |
---|
136 | web_port0 = allocate_tcp_port() |
---|
137 | web_port1 = allocate_tcp_port() |
---|
138 | yield _create_anonymous_node(reactor, 'carol_i2p', web_port0, request, temp_dir, flog_gatherer, i2p_network, i2p_introducer_furl) |
---|
139 | yield _create_anonymous_node(reactor, 'dave_i2p', web_port1, request, temp_dir, flog_gatherer, i2p_network, i2p_introducer_furl) |
---|
140 | # ensure both nodes are connected to "a grid" by uploading |
---|
141 | # something via carol, and retrieve it using dave. |
---|
142 | gold_path = join(temp_dir, "gold") |
---|
143 | with open(gold_path, "w") as f: |
---|
144 | f.write( |
---|
145 | "The object-capability model is a computer security model. A " |
---|
146 | "capability describes a transferable right to perform one (or " |
---|
147 | "more) operations on a given object." |
---|
148 | ) |
---|
149 | # XXX could use treq or similar to POST these to their respective |
---|
150 | # WUIs instead ... |
---|
151 | |
---|
152 | proto = util._CollectOutputProtocol() |
---|
153 | reactor.spawnProcess( |
---|
154 | proto, |
---|
155 | sys.executable, |
---|
156 | ( |
---|
157 | sys.executable, '-b', '-m', 'allmydata.scripts.runner', |
---|
158 | '-d', join(temp_dir, 'carol_i2p'), |
---|
159 | 'put', gold_path, |
---|
160 | ), |
---|
161 | env=environ, |
---|
162 | ) |
---|
163 | yield proto.done |
---|
164 | cap = proto.output.getvalue().strip().split()[-1] |
---|
165 | print("TEH CAP!", cap) |
---|
166 | |
---|
167 | proto = util._CollectOutputProtocol(capture_stderr=False) |
---|
168 | reactor.spawnProcess( |
---|
169 | proto, |
---|
170 | sys.executable, |
---|
171 | ( |
---|
172 | sys.executable, '-b', '-m', 'allmydata.scripts.runner', |
---|
173 | '-d', join(temp_dir, 'dave_i2p'), |
---|
174 | 'get', cap, |
---|
175 | ), |
---|
176 | env=environ, |
---|
177 | ) |
---|
178 | yield proto.done |
---|
179 | |
---|
180 | dave_got = proto.output.getvalue().strip() |
---|
181 | assert dave_got == open(gold_path, 'rb').read().strip() |
---|
182 | |
---|
183 | |
---|
184 | @pytest_twisted.inlineCallbacks |
---|
185 | def _create_anonymous_node(reactor, name, web_port, request, temp_dir, flog_gatherer, i2p_network, introducer_furl): |
---|
186 | node_dir = FilePath(temp_dir).child(name) |
---|
187 | |
---|
188 | print("creating", node_dir.path) |
---|
189 | node_dir.makedirs() |
---|
190 | proto = util._DumpOutputProtocol(None) |
---|
191 | reactor.spawnProcess( |
---|
192 | proto, |
---|
193 | sys.executable, |
---|
194 | ( |
---|
195 | sys.executable, '-b', '-m', 'allmydata.scripts.runner', |
---|
196 | 'create-node', |
---|
197 | '--nickname', name, |
---|
198 | '--introducer', introducer_furl, |
---|
199 | '--hide-ip', |
---|
200 | '--listen', 'i2p', |
---|
201 | node_dir.path, |
---|
202 | ), |
---|
203 | env=environ, |
---|
204 | ) |
---|
205 | yield proto.done |
---|
206 | |
---|
207 | |
---|
208 | # Which services should this client connect to? |
---|
209 | write_introducer(node_dir, "default", introducer_furl) |
---|
210 | with node_dir.child('tahoe.cfg').open('w') as f: |
---|
211 | node_config = ''' |
---|
212 | [node] |
---|
213 | nickname = %(name)s |
---|
214 | web.port = %(web_port)s |
---|
215 | web.static = public_html |
---|
216 | log_gatherer.furl = %(log_furl)s |
---|
217 | |
---|
218 | [i2p] |
---|
219 | enabled = true |
---|
220 | |
---|
221 | [client] |
---|
222 | shares.needed = 1 |
---|
223 | shares.happy = 1 |
---|
224 | shares.total = 2 |
---|
225 | |
---|
226 | ''' % { |
---|
227 | 'name': name, |
---|
228 | 'web_port': web_port, |
---|
229 | 'log_furl': flog_gatherer, |
---|
230 | } |
---|
231 | node_config = node_config.encode("utf-8") |
---|
232 | f.write(node_config) |
---|
233 | |
---|
234 | print("running") |
---|
235 | yield util._run_node(reactor, node_dir.path, request, None) |
---|
236 | print("okay, launched") |
---|