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
diff --git a/src/maasserver/websockets/handlers/machine.py b/src/maasserver/websockets/handlers/machine.py
index 8a2b95f..754f5df 100644
--- a/src/maasserver/websockets/handlers/machine.py
+++ b/src/maasserver/websockets/handlers/machine.py
@@ -10,7 +10,10 @@ __all__ = [
10from functools import partial10from functools import partial
11from operator import itemgetter11from operator import itemgetter
1212
13from django.core.exceptions import ValidationError13from django.core.exceptions import (
14 ObjectDoesNotExist,
15 ValidationError,
16)
14from maasserver.enum import (17from maasserver.enum import (
15 BMC_TYPE,18 BMC_TYPE,
16 INTERFACE_LINK_TYPE,19 INTERFACE_LINK_TYPE,
@@ -30,11 +33,13 @@ from maasserver.forms import (
30 CreateCacheSetForm,33 CreateCacheSetForm,
31 CreateLogicalVolumeForm,34 CreateLogicalVolumeForm,
32 CreateRaidForm,35 CreateRaidForm,
36 CreateVMFSForm,
33 CreateVolumeGroupForm,37 CreateVolumeGroupForm,
34 FormatBlockDeviceForm,38 FormatBlockDeviceForm,
35 FormatPartitionForm,39 FormatPartitionForm,
36 UpdatePhysicalBlockDeviceForm,40 UpdatePhysicalBlockDeviceForm,
37 UpdateVirtualBlockDeviceForm,41 UpdateVirtualBlockDeviceForm,
42 UpdateVMFSForm,
38)43)
39from maasserver.forms.filesystem import (44from maasserver.forms.filesystem import (
40 MountFilesystemForm,45 MountFilesystemForm,
@@ -64,9 +69,15 @@ from maasserver.models.partition import Partition
64from maasserver.models.subnet import Subnet69from maasserver.models.subnet import Subnet
65from maasserver.node_action import compile_node_actions70from maasserver.node_action import compile_node_actions
66from maasserver.permissions import NodePermission71from maasserver.permissions import NodePermission
72from maasserver.storage_layouts import (
73 StorageLayoutError,
74 StorageLayoutForm,
75 StorageLayoutMissingBootDiskError,
76)
67from maasserver.utils.orm import transactional77from maasserver.utils.orm import transactional
68from maasserver.utils.threads import deferToDatabase78from maasserver.utils.threads import deferToDatabase
69from maasserver.websockets.base import (79from maasserver.websockets.base import (
80 HandlerDoesNotExistError,
70 HandlerError,81 HandlerError,
71 HandlerPermissionError,82 HandlerPermissionError,
72 HandlerValidationError,83 HandlerValidationError,
@@ -138,13 +149,17 @@ class MachineHandler(NodeHandler):
138 'delete_volume_group',149 'delete_volume_group',
139 'delete_cache_set',150 'delete_cache_set',
140 'delete_filesystem',151 'delete_filesystem',
152 'delete_vmfs_datastore',
153 'update_vmfs_datastore',
141 'create_partition',154 'create_partition',
142 'create_cache_set',155 'create_cache_set',
143 'create_bcache',156 'create_bcache',
144 'create_raid',157 'create_raid',
145 'create_volume_group',158 'create_volume_group',
146 'create_logical_volume',159 'create_logical_volume',
160 'create_vmfs_datastore',
147 'set_boot_disk',161 'set_boot_disk',
162 'apply_storage_layout',
148 'default_user',163 'default_user',
149 'get_summary_xml',164 'get_summary_xml',
150 'get_summary_yaml',165 'get_summary_yaml',
@@ -620,6 +635,34 @@ class MachineHandler(NodeHandler):
620 fs = Filesystem.objects.get(partition=partition, id=filesystem_id)635 fs = Filesystem.objects.get(partition=partition, id=filesystem_id)
621 fs.delete()636 fs.delete()
622637
638 def _get_vmfs_datastore(self, params):
639 """Get the VMFS datastore from the given system_id and id."""
640 node = self._get_node_or_permission_error(
641 params, permission=self._meta.edit_permission)
642 vmfs_datastore_id = params.get('vmfs_datastore_id')
643 try:
644 vbd = node.virtualblockdevice_set.get(
645 filesystem_group_id=vmfs_datastore_id)
646 except ObjectDoesNotExist:
647 raise HandlerDoesNotExistError(vmfs_datastore_id)
648 if not vbd.filesystem_group:
649 raise HandlerDoesNotExistError(vmfs_datastore_id)
650 return vbd.filesystem_group
651
652 def delete_vmfs_datastore(self, params):
653 """Delete a VMFS datastore."""
654 vmfs = self._get_vmfs_datastore(params)
655 vmfs.delete()
656
657 def update_vmfs_datastore(self, params):
658 """Add or remove block devices or partitions from a datastore."""
659 vmfs = self._get_vmfs_datastore(params)
660 form = UpdateVMFSForm(vmfs, data=params)
661 if not form.is_valid():
662 raise HandlerError(form.errors)
663 else:
664 form.save()
665
623 def create_partition(self, params):666 def create_partition(self, params):
624 """Create a partition."""667 """Create a partition."""
625 node = self._get_node_or_permission_error(668 node = self._get_node_or_permission_error(
@@ -744,6 +787,16 @@ class MachineHandler(NodeHandler):
744 logical_volume, params.get("fstype"),787 logical_volume, params.get("fstype"),
745 params.get("mount_point"), params.get("mount_options"))788 params.get("mount_point"), params.get("mount_options"))
746789
790 def create_vmfs_datastore(self, params):
791 """Create a VMFS datastore."""
792 node = self._get_node_or_permission_error(
793 params, permission=self._meta.edit_permission)
794 form = CreateVMFSForm(node, data=params)
795 if not form.is_valid():
796 raise HandlerError(form.errors)
797 else:
798 form.save()
799
747 def set_boot_disk(self, params):800 def set_boot_disk(self, params):
748 """Set the disk as the boot disk."""801 """Set the disk as the boot disk."""
749 node = self._get_node_or_permission_error(802 node = self._get_node_or_permission_error(
@@ -756,6 +809,25 @@ class MachineHandler(NodeHandler):
756 node.boot_disk = device809 node.boot_disk = device
757 node.save()810 node.save()
758811
812 def apply_storage_layout(self, params):
813 """Apply the specified storage layout."""
814 node = self._get_node_or_permission_error(
815 params, permission=self._meta.edit_permission)
816 form = StorageLayoutForm(required=True, data=params)
817 if not form.is_valid():
818 raise HandlerError(form.errors)
819 storage_layout = params.get('storage_layout')
820 try:
821 node.set_storage_layout(storage_layout)
822 except StorageLayoutMissingBootDiskError:
823 raise HandlerError(
824 "Machine is missing a boot disk; no storage layout can be "
825 "applied.")
826 except StorageLayoutError as e:
827 raise HandlerError(
828 "Failed to configure storage layout '%s': %s" % (
829 storage_layout, str(e)))
830
759 def action(self, params):831 def action(self, params):
760 """Perform the action on the object."""832 """Perform the action on the object."""
761 # `compile_node_actions` handles the permission checking internally833 # `compile_node_actions` handles the permission checking internally
diff --git a/src/maasserver/websockets/handlers/tests/test_machine.py b/src/maasserver/websockets/handlers/tests/test_machine.py
index ea646ca..7c91872 100644
--- a/src/maasserver/websockets/handlers/tests/test_machine.py
+++ b/src/maasserver/websockets/handlers/tests/test_machine.py
@@ -66,6 +66,7 @@ from maasserver.rbac import (
66 FakeRBACClient,66 FakeRBACClient,
67 rbac,67 rbac,
68)68)
69from maasserver.storage_layouts import get_storage_layout_choices
69from maasserver.testing.architecture import make_usable_architecture70from maasserver.testing.architecture import make_usable_architecture
70from maasserver.testing.factory import factory71from maasserver.testing.factory import factory
71from maasserver.testing.fixtures import (72from maasserver.testing.fixtures import (
@@ -2432,6 +2433,78 @@ class TestMachineHandler(MAASServerTestCase):
2432 self.assertRaises(2433 self.assertRaises(
2433 HandlerPermissionError, handler.delete_filesystem, params)2434 HandlerPermissionError, handler.delete_filesystem, params)
24342435
2436 def test_delete_vmfs_datastore(self):
2437 user = factory.make_admin()
2438 handler = MachineHandler(user, {}, None)
2439 node = factory.make_Node()
2440 vmfs = factory.make_VMFS(node=node)
2441 params = {
2442 'system_id': node.system_id,
2443 'vmfs_datastore_id': vmfs.id,
2444 }
2445 handler.delete_vmfs_datastore(params)
2446 self.assertIsNone(reload_object(vmfs))
2447
2448 def test_delete_vmfs_datastore_invalid_id(self):
2449 user = factory.make_admin()
2450 handler = MachineHandler(user, {}, None)
2451 node = factory.make_Node()
2452 vmfs = factory.make_VMFS(node=node)
2453 params = {
2454 'system_id': node.system_id,
2455 'vmfs_datastore_id': 999,
2456 }
2457 self.assertRaises(
2458 HandlerDoesNotExistError,
2459 handler.delete_vmfs_datastore, params)
2460 self.assertIsNotNone(reload_object(vmfs))
2461
2462 def test_update_vmfs_datastore(self):
2463 user = factory.make_admin()
2464 handler = MachineHandler(user, {}, None)
2465 node = factory.make_Node()
2466 vmfs = factory.make_VMFS(node=node)
2467 block_device = factory.make_PhysicalBlockDevice(node=node)
2468 partition_table = factory.make_PartitionTable(
2469 block_device=block_device)
2470 partition = factory.make_Partition(partition_table=partition_table)
2471 factory.make_Filesystem(
2472 fstype=FILESYSTEM_TYPE.LVM_PV, partition=partition,
2473 filesystem_group=vmfs)
2474 params = {
2475 'system_id': node.system_id,
2476 'vmfs_datastore_id': vmfs.id,
2477 'remove_partitions': [partition.id],
2478 }
2479 handler.update_vmfs_datastore(params)
2480 self.assertIsNone(partition.get_effective_filesystem())
2481
2482 def test_update_vmfs_datastore_invalid_id(self):
2483 user = factory.make_admin()
2484 handler = MachineHandler(user, {}, None)
2485 node = factory.make_Node()
2486 vmfs = factory.make_VMFS(node=node)
2487 params = {
2488 'system_id': node.system_id,
2489 'vmfs_datastore_id': 999,
2490 }
2491 self.assertRaises(
2492 HandlerDoesNotExistError,
2493 handler.update_vmfs_datastore, params)
2494 self.assertIsNotNone(reload_object(vmfs))
2495
2496 def test_update_vmfs_datastore_raises_errors(self):
2497 user = factory.make_admin()
2498 handler = MachineHandler(user, {}, None)
2499 node = factory.make_Node()
2500 vmfs = factory.make_VMFS(node=node)
2501 params = {
2502 'system_id': node.system_id,
2503 'vmfs_datastore_id': vmfs.id,
2504 'remove_partitions': [999],
2505 }
2506 self.assertRaises(HandlerError, handler.update_vmfs_datastore, params)
2507
2435 def test_create_partition(self):2508 def test_create_partition(self):
2436 user = factory.make_admin()2509 user = factory.make_admin()
2437 handler = MachineHandler(user, {}, None)2510 handler = MachineHandler(user, {}, None)
@@ -2881,6 +2954,37 @@ class TestMachineHandler(MAASServerTestCase):
2881 self.assertRaises(2954 self.assertRaises(
2882 HandlerPermissionError, handler.create_logical_volume, params)2955 HandlerPermissionError, handler.create_logical_volume, params)
28832956
2957 def test_create_vmfs_datastore(self):
2958 user = factory.make_admin()
2959 handler = MachineHandler(user, {}, None)
2960 node = factory.make_Node()
2961 bd_ids = [
2962 factory.make_PhysicalBlockDevice(node=node).id
2963 for _ in range(3)
2964 ]
2965 params = {
2966 'system_id': node.system_id,
2967 'name': 'datastore1',
2968 'block_devices': bd_ids,
2969 }
2970 handler.create_vmfs_datastore(params)
2971 vbd = node.virtualblockdevice_set.get(name='datastore1')
2972 vmfs = vbd.filesystem_group
2973 self.assertItemsEqual(
2974 bd_ids,
2975 [
2976 fs.get_parent().partition_table.block_device.id
2977 for fs in vmfs.filesystems.all()
2978 ])
2979
2980 def test_create_vmfs_datastore_raises_errors(self):
2981 user = factory.make_admin()
2982 handler = MachineHandler(user, {}, None)
2983 node = factory.make_Node()
2984 self.assertRaises(
2985 HandlerError, handler.create_vmfs_datastore,
2986 {'system_id': node.system_id})
2987
2884 def test_set_boot_disk(self):2988 def test_set_boot_disk(self):
2885 user = factory.make_admin()2989 user = factory.make_admin()
2886 handler = MachineHandler(user, {}, None)2990 handler = MachineHandler(user, {}, None)
@@ -2917,6 +3021,54 @@ class TestMachineHandler(MAASServerTestCase):
2917 self.assertRaises(3021 self.assertRaises(
2918 HandlerPermissionError, handler.set_boot_disk, params)3022 HandlerPermissionError, handler.set_boot_disk, params)
29193023
3024 def test_apply_storage_layout(self):
3025 user = factory.make_admin()
3026 handler = MachineHandler(user, {}, None)
3027 node = factory.make_Node(with_boot_disk=False)
3028 node.boot_disk = factory.make_PhysicalBlockDevice(
3029 node=node, size=10 * 1024 ** 3)
3030 factory.make_PhysicalBlockDevice(node=node, size=10 * 2024 ** 3)
3031 params = {
3032 'system_id': node.system_id,
3033 'storage_layout': factory.pick_choice(get_storage_layout_choices())
3034 }
3035 handler.apply_storage_layout(params)
3036 self.assertTrue(node.boot_disk.partitiontable_set.exists())
3037
3038 def test_apply_storage_layout_validates_layout_name(self):
3039 user = factory.make_admin()
3040 handler = MachineHandler(user, {}, None)
3041 node = factory.make_Node()
3042 params = {
3043 'system_id': node.system_id,
3044 'storage_layout': factory.make_name('storage_layout'),
3045 }
3046 self.assertRaises(
3047 HandlerError, handler.apply_storage_layout, params)
3048
3049 def test_apply_storage_layout_raises_missing_boot_disk_error(self):
3050 user = factory.make_admin()
3051 handler = MachineHandler(user, {}, None)
3052 node = factory.make_Node(with_boot_disk=False)
3053 params = {
3054 'system_id': node.system_id,
3055 'storage_layout': factory.pick_choice(get_storage_layout_choices())
3056 }
3057 self.assertRaises(
3058 HandlerError, handler.apply_storage_layout, params)
3059
3060 def test_apply_storage_layout_raises_errors(self):
3061 user = factory.make_admin()
3062 handler = MachineHandler(user, {}, None)
3063 node = factory.make_Node(with_boot_disk=False)
3064 factory.make_PhysicalBlockDevice(size=1024 ** 3)
3065 params = {
3066 'system_id': node.system_id,
3067 'storage_layout': factory.pick_choice(get_storage_layout_choices())
3068 }
3069 self.assertRaises(
3070 HandlerError, handler.apply_storage_layout, params)
3071
2920 def test_update_raise_HandlerError_if_tag_has_definition(self):3072 def test_update_raise_HandlerError_if_tag_has_definition(self):
2921 user = factory.make_admin()3073 user = factory.make_admin()
2922 handler = MachineHandler(user, {}, None)3074 handler = MachineHandler(user, {}, None)

Subscribers

People subscribed via source and target branches