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
diff --git a/curtin/block/dasd.py b/curtin/block/dasd.py
index daafeb8..7450300 100644
--- a/curtin/block/dasd.py
+++ b/curtin/block/dasd.py
@@ -2,7 +2,6 @@
22
3import collections3import collections
4import os4import os
5import re
6import tempfile5import tempfile
7from curtin import util6from curtin import util
8from curtin.log import LOG, logged_time7from curtin.log import LOG, logged_time
@@ -10,6 +9,86 @@ from curtin.log import LOG, logged_time
10Dasdvalue = collections.namedtuple('Dasdvalue', ['hex', 'dec', 'txt'])9Dasdvalue = collections.namedtuple('Dasdvalue', ['hex', 'dec', 'txt'])
1110
1211
12class DasdPartition:
13 def __init__(self, table, device, start, end, length, id, system):
14 self.device = device
15 self.start = int(start)
16 self.end = int(end)
17 self.length = int(length)
18 self.id = id
19 self.system = system
20
21
22class DasdPartitionTable:
23 def __init__(self, devname, blocks_per_track, bytes_per_block):
24 self.devname = devname
25 self.blocks_per_track = blocks_per_track
26 self.bytes_per_block = bytes_per_block
27 self.partitions = []
28
29 @property
30 def bytes_per_track(self):
31 return self.bytes_per_block * self.blocks_per_track
32
33 def tracks_needed(self, size_in_bytes):
34 return ((size_in_bytes - 1) // self.bytes_per_track) + 1
35
36 @classmethod
37 def from_fdasd(cls, devname):
38 """Use fdasd to construct a DasdPartitionTable.
39
40 % fdasd --table /dev/dasdc
41 reading volume label ..: VOL1
42 reading vtoc ..........: ok
43
44
45 Disk /dev/dasdc:
46 cylinders ............: 10017
47 tracks per cylinder ..: 15
48 blocks per track .....: 12
49 bytes per block ......: 4096
50 volume label .........: VOL1
51 volume serial ........: 0X1522
52 max partitions .......: 3
53
54 ------------------------------- tracks -------------------------------
55 Device start end length Id System
56 /dev/dasdc1 2 43694 43693 1 Linux native
57 /dev/dasdc2 43695 87387 43693 2 Linux native
58 /dev/dasdc3 87388 131080 43693 3 Linux native
59 131081 150254 19174 unused
60 exiting...
61 """
62 cmd = ['fdasd', '--table', devname]
63 out, _err = util.subp(cmd, capture=True)
64 line_iter = iter(out.splitlines())
65 for line in line_iter:
66 if line.startswith("Disk"):
67 break
68 kw = {'devname': devname}
69 label_to_attr = {
70 'blocks per track': 'blocks_per_track',
71 'bytes per block': 'bytes_per_block'
72 }
73 for line in line_iter:
74 if '--- tracks ---' in line:
75 break
76 if ':' in line:
77 label, value = line.split(':', 1)
78 label = label.strip(' .')
79 value = value.strip()
80 if label in label_to_attr:
81 kw[label_to_attr[label]] = int(value)
82 table = cls(**kw)
83 for line in line_iter:
84 if line.startswith('exiting'):
85 break
86 vals = line.split(maxsplit=5)
87 if vals[0].startswith('/dev/'):
88 table.partitions.append(DasdPartition(*vals))
89 return table
90
91
13def dasdinfo(device_id):92def dasdinfo(device_id):
14 ''' Run dasdinfo command and return the exported values.93 ''' Run dasdinfo command and return the exported values.
1594
@@ -325,83 +404,6 @@ class DasdDevice(CcwDevice):
325 def devname(self):404 def devname(self):
326 return '/dev/%s' % self.kname405 return '/dev/%s' % self.kname
327406
328 def _bytes_to_tracks(self, geometry, request_size):
329 """ Return the number of tracks needed to hold the request size.
330
331 :param geometry: dictionary from dasdview output which includes
332 info on number of cylinders, tracks and blocksize.
333 :param request_size: size in Bytes
334
335 :raises: ValueError on missing or invalid geometry dict, missing
336 request_size.
337
338 Example geometry:
339 'geometry': {
340 'blocks_per_track': Dasdvalue(hex='0xc', dec=12, txt=None),
341 'blocksize': Dasdvalue(hex='0x1000', dec=4096, txt=None),
342 'number_of_cylinders':
343 Dasdvalue(hex='0x2721', dec=10017, txt=None),
344 'tracks_per_cylinder': Dasdvalue(hex='0xf', dec=15, txt=None)}
345 }
346 """
347
348 if not geometry or not isinstance(geometry, dict):
349 raise ValueError('Missing or invalid geometry parameter.')
350
351 if not all([key for key in geometry.keys()
352 if key in ['blocksize', 'blocks_per_track']]):
353 raise ValueError('Geometry dict missing required keys')
354
355 if not request_size or not isinstance(request_size,
356 util.numeric_types):
357 raise ValueError('Missing or invalid request_size.')
358
359 # helper to extract the decimal value from Dasdvalue objects
360 def _dval(dval):
361 return dval.dec
362
363 bytes_per_track = (
364 _dval(geometry['blocksize']) * _dval(geometry['blocks_per_track']))
365 tracks_needed = ((request_size - 1) // bytes_per_track) + 1
366 return tracks_needed
367
368 def get_partition_table(self):
369 """ Use fdasd to query the partition table (VTOC).
370
371 Returns a list of tuples, each tuple composed of the first 6
372 fields of matching lines in the output.
373
374 % fdasd --table /dev/dasdc
375 reading volume label ..: VOL1
376 reading vtoc ..........: ok
377
378
379 Disk /dev/dasdc:
380 cylinders ............: 10017
381 tracks per cylinder ..: 15
382 blocks per track .....: 12
383 bytes per block ......: 4096
384 volume label .........: VOL1
385 volume serial ........: 0X1522
386 max partitions .......: 3
387
388 ------------------------------- tracks -------------------------------
389 Device start end length Id System
390 /dev/dasdc1 2 43694 43693 1 Linux native
391 /dev/dasdc2 43695 87387 43693 2 Linux native
392 /dev/dasdc3 87388 131080 43693 3 Linux native
393 131081 150254 19174 unused
394 exiting...
395 """
396 cmd = ['fdasd', '--table', self.devname]
397 out, _err = util.subp(cmd, capture=True)
398 lines = re.findall('.*%s.*Linux.*' % self.devname, out)
399 partitions = []
400 for line in lines:
401 partitions.append(tuple(line.split()[0:5]))
402
403 return partitions
404
405 def partition(self, partnumber, partsize, strict=True):407 def partition(self, partnumber, partsize, strict=True):
406 """ Add a partition to this DasdDevice specifying partnumber and size.408 """ Add a partition to this DasdDevice specifying partnumber and size.
407409
@@ -422,31 +424,24 @@ class DasdDevice(CcwDevice):
422 if strict and not os.path.exists(self.devname):424 if strict and not os.path.exists(self.devname):
423 raise RuntimeError("devname '%s' does not exist" % self.devname)425 raise RuntimeError("devname '%s' does not exist" % self.devname)
424426
425 info = dasdview(self.devname)427 pt = DasdPartitionTable.from_fdasd(self.devname)
426 geo = info['geometry']428 new_partitions = []
427429 for partinfo in pt.partitions[0:partnumber]:
428 existing_partitions = self.get_partition_table()430 new_partitions.append((partinfo.start, partinfo.end))
429 partitions = []
430 for partinfo in existing_partitions[0:partnumber]:
431 # (devpath, start_track, end_track, nr_tracks, partnum)
432 start = partinfo[1]
433 end = partinfo[2]
434 partitions.append((start, end))
435431
436 # first partition always starts at track 2432 # first partition always starts at track 2
437 # all others start after the previous partition ends433 # all others start after the previous partition ends
438 if partnumber == 1:434 if partnumber == 1:
439 start = 2435 start = 2
440 else:436 else:
441 start = int(partitions[-1][1]) + 1437 start = int(pt.partitions[-1].end) + 1
442 # end is size + 1438 # end is inclusive
443 tracks_needed = int(self._bytes_to_tracks(geo, partsize))439 end = start + pt.tracks_needed(partsize) - 1
444 end = start + tracks_needed + 1440 new_partitions.append((start, end))
445 partitions.append(("%s" % start, "%s" % end))
446441
447 content = "\n".join(["[%s,%s]" % (part[0], part[1])442 content = "\n".join(["[%s,%s]" % (part[0], part[1])
448 for part in partitions])443 for part in new_partitions])
449 LOG.debug("fdasd: partitions to be created: %s", partitions)444 LOG.debug("fdasd: partitions to be created: %s", new_partitions)
450 LOG.debug("fdasd: content=\n%s", content)445 LOG.debug("fdasd: content=\n%s", content)
451 wfp = tempfile.NamedTemporaryFile(suffix=".fdasd", delete=False)446 wfp = tempfile.NamedTemporaryFile(suffix=".fdasd", delete=False)
452 wfp.close()447 wfp.close()
@@ -479,14 +474,6 @@ class DasdDevice(CcwDevice):
479 """474 """
480 return self.ccw_device_attr('online') == "1"475 return self.ccw_device_attr('online') == "1"
481476
482 def status(self):
483 """ Read and return device_id's 'status' sysfs attribute value'
484
485 :param device_id: string of device ccw bus_id.
486 :returns: string: the value inside the 'status' sysfs attribute.
487 """
488 return self.ccw_device_attr('status')
489
490 def blocksize(self):477 def blocksize(self):
491 """ Read and return device_id's 'blocksize' value.478 """ Read and return device_id's 'blocksize' value.
492479
diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py
index 874ccbb..66dfdb4 100644
--- a/curtin/commands/block_meta.py
+++ b/curtin/commands/block_meta.py
@@ -767,7 +767,7 @@ def verify_ptable_flag(devpath, expected_flag, sfdisk_info=None):
767 raise RuntimeError(msg)767 raise RuntimeError(msg)
768768
769769
770def partition_verify(devpath, info):770def partition_verify_sfdisk(devpath, info):
771 verify_exists(devpath)771 verify_exists(devpath)
772 sfdisk_info = block.sfdisk_info(devpath)772 sfdisk_info = block.sfdisk_info(devpath)
773 if not sfdisk_info:773 if not sfdisk_info:
@@ -779,6 +779,21 @@ def partition_verify(devpath, info):
779 verify_ptable_flag(devpath, info['flag'], sfdisk_info=sfdisk_info)779 verify_ptable_flag(devpath, info['flag'], sfdisk_info=sfdisk_info)
780780
781781
782def partition_verify_fdasd(disk_path, partnumber, info):
783 verify_exists(disk_path)
784 pt = dasd.DasdPartitionTable.from_fdasd(disk_path)
785 pt_entry = pt.partitions[partnumber-1]
786 expected_tracks = pt.tracks_needed(util.human2bytes(info['size']))
787 msg = (
788 'Verifying %s part %s size, expecting %s tracks, found %s tracks' % (
789 disk_path, partnumber, expected_tracks, pt_entry.length))
790 LOG.debug(msg)
791 if expected_tracks != pt_entry.length:
792 raise RuntimeError(msg)
793 if info.get('flag', 'linux') != 'linux':
794 raise RuntimeError("dasd partitions do not support flags")
795
796
782def partition_handler(info, storage_config):797def partition_handler(info, storage_config):
783 device = info.get('device')798 device = info.get('device')
784 size = info.get('size')799 size = info.get('size')
@@ -870,9 +885,12 @@ def partition_handler(info, storage_config):
870 # Handle preserve flag885 # Handle preserve flag
871 create_partition = True886 create_partition = True
872 if config.value_as_boolean(info.get('preserve')):887 if config.value_as_boolean(info.get('preserve')):
873 part_path = block.dev_path(888 if disk_ptable == 'vtoc':
874 block.partition_kname(disk_kname, partnumber))889 partition_verify_fdasd(disk, partnumber, info)
875 partition_verify(part_path, info)890 else:
891 part_path = block.dev_path(
892 block.partition_kname(disk_kname, partnumber))
893 partition_verify_sfdisk(part_path, info)
876 LOG.debug('Partition %s already present, skipping create', part_path)894 LOG.debug('Partition %s already present, skipping create', part_path)
877 create_partition = False895 create_partition = False
878896
diff --git a/tests/unittests/test_block_dasd.py b/tests/unittests/test_block_dasd.py
index 393e288..dfd62d3 100644
--- a/tests/unittests/test_block_dasd.py
+++ b/tests/unittests/test_block_dasd.py
@@ -204,11 +204,6 @@ class TestDasdCcwDeviceAttr(CiTestCase):
204 self.m_loadfile.return_value = self.random_string()204 self.m_loadfile.return_value = self.random_string()
205 self.assertFalse(self.dasd.is_online())205 self.assertFalse(self.dasd.is_online())
206206
207 def test_status_returns_device_status_attr(self):
208 status_val = self.random_string()
209 self.m_loadfile.return_value = status_val
210 self.assertEqual(status_val, self.dasd.status())
211
212 def test_blocksize(self):207 def test_blocksize(self):
213 blocksize_val = '%d' % random.choice([512, 1024, 2048, 4096])208 blocksize_val = '%d' % random.choice([512, 1024, 2048, 4096])
214 self.m_loadfile.return_value = blocksize_val209 self.m_loadfile.return_value = blocksize_val
diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py
index ffec434..a5129a5 100644
--- a/tests/unittests/test_commands_block_meta.py
+++ b/tests/unittests/test_commands_block_meta.py
@@ -5,7 +5,9 @@ from collections import OrderedDict
5import copy5import copy
6from mock import patch, call6from mock import patch, call
7import os7import os
8import random
89
10from curtin.block import dasd
9from curtin.commands import block_meta11from curtin.commands import block_meta
10from curtin import paths, util12from curtin import paths, util
11from .helpers import CiTestCase13from .helpers import CiTestCase
@@ -2398,10 +2400,10 @@ class TestCalcDMPartitionInfo(CiTestCase):
2398 self.m_subp.call_args_list)2400 self.m_subp.call_args_list)
23992401
24002402
2401class TestPartitionVerify(CiTestCase):2403class TestPartitionVerifySfdisk(CiTestCase):
24022404
2403 def setUp(self):2405 def setUp(self):
2404 super(TestPartitionVerify, self).setUp()2406 super(TestPartitionVerifySfdisk, self).setUp()
2405 base = 'curtin.commands.block_meta.'2407 base = 'curtin.commands.block_meta.'
2406 self.add_patch(base + 'verify_exists', 'm_verify_exists')2408 self.add_patch(base + 'verify_exists', 'm_verify_exists')
2407 self.add_patch(base + 'block.sfdisk_info', 'm_block_sfdisk_info')2409 self.add_patch(base + 'block.sfdisk_info', 'm_block_sfdisk_info')
@@ -2418,8 +2420,8 @@ class TestPartitionVerify(CiTestCase):
2418 self.part_size = int(util.human2bytes(self.info['size']))2420 self.part_size = int(util.human2bytes(self.info['size']))
2419 self.devpath = self.random_string()2421 self.devpath = self.random_string()
24202422
2421 def test_partition_verify(self):2423 def test_partition_verify_sfdisk(self):
2422 block_meta.partition_verify(self.devpath, self.info)2424 block_meta.partition_verify_sfdisk(self.devpath, self.info)
2423 self.assertEqual(2425 self.assertEqual(
2424 [call(self.devpath)],2426 [call(self.devpath)],
2425 self.m_verify_exists.call_args_list)2427 self.m_verify_exists.call_args_list)
@@ -2437,7 +2439,7 @@ class TestPartitionVerify(CiTestCase):
24372439
2438 def test_partition_verify_skips_ptable_no_flag(self):2440 def test_partition_verify_skips_ptable_no_flag(self):
2439 del self.info['flag']2441 del self.info['flag']
2440 block_meta.partition_verify(self.devpath, self.info)2442 block_meta.partition_verify_sfdisk(self.devpath, self.info)
2441 self.assertEqual(2443 self.assertEqual(
2442 [call(self.devpath)],2444 [call(self.devpath)],
2443 self.m_verify_exists.call_args_list)2445 self.m_verify_exists.call_args_list)
@@ -2451,6 +2453,47 @@ class TestPartitionVerify(CiTestCase):
2451 self.assertEqual([], self.m_verify_ptable_flag.call_args_list)2453 self.assertEqual([], self.m_verify_ptable_flag.call_args_list)
24522454
24532455
2456class TestPartitionVerifyFdasd(CiTestCase):
2457
2458 def setUp(self):
2459 super(TestPartitionVerifyFdasd, self).setUp()
2460 base = 'curtin.commands.block_meta.'
2461 self.add_patch(base + 'verify_exists', 'm_verify_exists')
2462 self.add_patch(
2463 base + 'dasd.DasdPartitionTable.from_fdasd', 'm_from_fdasd',
2464 autospec=False, spec=dasd.DasdPartitionTable.from_fdasd)
2465 self.add_patch(base + 'verify_size', 'm_verify_size')
2466 self.info = {
2467 'id': 'disk-sda-part-2',
2468 'type': 'partition',
2469 'device': 'sda',
2470 'number': 2,
2471 'size': '5GB',
2472 'flag': 'linux',
2473 }
2474 self.part_size = int(util.human2bytes(self.info['size']))
2475 self.devpath = self.random_string()
2476
2477 def test_partition_verify_fdasd(self):
2478 blocks_per_track = random.randint(10, 20)
2479 bytes_per_block = random.randint(1, 8)*512
2480 fake_pt = dasd.DasdPartitionTable(
2481 '/dev/dasdfoo', blocks_per_track, bytes_per_block)
2482 tracks_needed = fake_pt.tracks_needed(
2483 util.human2bytes(self.info['size']))
2484 fake_pt.partitions = [
2485 dasd.DasdPartition(fake_pt, '', 0, 0, tracks_needed, '1', 'Linux'),
2486 ]
2487 self.m_from_fdasd.side_effect = iter([fake_pt])
2488 block_meta.partition_verify_fdasd(self.devpath, 1, self.info)
2489 self.assertEqual(
2490 [call(self.devpath)],
2491 self.m_verify_exists.call_args_list)
2492 self.assertEqual(
2493 [call(self.devpath)],
2494 self.m_from_fdasd.call_args_list)
2495
2496
2454class TestVerifyExists(CiTestCase):2497class TestVerifyExists(CiTestCase):
24552498
2456 def setUp(self):2499 def setUp(self):

Subscribers

People subscribed via source and target branches