Merge ~mwhudson/curtin:partition-verify-dasd into curtin:master

Proposed by Michael Hudson-Doyle
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)
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_verify_sfdisk, adds
parition_verify_fdasd and calls the latter when ptable ==
'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

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Dimitri John Ledkov (xnox) wrote :

This is fine refactor.

review: Approve
Revision history for this message
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.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

Commit message lints:
- Line #2 has 360 too many characters. Line starts with: "Currently curtin verifies"...

review: Needs Fixing
Revision history for this message
Server Team CI bot (server-team-bot) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/curtin/block/dasd.py b/curtin/block/dasd.py
2index 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
241diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py
242index 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
292diff --git a/tests/unittests/test_block_dasd.py b/tests/unittests/test_block_dasd.py
293index 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
308diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py
309index 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):

Subscribers

People subscribed via source and target branches