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
=== modified file 'src/maasserver/api/rackcontrollers.py'
--- src/maasserver/api/rackcontrollers.py 2016-05-16 08:36:33 +0000
+++ src/maasserver/api/rackcontrollers.py 2016-06-09 23:56:03 +0000
@@ -124,21 +124,6 @@
124124
125 @admin_method125 @admin_method
126 @operation(idempotent=False)126 @operation(idempotent=False)
127 def refresh(self, request, system_id):
128 """Refresh the hardware information for a specific rack controller.
129
130 Returns 404 if the rack-controller is not found.
131 Returns 403 if the user does not have permission to refresh the rack.
132 """
133 rack = self.model.objects.get_node_or_404(
134 system_id=system_id, user=request.user, perm=NODE_PERMISSION.EDIT)
135 rack.refresh()
136 return HttpResponse(
137 "Refresh of %s has begun" % rack.hostname,
138 content_type=("text/plain; charset=%s" % settings.DEFAULT_CHARSET))
139
140 @admin_method
141 @operation(idempotent=False)
142 def import_boot_images(self, request, system_id):127 def import_boot_images(self, request, system_id):
143 """Import the boot images on this rack controller.128 """Import the boot images on this rack controller.
144129
145130
=== modified file 'src/maasserver/api/tests/test_rackcontroller.py'
--- src/maasserver/api/tests/test_rackcontroller.py 2016-05-24 22:05:45 +0000
+++ src/maasserver/api/tests/test_rackcontroller.py 2016-06-09 23:56:03 +0000
@@ -7,7 +7,6 @@
77
8from django.core.urlresolvers import reverse8from django.core.urlresolvers import reverse
9from maasserver.api import rackcontrollers9from maasserver.api import rackcontrollers
10from maasserver.models import node as node_module
11from maasserver.testing.api import (10from maasserver.testing.api import (
12 APITestCase,11 APITestCase,
13 explain_unexpected_response,12 explain_unexpected_response,
@@ -49,22 +48,6 @@
49 response = self.client.put(self.get_rack_uri(rack), {})48 response = self.client.put(self.get_rack_uri(rack), {})
50 self.assertEqual(http.client.FORBIDDEN, response.status_code)49 self.assertEqual(http.client.FORBIDDEN, response.status_code)
5150
52 def test_POST_refresh_checks_permission(self):
53 self.patch(node_module.RackController, 'refresh')
54 rack = factory.make_RackController(owner=factory.make_User())
55 response = self.client.post(self.get_rack_uri(rack), {'op': 'refresh'})
56 self.assertEqual(http.client.FORBIDDEN, response.status_code)
57
58 def test_POST_refresh_returns_null(self):
59 self.patch(node_module.RackController, 'refresh')
60 self.become_admin()
61 rack = factory.make_RackController(owner=factory.make_User())
62 response = self.client.post(self.get_rack_uri(rack), {'op': 'refresh'})
63 self.assertEqual(http.client.OK, response.status_code)
64 self.assertEqual(
65 ('Refresh of %s has begun' % rack.hostname).encode('utf-8'),
66 response.content)
67
68 def test_POST_import_boot_images_import_to_rack_controllers(self):51 def test_POST_import_boot_images_import_to_rack_controllers(self):
69 from maasserver.clusterrpc import boot_images52 from maasserver.clusterrpc import boot_images
70 self.patch(boot_images, "RackControllersImporter")53 self.patch(boot_images, "RackControllersImporter")
7154
=== modified file 'src/maasserver/models/node.py'
--- src/maasserver/models/node.py 2016-06-08 08:37:14 +0000
+++ src/maasserver/models/node.py 2016-06-09 23:56:03 +0000
@@ -3606,7 +3606,11 @@
3606 def _get_token_for_controller(self):3606 def _get_token_for_controller(self):
3607 # Avoid circular imports.3607 # Avoid circular imports.
3608 from metadataserver.models import NodeKey3608 from metadataserver.models import NodeKey
3609 return NodeKey.objects.get_token_for_node(self)3609 token = NodeKey.objects.get_token_for_node(self)
3610 # Pull consumer into memory so it can be accessed outside a
3611 # database thread
3612 token.consumer
3613 return token
36103614
3611 @transactional3615 @transactional
3612 def _get_current_node_result_ids(self):3616 def _get_current_node_result_ids(self):
36133617
=== modified file 'src/maasserver/node_action.py'
--- src/maasserver/node_action.py 2016-05-26 06:47:45 +0000
+++ src/maasserver/node_action.py 2016-06-09 23:56:03 +0000
@@ -482,25 +482,6 @@
482 raise NodeActionError(exception)482 raise NodeActionError(exception)
483483
484484
485class Refresh(NodeAction):
486 """Refresh a rack or region and rack controller."""
487 name = "refresh"
488 display = "Refresh"
489 display_sentence = "refreshed"
490 permission = NODE_PERMISSION.ADMIN
491 for_type = {
492 NODE_TYPE.RACK_CONTROLLER,
493 NODE_TYPE.REGION_AND_RACK_CONTROLLER
494 }
495
496 def execute(self):
497 """See `NodeAction.execute`."""
498 try:
499 self.node.refresh()
500 except RPC_EXCEPTIONS + (ExternalProcessError,) as exception:
501 raise NodeActionError(exception)
502
503
504ACTION_CLASSES = (485ACTION_CLASSES = (
505 Commission,486 Commission,
506 Acquire,487 Acquire,
@@ -514,7 +495,6 @@
514 SetZone,495 SetZone,
515 Delete,496 Delete,
516 ImportImages,497 ImportImages,
517 Refresh,
518)498)
519499
520ACTIONS_DICT = OrderedDict((action.name, action) for action in ACTION_CLASSES)500ACTIONS_DICT = OrderedDict((action.name, action) for action in ACTION_CLASSES)
521501
=== modified file 'src/maasserver/rpc/rackcontrollers.py'
--- src/maasserver/rpc/rackcontrollers.py 2016-05-31 19:15:43 +0000
+++ src/maasserver/rpc/rackcontrollers.py 2016-06-09 23:56:03 +0000
@@ -80,7 +80,7 @@
80 registered and successfully connected we will refresh all commissioning80 registered and successfully connected we will refresh all commissioning
81 data.81 data.
8282
83 :return: A ``(rack-controller, refresh-hint)`` tuple.83 :return: A ``rack-controller``.
84 """84 """
85 if interfaces is None:85 if interfaces is None:
86 interfaces = {}86 interfaces = {}
@@ -91,13 +91,18 @@
91 maaslog.info("Created new rack controller %s.", node.hostname)91 maaslog.info("Created new rack controller %s.", node.hostname)
92 elif node.is_rack_controller:92 elif node.is_rack_controller:
93 maaslog.info("Registering existing rack controller %s.", node.hostname)93 maaslog.info("Registering existing rack controller %s.", node.hostname)
94 elif node.is_region_controller:
95 maaslog.info(
96 "Converting %s into a region and rack controller.", node.hostname)
97 node.node_type = NODE_TYPE.REGION_AND_RACK_CONTROLLER
98 node.save()
94 else:99 else:
95 maaslog.info(100 maaslog.info("Converting %s into a rack controller.", node.hostname)
96 "Found existing node %s as candidate for rack controller.",101 node.node_type = NODE_TYPE.RACK_CONTROLLER
97 node.hostname)102 node.save()
98103
99 # This may be a no-op, but it does provide us with a refresh hint.104 rackcontroller = typecast_node(node, RackController)
100 rackcontroller, needs_refresh = upgrade(node)105
101 # Update `rackcontroller.url` from the given URL, but only when the106 # Update `rackcontroller.url` from the given URL, but only when the
102 # hostname is not 'localhost' (i.e. the default value used when the master107 # hostname is not 'localhost' (i.e. the default value used when the master
103 # cluster connects).108 # cluster connects).
@@ -112,8 +117,7 @@
112 rackcontroller.save(update_fields=update_fields)117 rackcontroller.save(update_fields=update_fields)
113 # Update networking information every time we see a rack.118 # Update networking information every time we see a rack.
114 rackcontroller.update_interfaces(interfaces)119 rackcontroller.update_interfaces(interfaces)
115 # Hint to callers whether or not this rack needs to be refreshed.120 return rackcontroller
116 return rackcontroller, needs_refresh
117121
118122
119@typed123@typed
@@ -136,40 +140,6 @@
136 return Node.objects.filter(query).first()140 return Node.objects.filter(query).first()
137141
138142
139def upgrade(node):
140 """Upgrade `node` to a rack controller if it isn't already.
141
142 Return a hint as to whether a refresh is necessary.
143
144 :return: A ``(rack-controller, refresh-hint)`` tuple.
145 """
146 # Refresh whenever an existing node is converted for use as a rack
147 # controller. This is needed for two reasons. First, when the region starts
148 # it creates a node for itself but only gathers networking information.
149 # Second, information about the node may have changed since its last use.
150 needs_refresh = True
151
152 if node.is_rack_controller:
153 # We don't want to refresh existing rack controllers as each time a
154 # rack controller connects to a region it creates four connections.
155 # This means for every region we connect to we would refresh
156 # 4 * regions every time the rack controller restarts. If the cpu_count
157 # and memory is non-zero our information at this point should be
158 # current and the user can always manually refresh.
159 needs_refresh = (node.cpu_count == 0 or node.memory == 0)
160 elif node.is_region_controller:
161 maaslog.info(
162 "Converting %s into a region and rack controller.", node.hostname)
163 node.node_type = NODE_TYPE.REGION_AND_RACK_CONTROLLER
164 node.save()
165 else:
166 maaslog.info("Converting %s into a rack controller.", node.hostname)
167 node.node_type = NODE_TYPE.RACK_CONTROLLER
168 node.save()
169
170 return typecast_node(node, RackController), needs_refresh
171
172
173@transactional143@transactional
174def update_foreign_dhcp(system_id, interface_name, dhcp_ip=None):144def update_foreign_dhcp(system_id, interface_name, dhcp_ip=None):
175 """Update the external_dhcp field of the VLAN for the interface.145 """Update the external_dhcp field of the VLAN for the interface.
176146
=== modified file 'src/maasserver/rpc/regionservice.py'
--- src/maasserver/rpc/regionservice.py 2016-05-27 13:14:01 +0000
+++ src/maasserver/rpc/regionservice.py 2016-06-09 23:56:03 +0000
@@ -445,6 +445,18 @@
445 return deferToDatabase(445 return deferToDatabase(
446 update_services, system_id, services)446 update_services, system_id, services)
447447
448 @region.RequestRackRefresh.responder
449 def request_rack_refresh(self, system_id):
450 """Request a refresh of the rack
451
452 Implementation of
453 :py:class:`~provisioningserver.rpc.region.RequestRackRefresh`.
454 """
455 d = deferToDatabase(RackController.objects.get, system_id=system_id)
456 d.addCallback(lambda rack: rack.refresh())
457 d.addCallback(lambda _: {})
458 return d
459
448460
449@transactional461@transactional
450def registerConnection(region_id, rack_controller, host):462def registerConnection(region_id, rack_controller, host):
@@ -534,7 +546,7 @@
534546
535 try:547 try:
536 # Register, which includes updating interfaces.548 # Register, which includes updating interfaces.
537 rack_controller, needs_refresh = yield deferToDatabase(549 rack_controller = yield deferToDatabase(
538 rackcontrollers.register, system_id=system_id,550 rackcontrollers.register, system_id=system_id,
539 hostname=hostname, interfaces=interfaces, url=url)551 hostname=hostname, interfaces=interfaces, url=url)
540552
@@ -568,13 +580,6 @@
568 # the information about the rack controller if needed.580 # the information about the rack controller if needed.
569 log.msg(581 log.msg(
570 "Rack controller '%s' has been registered." % self.ident)582 "Rack controller '%s' has been registered." % self.ident)
571 if self.hostIsRemote and needs_refresh:
572 # Needs to be refresh. Perform this operation in a thread but
573 # we ignore when it is done.
574 log.msg(
575 "Rack controller '%s' needs to be refreshed; "
576 "performing the refresh operation." % self.ident)
577 deferToDatabase(rack_controller.refresh)
578583
579 # Done registering the rack controller and connection.584 # Done registering the rack controller and connection.
580 return {'system_id': self.ident}585 return {'system_id': self.ident}
581586
=== modified file 'src/maasserver/rpc/testing/fixtures.py'
--- src/maasserver/rpc/testing/fixtures.py 2016-05-27 13:14:01 +0000
+++ src/maasserver/rpc/testing/fixtures.py 2016-06-09 23:56:03 +0000
@@ -321,7 +321,7 @@
321 # Mock the registration into the database, as the rack controller is321 # Mock the registration into the database, as the rack controller is
322 # already created. We reset this once registration is complete so as322 # already created. We reset this once registration is complete so as
323 # to not interfere with other tests.323 # to not interfere with other tests.
324 registered = rack_controller, False # Hint that no refresh needed.324 registered = rack_controller
325 patcher = MonkeyPatcher()325 patcher = MonkeyPatcher()
326 patcher.add_patch(326 patcher.add_patch(
327 rackcontrollers, "register",327 rackcontrollers, "register",
328328
=== modified file 'src/maasserver/rpc/tests/test_rackcontrollers.py'
--- src/maasserver/rpc/tests/test_rackcontrollers.py 2016-05-31 19:17:13 +0000
+++ src/maasserver/rpc/tests/test_rackcontrollers.py 2016-06-09 23:56:03 +0000
@@ -108,23 +108,23 @@
108108
109 def test_sets_owner_to_worker_when_none(self):109 def test_sets_owner_to_worker_when_none(self):
110 node = factory.make_Node()110 node = factory.make_Node()
111 rack_registered, needs_refresh = register(system_id=node.system_id)111 rack_registered = register(system_id=node.system_id)
112 self.assertEqual(worker_user.get_worker_user(), rack_registered.owner)112 self.assertEqual(worker_user.get_worker_user(), rack_registered.owner)
113113
114 def test_leaves_owner_when_owned(self):114 def test_leaves_owner_when_owned(self):
115 user = factory.make_User()115 user = factory.make_User()
116 node = factory.make_Machine(owner=user)116 node = factory.make_Machine(owner=user)
117 rack_registered, needs_refresh = register(system_id=node.system_id)117 rack_registered = register(system_id=node.system_id)
118 self.assertEqual(user, rack_registered.owner)118 self.assertEqual(user, rack_registered.owner)
119119
120 def test_finds_existing_node_by_system_id(self):120 def test_finds_existing_node_by_system_id(self):
121 node = factory.make_Node()121 node = factory.make_Node()
122 rack_registered, needs_refresh = register(system_id=node.system_id)122 rack_registered = register(system_id=node.system_id)
123 self.assertEqual(node.system_id, rack_registered.system_id)123 self.assertEqual(node.system_id, rack_registered.system_id)
124124
125 def test_finds_existing_node_by_hostname(self):125 def test_finds_existing_node_by_hostname(self):
126 node = factory.make_Node()126 node = factory.make_Node()
127 rack_registered, needs_refresh = register(hostname=node.hostname)127 rack_registered = register(hostname=node.hostname)
128 self.assertEqual(node.system_id, rack_registered.system_id)128 self.assertEqual(node.system_id, rack_registered.system_id)
129129
130 def test_finds_existing_node_by_mac(self):130 def test_finds_existing_node_by_mac(self):
@@ -140,39 +140,9 @@
140 "enabled": True,140 "enabled": True,
141 }141 }
142 }142 }
143 rack_registered, needs_refresh = register(interfaces=interfaces)143 rack_registered = register(interfaces=interfaces)
144 self.assertEqual(node.system_id, rack_registered.system_id)144 self.assertEqual(node.system_id, rack_registered.system_id)
145145
146 def test_finds_existing_controller_needs_refresh_with_bad_info(self):
147 node_type = random.choice([
148 NODE_TYPE.RACK_CONTROLLER,
149 NODE_TYPE.REGION_AND_RACK_CONTROLLER,
150 ])
151 node = factory.make_Node(node_type=node_type)
152 rack_registered, needs_refresh = register(system_id=node.system_id)
153 self.assertTrue(needs_refresh)
154
155 def test_finds_existing_controller_doesnt_need_refresh_good_info(self):
156 node_type = random.choice([
157 NODE_TYPE.RACK_CONTROLLER,
158 NODE_TYPE.REGION_AND_RACK_CONTROLLER,
159 ])
160 node = factory.make_Node(
161 node_type=node_type, cpu_count=random.randint(1, 32),
162 memory=random.randint(1024, 8096))
163 rack_registered, needs_refresh = register(system_id=node.system_id)
164 self.assertFalse(needs_refresh)
165
166 def test_converts_existing_node_sets_needs_refresh_to_true(self):
167 node_type = random.choice([
168 NODE_TYPE.MACHINE,
169 NODE_TYPE.DEVICE,
170 NODE_TYPE.REGION_CONTROLLER,
171 ])
172 node = factory.make_Node(node_type=node_type)
173 rack_registered, needs_refresh = register(system_id=node.system_id)
174 self.assertTrue(needs_refresh)
175
176 def test_find_existing_keeps_type(self):146 def test_find_existing_keeps_type(self):
177 node_type = random.choice(147 node_type = random.choice(
178 (NODE_TYPE.RACK_CONTROLLER, NODE_TYPE.REGION_AND_RACK_CONTROLLER))148 (NODE_TYPE.RACK_CONTROLLER, NODE_TYPE.REGION_AND_RACK_CONTROLLER))
@@ -190,7 +160,7 @@
190160
191 def test_converts_region_controller(self):161 def test_converts_region_controller(self):
192 node = factory.make_Node(node_type=NODE_TYPE.REGION_CONTROLLER)162 node = factory.make_Node(node_type=NODE_TYPE.REGION_CONTROLLER)
193 rack_registered, needs_refresh = register(system_id=node.system_id)163 rack_registered = register(system_id=node.system_id)
194 self.assertEqual(164 self.assertEqual(
195 rack_registered.node_type, NODE_TYPE.REGION_AND_RACK_CONTROLLER)165 rack_registered.node_type, NODE_TYPE.REGION_AND_RACK_CONTROLLER)
196166
@@ -199,14 +169,12 @@
199 node = factory.make_Node(node_type=NODE_TYPE.REGION_CONTROLLER)169 node = factory.make_Node(node_type=NODE_TYPE.REGION_CONTROLLER)
200 register(system_id=node.system_id)170 register(system_id=node.system_id)
201 self.assertEqual(171 self.assertEqual(
202 "Found existing node %s as candidate for rack controller.\n"172 "Converting %s into a region and rack controller.\n" %
203 "Converting %s into a region and rack controller.\n"173 node.hostname, logger.output)
204 % (node.hostname, node.hostname),
205 logger.output)
206174
207 def test_converts_existing_node(self):175 def test_converts_existing_node(self):
208 node = factory.make_Node(node_type=NODE_TYPE.MACHINE)176 node = factory.make_Node(node_type=NODE_TYPE.MACHINE)
209 rack_registered, needs_refresh = register(system_id=node.system_id)177 rack_registered = register(system_id=node.system_id)
210 self.assertEqual(rack_registered.node_type, NODE_TYPE.RACK_CONTROLLER)178 self.assertEqual(rack_registered.node_type, NODE_TYPE.RACK_CONTROLLER)
211179
212 def test_logs_converting_existing_node(self):180 def test_logs_converting_existing_node(self):
@@ -214,9 +182,7 @@
214 node = factory.make_Node(node_type=NODE_TYPE.MACHINE)182 node = factory.make_Node(node_type=NODE_TYPE.MACHINE)
215 register(system_id=node.system_id)183 register(system_id=node.system_id)
216 self.assertEqual(184 self.assertEqual(
217 "Found existing node %s as candidate for rack controller.\n"185 "Converting %s into a rack controller.\n" % node.hostname,
218 "Converting %s into a rack controller.\n"
219 % (node.hostname, node.hostname),
220 logger.output)186 logger.output)
221187
222 def test_creates_new_rackcontroller(self):188 def test_creates_new_rackcontroller(self):
@@ -234,19 +200,6 @@
234 register(interfaces=interfaces)200 register(interfaces=interfaces)
235 self.assertEqual(node_count + 1, len(Node.objects.all()))201 self.assertEqual(node_count + 1, len(Node.objects.all()))
236202
237 def test_creates_new_rackcontroller_sets_needs_refresh_to_true(self):
238 interfaces = {
239 factory.make_name("eth0"): {
240 "type": "physical",
241 "mac_address": factory.make_mac_address(),
242 "parents": [],
243 "links": [],
244 "enabled": True,
245 }
246 }
247 rack_registered, needs_refresh = register(interfaces=interfaces)
248 self.assertTrue(needs_refresh)
249
250 def test_logs_creating_new_rackcontroller(self):203 def test_logs_creating_new_rackcontroller(self):
251 logger = self.useFixture(FakeLogger("maas"))204 logger = self.useFixture(FakeLogger("maas"))
252 hostname = factory.make_name("hostname")205 hostname = factory.make_name("hostname")
@@ -266,7 +219,7 @@
266 "enabled": True,219 "enabled": True,
267 }220 }
268 }221 }
269 rack_registered, needs_refresh = register(interfaces=interfaces)222 rack_registered = register(interfaces=interfaces)
270 self.assertThat(223 self.assertThat(
271 rack_registered.interface_set.all(),224 rack_registered.interface_set.all(),
272 MatchesSetwise(*(225 MatchesSetwise(*(
@@ -293,7 +246,7 @@
293 "enabled": True,246 "enabled": True,
294 }247 }
295 }248 }
296 rack_registered, needs_refresh = register(249 rack_registered = register(
297 rack_controller.system_id, interfaces=interfaces)250 rack_controller.system_id, interfaces=interfaces)
298 self.assertThat(251 self.assertThat(
299 rack_registered.interface_set.all(),252 rack_registered.interface_set.all(),
300253
=== modified file 'src/maasserver/rpc/tests/test_regionservice.py'
--- src/maasserver/rpc/tests/test_regionservice.py 2016-05-27 13:14:01 +0000
+++ src/maasserver/rpc/tests/test_regionservice.py 2016-06-09 23:56:03 +0000
@@ -523,7 +523,7 @@
523 protocol.transport.getHost.return_value = host523 protocol.transport.getHost.return_value = host
524 mock_deferToDatabase = self.patch(regionservice, "deferToDatabase")524 mock_deferToDatabase = self.patch(regionservice, "deferToDatabase")
525 mock_deferToDatabase.side_effect = [525 mock_deferToDatabase.side_effect = [
526 succeed((rack_controller, False)),526 succeed(rack_controller),
527 succeed(None),527 succeed(None),
528 ]528 ]
529 yield call_responder(529 yield call_responder(
530530
=== modified file 'src/maasserver/rpc/tests/test_regionservice_calls.py'
--- src/maasserver/rpc/tests/test_regionservice_calls.py 2016-05-25 21:03:50 +0000
+++ src/maasserver/rpc/tests/test_regionservice_calls.py 2016-06-09 23:56:03 +0000
@@ -59,6 +59,7 @@
59)59)
60from maasserver.utils.threads import deferToDatabase60from maasserver.utils.threads import deferToDatabase
61from maastesting.matchers import (61from maastesting.matchers import (
62 MockCalledOnce,
62 MockCalledOnceWith,63 MockCalledOnceWith,
63 MockCalledWith,64 MockCalledWith,
64)65)
@@ -84,6 +85,7 @@
84 ReportBootImages,85 ReportBootImages,
85 ReportForeignDHCPServer,86 ReportForeignDHCPServer,
86 RequestNodeInfoByMACAddress,87 RequestNodeInfoByMACAddress,
88 RequestRackRefresh,
87 SendEvent,89 SendEvent,
88 SendEventMACAddress,90 SendEventMACAddress,
89 UpdateInterfaces,91 UpdateInterfaces,
@@ -1233,3 +1235,25 @@
1233 params = {'mac_address': factory.make_mac_address()}1235 params = {'mac_address': factory.make_mac_address()}
1234 d = call_responder(Region(), RequestNodeInfoByMACAddress, params)1236 d = call_responder(Region(), RequestNodeInfoByMACAddress, params)
1235 return assert_fails_with(d, NoSuchNode)1237 return assert_fails_with(d, NoSuchNode)
1238
1239
1240class TestRegionProtocol_RequestRefresh(MAASTransactionServerTestCase):
1241
1242 def test_request_refresh_is_registered(self):
1243 protocol = Region()
1244 responder = protocol.locateResponder(
1245 RequestRackRefresh.commandName)
1246 self.assertIsNotNone(responder)
1247
1248 @wait_for_reactor
1249 @inlineCallbacks
1250 def test_calls_refresh(self):
1251 rack = yield deferToDatabase(factory.make_RackController)
1252 self.patch(regionservice, 'deferToDatabase').return_value = succeed(
1253 rack)
1254 mock_refresh = self.patch(rack, 'refresh')
1255 response = yield call_responder(
1256 Region(), RequestRackRefresh, {'system_id': rack.system_id})
1257 self.assertIsNotNone(response)
1258
1259 self.assertThat(mock_refresh, MockCalledOnce())
12361260
=== modified file 'src/maasserver/tests/test_node_action.py'
--- src/maasserver/tests/test_node_action.py 2016-05-26 06:47:45 +0000
+++ src/maasserver/tests/test_node_action.py 2016-06-09 23:56:03 +0000
@@ -5,7 +5,6 @@
55
6__all__ = []6__all__ = []
77
8import random
9from unittest.mock import ANY8from unittest.mock import ANY
109
11from django.db import transaction10from django.db import transaction
@@ -28,10 +27,7 @@
28 signals,27 signals,
29 StaticIPAddress,28 StaticIPAddress,
30)29)
31from maasserver.models.node import (30from maasserver.models.node import typecast_to_node_type
32 RackController,
33 typecast_to_node_type,
34)
35from maasserver.models.signals.testing import SignalsDisabled31from maasserver.models.signals.testing import SignalsDisabled
36from maasserver.node_action import (32from maasserver.node_action import (
37 Abort,33 Abort,
@@ -46,7 +42,6 @@
46 NodeAction,42 NodeAction,
47 PowerOff,43 PowerOff,
48 PowerOn,44 PowerOn,
49 Refresh,
50 Release,45 Release,
51 RPC_EXCEPTIONS,46 RPC_EXCEPTIONS,
52 SetZone,47 SetZone,
@@ -854,33 +849,6 @@
854 self.assertFalse(ImportImages(node, user).is_actionable())849 self.assertFalse(ImportImages(node, user).is_actionable())
855850
856851
857class TestRefresh(MAASServerTestCase):
858
859 def test_refresh(self):
860 user = factory.make_admin()
861 rack = factory.make_RackController()
862 mock_refresh = self.patch(rack, 'refresh')
863
864 with post_commit_hooks:
865 Refresh(rack, user).execute()
866
867 self.assertThat(mock_refresh, MockCalledOnce())
868
869 def test_requires_admin_permission(self):
870 user = factory.make_User()
871 rack = factory.make_RackController()
872 self.assertFalse(Refresh(rack, user).is_permitted())
873
874 def test_requires_rack(self):
875 user = factory.make_User()
876 node = factory.make_Node(
877 node_type=factory.pick_choice(
878 NODE_TYPE_CHOICES, but_not=[
879 NODE_TYPE.RACK_CONTROLLER,
880 NODE_TYPE.REGION_AND_RACK_CONTROLLER]))
881 self.assertFalse(Refresh(node, user).is_actionable())
882
883
884class TestActionsErrorHandling(MAASServerTestCase):852class TestActionsErrorHandling(MAASServerTestCase):
885 """Tests for error handling in actions.853 """Tests for error handling in actions.
886854
@@ -904,8 +872,6 @@
904 exception = self.make_exception()872 exception = self.make_exception()
905 self.patch(node, '_start').side_effect = exception873 self.patch(node, '_start').side_effect = exception
906 self.patch(node, '_stop').side_effect = exception874 self.patch(node, '_stop').side_effect = exception
907 if isinstance(node, RackController):
908 self.patch(node, 'refresh').side_effect = exception
909875
910 def make_action(876 def make_action(
911 self, action_class, node_status, power_state=None,877 self, action_class, node_status, power_state=None,
@@ -961,15 +927,3 @@
961 self.assertEqual(927 self.assertEqual(
962 get_error_message_for_exception(action.node._stop.side_effect),928 get_error_message_for_exception(action.node._stop.side_effect),
963 str(exception))929 str(exception))
964
965 def test_Refresh_handles_rpc_errors(self):
966 action = self.make_action(
967 Refresh, NODE_STATUS.ALLOCATED, power_state=POWER_STATE.ON,
968 node_type=random.choice([
969 NODE_TYPE.RACK_CONTROLLER,
970 NODE_TYPE.REGION_AND_RACK_CONTROLLER]))
971 self.patch_rpc_methods(action.node)
972 exception = self.assertRaises(NodeActionError, action.execute)
973 self.assertEqual(
974 get_error_message_for_exception(action.node._stop.side_effect),
975 str(exception))
976930
=== modified file 'src/maasserver/websockets/handlers/tests/test_general.py'
--- src/maasserver/websockets/handlers/tests/test_general.py 2016-05-26 06:47:45 +0000
+++ src/maasserver/websockets/handlers/tests/test_general.py 2016-06-09 23:56:03 +0000
@@ -144,7 +144,7 @@
144 def test_rack_controller_actions_for_admin(self):144 def test_rack_controller_actions_for_admin(self):
145 handler = GeneralHandler(factory.make_admin(), {})145 handler = GeneralHandler(factory.make_admin(), {})
146 self.assertItemsEqual(146 self.assertItemsEqual(
147 ['delete', 'import-images', 'off', 'on', 'refresh', 'set-zone'],147 ['delete', 'import-images', 'off', 'on', 'set-zone'],
148 [action['name'] for action in handler.rack_controller_actions({})])148 [action['name'] for action in handler.rack_controller_actions({})])
149149
150 def test_rack_controller_actions_for_non_admin(self):150 def test_rack_controller_actions_for_non_admin(self):
@@ -154,7 +154,7 @@
154 def test_region_and_rack_controller_actions_for_admin(self):154 def test_region_and_rack_controller_actions_for_admin(self):
155 handler = GeneralHandler(factory.make_admin(), {})155 handler = GeneralHandler(factory.make_admin(), {})
156 self.assertItemsEqual(156 self.assertItemsEqual(
157 ['set-zone', 'refresh', 'delete', 'import-images'],157 ['set-zone', 'delete', 'import-images'],
158 [action['name']158 [action['name']
159 for action in handler.region_and_rack_controller_actions({})])159 for action in handler.region_and_rack_controller_actions({})])
160160
161161
=== modified file 'src/provisioningserver/pserv_services/networks_monitoring_service.py'
--- src/provisioningserver/pserv_services/networks_monitoring_service.py 2016-06-03 17:31:53 +0000
+++ src/provisioningserver/pserv_services/networks_monitoring_service.py 2016-06-09 23:56:03 +0000
@@ -8,7 +8,10 @@
8]8]
99
10from provisioningserver.logger.log import get_maas_logger10from provisioningserver.logger.log import get_maas_logger
11from provisioningserver.rpc.region import UpdateInterfaces11from provisioningserver.rpc.region import (
12 RequestRackRefresh,
13 UpdateInterfaces,
14)
12from provisioningserver.utils.services import NetworksMonitoringService15from provisioningserver.utils.services import NetworksMonitoringService
1316
1417
@@ -25,6 +28,10 @@
25 def recordInterfaces(self, interfaces):28 def recordInterfaces(self, interfaces):
26 """Record the interfaces information."""29 """Record the interfaces information."""
27 client = self.clientService.getClient()30 client = self.clientService.getClient()
28 return client(31 # On first run perform a refresh
29 UpdateInterfaces, system_id=client.localIdent,32 if self._recorded is None:
30 interfaces=interfaces)33 return client(RequestRackRefresh, system_id=client.localIdent)
34 else:
35 return client(
36 UpdateInterfaces, system_id=client.localIdent,
37 interfaces=interfaces)
3138
=== modified file 'src/provisioningserver/pserv_services/tests/test_networks_monitoring_service.py'
--- src/provisioningserver/pserv_services/tests/test_networks_monitoring_service.py 2016-06-04 08:51:04 +0000
+++ src/provisioningserver/pserv_services/tests/test_networks_monitoring_service.py 2016-06-09 23:56:03 +0000
@@ -29,6 +29,22 @@
29 run_tests_with = MAASTwistedRunTest.make_factory(timeout=5)29 run_tests_with = MAASTwistedRunTest.make_factory(timeout=5)
3030
31 @inlineCallbacks31 @inlineCallbacks
32 def test_runs_refresh_first_time(self):
33 fixture = self.useFixture(MockLiveClusterToRegionRPCFixture())
34 protocol, connecting = fixture.makeEventLoop(region.RequestRackRefresh)
35 self.addCleanup((yield connecting))
36
37 rpc_service = services.getServiceNamed('rpc')
38 service = RackNetworksMonitoringService(rpc_service, Clock())
39
40 yield service.startService()
41 yield service.stopService()
42
43 self.assertThat(
44 protocol.RequestRackRefresh, MockCalledOnceWith(
45 protocol, system_id=rpc_service.getClient().localIdent))
46
47 @inlineCallbacks
32 def test_reports_interfaces_to_region(self):48 def test_reports_interfaces_to_region(self):
33 fixture = self.useFixture(MockLiveClusterToRegionRPCFixture())49 fixture = self.useFixture(MockLiveClusterToRegionRPCFixture())
34 protocol, connecting = fixture.makeEventLoop(region.UpdateInterfaces)50 protocol, connecting = fixture.makeEventLoop(region.UpdateInterfaces)
@@ -47,6 +63,10 @@
47 rpc_service = services.getServiceNamed('rpc')63 rpc_service = services.getServiceNamed('rpc')
48 service = RackNetworksMonitoringService(rpc_service, Clock())64 service = RackNetworksMonitoringService(rpc_service, Clock())
49 service.getInterfaces = lambda: succeed(interfaces)65 service.getInterfaces = lambda: succeed(interfaces)
66 # Put something in the cache. This tells recordInterfaces that refresh
67 # has already run but the interfaces have changed thus they need to be
68 # updated.
69 service._interfacesRecorded({})
5070
51 yield service.startService()71 yield service.startService()
52 yield service.stopService()72 yield service.stopService()
5373
=== modified file 'src/provisioningserver/rpc/region.py'
--- src/provisioningserver/rpc/region.py 2016-05-11 00:25:14 +0000
+++ src/provisioningserver/rpc/region.py 2016-06-09 23:56:03 +0000
@@ -480,3 +480,16 @@
480 errors = {480 errors = {
481 NoSuchCluster: b"NoSuchCluster",481 NoSuchCluster: b"NoSuchCluster",
482 }482 }
483
484
485class RequestRackRefresh(amp.Command):
486 """Request a refresh of the rack from the region.
487
488 :since: 2.0
489 """
490
491 arguments = [
492 (b"system_id", amp.Unicode()),
493 ]
494 response = []
495 errors = []