Merge ~newell-jensen/maas:ui-pod-zones into maas:master

Proposed by Newell Jensen
Status: Merged
Approved by: Newell Jensen
Approved revision: a86c1048c906d518b102959ae95f8f8d4aacd0c5
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~newell-jensen/maas:ui-pod-zones
Merge into: maas:master
Diff against target: 693 lines (+221/-35)
10 files modified
src/maasserver/api/tests/test_pods.py (+3/-0)
src/maasserver/forms/pods.py (+10/-3)
src/maasserver/forms/tests/test_pods.py (+49/-7)
src/maasserver/static/js/angular/controllers/pod_details.js (+35/-7)
src/maasserver/static/js/angular/controllers/pods_list.js (+7/-5)
src/maasserver/static/js/angular/controllers/tests/test_pod_details.js (+77/-4)
src/maasserver/static/js/angular/controllers/tests/test_pods_list.js (+9/-5)
src/maasserver/static/partials/pod-details.html (+25/-4)
src/maasserver/static/partials/pods-list.html (+2/-0)
src/maasserver/websockets/handlers/tests/test_pod.py (+4/-0)
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Needs Fixing
MAAS Lander Needs Fixing
Lee Trager (community) Approve
Review via email: mp+340073@code.launchpad.net

Commit message

Add Pod zones to the UI for both adding and updating the configuration of a Pod.

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

UNIT TESTS
-b ui-pod-zones lp:~newell-jensen/maas/+git/maas into -b master lp:~maas-committers/maas

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

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

LGTM!

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

UNIT TESTS
-b ui-pod-zones lp:~newell-jensen/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/1796/console
COMMIT: 428f294ea39d53855fc55aa836d41bf13e40d2d8

review: Needs Fixing
Revision history for this message
Andres Rodriguez (andreserl) wrote :

======================================================================
ERROR: maasserver.websockets.handlers.tests.test_pod.TestPodHandler.test_update
----------------------------------------------------------------------
Traceback (most recent call last):
testtools.testresult.real._StringException: Empty attachments:
  Twisted logs

Traceback (most recent call last):
  File "/run/build/maas/src/maastesting/runtest.py", line 138, in _run_user
    result = function(*args, **kwargs)
  File "/home/ubuntu/.buildout/eggs/testtools-2.2.0-py3.6.egg/testtools/testcase.py", line 719, in _run_test_method
    return self._get_test_method()()
  File "/usr/lib/python3/dist-packages/crochet/_eventloop.py", line 461, in wrapper
    return eventual_result.wait(timeout)
  File "/usr/lib/python3/dist-packages/crochet/_eventloop.py", line 231, in wait
    result.raiseException()
  File "/usr/lib/python3/dist-packages/twisted/python/failure.py", line 385, in raiseException
    raise self.value.with_traceback(self.tb)
maasserver.websockets.base.HandlerValidationError: {'zone': ['Select a valid choice. That choice is not one of the available choices.']}
======================================================================
ERROR: maasserver.websockets.handlers.tests.test_pod.TestPodHandler.test_create
----------------------------------------------------------------------
Traceback (most recent call last):
testtools.testresult.real._StringException: Empty attachments:
  Twisted logs

Traceback (most recent call last):
  File "/run/build/maas/src/maastesting/runtest.py", line 138, in _run_user
    result = function(*args, **kwargs)
  File "/home/ubuntu/.buildout/eggs/testtools-2.2.0-py3.6.egg/testtools/testcase.py", line 719, in _run_test_method
    return self._get_test_method()()
  File "/usr/lib/python3/dist-packages/crochet/_eventloop.py", line 461, in wrapper
    return eventual_result.wait(timeout)
  File "/usr/lib/python3/dist-packages/crochet/_eventloop.py", line 231, in wait
    result.raiseException()
  File "/usr/lib/python3/dist-packages/twisted/python/failure.py", line 385, in raiseException
    raise self.value.with_traceback(self.tb)
maasserver.websockets.base.HandlerValidationError: {'zone': ['Select a valid choice. That choice is not one of the available choices.']}

review: Needs Fixing
~newell-jensen/maas:ui-pod-zones updated
a86c104... by Newell Jensen

