← Back to team overview

curtin-dev team mailing list archive

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

 

Michael Hudson-Doyle has proposed merging ~mwhudson/curtin:partition-verify-dasd into curtin:master.

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

Requested reviews:
  Server Team CI bot (server-team-bot): continuous-integration
  curtin developers (curtin-dev)

For more details, see:
https://code.launchpad.net/~mwhudson/curtin/+git/curtin/+merge/393791
-- 
Your team curtin developers is requested to review the proposed merge of ~mwhudson/curtin:partition-verify-dasd into curtin:master.
diff --git a/curtin/block/dasd.py b/curtin/block/dasd.py
index b7008f6..955cf1f 100644
--- a/curtin/block/dasd.py
+++ b/curtin/block/dasd.py
@@ -2,7 +2,6 @@
 
 import collections
 import os
-import re
 import tempfile
 from curtin import util
 from curtin.log import LOG, logged_time
@@ -10,6 +9,86 @@ from curtin.log import LOG, logged_time
 Dasdvalue = collections.namedtuple('Dasdvalue', ['hex', 'dec', 'txt'])
 
 
+class DasdPartition:
+    def __init__(self, table, device, start, end, length, id, system):
+        self.device = device
+        self.start = int(start)
+        self.end = int(end)
+        self.length = int(length)
+        self.id = id
+        self.system = system
+
+
+class DasdPartitionTable:
+    def __init__(self, devname, blocks_per_track, bytes_per_block):
+        self.devname = devname
+        self.blocks_per_track = blocks_per_track
+        self.bytes_per_block = bytes_per_block
+        self.partitions = []
+
+    @property
+    def bytes_per_track(self):
+        return self.bytes_per_block * self.blocks_per_track
+
+    def tracks_needed(self, size_in_bytes):
+        return ((size_in_bytes - 1) // self.bytes_per_track) + 1
+
+    @classmethod
+    def from_fdasd(cls, devname):
+        """Use fdasd to construct a DasdPartitionTable.
+
+        % fdasd --table /dev/dasdc
+        reading volume label ..: VOL1
+        reading vtoc ..........: ok
+
+
+        Disk /dev/dasdc:
+          cylinders ............: 10017
+          tracks per cylinder ..: 15
+          blocks per track .....: 12
+          bytes per block ......: 4096
+          volume label .........: VOL1
+          volume serial ........: 0X1522
+          max partitions .......: 3
+
+         ------------------------------- tracks -------------------------------
+                       Device      start      end   length   Id  System
+                  /dev/dasdc1          2    43694    43693    1  Linux native
+                  /dev/dasdc2      43695    87387    43693    2  Linux native
+                  /dev/dasdc3      87388   131080    43693    3  Linux native
+                                  131081   150254    19174       unused
+        exiting...
+        """
+        cmd = ['fdasd', '--table', devname]
+        out, _err = util.subp(cmd, capture=True)
+        line_iter = iter(out.splitlines())
+        for line in line_iter:
+            if line.startswith("Disk"):
+                break
+        kw = {'devname': devname}
+        label_to_attr = {
+            'blocks per track': 'blocks_per_track',
+            'bytes per block': 'bytes_per_block'
+            }
+        for line in line_iter:
+            if '--- tracks ---' in line:
+                break
+            if ':' in line:
+                label, value = line.split(':', 1)
+                label = label.strip(' .')
+                value = value.strip()
+                if label in label_to_attr:
+                    kw[label_to_attr[label]] = int(value)
+        table = cls(**kw)
+        for line in line_iter:
+            if line.startswith('exiting'):
+                break
+            vals = line.split(maxsplit=5)
+            if vals[0].startswith('/dev/'):
+                table.partitions.append(DasdPartition(*vals))
+        return table
+
+
 def dasdinfo(device_id, rawoutput=False, strict=False):
     ''' Run dasdinfo command and return the exported values.
 
@@ -378,43 +457,6 @@ class DasdDevice(CcwDevice):
         tracks_needed = ((request_size - 1) // bytes_per_track) + 1
         return tracks_needed
 
-    def get_partition_table(self):
-        """ Use fdasd to query the partition table (VTOC).
-
-            Returns a list of tuples, each tuple composed of the first 6
-            fields of matching lines in the output.
-
-        % fdasd --table /dev/dasdc
-        reading volume label ..: VOL1
-        reading vtoc ..........: ok
-
-
-        Disk /dev/dasdc:
-          cylinders ............: 10017
-          tracks per cylinder ..: 15
-          blocks per track .....: 12
-          bytes per block ......: 4096
-          volume label .........: VOL1
-          volume serial ........: 0X1522
-          max partitions .......: 3
-
-         ------------------------------- tracks -------------------------------
-                       Device      start      end   length   Id  System
-                  /dev/dasdc1          2    43694    43693    1  Linux native
-                  /dev/dasdc2      43695    87387    43693    2  Linux native
-                  /dev/dasdc3      87388   131080    43693    3  Linux native
-                                  131081   150254    19174       unused
-        exiting...
-        """
-        cmd = ['fdasd', '--table', self.devname]
-        out, _err = util.subp(cmd, capture=True)
-        lines = re.findall('.*%s.*Linux.*' % self.devname, out)
-        partitions = []
-        for line in lines:
-            partitions.append(tuple(line.split()[0:5]))
-
-        return partitions
-
     def partition(self, partnumber, partsize, strict=True):
         """ Add a partition to this DasdDevice specifying partnumber and size.
 
@@ -435,31 +477,24 @@ class DasdDevice(CcwDevice):
         if strict and not os.path.exists(self.devname):
             raise RuntimeError("devname '%s' does not exist" % self.devname)
 
-        info = dasdview(self.devname)
-        geo = info['geometry']
-
-        existing_partitions = self.get_partition_table()
-        partitions = []
-        for partinfo in existing_partitions[0:partnumber]:
-            # (devpath, start_track, end_track, nr_tracks, partnum)
-            start = partinfo[1]
-            end = partinfo[2]
-            partitions.append((start, end))
+        pt = DasdPartitionTable.from_fdasd(self.devname)
+        new_partitions = []
+        for partinfo in pt.partitions[0:partnumber]:
+            new_partitions.append((partinfo.start, partinfo.end))
 
         # first partition always starts at track 2
         # all others start after the previous partition ends
         if partnumber == 1:
             start = 2
         else:
-            start = int(partitions[-1][1]) + 1
-        # end is size + 1
-        tracks_needed = int(self._bytes_to_tracks(geo, partsize))
-        end = start + tracks_needed + 1
-        partitions.append(("%s" % start, "%s" % end))
+            start = int(pt.partitions[-1].end) + 1
+        # end is inclusive
+        end = start + pt.tracks_needed(partsize) - 1
+        new_partitions.append((start, end))
 
         content = "\n".join(["[%s,%s]" % (part[0], part[1])
-                             for part in partitions])
-        LOG.debug("fdasd: partitions to be created: %s", partitions)
+                             for part in new_partitions])
+        LOG.debug("fdasd: partitions to be created: %s", new_partitions)
         LOG.debug("fdasd: content=\n%s", content)
         wfp = tempfile.NamedTemporaryFile(suffix=".fdasd", delete=False)
         wfp.close()
diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py
index 30fc817..29a9955 100644
--- a/curtin/commands/block_meta.py
+++ b/curtin/commands/block_meta.py
@@ -772,7 +772,7 @@ def verify_ptable_flag(devpath, expected_flag, sfdisk_info=None):
         raise RuntimeError(msg)
 
 
-def partition_verify(devpath, info):
+def partition_verify_sfdisk(devpath, info):
     verify_exists(devpath)
     sfdisk_info = block.sfdisk_info(devpath)
     if not sfdisk_info:
@@ -784,6 +784,21 @@ def partition_verify(devpath, info):
         verify_ptable_flag(devpath, info['flag'], sfdisk_info=sfdisk_info)
 
 
+def partition_verify_fdasd(disk_path, partnumber, info):
+    verify_exists(disk_path)
+    pt = dasd.DasdPartitionTable.from_fdasd(disk_path)
+    pt_entry = pt.partitions[partnumber-1]
+    expected_tracks = pt.tracks_needed(util.human2bytes(info['size']))
+    msg = (
+        'Verifying %s part %s size, expecting %s tracks, found %s tracks' % (
+         disk_path, partnumber, expected_tracks, pt_entry.length))
+    LOG.debug(msg)
+    if expected_tracks != pt_entry.length:
+        raise RuntimeError(msg)
+    if info.get('flag', 'linux') != 'linux':
+        raise RuntimeError("dasd partitions do not support flags")
+
+
 def partition_handler(info, storage_config):
     device = info.get('device')
     size = info.get('size')
@@ -875,9 +890,12 @@ def partition_handler(info, storage_config):
     # Handle preserve flag
     create_partition = True
     if config.value_as_boolean(info.get('preserve')):
-        part_path = block.dev_path(
-            block.partition_kname(disk_kname, partnumber))
-        partition_verify(part_path, info)
+        if disk_ptable == 'vtoc':
+            partition_verify_fdasd(disk, partnumber, info)
+        else:
+            part_path = block.dev_path(
+                block.partition_kname(disk_kname, partnumber))
+            partition_verify_sfdisk(part_path, info)
         LOG.debug('Partition %s already present, skipping create', part_path)
         create_partition = False
 
diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py
index 98be573..bb56832 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
 import copy
 from mock import patch, call
 import os
+import random
 
+from curtin.block import dasd
 from curtin.commands import block_meta
 from curtin import paths, util
 from .helpers import CiTestCase
@@ -2387,10 +2389,10 @@ class TestCalcDMPartitionInfo(CiTestCase):
             self.m_subp.call_args_list)
 
 
-class TestPartitionVerify(CiTestCase):
+class TestPartitionVerifySfdisk(CiTestCase):
 
     def setUp(self):
-        super(TestPartitionVerify, self).setUp()
+        super(TestPartitionVerifySfdisk, self).setUp()
         base = 'curtin.commands.block_meta.'
         self.add_patch(base + 'verify_exists', 'm_verify_exists')
         self.add_patch(base + 'block.sfdisk_info', 'm_block_sfdisk_info')
@@ -2407,8 +2409,8 @@ class TestPartitionVerify(CiTestCase):
         self.part_size = int(util.human2bytes(self.info['size']))
         self.devpath = self.random_string()
 
-    def test_partition_verify(self):
-        block_meta.partition_verify(self.devpath, self.info)
+    def test_partition_verify_sfdisk(self):
+        block_meta.partition_verify_sfdisk(self.devpath, self.info)
         self.assertEqual(
             [call(self.devpath)],
             self.m_verify_exists.call_args_list)
@@ -2426,7 +2428,7 @@ class TestPartitionVerify(CiTestCase):
 
     def test_partition_verify_skips_ptable_no_flag(self):
         del self.info['flag']
-        block_meta.partition_verify(self.devpath, self.info)
+        block_meta.partition_verify_sfdisk(self.devpath, self.info)
         self.assertEqual(
             [call(self.devpath)],
             self.m_verify_exists.call_args_list)
@@ -2440,6 +2442,46 @@ class TestPartitionVerify(CiTestCase):
         self.assertEqual([], self.m_verify_ptable_flag.call_args_list)
 
 
+class TestPartitionVerifyFdasd(CiTestCase):
+
+    def setUp(self):
+        super(TestPartitionVerifyFdasd, self).setUp()
+        base = 'curtin.commands.block_meta.'
+        self.add_patch(base + 'verify_exists', 'm_verify_exists')
+        self.add_patch(
+            base + 'dasd.DasdPartitionTable.from_fdasd', 'm_from_fdasd')
+        self.add_patch(base + 'verify_size', 'm_verify_size')
+        self.info = {
+            'id': 'disk-sda-part-2',
+            'type': 'partition',
+            'device': 'sda',
+            'number': 2,
+            'size': '5GB',
+            'flag': 'linux',
+        }
+        self.part_size = int(util.human2bytes(self.info['size']))
+        self.devpath = self.random_string()
+
+    def test_partition_verify_sfdisk(self):
+        blocks_per_track = random.randint(10, 20)
+        bytes_per_block = random.randint(1, 8)*512
+        fake_pt = dasd.DasdPartitionTable(
+            '/dev/dasdfoo', blocks_per_track, bytes_per_block)
+        tracks_needed = fake_pt.tracks_needed(
+            util.human2bytes(self.info['size']))
+        fake_pt.partitions = [
+            dasd.DasdPartition(fake_pt, '', 0, 0, tracks_needed, '1', 'Linux'),
+            ]
+        self.m_from_fdasd.side_effect = iter([fake_pt])
+        block_meta.partition_verify_fdasd(self.devpath, 1, self.info)
+        self.assertEqual(
+            [call(self.devpath)],
+            self.m_verify_exists.call_args_list)
+        self.assertEqual(
+            [call(self.devpath)],
+            self.m_from_fdasd.call_args_list)
+
+
 class TestVerifyExists(CiTestCase):
 
     def setUp(self):