From 90c092a1046dbd4110e0a833a055ed5868d62cc7 Mon Sep 17 00:00:00 2001 From: Christian Rohmann Date: Fri, 22 Mar 2019 13:51:39 +0100 Subject: [PATCH] Allow configuration of connection_limit per postgresql database (postgresql_db) (#40345) Fixes #40060 * Fix coding style errors * Use CONNECTION LIMIT (no underscore) * From review done by amenonsen and bcoca - Set default at None, make the change detection less confusing * Added EXAMPLE on how to apply a database specific connection limit * Added some basic tests for conn_limit applied to a database * Check that conn_limit has actually been set / updated to 200 * Add changelog fragment regarding postgresql_db conn_limit parameter --- .../fragments/postgresql-db-conn-limit.yaml | 2 + .../database/postgresql/postgresql_db.py | 49 +++++++++++++++---- .../targets/postgresql/tasks/main.yml | 38 ++++++++++++-- 3 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 changelogs/fragments/postgresql-db-conn-limit.yaml diff --git a/changelogs/fragments/postgresql-db-conn-limit.yaml b/changelogs/fragments/postgresql-db-conn-limit.yaml new file mode 100644 index 00000000000..cf4ddf49a92 --- /dev/null +++ b/changelogs/fragments/postgresql-db-conn-limit.yaml @@ -0,0 +1,2 @@ +minor_changes: + - postgresql_db - Added paramter conn_limit to limit the number of concurrent connection to a certain database diff --git a/lib/ansible/modules/database/postgresql/postgresql_db.py b/lib/ansible/modules/database/postgresql/postgresql_db.py index db4fbd3ba54..942b2bdd7af 100644 --- a/lib/ansible/modules/database/postgresql/postgresql_db.py +++ b/lib/ansible/modules/database/postgresql/postgresql_db.py @@ -83,6 +83,11 @@ options: type: str default: postgres version_added: "2.5" + conn_limit: + description: + - Specifies the database connection limit. + type: str + version_added: '2.8' notes: - State C(dump) and C(restore) don't require I(psycopg2) since version 2.8. author: "Ansible Core Team" @@ -104,6 +109,12 @@ EXAMPLES = r''' lc_ctype: de_DE.UTF-8 template: template0 +# Note: Default limit for the number of concurrent connections to a specific database is "-1", which means "unlimited" +- name: Create a new database with name "acme" which has a limit of 100 concurrent connections + postgresql_db: + name: acme + conn_limit: "100" + - name: Dump an existing database to a file postgresql_db: name: acme @@ -162,6 +173,14 @@ def set_owner(cursor, db, owner): return True +def set_conn_limit(cursor, db, conn_limit): + query = "ALTER DATABASE %s CONNECTION LIMIT %s" % ( + pg_quote_identifier(db, 'database'), + conn_limit) + cursor.execute(query) + return True + + def get_encoding_id(cursor, encoding): query = "SELECT pg_char_to_encoding(%(encoding)s) AS encoding_id;" cursor.execute(query, {'encoding': encoding}) @@ -172,7 +191,7 @@ def get_db_info(cursor, db): query = """ SELECT rolname AS owner, pg_encoding_to_char(encoding) AS encoding, encoding AS encoding_id, - datcollate AS lc_collate, datctype AS lc_ctype + datcollate AS lc_collate, datctype AS lc_ctype, pg_database.datconnlimit AS conn_limit FROM pg_database JOIN pg_roles ON pg_roles.oid = pg_database.datdba WHERE datname = %(db)s """ @@ -195,8 +214,8 @@ def db_delete(cursor, db): return False -def db_create(cursor, db, owner, template, encoding, lc_collate, lc_ctype): - params = dict(enc=encoding, collate=lc_collate, ctype=lc_ctype) +def db_create(cursor, db, owner, template, encoding, lc_collate, lc_ctype, conn_limit): + params = dict(enc=encoding, collate=lc_collate, ctype=lc_ctype, conn_limit=conn_limit) if not db_exists(cursor, db): query_fragments = ['CREATE DATABASE %s' % pg_quote_identifier(db, 'database')] if owner: @@ -209,6 +228,8 @@ def db_create(cursor, db, owner, template, encoding, lc_collate, lc_ctype): query_fragments.append('LC_COLLATE %(collate)s') if lc_ctype: query_fragments.append('LC_CTYPE %(ctype)s') + if conn_limit: + query_fragments.append("CONNECTION LIMIT %(conn_limit)s" % {"conn_limit": conn_limit}) query = ' '.join(query_fragments) cursor.execute(query, params) return True @@ -230,13 +251,19 @@ def db_create(cursor, db, owner, template, encoding, lc_collate, lc_ctype): 'Changing LC_CTYPE is not supported.' 'Current LC_CTYPE: %s' % db_info['lc_ctype'] ) - elif owner and owner != db_info['owner']: - return set_owner(cursor, db, owner) else: - return False + changed = False + + if owner and owner != db_info['owner']: + changed = set_owner(cursor, db, owner) + + if conn_limit and conn_limit != str(db_info['conn_limit']): + changed = set_conn_limit(cursor, db, conn_limit) + + return changed -def db_matches(cursor, db, owner, template, encoding, lc_collate, lc_ctype): +def db_matches(cursor, db, owner, template, encoding, lc_collate, lc_ctype, conn_limit): if not db_exists(cursor, db): return False else: @@ -250,6 +277,8 @@ def db_matches(cursor, db, owner, template, encoding, lc_collate, lc_ctype): return False elif owner and owner != db_info['owner']: return False + elif conn_limit and conn_limit != str(db_info['conn_limit']): + return False else: return True @@ -397,6 +426,7 @@ def main(): target_opts=dict(type='str', default=''), maintenance_db=dict(type='str', default="postgres"), session_role=dict(type='str'), + conn_limit=dict(type='str', default='') ) module = AnsibleModule( @@ -416,6 +446,7 @@ def main(): changed = False maintenance_db = module.params['maintenance_db'] session_role = module.params["session_role"] + conn_limit = module.params['conn_limit'] raw_connection = state in ("dump", "restore") @@ -481,7 +512,7 @@ def main(): if state == "absent": changed = db_exists(cursor, db) elif state == "present": - changed = not db_matches(cursor, db, owner, template, encoding, lc_collate, lc_ctype) + changed = not db_matches(cursor, db, owner, template, encoding, lc_collate, lc_ctype, conn_limit) module.exit_json(changed=changed, db=db) if state == "absent": @@ -492,7 +523,7 @@ def main(): elif state == "present": try: - changed = db_create(cursor, db, owner, template, encoding, lc_collate, lc_ctype) + changed = db_create(cursor, db, owner, template, encoding, lc_collate, lc_ctype, conn_limit) except SQLParseError as e: module.fail_json(msg=to_native(e), exception=traceback.format_exc()) diff --git a/test/integration/targets/postgresql/tasks/main.yml b/test/integration/targets/postgresql/tasks/main.yml index 8704ca73259..7f34dd7aabd 100644 --- a/test/integration/targets/postgresql/tasks/main.yml +++ b/test/integration/targets/postgresql/tasks/main.yml @@ -101,14 +101,15 @@ # that: "result.stdout_lines[-1] == '(0 rows)'" # -# Test encoding, collate, ctype, template options +# Test conn_limit, encoding, collate, ctype, template options # -- name: Create a DB with encoding, collate, ctype, and template options +- name: Create a DB with conn_limit, encoding, collate, ctype, and template options become_user: "{{ pg_user }}" become: True postgresql_db: name: '{{ db_name }}' state: 'present' + conn_limit: '100' encoding: 'LATIN1' lc_collate: 'pt_BR{{ locale_latin_suffix }}' lc_ctype: 'es_ES{{ locale_latin_suffix }}' @@ -118,7 +119,7 @@ - name: Check that the DB has all of our options become_user: "{{ pg_user }}" become: True - shell: echo "select datname, pg_encoding_to_char(encoding), datcollate, datctype from pg_database where datname = '{{ db_name }}';" | psql -d postgres + shell: echo "select datname, datconnlimit, pg_encoding_to_char(encoding), datcollate, datctype from pg_database where datname = '{{ db_name }}';" | psql -d postgres register: result - assert: @@ -129,6 +130,7 @@ - "'es_ES' in result.stdout_lines[-2]" - "'UTF8' not in result.stdout_lines[-2]" - "'en_US' not in result.stdout_lines[-2]" + - "'100' in result.stdout_lines[-2]" - name: Check that running db cration with options a second time does nothing become_user: "{{ pg_user }}" @@ -136,6 +138,7 @@ postgresql_db: name: '{{ db_name }}' state: 'present' + conn_limit: '100' encoding: 'LATIN1' lc_collate: 'pt_BR{{ locale_latin_suffix }}' lc_ctype: 'es_ES{{ locale_latin_suffix }}' @@ -166,6 +169,35 @@ that: - 'result.failed == True' +- name: Check that changing the conn_limit actually works + become_user: "{{ pg_user }}" + become: True + postgresql_db: + name: '{{ db_name }}' + state: 'present' + conn_limit: '200' + encoding: 'LATIN1' + lc_collate: 'pt_BR{{ locale_latin_suffix }}' + lc_ctype: 'es_ES{{ locale_latin_suffix }}' + template: 'template0' + login_user: "{{ pg_user }}" + register: result + +- assert: + that: + - 'result.changed == True' + +- name: Check that conn_limit has actually been set / updated to 200 + become_user: "{{ pg_user }}" + become: True + shell: echo "SELECT datconnlimit AS conn_limit FROM pg_database WHERE datname = '{{ db_name }}';" | psql -d postgres + register: result + +- assert: + that: + - "result.stdout_lines[-1] == '(1 row)'" + - "'200' == '{{ result.stdout_lines[-2] | trim }}'" + - name: Cleanup test DB become_user: "{{ pg_user }}" become: True