Merge lp:~verterok/landscape-charm/support-postgresql-charm-v2-protocol into lp:~landscape/landscape-charm/trunk

Proposed by Guillermo Gonzalez
Status: Merged
Approved by: Guillermo Gonzalez
Approved revision: 402
Merged at revision: 399
Proposed branch: lp:~verterok/landscape-charm/support-postgresql-charm-v2-protocol
Merge into: lp:~landscape/landscape-charm/trunk
Diff against target: 296 lines (+78/-61)
9 files modified
lib/apt.py (+1/-1)
lib/callbacks/scripts.py (+1/-1)
lib/callbacks/tests/test_scripts.py (+11/-0)
lib/relations/postgresql.py (+30/-35)
lib/relations/tests/test_postgresql.py (+18/-17)
lib/services.py (+2/-1)
lib/tests/sample.py (+4/-3)
lib/tests/stubs.py (+1/-1)
lib/tests/test_services.py (+10/-2)
To merge this branch: bzr merge lp:~verterok/landscape-charm/support-postgresql-charm-v2-protocol
Reviewer Review Type Date Requested Status
Simon Poirier (community) Approve
🤖 Landscape Builder test results Approve
Review via email: mp+370150@code.launchpad.net

This proposal supersedes a proposal from 2019-07-10.

Commit message

use postgresql-charm v2 protocol, manually parse the dsn as the available psycopg2 (2.6.x) doesn't provide parse_dsn (added in 2.7.x)

Description of the change

use postgresql-charm v2 protocol.
As we are using system packages for python deps, manually parse the dsn as the available psycopg2 (2.6.x) doesn't provide parse_dsn (added in 2.7.x).

This change is to be able to replace postgresql unit in the juju deploy with a external-services charm which acts as a proxy to connect to metal/external deployments of postgresql.

To post a comment you must log in.
Revision history for this message
🤖 Landscape Builder (landscape-builder) : Posted in a previous version of this proposal
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote : Posted in a previous version of this proposal
review: Needs Fixing (test results)
Revision history for this message
Guillermo Gonzalez (verterok) wrote : Posted in a previous version of this proposal

Oh, unittests...fancy. will fix and re-submit.

Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :
review: Approve (test results)
402. By Guillermo Gonzalez

add test for PostgreSQLProvider

Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :
review: Approve (test results)
Revision history for this message
Simon Poirier (simpoir) wrote :

