Merge ~ltrager/maas:vmfs_websocket into maas:master
- Git
- lp:~ltrager/maas
- vmfs_websocket
- Merge into master
Proposed by
Lee Trager
Status: | Merged |
---|---|
Approved by: | Lee Trager |
Approved revision: | 6484175ade5458d0e494a737ea84298173af61c0 |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~ltrager/maas:vmfs_websocket |
Merge into: | maas:master |
Diff against target: |
326 lines (+225/-1) 2 files modified
src/maasserver/websockets/handlers/machine.py (+73/-1) src/maasserver/websockets/handlers/tests/test_machine.py (+152/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Newell Jensen (community) | Approve | ||
Björn Tillenius | Approve | ||
MAAS Lander | Approve | ||
Review via email: mp+364790@code.launchpad.net |
Commit message
Add VMFS websocket operations
Create, update, and delete websocket operations have been added for VMFS
datastores. Additionally a new websocket operation, apply_storage_
allows the websocket so select a storage layout for an individual machine.
Description of the change
To post a comment you must log in.
Revision history for this message
Björn Tillenius (bjornt) wrote : | # |
The code itself looks good. But I'd like to see some more testing being added. First of all, for all the methods, there should be a test showing that a non-admin user can't use them.
But I also added comments for parts of the code that are still untested.
+1, assuming that you'll add those tests.
review:
Approve
~ltrager/maas:vmfs_websocket
updated
- 91efe01... by Lee Trager
-
Merge branch 'master' into vmfs_websocket
- 6484175... by Lee Trager
-
Add tests for error cases
Revision history for this message
Newell Jensen (newell-jensen) wrote : | # |
Looks good. All tests look to be there as well. +1
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/src/maasserver/websockets/handlers/machine.py b/src/maasserver/websockets/handlers/machine.py | |||
2 | index 8a2b95f..754f5df 100644 | |||
3 | --- a/src/maasserver/websockets/handlers/machine.py | |||
4 | +++ b/src/maasserver/websockets/handlers/machine.py | |||
5 | @@ -10,7 +10,10 @@ __all__ = [ | |||
6 | 10 | from functools import partial | 10 | from functools import partial |
7 | 11 | from operator import itemgetter | 11 | from operator import itemgetter |
8 | 12 | 12 | ||
10 | 13 | from django.core.exceptions import ValidationError | 13 | from django.core.exceptions import ( |
11 | 14 | ObjectDoesNotExist, | ||
12 | 15 | ValidationError, | ||
13 | 16 | ) | ||
14 | 14 | from maasserver.enum import ( | 17 | from maasserver.enum import ( |
15 | 15 | BMC_TYPE, | 18 | BMC_TYPE, |
16 | 16 | INTERFACE_LINK_TYPE, | 19 | INTERFACE_LINK_TYPE, |
17 | @@ -30,11 +33,13 @@ from maasserver.forms import ( | |||
18 | 30 | CreateCacheSetForm, | 33 | CreateCacheSetForm, |
19 | 31 | CreateLogicalVolumeForm, | 34 | CreateLogicalVolumeForm, |
20 | 32 | CreateRaidForm, | 35 | CreateRaidForm, |
21 | 36 | CreateVMFSForm, | ||
22 | 33 | CreateVolumeGroupForm, | 37 | CreateVolumeGroupForm, |
23 | 34 | FormatBlockDeviceForm, | 38 | FormatBlockDeviceForm, |
24 | 35 | FormatPartitionForm, | 39 | FormatPartitionForm, |
25 | 36 | UpdatePhysicalBlockDeviceForm, | 40 | UpdatePhysicalBlockDeviceForm, |
26 | 37 | UpdateVirtualBlockDeviceForm, | 41 | UpdateVirtualBlockDeviceForm, |
27 | 42 | UpdateVMFSForm, | ||
28 | 38 | ) | 43 | ) |
29 | 39 | from maasserver.forms.filesystem import ( | 44 | from maasserver.forms.filesystem import ( |
30 | 40 | MountFilesystemForm, | 45 | MountFilesystemForm, |
31 | @@ -64,9 +69,15 @@ from maasserver.models.partition import Partition | |||
32 | 64 | from maasserver.models.subnet import Subnet | 69 | from maasserver.models.subnet import Subnet |
33 | 65 | from maasserver.node_action import compile_node_actions | 70 | from maasserver.node_action import compile_node_actions |
34 | 66 | from maasserver.permissions import NodePermission | 71 | from maasserver.permissions import NodePermission |
35 | 72 | from maasserver.storage_layouts import ( | ||
36 | 73 | StorageLayoutError, | ||
37 | 74 | StorageLayoutForm, | ||
38 | 75 | StorageLayoutMissingBootDiskError, | ||
39 | 76 | ) | ||
40 | 67 | from maasserver.utils.orm import transactional | 77 | from maasserver.utils.orm import transactional |
41 | 68 | from maasserver.utils.threads import deferToDatabase | 78 | from maasserver.utils.threads import deferToDatabase |
42 | 69 | from maasserver.websockets.base import ( | 79 | from maasserver.websockets.base import ( |
43 | 80 | HandlerDoesNotExistError, | ||
44 | 70 | HandlerError, | 81 | HandlerError, |
45 | 71 | HandlerPermissionError, | 82 | HandlerPermissionError, |
46 | 72 | HandlerValidationError, | 83 | HandlerValidationError, |
47 | @@ -138,13 +149,17 @@ class MachineHandler(NodeHandler): | |||
48 | 138 | 'delete_volume_group', | 149 | 'delete_volume_group', |
49 | 139 | 'delete_cache_set', | 150 | 'delete_cache_set', |
50 | 140 | 'delete_filesystem', | 151 | 'delete_filesystem', |
51 | 152 | 'delete_vmfs_datastore', | ||
52 | 153 | 'update_vmfs_datastore', | ||
53 | 141 | 'create_partition', | 154 | 'create_partition', |
54 | 142 | 'create_cache_set', | 155 | 'create_cache_set', |
55 | 143 | 'create_bcache', | 156 | 'create_bcache', |
56 | 144 | 'create_raid', | 157 | 'create_raid', |
57 | 145 | 'create_volume_group', | 158 | 'create_volume_group', |
58 | 146 | 'create_logical_volume', | 159 | 'create_logical_volume', |
59 | 160 | 'create_vmfs_datastore', | ||
60 | 147 | 'set_boot_disk', | 161 | 'set_boot_disk', |
61 | 162 | 'apply_storage_layout', | ||
62 | 148 | 'default_user', | 163 | 'default_user', |
63 | 149 | 'get_summary_xml', | 164 | 'get_summary_xml', |
64 | 150 | 'get_summary_yaml', | 165 | 'get_summary_yaml', |
65 | @@ -620,6 +635,34 @@ class MachineHandler(NodeHandler): | |||
66 | 620 | fs = Filesystem.objects.get(partition=partition, id=filesystem_id) | 635 | fs = Filesystem.objects.get(partition=partition, id=filesystem_id) |
67 | 621 | fs.delete() | 636 | fs.delete() |
68 | 622 | 637 | ||
69 | 638 | def _get_vmfs_datastore(self, params): | ||
70 | 639 | """Get the VMFS datastore from the given system_id and id.""" | ||
71 | 640 | node = self._get_node_or_permission_error( | ||
72 | 641 | params, permission=self._meta.edit_permission) | ||
73 | 642 | vmfs_datastore_id = params.get('vmfs_datastore_id') | ||
74 | 643 | try: | ||
75 | 644 | vbd = node.virtualblockdevice_set.get( | ||
76 | 645 | filesystem_group_id=vmfs_datastore_id) | ||
77 | 646 | except ObjectDoesNotExist: | ||
78 | 647 | raise HandlerDoesNotExistError(vmfs_datastore_id) | ||
79 | 648 | if not vbd.filesystem_group: | ||
80 | 649 | raise HandlerDoesNotExistError(vmfs_datastore_id) | ||
81 | 650 | return vbd.filesystem_group | ||
82 | 651 | |||
83 | 652 | def delete_vmfs_datastore(self, params): | ||
84 | 653 | """Delete a VMFS datastore.""" | ||
85 | 654 | vmfs = self._get_vmfs_datastore(params) | ||
86 | 655 | vmfs.delete() | ||
87 | 656 | |||
88 | 657 | def update_vmfs_datastore(self, params): | ||
89 | 658 | """Add or remove block devices or partitions from a datastore.""" | ||
90 | 659 | vmfs = self._get_vmfs_datastore(params) | ||
91 | 660 | form = UpdateVMFSForm(vmfs, data=params) | ||
92 | 661 | if not form.is_valid(): | ||
93 | 662 | raise HandlerError(form.errors) | ||
94 | 663 | else: | ||
95 | 664 | form.save() | ||
96 | 665 | |||
97 | 623 | def create_partition(self, params): | 666 | def create_partition(self, params): |
98 | 624 | """Create a partition.""" | 667 | """Create a partition.""" |
99 | 625 | node = self._get_node_or_permission_error( | 668 | node = self._get_node_or_permission_error( |
100 | @@ -744,6 +787,16 @@ class MachineHandler(NodeHandler): | |||
101 | 744 | logical_volume, params.get("fstype"), | 787 | logical_volume, params.get("fstype"), |
102 | 745 | params.get("mount_point"), params.get("mount_options")) | 788 | params.get("mount_point"), params.get("mount_options")) |
103 | 746 | 789 | ||
104 | 790 | def create_vmfs_datastore(self, params): | ||
105 | 791 | """Create a VMFS datastore.""" | ||
106 | 792 | node = self._get_node_or_permission_error( | ||
107 | 793 | params, permission=self._meta.edit_permission) | ||
108 | 794 | form = CreateVMFSForm(node, data=params) | ||
109 | 795 | if not form.is_valid(): | ||
110 | 796 | raise HandlerError(form.errors) | ||
111 | 797 | else: | ||
112 | 798 | form.save() | ||
113 | 799 | |||
114 | 747 | def set_boot_disk(self, params): | 800 | def set_boot_disk(self, params): |
115 | 748 | """Set the disk as the boot disk.""" | 801 | """Set the disk as the boot disk.""" |
116 | 749 | node = self._get_node_or_permission_error( | 802 | node = self._get_node_or_permission_error( |
117 | @@ -756,6 +809,25 @@ class MachineHandler(NodeHandler): | |||
118 | 756 | node.boot_disk = device | 809 | node.boot_disk = device |
119 | 757 | node.save() | 810 | node.save() |
120 | 758 | 811 | ||
121 | 812 | def apply_storage_layout(self, params): | ||
122 | 813 | """Apply the specified storage layout.""" | ||
123 | 814 | node = self._get_node_or_permission_error( | ||
124 | 815 | params, permission=self._meta.edit_permission) | ||
125 | 816 | form = StorageLayoutForm(required=True, data=params) | ||
126 | 817 | if not form.is_valid(): | ||
127 | 818 | raise HandlerError(form.errors) | ||
128 | 819 | storage_layout = params.get('storage_layout') | ||
129 | 820 | try: | ||
130 | 821 | node.set_storage_layout(storage_layout) | ||
131 | 822 | except StorageLayoutMissingBootDiskError: | ||
132 | 823 | raise HandlerError( | ||
133 | 824 | "Machine is missing a boot disk; no storage layout can be " | ||
134 | 825 | "applied.") | ||
135 | 826 | except StorageLayoutError as e: | ||
136 | 827 | raise HandlerError( | ||
137 | 828 | "Failed to configure storage layout '%s': %s" % ( | ||
138 | 829 | storage_layout, str(e))) | ||
139 | 830 | |||
140 | 759 | def action(self, params): | 831 | def action(self, params): |
141 | 760 | """Perform the action on the object.""" | 832 | """Perform the action on the object.""" |
142 | 761 | # `compile_node_actions` handles the permission checking internally | 833 | # `compile_node_actions` handles the permission checking internally |
143 | diff --git a/src/maasserver/websockets/handlers/tests/test_machine.py b/src/maasserver/websockets/handlers/tests/test_machine.py | |||
144 | index ea646ca..7c91872 100644 | |||
145 | --- a/src/maasserver/websockets/handlers/tests/test_machine.py | |||
146 | +++ b/src/maasserver/websockets/handlers/tests/test_machine.py | |||
147 | @@ -66,6 +66,7 @@ from maasserver.rbac import ( | |||
148 | 66 | FakeRBACClient, | 66 | FakeRBACClient, |
149 | 67 | rbac, | 67 | rbac, |
150 | 68 | ) | 68 | ) |
151 | 69 | from maasserver.storage_layouts import get_storage_layout_choices | ||
152 | 69 | from maasserver.testing.architecture import make_usable_architecture | 70 | from maasserver.testing.architecture import make_usable_architecture |
153 | 70 | from maasserver.testing.factory import factory | 71 | from maasserver.testing.factory import factory |
154 | 71 | from maasserver.testing.fixtures import ( | 72 | from maasserver.testing.fixtures import ( |
155 | @@ -2432,6 +2433,78 @@ class TestMachineHandler(MAASServerTestCase): | |||
156 | 2432 | self.assertRaises( | 2433 | self.assertRaises( |
157 | 2433 | HandlerPermissionError, handler.delete_filesystem, params) | 2434 | HandlerPermissionError, handler.delete_filesystem, params) |
158 | 2434 | 2435 | ||
159 | 2436 | def test_delete_vmfs_datastore(self): | ||
160 | 2437 | user = factory.make_admin() | ||
161 | 2438 | handler = MachineHandler(user, {}, None) | ||
162 | 2439 | node = factory.make_Node() | ||
163 | 2440 | vmfs = factory.make_VMFS(node=node) | ||
164 | 2441 | params = { | ||
165 | 2442 | 'system_id': node.system_id, | ||
166 | 2443 | 'vmfs_datastore_id': vmfs.id, | ||
167 | 2444 | } | ||
168 | 2445 | handler.delete_vmfs_datastore(params) | ||
169 | 2446 | self.assertIsNone(reload_object(vmfs)) | ||
170 | 2447 | |||
171 | 2448 | def test_delete_vmfs_datastore_invalid_id(self): | ||
172 | 2449 | user = factory.make_admin() | ||
173 | 2450 | handler = MachineHandler(user, {}, None) | ||
174 | 2451 | node = factory.make_Node() | ||
175 | 2452 | vmfs = factory.make_VMFS(node=node) | ||
176 | 2453 | params = { | ||
177 | 2454 | 'system_id': node.system_id, | ||
178 | 2455 | 'vmfs_datastore_id': 999, | ||
179 | 2456 | } | ||
180 | 2457 | self.assertRaises( | ||
181 | 2458 | HandlerDoesNotExistError, | ||
182 | 2459 | handler.delete_vmfs_datastore, params) | ||
183 | 2460 | self.assertIsNotNone(reload_object(vmfs)) | ||
184 | 2461 | |||
185 | 2462 | def test_update_vmfs_datastore(self): | ||
186 | 2463 | user = factory.make_admin() | ||
187 | 2464 | handler = MachineHandler(user, {}, None) | ||
188 | 2465 | node = factory.make_Node() | ||
189 | 2466 | vmfs = factory.make_VMFS(node=node) | ||
190 | 2467 | block_device = factory.make_PhysicalBlockDevice(node=node) | ||
191 | 2468 | partition_table = factory.make_PartitionTable( | ||
192 | 2469 | block_device=block_device) | ||
193 | 2470 | partition = factory.make_Partition(partition_table=partition_table) | ||
194 | 2471 | factory.make_Filesystem( | ||
195 | 2472 | fstype=FILESYSTEM_TYPE.LVM_PV, partition=partition, | ||
196 | 2473 | filesystem_group=vmfs) | ||
197 | 2474 | params = { | ||
198 | 2475 | 'system_id': node.system_id, | ||
199 | 2476 | 'vmfs_datastore_id': vmfs.id, | ||
200 | 2477 | 'remove_partitions': [partition.id], | ||
201 | 2478 | } | ||
202 | 2479 | handler.update_vmfs_datastore(params) | ||
203 | 2480 | self.assertIsNone(partition.get_effective_filesystem()) | ||
204 | 2481 | |||
205 | 2482 | def test_update_vmfs_datastore_invalid_id(self): | ||
206 | 2483 | user = factory.make_admin() | ||
207 | 2484 | handler = MachineHandler(user, {}, None) | ||
208 | 2485 | node = factory.make_Node() | ||
209 | 2486 | vmfs = factory.make_VMFS(node=node) | ||
210 | 2487 | params = { | ||
211 | 2488 | 'system_id': node.system_id, | ||
212 | 2489 | 'vmfs_datastore_id': 999, | ||
213 | 2490 | } | ||
214 | 2491 | self.assertRaises( | ||
215 | 2492 | HandlerDoesNotExistError, | ||
216 | 2493 | handler.update_vmfs_datastore, params) | ||
217 | 2494 | self.assertIsNotNone(reload_object(vmfs)) | ||
218 | 2495 | |||
219 | 2496 | def test_update_vmfs_datastore_raises_errors(self): | ||
220 | 2497 | user = factory.make_admin() | ||
221 | 2498 | handler = MachineHandler(user, {}, None) | ||
222 | 2499 | node = factory.make_Node() | ||
223 | 2500 | vmfs = factory.make_VMFS(node=node) | ||
224 | 2501 | params = { | ||
225 | 2502 | 'system_id': node.system_id, | ||
226 | 2503 | 'vmfs_datastore_id': vmfs.id, | ||
227 | 2504 | 'remove_partitions': [999], | ||
228 | 2505 | } | ||
229 | 2506 | self.assertRaises(HandlerError, handler.update_vmfs_datastore, params) | ||
230 | 2507 | |||
231 | 2435 | def test_create_partition(self): | 2508 | def test_create_partition(self): |
232 | 2436 | user = factory.make_admin() | 2509 | user = factory.make_admin() |
233 | 2437 | handler = MachineHandler(user, {}, None) | 2510 | handler = MachineHandler(user, {}, None) |
234 | @@ -2881,6 +2954,37 @@ class TestMachineHandler(MAASServerTestCase): | |||
235 | 2881 | self.assertRaises( | 2954 | self.assertRaises( |
236 | 2882 | HandlerPermissionError, handler.create_logical_volume, params) | 2955 | HandlerPermissionError, handler.create_logical_volume, params) |
237 | 2883 | 2956 | ||
238 | 2957 | def test_create_vmfs_datastore(self): | ||
239 | 2958 | user = factory.make_admin() | ||
240 | 2959 | handler = MachineHandler(user, {}, None) | ||
241 | 2960 | node = factory.make_Node() | ||
242 | 2961 | bd_ids = [ | ||
243 | 2962 | factory.make_PhysicalBlockDevice(node=node).id | ||
244 | 2963 | for _ in range(3) | ||
245 | 2964 | ] | ||
246 | 2965 | params = { | ||
247 | 2966 | 'system_id': node.system_id, | ||
248 | 2967 | 'name': 'datastore1', | ||
249 | 2968 | 'block_devices': bd_ids, | ||
250 | 2969 | } | ||
251 | 2970 | handler.create_vmfs_datastore(params) | ||
252 | 2971 | vbd = node.virtualblockdevice_set.get(name='datastore1') | ||
253 | 2972 | vmfs = vbd.filesystem_group | ||
254 | 2973 | self.assertItemsEqual( | ||
255 | 2974 | bd_ids, | ||
256 | 2975 | [ | ||
257 | 2976 | fs.get_parent().partition_table.block_device.id | ||
258 | 2977 | for fs in vmfs.filesystems.all() | ||
259 | 2978 | ]) | ||
260 | 2979 | |||
261 | 2980 | def test_create_vmfs_datastore_raises_errors(self): | ||
262 | 2981 | user = factory.make_admin() | ||
263 | 2982 | handler = MachineHandler(user, {}, None) | ||
264 | 2983 | node = factory.make_Node() | ||
265 | 2984 | self.assertRaises( | ||
266 | 2985 | HandlerError, handler.create_vmfs_datastore, | ||
267 | 2986 | {'system_id': node.system_id}) | ||
268 | 2987 | |||
269 | 2884 | def test_set_boot_disk(self): | 2988 | def test_set_boot_disk(self): |
270 | 2885 | user = factory.make_admin() | 2989 | user = factory.make_admin() |
271 | 2886 | handler = MachineHandler(user, {}, None) | 2990 | handler = MachineHandler(user, {}, None) |
272 | @@ -2917,6 +3021,54 @@ class TestMachineHandler(MAASServerTestCase): | |||
273 | 2917 | self.assertRaises( | 3021 | self.assertRaises( |
274 | 2918 | HandlerPermissionError, handler.set_boot_disk, params) | 3022 | HandlerPermissionError, handler.set_boot_disk, params) |
275 | 2919 | 3023 | ||
276 | 3024 | def test_apply_storage_layout(self): | ||
277 | 3025 | user = factory.make_admin() | ||
278 | 3026 | handler = MachineHandler(user, {}, None) | ||
279 | 3027 | node = factory.make_Node(with_boot_disk=False) | ||
280 | 3028 | node.boot_disk = factory.make_PhysicalBlockDevice( | ||
281 | 3029 | node=node, size=10 * 1024 ** 3) | ||
282 | 3030 | factory.make_PhysicalBlockDevice(node=node, size=10 * 2024 ** 3) | ||
283 | 3031 | params = { | ||
284 | 3032 | 'system_id': node.system_id, | ||
285 | 3033 | 'storage_layout': factory.pick_choice(get_storage_layout_choices()) | ||
286 | 3034 | } | ||
287 | 3035 | handler.apply_storage_layout(params) | ||
288 | 3036 | self.assertTrue(node.boot_disk.partitiontable_set.exists()) | ||
289 | 3037 | |||
290 | 3038 | def test_apply_storage_layout_validates_layout_name(self): | ||
291 | 3039 | user = factory.make_admin() | ||
292 | 3040 | handler = MachineHandler(user, {}, None) | ||
293 | 3041 | node = factory.make_Node() | ||
294 | 3042 | params = { | ||
295 | 3043 | 'system_id': node.system_id, | ||
296 | 3044 | 'storage_layout': factory.make_name('storage_layout'), | ||
297 | 3045 | } | ||
298 | 3046 | self.assertRaises( | ||
299 | 3047 | HandlerError, handler.apply_storage_layout, params) | ||
300 | 3048 | |||
301 | 3049 | def test_apply_storage_layout_raises_missing_boot_disk_error(self): | ||
302 | 3050 | user = factory.make_admin() | ||
303 | 3051 | handler = MachineHandler(user, {}, None) | ||
304 | 3052 | node = factory.make_Node(with_boot_disk=False) | ||
305 | 3053 | params = { | ||
306 | 3054 | 'system_id': node.system_id, | ||
307 | 3055 | 'storage_layout': factory.pick_choice(get_storage_layout_choices()) | ||
308 | 3056 | } | ||
309 | 3057 | self.assertRaises( | ||
310 | 3058 | HandlerError, handler.apply_storage_layout, params) | ||
311 | 3059 | |||
312 | 3060 | def test_apply_storage_layout_raises_errors(self): | ||
313 | 3061 | user = factory.make_admin() | ||
314 | 3062 | handler = MachineHandler(user, {}, None) | ||
315 | 3063 | node = factory.make_Node(with_boot_disk=False) | ||
316 | 3064 | factory.make_PhysicalBlockDevice(size=1024 ** 3) | ||
317 | 3065 | params = { | ||
318 | 3066 | 'system_id': node.system_id, | ||
319 | 3067 | 'storage_layout': factory.pick_choice(get_storage_layout_choices()) | ||
320 | 3068 | } | ||
321 | 3069 | self.assertRaises( | ||
322 | 3070 | HandlerError, handler.apply_storage_layout, params) | ||
323 | 3071 | |||
324 | 2920 | def test_update_raise_HandlerError_if_tag_has_definition(self): | 3072 | def test_update_raise_HandlerError_if_tag_has_definition(self): |
325 | 2921 | user = factory.make_admin() | 3073 | user = factory.make_admin() |
326 | 2922 | handler = MachineHandler(user, {}, None) | 3074 | handler = MachineHandler(user, {}, None) |
UNIT TESTS
-b vmfs_websocket lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: SUCCESS 2abccfd7672a516 78b3af9cb6
COMMIT: 138afd9cbc2ce58