From 2aa1be19ec7289cc66a52e8b1ad01b209fe352f4 Mon Sep 17 00:00:00 2001 From: arbulu89 Date: Thu, 24 Mar 2022 09:00:48 +0100 Subject: [PATCH 1/8] Copy current checks selection view to the catalog sidebar element --- .../ChecksCatalog/ChecksCatalog.jsx | 117 ++++++++++++++++++ assets/js/components/ChecksCatalog/index.js | 3 + assets/js/components/Layout/Layout.jsx | 6 + assets/js/trento.jsx | 2 + 4 files changed, 128 insertions(+) create mode 100644 assets/js/components/ChecksCatalog/ChecksCatalog.jsx create mode 100644 assets/js/components/ChecksCatalog/index.js diff --git a/assets/js/components/ChecksCatalog/ChecksCatalog.jsx b/assets/js/components/ChecksCatalog/ChecksCatalog.jsx new file mode 100644 index 0000000000..68169359c8 --- /dev/null +++ b/assets/js/components/ChecksCatalog/ChecksCatalog.jsx @@ -0,0 +1,117 @@ +import React, { useState, useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { useParams, useNavigate } from 'react-router-dom'; + +import { Switch } from '@headlessui/react'; + +function classNames(...classes) { + return classes.filter(Boolean).join(' '); +} + +const toggle = (list, element) => + list.includes(element) + ? list.filter((string) => string !== element) + : [...list, element]; + +const ChecksCatalog = () => { + const { clusterID } = useParams(); + const navigate = useNavigate(); + const dispatch = useDispatch(); + + const catalog = useSelector((state) => state.catalog.catalog); + const clusters = useSelector((state) => state.clustersList.clusters); + const cluster = clusters.find((cluster) => cluster.id === clusterID); + + const [selectedChecks, setSelectedChecks] = useState( + cluster ? cluster.selected_checks : [] + ); + + const isSelected = (check_id) => + selectedChecks ? selectedChecks.includes(check_id) : false; + + useEffect(() => { + if (cluster) { + setSelectedChecks(cluster.selected_checks ? cluster.selected_checks : []); + } + }, [cluster]); + + return ( +
+ {catalog.map(({ group, checks }) => ( +
+
+

+ {group} +

+
+ +
+ ))} +
+ +
+
+ ); +}; + +export default ChecksCatalog; diff --git a/assets/js/components/ChecksCatalog/index.js b/assets/js/components/ChecksCatalog/index.js new file mode 100644 index 0000000000..e2133b19cf --- /dev/null +++ b/assets/js/components/ChecksCatalog/index.js @@ -0,0 +1,3 @@ +import ChecksCatalog from './ChecksCatalog'; + +export default ChecksCatalog; diff --git a/assets/js/components/Layout/Layout.jsx b/assets/js/components/Layout/Layout.jsx index 83b61cf924..393f5b383b 100644 --- a/assets/js/components/Layout/Layout.jsx +++ b/assets/js/components/Layout/Layout.jsx @@ -8,6 +8,7 @@ import { EOS_INFO, EOS_SYSTEM_GROUP, EOS_STORAGE, + EOS_LIST, } from 'eos-icons-react'; import TrentoLogo from '../../../static/trento-logo-stacked.svg'; @@ -35,6 +36,11 @@ const navigation = [ href: '/databases', icon: EOS_STORAGE, }, + { + name: 'Checks catalog', + href: '/catalog', + icon: EOS_LIST + }, { name: 'About', href: '/about', icon: EOS_INFO }, ]; diff --git a/assets/js/trento.jsx b/assets/js/trento.jsx index 9792df7219..6e918fcfeb 100644 --- a/assets/js/trento.jsx +++ b/assets/js/trento.jsx @@ -20,6 +20,7 @@ import HostDetails from '@components/HostDetails'; import DatabasesOverview from '@components/DatabasesOverview'; import SapSystemDetails from './components/SapSystemDetails/SapSystemDetails'; import DatabaseDetails from './components/DatabaseDetails'; +import ChecksCatalog from '@components/ChecksCatalog'; const App = () => { return ( @@ -34,6 +35,7 @@ const App = () => { } /> } /> } /> + } /> } /> Date: Thu, 24 Mar 2022 14:08:25 +0100 Subject: [PATCH 2/8] Add collapsable element to the checks catalog --- .../ChecksCatalog/ChecksCatalog.jsx | 120 ++++++------------ assets/package.json | 4 +- 2 files changed, 44 insertions(+), 80 deletions(-) diff --git a/assets/js/components/ChecksCatalog/ChecksCatalog.jsx b/assets/js/components/ChecksCatalog/ChecksCatalog.jsx index 68169359c8..45def55af9 100644 --- a/assets/js/components/ChecksCatalog/ChecksCatalog.jsx +++ b/assets/js/components/ChecksCatalog/ChecksCatalog.jsx @@ -2,38 +2,15 @@ import React, { useState, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { useParams, useNavigate } from 'react-router-dom'; -import { Switch } from '@headlessui/react'; +import { Switch, Disclosure, Transition } from '@headlessui/react'; +import { ChevronRightIcon } from '@heroicons/react/solid' -function classNames(...classes) { - return classes.filter(Boolean).join(' '); -} +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' -const toggle = (list, element) => - list.includes(element) - ? list.filter((string) => string !== element) - : [...list, element]; const ChecksCatalog = () => { - const { clusterID } = useParams(); - const navigate = useNavigate(); - const dispatch = useDispatch(); - const catalog = useSelector((state) => state.catalog.catalog); - const clusters = useSelector((state) => state.clustersList.clusters); - const cluster = clusters.find((cluster) => cluster.id === clusterID); - - const [selectedChecks, setSelectedChecks] = useState( - cluster ? cluster.selected_checks : [] - ); - - const isSelected = (check_id) => - selectedChecks ? selectedChecks.includes(check_id) : false; - - useEffect(() => { - if (cluster) { - setSelectedChecks(cluster.selected_checks ? cluster.selected_checks : []); - } - }, [cluster]); return ( ))} -
- -
); }; diff --git a/assets/package.json b/assets/package.json index 317063c588..f5cea9f107 100644 --- a/assets/package.json +++ b/assets/package.json @@ -28,9 +28,11 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-hot-toast": "^2.2.0", + "react-markdown": "^8.0.1", "react-redux": "^7.2.6", "react-router-dom": "^6.2.2", - "redux-saga": "^1.1.3" + "redux-saga": "^1.1.3", + "remark-gfm": "^3.0.1" }, "scripts": { "tailwind:build": "tailwindcss --postcss --minify --input=css/app.css --output=../priv/static/assets/app.css", From 8ac5858a858f8f1130faaa59dd76dfa8a3540e38 Mon Sep 17 00:00:00 2001 From: arbulu89 Date: Fri, 25 Mar 2022 08:54:15 +0100 Subject: [PATCH 3/8] Change hardcoded catalog data to include the provider --- priv/data/catalog.json | 937 ++++++++++++++++++++++++++--------------- 1 file changed, 593 insertions(+), 344 deletions(-) diff --git a/priv/data/catalog.json b/priv/data/catalog.json index f40222382f..e9ea5db5b0 100644 --- a/priv/data/catalog.json +++ b/priv/data/catalog.json @@ -1,376 +1,625 @@ [ { - "group": "Corosync", - "checks": [ + "provider": "azure", + "groups": [ { - "id": "156F64", - "name": "1.1.1", "group": "Corosync", - "description": "Corosync `token` timeout is set to `30000`\n", - "remediation": "## Remediation\nAdjust the Corosync `token` 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:\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", - "premium": false + "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" + } + ] }, { - "id": "53D035", - "name": "1.1.1.runtime", - "group": "Corosync", - "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\nAdjust the corosync `token` 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.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", - "premium": false - }, - { - "id": "A1244C", - "name": "1.1.2", - "group": "Corosync", - "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", - "premium": false - }, - { - "id": "FB0E0D", - "name": "1.1.2.runtime", - "group": "Corosync", - "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", - "premium": false - }, - { - "id": "845CC9", - "name": "1.1.3", - "group": "Corosync", - "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", - "premium": false - }, - { - "id": "00081D", - "name": "1.1.3.runtime", - "group": "Corosync", - "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", - "premium": false - }, - { - "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", - "premium": false - }, - { - "id": "822E47", - "name": "1.1.4.runtime", - "group": "Corosync", - "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", - "premium": false - }, - { - "id": "21FCA6", - "name": "1.1.5", - "group": "Corosync", - "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", - "premium": false - }, - { - "id": "15F7A8", - "name": "1.1.5.runtime", - "group": "Corosync", - "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", - "premium": false - }, - { - "id": "33403D", - "name": "1.1.6", - "group": "Corosync", - "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", - "premium": false - }, - { - "id": "7E0221", - "name": "1.1.6.runtime", - "group": "Corosync", - "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", - "premium": false - }, - { - "id": "C620DC", - "name": "1.1.7", - "group": "Corosync", - "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", - "premium": false - }, - { - "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", - "premium": false + "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" + } + ] }, { - "id": "D78671", - "name": "1.1.8.runtime", - "group": "Corosync", - "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", - "premium": false + "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" + } + ] }, { - "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\" ]] \u0026\u0026 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 \u003e 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", - "premium": false + "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" + } + ] }, { - "id": "32CFC6", - "name": "1.1.9.runtime", - "group": "Corosync", - "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\" ]] \u0026\u0026 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 \u003e 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", - "premium": false + "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" + } + ] } ] }, { - "group": "Pacemaker", - "checks": [ + "provider": "aws", + "groups": [ { - "id": "205AF7", - "name": "1.2.1", - "group": "Pacemaker", - "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", - "premium": false + "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" + } + ] }, { - "id": "373DB8", - "name": "1.2.2", "group": "Pacemaker", - "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\" \u003e /dev/null 2\u003e\u00261 ]]; 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 \u003e 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", - "premium": false - } - ] - }, - { - "group": "SBD", - "checks": [ - { - "id": "0B6DB2", - "name": "1.3.1", - "group": "SBD", - "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", - "premium": false - }, - { - "id": "49591F", - "name": "1.3.2", - "group": "SBD", - "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", - "premium": false - }, - { - "id": "816815", - "name": "1.3.3", - "group": "SBD", - "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", - "premium": false - }, - { - "id": "61451E", - "name": "1.3.4", - "group": "SBD", - "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", - "premium": false - }, - { - "id": "B089BE", - "name": "1.3.5", - "group": "SBD", - "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", - "premium": false + "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" + } + ] }, { - "id": "68626E", - "name": "1.3.6", "group": "SBD", - "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", - "premium": false + "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" + } + ] }, { - "id": "A2EF8C", - "name": "1.3.7", - "group": "SBD", - "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\" ]] \u0026\u0026 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 \u003e 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", - "premium": false - } - ] - }, - { - "group": "Miscellaneous", - "checks": [ - { - "id": "790926", - "name": "1.5.2", "group": "Miscellaneous", - "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} ]] \u0026\u0026 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 \u003e 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", - "premium": false - } - ] - }, - { - "group": "OS and package versions", - "checks": [ - { - "id": "CAEFF1", - "name": "2.2.1", - "group": "OS and package versions", - "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", - "premium": false - }, - { - "id": "D028B9", - "name": "2.2.2", - "group": "OS and package versions", - "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], '\u003e=') }}\"", - "labels": "hana", - "premium": false - }, - { - "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], '\u003e=') }}\"", - "labels": "hana", - "premium": false - }, - { - "id": "9FAAD0", - "name": "2.2.3.exclude", - "group": "OS and package versions", - "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 \u003e 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", - "premium": false - }, - { - "id": "DC5429", - "name": "2.2.4", - "group": "OS and package versions", - "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], '\u003e=') }}\"", - "labels": "hana", - "premium": false - }, - { - "id": "222A57", - "name": "2.2.5", - "group": "OS and package versions", - "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], '\u003e=') }}\"", - "labels": "hana", - "premium": false - }, - { - "id": "C3166E", - "name": "2.2.5.exclude", - "group": "OS and package versions", - "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 \u003e 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", - "premium": false + "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" + } + ] }, { - "id": "F50AF5", - "name": "2.2.7", "group": "OS and package versions", - "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], '\u003e=') }}\"", - "labels": "hana", - "premium": false + "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" + } + ] } ] } From 1c09ab45e5c9d4894e602a22db4bd7b29240712f Mon Sep 17 00:00:00 2001 From: arbulu89 Date: Fri, 25 Mar 2022 08:54:45 +0100 Subject: [PATCH 4/8] Add cloud provider selection in the catalog --- .../ChecksCatalog/ChecksCatalog.jsx | 131 ++++++++++-------- .../ChecksCatalog/ProviderSelection.jsx | 64 +++++++++ 2 files changed, 135 insertions(+), 60 deletions(-) create mode 100644 assets/js/components/ChecksCatalog/ProviderSelection.jsx diff --git a/assets/js/components/ChecksCatalog/ChecksCatalog.jsx b/assets/js/components/ChecksCatalog/ChecksCatalog.jsx index 45def55af9..14d024adc2 100644 --- a/assets/js/components/ChecksCatalog/ChecksCatalog.jsx +++ b/assets/js/components/ChecksCatalog/ChecksCatalog.jsx @@ -1,77 +1,88 @@ import React, { useState, useEffect } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { useParams, useNavigate } from 'react-router-dom'; +import { useSelector } from 'react-redux'; -import { Switch, Disclosure, Transition } from '@headlessui/react'; -import { ChevronRightIcon } from '@heroicons/react/solid' +import { Disclosure, Transition } from '@headlessui/react'; import ReactMarkdown from 'react-markdown' import remarkGfm from 'remark-gfm' +import ProviderSelection from './ProviderSelection'; const ChecksCatalog = () => { + const catalog = useSelector((state) => state.catalog.catalog); + const providers = catalog.map(provider => provider.provider) + const [selected, setSelected] = useState(providers[0]) + + useEffect(() => { + setSelected(providers[0]) + },[providers[0]]) return (
- {catalog.map(({ group, checks }) => ( -
-
-

- {group} -

-
-
    - {checks.map((check) => ( -
  • - - -
    -
    -

    - {check.id} -

    - {check.premium > 0 && -

    - Premium -

    - } -
    -
    -
    -

    - -

    + + {catalog.filter(provider => provider.provider == selected) + .map(({provider, groups}) => + groups.map(({group, checks}) => +
    +
    +

    + {group} +

    +
    +
      + {checks.map((check) => ( +
    • + + +
      +
      +

      + {check.id} +

      + {check.premium > 0 && +

      + Premium +

      + } +
      +
      +
      +

      + +

      +
      +
      -
    -
    - - - -
    -
    - -
    -
    -
    -
    - -
  • - ))} -
-
- ))} + + + +
+
+ +
+
+
+
+ + + ))} + +
+ ) + ) + } ); }; diff --git a/assets/js/components/ChecksCatalog/ProviderSelection.jsx b/assets/js/components/ChecksCatalog/ProviderSelection.jsx new file mode 100644 index 0000000000..65d7262584 --- /dev/null +++ b/assets/js/components/ChecksCatalog/ProviderSelection.jsx @@ -0,0 +1,64 @@ +import React, { Fragment, useState } from 'react'; + +import { Listbox, Transition } from '@headlessui/react'; +import { CheckIcon, SelectorIcon } from '@heroicons/react/solid' + +const ProviderSelection = ({providers, selected, setSelected}) => { + + return ( +
+ +
+ + {selected} + + + + + + {providers.map((provider, providerIdx) => ( + + `cursor-default select-none relative py-2 pl-10 pr-4 ${ + active ? 'text-green-900 bg-green-100' : 'text-gray-900' + }` + } + value={provider} + > + {({ selected }) => ( + <> + + {provider} + + {selected ? ( + + + ) : null} + + )} + + ))} + + +
+
+
+ ); +}; + +export default ProviderSelection; From 4c3495a48353c1b3395445bb05f788570c1920e9 Mon Sep 17 00:00:00 2001 From: arbulu89 Date: Fri, 25 Mar 2022 11:36:44 +0100 Subject: [PATCH 5/8] Apply new css styles to markdown blocks --- assets/css/app.css | 2 + assets/css/markdown.scss | 93 +++++++++++++++++++ .../ChecksCatalog/ChecksCatalog.jsx | 2 +- assets/package.json | 1 + assets/postcss.config.js | 2 + 5 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 assets/css/markdown.scss diff --git a/assets/css/app.css b/assets/css/app.css index 95e9bc14e2..382c26754a 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -1,4 +1,6 @@ /* This file is for your main application CSS */ +@import "./markdown.scss"; + @tailwind base; @tailwind components; diff --git a/assets/css/markdown.scss b/assets/css/markdown.scss new file mode 100644 index 0000000000..7c0ff054a3 --- /dev/null +++ b/assets/css/markdown.scss @@ -0,0 +1,93 @@ +.markdown { + *{margin:0;padding:0;} +body { + font:13.34px helvetica,arial,freesans,clean,sans-serif; + color:black; + line-height:1.4em; + background-color: #F8F8F8; +} +p { + margin:1em 0; + line-height:1.5em; +} +table { + font-size:inherit; + font:100%; + margin:1em; +} +table th{border-bottom:1px solid #bbb;padding:.2em 1em;} +table td{border-bottom:1px solid #ddd;padding:.2em 1em;} +input[type=text],input[type=password],input[type=image],textarea{font:99% helvetica,arial,freesans,sans-serif;} +select,option{padding:0 .25em;} +optgroup{margin-top:.5em;} +pre,code{font:12px Menlo, Monaco, "DejaVu Sans Mono", "Bitstream Vera Sans Mono",monospace;} +pre { + margin:1em 0; + font-size:12px; + background-color:#eee; + border:1px solid #ddd; + padding:5px; + line-height:1.5em; + color:#444; + overflow:auto; + -webkit-box-shadow:rgba(0,0,0,0.07) 0 1px 2px inset; + -webkit-border-radius:3px; + -moz-border-radius:3px;border-radius:3px; +} +pre code { + padding:0; + font-size:12px; + background-color:#eee; + border:none; +} +code { + font-size:12px; + background-color:#f8f8ff; + color:#444; + padding:0 .2em; + border:1px solid #dedede; +} +img{border:0;max-width:100%;} +abbr{border-bottom:none;} +a{color:#4183c4;text-decoration:none;} +a:hover{text-decoration:underline;} +a code,a:link code,a:visited code{color:#4183c4;} +h2,h3{margin:1em 0;} +h1,h2,h3,h4,h5,h6{border:0;} +h1{font-size:170%;border-top:4px solid #aaa;padding-top:.5em;margin-top:1.5em;} +h1:first-child{margin-top:0;padding-top:.25em;border-top:none;} +h2{font-size:150%;margin-top:.5em;border-top:2px solid #e0e0e0;padding-top:.5em;} +h3{margin-top:1em;} +hr{border:1px solid #ddd;} +ul{margin:1em 0 1em 2em;list-style-type: circle;} +ol{margin:1em 0 1em 2em;list-style-type: decimal;} +ul li,ol li{margin-top:.5em;margin-bottom:.5em;} +ul ul,ul ol,ol ol,ol ul{margin-top:0;margin-bottom:0;} +blockquote{margin:1em 0;border-left:5px solid #ddd;padding-left:.6em;color:#555;} +dt{font-weight:bold;margin-left:1em;} +dd{margin-left:2em;margin-bottom:1em;} +sup { + font-size: 0.83em; + vertical-align: super; + line-height: 0; +} +* { + -webkit-print-color-adjust: exact; +} +@media screen and (min-width: 914px) { + body { + width: 854px; + margin:0 auto; + } +} +@media print { + table, pre { + page-break-inside: avoid; + } + pre { + word-wrap: break-word; + } +} + + +} diff --git a/assets/js/components/ChecksCatalog/ChecksCatalog.jsx b/assets/js/components/ChecksCatalog/ChecksCatalog.jsx index 14d024adc2..124188315a 100644 --- a/assets/js/components/ChecksCatalog/ChecksCatalog.jsx +++ b/assets/js/components/ChecksCatalog/ChecksCatalog.jsx @@ -70,7 +70,7 @@ const ChecksCatalog = () => {
- +
diff --git a/assets/package.json b/assets/package.json index f5cea9f107..36d17d022c 100644 --- a/assets/package.json +++ b/assets/package.json @@ -25,6 +25,7 @@ "classnames": "^2.3.1", "dayjs": "^1.11.0", "eos-icons-react": "^2.2.0", + "postcss-import": "^14.1.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-hot-toast": "^2.2.0", diff --git a/assets/postcss.config.js b/assets/postcss.config.js index 63ef4c6bc7..da783f1536 100644 --- a/assets/postcss.config.js +++ b/assets/postcss.config.js @@ -1,6 +1,8 @@ /* eslint-disable no-undef */ module.exports = { plugins: { + 'postcss-import': {}, + 'tailwindcss/nesting': {}, tailwindcss: {}, autoprefixer: {}, }, From 5bdd07f6f1413c017b3a77f94d2af5060570f267 Mon Sep 17 00:00:00 2001 From: arbulu89 Date: Fri, 25 Mar 2022 13:38:48 +0100 Subject: [PATCH 6/8] Fix eslint issues --- assets/css/app.css | 2 +- assets/css/markdown.scss | 272 ++++++++++++------ .../ChecksCatalog/ChecksCatalog.jsx | 54 ++-- .../ChecksCatalog/ProviderSelection.jsx | 7 +- assets/js/components/Layout/Layout.jsx | 2 +- 5 files changed, 221 insertions(+), 116 deletions(-) diff --git a/assets/css/app.css b/assets/css/app.css index 382c26754a..9079806482 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -1,5 +1,5 @@ /* This file is for your main application CSS */ -@import "./markdown.scss"; +@import './markdown.scss'; @tailwind base; diff --git a/assets/css/markdown.scss b/assets/css/markdown.scss index 7c0ff054a3..5c89e14efc 100644 --- a/assets/css/markdown.scss +++ b/assets/css/markdown.scss @@ -1,93 +1,185 @@ .markdown { - *{margin:0;padding:0;} -body { - font:13.34px helvetica,arial,freesans,clean,sans-serif; - color:black; - line-height:1.4em; - background-color: #F8F8F8; -} -p { - margin:1em 0; - line-height:1.5em; -} -table { - font-size:inherit; - font:100%; - margin:1em; -} -table th{border-bottom:1px solid #bbb;padding:.2em 1em;} -table td{border-bottom:1px solid #ddd;padding:.2em 1em;} -input[type=text],input[type=password],input[type=image],textarea{font:99% helvetica,arial,freesans,sans-serif;} -select,option{padding:0 .25em;} -optgroup{margin-top:.5em;} -pre,code{font:12px Menlo, Monaco, "DejaVu Sans Mono", "Bitstream Vera Sans Mono",monospace;} -pre { - margin:1em 0; - font-size:12px; - background-color:#eee; - border:1px solid #ddd; - padding:5px; - line-height:1.5em; - color:#444; - overflow:auto; - -webkit-box-shadow:rgba(0,0,0,0.07) 0 1px 2px inset; - -webkit-border-radius:3px; - -moz-border-radius:3px;border-radius:3px; -} -pre code { - padding:0; - font-size:12px; - background-color:#eee; - border:none; -} -code { - font-size:12px; - background-color:#f8f8ff; - color:#444; - padding:0 .2em; - border:1px solid #dedede; -} -img{border:0;max-width:100%;} -abbr{border-bottom:none;} -a{color:#4183c4;text-decoration:none;} -a:hover{text-decoration:underline;} -a code,a:link code,a:visited code{color:#4183c4;} -h2,h3{margin:1em 0;} -h1,h2,h3,h4,h5,h6{border:0;} -h1{font-size:170%;border-top:4px solid #aaa;padding-top:.5em;margin-top:1.5em;} -h1:first-child{margin-top:0;padding-top:.25em;border-top:none;} -h2{font-size:150%;margin-top:.5em;border-top:2px solid #e0e0e0;padding-top:.5em;} -h3{margin-top:1em;} -hr{border:1px solid #ddd;} -ul{margin:1em 0 1em 2em;list-style-type: circle;} -ol{margin:1em 0 1em 2em;list-style-type: decimal;} -ul li,ol li{margin-top:.5em;margin-bottom:.5em;} -ul ul,ul ol,ol ol,ol ul{margin-top:0;margin-bottom:0;} -blockquote{margin:1em 0;border-left:5px solid #ddd;padding-left:.6em;color:#555;} -dt{font-weight:bold;margin-left:1em;} -dd{margin-left:2em;margin-bottom:1em;} -sup { - font-size: 0.83em; - vertical-align: super; - line-height: 0; -} -* { - -webkit-print-color-adjust: exact; -} -@media screen and (min-width: 914px) { - body { + * { + margin: 0; + padding: 0; + } + body { + font: 13.34px helvetica, arial, freesans, clean, sans-serif; + color: black; + line-height: 1.4em; + background-color: #f8f8f8; + } + p { + margin: 1em 0; + line-height: 1.5em; + } + table { + font-size: inherit; + font: 100%; + margin: 1em; + } + table th { + border-bottom: 1px solid #bbb; + padding: 0.2em 1em; + } + table td { + border-bottom: 1px solid #ddd; + padding: 0.2em 1em; + } + input[type='text'], + input[type='password'], + input[type='image'], + textarea { + font: 99% helvetica, arial, freesans, sans-serif; + } + select, + option { + padding: 0 0.25em; + } + optgroup { + margin-top: 0.5em; + } + pre, + code { + font: 12px Menlo, Monaco, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', + monospace; + } + pre { + margin: 1em 0; + font-size: 12px; + background-color: #eee; + border: 1px solid #ddd; + padding: 5px; + line-height: 1.5em; + color: #444; + overflow: auto; + -webkit-box-shadow: rgba(0, 0, 0, 0.07) 0 1px 2px inset; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + } + pre code { + padding: 0; + font-size: 12px; + background-color: #eee; + border: none; + } + code { + font-size: 12px; + background-color: #f8f8ff; + color: #444; + padding: 0 0.2em; + border: 1px solid #dedede; + } + img { + border: 0; + max-width: 100%; + } + abbr { + border-bottom: none; + } + a { + color: #4183c4; + text-decoration: none; + } + a:hover { + text-decoration: underline; + } + a code, + a:link code, + a:visited code { + color: #4183c4; + } + h2, + h3 { + margin: 1em 0; + } + h1, + h2, + h3, + h4, + h5, + h6 { + border: 0; + } + h1 { + font-size: 170%; + border-top: 4px solid #aaa; + padding-top: 0.5em; + margin-top: 1.5em; + } + h1:first-child { + margin-top: 0; + padding-top: 0.25em; + border-top: none; + } + h2 { + font-size: 150%; + margin-top: 0.5em; + border-top: 2px solid #e0e0e0; + padding-top: 0.5em; + } + h3 { + margin-top: 1em; + } + hr { + border: 1px solid #ddd; + } + ul { + margin: 1em 0 1em 2em; + list-style-type: circle; + } + ol { + margin: 1em 0 1em 2em; + list-style-type: decimal; + } + ul li, + ol li { + margin-top: 0.5em; + margin-bottom: 0.5em; + } + ul ul, + ul ol, + ol ol, + ol ul { + margin-top: 0; + margin-bottom: 0; + } + blockquote { + margin: 1em 0; + border-left: 5px solid #ddd; + padding-left: 0.6em; + color: #555; + } + dt { + font-weight: bold; + margin-left: 1em; + } + dd { + margin-left: 2em; + margin-bottom: 1em; + } + sup { + font-size: 0.83em; + vertical-align: super; + line-height: 0; + } + * { + -webkit-print-color-adjust: exact; + } + @media screen and (min-width: 914px) { + body { width: 854px; - margin:0 auto; - } -} -@media print { - table, pre { - page-break-inside: avoid; - } - pre { - word-wrap: break-word; - } -} - - + margin: 0 auto; + } + } + @media print { + table, + pre { + page-break-inside: avoid; + } + pre { + word-wrap: break-word; + } + } } diff --git a/assets/js/components/ChecksCatalog/ChecksCatalog.jsx b/assets/js/components/ChecksCatalog/ChecksCatalog.jsx index 124188315a..f9387d626a 100644 --- a/assets/js/components/ChecksCatalog/ChecksCatalog.jsx +++ b/assets/js/components/ChecksCatalog/ChecksCatalog.jsx @@ -3,27 +3,31 @@ import { useSelector } from 'react-redux'; import { Disclosure, Transition } from '@headlessui/react'; -import ReactMarkdown from 'react-markdown' -import remarkGfm from 'remark-gfm' +import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; import ProviderSelection from './ProviderSelection'; const ChecksCatalog = () => { - const catalog = useSelector((state) => state.catalog.catalog); - const providers = catalog.map(provider => provider.provider) - const [selected, setSelected] = useState(providers[0]) + const providers = catalog.map((provider) => provider.provider); + const [selected, setSelected] = useState(providers[0]); useEffect(() => { - setSelected(providers[0]) - },[providers[0]]) + setSelected(providers[0]); + }, [providers[0]]); return (
- - {catalog.filter(provider => provider.provider == selected) - .map(({provider, groups}) => - groups.map(({group, checks}) => + + {catalog + .filter((provider) => provider.provider == selected) + .map(({ _, groups }) => + groups.map(({ group, checks }) => (
{ {checks.map((check) => (
  • - +

    {check.id}

    - {check.premium > 0 && + {check.premium > 0 && (

    Premium

    - } + )}

    - + + {check.description} +

    -
    {
    - + + {check.remediation} +
    @@ -80,9 +95,8 @@ const ChecksCatalog = () => { ))}
  • - ) - ) - } + )) + )}
    ); }; diff --git a/assets/js/components/ChecksCatalog/ProviderSelection.jsx b/assets/js/components/ChecksCatalog/ProviderSelection.jsx index 65d7262584..c80eb396e3 100644 --- a/assets/js/components/ChecksCatalog/ProviderSelection.jsx +++ b/assets/js/components/ChecksCatalog/ProviderSelection.jsx @@ -1,10 +1,9 @@ -import React, { Fragment, useState } from 'react'; +import React, { Fragment } from 'react'; import { Listbox, Transition } from '@headlessui/react'; -import { CheckIcon, SelectorIcon } from '@heroicons/react/solid' - -const ProviderSelection = ({providers, selected, setSelected}) => { +import { CheckIcon, SelectorIcon } from '@heroicons/react/solid'; +const ProviderSelection = ({ providers, selected, setSelected }) => { return (
    diff --git a/assets/js/components/Layout/Layout.jsx b/assets/js/components/Layout/Layout.jsx index 393f5b383b..abb3b60d40 100644 --- a/assets/js/components/Layout/Layout.jsx +++ b/assets/js/components/Layout/Layout.jsx @@ -39,7 +39,7 @@ const navigation = [ { name: 'Checks catalog', href: '/catalog', - icon: EOS_LIST + icon: EOS_LIST, }, { name: 'About', href: '/about', icon: EOS_INFO }, ]; From 931ca4d3cfdebf83f90740b65deabc9f99dbbe75 Mon Sep 17 00:00:00 2001 From: arbulu89 Date: Fri, 25 Mar 2022 14:32:30 +0100 Subject: [PATCH 7/8] Render markdown styles in the check description --- assets/js/components/ChecksCatalog/ChecksCatalog.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/js/components/ChecksCatalog/ChecksCatalog.jsx b/assets/js/components/ChecksCatalog/ChecksCatalog.jsx index f9387d626a..ff6b366a25 100644 --- a/assets/js/components/ChecksCatalog/ChecksCatalog.jsx +++ b/assets/js/components/ChecksCatalog/ChecksCatalog.jsx @@ -60,6 +60,7 @@ const ChecksCatalog = () => {

    {check.description} From 2204511c4bde44584b72ad4dcd3afbf4d7fcda1d Mon Sep 17 00:00:00 2001 From: arbulu89 Date: Fri, 25 Mar 2022 16:18:33 +0100 Subject: [PATCH 8/8] Change setSelected property name to onChange --- assets/js/components/ChecksCatalog/ChecksCatalog.jsx | 2 +- assets/js/components/ChecksCatalog/ProviderSelection.jsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/js/components/ChecksCatalog/ChecksCatalog.jsx b/assets/js/components/ChecksCatalog/ChecksCatalog.jsx index ff6b366a25..c2d93a0767 100644 --- a/assets/js/components/ChecksCatalog/ChecksCatalog.jsx +++ b/assets/js/components/ChecksCatalog/ChecksCatalog.jsx @@ -22,7 +22,7 @@ const ChecksCatalog = () => { {catalog .filter((provider) => provider.provider == selected) diff --git a/assets/js/components/ChecksCatalog/ProviderSelection.jsx b/assets/js/components/ChecksCatalog/ProviderSelection.jsx index c80eb396e3..592f890349 100644 --- a/assets/js/components/ChecksCatalog/ProviderSelection.jsx +++ b/assets/js/components/ChecksCatalog/ProviderSelection.jsx @@ -3,10 +3,10 @@ import React, { Fragment } from 'react'; import { Listbox, Transition } from '@headlessui/react'; import { CheckIcon, SelectorIcon } from '@heroicons/react/solid'; -const ProviderSelection = ({ providers, selected, setSelected }) => { +const ProviderSelection = ({ providers, selected, onChange }) => { return (

    - +
    {selected}