Merge ~blake-rouse/maas:lp1723425 into maas:master

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: 6351fe88d73b42a1fb9abe2f87c7ef8cad463c19
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~blake-rouse/maas:lp1723425
Merge into: maas:master
Diff against target: 519 lines (+316/-8)
12 files modified
src/maasserver/api/scriptresults.py (+3/-0)
src/maasserver/api/tests/test_scriptresults.py (+9/-0)
src/maasserver/static/js/angular/directives/script_runtime.js (+57/-0)
src/maasserver/static/js/angular/directives/tests/test_script_runtime.js (+89/-0)
src/maasserver/static/partials/script-results-list.html (+8/-8)
src/maasserver/views/combo.py (+1/-0)
src/maasserver/websockets/handlers/node.py (+6/-0)
src/maasserver/websockets/handlers/node_result.py (+6/-0)
src/maasserver/websockets/handlers/tests/test_machine.py (+12/-0)
src/maasserver/websockets/handlers/tests/test_node_result.py (+6/-0)
src/metadataserver/models/scriptresult.py (+45/-0)
src/metadataserver/models/tests/test_scriptresult.py (+74/-0)
Reviewer Review Type Date Requested Status
MAAS Lander Approve
Lee Trager (community) Approve
Mike Pontillo (community) Approve
Review via email: mp+333281@code.launchpad.net

Commit message

Fixes LP: #1723425 - Add a maas-script-runtime-directive to render client side the current runtime of a script.

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b lp1723425 lp:~blake-rouse/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/662/console
COMMIT: a55e2f46eeba4ca6db6548ec1ada82526f2e9186

review: Needs Fixing
Revision history for this message
Mike Pontillo (mpontillo) wrote :

New code looks good. (Of course, I trust you to fix the regressions in the test suite noted by the lander!)

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

LGTM!

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b lp1723425 lp:~blake-rouse/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 6351fe88d73b42a1fb9abe2f87c7ef8cad463c19

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/maasserver/api/scriptresults.py b/src/maasserver/api/scriptresults.py
index 80fc7f3..b11ba66 100644
--- a/src/maasserver/api/scriptresults.py
+++ b/src/maasserver/api/scriptresults.py
@@ -210,6 +210,9 @@ class NodeScriptResultHandler(OperationsHandler):
210 'started': fmt_time(script_result.started),210 'started': fmt_time(script_result.started),
211 'ended': fmt_time(script_result.ended),211 'ended': fmt_time(script_result.ended),
212 'runtime': script_result.runtime,212 'runtime': script_result.runtime,
213 'starttime': script_result.starttime,
214 'endtime': script_result.endtime,
215 'estimated_runtime': script_result.estimated_runtime,
213 'parameters': script_result.parameters,216 'parameters': script_result.parameters,
214 'script_id': script_result.script_id,217 'script_id': script_result.script_id,
215 'script_revision_id': script_result.script_version_id,218 'script_revision_id': script_result.script_version_id,
diff --git a/src/maasserver/api/tests/test_scriptresults.py b/src/maasserver/api/tests/test_scriptresults.py
index d80b68f..ee942a9 100644
--- a/src/maasserver/api/tests/test_scriptresults.py
+++ b/src/maasserver/api/tests/test_scriptresults.py
@@ -269,6 +269,9 @@ class TestNodeScriptResultAPI(APITestCase.ForUser):
269 'started': fmt_time(script_result.started),269 'started': fmt_time(script_result.started),
270 'ended': fmt_time(script_result.ended),270 'ended': fmt_time(script_result.ended),
271 'runtime': script_result.runtime,271 'runtime': script_result.runtime,
272 'starttime': script_result.starttime,
273 'endtime': script_result.endtime,
274 'estimated_runtime': script_result.estimated_runtime,
272 'parameters': script_result.parameters,275 'parameters': script_result.parameters,
273 'script_id': script_result.script_id,276 'script_id': script_result.script_id,
274 'script_revision_id': script_result.script_version_id,277 'script_revision_id': script_result.script_version_id,
@@ -315,6 +318,9 @@ class TestNodeScriptResultAPI(APITestCase.ForUser):
315 'started': fmt_time(script_result.started),318 'started': fmt_time(script_result.started),
316 'ended': fmt_time(script_result.ended),319 'ended': fmt_time(script_result.ended),
317 'runtime': script_result.runtime,320 'runtime': script_result.runtime,
321 'starttime': script_result.starttime,
322 'endtime': script_result.endtime,
323 'estimated_runtime': script_result.estimated_runtime,
318 'parameters': script_result.parameters,324 'parameters': script_result.parameters,
319 'script_id': script_result.script_id,325 'script_id': script_result.script_id,
320 'script_revision_id': script_result.script_version_id,326 'script_revision_id': script_result.script_version_id,
@@ -378,6 +384,9 @@ class TestNodeScriptResultAPI(APITestCase.ForUser):
378 'started': fmt_time(script_result.started),384 'started': fmt_time(script_result.started),
379 'ended': fmt_time(script_result.ended),385 'ended': fmt_time(script_result.ended),
380 'runtime': script_result.runtime,386 'runtime': script_result.runtime,
387 'starttime': script_result.starttime,
388 'endtime': script_result.endtime,
389 'estimated_runtime': script_result.estimated_runtime,
381 'parameters': script_result.parameters,390 'parameters': script_result.parameters,
382 'script_id': script_result.script_id,391 'script_id': script_result.script_id,
383 'script_revision_id': script_result.script_version_id,392 'script_revision_id': script_result.script_version_id,
diff --git a/src/maasserver/static/js/angular/directives/script_runtime.js b/src/maasserver/static/js/angular/directives/script_runtime.js
384new file mode 100644393new file mode 100644
index 0000000..3a198c3
--- /dev/null
+++ b/src/maasserver/static/js/angular/directives/script_runtime.js
@@ -0,0 +1,57 @@
1/* Copyright 2017 Canonical Ltd. This software is licensed under the
2 * GNU Affero General Public License version 3 (see the file LICENSE).
3 *
4 * Script runtime counter directive.
5 */
6
7angular.module('MAAS').run(['$templateCache', function ($templateCache) {
8 // Inject the script_runtime.html into the template cache.
9 $templateCache.put('directive/templates/script_runtime.html', [
10 '<span data-ng-if="(scriptStatus === 1 || scriptStatus === 7) &&',
11 " estimatedRunTime !== 'Unknown'" + '">{{counter}} of ',
12 '~{{estimatedRunTime}}</span>',
13 '<span data-ng-if="(scriptStatus === 1 || scriptStatus === 7) &&',
14 " estimatedRunTime == 'Unknown'" + '">{{counter}}</span>',
15 '<span data-ng-if="scriptStatus === 0 && estimatedRunTime !== ',
16 "'Unknown'" + '">~{{estimatedRunTime}}</span>',
17 '<span data-ng-if="scriptStatus !== 0 && scriptStatus !== 1 ',
18 '&& scriptStatus !== 7">{{runTime}}</span>'
19 ].join(''));
20}]);
21
22angular.module('MAAS').directive('maasScriptRunTime', function() {
23 return {
24 restrict: "A",
25 require: ["startTime", "runTime", "estimatedRunTime", "scriptStatus"],
26 scope: {
27 startTime: '=',
28 runTime: '@',
29 estimatedRunTime: '@',
30 scriptStatus: '='
31 },
32 templateUrl: 'directive/templates/script_runtime.html',
33 controller: function($scope, $interval) {
34 $scope.counter = "0:00:00";
35
36 function incrementCounter() {
37 if(($scope.scriptStatus === 1 || $scope.scriptStatus === 7) &&
38 $scope.startTime) {
39 var date = new Date(null);
40 date.setSeconds((Date.now()/1000) - $scope.startTime);
41 $scope.counter = date.toISOString().substr(11, 8);
42 if($scope.counter.indexOf('00:') === 0) {
43 $scope.counter = $scope.counter.substr(1);
44 }
45 }
46 }
47
48 // Update the counter on init, start the interval and stop it when
49 // the directive is destroyed.
50 incrementCounter();
51 var promise = $interval(incrementCounter, 1000);
52 $scope.$on('$destroy', function() {
53 $interval.cancel(promise);
54 });
55 }
56 };
57});
diff --git a/src/maasserver/static/js/angular/directives/tests/test_script_runtime.js b/src/maasserver/static/js/angular/directives/tests/test_script_runtime.js
0new file mode 10064458new file mode 100644
index 0000000..5839f3f
--- /dev/null
+++ b/src/maasserver/static/js/angular/directives/tests/test_script_runtime.js
@@ -0,0 +1,89 @@
1/* Copyright 2017 Canonical Ltd. This software is licensed under the
2 * GNU Affero General Public License version 3 (see the file LICENSE).
3 *
4 * Unit tests for script runtime directive.
5 */
6
7describe("maasScriptRunTime", function() {
8
9 // Load the MAAS module.
10 beforeEach(module("MAAS"));
11
12 // Create a new scope before each test.
13 var $scope, $interval;
14 beforeEach(inject(function($rootScope, $injector) {
15 $interval = $injector.get('$interval');
16 $scope = $rootScope.$new();
17 $scope.startTime = null;
18 $scope.runTime = null;
19 $scope.estimatedRunTime = null;
20 $scope.scriptStatus = null;
21 }));
22
23 // Return the compiled directive.
24 function compileDirective(
25 startTime, runTime, estimatedRunTime, scriptStatus) {
26 var directive;
27 var html = '<span data-maas-script-run-time="script-runtime" ' +
28 'data-start-time="' + startTime + '" data-run-time="' +
29 runTime + '" data-estimated-run-time="' + estimatedRunTime +
30 '" data-script-status="' + scriptStatus + '"></span>';
31
32 // Compile the directive.
33 inject(function($compile) {
34 directive = $compile(html)($scope);
35 });
36
37 // Perform the digest cycle to finish the compile.
38 $scope.$digest();
39 return directive;
40 }
41
42 it('should have span element', function () {
43 var startTime = Date.now() / 1000;
44 var runTime = '0:00:30';
45 var estimatedRunTime = '0:00:35';
46 var scriptStatus = 7;
47 var directive = compileDirective(
48 startTime, runTime, estimatedRunTime, scriptStatus);
49 var spanElement = directive.find('span');
50 expect(spanElement).toBeDefined();
51 expect(spanElement.text()).toEqual('0:00:00 of ~' + estimatedRunTime);
52 });
53
54 it('should have applied template', function () {
55 var startTime = Date.now() / 1000;
56 var runTime = '0:00:30';
57 var estimatedRunTime = '0:00:35';
58 var scriptStatus = 7;
59 var directive = compileDirective(
60 startTime, runTime, estimatedRunTime, scriptStatus);
61 expect(directive.html()).not.toEqual('');
62 });
63
64 it('should counter based on passed time', function () {
65 var startTime = (Date.now() / 1000) - 5; // 5 seconds
66 var runTime = '0:00:30';
67 var estimatedRunTime = '0:00:35';
68 var scriptStatus = 7;
69 var directive = compileDirective(
70 startTime, runTime, estimatedRunTime, scriptStatus);
71 var spanElement = directive.find('span');
72 expect(spanElement).toBeDefined();
73 expect(spanElement.text()).toEqual('0:00:05 of ~' + estimatedRunTime);
74 });
75
76 it('counter updated based on passed time not $interval', function () {
77 var startTime = (Date.now() / 1000) - 5; // 5 seconds
78 var runTime = '0:00:30';
79 var estimatedRunTime = '0:00:35';
80 var scriptStatus = 7;
81 var directive = compileDirective(
82 startTime, runTime, estimatedRunTime, scriptStatus);
83 // Flush should not cause the passed time to change.
84 $interval.flush(1000);
85 var spanElement = directive.find('span');
86 expect(spanElement).toBeDefined();
87 expect(spanElement.text()).toEqual('0:00:05 of ~' + estimatedRunTime);
88 });
89});
diff --git a/src/maasserver/static/partials/script-results-list.html b/src/maasserver/static/partials/script-results-list.html
index 974e44f..4fd4130 100644
--- a/src/maasserver/static/partials/script-results-list.html
+++ b/src/maasserver/static/partials/script-results-list.html
@@ -13,9 +13,9 @@
13 <div class="table__header table-col--2 u-padding--left-none"></div>13 <div class="table__header table-col--2 u-padding--left-none"></div>
14 <div class="table__header table-col--24">Name</div>14 <div class="table__header table-col--24">Name</div>
15 <div class="table__header table-col--24">Tags</div>15 <div class="table__header table-col--24">Tags</div>
16 <div class="table__header table-col--10">Runtime</div>16 <div class="table__header table-col--15">Runtime</div>
17 <div class="table__header table-col--20">Date</div>17 <div class="table__header table-col--20">Date</div>
18 <div class="table__header table-col--15">Result</div>18 <div class="table__header table-col--10">Result</div>
19 <div class="table__header table-col--5 u-align--right">Actions</div>19 <div class="table__header table-col--5 u-align--right">Actions</div>
20 </div>20 </div>
21 </header>21 </header>
@@ -30,9 +30,9 @@
30 <button class="icon u-margin--top-tiny u-float--right" data-ng-class="{'icon--open': !result.showing_results, 'icon--close': result.showing_results}" data-ng-if="!result.showing_history"></button>30 <button class="icon u-margin--top-tiny u-float--right" data-ng-class="{'icon--open': !result.showing_results, 'icon--close': result.showing_results}" data-ng-if="!result.showing_history"></button>
31 </div>31 </div>
32 <div class="table__data table-col--24" aria-label="Tags"><span data-ng-hide="result.showing_history">{$ result.tags $}</span></div>32 <div class="table__data table-col--24" aria-label="Tags"><span data-ng-hide="result.showing_history">{$ result.tags $}</span></div>
33 <div class="table__data table-col--10" aria-label="Runtime"><span data-ng-hide="result.showing_history">{$ result.runtime $}</span></div>33 <div class="table__data table-col--15" aria-label="Runtime"><span data-ng-hide="result.showing_history" data-maas-script-run-time="script-runtime" data-start-time="result.starttime" data-run-time="{{result.runtime}}" data-estimated-run-time="{{result.estimated_runtime}}" data-script-status="result.status"></span></div>
34 <div class="table__data table-col--20" aria-label="Date"><span data-ng-hide="result.showing_history">{$ result.updated $}</span></div>34 <div class="table__data table-col--20" aria-label="Date"><span data-ng-hide="result.showing_history">{$ result.updated $}</span></div>
35 <div class="table__data table-col--15" aria-label="Status">35 <div class="table__data table-col--10" aria-label="Status">
36 <span data-ng-hide="result.showing_history">36 <span data-ng-hide="result.showing_history">
37 <!-- Only link to the testing result when we've received it. This is indicated with status 2(passed), 3(failed), 4(timedout), 6(degraded), 8(failed installing)-->37 <!-- Only link to the testing result when we've received it. This is indicated with status 2(passed), 3(failed), 4(timedout), 6(degraded), 8(failed installing)-->
38 {$ result.status_name $} <a data-ng-if="result.status === 2 || result.status === 3 || result.status === 4 || result.status === 6 || result.status === 8" href="#/node/{$ type_name $}/{$ node.system_id $}/{$ section.area $}/{$ result.id $}">View log</a>38 {$ result.status_name $} <a data-ng-if="result.status === 2 || result.status === 3 || result.status === 4 || result.status === 6 || result.status === 8" href="#/node/{$ type_name $}/{$ node.system_id $}/{$ section.area $}/{$ result.id $}">View log</a>
@@ -69,11 +69,11 @@
69 <div class="table__data table-col--2 u-padding--left-none" aria-label="Status">69 <div class="table__data table-col--2 u-padding--left-none" aria-label="Status">
70 <span data-maas-script-status="script-status" data-script-status="item.status"></span>70 <span data-maas-script-status="script-status" data-script-status="item.status"></span>
71 </div>71 </div>
72 <div class="table__data table-col--20" aria-label="Name">{$ result.name $}</div>72 <div class="table__data table-col--24" aria-label="Name">{$ result.name $}</div>
73 <div class="table__data table-col--25" aria-label="Tags">{$ result.tags $}</div>73 <div class="table__data table-col--24" aria-label="Tags">{$ result.tags $}</div>
74 <div class="table__data table-col--10" aria-label="Runtime">{$ item.runtime $}</div>74 <div class="table__data table-col--15" aria-label="Runtime"><span data-maas-script-run-time="script-runtime" data-start-time="item.starttime" data-run-time="{{item.runtime}}" data-estimated-run-time="{{item.estimated_runtime}}" data-script-status="item.status"></span></div>
75 <div class="table__data table-col--20" aria-label="Date">{$ item.updated $}</div>75 <div class="table__data table-col--20" aria-label="Date">{$ item.updated $}</div>
76 <div class="table__data table-col--23" aria-label="Status">76 <div class="table__data table-col--10" aria-label="Status">
77 <!-- Only link to the testing result when we've received it. This is indicated with status 2(passed), 3(failed), 4(timedout), 6(degraded), 8(failed installing)-->77 <!-- Only link to the testing result when we've received it. This is indicated with status 2(passed), 3(failed), 4(timedout), 6(degraded), 8(failed installing)-->
78 {$ item.status_name $} <a data-ng-if="item.status === 2 || item.status === 3 || item.status === 4 || item.status === 6 || item.status === 8" href="#/node/{$ type_name $}/{$ node.system_id $}/{$ section.area $}/{$ item.id $}">View log</a>78 {$ item.status_name $} <a data-ng-if="item.status === 2 || item.status === 3 || item.status === 4 || item.status === 6 || item.status === 8" href="#/node/{$ type_name $}/{$ node.system_id $}/{$ section.area $}/{$ item.id $}">View log</a>
79 </div>79 </div>
diff --git a/src/maasserver/views/combo.py b/src/maasserver/views/combo.py
index 6eb2fd9..e8fc6e1 100755
--- a/src/maasserver/views/combo.py
+++ b/src/maasserver/views/combo.py
@@ -128,6 +128,7 @@ MERGE_VIEWS = {
128 "js/angular/directives/release_name.js",128 "js/angular/directives/release_name.js",
129 "js/angular/directives/release_options.js",129 "js/angular/directives/release_options.js",
130 "js/angular/directives/script_results_list.js",130 "js/angular/directives/script_results_list.js",
131 "js/angular/directives/script_runtime.js",
131 "js/angular/directives/script_select.js",132 "js/angular/directives/script_select.js",
132 "js/angular/directives/script_status.js",133 "js/angular/directives/script_status.js",
133 "js/angular/directives/ssh_keys.js",134 "js/angular/directives/ssh_keys.js",
diff --git a/src/maasserver/websockets/handlers/node.py b/src/maasserver/websockets/handlers/node.py
index 7b719a4..95c70b7 100644
--- a/src/maasserver/websockets/handlers/node.py
+++ b/src/maasserver/websockets/handlers/node.py
@@ -605,6 +605,9 @@ class NodeHandler(TimestampedModelHandler):
605 'started': dehydrate_datetime(script_result.started),605 'started': dehydrate_datetime(script_result.started),
606 'ended': dehydrate_datetime(script_result.ended),606 'ended': dehydrate_datetime(script_result.ended),
607 'runtime': script_result.runtime,607 'runtime': script_result.runtime,
608 'starttime': script_result.starttime,
609 'endtime': script_result.endtime,
610 'estimated_runtime': script_result.estimated_runtime,
608 })611 })
609 if (script_result.stderr != b'' and612 if (script_result.stderr != b'' and
610 script_set.result_type != RESULT_TYPE.TESTING):613 script_set.result_type != RESULT_TYPE.TESTING):
@@ -621,6 +624,9 @@ class NodeHandler(TimestampedModelHandler):
621 'started': dehydrate_datetime(script_result.started),624 'started': dehydrate_datetime(script_result.started),
622 'ended': dehydrate_datetime(script_result.ended),625 'ended': dehydrate_datetime(script_result.ended),
623 'runtime': script_result.runtime,626 'runtime': script_result.runtime,
627 'starttime': script_result.starttime,
628 'endtime': script_result.endtime,
629 'estimated_runtime': script_result.estimated_runtime,
624 })630 })
625 return sorted(ret, key=lambda i: i['name'])631 return sorted(ret, key=lambda i: i['name'])
626632
diff --git a/src/maasserver/websockets/handlers/node_result.py b/src/maasserver/websockets/handlers/node_result.py
index a405d06..e6e516e 100644
--- a/src/maasserver/websockets/handlers/node_result.py
+++ b/src/maasserver/websockets/handlers/node_result.py
@@ -76,6 +76,9 @@ class NodeResultHandler(TimestampedModelHandler):
76 data["name"] = obj.name76 data["name"] = obj.name
77 data["status_name"] = obj.status_name77 data["status_name"] = obj.status_name
78 data["runtime"] = obj.runtime78 data["runtime"] = obj.runtime
79 data["starttime"] = obj.starttime
80 data["endtime"] = obj.endtime
81 data["estimated_runtime"] = obj.estimated_runtime
79 data["result_type"] = obj.script_set.result_type82 data["result_type"] = obj.script_set.result_type
80 if obj.script is not None:83 if obj.script is not None:
81 data["hardware_type"] = obj.script.hardware_type84 data["hardware_type"] = obj.script.hardware_type
@@ -92,6 +95,9 @@ class NodeResultHandler(TimestampedModelHandler):
92 "status": history.status,95 "status": history.status,
93 "status_name": history.status_name,96 "status_name": history.status_name,
94 "runtime": history.runtime,97 "runtime": history.runtime,
98 "starttime": history.starttime,
99 "endtime": history.endtime,
100 "estimated_runtime": history.estimated_runtime,
95 } for history in obj.history101 } for history in obj.history
96 ]102 ]
97 try:103 try:
diff --git a/src/maasserver/websockets/handlers/tests/test_machine.py b/src/maasserver/websockets/handlers/tests/test_machine.py
index edebbea..5080cfa 100644
--- a/src/maasserver/websockets/handlers/tests/test_machine.py
+++ b/src/maasserver/websockets/handlers/tests/test_machine.py
@@ -1277,6 +1277,9 @@ class TestMachineHandler(MAASServerTestCase):
1277 'started': dehydrate_datetime(script_result.started),1277 'started': dehydrate_datetime(script_result.started),
1278 'ended': dehydrate_datetime(script_result.ended),1278 'ended': dehydrate_datetime(script_result.ended),
1279 'runtime': script_result.runtime,1279 'runtime': script_result.runtime,
1280 'starttime': script_result.starttime,
1281 'endtime': script_result.endtime,
1282 'estimated_runtime': script_result.estimated_runtime,
1280 },1283 },
1281 {1284 {
1282 'id': script_result.id,1285 'id': script_result.id,
@@ -1292,6 +1295,9 @@ class TestMachineHandler(MAASServerTestCase):
1292 'started': dehydrate_datetime(script_result.started),1295 'started': dehydrate_datetime(script_result.started),
1293 'ended': dehydrate_datetime(script_result.ended),1296 'ended': dehydrate_datetime(script_result.ended),
1294 'runtime': script_result.runtime,1297 'runtime': script_result.runtime,
1298 'starttime': script_result.starttime,
1299 'endtime': script_result.endtime,
1300 'estimated_runtime': script_result.estimated_runtime,
1295 }], handler.dehydrate_script_set(script_set))1301 }], handler.dehydrate_script_set(script_set))
12961302
1297 def test_dehydrate_script_set_returns_output_if_stdout_empty(self):1303 def test_dehydrate_script_set_returns_output_if_stdout_empty(self):
@@ -1318,6 +1324,9 @@ class TestMachineHandler(MAASServerTestCase):
1318 'started': dehydrate_datetime(script_result.started),1324 'started': dehydrate_datetime(script_result.started),
1319 'ended': dehydrate_datetime(script_result.ended),1325 'ended': dehydrate_datetime(script_result.ended),
1320 'runtime': script_result.runtime,1326 'runtime': script_result.runtime,
1327 'starttime': script_result.starttime,
1328 'endtime': script_result.endtime,
1329 'estimated_runtime': script_result.estimated_runtime,
1321 }, handler.dehydrate_script_set(script_result.script_set)[0])1330 }, handler.dehydrate_script_set(script_result.script_set)[0])
13221331
1323 def test_dehydrate_script_set_returns_combined_for_testing(self):1332 def test_dehydrate_script_set_returns_combined_for_testing(self):
@@ -1341,6 +1350,9 @@ class TestMachineHandler(MAASServerTestCase):
1341 'started': dehydrate_datetime(script_result.started),1350 'started': dehydrate_datetime(script_result.started),
1342 'ended': dehydrate_datetime(script_result.ended),1351 'ended': dehydrate_datetime(script_result.ended),
1343 'runtime': script_result.runtime,1352 'runtime': script_result.runtime,
1353 'starttime': script_result.starttime,
1354 'endtime': script_result.endtime,
1355 'estimated_runtime': script_result.estimated_runtime,
1344 }, handler.dehydrate_script_set(script_result.script_set)[0])1356 }, handler.dehydrate_script_set(script_result.script_set)[0])
13451357
1346 def test_dehydrate_script_set_status(self):1358 def test_dehydrate_script_set_status(self):
diff --git a/src/maasserver/websockets/handlers/tests/test_node_result.py b/src/maasserver/websockets/handlers/tests/test_node_result.py
index 0c3013e..5b3fc37 100644
--- a/src/maasserver/websockets/handlers/tests/test_node_result.py
+++ b/src/maasserver/websockets/handlers/tests/test_node_result.py
@@ -42,6 +42,9 @@ class TestNodeResultHandler(MAASServerTestCase):
42 "started": dehydrate_datetime(script_result.started),42 "started": dehydrate_datetime(script_result.started),
43 "ended": dehydrate_datetime(script_result.ended),43 "ended": dehydrate_datetime(script_result.ended),
44 "runtime": script_result.runtime,44 "runtime": script_result.runtime,
45 "starttime": script_result.starttime,
46 "endtime": script_result.endtime,
47 "estimated_runtime": script_result.estimated_runtime,
45 "name": script_result.name,48 "name": script_result.name,
46 "result_type": script_result.script_set.result_type,49 "result_type": script_result.script_set.result_type,
47 "hardware_type": script_result.script.hardware_type,50 "hardware_type": script_result.script.hardware_type,
@@ -52,6 +55,9 @@ class TestNodeResultHandler(MAASServerTestCase):
52 "status": history.status,55 "status": history.status,
53 "status_name": history.status_name,56 "status_name": history.status_name,
54 "runtime": history.runtime,57 "runtime": history.runtime,
58 "starttime": history.starttime,
59 "endtime": history.endtime,
60 "estimated_runtime": history.estimated_runtime,
55 } for history in script_result.history],61 } for history in script_result.history],
56 "results": [{62 "results": [{
57 "name": key,63 "name": key,
diff --git a/src/metadataserver/models/scriptresult.py b/src/metadataserver/models/scriptresult.py
index 7620ddd..dccda0a 100644
--- a/src/metadataserver/models/scriptresult.py
+++ b/src/metadataserver/models/scriptresult.py
@@ -117,6 +117,51 @@ class ScriptResult(CleanSave, TimestampedModel):
117 else:117 else:
118 return ''118 return ''
119119
120 @property
121 def starttime(self):
122 if self.started is not None:
123 return self.started.timestamp()
124 else:
125 return ''
126
127 @property
128 def endtime(self):
129 if self.ended is not None:
130 return self.ended.timestamp()
131 else:
132 return ''
133
134 @property
135 def estimated_runtime(self):
136 # If there is a runtime the script has completed, no need to calculate
137 # an estimate.
138 if self.runtime != '':
139 return self.runtime
140 runtime = None
141 # Get an estimated runtime from previous runs.
142 for script_result in self.history:
143 # Only look at passed results when calculating an estimated
144 # runtime. Failed results may take longer or shorter than
145 # average. Don't use self.history.filter for this as the now
146 # cached history list may be used elsewhere.
147 if script_result.status != SCRIPT_STATUS.PASSED:
148 continue
149 previous_runtime = script_result.ended - script_result.started
150 if runtime is None:
151 runtime = previous_runtime
152 else:
153 runtime += previous_runtime
154 runtime = runtime / 2
155 if runtime is None:
156 if self.script is not None and self.script.timeout != timedelta(0):
157 # If there were no previous runs use the script's timeout.
158 return str(self.script.timeout - timedelta(
159 microseconds=self.script.timeout.microseconds))
160 else:
161 return 'Unknown'
162 else:
163 return str(runtime - timedelta(microseconds=runtime.microseconds))
164
120 def __str__(self):165 def __str__(self):
121 return "%s/%s" % (self.script_set.node.system_id, self.name)166 return "%s/%s" % (self.script_set.node.system_id, self.name)
122167
diff --git a/src/metadataserver/models/tests/test_scriptresult.py b/src/metadataserver/models/tests/test_scriptresult.py
index ed8f41e..65f4fc7 100644
--- a/src/metadataserver/models/tests/test_scriptresult.py
+++ b/src/metadataserver/models/tests/test_scriptresult.py
@@ -417,6 +417,80 @@ class TestScriptResult(MAASServerTestCase):
417 script_result = factory.make_ScriptResult(status=SCRIPT_STATUS.PENDING)417 script_result = factory.make_ScriptResult(status=SCRIPT_STATUS.PENDING)
418 self.assertEquals('', script_result.runtime)418 self.assertEquals('', script_result.runtime)
419419
420 def test_get_starttime(self):
421 now = datetime.now()
422 script_result = factory.make_ScriptResult(
423 status=SCRIPT_STATUS.PASSED,
424 started=now, ended=now)
425 self.assertEquals(now.timestamp(), script_result.starttime)
426
427 def test_get_starttime_None(self):
428 script_result = factory.make_ScriptResult(
429 status=SCRIPT_STATUS.PENDING)
430 self.assertEquals('', script_result.starttime)
431
432 def test_get_endtime(self):
433 now = datetime.now()
434 script_result = factory.make_ScriptResult(
435 status=SCRIPT_STATUS.PASSED,
436 started=now, ended=now)
437 self.assertEquals(now.timestamp(), script_result.endtime)
438
439 def test_get_endtime_None(self):
440 script_result = factory.make_ScriptResult(
441 status=SCRIPT_STATUS.PENDING)
442 self.assertEquals('', script_result.endtime)
443
444 def test_estimated_runtime_returns_set_runtime(self):
445 now = datetime.now()
446 script_result = factory.make_ScriptResult(
447 status=SCRIPT_STATUS.PENDING,
448 started=now, ended=(now + factory.make_timedelta()))
449 self.assertEquals(
450 script_result.runtime, script_result.estimated_runtime)
451
452 def test_estimated_runtime_returns_average_of_previous(self):
453 script = factory.make_Script()
454 script_set = factory.make_ScriptSet()
455 old_results = [
456 factory.make_ScriptResult(
457 status=SCRIPT_STATUS.PASSED,
458 script=script, script_set=script_set)
459 for _ in range(10)
460 ]
461 factory.make_ScriptResult(
462 status=SCRIPT_STATUS.FAILED,
463 script=script, script_set=script_set)
464 average_runtime = (old_results[9].ended - old_results[9].started)
465 for result in reversed(old_results[:-1]):
466 average_runtime += result.ended - result.started
467 average_runtime = average_runtime / 2
468 now = datetime.now()
469 script_result = factory.make_ScriptResult(
470 status=SCRIPT_STATUS.RUNNING, started=now,
471 script=script, script_set=script_set)
472 expected = str(
473 average_runtime - timedelta(
474 microseconds=average_runtime.microseconds))
475 self.assertEquals(
476 expected, script_result.estimated_runtime)
477
478 def test_estimated_runtime_uses_timeout(self):
479 now = datetime.now()
480 script_result = factory.make_ScriptResult(
481 status=SCRIPT_STATUS.RUNNING, started=now)
482 expected = str(script_result.script.timeout - timedelta(
483 microseconds=script_result.script.timeout.microseconds))
484 self.assertEquals(expected, script_result.estimated_runtime)
485
486 def test_estimated_runtime_returns_Unknown(self):
487 now = datetime.now()
488 script_result = factory.make_ScriptResult(
489 status=SCRIPT_STATUS.RUNNING, started=now)
490 script_result.script.timeout = timedelta(0)
491 script_result.script.save()
492 self.assertEquals("Unknown", script_result.estimated_runtime)
493
420 def test_read_results(self):494 def test_read_results(self):
421 results = {495 results = {
422 'status': random.choice(496 'status': random.choice(

Subscribers

People subscribed via source and target branches