Merge lp:~ltrager/maas/push_rack_refresh into lp:~maas-committers/maas/trunk

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: no longer in the source branch.
Merged at revision: 5096
Proposed branch: lp:~ltrager/maas/push_rack_refresh
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 673 lines (+116/-218)
15 files modified
src/maasserver/api/rackcontrollers.py (+0/-15)
src/maasserver/api/tests/test_rackcontroller.py (+0/-17)
src/maasserver/models/node.py (+5/-1)
src/maasserver/node_action.py (+0/-20)
src/maasserver/rpc/rackcontrollers.py (+13/-43)
src/maasserver/rpc/regionservice.py (+13/-8)
src/maasserver/rpc/testing/fixtures.py (+1/-1)
src/maasserver/rpc/tests/test_rackcontrollers.py (+12/-59)
src/maasserver/rpc/tests/test_regionservice.py (+1/-1)
src/maasserver/rpc/tests/test_regionservice_calls.py (+24/-0)
src/maasserver/tests/test_node_action.py (+1/-47)
src/maasserver/websockets/handlers/tests/test_general.py (+2/-2)
src/provisioningserver/pserv_services/networks_monitoring_service.py (+11/-4)
src/provisioningserver/pserv_services/tests/test_networks_monitoring_service.py (+20/-0)
src/provisioningserver/rpc/region.py (+13/-0)
To merge this branch: bzr merge lp:~ltrager/maas/push_rack_refresh
Reviewer Review Type Date Requested Status
Lee Trager (community) Approve
Blake Rouse Pending
Review via email: mp+297014@code.launchpad.net

This proposal supersedes a proposal from 2016-06-08.

Commit message

Have the rack request a refresh on connect instead of the region determining if one is needed.

Description of the change

This modifies the rack registration process so the region no longer determines if a refresh is needed. The network monitoring service will now request a refresh the first time it can connect to a region. As the network monitoring service runs every 30 seconds the refresh typically doesn't occur until the rack has been running for 30 seconds. This is because as soon as the rack starts the network monitoring service tries and fails to obtain an RPC client for the region as the connection hasn't been made yet. lp:~ltrager/maas/region_refresh modifies the region to refresh itself on start which will ensure region and rack controllers are refreshed as soon as the region service comes up.

As discussed this also removes the refresh API and websockets. Users who want to run a refresh on a controller will have to restart the maas-regiond or maas-rackd service.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote : Posted in a previous version of this proposal

Looks good. Just one comment.

review: Approve
Revision history for this message
Lee Trager (ltrager) wrote :

