Browse code

postgresql_idx: added CI tests for check_mode, rewrite code related with check_mode, misc fixes (#54848)

* postgresql_idx: added CI tests, misc fixes

* postgresql_idx: fix sanity

Andrey Klychkov authored on 2019/04/04 21:31:14
Showing 2 changed files
... ...
@@ -18,9 +18,10 @@ DOCUMENTATION = r'''
18 18
 module: postgresql_idx
19 19
 short_description: Create or drop indexes from a PostgreSQL database
20 20
 description:
21
-- Creates or drops indexes from a remote PostgreSQL database
21
+- Create or drop indexes from a PostgreSQL database
22 22
   U(https://www.postgresql.org/docs/current/sql-createindex.html).
23
-version_added: "2.8"
23
+version_added: '2.8'
24
+
24 25
 options:
25 26
   idxname:
26 27
     description:
... ...
@@ -103,6 +104,8 @@ options:
103 103
     - List of index columns.
104 104
     - Mutually exclusive with I(state=absent).
105 105
     type: list
106
+    aliases:
107
+    - column
106 108
   cond:
107 109
     description:
108 110
     - Index conditions.
... ...
@@ -118,7 +121,7 @@ options:
118 118
   concurrent:
119 119
     description:
120 120
     - Enable or disable concurrent mode (CREATE / DROP INDEX CONCURRENTLY).
121
-    - Mutually exclusive with check mode and I(cascade=yes).
121
+    - Mutually exclusive with I(cascade=yes).
122 122
     type: bool
123 123
     default: yes
124 124
   tablespace:
... ...
@@ -140,31 +143,31 @@ options:
140 140
     - Mutually exclusive with I(concurrent=yes)
141 141
     type: bool
142 142
     default: no
143
+
143 144
 notes:
144 145
 - The default authentication assumes that you are either logging in as or
145 146
   sudo'ing to the postgres account on the host.
146
-- I(cuncurrent=yes) cannot be used in check mode because
147
-  "CREATE INDEX CONCURRENTLY" cannot run inside a transaction block.
148 147
 - This module uses psycopg2, a Python PostgreSQL database adapter. You must
149 148
   ensure that psycopg2 is installed on the host before using this module.
150 149
 - If the remote host is the PostgreSQL server (which is the default case), then
151 150
   PostgreSQL must also be installed on the remote host.
152 151
 - For Ubuntu-based systems, install the postgresql, libpq-dev, and python-psycopg2 packages
153 152
   on the remote host before using this module.
154
-requirements: [ psycopg2 ]
153
+
154
+requirements:
155
+- psycopg2
156
+
155 157
 author:
156 158
 - Andrew Klychkov (@Andersson007)
157 159
 '''
158 160
 
159 161
 EXAMPLES = r'''
160
-# For create / drop index in check mode use concurrent=no and --check
161
-
162 162
 - name: Create btree index if not exists test_idx concurrently covering columns id and name of table products
163 163
   postgresql_idx:
164 164
     db: acme
165 165
     table: products
166 166
     columns: id,name
167
-    idxname: test_idx
167
+    name: test_idx
168 168
 
169 169
 - name: Create btree index test_idx concurrently with tablespace called ssd and storage parameter
170 170
   postgresql_idx:
... ...
@@ -258,14 +261,15 @@ valid:
258 258
 
259 259
 import traceback
260 260
 
261
+PSYCOPG2_IMP_ERR = None
261 262
 try:
262 263
     import psycopg2
263 264
     HAS_PSYCOPG2 = True
264 265
 except ImportError:
265 266
     HAS_PSYCOPG2 = False
267
+    PSYCOPG2_IMP_ERR = traceback.format_exc()
266 268
 
267
-import ansible.module_utils.postgres as pgutils
268
-from ansible.module_utils.basic import AnsibleModule
269
+from ansible.module_utils.basic import AnsibleModule, missing_required_lib
269 270
 from ansible.module_utils.database import SQLParseError
270 271
 from ansible.module_utils.postgres import postgres_common_argument_spec
271 272
 from ansible.module_utils._text import to_native
... ...
@@ -441,7 +445,7 @@ def main():
441 441
         concurrent=dict(type='bool', default=True),
442 442
         table=dict(type='str'),
443 443
         idxtype=dict(type='str', aliases=['type']),
444
-        columns=dict(type='list'),
444
+        columns=dict(type='list', aliases=['column']),
445 445
         cond=dict(type='str'),
446 446
         session_role=dict(type='str'),
447 447
         tablespace=dict(type='str'),
... ...
@@ -454,6 +458,9 @@ def main():
454 454
         supports_check_mode=True,
455 455
     )
456 456
 
457
+    if not HAS_PSYCOPG2:
458
+        module.fail_json(msg=missing_required_lib('psycopg2'), exception=PSYCOPG2_IMP_ERR)
459
+
457 460
     idxname = module.params["idxname"]
458 461
     state = module.params["state"]
459 462
     concurrent = module.params["concurrent"]
... ...
@@ -468,8 +475,8 @@ def main():
468 468
     cascade = module.params["cascade"]
469 469
     schema = module.params["schema"]
470 470
 
471
-    if concurrent and (module.check_mode or cascade):
472
-        module.fail_json(msg="Cuncurrent mode and check mode/cascade are mutually exclusive")
471
+    if concurrent and cascade:
472
+        module.fail_json(msg="Cuncurrent mode and cascade parameters are mutually exclusive")
473 473
 
474 474
     if state == 'present':
475 475
         if not table:
... ...
@@ -485,9 +492,6 @@ def main():
485 485
     if cascade and state != 'absent':
486 486
         module.fail_json(msg="cascade parameter used only with state=absent")
487 487
 
488
-    if not HAS_PSYCOPG2:
489
-        module.fail_json(msg="the python psycopg2 module is required")
490
-
491 488
     # To use defaults values, keyword arguments must be absent, so
492 489
     # check which values are empty and don't include in the **kw
493 490
     # dictionary
... ...
@@ -511,11 +515,6 @@ def main():
511 511
     if psycopg2.__version__ < '2.4.3' and sslrootcert is not None:
512 512
         module.fail_json(msg='psycopg2 must be at least 2.4.3 in order to user the ca_cert parameter')
513 513
 
514
-    if module.check_mode and concurrent:
515
-        module.fail_json(msg="Cannot concurrently create or drop index %s "
516
-                             "inside the transaction block. The check is possible "
517
-                             "in not concurrent mode only" % idxname)
518
-
519 514
     try:
520 515
         db_connection = psycopg2.connect(**kw)
521 516
         if concurrent:
... ...
@@ -529,9 +528,9 @@ def main():
529 529
         if 'sslrootcert' in e.args[0]:
530 530
             module.fail_json(msg='Postgresql server must be at least version 8.4 to support sslrootcert')
531 531
 
532
-        module.fail_json(msg="unable to connect to database: %s" % to_native(e))
532
+        module.fail_json(msg="Unable to connect to database: %s" % to_native(e))
533 533
     except Exception as e:
534
-        module.fail_json(msg="unable to connect to database: %s" % to_native(e))
534
+        module.fail_json(msg="Unable to connect to database: %s" % to_native(e))
535 535
 
536 536
     if session_role:
537 537
         try:
... ...
@@ -547,6 +546,27 @@ def main():
547 547
     kw = index.get_info()
548 548
     kw['query'] = ''
549 549
 
550
+    #
551
+    # check_mode start
552
+    if module.check_mode:
553
+        if state == 'present' and index.exists:
554
+            kw['changed'] = False
555
+            module.exit_json(**kw)
556
+
557
+        elif state == 'present' and not index.exists:
558
+            kw['changed'] = True
559
+            module.exit_json(**kw)
560
+
561
+        elif state == 'absent' and not index.exists:
562
+            kw['changed'] = False
563
+            module.exit_json(**kw)
564
+
565
+        elif state == 'absent' and index.exists:
566
+            kw['changed'] = True
567
+            module.exit_json(**kw)
568
+    # check_mode end
569
+    #
570
+
550 571
     if state == "present":
551 572
         if idxtype and idxtype.upper() not in VALID_IDX_TYPES:
552 573
             module.fail_json(msg="Index type '%s' of %s is not in valid types" % (idxtype, idxname))
... ...
@@ -570,7 +590,7 @@ def main():
570 570
             kw['state'] = 'absent'
571 571
             kw['query'] = index.executed_query
572 572
 
573
-    if not module.check_mode and not kw['valid'] and concurrent:
573
+    if not kw['valid']:
574 574
         db_connection.rollback()
575 575
         module.warn("Index %s is invalid! ROLLBACK" % idxname)
576 576
 
... ...
@@ -59,6 +59,46 @@
59 59
 # Do main tests
60 60
 #
61 61
 
62
+# Create index in check_mode
63
+- name: postgresql_idx - create btree index in check_mode
64
+  become_user: "{{ pg_user }}"
65
+  become: yes
66
+  postgresql_idx:
67
+    db: postgres
68
+    login_user: "{{ pg_user }}"
69
+    table: test_table
70
+    columns: id, story
71
+    idxname: test0_idx
72
+  check_mode: yes
73
+  register: result
74
+  ignore_errors: yes
75
+
76
+- assert:
77
+    that:
78
+    - result.changed == true
79
+    - result.tblname == ''
80
+    - result.name == 'test0_idx'
81
+    - result.state == 'absent'
82
+    - result.valid != ''
83
+    - result.tblspace == ''
84
+    - result.storage_params == []
85
+    - result.schema == ''
86
+    - result.query == ''
87
+
88
+# Check that actually nothing changed, rowcount must be 0
89
+- name: postgresql_idx - check nothing changed after the previous step
90
+  become_user: "{{ pg_user }}"
91
+  become: yes
92
+  postgresql_query:
93
+    db: postgres
94
+    login_user: "{{ pg_user }}"
95
+    query: "SELECT 1 FROM pg_indexes WHERE indexname = 'test0_idx'"
96
+  register: result
97
+
98
+- assert:
99
+    that:
100
+    - result.rowcount == 0
101
+
62 102
 # Create btree index if not exists test_idx concurrently covering id and story columns
63 103
 - name: postgresql_idx - create btree index concurrently
64 104
   become_user: "{{ pg_user }}"
... ...
@@ -84,6 +124,20 @@
84 84
     - result.schema == 'public'
85 85
     - result.query == 'CREATE INDEX CONCURRENTLY test0_idx ON public.test_table USING BTREE (id, story)'
86 86
 
87
+# Check that the index exists after the previous step, rowcount must be 1
88
+- name: postgresql_idx - check the index exists after the previous step
89
+  become_user: "{{ pg_user }}"
90
+  become: yes
91
+  postgresql_query:
92
+    db: postgres
93
+    login_user: "{{ pg_user }}"
94
+    query: "SELECT 1 FROM pg_indexes WHERE indexname = 'test0_idx'"
95
+  register: result
96
+
97
+- assert:
98
+    that:
99
+    - result.rowcount == 1
100
+
87 101
 # Check that if index exists that changes nothing
88 102
 - name: postgresql_idx - try to create existing index again
89 103
   become_user: "{{ pg_user }}"
... ...
@@ -198,6 +252,47 @@
198 198
     - result.schema == 'public'
199 199
     - result.query == 'CREATE INDEX CONCURRENTLY test1_idx ON public.test_table USING BTREE (id) WHERE id > 1 AND id != 10'
200 200
 
201
+# Drop index from spacific schema with cascade in check_mode
202
+- name: postgresql_idx - drop index from specific schema cascade in check_mode
203
+  become_user: "{{ pg_user }}"
204
+  become: yes
205
+  postgresql_idx:
206
+    db: postgres
207
+    login_user: "{{ pg_user }}"
208
+    schema: foo
209
+    name: foo_test_idx
210
+    cascade: yes
211
+    state: absent
212
+    concurrent: no
213
+  check_mode: yes
214
+  register: result
215
+  ignore_errors: yes
216
+
217
+- assert:
218
+    that:
219
+    - result.changed == true
220
+    - result.name == 'foo_test_idx'
221
+    - result.state == 'present'
222
+    - result.schema == 'foo'
223
+    - result.query == ''
224
+  when: tablespace.rc == 0
225
+
226
+# Check that the index exists after the previous step, rowcount must be 1
227
+- name: postgresql_idx - check the index exists after the previous step
228
+  become_user: "{{ pg_user }}"
229
+  become: yes
230
+  postgresql_query:
231
+    db: postgres
232
+    login_user: "{{ pg_user }}"
233
+    query: "SELECT 1 FROM pg_indexes WHERE indexname = 'foo_test_idx'"
234
+  register: result
235
+  when: tablespace.rc == 0
236
+
237
+- assert:
238
+    that:
239
+    - result.rowcount == 1
240
+  when: tablespace.rc == 0
241
+
201 242
 # Drop index from spacific schema with cascade
202 243
 - name: postgresql_idx - drop index from specific schema cascade
203 244
   become_user: "{{ pg_user }}"
... ...
@@ -222,6 +317,22 @@
222 222
     - result.query == 'DROP INDEX foo.foo_test_idx CASCADE'
223 223
   when: tablespace.rc == 0
224 224
 
225
+# Check that the index doesn't exist after the previous step, rowcount must be 0
226
+- name: postgresql_idx - check the index doesn't exist after the previous step
227
+  become_user: "{{ pg_user }}"
228
+  become: yes
229
+  postgresql_query:
230
+    db: postgres
231
+    login_user: "{{ pg_user }}"
232
+    query: "SELECT 1 FROM pg_indexes WHERE indexname = 'foo_test_idx'"
233
+  register: result
234
+  when: tablespace.rc == 0
235
+
236
+- assert:
237
+    that:
238
+    - result.rowcount == 0
239
+  when: tablespace.rc == 0
240
+
225 241
 # Try to drop not existing index
226 242
 - name: postgresql_idx - try to drop not existing index
227 243
   become_user: "{{ pg_user }}"