Merge ~ltrager/maas:lp1825240 into maas:master

Proposed by Lee Trager
Status: Merged
Approved by: Lee Trager
Approved revision: 1f26fb3b9a7f93afeeb0c5d1343328eb55fe44ba
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~ltrager/maas:lp1825240
Merge into: maas:master
Prerequisite: ~ltrager/maas:lp1821220
Diff against target: 339 lines (+96/-19)
6 files modified
src/maasserver/api/tests/test_vmfs_datastores.py (+8/-1)
src/maasserver/forms/__init__.py (+12/-0)
src/maasserver/forms/tests/test_vmfs.py (+38/-15)
src/maasserver/models/node.py (+18/-0)
src/maasserver/models/tests/test_node.py (+11/-0)
src/maasserver/websockets/handlers/tests/test_machine.py (+9/-3)
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+366331@code.launchpad.net

Commit message

LP: #1825240 - Do not allow datastores to be created unless the VMFS6 storage layout has been applied.

VMFS datastores only work with VMware ESXi. Prevent them from being created
unless the VMFS6 storage layout has been previously applied. This prevents
users from thinking we support VMFS Datastores on other operating systems.

To be backwards compatible with previous versions of MAAS VMware ESXi can
still be deployed without applying the VMFS6 storage layout. In that case
the default layout, including a datastore will be applied on deployment.
If the VMFS6 storage layout is applied make sure a datastore is defined
as one will always be created.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good, minus the very minor nitpick.

review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
~ltrager/maas:lp1825240 updated
ab519ac... by Lee Trager

Merge branch 'master' into lp1825240

1f26fb3... by Lee Trager

