Skip to content

Commit 9b3a8c5

Browse files
zhuatuzilospetrosi
authored andcommitted
feat: Add mssql_ha_ag_is_contained and mssql_ha_ag_reuse_system_db
* Update configure_ag.j2 to drop AG in the case is_contained or reuse_system_db is configured incorrecty on the existing AG * Instead of checking if primary node is available, check if none of the nodes failed * Use ansible_play_hosts_all in configure_ag.j2 * Restore databases if in restoring state * Only verify is_contained. There is no way to verify is_reuse_system_db * Add cleanups for AG and DBs * Split multi-host test and a single-node test * Split tests_ha_single into versions * Reset inventory_hostname after ha_cluster setup * Verify if mssql_version is not bigger then the existing SQL Server version
1 parent a641fc8 commit 9b3a8c5

20 files changed

+1601
-941
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,28 @@ Default: `[]`
864864

865865
Type: `list`
866866

867+
#### mssql_ha_ag_is_contained
868+
869+
Whether to configure the availability group provided with [mssql_ha_ag_name](#mssql_ha_ag_name) as contained.
870+
871+
This is supported since SQL Server version 2022.
872+
873+
Default: `false`
874+
875+
Type: `bool`
876+
877+
#### mssql_ha_ag_reuse_system_db
878+
879+
Only applicable when [mssql_ha_ag_is_contained](#mssql_ha_ag_is_contained) is true.
880+
881+
Whether to configure the availability group provided with [mssql_ha_ag_name](#mssql_ha_ag_name) with the reuse_system_db setting.
882+
883+
This is supported since SQL Server version 2022.
884+
885+
Default: `false`
886+
887+
Type: `bool`
888+
867889
### Configuring Pacemaker Variables
868890

869891
With these variables, you can specify whether to configure Pacemaker HA solution and its configuration.

defaults/main.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ mssql_ha_reset_cert: false
5252
mssql_ha_endpoint_name: null
5353
mssql_ha_ag_name: null
5454
mssql_ha_db_names: []
55+
mssql_ha_ag_is_contained: false
56+
# if mssql_ha_ag_is_contained=false, mssql_ha_ag_reuse_system_db will be false
57+
mssql_ha_ag_reuse_system_db: false
5558

5659
mssql_ha_prep_for_pacemaker: "{{ mssql_ha_ag_cluster_type | lower != 'none' }}"
5760
mssql_ha_virtual_ip: null

tasks/main.yml

Lines changed: 82 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55

66
# This is required to prevent the role from using a __mssql_sqlcmd_login_cmd
77
# variable value from a previous role invocation
8-
- name: Unset the __mssql_sqlcmd_login_cmd fact
8+
- name: Unset internal role facts
99
set_fact:
1010
__mssql_sqlcmd_login_cmd: null
11+
__mssql_is_active: null
1112

1213
- name: Link the deprecated mssql_input_sql_file fact
1314
when: mssql_input_sql_file is defined
@@ -165,6 +166,28 @@
165166
mssql_run_selinux_confined is set to true. You must either set both
166167
variables to true or to false.
167168
169+
- name: Verify that mssql_ha_ag_is_contained is not provided with version 2017
170+
when:
171+
- mssql_ha_ag_is_contained | bool
172+
- mssql_version is version('2022', '<')
173+
fail:
174+
msg: >-
175+
mssql_ha_ag_is_contained is not supported on SQL Server < 2022.
176+
SQL Server 2019 and greater supports mssql_ha_ag_is_contained.
177+
You must either upgrade to a greater version or set
178+
mssql_ha_ag_is_contained to false.
179+
180+
- name: Verify fail reuse_system_db=true is not provided without ag_is_contained
181+
when:
182+
- not mssql_ha_ag_is_contained
183+
- mssql_ha_ag_reuse_system_db | bool
184+
fail:
185+
msg: >-
186+
mssql_ha_ag_reuse_system_db is not appilcable when
187+
mssql_ha_ag_is_contained = false. You must either set
188+
mssql_ha_ag_reuse_system_db to false or set mssql_ha_ag_is_contained
189+
to true.
190+
168191
- name: Gather package facts
169192
package_facts:
170193
no_log: "{{ __mssql_gather_facts_no_log | d(false) }}"
@@ -178,6 +201,19 @@
178201
| first | int == item.value
179202
with_dict: "{{ __mssql_version_package_mapping }}"
180203

204+
- name: >-
205+
Verify if mssql_version is not bigger then the existing SQL Server version
206+
fail:
207+
msg: >-
208+
You set mssql_version to {{ mssql_version }}, but your SQL Server is
209+
version {{ __mssql_current_version }}. You must either set mssql_upgrade
210+
to true to upgrade or not set the mssql_version variable"
211+
when:
212+
- not mssql_upgrade
213+
- mssql_version is not none
214+
- __mssql_current_version is defined
215+
- mssql_version | int > __mssql_current_version | int
216+
181217
- name: Set mssql_version variable if user does not define it
182218
set_fact:
183219
mssql_version: "{{ __mssql_current_version }}"
@@ -1121,21 +1157,30 @@
11211157
- "{{ ansible_failed_result.stdout_lines }}"
11221158

11231159
- name: Configure for high availability
1124-
any_errors_fatal: true
11251160
when: mssql_ha_configure | bool
11261161
block:
1127-
- name: Verify that hosts with mssql_ha_replica_type=primary is available
1128-
assert:
1129-
that: ansible_play_hosts |
1130-
map('extract', hostvars, 'mssql_ha_replica_type') |
1131-
select('match', '^primary$') |
1132-
list |
1133-
length == 1
1134-
fail_msg:
1135-
- Host with mssql_ha_replica_type=primary failed earlier in the play.
1136-
- Therefore, it is not possible to configure for high availability.
1137-
- Fix the above error on the primary replica and re-run the role.
1162+
- name: Mark active hosts
1163+
set_fact:
1164+
__mssql_is_active: true
1165+
1166+
- name: Fail if any hosts are no longer active
11381167
run_once: true
1168+
vars:
1169+
hostvars_list: "{{ ansible_play_hosts_all | map('extract', hostvars) | list }}"
1170+
active_hosts: >-
1171+
{{ hostvars_list
1172+
| selectattr('__mssql_is_active', 'defined')
1173+
| selectattr('__mssql_is_active')
1174+
| map(attribute='inventory_hostname')
1175+
| list }}
1176+
failed_hosts: "{{ ansible_play_hosts_all | difference(active_hosts) }}"
1177+
fail:
1178+
msg: "These hosts are no longer active: {{ failed_hosts | join(', ') }}"
1179+
when: failed_hosts | length > 0
1180+
1181+
- name: Unset __mssql_is_active
1182+
set_fact:
1183+
__mssql_is_active: null
11391184

11401185
- name: Open required firewall ports and set required facts
11411186
block:
@@ -1314,11 +1359,11 @@
13141359
13151360
- name: Verify that primary instance hosts is available
13161361
assert:
1317-
that: ansible_play_hosts |
1318-
map('extract', hostvars, '__mssql_ha_is_primary') |
1319-
select('match', '^[Tt]rue$') |
1320-
list |
1321-
length == 1
1362+
that: ansible_play_hosts
1363+
| map('extract', hostvars, '__mssql_ha_is_primary')
1364+
| select
1365+
| list
1366+
| length == 1
13221367
fail_msg:
13231368
- The role was not able to identify primary replica.
13241369
run_once: true
@@ -1354,6 +1399,25 @@
13541399
- replicate_db.j2
13551400
include_tasks: input_sql_files.yml
13561401

1402+
- name: Mark active hosts
1403+
set_fact:
1404+
__mssql_is_active: true
1405+
1406+
- name: Fail if any hosts are no longer active
1407+
run_once: true
1408+
vars:
1409+
hostvars_list: "{{ ansible_play_hosts_all | map('extract', hostvars) | list }}"
1410+
active_hosts: >-
1411+
{{ hostvars_list
1412+
| selectattr('__mssql_is_active', 'defined')
1413+
| selectattr('__mssql_is_active')
1414+
| map(attribute='inventory_hostname')
1415+
| list }}
1416+
failed_hosts: "{{ ansible_play_hosts_all | difference(active_hosts) }}"
1417+
fail:
1418+
msg: "These hosts are no longer active: {{ failed_hosts | join(', ') }}"
1419+
when: failed_hosts | length > 0
1420+
13571421
# In the case of fail over when primary is moved grant permission task above
13581422
# is not run on primary above hence we need to run it separately
13591423
- name: Grant permissions to HA login
@@ -1369,19 +1433,6 @@
13691433
- name: Configure availability group on not primary replicas
13701434
when: mssql_ha_replica_type in __mssql_ha_replica_types_secondary
13711435
block:
1372-
- name: Verify that hosts with replica_type=primary is available
1373-
assert:
1374-
that: ansible_play_hosts |
1375-
map('extract', hostvars, 'mssql_ha_replica_type') |
1376-
select('match', '^primary$') |
1377-
list |
1378-
length == 1
1379-
fail_msg:
1380-
- Host with mssql_ha_replica_type=primary failed earlier
1381-
- Therefore, it is not possible to configure for high availability
1382-
- Fix the above error on the primary replica and re-run the role
1383-
run_once: true
1384-
13851436
- name: Ensure the package {{ __mssql_server_ha_packages }}
13861437
package:
13871438
name: "{{ __mssql_server_ha_packages }}"

templates/configure_ag.j2

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,38 @@
11
IF EXISTS (
22
SELECT name, cluster_type_desc
33
FROM sys.availability_groups
4-
WHERE name = '{{ mssql_ha_ag_name }}' AND
5-
cluster_type_desc != '{{ mssql_ha_ag_cluster_type }}'
4+
WHERE name = '{{ mssql_ha_ag_name }}'
65
)
76
BEGIN
8-
PRINT 'The existing {{ mssql_ha_ag_name }} availability group has \
7+
PRINT 'Verify existing availability group {{ mssql_ha_ag_name }}'
8+
IF EXISTS (
9+
SELECT name, cluster_type_desc
10+
FROM sys.availability_groups
11+
WHERE name = '{{ mssql_ha_ag_name }}'
12+
AND cluster_type_desc != '{{ mssql_ha_ag_cluster_type }}'
13+
)
14+
BEGIN
15+
PRINT 'The existing {{ mssql_ha_ag_name }} availability group has \
916
incorrect cluster type set, dropping the group to re-create it';
10-
DROP AVAILABILITY GROUP {{ mssql_ha_ag_name }};
11-
PRINT 'The {{ mssql_ha_ag_name }} availability group dropped successfully';
17+
DROP AVAILABILITY GROUP {{ mssql_ha_ag_name }};
18+
PRINT 'The {{ mssql_ha_ag_name }} availability group dropped successfully';
19+
END
20+
21+
{# is_contained was added in SQL Server 2019 #}
22+
{% if mssql_version is version('2019', '>=') %}
23+
IF EXISTS (
24+
SELECT name, is_contained
25+
FROM sys.availability_groups
26+
WHERE name = '{{ mssql_ha_ag_name }}'
27+
AND is_contained != {{ 1 if mssql_ha_ag_is_contained else 0 }}
28+
)
29+
BEGIN
30+
PRINT 'The existing {{ mssql_ha_ag_name }} availability group has \
31+
incorrect is_contained setting, dropping the group to re-create it';
32+
DROP AVAILABILITY GROUP {{ mssql_ha_ag_name }};
33+
PRINT 'The {{ mssql_ha_ag_name }} availability group dropped successfully';
34+
END
35+
{% endif %}
1236
END
1337

1438
IF EXISTS (
@@ -18,6 +42,7 @@ IF EXISTS (
1842
)
1943
BEGIN
2044
PRINT 'Verifying the existing availability group {{ mssql_ha_ag_name }}'
45+
PRINT 'Verifying {{ mssql_ha_ag_name }} DB_FAILOVER status'
2146
IF NOT EXISTS (
2247
SELECT name, db_failover
2348
FROM sys.availability_groups
@@ -27,14 +52,15 @@ BEGIN
2752
BEGIN
2853
ALTER AVAILABILITY GROUP {{ mssql_ha_ag_name }}
2954
SET (DB_FAILOVER = {{ __mssql_ha_db_failover }})
30-
PRINT 'Set DB_FAILOVER to ON succesfully'
55+
PRINT 'Set DB_FAILOVER to ON successfully'
3156
END
3257
ELSE
3358
BEGIN
3459
PRINT 'DB_FAILOVER = ON is already set, skipping'
3560
END
61+
3662
PRINT 'Verifying replicas'
37-
{# Sort ansible_play_hosts #}
63+
{# Sort ansible_play_hosts_all #}
3864
{# Sort primary replica as the last in the list #}
3965
{# Sort witness replica as the next-to-last in the list #}
4066
{# Configure primary last because it has information about other replicas #}
@@ -43,15 +69,15 @@ BEGIN
4369
{# replica which has configuration-only availability mode.' #}
4470
{% set ag_replicas_primary_last = [] %}
4571
{# In the first loop, add primaries at the end and witness at the beginning #}
46-
{% for item in ansible_play_hosts %}
72+
{% for item in ansible_play_hosts_all %}
4773
{% if hostvars[item]['mssql_ha_replica_type'] == 'primary' %}
4874
{{ ag_replicas_primary_last.append(item) -}}
4975
{% elif hostvars[item]['mssql_ha_replica_type'] == 'witness' %}
5076
{{ ag_replicas_primary_last.insert(0, item) -}}
5177
{% endif %}
5278
{% endfor %}
5379
{# In the second loop, add others at the beginning #}
54-
{% for item in ansible_play_hosts %}
80+
{% for item in ansible_play_hosts_all %}
5581
{% if hostvars[item]['mssql_ha_replica_type']
5682
not in ag_replicas_primary_last %}
5783
{{ ag_replicas_primary_last.insert(0, item) -}}
@@ -209,31 +235,62 @@ successfully';
209235
{% endfor %}
210236
END
211237

238+
{# is_contained was added in SQL Server 2019 #}
239+
{% if (not mssql_ha_ag_is_contained) or (mssql_version is version('2019', '<')) %}
240+
{% set __contained_reuse_system_db = "" %}
241+
{% elif mssql_ha_ag_is_contained %}
242+
{% set __contained_reuse_system_db = ",CONTAINED" %}
243+
{% if mssql_ha_ag_reuse_system_db %}
244+
{% set __contained_reuse_system_db = __contained_reuse_system_db + ",REUSE_SYSTEM_DATABASES" %}
245+
{% endif %}
246+
{% endif %}
247+
212248
IF NOT EXISTS (
213249
SELECT name, cluster_type_desc
214250
FROM sys.availability_groups
215251
WHERE name = '{{ mssql_ha_ag_name }}'
216252
)
217253
BEGIN
254+
{% if mssql_ha_ag_reuse_system_db %}
255+
{% set sys_dbs = [mssql_ha_ag_name ~ '_master', mssql_ha_ag_name ~ '_msdb'] %}
256+
{% for sys_db in sys_dbs %}
257+
IF EXISTS (
258+
SELECT name, state_desc
259+
FROM sys.databases
260+
WHERE state_desc = 'RESTORING'
261+
AND name='{{ sys_db }}'
262+
)
263+
BEGIN
264+
PRINT 'The system database {{ sys_db }} is in restoring state, running \
265+
restore to reuse it for the newly created AG {{ mssql_ha_ag_name }}';
266+
RESTORE DATABASE [{{ sys_db }}];
267+
PRINT 'The {{ sys_db }} database restored successfully';
268+
END
269+
ELSE
270+
BEGIN
271+
PRINT 'The {{ sys_db }} database is already online, skipping';
272+
END
273+
{% endfor %}
274+
{% endif %}
218275
PRINT 'Creating the {{ mssql_ha_ag_name }} availability group';
219276
CREATE AVAILABILITY GROUP {{ mssql_ha_ag_name }}
220277
WITH (
221278
DB_FAILOVER = {{ __mssql_ha_db_failover }},
222-
CLUSTER_TYPE = {{ mssql_ha_ag_cluster_type }}
279+
CLUSTER_TYPE = {{ mssql_ha_ag_cluster_type }}{{ __contained_reuse_system_db }}
223280
)
224281
FOR REPLICA ON
225-
{# Sort ansible_play_hosts #}
282+
{# Sort ansible_play_hosts_all #}
226283
{# Sort primary replica as the first in the list #}
227284
{# Configure AG with primary replica first because SQL Server requires this #}
228285
{% set ag_replicas_primary_first = [] %}
229286
{# In the first loop, add not primaries to a new list #}
230-
{% for item in ansible_play_hosts %}
287+
{% for item in ansible_play_hosts_all %}
231288
{% if hostvars[item]['mssql_ha_replica_type'] != 'primary' %}
232289
{{ ag_replicas_primary_first.append(item) -}}
233290
{% endif %}
234291
{% endfor %}
235292
{# In the second loop, add primary at the beginning #}
236-
{% for item in ansible_play_hosts %}
293+
{% for item in ansible_play_hosts_all %}
237294
{% if hostvars[item]['mssql_ha_replica_type'] == 'primary' %}
238295
{{ ag_replicas_primary_first.insert(0, item) -}}
239296
{% endif %}

templates/replicate_db.j2

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,23 @@ BEGIN
1717
PRINT 'RECOVERY FULL on the {{ item }} database is set, skipping';
1818
END
1919

20+
IF EXISTS (
21+
SELECT name, state_desc
22+
FROM sys.databases
23+
WHERE state_desc = 'RESTORING'
24+
AND name='{{ item }}'
25+
)
26+
BEGIN
27+
PRINT 'The {{ item }} database is in restoring state, running restore with \
28+
recovery to add it to AG after';
29+
RESTORE DATABASE [{{ item }}] WITH RECOVERY;
30+
PRINT 'The {{ item }} database restored with recovery successfully';
31+
END
32+
ELSE
33+
BEGIN
34+
PRINT 'The {{ item }} database is already online, skipping';
35+
END
36+
2037
IF NOT EXISTS (
2138
SELECT [database_name], backup_start_date, backup_finish_date, [type]
2239
FROM msdb.dbo.backupset

0 commit comments

Comments
 (0)