Merge ~smoser/cloud-init:bug/1692087-disk_setup-gpt-improvements into cloud-init:master

Proposed by Scott Moser on 2017-05-19
Status: Merged
Merged at revision: 3507b59eaa4914ba041f9c7ae987a2cfb036d8b5
Proposed branch: ~smoser/cloud-init:bug/1692087-disk_setup-gpt-improvements
Merge into: cloud-init:master
Diff against target: 151 lines (+46/-36)
2 files modified
cloudinit/config/cc_disk_setup.py (+45/-35)
tests/unittests/test_handler/test_handler_disk_setup.py (+1/-1)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve on 2017-05-22
Ryan Harper 2017-05-19 Approve on 2017-05-22
Review via email: mp+324338@code.launchpad.net

Commit Message

disk_setup: fix several issues with gpt disk partitions.

This fixes several shortcomings of disk_setup with gpt disks.
 * 'sgdisk -p' was being used to determine the size of a disk.
   this can fail if it believes there is a bad gpt partition table.
   Instead we just use blockdev now for both mbr or gpt disks.
 * parsing of sgdisk -p output assumed that the 'name' of the partition
   type would not have any spaces (Microsoft basic data)
 * interaction with sgdisk did not realize that sgdisk wants input
   of '8300' rather than '83' and will output the same.

LP: #1692087

To post a comment you must log in.
Ryan Harper (raharper) :
Scott Moser (smoser) wrote :

I generally agree with your comments.
The code needs overhaul and better tests.

I'm trying to avoid doing too much to it now.

Ryan Harper (raharper) wrote :

