diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 88f6ed9d..51eba4c4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,6 +30,7 @@ variables: .tier1_tests: &TIER1_TESTS - cib_constraints_create + - cib_node_attributes - cib_properties_empty - cib_properties_one_set - cib_resources_create @@ -49,6 +50,7 @@ variables: - cluster_basic - cluster_destroy - default + - node_options_check - qdevice_all_options - qdevice_minimal - qdevice_tls_kaptb_options diff --git a/README.md b/README.md index badd3307..18bfc95b 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ An Ansible role for managing High Availability Clustering. * resource defaults and resource operation defaults * stonith levels, also known as fencing topology * resource constraints + * Pacemaker node attributes ## Requirements @@ -489,6 +490,41 @@ Currently, only one set is supported. You may take a look at [an example](#configuring-cluster-properties). +#### `ha_cluster_node_options` + +structure, default: no node options + +```yaml +ha_cluster_node_options: + - node_name: node1 + attributes: + - attrs: + - name: attribute1 + value: value1_node1 + - name: attribute2 + value: value2_node1 + - node_name: node2 + attributes: + - attrs: + - name: attribute1 + value: value1_node2 + - name: attribute2 + value: value2_node2 +``` + +This variable defines various settings which vary from cluster node to cluster +node. **Note:** Use an inventory or playbook hosts to specify which nodes form +the cluster. This variable merely sets options for the specified nodes. The +items are as follows: + +* `node_name` (mandatory) - Node name. +* `attributes` (optional) - List of sets of Pacemaker node attributes for the + node. Currently, no more than one set for each node is supported. + +You may take a look at examples: + +* [configuring node attributes](#configuring-node-attributes) + #### `ha_cluster_resource_primitives` structure, default: no resources @@ -1855,6 +1891,33 @@ Note that you cannot run a quorum device on a cluster node. - linux-system-roles.ha_cluster ``` +### Configuring node attributes + +```yaml +- hosts: node1 node2 + vars: + ha_cluster_cluster_name: my-new-cluster + ha_cluster_hacluster_password: password + ha_cluster_node_options: + - node_name: node1 + attributes: + - attrs: + - name: attribute1 + value: value1A + - name: attribute2 + value: value2A + - node_name: node2 + attributes: + - attrs: + - name: attribute1 + value: value1B + - name: attribute2 + value: value2B + + roles: + - linux-system-roles.ha_cluster +``` + ### Purging all cluster configuration ```yaml diff --git a/defaults/main.yml b/defaults/main.yml index 594239aa..f2e3712a 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -46,6 +46,7 @@ ha_cluster_pcs_permission_list: - write ha_cluster_cluster_properties: [] +ha_cluster_node_options: [] ha_cluster_resource_defaults: {} ha_cluster_resource_operation_defaults: {} diff --git a/examples/node-attributes.yml b/examples/node-attributes.yml new file mode 100644 index 00000000..65c1917f --- /dev/null +++ b/examples/node-attributes.yml @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: MIT +--- +- name: Example ha_cluster role invocation - node attributes definition + hosts: all + vars: + ha_cluster_manage_firewall: true + ha_cluster_manage_selinux: true + ha_cluster_cluster_name: my-new-cluster + ha_cluster_hacluster_password: password + ha_cluster_node_options: + - node_name: node1 + attributes: + - attrs: + - name: attribute1 + value: value1A + - name: attribute2 + value: value2A + - node_name: node2 + attributes: + - attrs: + - name: attribute1 + value: value1B + - name: attribute2 + value: value2B + + roles: + - linux-system-roles.ha_cluster diff --git a/tasks/shell_pcs/check-and-prepare-role-variables.yml b/tasks/shell_pcs/check-and-prepare-role-variables.yml index 283c5405..0056e05f 100644 --- a/tasks/shell_pcs/check-and-prepare-role-variables.yml +++ b/tasks/shell_pcs/check-and-prepare-role-variables.yml @@ -1,5 +1,17 @@ # SPDX-License-Identifier: MIT --- +- name: Discover cluster node names + set_fact: + __ha_cluster_node_name: "{{ ha_cluster.node_name | d(inventory_hostname) }}" + +- name: Collect cluster node names + set_fact: + __ha_cluster_all_node_names: "{{ + ansible_play_hosts + | map('extract', hostvars, '__ha_cluster_node_name') + | list + }}" + - name: Check cluster configuration variables block: - name: Fail if passwords are not specified @@ -59,17 +71,26 @@ loop: "{{ ha_cluster_stonith_levels }}" run_once: true -- name: Discover cluster node names - set_fact: - __ha_cluster_node_name: "{{ ha_cluster.node_name | d(inventory_hostname) }}" - -- name: Collect cluster node names - set_fact: - __ha_cluster_all_node_names: "{{ - ansible_play_hosts - | map('extract', hostvars, '__ha_cluster_node_name') - | list - }}" + - name: Check ha_cluster_node_options + run_once: true + vars: + __nodes_from_options: "{{ + ha_cluster_node_options | map(attribute='node_name') | list }}" + block: + - name: > + Fail if ha_cluster_node_options contains unknown or duplicate nodes + fail: + msg: > + node_name fields in ha_cluster_node_options must be unique + and they must match cluster nodes + when: + - > + ( + (__nodes_from_options | length) != + (__nodes_from_options | unique | length) + ) or ( + __nodes_from_options | difference(__ha_cluster_all_node_names) + ) - name: Extract qdevice settings set_fact: diff --git a/tasks/shell_pcs/create-and-push-cib.yml b/tasks/shell_pcs/create-and-push-cib.yml index 4520f1ff..8c6aa2bf 100644 --- a/tasks/shell_pcs/create-and-push-cib.yml +++ b/tasks/shell_pcs/create-and-push-cib.yml @@ -88,13 +88,22 @@ ## Cluster properties - name: Configure cluster properties include_tasks: pcs-cluster-properties.yml - # pcs-0.10 supports only one set of attributes, that's the reason for + # pcs-0.11 supports only one set of attributes, that's the reason for # 'ha_cluster_cluster_properties[0]' and 'when' instead of looping over # ha_cluster_cluster_properties vars: properties_set: "{{ ha_cluster_cluster_properties[0] }}" when: ha_cluster_cluster_properties[0].attrs | d([]) + ## Node attributes + - name: Configure node attributes + include_tasks: pcs-node-attributes.yml + loop: "{{ ha_cluster_node_options | default([]) | + selectattr('attributes', 'defined') | list }}" + loop_control: + loop_var: node_options + + ## Resource and operation defaults # RHEL 8.3, which is the oldest version supported by the role, supports # multiple sets of resources and resources operations defaults. Therefore, # we don't need any checks for pcs capabilities. diff --git a/tasks/shell_pcs/pcs-node-attributes.yml b/tasks/shell_pcs/pcs-node-attributes.yml new file mode 100644 index 00000000..6393dedd --- /dev/null +++ b/tasks/shell_pcs/pcs-node-attributes.yml @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: MIT +--- +- name: Configure node attributes set for node {{ node_options.node_name }} + command: + # Multiple sets of attributes (with rules) per node are not supported by + # pcs (and therefore the role) as of yet + cmd: > + pcs -f {{ __ha_cluster_tempfile_cib_xml.path | quote }} + -- node attribute {{ node_options.node_name | quote }} + {% for attr_set in node_options.attributes | d([], true) %} + {% for attr in attr_set.attrs | d([]) %} + {{ attr.name | quote }}={{ attr.value | quote }} + {% endfor %} + {% endfor %} + # We always need to create CIB to see whether it's the same as what is + # already present in the cluster. However, we don't want to report it as a + # change since the only thing which matters is pushing the resulting CIB to + # the cluster. + check_mode: false + changed_when: not ansible_check_mode diff --git a/tests/tasks/assert_node_options_check.yml b/tests/tasks/assert_node_options_check.yml new file mode 100644 index 00000000..b2d84d5a --- /dev/null +++ b/tests/tasks/assert_node_options_check.yml @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: MIT +--- +- name: Debug ha_cluster_node_options + debug: + var: ha_cluster_node_options + +- name: Run the role and check for errors + block: + - name: Run the role + include_role: + name: linux-system-roles.ha_cluster + + - name: Fail + fail: + msg: Expected failure did not occur + rescue: + - name: Check errors + assert: + that: ansible_failed_result.msg | trim == expected_msg + run_once: true # noqa: run_once[task] + vars: + expected_msg: >- + node_name fields in ha_cluster_node_options + must be unique and they must match cluster nodes diff --git a/tests/tests_cib_node_attributes.yml b/tests/tests_cib_node_attributes.yml new file mode 100644 index 00000000..c5da1728 --- /dev/null +++ b/tests/tests_cib_node_attributes.yml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: MIT +--- +- name: Configure node attributes + hosts: all + vars_files: vars/main.yml + + tasks: + - name: Run test + tags: tests::verify + block: + - name: Set up test environment + include_role: + name: linux-system-roles.ha_cluster + tasks_from: test_setup.yml + + - name: Find first node name + set_fact: + __test_first_node: "{{ + (ansible_play_hosts_all | length == 1) + | ternary('localhost', ansible_play_hosts[0]) }}" + + - name: Run HA Cluster role + include_role: + name: linux-system-roles.ha_cluster + public: true + vars: + ha_cluster_cluster_name: test-cluster + ha_cluster_manage_firewall: true + ha_cluster_manage_selinux: true + ha_cluster_node_options: + - node_name: "{{ __test_first_node }}" + attributes: + - attrs: + - name: attr1 + value: val1 + - name: attr2 + value: val2 + - attrs: + - name: attr3 + value: val3 + + - name: Verify node attributes + vars: + __test_expected_lines: + - "Node Attributes:" + - " {{ __test_first_node }}: attr1=val1 attr2=val2 attr3=val3" + block: + - name: Fetch node attributes configuration from the cluster + command: + cmd: pcs node attribute + register: __test_pcs_node_attribute_config + changed_when: false + + - name: Print real node attributes configuration + debug: + var: __test_pcs_node_attribute_config + + - name: Print expected node attributes configuration + debug: + var: __test_expected_lines | list + + - name: Check node attributes configuration + assert: + that: + - __test_pcs_node_attribute_config.stdout_lines + == __test_expected_lines | list + + - name: Check firewall and selinux state + include_tasks: tasks/check_firewall_selinux.yml diff --git a/tests/tests_node_options_check.yml b/tests/tests_node_options_check.yml new file mode 100644 index 00000000..68ce2ac4 --- /dev/null +++ b/tests/tests_node_options_check.yml @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: MIT +--- +- name: Verify node_name in ha_cluster_node_options check is working + hosts: all + vars_files: vars/main.yml + + tasks: + - name: Run test + tags: tests::verify + block: + - name: Set up test environment + include_role: + name: linux-system-roles.ha_cluster + tasks_from: test_setup.yml + + - name: Find first node name + set_fact: + __test_first_node: "{{ + (ansible_play_hosts_all | length == 1) + | ternary('localhost', ansible_play_hosts[0]) }}" + + - name: Verify node options check is working for various input data + include_tasks: tasks/assert_node_options_check.yml + loop: + # yamllint disable rule:hyphens + - + - node_name: "{{ __test_first_node }}" + - node_name: "{{ __test_first_node }}" + - + - node_name: "{{ __test_first_node }}" + - node_name: "{{ __test_first_node ~ 'x' }}" + # yamllint enable rule:hyphens + loop_control: + loop_var: ha_cluster_node_options