Accidentally had this to merge into a previous branch and not trunk.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/api/rackcontrollers.py'
2--- src/maasserver/api/rackcontrollers.py 2016-05-16 08:36:33 +0000
3+++ src/maasserver/api/rackcontrollers.py 2016-06-09 23:56:03 +0000
4@@ -124,21 +124,6 @@
5
6 @admin_method
7 @operation(idempotent=False)
8- def refresh(self, request, system_id):
9- """Refresh the hardware information for a specific rack controller.
10-
11- Returns 404 if the rack-controller is not found.
12- Returns 403 if the user does not have permission to refresh the rack.
13- """
14- rack = self.model.objects.get_node_or_404(
15- system_id=system_id, user=request.user, perm=NODE_PERMISSION.EDIT)
16- rack.refresh()
17- return HttpResponse(
18- "Refresh of %s has begun" % rack.hostname,
19- content_type=("text/plain; charset=%s" % settings.DEFAULT_CHARSET))
20-
21- @admin_method
22- @operation(idempotent=False)
23 def import_boot_images(self, request, system_id):
24 """Import the boot images on this rack controller.
25
26
27=== modified file 'src/maasserver/api/tests/test_rackcontroller.py'
28--- src/maasserver/api/tests/test_rackcontroller.py 2016-05-24 22:05:45 +0000
29+++ src/maasserver/api/tests/test_rackcontroller.py 2016-06-09 23:56:03 +0000
30@@ -7,7 +7,6 @@
31
32 from django.core.urlresolvers import reverse
33 from maasserver.api import rackcontrollers
34-from maasserver.models import node as node_module
35 from maasserver.testing.api import (
36 APITestCase,
37 explain_unexpected_response,
38@@ -49,22 +48,6 @@
39 response = self.client.put(self.get_rack_uri(rack), {})
40 self.assertEqual(http.client.FORBIDDEN, response.status_code)
41
42- def test_POST_refresh_checks_permission(self):
43- self.patch(node_module.RackController, 'refresh')
44- rack = factory.make_RackController(owner=factory.make_User())
45- response = self.client.post(self.get_rack_uri(rack), {'op': 'refresh'})
46- self.assertEqual(http.client.FORBIDDEN, response.status_code)
47-
48- def test_POST_refresh_returns_null(self):
49- self.patch(node_module.RackController, 'refresh')
50- self.become_admin()
51- rack = factory.make_RackController(owner=factory.make_User())
52- response = self.client.post(self.get_rack_uri(rack), {'op': 'refresh'})
53- self.assertEqual(http.client.OK, response.status_code)
54- self.assertEqual(
55- ('Refresh of %s has begun' % rack.hostname).encode('utf-8'),
56- response.content)
57-
58 def test_POST_import_boot_images_import_to_rack_controllers(self):
59 from maasserver.clusterrpc import boot_images
60 self.patch(boot_images, "RackControllersImporter")
61
62=== modified file 'src/maasserver/models/node.py'
63--- src/maasserver/models/node.py 2016-06-08 08:37:14 +0000
64+++ src/maasserver/models/node.py 2016-06-09 23:56:03 +0000
65@@ -3606,7 +3606,11 @@
66 def _get_token_for_controller(self):
67 # Avoid circular imports.
68 from metadataserver.models import NodeKey
69- return NodeKey.objects.get_token_for_node(self)
70+ token = NodeKey.objects.get_token_for_node(self)
71+ # Pull consumer into memory so it can be accessed outside a
72+ # database thread
73+ token.consumer
74+ return token
75
76 @transactional
77 def _get_current_node_result_ids(self):
78
79=== modified file 'src/maasserver/node_action.py'
80--- src/maasserver/node_action.py 2016-05-26 06:47:45 +0000
81+++ src/maasserver/node_action.py 2016-06-09 23:56:03 +0000
82@@ -482,25 +482,6 @@
83 raise NodeActionError(exception)
84
85
86-class Refresh(NodeAction):
87- """Refresh a rack or region and rack controller."""
88- name = "refresh"
89- display = "Refresh"
90- display_sentence = "refreshed"
91- permission = NODE_PERMISSION.ADMIN
92- for_type = {
93- NODE_TYPE.RACK_CONTROLLER,
94- NODE_TYPE.REGION_AND_RACK_CONTROLLER
95- }
96-
97- def execute(self):
98- """See `NodeAction.execute`."""
99- try:
100- self.node.refresh()
101- except RPC_EXCEPTIONS + (ExternalProcessError,) as exception:
102- raise NodeActionError(exception)
103-
104-
105 ACTION_CLASSES = (
106 Commission,
107 Acquire,
108@@ -514,7 +495,6 @@
109 SetZone,
110 Delete,
111 ImportImages,
112- Refresh,
113 )
114
115 ACTIONS_DICT = OrderedDict((action.name, action) for action in ACTION_CLASSES)
116
117=== modified file 'src/maasserver/rpc/rackcontrollers.py'
118--- src/maasserver/rpc/rackcontrollers.py 2016-05-31 19:15:43 +0000
119+++ src/maasserver/rpc/rackcontrollers.py 2016-06-09 23:56:03 +0000
120@@ -80,7 +80,7 @@
121 registered and successfully connected we will refresh all commissioning
122 data.
123
124- :return: A ``(rack-controller, refresh-hint)`` tuple.
125+ :return: A ``rack-controller``.
126 """
127 if interfaces is None:
128 interfaces = {}
129@@ -91,13 +91,18 @@
130 maaslog.info("Created new rack controller %s.", node.hostname)
131 elif node.is_rack_controller:
132 maaslog.info("Registering existing rack controller %s.", node.hostname)
133+ elif node.is_region_controller:
134+ maaslog.info(
135+ "Converting %s into a region and rack controller.", node.hostname)
136+ node.node_type = NODE_TYPE.REGION_AND_RACK_CONTROLLER
137+ node.save()
138 else:
139- maaslog.info(
140- "Found existing node %s as candidate for rack controller.",
141- node.hostname)
142-
143- # This may be a no-op, but it does provide us with a refresh hint.
144- rackcontroller, needs_refresh = upgrade(node)
145+ maaslog.info("Converting %s into a rack controller.", node.hostname)
146+ node.node_type = NODE_TYPE.RACK_CONTROLLER
147+ node.save()
148+
149+ rackcontroller = typecast_node(node, RackController)
150+
151 # Update `rackcontroller.url` from the given URL, but only when the
152 # hostname is not 'localhost' (i.e. the default value used when the master
153 # cluster connects).
154@@ -112,8 +117,7 @@
155 rackcontroller.save(update_fields=update_fields)
156 # Update networking information every time we see a rack.
157 rackcontroller.update_interfaces(interfaces)
158- # Hint to callers whether or not this rack needs to be refreshed.
159- return rackcontroller, needs_refresh
160+ return rackcontroller
161
162
163 @typed
164@@ -136,40 +140,6 @@
165 return Node.objects.filter(query).first()
166
167
168-def upgrade(node):
169- """Upgrade `node` to a rack controller if it isn't already.
170-
171- Return a hint as to whether a refresh is necessary.
172-
173- :return: A ``(rack-controller, refresh-hint)`` tuple.
174- """
175- # Refresh whenever an existing node is converted for use as a rack
176- # controller. This is needed for two reasons. First, when the region starts
177- # it creates a node for itself but only gathers networking information.
178- # Second, information about the node may have changed since its last use.
179- needs_refresh = True
180-
181- if node.is_rack_controller:
182- # We don't want to refresh existing rack controllers as each time a
183- # rack controller connects to a region it creates four connections.
184- # This means for every region we connect to we would refresh
185- # 4 * regions every time the rack controller restarts. If the cpu_count
186- # and memory is non-zero our information at this point should be
187- # current and the user can always manually refresh.
188- needs_refresh = (node.cpu_count == 0 or node.memory == 0)
189- elif node.is_region_controller:
190- maaslog.info(
191- "Converting %s into a region and rack controller.", node.hostname)
192- node.node_type = NODE_TYPE.REGION_AND_RACK_CONTROLLER
193- node.save()
194- else:
195- maaslog.info("Converting %s into a rack controller.", node.hostname)
196- node.node_type = NODE_TYPE.RACK_CONTROLLER
197- node.save()
198-
199- return typecast_node(node, RackController), needs_refresh
200-
201-
202 @transactional
203 def update_foreign_dhcp(system_id, interface_name, dhcp_ip=None):
204 """Update the external_dhcp field of the VLAN for the interface.
205
206=== modified file 'src/maasserver/rpc/regionservice.py'
207--- src/maasserver/rpc/regionservice.py 2016-05-27 13:14:01 +0000
208+++ src/maasserver/rpc/regionservice.py 2016-06-09 23:56:03 +0000
209@@ -445,6 +445,18 @@
210 return deferToDatabase(
211 update_services, system_id, services)
212
213+ @region.RequestRackRefresh.responder
214+ def request_rack_refresh(self, system_id):
215+ """Request a refresh of the rack
216+
217+ Implementation of
218+ :py:class:`~provisioningserver.rpc.region.RequestRackRefresh`.
219+ """
220+ d = deferToDatabase(RackController.objects.get, system_id=system_id)
221+ d.addCallback(lambda rack: rack.refresh())
222+ d.addCallback(lambda _: {})
223+ return d
224+
225
226 @transactional
227 def registerConnection(region_id, rack_controller, host):
228@@ -534,7 +546,7 @@
229
230 try:
231 # Register, which includes updating interfaces.
232- rack_controller, needs_refresh = yield deferToDatabase(
233+ rack_controller = yield deferToDatabase(
234 rackcontrollers.register, system_id=system_id,
235 hostname=hostname, interfaces=interfaces, url=url)
236
237@@ -568,13 +580,6 @@
238 # the information about the rack controller if needed.
239 log.msg(
240 "Rack controller '%s' has been registered." % self.ident)
241- if self.hostIsRemote and needs_refresh:
242- # Needs to be refresh. Perform this operation in a thread but
243- # we ignore when it is done.
244- log.msg(
245- "Rack controller '%s' needs to be refreshed; "
246- "performing the refresh operation." % self.ident)
247- deferToDatabase(rack_controller.refresh)
248
249 # Done registering the rack controller and connection.
250 return {'system_id': self.ident}
251
252=== modified file 'src/maasserver/rpc/testing/fixtures.py'
253--- src/maasserver/rpc/testing/fixtures.py 2016-05-27 13:14:01 +0000
254+++ src/maasserver/rpc/testing/fixtures.py 2016-06-09 23:56:03 +0000
255@@ -321,7 +321,7 @@
256 # Mock the registration into the database, as the rack controller is
257 # already created. We reset this once registration is complete so as
258 # to not interfere with other tests.
259- registered = rack_controller, False # Hint that no refresh needed.
260+ registered = rack_controller
261 patcher = MonkeyPatcher()
262 patcher.add_patch(
263 rackcontrollers, "register",
264
265=== modified file 'src/maasserver/rpc/tests/test_rackcontrollers.py'
266--- src/maasserver/rpc/tests/test_rackcontrollers.py 2016-05-31 19:17:13 +0000
267+++ src/maasserver/rpc/tests/test_rackcontrollers.py 2016-06-09 23:56:03 +0000
268@@ -108,23 +108,23 @@
269
270 def test_sets_owner_to_worker_when_none(self):
271 node = factory.make_Node()
272- rack_registered, needs_refresh = register(system_id=node.system_id)
273+ rack_registered = register(system_id=node.system_id)
274 self.assertEqual(worker_user.get_worker_user(), rack_registered.owner)
275
276 def test_leaves_owner_when_owned(self):
277 user = factory.make_User()
278 node = factory.make_Machine(owner=user)
279- rack_registered, needs_refresh = register(system_id=node.system_id)
280+ rack_registered = register(system_id=node.system_id)
281 self.assertEqual(user, rack_registered.owner)
282
283 def test_finds_existing_node_by_system_id(self):
284 node = factory.make_Node()
285- rack_registered, needs_refresh = register(system_id=node.system_id)
286+ rack_registered = register(system_id=node.system_id)
287 self.assertEqual(node.system_id, rack_registered.system_id)
288
289 def test_finds_existing_node_by_hostname(self):
290 node = factory.make_Node()
291- rack_registered, needs_refresh = register(hostname=node.hostname)
292+ rack_registered = register(hostname=node.hostname)
293 self.assertEqual(node.system_id, rack_registered.system_id)
294
295 def test_finds_existing_node_by_mac(self):
296@@ -140,39 +140,9 @@
297 "enabled": True,
298 }
299 }
300- rack_registered, needs_refresh = register(interfaces=interfaces)
301+ rack_registered = register(interfaces=interfaces)
302 self.assertEqual(node.system_id, rack_registered.system_id)
303
304- def test_finds_existing_controller_needs_refresh_with_bad_info(self):
305- node_type = random.choice([
306- NODE_TYPE.RACK_CONTROLLER,
307- NODE_TYPE.REGION_AND_RACK_CONTROLLER,
308- ])
309- node = factory.make_Node(node_type=node_type)
310- rack_registered, needs_refresh = register(system_id=node.system_id)
311- self.assertTrue(needs_refresh)
312-
313- def test_finds_existing_controller_doesnt_need_refresh_good_info(self):
314- node_type = random.choice([
315- NODE_TYPE.RACK_CONTROLLER,
316- NODE_TYPE.REGION_AND_RACK_CONTROLLER,
317- ])
318- node = factory.make_Node(
319- node_type=node_type, cpu_count=random.randint(1, 32),
320- memory=random.randint(1024, 8096))
321- rack_registered, needs_refresh = register(system_id=node.system_id)
322- self.assertFalse(needs_refresh)
323-
324- def test_converts_existing_node_sets_needs_refresh_to_true(self):
325- node_type = random.choice([
326- NODE_TYPE.MACHINE,
327- NODE_TYPE.DEVICE,
328- NODE_TYPE.REGION_CONTROLLER,
329- ])
330- node = factory.make_Node(node_type=node_type)
331- rack_registered, needs_refresh = register(system_id=node.system_id)
332- self.assertTrue(needs_refresh)
333-
334 def test_find_existing_keeps_type(self):
335 node_type = random.choice(
336 (NODE_TYPE.RACK_CONTROLLER, NODE_TYPE.REGION_AND_RACK_CONTROLLER))
337@@ -190,7 +160,7 @@
338
339 def test_converts_region_controller(self):
340 node = factory.make_Node(node_type=NODE_TYPE.REGION_CONTROLLER)
341- rack_registered, needs_refresh = register(system_id=node.system_id)
342+ rack_registered = register(system_id=node.system_id)
343 self.assertEqual(
344 rack_registered.node_type, NODE_TYPE.REGION_AND_RACK_CONTROLLER)
345
346@@ -199,14 +169,12 @@
347 node = factory.make_Node(node_type=NODE_TYPE.REGION_CONTROLLER)
348 register(system_id=node.system_id)
349 self.assertEqual(
350- "Found existing node %s as candidate for rack controller.\n"
351- "Converting %s into a region and rack controller.\n"
352- % (node.hostname, node.hostname),
353- logger.output)
354+ "Converting %s into a region and rack controller.\n" %
355+ node.hostname, logger.output)
356
357 def test_converts_existing_node(self):
358 node = factory.make_Node(node_type=NODE_TYPE.MACHINE)
359- rack_registered, needs_refresh = register(system_id=node.system_id)
360+ rack_registered = register(system_id=node.system_id)
361 self.assertEqual(rack_registered.node_type, NODE_TYPE.RACK_CONTROLLER)
362
363 def test_logs_converting_existing_node(self):
364@@ -214,9 +182,7 @@
365 node = factory.make_Node(node_type=NODE_TYPE.MACHINE)
366 register(system_id=node.system_id)
367 self.assertEqual(
368- "Found existing node %s as candidate for rack controller.\n"
369- "Converting %s into a rack controller.\n"
370- % (node.hostname, node.hostname),
371+ "Converting %s into a rack controller.\n" % node.hostname,
372 logger.output)
373
374 def test_creates_new_rackcontroller(self):
375@@ -234,19 +200,6 @@
376 register(interfaces=interfaces)
377 self.assertEqual(node_count + 1, len(Node.objects.all()))
378
379- def test_creates_new_rackcontroller_sets_needs_refresh_to_true(self):
380- interfaces = {
381- factory.make_name("eth0"): {
382- "type": "physical",
383- "mac_address": factory.make_mac_address(),
384- "parents": [],
385- "links": [],
386- "enabled": True,
387- }
388- }
389- rack_registered, needs_refresh = register(interfaces=interfaces)
390- self.assertTrue(needs_refresh)
391-
392 def test_logs_creating_new_rackcontroller(self):
393 logger = self.useFixture(FakeLogger("maas"))
394 hostname = factory.make_name("hostname")
395@@ -266,7 +219,7 @@
396 "enabled": True,
397 }
398 }
399- rack_registered, needs_refresh = register(interfaces=interfaces)
400+ rack_registered = register(interfaces=interfaces)
401 self.assertThat(
402 rack_registered.interface_set.all(),
403 MatchesSetwise(*(
404@@ -293,7 +246,7 @@
405 "enabled": True,
406 }
407 }
408- rack_registered, needs_refresh = register(
409+ rack_registered = register(
410 rack_controller.system_id, interfaces=interfaces)
411 self.assertThat(
412 rack_registered.interface_set.all(),
413
414=== modified file 'src/maasserver/rpc/tests/test_regionservice.py'
415--- src/maasserver/rpc/tests/test_regionservice.py 2016-05-27 13:14:01 +0000
416+++ src/maasserver/rpc/tests/test_regionservice.py 2016-06-09 23:56:03 +0000
417@@ -523,7 +523,7 @@
418 protocol.transport.getHost.return_value = host
419 mock_deferToDatabase = self.patch(regionservice, "deferToDatabase")
420 mock_deferToDatabase.side_effect = [
421- succeed((rack_controller, False)),
422+ succeed(rack_controller),
423 succeed(None),
424 ]
425 yield call_responder(
426
427=== modified file 'src/maasserver/rpc/tests/test_regionservice_calls.py'
428--- src/maasserver/rpc/tests/test_regionservice_calls.py 2016-05-25 21:03:50 +0000
429+++ src/maasserver/rpc/tests/test_regionservice_calls.py 2016-06-09 23:56:03 +0000
430@@ -59,6 +59,7 @@
431 )
432 from maasserver.utils.threads import deferToDatabase
433 from maastesting.matchers import (
434+ MockCalledOnce,
435 MockCalledOnceWith,
436 MockCalledWith,
437 )
438@@ -84,6 +85,7 @@
439 ReportBootImages,
440 ReportForeignDHCPServer,
441 RequestNodeInfoByMACAddress,
442+ RequestRackRefresh,
443 SendEvent,
444 SendEventMACAddress,
445 UpdateInterfaces,
446@@ -1233,3 +1235,25 @@
447 params = {'mac_address': factory.make_mac_address()}
448 d = call_responder(Region(), RequestNodeInfoByMACAddress, params)
449 return assert_fails_with(d, NoSuchNode)
450+
451+
452+class TestRegionProtocol_RequestRefresh(MAASTransactionServerTestCase):
453+
454+ def test_request_refresh_is_registered(self):
455+ protocol = Region()
456+ responder = protocol.locateResponder(
457+ RequestRackRefresh.commandName)
458+ self.assertIsNotNone(responder)
459+
460+ @wait_for_reactor
461+ @inlineCallbacks
462+ def test_calls_refresh(self):
463+ rack = yield deferToDatabase(factory.make_RackController)
464+ self.patch(regionservice, 'deferToDatabase').return_value = succeed(
465+ rack)
466+ mock_refresh = self.patch(rack, 'refresh')
467+ response = yield call_responder(
468+ Region(), RequestRackRefresh, {'system_id': rack.system_id})
469+ self.assertIsNotNone(response)
470+
471+ self.assertThat(mock_refresh, MockCalledOnce())
472
473=== modified file 'src/maasserver/tests/test_node_action.py'
474--- src/maasserver/tests/test_node_action.py 2016-05-26 06:47:45 +0000
475+++ src/maasserver/tests/test_node_action.py 2016-06-09 23:56:03 +0000
476@@ -5,7 +5,6 @@
477
478 __all__ = []
479
480-import random
481 from unittest.mock import ANY
482
483 from django.db import transaction
484@@ -28,10 +27,7 @@
485 signals,
486 StaticIPAddress,
487 )
488-from maasserver.models.node import (
489- RackController,
490- typecast_to_node_type,
491-)
492+from maasserver.models.node import typecast_to_node_type
493 from maasserver.models.signals.testing import SignalsDisabled
494 from maasserver.node_action import (
495 Abort,
496@@ -46,7 +42,6 @@
497 NodeAction,
498 PowerOff,
499 PowerOn,
500- Refresh,
501 Release,
502 RPC_EXCEPTIONS,
503 SetZone,
504@@ -854,33 +849,6 @@
505 self.assertFalse(ImportImages(node, user).is_actionable())
506
507
508-class TestRefresh(MAASServerTestCase):
509-
510- def test_refresh(self):
511- user = factory.make_admin()
512- rack = factory.make_RackController()
513- mock_refresh = self.patch(rack, 'refresh')
514-
515- with post_commit_hooks:
516- Refresh(rack, user).execute()
517-
518- self.assertThat(mock_refresh, MockCalledOnce())
519-
520- def test_requires_admin_permission(self):
521- user = factory.make_User()
522- rack = factory.make_RackController()
523- self.assertFalse(Refresh(rack, user).is_permitted())
524-
525- def test_requires_rack(self):
526- user = factory.make_User()
527- node = factory.make_Node(
528- node_type=factory.pick_choice(
529- NODE_TYPE_CHOICES, but_not=[
530- NODE_TYPE.RACK_CONTROLLER,
531- NODE_TYPE.REGION_AND_RACK_CONTROLLER]))
532- self.assertFalse(Refresh(node, user).is_actionable())
533-
534-
535 class TestActionsErrorHandling(MAASServerTestCase):
536 """Tests for error handling in actions.
537
538@@ -904,8 +872,6 @@
539 exception = self.make_exception()
540 self.patch(node, '_start').side_effect = exception
541 self.patch(node, '_stop').side_effect = exception
542- if isinstance(node, RackController):
543- self.patch(node, 'refresh').side_effect = exception
544
545 def make_action(
546 self, action_class, node_status, power_state=None,
547@@ -961,15 +927,3 @@
548 self.assertEqual(
549 get_error_message_for_exception(action.node._stop.side_effect),
550 str(exception))
551-
552- def test_Refresh_handles_rpc_errors(self):
553- action = self.make_action(
554- Refresh, NODE_STATUS.ALLOCATED, power_state=POWER_STATE.ON,
555- node_type=random.choice([
556- NODE_TYPE.RACK_CONTROLLER,
557- NODE_TYPE.REGION_AND_RACK_CONTROLLER]))
558- self.patch_rpc_methods(action.node)
559- exception = self.assertRaises(NodeActionError, action.execute)
560- self.assertEqual(
561- get_error_message_for_exception(action.node._stop.side_effect),
562- str(exception))
563
564=== modified file 'src/maasserver/websockets/handlers/tests/test_general.py'
565--- src/maasserver/websockets/handlers/tests/test_general.py 2016-05-26 06:47:45 +0000
566+++ src/maasserver/websockets/handlers/tests/test_general.py 2016-06-09 23:56:03 +0000
567@@ -144,7 +144,7 @@
568 def test_rack_controller_actions_for_admin(self):
569 handler = GeneralHandler(factory.make_admin(), {})
570 self.assertItemsEqual(
571- ['delete', 'import-images', 'off', 'on', 'refresh', 'set-zone'],
572+ ['delete', 'import-images', 'off', 'on', 'set-zone'],
573 [action['name'] for action in handler.rack_controller_actions({})])
574
575 def test_rack_controller_actions_for_non_admin(self):
576@@ -154,7 +154,7 @@
577 def test_region_and_rack_controller_actions_for_admin(self):
578 handler = GeneralHandler(factory.make_admin(), {})
579 self.assertItemsEqual(
580- ['set-zone', 'refresh', 'delete', 'import-images'],
581+ ['set-zone', 'delete', 'import-images'],
582 [action['name']
583 for action in handler.region_and_rack_controller_actions({})])
584
585
586=== modified file 'src/provisioningserver/pserv_services/networks_monitoring_service.py'
587--- src/provisioningserver/pserv_services/networks_monitoring_service.py 2016-06-03 17:31:53 +0000
588+++ src/provisioningserver/pserv_services/networks_monitoring_service.py 2016-06-09 23:56:03 +0000
589@@ -8,7 +8,10 @@
590 ]
591
592 from provisioningserver.logger.log import get_maas_logger
593-from provisioningserver.rpc.region import UpdateInterfaces
594+from provisioningserver.rpc.region import (
595+ RequestRackRefresh,
596+ UpdateInterfaces,
597+)
598 from provisioningserver.utils.services import NetworksMonitoringService
599
600
601@@ -25,6 +28,10 @@
602 def recordInterfaces(self, interfaces):
603 """Record the interfaces information."""
604 client = self.clientService.getClient()
605- return client(
606- UpdateInterfaces, system_id=client.localIdent,
607- interfaces=interfaces)
608+ # On first run perform a refresh
609+ if self._recorded is None:
610+ return client(RequestRackRefresh, system_id=client.localIdent)
611+ else:
612+ return client(
613+ UpdateInterfaces, system_id=client.localIdent,
614+ interfaces=interfaces)
615
616=== modified file 'src/provisioningserver/pserv_services/tests/test_networks_monitoring_service.py'
617--- src/provisioningserver/pserv_services/tests/test_networks_monitoring_service.py 2016-06-04 08:51:04 +0000
618+++ src/provisioningserver/pserv_services/tests/test_networks_monitoring_service.py 2016-06-09 23:56:03 +0000
619@@ -29,6 +29,22 @@
620 run_tests_with = MAASTwistedRunTest.make_factory(timeout=5)
621
622 @inlineCallbacks
623+ def test_runs_refresh_first_time(self):
624+ fixture = self.useFixture(MockLiveClusterToRegionRPCFixture())
625+ protocol, connecting = fixture.makeEventLoop(region.RequestRackRefresh)
626+ self.addCleanup((yield connecting))
627+
628+ rpc_service = services.getServiceNamed('rpc')
629+ service = RackNetworksMonitoringService(rpc_service, Clock())
630+
631+ yield service.startService()
632+ yield service.stopService()
633+
634+ self.assertThat(
635+ protocol.RequestRackRefresh, MockCalledOnceWith(
636+ protocol, system_id=rpc_service.getClient().localIdent))
637+
638+ @inlineCallbacks
639 def test_reports_interfaces_to_region(self):
640 fixture = self.useFixture(MockLiveClusterToRegionRPCFixture())
641 protocol, connecting = fixture.makeEventLoop(region.UpdateInterfaces)
642@@ -47,6 +63,10 @@
643 rpc_service = services.getServiceNamed('rpc')
644 service = RackNetworksMonitoringService(rpc_service, Clock())
645 service.getInterfaces = lambda: succeed(interfaces)
646+ # Put something in the cache. This tells recordInterfaces that refresh
647+ # has already run but the interfaces have changed thus they need to be
648+ # updated.
649+ service._interfacesRecorded({})
650
651 yield service.startService()
652 yield service.stopService()
653
654=== modified file 'src/provisioningserver/rpc/region.py'
655--- src/provisioningserver/rpc/region.py 2016-05-11 00:25:14 +0000
656+++ src/provisioningserver/rpc/region.py 2016-06-09 23:56:03 +0000
657@@ -480,3 +480,16 @@
658 errors = {
659 NoSuchCluster: b"NoSuchCluster",
660 }
661+
662+
663+class RequestRackRefresh(amp.Command):
664+ """Request a refresh of the rack from the region.
665+
666+ :since: 2.0
667+ """
668+
669+ arguments = [
670+ (b"system_id", amp.Unicode()),
671+ ]
672+ response = []
673+ errors = []