Merge ~ack/maas:postgres-notifier-1817484 into maas:master

Proposed by Alberto Donato
Status: Merged
Approved by: Alberto Donato
Approved revision: 2c7acda9b451d0adf21a5e8ae01a49b29ba28c9e
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ack/maas:postgres-notifier-1817484
Merge into: maas:master
Diff against target: 265 lines (+81/-36)
8 files modified
src/maasserver/config.py (+16/-0)
src/maasserver/djangosettings/settings.py (+6/-0)
src/maasserver/listener.py (+4/-0)
src/maasserver/management/commands/tests/test_config.py (+7/-2)
src/maasserver/region_controller.py (+9/-5)
src/maasserver/tests/test_config.py (+12/-1)
src/maasserver/tests/test_listener.py (+17/-0)
src/maasserver/tests/test_region_controller.py (+10/-28)
Reviewer Review Type Date Requested Status
MAAS Lander Needs Fixing
Blake Rouse (community) Approve
Newell Jensen (community) Approve
Review via email: mp+363768@code.launchpad.net

Commit message

LP: #1817484 - add keepalive for Postgres connections and connect/disconnect events for the listener

To post a comment you must log in.
Revision history for this message
Newell Jensen (newell-jensen) wrote :

LGTM syntax wise. Did not test it myself.

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b postgres-notifier-1817484 lp:~ack/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: b705ee247aafa8ba6d3b891a5c5e1ab6490d8d90

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b postgres-notifier-1817484 lp:~ack/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/5231/console
COMMIT: 4c1b3df86c1d00efeb63a5c816078e7417099467

review: Needs Fixing
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good.

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Revision history for this message
MAAS Lander (maas-lander) wrote :
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b postgres-notifier-1817484 lp:~ack/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/5240/console
COMMIT: fc8d35a8cbb3dff7b175e2ffc9833082702b9e9b

review: Needs Fixing
Revision history for this message
MAAS Lander (maas-lander) wrote :
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b postgres-notifier-1817484 lp:~ack/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/5249/console
COMMIT: 2c7acda9b451d0adf21a5e8ae01a49b29ba28c9e

review: Needs Fixing

