Merge ~ltrager/maas:2.3_1752754 into maas:2.3

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: 2bd80569e4a6e5c9bd25eaaf4f70a68a701bdc5c
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ltrager/maas:2.3_1752754
Merge into: maas:2.3
Prerequisite: ~ltrager/maas:2.3_fix_scriptresult_updates_on_trigger
Diff against target: 415 lines (+189/-40)
9 files modified
src/maasserver/static/js/angular/controllers/node_results.js (+21/-10)
src/maasserver/static/js/angular/controllers/tests/test_node_results.js (+46/-0)
src/maasserver/static/js/angular/factories/node_results.js (+10/-1)
src/maasserver/static/js/angular/factories/tests/test_node_results.js (+22/-1)
src/maasserver/static/partials/script-results-list.html (+7/-1)
src/maasserver/websockets/handlers/node_result.py (+21/-12)
src/maasserver/websockets/handlers/tests/test_node_result.py (+32/-11)
src/metadataserver/models/scriptresult.py (+4/-2)
src/metadataserver/models/tests/test_scriptresult.py (+26/-2)
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+368114@code.launchpad.net

Commit message

LP: #1752754 - Request historic results over the websocket instead of always sending them.

Backport of 9e35f1a for LP: #1830365

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

Looks good

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
~ltrager/maas:2.3_1752754 updated
2bd8056... by Lee Trager

