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

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: no longer in the source branch.
Merged at revision: 6004
Proposed branch: lp:~ltrager/maas/installation_tab
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 222 lines (+70/-32)
7 files modified
src/maasserver/static/js/angular/controllers/node_details.js (+21/-8)
src/maasserver/static/js/angular/controllers/tests/test_node_details.js (+8/-1)
src/maasserver/static/partials/node-details.html (+8/-4)
src/maasserver/websockets/handlers/node.py (+0/-7)
src/maasserver/websockets/handlers/tests/test_machine.py (+0/-11)
src/metadataserver/api_twisted.py (+16/-1)
src/metadataserver/tests/test_api_twisted.py (+17/-0)
To merge this branch: bzr merge lp:~ltrager/maas/installation_tab
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Andres Rodriguez (community) Needs Fixing
Review via email: mp+322822@code.launchpad.net

Commit message

Add installation tab to UI

The installation tab appears when a node has a current_installation_script_set. When no output is available a status message now appears. Like commissioning and testing a status icon is on the installation tab. To capture when installation starts the metadata server now looks for the status start message from curtin.

To post a comment you must log in.
Revision history for this message
Andres Rodriguez (andreserl) wrote :

Having an installation tab that will be empty most of the lifecycle of a machine is not really ideal. Put the installation output on the summary page.

review: Needs Fixing
Revision history for this message
Blake Rouse (blake-rouse) wrote :

