Merge ~hloeung/content-cache-charm:add-haproxy-v2 into content-cache-charm:master
- Git
- lp:~hloeung/content-cache-charm
- add-haproxy-v2
- Merge into master
Proposed by
Haw Loeung
Status: | Merged |
---|---|
Approved by: | Haw Loeung |
Approved revision: | d63023e1646c17171c11c8962d77739d858d7105 |
Merged at revision: | 16f2684a0ce7bf2bb03dc31d968e951f18161865 |
Proposed branch: | ~hloeung/content-cache-charm:add-haproxy-v2 |
Merge into: | content-cache-charm:master |
Diff against target: |
491 lines (+385/-5) 10 files modified
layer.yaml (+1/-0) lib/haproxy.py (+100/-0) reactive/content_cache.py (+8/-1) templates/haproxy_cfg.tmpl (+46/-0) tests/unit/files/config_test_config.txt (+23/-0) tests/unit/files/haproxy_config_rendered_backends_stanzas_test_output.txt (+24/-0) tests/unit/files/haproxy_config_rendered_listen_stanzas_test_output.txt (+12/-0) tests/unit/files/haproxy_config_rendered_test_output.txt (+75/-0) tests/unit/test_content_cache.py (+24/-4) tests/unit/test_haproxy.py (+72/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Joel Sing (community) | +1 | Approve | |
Haw Loeung | Pending | ||
Canonical IS Reviewers | Pending | ||
Review via email: mp+364417@code.launchpad.net |
This proposal supersedes a proposal from 2019-03-11.
Commit message
Add HAProxy configuration generation and service start/restart
Description of the change
To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : Posted in a previous version of this proposal | # |
Revision history for this message
Joel Sing (jsing) wrote : Posted in a previous version of this proposal | # |
Generally looks good - handful of mostly minor things to address.
review:
Needs Fixing
Revision history for this message
Haw Loeung (hloeung) : Posted in a previous version of this proposal | # |
Revision history for this message
Haw Loeung (hloeung) wrote : Posted in a previous version of this proposal | # |
New MP with fixes and this as a pre-req - https:/
Revision history for this message
Haw Loeung (hloeung) : Posted in a previous version of this proposal | # |
review:
Needs Resubmitting
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
This merge proposal is being monitored by mergebot. Change the status to Approved to merge.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
Change successfully merged at revision 16f2684a0ce7bf2
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/layer.yaml b/layer.yaml | |||
2 | index 253b93a..d102da7 100644 | |||
3 | --- a/layer.yaml | |||
4 | +++ b/layer.yaml | |||
5 | @@ -9,3 +9,4 @@ options: | |||
6 | 9 | packages: | 9 | packages: |
7 | 10 | - haproxy | 10 | - haproxy |
8 | 11 | - nginx | 11 | - nginx |
9 | 12 | - python3-jinja2 | ||
10 | diff --git a/lib/haproxy.py b/lib/haproxy.py | |||
11 | 12 | new file mode 100644 | 13 | new file mode 100644 |
12 | index 0000000..a64f3af | |||
13 | --- /dev/null | |||
14 | +++ b/lib/haproxy.py | |||
15 | @@ -0,0 +1,100 @@ | |||
16 | 1 | import os | ||
17 | 2 | |||
18 | 3 | import jinja2 | ||
19 | 4 | |||
20 | 5 | |||
21 | 6 | HAPROXY_BASE_PATH = '/etc/haproxy' | ||
22 | 7 | INDENT = ' '*4 | ||
23 | 8 | |||
24 | 9 | |||
25 | 10 | class HAProxyConf: | ||
26 | 11 | |||
27 | 12 | def __init__(self, conf_path=HAPROXY_BASE_PATH): | ||
28 | 13 | self._conf_path = conf_path | ||
29 | 14 | |||
30 | 15 | @property | ||
31 | 16 | def conf_path(self): | ||
32 | 17 | return self._conf_path | ||
33 | 18 | |||
34 | 19 | @property | ||
35 | 20 | def conf_file(self): | ||
36 | 21 | return os.path.join(self._conf_path, 'haproxy.cfg') | ||
37 | 22 | |||
38 | 23 | def _generate_stanza_name(self, name): | ||
39 | 24 | return name.replace('.', '-')[0:32] | ||
40 | 25 | |||
41 | 26 | def render_stanza_listen(self, config): | ||
42 | 27 | listen_stanza = """ | ||
43 | 28 | listen {name} | ||
44 | 29 | {indent}bind 0.0.0.0:{port}{tls} | ||
45 | 30 | {indent}default_backend cached-{name} | ||
46 | 31 | """ | ||
47 | 32 | rendered_output = [] | ||
48 | 33 | for site in config.keys(): | ||
49 | 34 | default_port = 80 | ||
50 | 35 | tls_config = '' | ||
51 | 36 | |||
52 | 37 | tls_cert_bundle_path = config[site].get('tls-cert-bundle-path') | ||
53 | 38 | if tls_cert_bundle_path: | ||
54 | 39 | default_port = 443 | ||
55 | 40 | tls_config = ' ssl crt {}'.format(tls_cert_bundle_path) | ||
56 | 41 | |||
57 | 42 | port = config[site].get('port', default_port) | ||
58 | 43 | |||
59 | 44 | output = listen_stanza.format(name=self._generate_stanza_name(site), | ||
60 | 45 | port=port, tls=tls_config, indent=INDENT) | ||
61 | 46 | rendered_output.append(output) | ||
62 | 47 | |||
63 | 48 | return rendered_output | ||
64 | 49 | |||
65 | 50 | def render_stanza_backend(self, config): | ||
66 | 51 | backend_stanza = """ | ||
67 | 52 | backend cached-{name} | ||
68 | 53 | {indent}option httpchk HEAD / HTTP/1.0\\r\\nHost:\\ {site}\\r\\nUser-Agent:\\ haproxy/httpchk | ||
69 | 54 | {indent}http-request set-header Host {site} | ||
70 | 55 | {indent}balance leastconn | ||
71 | 56 | {backends} | ||
72 | 57 | """ | ||
73 | 58 | rendered_output = [] | ||
74 | 59 | for site in config.keys(): | ||
75 | 60 | tls_config = '' | ||
76 | 61 | if config[site].get('backend-tls'): | ||
77 | 62 | tls_config = ' ssl sni str({site}) check-sni {site} verify required ca-file ca-certificates.crt' \ | ||
78 | 63 | .format(site=site) | ||
79 | 64 | backends = [] | ||
80 | 65 | count = 0 | ||
81 | 66 | for backend in config[site]['backends']: | ||
82 | 67 | count += 1 | ||
83 | 68 | name = 'server_{}'.format(count) | ||
84 | 69 | backends.append('{indent}server {name} {backend} check inter 5000 rise 2 fall 5 maxconn 16{tls}' | ||
85 | 70 | .format(name=name, backend=backend, tls=tls_config, indent=INDENT)) | ||
86 | 71 | |||
87 | 72 | output = backend_stanza.format(name=self._generate_stanza_name(site), | ||
88 | 73 | site=site, backends='\n'.join(backends), indent=INDENT) | ||
89 | 74 | |||
90 | 75 | rendered_output.append(output) | ||
91 | 76 | |||
92 | 77 | return rendered_output | ||
93 | 78 | |||
94 | 79 | def render(self, config, num_procs): | ||
95 | 80 | base = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) | ||
96 | 81 | env = jinja2.Environment(loader=jinja2.FileSystemLoader(base)) | ||
97 | 82 | template = env.get_template('templates/haproxy_cfg.tmpl') | ||
98 | 83 | return template.render({ | ||
99 | 84 | 'listen': self.render_stanza_listen(config), | ||
100 | 85 | 'backend': self.render_stanza_backend(config), | ||
101 | 86 | 'num_procs': num_procs, | ||
102 | 87 | }) | ||
103 | 88 | |||
104 | 89 | def write(self, content): | ||
105 | 90 | # Check if contents changed | ||
106 | 91 | try: | ||
107 | 92 | with open(self.conf_file, 'r', encoding='utf-8') as f: | ||
108 | 93 | current = f.read() | ||
109 | 94 | except FileNotFoundError: | ||
110 | 95 | current = '' | ||
111 | 96 | if content == current: | ||
112 | 97 | return False | ||
113 | 98 | with open(self.conf_file, 'w', encoding='utf-8') as f: | ||
114 | 99 | f.write(content) | ||
115 | 100 | return True | ||
116 | diff --git a/reactive/content_cache.py b/reactive/content_cache.py | |||
117 | index d620521..78f6933 100644 | |||
118 | --- a/reactive/content_cache.py | |||
119 | +++ b/reactive/content_cache.py | |||
120 | @@ -1,9 +1,12 @@ | |||
121 | 1 | import multiprocessing | ||
122 | 1 | import yaml | 2 | import yaml |
123 | 2 | 3 | ||
124 | 3 | from charms import reactive | 4 | from charms import reactive |
125 | 4 | from charms.layer import status | 5 | from charms.layer import status |
126 | 5 | from charmhelpers.core import hookenv, host | 6 | from charmhelpers.core import hookenv, host |
127 | 7 | |||
128 | 6 | from lib import nginx | 8 | from lib import nginx |
129 | 9 | from lib import haproxy as HAProxy | ||
130 | 7 | 10 | ||
131 | 8 | 11 | ||
132 | 9 | @reactive.hook('upgrade-charm') | 12 | @reactive.hook('upgrade-charm') |
133 | @@ -81,6 +84,10 @@ def configure_haproxy(): | |||
134 | 81 | reactive.clear_flag('content_cache.active') | 84 | reactive.clear_flag('content_cache.active') |
135 | 82 | return | 85 | return |
136 | 83 | 86 | ||
138 | 84 | # TODO: Configure up and start/restart HAProxy | 87 | haproxy = HAProxy.HAProxyConf() |
139 | 88 | num_procs = multiprocessing.cpu_count() | ||
140 | 89 | conf = yaml.safe_load(config.get('sites')) | ||
141 | 90 | if haproxy.write(haproxy.render(conf, num_procs)): | ||
142 | 91 | service_start_or_restart('haproxy') | ||
143 | 85 | 92 | ||
144 | 86 | reactive.set_flag('content_cache.haproxy.configured') | 93 | reactive.set_flag('content_cache.haproxy.configured') |
145 | diff --git a/templates/haproxy_cfg.tmpl b/templates/haproxy_cfg.tmpl | |||
146 | 87 | new file mode 100644 | 94 | new file mode 100644 |
147 | index 0000000..e6d367f | |||
148 | --- /dev/null | |||
149 | +++ b/templates/haproxy_cfg.tmpl | |||
150 | @@ -0,0 +1,46 @@ | |||
151 | 1 | global | ||
152 | 2 | nbproc {{num_procs}} | ||
153 | 3 | log /dev/log local0 | ||
154 | 4 | log /dev/log local1 notice | ||
155 | 5 | chroot /var/lib/haproxy | ||
156 | 6 | stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners | ||
157 | 7 | stats timeout 30s | ||
158 | 8 | user haproxy | ||
159 | 9 | group haproxy | ||
160 | 10 | daemon | ||
161 | 11 | |||
162 | 12 | # Default SSL material locations | ||
163 | 13 | ca-base /etc/ssl/certs | ||
164 | 14 | crt-base /etc/ssl/private | ||
165 | 15 | |||
166 | 16 | # Default ciphers to use on SSL-enabled listening sockets. | ||
167 | 17 | # For more information, see ciphers(1SSL). This list is from: | ||
168 | 18 | # https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ | ||
169 | 19 | # An alternative list with additional directives can be obtained from | ||
170 | 20 | # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=haproxy | ||
171 | 21 | ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS | ||
172 | 22 | ssl-default-bind-options no-sslv3 | ||
173 | 23 | |||
174 | 24 | defaults | ||
175 | 25 | log global | ||
176 | 26 | mode http | ||
177 | 27 | option httplog | ||
178 | 28 | option dontlognull | ||
179 | 29 | timeout connect 5000 | ||
180 | 30 | timeout client 50000 | ||
181 | 31 | timeout server 50000 | ||
182 | 32 | errorfile 400 /etc/haproxy/errors/400.http | ||
183 | 33 | errorfile 403 /etc/haproxy/errors/403.http | ||
184 | 34 | errorfile 408 /etc/haproxy/errors/408.http | ||
185 | 35 | errorfile 500 /etc/haproxy/errors/500.http | ||
186 | 36 | errorfile 502 /etc/haproxy/errors/502.http | ||
187 | 37 | errorfile 503 /etc/haproxy/errors/503.http | ||
188 | 38 | errorfile 504 /etc/haproxy/errors/504.http | ||
189 | 39 | |||
190 | 40 | {% for stanza in listen -%} | ||
191 | 41 | {{stanza}} | ||
192 | 42 | {%- endfor -%} | ||
193 | 43 | |||
194 | 44 | {% for stanza in backend -%} | ||
195 | 45 | {{stanza}} | ||
196 | 46 | {%- endfor -%} | ||
197 | diff --git a/tests/unit/files/config_test_config.txt b/tests/unit/files/config_test_config.txt | |||
198 | 0 | new file mode 100644 | 47 | new file mode 100644 |
199 | index 0000000..b27ec91 | |||
200 | --- /dev/null | |||
201 | +++ b/tests/unit/files/config_test_config.txt | |||
202 | @@ -0,0 +1,23 @@ | |||
203 | 1 | # Test 1: The basic port and backends (HTTP) | ||
204 | 2 | site1.local: | ||
205 | 3 | port: 80 | ||
206 | 4 | backends: | ||
207 | 5 | - 127.0.1.10:80 | ||
208 | 6 | - 127.0.1.11:80 | ||
209 | 7 | - 127.0.1.12:80 | ||
210 | 8 | |||
211 | 9 | # Test 2: TLS/SSL as well as backends (HTTPS) | ||
212 | 10 | site2.local: | ||
213 | 11 | tls-cert-bundle-path: /etc/haproxy/some-bundle.crt | ||
214 | 12 | backend-tls: True | ||
215 | 13 | backends: | ||
216 | 14 | - 127.0.1.10:443 | ||
217 | 15 | - 127.0.1.11:443 | ||
218 | 16 | - 127.0.1.12:443 | ||
219 | 17 | |||
220 | 18 | # Test 3: No port, just backends (HTTP) | ||
221 | 19 | site3.local: | ||
222 | 20 | backends: | ||
223 | 21 | - 127.0.1.10:80 | ||
224 | 22 | - 127.0.1.11:80 | ||
225 | 23 | - 127.0.1.12:80 | ||
226 | diff --git a/tests/unit/files/haproxy_config_rendered_backends_stanzas_test_output.txt b/tests/unit/files/haproxy_config_rendered_backends_stanzas_test_output.txt | |||
227 | 0 | new file mode 100644 | 24 | new file mode 100644 |
228 | index 0000000..6b76e4e | |||
229 | --- /dev/null | |||
230 | +++ b/tests/unit/files/haproxy_config_rendered_backends_stanzas_test_output.txt | |||
231 | @@ -0,0 +1,24 @@ | |||
232 | 1 | |||
233 | 2 | backend cached-site1-local | ||
234 | 3 | option httpchk HEAD / HTTP/1.0\r\nHost:\ site1.local\r\nUser-Agent:\ haproxy/httpchk | ||
235 | 4 | http-request set-header Host site1.local | ||
236 | 5 | balance leastconn | ||
237 | 6 | server server_1 127.0.1.10:80 check inter 5000 rise 2 fall 5 maxconn 16 | ||
238 | 7 | server server_2 127.0.1.11:80 check inter 5000 rise 2 fall 5 maxconn 16 | ||
239 | 8 | server server_3 127.0.1.12:80 check inter 5000 rise 2 fall 5 maxconn 16 | ||
240 | 9 | |||
241 | 10 | backend cached-site2-local | ||
242 | 11 | option httpchk HEAD / HTTP/1.0\r\nHost:\ site2.local\r\nUser-Agent:\ haproxy/httpchk | ||
243 | 12 | http-request set-header Host site2.local | ||
244 | 13 | balance leastconn | ||
245 | 14 | server server_1 127.0.1.10:443 check inter 5000 rise 2 fall 5 maxconn 16 ssl sni str(site2.local) check-sni site2.local verify required ca-file ca-certificates.crt | ||
246 | 15 | server server_2 127.0.1.11:443 check inter 5000 rise 2 fall 5 maxconn 16 ssl sni str(site2.local) check-sni site2.local verify required ca-file ca-certificates.crt | ||
247 | 16 | server server_3 127.0.1.12:443 check inter 5000 rise 2 fall 5 maxconn 16 ssl sni str(site2.local) check-sni site2.local verify required ca-file ca-certificates.crt | ||
248 | 17 | |||
249 | 18 | backend cached-site3-local | ||
250 | 19 | option httpchk HEAD / HTTP/1.0\r\nHost:\ site3.local\r\nUser-Agent:\ haproxy/httpchk | ||
251 | 20 | http-request set-header Host site3.local | ||
252 | 21 | balance leastconn | ||
253 | 22 | server server_1 127.0.1.10:80 check inter 5000 rise 2 fall 5 maxconn 16 | ||
254 | 23 | server server_2 127.0.1.11:80 check inter 5000 rise 2 fall 5 maxconn 16 | ||
255 | 24 | server server_3 127.0.1.12:80 check inter 5000 rise 2 fall 5 maxconn 16 | ||
256 | diff --git a/tests/unit/files/haproxy_config_rendered_listen_stanzas_test_output.txt b/tests/unit/files/haproxy_config_rendered_listen_stanzas_test_output.txt | |||
257 | 0 | new file mode 100644 | 25 | new file mode 100644 |
258 | index 0000000..e60ae6c | |||
259 | --- /dev/null | |||
260 | +++ b/tests/unit/files/haproxy_config_rendered_listen_stanzas_test_output.txt | |||
261 | @@ -0,0 +1,12 @@ | |||
262 | 1 | |||
263 | 2 | listen site1-local | ||
264 | 3 | bind 0.0.0.0:80 | ||
265 | 4 | default_backend cached-site1-local | ||
266 | 5 | |||
267 | 6 | listen site2-local | ||
268 | 7 | bind 0.0.0.0:443 ssl crt /etc/haproxy/some-bundle.crt | ||
269 | 8 | default_backend cached-site2-local | ||
270 | 9 | |||
271 | 10 | listen site3-local | ||
272 | 11 | bind 0.0.0.0:80 | ||
273 | 12 | default_backend cached-site3-local | ||
274 | diff --git a/tests/unit/files/haproxy_config_rendered_test_output.txt b/tests/unit/files/haproxy_config_rendered_test_output.txt | |||
275 | 0 | new file mode 100644 | 13 | new file mode 100644 |
276 | index 0000000..71954ac | |||
277 | --- /dev/null | |||
278 | +++ b/tests/unit/files/haproxy_config_rendered_test_output.txt | |||
279 | @@ -0,0 +1,75 @@ | |||
280 | 1 | global | ||
281 | 2 | nbproc 4 | ||
282 | 3 | log /dev/log local0 | ||
283 | 4 | log /dev/log local1 notice | ||
284 | 5 | chroot /var/lib/haproxy | ||
285 | 6 | stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners | ||
286 | 7 | stats timeout 30s | ||
287 | 8 | user haproxy | ||
288 | 9 | group haproxy | ||
289 | 10 | daemon | ||
290 | 11 | |||
291 | 12 | # Default SSL material locations | ||
292 | 13 | ca-base /etc/ssl/certs | ||
293 | 14 | crt-base /etc/ssl/private | ||
294 | 15 | |||
295 | 16 | # Default ciphers to use on SSL-enabled listening sockets. | ||
296 | 17 | # For more information, see ciphers(1SSL). This list is from: | ||
297 | 18 | # https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/ | ||
298 | 19 | # An alternative list with additional directives can be obtained from | ||
299 | 20 | # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=haproxy | ||
300 | 21 | ssl-default-bind-ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS | ||
301 | 22 | ssl-default-bind-options no-sslv3 | ||
302 | 23 | |||
303 | 24 | defaults | ||
304 | 25 | log global | ||
305 | 26 | mode http | ||
306 | 27 | option httplog | ||
307 | 28 | option dontlognull | ||
308 | 29 | timeout connect 5000 | ||
309 | 30 | timeout client 50000 | ||
310 | 31 | timeout server 50000 | ||
311 | 32 | errorfile 400 /etc/haproxy/errors/400.http | ||
312 | 33 | errorfile 403 /etc/haproxy/errors/403.http | ||
313 | 34 | errorfile 408 /etc/haproxy/errors/408.http | ||
314 | 35 | errorfile 500 /etc/haproxy/errors/500.http | ||
315 | 36 | errorfile 502 /etc/haproxy/errors/502.http | ||
316 | 37 | errorfile 503 /etc/haproxy/errors/503.http | ||
317 | 38 | errorfile 504 /etc/haproxy/errors/504.http | ||
318 | 39 | |||
319 | 40 | |||
320 | 41 | listen site1-local | ||
321 | 42 | bind 0.0.0.0:80 | ||
322 | 43 | default_backend cached-site1-local | ||
323 | 44 | |||
324 | 45 | listen site2-local | ||
325 | 46 | bind 0.0.0.0:443 ssl crt /etc/haproxy/some-bundle.crt | ||
326 | 47 | default_backend cached-site2-local | ||
327 | 48 | |||
328 | 49 | listen site3-local | ||
329 | 50 | bind 0.0.0.0:80 | ||
330 | 51 | default_backend cached-site3-local | ||
331 | 52 | |||
332 | 53 | backend cached-site1-local | ||
333 | 54 | option httpchk HEAD / HTTP/1.0\r\nHost:\ site1.local\r\nUser-Agent:\ haproxy/httpchk | ||
334 | 55 | http-request set-header Host site1.local | ||
335 | 56 | balance leastconn | ||
336 | 57 | server server_1 127.0.1.10:80 check inter 5000 rise 2 fall 5 maxconn 16 | ||
337 | 58 | server server_2 127.0.1.11:80 check inter 5000 rise 2 fall 5 maxconn 16 | ||
338 | 59 | server server_3 127.0.1.12:80 check inter 5000 rise 2 fall 5 maxconn 16 | ||
339 | 60 | |||
340 | 61 | backend cached-site2-local | ||
341 | 62 | option httpchk HEAD / HTTP/1.0\r\nHost:\ site2.local\r\nUser-Agent:\ haproxy/httpchk | ||
342 | 63 | http-request set-header Host site2.local | ||
343 | 64 | balance leastconn | ||
344 | 65 | server server_1 127.0.1.10:443 check inter 5000 rise 2 fall 5 maxconn 16 ssl sni str(site2.local) check-sni site2.local verify required ca-file ca-certificates.crt | ||
345 | 66 | server server_2 127.0.1.11:443 check inter 5000 rise 2 fall 5 maxconn 16 ssl sni str(site2.local) check-sni site2.local verify required ca-file ca-certificates.crt | ||
346 | 67 | server server_3 127.0.1.12:443 check inter 5000 rise 2 fall 5 maxconn 16 ssl sni str(site2.local) check-sni site2.local verify required ca-file ca-certificates.crt | ||
347 | 68 | |||
348 | 69 | backend cached-site3-local | ||
349 | 70 | option httpchk HEAD / HTTP/1.0\r\nHost:\ site3.local\r\nUser-Agent:\ haproxy/httpchk | ||
350 | 71 | http-request set-header Host site3.local | ||
351 | 72 | balance leastconn | ||
352 | 73 | server server_1 127.0.1.10:80 check inter 5000 rise 2 fall 5 maxconn 16 | ||
353 | 74 | server server_2 127.0.1.11:80 check inter 5000 rise 2 fall 5 maxconn 16 | ||
354 | 75 | server server_3 127.0.1.12:80 check inter 5000 rise 2 fall 5 maxconn 16 | ||
355 | diff --git a/tests/unit/test_content_cache.py b/tests/unit/test_content_cache.py | |||
356 | index 2366df8..618e3cd 100644 | |||
357 | --- a/tests/unit/test_content_cache.py | |||
358 | +++ b/tests/unit/test_content_cache.py | |||
359 | @@ -5,12 +5,13 @@ import tempfile | |||
360 | 5 | import unittest | 5 | import unittest |
361 | 6 | from unittest import mock | 6 | from unittest import mock |
362 | 7 | 7 | ||
363 | 8 | # Add path to where our reactive layer lives and import. | ||
364 | 9 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) | ||
365 | 10 | # We also need to mock up charms.layer so we can run unit tests without having | 8 | # We also need to mock up charms.layer so we can run unit tests without having |
366 | 11 | # to build the charm and pull in layers such as layer-status. | 9 | # to build the charm and pull in layers such as layer-status. |
367 | 12 | sys.modules['charms.layer'] = mock.MagicMock() | 10 | sys.modules['charms.layer'] = mock.MagicMock() |
368 | 11 | |||
369 | 13 | from charms.layer import status # NOQA: E402 | 12 | from charms.layer import status # NOQA: E402 |
370 | 13 | # Add path to where our reactive layer lives and import. | ||
371 | 14 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) | ||
372 | 14 | from reactive import content_cache # NOQA: E402 | 15 | from reactive import content_cache # NOQA: E402 |
373 | 15 | 16 | ||
374 | 16 | 17 | ||
375 | @@ -41,6 +42,11 @@ class TestCharm(unittest.TestCase): | |||
376 | 41 | self.addCleanup(patcher.stop) | 42 | self.addCleanup(patcher.stop) |
377 | 42 | self.mock_config.return_value = {} | 43 | self.mock_config.return_value = {} |
378 | 43 | 44 | ||
379 | 45 | patcher = mock.patch('multiprocessing.cpu_count') | ||
380 | 46 | self.mock_cpu_count = patcher.start() | ||
381 | 47 | self.addCleanup(patcher.stop) | ||
382 | 48 | self.mock_cpu_count.return_value = 4 | ||
383 | 49 | |||
384 | 44 | @mock.patch('charms.reactive.clear_flag') | 50 | @mock.patch('charms.reactive.clear_flag') |
385 | 45 | def test_hook_upgrade_charm_flags(self, clear_flag): | 51 | def test_hook_upgrade_charm_flags(self, clear_flag): |
386 | 46 | '''Test correct flags set via upgrade-charm hook''' | 52 | '''Test correct flags set via upgrade-charm hook''' |
387 | @@ -142,10 +148,24 @@ class TestCharm(unittest.TestCase): | |||
388 | 142 | 148 | ||
389 | 143 | @mock.patch('reactive.content_cache.service_start_or_restart') | 149 | @mock.patch('reactive.content_cache.service_start_or_restart') |
390 | 144 | def test_configure_haproxy_sites(self, service_start_or_restart): | 150 | def test_configure_haproxy_sites(self, service_start_or_restart): |
392 | 145 | with open('tests/unit/files/nginx_config_test_config.txt', 'r', encoding='utf-8') as f: | 151 | with open('tests/unit/files/config_test_config.txt', 'r', encoding='utf-8') as f: |
393 | 146 | ngx_config = f.read() | 152 | ngx_config = f.read() |
394 | 147 | self.mock_config.return_value = {'sites': ngx_config} | 153 | self.mock_config.return_value = {'sites': ngx_config} |
396 | 148 | content_cache.configure_haproxy() | 154 | |
397 | 155 | with open('tests/unit/files/haproxy_config_rendered_test_output.txt', 'r', encoding='utf-8') as f: | ||
398 | 156 | expected = f.read() | ||
399 | 157 | with mock.patch('lib.haproxy.HAProxyConf.conf_file', new_callable=mock.PropertyMock) as mock_conf_file: | ||
400 | 158 | mock_conf_file.return_value = os.path.join(self.tmpdir, 'haproxy.cfg') | ||
401 | 159 | content_cache.configure_haproxy() | ||
402 | 160 | with open(os.path.join(self.tmpdir, 'haproxy.cfg'), 'r', encoding='utf-8') as f: | ||
403 | 161 | current = f.read() | ||
404 | 162 | self.assertEqual(expected, current) | ||
405 | 163 | self.assertFalse(service_start_or_restart.assert_called_with('haproxy')) | ||
406 | 164 | |||
407 | 165 | # Again, this time should be no change so no need to restart HAProxy | ||
408 | 166 | service_start_or_restart.reset_mock() | ||
409 | 167 | content_cache.configure_haproxy() | ||
410 | 168 | self.assertFalse(service_start_or_restart.assert_not_called()) | ||
411 | 149 | 169 | ||
412 | 150 | 170 | ||
413 | 151 | if __name__ == '__main__': | 171 | if __name__ == '__main__': |
414 | diff --git a/tests/unit/test_haproxy.py b/tests/unit/test_haproxy.py | |||
415 | 152 | new file mode 100644 | 172 | new file mode 100644 |
416 | index 0000000..19025a3 | |||
417 | --- /dev/null | |||
418 | +++ b/tests/unit/test_haproxy.py | |||
419 | @@ -0,0 +1,72 @@ | |||
420 | 1 | import os | ||
421 | 2 | import shutil | ||
422 | 3 | import sys | ||
423 | 4 | import tempfile | ||
424 | 5 | import unittest | ||
425 | 6 | import yaml | ||
426 | 7 | |||
427 | 8 | sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))) | ||
428 | 9 | from lib import haproxy as HAProxy # NOQA: E402 | ||
429 | 10 | |||
430 | 11 | |||
431 | 12 | class TestLibHAProxy(unittest.TestCase): | ||
432 | 13 | def setUp(self): | ||
433 | 14 | self.maxDiff = None | ||
434 | 15 | self.tmpdir = tempfile.mkdtemp(prefix='charm-unittests-') | ||
435 | 16 | self.addCleanup(shutil.rmtree, self.tmpdir) | ||
436 | 17 | self.charm_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) | ||
437 | 18 | with open('tests/unit/files/config_test_config.txt', 'r', encoding='utf-8') as f: | ||
438 | 19 | self.site_config = yaml.safe_load(f.read()) | ||
439 | 20 | |||
440 | 21 | def test_haproxy_config_path(self): | ||
441 | 22 | conf_path = '/etc/haproxy' | ||
442 | 23 | haproxy = HAProxy.HAProxyConf() | ||
443 | 24 | self.assertEqual(haproxy.conf_path, conf_path) | ||
444 | 25 | |||
445 | 26 | def test_haproxy_config_file(self): | ||
446 | 27 | conf_file = '/etc/haproxy/haproxy.cfg' | ||
447 | 28 | haproxy = HAProxy.HAProxyConf() | ||
448 | 29 | self.assertEqual(haproxy.conf_file, conf_file) | ||
449 | 30 | |||
450 | 31 | def test_haproxy_config_generate_stanza_names(self): | ||
451 | 32 | haproxy = HAProxy.HAProxyConf(self.tmpdir) | ||
452 | 33 | self.assertEqual(haproxy._generate_stanza_name('site1'), 'site1') | ||
453 | 34 | self.assertEqual(haproxy._generate_stanza_name('site1.local'), 'site1-local') | ||
454 | 35 | self.assertEqual(haproxy._generate_stanza_name('site1-canonical-com-canonical-com'), | ||
455 | 36 | 'site1-canonical-com-canonical-co') | ||
456 | 37 | |||
457 | 38 | def test_haproxy_config_rendered_listen_stanzas(self): | ||
458 | 39 | haproxy = HAProxy.HAProxyConf(self.tmpdir) | ||
459 | 40 | config = self.site_config | ||
460 | 41 | with open('tests/unit/files/haproxy_config_rendered_listen_stanzas_test_output.txt', 'r', | ||
461 | 42 | encoding='utf-8') as f: | ||
462 | 43 | expected = f.read() | ||
463 | 44 | self.assertEqual(''.join(haproxy.render_stanza_listen(config)), expected) | ||
464 | 45 | |||
465 | 46 | def test_haproxy_config_rendered_backend_stanzas(self): | ||
466 | 47 | haproxy = HAProxy.HAProxyConf(self.tmpdir) | ||
467 | 48 | config = self.site_config | ||
468 | 49 | with open('tests/unit/files/haproxy_config_rendered_backends_stanzas_test_output.txt', 'r', | ||
469 | 50 | encoding='utf-8') as f: | ||
470 | 51 | expected = f.read() | ||
471 | 52 | self.assertEqual(''.join(haproxy.render_stanza_backend(config)), expected) | ||
472 | 53 | |||
473 | 54 | def test_haproxy_config_rendered_full_config(self): | ||
474 | 55 | haproxy = HAProxy.HAProxyConf(self.tmpdir) | ||
475 | 56 | config = self.site_config | ||
476 | 57 | num_procs = 4 | ||
477 | 58 | self.assertTrue(haproxy.write(haproxy.render(config, num_procs))) | ||
478 | 59 | with open(haproxy.conf_file, 'r') as f: | ||
479 | 60 | new_conf = f.read() | ||
480 | 61 | with open('tests/unit/files/haproxy_config_rendered_test_output.txt', 'r') as f: | ||
481 | 62 | expected = f.read() | ||
482 | 63 | self.assertEqual(new_conf, expected) | ||
483 | 64 | |||
484 | 65 | def test_haproxy_config_write(self): | ||
485 | 66 | haproxy = HAProxy.HAProxyConf(self.tmpdir) | ||
486 | 67 | with open('tests/unit/files/haproxy_config_rendered_test_output.txt', 'r', encoding='utf-8') as f: | ||
487 | 68 | conf = f.read() | ||
488 | 69 | self.assertTrue(haproxy.write(conf)) | ||
489 | 70 | # Write again with same contents, this time it should return 'False' | ||
490 | 71 | # as there should be no change. | ||
491 | 72 | self.assertFalse(haproxy.write(conf)) |
This merge proposal is being monitored by mergebot. Change the status to Approved to merge.