Merge ~stub/postgresql-charm:upgrade-series into postgresql-charm:master

Proposed by Stuart Bishop
Status: Merged
Merged at revision: a1d109cbbfb999f9c5715e268cd0226668f7d8cf
Proposed branch: ~stub/postgresql-charm:upgrade-series
Merge into: postgresql-charm:master
Diff against target: 476 lines (+114/-125)
7 files modified
Makefile (+2/-19)
reactive/postgresql/postgresql.py (+21/-4)
reactive/postgresql/service.py (+32/-9)
reactive/postgresql/upgrade.py (+6/-0)
requirements.txt (+3/-2)
tox.ini (+4/-3)
unit_tests/test_postgresql.py (+46/-88)
Reviewer Review Type Date Requested Status
Barry Price Approve
Canonical IS Reviewers Pending
PostgreSQL Charm Maintainers Pending
Review via email: mp+395355@code.launchpad.net

Commit message

upgrade-series support, resurrect unit tests

The charm would already probably survive a series upgrade, but
these changes should ensure the service always gets back up on
its feet. PostgreSQL major version upgrades might even work too,
although that would be better left to an action.

Also gets the unit tests back running, which had atrophied.
They are still rather awkward to run (they need to run in the
built charm directory), but are passing again.

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Barry Price (barryprice) wrote :

Couple of nitpick questions on the Makefile, otherwise LGTM

review: Approve
Revision history for this message
Barry Price (barryprice) wrote :

Actually save the second of those questions :)

