# Copyright 2016 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# == Class: tripleo::profile::base::pacemaker
#
# Pacemaker profile for tripleo
#
# === Parameters
#
# [*step*]
#   (Optional) The current step in deployment. See tripleo-heat-templates
#   for more details.
#   Defaults to hiera('step')
#
# [*pcs_tries*]
#   (Optional) The number of times pcs commands should be retried.
#   Defaults to hiera('pcs_tries', 20)
#
# [*pcs_user*]
#   (Optional) The user to set up pcsd with
#   Defaults to 'hacluster'
#
# [*pcs_password*]
#   (Optional) The password to be used for the pcs_user. While it is
#   optional as a parameter, the hiera key 'hacluster_pwd' *must* not
#   be undefined or an error will be generated.
#   Defaults to hiera('hacluster_pwd', undef)
#
# [*remote_short_node_names*]
#   (Optional) List of short node names for pacemaker remote nodes
#   Defaults to hiera('pacemaker_remote_short_node_names', [])
#
# [*remote_node_ips*]
#   (Optional) List of node ips for pacemaker remote nodes
#   Defaults to hiera('pacemaker_remote_node_ips', [])
#
# [*remote_authkey*]
#   (Optional) Authkey for pacemaker remote nodes
#   Defaults to undef
#
# [*remote_reconnect_interval*]
#   (Optional) Reconnect interval for the remote
#   Defaults to hiera('pacemaker_remote_reconnect_interval', 60)
#
# [*remote_monitor_interval*]
#   (Optional) Monitor interval for the remote
#   Defaults to hiera('pacemaker_monitor_reconnect_interval', 20)
#
# [*remote_tries*]
#   (Optional) Number of tries for the remote resource creation
#   Defaults to hiera('pacemaker_remote_tries', 5)
#
# [*remote_try_sleep*]
#   (Optional) Number of seconds to sleep between remote creation tries
#   Defaults to hiera('pacemaker_remote_try_sleep', 60)
#
# [*cluster_recheck_interval*]
#   (Optional) Set the cluster-wide cluster-recheck-interval property
#   If the hiera key does not exist or if it is set to undef, the property
#   won't be changed from its default value when there are no pacemaker_remote
#   nodes. In presence of pacemaker_remote nodes and an undef value it will
#   be set to 60s.
#   Defaults to hiera('pacemaker_cluster_recheck_interval', undef)
#
# [*encryption*]
#   (Optional) Whether or not to enable encryption of the pacemaker traffic
#   Defaults to true
#
# [*resource_op_defaults*]
#   (Optional) A hash allowing to override pacemaker resource op defaults
#   Defaults to undef
#
# [*enable_instanceha*]
#  (Optional) Boolean driving the Instance HA controlplane configuration
#  Defaults to false
#
# [*pcsd_bind_addr*]
#   (Optional) List of IP addresses pcsd should bind to
#   Defaults to undef
#
# [*tls_priorities*]
#   (optional) Sets PCMK_tls_priorities in /etc/sysconfig/pacemaker when set
#   Defaults to hiera('tripleo::pacemaker::tls_priorities', undef)
#
# [*cluster_properties*]
#   (optional) Cluster-wide properties that can be set by an operator via hiera
#   Defaults to {}