There was an error fetching revisions from git servers. Please try again in a few minutes. If the problem persists, contact Launchpad support.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/config.py b/src/maasserver/config.py
2index 0b613bd..1eabe0d 100644
3--- a/src/maasserver/config.py
4+++ b/src/maasserver/config.py
5@@ -58,6 +58,22 @@ class RegionConfiguration(Configuration, metaclass=RegionConfigurationMeta):
6 "database_conn_max_age",
7 "The lifetime of a database connection, in seconds.",
8 Int(if_missing=(5 * 60), accept_python=False, min=0))
9+ database_keepalive = ConfigurationOption(
10+ "database_keepalive",
11+ "Whether keepalive for database connections is enabled.",
12+ StringBool(if_missing=True))
13+ database_keepalive_idle = ConfigurationOption(
14+ "database_keepalive_idle",
15+ "Time (in seconds) after which keepalives will be started.",
16+ Int(if_missing=15))
17+ database_keepalive_interval = ConfigurationOption(
18+ "database_keepalive_interval",
19+ "Interval (in seconds) between keepaliveds.",
20+ Int(if_missing=15))
21+ database_keepalive_count = ConfigurationOption(
22+ "database_keepalive_count",
23+ "Number of keeaplives that can be lost before connection is reset.",
24+ Int(if_missing=2))
25
26 # Worker options.
27 num_workers = ConfigurationOption(
28diff --git a/src/maasserver/djangosettings/settings.py b/src/maasserver/djangosettings/settings.py
29index 28a0308..172cbaf 100644
30--- a/src/maasserver/djangosettings/settings.py
31+++ b/src/maasserver/djangosettings/settings.py
32@@ -115,6 +115,12 @@ try:
33 'HOST': config.database_host,
34 'PORT': str(config.database_port),
35 'CONN_MAX_AGE': config.database_conn_max_age,
36+ 'OPTIONS': {
37+ 'keepalives': int(config.database_keepalive),
38+ 'keepalives_idle': config.database_keepalive_idle,
39+ 'keepalives_interval': config.database_keepalive_interval,
40+ 'keepalives_count': config.database_keepalive_count
41+ }
42 }
43 }
44 DEBUG = config.debug
45diff --git a/src/maasserver/listener.py b/src/maasserver/listener.py
46index 1e4d669..e30b1c9 100644
47--- a/src/maasserver/listener.py
48+++ b/src/maasserver/listener.py
49@@ -15,6 +15,7 @@ from errno import ENOENT
50 from django.db import connections
51 from django.db.utils import load_backend
52 from provisioningserver.utils.enum import map_enum
53+from provisioningserver.utils.events import EventGroup
54 from provisioningserver.utils.twisted import (
55 callOut,
56 suppress,
57@@ -98,6 +99,7 @@ class PostgresListenerService(Service, object):
58 self.disconnecting = None
59 self.registeredChannels = False
60 self.log = Logger(__name__, self)
61+ self.events = EventGroup("connected", "disconnected")
62
63 def startService(self):
64 """Start the listener."""
65@@ -300,6 +302,7 @@ class PostgresListenerService(Service, object):
66 def connect(interval=self.HANDLE_NOTIFY_DELAY):
67 d = deferToThread(self.startConnection)
68 d.addCallback(callOut, deferToThread, self.registerChannels)
69+ d.addCallback(callOut, self.events.connected.fire)
70 d.addCallback(callOut, self.startReading)
71 d.addCallback(callOut, self.runHandleNotify, interval)
72 # On failure ensure that the database connection is stopped.
73@@ -350,6 +353,7 @@ class PostgresListenerService(Service, object):
74 self.log.failure("Connection lost.", reason)
75 if self.autoReconnect:
76 reactor.callLater(3, self.tryConnection)
77+ self.events.disconnected.fire(reason)
78
79 def registerChannel(self, channel):
80 """Register the channel."""
81diff --git a/src/maasserver/management/commands/tests/test_config.py b/src/maasserver/management/commands/tests/test_config.py
82index 63dc5ea..e738265 100644
83--- a/src/maasserver/management/commands/tests/test_config.py
84+++ b/src/maasserver/management/commands/tests/test_config.py
85@@ -116,11 +116,16 @@ class TestConfigurationSet(MAASTestCase):
86 # Set the option to a random value.
87 if self.option == "database_port":
88 value = factory.pick_port()
89- elif self.option == "database_conn_max_age":
90+ elif self.option in (
91+ "database_conn_max_age", "database_keepalive_count",
92+ "database_keepalive_interval",
93+ "database_keepalive_idle"):
94 value = random.randint(0, 60)
95 elif self.option == "num_workers":
96 value = random.randint(1, 16)
97- elif self.option in ["debug", "debug_queries", "debug_http"]:
98+ elif self.option in [
99+ "debug", "debug_queries", "debug_http",
100+ "database_keepalive"]:
101 value = random.choice(['true', 'false'])
102 else:
103 value = factory.make_name("foobar")
104diff --git a/src/maasserver/region_controller.py b/src/maasserver/region_controller.py
105index 4087a73..9a41c72 100644
106--- a/src/maasserver/region_controller.py
107+++ b/src/maasserver/region_controller.py
108@@ -119,16 +119,15 @@ class RegionControllerService(Service):
109 self.postgresListener.register("sys_dns", self.markDNSForUpdate)
110 self.postgresListener.register("sys_proxy", self.markProxyForUpdate)
111 self.postgresListener.register("sys_rbac", self.markRBACForUpdate)
112-
113- # Update DNS and proxy on first start.
114- self.markDNSForUpdate(None, None)
115- self.markProxyForUpdate(None, None)
116- self.markRBACForUpdate(None, None)
117+ self.postgresListener.events.connected.registerHandler(
118+ self.markAllForUpdate)
119
120 @asynchronous(timeout=FOREVER)
121 def stopService(self):
122 """Close the controller."""
123 super(RegionControllerService, self).stopService()
124+ self.postgresListener.events.connected.unregisterHandler(
125+ self.markAllForUpdate)
126 self.postgresListener.unregister("sys_dns", self.markDNSForUpdate)
127 self.postgresListener.unregister("sys_proxy", self.markProxyForUpdate)
128 self.postgresListener.unregister("sys_rbac", self.markRBACForUpdate)
129@@ -137,6 +136,11 @@ class RegionControllerService(Service):
130 self.processing.stop()
131 return d
132
133+ def markAllForUpdate(self):
134+ self.markDNSForUpdate(None, None)
135+ self.markProxyForUpdate(None, None)
136+ self.markRBACForUpdate(None, None)
137+
138 def markDNSForUpdate(self, channel, message):
139 """Called when the `sys_dns` message is received."""
140 self.needsDNSUpdate = True
141diff --git a/src/maasserver/tests/test_config.py b/src/maasserver/tests/test_config.py
142index 19edf83..1878e10 100644
143--- a/src/maasserver/tests/test_config.py
144+++ b/src/maasserver/tests/test_config.py
145@@ -74,6 +74,10 @@ class TestRegionConfigurationDatabaseOptions(MAASTestCase):
146 "database_user": "maas",
147 "database_pass": "",
148 "database_conn_max_age": 5 * 60,
149+ "database_keepalive": True,
150+ "database_keepalive_idle": 15,
151+ "database_keepalive_interval": 15,
152+ "database_keepalive_count": 2
153 }
154
155 scenarios = tuple(
156@@ -89,6 +93,8 @@ class TestRegionConfigurationDatabaseOptions(MAASTestCase):
157 config = RegionConfiguration({})
158 if isinstance(getattr(config, self.option), str):
159 example_value = factory.make_name(self.option)
160+ elif self.option == 'database_keepalive':
161+ example_value = random.choice(['true', 'false'])
162 else:
163 example_value = factory.pick_port()
164 # Argument values will most often be passed in from the command-line,
165@@ -96,7 +102,12 @@ class TestRegionConfigurationDatabaseOptions(MAASTestCase):
166 setattr(config, self.option, str(example_value))
167 self.assertEqual(example_value, getattr(config, self.option))
168 # It's also stored in the configuration database.
169- self.assertEqual({self.option: example_value}, config.store)
170+ expected_value = example_value
171+ if example_value == 'true':
172+ expected_value = True
173+ elif expected_value == 'false':
174+ expected_value = False
175+ self.assertEqual({self.option: expected_value}, config.store)
176
177
178 class TestRegionConfigurationWorkerOptions(MAASTestCase):
179diff --git a/src/maasserver/tests/test_listener.py b/src/maasserver/tests/test_listener.py
180index e58118c..fdbbb10 100644
181--- a/src/maasserver/tests/test_listener.py
182+++ b/src/maasserver/tests/test_listener.py
183@@ -111,6 +111,23 @@ class TestPostgresListenerService(MAASServerTestCase):
184
185 @wait_for_reactor
186 @inlineCallbacks
187+ def test_event_on_connection(self):
188+ listener = PostgresListenerService()
189+ start_calls = []
190+ stop_calls = []
191+ listener.events.connected.registerHandler(
192+ lambda: start_calls.append(True))
193+ listener.events.disconnected.registerHandler(
194+ lambda reason: stop_calls.append(reason))
195+ yield listener.startService()
196+ self.assertEqual(len(start_calls), 1)
197+ yield listener.stopService()
198+ self.assertEqual(len(stop_calls), 1)
199+ [failure] = stop_calls
200+ self.assertIsInstance(failure.value, error.ConnectionDone)
201+
202+ @wait_for_reactor
203+ @inlineCallbacks
204 def test__handles_missing_system_handler_on_notification(self):
205 # Captured notifications from the database will go here.
206 notices = DeferredQueue()
207diff --git a/src/maasserver/tests/test_region_controller.py b/src/maasserver/tests/test_region_controller.py
208index 6b67be8..2c109db 100644
209--- a/src/maasserver/tests/test_region_controller.py
210+++ b/src/maasserver/tests/test_region_controller.py
211@@ -43,6 +43,7 @@ from maastesting.matchers import (
212 MockCallsMatch,
213 MockNotCalled,
214 )
215+from provisioningserver.utils.events import Event
216 from testtools import ExpectedException
217 from testtools.matchers import MatchesStructure
218 from twisted.internet import reactor
219@@ -92,37 +93,18 @@ class TestRegionControllerService(MAASServerTestCase):
220 call("sys_proxy", service.markProxyForUpdate),
221 call("sys_rbac", service.markRBACForUpdate)))
222
223- @wait_for_reactor
224- @inlineCallbacks
225- def test_startService_calls_markDNSForUpdate(self):
226- listener = MagicMock()
227- service = self.make_service(listener)
228- mock_markDNSForUpdate = self.patch(service, "markDNSForUpdate")
229- service.startService()
230- yield service.processingDefer
231- self.assertThat(mock_markDNSForUpdate, MockCalledOnceWith(None, None))
232-
233- @wait_for_reactor
234- @inlineCallbacks
235- def test_startService_calls_markProxyForUpdate(self):
236+ def test_startService_markAllForUpdate_on_connect(self):
237 listener = MagicMock()
238+ listener.events.connected = Event()
239 service = self.make_service(listener)
240- mock_markProxyForUpdate = self.patch(service, "markProxyForUpdate")
241+ mock_mark_dns_for_update = self.patch(service, 'markDNSForUpdate')
242+ mock_mark_rbac_for_update = self.patch(service, 'markRBACForUpdate')
243+ mock_mark_proxy_for_update = self.patch(service, 'markProxyForUpdate')
244 service.startService()
245- yield service.processingDefer
246- self.assertThat(
247- mock_markProxyForUpdate, MockCalledOnceWith(None, None))
248-
249- @wait_for_reactor
250- @inlineCallbacks
251- def test_startService_calls_markRBACForUpdate(self):
252- listener = MagicMock()
253- service = self.make_service(listener)
254- mock_markRBACForUpdate = self.patch(service, "markRBACForUpdate")
255- service.startService()
256- yield service.processingDefer
257- self.assertThat(
258- mock_markRBACForUpdate, MockCalledOnceWith(None, None))
259+ service.postgresListener.events.connected.fire()
260+ mock_mark_dns_for_update.assert_called_once()
261+ mock_mark_rbac_for_update.assert_called_once()
262+ mock_mark_proxy_for_update.assert_called_once()
263
264 def test_stopService_calls_unregister_on_the_listener(self):
265 listener = MagicMock()

Subscribers

People subscribed via source and target branches