Merge ~hloeung/content-cache-charm:add-haproxy-v2 into content-cache-charm: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)
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

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

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

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
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
Joel Sing (jsing) wrote :

LGTM

review: Approve (+1)
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision 16f2684a0ce7bf2bb03dc31d968e951f18161865

Preview Diff

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

Subscribers

People subscribed via source and target branches