class tripleo::profile::base::pacemaker (
  $step                      = Integer(hiera('step')),
  $pcs_tries                 = hiera('pcs_tries', 20),
  $pcs_user                  = 'hacluster',
  $pcs_password              = hiera('hacluster_pwd', undef),
  $remote_short_node_names   = hiera('pacemaker_remote_short_node_names', []),
  $remote_node_ips           = hiera('pacemaker_remote_node_ips', []),
  $remote_authkey            = undef,
  $remote_reconnect_interval = hiera('pacemaker_remote_reconnect_interval', 60),
  $remote_monitor_interval   = hiera('pacemaker_remote_monitor_interval', 20),
  $remote_tries              = hiera('pacemaker_remote_tries', 5),
  $remote_try_sleep          = hiera('pacemaker_remote_try_sleep', 60),
  $cluster_recheck_interval  = hiera('pacemaker_cluster_recheck_interval', undef),
  $encryption                = true,
  $resource_op_defaults      = undef,
  $enable_instanceha         = hiera('tripleo::instanceha', false),
  $pcsd_bind_addr            = undef,
  $tls_priorities            = hiera('tripleo::pacemaker::tls_priorities', undef),
  $cluster_properties        = {},
) {

  if count($remote_short_node_names) != count($remote_node_ips) {
    fail("Count of ${remote_short_node_names} is not equal to count of ${remote_node_ips}")
  }
  validate_legacy(Hash, 'validate_hash', $cluster_properties)

  if $pcs_password == undef {
    fail('The $pcs_password param is undefined, did you forget to include tripleo::profile::base::pacemaker in your role?')
  }
  # During FFU when override keys are set we need to use the old authkey style
  # This should be kept until FFU from CentOS 7->8 is being supported
  if count(hiera('pacemaker_node_ips_override', [])) > 0 {
    $force_old_style_remotes_real = true
  } else {
    $force_old_style_remotes_real = false
  }


  Pcmk_resource <| |> {
    tries     => 10,
    try_sleep => 3,
  }

  if $::hostname == downcase(hiera('pacemaker_short_bootstrap_node_name')) {
    $pacemaker_master = true
  } else {
    $pacemaker_master = false
  }

  # enable_fencing guides the enablement of the stonith-enabled cluster-wide property
  # enable_stonith_resources drives the creation of the stonith resources themselves and happens at
  # step2. The reason for step2 is the following:
  # During step1 the cluster is created (and also the pcmk remote resources in case of IHA)
  # Since stonith resources are created on each node separately we need to have the guarantee that
  # all cluster nodes + remote exist before creating stonith resources for them
  $enable_fencing = str2bool(hiera('enable_fencing', false)) and $step >= 5
  $enable_stonith_resources = str2bool(hiera('enable_fencing', false)) and $step >= 2

  if $step >= 1 {
    if (hiera('pacemaker_short_node_names_override', undef)) {
      $pacemaker_short_node_names = join(hiera('pacemaker_short_node_names_override'), ',')
    } else {
      $pacemaker_short_node_names = join(hiera('pacemaker_short_node_names'), ',')
    }

    $pacemaker_cluster_members = downcase(regsubst($pacemaker_short_node_names, ',', ' ', 'G'))
    $cluster_setup_extras = {
      "totem token=${hiera('corosync_token_timeout', 1000)}" => '',
    }
    # If pacemaker_node_ips is not empty we want to create the array
    # for puppet pacemaker to use as addresses list which is an array of arrays.
    $pacemaker_node_ips = hiera('pacemaker_node_ips_override', hiera('pacemaker_node_ips', []))
    if count($pacemaker_node_ips) > 0 {
      $pacemaker_node_ips_real = $pacemaker_node_ips.map |$x| { Array([$x]) }
    } else {
      $pacemaker_node_ips_real = []
    }

    # (bandini) We want to make sure that any rule that opens up services takes place
    # before we invoke pcs commands (see LP#1866209)
    Firewall<|tag == 'tripleo-firewall-rule'|> -> Exec <|tag == 'pacemaker-auth'|>

    class { 'pacemaker':
      hacluster_pwd => $pcs_password,
    }
    -> class { 'pacemaker::corosync':
      cluster_members      => $pacemaker_cluster_members,
      setup_cluster        => $pacemaker_master,
      cluster_setup_extras => $cluster_setup_extras,
      remote_authkey       => $remote_authkey,
      force_authkey        => $force_old_style_remotes_real,
      cluster_members_addr => $pacemaker_node_ips_real,
      pcsd_bind_addr       => $pcsd_bind_addr,
      tls_priorities       => $tls_priorities,
    }
    if str2bool(hiera('docker_enabled', false)) {
      include systemd::systemctl::daemon_reload

      Package<| name == 'docker' |>
      -> file { '/etc/systemd/system/resource-agents-deps.target.wants':
        ensure => directory,
      }
      -> systemd::unit_file { 'docker.service':
        path   => '/etc/systemd/system/resource-agents-deps.target.wants',
        target => '/usr/lib/systemd/system/docker.service',
        before => Class['pacemaker'],
      }
      -> systemd::unit_file { 'rhel-push-plugin.service':
        path   => '/etc/systemd/system/resource-agents-deps.target.wants',
        target => '/usr/lib/systemd/system/rhel-push-plugin.service',
        before => Class['pacemaker'],
      }
      ~> Class['systemd::systemctl::daemon_reload']
    }

    if $pacemaker_master {
      class { 'pacemaker::stonith':
        disable => !$enable_fencing,
        tries   => $pcs_tries,
      }
      Class['pacemaker::stonith'] -> Exec<|tag == 'pacemaker-scaleup'|>
    }
    if $enable_stonith_resources {
      include tripleo::fencing

      # enable stonith after all Pacemaker resources have been created
      Pcmk_resource<||> -> Class['tripleo::fencing']
      Pcmk_constraint<||> -> Class['tripleo::fencing']
      Exec <| tag == 'pacemaker_constraint' |> -> Class['tripleo::fencing']
      # enable stonith after all fencing devices have been created
      Class['tripleo::fencing'] -> Pcmk_property<|title == 'Enable STONITH'|>
    }
    # We have pacemaker remote nodes configured so let's add them as resources
    # We do this during step 1 right after wait-for-settle, because during step 2
    # resources might already be created on pacemaker remote nodes and we need
    # a guarantee that remote nodes are already up
    if $pacemaker_master and count($remote_short_node_names) > 0 {
      # Creates a { "node" => "ip_address", ...} hash
      $remotes_hash = hash(zip($remote_short_node_names, $remote_node_ips))
      $remote_short_node_names.each |String $remote_short_node| {
        pacemaker::resource::remote { $remote_short_node:
          remote_address     => $remotes_hash[$remote_short_node],
          reconnect_interval => $remote_reconnect_interval,
          op_params          => "monitor interval=${remote_monitor_interval}",
          tries              => $remote_tries,
          try_sleep          => $remote_try_sleep,
          pcs_user           => $pcs_user,
          pcs_password       => $pcs_password,
          # When we force the use of old style remotes we must also use --force
          force              => $force_old_style_remotes_real,
          force_oldstyle     => $force_old_style_remotes_real,
          before             => Exec["exec-wait-for-${remote_short_node}"],
          notify             => Exec["exec-wait-for-${remote_short_node}"],
        }
        $check_command = "pcs status | grep -q -e \"${remote_short_node}[[:blank:]].*Started\""
        exec { "exec-wait-for-${remote_short_node}":
          path      => '/usr/sbin:/usr/bin:/sbin:/bin',
          command   => $check_command,
          unless    => $check_command,
          timeout   => 30,
          tries     => 180,
          try_sleep => 10,
          tag       => 'remote_ready',
        }
      }
    }
  }

  if $enable_instanceha and $pacemaker_master {
    include tripleo::profile::base::pacemaker::instance_ha
  }

  if ($step >= 2 and $pacemaker_master) {
    create_resources('pacemaker::property', $cluster_properties)

    if ! $enable_instanceha {
      include pacemaker::resource_defaults
    }
    # Set pacemaker resource op defaults only when specified
    if $resource_op_defaults != undef {
      class { 'pacemaker::resource_op_defaults':
        tries    => $pcs_tries,
        defaults => $resource_op_defaults,
      }
    }
    # When we have a non-zero number of pacemaker remote nodes we
    # want to set the cluster-recheck-interval property to something
    # lower (unless the operator has explicitely set a value)
    if count($remote_short_node_names) > 0 and $cluster_recheck_interval == undef {
      pacemaker::property{ 'cluster-recheck-interval-property':
        property => 'cluster-recheck-interval',
        value    => '60s',
        tries    => $pcs_tries,
      }
    } elsif $cluster_recheck_interval != undef {
      pacemaker::property{ 'cluster-recheck-interval-property':
        property => 'cluster-recheck-interval',
        value    => $cluster_recheck_interval,
        tries    => $pcs_tries,
      }
    }
    # See LP#1948032. We want to ensure that we set the default resource time out
    # before we create any bundles. Because the default timeout affects how long
    # we wait for podman, and on a starved system the default of 20s might bite us
    # too early on
    Pcmk_resource_default<||> -> Pcmk_bundle<||>
  }
}
