xref: /unit/test/test_configuration.py (revision 2616:ab2896c980ab)
1import socket
2
3import pytest
4
5from unit.control import Control
6
7prerequisites = {'modules': {'python': 'any'}}
8
9client = Control()
10
11
12def try_addr(addr):
13    return client.conf(
14        {
15            "listeners": {addr: {"pass": "routes"}},
16            "routes": [{"action": {"return": 200}}],
17            "applications": {},
18        }
19    )
20
21
22def test_json_empty():
23    assert 'error' in client.conf(''), 'empty'
24
25
26def test_json_leading_zero():
27    assert 'error' in client.conf('00'), 'leading zero'
28
29
30def test_json_unicode():
31    assert 'success' in client.conf(
32        """
33        {
34            "ap\u0070": {
35                "type": "\u0070ython",
36                "processes": { "spare": 0 },
37                "path": "\u002Fapp",
38                "module": "wsgi"
39            }
40        }
41        """,
42        'applications',
43    ), 'unicode'
44
45    assert client.conf_get('applications') == {
46        "app": {
47            "type": "python",
48            "processes": {"spare": 0},
49            "path": "/app",
50            "module": "wsgi",
51        }
52    }, 'unicode get'
53
54
55def test_json_unicode_2():
56    assert 'success' in client.conf(
57        {
58            "приложение": {
59                "type": "python",
60                "processes": {"spare": 0},
61                "path": "/app",
62                "module": "wsgi",
63            }
64        },
65        'applications',
66    ), 'unicode 2'
67
68    assert 'приложение' in client.conf_get('applications')
69
70
71def test_json_unicode_number():
72    assert 'success' in client.conf(
73        """
74        {
75            "app": {
76                "type": "python",
77                "processes": { "spare": \u0030 },
78                "path": "/app",
79                "module": "wsgi"
80            }
81        }
82        """,
83        'applications',
84    ), 'unicode number'
85
86
87def test_json_utf8_bom():
88    assert 'success' in client.conf(
89        b"""\xEF\xBB\xBF
90        {
91            "app": {
92                "type": "python",
93                "processes": {"spare": 0},
94                "path": "/app",
95                "module": "wsgi"
96            }
97        }
98        """,
99        'applications',
100    ), 'UTF-8 BOM'
101
102
103def test_json_comment_single_line():
104    assert 'success' in client.conf(
105        b"""
106        // this is bridge
107        {
108            "//app": {
109                "type": "python", // end line
110                "processes": {"spare": 0},
111                // inside of block
112                "path": "/app",
113                "module": "wsgi"
114            }
115            // double //
116        }
117        // end of json \xEF\t
118        """,
119        'applications',
120    ), 'single line comments'
121
122
123def test_json_comment_multi_line():
124    assert 'success' in client.conf(
125        b"""
126        /* this is bridge */
127        {
128            "/*app": {
129            /**
130             * multiple lines
131             **/
132                "type": "python",
133                "processes": /* inline */ {"spare": 0},
134                "path": "/app",
135                "module": "wsgi"
136                /*
137                // end of block */
138            }
139            /* blah * / blah /* blah */
140        }
141        /* end of json \xEF\t\b */
142        """,
143        'applications',
144    ), 'multi line comments'
145
146
147def test_json_comment_invalid():
148    assert 'error' in client.conf(b'/{}', 'applications'), 'slash'
149    assert 'error' in client.conf(b'//{}', 'applications'), 'comment'
150    assert 'error' in client.conf(b'{} /', 'applications'), 'slash end'
151    assert 'error' in client.conf(b'/*{}', 'applications'), 'slash star'
152    assert 'error' in client.conf(b'{} /*', 'applications'), 'slash star end'
153
154
155def test_applications_open_brace():
156    assert 'error' in client.conf('{', 'applications'), 'open brace'
157
158
159def test_applications_string():
160    assert 'error' in client.conf('"{}"', 'applications'), 'string'
161
162
163@pytest.mark.skip('not yet, unsafe')
164def test_applications_type_only():
165    assert 'error' in client.conf(
166        {"app": {"type": "python"}}, 'applications'
167    ), 'type only'
168
169
170def test_applications_miss_quote():
171    assert 'error' in client.conf(
172        """
173        {
174            app": {
175                "type": "python",
176                "processes": { "spare": 0 },
177                "path": "/app",
178                "module": "wsgi"
179            }
180        }
181        """,
182        'applications',
183    ), 'miss quote'
184
185
186def test_applications_miss_colon():
187    assert 'error' in client.conf(
188        """
189        {
190            "app" {
191                "type": "python",
192                "processes": { "spare": 0 },
193                "path": "/app",
194                "module": "wsgi"
195            }
196        }
197        """,
198        'applications',
199    ), 'miss colon'
200
201
202def test_applications_miss_comma():
203    assert 'error' in client.conf(
204        """
205        {
206            "app": {
207                "type": "python"
208                "processes": { "spare": 0 },
209                "path": "/app",
210                "module": "wsgi"
211            }
212        }
213        """,
214        'applications',
215    ), 'miss comma'
216
217
218def test_applications_skip_spaces():
219    assert 'success' in client.conf(b'{ \n\r\t}', 'applications'), 'skip spaces'
220
221
222def test_applications_relative_path():
223    assert 'success' in client.conf(
224        {
225            "app": {
226                "type": "python",
227                "processes": {"spare": 0},
228                "path": "../app",
229                "module": "wsgi",
230            }
231        },
232        'applications',
233    ), 'relative path'
234
235
236@pytest.mark.skip('not yet, unsafe')
237def test_listeners_empty():
238    assert 'error' in client.conf({"*:8080": {}}, 'listeners'), 'listener empty'
239
240
241def test_listeners_no_app():
242    assert 'error' in client.conf(
243        {"*:8080": {"pass": "applications/app"}}, 'listeners'
244    ), 'listeners no app'
245
246
247def test_listeners_unix_abstract(system):
248    if system != 'Linux':
249        assert 'error' in try_addr("unix:@sock"), 'abstract at'
250
251    pytest.skip('not yet')
252
253    assert 'error' in try_addr("unix:\0soc"), 'abstract \0'
254    assert 'error' in try_addr("unix:\u0000soc"), 'abstract \0 unicode'
255
256
257def test_listeners_addr():
258    assert 'success' in try_addr("*:8080"), 'wildcard'
259    assert 'success' in try_addr("127.0.0.1:8081"), 'explicit'
260    assert 'success' in try_addr("[::1]:8082"), 'explicit ipv6'
261
262
263def test_listeners_addr_error():
264    assert 'error' in try_addr("127.0.0.1"), 'no port'
265
266
267def test_listeners_addr_error_2(skip_alert):
268    skip_alert(r'bind.*failed', r'failed to apply new conf')
269
270    assert 'error' in try_addr("[f607:7403:1e4b:6c66:33b2:843f:2517:da27]:8080")
271
272
273def test_listeners_port_release():
274    for _ in range(10):
275        fail = False
276        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
277            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
278
279            client.conf(
280                {
281                    "listeners": {"127.0.0.1:8080": {"pass": "routes"}},
282                    "routes": [],
283                }
284            )
285
286            resp = client.conf({"listeners": {}, "applications": {}})
287
288            try:
289                s.bind(('127.0.0.1', 8080))
290                s.listen()
291
292            except OSError:
293                fail = True
294
295            if fail:
296                pytest.fail('cannot bind or listen to the address')
297
298            assert 'success' in resp, 'port release'
299
300
301def test_json_application_name_large():
302    name = "X" * 1024 * 1024
303
304    assert 'success' in client.conf(
305        {
306            "listeners": {"*:8080": {"pass": f"applications/{name}"}},
307            "applications": {
308                name: {
309                    "type": "python",
310                    "processes": {"spare": 0},
311                    "path": "/app",
312                    "module": "wsgi",
313                }
314            },
315        }
316    )
317
318
319@pytest.mark.skip('not yet')
320def test_json_application_many():
321    apps = 999
322
323    conf = {
324        "applications": {
325            f"app-{a}": {
326                "type": "python",
327                "processes": {"spare": 0},
328                "path": "/app",
329                "module": "wsgi",
330            }
331            for a in range(apps)
332        },
333        "listeners": {
334            f"*:{(7000 + a)}": {"pass": f"applications/app-{a}"}
335            for a in range(apps)
336        },
337    }
338
339    assert 'success' in client.conf(conf)
340
341
342def test_json_application_python_prefix():
343    conf = {
344        "applications": {
345            "sub-app": {
346                "type": "python",
347                "processes": {"spare": 0},
348                "path": "/app",
349                "module": "wsgi",
350                "prefix": "/app",
351            }
352        },
353        "listeners": {"*:8080": {"pass": "routes"}},
354        "routes": [
355            {
356                "match": {"uri": "/app/*"},
357                "action": {"pass": "applications/sub-app"},
358            }
359        ],
360    }
361
362    assert 'success' in client.conf(conf)
363
364
365def test_json_application_prefix_target():
366    conf = {
367        "applications": {
368            "sub-app": {
369                "type": "python",
370                "processes": {"spare": 0},
371                "path": "/app",
372                "targets": {
373                    "foo": {"module": "foo.wsgi", "prefix": "/app"},
374                    "bar": {
375                        "module": "bar.wsgi",
376                        "callable": "bar",
377                        "prefix": "/api",
378                    },
379                },
380            }
381        },
382        "listeners": {"*:8080": {"pass": "routes"}},
383        "routes": [
384            {
385                "match": {"uri": "/app/*"},
386                "action": {"pass": "applications/sub-app/foo"},
387            },
388            {
389                "match": {"uri": "/api/*"},
390                "action": {"pass": "applications/sub-app/bar"},
391            },
392        ],
393    }
394
395    assert 'success' in client.conf(conf)
396
397
398def test_json_application_invalid_python_prefix():
399    conf = {
400        "applications": {
401            "sub-app": {
402                "type": "python",
403                "processes": {"spare": 0},
404                "path": "/app",
405                "module": "wsgi",
406                "prefix": "app",
407            }
408        },
409        "listeners": {"*:8080": {"pass": "applications/sub-app"}},
410    }
411
412    assert 'error' in client.conf(conf)
413
414
415def test_json_application_empty_python_prefix():
416    conf = {
417        "applications": {
418            "sub-app": {
419                "type": "python",
420                "processes": {"spare": 0},
421                "path": "/app",
422                "module": "wsgi",
423                "prefix": "",
424            }
425        },
426        "listeners": {"*:8080": {"pass": "applications/sub-app"}},
427    }
428
429    assert 'error' in client.conf(conf)
430
431
432def test_json_application_many2():
433    conf = {
434        "applications": {
435            f"app-{a}": {
436                "type": "python",
437                "processes": {"spare": 0},
438                "path": "/app",
439                "module": "wsgi",
440            }
441            # Larger number of applications can cause test fail with default
442            # open files limit due to the lack of file descriptors.
443            for a in range(100)
444        },
445        "listeners": {"*:8080": {"pass": "applications/app-1"}},
446    }
447
448    assert 'success' in client.conf(conf)
449
450
451def test_unprivileged_user_error(require, skip_alert):
452    require({'privileged_user': False})
453
454    skip_alert(r'cannot set user "root"', r'failed to apply new conf')
455
456    assert 'error' in client.conf(
457        {
458            "app": {
459                "type": "external",
460                "processes": 1,
461                "executable": "/app",
462                "user": "root",
463            }
464        },
465        'applications',
466    ), 'setting user'
467