instance-kvm.cfg.jinja2 40.1 KB
Newer Older
1 2 3 4 5 6 7
{# Workaround empty parameter passing #}
{# In case of resilient '' is converted to 'None' string, but with slapproxy '' becomes None #}
{% for k, v in slapparameter_dict.items() %}
{%   if v == 'None' or v is none %}
{%     do slapparameter_dict.__setitem__(k, '') %}
{%   endif %}
{% endfor %}
8
{% set additional_frontend = slapparameter_dict.get('frontend-additional-instance-guid') %}
9 10 11 12 13
{% set enable_http = str(slapparameter_dict.get('enable-http-server', False)).lower() == 'true' -%}
{% set use_tap = str(slapparameter_dict.get('use-tap', True)).lower() == 'true' -%}
{% set use_nat = str(slapparameter_dict.get('use-nat', True)).lower() == 'true' -%}
{% set wipe_disk = str(slapparameter_dict.get('wipe-disk-ondestroy', False)).lower() == 'true' -%}
{% set nat_restrict = str(slapparameter_dict.get('nat-restrict-mode', False)).lower() == 'true' -%}
14
{% set name = slapparameter_dict.get('name', 'localhost') -%}
15
{% set disable_ansible_promise = str(slapparameter_dict.get('disable-ansible-promise', True)).lower() == 'true' -%}
16 17
{% set instance_type = slapparameter_dict.get('type', 'standalone') -%}
{% set nat_rule_list = slapparameter_dict.get('nat-rules', '22 80 443') -%}
18
{% set disk_device_path = slapparameter_dict.get('disk-device-path', None) -%}
19
{% set boot_image_url_list_enabled = 'boot-image-url-list' in slapparameter_dict %}
20
{% set boot_image_url_select_enabled = 'boot-image-url-select' in slapparameter_dict %}
21 22
{% set cpu_max_count = dumps(slapparameter_dict.get('cpu-max-count', int(slapparameter_dict.get('cpu-count', 1)) + 1)) %}
{% set ram_max_size = dumps(slapparameter_dict.get('ram-max-size', int(slapparameter_dict.get('ram-size', 1024)) + 512)) %}
Alain Takoudjou's avatar
Alain Takoudjou committed
23
{% set extends_list = [] -%}
24
{% set part_list = [] -%}
Alain Takoudjou's avatar
Alain Takoudjou committed
25

26 27 28 29 30 31 32 33
{% set bootstrap_url = '' -%}
{% set bootstrap_url_md5sum = '' -%}
{% if slapparameter_dict.get('bootstrap-script-url', '') -%}
{% set url_info_list = slapparameter_dict['bootstrap-script-url'].split('#') -%}
{% set bootstrap_url = url_info_list[0] -%}
{% set bootstrap_url_md5sum = url_info_list[1] -%}
{% endif -%}

34 35 36 37 38 39 40
{% if instance_type == 'cluster' -%}
{% set nat_rule_list = slapparameter_dict.get('nat-rules', '') %}
{% endif -%}
{% if not nat_rule_list or not nat_rule_list.strip() -%}
{%   set nat_rule_list = '' %}
{% endif -%}

Alain Takoudjou's avatar
Alain Takoudjou committed
41 42
{% do extends_list.append(template_monitor) -%}
{% do extends_list.append(logrotate_cfg) -%}
43

44 45 46 47
[slap-network-information]
local-ipv4 = {{ slap_configuration['ipv4-random'] }}
global-ipv6 = {{ slap_configuration['ipv6-random'] }}

48 49 50 51 52 53 54 55 56 57 58
[directory]
recipe = slapos.cookbook:mkdirectory
etc = ${buildout:directory}/etc
bin = ${buildout:directory}/bin
srv = ${buildout:directory}/srv
var = ${buildout:directory}/var
log = ${:var}/log
scripts = ${:etc}/run
services = ${:etc}/service
novnc-conf = ${:etc}/novnc
run = ${:var}/run
59
prerm = ${:etc}/prerm
60
ca-dir = ${:srv}/ssl
61
public = ${:srv}/public/
62 63 64
cron-entries = ${:etc}/cron.d
crontabs = ${:etc}/crontabs
cronstamps = ${:etc}/cronstamps
65 66 67 68
{%- if boot_image_url_list_enabled %}
boot-image-url-list-repository = ${:srv}/boot-image-url-list-repository
boot-image-url-list-var = ${:var}/boot-image-url-list
boot-image-url-list-expose = ${monitor-directory:private}/boot-image-url-list
69
{%- endif %}
70 71 72 73 74
{%- if boot_image_url_select_enabled %}
boot-image-url-select-repository = ${:srv}/boot-image-url-select-repository
boot-image-url-select-var = ${:var}/boot-image-url-select
boot-image-url-select-expose = ${monitor-directory:private}/boot-image-url-select
{%- endif %}
75 76 77 78 79

[create-mac]
recipe = slapos.cookbook:generate.mac
storage-path = ${directory:srv}/mac

80 81 82 83
[create-tap-mac]
recipe = slapos.cookbook:generate.mac
storage-path = ${directory:srv}/tap_mac

84 85 86 87 88
[gen-passwd]
recipe = slapos.cookbook:generate.password
storage-path = ${directory:srv}/passwd
bytes = 8

89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
{% if boot_image_url_select_enabled %}
## boot-image-url-select support BEGIN
[empty-file-state-base-select-promise]
<= monitor-promise-base
module = check_file_state
name = ${:_buildout_section_name_}.py
config-state = empty
# It's very hard to put the username and password correctly, after schema://
# and before the host, as it's not the way how one can use monitor provided
# information, so just show the information in the URL
config-url = ${monitor-base:base-url}/private/boot-image-url-select/${:filename} with username ${monitor-publish-parameters:monitor-user} and password ${monitor-publish-parameters:monitor-password}

[boot-image-url-select-source-config]
recipe = slapos.recipe.template:jinja2
template = inline:
{%- raw %}
  {{ boot_image_url_select }}
{% endraw -%}
107
boot-image-url-select = {{ dumps(slapparameter_dict['boot-image-url-select']) }}
108
context =
109
  key boot_image_url_select :boot-image-url-select
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
rendered = ${directory:etc}/boot-image-url-select.json

[boot-image-url-select-processed-config]
# compares if the current configuration has been used by
# the boot-image-url-select-download, if not, exposes it as not empty file with
# information
recipe = slapos.recipe.build
install =
  import os
  import hashlib
  if not os.path.exists(location):
    os.mkdir(location)
  with open('${:state-file}', 'w') as state_handler:
    try:
      with open('${:config-file}', 'rb') as config_handler, open('${:processed-md5sum}') as processed_handler:
        config_md5sum = hashlib.md5(config_handler.read()).hexdigest()
        processed_md5sum = processed_handler.read()
        if config_md5sum == processed_md5sum:
          state_handler.write('')
        else:
          state_handler.write('config %s != processed %s' % (config_md5sum, processed_md5sum))
    except Exception as e:
      state_handler.write(str(e))

update = ${:install}
config-file = ${boot-image-url-select-source-config:rendered}
state-filename = boot-image-url-select-processed-config.state
state-file = ${directory:boot-image-url-select-expose}/${:state-filename}
processed-md5sum = ${directory:boot-image-url-select-var}/update-image-processed.md5sum

[boot-image-url-select-processed-config-promise]
# promise to check if the configuration provided by the user has been already
# processed by the boot-image-url-select-download script, which runs asynchronously
<= empty-file-state-base-select-promise
filename = ${boot-image-url-select-processed-config:state-filename}
config-filename = ${boot-image-url-select-processed-config:state-file}

[boot-image-url-select-json-config]
# generates json configuration from user configuration
recipe = plone.recipe.command
command = {{ python_executable }} {{ image_download_config_creator }} ${boot-image-url-select-source-config:rendered} ${:rendered} ${directory:boot-image-url-select-repository} ${:error-state-file}
update-command = ${:command}
rendered = ${directory:boot-image-url-select-var}/boot-image-url-select.json
error-state-filename = boot-image-url-select-json-config-error.txt
error-state-file = ${directory:boot-image-url-select-expose}/${:error-state-filename}

[boot-image-url-select-config-state-promise]
# promise to check if configuration has been parsed without errors
<= empty-file-state-base-select-promise
filename = ${boot-image-url-select-json-config:error-state-filename}
config-filename = ${boot-image-url-select-json-config:error-state-file}

[boot-image-url-select-download-wrapper]
# wrapper to execute boot-image-url-select-download on each run
recipe = slapos.cookbook:wrapper
wrapper-path = ${directory:scripts}/boot-image-url-select-updater
command-line = {{ python_executable }} {{ image_download_controller }} ${boot-image-url-select-json-config:rendered} {{ curl_executable_location }} ${:md5sum-state-file} ${:error-state-file} ${boot-image-url-select-processed-config:processed-md5sum}
md5sum-state-filename = boot-image-url-select-download-controller-md5sum-fail.json
md5sum-state-file = ${directory:boot-image-url-select-expose}/${:md5sum-state-filename}
error-state-filename = boot-image-url-select-download-controller-error.text
error-state-file = ${directory:boot-image-url-select-expose}/${:error-state-filename}
hash-existing-files = ${buildout:directory}/software_release/buildout.cfg

[boot-image-url-select-download-md5sum-promise]
# promise to report errors with problems with calculating md5sum of the
# downloaded images
<= empty-file-state-base-select-promise
filename = ${boot-image-url-select-download-wrapper:md5sum-state-filename}
config-filename = ${boot-image-url-select-download-wrapper:md5sum-state-file}

[boot-image-url-select-download-state-promise]
# promise to report errors during download
<= empty-file-state-base-select-promise
filename = ${boot-image-url-select-download-wrapper:error-state-filename}
config-filename = ${boot-image-url-select-download-wrapper:error-state-file}
## boot-image-url-select support END
{% endif %} {# if boot_image_url_select_enabled #}

188 189
{% if boot_image_url_list_enabled %}
## boot-image-url-list support BEGIN
190
[empty-file-state-base-list-promise]
191 192 193 194 195 196 197
<= monitor-promise-base
module = check_file_state
name = ${:_buildout_section_name_}.py
config-state = empty
# It's very hard to put the username and password correctly, after schema://
# and before the host, as it's not the way how one can use monitor provided
# information, so just show the information in the URL
198
config-url = ${monitor-base:base-url}/private/boot-image-url-list/${:filename} with username ${monitor-publish-parameters:monitor-user} and password ${monitor-publish-parameters:monitor-password}
199

200
[boot-image-url-list-source-config]
201 202 203
recipe = slapos.recipe.template:jinja2
template = inline:
{%- raw %}
204
  {{ boot_image_url_list }}
205
{% endraw -%}
206
boot-image-url-list = {{ dumps(slapparameter_dict['boot-image-url-list']) }}
207
context =
208
  key boot_image_url_list :boot-image-url-list
209
rendered = ${directory:etc}/boot-image-url-list.conf
210

211
[boot-image-url-list-processed-config]
212
# compares if the current configuration has been used by
213
# the boot-image-url-list-download, if not, exposes it as not empty file with
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
# information
recipe = slapos.recipe.build
install =
  import os
  import hashlib
  if not os.path.exists(location):
    os.mkdir(location)
  with open('${:state-file}', 'w') as state_handler:
    try:
      with open('${:config-file}', 'rb') as config_handler, open('${:processed-md5sum}') as processed_handler:
        config_md5sum = hashlib.md5(config_handler.read()).hexdigest()
        processed_md5sum = processed_handler.read()
        if config_md5sum == processed_md5sum:
          state_handler.write('')
        else:
          state_handler.write('config %s != processed %s' % (config_md5sum, processed_md5sum))
    except Exception as e:
      state_handler.write(str(e))

update = ${:install}
234 235 236 237
config-file = ${boot-image-url-list-source-config:rendered}
state-filename = boot-image-url-list-processed-config.state
state-file = ${directory:boot-image-url-list-expose}/${:state-filename}
processed-md5sum = ${directory:boot-image-url-list-var}/update-image-processed.md5sum
238

239
[boot-image-url-list-processed-config-promise]
240
# promise to check if the configuration provided by the user has been already
241
# processed by the boot-image-url-list-download script, which runs asynchronously
242
<= empty-file-state-base-list-promise
243 244
filename = ${boot-image-url-list-processed-config:state-filename}
config-filename = ${boot-image-url-list-processed-config:state-file}
245

246
[boot-image-url-list-json-config]
247 248
# generates json configuration from user configuration
recipe = plone.recipe.command
249
command = {{ python_executable }} {{ image_download_config_creator }} ${boot-image-url-list-source-config:rendered} ${:rendered} ${directory:boot-image-url-list-repository} ${:error-state-file}
250
update-command = ${:command}
251 252 253
rendered = ${directory:boot-image-url-list-var}/boot-image-url-list.json
error-state-filename = boot-image-url-list-json-config-error.txt
error-state-file = ${directory:boot-image-url-list-expose}/${:error-state-filename}
254

255
[boot-image-url-list-config-state-promise]
256
# promise to check if configuration has been parsed without errors
257
<= empty-file-state-base-list-promise
258 259
filename = ${boot-image-url-list-json-config:error-state-filename}
config-filename = ${boot-image-url-list-json-config:error-state-file}
260

261 262
[boot-image-url-list-download-wrapper]
# wrapper to execute boot-image-url-list-download on each run
263
recipe = slapos.cookbook:wrapper
264 265 266 267 268 269
wrapper-path = ${directory:scripts}/boot-image-url-list-updater
command-line = {{ python_executable }} {{ image_download_controller }} ${boot-image-url-list-json-config:rendered} {{ curl_executable_location }} ${:md5sum-state-file} ${:error-state-file} ${boot-image-url-list-processed-config:processed-md5sum}
md5sum-state-filename = boot-image-url-list-download-controller-md5sum-fail.json
md5sum-state-file = ${directory:boot-image-url-list-expose}/${:md5sum-state-filename}
error-state-filename = boot-image-url-list-download-controller-error.text
error-state-file = ${directory:boot-image-url-list-expose}/${:error-state-filename}
270 271
hash-existing-files = ${buildout:directory}/software_release/buildout.cfg

272
[boot-image-url-list-download-md5sum-promise]
273 274
# promise to report errors with problems with calculating md5sum of the
# downloaded images
275
<= empty-file-state-base-list-promise
276 277
filename = ${boot-image-url-list-download-wrapper:md5sum-state-filename}
config-filename = ${boot-image-url-list-download-wrapper:md5sum-state-file}
278

279
[boot-image-url-list-download-state-promise]
280
# promise to report errors during download
281
<= empty-file-state-base-list-promise
282 283 284 285
filename = ${boot-image-url-list-download-wrapper:error-state-filename}
config-filename = ${boot-image-url-list-download-wrapper:error-state-file}
## boot-image-url-list support END
{% endif %} {# if boot_image_url_list_enabled #}
286

287
[kvm-controller-parameter-dict]
288
python-path = {{ python_eggs_executable }}
289
vnc-passwd = ${gen-passwd:passwd}
290
socket-path = ${directory:var}/qmp_socket
291
pid-file = ${directory:run}/pid_file
292
kvm-status-path = ${directory:var}/qemu-vm-is-ready
293
cpu-count = ${slap-parameter:cpu-count}
294
cpu-model = ${slap-parameter:cpu-model}
295 296 297
ram-hotplug-slot-size = ${slap-parameter:ram-hotplug-slot-size}
ram-size = ${slap-parameter:ram-size}
enable-device-hotplug = ${slap-parameter:enable-device-hotplug}
298

299 300
[kvm-parameter-dict]
python-path = {{ python_executable }}
301 302 303 304 305
ipv4 = ${slap-network-information:local-ipv4}
ipv6 = ${slap-network-information:global-ipv6}
vnc-ip = ${:ipv4}
vnc-port = 5901

306
default-cdrom-iso = {{ debian_amd64_netinst_location }}
307 308
{% if boot_image_url_list_enabled %}
boot-image-url-list-json-config = ${boot-image-url-list-json-config:rendered}
309
{% else %}
310
boot-image-url-list-json-config =
311
{% endif %}
312 313 314 315 316
{% if boot_image_url_select_enabled %}
boot-image-url-select-json-config = ${boot-image-url-select-json-config:rendered}
{% else %}
boot-image-url-select-json-config =
{% endif %}
317 318 319 320 321
nbd-host = ${slap-parameter:nbd-host}
nbd-port = ${slap-parameter:nbd-port}
nbd2-host = ${slap-parameter:nbd2-host}
nbd2-port = ${slap-parameter:nbd2-port}

322
tap-interface = {{ slap_configuration.get('tap-name', '') }}
323
tap-ipv6-addr = {{ slap_configuration.get('tap-ipv6-addr', '') }}
324 325 326

disk-size = ${slap-parameter:disk-size}
disk-type = ${slap-parameter:disk-type}
327
disk-format = ${slap-parameter:disk-format}
328
disk-device-path = ${slap-parameter:disk-device-path}
329
disk-path = ${directory:srv}/virtual.${slap-parameter:disk-format}
330

331
pid-file-path = ${kvm-controller-parameter-dict:pid-file}
332
socket-path = ${kvm-controller-parameter-dict:socket-path}
333

334 335
enable-device-hotplug = ${kvm-controller-parameter-dict:enable-device-hotplug}
smp-count = ${kvm-controller-parameter-dict:cpu-count}
336
smp-max-count = {{ cpu_max_count }}
337 338

ram-size = ${kvm-controller-parameter-dict:ram-size}
339
ram-max-size = {{ ram_max_size }}
340
init-ram-size = 1024
341
mac-address = ${create-mac:mac-address}
342
tap-mac-address = ${create-tap-mac:mac-address}
343 344

use-tap = ${slap-parameter:use-tap}
345
use-nat = ${slap-parameter:use-nat}
346
nat-rules = {{ nat_rule_list }}
347
nat-restrict= {{ dumps(nat_restrict) }}
348
enable-vhost = ${slap-parameter:enable-vhost}
349 350 351

virtual-hard-drive-url = ${slap-parameter:virtual-hard-drive-url}
virtual-hard-drive-md5sum = ${slap-parameter:virtual-hard-drive-md5sum}
352
virtual-hard-drive-gzipped = ${slap-parameter:virtual-hard-drive-gzipped}
353
hard-drive-url-check-certificate = ${slap-parameter:hard-drive-url-check-certificate}
354 355 356 357 358

shell-path = {{ dash_executable_location }}
qemu-path =  {{ qemu_executable_location }}
qemu-img-path = {{ qemu_img_executable_location }}

359
etc-directory = ${directory:etc}
360
disk-storage-list =
361 362 363 364 365
{% for key, path in storage_dict.items() -%}
{{ '  ' ~ key ~ ' ' ~ path }}
{% endfor -%}
external-disk-number = ${slap-parameter:external-disk-number}
external-disk-size = ${slap-parameter:external-disk-size}
366
external-disk-format = ${slap-parameter:external-disk-format}
367

368
{% if enable_http -%}
369 370 371 372
httpd-port = ${slap-parameter:httpd-port}
{% else -%}
httpd-port = 0
{% endif -%}
373 374 375 376 377 378

# Main instance document server info
{% if slapparameter_dict.get('document-host', '') and slapparameter_dict.get('document-port', '') -%}
cluster-doc-host = ${tunnel-cluster-url:ipv4}
cluster-doc-port = ${tunnel-cluster-url:ipv4-port}
{% else -%}
379
cluster-doc-host =
380 381
cluster-doc-port = 0
{% endif -%}
382
netcat-binary = {{ netcat_bin }}
383
language = ${slap-parameter:keyboard-layout-language}
384

385 386 387 388 389
name = {{ slapparameter_dict.get('name', 'Single KVM') }}
disk-cache = ${slap-parameter:disk-cache}
disk-aio = ${slap-parameter:disk-aio}
auto-ballooning = ${slap-parameter:auto-ballooning}
machine-options = ${slap-parameter:machine-options}
390
cpu-model = ${slap-parameter:cpu-model}
391 392 393

log-file = ${directory:log}/qemu.log

394 395 396 397 398
[kvm-run]
recipe = slapos.recipe.template:jinja2
template = {{ template_kvm_run }}
rendered = ${directory:bin}/kvm_raw
mode = 700
399
context =
400 401
  section parameter_dict kvm-parameter-dict

402
[kvm-controller]
403 404
recipe = slapos.recipe.template:jinja2
template = {{ template_kvm_controller_run }}
405
rendered = ${directory:bin}/kvm_controller_raw
406
mode = 700
407
context =
408 409 410 411 412 413 414 415
  section parameter_dict kvm-controller-parameter-dict

[tunnel-6to4-base]
recipe = slapos.cookbook:wrapper
ipv4 = ${slap-network-information:local-ipv4}
ipv6 = ${slap-network-information:global-ipv6}
wrapper-path = ${directory:services}/6tunnel-${:ipv6-port}
command-line = {{ sixtunnel_executable_location }} -6 -4 -d -l ${:ipv6} ${:ipv6-port} ${:ipv4} ${:ipv4-port}
416
hash-existing-files = ${buildout:directory}/software_release/buildout.cfg
417

418
{% if use_nat and nat_rule_list -%}
419
{%   for port in nat_rule_list.split(' ') -%}
420 421 422 423 424
{%     if ':' in port -%}
{%       set proto, port = port.split(':') -%}
{%     else -%}
{%       set proto, port = 'tcp', port -%}
{%     endif -%}
425
{%     set external_port = 10000 + port|int() -%}
426
{%     set section_name = '6tunnel-' ~ proto ~ '-' ~ external_port -%}
427 428 429 430 431 432 433 434 435 436 437 438 439
[{{ section_name }}]
<= tunnel-6to4-base
ipv4-port = {{ external_port }}
ipv6-port = {{ external_port }}
{%     do part_list.append(section_name) -%}
{%   endfor -%}
{% endif -%}

[kvm-instance]
recipe = slapos.cookbook:wrapper
socket-path = ${kvm-controller-parameter-dict:socket-path}
wrapper-path = ${directory:services}/kvm
command-line = ${kvm-run:rendered}
440
kvm-controller = ${kvm-controller-wrapper:wrapper-path}
441
hash-existing-files = ${buildout:directory}/software_release/buildout.cfg
442 443 444 445 446 447


[kvm-controller-wrapper]
recipe = slapos.cookbook:wrapper
wrapper-path = ${directory:scripts}/kvm_controller
command-line = ${kvm-controller:rendered}
448 449


450
[kvm-vnc-promise]
451 452 453 454 455
<= monitor-promise-base
module = check_port_listening
name = vnc_promise.py
config-hostname = ${kvm-parameter-dict:vnc-ip}
config-port = ${kvm-parameter-dict:vnc-port}
456

457
[kvm-disk-image-corruption-bin]
458 459
recipe = collective.recipe.template
input = inline:#!/bin/sh
460 461 462
  # Return code 0 is "OK"
  # Return code 3 is "found leaks, but image is OK"
  # http://git.qemu.org/?p=qemu.git;a=blob;f=qemu-img.c;h=4e9a7f5741c9cb863d978225829e68fefcae3947;hb=HEAD#l702
463 464 465 466
  if [ "${slap-parameter:disk-device-path}" != "" ]; then
    # disk device option is used, skip qemu img check
    exit 0
  fi
467
  ${kvm-parameter-dict:qemu-img-path} check -U ${kvm-parameter-dict:disk-path} > /dev/null 2>&1
468 469 470 471 472 473
  RETURN_CODE=$?
  if [ $RETURN_CODE -eq 0 ] || [ $RETURN_CODE -eq 3 ]; then
    exit 0
  else
    exit 1
  fi
474
output = ${directory:bin}/kvm-disk-image-corruption
475 476
mode = 700

477 478 479 480 481 482 483
[kvm-disk-image-corruption-promise]
# Check that disk image is not corrupted
<= monitor-promise-base
module = check_command_execute
name = kvm-disk-image-corruption.py
config-command = ${kvm-disk-image-corruption-bin:output}

484
{% if wipe_disk -%}
485 486 487 488 489 490 491 492
{% do part_list.append('wipe-disk-wrapper') -%}
{% set wipe_file_list = '${kvm-parameter-dict:disk-path}' -%}
{% if storage_dict -%}
{% set wipe_file_list = '${kvm-parameter-dict:disk-path}' ~ ' ' ~ '/* '.join(storage_dict.values()) ~ '/*' -%}
{% endif -%}
[wipe-disk-wrapper]
recipe = slapos.cookbook:wrapper
wrapper-path = ${directory:prerm}/slapos_wipe_qemu_disk
493
command-line =
494 495 496
  {{ wipe_disk_wrapper }} -n {{ slapparameter_dict.get('wipe-disk-iterations', 1) }} -suz --check-pid-file ${kvm-parameter-dict:pid-file-path} --file {{ wipe_file_list }}
{% endif -%}

497
[kvm-started-bin]
498 499
recipe = slapos.recipe.template:jinja2
template = {{ qemu_start_promise_tpl }}
500
rendered = ${directory:bin}/qemu-virtual-machine-is-ready
501
mode = 700
502
context =
503 504 505
  raw dash {{ dash_executable_location }}
  raw qemu_ready_path ${kvm-controller-parameter-dict:kvm-status-path}
  raw qemu_service_log_file ${buildout:directory}/.${slap-connection:partition-id}_kvm.log
506

507 508 509 510 511 512
[kvm-started-promise]
<= monitor-promise-base
module = check_command_execute
name = qemu-virtual-machine-is-ready.py
config-command = ${kvm-started-bin:rendered}

513 514 515 516 517
[novnc-instance]
recipe = slapos.cookbook:novnc
path = ${ca-novnc:executable}
ip = ${slap-network-information:global-ipv6}
port = 6080
518 519
vnc-ip = ${kvm-parameter-dict:vnc-ip}
vnc-port = ${kvm-parameter-dict:vnc-port}
520 521 522 523 524 525 526
novnc-location = {{ novnc_location }}
websockify-path = {{ websockify_executable_location }}
ssl-key-path = ${ca-novnc:key-file}
ssl-cert-path = ${ca-novnc:cert-file}

[websockify-sighandler]
recipe = slapos.cookbook:signalwrapper
527
wrapper-path = ${directory:bin}/websockify-sighandler
528 529
wrapped-path = ${novnc-instance:path}

530 531 532 533
[websockify-sighandler-service]
recipe = slapos.cookbook:wrapper
command-line = ${websockify-sighandler:wrapper-path}
wrapper-path = ${directory:services}/websockify
534
hash-existing-files = ${buildout:directory}/software_release/buildout.cfg
535 536 537
wait-for-files =
  ${ca-novnc:key-file}
  ${ca-novnc:cert-file}
538

539 540 541 542 543
[certificate-authority]
recipe = slapos.cookbook:certificate_authority
openssl-binary = {{ openssl_executable_location }}
ca-dir = ${directory:ca-dir}
requests-directory = ${cadirectory:requests}
544
wrapper = ${directory:bin}/certificate_authority
545 546 547 548 549
ca-private = ${cadirectory:private}
ca-certs = ${cadirectory:certs}
ca-newcerts = ${cadirectory:newcerts}
ca-crl = ${cadirectory:crl}

550 551 552 553
[certificate-authority-service]
recipe = slapos.cookbook:wrapper
command-line = ${certificate-authority:wrapper}
wrapper-path = ${directory:services}/certificate_authority
554
hash-existing-files = ${buildout:directory}/software_release/buildout.cfg
555

556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
[cadirectory]
recipe = slapos.cookbook:mkdirectory
requests = ${directory:ca-dir}/requests/
private = ${directory:ca-dir}/private/
certs = ${directory:ca-dir}/certs/
newcerts = ${directory:ca-dir}/newcerts/
crl = ${directory:ca-dir}/crl/

[ca-novnc]
<= certificate-authority
recipe = slapos.cookbook:certificate_authority.request
key-file = ${directory:novnc-conf}/novnc.key
cert-file = ${directory:novnc-conf}/novnc.crt
executable = ${directory:bin}/novnc
wrapper = ${directory:bin}/websockify

[novnc-promise]
573 574 575 576 577
<= monitor-promise-base
module = check_port_listening
name = novnc_promise.py
config-hostname = ${novnc-instance:ip}
config-port = ${novnc-instance:port}
578 579 580 581 582 583 584 585 586 587 588 589 590


#----------------
#--
#-- Deploy cron.

[cron]
recipe = slapos.cookbook:cron
dcrond-binary = {{ dcron_executable_location }}
cron-entries = ${directory:cron-entries}
crontabs = ${directory:crontabs}
cronstamps = ${directory:cronstamps}
catcher = ${cron-simplelogger:wrapper}
591 592 593 594 595 596
binary = ${directory:bin}/crond_raw

[cron-service]
recipe = slapos.cookbook:wrapper
command-line = ${cron:binary}
wrapper-path = ${directory:services}/crond
597
hash-existing-files = ${buildout:directory}/software_release/buildout.cfg
598 599 600 601 602 603 604 605 606 607

[cron-simplelogger]
recipe = slapos.cookbook:simplelogger
wrapper = ${directory:bin}/cron_simplelogger
log = ${directory:log}/crond.log

#----------------
#--
#-- Deploy frontend.

608
[request-slave-frontend-base]
609 610 611 612 613 614 615
recipe = slapos.cookbook:requestoptional
server-url = ${slap-connection:server-url}
key-file = ${slap-connection:key-file}
cert-file = ${slap-connection:cert-file}
computer-id = ${slap-connection:computer-id}
partition-id = ${slap-connection:partition-id}
slave = true
616 617 618 619 620 621 622 623 624 625
config-https-only = True
config-type = websocket
config-url = https://[${novnc-instance:ip}]:${novnc-instance:port}
return = secure_access domain

[request-slave-frontend]
<= request-slave-frontend-base
software-url = ${slap-parameter:frontend-software-url}
software-type = ${slap-parameter:frontend-software-type}
name = ${slap-parameter:frontend-instance-name}
626 627 628
sla-instance_guid = ${slap-parameter:frontend-instance-guid}

[frontend-promise]
629 630 631 632
<= monitor-promise-base
module = check_url_available
name = frontend_promise.py
config-url = ${publish-connection-information:url}
633

634 635 636 637 638 639 640 641 642
{% if additional_frontend %}
[request-slave-frontend-additional]
<= request-slave-frontend-base
software-url = ${slap-parameter:frontend-additional-software-url}
software-type = ${slap-parameter:frontend-additional-software-type}
name = ${slap-parameter:frontend-additional-instance-name}
sla-instance_guid = ${slap-parameter:frontend-additional-instance-guid}

[frontend-additional-promise]
643 644 645 646
<= monitor-promise-base
module = check_url_available
name = frontend_additional_promise.py
config-url = ${publish-connection-information:url-additional}
647
{% endif %}
648

649
{% if enable_http %}
650 651 652 653 654
[httpd]
recipe = slapos.cookbook:simplehttpserver
host = ${slap-network-information:local-ipv4}
port = ${slap-parameter:httpd-port}
base-path = ${directory:public}
655
wrapper = ${directory:bin}/http-server
656 657 658
log-file = ${directory:log}/httpd.log
use-hash-url = false

659 660 661 662
[httpd-service]
recipe = slapos.cookbook:wrapper
command-line = ${httpd:wrapper}
wrapper-path = ${directory:services}/http-server
663
hash-existing-files = ${buildout:directory}/software_release/buildout.cfg
664

665
[httpd-promise]
666 667 668 669 670
<= monitor-promise-base
module = check_port_listening
name = httpd.py
config-hostname = ${httpd:host}
config-port = ${httpd:port}
671 672
{% endif %}

673 674 675
[monitor-instance-parameter]
monitor-httpd-port = 8026
monitor-title = {{ slapparameter_dict.get('name', 'KVM Standalone') }}
676
cors-domains = {{ slapparameter_dict.get('monitor-cors-domains', 'monitor.app.officejs.com') }}
677
{% if slapparameter_dict.get('monitor-username', '') -%}
678
username = {{ slapparameter_dict['monitor-username'] }}
679 680
{% endif -%}
{% if slapparameter_dict.get('monitor-password', '') -%}
681
password = {{ slapparameter_dict['monitor-password'] }}
Alain Takoudjou's avatar
Alain Takoudjou committed
682
{% endif -%}
683
interface-url = {{ slapparameter_dict.get('monitor-interface-url', 'https://monitor.app.officejs.com') }}
Alain Takoudjou's avatar
Alain Takoudjou committed
684

685 686 687 688
# this helper just gives a blank line to insert in multiline values
[helper]
blank-line =

689
[publish-connection-information]
690
<= monitor-publish
691
recipe = slapos.cookbook:publish
Alain Takoudjou's avatar
Alain Takoudjou committed
692
ipv6 = ${slap-network-information:global-ipv6}
693 694
backend-url = https://[${novnc-instance:ip}]:${novnc-instance:port}/vnc.html?auto=1&encrypt=1&password=${kvm-controller-parameter-dict:vnc-passwd}
url = ${request-slave-frontend:connection-secure_access}/vnc.html?auto=1&encrypt=1&password=${kvm-controller-parameter-dict:vnc-passwd}
695
{% if additional_frontend %}
696
url-additional = ${request-slave-frontend-additional:connection-secure_access}/vnc.html?auto=1&encrypt=1&password=${kvm-controller-parameter-dict:vnc-passwd}
697
{% endif %}
698 699
{% set disk_number = len(storage_dict) -%}
maximum-extra-disk-amount = {{ disk_number }}
700
{% set iface = 'ens3' -%}
701
{% if use_nat -%}
702
{%   set iface = 'ens4' -%}
703
{%   if nat_rule_list -%}
704
# Publish NAT port mapping status
705
{%     for port in nat_rule_list.split(' ') -%}
706 707 708 709 710
{%       if ':' in port -%}
{%         set proto, port = port.split(':') -%}
{%       else -%}
{%         set proto, port = 'tcp', port -%}
{%       endif -%}
711
{%       set external_port = 10000 + port|int() -%}
712
nat-rule-port-{{proto}}-{{port}} = ${slap-network-information:global-ipv6} : ${6tunnel-{{proto}}-{{external_port}}:ipv6-port}
713
{%       if slapparameter_dict.get('publish-nat-url', False) -%}
Thomas Gambier's avatar
Thomas Gambier committed
714
nat-rule-url-{{proto}}-{{port}} = [${slap-network-information:global-ipv6}]:${6tunnel-{{proto}}-{{external_port}}:ipv6-port}
715 716 717
{%       endif -%}
{%     endfor -%}
{%   endif -%}
718
{% endif -%}
719
{% if use_tap -%}
720 721
tap-ipv4 = {{ slap_configuration.get('tap-ipv4-addr', '') }}
tap-ipv6 = {{ slap_configuration.get('tap-ipv6-addr', '') }}
722
{% endif -%}
723 724

{% set kvm_http = 'http://${slap-network-information:local-ipv4}:' ~ slapparameter_dict.get('httpd-port', 8081) -%}
725 726
{% if enable_http %}
{%   if use_nat -%}
727 728
{%     set kvm_http = 'http://10.0.2.100' -%}
{%   endif %}
Alain Takoudjou's avatar
Alain Takoudjou committed
729
{%   if slapparameter_dict.get('authorized-key', '') and slapparameter_dict.get('type', '') == 'cluster' -%}
730
key_info = Get the publick key file in your VM with the command: wget {{ kvm_http }}/authorized_keys
731 732 733
{%   endif %}
{% endif %}

734
ipv6-network-info =
735
{% if use_tap and slap_configuration.get('tap-ipv6-addr') %}
736
  PERMANENT SOLUTION: in your VM, add the lines below in /etc/network/interfaces and then run: "ifup {{ iface }}"
737 738 739 740 741
  auto {{ iface }}
  iface {{ iface }} inet6 static
  	address {{ slap_configuration.get('tap-ipv6-gateway') }}
  	netmask {{ slap_configuration.get('tap-ipv6-network').split('/')[1] }}
  	gateway {{ slap_configuration.get('tap-ipv6-addr') }}
742
{% if enable_http %}
743
  ${helper:blank-line}
744 745
  TEMPORARY SOLUTION: run in your VM the command: "wget -O- {{ kvm_http }}/${network-config-ipv6:filename} | /bin/sh -"
  (the configuration will be gone after the next reboot)
746 747 748 749
{% endif %}
{% endif %}


750
{% if use_tap and slap_configuration.get('tap-ipv4-addr') -%}
751
[network-config-ipv4]
752
recipe = plone.recipe.command
753 754
filename = netconfig.sh
path = ${directory:public}/${:filename}
755
ipv4-add-address = ip -4 address add {{ slap_configuration.get('tap-ipv4-addr') }}/{{ slap_configuration.get('tap-ipv4-netmask') }} dev \$IFACE noprefixroute
756
ipv4-add-gateway-route = ip route add {{ slap_configuration.get('tap-ipv4-gateway') }} dev \$IFACE
757
{%   if nat_restrict -%}
758
ipv4-add-default-route = ip route add default via {{ slap_configuration.get('tap-ipv4-gateway') }} dev \$IFACE
759
{%   elif global_ipv4_prefix -%}
760
ipv4-add-default-route = ip route add {{ global_ipv4_prefix }} via {{ slap_configuration.get('tap-ipv4-gateway') }} dev \$IFACE src {{ slap_configuration.get('tap-ipv4-addr') }}
761
{%   else -%}
762
ipv4-add-default-route =
763
{%   endif -%}
764
ipv4-set-link-up = ip link set dev \$IFACE up
765
command =
766 767 768 769
  cat > ${:path} << EOF
  #!/bin/sh
  IFACE={{ iface }}
  #try to be compatible with OS with old names
770 771
  ip a | grep eth0: && [ \$IFACE = ens3 ] && IFACE=eth0
  ip a | grep eth1: && [ \$IFACE = ens4 ] && IFACE=eth1
772
  ${:ipv4-add-address}
773
  ${:ipv4-add-gateway-route}
774 775
  ${:ipv4-add-default-route}
  ${:ipv4-set-link-up}
776 777 778 779
  EOF
update-command = ${:command}
{% endif -%}

780
{% if use_tap and slap_configuration.get('tap-ipv6-addr') -%}
781 782
[network-config-ipv6]
recipe = plone.recipe.command
783 784 785
filename = ipv6_config.sh
path = ${directory:public}/${:filename}
ipv6-add-address = ip -6 address add {{ slap_configuration.get('tap-ipv6-gateway') }}/{{ slap_configuration.get('tap-ipv6-network').split('/')[1] }} dev \$IFACE
786
ipv6-add-default-route =
787 788
  ip -6 route del default ; ip -6 route add default dev \$IFACE via {{ slap_configuration.get('tap-ipv6-addr') }}
ipv6-set-link-up = ip link set dev \$IFACE up
789 790 791 792 793 794
command =
  cat > ${:path} << EOF
  #!/bin/sh
  IFACE={{ iface }}
  ${:ipv6-add-address}
  ${:ipv6-add-default-route}
795
  ${:ipv6-set-link-up}
796
  EOF
797
update-command = ${:command}
798
{% endif -%}
799

800 801 802 803 804 805 806 807 808 809 810 811 812
{% macro writefile(section_name, file_path, content, mode='') -%}
{% set data_list =  content.split('\n') -%}
[{{ section_name }}]
recipe = collective.recipe.template
input = inline:
  {{ data_list | join('\n  ') }}
output = {{ file_path }}
mode = {{ mode }}
{% endmacro -%}

# write vm-data into file public/data
{{ writefile('vm-data-content', '${directory:public}/data', slapparameter_dict.get('data-to-vm', ''), '700') }}

813
{% if slapparameter_dict.get('authorized-key', '') -%}
814
# write public key for vms to public/authorized_keys
815 816
{{   writefile('get-authorized-key', '${directory:public}/authorized_keys', slapparameter_dict.get('authorized-key', ''), '700') }}
{% endif -%}
817

818
{% if use_tap and nat_restrict -%}
819 820 821 822 823
# Ask to set default to tap interface in the vm
{{ writefile('set-default-interface', '${directory:public}/delDefaultIface', iface, '600') }}
{% do part_list.append('set-default-interface') -%}
{% endif -%}

824 825 826
[publish-host-config]
recipe = plone.recipe.command
name = {{ slapparameter_dict.get('name', 'localhost') }}
827
{% if use_tap and slap_configuration.get('tap-ipv4-addr') -%}
828
local-ipv4 = {{ slap_configuration['tap-ipv4-addr'] }}
829 830 831
gateway = {{ slap_configuration.get('tap-ipv4-gateway') }}
netmask = {{ slap_configuration.get('tap-ipv4-network') }}
network = {{ slap_configuration.get('tap-ipv4-netmask') }}
832 833
{% else -%}
local-ipv4 = 127.0.0.1
834 835 836
gateway =
netmask =
network =
837 838 839
{% endif -%}
path-host = ${directory:public}/hostname
path-ip = ${directory:public}/ipv4
840 841 842
path-gateway = ${directory:public}/gateway
path-network = ${directory:public}/network
path-netmask = ${directory:public}/netmask
843
command =
844 845
  rm -f ${:path-host}
  rm -f ${:path-ip}
846 847 848
  rm -f ${:path-gateway}
  rm -f ${:path-network}
  rm -f ${:path-netmask}
849 850
  echo "${:name}" > ${:path-host}
  echo "${:local-ipv4}" > ${:path-ip}
851 852 853
  echo "${:gateway}" > ${:path-gateway}
  echo "${:network}" > ${:path-network}
  echo "${:netmask}" > ${:path-netmask}
854 855
update-command = ${:command}

856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875
# To access documents of main instance (in case of kvm-cluster) through http
[cluster-url-path]
recipe = slapos.recipe.template:jinja2
template = {{ template_content }}
filename = cluster.hash
rendered = ${directory:public}/${:filename}
hash-url = https://10.0.2.101:443/{{ slapparameter_dict.get('document-path', '') }}
context =
    key content_list :hash-url
    raw sep #

# This 6to4 tunnel help to access document url in ipv4
[tunnel-cluster-url]
recipe = slapos.cookbook:ipv4toipv6
ipv6 = {{ slapparameter_dict.get('document-host', '') }}
ipv4 = ${slap-network-information:local-ipv4}
ipv6-port = {{ slapparameter_dict.get('document-port', '') }}
ipv4-port = 16936
shell-path = {{ dash_executable_location }}
6tunnel-path = {{ sixtunnel_executable_location }}
876 877 878 879 880 881
runner-path = ${directory:bin}/6tunnel-cluster

[tunnel-cluster-service]
recipe = slapos.cookbook:wrapper
command-line = ${tunnel-cluster-url:runner-path}
wrapper-path = ${directory:services}/6tunnel-cluster
882
hash-existing-files = ${buildout:directory}/software_release/buildout.cfg
883

884
[ansible-vm-bin]
885 886
recipe = slapos.recipe.template:jinja2
template = {{ ansible_promise_tpl }}
887
rendered = ${directory:bin}/ansible_{{ name }}
888 889 890 891 892
extensions = jinja2.ext.do
context =
  raw logs ${directory:public}/ansible
  raw name {{ name }}

893 894
[ansible-vm-promise]
<= monitor-promise-base
895
module = check_command_execute
896 897 898
name = ansible_{{ name }}.py
config-command = ${ansible-vm-bin:rendered}

899 900 901 902 903 904 905
[download-bootstrap-script]
recipe = plone.recipe.command
file-location = ${directory:public}/vm-bootstrap
command = {{ python_executable }} {{ file_download_script }} {{ bootstrap_url }} {{ bootstrap_url_md5sum }} ${:file-location}
update-command =
stop-on-error = true

906 907 908 909 910
[logrotate-vm-bootstrap]
< = logrotate-entry-base
name = vm-bootstrap
log = ${directory:public}/ansible/vm-bootstrap.log

911 912
[slap-parameter]
# Default values if not specified
913 914
frontend-software-type = RootSoftwareInstance
frontend-software-url = http://git.erp5.org/gitweb/slapos.git/blob_plain/HEAD:/software/apache-frontend/software.cfg
915
frontend-instance-guid =
916 917 918 919 920
frontend-instance-name = VNC Real Frontend
frontend-additional-software-type = RootSoftwareInstance
frontend-additional-software-url = http://git.erp5.org/gitweb/slapos.git/blob_plain/HEAD:/software/apache-frontend/software.cfg
frontend-additional-instance-guid =
frontend-additional-instance-name = VNC Real Frontend Additional
921 922 923 924
nbd-port = 1024
nbd-host =
nbd2-port = 1024
nbd2-host =
925
boot-image-url-list =
926

927
enable-device-hotplug = False
928
ram-size = 1024
929
ram-hotplug-slot-size = 512
930 931
disk-size = 10
disk-type = virtio
932 933
disk-format = qcow2
disk-device-path =
934 935

cpu-count = 1
936 937 938
disk-cache = writeback
disk-aio = native
auto-ballooning = True
939
machine-options =
940
cpu-model = host
941 942

nat-rules = 22 80 443
943
use-nat = True
944
use-tap = True
945
nat-restrict-mode = False
946
enable-vhost = False
947 948 949

virtual-hard-drive-url =
virtual-hard-drive-md5sum =
950
virtual-hard-drive-gzipped = False
951 952
# if virtual-hard-drive-url use https, then specify if https certificate should be checked or not
hard-drive-url-check-certificate = True
953 954 955

external-disk-number = 0
external-disk-size = 20
956
external-disk-format = qcow2
957

958
# Help to get some configuration files into the vm from http
959 960
enable-http-server = False
httpd-port = 8081
961
# for auto config, the public key file will be available in the VM via url http://10.0.2.100/authorized_key if use-nat = True
962
authorized-key =
963 964 965

# send some content which will be accessible to the vm through static url: http://10.0.2.100/data
data-to-vm =
966

967 968
# Change keyboard layout language (Change to en-us if you face some bad bihaviors)
keyboard-layout-language = fr
969

970
{% for k, v in slapparameter_dict.items() -%}
971 972 973 974
{% if k == 'authorized-key' and v -%}
{% set key_list =  v.split('\n') -%}
{{ k }} =
  {{ key_list | join('\n  ') }}
975
{% elif k == 'boot-image-url-list' %}
976 977
{# needs to decorate possibly multiline or maybe unsafe value #}
{{ k }} = {{ dumps(v) }}
978
{% else -%}
979
{{ k }} = {{ v }}
980
{% endif -%}
981 982
{% endfor -%}

983 984 985 986 987 988 989
#############################
#
# Instanciate kvm (Buildout Section)
#
#############################

{% if slapparameter_dict.get('document-host', '') %}
990
# Set Additionals parts
991 992
{%   do part_list.append('cluster-url-path') -%}
{% endif -%}
993
{% if enable_http %}
994
{%   do part_list.extend(['httpd', 'httpd-service', 'httpd-promise', 'publish-host-config']) -%}
995 996 997
{% if slapparameter_dict.get('data-to-vm', '') %}
{%   do part_list.append('vm-data-content') -%}
{% endif -%}
998
{% if not disable_ansible_promise %}
999 1000
{%   do part_list.extend(['ansible-vm-promise', 'logrotate-vm-bootstrap']) -%}
{% endif -%}
Alain Takoudjou's avatar
Alain Takoudjou committed
1001
{% if slapparameter_dict.get('authorized-key', '') and slapparameter_dict.get('type', '') == 'cluster' %}
1002
{%   do part_list.append('get-authorized-key') -%}
1003 1004 1005 1006
{% endif -%}
{% if slapparameter_dict.get('bootstrap-script-url', '') -%}
{%   do part_list.append('download-bootstrap-script') -%}
{% endif -%}
1007 1008 1009
{% if slapparameter_dict.get('document-port', '') -%}
{%   do part_list.append('tunnel-cluster-service') -%}
{% endif -%}
1010 1011 1012 1013

{% endif -%}


1014 1015 1016 1017 1018 1019
{% if disk_device_path %}
{%   do part_list.append('disk-device-permission') -%}
[disk-device-permission]
recipe = slapos.recipe.template:jinja2
template = inline:
{%- raw %}
1020 1021 1022 1023 1024
  {%- set disk_list = [] %}
  {%- for disk in disk_device_path.split() %}
  {%-   do disk_list.append({'disk': disk}) %}
  {%- endfor -%}
  {{ json_module.dumps(disk_list) }}
1025 1026
{% endraw -%}
rendered = ${buildout:directory}/.slapos-disk-permission
1027
extensions = jinja2.ext.do
1028
context =
1029
  import json_module json
1030
  raw disk_device_path {{disk_device_path}}
1031 1032 1033 1034 1035 1036 1037 1038

{%   do part_list.append('wipe-disk-device-wrapper') -%}
[wipe-disk-device-wrapper]
recipe = slapos.cookbook:wrapper
wrapper-path = ${directory:prerm}/slapos_wipe_device_disk
command-line =
  dd if=/dev/zero of={{disk_device_path}} bs=4096 count=500k

1039 1040
{% endif -%}

1041 1042
[instance-kvm-parts]
# Expose parts for easy addition in profiles which extend this one like resilient
1043 1044
parts =
  certificate-authority
1045
  certificate-authority-service
1046 1047
  publish-connection-information
  kvm-instance
1048
  kvm-controller-wrapper
1049 1050 1051
  kvm-vnc-promise
  kvm-disk-image-corruption-promise
  websockify-sighandler
1052
  websockify-sighandler-service
1053
  novnc-promise
1054
  kvm-started-promise
1055
  cron
1056
  cron-service
1057
  cron-entry-logrotate
1058
  frontend-promise
1059 1060 1061 1062 1063 1064
{% if boot_image_url_list_enabled %}
  boot-image-url-list-download-wrapper
  boot-image-url-list-config-state-promise
  boot-image-url-list-download-md5sum-promise
  boot-image-url-list-download-state-promise
  boot-image-url-list-processed-config-promise
1065
{% endif %}
1066 1067 1068 1069 1070 1071 1072
{% if boot_image_url_select_enabled %}
  boot-image-url-select-download-wrapper
  boot-image-url-select-config-state-promise
  boot-image-url-select-download-md5sum-promise
  boot-image-url-select-download-state-promise
  boot-image-url-select-processed-config-promise
{% endif %}
1073 1074 1075
{% if additional_frontend %}
  frontend-additional-promise
{% endif %}
1076
# monitor parts
1077
  monitor-base
1078 1079 1080
# Complete parts with sections
  {{ part_list | join('\n  ') }}

1081 1082 1083
[buildout]
parts = ${instance-kvm-parts:parts}

1084
extends =
1085 1086 1087 1088 1089 1090
#  Add extends list
 {{ extends_list | join('\n  ') }}
#  {{ template_httpd_cfg }}

eggs-directory = {{ eggs_directory }}
develop-eggs-directory = {{ develop_eggs_directory }}
1091
offline = true