This looks good. The installation tab only shows up when in deploying, deployed, failed deployment. The icons on the tab are really cool and the messaging you show is very nice. This is a huge improvement!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/static/js/angular/controllers/node_details.js'
2--- src/maasserver/static/js/angular/controllers/node_details.js 2017-04-04 19:10:53 +0000
3+++ src/maasserver/static/js/angular/controllers/node_details.js 2017-04-20 09:13:49 +0000
4@@ -969,15 +969,28 @@
5 // results, but it is unused. Only one installation result will
6 // exists for a node. Grab the first one in the array.
7 var results = $scope.node.installation_results;
8- if(!angular.isArray(results)) {
9- return "";
10- }
11- if(results.length === 0) {
12- return "";
13+ if(!angular.isArray(results) ||
14+ results.length === 0 || results[0].output === "") {
15+ switch($scope.node.installation_script_set_status) {
16+ case 0:
17+ return "System is booting...";
18+ case 1:
19+ return "Installation has begun!";
20+ case 2:
21+ return ("Installation has succeeded but no " +
22+ "output was given.");
23+ case 3:
24+ return ("Installation has failed and no output was " +
25+ "given.");
26+ case 4:
27+ return "Installation failed after 40 minutes.";
28+ case 5:
29+ return "Installation was aborted.";
30+ default:
31+ return "";
32+ }
33 } else {
34- // Prepend a newline before the data, because the code
35- // tag requires that the content start on a newline.
36- return "\n" + results[0].output;
37+ return results[0].output;
38 }
39 };
40
41
42=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details.js'
43--- src/maasserver/static/js/angular/controllers/tests/test_node_details.js 2017-04-04 19:10:53 +0000
44+++ src/maasserver/static/js/angular/controllers/tests/test_node_details.js 2017-04-20 09:13:49 +0000
45@@ -2165,7 +2165,14 @@
46 $scope.node.installation_results.push({
47 output: {}
48 });
49- expect($scope.getInstallationData()).toBe("\n" + install_result);
50+ expect($scope.getInstallationData()).toBe(install_result);
51+ });
52+
53+ it("returns status message when no output and status", function() {
54+ var controller = makeController();
55+ $scope.node = makeNode();
56+ $scope.node.installation_script_set_status = makeInteger(0, 5);
57+ expect($scope.getInstallationData()).not.toBe("");
58 });
59 });
60
61
62=== modified file 'src/maasserver/static/partials/node-details.html'
63--- src/maasserver/static/partials/node-details.html 2017-04-18 14:59:13 +0000
64+++ src/maasserver/static/partials/node-details.html 2017-04-20 09:13:49 +0000
65@@ -205,6 +205,9 @@
66 <button role="tab" class="page-navigation__link" data-ng-if="!isController && !isDevice"
67 data-ng-class="{ 'is-active': section.area === 'storage'}"
68 data-ng-click="section.area = 'storage'">Storage</button>
69+ <button role="tab" class="page-navigation__link" data-ng-if="node.installation_results.length > 0"
70+ data-ng-class="{ 'is-active': section.area === 'installation'}"
71+ data-ng-click="section.area = 'installation'"><span class="icon" data-ng-class="{ 'icon--warning': node.installation_script_set_status === -1, 'icon--pending': node.installation_script_set_status === 0, 'icon--running': node.installation_script_set_status === 1, 'icon--pass': node.installation_script_set_status === 2, 'icon--status-failed': node.installation_script_set_status === 3 || node.installation_script_set_status === 5, 'icon--timed-out': node.installation_script_set_status === 4 }"></span> Installation</button>
72 <button role="tab" class="page-navigation__link" data-ng-if="node.commissioning_results.length > 0"
73 data-ng-class="{ 'is-active': section.area === 'commissioning'}"
74 data-ng-click="section.area = 'commissioning'"><span class="icon" data-ng-class="{ 'icon--warning': node.commissioning_script_set_status === -1, 'icon--pending': node.commissioning_script_set_status === 0, 'icon--running': node.commissioning_script_set_status === 1, 'icon--pass': node.commissioning_script_set_status === 2, 'icon--status-failed': node.commissioning_script_set_status === 3 || node.commissioning_script_set_status === 5, 'icon--timed-out': node.commissioning_script_set_status === 4 }"></span> Commissioning</button>
75@@ -2329,6 +2332,11 @@
76 </div>
77 </div>
78 </section>
79+ <section class="row" data-ng-if="section.area === 'installation'">
80+ <div class="wrapper--inner">
81+ <pre class="code-block code-block--numbering code-block--terminal" data-maas-code-lines="getInstallationData()"></pre>
82+ </div>
83+ </section>
84 <section class="row" data-ng-if="section.area === 'commissioning'">
85 <div class="wrapper--inner">
86 <div class="twelve-col">
87@@ -2355,10 +2363,6 @@
88 </tbody>
89 </table>
90 </div>
91- <div class="twelve-col ng-hide" data-ng-show="machine_output.selectedView.name === 'installation'">
92- <pre class="code-block code-block--numbering code-block--terminal" data-maas-code-lines="getInstallationData()">
93- </pre>
94- </div>
95 </div>
96 </section>
97 <section class="row" data-ng-if="section.area === 'testing'">
98
99=== modified file 'src/maasserver/websockets/handlers/node.py'
100--- src/maasserver/websockets/handlers/node.py 2017-04-05 16:41:49 +0000
101+++ src/maasserver/websockets/handlers/node.py 2017-04-20 09:13:49 +0000
102@@ -449,13 +449,6 @@
103 else:
104 output = script_result.stdout
105
106- # MAAS creates an empty script result when commissioning starts for
107- # tracking. Don't show the result in the UI until we actually have
108- # something to display.
109- if (script_set.result_type == RESULT_TYPE.INSTALLATION and
110- output == b''):
111- continue
112-
113 if script_result.script is not None:
114 tags = ', '.join(script_result.script.tags)
115 title = script_result.script.title
116
117=== modified file 'src/maasserver/websockets/handlers/tests/test_machine.py'
118--- src/maasserver/websockets/handlers/tests/test_machine.py 2017-04-17 21:39:12 +0000
119+++ src/maasserver/websockets/handlers/tests/test_machine.py 2017-04-20 09:13:49 +0000
120@@ -946,17 +946,6 @@
121 'runtime': script_result.runtime,
122 }, handler.dehydrate_script_set(script_result.script_set)[0])
123
124- def test_dehydrate_script_set_returns_nothing_for_empty_installation(self):
125- owner = factory.make_User()
126- handler = MachineHandler(owner, {})
127- script_set = factory.make_ScriptSet(
128- result_type=RESULT_TYPE.INSTALLATION)
129- script_result = factory.make_ScriptResult(
130- stdout=b'', stderr=b'', output=b'',
131- status=SCRIPT_STATUS.PASSED, script_set=script_set)
132- self.assertItemsEqual(
133- [], handler.dehydrate_script_set(script_result.script_set))
134-
135 def test_dehydrate_script_set_status(self):
136 owner = factory.make_User()
137 handler = MachineHandler(owner, {})
138
139=== modified file 'src/metadataserver/api_twisted.py'
140--- src/metadataserver/api_twisted.py 2017-04-13 20:40:33 +0000
141+++ src/metadataserver/api_twisted.py 2017-04-20 09:13:49 +0000
142@@ -16,6 +16,7 @@
143 NODE_TYPE,
144 )
145 from maasserver.models.timestampedmodel import now
146+from maasserver.preseed import CURTIN_INSTALL_LOG
147 from maasserver.utils.orm import (
148 in_transaction,
149 make_serialization_failure,
150@@ -28,6 +29,7 @@
151 add_event_to_node_event_log,
152 process_file,
153 )
154+from metadataserver.enum import SCRIPT_STATUS
155 from metadataserver.models import (
156 NodeKey,
157 ScriptSet,
158@@ -354,6 +356,14 @@
159 node.owner = None
160 node.error = 'failed: %s' % description
161 save_node = True
162+ elif self._is_top_level(activity_name) and event_type == 'start':
163+ if (node.status == NODE_STATUS.DEPLOYING and
164+ activity_name == 'cmd-install' and origin == 'curtin'):
165+ script_set = node.current_installation_script_set
166+ script_result = script_set.find_script_result(
167+ script_name=CURTIN_INSTALL_LOG)
168+ script_result.status = SCRIPT_STATUS.RUNNING
169+ script_result.save(update_fields=['status'])
170
171 if save_node:
172 node.save()
173@@ -413,11 +423,16 @@
174 message['timestamp'] = datetime.utcnow()
175
176 # Determine if this messsage needs to be processed immediately.
177+ is_starting_event = (
178+ self._is_top_level(message['name']) and
179+ message['name'] == 'cmd-install' and
180+ message['event_type'] == 'start' and
181+ message['origin'] == 'curtin')
182 is_final_event = (
183 self._is_top_level(message['name']) and
184 message['event_type'] == 'finish')
185 has_files = len(message.get('files', [])) > 0
186- if is_final_event or has_files:
187+ if is_starting_event or is_final_event or has_files:
188 d = deferToDatabase(
189 self._processMessageNow, authorization, message)
190 d.addErrback(
191
192=== modified file 'src/metadataserver/tests/test_api_twisted.py'
193--- src/metadataserver/tests/test_api_twisted.py 2017-04-13 20:40:33 +0000
194+++ src/metadataserver/tests/test_api_twisted.py 2017-04-20 09:13:49 +0000
195@@ -23,6 +23,7 @@
196 Tag,
197 )
198 from maasserver.models.signals.testing import SignalsDisabled
199+from maasserver.preseed import CURTIN_INSTALL_LOG
200 from maasserver.testing.factory import factory
201 from maasserver.testing.testcase import (
202 MAASServerTestCase,
203@@ -845,3 +846,19 @@
204 }
205 script_set = script_set_statuses.get(node.status)
206 self.assertIsNotNone(script_set.last_ping)
207+
208+ def test_captures_installation_start(self):
209+ node = factory.make_Node(
210+ status=NODE_STATUS.DEPLOYING, with_empty_script_sets=True)
211+ payload = {
212+ 'event_type': 'start',
213+ 'origin': 'curtin',
214+ 'name': 'cmd-install',
215+ 'description': 'Installation has started.',
216+ 'timestamp': datetime.utcnow(),
217+ }
218+ self.processMessage(node, payload)
219+ script_set = node.current_installation_script_set
220+ script_result = script_set.find_script_result(
221+ script_name=CURTIN_INSTALL_LOG)
222+ self.assertEqual(SCRIPT_STATUS.RUNNING, script_result.status)