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
This commit is contained in:
Christian Rohmann 2019-03-22 13:51:39 +01:00 committed by Abhijeet Kasurde
parent 768bf5844a
commit 90c092a104
3 changed files with 77 additions and 12 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- postgresql_db - Added paramter conn_limit to limit the number of concurrent connection to a certain database

View file

@ -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())

View file

@ -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