Merge lp:~blake-rouse/maas/fix-1630667 into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
Status: Merged
Approved by: Andres Rodriguez
Approved revision: no longer in the source branch.
Merged at revision: 5474
Proposed branch: lp:~blake-rouse/maas/fix-1630667
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 424 lines (+206/-16)
6 files modified
src/maasserver/models/partition.py (+12/-3)
src/maasserver/models/partitiontable.py (+9/-0)
src/maasserver/models/tests/test_partition.py (+25/-3)
src/maasserver/models/tests/test_partitiontable.py (+25/-0)
src/maasserver/preseed_storage.py (+51/-7)
src/maasserver/tests/test_preseed_storage.py (+84/-3)
To merge this branch: bzr merge lp:~blake-rouse/maas/fix-1630667
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+307965@code.launchpad.net

Commit message

Create bios_grub partition on boot disk when the machine is set to PXE boot and the boot disk is larger than 2TiB on amd64.

On i386 machines they cannot use more than a 2TiB disk so this is limited to only amd64. When the boot disk is larger than 2TiB the first partition placed on the disk is now bios_grub flagged partition. Grub will use this partition to place it stage 1 similar to how MBR works. This is only used for PXE booted machines, UEFI machines do not create this partition and still use the /boot/efi partition to place the bootloader.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Looks good to me.

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/partition.py'
2--- src/maasserver/models/partition.py 2016-05-12 19:07:37 +0000
3+++ src/maasserver/models/partition.py 2016-10-07 15:48:42 +0000
4@@ -172,21 +172,30 @@
5
6 def get_partition_number(self):
7 """Return the partition number in the table."""
8+ # Circular imports.
9+ from maasserver.models.partitiontable import GPT_REQUIRED_SIZE
10 # Sort manually instead of with `order_by`, this will prevent django
11 # from making a query if the partitions are already cached.
12 partitions_in_table = self.partition_table.partitions.all()
13 partitions_in_table = sorted(partitions_in_table, key=attrgetter('id'))
14 idx = partitions_in_table.index(self)
15 if self.partition_table.table_type == PARTITION_TABLE_TYPE.GPT:
16- # ppc64el machines get part1 skipped when this partition is on
17- # the boot disk. This is because the prep partition is part1 and
18- # is added when the preseed for storage is generated.
19+ # In some instances the first partition is skipped because it
20+ # is used by the machine architecture for a specific reason.
21+ # * ppc64el - reserved for prep partition
22+ # * amd64 (disk >= 2TiB) - reserved for bios_grub partition
23 node = self.get_node()
24 arch, _ = node.split_arch()
25 boot_disk = node.get_boot_disk()
26+ bios_boot_method = node.get_bios_boot_method()
27 if (arch == "ppc64el" and
28 self.partition_table.block_device.id == boot_disk.id):
29 return idx + 2
30+ elif (arch == "amd64" and
31+ self.partition_table.block_device.id == boot_disk.id and
32+ bios_boot_method != "uefi" and
33+ boot_disk.size >= GPT_REQUIRED_SIZE):
34+ return idx + 2
35 else:
36 return idx + 1
37 elif self.partition_table.table_type == PARTITION_TABLE_TYPE.MBR:
38
39=== modified file 'src/maasserver/models/partitiontable.py'
40--- src/maasserver/models/partitiontable.py 2016-10-03 18:57:27 +0000
41+++ src/maasserver/models/partitiontable.py 2016-10-07 15:48:42 +0000
42@@ -51,6 +51,11 @@
43 # is forced on the boot disk unless the disk is larger than 2TiB.
44 GPT_REQUIRED_SIZE = 2 * 1024 * 1024 * 1024 * 1024
45
46+# The amount of space required to be reserved for the bios_grub partition.
47+# bios_grub partition is required on amd64 architectures when grub is used
48+# on the boot disk and the disk is larger than GPT_REQUIRED_SIZE.
49+BIOS_GRUB_PARTITION_SIZE = 1 * 1024 * 1024 # 1MiB
50+
51
52 class PartitionTable(CleanSave, TimestampedModel):
53 """A partition table on a block device.
54@@ -90,6 +95,10 @@
55 node_arch, _ = self.block_device.node.split_arch()
56 if node_arch == "ppc64el":
57 extra_space += PREP_PARTITION_SIZE
58+ elif (node_arch == "amd64" and
59+ self.block_device.node.bios_boot_method != "uefi" and
60+ self.block_device.size >= GPT_REQUIRED_SIZE):
61+ extra_space += BIOS_GRUB_PARTITION_SIZE
62 return extra_space
63
64 def get_used_size(self, ignore_partitions=[]):
65
66=== modified file 'src/maasserver/models/tests/test_partition.py'
67--- src/maasserver/models/tests/test_partition.py 2016-05-12 19:07:37 +0000
68+++ src/maasserver/models/tests/test_partition.py 2016-10-07 15:48:42 +0000
69@@ -6,7 +6,6 @@
70 __all__ = []
71
72 import random
73-from unittest import skip
74 from unittest.mock import sentinel
75 from uuid import uuid4
76
77@@ -25,6 +24,7 @@
78 PARTITION_ALIGNMENT_SIZE,
79 )
80 from maasserver.models.partitiontable import (
81+ BIOS_GRUB_PARTITION_SIZE,
82 PARTITION_TABLE_EXTRA_SPACE,
83 PREP_PARTITION_SIZE,
84 )
85@@ -306,10 +306,10 @@
86 self.expectThat(idx, Equals(partition.get_partition_number()))
87 idx += 1
88
89- @skip("XXX: GavinPanella 2016-04-12 bug=1569365: Fails spuriously.")
90 def test_get_partition_number_returns_starting_at_2_for_ppc64el(self):
91 node = factory.make_Node(
92- architecture="ppc64el/generic", bios_boot_method="uefi")
93+ architecture="ppc64el/generic", bios_boot_method="uefi",
94+ with_boot_disk=False)
95 block_device = factory.make_PhysicalBlockDevice(
96 node=node,
97 size=(
98@@ -328,6 +328,28 @@
99 self.expectThat(idx, Equals(partition.get_partition_number()))
100 idx += 1
101
102+ def test_get_partition_number_returns_starting_at_2_for_amd64_gpt(self):
103+ node = factory.make_Node(
104+ architecture="amd64/generic", bios_boot_method="pxe",
105+ with_boot_disk=False)
106+ block_device = factory.make_PhysicalBlockDevice(
107+ node=node,
108+ size=(
109+ (2 * (1024 ** 4)) + PARTITION_TABLE_EXTRA_SPACE +
110+ BIOS_GRUB_PARTITION_SIZE))
111+ node.boot_disk = block_device
112+ node.save()
113+ partition_table = factory.make_PartitionTable(
114+ block_device=block_device, table_type=PARTITION_TABLE_TYPE.GPT)
115+ partitions = [
116+ partition_table.add_partition(size=MIN_BLOCK_DEVICE_SIZE)
117+ for _ in range(4)
118+ ]
119+ idx = 2
120+ for partition in partitions:
121+ self.expectThat(idx, Equals(partition.get_partition_number()))
122+ idx += 1
123+
124 def test_get_partition_number_returns_correct_numbering_for_mbr(self):
125 block_device = factory.make_PhysicalBlockDevice(
126 size=(MIN_BLOCK_DEVICE_SIZE * 6) + PARTITION_TABLE_EXTRA_SPACE)
127
128=== modified file 'src/maasserver/models/tests/test_partitiontable.py'
129--- src/maasserver/models/tests/test_partitiontable.py 2016-10-03 18:57:27 +0000
130+++ src/maasserver/models/tests/test_partitiontable.py 2016-10-07 15:48:42 +0000
131@@ -20,6 +20,7 @@
132 PARTITION_ALIGNMENT_SIZE,
133 )
134 from maasserver.models.partitiontable import (
135+ BIOS_GRUB_PARTITION_SIZE,
136 PARTITION_TABLE_EXTRA_SPACE,
137 PREP_PARTITION_SIZE,
138 )
139@@ -59,6 +60,20 @@
140 False),
141 partition_table.get_size())
142
143+ def test_get_size_returns_block_device_size_minus_amd64_gpt(self):
144+ node = factory.make_Node(architecture="amd64/generic")
145+ block_device = factory.make_PhysicalBlockDevice(
146+ node=node, size=2 * (1024 ** 4))
147+ partition_table = factory.make_PartitionTable(
148+ block_device=block_device)
149+ self.assertEqual(
150+ round_size_to_nearest_block(
151+ partition_table.block_device.size -
152+ PARTITION_TABLE_EXTRA_SPACE - BIOS_GRUB_PARTITION_SIZE,
153+ PARTITION_ALIGNMENT_SIZE,
154+ False),
155+ partition_table.get_size())
156+
157 def test_get_block_size_returns_block_device_block_size(self):
158 partition_table = factory.make_PartitionTable()
159 self.assertEqual(
160@@ -148,6 +163,16 @@
161 PARTITION_TABLE_EXTRA_SPACE + PREP_PARTITION_SIZE,
162 partition_table.get_overhead_size())
163
164+ def test_get_overhead_size_for_amd64_gpt(self):
165+ node = factory.make_Node(architecture="amd64/generic")
166+ block_device = factory.make_PhysicalBlockDevice(
167+ node=node, size=2 * (1024 ** 4))
168+ partition_table = factory.make_PartitionTable(
169+ block_device=block_device)
170+ self.assertEquals(
171+ PARTITION_TABLE_EXTRA_SPACE + BIOS_GRUB_PARTITION_SIZE,
172+ partition_table.get_overhead_size())
173+
174 def test_get_available_size(self):
175 block_size = 4096
176 device = factory.make_BlockDevice(
177
178=== modified file 'src/maasserver/preseed_storage.py'
179--- src/maasserver/preseed_storage.py 2016-10-04 18:37:44 +0000
180+++ src/maasserver/preseed_storage.py 2016-10-07 15:48:42 +0000
181@@ -17,6 +17,7 @@
182 )
183 from maasserver.models.partition import Partition
184 from maasserver.models.partitiontable import (
185+ BIOS_GRUB_PARTITION_SIZE,
186 GPT_REQUIRED_SIZE,
187 INITIAL_PARTITION_OFFSET,
188 PARTITION_TABLE_EXTRA_SPACE,
189@@ -118,6 +119,15 @@
190 self.boot_disk.id == block_device.id and
191 arch == "ppc64el")
192
193+ def _requires_bios_grub_partition(self, block_device):
194+ """Return True if block device requires the bios_grub partition."""
195+ arch, _ = self.node.split_arch()
196+ bios_boot_method = self.node.get_bios_boot_method()
197+ return (
198+ arch == "amd64" and
199+ bios_boot_method != "uefi" and
200+ block_device.size >= GPT_REQUIRED_SIZE)
201+
202 def _add_partition_operations(self):
203 """Add all the partition operations.
204
205@@ -126,14 +136,16 @@
206 """
207 for block_device in self.node.blockdevice_set.order_by('id'):
208 requires_prep = self._requires_prep_partition(block_device)
209+ requires_bios_grub = self._requires_bios_grub_partition(
210+ block_device)
211 partition_table = block_device.get_partitiontable()
212 if partition_table is not None:
213 partitions = list(partition_table.partitions.order_by('id'))
214 for idx, partition in enumerate(partitions):
215- # If this is the last partition and prep partition is
216- # required then set boot_disk_first_partition so extra
217- # space can be removed.
218- if requires_prep and idx == 0:
219+ # If this is the first partition and prep or bios_grub
220+ # partition is required then set boot_disk_first_partition
221+ # so partition creation can occur in the correct order.
222+ if (requires_prep or requires_bios_grub) and idx == 0:
223 self.boot_disk_first_partition = partition
224 self.operations["partition"].append(partition)
225
226@@ -195,6 +207,7 @@
227 # Set the partition table type if a partition table exists or if this
228 # is the boot disk.
229 add_prep_partition = False
230+ add_bios_grub_partition = False
231 partition_table = block_device.get_partitiontable()
232 if partition_table is not None:
233 disk_operation["ptable"] = self._get_ptable_type(
234@@ -207,8 +220,10 @@
235 disk_operation["ptable"] = "gpt"
236 if node_arch == "ppc64el":
237 add_prep_partition = True
238- elif block_device.size >= GPT_REQUIRED_SIZE:
239+ elif (block_device.size >= GPT_REQUIRED_SIZE and
240+ node_arch == "amd64"):
241 disk_operation["ptable"] = "gpt"
242+ add_bios_grub_partition = True
243 else:
244 disk_operation["ptable"] = "msdos"
245
246@@ -221,10 +236,16 @@
247 disk_operation["grub_device"] = True
248 self.storage_config.append(disk_operation)
249
250- # Add the prep partition at the end of the disk when it is required.
251+ # Add the prep partition at the beginning of the disk
252+ # when it is required.
253 if add_prep_partition:
254 self._generate_prep_partition(block_device.get_name())
255
256+ # Add the bios_grub partition at the beginning of the disk
257+ # when it is required.
258+ if add_bios_grub_partition:
259+ self._generate_bios_grub_partition(block_device.get_name())
260+
261 def _get_ptable_type(self, partition_table):
262 """Return the value for the "ptable" entry in the physical operation.
263 """
264@@ -254,6 +275,20 @@
265 }
266 self.storage_config.append(partition_operation)
267
268+ def _generate_bios_grub_partition(self, device_name):
269+ """Generate the bios_grub partition at the beginning of the device."""
270+ partition_operation = {
271+ "id": "%s-part1" % (device_name),
272+ "type": "partition",
273+ "number": 1,
274+ "offset": "%dB" % INITIAL_PARTITION_OFFSET,
275+ "size": "%dB" % BIOS_GRUB_PARTITION_SIZE,
276+ "device": device_name,
277+ "wipe": "zero",
278+ "flag": "bios_grub",
279+ }
280+ self.storage_config.append(partition_operation)
281+
282 def _generate_partition_operations(self):
283 """Generate all partition operations."""
284 for partition in self.operations["partition"]:
285@@ -261,7 +296,16 @@
286 # This is the first partition in the boot disk and add prep
287 # partition at the beginning of the partition table.
288 device_name = partition.partition_table.block_device.get_name()
289- self._generate_prep_partition(device_name)
290+ if self._requires_prep_partition(
291+ partition.partition_table.block_device):
292+ self._generate_prep_partition(device_name)
293+ elif self._requires_bios_grub_partition(
294+ partition.partition_table.block_device):
295+ self._generate_bios_grub_partition(device_name)
296+ else:
297+ raise ValueError(
298+ "boot_disk_first_partition set when prep and "
299+ "bios_grub partition are not required.")
300 self._generate_partition_operation(
301 partition, include_initial=False)
302 else:
303
304=== modified file 'src/maasserver/tests/test_preseed_storage.py'
305--- src/maasserver/tests/test_preseed_storage.py 2016-10-03 18:57:27 +0000
306+++ src/maasserver/tests/test_preseed_storage.py 2016-10-07 15:48:42 +0000
307@@ -22,6 +22,7 @@
308 VolumeGroup,
309 )
310 from maasserver.models.partitiontable import (
311+ BIOS_GRUB_PARTITION_SIZE,
312 PARTITION_TABLE_EXTRA_SPACE,
313 PREP_PARTITION_SIZE,
314 )
315@@ -546,6 +547,14 @@
316 ptable: gpt
317 path: /dev/disk/by-id/wwn-0x55cd2e400009bf84
318 grub_device: true
319+ - id: sdb-part1
320+ type: partition
321+ number: 1
322+ size: 1048576B
323+ device: sdb
324+ wipe: zero
325+ offset: 4194304B
326+ flag: bios_grub
327 - id: sda-part1
328 name: sda-part1
329 type: partition
330@@ -570,13 +579,14 @@
331
332 def test__renders_expected_output(self):
333 node = factory.make_Node(
334- status=NODE_STATUS.ALLOCATED, with_boot_disk=False)
335+ status=NODE_STATUS.ALLOCATED, architecture="amd64/generic",
336+ with_boot_disk=False)
337 first_disk = factory.make_PhysicalBlockDevice(
338 node=node, size=8 * 1024 ** 3, name="sda",
339 model="QEMU HARDDISK", serial="QM00001") # 8 GiB
340 boot_disk = factory.make_PhysicalBlockDevice(
341- node=node, size=8 * 1024 ** 4, name="sdb",
342- id_path="/dev/disk/by-id/wwn-0x55cd2e400009bf84")
343+ node=node, size=2 * 1024 ** 4, name="sdb",
344+ id_path="/dev/disk/by-id/wwn-0x55cd2e400009bf84") # 2 TiB
345 node.boot_disk = boot_disk
346 node.save()
347 partition_table = factory.make_PartitionTable(
348@@ -596,6 +606,77 @@
349 self.assertStorageConfig(self.STORAGE_CONFIG, config)
350
351
352+class TestGPTPXELargeBootDiskLayout(
353+ MAASServerTestCase, AssertStorageConfigMixin):
354+
355+ STORAGE_CONFIG = dedent("""\
356+ config:
357+ - id: sda
358+ name: sda
359+ type: disk
360+ wipe: superblock
361+ ptable: gpt
362+ model: QEMU HARDDISK
363+ serial: QM00001
364+ grub_device: true
365+ - id: sda-part1
366+ type: partition
367+ number: 1
368+ size: 1048576B
369+ device: sda
370+ wipe: zero
371+ offset: 4194304B
372+ flag: bios_grub
373+ - id: sda-part2
374+ name: sda-part2
375+ type: partition
376+ number: 2
377+ uuid: 6efc2c3d-bc9d-4ee5-a7ed-c6e1574d5398
378+ size: 8581545984B
379+ device: sda
380+ wipe: superblock
381+ - id: sda-part2_format
382+ type: format
383+ fstype: ext4
384+ label: root
385+ uuid: 90a69b22-e281-4c5b-8df9-b09514f27ba1
386+ volume: sda-part2
387+ - id: sda-part2_mount
388+ type: mount
389+ path: /
390+ options: rw,relatime,errors=remount-ro,data=journal
391+ device: sda-part2_format
392+ """)
393+
394+ def test__renders_expected_output(self):
395+ node = factory.make_Node(
396+ status=NODE_STATUS.ALLOCATED, architecture="amd64/generic",
397+ with_boot_disk=False)
398+ boot_disk = factory.make_PhysicalBlockDevice(
399+ node=node, size=2 * 1024 ** 4, name="sda",
400+ model="QEMU HARDDISK", serial="QM00001") # 2 TiB
401+ node.boot_disk = boot_disk
402+ node.save()
403+ partition_table = factory.make_PartitionTable(
404+ table_type=PARTITION_TABLE_TYPE.GPT, block_device=boot_disk)
405+ root_partition = factory.make_Partition(
406+ partition_table=partition_table,
407+ uuid="6efc2c3d-bc9d-4ee5-a7ed-c6e1574d5398",
408+ size=(
409+ (8 * 1024 ** 3) -
410+ PARTITION_TABLE_EXTRA_SPACE -
411+ BIOS_GRUB_PARTITION_SIZE),
412+ bootable=False)
413+ factory.make_Filesystem(
414+ partition=root_partition, fstype=FILESYSTEM_TYPE.EXT4,
415+ uuid="90a69b22-e281-4c5b-8df9-b09514f27ba1", label="root",
416+ mount_point="/", mount_options=(
417+ "rw,relatime,errors=remount-ro,data=journal"))
418+ node._create_acquired_filesystems()
419+ config = compose_curtin_storage_config(node)
420+ self.assertStorageConfig(self.STORAGE_CONFIG, config)
421+
422+
423 class TestComplexDiskLayout(
424 MAASServerTestCase, AssertStorageConfigMixin):
425