← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:charm-postgresql-extras into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:charm-postgresql-extras into launchpad:master.

Commit message:
charm: Add launchpad-postgresql-extras

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/450894

The current production databases have a few unusual quirks, applied using the `ols-postgresql-extras` subordinate charm.  This hasn't been open-sourced and has quite a lot of cruft, so I thought it would be best to have our own modernized version of it.  This charm should be suitable for use in all environments.

When related to the `postgresql` charm, this adds the following features:

 * Installs `launchpad-database-dependencies` (thereby pulling in various extensions we need).
 * Installs and configures `pgbouncer`, optionally reporting metrics to `telegraf` (requires https://code.launchpad.net/~cjwatson/charm-telegraf/+git/charm-telegraf/+merge/450883).
 * Optionally installs a cron job to push full database backups to another machine, which can be used for the weekly sync of staging from production.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:charm-postgresql-extras into launchpad:master.
diff --git a/charm/launchpad-postgresql-extras/README.md b/charm/launchpad-postgresql-extras/README.md
new file mode 100644
index 0000000..de51763
--- /dev/null
+++ b/charm/launchpad-postgresql-extras/README.md
@@ -0,0 +1,9 @@
+# Extra Launchpad PostgreSQL configuration
+
+The `launchpad-postgresql-extras` subordinate adds extra things needed on
+the Launchpad database units, particularly `pgbouncer` configuration and
+some refinements to backup handling.
+
+The following relation is useful:
+
+    juju relate postgresql:juju-info launchpad-postgresql-extras:juju-info
diff --git a/charm/launchpad-postgresql-extras/charmcraft.yaml b/charm/launchpad-postgresql-extras/charmcraft.yaml
new file mode 100644
index 0000000..bb8d8bc
--- /dev/null
+++ b/charm/launchpad-postgresql-extras/charmcraft.yaml
@@ -0,0 +1,47 @@
+type: charm
+bases:
+  - build-on:
+    - name: ubuntu
+      channel: "20.04"
+      architectures: [amd64]
+    run-on:
+    - name: ubuntu
+      channel: "20.04"
+      architectures: [amd64]
+parts:
+  charm-wheels:
+    source: https://git.launchpad.net/~ubuntuone-hackers/ols-charm-deps/+git/wheels
+    source-commit: "42c89d9c66dbe137139b047fd54aed49b66d1a5e"
+    source-submodules: []
+    source-type: git
+    plugin: dump
+    organize:
+      "*": charm-wheels/
+    prime:
+      - "-charm-wheels"
+  ols-layers:
+    source: https://git.launchpad.net/ols-charm-deps
+    source-commit: "f63ae0386275bf9089b30c8abae252a0ea523633"
+    source-submodules: []
+    source-type: git
+    plugin: dump
+    organize:
+      "*": layers/
+    stage:
+      - layers
+    prime:
+      - "-layers"
+  charm:
+    after:
+      - charm-wheels
+      - ols-layers
+    source: .
+    plugin: reactive
+    build-snaps: [charm]
+    build-packages: [libpq-dev, python3-dev]
+    build-environment:
+      - CHARM_LAYERS_DIR: $CRAFT_STAGE/layers/layer
+      - CHARM_INTERFACES_DIR: $CRAFT_STAGE/layers/interface
+      - PIP_NO_INDEX: "true"
+      - PIP_FIND_LINKS: $CRAFT_STAGE/charm-wheels
+    reactive-charm-build-arguments: [--binary-wheels-from-source]
diff --git a/charm/launchpad-postgresql-extras/config.yaml b/charm/launchpad-postgresql-extras/config.yaml
new file mode 100644
index 0000000..f9e9495
--- /dev/null
+++ b/charm/launchpad-postgresql-extras/config.yaml
@@ -0,0 +1,56 @@
+options:
+  cron_mailto:
+    type: string
+    description: Email address for output from cron jobs.
+    default: "error-reports@xxxxxxxxxxxxxx"
+  install_sources:
+    default: |
+      - ppa:launchpad/ppa
+  install_keys:
+    default: |
+      - null  # PPA keys securely added via Launchpad.
+  pgbouncer_db_config:
+    type: string
+    default: ""
+    description: Content of pgbouncer.ini's [databases] section.
+  pgbouncer_extra_config:
+    type: string
+    default: |
+      client_idle_timeout = 0
+      client_login_timeout = 60
+      default_pool_size = 50
+      idle_transaction_timeout = 90
+      ignore_startup_parameters = application_name,extra_float_digits
+      max_client_conn = 3000
+      pool_mode = transaction
+      reserve_pool_size = 0
+      reserve_pool_timeout = 1
+      server_check_delay = 30
+      server_connect_timeout = 15
+      server_idle_timeout = 600
+      server_login_retry = 15
+      stats_users = nagios,telegraf_stats
+    description: >
+      Extra pgbouncer configuration, included as-is from the main config file.
+  pgbouncer_port:
+    type: int
+    default: 5433
+    description: Port number for pgbouncer.
+  pgbouncer_userlist:
+    type: string
+    default: ""
+    description: Content of pgbouncer's userlist.txt.
+  push_backups_databases:
+    type: string
+    default: ""
+    description: >
+      Space-separated list of database names for which to push full backups.
+  push_backups_destination:
+    type: string
+    default: ""
+    description: If set, push full database backups here.
+  push_backups_private_ssh_key:
+    type: string
+    default: ""
+    description: >
+      Base64-encoded private SSH key to use to push full database backups.
diff --git a/charm/launchpad-postgresql-extras/files/pgbouncer_override.conf b/charm/launchpad-postgresql-extras/files/pgbouncer_override.conf
new file mode 100644
index 0000000..8313ac0
--- /dev/null
+++ b/charm/launchpad-postgresql-extras/files/pgbouncer_override.conf
@@ -0,0 +1,5 @@
+# This file is managed by the launchpad-postgresql-extras charm. If you
+# change this, backport changes to the branch or risk losing them next charm
+# upgrade.
+[Service]
+LimitNOFILE=65535
diff --git a/charm/launchpad-postgresql-extras/files/push-backups b/charm/launchpad-postgresql-extras/files/push-backups
new file mode 100755
index 0000000..8a48574
--- /dev/null
+++ b/charm/launchpad-postgresql-extras/files/push-backups
@@ -0,0 +1,80 @@
+#! /usr/bin/python3
+# Push backups to another machine.  This is useful for regularly restoring
+# staging from production.
+
+import glob
+import shlex
+import subprocess
+from argparse import ArgumentParser
+from pathlib import Path
+
+
+def find_latest_dumps(backups_path, database, count):
+    yield from sorted(backups_path.glob(f"{glob.escape(database)}.*.dump"))[
+        -count:
+    ]
+
+
+def main():
+    parser = ArgumentParser()
+    parser.add_argument(
+        "--backups-path",
+        type=Path,
+        default="/var/lib/postgresql/backups",
+        help=(
+            "Directory where backups are stored "
+            "(default: /var/lib/postgresql/backups)"
+        ),
+    )
+    parser.add_argument(
+        "--count",
+        type=int,
+        default=2,
+        help="Number of backups to push (default: 2)",
+    )
+    parser.add_argument("--bwlimit", help="Bandwidth limit")
+    parser.add_argument(
+        "--ssh-key",
+        help="SSH key to use to authenticate to destination system",
+    )
+    parser.add_argument(
+        "--dry-run",
+        default=False,
+        action="store_true",
+        help="Only show the command to run",
+    )
+    parser.add_argument(
+        "destination", help="Destination system, e.g. postgres@hostname:/path/"
+    )
+    parser.add_argument("databases", metavar="database", nargs="+")
+    args = parser.parse_args()
+
+    command = ["rsync"]
+    if args.bwlimit is not None:
+        command.append(f"--bwlimit={args.bwlimit}")
+    if args.ssh_key:
+        command.append(f"--rsh=ssh -i {shlex.quote(args.ssh_key)}")
+    else:
+        command.append("--rsh=ssh")
+    command.extend(["-a", "--delete-after"])
+    for database in args.databases:
+        for path in find_latest_dumps(args.backups_path, database, args.count):
+            command.extend(["--include", path.name])
+    command.extend(["--exclude", "*"])
+    # rsync wants a trailing slash on both source and destination.
+    # str(Path(...)) strips any trailing slash so we can add one
+    # unconditionally there; only add one to the destination if the command
+    # line didn't include it.
+    destination = args.destination
+    if not destination.endswith("/"):
+        destination += "/"
+    command.extend([f"{args.backups_path}/", destination])
+
+    if args.dry_run:
+        print("Would run:", " ".join(shlex.quote(arg) for arg in command))
+    else:
+        subprocess.run(command, check=True)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/charm/launchpad-postgresql-extras/icon.svg b/charm/launchpad-postgresql-extras/icon.svg
new file mode 100644
index 0000000..b2889cc
--- /dev/null
+++ b/charm/launchpad-postgresql-extras/icon.svg
@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg"; viewBox="0 0 165.39062 165.39062"><defs><style>.cls-1{fill:#e9500e;}.cls-2{fill:#fff;}</style></defs><rect class="cls-1" width="165.39062" height="165.39062"/><path class="cls-2" d="M29.63876,57.97189C43.189,67.692,61.13456,69.25577,77.65457,62.15038c16.25576-6.87157,27.74036-21.43444,29.97828-38.0075.04663-.34331.11016-.81367-1.59861-1.24044l-10.10934-2.197c-.3254-.04494-.79136-.04967-1.15258,1.22455C91.37844,36.07384,84.34062,45.04243,72.6347,50.1123c-11.77316,5.10029-23.18748,4.05279-35.91893-3.29386-.58119-.27843-.91909-.26086-1.45568.52577l-5.77947,8.65163A1.34512,1.34512,0,0,0,29.63876,57.97189Z" transform="translate(0.39062 0.39062)"/><path class="cls-2" d="M79.86106,139.66026l10.3631.565c1.74155.03446,1.79122-.42981,1.83717-.77312,2.23826-16.5734-4.97222-33.66107-18.81739-44.59422C59.196,83.62132,41.47815,80.36935,25.83365,86.14747a1.33956,1.33956,0,0,0-.67918,1.85373l3.28,9.88226c.30952.90153.62816,1.011,1.26443.89409,14.22464-3.70543,25.50717-1.68748,35.50635,6.3512,9.94174,7.9934,14.34865,18.50754,13.86883,33.08867C79.08524,139.50144,79.53735,139.615,79.86106,139.66026Z" transform="translate(0.39062 0.39062)"/><path class="cls-2" d="M86.50488,70.59048a10.50817,10.50817,0,0,0-1.39587-.09461A9.35237,9.35237,0,0,0,79.39915,72.382a9.61981,9.61981,0,1,0,7.10573-1.79156Z" transform="translate(0.39062 0.39062)"/><path class="cls-2" d="M138.26869,53.18923,133.457,43.97736c-.68628-1.51583-1.22793-1.36985-1.79594-1.17657-15.382,6.63165-25.99848,21.22156-28.40434,39.03776-2.40755,17.82971,3.97169,34.72681,17.0647,45.19906a1.177,1.177,0,0,0,.90794.32844,1.48362,1.48362,0,0,0,.99546-.54l6.76175-8.11166c.62342-.78393.35783-1.18333.0321-1.52461-10.60639-10.44454-14.5764-20.81677-12.84905-33.60769,1.73682-12.86121,8.51918-22.08254,21.34457-29.019C138.52854,53.95289,138.36533,53.421,138.26869,53.18923Z" transform="translate(0.39062 0.39062)"/></svg>
\ No newline at end of file
diff --git a/charm/launchpad-postgresql-extras/layer.yaml b/charm/launchpad-postgresql-extras/layer.yaml
new file mode 100644
index 0000000..879d9e6
--- /dev/null
+++ b/charm/launchpad-postgresql-extras/layer.yaml
@@ -0,0 +1,10 @@
+includes:
+  - layer:basic
+  - layer:apt
+  - interface:pgsql
+repo: https://git.launchpad.net/launchpad
+options:
+  apt:
+    packages:
+      - launchpad-database-dependencies
+      - pgbouncer
diff --git a/charm/launchpad-postgresql-extras/metadata.yaml b/charm/launchpad-postgresql-extras/metadata.yaml
new file mode 100644
index 0000000..af95541
--- /dev/null
+++ b/charm/launchpad-postgresql-extras/metadata.yaml
@@ -0,0 +1,20 @@
+name: launchpad-postgresql-extras
+display-name: launchpad-postgresql-extras
+summary: Customization for Launchpad's PostgreSQL services
+maintainer: Launchpad Developers <launchpad-dev@xxxxxxxxxxxxxxxxxxx>
+description: |
+  Launchpad is an open source suite of tools that help people and teams
+  to work together on software projects.
+
+  This charm adds pgbouncer and other necessary customizations to
+  Launchpad's PostgreSQL units.
+subordinate: true
+requires:
+  juju-info:
+    interface: juju-info
+    scope: container
+provides:
+  # For this to work, the pgbouncer_userlist option must include a line of
+  # the form `"telegraf_stats" "PASSWORD"`.
+  telegraf-stats:
+    interface: pgsql
diff --git a/charm/launchpad-postgresql-extras/reactive/launchpad-postgresql-extras.py b/charm/launchpad-postgresql-extras/reactive/launchpad-postgresql-extras.py
new file mode 100644
index 0000000..030a6df
--- /dev/null
+++ b/charm/launchpad-postgresql-extras/reactive/launchpad-postgresql-extras.py
@@ -0,0 +1,206 @@
+# Copyright 2022 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+import base64
+import os.path
+import re
+import shutil
+import subprocess
+from pathlib import Path
+from typing import Dict
+
+from charmhelpers.core import hookenv, host, templating
+from charms.reactive import (
+    clear_flag,
+    endpoint_from_flag,
+    hook,
+    set_flag,
+    when,
+    when_not,
+    when_not_all,
+)
+from psycopg2.extensions import make_dsn
+
+
+def install_scripts(config):
+    hookenv.log("Installing scripts.")
+    host.mkdir(config["scripts_dir"], perms=0o755)
+    shutil.copy2(
+        "files/push-backups", Path(config["scripts_dir"], "push-backups")
+    )
+    if config["push_backups_private_ssh_key"]:
+        postgres_ssh = Path(os.path.expanduser("~postgres"), ".ssh")
+        host.mkdir(
+            postgres_ssh, owner="postgres", group="postgres", perms=0o700
+        )
+        config["push_backups_private_ssh_key_path"] = str(
+            postgres_ssh / "id-launchpad-postgresql-extras"
+        )
+        host.write_file(
+            config["push_backups_private_ssh_key_path"],
+            base64.b64decode(config["push_backups_private_ssh_key"]),
+            owner="postgres",
+            group="postgres",
+            perms=0o600,
+        )
+
+
+def reload_or_restart_pgbouncer(service):
+    subprocess.run(
+        ["systemctl", "reload-or-restart", "pgbouncer.service"], check=True
+    )
+
+
+pgbouncer_config = Path("/etc/pgbouncer/pgbouncer.ini")
+pgbouncer_databases = Path("/etc/pgbouncer/databases.ini")
+pgbouncer_extra_config = Path("/etc/pgbouncer/extra_config.txt")
+pgbouncer_userlist = Path("/etc/pgbouncer/userlist.txt")
+pgbouncer_override = Path(
+    "/etc/systemd/system/pgbouncer.service.d/override.conf"
+)
+
+
+@host.restart_on_change(
+    {
+        str(pgbouncer_config): ["reload-pgbouncer"],
+        str(pgbouncer_databases): ["reload-pgbouncer"],
+        str(pgbouncer_extra_config): ["reload-pgbouncer"],
+        str(pgbouncer_userlist): ["reload-pgbouncer"],
+        str(pgbouncer_override): ["pgbouncer.service"],
+    },
+    restart_functions={"reload-pgbouncer": reload_or_restart_pgbouncer},
+)
+def configure_pgbouncer(config):
+    hookenv.log("Configuring pgbouncer.")
+    templating.render(
+        "pgbouncer.ini.j2",
+        pgbouncer_config,
+        config,
+        owner="postgres",
+        group="postgres",
+        perms=0o644,
+    )
+    host.write_file(
+        pgbouncer_databases,
+        config["pgbouncer_db_config"],
+        owner="postgres",
+        group="postgres",
+        perms=0o644,
+    )
+    host.write_file(
+        pgbouncer_extra_config,
+        config["pgbouncer_extra_config"],
+        owner="postgres",
+        group="postgres",
+        perms=0o644,
+    )
+    host.write_file(
+        pgbouncer_userlist,
+        config["pgbouncer_userlist"],
+        owner="postgres",
+        group="postgres",
+        perms=0o640,
+    )
+    host.mkdir(pgbouncer_override.parent, perms=0o755)
+    shutil.copy2("files/pgbouncer_override.conf", pgbouncer_override)
+    subprocess.run(["systemctl", "daemon-reload"], check=True)
+    hookenv.open_port(config["pgbouncer_port"])
+
+
+def configure_cron(config):
+    hookenv.log("Writing crontab.")
+    templating.render(
+        "crontab.j2",
+        "/etc/cron.d/launchpad-postgresql-extras",
+        config,
+        perms=0o644,
+    )
+
+
+@when_not("apt.queued_installs", "service.configured")
+def configure():
+    config = dict(hookenv.config())
+
+    config["scripts_dir"] = "/srv/launchpad/scripts"
+    install_scripts(config)
+
+    configure_pgbouncer(config)
+    host.service_resume("pgbouncer.service")
+    configure_cron(config)
+
+    set_flag("service.configured")
+
+
+@when("service.configured")
+def check_is_running():
+    hookenv.status_set("active", "Ready")
+
+
+@hook("upgrade-charm")
+def upgrade_charm():
+    clear_flag("service.configured")
+
+
+@when("config.changed")
+def config_changed():
+    clear_flag("service.configured")
+
+
+def parse_userlist(userlist: str) -> Dict[str, str]:
+    """Parse a pgbouncer userlist file.
+
+    This is a deliberately forgiving parser; we only need enough to extract
+    the telegraf_stats user's password.  See the pgbouncer(5) manual page
+    for the format.
+    """
+    credentials = {}
+    for line in userlist.splitlines():
+        m = re.match(r'"([^"]*)" "([^"]*)".*', line)
+        if m is not None:
+            credentials[m.group(1)] = m.group(2)
+    return credentials
+
+
+@when("service.configured", "telegraf-stats.connected")
+@when_not("service.telegraf-stats.configured")
+def configure_telegraf_stats():
+    config = hookenv.config()
+    pgsql = endpoint_from_flag("telegraf-stats.connected")
+    credentials = parse_userlist(config["pgbouncer_userlist"])
+    telegraf_stats_password = credentials.get("telegraf_stats")
+    if not telegraf_stats_password:
+        hookenv.status_set(
+            "blocked", "No telegraf_stats password in pgbouncer_userlist"
+        )
+        return
+    for relation in pgsql.relations:
+        relation.to_publish_raw.update(
+            {
+                "allowed-subnets": ",".join(
+                    sorted(
+                        {
+                            subnet: True
+                            for subnet in hookenv.egress_subnets(
+                                relation.relation_id, hookenv.local_unit()
+                            )
+                        }
+                    )
+                ),
+                "master": make_dsn(
+                    database="pgbouncer",
+                    host=hookenv.ingress_address(
+                        relation.relation_id, hookenv.local_unit()
+                    ),
+                    password=telegraf_stats_password,
+                    port=config["pgbouncer_port"],
+                    user="telegraf_stats",
+                ),
+            }
+        )
+    set_flag("service.telegraf-stats.configured")
+
+
+@when("service.telegraf-stats.configured")
+@when_not_all("service.configured", "telegraf-stats.connected")
+def deconfigure_telegraf_stats():
+    clear_flag("service.telegraf-stats.configured")
diff --git a/charm/launchpad-postgresql-extras/templates/crontab.j2 b/charm/launchpad-postgresql-extras/templates/crontab.j2
new file mode 100644
index 0000000..f873f31
--- /dev/null
+++ b/charm/launchpad-postgresql-extras/templates/crontab.j2
@@ -0,0 +1,9 @@
+TZ=UTC
+MAILTO={{ cron_mailto }}
+
+{% if push_backups_databases and push_backups_destination -%}
+# The PostgreSQL charm handles backups, but on production we also need to
+# push backups to staging for weekly restores.
+22 * * * * postgres	{{ scripts_dir }}/push-backups --bwlimit=10m {% if push_backups_private_ssh_key_path %}--ssh-key {{ push_backups_private_ssh_key_path }} {% endif %}{{ push_backups_databases }} {{ push_backups_destination }}
+{% endif %}
+
diff --git a/charm/launchpad-postgresql-extras/templates/pgbouncer.ini.j2 b/charm/launchpad-postgresql-extras/templates/pgbouncer.ini.j2
new file mode 100644
index 0000000..aecd935
--- /dev/null
+++ b/charm/launchpad-postgresql-extras/templates/pgbouncer.ini.j2
@@ -0,0 +1,209 @@
+;; This file is maintained by Juju.
+;; lp:launchpad-postgresql-extras
+;;
+;; Edits may be made in place, but will be overwritten next
+;; time the charm is upgraded. If you make edits in place,
+;; make sure they are synced back to the above branch.
+;;
+;; We run pgbouncer with identical configuration on both primary
+;; and standbys. After failover or switchover, the hardcoded DNS 
+;; names will need to be changed to match.
+
+
+%include /etc/pgbouncer/databases.ini
+
+[pgbouncer]
+
+logfile = /var/log/postgresql/pgbouncer.log
+pidfile = /var/run/postgresql/pgbouncer.pid
+
+listen_addr = *
+listen_port = {{ pgbouncer_port }}
+
+; unix socket is also used for -R.
+; On debian it should be /var/run/postgresql
+;unix_socket_dir = /tmp
+;unix_socket_mode = 0777
+;unix_socket_group =
+unix_socket_dir = /var/run/postgresql
+
+;;;
+;;; Authentication settings
+;;;
+
+; any, trust, plain, crypt, md5
+auth_type = md5
+;auth_file = /8.0/main/global/pg_auth
+auth_file = /etc/pgbouncer/userlist.txt
+
+; comma-separated list of users, who are allowed to change settings
+admin_users = pgbouncer,canonicaladmin
+
+; comma-separated list of users who are just allowed to use SHOW command
+;stats_users = nagios,telegraf_stats
+
+;;;
+;;; Pooler personality questions
+;;;
+
+; When server connection is released back to pool:
+;   session      - after client disconnects
+;   transaction  - after transaction finishes
+;   statement    - after statement finishes
+
+; pool_mode transaction assumes we don't use certain features of PG
+; See https://wiki.canonical.com/UbuntuOne/OperationsPlus/PoolingDBConnections#SQL feature map for pooling modes
+; This can now be overridden per database.
+;pool_mode = transaction
+
+;
+; Query for cleaning connection immediately after releasing from client.
+; No need to put ROLLBACK here, pgbouncer does not reuse connections
+; where transaction is left open.
+;
+; Query for 8.3+:
+;   DISCARD ALL;
+;
+; Older versions:
+;   RESET ALL; SET SESSION AUTHORIZATION DEFAULT
+;
+; Empty if transaction pooling is in use.
+;
+server_reset_query = DISCARD ALL
+
+;
+; Comma-separated list of parameters to ignore when given
+; in startup packet.  Newer JDBC versions require the
+; extra_float_digits here.
+;
+;ignore_startup_parameters = application_name,extra_float_digits
+
+;
+; When taking idle server into use, this query is ran first.
+;   SELECT 1
+;
+server_check_query = select 1
+
+; If server was used more recently that this many seconds ago,
+; skip the check query.  Value 0 may or may not run in immediately.
+;server_check_delay = 30
+
+;;;
+;;; Connection limits
+;;;
+
+; total number of clients that can connect
+;max_client_conn = 3000
+
+; default pool size.  20 is good number when transaction pooling
+; is in use, in session pooling it needs to be the number of
+; max clients you want to handle at any moment
+;default_pool_size = 50
+
+; how many additional connection to allow in case of trouble
+;reserve_pool_size = 0
+
+; if a clients needs to wait more than this many seconds, use reserve pool
+;reserve_pool_timeout = 1
+
+; log if client connects or server connection is made
+;log_connections = 1
+
+; log if and why connection was closed
+;log_disconnections = 1
+
+; log error messages pooler sends to clients
+;log_pooler_errors = 1
+
+
+; If off, then server connections are reused in LIFO manner
+;server_round_robin = 0
+
+;;;
+;;; Timeouts
+;;;
+
+;; Close server connection if its been connected longer.
+server_lifetime = 3600
+
+;; Close server connection if its not been used in this time.
+;; Allows to clean unnecessary connections from pool after peak.
+;server_idle_timeout = 600
+
+;; Cancel connection attempt if server does not answer takes longer.
+;server_connect_timeout = 15
+
+;; If server login failed (server_connect_timeout or auth failure)
+;; then wait this many second.
+;server_login_retry = 15
+
+;; Dangerous.  Server connection is closed if query does not return
+;; in this time.  Should be used to survive network problems,
+;; _not_ as statement_timeout. (default: 0)
+;query_timeout = 0
+
+;; Dangerous.  Client connection is closed if the query is not assigned
+;; to a server in this time.  Should be used to limit the number of queued
+;; queries in case of a database or network failure. (default: 0)
+;query_wait_timeout = 0
+
+;; Dangerous.  Client connection is closed if no activity in this time.
+;; Should be used to survive network problems. (default: 0)
+;client_idle_timeout = 0
+
+;; Disconnect clients who have not managed to log in after connecting
+;; in this many seconds.
+;client_login_timeout = 60
+
+;; Disconnect clients idling inside a transaction for this long. This
+;; is required to stop misbehaving clients victimizing other clients.
+;idle_transaction_timeout = 90
+
+;; Clean automatically created database entries (via "*") if they
+;; stay unused in this many seconds.
+; autodb_idle_timeout = 3600
+
+;;;
+;;; Low-level tuning options
+;;;
+
+;; buffer for streaming packets
+;pkt_buf = 2048
+
+;; man 2 listen
+;listen_backlog = 128
+
+;; networking options, for info: man 7 tcp
+
+;; Linux: notify program about new connection only if there
+;; is also data received.  (Seconds to wait.)
+;; On Linux the default is 45, on other OS'es 0.
+;tcp_defer_accept = 0
+
+;; In-kernel buffer size (Linux default: 4096)
+;tcp_socket_buffer = 0
+
+;; whether tcp keepalive should be turned on (0/1)
+;tcp_keepalive = 1
+
+;; following options are Linux-specific.
+;; they also require tcp_keepalive=1
+
+;; count of keepalive packets
+;tcp_keepcnt = 0
+
+;; how long the connection can be idle,
+;; before sending keepalive packets
+;tcp_keepidle = 0
+
+;; The time between individual keepalive probes.
+;tcp_keepintvl = 0
+
+;; DNS lookup caching time
+;dns_max_ttl = 15
+
+;; DNS zone SOA lookup period
+;dns_zone_check_period = 0
+
+%include /etc/pgbouncer/extra_config.txt
+
diff --git a/charm/launchpad-postgresql-extras/wheelhouse.txt b/charm/launchpad-postgresql-extras/wheelhouse.txt
new file mode 100644
index 0000000..f0b5222
--- /dev/null
+++ b/charm/launchpad-postgresql-extras/wheelhouse.txt
@@ -0,0 +1 @@
+psycopg2==2.8.4