Fix failing tests

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/api/tests/test_vmfs_datastores.py b/src/maasserver/api/tests/test_vmfs_datastores.py
2index 167d7bc..15f405a 100644
3--- a/src/maasserver/api/tests/test_vmfs_datastores.py
4+++ b/src/maasserver/api/tests/test_vmfs_datastores.py
5@@ -15,9 +15,11 @@ from maasserver.enum import (
6 )
7 from maasserver.models.partition import MIN_PARTITION_SIZE
8 from maasserver.models.partitiontable import PARTITION_TABLE_EXTRA_SPACE
9+from maasserver.storage_layouts import VMFS6StorageLayout
10 from maasserver.testing.api import APITestCase
11 from maasserver.testing.factory import factory
12 from maasserver.testing.matchers import HasStatusCode
13+from maasserver.tests.test_storage_layouts import LARGE_BLOCK_DEVICE
14 from maasserver.utils.converters import (
15 human_readable_bytes,
16 json_load_bytes,
17@@ -89,7 +91,12 @@ class TestVMFSDatastoresAPI(APITestCase.ForUser):
18
19 def test_POST_creates_with_block_devices_and_partitions(self):
20 self.become_admin()
21- node = factory.make_Machine(status=NODE_STATUS.READY)
22+ node = factory.make_Machine(
23+ status=NODE_STATUS.READY, with_boot_disk=False)
24+ node.boot_disk = factory.make_PhysicalBlockDevice(
25+ node=node, size=LARGE_BLOCK_DEVICE)
26+ layout = VMFS6StorageLayout(node)
27+ layout.configure()
28 block_devices = [
29 factory.make_PhysicalBlockDevice(node=node)
30 for _ in range(3)
31diff --git a/src/maasserver/forms/__init__.py b/src/maasserver/forms/__init__.py
32index 3a25954..021acdd 100644
33--- a/src/maasserver/forms/__init__.py
34+++ b/src/maasserver/forms/__init__.py
35@@ -166,6 +166,7 @@ from maasserver.permissions import (
36 NodePermission,
37 ResourcePoolPermission,
38 )
39+from maasserver.storage_layouts import VMFS6StorageLayout
40 from maasserver.utils.converters import machine_readable_bytes
41 from maasserver.utils.forms import (
42 compose_invalid_choice_text,
43@@ -3793,6 +3794,17 @@ class CreateLogicalVolumeForm(Form):
44 class CreateVMFSForm(CreateVolumeGroupForm):
45 """For validating and saving a new VMFS group."""
46
47+ def clean(self):
48+ """Validate that the VMFS6 storage layout is applied."""
49+ cleaned_data = super().clean()
50+ vmfs_layout = VMFS6StorageLayout(self.node)
51+ vmfs_bd = vmfs_layout.is_layout()
52+ if vmfs_bd is None:
53+ set_form_error(
54+ self, 'VMFS6', 'VMFS Datastores may only be created after the '
55+ 'VMFS6 storage layout has been applied.')
56+ return cleaned_data
57+
58 def save(self):
59 """Persist the `VMFS` into the database."""
60 block_devices = list(BlockDevice.objects.filter(
61diff --git a/src/maasserver/forms/tests/test_vmfs.py b/src/maasserver/forms/tests/test_vmfs.py
62index 233cc6c..ccff190 100644
63--- a/src/maasserver/forms/tests/test_vmfs.py
64+++ b/src/maasserver/forms/tests/test_vmfs.py
65@@ -14,20 +14,32 @@ from maasserver.forms import (
66 )
67 from maasserver.models.blockdevice import MIN_BLOCK_DEVICE_SIZE
68 from maasserver.models.partitiontable import PARTITION_TABLE_EXTRA_SPACE
69+from maasserver.storage_layouts import VMFS6StorageLayout
70 from maasserver.testing.factory import factory
71 from maasserver.testing.testcase import MAASServerTestCase
72+from maasserver.tests.test_storage_layouts import LARGE_BLOCK_DEVICE
73+
74+
75+def make_Node_with_VMFS6_layout(*args, **kwargs):
76+ """Create a node with the VMFS6 storage layout applied."""
77+ kwargs['with_boot_disk'] = False
78+ node = factory.make_Node(*args, **kwargs)
79+ factory.make_PhysicalBlockDevice(node=node, size=LARGE_BLOCK_DEVICE)
80+ layout = VMFS6StorageLayout(node)
81+ layout.configure()
82+ return node
83
84
85 class TestCreateVMFSForm(MAASServerTestCase):
86
87 def test_requires_fields(self):
88- node = factory.make_Node()
89+ node = make_Node_with_VMFS6_layout()
90 form = CreateVMFSForm(node, data={})
91 self.assertFalse(form.is_valid(), form.errors)
92 self.assertItemsEqual(['name'], form.errors.keys())
93
94 def test_is_not_valid_if_invalid_uuid(self):
95- node = factory.make_Node()
96+ node = make_Node_with_VMFS6_layout()
97 block_device = factory.make_PhysicalBlockDevice(node=node)
98 data = {
99 'name': factory.make_name("name"),
100@@ -41,7 +53,7 @@ class TestCreateVMFSForm(MAASServerTestCase):
101 self.assertEqual({'uuid': ['Enter a valid value.']}, form._errors)
102
103 def test_is_not_valid_missing_block_devices_and_partitions(self):
104- node = factory.make_Node()
105+ node = make_Node_with_VMFS6_layout()
106 data = {
107 'name': factory.make_name('name'),
108 'uuid': uuid.uuid4(),
109@@ -57,7 +69,7 @@ class TestCreateVMFSForm(MAASServerTestCase):
110 ]}, form._errors)
111
112 def test_is_not_valid_if_block_device_does_not_belong_to_node(self):
113- node = factory.make_Node()
114+ node = make_Node_with_VMFS6_layout()
115 block_device = factory.make_PhysicalBlockDevice()
116 data = {
117 'name': factory.make_name('name'),
118@@ -75,7 +87,7 @@ class TestCreateVMFSForm(MAASServerTestCase):
119 ]}, form._errors)
120
121 def test_is_not_valid_if_partition_does_not_belong_to_node(self):
122- node = factory.make_Node()
123+ node = make_Node_with_VMFS6_layout()
124 partition = factory.make_Partition()
125 data = {
126 'name': factory.make_name('name'),
127@@ -92,8 +104,19 @@ class TestCreateVMFSForm(MAASServerTestCase):
128 'choices.' % partition.id,
129 ]}, form._errors)
130
131+ def test_is_not_valid_if_vmfs_layout_is_not_applied(self):
132+ node = factory.make_Node(with_boot_disk=False)
133+ block_device = factory.make_PhysicalBlockDevice(node=node)
134+ data = {
135+ 'name': factory.make_name('name'),
136+ 'block_devices': [block_device.id],
137+ }
138+ form = CreateVMFSForm(node, data=data)
139+ self.assertFalse(form.is_valid())
140+ self.assertIn('VMFS6', form.errors)
141+
142 def test_creates_volume_group_with_block_devices(self):
143- node = factory.make_Node()
144+ node = make_Node_with_VMFS6_layout()
145 block_devices = [
146 factory.make_PhysicalBlockDevice(node=node)
147 for _ in range(3)
148@@ -117,7 +140,7 @@ class TestCreateVMFSForm(MAASServerTestCase):
149 ])
150
151 def test_creates_with_block_devices_by_name(self):
152- node = factory.make_Node()
153+ node = make_Node_with_VMFS6_layout()
154 block_devices = [
155 factory.make_PhysicalBlockDevice(node=node)
156 for _ in range(3)
157@@ -142,7 +165,7 @@ class TestCreateVMFSForm(MAASServerTestCase):
158 ])
159
160 def test_creates_with_partitions(self):
161- node = factory.make_Node()
162+ node = make_Node_with_VMFS6_layout()
163 block_device = factory.make_PhysicalBlockDevice(
164 node=node,
165 size=(MIN_BLOCK_DEVICE_SIZE * 3) + PARTITION_TABLE_EXTRA_SPACE)
166@@ -168,7 +191,7 @@ class TestCreateVMFSForm(MAASServerTestCase):
167 [fs.get_parent().id for fs in vmfs.filesystems.all()])
168
169 def test_creates_with_partitions_by_name(self):
170- node = factory.make_Node()
171+ node = make_Node_with_VMFS6_layout()
172 block_device = factory.make_PhysicalBlockDevice(
173 node=node,
174 size=(MIN_BLOCK_DEVICE_SIZE * 3) + PARTITION_TABLE_EXTRA_SPACE)
175@@ -228,7 +251,7 @@ class TestUpdateVMFSForm(MAASServerTestCase):
176 self.assertEqual(new_uuid, vmfs.uuid)
177
178 def test_adds_block_device(self):
179- node = factory.make_Node()
180+ node = make_Node_with_VMFS6_layout()
181 vmfs = factory.make_VMFS(node=node)
182 block_device = factory.make_PhysicalBlockDevice(node=node)
183 data = {
184@@ -243,7 +266,7 @@ class TestUpdateVMFSForm(MAASServerTestCase):
185 part.get_effective_filesystem().filesystem_group_id)
186
187 def test_adds_block_device_by_name(self):
188- node = factory.make_Node()
189+ node = make_Node_with_VMFS6_layout()
190 vmfs = factory.make_VMFS(node=node)
191 block_device = factory.make_PhysicalBlockDevice(node=node)
192 data = {
193@@ -258,7 +281,7 @@ class TestUpdateVMFSForm(MAASServerTestCase):
194 part.get_effective_filesystem().filesystem_group_id)
195
196 def test_adds_partition(self):
197- node = factory.make_Node()
198+ node = make_Node_with_VMFS6_layout()
199 vmfs = factory.make_VMFS(node=node)
200 block_device = factory.make_PhysicalBlockDevice(node=node)
201 partition_table = factory.make_PartitionTable(
202@@ -275,7 +298,7 @@ class TestUpdateVMFSForm(MAASServerTestCase):
203 partition.get_effective_filesystem().filesystem_group.id)
204
205 def test_adds_partition_by_name(self):
206- node = factory.make_Node()
207+ node = make_Node_with_VMFS6_layout()
208 vmfs = factory.make_VMFS(node=node)
209 block_device = factory.make_PhysicalBlockDevice(node=node)
210 partition_table = factory.make_PartitionTable(
211@@ -292,7 +315,7 @@ class TestUpdateVMFSForm(MAASServerTestCase):
212 partition.get_effective_filesystem().filesystem_group.id)
213
214 def test_removes_partition(self):
215- node = factory.make_Node()
216+ node = make_Node_with_VMFS6_layout()
217 vmfs = factory.make_VMFS(node=node)
218 block_device = factory.make_PhysicalBlockDevice(node=node)
219 partition_table = factory.make_PartitionTable(
220@@ -310,7 +333,7 @@ class TestUpdateVMFSForm(MAASServerTestCase):
221 self.assertIsNone(partition.get_effective_filesystem())
222
223 def test_removes_partition_by_name(self):
224- node = factory.make_Node()
225+ node = make_Node_with_VMFS6_layout()
226 vmfs = factory.make_VMFS(node=node)
227 block_device = factory.make_PhysicalBlockDevice(node=node)
228 partition_table = factory.make_PartitionTable(
229diff --git a/src/maasserver/models/node.py b/src/maasserver/models/node.py
230index 84fcfa3..4225ce5 100644
231--- a/src/maasserver/models/node.py
232+++ b/src/maasserver/models/node.py
233@@ -78,6 +78,7 @@ from maasserver.enum import (
234 ALLOCATED_NODE_STATUSES,
235 BMC_TYPE,
236 FILESYSTEM_FORMAT_TYPE_CHOICES_DICT,
237+ FILESYSTEM_GROUP_TYPE,
238 FILESYSTEM_TYPE,
239 INTERFACE_LINK_TYPE,
240 INTERFACE_TYPE,
241@@ -159,6 +160,7 @@ from maasserver.storage_layouts import (
242 get_storage_layout_for_node,
243 StorageLayoutError,
244 StorageLayoutMissingBootDiskError,
245+ VMFS6StorageLayout,
246 )
247 from maasserver.utils import synchronised
248 from maasserver.utils.dns import validate_hostname
249@@ -1367,6 +1369,22 @@ class Node(CleanSave, TimestampedModel):
250 if not has_boot:
251 issues.append(
252 "Specify a storage device to be able to deploy this node.")
253+ if self.osystem == 'esxi':
254+ # MAAS 2.6 added VMware ESXi storage support. To be backwards
255+ # compatible with previous versions of MAAS deploying with a Linux
256+ # layout is fine. In this case the default VMware ESXi storage
257+ # layout is created with a datastore. If the user applied the VMFS
258+ # storage layout a datastore must be defined as one will always be
259+ # created.
260+ vmfs_layout = VMFS6StorageLayout(self)
261+ if vmfs_layout.is_layout() is not None:
262+ fs_groups = self.virtualblockdevice_set.filter(
263+ filesystem_group__group_type=FILESYSTEM_GROUP_TYPE.VMFS6)
264+ if not fs_groups.exists():
265+ issues.append(
266+ "A datastore must be defined when deploying "
267+ "VMware ESXi.")
268+ return issues
269 # The remaining storage issue checks are only for Ubuntu, CentOS, and
270 # RHEL. All other osystems storage isn't supported or in ESXi's case
271 # we ignore unknown filesystems given.
272diff --git a/src/maasserver/models/tests/test_node.py b/src/maasserver/models/tests/test_node.py
273index 0c88692..a367a78 100644
274--- a/src/maasserver/models/tests/test_node.py
275+++ b/src/maasserver/models/tests/test_node.py
276@@ -7201,6 +7201,17 @@ class TestNode_Start(MAASTransactionServerTestCase):
277 layout.configure()
278 self.assertItemsEqual([], node.storage_layout_issues())
279
280+ def test_storage_layout_issues_is_invalid_no_datastore_on_esxi(self):
281+ node = factory.make_Node(
282+ osystem='esxi', distro_series='6.7', with_boot_disk=False)
283+ factory.make_PhysicalBlockDevice(node=node, size=(100 * 1024 ** 3))
284+ layout = VMFS6StorageLayout(node)
285+ layout.configure()
286+ node.virtualblockdevice_set.delete()
287+ self.assertEqual(
288+ ["A datastore must be defined when deploying VMware ESXi."],
289+ node.storage_layout_issues())
290+
291 def test_storage_layout_issues_vmfs_not_esxi(self):
292 node = factory.make_Node(
293 osystem=random.choice(['ubuntu', 'centos', 'rhel']),
294diff --git a/src/maasserver/websockets/handlers/tests/test_machine.py b/src/maasserver/websockets/handlers/tests/test_machine.py
295index 352fab6..5257e2a 100644
296--- a/src/maasserver/websockets/handlers/tests/test_machine.py
297+++ b/src/maasserver/websockets/handlers/tests/test_machine.py
298@@ -69,6 +69,7 @@ from maasserver.rbac import (
299 from maasserver.storage_layouts import (
300 get_applied_storage_layout_for_node,
301 get_storage_layout_choices,
302+ VMFS6StorageLayout,
303 )
304 from maasserver.testing.architecture import make_usable_architecture
305 from maasserver.testing.factory import factory
306@@ -81,6 +82,7 @@ from maasserver.testing.testcase import (
307 MAASServerTestCase,
308 MAASTransactionServerTestCase,
309 )
310+from maasserver.tests.test_storage_layouts import LARGE_BLOCK_DEVICE
311 from maasserver.third_party_drivers import get_third_party_driver
312 from maasserver.utils.converters import (
313 human_readable_bytes,
314@@ -2966,18 +2968,22 @@ class TestMachineHandler(MAASServerTestCase):
315 def test_create_vmfs_datastore(self):
316 user = factory.make_admin()
317 handler = MachineHandler(user, {}, None)
318- node = factory.make_Node()
319+ node = factory.make_Node(with_boot_disk=False)
320+ node.boot_disk = factory.make_PhysicalBlockDevice(
321+ node=node, size=LARGE_BLOCK_DEVICE)
322+ layout = VMFS6StorageLayout(node)
323+ layout.configure()
324 bd_ids = [
325 factory.make_PhysicalBlockDevice(node=node).id
326 for _ in range(3)
327 ]
328 params = {
329 'system_id': node.system_id,
330- 'name': 'datastore1',
331+ 'name': 'datastore2',
332 'block_devices': bd_ids,
333 }
334 handler.create_vmfs_datastore(params)
335- vbd = node.virtualblockdevice_set.get(name='datastore1')
336+ vbd = node.virtualblockdevice_set.get(name='datastore2')
337 vmfs = vbd.filesystem_group
338 self.assertItemsEqual(
339 bd_ids,

Subscribers

People subscribed via source and target branches