Merge branch '2.3' into 2.3_1752754

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/maasserver/static/js/angular/controllers/node_results.js b/src/maasserver/static/js/angular/controllers/node_results.js
index 101e500..59f3f6f 100644
--- a/src/maasserver/static/js/angular/controllers/node_results.js
+++ b/src/maasserver/static/js/angular/controllers/node_results.js
@@ -1,4 +1,4 @@
1/* Copyright 2017 Canonical Ltd. This software is licensed under the1/* Copyright 2017-2018 Canonical Ltd. This software is licensed under the
2 * GNU Affero General Public License version 3 (see the file LICENSE).2 * GNU Affero General Public License version 3 (see the file LICENSE).
3 *3 *
4 * MAAS Node Results Controller4 * MAAS Node Results Controller
@@ -135,17 +135,12 @@ angular.module('MAAS').controller('NodeResultsController', [
135 });135 });
136 } else {136 } else {
137 var result = null;137 var result = null;
138 var i, j;138 var i;
139 // Find the installation result to be displayed.139 // Find the installation result to be displayed.
140 for(i = 0; i < $scope.installation_results.length; i++) {140 for(i = 0; i < $scope.installation_results.length; i++) {
141 var hlist = $scope.installation_results[i].history_list;141 if($scope.installation_results[i].id ===
142 for(j = 0; j < hlist.length; j++) {142 $scope.logs.option.id) {
143 if(hlist[j].id === $scope.logs.option.id) {143 result = $scope.installation_results[i];
144 result = hlist[j];
145 break;
146 }
147 }
148 if(result) {
149 break;144 break;
150 }145 }
151 }146 }
@@ -195,6 +190,22 @@ angular.module('MAAS').controller('NodeResultsController', [
195 }190 }
196 };191 };
197192
193 $scope.loadHistory = function(result) {
194 result.showing_results = false;
195 // History has already been loaded, no need to request it.
196 if(angular.isArray(result.history_list)) {
197 result.showing_history = true;
198 return;
199 }
200 result.loading_history = true;
201 $scope.nodeResultsManager.get_history(result.id).then(
202 function(history) {
203 result.history_list = history;
204 result.loading_history = false;
205 result.showing_history = true;
206 });
207 };
208
198 // Destroy the NodeResultsManager when the scope is destroyed. This is209 // Destroy the NodeResultsManager when the scope is destroyed. This is
199 // so the client will not recieve any more notifications about results210 // so the client will not recieve any more notifications about results
200 // from this node.211 // from this node.
diff --git a/src/maasserver/static/js/angular/controllers/tests/test_node_results.js b/src/maasserver/static/js/angular/controllers/tests/test_node_results.js
index f2f44d7..fbeb5a1 100644
--- a/src/maasserver/static/js/angular/controllers/tests/test_node_results.js
+++ b/src/maasserver/static/js/angular/controllers/tests/test_node_results.js
@@ -433,4 +433,50 @@ describe("NodeResultsController", function() {
433 "BUG: Unknown log status " + installation_result.status);433 "BUG: Unknown log status " + installation_result.status);
434 });434 });
435 });435 });
436
437 describe("loadHistory", function() {
438 it("loads results", function() {
439 var defer = $q.defer();
440 var controller = makeController();
441 var result = {
442 id: makeInteger(0, 100)
443 };
444 var history_list = [
445 {id: makeInteger(0, 100)},
446 {id: makeInteger(0, 100)},
447 {id: makeInteger(0, 100)}
448 ];
449 $scope.node = node;
450 $scope.nodeResultsManager = NodeResultsManagerFactory.getManager(
451 $scope.node);
452 spyOn($scope.nodeResultsManager, "get_history").and.returnValue(
453 defer.promise);
454
455 $scope.loadHistory(result);
456 defer.resolve(history_list);
457 $rootScope.$digest();
458
459 expect(result.history_list).toBe(history_list);
460 expect(result.loading_history).toBe(false);
461 expect(result.showing_history).toBe(true);
462 });
463
464 it("doesnt reload", function() {
465 var controller = makeController();
466 var result = {
467 id: makeInteger(0, 100),
468 history_list: [{id: makeInteger(0, 100)}]
469 };
470 $scope.node = node;
471 $scope.nodeResultsManager = NodeResultsManagerFactory.getManager(
472 $scope.node);
473 spyOn($scope.nodeResultsManager, "get_history");
474
475 $scope.loadHistory(result);
476
477 expect(result.showing_history).toBe(true);
478 expect(
479 $scope.nodeResultsManager.get_history).not.toHaveBeenCalled();
480 });
481 });
436});482});
diff --git a/src/maasserver/static/js/angular/factories/node_results.js b/src/maasserver/static/js/angular/factories/node_results.js
index 1a0a5de..94a52f7 100644
--- a/src/maasserver/static/js/angular/factories/node_results.js
+++ b/src/maasserver/static/js/angular/factories/node_results.js
@@ -1,4 +1,4 @@
1/* Copyright 2017 Canonical Ltd. This software is licensed under the1/* Copyright 2017-2018 Canonical Ltd. This software is licensed under the
2 * GNU Affero General Public License version 3 (see the file LICENSE).2 * GNU Affero General Public License version 3 (see the file LICENSE).
3 *3 *
4 * MAAS NodeResultsManager Manager4 * MAAS NodeResultsManager Manager
@@ -266,6 +266,15 @@ angular.module('MAAS').factory(
266 return RegionConnection.callMethod(method, params);266 return RegionConnection.callMethod(method, params);
267 };267 };
268268
269 // Get historic data.
270 NodeResultsManager.prototype.get_history = function(script_id) {
271 var method = this._handler + ".get_history";
272 var params = {
273 id: script_id,
274 };
275 return RegionConnection.callMethod(method, params);
276 };
277
269 // Factory that holds all created NodeResultsManagers.278 // Factory that holds all created NodeResultsManagers.
270 function NodeResultsManagerFactory() {279 function NodeResultsManagerFactory() {
271 // Holds a list of all NodeResultsManagers that have been created.280 // Holds a list of all NodeResultsManagers that have been created.
diff --git a/src/maasserver/static/js/angular/factories/tests/test_node_results.js b/src/maasserver/static/js/angular/factories/tests/test_node_results.js
index 859dda3..2508969 100644
--- a/src/maasserver/static/js/angular/factories/tests/test_node_results.js
+++ b/src/maasserver/static/js/angular/factories/tests/test_node_results.js
@@ -1,4 +1,4 @@
1/* Copyright 2017 Canonical Ltd. This software is licensed under the1/* Copyright 2017-2018 Canonical Ltd. This software is licensed under the
2 * GNU Affero General Public License version 3 (see the file LICENSE).2 * GNU Affero General Public License version 3 (see the file LICENSE).
3 *3 *
4 * Unit tests for NodeResultsManagerFactory.4 * Unit tests for NodeResultsManagerFactory.
@@ -532,6 +532,27 @@ describe("NodeResultsManagerFactory", function() {
532 });532 });
533 });533 });
534534
535 describe("get_history", function() {
536
537 it("calls NodeResultHandler.get_history", function(done) {
538 var node = makenode();
539 var output = [{
540 id: makeInteger(0, 100),
541 name: makeName("output"),
542 status: makeInteger(0, 100)
543 }];
544 var id = makeInteger(0, 100);
545 webSocket.returnData.push(makeFakeResponse(output));
546 NodeResultsManager = NodeResultsManagerFactory.getManager(node);
547 NodeResultsManager.get_history(id).then(function() {
548 var sentObject = angular.fromJson(webSocket.sentData[0]);
549 expect(sentObject.method).toBe("noderesult.get_history");
550 expect(sentObject.params.id).toEqual(id);
551 done();
552 });
553 });
554 });
555
535 describe("destroyManager", function() {556 describe("destroyManager", function() {
536557
537 it("removes manager from _managers", function() {558 it("removes manager from _managers", function() {
diff --git a/src/maasserver/static/partials/script-results-list.html b/src/maasserver/static/partials/script-results-list.html
index 913978c..67ca3ab 100644
--- a/src/maasserver/static/partials/script-results-list.html
+++ b/src/maasserver/static/partials/script-results-list.html
@@ -44,7 +44,7 @@
44 <div class="table__controls-menu ng-hide" role="menu" data-ng-show="isToggled">44 <div class="table__controls-menu ng-hide" role="menu" data-ng-show="isToggled">
45 <button class="table__controls-action" aria-label="View metrics" data-ng-if="!result.showing_results" data-ng-click="toggleMenu(); result.showing_history = false; result.showing_results = true">View metrics</button>45 <button class="table__controls-action" aria-label="View metrics" data-ng-if="!result.showing_results" data-ng-click="toggleMenu(); result.showing_history = false; result.showing_results = true">View metrics</button>
46 <button class="table__controls-action" aria-label="Hide metrics" data-ng-if="result.showing_results" data-ng-click="toggleMenu(); result.showing_history = false; result.showing_results = false">Hide metrics</button>46 <button class="table__controls-action" aria-label="Hide metrics" data-ng-if="result.showing_results" data-ng-click="toggleMenu(); result.showing_history = false; result.showing_results = false">Hide metrics</button>
47 <button class="table__controls-action" aria-label="View previous {$ result.result_section $}" data-ng-if="!result.showing_history" data-ng-click="toggleMenu(); result.showing_results = false; result.showing_history = true">View previous {$ result.result_section $}</button>47 <button class="table__controls-action" aria-label="View previous {$ result.result_section $}" data-ng-if="!result.showing_history" data-ng-click="toggleMenu(); loadHistory(result)">View previous {$ result.result_section $}</button>
48 <button class="table__controls-action" aria-label="Hide previous {$ result.result_section $}" data-ng-if="result.showing_history" data-ng-click="toggleMenu(); result.showing_results = false; result.showing_history = false">Hide previous {$ result.result_section $}</button>48 <button class="table__controls-action" aria-label="Hide previous {$ result.result_section $}" data-ng-if="result.showing_history" data-ng-click="toggleMenu(); result.showing_results = false; result.showing_history = false">Hide previous {$ result.result_section $}</button>
49 </div>49 </div>
50 </div>50 </div>
@@ -60,6 +60,11 @@
60 </div>60 </div>
61 </div>61 </div>
6262
63 <div class="p-table-expanding__panel col-12" aria-label="loading history" data-ng-if="result.loading_history">
64 <div class="col-12">
65 <p class="u-text--loading"><i class="p-icon--spinner u-animation--spin"></i>&nbsp;&nbsp;Loading...</p>
66 </div>
67 </div>
63 <div class="table__dropdown" aria-label="history" data-ng-if="result.showing_history">68 <div class="table__dropdown" aria-label="history" data-ng-if="result.showing_history">
64 <div class="table__row is-active">69 <div class="table__row is-active">
65 <div class="table__data table-col--100">70 <div class="table__data table-col--100">
@@ -83,6 +88,7 @@
83 <p class="u-align--center u-margin--bottom">88 <p class="u-align--center u-margin--bottom">
84 <button class="button--secondary button--inline" data-ng-click="result.showing_history = false">Hide previous {$ result.result_section $}</button>89 <button class="button--secondary button--inline" data-ng-click="result.showing_history = false">Hide previous {$ result.result_section $}</button>
85 </p>90 </p>
91
86 </div>92 </div>
87 </div>93 </div>
88 </div>94 </div>
diff --git a/src/maasserver/websockets/handlers/node_result.py b/src/maasserver/websockets/handlers/node_result.py
index e6e516e..cf04e58 100644
--- a/src/maasserver/websockets/handlers/node_result.py
+++ b/src/maasserver/websockets/handlers/node_result.py
@@ -32,6 +32,7 @@ class NodeResultHandler(TimestampedModelHandler):
32 'clear',32 'clear',
33 'get',33 'get',
34 'get_result_data',34 'get_result_data',
35 'get_history',
35 'list',36 'list',
36 ]37 ]
37 listen_channels = ['scriptresult']38 listen_channels = ['scriptresult']
@@ -88,18 +89,6 @@ class NodeResultHandler(TimestampedModelHandler):
88 # Script object.89 # Script object.
89 data["hardware_type"] = HARDWARE_TYPE.NODE90 data["hardware_type"] = HARDWARE_TYPE.NODE
90 data["tags"] = 'commissioning'91 data["tags"] = 'commissioning'
91 data["history_list"] = [
92 {
93 "id": history.id,
94 "updated": dehydrate_datetime(history.updated),
95 "status": history.status,
96 "status_name": history.status_name,
97 "runtime": history.runtime,
98 "starttime": history.starttime,
99 "endtime": history.endtime,
100 "estimated_runtime": history.estimated_runtime,
101 } for history in obj.history
102 ]
103 try:92 try:
104 results = obj.read_results()93 results = obj.read_results()
105 except ValidationError as e:94 except ValidationError as e:
@@ -213,6 +202,26 @@ class NodeResultHandler(TimestampedModelHandler):
213 else:202 else:
214 return "Unknown data_type %s" % data_type203 return "Unknown data_type %s" % data_type
215204
205 def get_history(self, params):
206 """Return a list of historic results."""
207 id = params.get('id')
208 script_result = ScriptResult.objects.filter(id=id).only(
209 'script_id', 'script_set_id', 'physical_blockdevice_id',
210 'script_name').first()
211 history_qs = script_result.history.only(
212 'id', 'updated', 'status', 'started', 'ended', 'script_id',
213 'script_name', 'script_set_id', 'physical_blockdevice_id')
214 return [{
215 'id': history.id,
216 'updated': dehydrate_datetime(history.updated),
217 'status': history.status,
218 'status_name': history.status_name,
219 'runtime': history.runtime,
220 'starttime': history.starttime,
221 'endtime': history.endtime,
222 'estimated_runtime': history.estimated_runtime,
223 } for history in history_qs]
224
216 def clear(self, params):225 def clear(self, params):
217 """Clears the current node for events.226 """Clears the current node for events.
218227
diff --git a/src/maasserver/websockets/handlers/tests/test_node_result.py b/src/maasserver/websockets/handlers/tests/test_node_result.py
index 5b3fc37..a8fe7fe 100644
--- a/src/maasserver/websockets/handlers/tests/test_node_result.py
+++ b/src/maasserver/websockets/handlers/tests/test_node_result.py
@@ -1,4 +1,4 @@
1# Copyright 2017 Canonical Ltd. This software is licensed under the1# Copyright 2017-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Tests for `maasserver.websockets.handlers.node_result`"""4"""Tests for `maasserver.websockets.handlers.node_result`"""
@@ -16,6 +16,7 @@ from maasserver.websockets.base import (
16 HandlerPKError,16 HandlerPKError,
17)17)
18from maasserver.websockets.handlers.node_result import NodeResultHandler18from maasserver.websockets.handlers.node_result import NodeResultHandler
19from maastesting.djangotestcase import CountQueries
19from metadataserver.enum import (20from metadataserver.enum import (
20 HARDWARE_TYPE,21 HARDWARE_TYPE,
21 HARDWARE_TYPE_CHOICES,22 HARDWARE_TYPE_CHOICES,
@@ -49,16 +50,6 @@ class TestNodeResultHandler(MAASServerTestCase):
49 "result_type": script_result.script_set.result_type,50 "result_type": script_result.script_set.result_type,
50 "hardware_type": script_result.script.hardware_type,51 "hardware_type": script_result.script.hardware_type,
51 "tags": ", ".join(script_result.script.tags),52 "tags": ", ".join(script_result.script.tags),
52 "history_list": [{
53 "id": history.id,
54 "updated": dehydrate_datetime(history.updated),
55 "status": history.status,
56 "status_name": history.status_name,
57 "runtime": history.runtime,
58 "starttime": history.starttime,
59 "endtime": history.endtime,
60 "estimated_runtime": history.estimated_runtime,
61 } for history in script_result.history],
62 "results": [{53 "results": [{
63 "name": key,54 "name": key,
64 "title": key,55 "title": key,
@@ -333,6 +324,36 @@ class TestNodeResultHandler(MAASServerTestCase):
333 'data_type': unknown_data_type,324 'data_type': unknown_data_type,
334 }))325 }))
335326
327 def test_get_history(self):
328 user = factory.make_User()
329 handler = NodeResultHandler(user, {})
330 node = factory.make_Node(owner=user)
331 script = factory.make_Script()
332 script_results = []
333 for _ in range(10):
334 script_set = factory.make_ScriptSet(node=node)
335 script_results.append(factory.make_ScriptResult(
336 script=script, script_set=script_set,
337 status=SCRIPT_STATUS.PASSED))
338 latest_script_result = script_results[-1]
339 script_results = sorted(
340 script_results, key=lambda i: i.id, reverse=True)
341 queries = CountQueries()
342 with queries:
343 ret = handler.get_history({'id': latest_script_result.id})
344 self.assertEqual(4, queries.num_queries)
345 for script_result, out in zip(script_results, ret):
346 self.assertDictEqual({
347 'id': script_result.id,
348 'updated': dehydrate_datetime(script_result.updated),
349 'status': script_result.status,
350 'status_name': script_result.status_name,
351 'runtime': script_result.runtime,
352 'starttime': script_result.starttime,
353 'endtime': script_result.endtime,
354 'estimated_runtime': script_result.estimated_runtime,
355 }, out)
356
336 def test_clear_removes_system_id_from_cache(self):357 def test_clear_removes_system_id_from_cache(self):
337 user = factory.make_User()358 user = factory.make_User()
338 handler = NodeResultHandler(user, {})359 handler = NodeResultHandler(user, {})
diff --git a/src/metadataserver/models/scriptresult.py b/src/metadataserver/models/scriptresult.py
index 741c6db..7f0ea1b 100644
--- a/src/metadataserver/models/scriptresult.py
+++ b/src/metadataserver/models/scriptresult.py
@@ -1,4 +1,4 @@
1# Copyright 2017 Canonical Ltd. This software is licensed under the1# Copyright 2017-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
3__all__ = [3__all__ = [
4 'ScriptResult',4 'ScriptResult',
@@ -139,7 +139,9 @@ class ScriptResult(CleanSave, TimestampedModel):
139 return self.runtime139 return self.runtime
140 runtime = None140 runtime = None
141 # Get an estimated runtime from previous runs.141 # Get an estimated runtime from previous runs.
142 for script_result in self.history:142 for script_result in self.history.only(
143 'status', 'started', 'ended', 'script_id', 'script_name',
144 'script_set_id', 'physical_blockdevice_id'):
143 # Only look at passed results when calculating an estimated145 # Only look at passed results when calculating an estimated
144 # runtime. Failed results may take longer or shorter than146 # runtime. Failed results may take longer or shorter than
145 # average. Don't use self.history.filter for this as the now147 # average. Don't use self.history.filter for this as the now
diff --git a/src/metadataserver/models/tests/test_scriptresult.py b/src/metadataserver/models/tests/test_scriptresult.py
index 11269d9..5930fae 100644
--- a/src/metadataserver/models/tests/test_scriptresult.py
+++ b/src/metadataserver/models/tests/test_scriptresult.py
@@ -1,4 +1,4 @@
1# Copyright 2017 Canonical Ltd. This software is licensed under the1# Copyright 2017-2018 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__all__ = []4__all__ = []
@@ -19,6 +19,7 @@ from maasserver.models import (
19from maasserver.testing.factory import factory19from maasserver.testing.factory import factory
20from maasserver.testing.testcase import MAASServerTestCase20from maasserver.testing.testcase import MAASServerTestCase
21from maasserver.utils.orm import reload_object21from maasserver.utils.orm import reload_object
22from maastesting.djangotestcase import CountQueries
22from maastesting.matchers import (23from maastesting.matchers import (
23 DocTestMatches,24 DocTestMatches,
24 MockCalledOnceWith,25 MockCalledOnceWith,
@@ -28,7 +29,10 @@ from metadataserver.enum import (
28 SCRIPT_STATUS,29 SCRIPT_STATUS,
29 SCRIPT_STATUS_CHOICES,30 SCRIPT_STATUS_CHOICES,
30)31)
31from metadataserver.models import scriptresult as scriptresult_module32from metadataserver.models import (
33 ScriptResult,
34 scriptresult as scriptresult_module,
35)
32from provisioningserver.events import EVENT_TYPES36from provisioningserver.events import EVENT_TYPES
33import yaml37import yaml
3438
@@ -624,6 +628,26 @@ class TestScriptResult(MAASServerTestCase):
624 script_result = script_results[-1]628 script_result = script_results[-1]
625 self.assertItemsEqual(script_results, script_result.history)629 self.assertItemsEqual(script_results, script_result.history)
626630
631 def test_history_query_count(self):
632 script_name = factory.make_name('script_name')
633 script_set = factory.make_ScriptSet()
634 script_results = [
635 factory.make_ScriptResult(
636 script_name=script_name, script_set=script_set)
637 for _ in range(10)
638 ]
639 queries_one = CountQueries()
640 script_result_one = ScriptResult.objects.get(id=script_results[0].id)
641 with queries_one:
642 script_result_one.history
643 queries_many = CountQueries()
644 script_result_many = ScriptResult.objects.get(
645 id=script_results[-1].id)
646 with queries_many:
647 script_result_many.history
648 self.assertEquals(1, queries_one.num_queries)
649 self.assertEquals(1, queries_many.num_queries)
650
627 def test_history_storage_device(self):651 def test_history_storage_device(self):
628 # Regression test for LP: #1721524652 # Regression test for LP: #1721524
629 script = factory.make_Script()653 script = factory.make_Script()

Subscribers

People subscribed via source and target branches