diff --git a/tasks/input_sql_file.yml b/tasks/input_sql_file.yml index ef4c5164..831205a4 100644 --- a/tasks/input_sql_file.yml +++ b/tasks/input_sql_file.yml @@ -53,7 +53,9 @@ include_tasks: verify_password.yml vars: __mssql_password: "{{ mssql_password }}" - when: __mssql_sqlcmd_login_cmd is none + when: >- + (__mssql_sqlcmd_login_cmd is none) or + (__mssql_sqlcmd_login_cmd is not defined) - name: Input {{ __mssql_input_sql_file }} with the sqlcmd command command: >- @@ -72,14 +74,20 @@ - __mssql_sqlcmd_input_file.stdout_lines | length > 0 - mssql_debug | bool - # Keep the file if the SQL command failed for debugging + # Keep the file if the SQL command failed or when mssql_debug is true + # for debugging - name: Remove the tempfile file: path: "{{ __mssql_sql_tempfile.path }}" state: absent - when: __mssql_sqlcmd_input_file is succeeded + when: + - __mssql_sqlcmd_input_file is succeeded + - not mssql_debug changed_when: false + # This is required because in the case when a task that precedes the input + # task fails, the print task prints a previous result - name: Unset the __mssql_sqlcmd_input_file variable set_fact: __mssql_sqlcmd_input_file: "" + when: not mssql_debug diff --git a/tasks/main.yml b/tasks/main.yml index 0d86e9dd..48efe645 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -41,11 +41,19 @@ - mssql_upgrade | bool - mssql_version | int == 2017 -- name: Verify if the mssql_ha_replica_type variable is provided correctly +- name: Verify that EL < 8 is not used with mssql_ha_configure=true assert: that: - - mssql_ha_replica_type in - ['primary', 'synchronous', 'witness', 'absent'] + - ansible_distribution in ['CentOS', 'RedHat'] + - ansible_distribution_version is version('8', '>') + fail_msg: >- + mssql_ha_configure=true does not support running against EL 7 hosts + when: mssql_ha_configure | bool + +- name: Verify if the mssql_ha_replica_type variable is provided correctly + assert: + that: mssql_ha_replica_type in + ['primary', 'synchronous', 'witness', 'absent'] fail_msg: >- You must set the mssql_ha_replica_type variable to one of 'primary', 'synchronous', 'witness', 'absent' diff --git a/templates/configure_ag.j2 b/templates/configure_ag.j2 index 6252c86a..8eaff1e2 100644 --- a/templates/configure_ag.j2 +++ b/templates/configure_ag.j2 @@ -1,61 +1,23 @@ -IF NOT EXISTS ( +IF EXISTS ( SELECT name, cluster_type_desc FROM sys.availability_groups WHERE name = '{{ mssql_ha_ag_name }}' AND - cluster_type_desc = 'external' + cluster_type_desc != 'external' ) BEGIN - PRINT 'Creating the {{ mssql_ha_ag_name }} availability group'; - CREATE AVAILABILITY GROUP {{ mssql_ha_ag_name }} -{% if ansible_os_family == 'RedHat' and - ansible_distribution_version is version('8.3', '<') %} - WITH (DB_FAILOVER = ON, CLUSTER_TYPE = EXTERNAL) -{% else %} - WITH (DB_FAILOVER = ON, CLUSTER_TYPE = EXTERNAL, WRITE_LEASE_VALIDITY=60) -{% endif %} - FOR REPLICA ON - N'{{ ansible_hostname }}' WITH ( - ENDPOINT_URL = N'tcp://{{ ansible_fqdn }}:{{ mssql_ha_listener_port }}', - AVAILABILITY_MODE = {{ __mssql_ha_availability_mode }}, - FAILOVER_MODE = {{ __mssql_ha_failover_mode }}, - SEEDING_MODE = {{ __mssql_ha_seeding_mode }}, - SECONDARY_ROLE (ALLOW_CONNECTIONS = ALL) -{% for item in ansible_play_hosts %} -{% if hostvars[item]['mssql_ha_replica_type'] == 'synchronous' %} - ), - N'{{ hostvars[item]['ansible_hostname'] }}' WITH ( - ENDPOINT_URL = N'tcp://{{ - hostvars[item]['ansible_fqdn'] }}:{{ mssql_ha_listener_port }}', - AVAILABILITY_MODE = {{ hostvars[item]['__mssql_ha_availability_mode'] }}, - FAILOVER_MODE = {{ hostvars[item]['__mssql_ha_failover_mode'] }}, - SEEDING_MODE = {{ hostvars[item]['__mssql_ha_seeding_mode'] }}, - SECONDARY_ROLE (ALLOW_CONNECTIONS = ALL) -{% elif hostvars[item]['mssql_ha_replica_type'] == 'witness' %} - ), - N'{{ hostvars[item]['ansible_hostname'] }}' WITH ( - ENDPOINT_URL = N'tcp://{{ - hostvars[item]['ansible_fqdn'] }}:{{ mssql_ha_listener_port }}', - AVAILABILITY_MODE = {{ hostvars[item]['__mssql_ha_availability_mode'] }} -{% endif %} -{% endfor %} - ); - PRINT 'The {{ mssql_ha_ag_name }} availability group created successfully'; + PRINT 'The existing {{ mssql_ha_ag_name }} availability group has \ +incorrect cluster type set, dropping the group to re-create it'; + DROP AVAILABILITY GROUP {{ mssql_ha_ag_name }}; + PRINT 'The {{ mssql_ha_ag_name }} availability group dropped successfully'; END -ELSE + +IF EXISTS ( + SELECT name, cluster_type_desc + FROM sys.availability_groups + WHERE name = '{{ mssql_ha_ag_name }}' +) BEGIN PRINT 'Verifying the existing availability group {{ mssql_ha_ag_name }}' - IF EXISTS ( - SELECT name, cluster_type_desc - FROM sys.availability_groups - WHERE name = '{{ mssql_ha_ag_name }}' AND - cluster_type_desc != 'external' - ) - BEGIN - PRINT 'The existing {{ mssql_ha_ag_name }} availability group has \ -incorrect cluster type set, dropping the groupt to re-create it'; - DROP AVAILABILITY GROUP ag1; - PRINT 'The {{ mssql_ha_ag_name }} availability group dropped successfully'; - END IF NOT EXISTS ( SELECT name, db_failover FROM sys.availability_groups @@ -63,7 +25,7 @@ incorrect cluster type set, dropping the groupt to re-create it'; db_failover = 1 ) BEGIN - ALTER AVAILABILITY GROUP ag1 SET (DB_FAILOVER = ON) + ALTER AVAILABILITY GROUP {{ mssql_ha_ag_name }} SET (DB_FAILOVER = ON) PRINT 'Set DB_FAILOVER to ON succesfully' END ELSE @@ -75,9 +37,13 @@ incorrect cluster type set, dropping the groupt to re-create it'; {% if hostvars[item]['mssql_ha_replica_type'] != 'absent' %} IF EXISTS ( SELECT replica_server_name, availability_mode_desc - FROM sys.availability_replicas - WHERE replica_server_name = '{{ hostvars[item]['ansible_hostname'] }}' AND - availability_mode_desc != + FROM sys.availability_replicas replicas + JOIN sys.availability_groups groups + ON replicas.group_id = groups.group_id + WHERE groups.name = '{{ mssql_ha_ag_name }}' AND + replicas.replica_server_name = + '{{ hostvars[item]['ansible_hostname'] }}' AND + replicas.availability_mode_desc != '{{ hostvars[item]['__mssql_ha_availability_mode'] }}' ) BEGIN @@ -90,8 +56,12 @@ removing this replica re-create it'; END IF NOT EXISTS ( SELECT replica_server_name - FROM sys.availability_replicas - WHERE replica_server_name = '{{ hostvars[item]['ansible_hostname'] }}' + FROM sys.availability_replicas replicas + JOIN sys.availability_groups groups + ON replicas.group_id = groups.group_id + WHERE groups.name = '{{ mssql_ha_ag_name }}' AND + replicas.replica_server_name = + '{{ hostvars[item]['ansible_hostname'] }}' ) BEGIN PRINT 'Adding the {{ hostvars[item]['ansible_hostname'] }} \ @@ -117,7 +87,7 @@ removing this replica re-create it'; END ELSE BEGIN - PRINT 'Verifying the existing replica {{ item }}'; + PRINT 'Verifying the existing replica {{ hostvars[item]['ansible_hostname'] }}'; {% if (hostvars[item]['mssql_ha_replica_type'] == 'primary') or (hostvars[item]['mssql_ha_replica_type'] == 'synchronous') %} {% set ag_replica_settings = ({ @@ -151,15 +121,17 @@ removing this replica re-create it'; "setting_value":"N'tcp://" + hostvars[item]['ansible_fqdn'] + ":" + mssql_ha_listener_port | string + "'" } - }) -%} +}) %} {% endif %} {% for key, value in ag_replica_settings.items() %} IF NOT EXISTS ( SELECT replica_server_name, {{ value.sys_setting_name }} - FROM sys.availability_replicas - WHERE replica_server_name = N'{{ hostvars[item]['ansible_hostname'] }}' - AND + FROM sys.availability_replicas replicas + JOIN sys.availability_groups groups + ON replicas.group_id = groups.group_id + WHERE groups.name = '{{ mssql_ha_ag_name }}' AND + replicas.replica_server_name = + '{{ hostvars[item]['ansible_hostname'] }}' AND {% if key == 'endpoint_url' %} {{ value.sys_setting_name }} = {{ value.setting_value }} {% else %} @@ -191,23 +163,72 @@ correctly, skipping'; {% elif hostvars[item]['mssql_ha_replica_type'] == 'absent' %} IF NOT EXISTS ( SELECT replica_server_name - FROM sys.availability_replicas - WHERE replica_server_name = '{{ hostvars[item]['ansible_hostname'] }}' + FROM sys.availability_replicas replicas + JOIN sys.availability_groups groups + ON replicas.group_id = groups.group_id + WHERE groups.name = '{{ mssql_ha_ag_name }}' AND + replicas.replica_server_name = + '{{ hostvars[item]['ansible_hostname'] }}' ) BEGIN - PRINT '{{ item }}: this replica is already removed, skipping'; + PRINT '{{ hostvars[item]['ansible_hostname'] }}: this replica is already \ +removed, skipping'; END ELSE BEGIN - PRINT '{{ item }}: Removing this replica'; + PRINT '{{ hostvars[item]['ansible_hostname'] }}: Removing this replica'; ALTER AVAILABILITY GROUP {{ mssql_ha_ag_name }} REMOVE REPLICA ON N'{{ hostvars[item]['ansible_hostname'] }}'; - PRINT '{{ item }}: This replica is removed successfully'; + PRINT '{{ hostvars[item]['ansible_hostname'] }}: This replica is removed \ +successfully'; END {% endif %} {% endfor %} END +IF NOT EXISTS ( + SELECT name, cluster_type_desc + FROM sys.availability_groups + WHERE name = '{{ mssql_ha_ag_name }}' +) +BEGIN + PRINT 'Creating the {{ mssql_ha_ag_name }} availability group'; + CREATE AVAILABILITY GROUP {{ mssql_ha_ag_name }} +{% if ansible_os_family == 'RedHat' and + ansible_distribution_version is version('8.3', '<') %} + WITH (DB_FAILOVER = ON, CLUSTER_TYPE = EXTERNAL) +{% else %} + WITH (DB_FAILOVER = ON, CLUSTER_TYPE = EXTERNAL) +{% endif %} + FOR REPLICA ON + N'{{ ansible_hostname }}' WITH ( + ENDPOINT_URL = N'tcp://{{ ansible_fqdn }}:{{ mssql_ha_listener_port }}', + AVAILABILITY_MODE = {{ __mssql_ha_availability_mode }}, + FAILOVER_MODE = {{ __mssql_ha_failover_mode }}, + SEEDING_MODE = {{ __mssql_ha_seeding_mode }}, + SECONDARY_ROLE (ALLOW_CONNECTIONS = ALL) +{% for item in ansible_play_hosts %} +{% if hostvars[item]['mssql_ha_replica_type'] == 'synchronous' %} + ), + N'{{ hostvars[item]['ansible_hostname'] }}' WITH ( + ENDPOINT_URL = N'tcp://{{ + hostvars[item]['ansible_fqdn'] }}:{{ mssql_ha_listener_port }}', + AVAILABILITY_MODE = {{ hostvars[item]['__mssql_ha_availability_mode'] }}, + FAILOVER_MODE = {{ hostvars[item]['__mssql_ha_failover_mode'] }}, + SEEDING_MODE = {{ hostvars[item]['__mssql_ha_seeding_mode'] }}, + SECONDARY_ROLE (ALLOW_CONNECTIONS = ALL) +{% elif hostvars[item]['mssql_ha_replica_type'] == 'witness' %} + ), + N'{{ hostvars[item]['ansible_hostname'] }}' WITH ( + ENDPOINT_URL = N'tcp://{{ + hostvars[item]['ansible_fqdn'] }}:{{ mssql_ha_listener_port }}', + AVAILABILITY_MODE = {{ hostvars[item]['__mssql_ha_availability_mode'] }} +{% endif %} +{% endfor %} + ); + PRINT 'The {{ mssql_ha_ag_name }} availability group created successfully'; +END + -- It is not possible to grant permissions fully idempotently ALTER AVAILABILITY GROUP {{ mssql_ha_ag_name }} GRANT CREATE ANY DATABASE; PRINT 'Granted the CREATE ANY DATABASE permission to the \ diff --git a/templates/configure_endpoint.j2 b/templates/configure_endpoint.j2 index 7e82ebd9..a21a6efa 100644 --- a/templates/configure_endpoint.j2 +++ b/templates/configure_endpoint.j2 @@ -66,12 +66,12 @@ endpoint is already set to {{ __mssql_ha_endpoint_role }}, skipping'; AUTHENTICATION = CERTIFICATE {{ mssql_ha_cert_name }} ); PRINT 'The certificate for the {{ mssql_ha_endpoint_name }} \ -endpoint updated successfully'; +endpoint updated to {{ mssql_ha_cert_name }} successfully'; END ELSE BEGIN PRINT 'The certificate for the {{ mssql_ha_endpoint_name }} \ -endpoint is already correct, skipping'; +endpoint is already set to {{ mssql_ha_cert_name }}, skipping'; END IF NOT EXISTS ( SELECT name, encryption_algorithm_desc @@ -83,12 +83,12 @@ endpoint is already correct, skipping'; ALTER ENDPOINT {{ mssql_ha_endpoint_name }} FOR DATABASE_MIRRORING (ENCRYPTION = REQUIRED ALGORITHM AES); PRINT 'The ENCRYPTION setting for the {{ mssql_ha_endpoint_name }} \ -endpoint updated successfully'; +endpoint updated to AES successfully'; END ELSE BEGIN PRINT 'The ENCRYPTION setting for the {{ mssql_ha_endpoint_name }} \ -endpoint is already correct, skipping'; +endpoint is already set to AES, skipping'; END IF NOT EXISTS ( SELECT name, state diff --git a/templates/create_ha_login.j2 b/templates/create_ha_login.j2 index b7ad00a7..c455af65 100644 --- a/templates/create_ha_login.j2 +++ b/templates/create_ha_login.j2 @@ -16,7 +16,7 @@ END IF IS_SRVROLEMEMBER ('sysadmin','{{ mssql_ha_login }}') = 1 BEGIN - PRINT '{{ mssql_ha_login }} is a mameber of sysadmin role, skipping'; + PRINT '{{ mssql_ha_login }} is a member of sysadmin role, skipping'; END ELSE BEGIN diff --git a/tests/clean_up_mssql_pacemaker.yml b/tests/clean_up_mssql_pacemaker.yml new file mode 100644 index 00000000..28d2f2dc --- /dev/null +++ b/tests/clean_up_mssql_pacemaker.yml @@ -0,0 +1,27 @@ +--- +- name: Purge cluster and mssql configuration + hosts: all + tasks: + - name: Purge cluster configuration + vars: + ha_cluster_cluster_present: no + ha_cluster_enable_repos: false + include_role: + name: fedora.linux_system_roles.ha_cluster + + - name: Remove cluster RPMs + package: + name: + - pacemaker + - pcs + - fence-agents-all + - resource-agents + state: absent + + - name: Remove mssql RPMs + shell: dnf remove mssql-* -y + changed_when: true + + - name: Remove related files + shell: rm -rf /var/opt/mssql/ /opt/mssql* /tmp/ansible.* + changed_when: true diff --git a/tests/templates/alter_ag.j2 b/tests/templates/alter_ag.j2 new file mode 100644 index 00000000..a618944a --- /dev/null +++ b/tests/templates/alter_ag.j2 @@ -0,0 +1,103 @@ +IF EXISTS ( + SELECT name, cluster_type_desc + FROM sys.availability_groups + WHERE name = '{{ mssql_ha_ag_name }}' +) +BEGIN + PRINT 'Altering the existing availability group {{ mssql_ha_ag_name }}' + IF NOT EXISTS ( + SELECT name, db_failover + FROM sys.availability_groups + WHERE name = '{{ mssql_ha_ag_name }}' AND + db_failover = 0 + ) + BEGIN + ALTER AVAILABILITY GROUP {{ mssql_ha_ag_name }} SET (DB_FAILOVER = ON) + PRINT 'Set DB_FAILOVER to ON succesfully' + END + ELSE + BEGIN + PRINT 'DB_FAILOVER = OFF is already set, skipping' + END + PRINT 'Altering replicas' +{% for item in ansible_play_hosts %} +{% if hostvars[item]['mssql_ha_replica_type'] != 'absent' %} + IF EXISTS ( + SELECT replica_server_name + FROM sys.availability_replicas + WHERE replica_server_name = '{{ hostvars[item]['ansible_hostname'] }}' + ) + BEGIN + PRINT 'Verifying the existing replica {{ item }}'; +{% if (hostvars[item]['mssql_ha_replica_type'] == 'primary') or + (hostvars[item]['mssql_ha_replica_type'] == 'synchronous') %} +{% set ag_replica_settings = ({ +"endpoint_url":{ + "sql_setting_name":"ENDPOINT_URL", + "sys_setting_name":"endpoint_url", + "setting_value":"N'tcp://" + hostvars[item]['ansible_fqdn'] + ":" + + "1234" + "'" +}, +"failover_mode":{ + "sql_setting_name":"FAILOVER_MODE", + "sys_setting_name":"failover_mode_desc", + "setting_value":"EXTERNAL" +}, +"seeding_mode":{ + "sql_setting_name":"SEEDING_MODE", + "sys_setting_name":"seeding_mode_desc", + "setting_value":"MANUAL" +}, +"allow_connections":{ + "sql_setting_name": "SECONDARY_ROLE (ALLOW_CONNECTIONS = NO)", + "sys_setting_name": "secondary_role_allow_connections_desc", + "setting_value": "NO" +} +}) %} +{% elif hostvars[item]['mssql_ha_replica_type'] == 'witness' %} +{% set ag_replica_settings = ({ +"endpoint_url":{ + "sql_setting_name":"ENDPOINT_URL", + "sys_setting_name":"endpoint_url", + "setting_value":"N'tcp://" + hostvars[item]['ansible_fqdn'] + ":" + + "1234" + "'" +} +}) %} +{% endif %} +{% for key, value in ag_replica_settings.items() %} + IF NOT EXISTS ( + SELECT replica_server_name, {{ value.sys_setting_name }} + FROM sys.availability_replicas + WHERE replica_server_name = N'{{ hostvars[item]['ansible_hostname'] }}' + AND +{% if key == 'endpoint_url' %} + {{ value.sys_setting_name }} = {{ value.setting_value }} +{% else %} + {{ value.sys_setting_name }} = '{{ value.setting_value }}' +{% endif %} + ) + BEGIN + ALTER AVAILABILITY GROUP {{ mssql_ha_ag_name }} MODIFY REPLICA ON + N'{{ hostvars[item]['ansible_hostname'] }}' WITH ( +{% if key == 'allow_connections' %} + {{ value.sql_setting_name }} +{% else %} + {{ value.sql_setting_name }} = {{ value.setting_value }} +{% endif %} + ); + PRINT '{{ hostvars[item]['ansible_hostname'] }}: \ +The {{ value.sql_setting_name }} setting on this \ +{{ hostvars[item]['mssql_ha_replica_type'] }} replica configured successfully'; + END + ELSE + BEGIN + PRINT '{{ hostvars[item]['ansible_hostname'] }}: \ +The {{ value.sql_setting_name }} setting on this \ +{{ hostvars[item]['mssql_ha_replica_type'] }} replica is already set \ +correctly, skipping'; + END +{% endfor %} + END +{% endif %} +{% endfor %} +END diff --git a/tests/templates/alter_endpoint.j2 b/tests/templates/alter_endpoint.j2 new file mode 100644 index 00000000..71d4a2ba --- /dev/null +++ b/tests/templates/alter_endpoint.j2 @@ -0,0 +1,24 @@ +IF NOT EXISTS( + SELECT name + FROM sys.certificates + WHERE name = 'test_cert' +) +BEGIN + PRINT 'Certificate test_cert does not exist, creating'; + CREATE CERTIFICATE test_cert + WITH SUBJECT = 'Test certificate'; + PRINT 'Certificate test_cert created successfully'; +END +ELSE +BEGIN + PRINT 'Certificate test_cert already exists, skipping'; +END + +ALTER ENDPOINT {{ mssql_ha_endpoint_name }} + STATE = STOPPED + AS TCP (LISTENER_PORT = 1234) + FOR DATABASE_MIRRORING ( + ROLE = PARTNER, + AUTHENTICATION = CERTIFICATE test_cert, + ENCRYPTION = REQUIRED ALGORITHM RC4 + ); diff --git a/tests/templates/configure_ag_cluster_type_none.j2 b/tests/templates/configure_ag_cluster_type_none.j2 new file mode 100644 index 00000000..2bd3533b --- /dev/null +++ b/tests/templates/configure_ag_cluster_type_none.j2 @@ -0,0 +1,10 @@ +CREATE AVAILABILITY GROUP {{ mssql_ha_ag_name }} + WITH (CLUSTER_TYPE = NONE) + FOR REPLICA ON + N'{{ ansible_hostname }}' WITH ( + ENDPOINT_URL = N'tcp://{{ ansible_fqdn }}:{{ mssql_ha_listener_port }}', + AVAILABILITY_MODE = SYNCHRONOUS_COMMIT, + FAILOVER_MODE = MANUAL, + SEEDING_MODE = AUTOMATIC, + SECONDARY_ROLE (ALLOW_CONNECTIONS = ALL) + ); diff --git a/tests/templates/disable_alwayson.j2 b/tests/templates/disable_alwayson.j2 new file mode 100644 index 00000000..ce0db8f5 --- /dev/null +++ b/tests/templates/disable_alwayson.j2 @@ -0,0 +1 @@ +ALTER EVENT SESSION AlwaysOn_health ON SERVER WITH (STARTUP_STATE=OFF); \ No newline at end of file diff --git a/tests/templates/drop_ag.j2 b/tests/templates/drop_ag.j2 new file mode 100644 index 00000000..d6194eed --- /dev/null +++ b/tests/templates/drop_ag.j2 @@ -0,0 +1,13 @@ +IF EXISTS ( + SELECT name + FROM sys.availability_groups + WHERE name = '{{ mssql_ha_ag_name }}' +) +BEGIN + DROP AVAILABILITY GROUP ag1; + PRINT 'The {{ mssql_ha_ag_name }} availability group dropped successfully'; +END +ELSE +BEGIN + PRINT 'The {{ mssql_ha_ag_name }} ag does not exist, skipping'; +END \ No newline at end of file diff --git a/tests/templates/drop_endpoint.j2 b/tests/templates/drop_endpoint.j2 new file mode 100644 index 00000000..a9485780 --- /dev/null +++ b/tests/templates/drop_endpoint.j2 @@ -0,0 +1,14 @@ +PRINT 'Dropping the {{ mssql_ha_endpoint_name }} endpoint' +IF NOT EXISTS ( + SELECT name + FROM sys.database_mirroring_endpoints + WHERE name = '{{ mssql_ha_endpoint_name }}' +) +BEGIN + PRINT 'Endpoint {{ mssql_ha_endpoint_name }} does not exist, skipping' +END +ELSE +BEGIN + DROP ENDPOINT {{ mssql_ha_endpoint_name }}; + PRINT 'Endpoint {{ mssql_ha_endpoint_name }} dropped successfully' +END \ No newline at end of file diff --git a/tests/templates/remove_sql_cert.j2 b/tests/templates/remove_sql_cert.j2 new file mode 100644 index 00000000..e30bef13 --- /dev/null +++ b/tests/templates/remove_sql_cert.j2 @@ -0,0 +1 @@ +DROP CERTIFICATE {{ mssql_ha_cert_name }}; \ No newline at end of file diff --git a/tests/templates/separate_from_ag.j2 b/tests/templates/separate_from_ag.j2 new file mode 100644 index 00000000..48f7223b --- /dev/null +++ b/tests/templates/separate_from_ag.j2 @@ -0,0 +1,2 @@ +DROP DATABASE {{ __mssql_test_database }} +DROP AVAILABILITY GROUP {{ mssql_ha_ag_name }} \ No newline at end of file diff --git a/tests/tests_configure_ha_cluster.yml b/tests/tests_configure_ha_cluster.yml index ffd301ed..f8043482 100644 --- a/tests/tests_configure_ha_cluster.yml +++ b/tests/tests_configure_ha_cluster.yml @@ -12,11 +12,24 @@ __mssql_test_db_name: "{{ mssql_ha_db_name }}" mssql_debug: true tasks: - - name: Skip this test on EL < 8 because ha_cluster does not support it - meta: end_play + - name: Verify that by default the role fails on EL < 8 when: - - ansible_distribution_version is version('8', '<') - ansible_distribution in ['CentOS', 'RedHat'] + - ansible_distribution_version is version('8', '<') + block: + - name: Run the role + include_role: + name: linux-system-roles.mssql + + - name: Unreachable task + fail: + msg: The above task must fail + rescue: + - name: Assert that the role failed with EL < 8 not supported + assert: + that: >- + 'mssql_ha_configure=true does not support running against EL 7 + hosts' in ansible_failed_result.msg.0 - name: Set the mssql_ha_replica_type fact to appear in hostvars set_fact: @@ -55,3 +68,567 @@ mssql_ha_virtual_ip: 192.168.122.10 include_role: name: linux-system-roles.mssql + +# This play tests if templates configure systems correctly +# Because CI runs on a single node, the following templates cannot be tested: +# remove_from_ag.j2, +# join_to_ag.j2, +# removing and adding replicas in configure_ag.j2 +- name: Test templates + hosts: all + vars: + mssql_accept_microsoft_odbc_driver_17_for_sql_server_eula: true + mssql_accept_microsoft_cli_utilities_for_sql_server_eula: true + mssql_accept_microsoft_sql_server_standard_eula: true + mssql_password: "p@55w0rD" + mssql_edition: Evaluation + mssql_ha_db_name: ExampleDB + __mssql_test_db_name: "{{ mssql_ha_db_name }}" + mssql_debug: true + mssql_ha_configure: true + mssql_ha_firewall_configure: true + mssql_ha_listener_port: 5022 + mssql_ha_cert_name: mssql_cert + mssql_ha_master_key_password: "p@55w0rD1" + mssql_ha_private_key_password: "p@55w0rD2" + mssql_ha_reset_cert: false + mssql_ha_endpoint_name: hadr_endpoint + mssql_ha_ag_name: ag1 + mssql_ha_db_backup_path: /var/opt/mssql/data/{{ mssql_ha_db_name }}.bak + mssql_ha_login: pacemakerLogin + mssql_ha_login_password: "p@55w0rD3" + mssql_ha_hacluster_password: "p@55w0rD4" + mssql_ha_virtual_ip: 192.168.122.10 + mssql_ha_sbd_enabled: true + tasks: + - name: Ensure ansible_facts and variables used by role + include_vars: ../vars/main.yml + + # enable_alwayson.j2 template test + - name: Enable AlwaysOn event session when it's enabled + vars: + __mssql_input_sql_file: enable_alwayson.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Assert that enabling is skipped + assert: + that: >- + "AlwaysOn Health events already enabled, skipping" + in __mssql_sqlcmd_input_file.stdout + + - name: Disable AlwaysOn event session + vars: + __mssql_input_sql_file: disable_alwayson.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Enable AlwaysOn event session + vars: + __mssql_input_sql_file: enable_alwayson.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Assert that the template reported changed state + assert: + that: >- + "AlwaysOn Health events enabled successfully" + in __mssql_sqlcmd_input_file.stdout + + # configure_endpoint.j2 template test + - name: Create endpoint when it exists + vars: + __mssql_input_sql_file: configure_endpoint.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Assert expected messages + assert: + that: + - >- + "Verifying the existing endpoint {{ mssql_ha_endpoint_name }}" + in __mssql_sqlcmd_input_file.stdout + - >- + "The LISTENER_PORT setting for the {{ mssql_ha_endpoint_name }} + endpoint is already set to {{ mssql_ha_listener_port }}, skipping" + in __mssql_sqlcmd_input_file.stdout + - >- + "The ROLE setting for the {{ mssql_ha_endpoint_name }} endpoint is + already set to {{ __mssql_ha_endpoint_role }}, skipping" + in __mssql_sqlcmd_input_file.stdout + - >- + "The certificate for the {{ mssql_ha_endpoint_name }} + endpoint is already set to {{ mssql_ha_cert_name }}, skipping" + in __mssql_sqlcmd_input_file.stdout + - >- + "The ENCRYPTION setting for the {{ mssql_ha_endpoint_name }} + endpoint is already set to AES, skipping" + in __mssql_sqlcmd_input_file.stdout + - >- + "Endpoint {{ mssql_ha_endpoint_name }} is already started, skipping" + in __mssql_sqlcmd_input_file.stdout + + - name: Alter endpoint to test how template handles incorrect endpoint + vars: + __mssql_input_sql_file: alter_endpoint.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Configure endpoint correctly + vars: + __mssql_input_sql_file: configure_endpoint.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Assert expected messages + assert: + that: + - >- + "Verifying the existing endpoint {{ mssql_ha_endpoint_name }}" + in __mssql_sqlcmd_input_file.stdout + - >- + "The LISTENER_PORT setting for the {{ mssql_ha_endpoint_name }} + endpoint updated to {{ mssql_ha_listener_port }} successfully" + in __mssql_sqlcmd_input_file.stdout + - >- + "The ROLE setting for the {{ mssql_ha_endpoint_name }} + endpoint updated to {{ __mssql_ha_endpoint_role }} successfully" + in __mssql_sqlcmd_input_file.stdout + - >- + "The certificate for the {{ mssql_ha_endpoint_name }} + endpoint updated to {{ mssql_ha_cert_name }} successfully" + in __mssql_sqlcmd_input_file.stdout + - >- + "The ENCRYPTION setting for the {{ mssql_ha_endpoint_name }} + endpoint updated to AES successfully" + in __mssql_sqlcmd_input_file.stdout + - >- + "Endpoint {{ mssql_ha_endpoint_name }} started successfully" + in __mssql_sqlcmd_input_file.stdout + + - name: Drop the test_cert certificate + vars: + __mssql_input_sql_file: drop_cert.j2 + mssql_ha_cert_name: test_cert + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + # create_master_key_encryption.j2 template test + - name: Create a master key when it exists + vars: + __mssql_input_sql_file: create_master_key_encryption.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Assert that creating is skipped + assert: + that: >- + "The provided master key password is correct" + in __mssql_sqlcmd_input_file.stdout + + - name: Verify creating master key with incorrect password + block: + - name: Try creating master key with incorrect password + vars: + __mssql_input_sql_file: create_master_key_encryption.j2 + mssql_ha_master_key_password: "p@55w0rD11" + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: This task shouldn't execute + fail: + rescue: + - name: Assert the incorrect password error message + assert: + that: >- + "You provided an incorrect master key password" + in __mssql_sqlcmd_input_file.stdout + + - name: Drop endpoint + vars: + __mssql_input_sql_file: create_master_key_encryption.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Remove certificate from SQL Server to verify re-creating master key + vars: + __mssql_input_sql_file: drop_cert.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Remove certificate and private key + file: + path: "{{ item }}" + state: absent + loop: + - "{{ __mssql_ha_cert_dest }}" + - "{{ __mssql_ha_private_key_dest }}" + + - name: >- + Verify creating master key with incorrect password and + mssql_ha_reset_cert set to true + vars: + __mssql_input_sql_file: create_master_key_encryption.j2 + mssql_ha_master_key_password: "p@55w0rD11" + mssql_ha_reset_cert: true + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Assert the expected error message + assert: + that: + - >- + "dropping master key to re-create it" + in __mssql_sqlcmd_input_file.stdout + - >- + "Master key dropped successfully" + in __mssql_sqlcmd_input_file.stdout + - >- + "Master key created successfully" + in __mssql_sqlcmd_input_file.stdout + + # # restore_cert.j2 template test + # - name: Create a certificate when it exists on secondary + # when: mssql_ha_replica_type in ['synchronous', 'witness'] + # block: + # - name: Input restore_cert.j2 + # vars: + # __mssql_input_sql_file: restore_cert.j2 + # include_role: + # name: linux-system-roles.mssql + # tasks_from: input_sql_file.yml + + # - name: Assert the already exists message + # assert: + # that: >- + # "Certificate {{ mssql_ha_cert_name }} already exists, skipping" + # in __mssql_sqlcmd_input_file.stdout + + # create_and_back_up_cert.j2 template test + - name: Create and back up an existing and backed up certificate + when: mssql_ha_replica_type == 'primary' + block: + - name: Input create_and_back_up_cert.j2 to create cert + vars: + __mssql_input_sql_file: create_and_back_up_cert.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Assert created successfully messages + assert: + that: + - >- + "Certificate {{ mssql_ha_cert_name }} created successfully" + in __mssql_sqlcmd_input_file.stdout + - >- + "{{ __mssql_ha_cert_dest }} and + {{ __mssql_ha_private_key_dest }} exported successfully" + in __mssql_sqlcmd_input_file.stdout + + - name: Input create_and_back_up_cert.j2 when cert exists + vars: + __mssql_input_sql_file: create_and_back_up_cert.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Assert task skipping messages + assert: + that: + - >- + "Certificate {{ mssql_ha_cert_name }} already exists, skipping" + in __mssql_sqlcmd_input_file.stdout + - >- + "{{ __mssql_ha_cert_dest }} and + {{ __mssql_ha_private_key_dest }} already exist, skipping" + in __mssql_sqlcmd_input_file.stdout + + - name: Back up certificate and private key + copy: + remote_src: true + src: "{{ item }}" + dest: /tmp/{{ item | basename }} + loop: + - "{{ __mssql_ha_cert_dest }}" + - "{{ __mssql_ha_private_key_dest }}" + + - name: Remove private key + file: + path: "{{ __mssql_ha_private_key_dest }}" + state: absent + + - name: Input create_and_back_up_cert.j2 + vars: + __mssql_input_sql_file: create_and_back_up_cert.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Assert expected error messages + assert: + that: + - >- + "Certificate {{ mssql_ha_cert_name }} already exists, skipping" + in __mssql_sqlcmd_input_file.stdout + - >- + "{{ __mssql_ha_private_key_dest }} does not exist while + {{ __mssql_ha_cert_dest }} exists." + in __mssql_sqlcmd_input_file.stdout + + - name: Remove certificate + file: + path: "{{ __mssql_ha_cert_dest }}" + state: absent + + - name: Return private key + copy: + remote_src: true + src: /tmp/{{ __mssql_ha_private_key_dest | basename }} + dest: "{{ __mssql_ha_private_key_dest }}" + + - name: Input create_and_back_up_cert.j2 + vars: + __mssql_input_sql_file: create_and_back_up_cert.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Assert expected error messages + assert: + that: + - >- + "Certificate {{ mssql_ha_cert_name }} already exists, skipping" + in __mssql_sqlcmd_input_file.stdout + - >- + "{{ __mssql_ha_cert_dest }} does not exist while + {{ __mssql_ha_private_key_dest }} exists." + in __mssql_sqlcmd_input_file.stdout + + - name: Return certificate + copy: + remote_src: true + src: /tmp/{{ __mssql_ha_cert_dest | basename }} + dest: "{{ __mssql_ha_cert_dest }}" + + - name: Drop certificate from SQL Server + vars: + __mssql_input_sql_file: drop_cert.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Create cert when cert files exist and SQL cert does not + block: + - name: Input create_and_back_up_cert.j2 + vars: + __mssql_input_sql_file: create_and_back_up_cert.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Unreachable task + fail: + msg: The above task must fail + rescue: + - name: Assert expected error messages + assert: + that: + - >- + "Certificate {{ mssql_ha_cert_name }} does not exist in SQL + Server, however, {{ __mssql_ha_cert_dest }} and/or + {{ __mssql_ha_private_key_dest }} files do exist." + in __mssql_sqlcmd_input_file.stdout + + - name: Remove certificate and private key + file: + path: "{{ item }}" + state: absent + loop: + - "{{ __mssql_ha_cert_dest }}" + - "{{ __mssql_ha_private_key_dest }}" + + - name: Ensure that certificates exist + vars: + __mssql_input_sql_file: create_and_back_up_cert.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Assert expected messages + assert: + that: >- + "Certificate {{ mssql_ha_cert_name }} created successfully" + in __mssql_sqlcmd_input_file.stdout + + # create_ha_login.j2 template test + - name: Create a {{ mssql_ha_login }} login when it already exists + vars: + __mssql_input_sql_file: create_ha_login.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Assert task skipping messages + assert: + that: + - >- + "A {{ mssql_ha_login }} login already exists, skipping" + in __mssql_sqlcmd_input_file.stdout + - >- + "{{ mssql_ha_login }} is a member of sysadmin role, skipping" + in __mssql_sqlcmd_input_file.stdout + + # replicate_db.j2 template test + - name: Replicate database what it is already replicated + when: mssql_ha_replica_type == 'primary' + block: + - name: Input replicate_db.j2 + vars: + __mssql_input_sql_file: replicate_db.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Assert task skipping messages + assert: + that: + - >- + "RECOVERY FULL on the {{ mssql_ha_db_name }} database is set, + skipping" in __mssql_sqlcmd_input_file.stdout + - >- + "The {{ mssql_ha_db_name }} database is already backed up, + skipping" in __mssql_sqlcmd_input_file.stdout + - >- + "database is already added to the {{ mssql_ha_ag_name }} + availability group, skipping" + in __mssql_sqlcmd_input_file.stdout + + # configure_ag.yml template test + - name: Ensure endpoint exists + vars: + __mssql_input_sql_file: configure_endpoint.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Test configure_ag.yml on primary + when: mssql_ha_replica_type == 'primary' + block: + - name: Input configure_ag.yml when AG exists + vars: + __mssql_input_sql_file: configure_ag.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Assert task skipping messages + assert: + that: + - >- + "DB_FAILOVER = ON is already set, skipping" + in __mssql_sqlcmd_input_file.stdout + - >- + "The ENDPOINT_URL setting on this {{ mssql_ha_replica_type }} + replica is already set correctly, skipping" + in __mssql_sqlcmd_input_file.stdout + - >- + "The FAILOVER_MODE setting on this {{ mssql_ha_replica_type }} + replica is already set correctly, skipping" + in __mssql_sqlcmd_input_file.stdout + - >- + "The SEEDING_MODE setting on this {{ mssql_ha_replica_type }} + replica is already set correctly, skipping" + in __mssql_sqlcmd_input_file.stdout + - >- + "The SECONDARY_ROLE (ALLOW_CONNECTIONS = ALL) setting on this + {{ mssql_ha_replica_type }} replica is already set correctly" + in __mssql_sqlcmd_input_file.stdout + + # Using mssql_ha_ag_name: ag2 because running tasks against ag1 fails + # with "Replica not Primary" + - name: Verify configuration of a new ag2 + vars: + __mssql_input_sql_file: configure_ag_cluster_type_none.j2 + mssql_ha_ag_name: ag2 + block: + - name: Configure AG with CLUSTER_TYPE = NONE + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Input configure_ag.yml with CLUSTER_TYPE = NONE + vars: + __mssql_input_sql_file: configure_ag.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Assert task skipping messages + assert: + that: + - >- + "The existing {{ mssql_ha_ag_name }} availability group has + incorrect cluster type set, dropping the group to re-create + it" + in __mssql_sqlcmd_input_file.stdout + - >- + "The {{ mssql_ha_ag_name }} availability group dropped + successfully" + in __mssql_sqlcmd_input_file.stdout + - >- + "The {{ mssql_ha_ag_name }} availability group created + successfully" + in __mssql_sqlcmd_input_file.stdout + + - name: Alter AG + vars: + __mssql_input_sql_file: alter_ag.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Input configure_ag.yml to configure ag properly + vars: + __mssql_input_sql_file: configure_ag.j2 + include_role: + name: linux-system-roles.mssql + tasks_from: input_sql_file.yml + + - name: Assert task skipping messages + assert: + that: + - >- + "Verifying the existing availability group + {{ mssql_ha_ag_name }}" + in __mssql_sqlcmd_input_file.stdout + - >- + "The ENDPOINT_URL setting on this + {{ mssql_ha_replica_type }} replica configured successfully" + in __mssql_sqlcmd_input_file.stdout + - >- + "The FAILOVER_MODE setting on this + {{ mssql_ha_replica_type }} replica is already set + correctly, skipping" + in __mssql_sqlcmd_input_file.stdout + - >- + "The SEEDING_MODE setting on this + {{ mssql_ha_replica_type }} replica configured successfully" + in __mssql_sqlcmd_input_file.stdout + - >- + "The SECONDARY_ROLE (ALLOW_CONNECTIONS = ALL) setting on + this {{ mssql_ha_replica_type }} replica configured + successfully" + in __mssql_sqlcmd_input_file.stdout