Merge ~newell-jensen/maas:curtin-cloud-init-status-message-events into maas:master
- Git
- lp:~newell-jensen/maas
- curtin-cloud-init-status-message-events
- Merge into master
Proposed by
Newell Jensen
Status: | Merged |
---|---|
Approved by: | Newell Jensen |
Approved revision: | 100c1efc94de192dab10bf76d3d7e9fe1829079f |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~newell-jensen/maas:curtin-cloud-init-status-message-events |
Merge into: | maas:master |
Diff against target: |
277 lines (+122/-13) 5 files modified
src/metadataserver/api.py (+24/-5) src/metadataserver/api_twisted.py (+14/-3) src/metadataserver/tests/test_api.py (+21/-3) src/metadataserver/tests/test_api_twisted.py (+19/-1) src/provisioningserver/events.py (+44/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Blake Rouse (community) | Approve | ||
Review via email: mp+365616@code.launchpad.net |
Commit message
Add new event types and events for cloud-init and curtin messages that we want to user in conjunction with status messages for a more granular boot process.
Description of the change
To post a comment you must log in.
Revision history for this message
Newell Jensen (newell-jensen) wrote : | # |
Replied inline.
- c2cc50a... by Newell Jensen
-
Fix typos.
- 100c1ef... by Newell Jensen
-
Key off the action instead of the curtin and cloudinit description. Update unit tests.
Revision history for this message
Blake Rouse (blake-rouse) wrote : | # |
Looks good, thanks for the fixes.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/src/metadataserver/api.py b/src/metadataserver/api.py | |||
2 | index 3470a29..59c12fa 100644 | |||
3 | --- a/src/metadataserver/api.py | |||
4 | +++ b/src/metadataserver/api.py | |||
5 | @@ -1,4 +1,4 @@ | |||
7 | 1 | # Copyright 2012-2018 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2012-2019 Canonical Ltd. This software is licensed under the |
8 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
9 | 3 | 3 | ||
10 | 4 | """Metadata API.""" | 4 | """Metadata API.""" |
11 | @@ -102,6 +102,7 @@ from metadataserver.vendor_data import get_vendor_data | |||
12 | 102 | from piston3.utils import rc | 102 | from piston3.utils import rc |
13 | 103 | from provisioningserver.events import ( | 103 | from provisioningserver.events import ( |
14 | 104 | EVENT_DETAILS, | 104 | EVENT_DETAILS, |
15 | 105 | EVENT_STATUS_MESSAGES, | ||
16 | 105 | EVENT_TYPES, | 106 | EVENT_TYPES, |
17 | 106 | ) | 107 | ) |
18 | 107 | from provisioningserver.logger import LegacyLogger | 108 | from provisioningserver.logger import LegacyLogger |
19 | @@ -190,7 +191,8 @@ def check_version(version): | |||
20 | 190 | 191 | ||
21 | 191 | 192 | ||
22 | 192 | def add_event_to_node_event_log( | 193 | def add_event_to_node_event_log( |
24 | 193 | node, origin, action, description, result=None, created=None): | 194 | node, origin, action, description, |
25 | 195 | event_type, result=None, created=None): | ||
26 | 194 | """Add an entry to the node's event log.""" | 196 | """Add an entry to the node's event log.""" |
27 | 195 | if node.status == NODE_STATUS.COMMISSIONING: | 197 | if node.status == NODE_STATUS.COMMISSIONING: |
28 | 196 | if result in ['SUCCESS', None]: | 198 | if result in ['SUCCESS', None]: |
29 | @@ -216,10 +218,27 @@ def add_event_to_node_event_log( | |||
30 | 216 | else: | 218 | else: |
31 | 217 | type_name = EVENT_TYPES.NODE_STATUS_EVENT | 219 | type_name = EVENT_TYPES.NODE_STATUS_EVENT |
32 | 218 | 220 | ||
34 | 219 | event_details = EVENT_DETAILS[type_name] | 221 | # Create an extra event for the machine status messages. |
35 | 222 | if action in EVENT_STATUS_MESSAGES: | ||
36 | 223 | # cloud-init sends the action 'modules-final' for | ||
37 | 224 | # both commissioning and deployment but we are only interested | ||
38 | 225 | # in setting the message for deployment. | ||
39 | 226 | if ((action == 'modules-final' and | ||
40 | 227 | node.status == NODE_STATUS.DEPLOYING and | ||
41 | 228 | event_type == 'finish') or ( | ||
42 | 229 | action != 'modules-final' and event_type == 'start')): | ||
43 | 230 | Event.objects.register_event_and_event_type( | ||
44 | 231 | EVENT_STATUS_MESSAGES[action], | ||
45 | 232 | type_level=EVENT_DETAILS[ | ||
46 | 233 | EVENT_STATUS_MESSAGES[action]].level, | ||
47 | 234 | type_description=EVENT_DETAILS[ | ||
48 | 235 | EVENT_STATUS_MESSAGES[action]].description, | ||
49 | 236 | event_action=action, event_description='', | ||
50 | 237 | system_id=node.system_id, created=created) | ||
51 | 238 | |||
52 | 220 | return Event.objects.register_event_and_event_type( | 239 | return Event.objects.register_event_and_event_type( |
55 | 221 | type_name, type_level=event_details.level, | 240 | type_name, type_level=EVENT_DETAILS[type_name].level, |
56 | 222 | type_description=event_details.description, | 241 | type_description=EVENT_DETAILS[type_name].description, |
57 | 223 | event_action=action, | 242 | event_action=action, |
58 | 224 | event_description="'%s' %s" % (origin, description), | 243 | event_description="'%s' %s" % (origin, description), |
59 | 225 | system_id=node.system_id, created=created) | 244 | system_id=node.system_id, created=created) |
60 | diff --git a/src/metadataserver/api_twisted.py b/src/metadataserver/api_twisted.py | |||
61 | index 0659d30..ddad25e 100644 | |||
62 | --- a/src/metadataserver/api_twisted.py | |||
63 | +++ b/src/metadataserver/api_twisted.py | |||
64 | @@ -1,4 +1,4 @@ | |||
66 | 1 | # Copyright 2017-2018 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2017-2019 Canonical Ltd. This software is licensed under the |
67 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
68 | 3 | 3 | ||
69 | 4 | """Metadata API that runs in the Twisted reactor.""" | 4 | """Metadata API that runs in the Twisted reactor.""" |
70 | @@ -33,6 +33,7 @@ from metadataserver.api import ( | |||
71 | 33 | ) | 33 | ) |
72 | 34 | from metadataserver.enum import SCRIPT_STATUS | 34 | from metadataserver.enum import SCRIPT_STATUS |
73 | 35 | from metadataserver.models import NodeKey | 35 | from metadataserver.models import NodeKey |
74 | 36 | from provisioningserver.events import EVENT_STATUS_MESSAGES | ||
75 | 36 | from provisioningserver.logger import LegacyLogger | 37 | from provisioningserver.logger import LegacyLogger |
76 | 37 | from provisioningserver.utils.twisted import deferred | 38 | from provisioningserver.utils.twisted import deferred |
77 | 38 | from twisted.application.internet import TimerService | 39 | from twisted.application.internet import TimerService |
78 | @@ -307,7 +308,7 @@ class StatusWorkerService(TimerService, object): | |||
79 | 307 | 308 | ||
80 | 308 | # Add this event to the node event log. | 309 | # Add this event to the node event log. |
81 | 309 | add_event_to_node_event_log( | 310 | add_event_to_node_event_log( |
83 | 310 | node, origin, activity_name, description, result, | 311 | node, origin, activity_name, description, event_type, result, |
84 | 311 | message['timestamp']) | 312 | message['timestamp']) |
85 | 312 | 313 | ||
86 | 313 | # Group files together with the ScriptResult they belong. | 314 | # Group files together with the ScriptResult they belong. |
87 | @@ -494,8 +495,18 @@ class StatusWorkerService(TimerService, object): | |||
88 | 494 | ] and | 495 | ] and |
89 | 495 | message['event_type'] in ['start', 'finish'] and | 496 | message['event_type'] in ['start', 'finish'] and |
90 | 496 | message['origin'] == 'curtin') | 497 | message['origin'] == 'curtin') |
91 | 498 | is_status_message_event = ( | ||
92 | 499 | message['name'] in EVENT_STATUS_MESSAGES and | ||
93 | 500 | message['event_type'] == 'start') | ||
94 | 501 | # modules-final is a cloudinit event that is part of | ||
95 | 502 | # EVENT_STATUS_MESSAGES but we want to create an event for | ||
96 | 503 | # the 'finish' event_type. | ||
97 | 504 | is_modules_final_event = ( | ||
98 | 505 | message['name'] == 'modules-final' and | ||
99 | 506 | message['event_type'] == 'finish') | ||
100 | 497 | if (is_starting_event or is_final_event or has_files or | 507 | if (is_starting_event or is_final_event or has_files or |
102 | 498 | is_curtin_early_late): | 508 | is_curtin_early_late or is_status_message_event or |
103 | 509 | is_modules_final_event): | ||
104 | 499 | d = deferToDatabase( | 510 | d = deferToDatabase( |
105 | 500 | self._processMessageNow, authorization, message) | 511 | self._processMessageNow, authorization, message) |
106 | 501 | d.addErrback( | 512 | d.addErrback( |
107 | diff --git a/src/metadataserver/tests/test_api.py b/src/metadataserver/tests/test_api.py | |||
108 | index 68d8b2f..1c48a98 100644 | |||
109 | --- a/src/metadataserver/tests/test_api.py | |||
110 | +++ b/src/metadataserver/tests/test_api.py | |||
111 | @@ -1,4 +1,4 @@ | |||
113 | 1 | # Copyright 2012-2018 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2012-2019 Canonical Ltd. This software is licensed under the |
114 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
115 | 3 | 3 | ||
116 | 4 | """Tests for the metadata API.""" | 4 | """Tests for the metadata API.""" |
117 | @@ -103,6 +103,7 @@ from netaddr import IPNetwork | |||
118 | 103 | from provisioningserver.events import ( | 103 | from provisioningserver.events import ( |
119 | 104 | EVENT_DETAILS, | 104 | EVENT_DETAILS, |
120 | 105 | EVENT_TYPES, | 105 | EVENT_TYPES, |
121 | 106 | EVENT_STATUS_MESSAGES, | ||
122 | 106 | ) | 107 | ) |
123 | 107 | from provisioningserver.refresh.node_info_scripts import ( | 108 | from provisioningserver.refresh.node_info_scripts import ( |
124 | 108 | NODE_INFO_SCRIPTS, | 109 | NODE_INFO_SCRIPTS, |
125 | @@ -227,7 +228,8 @@ class TestHelpers(MAASServerTestCase): | |||
126 | 227 | origin = factory.make_name('origin') | 228 | origin = factory.make_name('origin') |
127 | 228 | action = factory.make_name('action') | 229 | action = factory.make_name('action') |
128 | 229 | description = factory.make_name('description') | 230 | description = factory.make_name('description') |
130 | 230 | add_event_to_node_event_log(node, origin, action, description) | 231 | add_event_to_node_event_log( |
131 | 232 | node, origin, action, description, event_type='') | ||
132 | 231 | event = Event.objects.get(node=node) | 233 | event = Event.objects.get(node=node) |
133 | 232 | 234 | ||
134 | 233 | self.assertEqual(node, event.node) | 235 | self.assertEqual(node, event.node) |
135 | @@ -236,12 +238,28 @@ class TestHelpers(MAASServerTestCase): | |||
136 | 236 | self.assertIn(description, event.description) | 238 | self.assertIn(description, event.description) |
137 | 237 | self.assertEqual(expected_type[node.status], event.type.name) | 239 | self.assertEqual(expected_type[node.status], event.type.name) |
138 | 238 | 240 | ||
139 | 241 | def test_add_event_to_node_event_log_creates_events_status_messages(self): | ||
140 | 242 | for action in EVENT_STATUS_MESSAGES.keys(): | ||
141 | 243 | node = factory.make_Node(status=NODE_STATUS.DEPLOYING) | ||
142 | 244 | origin = factory.make_name('origin') | ||
143 | 245 | description = factory.make_name('description') | ||
144 | 246 | add_event_to_node_event_log( | ||
145 | 247 | node, origin, action, description, | ||
146 | 248 | event_type='start' if action != 'modules-final' else 'finish') | ||
147 | 249 | event = Event.objects.filter(node=node).first() | ||
148 | 250 | |||
149 | 251 | self.assertEqual(node, event.node) | ||
150 | 252 | self.assertEqual(action, event.action) | ||
151 | 253 | self.assertEqual(EVENT_DETAILS[EVENT_STATUS_MESSAGES[ | ||
152 | 254 | action]].description, event.type.description) | ||
153 | 255 | self.assertEqual('', event.description) | ||
154 | 256 | |||
155 | 239 | def test_add_event_to_node_event_log_logs_rack_refresh(self): | 257 | def test_add_event_to_node_event_log_logs_rack_refresh(self): |
156 | 240 | rack = factory.make_RackController() | 258 | rack = factory.make_RackController() |
157 | 241 | origin = factory.make_name('origin') | 259 | origin = factory.make_name('origin') |
158 | 242 | action = factory.make_name('action') | 260 | action = factory.make_name('action') |
159 | 243 | description = factory.make_name('description') | 261 | description = factory.make_name('description') |
161 | 244 | add_event_to_node_event_log(rack, origin, action, description) | 262 | add_event_to_node_event_log(rack, origin, action, description, '') |
162 | 245 | event = Event.objects.get(node=rack) | 263 | event = Event.objects.get(node=rack) |
163 | 246 | 264 | ||
164 | 247 | self.assertEqual(rack, event.node) | 265 | self.assertEqual(rack, event.node) |
165 | diff --git a/src/metadataserver/tests/test_api_twisted.py b/src/metadataserver/tests/test_api_twisted.py | |||
166 | index 65e5014..81b6fbf 100644 | |||
167 | --- a/src/metadataserver/tests/test_api_twisted.py | |||
168 | +++ b/src/metadataserver/tests/test_api_twisted.py | |||
169 | @@ -1,4 +1,4 @@ | |||
171 | 1 | # Copyright 2017-2018 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2017-2019 Canonical Ltd. This software is licensed under the |
172 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
173 | 3 | 3 | ||
174 | 4 | """Tests for the twisted metadata API.""" | 4 | """Tests for the twisted metadata API.""" |
175 | @@ -64,6 +64,7 @@ from metadataserver.enum import ( | |||
176 | 64 | SCRIPT_STATUS, | 64 | SCRIPT_STATUS, |
177 | 65 | ) | 65 | ) |
178 | 66 | from metadataserver.models import NodeKey | 66 | from metadataserver.models import NodeKey |
179 | 67 | from provisioningserver.events import EVENT_STATUS_MESSAGES | ||
180 | 67 | from testtools import ExpectedException | 68 | from testtools import ExpectedException |
181 | 68 | from testtools.matchers import ( | 69 | from testtools.matchers import ( |
182 | 69 | Equals, | 70 | Equals, |
183 | @@ -299,6 +300,23 @@ class TestStatusWorkerServiceTransactional(MAASTransactionServerTestCase): | |||
184 | 299 | 300 | ||
185 | 300 | @wait_for_reactor | 301 | @wait_for_reactor |
186 | 301 | @inlineCallbacks | 302 | @inlineCallbacks |
187 | 303 | def test_queueMessages_processes_top_level_status_messages_instantly(self): | ||
188 | 304 | for name in EVENT_STATUS_MESSAGES.keys(): | ||
189 | 305 | worker = StatusWorkerService(sentinel.dbtasks) | ||
190 | 306 | mock_processMessage = self.patch(worker, "_processMessage") | ||
191 | 307 | message = self.make_message() | ||
192 | 308 | message['event_type'] = 'start' | ||
193 | 309 | message['name'] = name | ||
194 | 310 | nodes_with_tokens = yield deferToDatabase( | ||
195 | 311 | self.make_nodes_with_tokens) | ||
196 | 312 | node, token = nodes_with_tokens[0] | ||
197 | 313 | yield worker.queueMessage(token.key, message) | ||
198 | 314 | self.assertThat( | ||
199 | 315 | mock_processMessage, | ||
200 | 316 | MockCalledOnceWith(node, message)) | ||
201 | 317 | |||
202 | 318 | @wait_for_reactor | ||
203 | 319 | @inlineCallbacks | ||
204 | 302 | def test_queueMessages_processes_files_message_instantly(self): | 320 | def test_queueMessages_processes_files_message_instantly(self): |
205 | 303 | worker = StatusWorkerService(sentinel.dbtasks) | 321 | worker = StatusWorkerService(sentinel.dbtasks) |
206 | 304 | mock_processMessage = self.patch(worker, "_processMessage") | 322 | mock_processMessage = self.patch(worker, "_processMessage") |
207 | diff --git a/src/provisioningserver/events.py b/src/provisioningserver/events.py | |||
208 | index a1e2415..e72f281 100644 | |||
209 | --- a/src/provisioningserver/events.py | |||
210 | +++ b/src/provisioningserver/events.py | |||
211 | @@ -1,10 +1,11 @@ | |||
213 | 1 | # Copyright 2014-2018 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2014-2019 Canonical Ltd. This software is licensed under the |
214 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
215 | 3 | 3 | ||
216 | 4 | """Event catalog.""" | 4 | """Event catalog.""" |
217 | 5 | 5 | ||
218 | 6 | __all__ = [ | 6 | __all__ = [ |
219 | 7 | 'EVENT_DETAILS', | 7 | 'EVENT_DETAILS', |
220 | 8 | 'EVENT_STATUS_MESSAGES', | ||
221 | 8 | 'EVENT_TYPES', | 9 | 'EVENT_TYPES', |
222 | 9 | 'send_node_event', | 10 | 'send_node_event', |
223 | 10 | 'send_node_event_mac_address', | 11 | 'send_node_event_mac_address', |
224 | @@ -141,6 +142,24 @@ class EVENT_TYPES: | |||
225 | 141 | NETWORKING = "NETWORKING" | 142 | NETWORKING = "NETWORKING" |
226 | 142 | # Zones events | 143 | # Zones events |
227 | 143 | ZONES = "ZONES" | 144 | ZONES = "ZONES" |
228 | 145 | # Status message events | ||
229 | 146 | CONFIGURING_STORAGE = "CONFIGURING_STORAGE" | ||
230 | 147 | CONFIGURING_NETWORKING = "CONFIGURING_NETWORKING" | ||
231 | 148 | INSTALLING_OS = "INSTALLING_OS" | ||
232 | 149 | CONFIGURING_OS = "CONFIGURING_OS" | ||
233 | 150 | APPLYING_POST_INSTALLATION_CONFIG = "APPLYING_POST_INSTALLATION_CONFIG" | ||
234 | 151 | REBOOTING_MACHINE = "REBOOTING_MACHINE" | ||
235 | 152 | |||
236 | 153 | # Used to create new events used for the machine's status. | ||
237 | 154 | # The keys are the messages sent from cloud-init and curtin | ||
238 | 155 | # to the metadataserver. | ||
239 | 156 | EVENT_STATUS_MESSAGES = { | ||
240 | 157 | 'cmd-install/stage-partitioning': EVENT_TYPES.CONFIGURING_STORAGE, | ||
241 | 158 | 'cmd-install/stage-network': EVENT_TYPES.CONFIGURING_NETWORKING, | ||
242 | 159 | 'cmd-install/stage-extract': EVENT_TYPES.INSTALLING_OS, | ||
243 | 160 | 'cmd-install/stage-curthooks': EVENT_TYPES.CONFIGURING_OS, | ||
244 | 161 | 'modules-final': EVENT_TYPES.APPLYING_POST_INSTALLATION_CONFIG, | ||
245 | 162 | } | ||
246 | 144 | 163 | ||
247 | 145 | 164 | ||
248 | 146 | EventDetail = namedtuple("EventDetail", ("description", "level")) | 165 | EventDetail = namedtuple("EventDetail", ("description", "level")) |
249 | @@ -401,6 +420,30 @@ EVENT_DETAILS = { | |||
250 | 401 | description=("Zones"), | 420 | description=("Zones"), |
251 | 402 | level=AUDIT, | 421 | level=AUDIT, |
252 | 403 | ), | 422 | ), |
253 | 423 | EVENT_TYPES.CONFIGURING_STORAGE: EventDetail( | ||
254 | 424 | description=("Configuring storage"), | ||
255 | 425 | level=INFO, | ||
256 | 426 | ), | ||
257 | 427 | EVENT_TYPES.CONFIGURING_NETWORKING: EventDetail( | ||
258 | 428 | description=("Configuring networking"), | ||
259 | 429 | level=INFO, | ||
260 | 430 | ), | ||
261 | 431 | EVENT_TYPES.INSTALLING_OS: EventDetail( | ||
262 | 432 | description=("Installing OS"), | ||
263 | 433 | level=INFO, | ||
264 | 434 | ), | ||
265 | 435 | EVENT_TYPES.CONFIGURING_OS: EventDetail( | ||
266 | 436 | description=("Configuring OS"), | ||
267 | 437 | level=INFO, | ||
268 | 438 | ), | ||
269 | 439 | EVENT_TYPES.APPLYING_POST_INSTALLATION_CONFIG: EventDetail( | ||
270 | 440 | description=("Applying post installation config"), | ||
271 | 441 | level=INFO, | ||
272 | 442 | ), | ||
273 | 443 | EVENT_TYPES.REBOOTING_MACHINE: EventDetail( | ||
274 | 444 | description=("Rebooting machine"), | ||
275 | 445 | level=INFO, | ||
276 | 446 | ), | ||
277 | 404 | } | 447 | } |
278 | 405 | 448 | ||
279 | 406 | 449 |
Overall this looks good, just a question. More about the code not about how it was implemented. Implementation seems correct, just as we discussed.