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

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