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
=== modified file 'src/maasserver/models/partition.py'
--- src/maasserver/models/partition.py 2016-05-12 19:07:37 +0000
+++ src/maasserver/models/partition.py 2016-10-07 15:48:42 +0000
@@ -172,21 +172,30 @@
172172
173 def get_partition_number(self):173 def get_partition_number(self):
174 """Return the partition number in the table."""174 """Return the partition number in the table."""
175 # Circular imports.
176 from maasserver.models.partitiontable import GPT_REQUIRED_SIZE
175 # Sort manually instead of with `order_by`, this will prevent django177 # Sort manually instead of with `order_by`, this will prevent django
176 # from making a query if the partitions are already cached.178 # from making a query if the partitions are already cached.
177 partitions_in_table = self.partition_table.partitions.all()179 partitions_in_table = self.partition_table.partitions.all()
178 partitions_in_table = sorted(partitions_in_table, key=attrgetter('id'))180 partitions_in_table = sorted(partitions_in_table, key=attrgetter('id'))
179 idx = partitions_in_table.index(self)181 idx = partitions_in_table.index(self)
180 if self.partition_table.table_type == PARTITION_TABLE_TYPE.GPT:182 if self.partition_table.table_type == PARTITION_TABLE_TYPE.GPT:
181 # ppc64el machines get part1 skipped when this partition is on183 # In some instances the first partition is skipped because it
182 # the boot disk. This is because the prep partition is part1 and184 # is used by the machine architecture for a specific reason.
183 # is added when the preseed for storage is generated.185 # * ppc64el - reserved for prep partition
186 # * amd64 (disk >= 2TiB) - reserved for bios_grub partition
184 node = self.get_node()187 node = self.get_node()
185 arch, _ = node.split_arch()188 arch, _ = node.split_arch()
186 boot_disk = node.get_boot_disk()189 boot_disk = node.get_boot_disk()
190 bios_boot_method = node.get_bios_boot_method()
187 if (arch == "ppc64el" and191 if (arch == "ppc64el" and
188 self.partition_table.block_device.id == boot_disk.id):192 self.partition_table.block_device.id == boot_disk.id):
189 return idx + 2193 return idx + 2
194 elif (arch == "amd64" and
195 self.partition_table.block_device.id == boot_disk.id and
196 bios_boot_method != "uefi" and
197 boot_disk.size >= GPT_REQUIRED_SIZE):
198 return idx + 2
190 else:199 else:
191 return idx + 1200 return idx + 1
192 elif self.partition_table.table_type == PARTITION_TABLE_TYPE.MBR:201 elif self.partition_table.table_type == PARTITION_TABLE_TYPE.MBR:
193202
=== modified file 'src/maasserver/models/partitiontable.py'
--- src/maasserver/models/partitiontable.py 2016-10-03 18:57:27 +0000
+++ src/maasserver/models/partitiontable.py 2016-10-07 15:48:42 +0000
@@ -51,6 +51,11 @@
51# is forced on the boot disk unless the disk is larger than 2TiB.51# is forced on the boot disk unless the disk is larger than 2TiB.
52GPT_REQUIRED_SIZE = 2 * 1024 * 1024 * 1024 * 102452GPT_REQUIRED_SIZE = 2 * 1024 * 1024 * 1024 * 1024
5353
54# The amount of space required to be reserved for the bios_grub partition.
55# bios_grub partition is required on amd64 architectures when grub is used
56# on the boot disk and the disk is larger than GPT_REQUIRED_SIZE.
57BIOS_GRUB_PARTITION_SIZE = 1 * 1024 * 1024 # 1MiB
58
5459
55class PartitionTable(CleanSave, TimestampedModel):60class PartitionTable(CleanSave, TimestampedModel):
56 """A partition table on a block device.61 """A partition table on a block device.
@@ -90,6 +95,10 @@
90 node_arch, _ = self.block_device.node.split_arch()95 node_arch, _ = self.block_device.node.split_arch()
91 if node_arch == "ppc64el":96 if node_arch == "ppc64el":
92 extra_space += PREP_PARTITION_SIZE97 extra_space += PREP_PARTITION_SIZE
98 elif (node_arch == "amd64" and
99 self.block_device.node.bios_boot_method != "uefi" and
100 self.block_device.size >= GPT_REQUIRED_SIZE):
101 extra_space += BIOS_GRUB_PARTITION_SIZE
93 return extra_space102 return extra_space
94103
95 def get_used_size(self, ignore_partitions=[]):104 def get_used_size(self, ignore_partitions=[]):
96105
=== modified file 'src/maasserver/models/tests/test_partition.py'
--- src/maasserver/models/tests/test_partition.py 2016-05-12 19:07:37 +0000
+++ src/maasserver/models/tests/test_partition.py 2016-10-07 15:48:42 +0000
@@ -6,7 +6,6 @@
6__all__ = []6__all__ = []
77
8import random8import random
9from unittest import skip
10from unittest.mock import sentinel9from unittest.mock import sentinel
11from uuid import uuid410from uuid import uuid4
1211
@@ -25,6 +24,7 @@
25 PARTITION_ALIGNMENT_SIZE,24 PARTITION_ALIGNMENT_SIZE,
26)25)
27from maasserver.models.partitiontable import (26from maasserver.models.partitiontable import (
27 BIOS_GRUB_PARTITION_SIZE,
28 PARTITION_TABLE_EXTRA_SPACE,28 PARTITION_TABLE_EXTRA_SPACE,
29 PREP_PARTITION_SIZE,29 PREP_PARTITION_SIZE,
30)30)
@@ -306,10 +306,10 @@
306 self.expectThat(idx, Equals(partition.get_partition_number()))306 self.expectThat(idx, Equals(partition.get_partition_number()))
307 idx += 1307 idx += 1
308308
309 @skip("XXX: GavinPanella 2016-04-12 bug=1569365: Fails spuriously.")
310 def test_get_partition_number_returns_starting_at_2_for_ppc64el(self):309 def test_get_partition_number_returns_starting_at_2_for_ppc64el(self):
311 node = factory.make_Node(310 node = factory.make_Node(
312 architecture="ppc64el/generic", bios_boot_method="uefi")311 architecture="ppc64el/generic", bios_boot_method="uefi",
312 with_boot_disk=False)
313 block_device = factory.make_PhysicalBlockDevice(313 block_device = factory.make_PhysicalBlockDevice(
314 node=node,314 node=node,
315 size=(315 size=(
@@ -328,6 +328,28 @@
328 self.expectThat(idx, Equals(partition.get_partition_number()))328 self.expectThat(idx, Equals(partition.get_partition_number()))
329 idx += 1329 idx += 1
330330
331 def test_get_partition_number_returns_starting_at_2_for_amd64_gpt(self):
332 node = factory.make_Node(
333 architecture="amd64/generic", bios_boot_method="pxe",
334 with_boot_disk=False)
335 block_device = factory.make_PhysicalBlockDevice(
336 node=node,
337 size=(
338 (2 * (1024 ** 4)) + PARTITION_TABLE_EXTRA_SPACE +
339 BIOS_GRUB_PARTITION_SIZE))
340 node.boot_disk = block_device
341 node.save()
342 partition_table = factory.make_PartitionTable(
343 block_device=block_device, table_type=PARTITION_TABLE_TYPE.GPT)
344 partitions = [
345 partition_table.add_partition(size=MIN_BLOCK_DEVICE_SIZE)
346 for _ in range(4)
347 ]
348 idx = 2
349 for partition in partitions:
350 self.expectThat(idx, Equals(partition.get_partition_number()))
351 idx += 1
352
331 def test_get_partition_number_returns_correct_numbering_for_mbr(self):353 def test_get_partition_number_returns_correct_numbering_for_mbr(self):
332 block_device = factory.make_PhysicalBlockDevice(354 block_device = factory.make_PhysicalBlockDevice(
333 size=(MIN_BLOCK_DEVICE_SIZE * 6) + PARTITION_TABLE_EXTRA_SPACE)355 size=(MIN_BLOCK_DEVICE_SIZE * 6) + PARTITION_TABLE_EXTRA_SPACE)
334356
=== modified file 'src/maasserver/models/tests/test_partitiontable.py'
--- src/maasserver/models/tests/test_partitiontable.py 2016-10-03 18:57:27 +0000
+++ src/maasserver/models/tests/test_partitiontable.py 2016-10-07 15:48:42 +0000
@@ -20,6 +20,7 @@
20 PARTITION_ALIGNMENT_SIZE,20 PARTITION_ALIGNMENT_SIZE,
21)21)
22from maasserver.models.partitiontable import (22from maasserver.models.partitiontable import (
23 BIOS_GRUB_PARTITION_SIZE,
23 PARTITION_TABLE_EXTRA_SPACE,24 PARTITION_TABLE_EXTRA_SPACE,
24 PREP_PARTITION_SIZE,25 PREP_PARTITION_SIZE,
25)26)
@@ -59,6 +60,20 @@
59 False),60 False),
60 partition_table.get_size())61 partition_table.get_size())
6162
63 def test_get_size_returns_block_device_size_minus_amd64_gpt(self):
64 node = factory.make_Node(architecture="amd64/generic")
65 block_device = factory.make_PhysicalBlockDevice(
66 node=node, size=2 * (1024 ** 4))
67 partition_table = factory.make_PartitionTable(
68 block_device=block_device)
69 self.assertEqual(
70 round_size_to_nearest_block(
71 partition_table.block_device.size -
72 PARTITION_TABLE_EXTRA_SPACE - BIOS_GRUB_PARTITION_SIZE,
73 PARTITION_ALIGNMENT_SIZE,
74 False),
75 partition_table.get_size())
76
62 def test_get_block_size_returns_block_device_block_size(self):77 def test_get_block_size_returns_block_device_block_size(self):
63 partition_table = factory.make_PartitionTable()78 partition_table = factory.make_PartitionTable()
64 self.assertEqual(79 self.assertEqual(
@@ -148,6 +163,16 @@
148 PARTITION_TABLE_EXTRA_SPACE + PREP_PARTITION_SIZE,163 PARTITION_TABLE_EXTRA_SPACE + PREP_PARTITION_SIZE,
149 partition_table.get_overhead_size())164 partition_table.get_overhead_size())
150165
166 def test_get_overhead_size_for_amd64_gpt(self):
167 node = factory.make_Node(architecture="amd64/generic")
168 block_device = factory.make_PhysicalBlockDevice(
169 node=node, size=2 * (1024 ** 4))
170 partition_table = factory.make_PartitionTable(
171 block_device=block_device)
172 self.assertEquals(
173 PARTITION_TABLE_EXTRA_SPACE + BIOS_GRUB_PARTITION_SIZE,
174 partition_table.get_overhead_size())
175
151 def test_get_available_size(self):176 def test_get_available_size(self):
152 block_size = 4096177 block_size = 4096
153 device = factory.make_BlockDevice(178 device = factory.make_BlockDevice(
154179
=== modified file 'src/maasserver/preseed_storage.py'
--- src/maasserver/preseed_storage.py 2016-10-04 18:37:44 +0000
+++ src/maasserver/preseed_storage.py 2016-10-07 15:48:42 +0000
@@ -17,6 +17,7 @@
17)17)
18from maasserver.models.partition import Partition18from maasserver.models.partition import Partition
19from maasserver.models.partitiontable import (19from maasserver.models.partitiontable import (
20 BIOS_GRUB_PARTITION_SIZE,
20 GPT_REQUIRED_SIZE,21 GPT_REQUIRED_SIZE,
21 INITIAL_PARTITION_OFFSET,22 INITIAL_PARTITION_OFFSET,
22 PARTITION_TABLE_EXTRA_SPACE,23 PARTITION_TABLE_EXTRA_SPACE,
@@ -118,6 +119,15 @@
118 self.boot_disk.id == block_device.id and119 self.boot_disk.id == block_device.id and
119 arch == "ppc64el")120 arch == "ppc64el")
120121
122 def _requires_bios_grub_partition(self, block_device):
123 """Return True if block device requires the bios_grub partition."""
124 arch, _ = self.node.split_arch()
125 bios_boot_method = self.node.get_bios_boot_method()
126 return (
127 arch == "amd64" and
128 bios_boot_method != "uefi" and
129 block_device.size >= GPT_REQUIRED_SIZE)
130
121 def _add_partition_operations(self):131 def _add_partition_operations(self):
122 """Add all the partition operations.132 """Add all the partition operations.
123133
@@ -126,14 +136,16 @@
126 """136 """
127 for block_device in self.node.blockdevice_set.order_by('id'):137 for block_device in self.node.blockdevice_set.order_by('id'):
128 requires_prep = self._requires_prep_partition(block_device)138 requires_prep = self._requires_prep_partition(block_device)
139 requires_bios_grub = self._requires_bios_grub_partition(
140 block_device)
129 partition_table = block_device.get_partitiontable()141 partition_table = block_device.get_partitiontable()
130 if partition_table is not None:142 if partition_table is not None:
131 partitions = list(partition_table.partitions.order_by('id'))143 partitions = list(partition_table.partitions.order_by('id'))
132 for idx, partition in enumerate(partitions):144 for idx, partition in enumerate(partitions):
133 # If this is the last partition and prep partition is145 # If this is the first partition and prep or bios_grub
134 # required then set boot_disk_first_partition so extra146 # partition is required then set boot_disk_first_partition
135 # space can be removed.147 # so partition creation can occur in the correct order.
136 if requires_prep and idx == 0:148 if (requires_prep or requires_bios_grub) and idx == 0:
137 self.boot_disk_first_partition = partition149 self.boot_disk_first_partition = partition
138 self.operations["partition"].append(partition)150 self.operations["partition"].append(partition)
139151
@@ -195,6 +207,7 @@
195 # Set the partition table type if a partition table exists or if this207 # Set the partition table type if a partition table exists or if this
196 # is the boot disk.208 # is the boot disk.
197 add_prep_partition = False209 add_prep_partition = False
210 add_bios_grub_partition = False
198 partition_table = block_device.get_partitiontable()211 partition_table = block_device.get_partitiontable()
199 if partition_table is not None:212 if partition_table is not None:
200 disk_operation["ptable"] = self._get_ptable_type(213 disk_operation["ptable"] = self._get_ptable_type(
@@ -207,8 +220,10 @@
207 disk_operation["ptable"] = "gpt"220 disk_operation["ptable"] = "gpt"
208 if node_arch == "ppc64el":221 if node_arch == "ppc64el":
209 add_prep_partition = True222 add_prep_partition = True
210 elif block_device.size >= GPT_REQUIRED_SIZE:223 elif (block_device.size >= GPT_REQUIRED_SIZE and
224 node_arch == "amd64"):
211 disk_operation["ptable"] = "gpt"225 disk_operation["ptable"] = "gpt"
226 add_bios_grub_partition = True
212 else:227 else:
213 disk_operation["ptable"] = "msdos"228 disk_operation["ptable"] = "msdos"
214229
@@ -221,10 +236,16 @@
221 disk_operation["grub_device"] = True236 disk_operation["grub_device"] = True
222 self.storage_config.append(disk_operation)237 self.storage_config.append(disk_operation)
223238
224 # Add the prep partition at the end of the disk when it is required.239 # Add the prep partition at the beginning of the disk
240 # when it is required.
225 if add_prep_partition:241 if add_prep_partition:
226 self._generate_prep_partition(block_device.get_name())242 self._generate_prep_partition(block_device.get_name())
227243
244 # Add the bios_grub partition at the beginning of the disk
245 # when it is required.
246 if add_bios_grub_partition:
247 self._generate_bios_grub_partition(block_device.get_name())
248
228 def _get_ptable_type(self, partition_table):249 def _get_ptable_type(self, partition_table):
229 """Return the value for the "ptable" entry in the physical operation.250 """Return the value for the "ptable" entry in the physical operation.
230 """251 """
@@ -254,6 +275,20 @@
254 }275 }
255 self.storage_config.append(partition_operation)276 self.storage_config.append(partition_operation)
256277
278 def _generate_bios_grub_partition(self, device_name):
279 """Generate the bios_grub partition at the beginning of the device."""
280 partition_operation = {
281 "id": "%s-part1" % (device_name),
282 "type": "partition",
283 "number": 1,
284 "offset": "%dB" % INITIAL_PARTITION_OFFSET,
285 "size": "%dB" % BIOS_GRUB_PARTITION_SIZE,
286 "device": device_name,
287 "wipe": "zero",
288 "flag": "bios_grub",
289 }
290 self.storage_config.append(partition_operation)
291
257 def _generate_partition_operations(self):292 def _generate_partition_operations(self):
258 """Generate all partition operations."""293 """Generate all partition operations."""
259 for partition in self.operations["partition"]:294 for partition in self.operations["partition"]:
@@ -261,7 +296,16 @@
261 # This is the first partition in the boot disk and add prep296 # This is the first partition in the boot disk and add prep
262 # partition at the beginning of the partition table.297 # partition at the beginning of the partition table.
263 device_name = partition.partition_table.block_device.get_name()298 device_name = partition.partition_table.block_device.get_name()
264 self._generate_prep_partition(device_name)299 if self._requires_prep_partition(
300 partition.partition_table.block_device):
301 self._generate_prep_partition(device_name)
302 elif self._requires_bios_grub_partition(
303 partition.partition_table.block_device):
304 self._generate_bios_grub_partition(device_name)
305 else:
306 raise ValueError(
307 "boot_disk_first_partition set when prep and "
308 "bios_grub partition are not required.")
265 self._generate_partition_operation(309 self._generate_partition_operation(
266 partition, include_initial=False)310 partition, include_initial=False)
267 else:311 else:
268312
=== modified file 'src/maasserver/tests/test_preseed_storage.py'
--- src/maasserver/tests/test_preseed_storage.py 2016-10-03 18:57:27 +0000
+++ src/maasserver/tests/test_preseed_storage.py 2016-10-07 15:48:42 +0000
@@ -22,6 +22,7 @@
22 VolumeGroup,22 VolumeGroup,
23)23)
24from maasserver.models.partitiontable import (24from maasserver.models.partitiontable import (
25 BIOS_GRUB_PARTITION_SIZE,
25 PARTITION_TABLE_EXTRA_SPACE,26 PARTITION_TABLE_EXTRA_SPACE,
26 PREP_PARTITION_SIZE,27 PREP_PARTITION_SIZE,
27)28)
@@ -546,6 +547,14 @@
546 ptable: gpt547 ptable: gpt
547 path: /dev/disk/by-id/wwn-0x55cd2e400009bf84548 path: /dev/disk/by-id/wwn-0x55cd2e400009bf84
548 grub_device: true549 grub_device: true
550 - id: sdb-part1
551 type: partition
552 number: 1
553 size: 1048576B
554 device: sdb
555 wipe: zero
556 offset: 4194304B
557 flag: bios_grub
549 - id: sda-part1558 - id: sda-part1
550 name: sda-part1559 name: sda-part1
551 type: partition560 type: partition
@@ -570,13 +579,14 @@
570579
571 def test__renders_expected_output(self):580 def test__renders_expected_output(self):
572 node = factory.make_Node(581 node = factory.make_Node(
573 status=NODE_STATUS.ALLOCATED, with_boot_disk=False)582 status=NODE_STATUS.ALLOCATED, architecture="amd64/generic",
583 with_boot_disk=False)
574 first_disk = factory.make_PhysicalBlockDevice(584 first_disk = factory.make_PhysicalBlockDevice(
575 node=node, size=8 * 1024 ** 3, name="sda",585 node=node, size=8 * 1024 ** 3, name="sda",
576 model="QEMU HARDDISK", serial="QM00001") # 8 GiB586 model="QEMU HARDDISK", serial="QM00001") # 8 GiB
577 boot_disk = factory.make_PhysicalBlockDevice(587 boot_disk = factory.make_PhysicalBlockDevice(
578 node=node, size=8 * 1024 ** 4, name="sdb",588 node=node, size=2 * 1024 ** 4, name="sdb",
579 id_path="/dev/disk/by-id/wwn-0x55cd2e400009bf84")589 id_path="/dev/disk/by-id/wwn-0x55cd2e400009bf84") # 2 TiB
580 node.boot_disk = boot_disk590 node.boot_disk = boot_disk
581 node.save()591 node.save()
582 partition_table = factory.make_PartitionTable(592 partition_table = factory.make_PartitionTable(
@@ -596,6 +606,77 @@
596 self.assertStorageConfig(self.STORAGE_CONFIG, config)606 self.assertStorageConfig(self.STORAGE_CONFIG, config)
597607
598608
609class TestGPTPXELargeBootDiskLayout(
610 MAASServerTestCase, AssertStorageConfigMixin):
611
612 STORAGE_CONFIG = dedent("""\
613 config:
614 - id: sda
615 name: sda
616 type: disk
617 wipe: superblock
618 ptable: gpt
619 model: QEMU HARDDISK
620 serial: QM00001
621 grub_device: true
622 - id: sda-part1
623 type: partition
624 number: 1
625 size: 1048576B
626 device: sda
627 wipe: zero
628 offset: 4194304B
629 flag: bios_grub
630 - id: sda-part2
631 name: sda-part2
632 type: partition
633 number: 2
634 uuid: 6efc2c3d-bc9d-4ee5-a7ed-c6e1574d5398
635 size: 8581545984B
636 device: sda
637 wipe: superblock
638 - id: sda-part2_format
639 type: format
640 fstype: ext4
641 label: root
642 uuid: 90a69b22-e281-4c5b-8df9-b09514f27ba1
643 volume: sda-part2
644 - id: sda-part2_mount
645 type: mount
646 path: /
647 options: rw,relatime,errors=remount-ro,data=journal
648 device: sda-part2_format
649 """)
650
651 def test__renders_expected_output(self):
652 node = factory.make_Node(
653 status=NODE_STATUS.ALLOCATED, architecture="amd64/generic",
654 with_boot_disk=False)
655 boot_disk = factory.make_PhysicalBlockDevice(
656 node=node, size=2 * 1024 ** 4, name="sda",
657 model="QEMU HARDDISK", serial="QM00001") # 2 TiB
658 node.boot_disk = boot_disk
659 node.save()
660 partition_table = factory.make_PartitionTable(
661 table_type=PARTITION_TABLE_TYPE.GPT, block_device=boot_disk)
662 root_partition = factory.make_Partition(
663 partition_table=partition_table,
664 uuid="6efc2c3d-bc9d-4ee5-a7ed-c6e1574d5398",
665 size=(
666 (8 * 1024 ** 3) -
667 PARTITION_TABLE_EXTRA_SPACE -
668 BIOS_GRUB_PARTITION_SIZE),
669 bootable=False)
670 factory.make_Filesystem(
671 partition=root_partition, fstype=FILESYSTEM_TYPE.EXT4,
672 uuid="90a69b22-e281-4c5b-8df9-b09514f27ba1", label="root",
673 mount_point="/", mount_options=(
674 "rw,relatime,errors=remount-ro,data=journal"))
675 node._create_acquired_filesystems()
676 config = compose_curtin_storage_config(node)
677 self.assertStorageConfig(self.STORAGE_CONFIG, config)
678
679
599class TestComplexDiskLayout(680class TestComplexDiskLayout(
600 MAASServerTestCase, AssertStorageConfigMixin):681 MAASServerTestCase, AssertStorageConfigMixin):
601682