{"heat_template_version": "wallaby", "description": "OpenStack containerized HAproxy service for pacemaker\n", "parameters": {"ContainerHAProxyImage": {"description": "image", "type": "string", "tags": ["role_specific"]}, "ContainerHAProxyConfigImage": {"description": "The container image to use for the haproxy config_volume", "type": "string", "tags": ["role_specific"]}, "ClusterCommonTag": {"default": false, "description": "When set to false, a pacemaker service is configured to use a floating tag for its container image name, e.g. 'REGISTRY/NAMESPACE/IMAGENAME:pcmklatest'. When set to true, the service uses a floating prefix as well, e.g. 'cluster.common.tag/IMAGENAME:pcmklatest'.", "type": "boolean"}, "ClusterFullTag": {"default": true, "description": "When set to true, the pacemaker service uses a fully constant tag for its container image name, e.g. 'cluster.common.tag/SERVICENAME:pcmklatest'.", "type": "boolean"}, "ServiceData": {"default": {}, "description": "Dictionary packing service data", "type": "json"}, "ServiceNetMap": {"default": {}, "description": "Mapping of service_name -> network name. Typically set via parameter_defaults in the resource registry. Use parameter_merge_strategies to merge it with the defaults.", "type": "json"}, "EndpointMap": {"default": {}, "description": "Mapping of service endpoint -> protocol. Typically set via parameter_defaults in the resource registry.", "type": "json"}, "SSLCertificate": {"default": "", "description": "The content of the SSL certificate (without Key) in PEM format.\n", "type": "string"}, "PublicSSLCertificateAutogenerated": {"default": false, "description": "Whether the public SSL certificate was autogenerated or not.\n", "type": "boolean"}, "EnablePublicTLS": {"default": true, "description": "Whether to enable TLS on the public interface or not.\n", "type": "boolean"}, "DeployedSSLCertificatePath": {"default": "/etc/pki/tls/private/overcloud_endpoint.pem", "description": "The filepath of the certificate as it will be stored in the controller.\n", "type": "string"}, "RoleName": {"default": "", "description": "Role name on which the service is applied", "type": "string"}, "RoleParameters": {"default": {}, "description": "Parameters specific to the role", "type": "json"}, "EnableInternalTLS": {"type": "boolean", "default": false}, "InternalTLSCAFile": {"default": "/etc/ipa/ca.crt", "type": "string", "description": "Specifies the default CA cert to use if TLS is used for services in the internal network."}, "HAProxyInternalTLSCertsDirectory": {"default": "/etc/pki/tls/certs/haproxy", "type": "string"}, "HAProxyInternalTLSKeysDirectory": {"default": "/etc/pki/tls/private/haproxy", "type": "string"}, "HAProxyLoggingSource": {"type": "json", "default": {"tag": "openstack.haproxy", "file": "/var/log/containers/haproxy/haproxy.log", "startmsg.regex": "^[a-zA-Z]{3} [ 123][0-9] [:0-9]{8}"}}, "HAProxySyslogAddress": {"default": "/dev/log", "description": "Syslog address where HAproxy will send its log", "type": "string"}, "HAProxySyslogFacility": {"default": "local0", "description": "Syslog facility HAProxy will use for its logs", "type": "string"}, "ConfigDebug": {"default": false, "description": "Whether to run config management (e.g. Puppet) in debug mode.", "type": "boolean"}, "ContainerCli": {"type": "string", "default": "podman", "description": "CLI tool used to manage containers.", "constraints": [{"allowed_values": ["docker", "podman"]}]}, "DeployIdentifier": {"default": "", "type": "string", "description": "Setting this to a unique value will re-run any deployment tasks which perform configuration on a Heat stack-update.\n"}}, "conditions": {"public_tls_enabled": {"and": [{"get_param": "EnablePublicTLS"}, {"or": [{"not": {"equals": [{"get_param": "SSLCertificate"}, ""]}}, {"get_param": "PublicSSLCertificateAutogenerated"}]}]}}, "resources": {"ContainersCommon": {"type": "file:///usr/share/openstack-tripleo-heat-templates/deployment/containers-common.yaml"}, "HAProxyBase": {"type": "file:///usr/share/openstack-tripleo-heat-templates/deployment/haproxy/haproxy-container-puppet.yaml", "properties": {"ServiceData": {"get_param": "ServiceData"}, "ServiceNetMap": {"get_param": "ServiceNetMap"}, "EndpointMap": {"get_param": "EndpointMap"}, "RoleName": {"get_param": "RoleName"}, "RoleParameters": {"get_param": "RoleParameters"}}}, "HAProxyPublicTLS": {"type": "OS::TripleO::Services::HAProxyPublicTLS", "properties": {"ServiceData": {"get_param": "ServiceData"}, "ServiceNetMap": {"get_param": "ServiceNetMap"}, "EndpointMap": {"get_param": "EndpointMap"}, "RoleName": {"get_param": "RoleName"}, "RoleParameters": {"get_param": "RoleParameters"}}}, "HAProxyInternalTLS": {"type": "OS::TripleO::Services::HAProxyInternalTLS", "properties": {"ServiceData": {"get_param": "ServiceData"}, "ServiceNetMap": {"get_param": "ServiceNetMap"}, "EndpointMap": {"get_param": "EndpointMap"}, "RoleName": {"get_param": "RoleName"}, "RoleParameters": {"get_param": "RoleParameters"}}}, "RoleParametersValue": {"type": "OS::Heat::Value", "properties": {"type": "json", "value": {"map_replace": [{"map_replace": [{"ContainerHAProxyImage": "ContainerHAProxyImage", "ContainerHAProxyConfigImage": "ContainerHAProxyConfigImage"}, {"values": {"get_param": ["RoleParameters"]}}]}, {"values": {"ContainerHAProxyImage": {"get_param": "ContainerHAProxyImage"}, "ContainerHAProxyConfigImage": {"get_param": "ContainerHAProxyConfigImage"}}}]}}}}, "outputs": {"role_data": {"description": "Role data for the HAproxy role.", "value": {"service_name": "haproxy", "firewall_rules": {"get_attr": ["HAProxyBase", "role_data", "firewall_rules"]}, "monitoring_subscription": {"get_attr": ["HAProxyBase", "role_data", "monitoring_subscription"]}, "ansible_group_vars": {"get_attr": ["HAProxyBase", "role_data", "ansible_group_vars"]}, "config_settings": {"map_merge": [{"get_attr": ["HAProxyBase", "role_data", "config_settings"]}, {"tripleo::haproxy::haproxy_service_manage": false, "tripleo::haproxy::mysql_clustercheck": true, "tripleo::haproxy::haproxy_log_address": {"get_param": "HAProxySyslogAddress"}, "tripleo::haproxy::haproxy_log_facility": {"get_param": "HAProxySyslogFacility"}}, {"haproxy_docker": true, "tripleo::profile::pacemaker::haproxy_bundle::container_backend": {"get_param": "ContainerCli"}, "tripleo::profile::pacemaker::haproxy_bundle::tls_mapping": {"list_concat": [{"if": ["public_tls_enabled", [{"get_param": "DeployedSSLCertificatePath"}]]}, {"if": [{"get_param": "EnableInternalTLS"}, [{"get_param": "InternalTLSCAFile"}, {"get_param": "HAProxyInternalTLSKeysDirectory"}, {"get_param": "HAProxyInternalTLSCertsDirectory"}]]}]}, "tripleo::profile::pacemaker::haproxy_bundle::tls_mapping_init_bundle": {"list_concat": [{"if": ["public_tls_enabled", [{"get_param": "DeployedSSLCertificatePath"}]]}, {"if": [{"get_param": "EnableInternalTLS"}, [{"get_param": "HAProxyInternalTLSKeysDirectory"}, {"get_param": "HAProxyInternalTLSCertsDirectory"}]]}]}, "tripleo::profile::pacemaker::haproxy_bundle::internal_certs_directory": {"get_param": "HAProxyInternalTLSCertsDirectory"}, "tripleo::profile::pacemaker::haproxy_bundle::internal_keys_directory": {"get_param": "HAProxyInternalTLSKeysDirectory"}, "tripleo::profile::pacemaker::haproxy_bundle::haproxy_docker_image": {"if": [{"get_param": "ClusterFullTag"}, "cluster.common.tag/haproxy:pcmklatest", {"yaql": {"data": {"if": [{"get_param": "ClusterCommonTag"}, {"yaql": {"data": {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyImage"]}, "expression": "concat(\"cluster.common.tag/\", $.data.rightSplit(separator => \"/\", maxSplits => 1)[1])"}}, {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyImage"]}]}, "expression": "concat($.data.rightSplit(separator => \":\", maxSplits => 1)[0], \":pcmklatest\")"}}]}}]}, "service_config_settings": {"rsyslog": {"tripleo_logging_sources_haproxy": [{"get_param": "HAProxyLoggingSource"}]}}, "puppet_config": {"config_volume": "haproxy", "puppet_tags": "haproxy_config", "step_config": {"list_join": ["\n", ["exec {'wait-for-settle': command => '/bin/true' }", "class tripleo::firewall(){}; define tripleo::firewall::rule( $port = undef, $dport = undef, $sport = undef, $proto = undef, $action = undef, $state = undef, $source = undef, $iniface = undef, $chain = undef, $destination = undef, $extras = undef){}", "['pcmk_bundle', 'pcmk_resource', 'pcmk_property', 'pcmk_constraint', 'pcmk_resource_default'].each |String $val| { noop_resource($val) }", "include tripleo::profile::pacemaker::haproxy_bundle"]]}, "config_image": {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyConfigImage"]}, "volumes": {"yaql": {"expression": "$.data.select($+\":\"+$+\":ro\")", "data": {"list_concat": [{"if": ["public_tls_enabled", [{"get_param": "DeployedSSLCertificatePath"}]]}, {"if": [{"get_param": "EnableInternalTLS"}, [{"get_param": "InternalTLSCAFile"}, {"get_param": "HAProxyInternalTLSKeysDirectory"}, {"get_param": "HAProxyInternalTLSCertsDirectory"}]]}]}}}}, "kolla_config": {"/var/lib/kolla/config_files/haproxy.json": {"command": "bash -c $* -- eval if [ -f /usr/sbin/haproxy-systemd-wrapper ]; then exec /usr/sbin/haproxy-systemd-wrapper -f /etc/haproxy/haproxy.cfg; else exec /usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -Ws; fi", "config_files": [{"source": "/var/lib/kolla/config_files/src/*", "dest": "/", "merge": true, "preserve_properties": true, "optional": true}, {"source": "/var/lib/kolla/config_files/src-tls/*", "dest": "/", "merge": true, "optional": true, "preserve_properties": true}], "permissions": [{"path": "/var/lib/haproxy", "owner": "haproxy:haproxy", "recurse": true}, {"path": {"list_join": ["", [{"get_param": "HAProxyInternalTLSCertsDirectory"}, "/*"]]}, "owner": "haproxy:haproxy", "perm": "0600", "optional": true}, {"path": {"list_join": ["", [{"get_param": "HAProxyInternalTLSKeysDirectory"}, "/*"]]}, "owner": "haproxy:haproxy", "perm": "0600", "optional": true}]}}, "container_config_scripts": {"get_attr": ["ContainersCommon", "container_config_scripts"]}, "host_prep_tasks": {"list_concat": [{"get_attr": ["HAProxyBase", "role_data", "host_prep_tasks"]}, [{"name": "Run puppet on the host to apply IPtables rules", "no_log": true, "shell": "puppet apply {{ (puppet_debug|bool) | ternary('--debug --verbose', '') }} --detailed-exitcodes --summarize --color=false \\\n --modulepath '{{ puppet_modulepath }}' --tags '{{ puppet_tags }}' -e '{{ puppet_execute }}'\n", "register": "puppet_host_outputs", "changed_when": "puppet_host_outputs.rc == 2", "failed_when": false, "vars": {"puppet_execute": "if hiera('enable_load_balancer', true) { class {'::tripleo::haproxy': use_internal_certificates => false, manage_firewall => hiera('tripleo::firewall::manage_firewall', true), }}", "puppet_tags": "tripleo::firewall::rule", "puppet_modulepath": "/etc/puppet/modules:/opt/stack/puppet-modules:/usr/share/openstack-puppet/modules", "puppet_debug": {"get_param": "ConfigDebug"}}}, {"name": "Debug output for task: Run puppet on the host to apply IPtables rules", "debug": {"var": "puppet_host_outputs.stdout_lines | default([]) | union(puppet_host_outputs.stderr_lines | default([]))"}, "when": ["not (ansible_check_mode | bool)", "puppet_host_outputs.rc is defined"], "failed_when": "puppet_host_outputs.rc not in [0, 2]"}]]}, "metadata_settings": {"get_attr": ["HAProxyBase", "role_data", "metadata_settings"]}, "deploy_steps_tasks": {"list_concat": [[{"name": "Configure rsyslog for HAproxy container managed by Pacemaker", "when": "step|int == 1", "block": [{"name": "Check if rsyslog exists", "shell": "systemctl is-active rsyslog", "register": "rsyslog_config"}, {"when": ["rsyslog_config is changed", "rsyslog_config.rc == 0"], "block": [{"name": "Forward logging to haproxy.log file", "blockinfile": {"content": "if $syslogfacility-text == '{{facility}}' and $programname == 'haproxy' then -/var/log/containers/haproxy/haproxy.log\n& stop\n", "create": true, "path": "/etc/rsyslog.d/openstack-haproxy.conf"}, "vars": {"facility": {"get_param": "HAProxySyslogFacility"}}, "register": "logconfig"}, {"name": "restart rsyslog service after logging conf change", "service": {"name": "rsyslog", "state": "restarted"}, "when": "logconfig is changed"}]}]}, {"name": "HAproxy tag container image for pacemaker", "when": "step|int == 1", "import_role": {"name": "tripleo_container_tag"}, "vars": {"container_image": {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyImage"]}, "container_image_latest": {"if": [{"get_param": "ClusterFullTag"}, "cluster.common.tag/haproxy:pcmklatest", {"yaql": {"data": {"if": [{"get_param": "ClusterCommonTag"}, {"yaql": {"data": {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyImage"]}, "expression": "concat(\"cluster.common.tag/\", $.data.rightSplit(separator => \"/\", maxSplits => 1)[1])"}}, {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyImage"]}]}, "expression": "concat($.data.rightSplit(separator => \":\", maxSplits => 1)[0], \":pcmklatest\")"}}]}}}, {"name": "HAproxy HA Wrappers Step", "when": "step|int == 2", "block": [{"name": "HAproxy puppet bundle", "import_role": {"name": "tripleo_ha_wrapper"}, "vars": {"tripleo_ha_wrapper_service_name": "haproxy", "tripleo_ha_wrapper_resource_name": "haproxy-bundle", "tripleo_ha_wrapper_bundle_name": "haproxy-bundle", "tripleo_ha_wrapper_resource_state": "Started", "tripleo_ha_wrapper_puppet_config_volume": "haproxy", "tripleo_ha_wrapper_puppet_execute": "include ::tripleo::profile::base::pacemaker; include ::tripleo::profile::pacemaker::haproxy_bundle", "tripleo_ha_wrapper_puppet_tags": "pacemaker::resource::bundle,pacemaker::property,pacemaker::resource::ip,pacemaker::resource::ocf,pacemaker::constraint::order,pacemaker::constraint::colocation", "tripleo_ha_wrapper_puppet_debug": {"get_param": "ConfigDebug"}, "tripleo_ha_wrapper_config_suffix": ".previous_run"}}]}], {"if": ["public_tls_enabled", {"get_attr": ["HAProxyPublicTLS", "role_data", "deploy_steps_tasks"]}, []]}, {"if": [{"get_param": "EnableInternalTLS"}, {"get_attr": ["HAProxyInternalTLS", "role_data", "deploy_steps_tasks"]}]}]}, "external_update_tasks": [{"when": "step|int == 1", "tags": "ha_image_update", "block": [{"block": [{"name": "Get haproxy image from pacemaker", "become": true, "register": "xmllint_pcmk_haproxy_image", "shell": "xmllint --xpath \"string(//bundle[@id='haproxy-bundle']/podman/@image)\" /var/lib/pacemaker/cib/cib.xml"}, {"name": "Get container haproxy image", "set_fact": {"haproxy_image": {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyImage"]}, "haproxy_image_latest": {"if": [{"get_param": "ClusterFullTag"}, "cluster.common.tag/haproxy:pcmklatest", {"yaql": {"data": {"if": [{"get_param": "ClusterCommonTag"}, {"yaql": {"data": {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyImage"]}, "expression": "concat(\"cluster.common.tag/\", $.data.rightSplit(separator => \"/\", maxSplits => 1)[1])"}}, {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyImage"]}]}, "expression": "concat($.data.rightSplit(separator => \":\", maxSplits => 1)[0], \":pcmklatest\")"}}]}, "pcmk_haproxy_image": "{{xmllint_pcmk_haproxy_image.stdout}}"}}], "delegate_to": "{{ (groups[\"haproxy\"] | difference(groups[\"excluded_overcloud\"]))[0] }}"}, {"name": "haproxy temporary pacemaker container tag in case of image switch", "become": true, "import_role": {"name": "tripleo_ha_image_update"}, "vars": {"tripleo_ha_image_update_node_names": "{{ groups[\"haproxy\"] | difference(groups[\"excluded_overcloud\"]) }}", "tripleo_ha_image_update_bundle": "haproxy-bundle", "tripleo_ha_image_update_new_image": "{{haproxy_image_latest}}", "tripleo_ha_image_update_old_image": "{{pcmk_haproxy_image}}"}, "when": ["(pcmk_haproxy_image != \"\")", "(pcmk_haproxy_image != haproxy_image_latest)"]}]}], "update_tasks": [{"block": [{"name": "Get haproxy image from pacemaker", "become": true, "register": "xmllint_pcmk_haproxy_image", "shell": "xmllint --xpath \"string(//bundle[@id='haproxy-bundle']/podman/@image)\" /var/lib/pacemaker/cib/cib.xml"}, {"name": "Get container haproxy image", "set_fact": {"haproxy_image": {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyImage"]}, "haproxy_image_latest": {"if": [{"get_param": "ClusterFullTag"}, "cluster.common.tag/haproxy:pcmklatest", {"yaql": {"data": {"if": [{"get_param": "ClusterCommonTag"}, {"yaql": {"data": {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyImage"]}, "expression": "concat(\"cluster.common.tag/\", $.data.rightSplit(separator => \"/\", maxSplits => 1)[1])"}}, {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyImage"]}]}, "expression": "concat($.data.rightSplit(separator => \":\", maxSplits => 1)[0], \":pcmklatest\")"}}]}, "pcmk_haproxy_image": "{{xmllint_pcmk_haproxy_image.stdout}}"}}], "when": "(step|int == 0 or step|int == 2)"}, {"name": "Check for update of haproxy container image name", "when": "step|int == 0", "assert": {"that": "pcmk_haproxy_image == haproxy_image_latest", "fail_msg": "haproxy image change detected, run overcloud external-update --tags ha_image_update first"}}, {"name": "Tear-down non-HA haproxy container", "when": ["step|int == 1"], "block": [{"name": "Remove non-HA haproxy container", "include_role": {"name": "tripleo_container_rm"}, "vars": {"tripleo_container_cli": "{{ container_cli }}", "tripleo_containers_to_rm": ["haproxy"]}}]}, {"name": "Set HAProxy upgrade facts", "when": ["step|int == 1"], "block": [{"name": "set is_haproxy_bootstrap_node fact", "tags": "common", "set_fact": "is_haproxy_bootstrap_node={{haproxy_short_bootstrap_node_name|lower == ansible_facts['hostname']|lower}}", "when": ["haproxy_short_bootstrap_node_name|default(false)"]}]}, {"name": "Mount TLS cert if needed", "when": ["step|int == 1", "is_haproxy_bootstrap_node"], "block": [{"name": "Check haproxy public certificate configuration in pacemaker", "command": "cibadmin --query --xpath \"//storage-mapping[@id='haproxy-cert']\"", "failed_when": false, "register": "haproxy_cert_mounted"}, {"name": "Disable the haproxy cluster resource", "pacemaker_resource": {"resource": "haproxy-bundle", "state": "disable", "wait_for_resource": true}, "register": "output", "retries": 5, "until": "output.rc == 0", "when": "haproxy_cert_mounted.rc == 6"}, {"name": "Set HAProxy public cert volume mount fact", "set_fact": {"haproxy_public_cert_path": {"get_param": "DeployedSSLCertificatePath"}, "haproxy_public_tls_enabled": {"if": ["public_tls_enabled", true, false]}}}, {"name": "Add a bind mount for public certificate in the haproxy bundle", "command": "pcs resource bundle update haproxy-bundle storage-map add id=haproxy-cert source-dir={{ haproxy_public_cert_path }} target-dir=/var/lib/kolla/config_files/src-tls/{{ haproxy_public_cert_path }} options=ro", "when": "haproxy_cert_mounted.rc == 6 and haproxy_public_tls_enabled|bool"}, {"name": "Enable the haproxy cluster resource", "pacemaker_resource": {"resource": "haproxy-bundle", "state": "enable", "wait_for_resource": true}, "register": "output", "retries": 5, "until": "output.rc == 0", "when": "haproxy_cert_mounted.rc == 6"}]}, {"name": "Haproxy fetch and retag container image for pacemaker", "tags": "haproxy_syn_block", "when": ["step|int == 2"], "block": [{"name": "Retag pcmklatest to latest haproxy image", "include_role": {"name": "tripleo_container_tag"}, "vars": {"container_image": "{{haproxy_image}}", "container_image_latest": "{{haproxy_image_latest}}"}}]}, {"name": "Move virtual IPs to another node before stopping pacemaker", "when": ["step|int == 1", "hostvars[inventory_hostname][\"haproxy_node_names\"]|default([])|length > 1"], "shell": "CLUSTER_NODE=$(crm_node -n)\necho \"Retrieving all the VIPs which are hosted on this node\"\nVIPS_TO_MOVE=$(crm_mon --as-xml | xmllint --xpath '//resource[@resource_agent=\"ocf:heartbeat:IPaddr2\" and @role = \"Started\" and @managed = \"true\" and ./node[@name = \"'${CLUSTER_NODE}'\"]]/@id' - | sed -e 's/id=//g' -e 's/\"//g')\nfor v in ${VIPS_TO_MOVE}; do\n echo \"Moving VIP $v on another node\"\n pcs resource ban $v ${CLUSTER_NODE} --wait=300\ndone\necho \"Removing the location constraints that were created to move the VIPs\"\nfor v in ${VIPS_TO_MOVE}; do\n echo \"Removing location ban for VIP $v\"\n ban_id=$(cibadmin --query | xmllint --xpath 'string(//rsc_location[@rsc=\"'${v}'\" and @node=\"'${CLUSTER_NODE}'\" and @score=\"-INFINITY\"]/@id)' -)\n if [ -n \"$ban_id\" ]; then\n pcs constraint remove ${ban_id}\n else\n echo \"Could not retrieve and clear location constraint for VIP $v\" 2>&1\n fi\ndone\n"}, {"name": "Wait for 10s to settle connections on new VIPs", "wait_for": {"timeout": 10}, "when": ["step|int == 1", "hostvars[inventory_hostname][\"haproxy_node_names\"]|default([])|length > 1"]}, {"name": "Block local INPUT SYN packets on the backends except mysql", "tags": "haproxy_syn_block", "when": ["step|int == 1"], "shell": "# server controller-0.storage.redhat.local 172.17.3.93:8080 check fall 5 inter 2000 rise 2\n# server controller-0.internalapi.redhat.local fd00:fd00:fd00:2000::176:9292 check fall 5 inter 2000 rise 2\nset -o pipefail\nsource /etc/os-release; test \"${VERSION_ID%*}\" = \"9.0\" && exit 0\ngrep {{ ansible_facts[\"hostname\"]|lower }} /var/lib/config-data/puppet-generated/haproxy/etc/haproxy/haproxy.cfg | grep -v \":3306 \" | \\\nawk '{print $3}' | \\\nwhile read BACKEND; do\n IP=${BACKEND%:*}\n PORT=${BACKEND#\"$IP:\"}\n if [[ $IP =~ .*:.* ]]\n then PROTOCOL=\"ip6\"\n else PROTOCOL=\"ip\"\n fi\necho \"insert rule $PROTOCOL filter INPUT $PROTOCOL daddr $IP tcp dport $PORT tcp flags syn / fin,syn,rst,ack meta time\"\ndone | xargs -i nft {} $(date +%s)-$(date -d'+20 minutes' +%s) counter drop comment \"{{ ansible_facts[\"hostname\"]|lower }}_haproxy_drop\"\n"}, {"name": "Generate block for other nodes OUTPUT SYN packets on the backends except mysql", "tags": "haproxy_syn_block", "when": ["step|int == 1"], "shell": "set -o pipefail\nsource /etc/os-release; test \"${VERSION_ID%*}\" = \"9.0\" && exit 0\ngrep {{ ansible_facts[\"hostname\"]|lower }} /var/lib/config-data/puppet-generated/haproxy/etc/haproxy/haproxy.cfg | grep -v \":3306 \" | \\\nawk '{print $3}' | \\\nwhile read BACKEND; do\n IP=${BACKEND%:*}\n PORT=${BACKEND#\"$IP:\"}\n if [[ $IP =~ .*:.* ]]\n then PROTOCOL=\"ip6\"\n else PROTOCOL=\"ip\"\n fi\n echo \"nft insert rule $PROTOCOL \\$TABLE OUTPUT $PROTOCOL daddr $IP tcp dport $PORT tcp flags syn / fin,syn,rst,ack meta time $(date +%s)-$(date -d'+20 minutes' +%s) counter drop comment \\\"{{ ansible_facts[\"hostname\"]|lower }}_haproxy_drop\\\" \"\ndone\n", "register": "haproxy_iptables_block"}, {"name": "Block OUTPUT SYN packets to this node on other haproxy nodes", "tags": "haproxy_syn_block", "delegate_to": "{{ item }}", "when": ["step|int == 1"], "shell": "set -o pipefail\ngrep {{ ansible_facts[\"hostname\"]|lower }} /var/lib/config-data/puppet-generated/haproxy/etc/haproxy/haproxy.cfg | head -n 1 \\\nawk '{print $3}' | while read BACKEND; do\n IP=${BACKEND%:*}\n if [[ $IP =~ .*:.* ]]\n then PROTOCOL=\"ip6\"\n else PROTOCOL=\"ip\"\n fi\ndone\nTABLE=$(nft list tables | grep -q \"$PROTOCOL raw\" && echo raw || echo filter )\necho \"{{ haproxy_iptables_block.stdout}}\" | while read i; do bash -c \"$i\"; done\n", "loop": "{{ groups[\"haproxy\"] | difference(groups[\"excluded_overcloud\"]) | difference(ansible_facts[\"hostname\"]|lower) }}"}], "post_update_tasks": [{"name": "HAProxy bundle post update", "when": "step|int == 1", "block": [{"name": "HAproxy puppet bundle", "import_role": {"name": "tripleo_ha_wrapper"}, "vars": {"tripleo_ha_wrapper_service_name": "haproxy", "tripleo_ha_wrapper_resource_name": "haproxy-bundle", "tripleo_ha_wrapper_bundle_name": "haproxy-bundle", "tripleo_ha_wrapper_resource_state": "Started", "tripleo_ha_wrapper_puppet_config_volume": "haproxy", "tripleo_ha_wrapper_puppet_execute": "include ::tripleo::profile::base::pacemaker; include ::tripleo::profile::pacemaker::haproxy_bundle", "tripleo_ha_wrapper_puppet_tags": "pacemaker::resource::bundle,pacemaker::property,pacemaker::resource::ip,pacemaker::resource::ocf,pacemaker::constraint::order,pacemaker::constraint::colocation", "tripleo_ha_wrapper_puppet_debug": {"get_param": "ConfigDebug"}, "tripleo_ha_wrapper_config_suffix": ".previous_run"}}], "vars": {"tripleo_ha_wrapper_minor_update": true}}, {"name": "Cleanup local SYN DROP rules", "tags": "haproxy_syn_block", "when": "step|int == 1", "shell": "nft -a list chain filter INPUT | grep {{ ansible_facts[\"hostname\"]|lower }} | grep haproxy_drop | \\\nawk -F# '{print \"nft delete rule filter INPUT \"$2}' | \\\nwhile read i; do bash -c \"$i\"; done\n"}, {"name": "Cleanup SYN DROP rules from other haproxy nodes", "tags": "haproxy_syn_block", "when": "step|int == 1", "delegate_to": "{{ item }}", "loop": "{{ groups[\"haproxy\"] | difference(groups[\"excluded_overcloud\"]) | difference(ansible_facts[\"hostname\"]|lower) }}", "shell": "nft -a list chain raw OUTPUT | grep {{ ansible_facts[\"hostname\"]|lower }} | grep haproxy_drop | \\\nawk -F# '{print \"nft delete rule raw OUTPUT \"$2}' | \\\nwhile read i; do bash -c \"$i\"; done\n"}], "upgrade_tasks": [{"name": "Tear-down non-HA haproxy container", "when": ["step|int == 0"], "block": [{"name": "Remove non-HA haproxy container", "include_role": {"name": "tripleo_container_rm"}, "vars": {"tripleo_container_cli": "{{ container_cli }}", "tripleo_containers_to_rm": ["haproxy"]}}]}, {"name": "Prepare switch of haproxy image name", "when": ["step|int == 0"], "block": [{"name": "Get haproxy image id currently used by pacemaker", "shell": "pcs resource config haproxy-bundle | grep -Eo 'image=[^ ]+' | awk -F= '{print $2;}'", "register": "haproxy_image_current_res", "failed_when": false}, {"name": "Image facts for haproxy", "set_fact": {"haproxy_image_latest": {"if": [{"get_param": "ClusterFullTag"}, "cluster.common.tag/haproxy:pcmklatest", {"yaql": {"data": {"if": [{"get_param": "ClusterCommonTag"}, {"yaql": {"data": {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyImage"]}, "expression": "concat(\"cluster.common.tag/\", $.data.rightSplit(separator => \"/\", maxSplits => 1)[1])"}}, {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyImage"]}]}, "expression": "concat($.data.rightSplit(separator => \":\", maxSplits => 1)[0], \":pcmklatest\")"}}]}, "haproxy_image_current": "{{haproxy_image_current_res.stdout}}"}}, {"name": "Temporarily tag the current haproxy image id with the upgraded image name", "import_role": {"name": "tripleo_container_tag"}, "vars": {"container_image": "{{haproxy_image_current}}", "container_image_latest": "{{haproxy_image_latest}}", "pull_image": false}, "when": ["haproxy_image_current != ''", "haproxy_image_current != haproxy_image_latest"]}, {"name": "Create haproxy retag statefile", "file": {"path": "/var/lib/tripleo/haproxy_needs_retag", "state": "touch"}, "when": ["haproxy_image_current != ''", "haproxy_image_current != haproxy_image_latest"]}]}, {"name": "Update haproxy pcs resource bundle for new container image", "when": ["step|int == 1"], "block": [{"name": "Set upgrade haproxy facts", "set_fact": {"is_haproxy_bootstrap_node": "{{haproxy_short_bootstrap_node_name|lower == ansible_facts['hostname']|lower}}"}}, {"name": "Check for haproxy retag statefile", "stat": {"path": "/var/lib/tripleo/haproxy_needs_retag"}, "register": "haproxy_retag_state_file"}]}, {"name": "Update haproxy pcs resource bundle for new container image", "when": ["step|int == 1", "is_haproxy_bootstrap_node|bool", "haproxy_retag_state_file.stat.exists|bool"], "block": [{"name": "Disable the haproxy cluster resource before container upgrade", "pacemaker_resource": {"resource": "haproxy-bundle", "state": "disable", "wait_for_resource": true}, "register": "output", "retries": 5, "until": "output.rc == 0"}, {"name": "Expose HAProxy stats socket on the host and mount TLS cert if needed", "block": [{"name": "Check haproxy stats socket configuration in pacemaker", "command": "cibadmin --query --xpath \"//storage-mapping[@id='haproxy-var-lib']\"", "failed_when": false, "register": "haproxy_stats_exposed"}, {"name": "Check haproxy public certificate configuration in pacemaker", "command": "cibadmin --query --xpath \"//storage-mapping[@id='haproxy-cert']\"", "failed_when": false, "register": "haproxy_cert_mounted"}, {"name": "Add a bind mount for stats socket in the haproxy bundle", "command": "pcs resource bundle update haproxy-bundle storage-map add id=haproxy-var-lib source-dir=/var/lib/haproxy target-dir=/var/lib/haproxy options=rw", "when": "haproxy_stats_exposed.rc == 6"}, {"name": "Set HAProxy public cert volume mount fact", "set_fact": {"haproxy_public_cert_path": {"get_param": "DeployedSSLCertificatePath"}, "haproxy_public_tls_enabled": {"if": ["public_tls_enabled", true, false]}}}, {"name": "Add a bind mount for public certificate in the haproxy bundle", "command": "pcs resource bundle update haproxy-bundle storage-map add id=haproxy-cert source-dir={{ haproxy_public_cert_path }} target-dir=/var/lib/kolla/config_files/src-tls/{{ haproxy_public_cert_path }} options=ro", "when": ["haproxy_cert_mounted.rc == 6", "haproxy_public_tls_enabled|bool"]}]}, {"name": "Update the haproxy bundle to use the new container image name", "command": "pcs resource bundle update haproxy-bundle container image={{haproxy_image_latest}}"}, {"name": "Enable the haproxy cluster resource", "pacemaker_resource": {"resource": "haproxy-bundle", "state": "enable", "wait_for_resource": true}, "register": "output", "retries": 5, "until": "output.rc == 0"}, {"name": "Remove haproxy retag statefile", "file": {"path": "/var/lib/tripleo/haproxy_needs_retag", "state": "absent"}}]}, {"name": "Retag the pacemaker image if containerized", "when": ["step|int == 3"], "block": [{"block": [{"name": "Get haproxy image from pacemaker", "become": true, "register": "xmllint_pcmk_haproxy_image", "shell": "xmllint --xpath \"string(//bundle[@id='haproxy-bundle']/podman/@image)\" /var/lib/pacemaker/cib/cib.xml"}, {"name": "Get container haproxy image", "set_fact": {"haproxy_image": {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyImage"]}, "haproxy_image_latest": {"if": [{"get_param": "ClusterFullTag"}, "cluster.common.tag/haproxy:pcmklatest", {"yaql": {"data": {"if": [{"get_param": "ClusterCommonTag"}, {"yaql": {"data": {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyImage"]}, "expression": "concat(\"cluster.common.tag/\", $.data.rightSplit(separator => \"/\", maxSplits => 1)[1])"}}, {"get_attr": ["RoleParametersValue", "value", "ContainerHAProxyImage"]}]}, "expression": "concat($.data.rightSplit(separator => \":\", maxSplits => 1)[0], \":pcmklatest\")"}}]}, "pcmk_haproxy_image": "{{xmllint_pcmk_haproxy_image.stdout}}"}}]}, {"block": [{"name": "Retag pcmklatest to latest haproxy image", "include_role": {"name": "tripleo_container_tag"}, "vars": {"container_image": "{{haproxy_image}}", "container_image_latest": "{{haproxy_image_latest}}"}}]}, {"name": "Ensure config works for the new config", "shell": "set -o pipefail\nawk -i inplace -v INPLACE_SUFFIX=.bak '/ rsprep/ {print \" http-response replace-header Location \" $3\" \"$5; next;}; {print} ' /var/lib/config-data/puppet-generated/haproxy/etc/haproxy/haproxy.cfg"}]}]}}}}