Merge lp:~blake-rouse/maas/create-bcache-ui into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: no longer in the source branch.
Merged at revision: 4393
Proposed branch: lp:~blake-rouse/maas/create-bcache-ui
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 2577 lines (+1829/-210)
9 files modified
src/maasserver/models/blockdevice.py (+26/-14)
src/maasserver/static/js/angular/controllers/node_details_storage.js (+400/-20)
src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js (+3/-3)
src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js (+803/-5)
src/maasserver/static/js/angular/factories/nodes.js (+37/-0)
src/maasserver/static/js/angular/factories/tests/test_nodes.js (+86/-0)
src/maasserver/static/partials/node-details.html (+153/-162)
src/maasserver/websockets/handlers/node.py (+105/-1)
src/maasserver/websockets/handlers/tests/test_node.py (+216/-5)
To merge this branch: bzr merge lp:~blake-rouse/maas/create-bcache-ui
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Approve
Review via email: mp+275090@code.launchpad.net

Commit message

Add the ability to create and delete cache sets. Add the ability to create a bcache device by selecting an available disk, its cache set, and its caching mode. Include cache sets in the JSON result over the websocket. Render the cache sets in their own section under filesystems.

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

lgtm!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/models/blockdevice.py'
2--- src/maasserver/models/blockdevice.py 2015-10-19 17:56:35 +0000
3+++ src/maasserver/models/blockdevice.py 2015-10-21 17:51:26 +0000
4@@ -232,24 +232,12 @@
5 @property
6 def used_size(self):
7 """Return the used size on the block device."""
8- filesystem = self.get_effective_filesystem()
9- if filesystem is not None:
10- return self.size
11- partitiontable = self.get_partitiontable()
12- if partitiontable is not None:
13- return partitiontable.get_used_size()
14- return 0
15+ return self.get_used_size()
16
17 @property
18 def available_size(self):
19 """Return the available size on the block device."""
20- filesystem = self.get_effective_filesystem()
21- if filesystem is not None:
22- return 0
23- partitiontable = self.get_partitiontable()
24- if partitiontable is not None:
25- return partitiontable.get_available_size()
26- return self.size
27+ return self.get_available_size()
28
29 @property
30 def used_for(self):
31@@ -261,6 +249,30 @@
32 size=human_readable_bytes(self.size),
33 node=self.node)
34
35+ def get_block_size(self):
36+ """Return the block size for the block device."""
37+ return self.block_size
38+
39+ def get_used_size(self):
40+ """Return the used size on the block device."""
41+ filesystem = self.get_effective_filesystem()
42+ if filesystem is not None:
43+ return self.size
44+ partitiontable = self.get_partitiontable()
45+ if partitiontable is not None:
46+ return partitiontable.get_used_size()
47+ return 0
48+
49+ def get_available_size(self):
50+ """Return the available size on the block device."""
51+ filesystem = self.get_effective_filesystem()
52+ if filesystem is not None:
53+ return 0
54+ partitiontable = self.get_partitiontable()
55+ if partitiontable is not None:
56+ return partitiontable.get_available_size()
57+ return self.size
58+
59 def delete(self):
60 """Delete the block device.
61
62
63=== modified file 'src/maasserver/static/js/angular/controllers/node_details_storage.js'
64--- src/maasserver/static/js/angular/controllers/node_details_storage.js 2015-10-19 20:12:24 +0000
65+++ src/maasserver/static/js/angular/controllers/node_details_storage.js 2015-10-21 17:51:26 +0000
66@@ -4,6 +4,44 @@
67 * MAAS Node Storage Controller
68 */
69
70+
71+// Filter that is specific to the NodeStorageController. Remove the available
72+// disks from the list if being used in the availableNew.
73+angular.module('MAAS').filter('removeAvailableByNew', function() {
74+ return function(disks, availableNew) {
75+ if(!angular.isObject(availableNew) || (
76+ !angular.isObject(availableNew.device) &&
77+ !angular.isArray(availableNew.devices))) {
78+ return disks;
79+ }
80+
81+ var filtered = [];
82+ var single = true;
83+ if(angular.isArray(availableNew.devices)) {
84+ single = false;
85+ }
86+ angular.forEach(disks, function(disk) {
87+ if(single) {
88+ if(disk !== availableNew.device) {
89+ filtered.push(disk);
90+ }
91+ } else {
92+ var i, found = false;
93+ for(i = 0; i < availableNew.devices.length; i++) {
94+ if(disk === availableNew.devices[i]) {
95+ found = true;
96+ break;
97+ }
98+ }
99+ if(!found) {
100+ filtered.push(disk);
101+ }
102+ }
103+ });
104+ return filtered;
105+ };
106+});
107+
108 angular.module('MAAS').controller('NodeStorageController', [
109 '$scope', 'NodesManager', 'ConverterService',
110 function($scope, NodesManager, ConverterService) {
111@@ -19,7 +57,8 @@
112 UNFORMAT: "unformat",
113 DELETE: "delete",
114 FORMAT_AND_MOUNT: "format-mount",
115- PARTITION: "partition"
116+ PARTITION: "partition",
117+ BCACHE: "bcache"
118 };
119
120 $scope.editing = false;
121@@ -30,10 +69,15 @@
122 $scope.filesystemsMap = {};
123 $scope.filesystemMode = SELECTION_MODE.NONE;
124 $scope.filesystemAllSelected = false;
125+ $scope.cachesets = [];
126+ $scope.cachesetsMap = {};
127+ $scope.cachesetsMode = SELECTION_MODE.NONE;
128+ $scope.cachesetsAllSelected = false;
129 $scope.available = [];
130 $scope.availableMap = {};
131 $scope.availableMode = SELECTION_MODE.NONE;
132 $scope.availableAllSelected = false;
133+ $scope.availableNew = {};
134 $scope.used = [];
135
136 // Give $parent which is the NodeDetailsController access to this scope
137@@ -62,7 +106,9 @@
138
139 // Return True if the item is in use.
140 function isInUse(item) {
141- if(angular.isObject(item.filesystem)) {
142+ if(item.type === "cache-set") {
143+ return true;
144+ } else if(angular.isObject(item.filesystem)) {
145 if(item.filesystem.is_format_fstype &&
146 angular.isString(item.filesystem.mount_point) &&
147 item.filesystem.mount_point !== "") {
148@@ -84,6 +130,19 @@
149 return tags;
150 }
151
152+ // Return a unique key that will never change.
153+ function getUniqueKey(disk) {
154+ if(disk.type === "cache-set") {
155+ return "cache-set-" + disk.cache_set_id;
156+ } else {
157+ var key = disk.type + "-" + disk.block_id;
158+ if(angular.isNumber(disk.partition_id)) {
159+ key += "-" + disk.partition_id;
160+ }
161+ return key;
162+ }
163+ }
164+
165 // Update the list of filesystems. Only filesystems with a mount point
166 // set go here. If no mount point is set, it goes in available.
167 function updateFilesystems() {
168@@ -92,6 +151,7 @@
169 angular.forEach($scope.node.disks, function(disk) {
170 if(hasMountedFilesystem(disk)) {
171 filesystems.push({
172+ "type": "filesystem",
173 "name": disk.name,
174 "size_human": disk.size_human,
175 "fstype": disk.filesystem.fstype,
176@@ -103,6 +163,7 @@
177 angular.forEach(disk.partitions, function(partition) {
178 if(hasMountedFilesystem(partition)) {
179 filesystems.push({
180+ "type": "filesystem",
181 "name": partition.name,
182 "size_human": partition.size_human,
183 "fstype": partition.filesystem.fstype,
184@@ -117,7 +178,8 @@
185 // Update the selected filesystems with the currently selected
186 // filesystems.
187 angular.forEach(filesystems, function(filesystem) {
188- var oldFilesystem = $scope.filesystemsMap[filesystem.name];
189+ var key = getUniqueKey(filesystem);
190+ var oldFilesystem = $scope.filesystemsMap[key];
191 if(angular.isObject(oldFilesystem)) {
192 filesystem.$selected = oldFilesystem.$selected;
193 } else {
194@@ -129,13 +191,52 @@
195 $scope.filesystems = filesystems;
196 $scope.filesystemsMap = {};
197 angular.forEach(filesystems, function(filesystem) {
198- $scope.filesystemsMap[filesystem.name] = filesystem;
199+ $scope.filesystemsMap[getUniqueKey(filesystem)] = filesystem;
200 });
201
202 // Update the selection mode.
203 $scope.updateFilesystemSelection(false);
204 }
205
206+ // Update the list of cache sets.
207+ function updateCacheSets() {
208+ // Create the new list of cache sets.
209+ var cachesets = [];
210+ angular.forEach($scope.node.disks, function(disk) {
211+ if(disk.type === "cache-set") {
212+ cachesets.push({
213+ "type": "cache-set",
214+ "name": disk.name,
215+ "size_human": disk.size_human,
216+ "cache_set_id": disk.id,
217+ "used_by": disk.used_for
218+ });
219+ }
220+ });
221+
222+ // Update the selected cache sets with the currently selected
223+ // cache sets.
224+ angular.forEach(cachesets, function(cacheset) {
225+ var key = getUniqueKey(cacheset);
226+ var oldCacheSet = $scope.cachesetsMap[key];
227+ if(angular.isObject(oldCacheSet)) {
228+ cacheset.$selected = oldCacheSet.$selected;
229+ } else {
230+ cacheset.$selected = false;
231+ }
232+ });
233+
234+ // Update the cachesets and cachesetsMap on the scope.
235+ $scope.cachesets = cachesets;
236+ $scope.cachesetsMap = {};
237+ angular.forEach(cachesets, function(cacheset) {
238+ $scope.cachesetsMap[getUniqueKey(cacheset)] = cacheset;
239+ });
240+
241+ // Update the selection mode.
242+ $scope.updateCacheSetsSelection(false);
243+ }
244+
245 // Update list of all available disks.
246 function updateAvailable() {
247 var available = [];
248@@ -192,7 +293,8 @@
249 // available disks. Also copy the $options so they are not lost
250 // for the current action.
251 angular.forEach(available, function(disk) {
252- var oldDisk = $scope.availableMap[disk.name];
253+ var key = getUniqueKey(disk);
254+ var oldDisk = $scope.availableMap[key];
255 if(angular.isObject(oldDisk)) {
256 disk.$selected = oldDisk.$selected;
257 disk.$options = oldDisk.$options;
258@@ -206,9 +308,31 @@
259 $scope.available = available;
260 $scope.availableMap = {};
261 angular.forEach(available, function(disk) {
262- $scope.availableMap[disk.name] = disk;
263+ $scope.availableMap[getUniqueKey(disk)] = disk;
264 });
265
266+ // Update device or devices on the availableNew object to be
267+ // there new objects.
268+ if(angular.isObject($scope.availableNew)) {
269+ // Update device.
270+ if(angular.isObject($scope.availableNew.device)) {
271+ var key = getUniqueKey($scope.availableNew.device);
272+ $scope.availableNew.device = $scope.availableMap[key];
273+ // Update devices.
274+ } else if(angular.isArray($scope.availableNew.devices)) {
275+ var newDevices = [];
276+ angular.forEach(
277+ $scope.availableNew.devices, function(device) {
278+ var key = getUniqueKey(device);
279+ var newDevice = $scope.availableMap[key];
280+ if(angular.isObject(newDevice)) {
281+ newDevices.push(newDevice);
282+ }
283+ });
284+ $scope.availableNew.devices = newDevices;
285+ }
286+ }
287+
288 // Update the selection mode.
289 $scope.updateAvailableSelection(false);
290 }
291@@ -217,18 +341,22 @@
292 function updateUsed() {
293 var used = [];
294 angular.forEach($scope.node.disks, function(disk) {
295- if(isInUse(disk)) {
296- used.push({
297+ if(isInUse(disk) && disk.type !== "cache-set") {
298+ var data = {
299 "name": disk.name,
300 "type": disk.type,
301 "model": disk.model,
302 "serial": disk.serial,
303 "tags": getTags(disk),
304 "used_for": disk.used_for
305- });
306+ };
307+ if(disk.type === "virtual") {
308+ data.parent_type = disk.parent.type;
309+ }
310+ used.push(data);
311 }
312 angular.forEach(disk.partitions, function(partition) {
313- if(isInUse(partition)) {
314+ if(isInUse(partition) && partition.type !== "cache-set") {
315 used.push({
316 "name": partition.name,
317 "type": "partition",
318@@ -248,6 +376,7 @@
319 if(angular.isArray($scope.node.disks)) {
320 $scope.has_disks = $scope.node.disks.length > 0;
321 updateFilesystems();
322+ updateCacheSets();
323 updateAvailable();
324 updateUsed();
325 } else {
326@@ -256,10 +385,15 @@
327 $scope.filesystemsMap = {};
328 $scope.filesystemMode = SELECTION_MODE.NONE;
329 $scope.filesystemAllSelected = false;
330+ $scope.cachesets = [];
331+ $scope.cachesetsMap = {};
332+ $scope.cachesetsMode = SELECTION_MODE.NONE;
333+ $scope.cachesetsAllSelected = false;
334 $scope.available = [];
335 $scope.availableMap = {};
336 $scope.availableMode = SELECTION_MODE.NONE;
337 $scope.availableAllSelected = false;
338+ $scope.availableNew = {};
339 $scope.used = [];
340 }
341 }
342@@ -282,18 +416,33 @@
343 return pattern.test(string);
344 }
345
346- // Convert the string to a byte.
347- function convertToBytes(string, units) {
348- var value = parseFloat(string);
349- if(units === "mb") {
350- return value * 1000 * 1000;
351- } else if(units === "gb") {
352- return value * 1000 * 1000 * 1000;
353- } else if(units === "tb") {
354- return value * 1000 * 1000 * 1000 * 1000;
355+ // Extract the index from the bcache name.
356+ function getBcacheIndexFromName(name) {
357+ var pattern = /^bcache([0-9]+)$/;
358+ var match = pattern.exec(name);
359+ if(angular.isArray(match) && match.length === 2) {
360+ return parseInt(match[1], 10);
361 }
362 }
363
364+ // Get the next bcache device name.
365+ function getNextBcacheName() {
366+ var idx = -1;
367+ angular.forEach($scope.node.disks, function(disk) {
368+ var bcacheIdx = getBcacheIndexFromName(disk.name);
369+ if(angular.isNumber(bcacheIdx)) {
370+ idx = Math.max(idx, bcacheIdx);
371+ }
372+ angular.forEach(disk.partitions, function(partition) {
373+ bcacheIdx = getBcacheIndexFromName(partition.name);
374+ if(angular.isNumber(bcacheIdx)) {
375+ idx = Math.max(idx, bcacheIdx);
376+ }
377+ });
378+ });
379+ return "bcache" + (idx + 1);
380+ }
381+
382 // Called by $parent when the node has been loaded.
383 $scope.nodeLoaded = function() {
384 $scope.$watch("node.disks", updateDisks);
385@@ -413,6 +562,10 @@
386
387 // Return the device type for the disk.
388 $scope.getDeviceType = function(disk) {
389+ if(angular.isUndefined(disk)) {
390+ return "";
391+ }
392+
393 if(disk.type === "virtual") {
394 if(disk.parent_type === "lvm-vg") {
395 return "Logical Volume";
396@@ -551,6 +704,7 @@
397 // Cancel the current available operation.
398 $scope.availableCancel = function() {
399 $scope.updateAvailableSelection(true);
400+ $scope.availableNew = {};
401 };
402
403 // Enter unformat mode.
404@@ -653,7 +807,7 @@
405 // Return true if the disk can be deleted.
406 $scope.canDelete = function(disk) {
407 if(!disk.has_partitions && (
408- !angular.isString(disk.fstype) || disk.fstype === "")) {
409+ !angular.isString(disk.fstype) || disk.fstype === "")) {
410 return true;
411 } else {
412 return false;
413@@ -777,6 +931,7 @@
414 }
415 };
416
417+ // Confirm the partition creation.
418 $scope.availableConfirmPartition = function(disk) {
419 // Do nothing if not valid.
420 if($scope.isAddPartitionSizeInvalid(disk)) {
421@@ -820,6 +975,231 @@
422 $scope.updateAvailableSelection(true);
423 };
424
425+ // Return array of selected cache sets.
426+ $scope.getSelectedCacheSets = function() {
427+ var cachesets = [];
428+ angular.forEach($scope.cachesets, function(cacheset) {
429+ if(cacheset.$selected) {
430+ cachesets.push(cacheset);
431+ }
432+ });
433+ return cachesets;
434+ };
435+
436+ // Update the currect mode for the cache sets section and the all
437+ // selected value.
438+ $scope.updateCacheSetsSelection = function(force) {
439+ if(angular.isUndefined(force)) {
440+ force = false;
441+ }
442+ var cachesets = $scope.getSelectedCacheSets();
443+ if(cachesets.length === 0) {
444+ $scope.cachesetsMode = SELECTION_MODE.NONE;
445+ } else if(cachesets.length === 1 && force) {
446+ $scope.cachesetsMode = SELECTION_MODE.SINGLE;
447+ } else if(force) {
448+ $scope.cachesetsMode = SELECTION_MODE.MUTLI;
449+ }
450+
451+ if($scope.cachesets.length === 0) {
452+ $scope.cachesetsAllSelected = false;
453+ } else if(cachesets.length === $scope.cachesets.length) {
454+ $scope.cachesetsAllSelected = true;
455+ } else {
456+ $scope.cachesetsAllSelected = false;
457+ }
458+ };
459+
460+ // Toggle the selection of the filesystem.
461+ $scope.toggleCacheSetSelect = function(cacheset) {
462+ cacheset.$selected = !cacheset.$selected;
463+ $scope.updateCacheSetsSelection(true);
464+ };
465+
466+ // Toggle the selection of all filesystems.
467+ $scope.toggleCacheSetAllSelect = function() {
468+ angular.forEach($scope.cachesets, function(cacheset) {
469+ if($scope.cachesetsAllSelected) {
470+ cacheset.$selected = false;
471+ } else {
472+ cacheset.$selected = true;
473+ }
474+ });
475+ $scope.updateCacheSetsSelection(true);
476+ };
477+
478+ // Return true if checkboxes in the cache sets section should be
479+ // disabled.
480+ $scope.isCacheSetsDisabled = function() {
481+ return (
482+ $scope.cachesetsMode !== SELECTION_MODE.NONE &&
483+ $scope.cachesetsMode !== SELECTION_MODE.SINGLE &&
484+ $scope.cachesetsMode !== SELECTION_MODE.MUTLI);
485+ };
486+
487+ // Cancel the current cache set operation.
488+ $scope.cacheSetCancel = function() {
489+ $scope.updateCacheSetsSelection(true);
490+ };
491+
492+ // Can delete the cache set.
493+ $scope.canDeleteCacheSet = function(cacheset) {
494+ return cacheset.used_by === "";
495+ };
496+
497+ // Enter delete mode.
498+ $scope.cacheSetDelete = function() {
499+ $scope.cachesetsMode = SELECTION_MODE.DELETE;
500+ };
501+
502+ // Quickly enter delete by selecting the cache set first.
503+ $scope.quickCacheSetDelete = function(cacheset) {
504+ deselectAll($scope.cachesets);
505+ cacheset.$selected = true;
506+ $scope.updateCacheSetsSelection(true);
507+ $scope.cacheSetDelete();
508+ };
509+
510+ // Confirm the delete action for cache set.
511+ $scope.cacheSetConfirmDelete = function(cacheset) {
512+ NodesManager.deleteCacheSet(
513+ $scope.node, cacheset.cache_set_id);
514+
515+ var idx = $scope.cachesets.indexOf(cacheset);
516+ $scope.cachesets.splice(idx, 1);
517+ $scope.updateCacheSetsSelection();
518+ };
519+
520+ // Return true if a cache set can be created.
521+ $scope.canCreateCacheSet = function() {
522+ if($scope.isAvailableDisabled()) {
523+ return false;
524+ }
525+
526+ var selected = $scope.getSelectedAvailable();
527+ if(selected.length === 1) {
528+ return !$scope.hasUnmountedFilesystem(selected[0]);
529+ }
530+ return false;
531+ };
532+
533+ // Called to create a cache set.
534+ $scope.createCacheSet = function() {
535+ if(!$scope.canCreateCacheSet()) {
536+ return;
537+ }
538+
539+ // Create cache set.
540+ var disk = $scope.getSelectedAvailable()[0];
541+ NodesManager.createCacheSet(
542+ $scope.node, disk.block_id, disk.partition_id);
543+
544+ // Remove from available.
545+ var idx = $scope.available.indexOf(disk);
546+ $scope.available.splice(idx, 1);
547+ };
548+
549+ // Return true if a bcache can be created.
550+ $scope.canCreateBcache = function() {
551+ if($scope.isAvailableDisabled()) {
552+ return false;
553+ }
554+
555+ var selected = $scope.getSelectedAvailable();
556+ if(selected.length === 1) {
557+ var allowed = !$scope.hasUnmountedFilesystem(selected[0]);
558+ return allowed && $scope.cachesets.length > 0;
559+ }
560+ return false;
561+ };
562+
563+ // Enter bcache mode.
564+ $scope.createBcache = function() {
565+ if(!$scope.canCreateBcache()) {
566+ return;
567+ }
568+ $scope.availableMode = SELECTION_MODE.BCACHE;
569+ $scope.availableNew = {
570+ name: getNextBcacheName(),
571+ device: $scope.getSelectedAvailable()[0],
572+ cacheset: $scope.cachesets[0],
573+ cacheMode: "writeback",
574+ fstype: null,
575+ mountPoint: ""
576+ };
577+ };
578+
579+ // Return true when the name of the new disk is invalid.
580+ $scope.isNewDiskNameInvalid = function() {
581+ if(!angular.isObject($scope.node) ||
582+ !angular.isArray($scope.node.disks)) {
583+ return true;
584+ }
585+
586+ if($scope.availableNew.name === "") {
587+ return true;
588+ } else {
589+ var i, j;
590+ for(i = 0; i < $scope.node.disks.length; i++) {
591+ var disk = $scope.node.disks[i];
592+ if($scope.availableNew.name === disk.name) {
593+ return true;
594+ }
595+ if(angular.isArray(disk.partitions)) {
596+ for(j = 0; j < disk.partitions.length; j++) {
597+ var partition = disk.partitions[j];
598+ if($scope.availableNew.name === partition.name) {
599+ return true;
600+ }
601+ }
602+ }
603+ }
604+ }
605+ return false;
606+ };
607+
608+ // Return true if bcache can be saved.
609+ $scope.createBcacheCanSave = function() {
610+ return (
611+ !$scope.isNewDiskNameInvalid() &&
612+ !$scope.isMountPointInvalid($scope.availableNew.mountPoint));
613+ };
614+
615+ // Confirm and create the bcache device.
616+ $scope.availableConfirmCreateBcache = function() {
617+ if(!$scope.createBcacheCanSave()) {
618+ return;
619+ }
620+
621+ // Create the bcache.
622+ var params = {
623+ name: $scope.availableNew.name,
624+ cache_set: $scope.availableNew.cacheset.cache_set_id,
625+ cache_mode: $scope.availableNew.cacheMode
626+ };
627+ if($scope.availableNew.device.type === "partition") {
628+ params.partition_id = $scope.availableNew.device.partition_id;
629+ } else {
630+ params.block_id = $scope.availableNew.device.block_id;
631+ }
632+ if(angular.isString($scope.availableNew.fstype) &&
633+ $scope.availableNew.fstype !== "") {
634+ params.fstype = $scope.availableNew.fstype;
635+ if($scope.availableNew.mountPoint !== "") {
636+ params.mount_point = $scope.availableNew.mountPoint;
637+ }
638+ }
639+ NodesManager.createBcache($scope.node, params);
640+
641+ // Remove device from available.
642+ var idx = $scope.available.indexOf($scope.availableNew.device);
643+ $scope.available.splice(idx, 1);
644+ $scope.availableNew = {};
645+
646+ // Update the selection.
647+ $scope.updateAvailableSelection(true);
648+ };
649+
650 // Called to enter tag editing mode
651 $scope.editTags = function() {
652 if($scope.$parent.canEdit() && !$scope.editing) {
653
654=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js'
655--- src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js 2015-10-19 20:12:24 +0000
656+++ src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js 2015-10-21 17:51:26 +0000
657@@ -2687,7 +2687,7 @@
658 type: "alias"
659 };
660 var nic2 = {
661- id: makeInteger(0, 100),
662+ id: makeInteger(101, 200),
663 link_id: makeInteger(0, 100),
664 type: "alias"
665 };
666@@ -2711,7 +2711,7 @@
667 vlan: {}
668 };
669 var nic2 = {
670- id: makeInteger(0, 100),
671+ id: makeInteger(101, 200),
672 link_id: makeInteger(0, 100),
673 type: "physical",
674 vlan: {}
675@@ -2737,7 +2737,7 @@
676 vlan: vlan
677 };
678 var nic2 = {
679- id: makeInteger(0, 100),
680+ id: makeInteger(101, 200),
681 link_id: makeInteger(0, 100),
682 type: "physical",
683 vlan: vlan
684
685=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js'
686--- src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js 2015-10-19 20:12:24 +0000
687+++ src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js 2015-10-21 17:51:26 +0000
688@@ -4,6 +4,79 @@
689 * Unit tests for NodeStorageController.
690 */
691
692+describe("removeAvailableByNew", function() {
693+
694+ // Load the MAAS module.
695+ beforeEach(module("MAAS"));
696+
697+ // Load the removeAvailableByNew.
698+ var removeAvailableByNew;
699+ beforeEach(inject(function($filter) {
700+ removeAvailableByNew = $filter("removeAvailableByNew");
701+ }));
702+
703+ it("returns disks if undefined availableNew", function() {
704+ var i, disk, disks = [];
705+ for(i = 0; i < 3; i++) {
706+ disk = {
707+ id: i
708+ };
709+ disks.push(disk);
710+ }
711+ expect(removeAvailableByNew(disks)).toBe(disks);
712+ });
713+
714+ it("returns disks if undefined device(s) in availableNew", function() {
715+ var i, disk, disks = [];
716+ for(i = 0; i < 3; i++) {
717+ disk = {
718+ id: i
719+ };
720+ disks.push(disk);
721+ }
722+ var availableNew = {};
723+ expect(removeAvailableByNew(disks, availableNew)).toBe(disks);
724+ });
725+
726+ it("removes availableNew.device from disks", function() {
727+ var i, disk, disks = [];
728+ for(i = 0; i < 3; i++) {
729+ disk = {
730+ id: i
731+ };
732+ disks.push(disk);
733+ }
734+
735+ var availableNew = {
736+ device: disks[0]
737+ };
738+ var expectedDisks = angular.copy(disks);
739+ expectedDisks.splice(0, 1);
740+
741+ expect(removeAvailableByNew(disks, availableNew)).toEqual(
742+ expectedDisks);
743+ });
744+
745+ it("removes availableNew.devices from disks", function() {
746+ var i, disk, disks = [];
747+ for(i = 0; i < 6; i++) {
748+ disk = {
749+ id: i
750+ };
751+ disks.push(disk);
752+ }
753+
754+ var availableNew = {
755+ devices: [disks[0], disks[1]]
756+ };
757+ var expectedDisks = angular.copy(disks);
758+ expectedDisks.splice(0, 2);
759+
760+ expect(removeAvailableByNew(disks, availableNew)).toEqual(
761+ expectedDisks);
762+ });
763+});
764+
765 describe("NodeStorageController", function() {
766 // Load the MAAS module.
767 beforeEach(module("MAAS"));
768@@ -154,6 +227,25 @@
769 used_for: "ext4 formatted filesystem mounted at /mnt."
770 }
771 ]
772+ },
773+ {
774+ // Disk that is a cache set.
775+ id: 4,
776+ name: "cache0",
777+ model: "",
778+ serial: "",
779+ tags: [],
780+ type: "cache-set",
781+ size: Math.pow(1024, 4),
782+ size_human: "1024 GB",
783+ available_size: 0,
784+ available_size_human: "0 GB",
785+ used_size: Math.pow(1024, 4),
786+ used_size_human: "1024 GB",
787+ partition_table_type: null,
788+ used_for: "",
789+ filesystem: null,
790+ partitions: null
791 }
792 ];
793 }
794@@ -171,6 +263,10 @@
795 expect($scope.availableMap).toEqual({});
796 expect($scope.availableMode).toBeNull();
797 expect($scope.availableAllSelected).toBe(false);
798+ expect($scope.cachesets).toEqual([]);
799+ expect($scope.cachesetsMap).toEqual({});
800+ expect($scope.cachesetsMode).toBeNull();
801+ expect($scope.cachesetsAllSelected).toBe(false);
802 expect($scope.used).toEqual([]);
803 });
804
805@@ -195,6 +291,7 @@
806
807 var filesystems = [
808 {
809+ type: "filesystem",
810 name: disks[2].name,
811 size_human: disks[2].size_human,
812 fstype: disks[2].filesystem.fstype,
813@@ -204,6 +301,7 @@
814 $selected: false
815 },
816 {
817+ type: "filesystem",
818 name: disks[3].partitions[1].name,
819 size_human: disks[3].partitions[1].size_human,
820 fstype: disks[3].partitions[1].filesystem.fstype,
821@@ -213,6 +311,16 @@
822 $selected: false
823 }
824 ];
825+ var cachesets = [
826+ {
827+ type: "cache-set",
828+ name: disks[4].name,
829+ size_human: disks[4].size_human,
830+ cache_set_id: disks[4].id,
831+ used_by: disks[4].used_for,
832+ $selected: false
833+ }
834+ ];
835 var available = [
836 {
837 name: disks[0].name,
838@@ -297,6 +405,7 @@
839 $rootScope.$digest();
840 expect($scope.has_disks).toEqual(true);
841 expect($scope.filesystems).toEqual(filesystems);
842+ expect($scope.cachesets).toEqual(cachesets);
843 expect($scope.available).toEqual(available);
844 expect($scope.used).toEqual(used);
845 });
846@@ -306,14 +415,17 @@
847 var disks = makeDisks();
848 node.disks = disks;
849
850- // Load the filesystems, available, and used once.
851+ // Load the filesystems, cachesets, available, and used once.
852 $scope.nodeLoaded();
853 $rootScope.$digest();
854
855- // Set all filesystems and available to selected.
856+ // Set all filesystems, cachesets, and available to selected.
857 angular.forEach($scope.filesystems, function(filesystem) {
858 filesystem.$selected = true;
859 });
860+ angular.forEach($scope.cachesets, function(cacheset) {
861+ cacheset.$selected = true;
862+ });
863 angular.forEach($scope.available, function(disk) {
864 disk.$selected = true;
865 });
866@@ -324,18 +436,21 @@
867 options.push(disk.$options);
868 });
869
870- // Force the disks to change so the filesystems, available, and used
871- // are reloaded.
872+ // Force the disks to change so the filesystems, cachesets, available,
873+ // and used are reloaded.
874 var firstFilesystem = $scope.filesystems[0];
875 node.disks = angular.copy(node.disks);
876 $rootScope.$digest();
877 expect($scope.filesystems[0]).not.toBe(firstFilesystem);
878 expect($scope.filesystems[0]).toEqual(firstFilesystem);
879
880- // All filesystems and available should be selected.
881+ // All filesystems, cachesets and available should be selected.
882 angular.forEach($scope.filesystems, function(filesystem) {
883 expect(filesystem.$selected).toBe(true);
884 });
885+ angular.forEach($scope.cachesets, function(cacheset) {
886+ expect(cacheset.$selected).toBe(true);
887+ });
888 angular.forEach($scope.available, function(disk) {
889 expect(disk.$selected).toBe(true);
890 });
891@@ -346,6 +461,51 @@
892 });
893 });
894
895+ it("availableNew.device object is updated", function() {
896+ var controller = makeController();
897+ var disks = makeDisks();
898+ node.disks = disks;
899+
900+ // Load the filesystems, cachesets, available, and used once.
901+ $scope.nodeLoaded();
902+ $rootScope.$digest();
903+
904+ // Set availableNew.device to a disk from available.
905+ var disk = $scope.available[0];
906+ $scope.availableNew.device = disk;
907+
908+ // Force the update. The device should be the same value but
909+ // a new object.
910+ node.disks = angular.copy(node.disks);
911+ $rootScope.$digest();
912+ expect($scope.availableNew.device).toEqual(disk);
913+ expect($scope.availableNew.device).not.toBe(disk);
914+ });
915+
916+ it("availableNew.devices array is updated", function() {
917+ var controller = makeController();
918+ var disks = makeDisks();
919+ node.disks = disks;
920+
921+ // Load the filesystems, cachesets, available, and used once.
922+ $scope.nodeLoaded();
923+ $rootScope.$digest();
924+
925+ // Set availableNew.device to a disk from available.
926+ var disk0 = $scope.available[0];
927+ var disk1 = $scope.available[1];
928+ $scope.availableNew.devices = [disk0, disk1];
929+
930+ // Force the update. The devices should be the same values but
931+ // a new objects.
932+ node.disks = angular.copy(node.disks);
933+ $rootScope.$digest();
934+ expect($scope.availableNew.devices[0]).toEqual(disk0);
935+ expect($scope.availableNew.devices[0]).not.toBe(disk0);
936+ expect($scope.availableNew.devices[1]).toEqual(disk1);
937+ expect($scope.availableNew.devices[1]).not.toBe(disk1);
938+ });
939+
940 describe("getSelectedFilesystems", function() {
941
942 it("returns selected filesystems", function() {
943@@ -1834,6 +1994,644 @@
944 });
945 });
946
947+ describe("getSelectedCacheSets", function() {
948+
949+ it("returns selected cachesets", function() {
950+ var controller = makeController();
951+ var cachesets = [
952+ { $selected: true },
953+ { $selected: true },
954+ { $selected: false },
955+ { $selected: false }
956+ ];
957+ $scope.cachesets = cachesets;
958+ expect($scope.getSelectedCacheSets()).toEqual(
959+ [cachesets[0], cachesets[1]]);
960+ });
961+ });
962+
963+ describe("updateCacheSetsSelection", function() {
964+
965+ it("sets cachesetsMode to NONE when none selected", function() {
966+ var controller = makeController();
967+ spyOn($scope, "getSelectedCacheSets").and.returnValue([]);
968+ $scope.cachesetsMode = "other";
969+
970+ $scope.updateCacheSetsSelection();
971+
972+ expect($scope.cachesetsMode).toBeNull();
973+ });
974+
975+ it("doesn't sets cachesetsMode to SINGLE when not force", function() {
976+ var controller = makeController();
977+ spyOn($scope, "getSelectedCacheSets").and.returnValue([{}]);
978+ $scope.cachesetsMode = "other";
979+
980+ $scope.updateCacheSetsSelection();
981+
982+ expect($scope.cachesetsMode).toBe("other");
983+ });
984+
985+ it("sets cachesetsMode to SINGLE when force", function() {
986+ var controller = makeController();
987+ spyOn($scope, "getSelectedCacheSets").and.returnValue([{}]);
988+ $scope.cachesetsMode = "other";
989+
990+ $scope.updateCacheSetsSelection(true);
991+
992+ expect($scope.cachesetsMode).toBe("single");
993+ });
994+
995+ it("doesn't sets cachesetsMode to MUTLI when not force", function() {
996+ var controller = makeController();
997+ spyOn($scope, "getSelectedCacheSets").and.returnValue([{}, {}]);
998+ $scope.cachesetsMode = "other";
999+
1000+ $scope.updateCacheSetsSelection();
1001+
1002+ expect($scope.cachesetsMode).toBe("other");
1003+ });
1004+
1005+ it("sets cachesetsMode to MULTI when force", function() {
1006+ var controller = makeController();
1007+ spyOn($scope, "getSelectedCacheSets").and.returnValue([{}, {}]);
1008+ $scope.cachesetsMode = "other";
1009+
1010+ $scope.updateCacheSetsSelection(true);
1011+
1012+ expect($scope.cachesetsMode).toBe("multi");
1013+ });
1014+
1015+ it("sets cachesetsAllSelected to false when none selected",
1016+ function() {
1017+ var controller = makeController();
1018+ spyOn($scope, "getSelectedCacheSets").and.returnValue([]);
1019+ $scope.cachesetsAllSelected = true;
1020+
1021+ $scope.updateCacheSetsSelection();
1022+
1023+ expect($scope.cachesetsAllSelected).toBe(false);
1024+ });
1025+
1026+ it("sets cachesetsAllSelected to false when not all selected",
1027+ function() {
1028+ var controller = makeController();
1029+ $scope.cachesets = [{}, {}];
1030+ spyOn($scope, "getSelectedCacheSets").and.returnValue([{}]);
1031+ $scope.cachesetsAllSelected = true;
1032+
1033+ $scope.updateCacheSetsSelection();
1034+
1035+ expect($scope.cachesetsAllSelected).toBe(false);
1036+ });
1037+
1038+ it("sets cachesetsAllSelected to true when all selected",
1039+ function() {
1040+ var controller = makeController();
1041+ $scope.cachesets = [{}, {}];
1042+ spyOn($scope, "getSelectedCacheSets").and.returnValue(
1043+ [{}, {}]);
1044+ $scope.cachesetsAllSelected = false;
1045+
1046+ $scope.updateCacheSetsSelection();
1047+
1048+ expect($scope.cachesetsAllSelected).toBe(true);
1049+ });
1050+ });
1051+
1052+ describe("toggleCacheSetSelect", function() {
1053+
1054+ it("inverts $selected", function() {
1055+ var controller = makeController();
1056+ var cacheset = { $selected: true };
1057+ spyOn($scope, "updateCacheSetsSelection");
1058+
1059+ $scope.toggleCacheSetSelect(cacheset);
1060+
1061+ expect(cacheset.$selected).toBe(false);
1062+ $scope.toggleCacheSetSelect(cacheset);
1063+ expect(cacheset.$selected).toBe(true);
1064+ expect($scope.updateCacheSetsSelection).toHaveBeenCalledWith(
1065+ true);
1066+ });
1067+ });
1068+
1069+ describe("toggleCacheSetAllSelect", function() {
1070+
1071+ it("sets all to true if not all selected", function() {
1072+ var controller = makeController();
1073+ var cachesets = [{ $selected: true }, { $selected: false }];
1074+ $scope.cachesets = cachesets;
1075+ $scope.cachesetsAllSelected = false;
1076+ spyOn($scope, "updateCacheSetsSelection");
1077+
1078+ $scope.toggleCacheSetAllSelect();
1079+
1080+ expect(cachesets[0].$selected).toBe(true);
1081+ expect(cachesets[1].$selected).toBe(true);
1082+ expect($scope.updateCacheSetsSelection).toHaveBeenCalledWith(
1083+ true);
1084+ });
1085+
1086+ it("sets all to false if all selected", function() {
1087+ var controller = makeController();
1088+ var cachesets = [{ $selected: true }, { $selected: true }];
1089+ $scope.cachesets = cachesets;
1090+ $scope.cachesetsAllSelected = true;
1091+ spyOn($scope, "updateCacheSetsSelection");
1092+
1093+ $scope.toggleCacheSetAllSelect();
1094+
1095+ expect(cachesets[0].$selected).toBe(false);
1096+ expect(cachesets[1].$selected).toBe(false);
1097+ expect($scope.updateCacheSetsSelection).toHaveBeenCalledWith(
1098+ true);
1099+ });
1100+ });
1101+
1102+ describe("isCacheSetsDisabled", function() {
1103+
1104+ it("returns false for NONE", function() {
1105+ var controller = makeController();
1106+ $scope.cachesetsMode = null;
1107+
1108+ expect($scope.isCacheSetsDisabled()).toBe(false);
1109+ });
1110+
1111+ it("returns false for SINGLE", function() {
1112+ var controller = makeController();
1113+ $scope.cachesetsMode = "single";
1114+
1115+ expect($scope.isCacheSetsDisabled()).toBe(false);
1116+ });
1117+
1118+ it("returns false for MULTI", function() {
1119+ var controller = makeController();
1120+ $scope.cachesetsMode = "multi";
1121+
1122+ expect($scope.isCacheSetsDisabled()).toBe(false);
1123+ });
1124+
1125+ it("returns true for DELETE", function() {
1126+ var controller = makeController();
1127+ $scope.cachesetsMode = "delete";
1128+
1129+ expect($scope.isCacheSetsDisabled()).toBe(true);
1130+ });
1131+ });
1132+
1133+ describe("cacheSetCancel", function() {
1134+
1135+ it("calls updateCacheSetsSelection with force true", function() {
1136+ var controller = makeController();
1137+ spyOn($scope, "updateCacheSetsSelection");
1138+
1139+ $scope.cacheSetCancel();
1140+
1141+ expect($scope.updateCacheSetsSelection).toHaveBeenCalledWith(
1142+ true);
1143+ });
1144+ });
1145+
1146+ describe("canDeleteCacheSet", function() {
1147+
1148+ it("returns true when not being used", function() {
1149+ var controller = makeController();
1150+ var cacheset = { used_by: "" };
1151+
1152+ expect($scope.canDeleteCacheSet(cacheset)).toBe(true);
1153+ });
1154+
1155+ it("returns false when being used", function() {
1156+ var controller = makeController();
1157+ var cacheset = { used_by: "bcache0" };
1158+
1159+ expect($scope.canDeleteCacheSet(cacheset)).toBe(false);
1160+ });
1161+ });
1162+
1163+ describe("cacheSetDelete", function() {
1164+
1165+ it("sets cachesetsMode to DELETE", function() {
1166+ var controller = makeController();
1167+ $scope.cachesetsMode = "other";
1168+
1169+ $scope.cacheSetDelete();
1170+
1171+ expect($scope.cachesetsMode).toBe("delete");
1172+ });
1173+ });
1174+
1175+ describe("quickCacheSetDelete", function() {
1176+
1177+ it("selects cacheset and calls cacheSetDelete", function() {
1178+ var controller = makeController();
1179+ var cachesets = [{ $selected: true }, { $selected: false }];
1180+ $scope.cachesets = cachesets;
1181+ spyOn($scope, "updateCacheSetsSelection");
1182+ spyOn($scope, "cacheSetDelete");
1183+
1184+ $scope.quickCacheSetDelete(cachesets[1]);
1185+
1186+ expect(cachesets[0].$selected).toBe(false);
1187+ expect(cachesets[1].$selected).toBe(true);
1188+ expect($scope.updateCacheSetsSelection).toHaveBeenCalledWith(
1189+ true);
1190+ expect($scope.cacheSetDelete).toHaveBeenCalled();
1191+ });
1192+ });
1193+
1194+ describe("cacheSetConfirmDelete", function() {
1195+
1196+ it("calls NodesManager.deleteCacheSet and removes from list",
1197+ function() {
1198+ var controller = makeController();
1199+ var cacheset = {
1200+ cache_set_id: makeInteger(0, 100)
1201+ };
1202+ $scope.cachesets = [cacheset];
1203+ spyOn(NodesManager, "deleteCacheSet");
1204+ spyOn($scope, "updateCacheSetsSelection");
1205+
1206+ $scope.cacheSetConfirmDelete(cacheset);
1207+
1208+ expect(NodesManager.deleteCacheSet).toHaveBeenCalledWith(
1209+ node, cacheset.cache_set_id);
1210+ expect($scope.cachesets).toEqual([]);
1211+ expect($scope.updateCacheSetsSelection).toHaveBeenCalledWith();
1212+ });
1213+ });
1214+
1215+ describe("canCreateCacheSet", function() {
1216+
1217+ it("returns false if isAvailableDisabled returns true", function() {
1218+ var controller = makeController();
1219+ spyOn($scope, "isAvailableDisabled").and.returnValue(true);
1220+
1221+ expect($scope.canCreateCacheSet()).toBe(false);
1222+ });
1223+
1224+ it("returns false if two selected", function() {
1225+ var controller = makeController();
1226+ spyOn($scope, "isAvailableDisabled").and.returnValue(false);
1227+ $scope.available = [ { $selected: true }, { $selected: true }];
1228+
1229+ expect($scope.canCreateCacheSet()).toBe(false);
1230+ });
1231+
1232+ it("returns false if selected has fstype", function() {
1233+ var controller = makeController();
1234+ spyOn($scope, "isAvailableDisabled").and.returnValue(false);
1235+ $scope.available = [
1236+ {
1237+ fstype: "ext4",
1238+ $selected: true
1239+ }
1240+ ];
1241+
1242+ expect($scope.canCreateCacheSet()).toBe(false);
1243+ });
1244+
1245+ it("returns true if selected has no fstype", function() {
1246+ var controller = makeController();
1247+ spyOn($scope, "isAvailableDisabled").and.returnValue(false);
1248+ $scope.available = [
1249+ {
1250+ fstype: null,
1251+ $selected: true
1252+ }
1253+ ];
1254+
1255+ expect($scope.canCreateCacheSet()).toBe(true);
1256+ });
1257+ });
1258+
1259+ describe("createCacheSet", function() {
1260+
1261+ it("does nothing if canCreateCacheSet returns false", function() {
1262+ var controller = makeController();
1263+ var disk = {
1264+ block_id: makeInteger(0, 100),
1265+ partition_id: makeInteger(0, 100),
1266+ $selected: true
1267+ };
1268+ $scope.available = [disk];
1269+ spyOn($scope, "canCreateCacheSet").and.returnValue(false);
1270+ spyOn(NodesManager, "createCacheSet");
1271+
1272+ $scope.createCacheSet();
1273+ expect(NodesManager.createCacheSet).not.toHaveBeenCalled();
1274+ });
1275+
1276+ it("calls NodesManager.createCacheSet and removes from available",
1277+ function() {
1278+ var controller = makeController();
1279+ var disk = {
1280+ block_id: makeInteger(0, 100),
1281+ partition_id: makeInteger(0, 100),
1282+ $selected: true
1283+ };
1284+ $scope.available = [disk];
1285+ spyOn($scope, "canCreateCacheSet").and.returnValue(true);
1286+ spyOn(NodesManager, "createCacheSet");
1287+
1288+ $scope.createCacheSet();
1289+ expect(NodesManager.createCacheSet).toHaveBeenCalledWith(
1290+ node, disk.block_id, disk.partition_id);
1291+ expect($scope.available).toEqual([]);
1292+ });
1293+ });
1294+
1295+ describe("canCreateBcache", function() {
1296+
1297+ it("returns false when isAvailableDisabled is true", function() {
1298+ var controller = makeController();
1299+ spyOn($scope, "isAvailableDisabled").and.returnValue(true);
1300+
1301+ expect($scope.canCreateBcache()).toBe(false);
1302+ });
1303+
1304+ it("returns false if two selected", function() {
1305+ var controller = makeController();
1306+ spyOn($scope, "isAvailableDisabled").and.returnValue(false);
1307+ $scope.available = [ { $selected: true }, { $selected: true }];
1308+
1309+ expect($scope.canCreateBcache()).toBe(false);
1310+ });
1311+
1312+ it("returns false if selected has fstype", function() {
1313+ var controller = makeController();
1314+ spyOn($scope, "isAvailableDisabled").and.returnValue(false);
1315+ $scope.available = [
1316+ {
1317+ fstype: "ext4",
1318+ $selected: true
1319+ }
1320+ ];
1321+ $scope.cachesets = [{}];
1322+
1323+ expect($scope.canCreateBcache()).toBe(false);
1324+ });
1325+
1326+ it("returns false if selected has no fstype but not cachesets ",
1327+ function() {
1328+ var controller = makeController();
1329+ spyOn($scope, "isAvailableDisabled").and.returnValue(false);
1330+ $scope.available = [
1331+ {
1332+ fstype: null,
1333+ $selected: true
1334+ }
1335+ ];
1336+ $scope.cachesets = [];
1337+
1338+ expect($scope.canCreateBcache()).toBe(false);
1339+ });
1340+
1341+ it("returns true if selected has no fstype but has cachesets ",
1342+ function() {
1343+ var controller = makeController();
1344+ spyOn($scope, "isAvailableDisabled").and.returnValue(false);
1345+ $scope.available = [
1346+ {
1347+ fstype: null,
1348+ $selected: true
1349+ }
1350+ ];
1351+ $scope.cachesets = [{}];
1352+
1353+ expect($scope.canCreateBcache()).toBe(true);
1354+ });
1355+ });
1356+
1357+ describe("createBcache", function() {
1358+
1359+ it("does nothing if canCreateBcache returns false", function() {
1360+ var controller = makeController();
1361+ $scope.availableMode = "other";
1362+ spyOn($scope, "canCreateBcache").and.returnValue(false);
1363+
1364+ $scope.createBcache();
1365+ expect($scope.availableMode).toBe("other");
1366+ });
1367+
1368+ it("sets availableMode and availableNew", function() {
1369+ var controller = makeController();
1370+ $scope.availableMode = "other";
1371+ spyOn($scope, "canCreateBcache").and.returnValue(true);
1372+
1373+ // Add bcache name to create a name after that index.
1374+ var otherBcache = {
1375+ name: "bcache4"
1376+ };
1377+ node.disks = [otherBcache];
1378+
1379+ // Will be set as the device.
1380+ var disk = {
1381+ $selected: true
1382+ };
1383+ $scope.available = [disk];
1384+
1385+ // Will be set as the cacheset.
1386+ var cacheset = {};
1387+ $scope.cachesets = [cacheset];
1388+
1389+ $scope.createBcache();
1390+ expect($scope.availableMode).toBe("bcache");
1391+ expect($scope.availableNew).toEqual({
1392+ name: "bcache5",
1393+ device: disk,
1394+ cacheset: cacheset,
1395+ cacheMode: "writeback",
1396+ fstype: null,
1397+ mountPoint: ""
1398+ });
1399+ expect($scope.availableNew.device).toBe(disk);
1400+ expect($scope.availableNew.cacheset).toBe(cacheset);
1401+ });
1402+ });
1403+
1404+ describe("isNewDiskNameInvalid", function() {
1405+
1406+ it("returns true if blank name", function() {
1407+ var controller = makeController();
1408+ $scope.node.disks = [];
1409+ $scope.availableNew.name = "";
1410+
1411+ expect($scope.isNewDiskNameInvalid()).toBe(true);
1412+ });
1413+
1414+ it("returns true if name used by disk", function() {
1415+ var controller = makeController();
1416+ var name = makeName("disk");
1417+ $scope.node.disks = [{
1418+ name: name
1419+ }];
1420+ $scope.availableNew.name = name;
1421+
1422+ expect($scope.isNewDiskNameInvalid()).toBe(true);
1423+ });
1424+
1425+ it("returns true if name used by partition", function() {
1426+ var controller = makeController();
1427+ var name = makeName("disk");
1428+ $scope.node.disks = [{
1429+ name: makeName("other"),
1430+ partitions: [
1431+ {
1432+ name: name
1433+ }
1434+ ]
1435+ }];
1436+ $scope.availableNew.name = name;
1437+
1438+ expect($scope.isNewDiskNameInvalid()).toBe(true);
1439+ });
1440+
1441+ it("returns false if the name is not already used", function() {
1442+ var controller = makeController();
1443+ var name = makeName("disk");
1444+ $scope.node.disks = [{
1445+ name: makeName("other"),
1446+ partitions: [
1447+ {
1448+ name: makeName("part")
1449+ }
1450+ ]
1451+ }];
1452+ $scope.availableNew.name = name;
1453+
1454+ expect($scope.isNewDiskNameInvalid()).toBe(false);
1455+ });
1456+ });
1457+
1458+ describe("createBcacheCanSave", function() {
1459+
1460+ it("returns false if isNewDiskNameInvalid returns true", function() {
1461+ var controller = makeController();
1462+ $scope.availableNew.mountPoint = "/";
1463+ spyOn($scope, "isNewDiskNameInvalid").and.returnValue(true);
1464+
1465+ expect($scope.createBcacheCanSave()).toBe(false);
1466+ });
1467+
1468+ it("returns false if isMountPointInvalid returns true", function() {
1469+ var controller = makeController();
1470+ $scope.availableNew.mountPoint = "not/absolute";
1471+ spyOn($scope, "isNewDiskNameInvalid").and.returnValue(false);
1472+
1473+ expect($scope.createBcacheCanSave()).toBe(false);
1474+ });
1475+
1476+ it("returns true if both return false", function() {
1477+ var controller = makeController();
1478+ $scope.availableNew.mountPoint = "/";
1479+ spyOn($scope, "isNewDiskNameInvalid").and.returnValue(false);
1480+
1481+ expect($scope.createBcacheCanSave()).toBe(true);
1482+ });
1483+ });
1484+
1485+ describe("availableConfirmCreateBcache", function() {
1486+
1487+ it("does nothing if createBcacheCanSave returns false", function() {
1488+ var controller = makeController();
1489+ spyOn($scope, "createBcacheCanSave").and.returnValue(false);
1490+ var availableNew = {
1491+ name: makeName("bcache"),
1492+ cacheset: {
1493+ cache_set_id: makeInteger(0, 100)
1494+ },
1495+ cacheMode: "writearound",
1496+ device: {
1497+ type: "partition",
1498+ partition_id: makeInteger(0, 100)
1499+ },
1500+ fstype: null,
1501+ mountPoint: ""
1502+ };
1503+ $scope.availableNew = availableNew;
1504+ spyOn(NodesManager, "createBcache");
1505+
1506+ $scope.availableConfirmCreateBcache();
1507+ expect(NodesManager.createBcache).not.toHaveBeenCalled();
1508+ });
1509+
1510+ it("calls NodesManager.createBcache for partition", function() {
1511+ var controller = makeController();
1512+ spyOn($scope, "createBcacheCanSave").and.returnValue(true);
1513+ var device = {
1514+ type: "partition",
1515+ partition_id: makeInteger(0, 100),
1516+ $selected: true
1517+ };
1518+ var availableNew = {
1519+ name: makeName("bcache"),
1520+ cacheset: {
1521+ cache_set_id: makeInteger(0, 100)
1522+ },
1523+ cacheMode: "writearound",
1524+ device: device,
1525+ fstype: "ext4",
1526+ mountPoint: "/"
1527+ };
1528+ $scope.available = [device];
1529+ $scope.availableNew = availableNew;
1530+ spyOn(NodesManager, "createBcache");
1531+ spyOn($scope, "updateAvailableSelection");
1532+
1533+ $scope.availableConfirmCreateBcache();
1534+ expect(NodesManager.createBcache).toHaveBeenCalledWith(
1535+ node, {
1536+ name: availableNew.name,
1537+ cache_set: availableNew.cacheset.cache_set_id,
1538+ cache_mode: "writearound",
1539+ partition_id: device.partition_id,
1540+ fstype: "ext4",
1541+ mount_point: "/"
1542+ });
1543+ expect($scope.available).toEqual([]);
1544+ expect($scope.updateAvailableSelection).toHaveBeenCalledWith(
1545+ true);
1546+ });
1547+
1548+ it("calls NodesManager.createBcache for block device", function() {
1549+ var controller = makeController();
1550+ spyOn($scope, "createBcacheCanSave").and.returnValue(true);
1551+ var device = {
1552+ type: "physical",
1553+ block_id: makeInteger(0, 100),
1554+ $selected: true
1555+ };
1556+ var availableNew = {
1557+ name: makeName("bcache"),
1558+ cacheset: {
1559+ cache_set_id: makeInteger(0, 100)
1560+ },
1561+ cacheMode: "writearound",
1562+ device: device,
1563+ fstype: null,
1564+ mountPoint: "/"
1565+ };
1566+ $scope.available = [device];
1567+ $scope.availableNew = availableNew;
1568+ spyOn(NodesManager, "createBcache");
1569+ spyOn($scope, "updateAvailableSelection");
1570+
1571+ $scope.availableConfirmCreateBcache();
1572+ expect(NodesManager.createBcache).toHaveBeenCalledWith(
1573+ node, {
1574+ name: availableNew.name,
1575+ cache_set: availableNew.cacheset.cache_set_id,
1576+ cache_mode: "writearound",
1577+ block_id: device.block_id
1578+ });
1579+ expect($scope.available).toEqual([]);
1580+ expect($scope.updateAvailableSelection).toHaveBeenCalledWith(
1581+ true);
1582+ });
1583+ });
1584+
1585 describe("editTags", function() {
1586
1587 it("doesnt sets editing to true if cannot edit", function() {
1588
1589=== modified file 'src/maasserver/static/js/angular/factories/nodes.js'
1590--- src/maasserver/static/js/angular/factories/nodes.js 2015-10-19 17:56:35 +0000
1591+++ src/maasserver/static/js/angular/factories/nodes.js 2015-10-21 17:51:26 +0000
1592@@ -211,6 +211,19 @@
1593 return RegionConnection.callMethod(method, params);
1594 };
1595
1596+ // Delete a cache set.
1597+ NodesManager.prototype.deleteCacheSet = function(
1598+ node, cache_set_id) {
1599+ var self = this;
1600+ var method = this._handler + ".delete_cache_set";
1601+ var params = {
1602+ system_id: node.system_id,
1603+ cache_set_id: cache_set_id
1604+ };
1605+ return RegionConnection.callMethod(method, params);
1606+ };
1607+
1608+ // Create a new partition.
1609 NodesManager.prototype.createPartition = function(
1610 node, block_id, size) {
1611 var self = this;
1612@@ -223,5 +236,29 @@
1613 return RegionConnection.callMethod(method, params);
1614 };
1615
1616+ // Create a new cache set.
1617+ NodesManager.prototype.createCacheSet = function(
1618+ node, block_id, partition_id) {
1619+ var self = this;
1620+ var method = this._handler + ".create_cache_set";
1621+ var params = {
1622+ system_id: node.system_id,
1623+ block_id: block_id,
1624+ partition_id: partition_id
1625+ };
1626+ return RegionConnection.callMethod(method, params);
1627+ };
1628+
1629+ // Create a new bcache device.
1630+ NodesManager.prototype.createBcache = function(
1631+ node, params) {
1632+ if(!angular.isObject(params)) {
1633+ params = {};
1634+ }
1635+ params.system_id = node.system_id;
1636+ return RegionConnection.callMethod(
1637+ "node.create_bcache", params);
1638+ };
1639+
1640 return new NodesManager();
1641 }]);
1642
1643=== modified file 'src/maasserver/static/js/angular/factories/tests/test_nodes.js'
1644--- src/maasserver/static/js/angular/factories/tests/test_nodes.js 2015-10-19 20:12:24 +0000
1645+++ src/maasserver/static/js/angular/factories/tests/test_nodes.js 2015-10-21 17:51:26 +0000
1646@@ -476,6 +476,27 @@
1647 });
1648 });
1649
1650+ describe("deleteCacheSet", function() {
1651+
1652+ it("calls node.delete_cache_set with correct params",
1653+ function(done) {
1654+ var node = makeNode(), cache_set_id = makeInteger(0, 100);
1655+ webSocket.returnData.push(makeFakeResponse("deleted"));
1656+ NodesManager.deleteCacheSet(node, cache_set_id).then(
1657+ function() {
1658+ var sentObject = angular.fromJson(
1659+ webSocket.sentData[0]);
1660+ expect(sentObject.method).toBe(
1661+ "node.delete_cache_set");
1662+ expect(sentObject.params.system_id).toBe(
1663+ node.system_id);
1664+ expect(sentObject.params.cache_set_id).toBe(
1665+ cache_set_id);
1666+ done();
1667+ });
1668+ });
1669+ });
1670+
1671 describe("createPartition", function() {
1672
1673 it("calls node.create_partition with correct params",
1674@@ -499,4 +520,69 @@
1675 });
1676 });
1677 });
1678+
1679+ describe("createCacheSet", function() {
1680+
1681+ it("calls node.create_cache_set", function(done) {
1682+ var fakeNode = makeNode();
1683+ webSocket.returnData.push(makeFakeResponse(null));
1684+ NodesManager.createCacheSet(
1685+ fakeNode, "", "").then(function() {
1686+ var sentObject = angular.fromJson(webSocket.sentData[0]);
1687+ expect(sentObject.method).toBe("node.create_cache_set");
1688+ done();
1689+ });
1690+ });
1691+
1692+ it("calls node.create_cache_set with params", function(done) {
1693+ var fakeNode = makeNode();
1694+ var block_id = makeName("block_id");
1695+ var partition_id = makeName("block_id");
1696+ webSocket.returnData.push(makeFakeResponse(null));
1697+ NodesManager.createCacheSet(
1698+ fakeNode, block_id, partition_id).then(
1699+ function() {
1700+ var sentObject = angular.fromJson(webSocket.sentData[0]);
1701+ expect(sentObject.method).toBe("node.create_cache_set");
1702+ expect(sentObject.params.system_id).toBe(fakeNode.system_id);
1703+ expect(sentObject.params.block_id).toBe(block_id);
1704+ expect(sentObject.params.partition_id).toBe(partition_id);
1705+ done();
1706+ });
1707+ });
1708+ });
1709+
1710+ describe("createBcache", function() {
1711+
1712+ it("calls node.create_bcache", function(done) {
1713+ var fakeNode = makeNode();
1714+ webSocket.returnData.push(makeFakeResponse(null));
1715+ NodesManager.createBcache(
1716+ fakeNode, {}).then(function() {
1717+ var sentObject = angular.fromJson(webSocket.sentData[0]);
1718+ expect(sentObject.method).toBe("node.create_bcache");
1719+ done();
1720+ });
1721+ });
1722+
1723+ it("calls node.create_bcache with params", function(done) {
1724+ var fakeNode = makeNode();
1725+ var params = {
1726+ block_id: makeName("block_id"),
1727+ partition_id: makeName("block_id")
1728+ };
1729+ webSocket.returnData.push(makeFakeResponse(null));
1730+ NodesManager.createBcache(
1731+ fakeNode, params).then(
1732+ function() {
1733+ var sentObject = angular.fromJson(webSocket.sentData[0]);
1734+ expect(sentObject.method).toBe("node.create_bcache");
1735+ expect(sentObject.params.system_id).toBe(fakeNode.system_id);
1736+ expect(sentObject.params.block_id).toBe(params.block_id);
1737+ expect(sentObject.params.partition_id).toBe(
1738+ params.partition_id);
1739+ done();
1740+ });
1741+ });
1742+ });
1743 });
1744
1745=== modified file 'src/maasserver/static/partials/node-details.html'
1746--- src/maasserver/static/partials/node-details.html 2015-10-21 12:36:21 +0000
1747+++ src/maasserver/static/partials/node-details.html 2015-10-21 17:51:26 +0000
1748@@ -106,7 +106,7 @@
1749
1750 <div class="page-header__feedback ng-hide" data-ng-show="hasActionPowerError(actionOption.name)">
1751 <p class="page-header__feedback-message info">
1752- Node cannot be {$ actionOption.sentence $}, because power control software for the
1753+ Node cannot be {$ actionOption.sentence $}, because power control software for the
1754 node is missing from its cluster controller. To proceed, install the
1755 {$ getPowerErrors() $} on the {$ node.nodegroup.cluster_name $} cluster.
1756 </p>
1757@@ -681,6 +681,70 @@
1758 </main>
1759 </section>
1760 </div>
1761+ <div class="twelve-col padding-bottom" data-ng-show="cachesets.length">
1762+ <h3>Available cache sets</h3>
1763+ <section class="table">
1764+ <header class="table__head">
1765+ <div class="table__row">
1766+ <div class="table__header table__column--3">
1767+ <input class="checkbox" type="checkbox" id="cachesets-check-all"
1768+ data-ng-checked="cachesetsAllSelected"
1769+ data-ng-click="toggleCacheSetAllSelect()"
1770+ data-ng-disabled="isCacheSetsDisabled()" />
1771+ <label class="checkbox-label" for="cachesets-check-all"></label>
1772+ </div>
1773+ <div class="table__header table__column--15">Name</div>
1774+ <div class="table__header table__column--27">Size</div>
1775+ <div class="table__header table__column--55">Used by</div>
1776+ </div>
1777+
1778+ </header>
1779+ <main class="table__body">
1780+ <div data-ng-repeat="cacheset in cachesets">
1781+ <div class="table__row"
1782+ data-ng-class="{ active: cacheset.$selected }">
1783+ <div class="table__data table__column--3">
1784+ <input class="checkbox" type="checkbox" id="{$ cacheset.name $}"
1785+ data-ng-checked="cacheset.$selected"
1786+ data-ng-click="toggleCacheSetSelect(cacheset)"
1787+ data-ng-disabled="isCacheSetsDisabled()" />
1788+ <label class="checkbox-label" for="{$ cacheset.name $}" ></label>
1789+ </div>
1790+ <div class="table__data table__column--15">{$ cacheset.name $}</div>
1791+ <div class="table__data table__column--27">{$ cacheset.size_human $}</div>
1792+ <div class="table__data table__column--45">{$ cacheset.used_by $}</div>
1793+ <div class="table__data table__column--10">
1794+ <div class="table__controls">
1795+ <a class="icon delete"
1796+ data-ng-show="canDeleteCacheSet(cacheset)"
1797+ data-ng-click="quickCacheSetDelete(cacheset)">Remove</a>
1798+ </div>
1799+ </div>
1800+ <div class="table__dropdown">
1801+ <div class="table__row table__dropdown-row">
1802+ <div class="ng-hide" data-ng-show="cachesetsMode === 'single' && canDeleteCacheSet(cacheset)">
1803+ <div class="table__data left">
1804+ <a class="link-cta-ubuntu text-button"
1805+ data-ng-show="canDeleteCacheSet(cacheset)"
1806+ data-ng-click="cacheSetDelete()">Remove</a>
1807+ </div>
1808+ </div>
1809+ <div class="ng-hide" data-ng-show="cachesetsMode === 'delete'">
1810+ <div class="table__data left margin-top--five">
1811+ <p><span class="icon warning margin-right--ten"></span> Are you sure you want to delete this cache set?</p>
1812+ </div>
1813+ <div class="table__data right">
1814+ <a class="link-cta-ubuntu text-button" data-ng-click="cacheSetCancel()">Cancel</a>
1815+ <button class="cta-ubuntu" data-ng-click="cacheSetConfirmDelete(cacheset)">Remove</button>
1816+ </div>
1817+ </div>
1818+ </div>
1819+ </div>
1820+ </div>
1821+ </div>
1822+ </main>
1823+ </section>
1824+ </div>
1825 <div class="twelve-col padding-bottom">
1826 <h3>Available disks and partitions</h3>
1827 <section class="table margin-bottom">
1828@@ -712,7 +776,7 @@
1829 No available disks or partitions.
1830 </div>
1831 </div>
1832- <div data-ng-repeat="item in available">
1833+ <div data-ng-repeat="item in available | removeAvailableByNew:availableNew">
1834 <div class="table__row"
1835 data-ng-class="{ active: item.$selected }">
1836 <div class="table__data table__column--3">
1837@@ -753,8 +817,8 @@
1838 data-ng-show="canAddPartition(item)"
1839 data-ng-click="availableQuickPartition(item)">Partition</a>
1840 <a class="icon delete"
1841- data-ng-show="canDelete(item)"
1842- data-ng-click="availableQuickDelete(item)">Delete</a>
1843+ data-ng-show="hasUnmountedFilesystem(item) || canDelete(item)"
1844+ data-ng-click="availableQuickDelete(item)">Remove</a>
1845 </div>
1846 </div>
1847 <div class="table__dropdown">
1848@@ -806,10 +870,10 @@
1849 <div class="table__row table__dropdown-row">
1850 <div class="ng-hide" data-ng-show="availableMode === 'single'">
1851 <div class="table__data left">
1852- <button class="cta-ubuntu"
1853+ <button class="cta-ubuntu secondary"
1854 data-ng-show="canFormatAndMount(item)"
1855 data-ng-click="availableFormatAndMount(item)">{$ getFormatAndMountButtonText (item) $}</button>
1856- <button class="cta-ubuntu"
1857+ <button class="cta-ubuntu secondary"
1858 data-ng-show="canAddPartition(item)"
1859 data-ng-click="availablePartiton(item)">{$ getPartitionButtonText(item) $}</button>
1860 <a class="link-cta-ubuntu text-button"
1861@@ -858,13 +922,91 @@
1862 </div>
1863 </div>
1864 </div>
1865+ <div class="table__row active" data-ng-show="availableMode === 'bcache'">
1866+ <div class="table__data table__column--3">
1867+ <input class="checkbox" type="checkbox" id="bcache-create" disabled="disabled" checked />
1868+ <label class="checkbox-label" for="bcache-create"></label>
1869+ </div>
1870+ <div class="table__data table__column--15">
1871+ <input type="text" class="table__input"
1872+ data-ng-class="{ invalid: isNewDiskNameInvalid() }"
1873+ data-ng-model="availableNew.name">
1874+ </div>
1875+ <div class="table__data table__column--15">{$ availableNew.device.size_human $}</div>
1876+ <div class="table__data table__column--15">Bcache</div>
1877+ <div class="table__data table__column--45"></div>
1878+ <div class="table__data table__column--7"></div>
1879+ <div class="table__dropdown">
1880+ <div class="table__row table__dropdown-row padding-bottom--ten">
1881+ <div class="table__data table__column--3"></div>
1882+ <div class="table__data table__column--15">
1883+ <label for="type">Cache set</label>
1884+ <select name="cache-set" id="cache-set"
1885+ data-ng-model="availableNew.cacheset"
1886+ data-ng-options="cacheset as cacheset.name for cacheset in cachesets">
1887+ </select>
1888+ </div>
1889+ <div class="table__data table__column--15">
1890+ <label for="type">Cache mode</label>
1891+ <select name="cache-mode" id="cache-mode"
1892+ data-ng-model="availableNew.cacheMode">
1893+ <option value="writeback">writeback</option>
1894+ <option value="writethrough">writethrough</option>
1895+ <option value="writearound">writearound</option>
1896+ </select>
1897+ </div>
1898+ <div class="table__data table__column--15"></div>
1899+ <div class="table__data table__column--15">
1900+ <label for="type">File system</label>
1901+ <select name="filesys"
1902+ data-ng-model="availableNew.fstype"
1903+ data-ng-options="fs.key as fs.key for fs in node.supported_filesystems">
1904+ <option value="">Unformatted</option>
1905+ </select>
1906+ </div>
1907+ <div class="table__data table__column--30">
1908+ <label for="mountpoint">Mount point</label>
1909+ <input type="text" id="mountpoint"
1910+ ng-model="availableNew.mountPoint"
1911+ ng-class="{ invalid: isMountPointInvalid(availableNew.mountPoint) }">
1912+ </div>
1913+ <div class="table__data table__column--7"></div>
1914+ </div>
1915+ <div class="table__row table__dropdown-row table__dropdown-row--head">
1916+ <div class="table__header table__column--5"></div>
1917+ <div class="table__header table__column--15">Name</div>
1918+ <div class="table__header table__column--15">Size</div>
1919+ <div class="table__header table__column--15">Device type</div>
1920+ <div class="table__header table__column--40"></div>
1921+ </div>
1922+ <div class="table__row table__dropdown-row no-border">
1923+ <div class="table__data no-padding-top table__column--5"></div>
1924+ <div class="table__data no-padding-top table__column--15">{$ availableNew.device.name $}</div>
1925+ <div class="table__data no-padding-top table__column--15">{$ availableNew.device.size_human $}</div>
1926+ <div class="table__data no-padding-top table__column--15">{$ getDeviceType(availableNew.device) $}</div>
1927+ <div class="table__data no-padding-top table__column--40"></div>
1928+ </div>
1929+ <div class="table__row table__dropdown-row">
1930+ <div class="table__data right">
1931+ <a class="link-cta-ubuntu text-button"
1932+ data-ng-click="availableCancel()">Cancel</a>
1933+ <button class="cta-ubuntu"
1934+ data-ng-disabled="!createBcacheCanSave()"
1935+ data-ng-click="availableConfirmCreateBcache()">Create Bcache</button>
1936+ </div>
1937+ </div>
1938+ </div>
1939+ </div>
1940 </main>
1941 </section>
1942- <div class="ng-hide">
1943- <a class="link-cta-ubuntu secondary margin-right">Create RAID</a>
1944- <a class="link-cta-ubuntu secondary margin-right">Create BCache</a>
1945- <a class="link-cta-ubuntu secondary">Create LVM</a>
1946- </div>
1947+ <a class="link-cta-ubuntu secondary margin-right ng-hide">Create RAID</a>
1948+ <a class="link-cta-ubuntu secondary margin-right ng-hide">Create LVM</a>
1949+ <a class="link-cta-ubuntu secondary margin-right"
1950+ data-ng-disabled="!canCreateCacheSet()"
1951+ data-ng-click="createCacheSet()">Create Cache Set</a>
1952+ <a class="link-cta-ubuntu secondary"
1953+ data-ng-disabled="!canCreateBcache()"
1954+ data-ng-click="createBcache()">Create Bcache</a>
1955 </div>
1956 <div class="twelve-col padding-bottom">
1957 <h3>Used disks and partitions</h3>
1958@@ -904,68 +1046,6 @@
1959
1960 <!--
1961
1962- // XXX ltrager 2015-09-16 - Mock static storage, to be transformed into
1963- // something more angular friendly
1964-
1965- // Available disks & Block devices (CREATE PARTITION ROW)
1966-
1967- // Markup for a row when create a partition has been clicked. All
1968- // Form elements creating the partition will be in table__dropdown
1969- // and the original table__row has a class of active applied
1970-
1971- <div class="table__row active">
1972- <div class="table__data table__column--3">
1973- <input class="checkbox" type="checkbox" id="sdb" />
1974- <label class="checkbox-label" for="sdb"></label>
1975- </div>
1976- <div class="table__data table__column--15">
1977- <input type="text" value="sdb" class="table__input">
1978- </div>
1979- <div class="table__data table__column--15">8.5 G</div>
1980- <div class="table__data table__column--15">Physical</div>
1981- <div class="table__data table__column--15">
1982- fat32 <a class="table__controls table__controls--secondary margin-left--ten">Mount</a>
1983- </div>
1984- <div class="table__data table__column--15">QEMU HARDDISK</div>
1985- <div class="table__data table__column--15 ng-hide">937368252</div>
1986- <div class="table__data table__column--15">
1987- <div class="table__tags">rotary, storage, test, hp, machine<span class="table__controls table__controls--secondary">, <a>edit</a></span></div>
1988- </div>
1989- <div class="table__data table__column--7">
1990- <div class="table__controls">
1991- <a class="icon partition">Partition</a>
1992- <a class="icon delete">Delete</a>
1993- </div>
1994- </div>
1995- <div class="table__dropdown">
1996- <div class="table__row table__dropdown-row padding-bottom--ten">
1997- <div class="table__data table__column--3"></div>
1998- <div class="table__data table__column--15">
1999- <label for="filesys">Filesystem</label>
2000- <select name="filesys" id="filesys">
2001- <option value="fat32">fat32</option>
2002- </select>
2003- </div>
2004- <div class="table__data table__column--30">
2005- <label for="mountpoint">Mountpoint</label>
2006- <input type="text" id="mountpoint">
2007- </div>
2008- <div class="table__data table__column--52">
2009- </div>
2010- </div>
2011- <div class="table__row table__dropdown-row">
2012- <div class="table__data right">
2013- <a class="link-cta-ubuntu text-button">Cancel</a>
2014- <button class="cta-ubuntu">Mount</button>
2015- </div>
2016- </div>
2017- </div>
2018- </div>
2019-
2020- -->
2021-
2022- <!--
2023-
2024 // Creating a Volume Group
2025
2026 // First state of creating a volume group. This row shows once you have
2027@@ -1236,95 +1316,6 @@
2028 </div>
2029
2030 -->
2031-
2032- <!--
2033-
2034- // Create Bchache
2035-
2036- <div class="table__row active">
2037- <div class="table__data table__column--3">
2038- <input class="checkbox" type="checkbox" id="check-all" />
2039- <label class="checkbox-label" for="check-all"></label>
2040- </div>
2041- <div class="table__data table__column--15">
2042- <input type="text" value="BChache0" class="table__input">
2043- </div>
2044- <div class="table__data table__column--15">34 G</div>
2045- <div class="table__data table__column--15">BChache</div>
2046- <div class="table__data table__column--45"></div>
2047- <div class="table__data table__column--7">
2048- <div class="table__controls">
2049- <a class="icon partition">Partition</a>
2050- <a class="icon delete">Delete</a>
2051- </div>
2052- </div>
2053- <div class="table__dropdown">
2054- <div class="table__row table__dropdown-row padding-bottom--ten">
2055- <div class="table__data table__column--3">
2056- </div>
2057- <div class="table__data table__column--15">
2058- <label for="type">BCache setting</label>
2059- <select name="type" id="type">
2060- <option value="raid5">Setting</option>
2061- </select>
2062- </div>
2063- <div class="table__data table__column--30"></div>
2064- <div class="table__data table__column--15">
2065- <label for="type">File system</label>
2066- <select name="type" id="type">
2067- <option value="raid5">fat32</option>
2068- </select>
2069- </div>
2070- <div class="table__data table__column--30">
2071- <label for="mtPoint">Mount point</label>
2072- <input type="text">
2073- </div>
2074- <div class="table__data table__column--7"></div>
2075- </div>
2076- <div class="table__row table__dropdown-row table__dropdown-row--head">
2077- <div class="table__header table__column--5"></div>
2078- <div class="table__header table__column--15">Name</div>
2079- <div class="table__header table__column--15">Size</div>
2080- <div class="table__header table__column--15">Device type</div>
2081- <div class="table__header table__column--10">Caching</div>
2082- <div class="table__header table__column--15">Backing</div>
2083- <div class="table__header table__column--25"></div>
2084- </div>
2085- <div class="table__row table__dropdown-row no-border">
2086- <div class="table__data no-padding-top table__column--5"></div>
2087- <div class="table__data no-padding-top table__column--15">sda</div>
2088- <div class="table__data no-padding-top table__column--15">8.5 G</div>
2089- <div class="table__data no-padding-top table__column--15">Physical</div>
2090- <div class="table__data no-padding-top table__column--10">
2091- <input type="radio" name="active" value="active">
2092- </div>
2093- <div class="table__data no-padding-top table__column--15">
2094- <input type="radio" name="active" value="spare">
2095- </div>
2096- <div class="table__data no-padding-top table__column--25"></div>
2097- </div>
2098- <div class="table__row table__dropdown-row no-border">
2099- <div class="table__data no-padding-top table__column--5"></div>
2100- <div class="table__data no-padding-top table__column--15">sdb</div>
2101- <div class="table__data no-padding-top table__column--15">8.5 G</div>
2102- <div class="table__data no-padding-top table__column--15">Physical</div>
2103- <div class="table__data no-padding-top table__column--10">
2104- <input type="radio" name="active" value="active">
2105- </div>
2106- <div class="table__data no-padding-top table__column--15">
2107- <input type="radio" name="active" value="spare" selected>
2108- </div>
2109- <div class="table__data no-padding-top table__column--25"></div>
2110- </div>
2111- <div class="table__row table__dropdown-row">
2112- <div class="table__data right">
2113- <a href="" class="link-cta-ubuntu text-button">Cancel</a>
2114- <button class="cta-ubuntu">Save</button>
2115- </div>
2116- </div>
2117- </div>
2118- </div>
2119- -->
2120 </form>
2121 </div>
2122 </div>
2123
2124=== modified file 'src/maasserver/websockets/handlers/node.py'
2125--- src/maasserver/websockets/handlers/node.py 2015-10-19 17:56:35 +0000
2126+++ src/maasserver/websockets/handlers/node.py 2015-10-21 17:51:26 +0000
2127@@ -33,6 +33,8 @@
2128 from maasserver.forms import (
2129 AddPartitionForm,
2130 AdminNodeWithMACAddressesForm,
2131+ CreateBcacheForm,
2132+ CreateCacheSetForm,
2133 FormatBlockDeviceForm,
2134 FormatPartitionForm,
2135 MountBlockDeviceForm,
2136@@ -45,6 +47,7 @@
2137 )
2138 from maasserver.forms_interface_link import InterfaceLinkForm
2139 from maasserver.models.blockdevice import BlockDevice
2140+from maasserver.models.cacheset import CacheSet
2141 from maasserver.models.config import Config
2142 from maasserver.models.event import Event
2143 from maasserver.models.filesystemgroup import VolumeGroup
2144@@ -134,7 +137,10 @@
2145 'delete_disk',
2146 'delete_partition',
2147 'delete_volume_group',
2148+ 'delete_cache_set',
2149 'create_partition',
2150+ 'create_cache_set',
2151+ 'create_bcache',
2152 ]
2153 form = AdminNodeWithMACAddressesForm
2154 exclude = [
2155@@ -277,6 +283,9 @@
2156 data["disks"] = data["disks"] + [
2157 self.dehydrate_volume_group(volume_group)
2158 for volume_group in VolumeGroup.objects.filter_by_node(obj)
2159+ ] + [
2160+ self.dehydrate_cache_set(cache_set)
2161+ for cache_set in CacheSet.objects.get_cache_sets_for_node(obj)
2162 ]
2163 data["disks"] = sorted(data["disks"], key=itemgetter("name"))
2164 data["supported_filesystems"] = [
2165@@ -373,7 +382,7 @@
2166 return data
2167
2168 def dehydrate_volume_group(self, volume_group):
2169- """Return `BlockDevice` formatted for JSON encoding."""
2170+ """Return `VolumeGroup` formatted for JSON encoding."""
2171 size = volume_group.get_size()
2172 available_size = volume_group.get_lvm_free_space()
2173 used_size = volume_group.get_lvm_allocated_size()
2174@@ -398,6 +407,36 @@
2175 "partitions": None,
2176 }
2177
2178+ def dehydrate_cache_set(self, cache_set):
2179+ """Return `CacheSet` formatted for JSON encoding."""
2180+ device = cache_set.get_device()
2181+ used_size = device.get_used_size()
2182+ available_size = device.get_available_size()
2183+ bcache_devices = sorted([
2184+ bcache.name
2185+ for bcache in cache_set.filesystemgroup_set.all()
2186+ ])
2187+ return {
2188+ "id": cache_set.id,
2189+ "name": cache_set.name,
2190+ "tags": [],
2191+ "type": "cache-set",
2192+ "path": "",
2193+ "size": device.size,
2194+ "size_human": human_readable_bytes(device.size),
2195+ "used_size": used_size,
2196+ "used_size_human": human_readable_bytes(used_size),
2197+ "available_size": available_size,
2198+ "available_size_human": human_readable_bytes(available_size),
2199+ "block_size": device.get_block_size(),
2200+ "model": "",
2201+ "serial": "",
2202+ "partition_table_type": "",
2203+ "used_for": ", ".join(bcache_devices),
2204+ "filesystem": None,
2205+ "partitions": None,
2206+ }
2207+
2208 def dehydrate_partitions(self, partition_table):
2209 """Return `PartitionTable` formatted for JSON encoding."""
2210 if partition_table is None:
2211@@ -762,6 +801,19 @@
2212 raise VolumeGroup.DoesNotExist()
2213 volume_group.delete()
2214
2215+ def delete_cache_set(self, params):
2216+ # Only admin users can perform delete.
2217+ if not self.user.is_superuser:
2218+ raise HandlerPermissionError()
2219+
2220+ node = self.get_object(params)
2221+ cache_set_id = params.get('cache_set_id')
2222+ if cache_set_id is not None:
2223+ cache_set = CacheSet.objects.get(id=cache_set_id)
2224+ if cache_set.get_node() != node:
2225+ raise CacheSet.DoesNotExist()
2226+ cache_set.delete()
2227+
2228 def create_partition(self, params):
2229 """Create a partition."""
2230 node = self.get_object(params)
2231@@ -775,6 +827,58 @@
2232 else:
2233 form.save()
2234
2235+ def create_cache_set(self, params):
2236+ """Create a cache set."""
2237+ node = self.get_object(params)
2238+ block_id = params.get('block_id')
2239+ partition_id = params.get('partition_id')
2240+
2241+ data = {}
2242+ if partition_id is not None:
2243+ data["cache_partition"] = partition_id
2244+ elif block_id is not None:
2245+ data["cache_device"] = block_id
2246+ else:
2247+ raise HandlerError(
2248+ "Either block_id or partition_id is required.")
2249+
2250+ form = CreateCacheSetForm(node=node, data=data)
2251+ if not form.is_valid():
2252+ raise HandlerError(form.errors)
2253+ else:
2254+ form.save()
2255+
2256+ def create_bcache(self, params):
2257+ """Create a bcache."""
2258+ node = self.get_object(params)
2259+ block_id = params.get('block_id')
2260+ partition_id = params.get('partition_id')
2261+
2262+ data = {
2263+ "name": params["name"],
2264+ "cache_set": params["cache_set"],
2265+ "cache_mode": params["cache_mode"],
2266+ }
2267+
2268+ if partition_id is not None:
2269+ data["backing_partition"] = partition_id
2270+ elif block_id is not None:
2271+ data["backing_device"] = block_id
2272+ else:
2273+ raise HandlerError(
2274+ "Either block_id or partition_id is required.")
2275+
2276+ form = CreateBcacheForm(node=node, data=data)
2277+ if not form.is_valid():
2278+ raise HandlerError(form.errors)
2279+ else:
2280+ bcache = form.save()
2281+
2282+ if 'fstype' in params:
2283+ self.update_blockdevice_filesystem(
2284+ node, bcache.virtual_device.id,
2285+ params.get("fstype"), params.get("mount_point"))
2286+
2287 def action(self, params):
2288 """Perform the action on the object."""
2289 obj = self.get_object(params)
2290
2291=== modified file 'src/maasserver/websockets/handlers/tests/test_node.py'
2292--- src/maasserver/websockets/handlers/tests/test_node.py 2015-10-19 20:12:24 +0000
2293+++ src/maasserver/websockets/handlers/tests/test_node.py 2015-10-21 17:51:26 +0000
2294@@ -18,16 +18,17 @@
2295 from operator import itemgetter
2296 import random
2297 import re
2298-from unittest import skip
2299
2300 from django.contrib.auth.models import User
2301 from django.core.exceptions import ValidationError
2302 from lxml import etree
2303 from maasserver.enum import (
2304 BOND_MODE,
2305+ CACHE_MODE_TYPE,
2306 FILESYSTEM_FORMAT_TYPE_CHOICES,
2307 FILESYSTEM_FORMAT_TYPE_CHOICES_DICT,
2308 FILESYSTEM_GROUP_TYPE,
2309+ FILESYSTEM_TYPE,
2310 INTERFACE_LINK_TYPE,
2311 INTERFACE_TYPE,
2312 IPADDRESS_TYPE,
2313@@ -37,8 +38,12 @@
2314 from maasserver.forms import AdminNodeWithMACAddressesForm
2315 from maasserver.models import interface as interface_module
2316 from maasserver.models.blockdevice import BlockDevice
2317+from maasserver.models.cacheset import CacheSet
2318 from maasserver.models.config import Config
2319-from maasserver.models.filesystemgroup import VolumeGroup
2320+from maasserver.models.filesystemgroup import (
2321+ Bcache,
2322+ VolumeGroup,
2323+)
2324 from maasserver.models.interface import Interface
2325 from maasserver.models.nodeprobeddetails import get_single_probed_details
2326 from maasserver.models.partition import Partition
2327@@ -123,6 +128,9 @@
2328 disks = disks + [
2329 handler.dehydrate_volume_group(volume_group)
2330 for volume_group in VolumeGroup.objects.filter_by_node(node)
2331+ ] + [
2332+ handler.dehydrate_cache_set(cache_set)
2333+ for cache_set in CacheSet.objects.get_cache_sets_for_node(node)
2334 ]
2335 disks = sorted(disks, key=itemgetter("name"))
2336 data = {
2337@@ -450,20 +458,62 @@
2338 "partitions": None,
2339 }, handler.dehydrate_volume_group(volume_group))
2340
2341+ def test_dehydrate_cache_set(self):
2342+ owner = factory.make_User()
2343+ node = factory.make_Node(owner=owner)
2344+ handler = NodeHandler(owner, {})
2345+ cache_set = factory.make_CacheSet(node=node)
2346+ backings = []
2347+ for _ in range(3):
2348+ backing = factory.make_PhysicalBlockDevice(node=node)
2349+ fs = factory.make_Filesystem(
2350+ block_device=backing, fstype=FILESYSTEM_TYPE.BCACHE_BACKING)
2351+ backings.append(
2352+ factory.make_FilesystemGroup(
2353+ group_type=FILESYSTEM_GROUP_TYPE.BCACHE,
2354+ filesystems=[fs], cache_set=cache_set))
2355+ self.assertEquals({
2356+ "id": cache_set.id,
2357+ "name": cache_set.name,
2358+ "tags": [],
2359+ "type": "cache-set",
2360+ "path": "",
2361+ "size": cache_set.get_device().size,
2362+ "size_human": human_readable_bytes(
2363+ cache_set.get_device().size),
2364+ "used_size": cache_set.get_device().get_used_size(),
2365+ "used_size_human": human_readable_bytes(
2366+ cache_set.get_device().get_used_size()),
2367+ "available_size": cache_set.get_device().get_available_size(),
2368+ "available_size_human": human_readable_bytes(
2369+ cache_set.get_device().get_available_size()),
2370+ "block_size": cache_set.get_device().get_block_size(),
2371+ "model": "",
2372+ "serial": "",
2373+ "partition_table_type": "",
2374+ "used_for": ", ".join(sorted([
2375+ backing_device.name
2376+ for backing_device in backings
2377+ ])),
2378+ "filesystem": None,
2379+ "partitions": None,
2380+ }, handler.dehydrate_cache_set(cache_set))
2381+
2382 def test_dehydrate_partitions_returns_None(self):
2383 owner = factory.make_User()
2384 handler = NodeHandler(owner, {})
2385 self.assertIsNone(handler.dehydrate_partitions(None))
2386
2387- @skip("XXX: GavinPanella 2015-10-01 bug=1501753: Fails spuriously")
2388 def test_dehydrate_partitions_returns_list_of_partitions(self):
2389 owner = factory.make_User()
2390 node = factory.make_Node(owner=owner)
2391 handler = NodeHandler(owner, {})
2392- blockdevice = factory.make_PhysicalBlockDevice(node=node)
2393+ blockdevice = factory.make_PhysicalBlockDevice(
2394+ node=node, size=10 * 1024 ** 3, block_size=512)
2395 partition_table = factory.make_PartitionTable(block_device=blockdevice)
2396 partitions = [
2397- factory.make_Partition(partition_table=partition_table)
2398+ factory.make_Partition(
2399+ partition_table=partition_table, size=1 * 1024 ** 3)
2400 for _ in range(3)
2401 ]
2402 expected = []
2403@@ -1193,6 +1243,18 @@
2404 })
2405 self.assertIsNone(reload_object(volume_group))
2406
2407+ def test_delete_cache_set(self):
2408+ user = factory.make_admin()
2409+ handler = NodeHandler(user, {})
2410+ architecture = make_usable_architecture(self)
2411+ node = factory.make_Node(interface=True, architecture=architecture)
2412+ cache_set = factory.make_CacheSet(node=node)
2413+ handler.delete_cache_set({
2414+ 'system_id': node.system_id,
2415+ 'cache_set_id': cache_set.id,
2416+ })
2417+ self.assertIsNone(reload_object(cache_set))
2418+
2419 def test_create_partition(self):
2420 user = factory.make_admin()
2421 handler = NodeHandler(user, {})
2422@@ -1211,6 +1273,155 @@
2423 human_readable_bytes(size),
2424 human_readable_bytes(Partition.objects.first().size))
2425
2426+ def test_create_cache_set_for_partition(self):
2427+ user = factory.make_admin()
2428+ handler = NodeHandler(user, {})
2429+ architecture = make_usable_architecture(self)
2430+ node = factory.make_Node(interface=True, architecture=architecture)
2431+ partition = factory.make_Partition(node=node)
2432+ handler.create_cache_set({
2433+ 'system_id': node.system_id,
2434+ 'partition_id': partition.id
2435+ })
2436+ cache_set = CacheSet.objects.get_cache_sets_for_node(node).first()
2437+ self.assertIsNotNone(cache_set)
2438+ self.assertEquals(partition, cache_set.get_filesystem().partition)
2439+
2440+ def test_create_cache_set_for_block_device(self):
2441+ user = factory.make_admin()
2442+ handler = NodeHandler(user, {})
2443+ architecture = make_usable_architecture(self)
2444+ node = factory.make_Node(interface=True, architecture=architecture)
2445+ block_device = factory.make_PhysicalBlockDevice(node=node)
2446+ handler.create_cache_set({
2447+ 'system_id': node.system_id,
2448+ 'block_id': block_device.id
2449+ })
2450+ cache_set = CacheSet.objects.get_cache_sets_for_node(node).first()
2451+ self.assertIsNotNone(cache_set)
2452+ self.assertEquals(
2453+ block_device.id, cache_set.get_filesystem().block_device.id)
2454+
2455+ def test_create_bcache_for_partition(self):
2456+ user = factory.make_admin()
2457+ handler = NodeHandler(user, {})
2458+ architecture = make_usable_architecture(self)
2459+ node = factory.make_Node(interface=True, architecture=architecture)
2460+ partition = factory.make_Partition(node=node)
2461+ name = factory.make_name("bcache")
2462+ cache_set = factory.make_CacheSet(node=node)
2463+ cache_mode = factory.pick_enum(CACHE_MODE_TYPE)
2464+ handler.create_bcache({
2465+ 'system_id': node.system_id,
2466+ 'partition_id': partition.id,
2467+ 'block_id': partition.partition_table.block_device.id,
2468+ 'name': name,
2469+ 'cache_set': cache_set.id,
2470+ 'cache_mode': cache_mode,
2471+ })
2472+ bcache = Bcache.objects.filter_by_node(node).first()
2473+ self.assertIsNotNone(bcache)
2474+ self.assertEquals(name, bcache.name)
2475+ self.assertEquals(cache_set, bcache.cache_set)
2476+ self.assertEquals(cache_mode, bcache.cache_mode)
2477+ self.assertEquals(
2478+ partition, bcache.get_bcache_backing_filesystem().partition)
2479+
2480+ def test_create_bcache_for_partition_with_filesystem(self):
2481+ user = factory.make_admin()
2482+ handler = NodeHandler(user, {})
2483+ architecture = make_usable_architecture(self)
2484+ node = factory.make_Node(interface=True, architecture=architecture)
2485+ partition = factory.make_Partition(node=node)
2486+ name = factory.make_name("bcache")
2487+ cache_set = factory.make_CacheSet(node=node)
2488+ cache_mode = factory.pick_enum(CACHE_MODE_TYPE)
2489+ fstype = factory.pick_choice(FILESYSTEM_FORMAT_TYPE_CHOICES)
2490+ mount_point = factory.make_absolute_path()
2491+ handler.create_bcache({
2492+ 'system_id': node.system_id,
2493+ 'partition_id': partition.id,
2494+ 'block_id': partition.partition_table.block_device.id,
2495+ 'name': name,
2496+ 'cache_set': cache_set.id,
2497+ 'cache_mode': cache_mode,
2498+ 'fstype': fstype,
2499+ 'mount_point': mount_point,
2500+ })
2501+ bcache = Bcache.objects.filter_by_node(node).first()
2502+ self.assertIsNotNone(bcache)
2503+ self.assertEquals(name, bcache.name)
2504+ self.assertEquals(cache_set, bcache.cache_set)
2505+ self.assertEquals(cache_mode, bcache.cache_mode)
2506+ self.assertEquals(
2507+ partition, bcache.get_bcache_backing_filesystem().partition)
2508+ self.assertEquals(
2509+ fstype,
2510+ bcache.virtual_device.get_effective_filesystem().fstype)
2511+ self.assertEquals(
2512+ mount_point,
2513+ bcache.virtual_device.get_effective_filesystem().mount_point)
2514+
2515+ def test_create_bcache_for_block_device(self):
2516+ user = factory.make_admin()
2517+ handler = NodeHandler(user, {})
2518+ architecture = make_usable_architecture(self)
2519+ node = factory.make_Node(interface=True, architecture=architecture)
2520+ block_device = factory.make_PhysicalBlockDevice(node=node)
2521+ name = factory.make_name("bcache")
2522+ cache_set = factory.make_CacheSet(node=node)
2523+ cache_mode = factory.pick_enum(CACHE_MODE_TYPE)
2524+ handler.create_bcache({
2525+ 'system_id': node.system_id,
2526+ 'block_id': block_device.id,
2527+ 'name': name,
2528+ 'cache_set': cache_set.id,
2529+ 'cache_mode': cache_mode,
2530+ })
2531+ bcache = Bcache.objects.filter_by_node(node).first()
2532+ self.assertIsNotNone(bcache)
2533+ self.assertEquals(name, bcache.name)
2534+ self.assertEquals(cache_set, bcache.cache_set)
2535+ self.assertEquals(cache_mode, bcache.cache_mode)
2536+ self.assertEquals(
2537+ block_device.id,
2538+ bcache.get_bcache_backing_filesystem().block_device.id)
2539+
2540+ def test_create_bcache_for_block_device_with_filesystem(self):
2541+ user = factory.make_admin()
2542+ handler = NodeHandler(user, {})
2543+ architecture = make_usable_architecture(self)
2544+ node = factory.make_Node(interface=True, architecture=architecture)
2545+ block_device = factory.make_PhysicalBlockDevice(node=node)
2546+ name = factory.make_name("bcache")
2547+ cache_set = factory.make_CacheSet(node=node)
2548+ cache_mode = factory.pick_enum(CACHE_MODE_TYPE)
2549+ fstype = factory.pick_choice(FILESYSTEM_FORMAT_TYPE_CHOICES)
2550+ mount_point = factory.make_absolute_path()
2551+ handler.create_bcache({
2552+ 'system_id': node.system_id,
2553+ 'block_id': block_device.id,
2554+ 'name': name,
2555+ 'cache_set': cache_set.id,
2556+ 'cache_mode': cache_mode,
2557+ 'fstype': fstype,
2558+ 'mount_point': mount_point,
2559+ })
2560+ bcache = Bcache.objects.filter_by_node(node).first()
2561+ self.assertIsNotNone(bcache)
2562+ self.assertEquals(name, bcache.name)
2563+ self.assertEquals(cache_set, bcache.cache_set)
2564+ self.assertEquals(cache_mode, bcache.cache_mode)
2565+ self.assertEquals(
2566+ block_device.id,
2567+ bcache.get_bcache_backing_filesystem().block_device.id)
2568+ self.assertEquals(
2569+ fstype,
2570+ bcache.virtual_device.get_effective_filesystem().fstype)
2571+ self.assertEquals(
2572+ mount_point,
2573+ bcache.virtual_device.get_effective_filesystem().mount_point)
2574+
2575 def test_update_raise_HandlerError_if_tag_has_definition(self):
2576 user = factory.make_admin()
2577 handler = NodeHandler(user, {})