instance-balancer.cfg.in 16.2 KB
Newer Older
1
{% import "caucase" as caucase with context %}
2 3
{% set part_list = [] -%}
{% macro section(name) %}{% do part_list.append(name) %}{{ name }}{% endmacro -%}
4
{% set ssl_parameter_dict = slapparameter_dict['ssl'] -%}
5
{% set frontend_caucase_url_list = ssl_parameter_dict.get('frontend-caucase-url-list', []) -%}
6 7 8 9 10
{#
XXX: This template only supports exactly one IPv4 and (if ipv6 is used) one IPv6
per partition. No more (undefined result), no less (IndexError).
-#}
{% set ipv4 = (ipv4_set | list)[0] -%}
11 12 13
{% if ipv6_set -%}
{%   set ipv6 = (ipv6_set | list)[0] -%}
{% endif -%}
14

15
[jinja2-template-base]
16
recipe = slapos.recipe.template:jinja2
17 18
mode = 644

19 20 21 22 23 24
{{ caucase.updater(
     prefix='caucase-updater',
     buildout_bin_directory=parameter_dict['bin-directory'],
     updater_path='${directory:services-on-watch}/caucase-updater',
     url=ssl_parameter_dict['caucase-url'],
     data_dir='${directory:srv}/caucase-updater',
25
     crt_path='${apache-conf-ssl:caucase-cert}',
26 27
     ca_path='${directory:srv}/caucase-updater/ca.crt',
     crl_path='${directory:srv}/caucase-updater/crl.pem',
28
     key_path='${apache-conf-ssl:caucase-key}',
29
     on_renew='${haproxy-reload:output}',
30 31 32 33
     max_sleep=ssl_parameter_dict.get('max-crl-update-delay', 1.0),
     template_csr_pem=ssl_parameter_dict.get('csr'),
     openssl=parameter_dict['openssl'] ~ '/bin/openssl',
)}}
34
{# XXX we don't use caucase yet.
35
{% do section('caucase-updater') -%}
36
{% do section('caucase-updater-promise') -%}
37
#}
38

39 40 41 42
{% set frontend_caucase_url_hash_list = [] -%}
{% for frontend_caucase_url in frontend_caucase_url_list -%}
{%   set hash = hashlib.md5(frontend_caucase_url).hexdigest() -%}
{%   do frontend_caucase_url_hash_list.append(hash) -%}
43
{%   set data_dir = '${directory:client-cert-ca}/%s' % hash -%}
44 45 46 47 48 49 50 51
{{   caucase.updater(
       prefix='caucase-updater-%s' % hash,
       buildout_bin_directory=parameter_dict['bin-directory'],
       updater_path='${directory:services-on-watch}/caucase-updater-%s' % hash,
       url=frontend_caucase_url,
       data_dir=data_dir,
       ca_path='%s/ca.crt' % data_dir,
       crl_path='%s/crl.pem' % data_dir,
52
       on_renew='${caucase-updater-housekeeper:output}',
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
       max_sleep=ssl_parameter_dict.get('max-crl-update-delay', 1.0),
       openssl=parameter_dict['openssl'] ~ '/bin/openssl',
     )}}
{%   do section('caucase-updater-%s' % hash) -%}
{% endfor -%}

{% if frontend_caucase_url_hash_list -%}
[caucase-updater-housekeeper]
recipe = collective.recipe.template
output = ${directory:bin}/caucase-updater-housekeeper
mode = 700
input =
  inline:
  #!${buildout:executable}
  import glob
  import os
  import subprocess
  hash_list = {{ repr(frontend_caucase_url_hash_list) }}
  crt_list = ['%s.crt' % e for e in hash_list]
72
  for path in glob.glob('${haproxy-conf-ssl:ca-cert-dir}/*.crt'):
73 74
    if os.path.basename(path) not in crt_list:
      os.unlink(path)
75 76
  crl_list = ['%s.crl' % e for e in hash_list]
  for path in glob.glob('${haproxy-conf-ssl:crl-dir}/*.crl'):
77 78
    if os.path.basename(path) not in crl_list:
      os.unlink(path)
79

80
  for hash in hash_list:
81 82 83 84
    crt = '${directory:client-cert-ca}/%s/ca.crt' % hash
    crt_link = '${haproxy-conf-ssl:ca-cert-dir}/%s.crt' % hash
    crl = '${directory:client-cert-ca}/%s/crl.pem' % hash
    crl_link = '${haproxy-conf-ssl:crl-dir}/%s.crl' % hash
85 86 87 88
    if os.path.isfile(crt) and not os.path.islink(crt_link):
      os.symlink(crt, crt_link)
    if os.path.isfile(crl) and not os.path.islink(crl_link):
      os.symlink(crl, crl_link)
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
  subprocess.check_call(['{{ parameter_dict["openssl"] }}/bin/c_rehash', '${haproxy-conf-ssl:ca-cert-dir}'])
  subprocess.check_call(['{{ parameter_dict["openssl"] }}/bin/c_rehash', '${haproxy-conf-ssl:crl-dir}'])

  # assemble all CA and all CRLs in one file for haproxy
  with open('${haproxy-conf-ssl:ca-cert}.tmp', 'w') as f:
    for path in glob.glob('${haproxy-conf-ssl:ca-cert-dir}/*.crt'):
      with open(path) as in_f:
        f.write('#{}\n'.format(path))
        f.write(in_f.read() + '\n')
  with open('${haproxy-conf-ssl:crl}.tmp', 'w') as f:
    for path in glob.glob('${haproxy-conf-ssl:crl-dir}/*.crl'):
      with open(path) as in_f:
        f.write('#{}\n'.format(path))
        f.write(in_f.read() + '\n')

  if os.path.exists('${haproxy-conf-ssl:ca-cert}'):
    os.unlink('${haproxy-conf-ssl:ca-cert}')
  if os.path.exists('${haproxy-conf-ssl:crl}'):
    os.unlink('${haproxy-conf-ssl:crl}')
  os.rename('${haproxy-conf-ssl:ca-cert}.tmp', '${haproxy-conf-ssl:ca-cert}')
  os.rename('${haproxy-conf-ssl:crl}.tmp', '${haproxy-conf-ssl:crl}')

  subprocess.check_call(['${haproxy-reload:output}'])

113 114 115 116 117 118 119

[caucase-updater-housekeeper-run]
recipe = plone.recipe.command
command = ${caucase-updater-housekeeper:output}
update-command = ${:command}
{% endif -%}

120
{% set haproxy_dict = {} -%}
121
{% set zope_virtualhost_monster_backend_dict = {} %}
122
{% set test_runner_url_dict = {} %} {# family_name => list of URLs #}
123
{% set next_port = itertools.count(slapparameter_dict['tcpv4-port']).next -%}
124 125
{% for family_name, parameter_id_list in sorted(
  slapparameter_dict['zope-family-dict'].iteritems()) -%}
126
{%   set zope_family_address_list = [] -%}
127
{%   set ssl_authentication = slapparameter_dict['ssl-authentication-dict'].get(family_name, False) -%}
128
{%   set has_webdav = [] -%}
129 130
{%   for parameter_id in parameter_id_list -%}
{%     set zope_address_list = slapparameter_dict[parameter_id] -%}
131 132 133 134
{%     for zope_address, maxconn, webdav in zope_address_list -%}
{%       if webdav -%}
{%         do has_webdav.append(None) %}
{%       endif -%}
135
{%       set zope_effective_address = zope_address -%}
136
{%       do zope_family_address_list.append((zope_effective_address, maxconn, webdav)) -%}
137
{%     endfor -%}
138

139 140 141
{#     # Generate entries with rewrite rule for test runnners #}
{%     set test_runner_address_list = slapparameter_dict.get(parameter_id ~ '-test-runner-address-list', []) %}
{%     if test_runner_address_list -%}
142
{%       set test_runner_backend_mapping = {} %}
143
{%       set test_runner_balancer_url_list = [] %}
144
{%       set test_runner_external_port = next_port() %}
145
{%       for i, (test_runner_internal_ip, test_runner_internal_port) in enumerate(test_runner_address_list) %}
146 147 148
{%         do test_runner_backend_mapping.__setitem__(
                'unit_test_' ~ i,
                'http://' ~ test_runner_internal_ip ~ ':' ~ test_runner_internal_port ) %}
149
{%         do test_runner_balancer_url_list.append(
150 151 152 153 154
                'https://' ~ ipv4 ~ ':' ~ test_runner_external_port ~ '/unit_test_' ~ i ~ '/' ) %}
{%       endfor %}
{%       do zope_virtualhost_monster_backend_dict.__setitem__(
              (ipv4, test_runner_external_port),
              ( ssl_authentication, test_runner_backend_mapping ) ) -%}
155
{%       do test_runner_url_dict.__setitem__(family_name, test_runner_balancer_url_list) -%}
156
{%     endif -%}
157
{%   endfor -%}
158

159 160 161 162
{# Make rendering fail artificially if any family has no known backend.
 # This is useful as haproxy's hot-reconfiguration mechanism is
 # supervisord-incompatible.
 # As jinja2 postpones KeyError until place-holder value is actually used,
163
 # do a no-op getitem.
164
-#}
165
{%   do zope_family_address_list[0][0] -%}
166 167 168 169 170 171 172 173

{#
 # We use to have haproxy then apache, now haproxy is playing apache's role
 # To keep port stable, we consume one port so that haproxy use the same port
 # that apache was using before.
-#}
{%   set _ = next_port() -%}

174
{%   set haproxy_port = next_port() -%}
175
{%   set backend_path = slapparameter_dict['backend-path-dict'][family_name] -%}
176 177 178 179 180
{%   if has_webdav -%}
{%     set external_scheme = 'webdavs' -%}
{%   else %}
{%     set external_scheme = 'https' -%}
{%   endif -%}
181
{%   do haproxy_dict.__setitem__(family_name, (haproxy_port, external_scheme, slapparameter_dict['ssl-authentication-dict'].get(family_name, False), zope_family_address_list)) -%}
182 183
{% endfor -%}

184
[haproxy-cfg-parameter-dict]
185 186 187 188 189 190 191
ipv4 = {{ ipv4 }}
ipv6 = {{ ipv6 }}
cert = ${haproxy-conf-ssl:certificate}
{% if frontend_caucase_url_list -%}
ca-cert = ${haproxy-conf-ssl:ca-cert}
crl = ${haproxy-conf-ssl:crl}
{% endif %}
192
stats-socket = ${directory:run}/ha.sock
193 194
path-routing-list = {{ dumps(slapparameter_dict['path-routing-list']) }}
family-path-routing-dict = {{ dumps(slapparameter_dict['family-path-routing-dict']) }}
195 196
pidfile = ${directory:run}/haproxy.pid
log-socket = ${rsyslogd-cfg-parameter-dict:log-socket}
197
server-check-path = {{ dumps(slapparameter_dict['haproxy-server-check-path']) }}
198
backend-dict = {{ dumps(haproxy_dict) }}
199 200
zope-virtualhost-monster-backend-dict = {{ dumps(zope_virtualhost_monster_backend_dict) }}

201 202

[haproxy-cfg]
203
< = jinja2-template-base
204 205
template = {{ parameter_dict['template-haproxy-cfg'] }}
rendered = ${directory:etc}/haproxy.cfg
206 207 208
context =
  section parameter_dict haproxy-cfg-parameter-dict
  import urlparse urlparse
209
extensions = jinja2.ext.do
210

211 212 213 214 215 216 217 218 219
[haproxy-reload]
recipe = collective.recipe.template
output = ${directory:bin}/${:_buildout_section_name_}
mode = 700
input =
  inline:
  #!/bin/sh
  kill -USR2 $(cat "${haproxy-cfg-parameter-dict:pidfile}")

220 221
[{{ section('haproxy') }}]
recipe = slapos.cookbook:wrapper
222
wrapper-path = ${directory:services-on-watch}/haproxy
223
command-line = "{{ parameter_dict['haproxy'] }}/sbin/haproxy" -f "${haproxy-cfg:rendered}"
224
hash-files = ${haproxy-cfg:rendered}
225

226
[apache-conf-ssl]
227 228 229 230
# XXX caucase is/was buggy and this certificate does not match key for instances
# that were updated, so don't use it yet.
caucase-cert = ${directory:apache-conf}/apache-caucase.crt
caucase-key = ${directory:apache-conf}/apache-caucase.pem
231

232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
[simplefile]
< = jinja2-template-base
template = inline:{{ '{{ content }}' }}

{% macro simplefile(section_name, file_path, content, mode='') -%}
{%   set content_section_name = section_name ~ '-content' -%}
[{{  content_section_name }}]
content = {{ dumps(content) }}

[{{  section(section_name) }}]
< = simplefile
rendered = {{ file_path }}
context = key content {{content_section_name}}:content
mode = {{ mode }}
{%- endmacro %}

248 249 250 251 252
[{{ section('haproxy-socat-stats')}}]
recipe = slapos.cookbook:wrapper
wrapper-path = ${directory:bin}/${:_buildout_section_name_}
command-line = "{{ parameter_dict['socat'] }}/bin/socat" unix-connect:${haproxy-cfg-parameter-dict:stats-socket} stdio

253 254 255 256 257 258
[rsyslogd-cfg-parameter-dict]
log-socket = ${directory:run}/log.sock
access-log-file = ${directory:log}/apache-access.log
error-log-file = ${directory:log}/apache-error.log
pid-file = ${directory:run}/rsyslogd.pid
spool-directory = ${directory:rsyslogd-spool}
259

260 261 262 263 264
[rsyslogd-cfg]
<= jinja2-template-base
template = {{ parameter_dict['template-rsyslogd-cfg'] }}
rendered = ${directory:etc}/rsyslogd.conf
context = section parameter_dict rsyslogd-cfg-parameter-dict
265

266
[{{ section ('rsyslogd') }}]
267
recipe = slapos.cookbook:wrapper
268 269 270 271
command-line = {{ parameter_dict['rsyslogd'] }}/sbin/rsyslogd -i ${rsyslogd-cfg-parameter-dict:pid-file} -n -f ${rsyslogd-cfg:rendered}
wrapper-path = ${directory:services-on-watch}/rsyslogd
hash-existing-files = ${buildout:directory}/software_release/buildout.cfg
hash-files = ${rsyslogd-cfg:rendered}
272

273 274
[{{ section ('rsyslogd-listen-promise') }}]
<= monitor-promise-base
275
promise = check_command_execute
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
name = rsyslogd_listen_promise.py
config-command = test -S ${rsyslogd-cfg-parameter-dict:log-socket}


[haproxy-conf-ssl]
certificate = ${build-certificate-and-key:certificate-and-key}
{% if frontend_caucase_url_list -%}
ca-cert = ${directory:etc}/frontend-ca.pem
ca-cert-dir = ${directory:ca-cert}
crl = ${directory:etc}/frontend-crl.pem
crl-dir = ${directory:crl}
depends = ${caucase-updater-housekeeper-run:recipe}
{%- endif %}

[build-certificate-and-key]
{% if ssl_parameter_dict.get('key') -%}
certificate-and-key = ${tls-certificate-and-key-from-parameters:rendered}
{{ simplefile(
    'tls-certificate-and-key-from-parameters',
    '${directory:etc}/certificate-and-key-from-parameters.pem',
    ssl_parameter_dict['cert'] ~ "\n" ~ ssl_parameter_dict['key']) }}
{% else %}
recipe = plone.recipe.command
command = "{{ parameter_dict['openssl'] }}/bin/openssl" req -newkey rsa -batch -new -x509 -days 3650 -nodes -keyout "${:certificate-and-key}" -out "${:certificate-and-key}"
certificate-and-key = ${directory:etc}/certificate-and-key-generated.pem
{%- endif %}
302

303
[{{ section('haproxy-promise') }}]
304
<= monitor-promise-base
305
# Check any haproxy port in ipv4, expect other ports and ipv6 to behave consistently
306
promise = check_socket_listening
307
name = haproxy.py
308
config-host = {{ ipv4 }}
309
config-port = {{ haproxy_dict.values()[0][0] }}
310

311
[{{ section('publish') }}]
312
recipe = slapos.cookbook:publish.serialised
313 314 315
{% for family_name, (port, scheme, _, _) in haproxy_dict.items() -%}
{{   family_name ~ '-v6' }} = {% if ipv6_set %}{{ scheme ~ '://[' ~ ipv6 ~ ']:' ~ port }}{% endif %}
{{   family_name }} = {{ scheme ~ '://' ~ ipv4 ~ ':' ~ port }}
316
{% endfor -%}
317 318 319
{% for family_name, test_runner_url_list in test_runner_url_dict.items() -%}
{{    family_name ~ '-test-runner-url-list' }} = {{ dumps(test_runner_url_list) }}
{% endfor -%}
320
monitor-base-url = ${monitor-publish-parameters:monitor-base-url}
321

322
[{{ section('logrotate-rsyslogd') }}]
323
< = logrotate-entry-base
324 325 326 327
name = rsyslogd
log = ${rsyslogd-cfg-parameter-dict:access-log-file} ${rsyslogd-cfg-parameter-dict:error-log-file}
post = test ! -s ${rsyslogd-cfg-parameter-dict:pid-file} || kill -HUP $(cat ${rsyslogd-cfg-parameter-dict:pid-file})

328 329 330 331 332 333

[directory]
recipe = slapos.cookbook:mkdirectory
bin = ${buildout:directory}/bin
etc = ${buildout:directory}/etc
services = ${:etc}/run
334
services-on-watch = ${:etc}/service
335 336 337
var = ${buildout:directory}/var
run = ${:var}/run
log = ${:var}/log
338
srv = ${buildout:directory}/srv
339
apachedex = ${monitor-directory:private}/apachedex
340 341 342 343 344 345
rsyslogd-spool = ${:run}/rsyslogd-spool
{% if frontend_caucase_url_list -%}
ca-cert = ${:etc}/ssl.crt
crl = ${:etc}/ssl.crl
client-cert-ca = ${:srv}/client-cert-ca
{% endif -%}
346

347 348 349
[{{ section('resiliency-exclude-file') }}]
# Generate rdiff exclude file in case of resiliency
< = jinja2-template-base
350
template = {{ 'inline:{{ "${directory:log}/**\\n" }}' }}
351 352
rendered = ${directory:srv}/exporter.exclude

353
[{{ section('monitor-generate-apachedex-report') }}]
354 355 356 357 358 359 360 361 362
recipe = slapos.cookbook:cron.d
cron-entries = ${cron:cron-entries}
name = generate-apachedex-report
# The goal is to be executed before logrotate log rotation.
# Here, logrotate-entry-base:frequency = daily, so we run at 23 o'clock every day.
frequency = 0 23 * * *
command = ${monitor-generate-apachedex-report-wrapper:wrapper-path}

[monitor-generate-apachedex-report-wrapper]
363
recipe = slapos.cookbook:wrapper
364
wrapper-path = ${directory:bin}/${:command}
365
command-line = "{{ parameter_dict['run-apachedex-location'] }}" "{{ parameter_dict['apachedex-location'] }}" "${directory:apachedex}" ${monitor-publish-parameters:monitor-base-url}/private/apachedex --apache-log-list "${apachedex-parameters:apache-log-list}" --configuration ${apachedex-parameters:configuration}
366
command = generate-apachedex-report
367

368 369 370 371 372 373 374 375 376 377
[monitor-apachedex-report-config]
recipe = slapos.recipe.template:jinja2
rendered = ${directory:etc}/${:_buildout_section_name_}
template = inline:
  {% for line in slapparameter_dict['apachedex-configuration'] %}
    {# apachedex config files use shlex.split, so we need to quote the arguments. #}
    {# BBB: in python 3 we can use shlex.quote instead. #}
    {{ repr(line.encode('utf-8')) }}
  {% endfor %}

378
[apachedex-parameters]
379
apache-log-list = ${rsyslogd-cfg-parameter-dict:access-log-file}
380
configuration = ${monitor-apachedex-report-config:rendered}
381
promise-threshold = {{ slapparameter_dict['apachedex-promise-threshold'] }}
382 383

[{{ section('monitor-promise-apachedex-result') }}]
384
<= monitor-promise-base
385
promise = check_command_execute
386 387
name = check-apachedex-result.py
config-command = "{{ parameter_dict['promise-check-apachedex-result'] }}" --apachedex_path "${directory:apachedex}" --status_file ${monitor-directory:private}/apachedex.report.json --threshold "${apachedex-parameters:promise-threshold}"
388

389
[{{ section('promise-check-computer-memory') }}]
390
<= monitor-promise-base
391
promise = check_command_execute
392 393
name = check-computer-memory.py
config-command = "{{ parameter_dict["check-computer-memory-binary"] }}" -db ${monitor-instance-parameter:collector-db} --threshold "{{ slapparameter_dict["computer-memory-percent-threshold"] }}" --unit percent
394

395 396
[monitor-instance-parameter]
monitor-httpd-ipv6 = {{ (ipv6_set | list)[0] }}
397
monitor-httpd-port = {{ next_port() }}
398
monitor-title = {{ slapparameter_dict['name'] }}
399
password = {{ slapparameter_dict['monitor-passwd'] }}
400

401
[buildout]
402
extends =
403
  {{ template_monitor }}
404 405
parts +=
  {{ part_list | join('\n  ') }}