Merge ~mwhudson/curtin:partition-verify-dasd into curtin:master
- Git
- lp:~mwhudson/curtin
- partition-verify-dasd
- Merge into master
Status: | Merged |
---|---|
Approved by: | Michael Hudson-Doyle |
Approved revision: | 0dafa72b72cf1fa6dab73230274e8c18a0442ce9 |
Merge reported by: | Server Team CI bot |
Merged at revision: | not available |
Proposed branch: | ~mwhudson/curtin:partition-verify-dasd |
Merge into: | curtin:master |
Diff against target: |
402 lines (+160/-117) 4 files modified
curtin/block/dasd.py (+90/-103) curtin/commands/block_meta.py (+22/-4) tests/unittests/test_block_dasd.py (+0/-5) tests/unittests/test_commands_block_meta.py (+48/-5) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
Dimitri John Ledkov (community) | Approve | ||
curtin developers | Pending | ||
Review via email: mp+393791@code.launchpad.net |
Commit message
fix verification of vtoc partitions
Currently curtin verifies the size and flags of a partition by
using sfdisk to inspect it and checking the results. But that
doesn't work on a dasd's vtoc partition table, so this branch
renames parition_verify to parition_
parition_
'vtoc'. The branch does a little more than is strictly necessary,
perhaps, but the other stuff will come in useful soon, I promise
:)
LP: #1899471
Description of the change
Server Team CI bot (server-team-bot) wrote : | # |
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:e6073221da5
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:a6f1f32c00e
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:653bb0fd959
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Dimitri John Ledkov (xnox) wrote : | # |
This is fine refactor.
Michael Hudson-Doyle (mwhudson) wrote : | # |
Thanks for the review. I actually think there is another branch I need to land before this one, let me get that up for review pronto.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:0dafa72b72c
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
Commit message lints:
- Line #2 has 360 too many characters. Line starts with: "Currently curtin verifies"...
Server Team CI bot (server-team-bot) : | # |
Preview Diff
1 | diff --git a/curtin/block/dasd.py b/curtin/block/dasd.py |
2 | index daafeb8..7450300 100644 |
3 | --- a/curtin/block/dasd.py |
4 | +++ b/curtin/block/dasd.py |
5 | @@ -2,7 +2,6 @@ |
6 | |
7 | import collections |
8 | import os |
9 | -import re |
10 | import tempfile |
11 | from curtin import util |
12 | from curtin.log import LOG, logged_time |
13 | @@ -10,6 +9,86 @@ from curtin.log import LOG, logged_time |
14 | Dasdvalue = collections.namedtuple('Dasdvalue', ['hex', 'dec', 'txt']) |
15 | |
16 | |
17 | +class DasdPartition: |
18 | + def __init__(self, table, device, start, end, length, id, system): |
19 | + self.device = device |
20 | + self.start = int(start) |
21 | + self.end = int(end) |
22 | + self.length = int(length) |
23 | + self.id = id |
24 | + self.system = system |
25 | + |
26 | + |
27 | +class DasdPartitionTable: |
28 | + def __init__(self, devname, blocks_per_track, bytes_per_block): |
29 | + self.devname = devname |
30 | + self.blocks_per_track = blocks_per_track |
31 | + self.bytes_per_block = bytes_per_block |
32 | + self.partitions = [] |
33 | + |
34 | + @property |
35 | + def bytes_per_track(self): |
36 | + return self.bytes_per_block * self.blocks_per_track |
37 | + |
38 | + def tracks_needed(self, size_in_bytes): |
39 | + return ((size_in_bytes - 1) // self.bytes_per_track) + 1 |
40 | + |
41 | + @classmethod |
42 | + def from_fdasd(cls, devname): |
43 | + """Use fdasd to construct a DasdPartitionTable. |
44 | + |
45 | + % fdasd --table /dev/dasdc |
46 | + reading volume label ..: VOL1 |
47 | + reading vtoc ..........: ok |
48 | + |
49 | + |
50 | + Disk /dev/dasdc: |
51 | + cylinders ............: 10017 |
52 | + tracks per cylinder ..: 15 |
53 | + blocks per track .....: 12 |
54 | + bytes per block ......: 4096 |
55 | + volume label .........: VOL1 |
56 | + volume serial ........: 0X1522 |
57 | + max partitions .......: 3 |
58 | + |
59 | + ------------------------------- tracks ------------------------------- |
60 | + Device start end length Id System |
61 | + /dev/dasdc1 2 43694 43693 1 Linux native |
62 | + /dev/dasdc2 43695 87387 43693 2 Linux native |
63 | + /dev/dasdc3 87388 131080 43693 3 Linux native |
64 | + 131081 150254 19174 unused |
65 | + exiting... |
66 | + """ |
67 | + cmd = ['fdasd', '--table', devname] |
68 | + out, _err = util.subp(cmd, capture=True) |
69 | + line_iter = iter(out.splitlines()) |
70 | + for line in line_iter: |
71 | + if line.startswith("Disk"): |
72 | + break |
73 | + kw = {'devname': devname} |
74 | + label_to_attr = { |
75 | + 'blocks per track': 'blocks_per_track', |
76 | + 'bytes per block': 'bytes_per_block' |
77 | + } |
78 | + for line in line_iter: |
79 | + if '--- tracks ---' in line: |
80 | + break |
81 | + if ':' in line: |
82 | + label, value = line.split(':', 1) |
83 | + label = label.strip(' .') |
84 | + value = value.strip() |
85 | + if label in label_to_attr: |
86 | + kw[label_to_attr[label]] = int(value) |
87 | + table = cls(**kw) |
88 | + for line in line_iter: |
89 | + if line.startswith('exiting'): |
90 | + break |
91 | + vals = line.split(maxsplit=5) |
92 | + if vals[0].startswith('/dev/'): |
93 | + table.partitions.append(DasdPartition(*vals)) |
94 | + return table |
95 | + |
96 | + |
97 | def dasdinfo(device_id): |
98 | ''' Run dasdinfo command and return the exported values. |
99 | |
100 | @@ -325,83 +404,6 @@ class DasdDevice(CcwDevice): |
101 | def devname(self): |
102 | return '/dev/%s' % self.kname |
103 | |
104 | - def _bytes_to_tracks(self, geometry, request_size): |
105 | - """ Return the number of tracks needed to hold the request size. |
106 | - |
107 | - :param geometry: dictionary from dasdview output which includes |
108 | - info on number of cylinders, tracks and blocksize. |
109 | - :param request_size: size in Bytes |
110 | - |
111 | - :raises: ValueError on missing or invalid geometry dict, missing |
112 | - request_size. |
113 | - |
114 | - Example geometry: |
115 | - 'geometry': { |
116 | - 'blocks_per_track': Dasdvalue(hex='0xc', dec=12, txt=None), |
117 | - 'blocksize': Dasdvalue(hex='0x1000', dec=4096, txt=None), |
118 | - 'number_of_cylinders': |
119 | - Dasdvalue(hex='0x2721', dec=10017, txt=None), |
120 | - 'tracks_per_cylinder': Dasdvalue(hex='0xf', dec=15, txt=None)} |
121 | - } |
122 | - """ |
123 | - |
124 | - if not geometry or not isinstance(geometry, dict): |
125 | - raise ValueError('Missing or invalid geometry parameter.') |
126 | - |
127 | - if not all([key for key in geometry.keys() |
128 | - if key in ['blocksize', 'blocks_per_track']]): |
129 | - raise ValueError('Geometry dict missing required keys') |
130 | - |
131 | - if not request_size or not isinstance(request_size, |
132 | - util.numeric_types): |
133 | - raise ValueError('Missing or invalid request_size.') |
134 | - |
135 | - # helper to extract the decimal value from Dasdvalue objects |
136 | - def _dval(dval): |
137 | - return dval.dec |
138 | - |
139 | - bytes_per_track = ( |
140 | - _dval(geometry['blocksize']) * _dval(geometry['blocks_per_track'])) |
141 | - tracks_needed = ((request_size - 1) // bytes_per_track) + 1 |
142 | - return tracks_needed |
143 | - |
144 | - def get_partition_table(self): |
145 | - """ Use fdasd to query the partition table (VTOC). |
146 | - |
147 | - Returns a list of tuples, each tuple composed of the first 6 |
148 | - fields of matching lines in the output. |
149 | - |
150 | - % fdasd --table /dev/dasdc |
151 | - reading volume label ..: VOL1 |
152 | - reading vtoc ..........: ok |
153 | - |
154 | - |
155 | - Disk /dev/dasdc: |
156 | - cylinders ............: 10017 |
157 | - tracks per cylinder ..: 15 |
158 | - blocks per track .....: 12 |
159 | - bytes per block ......: 4096 |
160 | - volume label .........: VOL1 |
161 | - volume serial ........: 0X1522 |
162 | - max partitions .......: 3 |
163 | - |
164 | - ------------------------------- tracks ------------------------------- |
165 | - Device start end length Id System |
166 | - /dev/dasdc1 2 43694 43693 1 Linux native |
167 | - /dev/dasdc2 43695 87387 43693 2 Linux native |
168 | - /dev/dasdc3 87388 131080 43693 3 Linux native |
169 | - 131081 150254 19174 unused |
170 | - exiting... |
171 | - """ |
172 | - cmd = ['fdasd', '--table', self.devname] |
173 | - out, _err = util.subp(cmd, capture=True) |
174 | - lines = re.findall('.*%s.*Linux.*' % self.devname, out) |
175 | - partitions = [] |
176 | - for line in lines: |
177 | - partitions.append(tuple(line.split()[0:5])) |
178 | - |
179 | - return partitions |
180 | - |
181 | def partition(self, partnumber, partsize, strict=True): |
182 | """ Add a partition to this DasdDevice specifying partnumber and size. |
183 | |
184 | @@ -422,31 +424,24 @@ class DasdDevice(CcwDevice): |
185 | if strict and not os.path.exists(self.devname): |
186 | raise RuntimeError("devname '%s' does not exist" % self.devname) |
187 | |
188 | - info = dasdview(self.devname) |
189 | - geo = info['geometry'] |
190 | - |
191 | - existing_partitions = self.get_partition_table() |
192 | - partitions = [] |
193 | - for partinfo in existing_partitions[0:partnumber]: |
194 | - # (devpath, start_track, end_track, nr_tracks, partnum) |
195 | - start = partinfo[1] |
196 | - end = partinfo[2] |
197 | - partitions.append((start, end)) |
198 | + pt = DasdPartitionTable.from_fdasd(self.devname) |
199 | + new_partitions = [] |
200 | + for partinfo in pt.partitions[0:partnumber]: |
201 | + new_partitions.append((partinfo.start, partinfo.end)) |
202 | |
203 | # first partition always starts at track 2 |
204 | # all others start after the previous partition ends |
205 | if partnumber == 1: |
206 | start = 2 |
207 | else: |
208 | - start = int(partitions[-1][1]) + 1 |
209 | - # end is size + 1 |
210 | - tracks_needed = int(self._bytes_to_tracks(geo, partsize)) |
211 | - end = start + tracks_needed + 1 |
212 | - partitions.append(("%s" % start, "%s" % end)) |
213 | + start = int(pt.partitions[-1].end) + 1 |
214 | + # end is inclusive |
215 | + end = start + pt.tracks_needed(partsize) - 1 |
216 | + new_partitions.append((start, end)) |
217 | |
218 | content = "\n".join(["[%s,%s]" % (part[0], part[1]) |
219 | - for part in partitions]) |
220 | - LOG.debug("fdasd: partitions to be created: %s", partitions) |
221 | + for part in new_partitions]) |
222 | + LOG.debug("fdasd: partitions to be created: %s", new_partitions) |
223 | LOG.debug("fdasd: content=\n%s", content) |
224 | wfp = tempfile.NamedTemporaryFile(suffix=".fdasd", delete=False) |
225 | wfp.close() |
226 | @@ -479,14 +474,6 @@ class DasdDevice(CcwDevice): |
227 | """ |
228 | return self.ccw_device_attr('online') == "1" |
229 | |
230 | - def status(self): |
231 | - """ Read and return device_id's 'status' sysfs attribute value' |
232 | - |
233 | - :param device_id: string of device ccw bus_id. |
234 | - :returns: string: the value inside the 'status' sysfs attribute. |
235 | - """ |
236 | - return self.ccw_device_attr('status') |
237 | - |
238 | def blocksize(self): |
239 | """ Read and return device_id's 'blocksize' value. |
240 | |
241 | diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py |
242 | index 874ccbb..66dfdb4 100644 |
243 | --- a/curtin/commands/block_meta.py |
244 | +++ b/curtin/commands/block_meta.py |
245 | @@ -767,7 +767,7 @@ def verify_ptable_flag(devpath, expected_flag, sfdisk_info=None): |
246 | raise RuntimeError(msg) |
247 | |
248 | |
249 | -def partition_verify(devpath, info): |
250 | +def partition_verify_sfdisk(devpath, info): |
251 | verify_exists(devpath) |
252 | sfdisk_info = block.sfdisk_info(devpath) |
253 | if not sfdisk_info: |
254 | @@ -779,6 +779,21 @@ def partition_verify(devpath, info): |
255 | verify_ptable_flag(devpath, info['flag'], sfdisk_info=sfdisk_info) |
256 | |
257 | |
258 | +def partition_verify_fdasd(disk_path, partnumber, info): |
259 | + verify_exists(disk_path) |
260 | + pt = dasd.DasdPartitionTable.from_fdasd(disk_path) |
261 | + pt_entry = pt.partitions[partnumber-1] |
262 | + expected_tracks = pt.tracks_needed(util.human2bytes(info['size'])) |
263 | + msg = ( |
264 | + 'Verifying %s part %s size, expecting %s tracks, found %s tracks' % ( |
265 | + disk_path, partnumber, expected_tracks, pt_entry.length)) |
266 | + LOG.debug(msg) |
267 | + if expected_tracks != pt_entry.length: |
268 | + raise RuntimeError(msg) |
269 | + if info.get('flag', 'linux') != 'linux': |
270 | + raise RuntimeError("dasd partitions do not support flags") |
271 | + |
272 | + |
273 | def partition_handler(info, storage_config): |
274 | device = info.get('device') |
275 | size = info.get('size') |
276 | @@ -870,9 +885,12 @@ def partition_handler(info, storage_config): |
277 | # Handle preserve flag |
278 | create_partition = True |
279 | if config.value_as_boolean(info.get('preserve')): |
280 | - part_path = block.dev_path( |
281 | - block.partition_kname(disk_kname, partnumber)) |
282 | - partition_verify(part_path, info) |
283 | + if disk_ptable == 'vtoc': |
284 | + partition_verify_fdasd(disk, partnumber, info) |
285 | + else: |
286 | + part_path = block.dev_path( |
287 | + block.partition_kname(disk_kname, partnumber)) |
288 | + partition_verify_sfdisk(part_path, info) |
289 | LOG.debug('Partition %s already present, skipping create', part_path) |
290 | create_partition = False |
291 | |
292 | diff --git a/tests/unittests/test_block_dasd.py b/tests/unittests/test_block_dasd.py |
293 | index 393e288..dfd62d3 100644 |
294 | --- a/tests/unittests/test_block_dasd.py |
295 | +++ b/tests/unittests/test_block_dasd.py |
296 | @@ -204,11 +204,6 @@ class TestDasdCcwDeviceAttr(CiTestCase): |
297 | self.m_loadfile.return_value = self.random_string() |
298 | self.assertFalse(self.dasd.is_online()) |
299 | |
300 | - def test_status_returns_device_status_attr(self): |
301 | - status_val = self.random_string() |
302 | - self.m_loadfile.return_value = status_val |
303 | - self.assertEqual(status_val, self.dasd.status()) |
304 | - |
305 | def test_blocksize(self): |
306 | blocksize_val = '%d' % random.choice([512, 1024, 2048, 4096]) |
307 | self.m_loadfile.return_value = blocksize_val |
308 | diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py |
309 | index ffec434..a5129a5 100644 |
310 | --- a/tests/unittests/test_commands_block_meta.py |
311 | +++ b/tests/unittests/test_commands_block_meta.py |
312 | @@ -5,7 +5,9 @@ from collections import OrderedDict |
313 | import copy |
314 | from mock import patch, call |
315 | import os |
316 | +import random |
317 | |
318 | +from curtin.block import dasd |
319 | from curtin.commands import block_meta |
320 | from curtin import paths, util |
321 | from .helpers import CiTestCase |
322 | @@ -2398,10 +2400,10 @@ class TestCalcDMPartitionInfo(CiTestCase): |
323 | self.m_subp.call_args_list) |
324 | |
325 | |
326 | -class TestPartitionVerify(CiTestCase): |
327 | +class TestPartitionVerifySfdisk(CiTestCase): |
328 | |
329 | def setUp(self): |
330 | - super(TestPartitionVerify, self).setUp() |
331 | + super(TestPartitionVerifySfdisk, self).setUp() |
332 | base = 'curtin.commands.block_meta.' |
333 | self.add_patch(base + 'verify_exists', 'm_verify_exists') |
334 | self.add_patch(base + 'block.sfdisk_info', 'm_block_sfdisk_info') |
335 | @@ -2418,8 +2420,8 @@ class TestPartitionVerify(CiTestCase): |
336 | self.part_size = int(util.human2bytes(self.info['size'])) |
337 | self.devpath = self.random_string() |
338 | |
339 | - def test_partition_verify(self): |
340 | - block_meta.partition_verify(self.devpath, self.info) |
341 | + def test_partition_verify_sfdisk(self): |
342 | + block_meta.partition_verify_sfdisk(self.devpath, self.info) |
343 | self.assertEqual( |
344 | [call(self.devpath)], |
345 | self.m_verify_exists.call_args_list) |
346 | @@ -2437,7 +2439,7 @@ class TestPartitionVerify(CiTestCase): |
347 | |
348 | def test_partition_verify_skips_ptable_no_flag(self): |
349 | del self.info['flag'] |
350 | - block_meta.partition_verify(self.devpath, self.info) |
351 | + block_meta.partition_verify_sfdisk(self.devpath, self.info) |
352 | self.assertEqual( |
353 | [call(self.devpath)], |
354 | self.m_verify_exists.call_args_list) |
355 | @@ -2451,6 +2453,47 @@ class TestPartitionVerify(CiTestCase): |
356 | self.assertEqual([], self.m_verify_ptable_flag.call_args_list) |
357 | |
358 | |
359 | +class TestPartitionVerifyFdasd(CiTestCase): |
360 | + |
361 | + def setUp(self): |
362 | + super(TestPartitionVerifyFdasd, self).setUp() |
363 | + base = 'curtin.commands.block_meta.' |
364 | + self.add_patch(base + 'verify_exists', 'm_verify_exists') |
365 | + self.add_patch( |
366 | + base + 'dasd.DasdPartitionTable.from_fdasd', 'm_from_fdasd', |
367 | + autospec=False, spec=dasd.DasdPartitionTable.from_fdasd) |
368 | + self.add_patch(base + 'verify_size', 'm_verify_size') |
369 | + self.info = { |
370 | + 'id': 'disk-sda-part-2', |
371 | + 'type': 'partition', |
372 | + 'device': 'sda', |
373 | + 'number': 2, |
374 | + 'size': '5GB', |
375 | + 'flag': 'linux', |
376 | + } |
377 | + self.part_size = int(util.human2bytes(self.info['size'])) |
378 | + self.devpath = self.random_string() |
379 | + |
380 | + def test_partition_verify_fdasd(self): |
381 | + blocks_per_track = random.randint(10, 20) |
382 | + bytes_per_block = random.randint(1, 8)*512 |
383 | + fake_pt = dasd.DasdPartitionTable( |
384 | + '/dev/dasdfoo', blocks_per_track, bytes_per_block) |
385 | + tracks_needed = fake_pt.tracks_needed( |
386 | + util.human2bytes(self.info['size'])) |
387 | + fake_pt.partitions = [ |
388 | + dasd.DasdPartition(fake_pt, '', 0, 0, tracks_needed, '1', 'Linux'), |
389 | + ] |
390 | + self.m_from_fdasd.side_effect = iter([fake_pt]) |
391 | + block_meta.partition_verify_fdasd(self.devpath, 1, self.info) |
392 | + self.assertEqual( |
393 | + [call(self.devpath)], |
394 | + self.m_verify_exists.call_args_list) |
395 | + self.assertEqual( |
396 | + [call(self.devpath)], |
397 | + self.m_from_fdasd.call_args_list) |
398 | + |
399 | + |
400 | class TestVerifyExists(CiTestCase): |
401 | |
402 | def setUp(self): |
FAILED: Continuous integration, rev:89b3d5deef6 15f40bb5e76cb45 def6b292cb7097 /jenkins. ubuntu. com/server/ job/curtin- ci/3/ /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= metal-amd64/ 3/ /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= metal-arm64/ 3/ /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= metal-ppc64el/ 3/ /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= metal-s390x/ 3/
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/curtin- ci/3//rebuild
https:/