Merge lp:~julian-edwards/maas/commission-monitor-bug-1303925 into lp:~maas-committers/maas/trunk
- commission-monitor-bug-1303925
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Julian Edwards |
Approved revision: | no longer in the source branch. |
Merged at revision: | 3332 |
Proposed branch: | lp:~julian-edwards/maas/commission-monitor-bug-1303925 |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
317 lines (+74/-3) 7 files modified
src/maasserver/api/tests/test_enlistment.py (+4/-1) src/maasserver/api/tests/test_nodes.py (+3/-0) src/maasserver/models/node.py (+14/-2) src/maasserver/models/tests/test_node.py (+29/-0) src/maasserver/tests/test_node_action.py (+4/-0) src/metadataserver/api.py (+1/-0) src/metadataserver/tests/test_api.py (+19/-0) |
To merge this branch: | bzr merge lp:~julian-edwards/maas/commission-monitor-bug-1303925 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jeroen T. Vermeulen (community) | Approve | ||
Review via email: mp+240541@code.launchpad.net |
Commit message
Add a transition monitor for commissioning, so that the node is marked as FAILED_
Description of the change
Note that the monitor stuff already coped with this failure type; the code to enforce it was just never written. This branch fixes that.
Julian Edwards (julian-edwards) wrote : | # |
On Tuesday 04 November 2014 08:23:10 you wrote:
> Review: Approve
Thanks for reviewing.
> > + def get_commissioni
> > + """Return the commissioning time of this node (in seconds)."""
>
> What is a node's "commissioning time"? Is it the time the node has been
> commissioning? Is it the time that commissioning is allowed to take? Is
> it the time the node started commissioning? A more specific name would
> help.
As I said yesterday I was being consistent with "get_deployment
made it clear in both docstrings what the func is doing.
> > class TestCommissioni
> > + def setUp(self):
> > + super(TestCommi
> > + self.patch(Node, 'stop_transitio
>
> I'm not familiar with transition monitors, so this makes me wonder: does the
> entire test case need this patch? If it's only for those two tests that
> query the mock, an explicit patch in the tests would be better than an
> implicit one on every test.
It make an RPC to the cluster, so it needs patching out globally, and makes
sense to do it in one place rather than everywhere.
Cheers!
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~julian-edwards/maas/commission-monitor-bug-1303925 into lp:maas failed. Below is the output from the failed tests.
Ign http://
Get:1 http://
Ign http://
Get:2 http://
Ign http://
Hit http://
Get:3 http://
Hit http://
Get:4 http://
Get:5 http://
Hit http://
Get:6 http://
Get:7 http://
Get:8 http://
Hit http://
Hit http://
Hit http://
Hit http://
Get:9 http://
Hit http://
Get:10 http://
Get:11 http://
Get:12 http://
Get:13 http://
Get:14 http://
Get:15 http://
Get:16 http://
Ign http://
Ign http://
Fetched 1,564 kB in 2s (522 kB/s)
Reading package lists...
sudo DEBIAN_
--
Preview Diff
1 | === modified file 'src/maasserver/api/tests/test_enlistment.py' |
2 | --- src/maasserver/api/tests/test_enlistment.py 2014-10-30 13:07:43 +0000 |
3 | +++ src/maasserver/api/tests/test_enlistment.py 2014-11-05 04:40:50 +0000 |
4 | @@ -52,6 +52,7 @@ |
5 | def setUp(self): |
6 | super(EnlistmentAPITest, self).setUp() |
7 | self.patch_autospec(node_module, 'wait_for_power_commands') |
8 | + self.patch_autospec(Node, 'start_transition_monitor') |
9 | |
10 | def test_POST_new_creates_node(self): |
11 | architecture = make_usable_architecture(self) |
12 | @@ -354,6 +355,7 @@ |
13 | def setUp(self): |
14 | super(NodeHostnameEnlistmentTest, self).setUp() |
15 | self.patch_autospec(node_module, 'wait_for_power_commands') |
16 | + self.patch_autospec(Node, 'start_transition_monitor') |
17 | |
18 | scenarios = [ |
19 | ('anon', dict(userfactory=lambda: AnonymousUser())), |
20 | @@ -639,6 +641,7 @@ |
21 | def setUp(self): |
22 | super(AdminLoggedInEnlistmentAPITest, self).setUp() |
23 | self.patch_autospec(node_module, 'wait_for_power_commands') |
24 | + self.patch_autospec(Node, 'start_transition_monitor') |
25 | |
26 | def test_POST_new_sets_power_type_if_admin(self): |
27 | self.client_log_in(as_admin=True) |
28 | @@ -715,9 +718,9 @@ |
29 | 'mac_addresses': ['AA:BB:CC:DD:EE:FF'], |
30 | }) |
31 | |
32 | + self.assertEqual(httplib.OK, response.status_code, response.content) |
33 | node = Node.objects.get( |
34 | system_id=json.loads(response.content)['system_id']) |
35 | - self.assertEqual(httplib.OK, response.status_code) |
36 | self.assertEqual( |
37 | {'param': param}, |
38 | reload_object(node).power_parameters) |
39 | |
40 | === modified file 'src/maasserver/api/tests/test_nodes.py' |
41 | --- src/maasserver/api/tests/test_nodes.py 2014-10-28 15:49:21 +0000 |
42 | +++ src/maasserver/api/tests/test_nodes.py 2014-11-05 04:40:50 +0000 |
43 | @@ -997,6 +997,7 @@ |
44 | def test_POST_accept_gets_node_out_of_declared_state(self): |
45 | # This will change when we add provisioning. Until then, |
46 | # acceptance gets a node straight to Ready state. |
47 | + self.patch_autospec(Node, 'start_transition_monitor') |
48 | self.become_admin() |
49 | target_state = NODE_STATUS.COMMISSIONING |
50 | |
51 | @@ -1066,6 +1067,7 @@ |
52 | def test_POST_accept_accepts_multiple_nodes(self): |
53 | # This will change when we add provisioning. Until then, |
54 | # acceptance gets a node straight to Ready state. |
55 | + self.patch_autospec(Node, 'start_transition_monitor') |
56 | self.become_admin() |
57 | target_state = NODE_STATUS.COMMISSIONING |
58 | |
59 | @@ -1083,6 +1085,7 @@ |
60 | [reload_object(node).status for node in nodes]) |
61 | |
62 | def test_POST_accept_returns_actually_accepted_nodes(self): |
63 | + self.patch_autospec(Node, 'start_transition_monitor') |
64 | self.become_admin() |
65 | acceptable_nodes = [ |
66 | factory.make_Node(status=NODE_STATUS.NEW) |
67 | |
68 | === modified file 'src/maasserver/models/node.py' |
69 | --- src/maasserver/models/node.py 2014-10-30 00:39:21 +0000 |
70 | +++ src/maasserver/models/node.py 2014-11-05 04:40:50 +0000 |
71 | @@ -543,11 +543,22 @@ |
72 | return self.hostname |
73 | |
74 | def get_deployment_time(self): |
75 | - """Return the deployment time of this node (in seconds).""" |
76 | + """Return the deployment time of this node (in seconds). |
77 | + |
78 | + This is the maximum time the deployment is allowed to take. |
79 | + """ |
80 | # Return a *very* conservative estimate for now. |
81 | # Something that shouldn't conflict with any deployment. |
82 | return timedelta(minutes=40).total_seconds() |
83 | |
84 | + def get_commissioning_time(self): |
85 | + """Return the commissioning time of this node (in seconds). |
86 | + |
87 | + This is the maximum time the commissioning is allowed to take. |
88 | + """ |
89 | + # Return a *very* conservative estimate for now. |
90 | + return timedelta(minutes=20).total_seconds() |
91 | + |
92 | def start_deployment(self): |
93 | """Mark a node as being deployed.""" |
94 | self.status = NODE_STATUS.DEPLOYING |
95 | @@ -824,7 +835,6 @@ |
96 | """Install OS and self-test a new node.""" |
97 | # Avoid circular imports. |
98 | from metadataserver.user_data.commissioning import generate_user_data |
99 | - from metadataserver.models import NodeResult |
100 | |
101 | commissioning_user_data = generate_user_data(node=self) |
102 | NodeResult.objects.clear_results(self) |
103 | @@ -838,6 +848,7 @@ |
104 | self.status = NODE_STATUS.COMMISSIONING |
105 | self.save() |
106 | transaction.commit() |
107 | + self.start_transition_monitor(self.get_commissioning_time()) |
108 | try: |
109 | self.start(user, user_data=commissioning_user_data) |
110 | except Exception as ex: |
111 | @@ -862,6 +873,7 @@ |
112 | % (self.system_id, NODE_STATUS_CHOICES_DICT[self.status])) |
113 | maaslog.info( |
114 | "%s: Aborting commissioning", self.hostname) |
115 | + self.stop_transition_monitor() |
116 | try: |
117 | self.stop(user) |
118 | except Exception as ex: |
119 | |
120 | === modified file 'src/maasserver/models/tests/test_node.py' |
121 | --- src/maasserver/models/tests/test_node.py 2014-10-30 00:39:21 +0000 |
122 | +++ src/maasserver/models/tests/test_node.py 2014-11-05 04:40:50 +0000 |
123 | @@ -1233,6 +1233,7 @@ |
124 | target_state = NODE_STATUS.COMMISSIONING |
125 | |
126 | node = factory.make_Node(status=NODE_STATUS.NEW) |
127 | + self.patch(node, 'start_transition_monitor') |
128 | return_value = node.accept_enlistment(factory.make_User()) |
129 | self.assertEqual((node, target_state), (return_value, node.status)) |
130 | |
131 | @@ -1293,6 +1294,7 @@ |
132 | node_start = self.patch(node, 'start') |
133 | factory.make_MACAddress(node=node) |
134 | admin = factory.make_admin() |
135 | + self.patch(node, 'start_transition_monitor') |
136 | node.start_commissioning(admin) |
137 | |
138 | expected_attrs = { |
139 | @@ -1305,6 +1307,7 @@ |
140 | def test_start_commissioning_sets_user_data(self): |
141 | node = factory.make_Node(status=NODE_STATUS.NEW) |
142 | node_start = self.patch(node, 'start') |
143 | + self.patch(node, 'start_transition_monitor') |
144 | user_data = factory.make_string().encode('ascii') |
145 | generate_user_data = self.patch( |
146 | commissioning, 'generate_user_data') |
147 | @@ -1316,6 +1319,7 @@ |
148 | |
149 | def test_start_commissioning_clears_node_commissioning_results(self): |
150 | node = factory.make_Node(status=NODE_STATUS.NEW) |
151 | + self.patch(node, 'start_transition_monitor') |
152 | NodeResult.objects.store_data( |
153 | node, factory.make_string(), |
154 | random.randint(0, 10), |
155 | @@ -1333,6 +1337,7 @@ |
156 | node, filename, script_result, RESULT_TYPE.COMMISSIONING, |
157 | Bin(data)) |
158 | other_node = factory.make_Node(status=NODE_STATUS.NEW) |
159 | + self.patch(other_node, 'start_transition_monitor') |
160 | other_node.start_commissioning(factory.make_admin()) |
161 | self.assertEqual( |
162 | data, NodeResult.objects.get_data(node, filename)) |
163 | @@ -1343,6 +1348,7 @@ |
164 | # status. |
165 | admin = factory.make_admin() |
166 | node = factory.make_Node(status=NODE_STATUS.NEW) |
167 | + self.patch(node, 'start_transition_monitor') |
168 | generate_user_data = self.patch(commissioning, 'generate_user_data') |
169 | node_start = self.patch(node, 'start') |
170 | node_start.side_effect = MultipleFailures( |
171 | @@ -1365,6 +1371,7 @@ |
172 | def test_start_commissioning_logs_and_raises_errors_in_starting(self): |
173 | admin = factory.make_admin() |
174 | node = factory.make_Node(status=NODE_STATUS.NEW) |
175 | + self.patch(node, 'start_transition_monitor') |
176 | maaslog = self.patch(node_module, 'maaslog') |
177 | exception = NoConnectionsAvailable(factory.make_name()) |
178 | self.patch(node, 'start').side_effect = exception |
179 | @@ -1383,6 +1390,7 @@ |
180 | admin = factory.make_admin() |
181 | node = factory.make_Node( |
182 | status=NODE_STATUS.COMMISSIONING, power_type="virsh") |
183 | + self.patch(node, 'stop_transition_monitor') |
184 | node_stop = self.patch(node, 'stop') |
185 | node_stop.side_effect = MultipleFailures( |
186 | Failure(NoConnectionsAvailable())) |
187 | @@ -1398,10 +1406,30 @@ |
188 | self.assertThat(node_stop, MockCalledOnceWith(admin)) |
189 | self.assertEqual(NODE_STATUS.COMMISSIONING, node.status) |
190 | |
191 | + def test_start_commissioning_starts_monitor(self): |
192 | + node = factory.make_Node(status=NODE_STATUS.NEW) |
193 | + monitor_timeout = random.randint(1, 100) |
194 | + self.patch( |
195 | + node, 'get_commissioning_time').return_value = monitor_timeout |
196 | + start_transition_monitor = self.patch( |
197 | + node, 'start_transition_monitor') |
198 | + admin = factory.make_admin() |
199 | + node.start_commissioning(admin) |
200 | + self.assertThat( |
201 | + start_transition_monitor, MockCalledOnceWith(monitor_timeout)) |
202 | + |
203 | + def test_abort_commissioning_stops_monitor(self): |
204 | + node = factory.make_Node(status=NODE_STATUS.COMMISSIONING) |
205 | + stop_transition_monitor = self.patch(node, 'stop_transition_monitor') |
206 | + admin = factory.make_admin() |
207 | + node.abort_commissioning(admin) |
208 | + self.assertThat(stop_transition_monitor, MockCalledOnceWith()) |
209 | + |
210 | def test_abort_commissioning_logs_and_raises_errors_in_stopping(self): |
211 | admin = factory.make_admin() |
212 | node = factory.make_Node(status=NODE_STATUS.COMMISSIONING) |
213 | maaslog = self.patch(node_module, 'maaslog') |
214 | + self.patch(node, 'stop_transition_monitor') |
215 | exception_class = factory.make_exception_type() |
216 | exception = exception_class(factory.make_name()) |
217 | self.patch(node, 'stop').side_effect = exception |
218 | @@ -1419,6 +1447,7 @@ |
219 | admin = factory.make_admin() |
220 | |
221 | node_stop = self.patch(node, 'stop') |
222 | + self.patch(node, 'stop_transition_monitor') |
223 | |
224 | node.abort_commissioning(admin) |
225 | expected_attrs = { |
226 | |
227 | === modified file 'src/maasserver/tests/test_node_action.py' |
228 | --- src/maasserver/tests/test_node_action.py 2014-10-30 02:50:27 +0000 |
229 | +++ src/maasserver/tests/test_node_action.py 2014-11-05 04:40:50 +0000 |
230 | @@ -233,6 +233,7 @@ |
231 | node = factory.make_Node( |
232 | mac=True, status=self.status, |
233 | power_type='ether_wake') |
234 | + self.patch_autospec(node, 'start_transition_monitor') |
235 | node_start = self.patch(node, 'start') |
236 | admin = factory.make_admin() |
237 | action = Commission(node, admin) |
238 | @@ -248,6 +249,7 @@ |
239 | node = factory.make_Node( |
240 | mac=True, status=NODE_STATUS.COMMISSIONING, |
241 | power_type='virsh') |
242 | + self.patch_autospec(node, 'stop_transition_monitor') |
243 | node_stop = self.patch_autospec(node, 'stop') |
244 | admin = factory.make_admin() |
245 | |
246 | @@ -588,6 +590,8 @@ |
247 | exception = self.make_exception() |
248 | self.patch(node, 'start').side_effect = exception |
249 | self.patch(node, 'stop').side_effect = exception |
250 | + self.patch_autospec(node, 'start_transition_monitor') |
251 | + self.patch_autospec(node, 'stop_transition_monitor') |
252 | |
253 | def make_action(self, action_class, node_status): |
254 | node = factory.make_Node( |
255 | |
256 | === modified file 'src/metadataserver/api.py' |
257 | --- src/metadataserver/api.py 2014-10-24 23:45:33 +0000 |
258 | +++ src/metadataserver/api.py 2014-11-05 04:40:50 +0000 |
259 | @@ -281,6 +281,7 @@ |
260 | if node.status == NODE_STATUS.COMMISSIONING: |
261 | self._store_commissioning_results(node, request) |
262 | store_node_power_parameters(node, request) |
263 | + node.stop_transition_monitor() |
264 | target_status = self.signaling_statuses.get(status) |
265 | elif node.status == NODE_STATUS.DEPLOYING: |
266 | self._store_installation_results(node, request) |
267 | |
268 | === modified file 'src/metadataserver/tests/test_api.py' |
269 | --- src/metadataserver/tests/test_api.py 2014-10-24 23:45:33 +0000 |
270 | +++ src/metadataserver/tests/test_api.py 2014-11-05 04:40:50 +0000 |
271 | @@ -41,6 +41,7 @@ |
272 | SSHKey, |
273 | Tag, |
274 | ) |
275 | +from maasserver.models.node import Node |
276 | from maasserver.rpc.testing.mixins import PreseedRPCMixin |
277 | from maasserver.testing.factory import factory |
278 | from maasserver.testing.oauthclient import OAuthAuthenticatedClient |
279 | @@ -501,6 +502,10 @@ |
280 | |
281 | class TestCommissioningAPI(MAASServerTestCase): |
282 | |
283 | + def setUp(self): |
284 | + super(TestCommissioningAPI, self).setUp() |
285 | + self.patch(Node, 'stop_transition_monitor') |
286 | + |
287 | def test_commissioning_scripts(self): |
288 | script = factory.make_CommissioningScript() |
289 | response = make_node_client().get( |
290 | @@ -620,6 +625,13 @@ |
291 | self.assertEqual(httplib.OK, response.status_code) |
292 | self.assertEqual(NODE_STATUS.READY, reload_object(node).status) |
293 | |
294 | + def test_signalling_commissioning_success_cancels_monitor(self): |
295 | + node = factory.make_Node(status=NODE_STATUS.COMMISSIONING) |
296 | + client = make_node_client(node=node) |
297 | + response = call_signal(client, status='OK') |
298 | + self.assertEqual(httplib.OK, response.status_code, response.content) |
299 | + self.assertThat(node.stop_transition_monitor, MockCalledOnceWith()) |
300 | + |
301 | def test_signaling_commissioning_success_is_idempotent(self): |
302 | node = factory.make_Node(status=NODE_STATUS.COMMISSIONING) |
303 | client = make_node_client(node=node) |
304 | @@ -645,6 +657,13 @@ |
305 | self.assertEqual( |
306 | NODE_STATUS.FAILED_COMMISSIONING, reload_object(node).status) |
307 | |
308 | + def test_signalling_commissioning_failure_cancels_monitor(self): |
309 | + node = factory.make_Node(status=NODE_STATUS.COMMISSIONING) |
310 | + client = make_node_client(node=node) |
311 | + response = call_signal(client, status='FAILED') |
312 | + self.assertEqual(httplib.OK, response.status_code, response.content) |
313 | + self.assertThat(node.stop_transition_monitor, MockCalledOnceWith()) |
314 | + |
315 | def test_signaling_commissioning_failure_is_idempotent(self): |
316 | node = factory.make_Node(status=NODE_STATUS.COMMISSIONING) |
317 | client = make_node_client(node=node) |
Two inline notes.