This is fine as it is.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/config/cc_disk_setup.py b/cloudinit/config/cc_disk_setup.py
2index 29eb5dd..e1505b3 100644
3--- a/cloudinit/config/cc_disk_setup.py
4+++ b/cloudinit/config/cc_disk_setup.py
5@@ -431,7 +431,7 @@ def get_dyn_func(*args):
6 raise Exception("No such function %s to call!" % func_name)
7
8
9-def get_mbr_hdd_size(device):
10+def get_hdd_size(device):
11 try:
12 size_in_bytes, _ = util.subp([BLKDEV_CMD, '--getsize64', device])
13 sector_size, _ = util.subp([BLKDEV_CMD, '--getss', device])
14@@ -441,22 +441,6 @@ def get_mbr_hdd_size(device):
15 return int(size_in_bytes) / int(sector_size)
16
17
18-def get_gpt_hdd_size(device):
19- out, _ = util.subp([SGDISK_CMD, '-p', device], update_env=LANG_C_ENV)
20- for line in out.splitlines():
21- if line.startswith("Disk"):
22- return line.split()[2]
23- raise Exception("Failed to get %s size from sgdisk" % (device))
24-
25-
26-def get_hdd_size(table_type, device):
27- """
28- Returns the hard disk size.
29- This works with any disk type, including GPT.
30- """
31- return get_dyn_func("get_%s_hdd_size", table_type, device)
32-
33-
34 def check_partition_mbr_layout(device, layout):
35 """
36 Returns true if the partition layout matches the one on the disk
37@@ -504,12 +488,35 @@ def check_partition_gpt_layout(device, layout):
38 device, e))
39
40 out_lines = iter(out.splitlines())
41- # Skip header
42+ # Skip header. Output looks like:
43+ # ***************************************************************
44+ # Found invalid GPT and valid MBR; converting MBR to GPT format
45+ # in memory.
46+ # ***************************************************************
47+ #
48+ # Disk /dev/vdb: 83886080 sectors, 40.0 GiB
49+ # Logical sector size: 512 bytes
50+ # Disk identifier (GUID): 8A7F11AD-3953-491B-8051-077E01C8E9A7
51+ # Partition table holds up to 128 entries
52+ # First usable sector is 34, last usable sector is 83886046
53+ # Partitions will be aligned on 2048-sector boundaries
54+ # Total free space is 83476413 sectors (39.8 GiB)
55+ #
56+ # Number Start (sector) End (sector) Size Code Name
57+ # 1 2048 206847 100.0 MiB 0700 Microsoft basic data
58 for line in out_lines:
59 if line.strip().startswith('Number'):
60 break
61
62- return [line.strip().split()[-1] for line in out_lines]
63+ codes = [line.strip().split()[5] for line in out_lines]
64+ cleaned = []
65+
66+ # user would expect a code '83' to be Linux, but sgdisk outputs 8300.
67+ for code in codes:
68+ if len(code) == 4 and code.endswith("00"):
69+ code = code[0:2]
70+ cleaned.append(code)
71+ return cleaned
72
73
74 def check_partition_layout(table_type, device, layout):
75@@ -523,6 +530,8 @@ def check_partition_layout(table_type, device, layout):
76 found_layout = get_dyn_func(
77 "check_partition_%s_layout", table_type, device, layout)
78
79+ LOG.debug("called check_partition_%s_layout(%s, %s), returned: %s",
80+ table_type, device, layout, found_layout)
81 if isinstance(layout, bool):
82 # if we are using auto partitioning, or "True" be happy
83 # if a single partition exists.
84@@ -530,18 +539,17 @@ def check_partition_layout(table_type, device, layout):
85 return True
86 return False
87
88- else:
89- if len(found_layout) != len(layout):
90- return False
91- else:
92- # This just makes sure that the number of requested
93- # partitions and the type labels are right
94- for x in range(1, len(layout) + 1):
95- if isinstance(layout[x - 1], tuple):
96- _, part_type = layout[x]
97- if int(found_layout[x]) != int(part_type):
98- return False
99- return True
100+ elif len(found_layout) == len(layout):
101+ # This just makes sure that the number of requested
102+ # partitions and the type labels are right
103+ layout_types = [str(x[1]) if isinstance(x, (tuple, list)) else None
104+ for x in layout]
105+ LOG.debug("Layout types=%s. Found types=%s",
106+ layout_types, found_layout)
107+ for itype, ftype in zip(layout_types, found_layout):
108+ if itype is not None and str(ftype) != str(itype):
109+ return False
110+ return True
111
112 return False
113
114@@ -704,9 +712,11 @@ def exec_mkpart_gpt(device, layout):
115 util.subp([SGDISK_CMD,
116 '-n', '{}:{}:{}'.format(index, start, end), device])
117 if partition_type is not None:
118+ # convert to a 4 char (or more) string right padded with 0
119+ # 82 -> 8200. 'Linux' -> 'Linux'
120+ pinput = str(partition_type).ljust(4, "0")
121 util.subp(
122- [SGDISK_CMD,
123- '-t', '{}:{}'.format(index, partition_type), device])
124+ [SGDISK_CMD, '-t', '{}:{}'.format(index, pinput), device])
125 except Exception:
126 LOG.warning("Failed to partition device %s", device)
127 raise
128@@ -777,8 +787,8 @@ def mkpart(device, definition):
129 LOG.debug("Skipping partitioning on configured device %s", device)
130 return
131
132- LOG.debug("Checking for device size")
133- device_size = get_hdd_size(table_type, device)
134+ LOG.debug("Checking for device size of %s", device)
135+ device_size = get_hdd_size(device)
136
137 LOG.debug("Calculating partition layout")
138 part_definition = get_partition_layout(table_type, device_size, layout)
139diff --git a/tests/unittests/test_handler/test_handler_disk_setup.py b/tests/unittests/test_handler/test_handler_disk_setup.py
140index 35dff3e..e322697 100644
141--- a/tests/unittests/test_handler/test_handler_disk_setup.py
142+++ b/tests/unittests/test_handler/test_handler_disk_setup.py
143@@ -66,7 +66,7 @@ class TestGetMbrHddSize(TestCase):
144 size_in_sectors = size_in_bytes / sector_size
145 self._configure_subp_mock(size_in_bytes, sector_size)
146 self.assertEqual(size_in_sectors,
147- cc_disk_setup.get_mbr_hdd_size('/dev/sda1'))
148+ cc_disk_setup.get_hdd_size('/dev/sda1'))
149
150 def test_size_for_512_byte_sectors(self):
151 self._test_for_sector_size(512)

Subscribers

People subscribed via source and target branches

to all changes: