Merge ~hloeung/content-cache-charm:haproxy-config into content-cache-charm:master

Proposed by Haw Loeung
Status: Merged
Approved by: Haw Loeung
Approved revision: 008d8f07f3131b418c4f868bb700f114292bfd40
Merged at revision: 941f22cb2ada546a8868e956002744c1fc1f9414
Proposed branch: ~hloeung/content-cache-charm:haproxy-config
Merge into: content-cache-charm:master
Diff against target: 120 lines (+70/-3)
4 files modified
lib/haproxy.py (+9/-3)
lib/utils.py (+28/-0)
tests/unit/files/limits.txt (+17/-0)
tests/unit/test_utils.py (+16/-0)
Reviewer Review Type Date Requested Status
Stuart Bishop (community) Approve
Canonical IS Reviewers Pending
Review via email: mp+381090@code.launchpad.net

Commit message

Set maxconns ceiling of system's init/PID 1's max. open files.

To post a comment you must log in.
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
Stuart Bishop (stub) wrote :

Looks fine, apart from tests that seem to rely on systemd defaults not being changed. Comments inline. Probably doesn't require rereview, but ping if it does.

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

Change successfully merged at revision 941f22cb2ada546a8868e956002744c1fc1f9414

Revision history for this message
Joel Sing (jsing) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/haproxy.py b/lib/haproxy.py
2index b8d8727..2d378e9 100644
3--- a/lib/haproxy.py
4+++ b/lib/haproxy.py
5@@ -244,23 +244,29 @@ backend backend-{name}
6 def render(self, config, num_threads=None, monitoring_password=None, tls_cipher_suites=None):
7 if not num_threads:
8 num_threads = multiprocessing.cpu_count()
9+
10+ listen_stanzas = self.render_stanza_listen(config)
11+
12 if self.max_connections:
13 max_connections = self.max_connections
14 else:
15 max_connections = num_threads * 2000
16+ global_max_connections = max_connections * len(listen_stanzas)
17+ init_maxfds = utils.process_rlimits(1, 'NOFILE')
18+ if init_maxfds != 'unlimited' and (global_max_connections * 2) > int(init_maxfds):
19+ global_max_connections = int(init_maxfds) // 2
20+
21 if not tls_cipher_suites:
22 tls_cipher_suites = TLS_CIPHER_SUITES
23 tls_cipher_suites = utils.tls_cipher_suites(tls_cipher_suites)
24
25- listen_stanzas = self.render_stanza_listen(config)
26-
27 base = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
28 env = jinja2.Environment(loader=jinja2.FileSystemLoader(base))
29 template = env.get_template('templates/haproxy_cfg.tmpl')
30 return template.render(
31 {
32 'backend': self.render_stanza_backend(config),
33- 'global_max_connections': max_connections * len(listen_stanzas),
34+ 'global_max_connections': global_max_connections,
35 'listen': listen_stanzas,
36 'max_connections': max_connections,
37 'monitoring_password': monitoring_password or self.monitoring_password,
38diff --git a/lib/utils.py b/lib/utils.py
39index a075d18..577d9e9 100644
40--- a/lib/utils.py
41+++ b/lib/utils.py
42@@ -168,3 +168,31 @@ def logrotate(path, retention=30, dateext=True):
43 new.append(line)
44
45 return '\n'.join(new)
46+
47+
48+LIMITS_MATCH = {
49+ 'NOFILE': 'Max open files',
50+}
51+
52+
53+def process_rlimits(pid, res, limits_file=None):
54+ if limits_file:
55+ limitsf = limits_file
56+ else:
57+ limitsf = os.path.join('/proc', str(pid), 'limits')
58+
59+ if not os.path.exists(limitsf):
60+ return None
61+
62+ with open(limitsf, 'r', encoding='utf-8') as f:
63+ limits = f.read()
64+
65+ if res not in LIMITS_MATCH:
66+ return None
67+
68+ for line in limits.split('\n'):
69+ m = re.match(r'^{}\s+\S+\s+(\S+)'.format(LIMITS_MATCH[res]), line)
70+ if m:
71+ return m.group(1)
72+
73+ return None
74diff --git a/tests/unit/files/limits.txt b/tests/unit/files/limits.txt
75new file mode 100644
76index 0000000..679cc39
77--- /dev/null
78+++ b/tests/unit/files/limits.txt
79@@ -0,0 +1,17 @@
80+Limit Soft Limit Hard Limit Units
81+Max cpu time unlimited unlimited seconds
82+Max file size unlimited unlimited bytes
83+Max data size unlimited unlimited bytes
84+Max stack size 8388608 unlimited bytes
85+Max core file size 0 unlimited bytes
86+Max resident set unlimited unlimited bytes
87+Max processes 31268 31268 processes
88+Max open files 1048576 1048576 files
89+Max locked memory 67108864 67108864 bytes
90+Max address space unlimited unlimited bytes
91+Max file locks unlimited unlimited locks
92+Max pending signals 31268 31268 signals
93+Max msgqueue size 819200 819200 bytes
94+Max nice priority 0 0
95+Max realtime priority 0 0
96+Max realtime timeout unlimited unlimited us
97diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
98index 301aafc..d4d4045 100644
99--- a/tests/unit/test_utils.py
100+++ b/tests/unit/test_utils.py
101@@ -169,3 +169,19 @@ class TestLibUtils(unittest.TestCase):
102
103 # Test when config file doesn't exist.
104 self.assertEqual(utils.logrotate(retention=14, path='tests/unit/files/some-file-that-doesnt-exist'), None)
105+
106+ def test_process_rlimits(self):
107+ # Read current Max open files from PID 1 and make sure
108+ # utils.process_rlimits() returns the same.
109+ with open('/proc/1/limits') as f:
110+ for line in f:
111+ if line.startswith('Max open files'):
112+ limit = str(line.split()[4])
113+ self.assertEqual(limit, utils.process_rlimits(1, 'NOFILE'))
114+ self.assertEqual(limit, utils.process_rlimits(1, 'NOFILE', None))
115+ self.assertEqual(limit, utils.process_rlimits(1, 'NOFILE', 'tests/unit/files/limits.txt'))
116+
117+ self.assertEqual(None, utils.process_rlimits(1, 'NOFILE', 'tests/unit/files/test_file.txt'))
118+ self.assertEqual(None, utils.process_rlimits(1, 'NOFILE', 'tests/unit/files/limits-file-does-not-exist.txt'))
119+
120+ self.assertEqual(None, utils.process_rlimits(1, 'NOMATCH'))

Subscribers

People subscribed via source and target branches