Revision history for this message
Stuart Bishop (stub) :
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change cannot be self approved, setting status to needs review.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/Makefile b/Makefile
index b05ce5b..089e02e 100644
--- a/Makefile
+++ b/Makefile
@@ -27,24 +27,7 @@ _success_ex:
27 true | ts27 true | ts
2828
2929
30# Munge the path so the requested version of Juju is found, and thus used30test: lint unittest integration
31# by Amulet and juju-deployer.
32export PATH := /usr/lib/juju-$(shell $(JUJU) --version | perl -p -e "s/-.*//")/bin:$(PATH)
33
34
35test: testdeps lint unittest integration
36
37testdeps:
38ifeq ($(HOST_SERIES),trusty)
39 sudo apt-get install -y python-tox python3-psycopg2 bzr moreutils \
40 software-properties-common
41else
42 sudo apt-get install -y tox python3-psycopg2 bzr moreutils \
43 software-properties-common
44endif
45 sudo add-apt-repository -y ppa:juju/stable
46 sudo apt-get install charm-tools
47
4831
49CHARM_NAME := postgresql32CHARM_NAME := postgresql
5033
@@ -69,7 +52,7 @@ $(BUILD_DIR):
69# updates.52# updates.
70.PHONY: build53.PHONY: build
71build: | $(BUILD_DIR)54build: | $(BUILD_DIR)
72 charm build -f -o $(BUILD_ROOT) -n $(CHARM_NAME)55 charm build -f -d $(BUILD_DIR)/.. -n $(CHARM_NAME)
7356
74# Generate a fresh development build and commit it to $(TEST_BRANCH).57# Generate a fresh development build and commit it to $(TEST_BRANCH).
75# Only builds work committed to $(LAYER_BRANCH).58# Only builds work committed to $(LAYER_BRANCH).
diff --git a/reactive/postgresql/postgresql.py b/reactive/postgresql/postgresql.py
index 373080d..35d3e02 100644
--- a/reactive/postgresql/postgresql.py
+++ b/reactive/postgresql/postgresql.py
@@ -79,6 +79,13 @@ def version():
79 if version:79 if version:
80 return version80 return version
8181
82 # If no cached version, use the version reported by
83 # pg_lsclusters
84 version = lsclusters_version()
85 if version:
86 unitdata.kv().set("postgresql.pg_version", version)
87 return version
88
82 # We use the charm configuration here, as multiple versions89 # We use the charm configuration here, as multiple versions
83 # of PostgreSQL may be installed.90 # of PostgreSQL may be installed.
84 config = hookenv.config()91 config = hookenv.config()
@@ -98,6 +105,19 @@ def version():
98 return version105 return version
99106
100107
108def clear_version_cache():
109 unitdata.kv().set("postgresql.pg_version", None)
110
111
112def lsclusters_version():
113 if not os.path.exists("/usr/bin/pg_lsclusters"):
114 return None
115 results = subprocess.check_output(["pg_lsclusters", "--no-header"]).decode().splitlines()
116 if len(results) == 1:
117 return results[0].split()[0]
118 return None
119
120
101def point_version():121def point_version():
102 """PostgreSQL version. major.minor.patch or major.patch, as a string."""122 """PostgreSQL version. major.minor.patch or major.patch, as a string."""
103 output = subprocess.check_output([postgres_path(), "-V"], universal_newlines=True)123 output = subprocess.check_output([postgres_path(), "-V"], universal_newlines=True)
@@ -473,10 +493,7 @@ def ensure_role(con, role):
473def ensure_extensions(con, extensions):493def ensure_extensions(con, extensions):
474 """extensions is a list of (name, schema) tuples"""494 """extensions is a list of (name, schema) tuples"""
475 cur = con.cursor()495 cur = con.cursor()
476 cur.execute(496 cur.execute("SELECT extname, nspname FROM pg_extension, pg_namespace WHERE pg_namespace.oid = extnamespace")
477 """SELECT extname,nspname FROM pg_extension,pg_namespace
478 WHERE pg_namespace.oid = extnamespace"""
479 )
480 installed_extensions = frozenset((x[0], x[1]) for x in cur.fetchall())497 installed_extensions = frozenset((x[0], x[1]) for x in cur.fetchall())
481 hookenv.log("ensure_extensions({}), have {}".format(extensions, installed_extensions), DEBUG)498 hookenv.log("ensure_extensions({}), have {}".format(extensions, installed_extensions), DEBUG)
482 extensions_set = frozenset(set(extensions))499 extensions_set = frozenset(set(extensions))
diff --git a/reactive/postgresql/service.py b/reactive/postgresql/service.py
index b7269b1..f5e1936 100644
--- a/reactive/postgresql/service.py
+++ b/reactive/postgresql/service.py
@@ -45,6 +45,18 @@ def install():
45 reactive.set_flag("postgresql.upgrade.systemd")45 reactive.set_flag("postgresql.upgrade.systemd")
4646
4747
48@hook("post-series-upgrade")
49def post_series_upgrade():
50 postgresql.clear_version_cache() # PG version upgrades should work on the master, but will break standbys
51 config = hookenv.config()
52 if config["pgdg"]:
53 add_pgdg_source()
54 if host.init_is_systemd():
55 reactive.set_flag("postgresql.upgrade.systemd")
56 reactive.clear_flag("postgresql.cluster.support-scripts")
57 reactive.clear_flag("postgresql.cluster.configured")
58
59
48@when("config.changed")60@when("config.changed")
49def config_changed():61def config_changed():
50 reactive.clear_flag("postgresql.cluster.support-scripts")62 reactive.clear_flag("postgresql.cluster.support-scripts")
@@ -143,12 +155,23 @@ def configure_sources():
143155
144 # Shortcut for the PGDG archive.156 # Shortcut for the PGDG archive.
145 if config["pgdg"]:157 if config["pgdg"]:
146 pgdg_url = "http://apt.postgresql.org/pub/repos/apt/"158 add_pgdg_source()
147 pgdg_src = "deb {} {}-pgdg main".format(pgdg_url, helpers.distro_codename())159
148 pgdg_key_path = os.path.join(hookenv.charm_dir(), "lib", "pgdg.key")160
161def add_pgdg_source():
162 pgdg_url = "http://apt.postgresql.org/pub/repos/apt/"
163 pgdg_src = "deb {} {}-pgdg main".format(pgdg_url, helpers.distro_codename())
164 pgdg_key_path = os.path.join(hookenv.charm_dir(), "lib", "pgdg.key")
165 kv = unitdata.kv()
166 k = "postgresql.service.pgdg_src"
167 # Add the source if the key has changed, or if the src has
168 # changed such as a distro upgrade. Check key change first,
169 # or short circuiting will add the source twice.
170 if reactive.helpers.any_file_changed([pgdg_key_path]) or pgdg_src != kv.get(k):
149 with open(pgdg_key_path, "r") as f:171 with open(pgdg_key_path, "r") as f:
150 hookenv.log("Adding PGDG archive")172 hookenv.log("Adding PGDG archive")
151 apt.add_source(pgdg_src, f.read())173 apt.add_source(pgdg_src, f.read())
174 kv.set(k, pgdg_src)
152175
153176
154@when("postgresql.cluster.locale.set")177@when("postgresql.cluster.locale.set")
@@ -192,13 +215,13 @@ def create_cluster():
192def create_pg_ctl_conf():215def create_pg_ctl_conf():
193 contents = textwrap.dedent(216 contents = textwrap.dedent(
194 """\217 """\
195 # Managed by Juju218 # Managed by Juju
196 # Automatic pg_ctl configuration219 # Automatic pg_ctl configuration
197 # This configuration file contains cluster specific options to be passed to220 # This configuration file contains cluster specific options to be passed to
198 # pg_ctl(1).221 # pg_ctl(1).
199222
200 pg_ctl_options = '-w -t 3600'223 pg_ctl_options = '-w -t 3600'
201 """224 """
202 )225 )
203 helpers.write(226 helpers.write(
204 postgresql.pg_ctl_conf_path(),227 postgresql.pg_ctl_conf_path(),
diff --git a/reactive/postgresql/upgrade.py b/reactive/postgresql/upgrade.py
index 9379c60..d056319 100644
--- a/reactive/postgresql/upgrade.py
+++ b/reactive/postgresql/upgrade.py
@@ -26,6 +26,7 @@ from reactive import workloadstatus
26from reactive.postgresql import helpers26from reactive.postgresql import helpers
27from reactive.postgresql import postgresql27from reactive.postgresql import postgresql
28from reactive.postgresql import replication28from reactive.postgresql import replication
29from reactive.postgresql import service
29from reactive.postgresql import storage30from reactive.postgresql import storage
3031
3132
@@ -134,6 +135,11 @@ def upgrade_charm():
134 reactive.clear_flag("postgresql.cluster.is_running")135 reactive.clear_flag("postgresql.cluster.is_running")
135 postgresql.stop_pgctlcluster()136 postgresql.stop_pgctlcluster()
136137
138 # Update the PGDG source, in case the signing key has changed.
139 config = hookenv.config()
140 if config["pgdg"]:
141 service.add_pgdg_source()
142
137143
138def migrate_user(old_username, new_username, password, superuser=False):144def migrate_user(old_username, new_username, password, superuser=False):
139 if postgresql.is_primary():145 if postgresql.is_primary():
diff --git a/requirements.txt b/requirements.txt
index 8aabe77..675a41a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -11,5 +11,6 @@ python-swiftclient
11charms.reactive11charms.reactive
12charmhelpers12charmhelpers
13juju-deployer13juju-deployer
14pip<8.1.214pip
15websocket-client<=0.40.015websocket-client
16tenacity
diff --git a/tox.ini b/tox.ini
index 71565ed..cb30fb6 100644
--- a/tox.ini
+++ b/tox.ini
@@ -29,9 +29,10 @@ commands=
29basepython=python329basepython=python3
30# pip install -I required to ensure scripts are created30# pip install -I required to ensure scripts are created
31# even if packages are already installed on the system31# even if packages are already installed on the system
32install_command=pip install -I {opts} {packages}32sitepackages=False
33sitepackages=True33deps =
34deps = -r{toxinidir}/requirements.txt34 -r{toxinidir}/requirements.txt
35 psycopg2
35passenv=JUJU_*36passenv=JUJU_*
36commands=37commands=
37 pytest {posargs:--verbose --tb=native unit_tests/}38 pytest {posargs:--verbose --tb=native unit_tests/}
diff --git a/unit_tests/test_postgresql.py b/unit_tests/test_postgresql.py
index 5524745..95cf828 100644
--- a/unit_tests/test_postgresql.py
+++ b/unit_tests/test_postgresql.py
@@ -26,47 +26,47 @@ sys.path.insert(1, ROOT)
26sys.path.insert(2, os.path.join(ROOT, 'lib'))26sys.path.insert(2, os.path.join(ROOT, 'lib'))
27sys.path.insert(3, os.path.join(ROOT, 'lib', 'testdeps'))27sys.path.insert(3, os.path.join(ROOT, 'lib', 'testdeps'))
2828
29from charmhelpers.core import hookenv29from charmhelpers.core import hookenv, host
30from charmhelpers.core import unitdata
31
32from reactive import workloadstatus30from reactive import workloadstatus
33from reactive.postgresql import helpers31from reactive.postgresql import helpers, postgresql
34from reactive.postgresql import postgresql
3532
3633
37class TestPostgresql(unittest.TestCase):34class TestPostgresql(unittest.TestCase):
35 @patch.object(postgresql, 'lsclusters_version')
38 @patch.object(hookenv, 'config')36 @patch.object(hookenv, 'config')
39 @patch.object(helpers, 'distro_codename')37 @patch.object(helpers, 'distro_codename')
40 def test_version(self, codename, config):38 def test_version(self, codename, config, lsclusters_version):
4139 # Installed version
42 def clear_cache():40 lsclusters_version.return_value = '9.5'
43 unitdata.kv().unset('postgresql.pg_version')41 postgresql.clear_version_cache()
42 self.assertEqual(postgresql.version(), '9.5')
43 lsclusters_version.return_value = None
4444
45 # Explicit version in config.45 # Explicit version in config.
46 config.return_value = {'version': '23'}46 config.return_value = {'version': '23'}
47 clear_cache()47 postgresql.clear_version_cache()
48 self.assertEqual(postgresql.version(), '23')48 self.assertEqual(postgresql.version(), '23')
4949
50 config.return_value = {'version': ''}50 config.return_value = {'version': ''}
5151
52 # Trusty default52 # Trusty default
53 codename.return_value = 'trusty'53 codename.return_value = 'trusty'
54 clear_cache()54 postgresql.clear_version_cache()
55 self.assertEqual(postgresql.version(), '9.3')55 self.assertEqual(postgresql.version(), '9.3')
5656
57 # Xenial default57 # Xenial default
58 codename.return_value = 'xenial'58 codename.return_value = 'xenial'
59 clear_cache()59 postgresql.clear_version_cache()
60 self.assertEqual(postgresql.version(), '9.5')60 self.assertEqual(postgresql.version(), '9.5')
6161
62 # Bionic default62 # Bionic default
63 codename.return_value = 'bionic'63 codename.return_value = 'bionic'
64 clear_cache()64 postgresql.clear_version_cache()
65 self.assertEqual(postgresql.version(), '10')65 self.assertEqual(postgresql.version(), '10')
6666
67 # No other fallbacks, yet.67 # No other fallbacks, yet.
68 codename.return_value = 'whatever'68 codename.return_value = 'whatever'
69 clear_cache()69 postgresql.clear_version_cache()
70 with self.assertRaises(NotImplementedError):70 with self.assertRaises(NotImplementedError):
71 postgresql.version()71 postgresql.version()
7272
@@ -251,8 +251,11 @@ class TestPostgresql(unittest.TestCase):
251 is_secondary.return_value = False251 is_secondary.return_value = False
252 self.assertTrue(postgresql.is_primary())252 self.assertTrue(postgresql.is_primary())
253253
254 @patch.object(postgresql, 'has_version')
255 @patch.object(hookenv, 'config')
254 @patch.object(postgresql, 'recovery_conf_path')256 @patch.object(postgresql, 'recovery_conf_path')
255 def test_is_secondary(self, recovery_path):257 def test_is_secondary(self, recovery_path, config, has_version):
258 has_version.return_value = False
256 # if recovery.conf exists, we are a secondary.259 # if recovery.conf exists, we are a secondary.
257 with tempfile.NamedTemporaryFile() as f:260 with tempfile.NamedTemporaryFile() as f:
258 recovery_path.return_value = f.name261 recovery_path.return_value = f.name
@@ -317,7 +320,7 @@ class TestPostgresql(unittest.TestCase):
317 check_call.assert_called_once_with(['pg_createcluster',320 check_call.assert_called_once_with(['pg_createcluster',
318 '-e', sentinel.encoding,321 '-e', sentinel.encoding,
319 '--locale', sentinel.locale,322 '--locale', sentinel.locale,
320 '9.2', 'main'],323 '9.2', 'main', '--', '--data-checksums'],
321 universal_newlines=True)324 universal_newlines=True)
322325
323 @patch('subprocess.check_call')326 @patch('subprocess.check_call')
@@ -345,7 +348,7 @@ class TestPostgresql(unittest.TestCase):
345 cur.execute.assert_has_calls([348 cur.execute.assert_has_calls([
346 call('SELECT datname FROM pg_database WHERE datname=%s',349 call('SELECT datname FROM pg_database WHERE datname=%s',
347 ('hello',)),350 ('hello',)),
348 call('CREATE DATABASE %s', (ANY,))])351 call('CREATE DATABASE %s OWNER %s', (ANY, ANY))])
349 # The database name in that last call was correctly quoted.352 # The database name in that last call was correctly quoted.
350 quoted_dbname = cur.execute.call_args[0][1][0]353 quoted_dbname = cur.execute.call_args[0][1][0]
351 self.assertIsInstance(quoted_dbname, postgresql.AsIs)354 self.assertIsInstance(quoted_dbname, postgresql.AsIs)
@@ -475,14 +478,16 @@ class TestPostgresql(unittest.TestCase):
475478
476 pgidentifier.side_effect = lambda d: 'q_{}'.format(d)479 pgidentifier.side_effect = lambda d: 'q_{}'.format(d)
477480
478 existing_extensions = set(['extA', 'extB'])481 existing_extensions = set([('extA', 'public'), ('extB', 'public')])
479 wanted_extensions = set(['extB', 'extC'])482 wanted_extensions = set([('extB', 'public'), ('extC', 'custom')])
480483
481 cur.fetchall.return_value = [[x] for x in existing_extensions]484 cur.fetchall.return_value = [x for x in existing_extensions]
482 postgresql.ensure_extensions(con, wanted_extensions)485 postgresql.ensure_extensions(con, wanted_extensions)
483 cur.execute.assert_has_calls([486 cur.execute.assert_has_calls([
484 call('SELECT extname FROM pg_extension'),487 call('SELECT extname, nspname FROM pg_extension, pg_namespace WHERE pg_namespace.oid = extnamespace'),
485 call('CREATE EXTENSION %s', ('q_extC',))])488 call('CREATE SCHEMA IF NOT EXISTS %s', ('q_custom',)),
489 call('GRANT USAGE ON SCHEMA %s TO PUBLIC', ('q_custom',)),
490 call('CREATE EXTENSION %s WITH SCHEMA %s', ('q_extC','q_custom'))])
486491
487 def test_addr_to_range(self):492 def test_addr_to_range(self):
488 eggs = [('hostname', 'hostname'),493 eggs = [('hostname', 'hostname'),
@@ -532,35 +537,18 @@ class TestPostgresql(unittest.TestCase):
532537
533 @patch.object(postgresql, 'emit_pg_log')538 @patch.object(postgresql, 'emit_pg_log')
534 @patch.object(workloadstatus, 'status_set')539 @patch.object(workloadstatus, 'status_set')
535 @patch('subprocess.check_call')540 @patch.object(host, 'service_start')
536 @patch.object(postgresql, 'version')541 @patch.object(postgresql, 'version')
537 def test_start(self, version, check_call, status_set, emit_pg_log):542 def test_start(self, version, service_start, status_set, emit_pg_log):
538 version.return_value = '9.9'543 version.return_value = '9.9'
539544
540 # When it works, it works.545 # When it works, it works.
541 postgresql.start()546 postgresql.start()
542 # Both -w and -t options are required to wait for startup.547 service_start.assert_called_once_with('postgresql@9.9-main')
543 # We wait a long time, as startup might take a long time.
544 # Maybe we should wait a lot longer.
545 check_call.assert_called_once_with(['pg_ctlcluster', '9.9', 'main',
546 'start', '--', '-w',
547 '-t', '86400'],
548 universal_newlines=True)
549 self.assertFalse(emit_pg_log.called)548 self.assertFalse(emit_pg_log.called)
550549
551 # If it is already running, pg_ctlcluster returns code 2.550 # Start failure we block, and terminate whatever hook is running.
552 # We block, and terminate whatever hook is running.551 service_start.return_value = False
553 check_call.side_effect = subprocess.CalledProcessError(2, 'whoops')
554 check_call.reset_mock()
555 postgresql.start()
556 check_call.assert_called_once_with(['pg_ctlcluster', '9.9', 'main',
557 'start', '--', '-w',
558 '-t', '86400'],
559 universal_newlines=True)
560
561 # Other failures block the unit. Perhaps it is just taking too
562 # perform recovery after a power outage.
563 check_call.side_effect = subprocess.CalledProcessError(42, 'whoops')
564 with self.assertRaises(SystemExit) as x:552 with self.assertRaises(SystemExit) as x:
565 postgresql.start()553 postgresql.start()
566 status_set.assert_called_once_with('blocked', ANY) # Set blocked.554 status_set.assert_called_once_with('blocked', ANY) # Set blocked.
@@ -569,59 +557,29 @@ class TestPostgresql(unittest.TestCase):
569557
570 @patch.object(hookenv, 'log')558 @patch.object(hookenv, 'log')
571 @patch.object(workloadstatus, 'status_set')559 @patch.object(workloadstatus, 'status_set')
572 @patch('subprocess.check_call')560 @patch.object(host, 'service_stop')
573 @patch.object(postgresql, 'version')561 @patch.object(postgresql, 'version')
574 def test_stop(self, version, check_call, status_set, log):562 def test_stop(self, version, service_stop, status_set, log):
575 version.return_value = '9.9'563 version.return_value = '9.9'
576564
577 # Normal shutdown shuts down.565 # Normal shutdown shuts down.
566 service_stop.return_value = True
578 postgresql.stop()567 postgresql.stop()
579 # -t option is required to wait for shutdown to complete. -w not568 service_stop.assert_called_once_with('postgresql@9.9-main')
580 # required unlike 'start', but lets be explicit.
581 check_call.assert_called_once_with(['pg_ctlcluster',
582 '--mode', 'fast', '9.9', 'main',
583 'stop', '--', '-w', '-t', '300'],
584 universal_newlines=True)
585
586 # If the server is not running, pg_ctlcluster(1) signals this with
587 # returncode 2.
588 check_call.side_effect = subprocess.CalledProcessError(2, 'whoops')
589 check_call.reset_mock()
590 postgresql.stop()
591 # -t option is required to wait for shutdown to complete. -w not
592 # required unlike 'start', but lets be explicit.
593 check_call.assert_called_once_with(['pg_ctlcluster',
594 '--mode', 'fast', '9.9', 'main',
595 'stop', '--', '-w', '-t', '300'],
596 universal_newlines=True)
597569
598 # If 'fast' shutdown fails, we retry with an 'immediate' shutdown570 # Failed shutdown blocks and terminates
599 check_call.side_effect = iter([subprocess.CalledProcessError(42, 'x'),571 service_stop.return_value = False
600 None])
601 check_call.reset_mock()
602 postgresql.stop()
603 check_call.assert_has_calls([
604 call(['pg_ctlcluster', '--mode', 'fast', '9.9', 'main',
605 'stop', '--', '-w', '-t', '300'],
606 universal_newlines=True),
607 call(['pg_ctlcluster', '--mode', 'immediate', '9.9', 'main',
608 'stop', '--', '-w', '-t', '300'],
609 universal_newlines=True)])
610
611 # If both fail, we block the unit.
612 check_call.side_effect = subprocess.CalledProcessError(42, 'x')
613 with self.assertRaises(SystemExit) as x:572 with self.assertRaises(SystemExit) as x:
614 postgresql.stop()573 postgresql.stop()
615 status_set.assert_called_once_with('blocked', ANY)574 status_set.assert_called_once_with('blocked', ANY)
616 self.assertEqual(x.exception.code, 0) # Exit cleanly575 self.assertEqual(x.exception.code, 0) # Exit cleanly
617576
618 @patch('subprocess.check_call')577 @patch.object(host, 'service_reload')
619 @patch.object(postgresql, 'version')578 @patch.object(postgresql, 'version')
620 def test_reload_config(self, version, check_call):579 def test_reload_config(self, version, service_reload):
621 version.return_value = '9.9'580 version.return_value = '9.9'
622 postgresql.reload_config()581 postgresql.reload_config()
623 check_call.assert_called_once_with(['pg_ctlcluster', '9.9', 'main',582 service_reload.assert_called_once_with('postgresql@9.9-main')
624 'reload'])
625583
626 def test_parse_config(self):584 def test_parse_config(self):
627 valid = [(r'# A comment', dict()),585 valid = [(r'# A comment', dict()),
@@ -653,7 +611,7 @@ class TestPostgresql(unittest.TestCase):
653611
654 with self.assertRaises(SyntaxError) as x:612 with self.assertRaises(SyntaxError) as x:
655 postgresql.parse_config("=")613 postgresql.parse_config("=")
656 self.assertEqual(str(x.exception), 'Missing key (line 1)')614 self.assertEqual(str(x.exception), "Missing key '=' (line 1)")
657 self.assertEqual(x.exception.lineno, 1)615 self.assertEqual(x.exception.lineno, 1)
658 self.assertEqual(x.exception.text, "=")616 self.assertEqual(x.exception.text, "=")
659617
@@ -665,19 +623,19 @@ class TestPostgresql(unittest.TestCase):
665623
666 with self.assertRaises(SyntaxError) as x:624 with self.assertRaises(SyntaxError) as x:
667 postgresql.parse_config("key='unterminated")625 postgresql.parse_config("key='unterminated")
668 self.assertEqual(str(x.exception), 'Badly quoted value (line 1)')626 self.assertEqual(str(x.exception), r'''Badly quoted value "'unterminated" (line 1)''')
669627
670 with self.assertRaises(SyntaxError) as x:628 with self.assertRaises(SyntaxError) as x:
671 postgresql.parse_config("key='unterminated 2 # comment")629 postgresql.parse_config("key='unterminated 2 # comment")
672 self.assertEqual(str(x.exception), 'Badly quoted value (line 1)')630 self.assertEqual(str(x.exception), r'''Badly quoted value "'unterminated 2" (line 1)''')
673631
674 with self.assertRaises(SyntaxError) as x:632 with self.assertRaises(SyntaxError) as x:
675 postgresql.parse_config("key='unte''''")633 postgresql.parse_config("key='unte''''")
676 self.assertEqual(str(x.exception), 'Badly quoted value (line 1)')634 self.assertEqual(str(x.exception), r"""Badly quoted value "'unte''''" (line 1)""")
677635
678 with self.assertRaises(SyntaxError) as x:636 with self.assertRaises(SyntaxError) as x:
679 postgresql.parse_config(r"key='\'")637 postgresql.parse_config(r"key='\'")
680 self.assertEqual(str(x.exception), 'Badly quoted value (line 1)')638 self.assertEqual(str(x.exception), r'''Badly quoted value "'\\'" (line 1)''')
681639
682 def test_convert_unit(self):640 def test_convert_unit(self):
683 c = postgresql.convert_unit641 c = postgresql.convert_unit

Subscribers

People subscribed via source and target branches

to all changes: