Merge ~blake-rouse/maas:lp1723425 into maas:master
- Git
- lp:~blake-rouse/maas
- lp1723425
- Merge into 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) |
Related bugs: |
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-
Description of the change
To post a comment you must log in.
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
MAAS Lander (maas-lander) wrote : | # |
LANDING
-b lp1723425 lp:~blake-rouse/maas into -b master lp:~maas-committers/maas
STATUS: FAILED BUILD
LOG: http://
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: 6351fe88d73b42a
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/src/maasserver/api/scriptresults.py b/src/maasserver/api/scriptresults.py | |||
2 | index 80fc7f3..b11ba66 100644 | |||
3 | --- a/src/maasserver/api/scriptresults.py | |||
4 | +++ b/src/maasserver/api/scriptresults.py | |||
5 | @@ -210,6 +210,9 @@ class NodeScriptResultHandler(OperationsHandler): | |||
6 | 210 | 'started': fmt_time(script_result.started), | 210 | 'started': fmt_time(script_result.started), |
7 | 211 | 'ended': fmt_time(script_result.ended), | 211 | 'ended': fmt_time(script_result.ended), |
8 | 212 | 'runtime': script_result.runtime, | 212 | 'runtime': script_result.runtime, |
9 | 213 | 'starttime': script_result.starttime, | ||
10 | 214 | 'endtime': script_result.endtime, | ||
11 | 215 | 'estimated_runtime': script_result.estimated_runtime, | ||
12 | 213 | 'parameters': script_result.parameters, | 216 | 'parameters': script_result.parameters, |
13 | 214 | 'script_id': script_result.script_id, | 217 | 'script_id': script_result.script_id, |
14 | 215 | 'script_revision_id': script_result.script_version_id, | 218 | 'script_revision_id': script_result.script_version_id, |
15 | diff --git a/src/maasserver/api/tests/test_scriptresults.py b/src/maasserver/api/tests/test_scriptresults.py | |||
16 | index d80b68f..ee942a9 100644 | |||
17 | --- a/src/maasserver/api/tests/test_scriptresults.py | |||
18 | +++ b/src/maasserver/api/tests/test_scriptresults.py | |||
19 | @@ -269,6 +269,9 @@ class TestNodeScriptResultAPI(APITestCase.ForUser): | |||
20 | 269 | 'started': fmt_time(script_result.started), | 269 | 'started': fmt_time(script_result.started), |
21 | 270 | 'ended': fmt_time(script_result.ended), | 270 | 'ended': fmt_time(script_result.ended), |
22 | 271 | 'runtime': script_result.runtime, | 271 | 'runtime': script_result.runtime, |
23 | 272 | 'starttime': script_result.starttime, | ||
24 | 273 | 'endtime': script_result.endtime, | ||
25 | 274 | 'estimated_runtime': script_result.estimated_runtime, | ||
26 | 272 | 'parameters': script_result.parameters, | 275 | 'parameters': script_result.parameters, |
27 | 273 | 'script_id': script_result.script_id, | 276 | 'script_id': script_result.script_id, |
28 | 274 | 'script_revision_id': script_result.script_version_id, | 277 | 'script_revision_id': script_result.script_version_id, |
29 | @@ -315,6 +318,9 @@ class TestNodeScriptResultAPI(APITestCase.ForUser): | |||
30 | 315 | 'started': fmt_time(script_result.started), | 318 | 'started': fmt_time(script_result.started), |
31 | 316 | 'ended': fmt_time(script_result.ended), | 319 | 'ended': fmt_time(script_result.ended), |
32 | 317 | 'runtime': script_result.runtime, | 320 | 'runtime': script_result.runtime, |
33 | 321 | 'starttime': script_result.starttime, | ||
34 | 322 | 'endtime': script_result.endtime, | ||
35 | 323 | 'estimated_runtime': script_result.estimated_runtime, | ||
36 | 318 | 'parameters': script_result.parameters, | 324 | 'parameters': script_result.parameters, |
37 | 319 | 'script_id': script_result.script_id, | 325 | 'script_id': script_result.script_id, |
38 | 320 | 'script_revision_id': script_result.script_version_id, | 326 | 'script_revision_id': script_result.script_version_id, |
39 | @@ -378,6 +384,9 @@ class TestNodeScriptResultAPI(APITestCase.ForUser): | |||
40 | 378 | 'started': fmt_time(script_result.started), | 384 | 'started': fmt_time(script_result.started), |
41 | 379 | 'ended': fmt_time(script_result.ended), | 385 | 'ended': fmt_time(script_result.ended), |
42 | 380 | 'runtime': script_result.runtime, | 386 | 'runtime': script_result.runtime, |
43 | 387 | 'starttime': script_result.starttime, | ||
44 | 388 | 'endtime': script_result.endtime, | ||
45 | 389 | 'estimated_runtime': script_result.estimated_runtime, | ||
46 | 381 | 'parameters': script_result.parameters, | 390 | 'parameters': script_result.parameters, |
47 | 382 | 'script_id': script_result.script_id, | 391 | 'script_id': script_result.script_id, |
48 | 383 | 'script_revision_id': script_result.script_version_id, | 392 | 'script_revision_id': script_result.script_version_id, |
49 | diff --git a/src/maasserver/static/js/angular/directives/script_runtime.js b/src/maasserver/static/js/angular/directives/script_runtime.js | |||
50 | 384 | new file mode 100644 | 393 | new file mode 100644 |
51 | index 0000000..3a198c3 | |||
52 | --- /dev/null | |||
53 | +++ b/src/maasserver/static/js/angular/directives/script_runtime.js | |||
54 | @@ -0,0 +1,57 @@ | |||
55 | 1 | /* Copyright 2017 Canonical Ltd. This software is licensed under the | ||
56 | 2 | * GNU Affero General Public License version 3 (see the file LICENSE). | ||
57 | 3 | * | ||
58 | 4 | * Script runtime counter directive. | ||
59 | 5 | */ | ||
60 | 6 | |||
61 | 7 | angular.module('MAAS').run(['$templateCache', function ($templateCache) { | ||
62 | 8 | // Inject the script_runtime.html into the template cache. | ||
63 | 9 | $templateCache.put('directive/templates/script_runtime.html', [ | ||
64 | 10 | '<span data-ng-if="(scriptStatus === 1 || scriptStatus === 7) &&', | ||
65 | 11 | " estimatedRunTime !== 'Unknown'" + '">{{counter}} of ', | ||
66 | 12 | '~{{estimatedRunTime}}</span>', | ||
67 | 13 | '<span data-ng-if="(scriptStatus === 1 || scriptStatus === 7) &&', | ||
68 | 14 | " estimatedRunTime == 'Unknown'" + '">{{counter}}</span>', | ||
69 | 15 | '<span data-ng-if="scriptStatus === 0 && estimatedRunTime !== ', | ||
70 | 16 | "'Unknown'" + '">~{{estimatedRunTime}}</span>', | ||
71 | 17 | '<span data-ng-if="scriptStatus !== 0 && scriptStatus !== 1 ', | ||
72 | 18 | '&& scriptStatus !== 7">{{runTime}}</span>' | ||
73 | 19 | ].join('')); | ||
74 | 20 | }]); | ||
75 | 21 | |||
76 | 22 | angular.module('MAAS').directive('maasScriptRunTime', function() { | ||
77 | 23 | return { | ||
78 | 24 | restrict: "A", | ||
79 | 25 | require: ["startTime", "runTime", "estimatedRunTime", "scriptStatus"], | ||
80 | 26 | scope: { | ||
81 | 27 | startTime: '=', | ||
82 | 28 | runTime: '@', | ||
83 | 29 | estimatedRunTime: '@', | ||
84 | 30 | scriptStatus: '=' | ||
85 | 31 | }, | ||
86 | 32 | templateUrl: 'directive/templates/script_runtime.html', | ||
87 | 33 | controller: function($scope, $interval) { | ||
88 | 34 | $scope.counter = "0:00:00"; | ||
89 | 35 | |||
90 | 36 | function incrementCounter() { | ||
91 | 37 | if(($scope.scriptStatus === 1 || $scope.scriptStatus === 7) && | ||
92 | 38 | $scope.startTime) { | ||
93 | 39 | var date = new Date(null); | ||
94 | 40 | date.setSeconds((Date.now()/1000) - $scope.startTime); | ||
95 | 41 | $scope.counter = date.toISOString().substr(11, 8); | ||
96 | 42 | if($scope.counter.indexOf('00:') === 0) { | ||
97 | 43 | $scope.counter = $scope.counter.substr(1); | ||
98 | 44 | } | ||
99 | 45 | } | ||
100 | 46 | } | ||
101 | 47 | |||
102 | 48 | // Update the counter on init, start the interval and stop it when | ||
103 | 49 | // the directive is destroyed. | ||
104 | 50 | incrementCounter(); | ||
105 | 51 | var promise = $interval(incrementCounter, 1000); | ||
106 | 52 | $scope.$on('$destroy', function() { | ||
107 | 53 | $interval.cancel(promise); | ||
108 | 54 | }); | ||
109 | 55 | } | ||
110 | 56 | }; | ||
111 | 57 | }); | ||
112 | 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 | |||
113 | 0 | new file mode 100644 | 58 | new file mode 100644 |
114 | index 0000000..5839f3f | |||
115 | --- /dev/null | |||
116 | +++ b/src/maasserver/static/js/angular/directives/tests/test_script_runtime.js | |||
117 | @@ -0,0 +1,89 @@ | |||
118 | 1 | /* Copyright 2017 Canonical Ltd. This software is licensed under the | ||
119 | 2 | * GNU Affero General Public License version 3 (see the file LICENSE). | ||
120 | 3 | * | ||
121 | 4 | * Unit tests for script runtime directive. | ||
122 | 5 | */ | ||
123 | 6 | |||
124 | 7 | describe("maasScriptRunTime", function() { | ||
125 | 8 | |||
126 | 9 | // Load the MAAS module. | ||
127 | 10 | beforeEach(module("MAAS")); | ||
128 | 11 | |||
129 | 12 | // Create a new scope before each test. | ||
130 | 13 | var $scope, $interval; | ||
131 | 14 | beforeEach(inject(function($rootScope, $injector) { | ||
132 | 15 | $interval = $injector.get('$interval'); | ||
133 | 16 | $scope = $rootScope.$new(); | ||
134 | 17 | $scope.startTime = null; | ||
135 | 18 | $scope.runTime = null; | ||
136 | 19 | $scope.estimatedRunTime = null; | ||
137 | 20 | $scope.scriptStatus = null; | ||
138 | 21 | })); | ||
139 | 22 | |||
140 | 23 | // Return the compiled directive. | ||
141 | 24 | function compileDirective( | ||
142 | 25 | startTime, runTime, estimatedRunTime, scriptStatus) { | ||
143 | 26 | var directive; | ||
144 | 27 | var html = '<span data-maas-script-run-time="script-runtime" ' + | ||
145 | 28 | 'data-start-time="' + startTime + '" data-run-time="' + | ||
146 | 29 | runTime + '" data-estimated-run-time="' + estimatedRunTime + | ||
147 | 30 | '" data-script-status="' + scriptStatus + '"></span>'; | ||
148 | 31 | |||
149 | 32 | // Compile the directive. | ||
150 | 33 | inject(function($compile) { | ||
151 | 34 | directive = $compile(html)($scope); | ||
152 | 35 | }); | ||
153 | 36 | |||
154 | 37 | // Perform the digest cycle to finish the compile. | ||
155 | 38 | $scope.$digest(); | ||
156 | 39 | return directive; | ||
157 | 40 | } | ||
158 | 41 | |||
159 | 42 | it('should have span element', function () { | ||
160 | 43 | var startTime = Date.now() / 1000; | ||
161 | 44 | var runTime = '0:00:30'; | ||
162 | 45 | var estimatedRunTime = '0:00:35'; | ||
163 | 46 | var scriptStatus = 7; | ||
164 | 47 | var directive = compileDirective( | ||
165 | 48 | startTime, runTime, estimatedRunTime, scriptStatus); | ||
166 | 49 | var spanElement = directive.find('span'); | ||
167 | 50 | expect(spanElement).toBeDefined(); | ||
168 | 51 | expect(spanElement.text()).toEqual('0:00:00 of ~' + estimatedRunTime); | ||
169 | 52 | }); | ||
170 | 53 | |||
171 | 54 | it('should have applied template', function () { | ||
172 | 55 | var startTime = Date.now() / 1000; | ||
173 | 56 | var runTime = '0:00:30'; | ||
174 | 57 | var estimatedRunTime = '0:00:35'; | ||
175 | 58 | var scriptStatus = 7; | ||
176 | 59 | var directive = compileDirective( | ||
177 | 60 | startTime, runTime, estimatedRunTime, scriptStatus); | ||
178 | 61 | expect(directive.html()).not.toEqual(''); | ||
179 | 62 | }); | ||
180 | 63 | |||
181 | 64 | it('should counter based on passed time', function () { | ||
182 | 65 | var startTime = (Date.now() / 1000) - 5; // 5 seconds | ||
183 | 66 | var runTime = '0:00:30'; | ||
184 | 67 | var estimatedRunTime = '0:00:35'; | ||
185 | 68 | var scriptStatus = 7; | ||
186 | 69 | var directive = compileDirective( | ||
187 | 70 | startTime, runTime, estimatedRunTime, scriptStatus); | ||
188 | 71 | var spanElement = directive.find('span'); | ||
189 | 72 | expect(spanElement).toBeDefined(); | ||
190 | 73 | expect(spanElement.text()).toEqual('0:00:05 of ~' + estimatedRunTime); | ||
191 | 74 | }); | ||
192 | 75 | |||
193 | 76 | it('counter updated based on passed time not $interval', function () { | ||
194 | 77 | var startTime = (Date.now() / 1000) - 5; // 5 seconds | ||
195 | 78 | var runTime = '0:00:30'; | ||
196 | 79 | var estimatedRunTime = '0:00:35'; | ||
197 | 80 | var scriptStatus = 7; | ||
198 | 81 | var directive = compileDirective( | ||
199 | 82 | startTime, runTime, estimatedRunTime, scriptStatus); | ||
200 | 83 | // Flush should not cause the passed time to change. | ||
201 | 84 | $interval.flush(1000); | ||
202 | 85 | var spanElement = directive.find('span'); | ||
203 | 86 | expect(spanElement).toBeDefined(); | ||
204 | 87 | expect(spanElement.text()).toEqual('0:00:05 of ~' + estimatedRunTime); | ||
205 | 88 | }); | ||
206 | 89 | }); | ||
207 | diff --git a/src/maasserver/static/partials/script-results-list.html b/src/maasserver/static/partials/script-results-list.html | |||
208 | index 974e44f..4fd4130 100644 | |||
209 | --- a/src/maasserver/static/partials/script-results-list.html | |||
210 | +++ b/src/maasserver/static/partials/script-results-list.html | |||
211 | @@ -13,9 +13,9 @@ | |||
212 | 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> |
213 | 14 | <div class="table__header table-col--24">Name</div> | 14 | <div class="table__header table-col--24">Name</div> |
214 | 15 | <div class="table__header table-col--24">Tags</div> | 15 | <div class="table__header table-col--24">Tags</div> |
216 | 16 | <div class="table__header table-col--10">Runtime</div> | 16 | <div class="table__header table-col--15">Runtime</div> |
217 | 17 | <div class="table__header table-col--20">Date</div> | 17 | <div class="table__header table-col--20">Date</div> |
219 | 18 | <div class="table__header table-col--15">Result</div> | 18 | <div class="table__header table-col--10">Result</div> |
220 | 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> |
221 | 20 | </div> | 20 | </div> |
222 | 21 | </header> | 21 | </header> |
223 | @@ -30,9 +30,9 @@ | |||
224 | 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> |
225 | 31 | </div> | 31 | </div> |
226 | 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> |
228 | 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> |
229 | 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> |
231 | 35 | <div class="table__data table-col--15" aria-label="Status"> | 35 | <div class="table__data table-col--10" aria-label="Status"> |
232 | 36 | <span data-ng-hide="result.showing_history"> | 36 | <span data-ng-hide="result.showing_history"> |
233 | 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)--> |
234 | 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> |
235 | @@ -69,11 +69,11 @@ | |||
236 | 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"> |
237 | 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> |
238 | 71 | </div> | 71 | </div> |
242 | 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> |
243 | 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> |
244 | 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> |
245 | 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> |
247 | 76 | <div class="table__data table-col--23" aria-label="Status"> | 76 | <div class="table__data table-col--10" aria-label="Status"> |
248 | 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)--> |
249 | 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> |
250 | 79 | </div> | 79 | </div> |
251 | diff --git a/src/maasserver/views/combo.py b/src/maasserver/views/combo.py | |||
252 | index 6eb2fd9..e8fc6e1 100755 | |||
253 | --- a/src/maasserver/views/combo.py | |||
254 | +++ b/src/maasserver/views/combo.py | |||
255 | @@ -128,6 +128,7 @@ MERGE_VIEWS = { | |||
256 | 128 | "js/angular/directives/release_name.js", | 128 | "js/angular/directives/release_name.js", |
257 | 129 | "js/angular/directives/release_options.js", | 129 | "js/angular/directives/release_options.js", |
258 | 130 | "js/angular/directives/script_results_list.js", | 130 | "js/angular/directives/script_results_list.js", |
259 | 131 | "js/angular/directives/script_runtime.js", | ||
260 | 131 | "js/angular/directives/script_select.js", | 132 | "js/angular/directives/script_select.js", |
261 | 132 | "js/angular/directives/script_status.js", | 133 | "js/angular/directives/script_status.js", |
262 | 133 | "js/angular/directives/ssh_keys.js", | 134 | "js/angular/directives/ssh_keys.js", |
263 | diff --git a/src/maasserver/websockets/handlers/node.py b/src/maasserver/websockets/handlers/node.py | |||
264 | index 7b719a4..95c70b7 100644 | |||
265 | --- a/src/maasserver/websockets/handlers/node.py | |||
266 | +++ b/src/maasserver/websockets/handlers/node.py | |||
267 | @@ -605,6 +605,9 @@ class NodeHandler(TimestampedModelHandler): | |||
268 | 605 | 'started': dehydrate_datetime(script_result.started), | 605 | 'started': dehydrate_datetime(script_result.started), |
269 | 606 | 'ended': dehydrate_datetime(script_result.ended), | 606 | 'ended': dehydrate_datetime(script_result.ended), |
270 | 607 | 'runtime': script_result.runtime, | 607 | 'runtime': script_result.runtime, |
271 | 608 | 'starttime': script_result.starttime, | ||
272 | 609 | 'endtime': script_result.endtime, | ||
273 | 610 | 'estimated_runtime': script_result.estimated_runtime, | ||
274 | 608 | }) | 611 | }) |
275 | 609 | if (script_result.stderr != b'' and | 612 | if (script_result.stderr != b'' and |
276 | 610 | script_set.result_type != RESULT_TYPE.TESTING): | 613 | script_set.result_type != RESULT_TYPE.TESTING): |
277 | @@ -621,6 +624,9 @@ class NodeHandler(TimestampedModelHandler): | |||
278 | 621 | 'started': dehydrate_datetime(script_result.started), | 624 | 'started': dehydrate_datetime(script_result.started), |
279 | 622 | 'ended': dehydrate_datetime(script_result.ended), | 625 | 'ended': dehydrate_datetime(script_result.ended), |
280 | 623 | 'runtime': script_result.runtime, | 626 | 'runtime': script_result.runtime, |
281 | 627 | 'starttime': script_result.starttime, | ||
282 | 628 | 'endtime': script_result.endtime, | ||
283 | 629 | 'estimated_runtime': script_result.estimated_runtime, | ||
284 | 624 | }) | 630 | }) |
285 | 625 | return sorted(ret, key=lambda i: i['name']) | 631 | return sorted(ret, key=lambda i: i['name']) |
286 | 626 | 632 | ||
287 | diff --git a/src/maasserver/websockets/handlers/node_result.py b/src/maasserver/websockets/handlers/node_result.py | |||
288 | index a405d06..e6e516e 100644 | |||
289 | --- a/src/maasserver/websockets/handlers/node_result.py | |||
290 | +++ b/src/maasserver/websockets/handlers/node_result.py | |||
291 | @@ -76,6 +76,9 @@ class NodeResultHandler(TimestampedModelHandler): | |||
292 | 76 | data["name"] = obj.name | 76 | data["name"] = obj.name |
293 | 77 | data["status_name"] = obj.status_name | 77 | data["status_name"] = obj.status_name |
294 | 78 | data["runtime"] = obj.runtime | 78 | data["runtime"] = obj.runtime |
295 | 79 | data["starttime"] = obj.starttime | ||
296 | 80 | data["endtime"] = obj.endtime | ||
297 | 81 | data["estimated_runtime"] = obj.estimated_runtime | ||
298 | 79 | data["result_type"] = obj.script_set.result_type | 82 | data["result_type"] = obj.script_set.result_type |
299 | 80 | if obj.script is not None: | 83 | if obj.script is not None: |
300 | 81 | data["hardware_type"] = obj.script.hardware_type | 84 | data["hardware_type"] = obj.script.hardware_type |
301 | @@ -92,6 +95,9 @@ class NodeResultHandler(TimestampedModelHandler): | |||
302 | 92 | "status": history.status, | 95 | "status": history.status, |
303 | 93 | "status_name": history.status_name, | 96 | "status_name": history.status_name, |
304 | 94 | "runtime": history.runtime, | 97 | "runtime": history.runtime, |
305 | 98 | "starttime": history.starttime, | ||
306 | 99 | "endtime": history.endtime, | ||
307 | 100 | "estimated_runtime": history.estimated_runtime, | ||
308 | 95 | } for history in obj.history | 101 | } for history in obj.history |
309 | 96 | ] | 102 | ] |
310 | 97 | try: | 103 | try: |
311 | diff --git a/src/maasserver/websockets/handlers/tests/test_machine.py b/src/maasserver/websockets/handlers/tests/test_machine.py | |||
312 | index edebbea..5080cfa 100644 | |||
313 | --- a/src/maasserver/websockets/handlers/tests/test_machine.py | |||
314 | +++ b/src/maasserver/websockets/handlers/tests/test_machine.py | |||
315 | @@ -1277,6 +1277,9 @@ class TestMachineHandler(MAASServerTestCase): | |||
316 | 1277 | 'started': dehydrate_datetime(script_result.started), | 1277 | 'started': dehydrate_datetime(script_result.started), |
317 | 1278 | 'ended': dehydrate_datetime(script_result.ended), | 1278 | 'ended': dehydrate_datetime(script_result.ended), |
318 | 1279 | 'runtime': script_result.runtime, | 1279 | 'runtime': script_result.runtime, |
319 | 1280 | 'starttime': script_result.starttime, | ||
320 | 1281 | 'endtime': script_result.endtime, | ||
321 | 1282 | 'estimated_runtime': script_result.estimated_runtime, | ||
322 | 1280 | }, | 1283 | }, |
323 | 1281 | { | 1284 | { |
324 | 1282 | 'id': script_result.id, | 1285 | 'id': script_result.id, |
325 | @@ -1292,6 +1295,9 @@ class TestMachineHandler(MAASServerTestCase): | |||
326 | 1292 | 'started': dehydrate_datetime(script_result.started), | 1295 | 'started': dehydrate_datetime(script_result.started), |
327 | 1293 | 'ended': dehydrate_datetime(script_result.ended), | 1296 | 'ended': dehydrate_datetime(script_result.ended), |
328 | 1294 | 'runtime': script_result.runtime, | 1297 | 'runtime': script_result.runtime, |
329 | 1298 | 'starttime': script_result.starttime, | ||
330 | 1299 | 'endtime': script_result.endtime, | ||
331 | 1300 | 'estimated_runtime': script_result.estimated_runtime, | ||
332 | 1295 | }], handler.dehydrate_script_set(script_set)) | 1301 | }], handler.dehydrate_script_set(script_set)) |
333 | 1296 | 1302 | ||
334 | 1297 | def test_dehydrate_script_set_returns_output_if_stdout_empty(self): | 1303 | def test_dehydrate_script_set_returns_output_if_stdout_empty(self): |
335 | @@ -1318,6 +1324,9 @@ class TestMachineHandler(MAASServerTestCase): | |||
336 | 1318 | 'started': dehydrate_datetime(script_result.started), | 1324 | 'started': dehydrate_datetime(script_result.started), |
337 | 1319 | 'ended': dehydrate_datetime(script_result.ended), | 1325 | 'ended': dehydrate_datetime(script_result.ended), |
338 | 1320 | 'runtime': script_result.runtime, | 1326 | 'runtime': script_result.runtime, |
339 | 1327 | 'starttime': script_result.starttime, | ||
340 | 1328 | 'endtime': script_result.endtime, | ||
341 | 1329 | 'estimated_runtime': script_result.estimated_runtime, | ||
342 | 1321 | }, handler.dehydrate_script_set(script_result.script_set)[0]) | 1330 | }, handler.dehydrate_script_set(script_result.script_set)[0]) |
343 | 1322 | 1331 | ||
344 | 1323 | def test_dehydrate_script_set_returns_combined_for_testing(self): | 1332 | def test_dehydrate_script_set_returns_combined_for_testing(self): |
345 | @@ -1341,6 +1350,9 @@ class TestMachineHandler(MAASServerTestCase): | |||
346 | 1341 | 'started': dehydrate_datetime(script_result.started), | 1350 | 'started': dehydrate_datetime(script_result.started), |
347 | 1342 | 'ended': dehydrate_datetime(script_result.ended), | 1351 | 'ended': dehydrate_datetime(script_result.ended), |
348 | 1343 | 'runtime': script_result.runtime, | 1352 | 'runtime': script_result.runtime, |
349 | 1353 | 'starttime': script_result.starttime, | ||
350 | 1354 | 'endtime': script_result.endtime, | ||
351 | 1355 | 'estimated_runtime': script_result.estimated_runtime, | ||
352 | 1344 | }, handler.dehydrate_script_set(script_result.script_set)[0]) | 1356 | }, handler.dehydrate_script_set(script_result.script_set)[0]) |
353 | 1345 | 1357 | ||
354 | 1346 | def test_dehydrate_script_set_status(self): | 1358 | def test_dehydrate_script_set_status(self): |
355 | diff --git a/src/maasserver/websockets/handlers/tests/test_node_result.py b/src/maasserver/websockets/handlers/tests/test_node_result.py | |||
356 | index 0c3013e..5b3fc37 100644 | |||
357 | --- a/src/maasserver/websockets/handlers/tests/test_node_result.py | |||
358 | +++ b/src/maasserver/websockets/handlers/tests/test_node_result.py | |||
359 | @@ -42,6 +42,9 @@ class TestNodeResultHandler(MAASServerTestCase): | |||
360 | 42 | "started": dehydrate_datetime(script_result.started), | 42 | "started": dehydrate_datetime(script_result.started), |
361 | 43 | "ended": dehydrate_datetime(script_result.ended), | 43 | "ended": dehydrate_datetime(script_result.ended), |
362 | 44 | "runtime": script_result.runtime, | 44 | "runtime": script_result.runtime, |
363 | 45 | "starttime": script_result.starttime, | ||
364 | 46 | "endtime": script_result.endtime, | ||
365 | 47 | "estimated_runtime": script_result.estimated_runtime, | ||
366 | 45 | "name": script_result.name, | 48 | "name": script_result.name, |
367 | 46 | "result_type": script_result.script_set.result_type, | 49 | "result_type": script_result.script_set.result_type, |
368 | 47 | "hardware_type": script_result.script.hardware_type, | 50 | "hardware_type": script_result.script.hardware_type, |
369 | @@ -52,6 +55,9 @@ class TestNodeResultHandler(MAASServerTestCase): | |||
370 | 52 | "status": history.status, | 55 | "status": history.status, |
371 | 53 | "status_name": history.status_name, | 56 | "status_name": history.status_name, |
372 | 54 | "runtime": history.runtime, | 57 | "runtime": history.runtime, |
373 | 58 | "starttime": history.starttime, | ||
374 | 59 | "endtime": history.endtime, | ||
375 | 60 | "estimated_runtime": history.estimated_runtime, | ||
376 | 55 | } for history in script_result.history], | 61 | } for history in script_result.history], |
377 | 56 | "results": [{ | 62 | "results": [{ |
378 | 57 | "name": key, | 63 | "name": key, |
379 | diff --git a/src/metadataserver/models/scriptresult.py b/src/metadataserver/models/scriptresult.py | |||
380 | index 7620ddd..dccda0a 100644 | |||
381 | --- a/src/metadataserver/models/scriptresult.py | |||
382 | +++ b/src/metadataserver/models/scriptresult.py | |||
383 | @@ -117,6 +117,51 @@ class ScriptResult(CleanSave, TimestampedModel): | |||
384 | 117 | else: | 117 | else: |
385 | 118 | return '' | 118 | return '' |
386 | 119 | 119 | ||
387 | 120 | @property | ||
388 | 121 | def starttime(self): | ||
389 | 122 | if self.started is not None: | ||
390 | 123 | return self.started.timestamp() | ||
391 | 124 | else: | ||
392 | 125 | return '' | ||
393 | 126 | |||
394 | 127 | @property | ||
395 | 128 | def endtime(self): | ||
396 | 129 | if self.ended is not None: | ||
397 | 130 | return self.ended.timestamp() | ||
398 | 131 | else: | ||
399 | 132 | return '' | ||
400 | 133 | |||
401 | 134 | @property | ||
402 | 135 | def estimated_runtime(self): | ||
403 | 136 | # If there is a runtime the script has completed, no need to calculate | ||
404 | 137 | # an estimate. | ||
405 | 138 | if self.runtime != '': | ||
406 | 139 | return self.runtime | ||
407 | 140 | runtime = None | ||
408 | 141 | # Get an estimated runtime from previous runs. | ||
409 | 142 | for script_result in self.history: | ||
410 | 143 | # Only look at passed results when calculating an estimated | ||
411 | 144 | # runtime. Failed results may take longer or shorter than | ||
412 | 145 | # average. Don't use self.history.filter for this as the now | ||
413 | 146 | # cached history list may be used elsewhere. | ||
414 | 147 | if script_result.status != SCRIPT_STATUS.PASSED: | ||
415 | 148 | continue | ||
416 | 149 | previous_runtime = script_result.ended - script_result.started | ||
417 | 150 | if runtime is None: | ||
418 | 151 | runtime = previous_runtime | ||
419 | 152 | else: | ||
420 | 153 | runtime += previous_runtime | ||
421 | 154 | runtime = runtime / 2 | ||
422 | 155 | if runtime is None: | ||
423 | 156 | if self.script is not None and self.script.timeout != timedelta(0): | ||
424 | 157 | # If there were no previous runs use the script's timeout. | ||
425 | 158 | return str(self.script.timeout - timedelta( | ||
426 | 159 | microseconds=self.script.timeout.microseconds)) | ||
427 | 160 | else: | ||
428 | 161 | return 'Unknown' | ||
429 | 162 | else: | ||
430 | 163 | return str(runtime - timedelta(microseconds=runtime.microseconds)) | ||
431 | 164 | |||
432 | 120 | def __str__(self): | 165 | def __str__(self): |
433 | 121 | return "%s/%s" % (self.script_set.node.system_id, self.name) | 166 | return "%s/%s" % (self.script_set.node.system_id, self.name) |
434 | 122 | 167 | ||
435 | diff --git a/src/metadataserver/models/tests/test_scriptresult.py b/src/metadataserver/models/tests/test_scriptresult.py | |||
436 | index ed8f41e..65f4fc7 100644 | |||
437 | --- a/src/metadataserver/models/tests/test_scriptresult.py | |||
438 | +++ b/src/metadataserver/models/tests/test_scriptresult.py | |||
439 | @@ -417,6 +417,80 @@ class TestScriptResult(MAASServerTestCase): | |||
440 | 417 | script_result = factory.make_ScriptResult(status=SCRIPT_STATUS.PENDING) | 417 | script_result = factory.make_ScriptResult(status=SCRIPT_STATUS.PENDING) |
441 | 418 | self.assertEquals('', script_result.runtime) | 418 | self.assertEquals('', script_result.runtime) |
442 | 419 | 419 | ||
443 | 420 | def test_get_starttime(self): | ||
444 | 421 | now = datetime.now() | ||
445 | 422 | script_result = factory.make_ScriptResult( | ||
446 | 423 | status=SCRIPT_STATUS.PASSED, | ||
447 | 424 | started=now, ended=now) | ||
448 | 425 | self.assertEquals(now.timestamp(), script_result.starttime) | ||
449 | 426 | |||
450 | 427 | def test_get_starttime_None(self): | ||
451 | 428 | script_result = factory.make_ScriptResult( | ||
452 | 429 | status=SCRIPT_STATUS.PENDING) | ||
453 | 430 | self.assertEquals('', script_result.starttime) | ||
454 | 431 | |||
455 | 432 | def test_get_endtime(self): | ||
456 | 433 | now = datetime.now() | ||
457 | 434 | script_result = factory.make_ScriptResult( | ||
458 | 435 | status=SCRIPT_STATUS.PASSED, | ||
459 | 436 | started=now, ended=now) | ||
460 | 437 | self.assertEquals(now.timestamp(), script_result.endtime) | ||
461 | 438 | |||
462 | 439 | def test_get_endtime_None(self): | ||
463 | 440 | script_result = factory.make_ScriptResult( | ||
464 | 441 | status=SCRIPT_STATUS.PENDING) | ||
465 | 442 | self.assertEquals('', script_result.endtime) | ||
466 | 443 | |||
467 | 444 | def test_estimated_runtime_returns_set_runtime(self): | ||
468 | 445 | now = datetime.now() | ||
469 | 446 | script_result = factory.make_ScriptResult( | ||
470 | 447 | status=SCRIPT_STATUS.PENDING, | ||
471 | 448 | started=now, ended=(now + factory.make_timedelta())) | ||
472 | 449 | self.assertEquals( | ||
473 | 450 | script_result.runtime, script_result.estimated_runtime) | ||
474 | 451 | |||
475 | 452 | def test_estimated_runtime_returns_average_of_previous(self): | ||
476 | 453 | script = factory.make_Script() | ||
477 | 454 | script_set = factory.make_ScriptSet() | ||
478 | 455 | old_results = [ | ||
479 | 456 | factory.make_ScriptResult( | ||
480 | 457 | status=SCRIPT_STATUS.PASSED, | ||
481 | 458 | script=script, script_set=script_set) | ||
482 | 459 | for _ in range(10) | ||
483 | 460 | ] | ||
484 | 461 | factory.make_ScriptResult( | ||
485 | 462 | status=SCRIPT_STATUS.FAILED, | ||
486 | 463 | script=script, script_set=script_set) | ||
487 | 464 | average_runtime = (old_results[9].ended - old_results[9].started) | ||
488 | 465 | for result in reversed(old_results[:-1]): | ||
489 | 466 | average_runtime += result.ended - result.started | ||
490 | 467 | average_runtime = average_runtime / 2 | ||
491 | 468 | now = datetime.now() | ||
492 | 469 | script_result = factory.make_ScriptResult( | ||
493 | 470 | status=SCRIPT_STATUS.RUNNING, started=now, | ||
494 | 471 | script=script, script_set=script_set) | ||
495 | 472 | expected = str( | ||
496 | 473 | average_runtime - timedelta( | ||
497 | 474 | microseconds=average_runtime.microseconds)) | ||
498 | 475 | self.assertEquals( | ||
499 | 476 | expected, script_result.estimated_runtime) | ||
500 | 477 | |||
501 | 478 | def test_estimated_runtime_uses_timeout(self): | ||
502 | 479 | now = datetime.now() | ||
503 | 480 | script_result = factory.make_ScriptResult( | ||
504 | 481 | status=SCRIPT_STATUS.RUNNING, started=now) | ||
505 | 482 | expected = str(script_result.script.timeout - timedelta( | ||
506 | 483 | microseconds=script_result.script.timeout.microseconds)) | ||
507 | 484 | self.assertEquals(expected, script_result.estimated_runtime) | ||
508 | 485 | |||
509 | 486 | def test_estimated_runtime_returns_Unknown(self): | ||
510 | 487 | now = datetime.now() | ||
511 | 488 | script_result = factory.make_ScriptResult( | ||
512 | 489 | status=SCRIPT_STATUS.RUNNING, started=now) | ||
513 | 490 | script_result.script.timeout = timedelta(0) | ||
514 | 491 | script_result.script.save() | ||
515 | 492 | self.assertEquals("Unknown", script_result.estimated_runtime) | ||
516 | 493 | |||
517 | 420 | def test_read_results(self): | 494 | def test_read_results(self): |
518 | 421 | results = { | 495 | results = { |
519 | 422 | 'status': random.choice( | 496 | 'status': random.choice( |
UNIT TESTS
-b lp1723425 lp:~blake-rouse/maas into -b master lp:~maas-committers/maas
STATUS: FAILED maas-ci- jenkins. internal: 8080/job/ maas/job/ branch- tester/ 662/console 6db6548ec1ada82 526f2e9186
LOG: http://
COMMIT: a55e2f46eeba4ca