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