+1 verified with clean deployment, and attached another franken-landscape using external-services

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/apt.py'
--- lib/apt.py 2019-05-28 20:36:35 +0000
+++ lib/apt.py 2019-07-16 16:09:37 +0000
@@ -145,7 +145,7 @@
145 if install_sources and source:145 if install_sources and source:
146 raise SourceConflictError(install_sources, source)146 raise SourceConflictError(install_sources, source)
147 if install_sources:147 if install_sources:
148 self._fetch.configure_sources()148 self._fetch.configure_sources(update=True)
149 return149 return
150 if not source:150 if not source:
151 raise AptNoSourceConfigError()151 raise AptNoSourceConfigError()
152152
=== modified file 'lib/callbacks/scripts.py'
--- lib/callbacks/scripts.py 2016-09-20 08:18:25 +0000
+++ lib/callbacks/scripts.py 2019-07-16 16:09:37 +0000
@@ -12,7 +12,7 @@
12 "source", "key", "ssl-cert", "ssl-key", "smtp-relay-host"}12 "source", "key", "ssl-cert", "ssl-key", "smtp-relay-host"}
1313
14# Database relation keys for which, in case of change, a restart is not needed.14# Database relation keys for which, in case of change, a restart is not needed.
15NO_RESTART_DB_RELATION_KEYS = {"allowed-units", "state"}15NO_RESTART_DB_RELATION_KEYS = {"allowed-units"}
1616
1717
18class ScriptCallback(ManagerCallback):18class ScriptCallback(ManagerCallback):
1919
=== modified file 'lib/callbacks/tests/test_scripts.py'
--- lib/callbacks/tests/test_scripts.py 2016-09-16 10:23:48 +0000
+++ lib/callbacks/tests/test_scripts.py 2019-07-16 16:09:37 +0000
@@ -244,6 +244,17 @@
244 The 'lsctl' script is invoked if db connection details have changed.244 The 'lsctl' script is invoked if db connection details have changed.
245 """245 """
246 old = SAMPLE_DB_UNIT_DATA.copy()246 old = SAMPLE_DB_UNIT_DATA.copy()
247 # update sample data
248 rel_data = {}
249 for item in old["master"].split(' '):
250 key, value = item.split('=')
251 if key == 'host':
252 assert value != "9.9.9.9"
253 rel_data[key] = "9.9.9.9"
254 else:
255 rel_data[key] = value
256 old["master"] = " ".join("{}={}".format(k, v)
257 for k, v in rel_data.items())
247 assert old["host"] != "9.9.9.9"258 assert old["host"] != "9.9.9.9"
248 old["host"] = "9.9.9.9"259 old["host"] = "9.9.9.9"
249 update_persisted_data("db", old, hookenv=self.hookenv)260 update_persisted_data("db", old, hookenv=self.hookenv)
250261
=== modified file 'lib/relations/postgresql.py'
--- lib/relations/postgresql.py 2015-02-03 10:26:59 +0000
+++ lib/relations/postgresql.py 2019-07-16 16:09:37 +0000
@@ -4,6 +4,18 @@
4from lib.relations.errors import UnitDataNotReadyError4from lib.relations.errors import UnitDataNotReadyError
55
66
7class PostgreSQLProvider(RelationContext):
8 name = "db"
9 interface = "pgsql"
10
11 def __init__(self, database=None):
12 super(PostgreSQLProvider, self).__init__()
13 self.database = database
14
15 def provide_data(self):
16 return {"database": self.database}
17
18
7class PostgreSQLRequirer(RelationContext):19class PostgreSQLRequirer(RelationContext):
8 """20 """
9 Relation context for the `pgsql` interface.21 Relation context for the `pgsql` interface.
@@ -11,13 +23,8 @@
11 name = "db"23 name = "db"
12 interface = "pgsql"24 interface = "pgsql"
13 required_keys = [25 required_keys = [
14 "host",26 "master",
15 "port",27 "allowed-units"]
16 'user',
17 "password",
18 "database",
19 "allowed-units",
20 "state"]
2128
22 def __init__(self, hookenv=hookenv):29 def __init__(self, hookenv=hookenv):
23 self._hookenv = hookenv30 self._hookenv = hookenv
@@ -37,7 +44,8 @@
3744
38 try:45 try:
39 self._check_allowed_units(unit_data)46 self._check_allowed_units(unit_data)
40 self._check_state(unit_data)47 if not unit_data.get('master'):
48 return False
41 except UnitDataNotReadyError:49 except UnitDataNotReadyError:
42 return False50 return False
4351
@@ -57,30 +65,17 @@
57 local_unit, allowed_units))65 local_unit, allowed_units))
58 raise UnitDataNotReadyError()66 raise UnitDataNotReadyError()
5967
60 def _check_state(self, unit_data):68 def get_data(self):
61 """Check that the postgresql unit at hand is a master one.69 super(PostgreSQLRequirer, self).get_data()
6270 for n in self.get(self.name, []):
63 From postgresql charm's README.md: "If there is more than one71 if n.get("master"):
64 PostgreSQL unit related, the client charm must only use units72 n.update(self.parse_dsn(n["master"]))
65 with state set to 'master' or 'hot standby'.".73
66 """74 def parse_dsn(self, dsn):
67 ignored_states = set(["hot standby", "failover"])75 data = {}
6876 for v in dsn.split(' '):
69 # XXX for now we support relating to at most one PostgreSQL service77 key, value = v.split("=")
70 # when we'll support sharding we'll want to account for more78 if key == 'port':
71 # than one master.79 value = int(value)
72 relation_id = self._hookenv.relation_ids(self.name)[0]80 data[key] = value
7381 return data
74 units_count = len(self._hookenv.related_units(relation_id))
75 if units_count > 1:
76 self._hookenv.log(
77 "The postgresql service is clustered with %s units. "
78 "Ignoring any intermittent 'standalone' states." % units_count)
79 ignored_states.add("standalone")
80
81 state = unit_data["state"]
82 if state in ignored_states:
83 self._hookenv.log(
84 "Discarding postgresql unit with invalid state '%s' for "
85 "host %s." % (state, unit_data["host"]))
86 raise UnitDataNotReadyError()
8782
=== modified file 'lib/relations/tests/test_postgresql.py'
--- lib/relations/tests/test_postgresql.py 2015-02-02 13:59:10 +0000
+++ lib/relations/tests/test_postgresql.py 2019-07-16 16:09:37 +0000
@@ -1,6 +1,6 @@
1from lib.tests.helpers import HookenvTest1from lib.tests.helpers import HookenvTest
2from lib.tests.sample import SAMPLE_DB_UNIT_DATA2from lib.tests.sample import SAMPLE_DB_UNIT_DATA
3from lib.relations.postgresql import PostgreSQLRequirer3from lib.relations.postgresql import PostgreSQLRequirer, PostgreSQLProvider
44
55
6class PostgreSQLRequirerTest(HookenvTest):6class PostgreSQLRequirerTest(HookenvTest):
@@ -14,8 +14,7 @@
14 ready.14 ready.
15 """15 """
16 self.assertEqual(16 self.assertEqual(
17 ["host", "port", "user", "password", "database",17 ["master", "allowed-units"],
18 "allowed-units", "state"],
19 PostgreSQLRequirer.required_keys)18 PostgreSQLRequirer.required_keys)
2019
21 def test_local_unit_in_allowed_units(self):20 def test_local_unit_in_allowed_units(self):
@@ -25,6 +24,7 @@
25 """24 """
26 unit_data = SAMPLE_DB_UNIT_DATA.copy()25 unit_data = SAMPLE_DB_UNIT_DATA.copy()
27 unit_data["allowed-units"] = ""26 unit_data["allowed-units"] = ""
27 unit_data["master"] = "host=10.0.3.169"
28 self.hookenv.relations = {28 self.hookenv.relations = {
29 "db": {29 "db": {
30 "db:1": {30 "db:1": {
@@ -45,14 +45,12 @@
45 is not a 'master'.45 is not a 'master'.
46 """46 """
47 unit_data1 = SAMPLE_DB_UNIT_DATA.copy()47 unit_data1 = SAMPLE_DB_UNIT_DATA.copy()
48 unit_data1["host"] = "10.0.3.170"48 unit_data1["standbys"] = "host=10.0.3.170\n"
49 unit_data1["state"] = "hot standby"49 unit_data1.pop('master')
50 unit_data2 = SAMPLE_DB_UNIT_DATA.copy()50 unit_data2 = SAMPLE_DB_UNIT_DATA.copy()
51 unit_data2["host"] = "10.0.3.169"51 unit_data2["master"] = "host=10.0.3.169"
52 unit_data2["state"] = "master"
53 unit_data3 = SAMPLE_DB_UNIT_DATA.copy()52 unit_data3 = SAMPLE_DB_UNIT_DATA.copy()
54 unit_data3["host"] = "10.0.3.171"53 unit_data3["master"] = "host=10.0.3.171"
55 unit_data3["state"] = "standalone"
56 self.hookenv.relations = {54 self.hookenv.relations = {
57 "db": {55 "db": {
58 "db:1": {56 "db:1": {
@@ -64,12 +62,15 @@
64 }62 }
65 relation = PostgreSQLRequirer(hookenv=self.hookenv)63 relation = PostgreSQLRequirer(hookenv=self.hookenv)
66 self.assertTrue(relation.is_ready())64 self.assertTrue(relation.is_ready())
67 self.assertIn(
68 ("Discarding postgresql unit with invalid state 'hot standby' "
69 "for host 10.0.3.170.", None),
70 self.hookenv.messages)
71 self.assertIn(
72 ("Discarding postgresql unit with invalid state 'standalone' "
73 "for host 10.0.3.171.", None),
74 self.hookenv.messages)
75 self.assertEqual("10.0.3.169", relation["db"][0]["host"])65 self.assertEqual("10.0.3.169", relation["db"][0]["host"])
66
67
68class PostgreSQLProviderTest(HookenvTest):
69
70 with_hookenv_monkey_patch = True
71
72 def test_scafolding(self):
73 relation = PostgreSQLProvider(database="test")
74 self.assertEqual({"database": "test"}, relation.provide_data())
75 self.assertEqual("db", relation.name)
76 self.assertEqual("pgsql", relation.interface)
7677
=== modified file 'lib/services.py'
--- lib/services.py 2016-11-03 11:28:38 +0000
+++ lib/services.py 2019-07-16 16:09:37 +0000
@@ -9,7 +9,7 @@
99
10from lib.hook import Hook10from lib.hook import Hook
11from lib.paths import default_paths11from lib.paths import default_paths
12from lib.relations.postgresql import PostgreSQLRequirer12from lib.relations.postgresql import PostgreSQLRequirer, PostgreSQLProvider
13from lib.relations.rabbitmq import RabbitMQRequirer, RabbitMQProvider13from lib.relations.rabbitmq import RabbitMQRequirer, RabbitMQProvider
14from lib.relations.haproxy import HAProxyProvider, HAProxyRequirer14from lib.relations.haproxy import HAProxyProvider, HAProxyRequirer
15from lib.relations.leader import LeaderProvider, LeaderRequirer15from lib.relations.leader import LeaderProvider, LeaderRequirer
@@ -57,6 +57,7 @@
57 HAProxyProvider(57 HAProxyProvider(
58 config_requirer, hosted_requirer, paths=self._paths),58 config_requirer, hosted_requirer, paths=self._paths),
59 RabbitMQProvider(),59 RabbitMQProvider(),
60 PostgreSQLProvider(database="landscape"),
60 ],61 ],
61 # Required data is available to the render_template calls below.62 # Required data is available to the render_template calls below.
62 "required_data": [63 "required_data": [
6364
=== modified file 'lib/tests/sample.py'
--- lib/tests/sample.py 2015-11-05 17:51:36 +0000
+++ lib/tests/sample.py 2019-07-16 16:09:37 +0000
@@ -2,13 +2,14 @@
22
33
4SAMPLE_DB_UNIT_DATA = {4SAMPLE_DB_UNIT_DATA = {
5 "database": "all",
6 "allowed-units": "landscape-server/0",5 "allowed-units": "landscape-server/0",
7 "state": "standalone",6 "master": ("host=10.0.3.168 user=db_admin_1 password=sekret port=5432 "
7 "database=all"),
8 "host": "10.0.3.168",8 "host": "10.0.3.168",
9 "user": "db_admin_1",9 "user": "db_admin_1",
10 "password": "sekret",10 "password": "sekret",
11 "port": "5432"11 "port": "5432",
12 "database": "all"
12}13}
1314
14SAMPLE_LEADER_CONTEXT_DATA = {15SAMPLE_LEADER_CONTEXT_DATA = {
1516
=== modified file 'lib/tests/stubs.py'
--- lib/tests/stubs.py 2019-01-17 14:23:49 +0000
+++ lib/tests/stubs.py 2019-07-16 16:09:37 +0000
@@ -128,7 +128,7 @@
128 def apt_install(self, packages, options=None, fatal=False):128 def apt_install(self, packages, options=None, fatal=False):
129 self.installed.append((packages, options, fatal))129 self.installed.append((packages, options, fatal))
130130
131 def configure_sources(self):131 def configure_sources(self, update=False):
132 install_sources = self._config().get('install_sources')132 install_sources = self._config().get('install_sources')
133 install_keys = self._config().get('install_keys')133 install_keys = self._config().get('install_keys')
134 self.configured_sources.append((install_sources, install_keys))134 self.configured_sources.append((install_sources, install_keys))
135135
=== modified file 'lib/tests/test_services.py'
--- lib/tests/test_services.py 2019-01-17 14:23:49 +0000
+++ lib/tests/test_services.py 2019-07-16 16:09:37 +0000
@@ -113,11 +113,19 @@
113 "config": config_expected,113 "config": config_expected,
114 "is_leader": True,114 "is_leader": True,
115 }115 }
116
117 for render in self.renders:116 for render in self.renders:
118 rendered_context = render[2]117 rendered_context = render[2]
119 for key in context.keys():118 for key in context.keys():
120 self.assertEqual(context[key], rendered_context[key])119 if key == 'db':
120 # check that all keys are in the rendered_context
121 expected = sorted(
122 ['master', 'host', 'port', 'user', 'password',
123 'database', 'allowed-units'])
124 self.assertEqual(expected,
125 sorted(rendered_context[key][0].keys()))
126 else:
127 self.assertEqual(context[key], rendered_context[key])
128
121 self.assertEqual(129 self.assertEqual(
122 ("service.conf", self.paths.service_conf()),130 ("service.conf", self.paths.service_conf()),
123 self.renders[0][:2])131 self.renders[0][:2])

Subscribers

People subscribed via source and target branches