Merge lp:~rvb/maas/cluster-own-ui4-1.5 into lp:maas/1.5
- cluster-own-ui4-1.5
- Merge into 1.5
Proposed by
Raphaël Badin
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Raphaël Badin | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 2220 | ||||
Proposed branch: | lp:~rvb/maas/cluster-own-ui4-1.5 | ||||
Merge into: | lp:maas/1.5 | ||||
Diff against target: |
959 lines (+412/-205) 18 files modified
docs/man/maas-import-pxe-files.8.rst (+1/-1) man/maas-import-pxe-files.8 (+2/-2) src/maasserver/api.py (+1/-2) src/maasserver/templates/maasserver/base.html (+5/-0) src/maasserver/templates/maasserver/cluster_listing.html (+54/-58) src/maasserver/templates/maasserver/cluster_listing_head.html (+10/-0) src/maasserver/templates/maasserver/cluster_listing_row.html (+16/-3) src/maasserver/templates/maasserver/no-bootimages-warning.html (+1/-1) src/maasserver/templates/maasserver/nodegroup_confirm_delete.html (+1/-1) src/maasserver/templates/maasserver/nodegroup_edit.html (+1/-1) src/maasserver/templates/maasserver/settings.html (+0/-5) src/maasserver/tests/test_api.py (+3/-1) src/maasserver/urls.py (+22/-9) src/maasserver/views/clusters.py (+108/-3) src/maasserver/views/settings.py (+1/-38) src/maasserver/views/tests/test_boot_image_list.py (+1/-1) src/maasserver/views/tests/test_clusters.py (+185/-6) src/maasserver/views/tests/test_settings.py (+0/-73) |
||||
To merge this branch: | bzr merge lp:~rvb/maas/cluster-own-ui4-1.5 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Raphaël Badin (community) | Approve | ||
Review via email: mp+214006@code.launchpad.net |
Commit message
Backport revision 2223: This branch makes the cluster listings deal gracefully with a large number of clusters. Additionally, it makes the cluster listing pages more accessible.
In details:
- Move the cluster listings to their own url (instead of displaying them on the 'settings' page).
- Split the cluster listings into 3 separate listings (one page for each possible cluster status).
- Add a link to the cluster listing page next to 'Nodes' at the top of the page.
- Add pagination to all the listings.
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'docs/man/maas-import-pxe-files.8.rst' |
2 | --- docs/man/maas-import-pxe-files.8.rst 2014-04-01 07:59:47 +0000 |
3 | +++ docs/man/maas-import-pxe-files.8.rst 2014-04-03 11:47:02 +0000 |
4 | @@ -20,7 +20,7 @@ |
5 | An easier way to run the script is to trigger it from the MAAS web user |
6 | interface. To do that, log in to your MAAS as an administrator using a |
7 | web browser, click the cogwheel icon in the top right of the page to go |
8 | -to the Settings page, and click "Import boot images." This will start |
9 | +to the Clusters page, and click "Import boot images." This will start |
10 | imports on all cluster controllers simultaneously. The same thing can |
11 | also be done through the region-controller API, or through the |
12 | command-line interface. |
13 | |
14 | === modified file 'man/maas-import-pxe-files.8' |
15 | --- man/maas-import-pxe-files.8 2014-04-01 07:59:47 +0000 |
16 | +++ man/maas-import-pxe-files.8 2014-04-03 11:47:02 +0000 |
17 | @@ -1,6 +1,6 @@ |
18 | .\" Man page generated from reStructuredText. |
19 | . |
20 | -.TH "MAAS-IMPORT-PXE-FILES" "8" "April 01, 2014" "1.5" "MAAS" |
21 | +.TH "MAAS-IMPORT-PXE-FILES" "8" "April 03, 2014" "1.5" "MAAS" |
22 | .SH NAME |
23 | maas-import-pxe-files \- MAAS helper script |
24 | . |
25 | @@ -46,7 +46,7 @@ |
26 | An easier way to run the script is to trigger it from the MAAS web user |
27 | interface. To do that, log in to your MAAS as an administrator using a |
28 | web browser, click the cogwheel icon in the top right of the page to go |
29 | -to the Settings page, and click "Import boot images." This will start |
30 | +to the Clusters page, and click "Import boot images." This will start |
31 | imports on all cluster controllers simultaneously. The same thing can |
32 | also be done through the region\-controller API, or through the |
33 | command\-line interface. |
34 | |
35 | === modified file 'src/maasserver/api.py' |
36 | --- src/maasserver/api.py 2014-03-27 17:24:02 +0000 |
37 | +++ src/maasserver/api.py 2014-04-03 11:47:02 +0000 |
38 | @@ -2492,8 +2492,7 @@ |
39 | nodegroups_without_images = nodegroups_without_images.filter( |
40 | status=NODEGROUP_STATUS.ACCEPTED) |
41 | if nodegroups_without_images.exists(): |
42 | - accepted_clusters_url = ( |
43 | - "%s#accepted-clusters" % absolute_reverse("settings")) |
44 | + accepted_clusters_url = absolute_reverse("cluster-list") |
45 | warning = dedent("""\ |
46 | Some cluster controllers are missing boot images. Either the |
47 | import task has not been initiated (for each cluster, the task |
48 | |
49 | === modified file 'src/maasserver/templates/maasserver/base.html' |
50 | --- src/maasserver/templates/maasserver/base.html 2014-02-25 09:57:24 +0000 |
51 | +++ src/maasserver/templates/maasserver/base.html 2014-04-03 11:47:02 +0000 |
52 | @@ -72,6 +72,11 @@ |
53 | <li class="{% block nav-active-node-list %}{% endblock %}"> |
54 | <a href="{% url 'node-list' %}">Nodes</a> |
55 | </li> |
56 | + {% if user.is_superuser %} |
57 | + <li class="{% block nav-active-cluster-list %}{% endblock %}"> |
58 | + <a href="{% url 'cluster-list' %}">Clusters</a> |
59 | + </li> |
60 | + {% endif %} |
61 | <li class="{% block nav-active-zone-list %}{% endblock %}"> |
62 | <a href="{% url 'zone-list' %}">Zones</a> |
63 | </li> |
64 | |
65 | === renamed file 'src/maasserver/templates/maasserver/settings_cluster_listing.html' => 'src/maasserver/templates/maasserver/cluster_listing.html' |
66 | --- src/maasserver/templates/maasserver/settings_cluster_listing.html 2012-11-07 10:39:19 +0000 |
67 | +++ src/maasserver/templates/maasserver/cluster_listing.html 2014-04-03 11:47:02 +0000 |
68 | @@ -1,58 +1,54 @@ |
69 | -<h2 id="accepted-clusters">Cluster controllers</h2> |
70 | -<table class="list" id="accepted-clusters-list"> |
71 | - <tbody> |
72 | - {% for cluster in accepted_clusters %} |
73 | - {% cycle 'even' 'odd' as cycle silent %} |
74 | - {% include "maasserver/settings_cluster_listing_row.html" with cycle=cycle %} |
75 | - {% endfor %} |
76 | - </tbody> |
77 | -</table> |
78 | -<div> |
79 | - <form method="POST" |
80 | - action="{% url 'settings' %}"> |
81 | - {% csrf_token %} |
82 | - <input type="hidden" name="import_all_boot_images" value="1" /> |
83 | - <input type="submit" class="button right" value="Import boot images" /> |
84 | - </form> |
85 | -</div> |
86 | -<div class="clear"></div> |
87 | -{% if pending_clusters %} |
88 | -<h3 id="pending-clusters">Pending clusters</h3> |
89 | -<table class="list" id="pending-clusters-list"> |
90 | - <tbody> |
91 | - {% for cluster in pending_clusters %} |
92 | - {% cycle 'even' 'odd' as cycle silent %} |
93 | - {% include "maasserver/settings_cluster_listing_row.html" with cycle=cycle %} |
94 | - {% endfor %} |
95 | - </tbody> |
96 | -</table> |
97 | -<div> |
98 | - <form id="reject_all_pending_nodegroups" |
99 | - method="POST" |
100 | - action="{% url 'settings' %}"> |
101 | - {% csrf_token %} |
102 | - <input type="hidden" name="mass_accept_submit" value="1" /> |
103 | - <input type="submit" class="button right" value="Accept all" /> |
104 | - </form> |
105 | - <form id="accept_all_pending_nodegroups" |
106 | - method="POST" |
107 | - action="{% url 'settings' %}"> |
108 | - {% csrf_token %} |
109 | - <input type="hidden" name="mass_reject_submit" value="1" /> |
110 | - <input type="submit" class="button right space-right-small" |
111 | - value="Reject all" /> |
112 | - </form> |
113 | -</div> |
114 | -<div class="clear"></div> |
115 | -{% endif %} |
116 | -{% if rejected_clusters %} |
117 | -<h3 id="rejected-clusters">Rejected clusters</h3> |
118 | -<table class="list" id="rejected-clusters-list"> |
119 | - <tbody> |
120 | - {% for cluster in rejected_clusters %} |
121 | - {% cycle 'even' 'odd' as cycle silent %} |
122 | - {% include "maasserver/settings_cluster_listing_row.html" with cycle=cycle %} |
123 | - {% endfor %} |
124 | - </tbody> |
125 | -</table> |
126 | -{% endif %} |
127 | +{% extends "maasserver/base.html" %} |
128 | + |
129 | +{% block nav-active-cluster-list %}active{% endblock %} |
130 | +{% block title %}{{ status_name }} clusters{% endblock %} |
131 | +{% block page-title %}{{ current_count }}{% if input_query %} matching{% endif %} {{ status_name|lower }} cluster{{ current_count|pluralize }} in {% include "maasserver/site_title.html" %}{% endblock %} |
132 | +{% block site-switcher %}{% endblock %} |
133 | +{% block header-search %}{% endblock %} |
134 | + |
135 | +{% block content %} |
136 | +<div id="clusters" style="position: relative;"> |
137 | + |
138 | +<h2 id="clusters">{{ title }}</h2> |
139 | +<table class="list" id="clusters-list"> |
140 | + {% include "maasserver/cluster_listing_head.html" %} |
141 | + <tbody> |
142 | + {% for cluster in cluster_list %} |
143 | + {% cycle 'even' 'odd' as cycle silent %} |
144 | + {% include "maasserver/cluster_listing_row.html" with cycle=cycle warning_no_images=warn_no_images %} |
145 | + {% endfor %} |
146 | + </tbody> |
147 | +</table> |
148 | +{% include "maasserver/pagination.html" %} |
149 | +<div> |
150 | + {% if status == statuses.ACCEPTED %} |
151 | + <form method="POST" |
152 | + action="{% url 'cluster-list' %}"> |
153 | + {% csrf_token %} |
154 | + <input type="hidden" name="import_all_boot_images" value="1" /> |
155 | + <input type="submit" class="button right" value="Import boot images" /> |
156 | + </form> |
157 | + {% endif %} |
158 | + {% if status == statuses.PENDING %} |
159 | + <form id="reject_all_pending_nodegroups" |
160 | + method="POST" |
161 | + action="{% url 'cluster-list' %}"> |
162 | + {% csrf_token %} |
163 | + <input type="hidden" name="mass_accept_submit" value="1" /> |
164 | + <input type="submit" class="button right" value="Accept all" /> |
165 | + </form> |
166 | + <form id="accept_all_pending_nodegroups" |
167 | + method="POST" |
168 | + action="{% url 'cluster-list' %}"> |
169 | + {% csrf_token %} |
170 | + <input type="hidden" name="mass_reject_submit" value="1" /> |
171 | + <input type="submit" class="button right space-right-small" |
172 | + value="Reject all" /> |
173 | + </form> |
174 | + {% endif %} |
175 | + |
176 | +</div> |
177 | +<div class="clear"></div> |
178 | + |
179 | + |
180 | +{% endblock %} |
181 | \ No newline at end of file |
182 | |
183 | === added file 'src/maasserver/templates/maasserver/cluster_listing_head.html' |
184 | --- src/maasserver/templates/maasserver/cluster_listing_head.html 1970-01-01 00:00:00 +0000 |
185 | +++ src/maasserver/templates/maasserver/cluster_listing_head.html 2014-04-03 11:47:02 +0000 |
186 | @@ -0,0 +1,10 @@ |
187 | +<thead> |
188 | + <tr> |
189 | + <th>Name</th> |
190 | + <th>Managed interfaces</th> |
191 | + <th>Boot images</th> |
192 | + <th>Nodes</th> |
193 | + {% comment %} Action buttons {% endcomment %} |
194 | + <th></th> |
195 | + </tr> |
196 | +</thead> |
197 | \ No newline at end of file |
198 | |
199 | === renamed file 'src/maasserver/templates/maasserver/settings_cluster_listing_row.html' => 'src/maasserver/templates/maasserver/cluster_listing_row.html' |
200 | --- src/maasserver/templates/maasserver/settings_cluster_listing_row.html 2012-12-06 04:22:51 +0000 |
201 | +++ src/maasserver/templates/maasserver/cluster_listing_row.html 2014-04-03 11:47:02 +0000 |
202 | @@ -1,11 +1,24 @@ |
203 | -<tr class="cluster {{ cycle }}" |
204 | +<tr class="cluster {{ cycle }} {% if warning_no_images and cluster.bootimage_set.count == 0 %}warning{% endif %}" |
205 | id="{{ cluster.uuid }}"> |
206 | <td> |
207 | - {{ cluster.cluster_name }} |
208 | + <a href="{% url 'cluster-edit' cluster.uuid %}">{{ cluster.cluster_name }}</a> |
209 | + </td> |
210 | + <td> |
211 | + {{ cluster.get_managed_interfaces|length }} |
212 | + </td> |
213 | + <td> |
214 | + {% if warning_no_images and cluster.bootimage_set.count == 0 %} |
215 | + 0 <img src="{{ STATIC_URL }}img/warning.png" title="Warning: this cluster has no boot images."/> |
216 | + {% else %} |
217 | {% if cluster.bootimage_set.count == 0 %} |
218 | - <span class="warning">(Warning: this cluster has no boot images.)</span> |
219 | + 0 |
220 | + {% else %} |
221 | + <a title="View boot images" |
222 | + href="{% url 'cluster-bootimages-list' cluster.uuid %}">{{ cluster.bootimage_set.count }}</a> |
223 | {% endif %} |
224 | + {% endif %} |
225 | </td> |
226 | + <td>{{ cluster.node_set.count }}</td> |
227 | <td class="icon-controls"> |
228 | <a href="{% url 'cluster-edit' cluster.uuid %}" |
229 | class="icon" |
230 | |
231 | === modified file 'src/maasserver/templates/maasserver/no-bootimages-warning.html' |
232 | --- src/maasserver/templates/maasserver/no-bootimages-warning.html 2014-03-27 10:51:28 +0000 |
233 | +++ src/maasserver/templates/maasserver/no-bootimages-warning.html 2014-04-03 11:47:02 +0000 |
234 | @@ -5,5 +5,5 @@ |
235 | <span class="console">/etc/maas/bootresources.yaml</span> on the cluster's |
236 | machine) suits the configuration of the machines this cluster will have to |
237 | handle and then run the import script by clicking the "Import boot images" |
238 | - button on the <a href="{% url 'settings' %}#clusters">cluster's listing page</a>. |
239 | + button on the <a href="{% url 'cluster-list' %}">cluster listing page</a>. |
240 | </p> |
241 | |
242 | === modified file 'src/maasserver/templates/maasserver/nodegroup_confirm_delete.html' |
243 | --- src/maasserver/templates/maasserver/nodegroup_confirm_delete.html 2012-10-08 12:50:32 +0000 |
244 | +++ src/maasserver/templates/maasserver/nodegroup_confirm_delete.html 2014-04-03 11:47:02 +0000 |
245 | @@ -17,7 +17,7 @@ |
246 | <input type="hidden" name="post" value="yes" /> |
247 | <input type="submit" value="Delete cluster controller" |
248 | class="right" /> |
249 | - <a href="{% url 'settings' %}">Cancel</a> |
250 | + <a href="{% url 'cluster-list' %}">Cancel</a> |
251 | </form> |
252 | </p> |
253 | </div> |
254 | |
255 | === modified file 'src/maasserver/templates/maasserver/nodegroup_edit.html' |
256 | --- src/maasserver/templates/maasserver/nodegroup_edit.html 2014-03-25 10:35:14 +0000 |
257 | +++ src/maasserver/templates/maasserver/nodegroup_edit.html 2014-04-03 11:47:02 +0000 |
258 | @@ -15,7 +15,7 @@ |
259 | </ul> |
260 | <input type="submit" value="Save cluster controller" |
261 | class="button right" /> |
262 | - <a class="link-button" href="{% url 'settings' %}">Cancel</a> |
263 | + <a class="link-button" href="{% url 'cluster-list' %}">Cancel</a> |
264 | <h2>Interfaces</h2> |
265 | {% with nb_interfaces=cluster.nodegroupinterface_set.count %} |
266 | This cluster controller has {{ nb_interfaces }} |
267 | |
268 | === modified file 'src/maasserver/templates/maasserver/settings.html' |
269 | --- src/maasserver/templates/maasserver/settings.html 2012-11-30 17:21:28 +0000 |
270 | +++ src/maasserver/templates/maasserver/settings.html 2014-04-03 11:47:02 +0000 |
271 | @@ -70,11 +70,6 @@ |
272 | <div class="clear"></div> |
273 | </div> |
274 | <div class="divider"></div> |
275 | - <div id="clusters" class="block size11 first"> |
276 | - {% include "maasserver/settings_cluster_listing.html" %} |
277 | - <div class="clear"></div> |
278 | - </div> |
279 | - <div class="divider"></div> |
280 | <div id="commissioning_scripts" class="block size11 first"> |
281 | {% include "maasserver/settings_commissioning_scripts.html" %} |
282 | <div class="clear"></div> |
283 | |
284 | === modified file 'src/maasserver/tests/test_api.py' |
285 | --- src/maasserver/tests/test_api.py 2014-03-19 13:54:20 +0000 |
286 | +++ src/maasserver/tests/test_api.py 2014-04-03 11:47:02 +0000 |
287 | @@ -53,6 +53,7 @@ |
288 | from maasserver.testing.oauthclient import OAuthAuthenticatedClient |
289 | from maasserver.testing.testcase import MAASServerTestCase |
290 | from maasserver.tests.test_forms import make_interface_settings |
291 | +from maasserver.utils import absolute_reverse |
292 | from maasserver.utils.orm import get_one |
293 | from maastesting.djangotestcase import TransactionTestCase |
294 | from maastesting.matchers import MockCalledOnceWith |
295 | @@ -754,7 +755,8 @@ |
296 | [args[0][0] for args in recorder.call_args_list]) |
297 | # The persistent error message links to the clusters listing. |
298 | self.assertIn( |
299 | - "/settings/#accepted-clusters", recorder.call_args_list[0][0][1]) |
300 | + absolute_reverse("cluster-list"), |
301 | + recorder.call_args_list[0][0][1]) |
302 | |
303 | def test_warns_if_any_nodegroup_has_no_images(self): |
304 | factory.make_node_group(status=NODEGROUP_STATUS.ACCEPTED) |
305 | |
306 | === modified file 'src/maasserver/urls.py' |
307 | --- src/maasserver/urls.py 2014-03-25 10:35:14 +0000 |
308 | +++ src/maasserver/urls.py 2014-04-03 11:47:02 +0000 |
309 | @@ -21,12 +21,22 @@ |
310 | url, |
311 | ) |
312 | from django.contrib.auth.decorators import user_passes_test |
313 | +from maasserver.enum import NODEGROUP_STATUS |
314 | from maasserver.models import Node |
315 | from maasserver.views import TextTemplateView |
316 | from maasserver.views.account import ( |
317 | login, |
318 | logout, |
319 | ) |
320 | +from maasserver.views.clusters import ( |
321 | + BootImagesListView, |
322 | + ClusterDelete, |
323 | + ClusterEdit, |
324 | + ClusterInterfaceCreate, |
325 | + ClusterInterfaceDelete, |
326 | + ClusterInterfaceEdit, |
327 | + ClusterListView, |
328 | + ) |
329 | from maasserver.views.networks import ( |
330 | NetworkAdd, |
331 | NetworkDelete, |
332 | @@ -60,14 +70,6 @@ |
333 | AccountsView, |
334 | settings, |
335 | ) |
336 | -from maasserver.views.settings_clusters import ( |
337 | - BootImagesListView, |
338 | - ClusterDelete, |
339 | - ClusterEdit, |
340 | - ClusterInterfaceCreate, |
341 | - ClusterInterfaceDelete, |
342 | - ClusterInterfaceEdit, |
343 | - ) |
344 | from maasserver.views.settings_commissioning_scripts import ( |
345 | CommissioningScriptCreate, |
346 | CommissioningScriptDelete, |
347 | @@ -116,7 +118,6 @@ |
348 | r'^account/prefs/sshkey/delete/(?P<keyid>\d*)/$', |
349 | SSHKeyDeleteView.as_view(), name='prefs-delete-sshkey'), |
350 | ) |
351 | - |
352 | # Logout view. |
353 | urlpatterns += patterns( |
354 | 'maasserver.views', |
355 | @@ -159,6 +160,18 @@ |
356 | urlpatterns += patterns( |
357 | 'maasserver.views', |
358 | adminurl( |
359 | + r'^clusters/$', |
360 | + ClusterListView.as_view(status=NODEGROUP_STATUS.ACCEPTED), |
361 | + name='cluster-list'), |
362 | + adminurl( |
363 | + r'^clusters/pending/$', |
364 | + ClusterListView.as_view(status=NODEGROUP_STATUS.PENDING), |
365 | + name='cluster-list-pending'), |
366 | + adminurl( |
367 | + r'^clusters/rejected/$', |
368 | + ClusterListView.as_view(status=NODEGROUP_STATUS.REJECTED), |
369 | + name='cluster-list-rejected'), |
370 | + adminurl( |
371 | r'^clusters/(?P<uuid>[\w\-]+)/edit/$', ClusterEdit.as_view(), |
372 | name='cluster-edit'), |
373 | adminurl( |
374 | |
375 | === renamed file 'src/maasserver/views/settings_clusters.py' => 'src/maasserver/views/clusters.py' |
376 | --- src/maasserver/views/settings_clusters.py 2014-03-25 10:35:14 +0000 |
377 | +++ src/maasserver/views/clusters.py 2014-04-03 11:47:02 +0000 |
378 | @@ -1,7 +1,7 @@ |
379 | # Copyright 2012 Canonical Ltd. This software is licensed under the |
380 | # GNU Affero General Public License version 3 (see the file LICENSE). |
381 | |
382 | -"""Cluster Settings views.""" |
383 | +"""Cluster views.""" |
384 | |
385 | from __future__ import ( |
386 | absolute_import, |
387 | @@ -19,17 +19,29 @@ |
388 | "ClusterInterfaceCreate", |
389 | "ClusterInterfaceDelete", |
390 | "ClusterInterfaceEdit", |
391 | + "ClusterListView", |
392 | ] |
393 | |
394 | +from collections import OrderedDict |
395 | + |
396 | from django.contrib import messages |
397 | from django.core.urlresolvers import reverse |
398 | from django.http import HttpResponseRedirect |
399 | from django.shortcuts import get_object_or_404 |
400 | +from django.utils.safestring import mark_safe |
401 | from django.views.generic import ( |
402 | CreateView, |
403 | DeleteView, |
404 | UpdateView, |
405 | ) |
406 | +from django.views.generic.edit import ( |
407 | + FormMixin, |
408 | + ProcessFormView, |
409 | + ) |
410 | +from maasserver.enum import ( |
411 | + NODEGROUP_STATUS, |
412 | + NODEGROUP_STATUS_CHOICES, |
413 | + ) |
414 | from maasserver.forms import ( |
415 | NodeGroupEdit, |
416 | NodeGroupInterfaceForm, |
417 | @@ -41,6 +53,99 @@ |
418 | from maasserver.views import PaginatedListView |
419 | |
420 | |
421 | +class ClusterListView(PaginatedListView, FormMixin, ProcessFormView): |
422 | + template_name = 'maasserver/cluster_listing.html' |
423 | + context_object_name = "cluster_list" |
424 | + status = None |
425 | + |
426 | + def get_queryset(self): |
427 | + return NodeGroup.objects.filter( |
428 | + status=self.status).order_by('cluster_name') |
429 | + |
430 | + # A record of the urls used to reach the clusters of different |
431 | + # statuses. |
432 | + status_links = OrderedDict(( |
433 | + (NODEGROUP_STATUS.ACCEPTED, 'cluster-list'), |
434 | + (NODEGROUP_STATUS.PENDING, 'cluster-list-pending'), |
435 | + (NODEGROUP_STATUS.REJECTED, 'cluster-list-rejected'), |
436 | + )) |
437 | + |
438 | + def make_title_entry(self, status, link_name): |
439 | + """Generate an entry as used by make_cluster_listing_title(). |
440 | + |
441 | + This is a utility method only used by make_cluster_listing_title. |
442 | + It is a separate method for clarity and to help testing.""" |
443 | + link = reverse(link_name) |
444 | + status_name = NODEGROUP_STATUS_CHOICES[status][1] |
445 | + nb_clusters = NodeGroup.objects.filter( |
446 | + status=status).count() |
447 | + entry = "%d %s cluster%s" % ( |
448 | + nb_clusters, |
449 | + status_name.lower(), |
450 | + 's' if nb_clusters != 1 else '') |
451 | + if nb_clusters != 0 and status != self.status: |
452 | + entry = '<a href="%s">%s</a>' % (link, entry) |
453 | + return entry |
454 | + |
455 | + def make_cluster_listing_title(self): |
456 | + """Generate this view's title with "tabs" for each cluster status. |
457 | + |
458 | + Generate a title for this view with the number of clusters for each |
459 | + possible status. The title includes the links to the other listings |
460 | + (i.e. if this is the listing for the accepted clusters, include links |
461 | + to the listings of the pending/rejected clusters). The title will be |
462 | + of the form: "3 accepted clusters / 1 pending cluster / 2 rejected |
463 | + clusters". (This is simpler to do in the view rather than write this |
464 | + using the template language.) |
465 | + """ |
466 | + return mark_safe( |
467 | + ' / '.join( |
468 | + self.make_title_entry(status, link_name) |
469 | + for status, link_name in self.status_links.items())) |
470 | + |
471 | + def get_context_data(self, **kwargs): |
472 | + context = super(ClusterListView, self).get_context_data(**kwargs) |
473 | + context['current_count'] = NodeGroup.objects.filter( |
474 | + status=self.status).count() |
475 | + context['title'] = self.make_cluster_listing_title() |
476 | + # Display warnings for clusters that have no images, but only for the |
477 | + # display of 'accepted' clusters. |
478 | + context['warn_no_images'] = self.status == NODEGROUP_STATUS.ACCEPTED |
479 | + context['status'] = self.status |
480 | + context['statuses'] = NODEGROUP_STATUS |
481 | + context['status_name'] = NODEGROUP_STATUS_CHOICES[self.status][1] |
482 | + return context |
483 | + |
484 | + def post(self, request, *args, **kwargs): |
485 | + """Handle a POST request.""" |
486 | + if 'mass_accept_submit' in request.POST: |
487 | + # Process accept clusters en masse. |
488 | + number = NodeGroup.objects.accept_all_pending() |
489 | + messages.info(request, "Accepted %d cluster(s)." % number) |
490 | + return HttpResponseRedirect(reverse('cluster-list')) |
491 | + |
492 | + elif 'mass_reject_submit' in request.POST: |
493 | + # Process reject clusters en masse. |
494 | + number = NodeGroup.objects.reject_all_pending() |
495 | + messages.info(request, "Rejected %d cluster(s)." % number) |
496 | + return HttpResponseRedirect(reverse('cluster-list')) |
497 | + |
498 | + elif 'import_all_boot_images' in request.POST: |
499 | + # Import PXE files for all the accepted clusters. |
500 | + NodeGroup.objects.import_boot_images_accepted_clusters() |
501 | + message = ( |
502 | + "Import of boot images started on all cluster controllers. " |
503 | + "Importing the boot images can take a long time depending on " |
504 | + "the available bandwidth.") |
505 | + messages.info(request, message) |
506 | + return HttpResponseRedirect(reverse('cluster-list')) |
507 | + |
508 | + else: |
509 | + # Unknown action: redirect to the cluster listing page (this |
510 | + # shouldn't happen). |
511 | + return HttpResponseRedirect(reverse('cluster-list')) |
512 | + |
513 | + |
514 | class ClusterEdit(UpdateView): |
515 | model = NodeGroup |
516 | template_name = 'maasserver/nodegroup_edit.html' |
517 | @@ -54,7 +159,7 @@ |
518 | return context |
519 | |
520 | def get_success_url(self): |
521 | - return reverse('settings') |
522 | + return reverse('cluster-list') |
523 | |
524 | def get_object(self): |
525 | uuid = self.kwargs.get('uuid', None) |
526 | @@ -75,7 +180,7 @@ |
527 | return get_object_or_404(NodeGroup, uuid=uuid) |
528 | |
529 | def get_next_url(self): |
530 | - return reverse('settings') |
531 | + return reverse('cluster-list') |
532 | |
533 | def delete(self, request, *args, **kwargs): |
534 | cluster = self.get_object() |
535 | |
536 | === modified file 'src/maasserver/views/settings.py' |
537 | --- src/maasserver/views/settings.py 2013-10-07 09:12:40 +0000 |
538 | +++ src/maasserver/views/settings.py 2014-04-03 11:47:02 +0000 |
539 | @@ -38,7 +38,6 @@ |
540 | from django.views.generic.base import TemplateView |
541 | from django.views.generic.detail import SingleObjectTemplateResponseMixin |
542 | from django.views.generic.edit import ModelFormMixin |
543 | -from maasserver.enum import NODEGROUP_STATUS |
544 | from maasserver.exceptions import CannotDeleteUserException |
545 | from maasserver.forms import ( |
546 | CommissioningForm, |
547 | @@ -48,10 +47,7 @@ |
548 | NewUserCreationForm, |
549 | UbuntuForm, |
550 | ) |
551 | -from maasserver.models import ( |
552 | - NodeGroup, |
553 | - UserProfile, |
554 | - ) |
555 | +from maasserver.models import UserProfile |
556 | from maasserver.views import process_form |
557 | from metadataserver.models import CommissioningScript |
558 | |
559 | @@ -186,36 +182,6 @@ |
560 | if response is not None: |
561 | return response |
562 | |
563 | - # Process accept clusters en masse. |
564 | - if 'mass_accept_submit' in request.POST: |
565 | - number = NodeGroup.objects.accept_all_pending() |
566 | - messages.info(request, "Accepted %d cluster(s)." % number) |
567 | - return HttpResponseRedirect(reverse('settings')) |
568 | - |
569 | - # Process reject clusters en masse. |
570 | - if 'mass_reject_submit' in request.POST: |
571 | - number = NodeGroup.objects.reject_all_pending() |
572 | - messages.info(request, "Rejected %d cluster(s)." % number) |
573 | - return HttpResponseRedirect(reverse('settings')) |
574 | - |
575 | - # Import PXE files for all the accepted clusters. |
576 | - if 'import_all_boot_images' in request.POST: |
577 | - NodeGroup.objects.import_boot_images_accepted_clusters() |
578 | - message = ( |
579 | - "Import of boot images started on all cluster controllers. " |
580 | - "Importing the boot images can take a long time depending on " |
581 | - "the available bandwidth.") |
582 | - messages.info(request, message) |
583 | - return HttpResponseRedirect(reverse('settings')) |
584 | - |
585 | - # Cluster listings. |
586 | - accepted_clusters = NodeGroup.objects.filter( |
587 | - status=NODEGROUP_STATUS.ACCEPTED).order_by('cluster_name') |
588 | - pending_clusters = NodeGroup.objects.filter( |
589 | - status=NODEGROUP_STATUS.PENDING).order_by('cluster_name') |
590 | - rejected_clusters = NodeGroup.objects.filter( |
591 | - status=NODEGROUP_STATUS.REJECTED).order_by('cluster_name') |
592 | - |
593 | # Commissioning scripts. |
594 | commissioning_scripts = CommissioningScript.objects.all() |
595 | |
596 | @@ -224,9 +190,6 @@ |
597 | { |
598 | 'user_list': user_list, |
599 | 'commissioning_scripts': commissioning_scripts, |
600 | - 'accepted_clusters': accepted_clusters, |
601 | - 'pending_clusters': pending_clusters, |
602 | - 'rejected_clusters': rejected_clusters, |
603 | 'maas_and_network_form': maas_and_network_form, |
604 | 'commissioning_form': commissioning_form, |
605 | 'ubuntu_form': ubuntu_form, |
606 | |
607 | === modified file 'src/maasserver/views/tests/test_boot_image_list.py' |
608 | --- src/maasserver/views/tests/test_boot_image_list.py 2014-03-27 07:39:38 +0000 |
609 | +++ src/maasserver/views/tests/test_boot_image_list.py 2014-04-03 11:47:02 +0000 |
610 | @@ -21,7 +21,7 @@ |
611 | from lxml.html import fromstring |
612 | from maasserver.testing.factory import factory |
613 | from maasserver.testing.testcase import MAASServerTestCase |
614 | -from maasserver.views.settings_clusters import BootImagesListView |
615 | +from maasserver.views.clusters import BootImagesListView |
616 | from testtools.matchers import ContainsAll |
617 | |
618 | |
619 | |
620 | === renamed file 'src/maasserver/views/tests/test_settings_clusters.py' => 'src/maasserver/views/tests/test_clusters.py' |
621 | --- src/maasserver/views/tests/test_settings_clusters.py 2014-03-25 12:53:32 +0000 |
622 | +++ src/maasserver/views/tests/test_clusters.py 2014-04-03 11:47:02 +0000 |
623 | @@ -20,10 +20,12 @@ |
624 | from lxml.html import fromstring |
625 | from maasserver.enum import ( |
626 | NODEGROUP_STATUS, |
627 | + NODEGROUP_STATUS_CHOICES, |
628 | NODEGROUPINTERFACE_MANAGEMENT, |
629 | ) |
630 | from maasserver.models import ( |
631 | NodeGroup, |
632 | + nodegroup as nodegroup_module, |
633 | NodeGroupInterface, |
634 | ) |
635 | from maasserver.testing import ( |
636 | @@ -33,25 +35,42 @@ |
637 | ) |
638 | from maasserver.testing.factory import factory |
639 | from maasserver.testing.testcase import MAASServerTestCase |
640 | +from maasserver.utils import map_enum |
641 | +from maasserver.views.clusters import ClusterListView |
642 | +from mock import ( |
643 | + ANY, |
644 | + call, |
645 | + ) |
646 | from testtools.matchers import ( |
647 | AllMatch, |
648 | Contains, |
649 | ContainsAll, |
650 | Equals, |
651 | + HasLength, |
652 | MatchesStructure, |
653 | ) |
654 | |
655 | |
656 | class ClusterListingTest(MAASServerTestCase): |
657 | |
658 | - def test_settings_contains_links_to_edit_and_delete_clusters(self): |
659 | + scenarios = [ |
660 | + ('accepted-clusters', {'status': NODEGROUP_STATUS.ACCEPTED}), |
661 | + ('pending-clusters', {'status': NODEGROUP_STATUS.PENDING}), |
662 | + ('rejected-clusters', {'status': NODEGROUP_STATUS.REJECTED}), |
663 | + ] |
664 | + |
665 | + def get_url(self): |
666 | + """Return the listing url used in this scenario.""" |
667 | + return reverse(ClusterListView.status_links[ |
668 | + self.status]) |
669 | + |
670 | + def test_cluster_listing_contains_links_to_manipulate_clusters(self): |
671 | self.client_log_in(as_admin=True) |
672 | nodegroups = { |
673 | - factory.make_node_group(status=NODEGROUP_STATUS.ACCEPTED), |
674 | - factory.make_node_group(status=NODEGROUP_STATUS.PENDING), |
675 | - factory.make_node_group(status=NODEGROUP_STATUS.REJECTED), |
676 | + factory.make_node_group(status=self.status) |
677 | + for _ in range(3) |
678 | } |
679 | - links = get_content_links(self.client.get(reverse('settings'))) |
680 | + links = get_content_links(self.client.get(self.get_url())) |
681 | nodegroup_edit_links = [ |
682 | reverse('cluster-edit', args=[nodegroup.uuid]) |
683 | for nodegroup in nodegroups] |
684 | @@ -62,6 +81,166 @@ |
685 | links, |
686 | ContainsAll(nodegroup_edit_links + nodegroup_delete_links)) |
687 | |
688 | + def make_listing_view(self, status): |
689 | + view = ClusterListView() |
690 | + view.status = status |
691 | + return view |
692 | + |
693 | + def test_make_title_entry_returns_link_for_other_status(self): |
694 | + # If the entry's status is different from the view's status, |
695 | + # the returned entry is a link. |
696 | + other_status = factory.getRandomChoice( |
697 | + NODEGROUP_STATUS_CHOICES, but_not=[self.status]) |
698 | + factory.make_node_group(status=other_status) |
699 | + link_name = ClusterListView.status_links[other_status] |
700 | + view = self.make_listing_view(self.status) |
701 | + entry = view.make_title_entry(other_status, link_name) |
702 | + status_name = NODEGROUP_STATUS_CHOICES[other_status][1] |
703 | + self.assertEqual( |
704 | + '<a href="%s">1 %s cluster</a>' % ( |
705 | + reverse(link_name), status_name.lower()), |
706 | + entry) |
707 | + |
708 | + def test_make_title_entry_returns_title_if_no_cluster(self): |
709 | + # If no cluster correspond to the entry's status, the returned |
710 | + # entry is not a link: it's a simple mention '0 <status> clusters'. |
711 | + other_status = factory.getRandomChoice( |
712 | + NODEGROUP_STATUS_CHOICES, but_not=[self.status]) |
713 | + link_name = ClusterListView.status_links[other_status] |
714 | + view = self.make_listing_view(self.status) |
715 | + entry = view.make_title_entry(other_status, link_name) |
716 | + status_name = NODEGROUP_STATUS_CHOICES[other_status][1] |
717 | + self.assertEqual( |
718 | + '0 %s clusters' % status_name.lower(), entry) |
719 | + |
720 | + def test_title_displays_number_of_clusters(self): |
721 | + for _ in range(3): |
722 | + factory.make_node_group(status=self.status) |
723 | + view = self.make_listing_view(self.status) |
724 | + status_name = NODEGROUP_STATUS_CHOICES[self.status][1] |
725 | + title = view.make_cluster_listing_title() |
726 | + self.assertIn("3 %s clusters" % status_name.lower(), title) |
727 | + |
728 | + def test_title_contains_links_to_other_listings(self): |
729 | + view = self.make_listing_view(self.status) |
730 | + other_statuses = [] |
731 | + # Compute a list with the statuses of the clusters not being |
732 | + # displayed by the 'view'. Create clusters with these statuses. |
733 | + for status in map_enum(NODEGROUP_STATUS).values(): |
734 | + if status != self.status: |
735 | + other_statuses.append(status) |
736 | + factory.make_node_group(status=status) |
737 | + for status in other_statuses: |
738 | + link_name = ClusterListView.status_links[status] |
739 | + title = view.make_cluster_listing_title() |
740 | + self.assertIn(reverse(link_name), title) |
741 | + |
742 | + def test_listing_is_paginated(self): |
743 | + self.patch(ClusterListView, "paginate_by", 2) |
744 | + self.client_log_in(as_admin=True) |
745 | + for _ in range(3): |
746 | + factory.make_node_group(status=self.status) |
747 | + response = self.client.get(self.get_url()) |
748 | + self.assertEqual(httplib.OK, response.status_code) |
749 | + doc = fromstring(response.content) |
750 | + self.assertThat( |
751 | + doc.cssselect('div.pagination'), |
752 | + HasLength(1), |
753 | + "Couldn't find pagination tag.") |
754 | + |
755 | + |
756 | +class ClusterListingAccess(MAASServerTestCase): |
757 | + |
758 | + def test_admin_sees_cluster_tab(self): |
759 | + self.client_log_in(as_admin=True) |
760 | + links = get_content_links( |
761 | + self.client.get(reverse('index')), element='#main-nav') |
762 | + self.assertIn(reverse('cluster-list'), links) |
763 | + |
764 | + def test_non_admin_doesnt_see_cluster_tab(self): |
765 | + self.client_log_in(as_admin=False) |
766 | + links = get_content_links( |
767 | + self.client.get(reverse('index')), element='#main-nav') |
768 | + self.assertNotIn(reverse('cluster-list'), links) |
769 | + |
770 | + |
771 | +class ClusterPendingListingTest(MAASServerTestCase): |
772 | + |
773 | + def test_pending_listing_contains_form_to_accept_all_nodegroups(self): |
774 | + self.client_log_in(as_admin=True) |
775 | + factory.make_node_group(status=NODEGROUP_STATUS.PENDING), |
776 | + response = self.client.get(reverse('cluster-list-pending')) |
777 | + doc = fromstring(response.content) |
778 | + forms = doc.cssselect('form#accept_all_pending_nodegroups') |
779 | + self.assertEqual(1, len(forms)) |
780 | + |
781 | + def test_pending_listing_contains_form_to_reject_all_nodegroups(self): |
782 | + self.client_log_in(as_admin=True) |
783 | + factory.make_node_group(status=NODEGROUP_STATUS.PENDING), |
784 | + response = self.client.get(reverse('cluster-list-pending')) |
785 | + doc = fromstring(response.content) |
786 | + forms = doc.cssselect('form#reject_all_pending_nodegroups') |
787 | + self.assertEqual(1, len(forms)) |
788 | + |
789 | + def test_pending_listing_accepts_all_pending_nodegroups_POST(self): |
790 | + self.client_log_in(as_admin=True) |
791 | + nodegroups = { |
792 | + factory.make_node_group(status=NODEGROUP_STATUS.PENDING), |
793 | + factory.make_node_group(status=NODEGROUP_STATUS.PENDING), |
794 | + } |
795 | + response = self.client.post( |
796 | + reverse('cluster-list-pending'), {'mass_accept_submit': 1}) |
797 | + self.assertEqual(httplib.FOUND, response.status_code) |
798 | + self.assertEqual( |
799 | + [reload_object(nodegroup).status for nodegroup in nodegroups], |
800 | + [NODEGROUP_STATUS.ACCEPTED] * 2) |
801 | + |
802 | + def test_pending_listing_rejects_all_pending_nodegroups_POST(self): |
803 | + self.client_log_in(as_admin=True) |
804 | + nodegroups = { |
805 | + factory.make_node_group(status=NODEGROUP_STATUS.PENDING), |
806 | + factory.make_node_group(status=NODEGROUP_STATUS.PENDING), |
807 | + } |
808 | + response = self.client.post( |
809 | + reverse('cluster-list-pending'), {'mass_reject_submit': 1}) |
810 | + self.assertEqual(httplib.FOUND, response.status_code) |
811 | + self.assertEqual( |
812 | + [reload_object(nodegroup).status for nodegroup in nodegroups], |
813 | + [NODEGROUP_STATUS.REJECTED] * 2) |
814 | + |
815 | + |
816 | +class ClusterAcceptedListingTest(MAASServerTestCase): |
817 | + |
818 | + def test_accepted_listing_import_boot_images_calls_tasks(self): |
819 | + self.client_log_in(as_admin=True) |
820 | + recorder = self.patch(nodegroup_module, 'import_boot_images') |
821 | + accepted_nodegroups = [ |
822 | + factory.make_node_group(status=NODEGROUP_STATUS.ACCEPTED), |
823 | + factory.make_node_group(status=NODEGROUP_STATUS.ACCEPTED), |
824 | + ] |
825 | + response = self.client.post( |
826 | + reverse('cluster-list'), {'import_all_boot_images': 1}) |
827 | + self.assertEqual(httplib.FOUND, response.status_code) |
828 | + calls = [ |
829 | + call(queue=nodegroup.work_queue, kwargs=ANY) |
830 | + for nodegroup in accepted_nodegroups |
831 | + ] |
832 | + self.assertItemsEqual(calls, recorder.apply_async.call_args_list) |
833 | + |
834 | + def test_a_warning_is_displayed_if_the_cluster_has_no_boot_images(self): |
835 | + self.client_log_in(as_admin=True) |
836 | + nodegroup = factory.make_node_group( |
837 | + status=NODEGROUP_STATUS.ACCEPTED) |
838 | + response = self.client.get(reverse('cluster-list')) |
839 | + document = fromstring(response.content) |
840 | + nodegroup_row = document.xpath("//tr[@id='%s']" % nodegroup.uuid)[0] |
841 | + self.assertIn('warning', nodegroup_row.get('class')) |
842 | + warning_elems = ( |
843 | + nodegroup_row.xpath( |
844 | + "//img[@title='Warning: this cluster has no boot images.']")) |
845 | + self.assertEqual( |
846 | + 1, len(warning_elems), "No warning about missing boot images.") |
847 | + |
848 | |
849 | class ClusterDeleteTest(MAASServerTestCase): |
850 | |
851 | @@ -71,7 +250,7 @@ |
852 | delete_link = reverse('cluster-delete', args=[nodegroup.uuid]) |
853 | response = self.client.post(delete_link, {'post': 'yes'}) |
854 | self.assertEqual( |
855 | - (httplib.FOUND, reverse('settings')), |
856 | + (httplib.FOUND, reverse('cluster-list')), |
857 | (response.status_code, extract_redirect(response))) |
858 | self.assertFalse( |
859 | NodeGroup.objects.filter(uuid=nodegroup.uuid).exists()) |
860 | |
861 | === modified file 'src/maasserver/views/tests/test_settings.py' |
862 | --- src/maasserver/views/tests/test_settings.py 2014-04-01 06:50:01 +0000 |
863 | +++ src/maasserver/views/tests/test_settings.py 2014-04-03 11:47:02 +0000 |
864 | @@ -23,11 +23,9 @@ |
865 | from maasserver.enum import ( |
866 | COMMISSIONING_DISTRO_SERIES_CHOICES, |
867 | DISTRO_SERIES, |
868 | - NODEGROUP_STATUS, |
869 | ) |
870 | from maasserver.models import ( |
871 | Config, |
872 | - nodegroup as nodegroup_module, |
873 | UserProfile, |
874 | ) |
875 | from maasserver.testing import ( |
876 | @@ -37,10 +35,6 @@ |
877 | ) |
878 | from maasserver.testing.factory import factory |
879 | from maasserver.testing.testcase import MAASServerTestCase |
880 | -from mock import ( |
881 | - ANY, |
882 | - call, |
883 | - ) |
884 | |
885 | |
886 | class SettingsTest(MAASServerTestCase): |
887 | @@ -185,73 +179,6 @@ |
888 | new_kernel_opts, |
889 | Config.objects.get_config('kernel_opts')) |
890 | |
891 | - def test_settings_contains_form_to_accept_all_nodegroups(self): |
892 | - self.client_log_in(as_admin=True) |
893 | - factory.make_node_group(status=NODEGROUP_STATUS.PENDING), |
894 | - response = self.client.get(reverse('settings')) |
895 | - doc = fromstring(response.content) |
896 | - forms = doc.cssselect('form#accept_all_pending_nodegroups') |
897 | - self.assertEqual(1, len(forms)) |
898 | - |
899 | - def test_settings_contains_form_to_reject_all_nodegroups(self): |
900 | - self.client_log_in(as_admin=True) |
901 | - factory.make_node_group(status=NODEGROUP_STATUS.PENDING), |
902 | - response = self.client.get(reverse('settings')) |
903 | - doc = fromstring(response.content) |
904 | - forms = doc.cssselect('form#reject_all_pending_nodegroups') |
905 | - self.assertEqual(1, len(forms)) |
906 | - |
907 | - def test_settings_accepts_all_pending_nodegroups_POST(self): |
908 | - self.client_log_in(as_admin=True) |
909 | - nodegroups = { |
910 | - factory.make_node_group(status=NODEGROUP_STATUS.PENDING), |
911 | - factory.make_node_group(status=NODEGROUP_STATUS.PENDING), |
912 | - } |
913 | - response = self.client.post( |
914 | - reverse('settings'), {'mass_accept_submit': 1}) |
915 | - self.assertEqual(httplib.FOUND, response.status_code) |
916 | - self.assertEqual( |
917 | - [reload_object(nodegroup).status for nodegroup in nodegroups], |
918 | - [NODEGROUP_STATUS.ACCEPTED] * 2) |
919 | - |
920 | - def test_settings_rejects_all_pending_nodegroups_POST(self): |
921 | - self.client_log_in(as_admin=True) |
922 | - nodegroups = { |
923 | - factory.make_node_group(status=NODEGROUP_STATUS.PENDING), |
924 | - factory.make_node_group(status=NODEGROUP_STATUS.PENDING), |
925 | - } |
926 | - response = self.client.post( |
927 | - reverse('settings'), {'mass_reject_submit': 1}) |
928 | - self.assertEqual(httplib.FOUND, response.status_code) |
929 | - self.assertEqual( |
930 | - [reload_object(nodegroup).status for nodegroup in nodegroups], |
931 | - [NODEGROUP_STATUS.REJECTED] * 2) |
932 | - |
933 | - def test_settings_import_boot_images_calls_tasks(self): |
934 | - self.client_log_in(as_admin=True) |
935 | - recorder = self.patch(nodegroup_module, 'import_boot_images') |
936 | - accepted_nodegroups = [ |
937 | - factory.make_node_group(status=NODEGROUP_STATUS.ACCEPTED), |
938 | - factory.make_node_group(status=NODEGROUP_STATUS.ACCEPTED), |
939 | - ] |
940 | - response = self.client.post( |
941 | - reverse('settings'), {'import_all_boot_images': 1}) |
942 | - self.assertEqual(httplib.FOUND, response.status_code) |
943 | - calls = [ |
944 | - call(queue=nodegroup.work_queue, kwargs=ANY) |
945 | - for nodegroup in accepted_nodegroups |
946 | - ] |
947 | - self.assertItemsEqual(calls, recorder.apply_async.call_args_list) |
948 | - |
949 | - def test_cluster_no_boot_images_message_displayed_if_no_boot_images(self): |
950 | - self.client_log_in(as_admin=True) |
951 | - nodegroup = factory.make_node_group( |
952 | - status=NODEGROUP_STATUS.ACCEPTED) |
953 | - response = self.client.get(reverse('settings')) |
954 | - document = fromstring(response.content) |
955 | - nodegroup_row = document.xpath("//tr[@id='%s']" % nodegroup.uuid)[0] |
956 | - self.assertIn('no boot images', nodegroup_row.text_content()) |
957 | - |
958 | |
959 | class NonAdminSettingsTest(MAASServerTestCase): |
960 |
Simple backport: self-approving.