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
This commit is contained in:
Andrey Klychkov 2019-04-04 15:31:14 +03:00 committed by John R Barker
parent 6a10c2a5f4
commit 2fbac8948d
2 changed files with 156 additions and 25 deletions

View file

@ -18,9 +18,10 @@ DOCUMENTATION = r'''
module: postgresql_idx module: postgresql_idx
short_description: Create or drop indexes from a PostgreSQL database short_description: Create or drop indexes from a PostgreSQL database
description: description:
- Creates or drops indexes from a remote PostgreSQL database - Create or drop indexes from a PostgreSQL database
U(https://www.postgresql.org/docs/current/sql-createindex.html). U(https://www.postgresql.org/docs/current/sql-createindex.html).
version_added: "2.8" version_added: '2.8'
options: options:
idxname: idxname:
description: description:
@ -103,6 +104,8 @@ options:
- List of index columns. - List of index columns.
- Mutually exclusive with I(state=absent). - Mutually exclusive with I(state=absent).
type: list type: list
aliases:
- column
cond: cond:
description: description:
- Index conditions. - Index conditions.
@ -118,7 +121,7 @@ options:
concurrent: concurrent:
description: description:
- Enable or disable concurrent mode (CREATE / DROP INDEX CONCURRENTLY). - Enable or disable concurrent mode (CREATE / DROP INDEX CONCURRENTLY).
- Mutually exclusive with check mode and I(cascade=yes). - Mutually exclusive with I(cascade=yes).
type: bool type: bool
default: yes default: yes
tablespace: tablespace:
@ -140,31 +143,31 @@ options:
- Mutually exclusive with I(concurrent=yes) - Mutually exclusive with I(concurrent=yes)
type: bool type: bool
default: no default: no
notes: notes:
- The default authentication assumes that you are either logging in as or - The default authentication assumes that you are either logging in as or
sudo'ing to the postgres account on the host. sudo'ing to the postgres account on the host.
- I(cuncurrent=yes) cannot be used in check mode because
"CREATE INDEX CONCURRENTLY" cannot run inside a transaction block.
- This module uses psycopg2, a Python PostgreSQL database adapter. You must - This module uses psycopg2, a Python PostgreSQL database adapter. You must
ensure that psycopg2 is installed on the host before using this module. ensure that psycopg2 is installed on the host before using this module.
- If the remote host is the PostgreSQL server (which is the default case), then - If the remote host is the PostgreSQL server (which is the default case), then
PostgreSQL must also be installed on the remote host. PostgreSQL must also be installed on the remote host.
- For Ubuntu-based systems, install the postgresql, libpq-dev, and python-psycopg2 packages - For Ubuntu-based systems, install the postgresql, libpq-dev, and python-psycopg2 packages
on the remote host before using this module. on the remote host before using this module.
requirements: [ psycopg2 ]
requirements:
- psycopg2
author: author:
- Andrew Klychkov (@Andersson007) - Andrew Klychkov (@Andersson007)
''' '''
EXAMPLES = r''' EXAMPLES = r'''
# For create / drop index in check mode use concurrent=no and --check
- name: Create btree index if not exists test_idx concurrently covering columns id and name of table products - name: Create btree index if not exists test_idx concurrently covering columns id and name of table products
postgresql_idx: postgresql_idx:
db: acme db: acme
table: products table: products
columns: id,name columns: id,name
idxname: test_idx name: test_idx
- name: Create btree index test_idx concurrently with tablespace called ssd and storage parameter - name: Create btree index test_idx concurrently with tablespace called ssd and storage parameter
postgresql_idx: postgresql_idx:
@ -258,14 +261,15 @@ valid:
import traceback import traceback
PSYCOPG2_IMP_ERR = None
try: try:
import psycopg2 import psycopg2
HAS_PSYCOPG2 = True HAS_PSYCOPG2 = True
except ImportError: except ImportError:
HAS_PSYCOPG2 = False HAS_PSYCOPG2 = False
PSYCOPG2_IMP_ERR = traceback.format_exc()
import ansible.module_utils.postgres as pgutils from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.database import SQLParseError from ansible.module_utils.database import SQLParseError
from ansible.module_utils.postgres import postgres_common_argument_spec from ansible.module_utils.postgres import postgres_common_argument_spec
from ansible.module_utils._text import to_native from ansible.module_utils._text import to_native
@ -441,7 +445,7 @@ def main():
concurrent=dict(type='bool', default=True), concurrent=dict(type='bool', default=True),
table=dict(type='str'), table=dict(type='str'),
idxtype=dict(type='str', aliases=['type']), idxtype=dict(type='str', aliases=['type']),
columns=dict(type='list'), columns=dict(type='list', aliases=['column']),
cond=dict(type='str'), cond=dict(type='str'),
session_role=dict(type='str'), session_role=dict(type='str'),
tablespace=dict(type='str'), tablespace=dict(type='str'),
@ -454,6 +458,9 @@ def main():
supports_check_mode=True, supports_check_mode=True,
) )
if not HAS_PSYCOPG2:
module.fail_json(msg=missing_required_lib('psycopg2'), exception=PSYCOPG2_IMP_ERR)
idxname = module.params["idxname"] idxname = module.params["idxname"]
state = module.params["state"] state = module.params["state"]
concurrent = module.params["concurrent"] concurrent = module.params["concurrent"]
@ -468,8 +475,8 @@ def main():
cascade = module.params["cascade"] cascade = module.params["cascade"]
schema = module.params["schema"] schema = module.params["schema"]
if concurrent and (module.check_mode or cascade): if concurrent and cascade:
module.fail_json(msg="Cuncurrent mode and check mode/cascade are mutually exclusive") module.fail_json(msg="Cuncurrent mode and cascade parameters are mutually exclusive")
if state == 'present': if state == 'present':
if not table: if not table:
@ -485,9 +492,6 @@ def main():
if cascade and state != 'absent': if cascade and state != 'absent':
module.fail_json(msg="cascade parameter used only with state=absent") module.fail_json(msg="cascade parameter used only with state=absent")
if not HAS_PSYCOPG2:
module.fail_json(msg="the python psycopg2 module is required")
# To use defaults values, keyword arguments must be absent, so # To use defaults values, keyword arguments must be absent, so
# check which values are empty and don't include in the **kw # check which values are empty and don't include in the **kw
# dictionary # dictionary
@ -511,11 +515,6 @@ def main():
if psycopg2.__version__ < '2.4.3' and sslrootcert is not None: if psycopg2.__version__ < '2.4.3' and sslrootcert is not None:
module.fail_json(msg='psycopg2 must be at least 2.4.3 in order to user the ca_cert parameter') module.fail_json(msg='psycopg2 must be at least 2.4.3 in order to user the ca_cert parameter')
if module.check_mode and concurrent:
module.fail_json(msg="Cannot concurrently create or drop index %s "
"inside the transaction block. The check is possible "
"in not concurrent mode only" % idxname)
try: try:
db_connection = psycopg2.connect(**kw) db_connection = psycopg2.connect(**kw)
if concurrent: if concurrent:
@ -529,9 +528,9 @@ def main():
if 'sslrootcert' in e.args[0]: if 'sslrootcert' in e.args[0]:
module.fail_json(msg='Postgresql server must be at least version 8.4 to support sslrootcert') module.fail_json(msg='Postgresql server must be at least version 8.4 to support sslrootcert')
module.fail_json(msg="unable to connect to database: %s" % to_native(e)) module.fail_json(msg="Unable to connect to database: %s" % to_native(e))
except Exception as e: except Exception as e:
module.fail_json(msg="unable to connect to database: %s" % to_native(e)) module.fail_json(msg="Unable to connect to database: %s" % to_native(e))
if session_role: if session_role:
try: try:
@ -547,6 +546,27 @@ def main():
kw = index.get_info() kw = index.get_info()
kw['query'] = '' kw['query'] = ''
#
# check_mode start
if module.check_mode:
if state == 'present' and index.exists:
kw['changed'] = False
module.exit_json(**kw)
elif state == 'present' and not index.exists:
kw['changed'] = True
module.exit_json(**kw)
elif state == 'absent' and not index.exists:
kw['changed'] = False
module.exit_json(**kw)
elif state == 'absent' and index.exists:
kw['changed'] = True
module.exit_json(**kw)
# check_mode end
#
if state == "present": if state == "present":
if idxtype and idxtype.upper() not in VALID_IDX_TYPES: if idxtype and idxtype.upper() not in VALID_IDX_TYPES:
module.fail_json(msg="Index type '%s' of %s is not in valid types" % (idxtype, idxname)) module.fail_json(msg="Index type '%s' of %s is not in valid types" % (idxtype, idxname))
@ -570,7 +590,7 @@ def main():
kw['state'] = 'absent' kw['state'] = 'absent'
kw['query'] = index.executed_query kw['query'] = index.executed_query
if not module.check_mode and not kw['valid'] and concurrent: if not kw['valid']:
db_connection.rollback() db_connection.rollback()
module.warn("Index %s is invalid! ROLLBACK" % idxname) module.warn("Index %s is invalid! ROLLBACK" % idxname)

View file

@ -59,6 +59,46 @@
# Do main tests # Do main tests
# #
# Create index in check_mode
- name: postgresql_idx - create btree index in check_mode
become_user: "{{ pg_user }}"
become: yes
postgresql_idx:
db: postgres
login_user: "{{ pg_user }}"
table: test_table
columns: id, story
idxname: test0_idx
check_mode: yes
register: result
ignore_errors: yes
- assert:
that:
- result.changed == true
- result.tblname == ''
- result.name == 'test0_idx'
- result.state == 'absent'
- result.valid != ''
- result.tblspace == ''
- result.storage_params == []
- result.schema == ''
- result.query == ''
# Check that actually nothing changed, rowcount must be 0
- name: postgresql_idx - check nothing changed after the previous step
become_user: "{{ pg_user }}"
become: yes
postgresql_query:
db: postgres
login_user: "{{ pg_user }}"
query: "SELECT 1 FROM pg_indexes WHERE indexname = 'test0_idx'"
register: result
- assert:
that:
- result.rowcount == 0
# Create btree index if not exists test_idx concurrently covering id and story columns # Create btree index if not exists test_idx concurrently covering id and story columns
- name: postgresql_idx - create btree index concurrently - name: postgresql_idx - create btree index concurrently
become_user: "{{ pg_user }}" become_user: "{{ pg_user }}"
@ -84,6 +124,20 @@
- result.schema == 'public' - result.schema == 'public'
- result.query == 'CREATE INDEX CONCURRENTLY test0_idx ON public.test_table USING BTREE (id, story)' - result.query == 'CREATE INDEX CONCURRENTLY test0_idx ON public.test_table USING BTREE (id, story)'
# Check that the index exists after the previous step, rowcount must be 1
- name: postgresql_idx - check the index exists after the previous step
become_user: "{{ pg_user }}"
become: yes
postgresql_query:
db: postgres
login_user: "{{ pg_user }}"
query: "SELECT 1 FROM pg_indexes WHERE indexname = 'test0_idx'"
register: result
- assert:
that:
- result.rowcount == 1
# Check that if index exists that changes nothing # Check that if index exists that changes nothing
- name: postgresql_idx - try to create existing index again - name: postgresql_idx - try to create existing index again
become_user: "{{ pg_user }}" become_user: "{{ pg_user }}"
@ -198,6 +252,47 @@
- result.schema == 'public' - result.schema == 'public'
- result.query == 'CREATE INDEX CONCURRENTLY test1_idx ON public.test_table USING BTREE (id) WHERE id > 1 AND id != 10' - result.query == 'CREATE INDEX CONCURRENTLY test1_idx ON public.test_table USING BTREE (id) WHERE id > 1 AND id != 10'
# Drop index from spacific schema with cascade in check_mode
- name: postgresql_idx - drop index from specific schema cascade in check_mode
become_user: "{{ pg_user }}"
become: yes
postgresql_idx:
db: postgres
login_user: "{{ pg_user }}"
schema: foo
name: foo_test_idx
cascade: yes
state: absent
concurrent: no
check_mode: yes
register: result
ignore_errors: yes
- assert:
that:
- result.changed == true
- result.name == 'foo_test_idx'
- result.state == 'present'
- result.schema == 'foo'
- result.query == ''
when: tablespace.rc == 0
# Check that the index exists after the previous step, rowcount must be 1
- name: postgresql_idx - check the index exists after the previous step
become_user: "{{ pg_user }}"
become: yes
postgresql_query:
db: postgres
login_user: "{{ pg_user }}"
query: "SELECT 1 FROM pg_indexes WHERE indexname = 'foo_test_idx'"
register: result
when: tablespace.rc == 0
- assert:
that:
- result.rowcount == 1
when: tablespace.rc == 0
# Drop index from spacific schema with cascade # Drop index from spacific schema with cascade
- name: postgresql_idx - drop index from specific schema cascade - name: postgresql_idx - drop index from specific schema cascade
become_user: "{{ pg_user }}" become_user: "{{ pg_user }}"
@ -222,6 +317,22 @@
- result.query == 'DROP INDEX foo.foo_test_idx CASCADE' - result.query == 'DROP INDEX foo.foo_test_idx CASCADE'
when: tablespace.rc == 0 when: tablespace.rc == 0
# Check that the index doesn't exist after the previous step, rowcount must be 0
- name: postgresql_idx - check the index doesn't exist after the previous step
become_user: "{{ pg_user }}"
become: yes
postgresql_query:
db: postgres
login_user: "{{ pg_user }}"
query: "SELECT 1 FROM pg_indexes WHERE indexname = 'foo_test_idx'"
register: result
when: tablespace.rc == 0
- assert:
that:
- result.rowcount == 0
when: tablespace.rc == 0
# Try to drop not existing index # Try to drop not existing index
- name: postgresql_idx - try to drop not existing index - name: postgresql_idx - try to drop not existing index
become_user: "{{ pg_user }}" become_user: "{{ pg_user }}"