Merge ~ltrager/maas:vmfs_websocket into maas: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)
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_layout,
allows the websocket so select a storage layout for an individual machine.

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

UNIT TESTS
-b vmfs_websocket lp:~ltrager/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: SUCCESS
COMMIT: 138afd9cbc2ce582abccfd7672a51678b3af9cb6

review: Approve
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
1diff --git a/src/maasserver/websockets/handlers/machine.py b/src/maasserver/websockets/handlers/machine.py
2index 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 from functools import partial
7 from operator import itemgetter
8
9-from django.core.exceptions import ValidationError
10+from django.core.exceptions import (
11+ ObjectDoesNotExist,
12+ ValidationError,
13+)
14 from maasserver.enum import (
15 BMC_TYPE,
16 INTERFACE_LINK_TYPE,
17@@ -30,11 +33,13 @@ from maasserver.forms import (
18 CreateCacheSetForm,
19 CreateLogicalVolumeForm,
20 CreateRaidForm,
21+ CreateVMFSForm,
22 CreateVolumeGroupForm,
23 FormatBlockDeviceForm,
24 FormatPartitionForm,
25 UpdatePhysicalBlockDeviceForm,
26 UpdateVirtualBlockDeviceForm,
27+ UpdateVMFSForm,
28 )
29 from maasserver.forms.filesystem import (
30 MountFilesystemForm,
31@@ -64,9 +69,15 @@ from maasserver.models.partition import Partition
32 from maasserver.models.subnet import Subnet
33 from maasserver.node_action import compile_node_actions
34 from maasserver.permissions import NodePermission
35+from maasserver.storage_layouts import (
36+ StorageLayoutError,
37+ StorageLayoutForm,
38+ StorageLayoutMissingBootDiskError,
39+)
40 from maasserver.utils.orm import transactional
41 from maasserver.utils.threads import deferToDatabase
42 from maasserver.websockets.base import (
43+ HandlerDoesNotExistError,
44 HandlerError,
45 HandlerPermissionError,
46 HandlerValidationError,
47@@ -138,13 +149,17 @@ class MachineHandler(NodeHandler):
48 'delete_volume_group',
49 'delete_cache_set',
50 'delete_filesystem',
51+ 'delete_vmfs_datastore',
52+ 'update_vmfs_datastore',
53 'create_partition',
54 'create_cache_set',
55 'create_bcache',
56 'create_raid',
57 'create_volume_group',
58 'create_logical_volume',
59+ 'create_vmfs_datastore',
60 'set_boot_disk',
61+ 'apply_storage_layout',
62 'default_user',
63 'get_summary_xml',
64 'get_summary_yaml',
65@@ -620,6 +635,34 @@ class MachineHandler(NodeHandler):
66 fs = Filesystem.objects.get(partition=partition, id=filesystem_id)
67 fs.delete()
68
69+ def _get_vmfs_datastore(self, params):
70+ """Get the VMFS datastore from the given system_id and id."""
71+ node = self._get_node_or_permission_error(
72+ params, permission=self._meta.edit_permission)
73+ vmfs_datastore_id = params.get('vmfs_datastore_id')
74+ try:
75+ vbd = node.virtualblockdevice_set.get(
76+ filesystem_group_id=vmfs_datastore_id)
77+ except ObjectDoesNotExist:
78+ raise HandlerDoesNotExistError(vmfs_datastore_id)
79+ if not vbd.filesystem_group:
80+ raise HandlerDoesNotExistError(vmfs_datastore_id)
81+ return vbd.filesystem_group
82+
83+ def delete_vmfs_datastore(self, params):
84+ """Delete a VMFS datastore."""
85+ vmfs = self._get_vmfs_datastore(params)
86+ vmfs.delete()
87+
88+ def update_vmfs_datastore(self, params):
89+ """Add or remove block devices or partitions from a datastore."""
90+ vmfs = self._get_vmfs_datastore(params)
91+ form = UpdateVMFSForm(vmfs, data=params)
92+ if not form.is_valid():
93+ raise HandlerError(form.errors)
94+ else:
95+ form.save()
96+
97 def create_partition(self, params):
98 """Create a partition."""
99 node = self._get_node_or_permission_error(
100@@ -744,6 +787,16 @@ class MachineHandler(NodeHandler):
101 logical_volume, params.get("fstype"),
102 params.get("mount_point"), params.get("mount_options"))
103
104+ def create_vmfs_datastore(self, params):
105+ """Create a VMFS datastore."""
106+ node = self._get_node_or_permission_error(
107+ params, permission=self._meta.edit_permission)
108+ form = CreateVMFSForm(node, data=params)
109+ if not form.is_valid():
110+ raise HandlerError(form.errors)
111+ else:
112+ form.save()
113+
114 def set_boot_disk(self, params):
115 """Set the disk as the boot disk."""
116 node = self._get_node_or_permission_error(
117@@ -756,6 +809,25 @@ class MachineHandler(NodeHandler):
118 node.boot_disk = device
119 node.save()
120
121+ def apply_storage_layout(self, params):
122+ """Apply the specified storage layout."""
123+ node = self._get_node_or_permission_error(
124+ params, permission=self._meta.edit_permission)
125+ form = StorageLayoutForm(required=True, data=params)
126+ if not form.is_valid():
127+ raise HandlerError(form.errors)
128+ storage_layout = params.get('storage_layout')
129+ try:
130+ node.set_storage_layout(storage_layout)
131+ except StorageLayoutMissingBootDiskError:
132+ raise HandlerError(
133+ "Machine is missing a boot disk; no storage layout can be "
134+ "applied.")
135+ except StorageLayoutError as e:
136+ raise HandlerError(
137+ "Failed to configure storage layout '%s': %s" % (
138+ storage_layout, str(e)))
139+
140 def action(self, params):
141 """Perform the action on the object."""
142 # `compile_node_actions` handles the permission checking internally
143diff --git a/src/maasserver/websockets/handlers/tests/test_machine.py b/src/maasserver/websockets/handlers/tests/test_machine.py
144index 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 FakeRBACClient,
149 rbac,
150 )
151+from maasserver.storage_layouts import get_storage_layout_choices
152 from maasserver.testing.architecture import make_usable_architecture
153 from maasserver.testing.factory import factory
154 from maasserver.testing.fixtures import (
155@@ -2432,6 +2433,78 @@ class TestMachineHandler(MAASServerTestCase):
156 self.assertRaises(
157 HandlerPermissionError, handler.delete_filesystem, params)
158
159+ def test_delete_vmfs_datastore(self):
160+ user = factory.make_admin()
161+ handler = MachineHandler(user, {}, None)
162+ node = factory.make_Node()
163+ vmfs = factory.make_VMFS(node=node)
164+ params = {
165+ 'system_id': node.system_id,
166+ 'vmfs_datastore_id': vmfs.id,
167+ }
168+ handler.delete_vmfs_datastore(params)
169+ self.assertIsNone(reload_object(vmfs))
170+
171+ def test_delete_vmfs_datastore_invalid_id(self):
172+ user = factory.make_admin()
173+ handler = MachineHandler(user, {}, None)
174+ node = factory.make_Node()
175+ vmfs = factory.make_VMFS(node=node)
176+ params = {
177+ 'system_id': node.system_id,
178+ 'vmfs_datastore_id': 999,
179+ }
180+ self.assertRaises(
181+ HandlerDoesNotExistError,
182+ handler.delete_vmfs_datastore, params)
183+ self.assertIsNotNone(reload_object(vmfs))
184+
185+ def test_update_vmfs_datastore(self):
186+ user = factory.make_admin()
187+ handler = MachineHandler(user, {}, None)
188+ node = factory.make_Node()
189+ vmfs = factory.make_VMFS(node=node)
190+ block_device = factory.make_PhysicalBlockDevice(node=node)
191+ partition_table = factory.make_PartitionTable(
192+ block_device=block_device)
193+ partition = factory.make_Partition(partition_table=partition_table)
194+ factory.make_Filesystem(
195+ fstype=FILESYSTEM_TYPE.LVM_PV, partition=partition,
196+ filesystem_group=vmfs)
197+ params = {
198+ 'system_id': node.system_id,
199+ 'vmfs_datastore_id': vmfs.id,
200+ 'remove_partitions': [partition.id],
201+ }
202+ handler.update_vmfs_datastore(params)
203+ self.assertIsNone(partition.get_effective_filesystem())
204+
205+ def test_update_vmfs_datastore_invalid_id(self):
206+ user = factory.make_admin()
207+ handler = MachineHandler(user, {}, None)
208+ node = factory.make_Node()
209+ vmfs = factory.make_VMFS(node=node)
210+ params = {
211+ 'system_id': node.system_id,
212+ 'vmfs_datastore_id': 999,
213+ }
214+ self.assertRaises(
215+ HandlerDoesNotExistError,
216+ handler.update_vmfs_datastore, params)
217+ self.assertIsNotNone(reload_object(vmfs))
218+
219+ def test_update_vmfs_datastore_raises_errors(self):
220+ user = factory.make_admin()
221+ handler = MachineHandler(user, {}, None)
222+ node = factory.make_Node()
223+ vmfs = factory.make_VMFS(node=node)
224+ params = {
225+ 'system_id': node.system_id,
226+ 'vmfs_datastore_id': vmfs.id,
227+ 'remove_partitions': [999],
228+ }
229+ self.assertRaises(HandlerError, handler.update_vmfs_datastore, params)
230+
231 def test_create_partition(self):
232 user = factory.make_admin()
233 handler = MachineHandler(user, {}, None)
234@@ -2881,6 +2954,37 @@ class TestMachineHandler(MAASServerTestCase):
235 self.assertRaises(
236 HandlerPermissionError, handler.create_logical_volume, params)
237
238+ def test_create_vmfs_datastore(self):
239+ user = factory.make_admin()
240+ handler = MachineHandler(user, {}, None)
241+ node = factory.make_Node()
242+ bd_ids = [
243+ factory.make_PhysicalBlockDevice(node=node).id
244+ for _ in range(3)
245+ ]
246+ params = {
247+ 'system_id': node.system_id,
248+ 'name': 'datastore1',
249+ 'block_devices': bd_ids,
250+ }
251+ handler.create_vmfs_datastore(params)
252+ vbd = node.virtualblockdevice_set.get(name='datastore1')
253+ vmfs = vbd.filesystem_group
254+ self.assertItemsEqual(
255+ bd_ids,
256+ [
257+ fs.get_parent().partition_table.block_device.id
258+ for fs in vmfs.filesystems.all()
259+ ])
260+
261+ def test_create_vmfs_datastore_raises_errors(self):
262+ user = factory.make_admin()
263+ handler = MachineHandler(user, {}, None)
264+ node = factory.make_Node()
265+ self.assertRaises(
266+ HandlerError, handler.create_vmfs_datastore,
267+ {'system_id': node.system_id})
268+
269 def test_set_boot_disk(self):
270 user = factory.make_admin()
271 handler = MachineHandler(user, {}, None)
272@@ -2917,6 +3021,54 @@ class TestMachineHandler(MAASServerTestCase):
273 self.assertRaises(
274 HandlerPermissionError, handler.set_boot_disk, params)
275
276+ def test_apply_storage_layout(self):
277+ user = factory.make_admin()
278+ handler = MachineHandler(user, {}, None)
279+ node = factory.make_Node(with_boot_disk=False)
280+ node.boot_disk = factory.make_PhysicalBlockDevice(
281+ node=node, size=10 * 1024 ** 3)
282+ factory.make_PhysicalBlockDevice(node=node, size=10 * 2024 ** 3)
283+ params = {
284+ 'system_id': node.system_id,
285+ 'storage_layout': factory.pick_choice(get_storage_layout_choices())
286+ }
287+ handler.apply_storage_layout(params)
288+ self.assertTrue(node.boot_disk.partitiontable_set.exists())
289+
290+ def test_apply_storage_layout_validates_layout_name(self):
291+ user = factory.make_admin()
292+ handler = MachineHandler(user, {}, None)
293+ node = factory.make_Node()
294+ params = {
295+ 'system_id': node.system_id,
296+ 'storage_layout': factory.make_name('storage_layout'),
297+ }
298+ self.assertRaises(
299+ HandlerError, handler.apply_storage_layout, params)
300+
301+ def test_apply_storage_layout_raises_missing_boot_disk_error(self):
302+ user = factory.make_admin()
303+ handler = MachineHandler(user, {}, None)
304+ node = factory.make_Node(with_boot_disk=False)
305+ params = {
306+ 'system_id': node.system_id,
307+ 'storage_layout': factory.pick_choice(get_storage_layout_choices())
308+ }
309+ self.assertRaises(
310+ HandlerError, handler.apply_storage_layout, params)
311+
312+ def test_apply_storage_layout_raises_errors(self):
313+ user = factory.make_admin()
314+ handler = MachineHandler(user, {}, None)
315+ node = factory.make_Node(with_boot_disk=False)
316+ factory.make_PhysicalBlockDevice(size=1024 ** 3)
317+ params = {
318+ 'system_id': node.system_id,
319+ 'storage_layout': factory.pick_choice(get_storage_layout_choices())
320+ }
321+ self.assertRaises(
322+ HandlerError, handler.apply_storage_layout, params)
323+
324 def test_update_raise_HandlerError_if_tag_has_definition(self):
325 user = factory.make_admin()
326 handler = MachineHandler(user, {}, None)

Subscribers

People subscribed via source and target branches