Fix broken tests.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/api/tests/test_pods.py b/src/maasserver/api/tests/test_pods.py
2index 87a61d3..ada7a53 100644
3--- a/src/maasserver/api/tests/test_pods.py
4+++ b/src/maasserver/api/tests/test_pods.py
5@@ -38,11 +38,13 @@ class PodMixin:
6 pod_ip_adddress = factory.make_ipv4_address()
7 pod_power_address = 'qemu+ssh://user@%s/system' % pod_ip_adddress
8 pod_password = factory.make_name('password')
9+ zone = factory.make_Zone()
10 return {
11 'type': pod_type,
12 'power_address': pod_power_address,
13 'power_pass': pod_password,
14 'ip_address': pod_ip_adddress,
15+ 'zone': zone.id
16 }
17
18 def fake_pod_discovery(self):
19@@ -234,6 +236,7 @@ class TestPodAPI(APITestCase.ForUser, PodMixin):
20 'name': new_name,
21 'power_address': pod_info['power_address'],
22 'power_pass': pod_info['power_pass'],
23+ 'zone': pod_info['zone'],
24 })
25 self.assertEqual(
26 http.client.OK, response.status_code, response.content)
27diff --git a/src/maasserver/forms/pods.py b/src/maasserver/forms/pods.py
28index c523e85..4ad1695 100644
29--- a/src/maasserver/forms/pods.py
30+++ b/src/maasserver/forms/pods.py
31@@ -113,6 +113,9 @@ class PodForm(MAASModelForm):
32 if not self.is_new:
33 if self.instance.power_type != '':
34 self.initial['type'] = self.instance.power_type
35+ self.fields['zone'] = ModelChoiceField(
36+ required=False, queryset=Zone.objects.all())
37+ self.initial['zone'] = Zone.objects.get_default_zone()
38
39 def _clean_fields(self):
40 """Override to dynamically add fields based on the value of `type`
41@@ -145,7 +148,7 @@ class PodForm(MAASModelForm):
42
43 def save(self, *args, **kwargs):
44 """Persist the pod into the database."""
45- def check_for_duplicate(power_type, power_parameters):
46+ def check_for_duplicate(zone, power_type, power_parameters):
47 # When the Pod is new try to get a BMC of the same type and
48 # parameters to convert the BMC to a new Pod. When the Pod is not
49 # new the form will use the already existing pod instance to update
50@@ -153,6 +156,7 @@ class PodForm(MAASModelForm):
51 # a validation erorr will be raised from the model level.
52 if self.is_new:
53 bmc = BMC.objects.filter(
54+ zone=zone,
55 power_type=power_type,
56 power_parameters=power_parameters).first()
57 if bmc is not None:
58@@ -174,10 +178,12 @@ class PodForm(MAASModelForm):
59 if existing_obj is not None:
60 self.instance = existing_obj
61 self.instance = super(PodForm, self).save(commit=False)
62+ self.instance.zone = zone
63 self.instance.power_type = power_type
64 self.instance.power_parameters = power_parameters
65 return self.instance
66
67+ zone = self.cleaned_data['zone']
68 power_type = self.cleaned_data['type']
69 # Set power_parameters to the generated param_fields.
70 power_parameters = {
71@@ -190,13 +196,14 @@ class PodForm(MAASModelForm):
72 # Running in twisted reactor, do the work inside the reactor.
73 d = deferToDatabase(
74 transactional(check_for_duplicate),
75- power_type, power_parameters)
76+ zone, power_type, power_parameters)
77 d.addCallback(update_obj)
78 d.addCallback(lambda _: self.discover_and_sync_pod())
79 return d
80 else:
81 # Perform the actions inside the executing thread.
82- existing_obj = check_for_duplicate(power_type, power_parameters)
83+ existing_obj = check_for_duplicate(
84+ zone, power_type, power_parameters)
85 if existing_obj is not None:
86 self.instance = existing_obj
87 self.instance = update_obj(self.instance)
88diff --git a/src/maasserver/forms/tests/test_pods.py b/src/maasserver/forms/tests/test_pods.py
89index 9c3b871..f20f2a7 100644
90--- a/src/maasserver/forms/tests/test_pods.py
91+++ b/src/maasserver/forms/tests/test_pods.py
92@@ -131,12 +131,15 @@ class TestPodForm(MAASTransactionServerTestCase):
93 [
94 'name',
95 'type',
96+ 'zone'
97 ], list(form.fields))
98
99 def test_creates_pod_with_discovered_information(self):
100 discovered_pod, discovered_racks, failed_racks = (
101 self.fake_pod_discovery())
102+ zone = factory.make_Zone()
103 pod_info = self.make_pod_info()
104+ pod_info['zone'] = zone.id
105 request = MagicMock()
106 request.user = factory.make_User()
107 form = PodForm(data=pod_info, request=request)
108@@ -148,6 +151,7 @@ class TestPodForm(MAASTransactionServerTestCase):
109 cores=Equals(discovered_pod.cores),
110 memory=Equals(discovered_pod.memory),
111 cpu_speed=Equals(discovered_pod.cpu_speed),
112+ zone=Equals(zone),
113 power_type=Equals(pod_info['type']),
114 power_parameters=Equals({
115 'power_address': pod_info['power_address'],
116@@ -171,7 +175,9 @@ class TestPodForm(MAASTransactionServerTestCase):
117 def test_creates_pod_with_name(self):
118 discovered_pod, discovered_racks, failed_racks = (
119 self.fake_pod_discovery())
120+ zone = factory.make_Zone()
121 pod_info = self.make_pod_info()
122+ pod_info['zone'] = zone.id
123 request = MagicMock()
124 request.user = factory.make_User()
125 pod_name = factory.make_name('pod')
126@@ -185,6 +191,7 @@ class TestPodForm(MAASTransactionServerTestCase):
127 cores=Equals(discovered_pod.cores),
128 memory=Equals(discovered_pod.memory),
129 cpu_speed=Equals(discovered_pod.cpu_speed),
130+ zone=Equals(zone),
131 power_type=Equals(pod_info['type']),
132 power_parameters=Equals({
133 'power_address': pod_info['power_address'],
134@@ -200,7 +207,9 @@ class TestPodForm(MAASTransactionServerTestCase):
135 self.fake_pod_discovery)
136 pods_module.discover_pod.return_value = succeed(
137 pods_module.discover_pod.return_value)
138+ zone = yield deferToDatabase(factory.make_Zone)
139 pod_info = self.make_pod_info()
140+ pod_info['zone'] = zone.id
141 request = MagicMock()
142 request.user = yield deferToDatabase(factory.make_User)
143 form = yield deferToDatabase(PodForm, data=pod_info, request=request)
144@@ -213,6 +222,7 @@ class TestPodForm(MAASTransactionServerTestCase):
145 cores=Equals(discovered_pod.cores),
146 memory=Equals(discovered_pod.memory),
147 cpu_speed=Equals(discovered_pod.cpu_speed),
148+ zone=Equals(zone),
149 power_type=Equals(pod_info['type']),
150 power_parameters=Equals({
151 'power_address': pod_info['power_address'],
152@@ -244,7 +254,9 @@ class TestPodForm(MAASTransactionServerTestCase):
153 self.fake_pod_discovery)
154 pods_module.discover_pod.return_value = succeed(
155 pods_module.discover_pod.return_value)
156+ zone = yield deferToDatabase(factory.make_Zone)
157 pod_info = self.make_pod_info()
158+ pod_info['zone'] = zone.id
159 pod_name = factory.make_name('pod')
160 pod_info['name'] = pod_name
161 request = MagicMock()
162@@ -259,6 +271,7 @@ class TestPodForm(MAASTransactionServerTestCase):
163 cores=Equals(discovered_pod.cores),
164 memory=Equals(discovered_pod.memory),
165 cpu_speed=Equals(discovered_pod.cpu_speed),
166+ zone=Equals(zone),
167 power_type=Equals(pod_info['type']),
168 power_parameters=Equals({
169 'power_address': pod_info['power_address'],
170@@ -273,7 +286,9 @@ class TestPodForm(MAASTransactionServerTestCase):
171 discovered_pod, discovered_racks, failed_racks = yield deferToDatabase(
172 self.fake_pod_discovery)
173 pods_module.discover_pod.return_value = fail(factory.make_exception())
174+ zone = yield deferToDatabase(factory.make_Zone)
175 pod_info = self.make_pod_info()
176+ pod_info['zone'] = zone.id
177 request = MagicMock()
178 request.user = yield deferToDatabase(factory.make_User)
179 form = yield deferToDatabase(PodForm, data=pod_info, request=request)
180@@ -289,7 +304,9 @@ class TestPodForm(MAASTransactionServerTestCase):
181
182 def test_prevents_duplicate_pod(self):
183 discovered_pod, _, _ = self.fake_pod_discovery()
184+ zone = factory.make_Zone()
185 pod_info = self.make_pod_info()
186+ pod_info['zone'] = zone.id
187 request = MagicMock()
188 request.user = factory.make_User()
189 form = PodForm(data=pod_info, request=request)
190@@ -301,9 +318,12 @@ class TestPodForm(MAASTransactionServerTestCase):
191
192 def test_takes_over_bmc_with_pod(self):
193 discovered_pod, _, _ = self.fake_pod_discovery()
194+ zone = factory.make_Zone()
195 pod_info = self.make_pod_info()
196+ pod_info['zone'] = zone.id
197 bmc = factory.make_BMC(
198- power_type=pod_info['type'], power_parameters={
199+ zone=zone, power_type=pod_info['type'],
200+ power_parameters={
201 'power_address': pod_info['power_address'],
202 'power_pass': pod_info['power_pass'],
203 })
204@@ -318,7 +338,9 @@ class TestPodForm(MAASTransactionServerTestCase):
205 def test_updates_existing_pod(self):
206 discovered_pod, discovered_racks, failed_racks = (
207 self.fake_pod_discovery())
208+ zone = factory.make_Zone()
209 pod_info = self.make_pod_info()
210+ pod_info['zone'] = zone.id
211 orig_pod = factory.make_Pod(pod_type=pod_info['type'])
212 new_name = factory.make_name("pod")
213 pod_info['name'] = new_name
214@@ -335,6 +357,7 @@ class TestPodForm(MAASTransactionServerTestCase):
215 cores=Equals(discovered_pod.cores),
216 memory=Equals(discovered_pod.memory),
217 cpu_speed=Equals(discovered_pod.cpu_speed),
218+ zone=Equals(zone),
219 power_type=Equals(pod_info['type']),
220 power_parameters=Equals({
221 'power_address': pod_info['power_address'],
222@@ -362,7 +385,9 @@ class TestPodForm(MAASTransactionServerTestCase):
223 self.fake_pod_discovery)
224 pods_module.discover_pod.return_value = succeed(
225 pods_module.discover_pod.return_value)
226+ zone = yield deferToDatabase(factory.make_Zone)
227 pod_info = self.make_pod_info()
228+ pod_info['zone'] = zone.id
229 orig_pod = yield deferToDatabase(
230 factory.make_Pod, pod_type=pod_info['type'])
231 new_name = factory.make_name("pod")
232@@ -382,6 +407,7 @@ class TestPodForm(MAASTransactionServerTestCase):
233 cores=Equals(discovered_pod.cores),
234 memory=Equals(discovered_pod.memory),
235 cpu_speed=Equals(discovered_pod.cpu_speed),
236+ zone=Equals(zone),
237 power_type=Equals(pod_info['type']),
238 power_parameters=Equals({
239 'power_address': pod_info['power_address'],
240@@ -409,8 +435,9 @@ class TestPodForm(MAASTransactionServerTestCase):
241 def test_discover_and_sync_existing_pod(self):
242 discovered_pod, discovered_racks, failed_racks = (
243 self.fake_pod_discovery())
244+ zone = factory.make_Zone()
245 pod_info = self.make_pod_info()
246- orig_pod = factory.make_Pod(pod_type=pod_info['type'])
247+ orig_pod = factory.make_Pod(zone=zone, pod_type=pod_info['type'])
248 request = MagicMock()
249 request.user = factory.make_User()
250 form = PodForm(data=pod_info, request=request, instance=orig_pod)
251@@ -423,6 +450,7 @@ class TestPodForm(MAASTransactionServerTestCase):
252 cores=Equals(discovered_pod.cores),
253 memory=Equals(discovered_pod.memory),
254 cpu_speed=Equals(discovered_pod.cpu_speed),
255+ zone=Equals(zone),
256 power_type=Equals(pod_info['type']),
257 power_parameters=Equals({}),
258 ip_address=Is(None),
259@@ -447,9 +475,10 @@ class TestPodForm(MAASTransactionServerTestCase):
260 self.fake_pod_discovery)
261 pods_module.discover_pod.return_value = succeed(
262 pods_module.discover_pod.return_value)
263+ zone = yield deferToDatabase(factory.make_Zone)
264 pod_info = self.make_pod_info()
265 orig_pod = yield deferToDatabase(
266- factory.make_Pod, pod_type=pod_info['type'])
267+ factory.make_Pod, zone=zone, pod_type=pod_info['type'])
268 request = MagicMock()
269 request.user = yield deferToDatabase(factory.make_User)
270 form = yield deferToDatabase(
271@@ -463,6 +492,7 @@ class TestPodForm(MAASTransactionServerTestCase):
272 cores=Equals(discovered_pod.cores),
273 memory=Equals(discovered_pod.memory),
274 cpu_speed=Equals(discovered_pod.cpu_speed),
275+ zone=Equals(zone),
276 power_type=Equals(pod_info['type']),
277 power_parameters=Equals({}),
278 ip_address=Is(None),
279@@ -486,7 +516,10 @@ class TestPodForm(MAASTransactionServerTestCase):
280
281 def test_raises_unable_to_discover_because_no_racks(self):
282 self.patch(pods_module, "discover_pod").return_value = ({}, {})
283- form = PodForm(data=self.make_pod_info())
284+ zone = factory.make_Zone()
285+ pod_info = self.make_pod_info()
286+ pod_info['zone'] = zone.id
287+ form = PodForm(data=pod_info)
288 self.assertTrue(form.is_valid(), form._errors)
289 error = self.assertRaises(PodProblem, form.save)
290 self.assertEquals(
291@@ -498,7 +531,10 @@ class TestPodForm(MAASTransactionServerTestCase):
292 def test_raises_unable_to_discover_because_no_racks_in_twisted(self):
293 self.patch(pods_module, "discover_pod").return_value = succeed(
294 ({}, {}))
295- form = yield deferToDatabase(PodForm, data=self.make_pod_info())
296+ zone = yield deferToDatabase(factory.make_Zone)
297+ pod_info = self.make_pod_info()
298+ pod_info['zone'] = zone.id
299+ form = yield deferToDatabase(PodForm, data=pod_info)
300 is_valid = yield deferToDatabase(form.is_valid)
301 self.assertTrue(is_valid, form._errors)
302
303@@ -519,7 +555,10 @@ class TestPodForm(MAASTransactionServerTestCase):
304 self.patch(pods_module, "discover_pod").return_value = ({}, {
305 failed_rack.system_id: exc,
306 })
307- form = PodForm(data=self.make_pod_info())
308+ zone = factory.make_Zone()
309+ pod_info = self.make_pod_info()
310+ pod_info['zone'] = zone.id
311+ form = PodForm(data=pod_info)
312 self.assertTrue(form.is_valid(), form._errors)
313 error = self.assertRaises(PodProblem, form.save)
314 self.assertEquals(str(exc), str(error))
315@@ -532,7 +571,10 @@ class TestPodForm(MAASTransactionServerTestCase):
316 self.patch(pods_module, "discover_pod").return_value = succeed(({}, {
317 failed_rack.system_id: exc,
318 }))
319- form = yield deferToDatabase(PodForm, data=self.make_pod_info())
320+ zone = yield deferToDatabase(factory.make_Zone)
321+ pod_info = self.make_pod_info()
322+ pod_info['zone'] = zone.id
323+ form = yield deferToDatabase(PodForm, data=pod_info)
324 is_valid = yield deferToDatabase(form.is_valid)
325 self.assertTrue(is_valid, form._errors)
326
327diff --git a/src/maasserver/static/js/angular/controllers/pod_details.js b/src/maasserver/static/js/angular/controllers/pod_details.js
328index cd47586..27d620a 100644
329--- a/src/maasserver/static/js/angular/controllers/pod_details.js
330+++ b/src/maasserver/static/js/angular/controllers/pod_details.js
331@@ -1,7 +1,7 @@
332-/* Copyright 2017 Canonical Ltd. This software is licensed under the
333+/* Copyright 2017-2018 Canonical Ltd. This software is licensed under the
334 * GNU Affero General Public License version 3 (see the file LICENSE).
335 *
336- * MAAS Node Details Controller
337+ * MAAS Pod Details Controller
338 */
339
340 angular.module('MAAS').controller('PodDetailsController', [
341@@ -54,19 +54,47 @@ angular.module('MAAS').controller('PodDetailsController', [
342 }]
343 }
344 };
345- $scope.powerTypes = GeneralManager.getData("power_types");
346+ $scope.power_types = GeneralManager.getData("power_types");
347 $scope.domains = DomainsManager.getItems();
348 $scope.zones = ZonesManager.getItems();
349 $scope.section = {
350 area: 'summary'
351 };
352 $scope.machinesSearch = 'pod-id:=invalid';
353+ $scope.editing = false;
354
355 // Return true if the authenticated user is super user.
356 $scope.isSuperUser = function() {
357 return UsersManager.isSuperUser();
358 };
359
360+ // Return true if at least a rack controller is connected to the
361+ // region controller.
362+ $scope.isRackControllerConnected = function() {
363+ // If power_types exist then a rack controller is connected.
364+ return $scope.power_types.length > 0;
365+ };
366+
367+ // Return true when the edit buttons can be clicked.
368+ $scope.canEdit = function() {
369+ return (
370+ $scope.isRackControllerConnected() &&
371+ $scope.isSuperUser());
372+ };
373+
374+ // Called to edit the pod configuration.
375+ $scope.editPodConfiguration = function() {
376+ if(!$scope.canEdit()) {
377+ return;
378+ }
379+ $scope.editing = true;
380+ };
381+
382+ // Called when the cancel or save button is pressed.
383+ $scope.exitEditPodConfiguration = function() {
384+ $scope.editing = false;
385+ };
386+
387 // Return true if there is an action error.
388 $scope.isActionError = function() {
389 return $scope.action.error !== null;
390@@ -104,10 +132,10 @@ angular.module('MAAS').controller('PodDetailsController', [
391 // Return the title of the pod type.
392 $scope.getPodTypeTitle = function() {
393 var i;
394- for(i = 0; i < $scope.powerTypes.length; i++) {
395- var powerType = $scope.powerTypes[i];
396- if(powerType.name === $scope.pod.type) {
397- return powerType.description;
398+ for(i = 0; i < $scope.power_types.length; i++) {
399+ var power_type = $scope.power_types[i];
400+ if(power_type.name === $scope.pod.type) {
401+ return power_type.description;
402 }
403 }
404 return $scope.pod.type;
405diff --git a/src/maasserver/static/js/angular/controllers/pods_list.js b/src/maasserver/static/js/angular/controllers/pods_list.js
406index 7de9734..c47adf7 100644
407--- a/src/maasserver/static/js/angular/controllers/pods_list.js
408+++ b/src/maasserver/static/js/angular/controllers/pods_list.js
409@@ -1,4 +1,4 @@
410-/* Copyright 2017 Canonical Ltd. This software is licensed under the
411+/* Copyright 2017-2018 Canonical Ltd. This software is licensed under the
412 * GNU Affero General Public License version 3 (see the file LICENSE).
413 *
414 * MAAS Pods List Controller
415@@ -6,9 +6,10 @@
416
417 angular.module('MAAS').controller('PodsListController', [
418 '$scope', '$rootScope',
419- 'PodsManager', 'UsersManager', 'GeneralManager', 'ManagerHelperService',
420- function($scope, $rootScope,
421- PodsManager, UsersManager, GeneralManager, ManagerHelperService) {
422+ 'PodsManager', 'UsersManager', 'GeneralManager', 'ZonesManager',
423+ 'ManagerHelperService', function(
424+ $scope, $rootScope, PodsManager, UsersManager, GeneralManager,
425+ ZonesManager, ManagerHelperService) {
426
427 // Set title and page.
428 $rootScope.title = "Pods";
429@@ -50,6 +51,7 @@ angular.module('MAAS').controller('PodsListController', [
430 obj: {}
431 };
432 $scope.powerTypes = GeneralManager.getData("power_types");
433+ $scope.zones = ZonesManager.getItems();
434
435 // Called to update `allViewableChecked`.
436 function updateAllViewableChecked() {
437@@ -223,7 +225,7 @@ angular.module('MAAS').controller('PodsListController', [
438
439 // Load the required managers for this controller.
440 ManagerHelperService.loadManagers($scope, [
441- PodsManager, UsersManager, GeneralManager]).then(
442+ PodsManager, UsersManager, GeneralManager, ZonesManager]).then(
443 function() {
444 $scope.loading = false;
445 });
446diff --git a/src/maasserver/static/js/angular/controllers/tests/test_pod_details.js b/src/maasserver/static/js/angular/controllers/tests/test_pod_details.js
447index e050b8f..269f6ed 100644
448--- a/src/maasserver/static/js/angular/controllers/tests/test_pod_details.js
449+++ b/src/maasserver/static/js/angular/controllers/tests/test_pod_details.js
450@@ -1,4 +1,4 @@
451-/* Copyright 2017 Canonical Ltd. This software is licensed under the
452+/* Copyright 2017-2018 Canonical Ltd. This software is licensed under the
453 * GNU Affero General Public License version 3 (see the file LICENSE).
454 *
455 * Unit tests for NodesListController.
456@@ -151,13 +151,14 @@ describe("PodDetailsController", function() {
457 }]
458 }
459 });
460- expect($scope.powerTypes).toBe(GeneralManager.getData('power_types'));
461+ expect($scope.power_types).toBe(GeneralManager.getData('power_types'));
462 expect($scope.domains).toBe(DomainsManager.getItems());
463 expect($scope.zones).toBe(ZonesManager.getItems());
464+ expect($scope.editing).toBe(false);
465 });
466
467- it("calls loadManagers with PodsManager, UsersManager, GeneralManager",
468- function() {
469+ it("calls loadManagers with PodsManager, UsersManager, GeneralManager, \
470+ DomainsManager, ZonesManager", function() {
471 var controller = makeController();
472 expect(ManagerHelperService.loadManagers).toHaveBeenCalledWith(
473 $scope,
474@@ -192,6 +193,78 @@ describe("PodDetailsController", function() {
475 });
476 });
477
478+ describe("isRackControllerConnected", function() {
479+ it("returns false no power_types", function() {
480+ var controller = makeController();
481+ $scope.power_types = [];
482+ expect($scope.isRackControllerConnected()).toBe(false);
483+ });
484+
485+ it("returns true if power_types", function() {
486+ var controller = makeController();
487+ $scope.power_types = [{}];
488+ expect($scope.isRackControllerConnected()).toBe(true);
489+ });
490+ });
491+
492+ describe("canEdit", function() {
493+ it("returns false if not super user", function() {
494+ var controller = makeController();
495+ spyOn($scope, "isSuperUser").and.returnValue(false);
496+ spyOn(
497+ $scope,
498+ "isRackControllerConnected").and.returnValue(true);
499+ expect($scope.canEdit()).toBe(false);
500+ });
501+
502+ it("returns false if rack disconnected", function() {
503+ var controller = makeController();
504+ spyOn(
505+ $scope,
506+ "isRackControllerConnected").and.returnValue(false);
507+ expect($scope.canEdit()).toBe(false);
508+ });
509+
510+ it("returns true if super user, rack connected", function() {
511+ var controller = makeController();
512+ spyOn($scope, "isSuperUser").and.returnValue(true);
513+ spyOn(
514+ $scope,
515+ "isRackControllerConnected").and.returnValue(true);
516+ expect($scope.canEdit()).toBe(true);
517+ });
518+ });
519+
520+ describe("editPodConfiguration", function() {
521+ it("sets editing to true if can edit",
522+ function() {
523+ var controller = makeController();
524+ spyOn($scope, "canEdit").and.returnValue(true);
525+ $scope.editing = false;
526+ $scope.editPodConfiguration();
527+ expect($scope.editing).toBe(true);
528+ });
529+
530+ it("doesnt set editing to true if cannot",
531+ function() {
532+ var controller = makeController();
533+ spyOn($scope, "canEdit").and.returnValue(false);
534+ $scope.editing = false;
535+ $scope.editPodConfiguration();
536+ expect($scope.editing).toBe(false);
537+ });
538+ });
539+
540+ describe("exitEditPodConfiguration", function() {
541+ it("sets editing to false on exiting pod configuration",
542+ function() {
543+ var controller = makeController();
544+ $scope.editing = true;
545+ $scope.exitEditPodConfiguration();
546+ expect($scope.editing).toBe(false);
547+ });
548+ });
549+
550 describe("isActionError", function() {
551
552 it("returns false if not action error", function() {
553diff --git a/src/maasserver/static/js/angular/controllers/tests/test_pods_list.js b/src/maasserver/static/js/angular/controllers/tests/test_pods_list.js
554index 213b731..a474945 100644
555--- a/src/maasserver/static/js/angular/controllers/tests/test_pods_list.js
556+++ b/src/maasserver/static/js/angular/controllers/tests/test_pods_list.js
557@@ -1,4 +1,4 @@
558-/* Copyright 2017 Canonical Ltd. This software is licensed under the
559+/* Copyright 2017-2018 Canonical Ltd. This software is licensed under the
560 * GNU Affero General Public License version 3 (see the file LICENSE).
561 *
562 * Unit tests for NodesListController.
563@@ -33,11 +33,13 @@ describe("PodsListController", function() {
564 }));
565
566 // Load the required managers.
567- var PodsManager, UsersManager, GeneralManager, ManagerHelperService;
568+ var PodsManager, UsersManager, GeneralManager;
569+ var ZonesManager, ManagerHelperService;
570 beforeEach(inject(function($injector) {
571 PodsManager = $injector.get("PodsManager");
572 UsersManager = $injector.get("UsersManager");
573 GeneralManager = $injector.get("GeneralManager");
574+ ZonesManager = $injector.get("ZonesManager");
575 ManagerHelperService = $injector.get("ManagerHelperService");
576 }));
577
578@@ -104,13 +106,15 @@ describe("PodsListController", function() {
579 expect($scope.action.option).toBeNull();
580 expect($scope.add.open).toBe(false);
581 expect($scope.powerTypes).toBe(GeneralManager.getData('power_types'));
582+ expect($scope.zones).toBe(ZonesManager.getItems());
583 });
584
585- it("calls loadManagers with PodsManager, UsersManager, GeneralManager",
586- function() {
587+ it("calls loadManagers with PodsManager, UsersManager, \
588+ GeneralManager, ZonesManager", function() {
589 var controller = makeController();
590 expect(ManagerHelperService.loadManagers).toHaveBeenCalledWith(
591- $scope, [PodsManager, UsersManager, GeneralManager]);
592+ $scope, [
593+ PodsManager, UsersManager, GeneralManager, ZonesManager]);
594 });
595
596 it("sets loading to false with loadManagers resolves", function() {
597diff --git a/src/maasserver/static/partials/pod-details.html b/src/maasserver/static/partials/pod-details.html
598index 5185ca6..eb0347a 100644
599--- a/src/maasserver/static/partials/pod-details.html
600+++ b/src/maasserver/static/partials/pod-details.html
601@@ -144,7 +144,7 @@
602 <a href="" role="tab" class="p-tabs__link" data-ng-class="{ 'is-active': section.area === 'summary'}" data-ng-click="section.area = 'summary'">{$ pod.composed_machines_count $} composed machines</a>
603 </li>
604 <li class="p-tabs__item" role="presentation">
605- <a href="" role="tab" class="p-tabs__link" data-ng-if="isSuperUser()" data-ng-class="{ 'is-active': section.area === 'power'}" data-ng-click="section.area= 'power'">Power</a>
606+ <a href="" role="tab" class="p-tabs__link" data-ng-if="isSuperUser()" data-ng-class="{ 'is-active': section.area === 'configuration'}" data-ng-click="section.area= 'configuration'">Configuration</a>
607 </li>
608 </nav>
609 </nav>
610@@ -201,22 +201,43 @@
611 </div>
612 </div>
613 </section>
614- <section class="p-strip" data-ng-if="pod && section.area === 'power'">
615- <div class="row">
616+ <section class="p-strip" data-ng-if="pod && section.area === 'configuration'">
617+ <div class="row">
618+ <div class="col-10">
619+ <h2>Pod configuration</h2>
620+ </div>
621+ <div class="col-2">
622+ <button class="p-button--neutral u-float--right"
623+ data-ng-if="canEdit()"
624+ data-ng-click="editPodConfiguration()">Edit</button>
625+ </div>
626+ </div>
627+ <div class="row">
628 <maas-obj-form obj="pod" manager="podManager" manager-method="updateItem"
629- table-form="true" save-on-blur="true">
630+ table-form="true" save-on-blur="false" data-ng-disabled="!editing">
631 <div class="col-6">
632 <div class="col-6">
633 <dl>
634 <dt>Type</dt>
635 <dd>{$ getPodTypeTitle() $}</dd>
636 </dl>
637+ <maas-obj-field type="options" key="zone" label="Zone" subtle="false" placeholder="Choose a zone"
638+ options="zone.id as zone.name for zone in zones"></maas-obj-field>
639 </div>
640 </div>
641 <div class="col-6">
642 <maas-pod-parameters hide-type="true"></maas-pod-parameters>
643 <maas-obj-errors></maas-obj-errors>
644 </div>
645+ <div class="row">
646+ <div class="col-12 u-align--right" data-ng-if="editing">
647+ <p maas-obj-hide-saving><maas-obj-errors></maas-obj-errors></p>
648+ <p maas-obj-show-saving><maas-obj-saving>Trying to connect and discover pod</maas-obj-saving></p>
649+ <button class="p-button--base"
650+ data-ng-click="exitEditPodConfiguration()">Cancel</button>
651+ <button class="p-button--positive" data-ng-click="exitEditPodConfiguration()" maas-obj-save>Save changes</button>
652+ </div>
653+ </div>
654 </maas-obj-form>
655 </div>
656 </section>
657diff --git a/src/maasserver/static/partials/pods-list.html b/src/maasserver/static/partials/pods-list.html
658index bd966ee..99b3f39 100644
659--- a/src/maasserver/static/partials/pods-list.html
660+++ b/src/maasserver/static/partials/pods-list.html
661@@ -59,6 +59,8 @@
662 <div class="row">
663 <div class="col-5">
664 <maas-obj-field type="text" key="name" label="Name" label-width="two" input-width="three" placeholder="Name (optional)"></maas-obj-field>
665+ <maas-obj-field type="options" key="zone" label="Zone" subtle="false" placeholder="Choose a zone"
666+ options="zone.id as zone.name for zone in zones"></maas-obj-field>
667 </div>
668 <div class="col-6 prefix-1">
669 <maas-pod-parameters></maas-pod-parameters>
670diff --git a/src/maasserver/websockets/handlers/tests/test_pod.py b/src/maasserver/websockets/handlers/tests/test_pod.py
671index 76eb0a2..9653bea 100644
672--- a/src/maasserver/websockets/handlers/tests/test_pod.py
673+++ b/src/maasserver/websockets/handlers/tests/test_pod.py
674@@ -222,7 +222,9 @@ class TestPodHandler(MAASTransactionServerTestCase):
675 def test_create(self):
676 user = yield deferToDatabase(factory.make_admin)
677 handler = PodHandler(user, {})
678+ zone = yield deferToDatabase(factory.make_Zone)
679 pod_info = self.make_pod_info()
680+ pod_info['zone'] = zone.id
681 yield deferToDatabase(self.fake_pod_discovery)
682 created_pod = yield handler.create(pod_info)
683 self.assertIsNotNone(created_pod['id'])
684@@ -232,7 +234,9 @@ class TestPodHandler(MAASTransactionServerTestCase):
685 def test_update(self):
686 user = yield deferToDatabase(factory.make_admin)
687 handler = PodHandler(user, {})
688+ zone = yield deferToDatabase(factory.make_Zone)
689 pod_info = self.make_pod_info()
690+ pod_info['zone'] = zone.id
691 pod = yield deferToDatabase(
692 factory.make_Pod, pod_type=pod_info['type'])
693 pod_info['id'] = pod.id

Subscribers

People subscribed via source and target branches