diff --git a/config/config.exs b/config/config.exs index cf1b0e5e8c..85e0fad0a4 100644 --- a/config/config.exs +++ b/config/config.exs @@ -90,6 +90,7 @@ config :trento, Trento.Scheduler, debug_logging: false config :trento, Trento.Integration.Telemetry, adapter: Trento.Integration.Telemetry.Suse +config :trento, Trento.Integration.Checks, adapter: Trento.Integration.Checks.Runner config :trento, uuid_namespace: "fb92284e-aa5e-47f6-a883-bf9469e7a0dc", diff --git a/config/runtime.exs b/config/runtime.exs index 52bc1690a0..a312ae650b 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -54,6 +54,15 @@ if config_env() == :prod do ], secret_key_base: secret_key_base + runner_url = + System.get_env("RUNNER_URL") || + raise """ + environment variable RUNNER_URL is missing. + For example: http://localhost:8080 + """ + + config :trento, Trento.Integration.Checks.Runner, runner_url: runner_url + # ## Using releases # # If you are doing OTP releases, you need to instruct Phoenix diff --git a/lib/trento/application/integration/checks/adapter/gen.ex b/lib/trento/application/integration/checks/adapter/gen.ex index 76734fde27..edb6d0c935 100644 --- a/lib/trento/application/integration/checks/adapter/gen.ex +++ b/lib/trento/application/integration/checks/adapter/gen.ex @@ -1,8 +1,10 @@ defmodule Trento.Integration.Checks.Gen do @moduledoc """ - Behaviour of a telemetry adapter. + Behaviour of a runner adapter. """ + alias Trento.Integration.Checks.Models.FlatCatalog + @callback request_execution( execution_id :: String.t(), cluster_id :: [String.t()], @@ -10,4 +12,7 @@ defmodule Trento.Integration.Checks.Gen do selected_checks :: [String.t()] ) :: :ok | {:error, any} + + @callback get_catalog() :: + {:ok, FlatCatalog.t()} | {:error, any} end diff --git a/lib/trento/application/integration/checks/adapter/mock_runner.ex b/lib/trento/application/integration/checks/adapter/mock_runner.ex index 29599108e3..ce1ae742bc 100644 --- a/lib/trento/application/integration/checks/adapter/mock_runner.ex +++ b/lib/trento/application/integration/checks/adapter/mock_runner.ex @@ -9,6 +9,12 @@ defmodule Trento.Integration.Checks.MockRunner do require Logger + alias Trento.Integration.Checks.Models.FlatCatalog + + @json_path Path.join(File.cwd!(), "priv/data/catalog.json") + @catalog @json_path |> File.read!() |> Jason.decode!() + @external_resource @json_path + defstruct [:expected_results] @type t :: %__MODULE__{ @@ -60,6 +66,11 @@ defmodule Trento.Integration.Checks.MockRunner do ) end + @impl true + def get_catalog do + FlatCatalog.new(%{checks: @catalog}) + end + @doc """ Set the expected results for the next execution. """ diff --git a/lib/trento/application/integration/checks/adapter/runner.ex b/lib/trento/application/integration/checks/adapter/runner.ex new file mode 100644 index 0000000000..7ed64cae4a --- /dev/null +++ b/lib/trento/application/integration/checks/adapter/runner.ex @@ -0,0 +1,39 @@ +defmodule Trento.Integration.Checks.Runner do + @moduledoc """ + Trento runner integration adapter + """ + + @behaviour Trento.Integration.Checks.Gen + + alias Trento.Integration.Checks.Models.FlatCatalog + + @impl true + def request_execution(_execution_id, _cluster_id, _hosts, _selected_checks) do + :ok + end + + @impl true + def get_catalog do + runner_url = runner_url() + + case HTTPoison.get("#{runner_url}/api/catalog") do + {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> + case Jason.decode(body) do + {:ok, catalog_json} -> FlatCatalog.new(%{checks: catalog_json}) + {:error, reason} -> {:error, reason} + end + + {:ok, %HTTPoison.Response{status_code: 204}} -> + {:error, :not_ready} + + {:error, %HTTPoison.Error{reason: reason}} -> + {:error, reason} + + _ -> + {:error, :unexpected_response} + end + end + + defp runner_url, + do: Application.fetch_env!(:trento, __MODULE__)[:runner_url] +end diff --git a/lib/trento/application/integration/checks/checks.ex b/lib/trento/application/integration/checks/checks.ex index 7e021e5f79..1cdc7f73ca 100644 --- a/lib/trento/application/integration/checks/checks.ex +++ b/lib/trento/application/integration/checks/checks.ex @@ -1,4 +1,9 @@ defmodule Trento.Integration.Checks do + alias Trento.Integration.Checks.Models.{ + Catalog, + FlatCatalog + } + @moduledoc """ Checks runner service integration """ @@ -7,6 +12,46 @@ defmodule Trento.Integration.Checks do def request_execution(execution_id, cluster_id, hosts, selected_checks), do: adapter().request_execution(execution_id, cluster_id, hosts, selected_checks) + @spec get_catalog :: + {:ok, FlatCatalog.t()} | {:error, any} + def get_catalog do + case adapter().get_catalog() do + {:ok, catalog} -> + {:ok, catalog} + + {:error, reason} -> + {:error, reason} + end + end + + @spec get_catalog_by_provider :: + {:ok, Catalog.t()} | {:error, any} + def get_catalog_by_provider do + case get_catalog() do + {:ok, content} -> + group_by_provider_by_group(content.checks) + + {:error, reason} -> + {:error, reason} + end + end + + defp group_by_provider_by_group(flat_catalog) do + normalized_catalog = + flat_catalog + |> Enum.map(&Map.from_struct/1) + |> Enum.group_by(&Map.take(&1, [:provider]), &Map.drop(&1, [:provider])) + |> Enum.map(fn {key, value} -> Map.put(key, :groups, group_by_group(value)) end) + + Catalog.new(%{providers: normalized_catalog}) + end + + defp group_by_group(groups) do + groups + |> Enum.group_by(&Map.take(&1, [:group]), &Map.drop(&1, [:group])) + |> Enum.map(fn {key, value} -> Map.put(key, :checks, value) end) + end + defp adapter, do: Application.fetch_env!(:trento, __MODULE__)[:adapter] end diff --git a/lib/trento/application/integration/checks/models/catalog.ex b/lib/trento/application/integration/checks/models/catalog.ex new file mode 100644 index 0000000000..79858ca313 --- /dev/null +++ b/lib/trento/application/integration/checks/models/catalog.ex @@ -0,0 +1,17 @@ +defmodule Trento.Integration.Checks.Models.Catalog do + @moduledoc """ + Checks catalog + """ + + @required_fields [ + :providers + ] + + use Trento.Type + + alias Trento.Integration.Checks.Models.Provider + + deftype do + embeds_many :providers, Provider + end +end diff --git a/lib/trento/application/integration/checks/models/check.ex b/lib/trento/application/integration/checks/models/check.ex new file mode 100644 index 0000000000..613accee0d --- /dev/null +++ b/lib/trento/application/integration/checks/models/check.ex @@ -0,0 +1,25 @@ +defmodule Trento.Integration.Checks.Models.Check do + @moduledoc """ + Catalog check + """ + + @required_fields [ + :id, + :name, + :description, + :remediation, + :implementation, + :labels + ] + + use Trento.Type + + deftype do + field :id, :string + field :name, :string + field :description, :string + field :remediation, :string + field :implementation, :string + field :labels, :string + end +end diff --git a/lib/trento/application/integration/checks/models/flat_catalog.ex b/lib/trento/application/integration/checks/models/flat_catalog.ex new file mode 100644 index 0000000000..016e215fcf --- /dev/null +++ b/lib/trento/application/integration/checks/models/flat_catalog.ex @@ -0,0 +1,17 @@ +defmodule Trento.Integration.Checks.Models.FlatCatalog do + @moduledoc """ + Flat checks catalog + """ + + @required_fields [ + :checks + ] + + use Trento.Type + + alias Trento.Integration.Checks.Models.FlatCheck + + deftype do + embeds_many :checks, FlatCheck + end +end diff --git a/lib/trento/application/integration/checks/models/flat_check.ex b/lib/trento/application/integration/checks/models/flat_check.ex new file mode 100644 index 0000000000..d2073d58ba --- /dev/null +++ b/lib/trento/application/integration/checks/models/flat_check.ex @@ -0,0 +1,29 @@ +defmodule Trento.Integration.Checks.Models.FlatCheck do + @moduledoc """ + Flat catalog check + """ + + @required_fields [ + :id, + :provider, + :group, + :name, + :description, + :remediation, + :implementation, + :labels + ] + + use Trento.Type + + deftype do + field :id, :string + field :provider, Ecto.Enum, values: [:azure, :aws, :gcp, :dev, :unknown] + field :group, :string + field :name, :string + field :description, :string + field :remediation, :string + field :implementation, :string + field :labels, :string + end +end diff --git a/lib/trento/application/integration/checks/models/group.ex b/lib/trento/application/integration/checks/models/group.ex new file mode 100644 index 0000000000..c585f2390f --- /dev/null +++ b/lib/trento/application/integration/checks/models/group.ex @@ -0,0 +1,20 @@ +defmodule Trento.Integration.Checks.Models.Group do + @moduledoc """ + Catalog entry by group + """ + + @required_fields [ + :group, + :checks + ] + + use Trento.Type + + alias Trento.Integration.Checks.Models.Check + + deftype do + field :group, :string + + embeds_many :checks, Check + end +end diff --git a/lib/trento/application/integration/checks/models/provider.ex b/lib/trento/application/integration/checks/models/provider.ex new file mode 100644 index 0000000000..7ff44abe00 --- /dev/null +++ b/lib/trento/application/integration/checks/models/provider.ex @@ -0,0 +1,20 @@ +defmodule Trento.Integration.Checks.Models.Provider do + @moduledoc """ + Catalog entry by provider + """ + + @required_fields [ + :provider, + :groups + ] + + use Trento.Type + + alias Trento.Integration.Checks.Models.Group + + deftype do + field :provider, Ecto.Enum, values: [:azure, :aws, :gcp, :dev, :unknown] + + embeds_many :groups, Group + end +end diff --git a/lib/trento_web/controllers/catalog_controller.ex b/lib/trento_web/controllers/catalog_controller.ex new file mode 100644 index 0000000000..611d38557a --- /dev/null +++ b/lib/trento_web/controllers/catalog_controller.ex @@ -0,0 +1,30 @@ +defmodule TrentoWeb.CatalogController do + use TrentoWeb, :controller + + alias Trento.Integration.Checks + + @spec checks_catalog(Plug.Conn.t(), map) :: Plug.Conn.t() + def checks_catalog(conn, %{"flat" => ""}) do + case Checks.get_catalog() do + {:ok, catalog} -> + json(conn, catalog.checks) + + {:error, reason} -> + conn + |> put_status(:bad_request) + |> json(%{error: reason}) + end + end + + def checks_catalog(conn, _) do + case Checks.get_catalog_by_provider() do + {:ok, catalog} -> + json(conn, catalog.providers) + + {:error, reason} -> + conn + |> put_status(:bad_request) + |> json(%{error: reason}) + end + end +end diff --git a/lib/trento_web/controllers/cluster_controller.ex b/lib/trento_web/controllers/cluster_controller.ex index 2f4df6a255..15447aa9b0 100644 --- a/lib/trento_web/controllers/cluster_controller.ex +++ b/lib/trento_web/controllers/cluster_controller.ex @@ -6,10 +6,6 @@ defmodule TrentoWeb.ClusterController do Tags } - @json_path Path.join(File.cwd!(), "priv/data/catalog.json") - @catalog @json_path |> File.read!() |> Jason.decode!() - @external_resource @json_path - @spec list(Plug.Conn.t(), map) :: Plug.Conn.t() def list(conn, _) do clusters = Clusters.get_all_clusters() @@ -98,11 +94,6 @@ defmodule TrentoWeb.ClusterController do end end - @spec checks_catalog(Plug.Conn.t(), map) :: Plug.Conn.t() - def checks_catalog(conn, _) do - json(conn, @catalog) - end - @spec select_checks(Plug.Conn.t(), map) :: Plug.Conn.t() def select_checks(conn, %{"cluster_id" => cluster_id, "checks" => checks}) do case Clusters.select_checks(cluster_id, checks) do diff --git a/lib/trento_web/router.ex b/lib/trento_web/router.ex index 4ab5d301db..597eb5befc 100644 --- a/lib/trento_web/router.ex +++ b/lib/trento_web/router.ex @@ -61,7 +61,7 @@ defmodule TrentoWeb.Router do ClusterController, :request_checks_execution - get "/checks/catalog", ClusterController, :checks_catalog + get "/checks/catalog", CatalogController, :checks_catalog end # Other scopes may use custom stacks. diff --git a/priv/data/catalog.json b/priv/data/catalog.json index e9ea5db5b0..22dc6d615f 100644 --- a/priv/data/catalog.json +++ b/priv/data/catalog.json @@ -1,626 +1,702 @@ [ { + "id": "156F64", + "name": "1.1.1", + "group": "Corosync", "provider": "azure", - "groups": [ - { - "group": "Corosync", - "checks": [ - { - "id": "156F64", - "name": "1.1.1", - "description": "Corosync `token` timeout is set to `30000`\n", - "remediation": "## Abstract\nThe value of the Corosync `token` timeout is not set as recommended.\n\n## Remediation\n\nAdjust the corosync `token` timeout as recommended on the best practices, and reload the corosync configuration\n\n1. Set the correct `token` timeout in the totem session in the corosync config file `/etc/corosync/corosync.conf`. This action must be repeated in all nodes of the cluster.\n ```\n [...]\n totem { \n token: \n }\n [...]\n ``` \n2. Reload the corosync configuration:\n `crm corosync reload`\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when:\n - ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "53D035", - "name": "1.1.1.runtime", - "description": "Corosync is running with `token` timeout set to `30000`\n", - "remediation": "## Abstract\nThe runtime value of the Corosync `token` timeout is not set as recommended.\n\n## Remediation\n\nAdjust the corosync `token` timeout as recommended on the best practices, and reload the corosync configuration\n\n\n1. Set the correct `token` timeout in the totem session in the corosync config file `/etc/corosync/corosync.conf`. This action must be repeated in all nodes of the cluster.\n ```\n [...]\n totem { \n token: \n }\n [...]\n ``` \n2. Reload the corosync configuration:\n `crm corosync reload`\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.token (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.1']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "A1244C", - "name": "1.1.2", - "description": "Corosync `consensus` timeout is set to `36000`\n", - "remediation": "## Remediation\nAdjust the Corosync `consensus` timeout as recommended by the Azure best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "FB0E0D", - "name": "1.1.2.runtime", - "description": "Corosync is running with `consensus` timeout set to `36000`\n", - "remediation": "## Abstract\nThe runtime value of the Corosync `consensus` timeout is not set as recommended.\n\n## Remediation\nAdjust the corosync `consensus` timeout as recommended by the Azure best practices, and reload the corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.consensus (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.2']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "845CC9", - "name": "1.1.3", - "description": "Corosync `max_messages` is set to `20`\n", - "remediation": "## Remediation\nAdjust the Corosync `max_messages` parameter as recommended by the Azure best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "00081D", - "name": "1.1.3.runtime", - "description": "Corosync is running with `max_messages` set to `20`\n", - "remediation": "## Abstract\nThe runtime value of the Corosync `max_messages` parameter is not set as recommended.\n\n## Remediation\nAdjust the corosync `max_messages` parameter as recommended by the Azure best practices, and reload the corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.max_messages (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.3']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "24ABCB", - "name": "1.1.4", - "group": "Corosync", - "description": "Corosync `join` is set to `60`\n", - "remediation": "## Remediation\nAdjust the Corosync `join` parameter as recommended by the Azure best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "822E47", - "name": "1.1.4.runtime", - "description": "Corosync is running with `join` set to `60`\n", - "remediation": "## Abstract\nThe runtime value of the Corosync `join` parameter is not set as recommended.\n\n## Remediation\nAdjust the corosync `join` parameter as recommended by the Azure best practices, and reload the corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.join (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.4']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "21FCA6", - "name": "1.1.5", - "description": "Corosync `token_retransmits_before_loss_const` is set to: `10`\n", - "remediation": "## Remediation\nAdjust the corosync `token_retransmits_before_loss_const` parameter to `10` as recommended by the Azure best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "15F7A8", - "name": "1.1.5.runtime", - "description": "Corosync is running with `token_retransmits_before_loss_const` set to `10`\n", - "remediation": "## Abstract\nThe runtime value of the corosync `token_retransmits_before_loss_const` parameter is not set as recommended\n\n## Remediation\nAdjust the corosync `token_retransmits_before_loss_const` parameter as recommended on the Azure best practices, and reload the corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.token_retransmits_before_loss_const (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.5']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "33403D", - "name": "1.1.6", - "description": "Corosync `transport` is set to `udpu`\n", - "remediation": "## Remediation\nTo change the corosync MCAST transport to UCAST edit the /etc/corosync/corosync.conf\nas in the example\n```\n max_messages: 20\n interface {\n ringnumber: 0\n- bindnetaddr: 10.162.32.167\n- mcastaddr: 239.11.100.41\n mcastport: 5405\n ttl: 1\n }\n+ transport: udpu\n...\n+nodelist {\n+ node {\n+ ring0_addr: 10.162.32.167\n+ nodeid: 1\n+ }\n+\n+ node {\n+ ring0_addr: 10.162.32.89\n+ nodeid: 2\n+ }\n+\n+}\n```\n1. stop the already running cluster by using **systemctl stop pacemaker**\n2. In the totem section, in the interface subsection remove the\nkeys-value pairs **bindnetaddr** and **mcastaddr**\n3. In the totem section add key-value pair **transport: udpu**\n4. Add section nodelist and subsections node for each nodes of the\ncluster, where the **ring0_addr** is the IP address of the node\n\n## References\n- section 9.1.3 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/#id-adapting-the-corosync-and-sbd-configuration\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "7E0221", - "name": "1.1.6.runtime", - "description": "Corosync is running with `transport` set to `udpu`\n", - "remediation": "## Remediation\nTo change the corosync MCAST transport to UCAST edit the /etc/corosync/corosync.conf\nas in the example\n```\n max_messages: 20\n interface {\n ringnumber: 0\n- bindnetaddr: 10.162.32.167\n- mcastaddr: 239.11.100.41\n mcastport: 5405\n ttl: 1\n }\n+ transport: udpu\n...\n+nodelist {\n+ node {\n+ ring0_addr: 10.162.32.167\n+ nodeid: 1\n+ }\n+\n+ node {\n+ ring0_addr: 10.162.32.89\n+ nodeid: 2\n+ }\n+\n+}\n```\n1. stop the already running cluster by using **systemctl stop pacemaker**\n2. In the totem section, in the interface subsection remove the\nkeys-value pairs **bindnetaddr** and **mcastaddr**\n3. In the totem section add key-value pair **transport: udpu**\n4. Add section nodelist and subsections node for each nodes of the\ncluster, where the **ring0_addr** is the IP address of the node\n\n## References\n- section 9.1.3 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/#id-adapting-the-corosync-and-sbd-configuration\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"totem.transport (str) = \" | sed \"s/.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.6']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "C620DC", - "name": "1.1.7", - "description": "Corosync `expected_votes` is set to `2`\n", - "remediation": "## Remediation\nAdjust the corosync `expected_votes` parameter to `2` to make sure pacemaker calculates the actions properly for a two-node cluster.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'quorum {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "6E9B82", - "name": "1.1.8", - "group": "Corosync", - "description": "Corosync `two_node` is set to `1`\n", - "remediation": "## Abstract\nThe runtime value of the corosync `two_node` parameter is not set as recommended.\n\n## Remediation\nAdjust the corosync two_node parameter to `1` to make sure Pacemaker calculates the actions properly for a two-node cluster.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'quorum {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "D78671", - "name": "1.1.8.runtime", - "description": "Corosync is running with `two_node` set to `1`\n", - "remediation": "## Abstract\nThe runtime value of the corosync `two_node` parameter is not set as recommended.\n\n## Remediation\nAdjust the corosync `two_node` parameter to `1` to make sure Pacemaker calculates the actions properly for a two-node cluster,\nand reload the Corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.votequorum.two_node (u8) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.8']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "DA114A", - "name": "1.1.9", - "group": "Corosync", - "description": "Corosync has at least 2 rings configured\n", - "remediation": "## Abstract\nIt is strongly recommended to add a second ring to the corosync communication.\n\n## References\n- section 9.1.3 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/#id-adapting-the-corosync-and-sbd-configuration\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n INTERFACE_COUNT=$(cat /etc/corosync/corosync.conf | grep interface | wc -l)\n [[ $INTERFACE_COUNT -ge \"2\" ]] && exit 0\n exit 1\n check_mode: false\n register: config_updated\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "32CFC6", - "name": "1.1.9.runtime", - "description": "Corosync is running with at least 2 rings\n", - "remediation": "## Abstract\nIt is strongly recommended to add a second ring to the corosync communication.\n\n## References\n- section 9.1.3 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/#id-adapting-the-corosync-and-sbd-configuration\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n INTERFACE_COUNT=$(corosync-cmapctl | grep totem.interface\\\\..*\\.ttl | wc -l)\n [[ ${INTERFACE_COUNT} -ge \"2\" ]] && exit 0\n exit 1\n check_mode: false\n register: config_updated\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - } - ] - }, - { - "group": "Pacemaker", - "checks": [ - { - "id": "205AF7", - "name": "1.2.1", - "description": "Fencing is enabled in the cluster attributes\n", - "remediation": "## Abstract\nFencing is mandatory to guarantee data integrity for your SAP Applications.\nRunning a HA Cluster without fencing is not supported and might cause data loss.\n\n## Remediation\nExecute the following command to enable it:\n```\ncrm configure property stonith-enabled=true\n```\n\n## References\n- https://documentation.suse.com/sle-ha/15-SP3/html/SLE-HA-all/cha-ha-fencing.html#sec-ha-fencing-recommend\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n command: 'crm_attribute -t crm_config -G -n stonith-enabled --quiet'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected[name]\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "373DB8", - "name": "1.2.2", - "description": "Cluster fencing timeout is configured correctly\n", - "remediation": "## Abstract\nThe fencing timeout (`stonith-timeout`) determines the time Pacemaker will wait for fencing to succeed.\nThe recommended values on Azure are `144` seconds for SBD only or `900` seconds when using SBD combined with the Azure Fence agent.\n\n## Remediation\nExecute the following command to adjust the timeout for your usecase:\n```crm configure property stonith-timeout=144```\nor\n```crm configure property stonith-timeout=900```\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n timeout=$(crm_attribute -t crm_config -G -n stonith-timeout --quiet)\n if [[cibadmin -Q --xpath \"//primitive[@type='fence_azure_arm']/@type\" > /dev/null 2>&1 ]]; then\n exit $([[ \"${timeout}\" =~ {{ expected[name + '.fence_azure_arm'] }}s?$ ]])\n else\n exit $([[ \"${timeout}\" =~ {{ expected[name + '.sbd'] }}s?$ ]])\n fi\n check_mode: false\n register: config_updated\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - } - ] - }, - { - "group": "SBD", - "checks": [ - { - "id": "0B6DB2", - "name": "1.3.1", - "description": "`SBD_PACEMAKER` value is correctly set in SBD configuration\n", - "remediation": "## Abstract\nFor proper SBD fencing, make sure that the integration with Pacemaker is enabled.\n**IMPORTANT**: Always verify these steps in a testing environment before doing so in production ones!\n\n## Remediation\nRun the following commands in order:\n\n1. Put cluster into maintenance mode:\n ```crm configure property maintenance-mode=true```\n2. Stop the cluster:\n ```crm cluster stop```\n3. Set the SBD_PACEMAKER parameter to `yes` on `/etc/sysconfig/sbd`:\n ```\n [...]\n SBD_PACEMAKER=\"yes\"\n [...]\n ```\n4. Restart the cluster:\n ```crm cluster start```\n5. Put cluster out of maintenance mode\n ```crm configure property maintenance-mode=false```\n\n## References\n- https://documentation.suse.com/sle-ha/15-SP3/html/SLE-HA-all/cha-ha-storage-protect.html\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/sysconfig/sbd\n regexp: '^SBD_PACEMAKER='\n line: 'SBD_PACEMAKER={{ expected[name] }}'\n register: config_updated\n when:\n - ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "49591F", - "name": "1.3.2", - "description": "`SBD_STARTMODE` is set to `always`\n", - "remediation": "## Abstract\nIf not set to always, SBD will not automatically start if the node was previously fenced as it will expect the cluster in a clean state.\n**IMPORTANT**: Always verify these steps in a testing environment before doing so in production ones!\n\n## Remediation\nRun the following commands in order:\n\n1. Put cluster into maintenance mode:\n ```crm configure property maintenance-mode=true```\n2. Stop the cluster:\n ```crm cluster stop```\n2. Set the SBD_STARTMODE parameter to `always` on `/etc/sysconfig/sbd`:\n ```\n [...]\n SBD_STARTMODE=\"always\"\n [...]\n ```\n3. Restart the cluster:\n ```crm cluster start```\n4. Put cluster out of maintenance mode:\n ```crm configure property maintenance-mode=false```\n\n## References\n- https://documentation.suse.com/sle-ha/15-SP3/html/SLE-HA-all/cha-ha-storage-protect.html\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/sysconfig/sbd\n regexp: '^SBD_STARTMODE='\n line: 'SBD_STARTMODE={{ expected[name] }}'\n register: config_updated\n when:\n - ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "816815", - "name": "1.3.3", - "description": "SBD service is enabled\n", - "remediation": "## Abstract\nIf not enabled, SBD service will not start automatically after reboots, affecting the correct cluster startup.\n\n## Remediation\nTo enable the service, run:\n```\nsystemctl enable sbd\n```\n\n## References\n- https://documentation.suse.com/sle-ha/15-SP3/html/SLE-HA-all/cha-ha-storage-protect.html#pro-ha-storage-protect-sbd-services\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n systemd:\n name: sbd\n enabled: true\n register: config_updated\n when:\n - ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "61451E", - "name": "1.3.4", - "description": "Multiple SBD devices are configured\n", - "remediation": "## Abstract\nIt is recommended to configure 3 SBD devices for production environments.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker#set-up-sbd-device\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n sbdarray=$(grep -E '^SBD_DEVICE=' /etc/sysconfig/sbd | grep -oP 'SBD_DEVICE=\\K[^.]+' | sed 's/\\\"//g')\n IFS=';' sbdarray=( $sbdarray )\n # convoluted, but normal count method does not work with jinja2\n # issue: https://github.com/ansible/ansible/issues/16968\n temp_ar=(${!sbdarray[@]}); device_count=`expr ${temp_ar[-1]} + 1`\n echo \"$device_count\"\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected[name]\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "B089BE", - "name": "1.3.5", - "description": "SBD watchdog timeout is set to `60`\n", - "remediation": "## Remediation\nMake sure you configure your SBD Watchdog Timeout to `60` seconds as recommended on the best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker#set-up-sbd-device\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n DEF_WDTIMEOUT={{ expected[name] }}\n result_wdtimeout=${DEF_WDTIMEOUT}\n sbdarray=$(grep -E '^SBD_DEVICE=' /etc/sysconfig/sbd | grep -oP 'SBD_DEVICE=\\K[^.]+' | sed 's/\\\"//g')\n IFS=';' sbdarray=( $sbdarray )\n for i in \"${sbdarray[@]}\"\n do\n wdtimeout=$(/usr/sbin/sbd -d ${i} dump | grep -oP 'Timeout \\(watchdog\\) *: \\K\\d+')|| echo \"\"\n if [[ \"${wdtimeout}\" -ne \"${DEF_WDTIMEOUT}\" ]]; then\n result_wdtimeout=\"${wdtimeout}\"\n fi\n done\n echo \"${result_wdtimeout}\"\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected[name]\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "68626E", - "name": "1.3.6", - "description": "SBD `msgwait` timeout value is two times the watchdog timeout\n", - "remediation": "## Remediation\nMake sure you configure your the SBD msgwait to 2 * (SBD Watchdog Timeout) as recommended on the best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker#set-up-sbd-device\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n DEF_MSGWAIT={{ expected[name] }}\n result_msgwait=${DEF_MSGWAIT}\n sbdarray=$(grep -E '^SBD_DEVICE=' /etc/sysconfig/sbd | grep -oP 'SBD_DEVICE=\\K[^.]+' | sed 's/\\\"//g')\n IFS=';' sbdarray=( $sbdarray )\n for i in \"${sbdarray[@]}\"\n do\n msgwait=$(/usr/sbin/sbd -d ${i} dump | grep -oP 'Timeout \\(msgwait\\) *: \\K\\d+')|| echo \"\"\n if [[ \"${msgwait}\" -ne \"${DEF_MSGWAIT}\" ]]; then\n result_msgwait=\"${msgwait}\"\n fi\n done\n echo $result_msgwait\n register: config_updated\n check_mode: false\n changed_when: config_updated.stdout != expected[name]\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "A2EF8C", - "name": "1.3.7", - "description": "The 2 nodes cluster has either disk-based SBD or Qdevice\n", - "remediation": "## Remediation\nHA cluster with 2 nodes must either have a disk-based SBD or a Qdevice.\n\n## References\n- section 2 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n if [[ $(crm_node -l | wc -l) != \"2\" ]]; then\n exit 0\n fi\n sbdarray=$(grep -E '^SBD_DEVICE=' /etc/sysconfig/sbd | grep -oP 'SBD_DEVICE=\\K[^.]+' | sed 's/\\\"//g')\n IFS=';' sbdarray=( $sbdarray )\n # convoluted, but normal count method does not work with jinja2\n # issue https://github.com/ansible/ansible/issues/16968\n temp_ar=(${!sbdarray[@]}); device_count=`expr ${temp_ar[-1]} + 1`\n # If there is at least 1 device and there is an sbd device used by pacemaker\n if [[ $device_count != \"0\" ]] && crm conf show | grep -q \"stonith:external/sbd\"; then\n exit 0\n fi\n # If the qdevice is configured it\\'s also good\n if corosync-quorumtool | tail -n1 | grep -i qdevice; then\n exit 0\n fi\n exit 1\n register: config_updated\n check_mode: false\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - } - ] - }, - { - "group": "Miscellaneous", - "checks": [ - { - "id": "790926", - "name": "1.5.2", - "description": "The `hacluster` user password has been changed from the default value `linux`\n", - "remediation": "## Abstract\nThe password of the `hacluster` user should be changed after setting up the cluster\n\n## Remediation\n```sudo passwd hacluster```\n\n## References\n- section 9.1.2 https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n # if hacluster passwd is linux, fail\n salt=$(sudo getent shadow hacluster | cut -d$ -f3)\n epassword=$(sudo getent shadow hacluster | cut -d: -f2)\n match=$(python3 -c 'import crypt; print(crypt.crypt(\"linux\", \"$6$'${salt}'\"))')\n [[ ${match} == ${epassword} ]] && exit 1\n exit 0\n check_mode: false\n register: config_updated\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - } - ] - }, - { - "group": "OS and package versions", - "checks": [ - { - "id": "CAEFF1", - "name": "2.2.1", - "description": "Operative system vendor is supported\n", - "remediation": "## Abstract\nSAPHanaSR is only supported on SUSE Linux Enterprise Server for SAP Applications.\n\n## Remediation\nPlease use SUSE Linux Enterprise Server for SAP Applications.\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ ansible_distribution is version(expected[name], '==') }}\"", - "labels": "hana" - }, - { - "id": "D028B9", - "name": "2.2.2", - "description": "Operative system version is supported\n", - "remediation": "## Abstract\nYou need at least SUSE Linux Enterprise Server for SAP Applications 15 SP1 or newer\n\n## Remediation\nPlease install or upgrade to a supported OS version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ ansible_distribution_version is version(expected[name], '>=') }}\"", - "labels": "hana" - }, - { - "id": "9FEFB0", - "name": "2.2.3", - "description": "Pacemaker version is supported\n", - "remediation": "## Abstract\nInstalled Pacemaker version must be equal or higher than 2.0.3\n\n## Remediation\nInstall or upgrade to a supported Pacemaker version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ 'pacemaker' in ansible_facts.packages and ansible_facts.packages['pacemaker'][0].version is version(expected[name], '>=') }}\"", - "labels": "hana" - }, - { - "id": "9FAAD0", - "name": "2.2.3.exclude", - "description": "Pacemaker version is not 2.0.3+20200511.2b248d828\n", - "remediation": "## Abstract\nInstalled Pacemaker version must not be equal than 2.0.3+20200511.2b248d828\n\n## Remediation\nInstall or upgrade to a supported Pacemaker version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n # Check the pacemaker version IS NOT\n # If not installed, exit with error\n rpm -q --qf \"%{VERSION}\\n\" pacemaker || exit 2\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout is version(expected[name], '=')\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "hana" - }, - { - "id": "DC5429", - "name": "2.2.4", - "description": "Corosync version is supported\n", - "remediation": "## Abstract\nInstalled Corosync version must be equal or higher than 2.4.5\n\n## Remediation\nInstall or upgrade to a supported Corosync version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ 'corosync' in ansible_facts.packages and ansible_facts.packages['corosync'][0].version is version(expected[name], '>=') }}\"", - "labels": "hana" - }, - { - "id": "222A57", - "name": "2.2.5", - "description": "SBD version is supported\n", - "remediation": "## Abstract\nInstalled SBD version must be equal or higher than 1.4.0\n\n## Remediation\nInstall or upgrade to a supported SBD version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ 'sbd' in ansible_facts.packages and ansible_facts.packages['sbd'][0].version is version(expected[name], '>=') }}\"", - "labels": "hana" - }, - { - "id": "C3166E", - "name": "2.2.5.exclude", - "description": "SBD version is not 1.4.0+20190326.c38c5e6\n", - "remediation": "## Abstract\nInstalled SBD version must not be equal than 1.4.0+20190326.c38c5e6\n\n## Remediation\nInstall or upgrade to a supported SBD version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n # Check the sbd version IS NOT\n # If not installed, exit with error\n rpm -q --qf \"%{VERSION}\\n\" sbd || exit 2\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout is version(expected[name], '=')\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "hana" - }, - { - "id": "F50AF5", - "name": "2.2.7", - "description": "Python3 version is supported\n", - "remediation": "## Abstract\nInstalled Python3 version must be equal or higher than 3.6.5\n\n## Remediation\nInstall or upgrade to a supported Python3 version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ 'python3' in ansible_facts.packages and ansible_facts.packages['python3'][0].version is version(expected[name], '>=') }}\"", - "labels": "hana" - } - ] - } - ] - }, - { - "provider": "aws", - "groups": [ - { - "group": "Corosync", - "checks": [ - { - "id": "156F64", - "name": "1.1.1", - "description": "Corosync `token` timeout is set to `5000`\n", - "remediation": "## Abstract\nThe value of the Corosync `token` timeout is not set as recommended.\n\n## Remediation\n\nAdjust the corosync `token` timeout as recommended on the best practices, and reload the corosync configuration\n\n1. Set the correct `token` timeout in the totem session in the corosync config file `/etc/corosync/corosync.conf`. This action must be repeated in all nodes of the cluster.\n ```\n [...]\n totem { \n token: \n }\n [...]\n ``` \n2. Reload the corosync configuration:\n `crm corosync reload`\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when:\n - ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "53D035", - "name": "1.1.1.runtime", - "description": "Corosync is running with `token` timeout set to `5000`\n", - "remediation": "## Abstract\nThe runtime value of the Corosync `token` timeout is not set as recommended.\n\n## Remediation\n\nAdjust the corosync `token` timeout as recommended on the best practices, and reload the corosync configuration\n\n\n1. Set the correct `token` timeout in the totem session in the corosync config file `/etc/corosync/corosync.conf`. This action must be repeated in all nodes of the cluster.\n ```\n [...]\n totem { \n token: \n }\n [...]\n ``` \n2. Reload the corosync configuration:\n `crm corosync reload`\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.token (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.1']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "A1244C", - "name": "1.1.2", - "description": "Corosync `consensus` timeout is set to `6000`\n", - "remediation": "## Remediation\nAdjust the Corosync `consensus` timeout as recommended by the Azure best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "FB0E0D", - "name": "1.1.2.runtime", - "description": "Corosync is running with `consensus` timeout set to `6000`\n", - "remediation": "## Abstract\nThe runtime value of the Corosync `consensus` timeout is not set as recommended.\n\n## Remediation\nAdjust the corosync `consensus` timeout as recommended by the Azure best practices, and reload the corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.consensus (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.2']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "845CC9", - "name": "1.1.3", - "description": "Corosync `max_messages` is set to `20`\n", - "remediation": "## Remediation\nAdjust the Corosync `max_messages` parameter as recommended by the Azure best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "00081D", - "name": "1.1.3.runtime", - "description": "Corosync is running with `max_messages` set to `20`\n", - "remediation": "## Abstract\nThe runtime value of the Corosync `max_messages` parameter is not set as recommended.\n\n## Remediation\nAdjust the corosync `max_messages` parameter as recommended by the Azure best practices, and reload the corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.max_messages (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.3']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "24ABCB", - "name": "1.1.4", - "description": "Corosync `join` is set to `60`\n", - "remediation": "## Remediation\nAdjust the Corosync `join` parameter as recommended by the Azure best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "822E47", - "name": "1.1.4.runtime", - "description": "Corosync is running with `join` set to `60`\n", - "remediation": "## Abstract\nThe runtime value of the Corosync `join` parameter is not set as recommended.\n\n## Remediation\nAdjust the corosync `join` parameter as recommended by the Azure best practices, and reload the corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.join (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.4']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "21FCA6", - "name": "1.1.5", - "description": "Corosync `token_retransmits_before_loss_const` is set to: `10`\n", - "remediation": "## Remediation\nAdjust the corosync `token_retransmits_before_loss_const` parameter to `10` as recommended by the Azure best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "15F7A8", - "name": "1.1.5.runtime", - "description": "Corosync is running with `token_retransmits_before_loss_const` set to `10`\n", - "remediation": "## Abstract\nThe runtime value of the corosync `token_retransmits_before_loss_const` parameter is not set as recommended\n\n## Remediation\nAdjust the corosync `token_retransmits_before_loss_const` parameter as recommended on the Azure best practices, and reload the corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.token_retransmits_before_loss_const (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.5']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "33403D", - "name": "1.1.6", - "description": "Corosync `transport` is set to `udpu`\n", - "remediation": "## Remediation\nTo change the corosync MCAST transport to UCAST edit the /etc/corosync/corosync.conf\nas in the example\n```\n max_messages: 20\n interface {\n ringnumber: 0\n- bindnetaddr: 10.162.32.167\n- mcastaddr: 239.11.100.41\n mcastport: 5405\n ttl: 1\n }\n+ transport: udpu\n...\n+nodelist {\n+ node {\n+ ring0_addr: 10.162.32.167\n+ nodeid: 1\n+ }\n+\n+ node {\n+ ring0_addr: 10.162.32.89\n+ nodeid: 2\n+ }\n+\n+}\n```\n1. stop the already running cluster by using **systemctl stop pacemaker**\n2. In the totem section, in the interface subsection remove the\nkeys-value pairs **bindnetaddr** and **mcastaddr**\n3. In the totem section add key-value pair **transport: udpu**\n4. Add section nodelist and subsections node for each nodes of the\ncluster, where the **ring0_addr** is the IP address of the node\n\n## References\n- section 9.1.3 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/#id-adapting-the-corosync-and-sbd-configuration\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "7E0221", - "name": "1.1.6.runtime", - "description": "Corosync is running with `transport` set to `udpu`\n", - "remediation": "## Remediation\nTo change the corosync MCAST transport to UCAST edit the /etc/corosync/corosync.conf\nas in the example\n```\n max_messages: 20\n interface {\n ringnumber: 0\n- bindnetaddr: 10.162.32.167\n- mcastaddr: 239.11.100.41\n mcastport: 5405\n ttl: 1\n }\n+ transport: udpu\n...\n+nodelist {\n+ node {\n+ ring0_addr: 10.162.32.167\n+ nodeid: 1\n+ }\n+\n+ node {\n+ ring0_addr: 10.162.32.89\n+ nodeid: 2\n+ }\n+\n+}\n```\n1. stop the already running cluster by using **systemctl stop pacemaker**\n2. In the totem section, in the interface subsection remove the\nkeys-value pairs **bindnetaddr** and **mcastaddr**\n3. In the totem section add key-value pair **transport: udpu**\n4. Add section nodelist and subsections node for each nodes of the\ncluster, where the **ring0_addr** is the IP address of the node\n\n## References\n- section 9.1.3 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/#id-adapting-the-corosync-and-sbd-configuration\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"totem.transport (str) = \" | sed \"s/.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.6']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "C620DC", - "name": "1.1.7", - "description": "Corosync `expected_votes` is set to `2`\n", - "remediation": "## Remediation\nAdjust the corosync `expected_votes` parameter to `2` to make sure pacemaker calculates the actions properly for a two-node cluster.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'quorum {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "6E9B82", - "name": "1.1.8", - "description": "Corosync `two_node` is set to `1`\n", - "remediation": "## Abstract\nThe runtime value of the corosync `two_node` parameter is not set as recommended.\n\n## Remediation\nAdjust the corosync two_node parameter to `1` to make sure Pacemaker calculates the actions properly for a two-node cluster.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'quorum {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "D78671", - "name": "1.1.8.runtime", - "description": "Corosync is running with `two_node` set to `1`\n", - "remediation": "## Abstract\nThe runtime value of the corosync `two_node` parameter is not set as recommended.\n\n## Remediation\nAdjust the corosync `two_node` parameter to `1` to make sure Pacemaker calculates the actions properly for a two-node cluster,\nand reload the Corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.votequorum.two_node (u8) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.8']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "DA114A", - "name": "1.1.9", - "description": "Corosync has at least 2 rings configured\n", - "remediation": "## Abstract\nIt is strongly recommended to add a second ring to the corosync communication.\n\n## References\n- section 9.1.3 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/#id-adapting-the-corosync-and-sbd-configuration\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n INTERFACE_COUNT=$(cat /etc/corosync/corosync.conf | grep interface | wc -l)\n [[ $INTERFACE_COUNT -ge \"2\" ]] && exit 0\n exit 1\n check_mode: false\n register: config_updated\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "32CFC6", - "name": "1.1.9.runtime", - "description": "Corosync is running with at least 2 rings\n", - "remediation": "## Abstract\nIt is strongly recommended to add a second ring to the corosync communication.\n\n## References\n- section 9.1.3 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/#id-adapting-the-corosync-and-sbd-configuration\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n INTERFACE_COUNT=$(corosync-cmapctl | grep totem.interface\\\\..*\\.ttl | wc -l)\n [[ ${INTERFACE_COUNT} -ge \"2\" ]] && exit 0\n exit 1\n check_mode: false\n register: config_updated\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - } - ] - }, - { - "group": "Pacemaker", - "checks": [ - { - "id": "205AF7", - "name": "1.2.1", - "description": "Fencing is enabled in the cluster attributes\n", - "remediation": "## Abstract\nFencing is mandatory to guarantee data integrity for your SAP Applications.\nRunning a HA Cluster without fencing is not supported and might cause data loss.\n\n## Remediation\nExecute the following command to enable it:\n```\ncrm configure property stonith-enabled=true\n```\n\n## References\n- https://documentation.suse.com/sle-ha/15-SP3/html/SLE-HA-all/cha-ha-fencing.html#sec-ha-fencing-recommend\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n command: 'crm_attribute -t crm_config -G -n stonith-enabled --quiet'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected[name]\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "373DB8", - "name": "1.2.2", - "description": "Cluster fencing timeout is configured correctly\n", - "remediation": "## Abstract\nThe fencing timeout (`stonith-timeout`) determines the time Pacemaker will wait for fencing to succeed.\nThe recommended values on Azure are `144` seconds for SBD only or `900` seconds when using SBD combined with the Azure Fence agent.\n\n## Remediation\nExecute the following command to adjust the timeout for your usecase:\n```crm configure property stonith-timeout=144```\nor\n```crm configure property stonith-timeout=900```\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n timeout=$(crm_attribute -t crm_config -G -n stonith-timeout --quiet)\n if [[cibadmin -Q --xpath \"//primitive[@type='fence_azure_arm']/@type\" > /dev/null 2>&1 ]]; then\n exit $([[ \"${timeout}\" =~ {{ expected[name + '.fence_azure_arm'] }}s?$ ]])\n else\n exit $([[ \"${timeout}\" =~ {{ expected[name + '.sbd'] }}s?$ ]])\n fi\n check_mode: false\n register: config_updated\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - } - ] - }, - { - "group": "SBD", - "checks": [ - { - "id": "0B6DB2", - "name": "1.3.1", - "description": "`SBD_PACEMAKER` value is correctly set in SBD configuration\n", - "remediation": "## Abstract\nFor proper SBD fencing, make sure that the integration with Pacemaker is enabled.\n**IMPORTANT**: Always verify these steps in a testing environment before doing so in production ones!\n\n## Remediation\nRun the following commands in order:\n\n1. Put cluster into maintenance mode:\n ```crm configure property maintenance-mode=true```\n2. Stop the cluster:\n ```crm cluster stop```\n3. Set the SBD_PACEMAKER parameter to `yes` on `/etc/sysconfig/sbd`:\n ```\n [...]\n SBD_PACEMAKER=\"yes\"\n [...]\n ```\n4. Restart the cluster:\n ```crm cluster start```\n5. Put cluster out of maintenance mode\n ```crm configure property maintenance-mode=false```\n\n## References\n- https://documentation.suse.com/sle-ha/15-SP3/html/SLE-HA-all/cha-ha-storage-protect.html\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/sysconfig/sbd\n regexp: '^SBD_PACEMAKER='\n line: 'SBD_PACEMAKER={{ expected[name] }}'\n register: config_updated\n when:\n - ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "49591F", - "name": "1.3.2", - "description": "`SBD_STARTMODE` is set to `always`\n", - "remediation": "## Abstract\nIf not set to always, SBD will not automatically start if the node was previously fenced as it will expect the cluster in a clean state.\n**IMPORTANT**: Always verify these steps in a testing environment before doing so in production ones!\n\n## Remediation\nRun the following commands in order:\n\n1. Put cluster into maintenance mode:\n ```crm configure property maintenance-mode=true```\n2. Stop the cluster:\n ```crm cluster stop```\n2. Set the SBD_STARTMODE parameter to `always` on `/etc/sysconfig/sbd`:\n ```\n [...]\n SBD_STARTMODE=\"always\"\n [...]\n ```\n3. Restart the cluster:\n ```crm cluster start```\n4. Put cluster out of maintenance mode:\n ```crm configure property maintenance-mode=false```\n\n## References\n- https://documentation.suse.com/sle-ha/15-SP3/html/SLE-HA-all/cha-ha-storage-protect.html\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/sysconfig/sbd\n regexp: '^SBD_STARTMODE='\n line: 'SBD_STARTMODE={{ expected[name] }}'\n register: config_updated\n when:\n - ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "816815", - "name": "1.3.3", - "description": "SBD service is enabled\n", - "remediation": "## Abstract\nIf not enabled, SBD service will not start automatically after reboots, affecting the correct cluster startup.\n\n## Remediation\nTo enable the service, run:\n```\nsystemctl enable sbd\n```\n\n## References\n- https://documentation.suse.com/sle-ha/15-SP3/html/SLE-HA-all/cha-ha-storage-protect.html#pro-ha-storage-protect-sbd-services\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n systemd:\n name: sbd\n enabled: true\n register: config_updated\n when:\n - ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "61451E", - "name": "1.3.4", - "description": "Multiple SBD devices are configured\n", - "remediation": "## Abstract\nIt is recommended to configure 3 SBD devices for production environments.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker#set-up-sbd-device\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n sbdarray=$(grep -E '^SBD_DEVICE=' /etc/sysconfig/sbd | grep -oP 'SBD_DEVICE=\\K[^.]+' | sed 's/\\\"//g')\n IFS=';' sbdarray=( $sbdarray )\n # convoluted, but normal count method does not work with jinja2\n # issue: https://github.com/ansible/ansible/issues/16968\n temp_ar=(${!sbdarray[@]}); device_count=`expr ${temp_ar[-1]} + 1`\n echo \"$device_count\"\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected[name]\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "B089BE", - "name": "1.3.5", - "description": "SBD watchdog timeout is set to `15`\n", - "remediation": "## Remediation\nMake sure you configure your SBD Watchdog Timeout to `15` seconds as recommended on the best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker#set-up-sbd-device\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n DEF_WDTIMEOUT={{ expected[name] }}\n result_wdtimeout=${DEF_WDTIMEOUT}\n sbdarray=$(grep -E '^SBD_DEVICE=' /etc/sysconfig/sbd | grep -oP 'SBD_DEVICE=\\K[^.]+' | sed 's/\\\"//g')\n IFS=';' sbdarray=( $sbdarray )\n for i in \"${sbdarray[@]}\"\n do\n wdtimeout=$(/usr/sbin/sbd -d ${i} dump | grep -oP 'Timeout \\(watchdog\\) *: \\K\\d+')|| echo \"\"\n if [[ \"${wdtimeout}\" -ne \"${DEF_WDTIMEOUT}\" ]]; then\n result_wdtimeout=\"${wdtimeout}\"\n fi\n done\n echo \"${result_wdtimeout}\"\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected[name]\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "68626E", - "name": "1.3.6", - "description": "SBD `msgwait` timeout value is two times the watchdog timeout\n", - "remediation": "## Remediation\nMake sure you configure your the SBD msgwait to 2 * (SBD Watchdog Timeout) as recommended on the best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker#set-up-sbd-device\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n DEF_MSGWAIT={{ expected[name] }}\n result_msgwait=${DEF_MSGWAIT}\n sbdarray=$(grep -E '^SBD_DEVICE=' /etc/sysconfig/sbd | grep -oP 'SBD_DEVICE=\\K[^.]+' | sed 's/\\\"//g')\n IFS=';' sbdarray=( $sbdarray )\n for i in \"${sbdarray[@]}\"\n do\n msgwait=$(/usr/sbin/sbd -d ${i} dump | grep -oP 'Timeout \\(msgwait\\) *: \\K\\d+')|| echo \"\"\n if [[ \"${msgwait}\" -ne \"${DEF_MSGWAIT}\" ]]; then\n result_msgwait=\"${msgwait}\"\n fi\n done\n echo $result_msgwait\n register: config_updated\n check_mode: false\n changed_when: config_updated.stdout != expected[name]\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - }, - { - "id": "A2EF8C", - "name": "1.3.7", - "description": "The 2 nodes cluster has either disk-based SBD or Qdevice\n", - "remediation": "## Remediation\nHA cluster with 2 nodes must either have a disk-based SBD or a Qdevice.\n\n## References\n- section 2 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n if [[ $(crm_node -l | wc -l) != \"2\" ]]; then\n exit 0\n fi\n sbdarray=$(grep -E '^SBD_DEVICE=' /etc/sysconfig/sbd | grep -oP 'SBD_DEVICE=\\K[^.]+' | sed 's/\\\"//g')\n IFS=';' sbdarray=( $sbdarray )\n # convoluted, but normal count method does not work with jinja2\n # issue https://github.com/ansible/ansible/issues/16968\n temp_ar=(${!sbdarray[@]}); device_count=`expr ${temp_ar[-1]} + 1`\n # If there is at least 1 device and there is an sbd device used by pacemaker\n if [[ $device_count != \"0\" ]] && crm conf show | grep -q \"stonith:external/sbd\"; then\n exit 0\n fi\n # If the qdevice is configured it\\'s also good\n if corosync-quorumtool | tail -n1 | grep -i qdevice; then\n exit 0\n fi\n exit 1\n register: config_updated\n check_mode: false\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - } - ] - }, - { - "group": "Miscellaneous", - "checks": [ - { - "id": "790926", - "name": "1.5.2", - "description": "The `hacluster` user password has been changed from the default value `linux`\n", - "remediation": "## Abstract\nThe password of the `hacluster` user should be changed after setting up the cluster\n\n## Remediation\n```sudo passwd hacluster```\n\n## References\n- section 9.1.2 https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n # if hacluster passwd is linux, fail\n salt=$(sudo getent shadow hacluster | cut -d$ -f3)\n epassword=$(sudo getent shadow hacluster | cut -d: -f2)\n match=$(python3 -c 'import crypt; print(crypt.crypt(\"linux\", \"$6$'${salt}'\"))')\n [[ ${match} == ${epassword} ]] && exit 1\n exit 0\n check_mode: false\n register: config_updated\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "generic" - } - ] - }, - { - "group": "OS and package versions", - "checks": [ - { - "id": "CAEFF1", - "name": "2.2.1", - "description": "Operative system vendor is supported\n", - "remediation": "## Abstract\nSAPHanaSR is only supported on SUSE Linux Enterprise Server for SAP Applications.\n\n## Remediation\nPlease use SUSE Linux Enterprise Server for SAP Applications.\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ ansible_distribution is version(expected[name], '==') }}\"", - "labels": "hana" - }, - { - "id": "D028B9", - "name": "2.2.2", - "description": "Operative system version is supported\n", - "remediation": "## Abstract\nYou need at least SUSE Linux Enterprise Server for SAP Applications 15 SP1 or newer\n\n## Remediation\nPlease install or upgrade to a supported OS version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ ansible_distribution_version is version(expected[name], '>=') }}\"", - "labels": "hana" - }, - { - "id": "9FEFB0", - "name": "2.2.3", - "group": "OS and package versions", - "description": "Pacemaker version is supported\n", - "remediation": "## Abstract\nInstalled Pacemaker version must be equal or higher than 2.0.3\n\n## Remediation\nInstall or upgrade to a supported Pacemaker version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ 'pacemaker' in ansible_facts.packages and ansible_facts.packages['pacemaker'][0].version is version(expected[name], '>=') }}\"", - "labels": "hana" - }, - { - "id": "9FAAD0", - "name": "2.2.3.exclude", - "description": "Pacemaker version is not 2.0.3+20200511.2b248d828\n", - "remediation": "## Abstract\nInstalled Pacemaker version must not be equal than 2.0.3+20200511.2b248d828\n\n## Remediation\nInstall or upgrade to a supported Pacemaker version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n # Check the pacemaker version IS NOT\n # If not installed, exit with error\n rpm -q --qf \"%{VERSION}\\n\" pacemaker || exit 2\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout is version(expected[name], '=')\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "hana" - }, - { - "id": "DC5429", - "name": "2.2.4", - "description": "Corosync version is supported\n", - "remediation": "## Abstract\nInstalled Corosync version must be equal or higher than 2.4.5\n\n## Remediation\nInstall or upgrade to a supported Corosync version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ 'corosync' in ansible_facts.packages and ansible_facts.packages['corosync'][0].version is version(expected[name], '>=') }}\"", - "labels": "hana" - }, - { - "id": "222A57", - "name": "2.2.5", - "description": "SBD version is supported\n", - "remediation": "## Abstract\nInstalled SBD version must be equal or higher than 1.4.0\n\n## Remediation\nInstall or upgrade to a supported SBD version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ 'sbd' in ansible_facts.packages and ansible_facts.packages['sbd'][0].version is version(expected[name], '>=') }}\"", - "labels": "hana" - }, - { - "id": "C3166E", - "name": "2.2.5.exclude", - "description": "SBD version is not 1.4.0+20190326.c38c5e6\n", - "remediation": "## Abstract\nInstalled SBD version must not be equal than 1.4.0+20190326.c38c5e6\n\n## Remediation\nInstall or upgrade to a supported SBD version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n # Check the sbd version IS NOT\n # If not installed, exit with error\n rpm -q --qf \"%{VERSION}\\n\" sbd || exit 2\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout is version(expected[name], '=')\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", - "labels": "hana" - }, - { - "id": "F50AF5", - "name": "2.2.7", - "description": "Python3 version is supported\n", - "remediation": "## Abstract\nInstalled Python3 version must be equal or higher than 3.6.5\n\n## Remediation\nInstall or upgrade to a supported Python3 version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", - "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ 'python3' in ansible_facts.packages and ansible_facts.packages['python3'][0].version is version(expected[name], '>=') }}\"", - "labels": "hana" - } - ] - } - ] + "description": "Corosync `token` timeout is set to `30000`\n", + "remediation": "## Abstract\nThe value of the Corosync `token` timeout is not set as recommended.\n\n## Remediation\n\nAdjust the corosync `token` timeout as recommended on the best practices, and reload the corosync configuration\n\n1. Set the correct `token` timeout in the totem session in the corosync config file `/etc/corosync/corosync.conf`. This action must be repeated in all nodes of the cluster.\n ```\n [...]\n totem { \n token: \n }\n [...]\n ``` \n2. Reload the corosync configuration:\n `crm corosync reload`\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when:\n - ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "53D035", + "name": "1.1.1.runtime", + "group": "Corosync", + "provider": "azure", + "description": "Corosync is running with `token` timeout set to `30000`\n", + "remediation": "## Abstract\nThe runtime value of the Corosync `token` timeout is not set as recommended.\n\n## Remediation\n\nAdjust the corosync `token` timeout as recommended on the best practices, and reload the corosync configuration\n\n\n1. Set the correct `token` timeout in the totem session in the corosync config file `/etc/corosync/corosync.conf`. This action must be repeated in all nodes of the cluster.\n ```\n [...]\n totem { \n token: \n }\n [...]\n ``` \n2. Reload the corosync configuration:\n `crm corosync reload`\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.token (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.1']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "A1244C", + "name": "1.1.2", + "group": "Corosync", + "provider": "azure", + "description": "Corosync `consensus` timeout is set to `36000`\n", + "remediation": "## Remediation\nAdjust the Corosync `consensus` timeout as recommended by the Azure best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "FB0E0D", + "name": "1.1.2.runtime", + "group": "Corosync", + "provider": "azure", + "description": "Corosync is running with `consensus` timeout set to `36000`\n", + "remediation": "## Abstract\nThe runtime value of the Corosync `consensus` timeout is not set as recommended.\n\n## Remediation\nAdjust the corosync `consensus` timeout as recommended by the Azure best practices, and reload the corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.consensus (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.2']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "845CC9", + "name": "1.1.3", + "group": "Corosync", + "provider": "azure", + "description": "Corosync `max_messages` is set to `20`\n", + "remediation": "## Remediation\nAdjust the Corosync `max_messages` parameter as recommended by the Azure best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "00081D", + "name": "1.1.3.runtime", + "group": "Corosync", + "provider": "azure", + "description": "Corosync is running with `max_messages` set to `20`\n", + "remediation": "## Abstract\nThe runtime value of the Corosync `max_messages` parameter is not set as recommended.\n\n## Remediation\nAdjust the corosync `max_messages` parameter as recommended by the Azure best practices, and reload the corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.max_messages (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.3']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "24ABCB", + "name": "1.1.4", + "group": "Corosync", + "provider": "azure", + "description": "Corosync `join` is set to `60`\n", + "remediation": "## Remediation\nAdjust the Corosync `join` parameter as recommended by the Azure best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "822E47", + "name": "1.1.4.runtime", + "group": "Corosync", + "provider": "azure", + "description": "Corosync is running with `join` set to `60`\n", + "remediation": "## Abstract\nThe runtime value of the Corosync `join` parameter is not set as recommended.\n\n## Remediation\nAdjust the corosync `join` parameter as recommended by the Azure best practices, and reload the corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.join (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.4']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "21FCA6", + "name": "1.1.5", + "group": "Corosync", + "provider": "azure", + "description": "Corosync `token_retransmits_before_loss_const` is set to: `10`\n", + "remediation": "## Remediation\nAdjust the corosync `token_retransmits_before_loss_const` parameter to `10` as recommended by the Azure best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "15F7A8", + "name": "1.1.5.runtime", + "group": "Corosync", + "provider": "azure", + "description": "Corosync is running with `token_retransmits_before_loss_const` set to `10`\n", + "remediation": "## Abstract\nThe runtime value of the corosync `token_retransmits_before_loss_const` parameter is not set as recommended\n\n## Remediation\nAdjust the corosync `token_retransmits_before_loss_const` parameter as recommended on the Azure best practices, and reload the corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.token_retransmits_before_loss_const (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.5']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "33403D", + "name": "1.1.6", + "group": "Corosync", + "provider": "azure", + "description": "Corosync `transport` is set to `udpu`\n", + "remediation": "## Remediation\nTo change the corosync MCAST transport to UCAST edit the /etc/corosync/corosync.conf\nas in the example\n```\n max_messages: 20\n interface {\n ringnumber: 0\n- bindnetaddr: 10.162.32.167\n- mcastaddr: 239.11.100.41\n mcastport: 5405\n ttl: 1\n }\n+ transport: udpu\n...\n+nodelist {\n+ node {\n+ ring0_addr: 10.162.32.167\n+ nodeid: 1\n+ }\n+\n+ node {\n+ ring0_addr: 10.162.32.89\n+ nodeid: 2\n+ }\n+\n+}\n```\n1. stop the already running cluster by using **systemctl stop pacemaker**\n2. In the totem section, in the interface subsection remove the\nkeys-value pairs **bindnetaddr** and **mcastaddr**\n3. In the totem section add key-value pair **transport: udpu**\n4. Add section nodelist and subsections node for each nodes of the\ncluster, where the **ring0_addr** is the IP address of the node\n\n## References\n- section 9.1.3 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/#id-adapting-the-corosync-and-sbd-configuration\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "7E0221", + "name": "1.1.6.runtime", + "group": "Corosync", + "provider": "azure", + "description": "Corosync is running with `transport` set to `udpu`\n", + "remediation": "## Remediation\nTo change the corosync MCAST transport to UCAST edit the /etc/corosync/corosync.conf\nas in the example\n```\n max_messages: 20\n interface {\n ringnumber: 0\n- bindnetaddr: 10.162.32.167\n- mcastaddr: 239.11.100.41\n mcastport: 5405\n ttl: 1\n }\n+ transport: udpu\n...\n+nodelist {\n+ node {\n+ ring0_addr: 10.162.32.167\n+ nodeid: 1\n+ }\n+\n+ node {\n+ ring0_addr: 10.162.32.89\n+ nodeid: 2\n+ }\n+\n+}\n```\n1. stop the already running cluster by using **systemctl stop pacemaker**\n2. In the totem section, in the interface subsection remove the\nkeys-value pairs **bindnetaddr** and **mcastaddr**\n3. In the totem section add key-value pair **transport: udpu**\n4. Add section nodelist and subsections node for each nodes of the\ncluster, where the **ring0_addr** is the IP address of the node\n\n## References\n- section 9.1.3 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/#id-adapting-the-corosync-and-sbd-configuration\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"totem.transport (str) = \" | sed \"s/.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.6']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "C620DC", + "name": "1.1.7", + "group": "Corosync", + "provider": "azure", + "description": "Corosync `expected_votes` is set to `2`\n", + "remediation": "## Remediation\nAdjust the corosync `expected_votes` parameter to `2` to make sure pacemaker calculates the actions properly for a two-node cluster.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'quorum {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "6E9B82", + "name": "1.1.8", + "group": "Corosync", + "provider": "azure", + "description": "Corosync `two_node` is set to `1`\n", + "remediation": "## Abstract\nThe runtime value of the corosync `two_node` parameter is not set as recommended.\n\n## Remediation\nAdjust the corosync two_node parameter to `1` to make sure Pacemaker calculates the actions properly for a two-node cluster.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'quorum {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "D78671", + "name": "1.1.8.runtime", + "group": "Corosync", + "provider": "azure", + "description": "Corosync is running with `two_node` set to `1`\n", + "remediation": "## Abstract\nThe runtime value of the corosync `two_node` parameter is not set as recommended.\n\n## Remediation\nAdjust the corosync `two_node` parameter to `1` to make sure Pacemaker calculates the actions properly for a two-node cluster,\nand reload the Corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.votequorum.two_node (u8) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.8']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "DA114A", + "name": "1.1.9", + "group": "Corosync", + "provider": "azure", + "description": "Corosync has at least 2 rings configured\n", + "remediation": "## Abstract\nIt is strongly recommended to add a second ring to the corosync communication.\n\n## References\n- section 9.1.3 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/#id-adapting-the-corosync-and-sbd-configuration\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n INTERFACE_COUNT=$(cat /etc/corosync/corosync.conf | grep interface | wc -l)\n [[ $INTERFACE_COUNT -ge \"2\" ]] && exit 0\n exit 1\n check_mode: false\n register: config_updated\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "32CFC6", + "name": "1.1.9.runtime", + "group": "Corosync", + "provider": "azure", + "description": "Corosync is running with at least 2 rings\n", + "remediation": "## Abstract\nIt is strongly recommended to add a second ring to the corosync communication.\n\n## References\n- section 9.1.3 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/#id-adapting-the-corosync-and-sbd-configuration\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n INTERFACE_COUNT=$(corosync-cmapctl | grep totem.interface\\\\..*\\.ttl | wc -l)\n [[ ${INTERFACE_COUNT} -ge \"2\" ]] && exit 0\n exit 1\n check_mode: false\n register: config_updated\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "205AF7", + "name": "1.2.1", + "group": "Pacemaker", + "provider": "azure", + "description": "Fencing is enabled in the cluster attributes\n", + "remediation": "## Abstract\nFencing is mandatory to guarantee data integrity for your SAP Applications.\nRunning a HA Cluster without fencing is not supported and might cause data loss.\n\n## Remediation\nExecute the following command to enable it:\n```\ncrm configure property stonith-enabled=true\n```\n\n## References\n- https://documentation.suse.com/sle-ha/15-SP3/html/SLE-HA-all/cha-ha-fencing.html#sec-ha-fencing-recommend\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n command: 'crm_attribute -t crm_config -G -n stonith-enabled --quiet'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected[name]\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "373DB8", + "name": "1.2.2", + "group": "Pacemaker", + "provider": "azure", + "description": "Cluster fencing timeout is configured correctly\n", + "remediation": "## Abstract\nThe fencing timeout (`stonith-timeout`) determines the time Pacemaker will wait for fencing to succeed.\nThe recommended values on Azure are `144` seconds for SBD only or `900` seconds when using SBD combined with the Azure Fence agent.\n\n## Remediation\nExecute the following command to adjust the timeout for your usecase:\n```crm configure property stonith-timeout=144```\nor\n```crm configure property stonith-timeout=900```\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n timeout=$(crm_attribute -t crm_config -G -n stonith-timeout --quiet)\n if [[cibadmin -Q --xpath \"//primitive[@type='fence_azure_arm']/@type\" > /dev/null 2>&1 ]]; then\n exit $([[ \"${timeout}\" =~ {{ expected[name + '.fence_azure_arm'] }}s?$ ]])\n else\n exit $([[ \"${timeout}\" =~ {{ expected[name + '.sbd'] }}s?$ ]])\n fi\n check_mode: false\n register: config_updated\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "0B6DB2", + "name": "1.3.1", + "group": "SBD", + "provider": "azure", + "description": "`SBD_PACEMAKER` value is correctly set in SBD configuration\n", + "remediation": "## Abstract\nFor proper SBD fencing, make sure that the integration with Pacemaker is enabled.\n**IMPORTANT**: Always verify these steps in a testing environment before doing so in production ones!\n\n## Remediation\nRun the following commands in order:\n\n1. Put cluster into maintenance mode:\n ```crm configure property maintenance-mode=true```\n2. Stop the cluster:\n ```crm cluster stop```\n3. Set the SBD_PACEMAKER parameter to `yes` on `/etc/sysconfig/sbd`:\n ```\n [...]\n SBD_PACEMAKER=\"yes\"\n [...]\n ```\n4. Restart the cluster:\n ```crm cluster start```\n5. Put cluster out of maintenance mode\n ```crm configure property maintenance-mode=false```\n\n## References\n- https://documentation.suse.com/sle-ha/15-SP3/html/SLE-HA-all/cha-ha-storage-protect.html\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/sysconfig/sbd\n regexp: '^SBD_PACEMAKER='\n line: 'SBD_PACEMAKER={{ expected[name] }}'\n register: config_updated\n when:\n - ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "49591F", + "name": "1.3.2", + "group": "SBD", + "provider": "azure", + "description": "`SBD_STARTMODE` is set to `always`\n", + "remediation": "## Abstract\nIf not set to always, SBD will not automatically start if the node was previously fenced as it will expect the cluster in a clean state.\n**IMPORTANT**: Always verify these steps in a testing environment before doing so in production ones!\n\n## Remediation\nRun the following commands in order:\n\n1. Put cluster into maintenance mode:\n ```crm configure property maintenance-mode=true```\n2. Stop the cluster:\n ```crm cluster stop```\n2. Set the SBD_STARTMODE parameter to `always` on `/etc/sysconfig/sbd`:\n ```\n [...]\n SBD_STARTMODE=\"always\"\n [...]\n ```\n3. Restart the cluster:\n ```crm cluster start```\n4. Put cluster out of maintenance mode:\n ```crm configure property maintenance-mode=false```\n\n## References\n- https://documentation.suse.com/sle-ha/15-SP3/html/SLE-HA-all/cha-ha-storage-protect.html\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/sysconfig/sbd\n regexp: '^SBD_STARTMODE='\n line: 'SBD_STARTMODE={{ expected[name] }}'\n register: config_updated\n when:\n - ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "816815", + "name": "1.3.3", + "group": "SBD", + "provider": "azure", + "description": "SBD service is enabled\n", + "remediation": "## Abstract\nIf not enabled, SBD service will not start automatically after reboots, affecting the correct cluster startup.\n\n## Remediation\nTo enable the service, run:\n```\nsystemctl enable sbd\n```\n\n## References\n- https://documentation.suse.com/sle-ha/15-SP3/html/SLE-HA-all/cha-ha-storage-protect.html#pro-ha-storage-protect-sbd-services\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n systemd:\n name: sbd\n enabled: true\n register: config_updated\n when:\n - ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "61451E", + "name": "1.3.4", + "group": "SBD", + "provider": "azure", + "description": "Multiple SBD devices are configured\n", + "remediation": "## Abstract\nIt is recommended to configure 3 SBD devices for production environments.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker#set-up-sbd-device\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n sbdarray=$(grep -E '^SBD_DEVICE=' /etc/sysconfig/sbd | grep -oP 'SBD_DEVICE=\\K[^.]+' | sed 's/\\\"//g')\n IFS=';' sbdarray=( $sbdarray )\n # convoluted, but normal count method does not work with jinja2\n # issue: https://github.com/ansible/ansible/issues/16968\n temp_ar=(${!sbdarray[@]}); device_count=`expr ${temp_ar[-1]} + 1`\n echo \"$device_count\"\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected[name]\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "B089BE", + "name": "1.3.5", + "group": "SBD", + "provider": "azure", + "description": "SBD watchdog timeout is set to `60`\n", + "remediation": "## Remediation\nMake sure you configure your SBD Watchdog Timeout to `60` seconds as recommended on the best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker#set-up-sbd-device\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n DEF_WDTIMEOUT={{ expected[name] }}\n result_wdtimeout=${DEF_WDTIMEOUT}\n sbdarray=$(grep -E '^SBD_DEVICE=' /etc/sysconfig/sbd | grep -oP 'SBD_DEVICE=\\K[^.]+' | sed 's/\\\"//g')\n IFS=';' sbdarray=( $sbdarray )\n for i in \"${sbdarray[@]}\"\n do\n wdtimeout=$(/usr/sbin/sbd -d ${i} dump | grep -oP 'Timeout \\(watchdog\\) *: \\K\\d+')|| echo \"\"\n if [[ \"${wdtimeout}\" -ne \"${DEF_WDTIMEOUT}\" ]]; then\n result_wdtimeout=\"${wdtimeout}\"\n fi\n done\n echo \"${result_wdtimeout}\"\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected[name]\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "68626E", + "name": "1.3.6", + "group": "SBD", + "provider": "azure", + "description": "SBD `msgwait` timeout value is two times the watchdog timeout\n", + "remediation": "## Remediation\nMake sure you configure your the SBD msgwait to 2 * (SBD Watchdog Timeout) as recommended on the best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker#set-up-sbd-device\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n DEF_MSGWAIT={{ expected[name] }}\n result_msgwait=${DEF_MSGWAIT}\n sbdarray=$(grep -E '^SBD_DEVICE=' /etc/sysconfig/sbd | grep -oP 'SBD_DEVICE=\\K[^.]+' | sed 's/\\\"//g')\n IFS=';' sbdarray=( $sbdarray )\n for i in \"${sbdarray[@]}\"\n do\n msgwait=$(/usr/sbin/sbd -d ${i} dump | grep -oP 'Timeout \\(msgwait\\) *: \\K\\d+')|| echo \"\"\n if [[ \"${msgwait}\" -ne \"${DEF_MSGWAIT}\" ]]; then\n result_msgwait=\"${msgwait}\"\n fi\n done\n echo $result_msgwait\n register: config_updated\n check_mode: false\n changed_when: config_updated.stdout != expected[name]\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "A2EF8C", + "name": "1.3.7", + "group": "SBD", + "provider": "azure", + "description": "The 2 nodes cluster has either disk-based SBD or Qdevice\n", + "remediation": "## Remediation\nHA cluster with 2 nodes must either have a disk-based SBD or a Qdevice.\n\n## References\n- section 2 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n if [[ $(crm_node -l | wc -l) != \"2\" ]]; then\n exit 0\n fi\n sbdarray=$(grep -E '^SBD_DEVICE=' /etc/sysconfig/sbd | grep -oP 'SBD_DEVICE=\\K[^.]+' | sed 's/\\\"//g')\n IFS=';' sbdarray=( $sbdarray )\n # convoluted, but normal count method does not work with jinja2\n # issue https://github.com/ansible/ansible/issues/16968\n temp_ar=(${!sbdarray[@]}); device_count=`expr ${temp_ar[-1]} + 1`\n # If there is at least 1 device and there is an sbd device used by pacemaker\n if [[ $device_count != \"0\" ]] && crm conf show | grep -q \"stonith:external/sbd\"; then\n exit 0\n fi\n # If the qdevice is configured it\\'s also good\n if corosync-quorumtool | tail -n1 | grep -i qdevice; then\n exit 0\n fi\n exit 1\n register: config_updated\n check_mode: false\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "790926", + "name": "1.5.2", + "group": "Miscellaneous", + "provider": "azure", + "description": "The `hacluster` user password has been changed from the default value `linux`\n", + "remediation": "## Abstract\nThe password of the `hacluster` user should be changed after setting up the cluster\n\n## Remediation\n```sudo passwd hacluster```\n\n## References\n- section 9.1.2 https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n # if hacluster passwd is linux, fail\n salt=$(sudo getent shadow hacluster | cut -d$ -f3)\n epassword=$(sudo getent shadow hacluster | cut -d: -f2)\n match=$(python3 -c 'import crypt; print(crypt.crypt(\"linux\", \"$6$'${salt}'\"))')\n [[ ${match} == ${epassword} ]] && exit 1\n exit 0\n check_mode: false\n register: config_updated\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "CAEFF1", + "name": "2.2.1", + "group": "OS and package versions", + "provider": "azure", + "description": "Operative system vendor is supported\n", + "remediation": "## Abstract\nSAPHanaSR is only supported on SUSE Linux Enterprise Server for SAP Applications.\n\n## Remediation\nPlease use SUSE Linux Enterprise Server for SAP Applications.\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ ansible_distribution is version(expected[name], '==') }}\"", + "labels": "hana" + }, + { + "id": "D028B9", + "name": "2.2.2", + "group": "OS and package versions", + "provider": "azure", + "description": "Operative system version is supported\n", + "remediation": "## Abstract\nYou need at least SUSE Linux Enterprise Server for SAP Applications 15 SP1 or newer\n\n## Remediation\nPlease install or upgrade to a supported OS version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ ansible_distribution_version is version(expected[name], '>=') }}\"", + "labels": "hana" + }, + { + "id": "9FEFB0", + "name": "2.2.3", + "group": "OS and package versions", + "provider": "azure", + "description": "Pacemaker version is supported\n", + "remediation": "## Abstract\nInstalled Pacemaker version must be equal or higher than 2.0.3\n\n## Remediation\nInstall or upgrade to a supported Pacemaker version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ 'pacemaker' in ansible_facts.packages and ansible_facts.packages['pacemaker'][0].version is version(expected[name], '>=') }}\"", + "labels": "hana" + }, + { + "id": "9FAAD0", + "name": "2.2.3.exclude", + "group": "OS and package versions", + "provider": "azure", + "description": "Pacemaker version is not 2.0.3+20200511.2b248d828\n", + "remediation": "## Abstract\nInstalled Pacemaker version must not be equal than 2.0.3+20200511.2b248d828\n\n## Remediation\nInstall or upgrade to a supported Pacemaker version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n # Check the pacemaker version IS NOT\n # If not installed, exit with error\n rpm -q --qf \"%{VERSION}\\n\" pacemaker || exit 2\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout is version(expected[name], '=')\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "hana" + }, + { + "id": "DC5429", + "name": "2.2.4", + "group": "OS and package versions", + "provider": "azure", + "description": "Corosync version is supported\n", + "remediation": "## Abstract\nInstalled Corosync version must be equal or higher than 2.4.5\n\n## Remediation\nInstall or upgrade to a supported Corosync version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ 'corosync' in ansible_facts.packages and ansible_facts.packages['corosync'][0].version is version(expected[name], '>=') }}\"", + "labels": "hana" + }, + { + "id": "222A57", + "name": "2.2.5", + "group": "OS and package versions", + "provider": "azure", + "description": "SBD version is supported\n", + "remediation": "## Abstract\nInstalled SBD version must be equal or higher than 1.4.0\n\n## Remediation\nInstall or upgrade to a supported SBD version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ 'sbd' in ansible_facts.packages and ansible_facts.packages['sbd'][0].version is version(expected[name], '>=') }}\"", + "labels": "hana" + }, + { + "id": "C3166E", + "name": "2.2.5.exclude", + "group": "OS and package versions", + "provider": "azure", + "description": "SBD version is not 1.4.0+20190326.c38c5e6\n", + "remediation": "## Abstract\nInstalled SBD version must not be equal than 1.4.0+20190326.c38c5e6\n\n## Remediation\nInstall or upgrade to a supported SBD version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n # Check the sbd version IS NOT\n # If not installed, exit with error\n rpm -q --qf \"%{VERSION}\\n\" sbd || exit 2\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout is version(expected[name], '=')\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "hana" + }, + { + "id": "F50AF5", + "name": "2.2.7", + "group": "OS and package versions", + "provider": "azure", + "description": "Python3 version is supported\n", + "remediation": "## Abstract\nInstalled Python3 version must be equal or higher than 3.6.5\n\n## Remediation\nInstall or upgrade to a supported Python3 version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ 'python3' in ansible_facts.packages and ansible_facts.packages['python3'][0].version is version(expected[name], '>=') }}\"", + "labels": "hana" + }, + { + "id": "156F64", + "name": "1.1.1", + "group": "Corosync", + "provider": "aws", + "description": "Corosync `token` timeout is set to `5000`\n", + "remediation": "## Abstract\nThe value of the Corosync `token` timeout is not set as recommended.\n\n## Remediation\n\nAdjust the corosync `token` timeout as recommended on the best practices, and reload the corosync configuration\n\n1. Set the correct `token` timeout in the totem session in the corosync config file `/etc/corosync/corosync.conf`. This action must be repeated in all nodes of the cluster.\n ```\n [...]\n totem { \n token: \n }\n [...]\n ``` \n2. Reload the corosync configuration:\n `crm corosync reload`\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when:\n - ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "53D035", + "name": "1.1.1.runtime", + "group": "Corosync", + "provider": "aws", + "description": "Corosync is running with `token` timeout set to `5000`\n", + "remediation": "## Abstract\nThe runtime value of the Corosync `token` timeout is not set as recommended.\n\n## Remediation\n\nAdjust the corosync `token` timeout as recommended on the best practices, and reload the corosync configuration\n\n\n1. Set the correct `token` timeout in the totem session in the corosync config file `/etc/corosync/corosync.conf`. This action must be repeated in all nodes of the cluster.\n ```\n [...]\n totem { \n token: \n }\n [...]\n ``` \n2. Reload the corosync configuration:\n `crm corosync reload`\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.token (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.1']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "A1244C", + "name": "1.1.2", + "group": "Corosync", + "provider": "aws", + "description": "Corosync `consensus` timeout is set to `6000`\n", + "remediation": "## Remediation\nAdjust the Corosync `consensus` timeout as recommended by the Azure best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "FB0E0D", + "name": "1.1.2.runtime", + "group": "Corosync", + "provider": "aws", + "description": "Corosync is running with `consensus` timeout set to `6000`\n", + "remediation": "## Abstract\nThe runtime value of the Corosync `consensus` timeout is not set as recommended.\n\n## Remediation\nAdjust the corosync `consensus` timeout as recommended by the Azure best practices, and reload the corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.consensus (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.2']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "845CC9", + "name": "1.1.3", + "group": "Corosync", + "provider": "aws", + "description": "Corosync `max_messages` is set to `20`\n", + "remediation": "## Remediation\nAdjust the Corosync `max_messages` parameter as recommended by the Azure best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "00081D", + "name": "1.1.3.runtime", + "group": "Corosync", + "provider": "aws", + "description": "Corosync is running with `max_messages` set to `20`\n", + "remediation": "## Abstract\nThe runtime value of the Corosync `max_messages` parameter is not set as recommended.\n\n## Remediation\nAdjust the corosync `max_messages` parameter as recommended by the Azure best practices, and reload the corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.max_messages (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.3']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "24ABCB", + "name": "1.1.4", + "group": "Corosync", + "provider": "aws", + "description": "Corosync `join` is set to `60`\n", + "remediation": "## Remediation\nAdjust the Corosync `join` parameter as recommended by the Azure best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "822E47", + "name": "1.1.4.runtime", + "group": "Corosync", + "provider": "aws", + "description": "Corosync is running with `join` set to `60`\n", + "remediation": "## Abstract\nThe runtime value of the Corosync `join` parameter is not set as recommended.\n\n## Remediation\nAdjust the corosync `join` parameter as recommended by the Azure best practices, and reload the corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.join (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.4']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "21FCA6", + "name": "1.1.5", + "group": "Corosync", + "provider": "aws", + "description": "Corosync `token_retransmits_before_loss_const` is set to: `10`\n", + "remediation": "## Remediation\nAdjust the corosync `token_retransmits_before_loss_const` parameter to `10` as recommended by the Azure best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "15F7A8", + "name": "1.1.5.runtime", + "group": "Corosync", + "provider": "aws", + "description": "Corosync is running with `token_retransmits_before_loss_const` set to `10`\n", + "remediation": "## Abstract\nThe runtime value of the corosync `token_retransmits_before_loss_const` parameter is not set as recommended\n\n## Remediation\nAdjust the corosync `token_retransmits_before_loss_const` parameter as recommended on the Azure best practices, and reload the corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.config.totem.token_retransmits_before_loss_const (u32) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.5']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "33403D", + "name": "1.1.6", + "group": "Corosync", + "provider": "aws", + "description": "Corosync `transport` is set to `udpu`\n", + "remediation": "## Remediation\nTo change the corosync MCAST transport to UCAST edit the /etc/corosync/corosync.conf\nas in the example\n```\n max_messages: 20\n interface {\n ringnumber: 0\n- bindnetaddr: 10.162.32.167\n- mcastaddr: 239.11.100.41\n mcastport: 5405\n ttl: 1\n }\n+ transport: udpu\n...\n+nodelist {\n+ node {\n+ ring0_addr: 10.162.32.167\n+ nodeid: 1\n+ }\n+\n+ node {\n+ ring0_addr: 10.162.32.89\n+ nodeid: 2\n+ }\n+\n+}\n```\n1. stop the already running cluster by using **systemctl stop pacemaker**\n2. In the totem section, in the interface subsection remove the\nkeys-value pairs **bindnetaddr** and **mcastaddr**\n3. In the totem section add key-value pair **transport: udpu**\n4. Add section nodelist and subsections node for each nodes of the\ncluster, where the **ring0_addr** is the IP address of the node\n\n## References\n- section 9.1.3 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/#id-adapting-the-corosync-and-sbd-configuration\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'totem {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "7E0221", + "name": "1.1.6.runtime", + "group": "Corosync", + "provider": "aws", + "description": "Corosync is running with `transport` set to `udpu`\n", + "remediation": "## Remediation\nTo change the corosync MCAST transport to UCAST edit the /etc/corosync/corosync.conf\nas in the example\n```\n max_messages: 20\n interface {\n ringnumber: 0\n- bindnetaddr: 10.162.32.167\n- mcastaddr: 239.11.100.41\n mcastport: 5405\n ttl: 1\n }\n+ transport: udpu\n...\n+nodelist {\n+ node {\n+ ring0_addr: 10.162.32.167\n+ nodeid: 1\n+ }\n+\n+ node {\n+ ring0_addr: 10.162.32.89\n+ nodeid: 2\n+ }\n+\n+}\n```\n1. stop the already running cluster by using **systemctl stop pacemaker**\n2. In the totem section, in the interface subsection remove the\nkeys-value pairs **bindnetaddr** and **mcastaddr**\n3. In the totem section add key-value pair **transport: udpu**\n4. Add section nodelist and subsections node for each nodes of the\ncluster, where the **ring0_addr** is the IP address of the node\n\n## References\n- section 9.1.3 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/#id-adapting-the-corosync-and-sbd-configuration\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"totem.transport (str) = \" | sed \"s/.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.6']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "C620DC", + "name": "1.1.7", + "group": "Corosync", + "provider": "aws", + "description": "Corosync `expected_votes` is set to `2`\n", + "remediation": "## Remediation\nAdjust the corosync `expected_votes` parameter to `2` to make sure pacemaker calculates the actions properly for a two-node cluster.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'quorum {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "6E9B82", + "name": "1.1.8", + "group": "Corosync", + "provider": "aws", + "description": "Corosync `two_node` is set to `1`\n", + "remediation": "## Abstract\nThe runtime value of the corosync `two_node` parameter is not set as recommended.\n\n## Remediation\nAdjust the corosync two_node parameter to `1` to make sure Pacemaker calculates the actions properly for a two-node cluster.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/corosync/corosync.conf\n regexp: '^(\\s+){{ key_name }}:'\n line: \"\\t{{ key_name }}: {{ expected[name] }}\"\n insertafter: 'quorum {'\n register: config_updated\n when: ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "D78671", + "name": "1.1.8.runtime", + "group": "Corosync", + "provider": "aws", + "description": "Corosync is running with `two_node` set to `1`\n", + "remediation": "## Abstract\nThe runtime value of the corosync `two_node` parameter is not set as recommended.\n\n## Remediation\nAdjust the corosync `two_node` parameter to `1` to make sure Pacemaker calculates the actions properly for a two-node cluster,\nand reload the Corosync service.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: 'corosync-cmapctl | grep \"runtime.votequorum.two_node (u8) = \" | sed \"s/^.*= //\"'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected['1.1.8']\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "DA114A", + "name": "1.1.9", + "group": "Corosync", + "provider": "aws", + "description": "Corosync has at least 2 rings configured\n", + "remediation": "## Abstract\nIt is strongly recommended to add a second ring to the corosync communication.\n\n## References\n- section 9.1.3 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/#id-adapting-the-corosync-and-sbd-configuration\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n INTERFACE_COUNT=$(cat /etc/corosync/corosync.conf | grep interface | wc -l)\n [[ $INTERFACE_COUNT -ge \"2\" ]] && exit 0\n exit 1\n check_mode: false\n register: config_updated\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "32CFC6", + "name": "1.1.9.runtime", + "group": "Corosync", + "provider": "aws", + "description": "Corosync is running with at least 2 rings\n", + "remediation": "## Abstract\nIt is strongly recommended to add a second ring to the corosync communication.\n\n## References\n- section 9.1.3 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/#id-adapting-the-corosync-and-sbd-configuration\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n INTERFACE_COUNT=$(corosync-cmapctl | grep totem.interface\\\\..*\\.ttl | wc -l)\n [[ ${INTERFACE_COUNT} -ge \"2\" ]] && exit 0\n exit 1\n check_mode: false\n register: config_updated\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "205AF7", + "name": "1.2.1", + "group": "Pacemaker", + "provider": "aws", + "description": "Fencing is enabled in the cluster attributes\n", + "remediation": "## Abstract\nFencing is mandatory to guarantee data integrity for your SAP Applications.\nRunning a HA Cluster without fencing is not supported and might cause data loss.\n\n## Remediation\nExecute the following command to enable it:\n```\ncrm configure property stonith-enabled=true\n```\n\n## References\n- https://documentation.suse.com/sle-ha/15-SP3/html/SLE-HA-all/cha-ha-fencing.html#sec-ha-fencing-recommend\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n command: 'crm_attribute -t crm_config -G -n stonith-enabled --quiet'\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected[name]\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "373DB8", + "name": "1.2.2", + "group": "Pacemaker", + "provider": "aws", + "description": "Cluster fencing timeout is configured correctly\n", + "remediation": "## Abstract\nThe fencing timeout (`stonith-timeout`) determines the time Pacemaker will wait for fencing to succeed.\nThe recommended values on Azure are `144` seconds for SBD only or `900` seconds when using SBD combined with the Azure Fence agent.\n\n## Remediation\nExecute the following command to adjust the timeout for your usecase:\n```crm configure property stonith-timeout=144```\nor\n```crm configure property stonith-timeout=900```\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n timeout=$(crm_attribute -t crm_config -G -n stonith-timeout --quiet)\n if [[cibadmin -Q --xpath \"//primitive[@type='fence_azure_arm']/@type\" > /dev/null 2>&1 ]]; then\n exit $([[ \"${timeout}\" =~ {{ expected[name + '.fence_azure_arm'] }}s?$ ]])\n else\n exit $([[ \"${timeout}\" =~ {{ expected[name + '.sbd'] }}s?$ ]])\n fi\n check_mode: false\n register: config_updated\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "0B6DB2", + "name": "1.3.1", + "group": "SBD", + "provider": "aws", + "description": "`SBD_PACEMAKER` value is correctly set in SBD configuration\n", + "remediation": "## Abstract\nFor proper SBD fencing, make sure that the integration with Pacemaker is enabled.\n**IMPORTANT**: Always verify these steps in a testing environment before doing so in production ones!\n\n## Remediation\nRun the following commands in order:\n\n1. Put cluster into maintenance mode:\n ```crm configure property maintenance-mode=true```\n2. Stop the cluster:\n ```crm cluster stop```\n3. Set the SBD_PACEMAKER parameter to `yes` on `/etc/sysconfig/sbd`:\n ```\n [...]\n SBD_PACEMAKER=\"yes\"\n [...]\n ```\n4. Restart the cluster:\n ```crm cluster start```\n5. Put cluster out of maintenance mode\n ```crm configure property maintenance-mode=false```\n\n## References\n- https://documentation.suse.com/sle-ha/15-SP3/html/SLE-HA-all/cha-ha-storage-protect.html\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/sysconfig/sbd\n regexp: '^SBD_PACEMAKER='\n line: 'SBD_PACEMAKER={{ expected[name] }}'\n register: config_updated\n when:\n - ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "49591F", + "name": "1.3.2", + "group": "SBD", + "provider": "aws", + "description": "`SBD_STARTMODE` is set to `always`\n", + "remediation": "## Abstract\nIf not set to always, SBD will not automatically start if the node was previously fenced as it will expect the cluster in a clean state.\n**IMPORTANT**: Always verify these steps in a testing environment before doing so in production ones!\n\n## Remediation\nRun the following commands in order:\n\n1. Put cluster into maintenance mode:\n ```crm configure property maintenance-mode=true```\n2. Stop the cluster:\n ```crm cluster stop```\n2. Set the SBD_STARTMODE parameter to `always` on `/etc/sysconfig/sbd`:\n ```\n [...]\n SBD_STARTMODE=\"always\"\n [...]\n ```\n3. Restart the cluster:\n ```crm cluster start```\n4. Put cluster out of maintenance mode:\n ```crm configure property maintenance-mode=false```\n\n## References\n- https://documentation.suse.com/sle-ha/15-SP3/html/SLE-HA-all/cha-ha-storage-protect.html\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n lineinfile:\n path: /etc/sysconfig/sbd\n regexp: '^SBD_STARTMODE='\n line: 'SBD_STARTMODE={{ expected[name] }}'\n register: config_updated\n when:\n - ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "816815", + "name": "1.3.3", + "group": "SBD", + "provider": "aws", + "description": "SBD service is enabled\n", + "remediation": "## Abstract\nIf not enabled, SBD service will not start automatically after reboots, affecting the correct cluster startup.\n\n## Remediation\nTo enable the service, run:\n```\nsystemctl enable sbd\n```\n\n## References\n- https://documentation.suse.com/sle-ha/15-SP3/html/SLE-HA-all/cha-ha-storage-protect.html#pro-ha-storage-protect-sbd-services\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n systemd:\n name: sbd\n enabled: true\n register: config_updated\n when:\n - ansible_check_mode\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "61451E", + "name": "1.3.4", + "group": "SBD", + "provider": "aws", + "description": "Multiple SBD devices are configured\n", + "remediation": "## Abstract\nIt is recommended to configure 3 SBD devices for production environments.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker#set-up-sbd-device\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n sbdarray=$(grep -E '^SBD_DEVICE=' /etc/sysconfig/sbd | grep -oP 'SBD_DEVICE=\\K[^.]+' | sed 's/\\\"//g')\n IFS=';' sbdarray=( $sbdarray )\n # convoluted, but normal count method does not work with jinja2\n # issue: https://github.com/ansible/ansible/issues/16968\n temp_ar=(${!sbdarray[@]}); device_count=`expr ${temp_ar[-1]} + 1`\n echo \"$device_count\"\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected[name]\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "B089BE", + "name": "1.3.5", + "group": "SBD", + "provider": "aws", + "description": "SBD watchdog timeout is set to `15`\n", + "remediation": "## Remediation\nMake sure you configure your SBD Watchdog Timeout to `15` seconds as recommended on the best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker#set-up-sbd-device\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n DEF_WDTIMEOUT={{ expected[name] }}\n result_wdtimeout=${DEF_WDTIMEOUT}\n sbdarray=$(grep -E '^SBD_DEVICE=' /etc/sysconfig/sbd | grep -oP 'SBD_DEVICE=\\K[^.]+' | sed 's/\\\"//g')\n IFS=';' sbdarray=( $sbdarray )\n for i in \"${sbdarray[@]}\"\n do\n wdtimeout=$(/usr/sbin/sbd -d ${i} dump | grep -oP 'Timeout \\(watchdog\\) *: \\K\\d+')|| echo \"\"\n if [[ \"${wdtimeout}\" -ne \"${DEF_WDTIMEOUT}\" ]]; then\n result_wdtimeout=\"${wdtimeout}\"\n fi\n done\n echo \"${result_wdtimeout}\"\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout != expected[name]\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "68626E", + "name": "1.3.6", + "group": "SBD", + "provider": "aws", + "description": "SBD `msgwait` timeout value is two times the watchdog timeout\n", + "remediation": "## Remediation\nMake sure you configure your the SBD msgwait to 2 * (SBD Watchdog Timeout) as recommended on the best practices.\n\n## References\n- https://docs.microsoft.com/en-us/azure/virtual-machines/workloads/sap/high-availability-guide-suse-pacemaker#set-up-sbd-device\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n DEF_MSGWAIT={{ expected[name] }}\n result_msgwait=${DEF_MSGWAIT}\n sbdarray=$(grep -E '^SBD_DEVICE=' /etc/sysconfig/sbd | grep -oP 'SBD_DEVICE=\\K[^.]+' | sed 's/\\\"//g')\n IFS=';' sbdarray=( $sbdarray )\n for i in \"${sbdarray[@]}\"\n do\n msgwait=$(/usr/sbin/sbd -d ${i} dump | grep -oP 'Timeout \\(msgwait\\) *: \\K\\d+')|| echo \"\"\n if [[ \"${msgwait}\" -ne \"${DEF_MSGWAIT}\" ]]; then\n result_msgwait=\"${msgwait}\"\n fi\n done\n echo $result_msgwait\n register: config_updated\n check_mode: false\n changed_when: config_updated.stdout != expected[name]\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "A2EF8C", + "name": "1.3.7", + "group": "SBD", + "provider": "aws", + "description": "The 2 nodes cluster has either disk-based SBD or Qdevice\n", + "remediation": "## Remediation\nHA cluster with 2 nodes must either have a disk-based SBD or a Qdevice.\n\n## References\n- section 2 in https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n if [[ $(crm_node -l | wc -l) != \"2\" ]]; then\n exit 0\n fi\n sbdarray=$(grep -E '^SBD_DEVICE=' /etc/sysconfig/sbd | grep -oP 'SBD_DEVICE=\\K[^.]+' | sed 's/\\\"//g')\n IFS=';' sbdarray=( $sbdarray )\n # convoluted, but normal count method does not work with jinja2\n # issue https://github.com/ansible/ansible/issues/16968\n temp_ar=(${!sbdarray[@]}); device_count=`expr ${temp_ar[-1]} + 1`\n # If there is at least 1 device and there is an sbd device used by pacemaker\n if [[ $device_count != \"0\" ]] && crm conf show | grep -q \"stonith:external/sbd\"; then\n exit 0\n fi\n # If the qdevice is configured it\\'s also good\n if corosync-quorumtool | tail -n1 | grep -i qdevice; then\n exit 0\n fi\n exit 1\n register: config_updated\n check_mode: false\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "790926", + "name": "1.5.2", + "group": "Miscellaneous", + "provider": "aws", + "description": "The `hacluster` user password has been changed from the default value `linux`\n", + "remediation": "## Abstract\nThe password of the `hacluster` user should be changed after setting up the cluster\n\n## Remediation\n```sudo passwd hacluster```\n\n## References\n- section 9.1.2 https://documentation.suse.com/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n # if hacluster passwd is linux, fail\n salt=$(sudo getent shadow hacluster | cut -d$ -f3)\n epassword=$(sudo getent shadow hacluster | cut -d: -f2)\n match=$(python3 -c 'import crypt; print(crypt.crypt(\"linux\", \"$6$'${salt}'\"))')\n [[ ${match} == ${epassword} ]] && exit 1\n exit 0\n check_mode: false\n register: config_updated\n changed_when: config_updated.rc != 0\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "generic" + }, + { + "id": "CAEFF1", + "name": "2.2.1", + "group": "OS and package versions", + "provider": "aws", + "description": "Operative system vendor is supported\n", + "remediation": "## Abstract\nSAPHanaSR is only supported on SUSE Linux Enterprise Server for SAP Applications.\n\n## Remediation\nPlease use SUSE Linux Enterprise Server for SAP Applications.\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ ansible_distribution is version(expected[name], '==') }}\"", + "labels": "hana" + }, + { + "id": "D028B9", + "name": "2.2.2", + "group": "OS and package versions", + "provider": "aws", + "description": "Operative system version is supported\n", + "remediation": "## Abstract\nYou need at least SUSE Linux Enterprise Server for SAP Applications 15 SP1 or newer\n\n## Remediation\nPlease install or upgrade to a supported OS version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ ansible_distribution_version is version(expected[name], '>=') }}\"", + "labels": "hana" + }, + { + "id": "9FEFB0", + "name": "2.2.3", + "group": "OS and package versions", + "provider": "aws", + "description": "Pacemaker version is supported\n", + "remediation": "## Abstract\nInstalled Pacemaker version must be equal or higher than 2.0.3\n\n## Remediation\nInstall or upgrade to a supported Pacemaker version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ 'pacemaker' in ansible_facts.packages and ansible_facts.packages['pacemaker'][0].version is version(expected[name], '>=') }}\"", + "labels": "hana" + }, + { + "id": "9FAAD0", + "name": "2.2.3.exclude", + "group": "OS and package versions", + "provider": "aws", + "description": "Pacemaker version is not 2.0.3+20200511.2b248d828\n", + "remediation": "## Abstract\nInstalled Pacemaker version must not be equal than 2.0.3+20200511.2b248d828\n\n## Remediation\nInstall or upgrade to a supported Pacemaker version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n # Check the pacemaker version IS NOT\n # If not installed, exit with error\n rpm -q --qf \"%{VERSION}\\n\" pacemaker || exit 2\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout is version(expected[name], '=')\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "hana" + }, + { + "id": "DC5429", + "name": "2.2.4", + "group": "OS and package versions", + "provider": "aws", + "description": "Corosync version is supported\n", + "remediation": "## Abstract\nInstalled Corosync version must be equal or higher than 2.4.5\n\n## Remediation\nInstall or upgrade to a supported Corosync version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ 'corosync' in ansible_facts.packages and ansible_facts.packages['corosync'][0].version is version(expected[name], '>=') }}\"", + "labels": "hana" + }, + { + "id": "222A57", + "name": "2.2.5", + "group": "OS and package versions", + "provider": "aws", + "description": "SBD version is supported\n", + "remediation": "## Abstract\nInstalled SBD version must be equal or higher than 1.4.0\n\n## Remediation\nInstall or upgrade to a supported SBD version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ 'sbd' in ansible_facts.packages and ansible_facts.packages['sbd'][0].version is version(expected[name], '>=') }}\"", + "labels": "hana" + }, + { + "id": "C3166E", + "name": "2.2.5.exclude", + "group": "OS and package versions", + "provider": "aws", + "description": "SBD version is not 1.4.0+20190326.c38c5e6\n", + "remediation": "## Abstract\nInstalled SBD version must not be equal than 1.4.0+20190326.c38c5e6\n\n## Remediation\nInstall or upgrade to a supported SBD version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- name: \"{{ name }}.check\"\n shell: |\n # Check the sbd version IS NOT\n # If not installed, exit with error\n rpm -q --qf \"%{VERSION}\\n\" sbd || exit 2\n check_mode: false\n register: config_updated\n changed_when: config_updated.stdout is version(expected[name], '=')\n failed_when: config_updated.rc > 1\n\n- block:\n - name: Post results\n import_role:\n name: post-results\n when:\n - ansible_check_mode\n vars:\n status: \"{{ config_updated is not changed }}\"", + "labels": "hana" + }, + { + "id": "F50AF5", + "name": "2.2.7", + "group": "OS and package versions", + "provider": "aws", + "description": "Python3 version is supported\n", + "remediation": "## Abstract\nInstalled Python3 version must be equal or higher than 3.6.5\n\n## Remediation\nInstall or upgrade to a supported Python3 version\n\n## Reference\n- https://documentation.suse.com/en-us/sbp/all/single-html/SLES4SAP-hana-sr-guide-PerfOpt-15/\n", + "implementation": "---\n\n- block:\n - name: \"{{ name }} Post results\"\n import_role:\n name: post-results\n vars:\n status: \"{{ 'python3' in ansible_facts.packages and ansible_facts.packages['python3'][0].version is version(expected[name], '>=') }}\"", + "labels": "hana" } ] diff --git a/test/fixtures/runner/catalog.json b/test/fixtures/runner/catalog.json new file mode 100644 index 0000000000..6ca0c4eb50 --- /dev/null +++ b/test/fixtures/runner/catalog.json @@ -0,0 +1,102 @@ +[ + { + "id": "1", + "name": "test 1", + "group": "Group 1", + "provider": "azure", + "description": "description 1", + "remediation": "remediation 1", + "implementation": "implementation 1", + "labels": "labels" + }, + { + "id": "2", + "name": "test 2", + "group": "Group 1", + "provider": "azure", + "description": "description 2", + "remediation": "remediation 2", + "implementation": "implementation 2", + "labels": "labels" + }, + { + "id": "3", + "name": "test 3", + "group": "Group 2", + "provider": "azure", + "description": "description 3", + "remediation": "remediation 3", + "implementation": "implementation 3", + "labels": "labels" + }, + { + "id": "4", + "name": "test 4", + "group": "Group 2", + "provider": "azure", + "description": "description 4", + "remediation": "remediation 4", + "implementation": "implementation 4", + "labels": "labels" + }, + { + "id": "5", + "name": "test 5", + "group": "Group 3", + "provider": "azure", + "description": "description 5", + "remediation": "remediation 5", + "implementation": "implementation 5", + "labels": "labels" + }, + { + "id": "1", + "name": "test 1", + "group": "Group 1", + "provider": "aws", + "description": "description 1", + "remediation": "remediation 1", + "implementation": "implementation 1", + "labels": "labels" + }, + { + "id": "2", + "name": "test 2", + "group": "Group 1", + "provider": "aws", + "description": "description 2", + "remediation": "remediation 2", + "implementation": "implementation 2", + "labels": "labels" + }, + { + "id": "3", + "name": "test 3", + "group": "Group 2", + "provider": "aws", + "description": "description 3", + "remediation": "remediation 3", + "implementation": "implementation 3", + "labels": "labels" + }, + { + "id": "4", + "name": "test 4", + "group": "Group 2", + "provider": "aws", + "description": "description 4", + "remediation": "remediation 4", + "implementation": "implementation 4", + "labels": "labels" + }, + { + "id": "5", + "name": "test 5", + "group": "Group 3", + "provider": "aws", + "description": "description 5", + "remediation": "remediation 5", + "implementation": "implementation 5", + "labels": "labels" + } +] diff --git a/test/test_helper.exs b/test/test_helper.exs index 6ee275d958..d56615754c 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,9 +1,12 @@ Mox.defmock(Trento.Integration.Telemetry.Mock, for: Trento.Integration.Telemetry.Gen) +Mox.defmock(Trento.Integration.Checks.Mock, for: Trento.Integration.Checks.Gen) Application.put_env(:trento, Trento.Integration.Telemetry, adapter: Trento.Integration.Telemetry.Mock ) +Application.put_env(:trento, Trento.Integration.Checks, adapter: Trento.Integration.Checks.Mock) + ExUnit.start() Ecto.Adapters.SQL.Sandbox.mode(Trento.Repo, :manual) diff --git a/test/trento/application/integration/checks/checks_test.exs b/test/trento/application/integration/checks/checks_test.exs new file mode 100644 index 0000000000..ad753fa056 --- /dev/null +++ b/test/trento/application/integration/checks/checks_test.exs @@ -0,0 +1,290 @@ +defmodule Trento.Integration.ChecksTest do + use ExUnit.Case + use Trento.DataCase + + import Mox + + alias Trento.Integration.Checks + + alias Trento.Integration.Checks.Models.{ + Catalog, + Check, + FlatCatalog, + FlatCheck, + Group, + Provider + } + + @runner_fixtures_path File.cwd!() <> "/test/fixtures/runner" + + def load_runner_fixture(name) do + @runner_fixtures_path + |> Path.join("#{name}.json") + |> File.read!() + |> Jason.decode!() + end + + test "should return an error if the runner is not reachable" do + expect(Trento.Integration.Checks.Mock, :get_catalog, fn -> + {:error, "some error"} + end) + + assert {:error, "some error"} = Checks.get_catalog() + end + + test "should return not ready if the runner is still building the catalog" do + expect(Trento.Integration.Checks.Mock, :get_catalog, fn -> + {:error, :not_ready} + end) + + assert {:error, :not_ready} = Checks.get_catalog() + end + + test "should return a flat catalog" do + raw_catalog = load_runner_fixture("catalog") + + Trento.Integration.Checks.Mock + |> expect(:get_catalog, fn -> FlatCatalog.new(%{checks: raw_catalog}) end) + + flat_catalog = %FlatCatalog{ + checks: [ + %FlatCheck{ + description: "description 1", + group: "Group 1", + id: "1", + implementation: "implementation 1", + labels: "labels", + name: "test 1", + provider: :azure, + remediation: "remediation 1" + }, + %FlatCheck{ + description: "description 2", + group: "Group 1", + id: "2", + implementation: "implementation 2", + labels: "labels", + name: "test 2", + provider: :azure, + remediation: "remediation 2" + }, + %FlatCheck{ + description: "description 3", + group: "Group 2", + id: "3", + implementation: "implementation 3", + labels: "labels", + name: "test 3", + provider: :azure, + remediation: "remediation 3" + }, + %FlatCheck{ + description: "description 4", + group: "Group 2", + id: "4", + implementation: "implementation 4", + labels: "labels", + name: "test 4", + provider: :azure, + remediation: "remediation 4" + }, + %FlatCheck{ + description: "description 5", + group: "Group 3", + id: "5", + implementation: "implementation 5", + labels: "labels", + name: "test 5", + provider: :azure, + remediation: "remediation 5" + }, + %FlatCheck{ + description: "description 1", + group: "Group 1", + id: "1", + implementation: "implementation 1", + labels: "labels", + name: "test 1", + provider: :aws, + remediation: "remediation 1" + }, + %FlatCheck{ + description: "description 2", + group: "Group 1", + id: "2", + implementation: "implementation 2", + labels: "labels", + name: "test 2", + provider: :aws, + remediation: "remediation 2" + }, + %FlatCheck{ + description: "description 3", + group: "Group 2", + id: "3", + implementation: "implementation 3", + labels: "labels", + name: "test 3", + provider: :aws, + remediation: "remediation 3" + }, + %FlatCheck{ + description: "description 4", + group: "Group 2", + id: "4", + implementation: "implementation 4", + labels: "labels", + name: "test 4", + provider: :aws, + remediation: "remediation 4" + }, + %FlatCheck{ + description: "description 5", + group: "Group 3", + id: "5", + implementation: "implementation 5", + labels: "labels", + name: "test 5", + provider: :aws, + remediation: "remediation 5" + } + ] + } + + assert {:ok, flat_catalog} == Checks.get_catalog() + end + + test "should return a catalog grouped by provider" do + raw_catalog = load_runner_fixture("catalog") + + Trento.Integration.Checks.Mock + |> expect(:get_catalog, fn -> FlatCatalog.new(%{checks: raw_catalog}) end) + + catalog_by_provider = %Catalog{ + providers: [ + %Provider{ + groups: [ + %Group{ + checks: [ + %Check{ + description: "description 1", + id: "1", + implementation: "implementation 1", + labels: "labels", + name: "test 1", + remediation: "remediation 1" + }, + %Check{ + description: "description 2", + id: "2", + implementation: "implementation 2", + labels: "labels", + name: "test 2", + remediation: "remediation 2" + } + ], + group: "Group 1" + }, + %Group{ + checks: [ + %Check{ + description: "description 3", + id: "3", + implementation: "implementation 3", + labels: "labels", + name: "test 3", + remediation: "remediation 3" + }, + %Check{ + description: "description 4", + id: "4", + implementation: "implementation 4", + labels: "labels", + name: "test 4", + remediation: "remediation 4" + } + ], + group: "Group 2" + }, + %Group{ + checks: [ + %Check{ + description: "description 5", + id: "5", + implementation: "implementation 5", + labels: "labels", + name: "test 5", + remediation: "remediation 5" + } + ], + group: "Group 3" + } + ], + provider: :aws + }, + %Provider{ + groups: [ + %Group{ + checks: [ + %Check{ + description: "description 1", + id: "1", + implementation: "implementation 1", + labels: "labels", + name: "test 1", + remediation: "remediation 1" + }, + %Check{ + description: "description 2", + id: "2", + implementation: "implementation 2", + labels: "labels", + name: "test 2", + remediation: "remediation 2" + } + ], + group: "Group 1" + }, + %Group{ + checks: [ + %Check{ + description: "description 3", + id: "3", + implementation: "implementation 3", + labels: "labels", + name: "test 3", + remediation: "remediation 3" + }, + %Check{ + description: "description 4", + id: "4", + implementation: "implementation 4", + labels: "labels", + name: "test 4", + remediation: "remediation 4" + } + ], + group: "Group 2" + }, + %Group{ + checks: [ + %Check{ + description: "description 5", + id: "5", + implementation: "implementation 5", + labels: "labels", + name: "test 5", + remediation: "remediation 5" + } + ], + group: "Group 3" + } + ], + provider: :azure + } + ] + } + + assert {:ok, catalog_by_provider} == Checks.get_catalog_by_provider() + end +end diff --git a/test/trento_web/controllers/catalog_controller_test.exs b/test/trento_web/controllers/catalog_controller_test.exs new file mode 100644 index 0000000000..4343faa00d --- /dev/null +++ b/test/trento_web/controllers/catalog_controller_test.exs @@ -0,0 +1,267 @@ +defmodule TrentoWeb.CatalogControllerTest do + use TrentoWeb.ConnCase, async: true + + import Mox + + alias Trento.Integration.Checks.Models.FlatCatalog + + @runner_fixtures_path File.cwd!() <> "/test/fixtures/runner" + + def load_runner_fixture(name) do + @runner_fixtures_path + |> Path.join("#{name}.json") + |> File.read!() + |> Jason.decode!() + end + + test "should return a catalog grouped by providers", %{conn: conn} do + raw_catalog = load_runner_fixture("catalog") + + Trento.Integration.Checks.Mock + |> expect(:get_catalog, fn -> FlatCatalog.new(%{checks: raw_catalog}) end) + + conn = get(conn, Routes.catalog_path(conn, :checks_catalog)) + + expected_json = [ + %{ + "groups" => [ + %{ + "checks" => [ + %{ + "description" => "description 1", + "id" => "1", + "implementation" => "implementation 1", + "labels" => "labels", + "name" => "test 1", + "remediation" => "remediation 1" + }, + %{ + "description" => "description 2", + "id" => "2", + "implementation" => "implementation 2", + "labels" => "labels", + "name" => "test 2", + "remediation" => "remediation 2" + } + ], + "group" => "Group 1" + }, + %{ + "checks" => [ + %{ + "description" => "description 3", + "id" => "3", + "implementation" => "implementation 3", + "labels" => "labels", + "name" => "test 3", + "remediation" => "remediation 3" + }, + %{ + "description" => "description 4", + "id" => "4", + "implementation" => "implementation 4", + "labels" => "labels", + "name" => "test 4", + "remediation" => "remediation 4" + } + ], + "group" => "Group 2" + }, + %{ + "checks" => [ + %{ + "description" => "description 5", + "id" => "5", + "implementation" => "implementation 5", + "labels" => "labels", + "name" => "test 5", + "remediation" => "remediation 5" + } + ], + "group" => "Group 3" + } + ], + "provider" => "aws" + }, + %{ + "groups" => [ + %{ + "checks" => [ + %{ + "description" => "description 1", + "id" => "1", + "implementation" => "implementation 1", + "labels" => "labels", + "name" => "test 1", + "remediation" => "remediation 1" + }, + %{ + "description" => "description 2", + "id" => "2", + "implementation" => "implementation 2", + "labels" => "labels", + "name" => "test 2", + "remediation" => "remediation 2" + } + ], + "group" => "Group 1" + }, + %{ + "checks" => [ + %{ + "description" => "description 3", + "id" => "3", + "implementation" => "implementation 3", + "labels" => "labels", + "name" => "test 3", + "remediation" => "remediation 3" + }, + %{ + "description" => "description 4", + "id" => "4", + "implementation" => "implementation 4", + "labels" => "labels", + "name" => "test 4", + "remediation" => "remediation 4" + } + ], + "group" => "Group 2" + }, + %{ + "checks" => [ + %{ + "description" => "description 5", + "id" => "5", + "implementation" => "implementation 5", + "labels" => "labels", + "name" => "test 5", + "remediation" => "remediation 5" + } + ], + "group" => "Group 3" + } + ], + "provider" => "azure" + } + ] + + assert expected_json == json_response(conn, 200) + end + + test "should return a flat catalog", %{conn: conn} do + raw_catalog = load_runner_fixture("catalog") + + Trento.Integration.Checks.Mock + |> expect(:get_catalog, fn -> FlatCatalog.new(%{checks: raw_catalog}) end) + + conn = + get(conn, Routes.catalog_path(conn, :checks_catalog), %{ + "flat" => "" + }) + + expected_json = [ + %{ + "provider" => "azure", + "description" => "description 1", + "group" => "Group 1", + "id" => "1", + "implementation" => "implementation 1", + "labels" => "labels", + "name" => "test 1", + "remediation" => "remediation 1" + }, + %{ + "provider" => "azure", + "description" => "description 2", + "group" => "Group 1", + "id" => "2", + "implementation" => "implementation 2", + "labels" => "labels", + "name" => "test 2", + "remediation" => "remediation 2" + }, + %{ + "description" => "description 3", + "group" => "Group 2", + "id" => "3", + "implementation" => "implementation 3", + "labels" => "labels", + "name" => "test 3", + "provider" => "azure", + "remediation" => "remediation 3" + }, + %{ + "description" => "description 4", + "group" => "Group 2", + "id" => "4", + "implementation" => "implementation 4", + "labels" => "labels", + "name" => "test 4", + "provider" => "azure", + "remediation" => "remediation 4" + }, + %{ + "description" => "description 5", + "group" => "Group 3", + "id" => "5", + "implementation" => "implementation 5", + "labels" => "labels", + "name" => "test 5", + "provider" => "azure", + "remediation" => "remediation 5" + }, + %{ + "description" => "description 1", + "group" => "Group 1", + "id" => "1", + "implementation" => "implementation 1", + "labels" => "labels", + "name" => "test 1", + "provider" => "aws", + "remediation" => "remediation 1" + }, + %{ + "description" => "description 2", + "group" => "Group 1", + "id" => "2", + "implementation" => "implementation 2", + "labels" => "labels", + "name" => "test 2", + "provider" => "aws", + "remediation" => "remediation 2" + }, + %{ + "description" => "description 3", + "group" => "Group 2", + "id" => "3", + "implementation" => "implementation 3", + "labels" => "labels", + "name" => "test 3", + "provider" => "aws", + "remediation" => "remediation 3" + }, + %{ + "description" => "description 4", + "group" => "Group 2", + "id" => "4", + "implementation" => "implementation 4", + "labels" => "labels", + "name" => "test 4", + "provider" => "aws", + "remediation" => "remediation 4" + }, + %{ + "description" => "description 5", + "group" => "Group 3", + "id" => "5", + "implementation" => "implementation 5", + "labels" => "labels", + "name" => "test 5", + "provider" => "aws", + "remediation" => "remediation 5" + } + ] + + assert expected_json == json_response(conn, 200) + end +end