[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[rhel6-branch] edd: refactor and enhance the edd module.



This strengthens the 'bios id' -> 'device name' mapping algorithm by
looking at the pci device and interface properties specified in the edd
data.

Includes a unit test.

Resolves: rhbz#621175
Related: rhbz#694800
---
 storage/devicelibs/edd.py            |  230 +++++++++++++++++++++++++---------
 tests/storage/devicelibs/edd_test.py |  133 ++++++++++++++++++++
 2 files changed, 303 insertions(+), 60 deletions(-)
 create mode 100644 tests/storage/devicelibs/edd_test.py

diff --git a/storage/devicelibs/edd.py b/storage/devicelibs/edd.py
index da03914..20a7e64 100644
--- a/storage/devicelibs/edd.py
+++ b/storage/devicelibs/edd.py
@@ -18,80 +18,190 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 # Author(s): Hans de Goede <hdegoede redhat com>
+#            Ales Kozumplik <akozumpl redhat com>
 #
 
+import glob
+import logging
 import os
+import re
 import struct
 
-import logging
 log = logging.getLogger("storage")
 
-def get_edd_dict(devices):
-    """Given an array of devices return a dict with the BIOS ID for them."""
-    edd_dict = {}
+re_host_bus = re.compile("^PCI\s*(\S*)\s*channel: (\S*)\s*$")
+re_interface_scsi = re.compile("^SCSI\s*id: (\S*)\s*lun: (\S*)\s*$")
+re_interface_ata = re.compile("^ATA\s*device: (\S*)\s*$")
 
-    for biosdev in range(80, 80 + 15):
-        sysfspath = "/sys/firmware/edd/int13_dev%d" % biosdev
-        if not os.path.exists(sysfspath):
-            break # We are done
+class EddEntry(object):
+    """ This object merely collects what the /sys/firmware/edd/* entries can
+        provide.
+    """
+    def __init__(self, sysfspath):
+        self.type = None
+
+        self.ata_device = None
+        self.channel = None
+        self.mbr_signature = None
+        self.pci_dev = None
+        self.scsi_id = None
+        self.scsi_lun = None
+        self.sectors = None
+
+        self.load(sysfspath)
+
+    def __str__(self):
+        return \
+            "\ttype: %(type)s, ata_device: %(ata_device)s\n" \
+            "\tchannel: %(channel)s, mbr_signature: %(mbr_signature)s\n" \
+            "\tpci_dev: %(pci_dev)s, scsi_id: %(scsi_id)s\n" \
+            "\tscsi_lun: %(scsi_lun)s, sectors: %(sectors)s" % self.__dict__
+
+    def _read_file(self, filename):
+        contents = None
+        if os.path.exists(filename):
+            with open(filename) as f:
+                contents = f.read().rstrip()
+        return contents
+
+    def load(self, sysfspath):
+        interface = self._read_file(os.path.join(sysfspath, "interface"))
+        if interface:
+            self.type = interface.split()[0]
+            if self.type == "SCSI":
+                match = re_interface_scsi.match(interface)
+                self.scsi_id = int(match.group(1))
+                self.scsi_lun = int(match.group(2))
+            elif self.type == "ATA":
+                match = re_interface_ata.match(interface)
+                self.ata_device = int(match.group(1))
+
+        self.mbr_signature = self._read_file(
+            os.path.join(sysfspath, "mbr_signature"))
+        sectors = self._read_file(os.path.join(sysfspath, "sectors"))
+        if sectors:
+            self.sectors = int(sectors)
+        hbus = self._read_file(os.path.join(sysfspath, "host_bus"))
+        if hbus:
+            match = re_host_bus.match(hbus)
+            self.pci_dev = match.group(1)
+            self.channel = int(match.group(2))
+
+class EddMatcher(object):
+    """ This object tries to match given entry to a disk device name.
+
+        Assuming, heuristic analysis and guessing hapens here.
+    """
+    def __init__(self, edd_entry):
+        self.edd = edd_entry
 
-        sysfspath = "/sys/firmware/edd/int13_dev%d/mbr_signature" % biosdev
+    def devname_from_pci_dev(self):
+        name = None
+        if self.edd.type == "ATA":
+            path = "/sys/devices/pci0000:00/0000:%(pci_dev)s/host%(chan)d/"\
+                "target%(chan)d:0:%(dev)d/%(chan)d:0:%(dev)d:0/block" % {
+                'pci_dev' : self.edd.pci_dev,
+                'chan' : self.edd.channel,
+                'dev' : self.edd.ata_device
+                }
+            block_entries = os.listdir(path)
+            if len(block_entries) == 1:
+                name = block_entries[0]
+        elif self.edd.type == "SCSI":
+            pattern = "/sys/devices/pci0000:00/0000:%(pci_dev)s/virtio*/block" % \
+                {'pci_dev' : self.edd.pci_dev}
+            matching_paths = glob.glob(pattern)
+            if len(matching_paths) != 1 or not os.path.exists(matching_paths[0]):
+                return None
+            block_entries = os.listdir(matching_paths[0])
+            if len(block_entries) == 1:
+                name = block_entries[0]
+        return name
+
+    def match_via_mbrsigs(self, mbr_dict):
+        """ Try to match the edd entry based on its mbr signature.
+
+            This will obviously fail for a fresh drive/image, but in extreme
+            cases can also show false positives for randomly matching data.
+        """
+        for (name, mbr_signature) in mbr_dict.items():
+            if mbr_signature == self.edd.mbr_signature:
+                return name
+        return None
+
+def biosdev_to_edd_dir(biosdev):
+    return "/sys/firmware/edd/int13_dev%x" % biosdev
+
+def collect_edd_data():
+    edd_data_dict = {}
+    for biosdev in range(0x80, 0x80+16):
+        sysfspath = biosdev_to_edd_dir(biosdev)
         if not os.path.exists(sysfspath):
-            log.warning("No mbrsig for biosdev: %d" % biosdev)
-            continue
+            break
+        edd_data_dict[biosdev] = EddEntry(sysfspath)
+    return edd_data_dict
 
+def collect_mbrs(devices):
+    mbr_dict = {}
+    for dev in devices:
         try:
-            file = open(sysfspath, "r")
-            eddsig = file.read()
-            file.close()
-        except (IOError, OSError) as e:
-            log.warning("Error reading EDD mbrsig for %d: %s" %
-                        (biosdev, str(e)))
+            fd = os.open(dev.path, os.O_RDONLY)
+            os.lseek(fd, 440, 0)
+            mbrsig = struct.unpack('I', os.read(fd, 4))
+            os.close(fd)
+        except OSError as e:
+            log.warning("Error reading mbrsig from disk %s: %s" %
+                        (dev.name, str(e)))
             continue
 
-        sysfspath = "/sys/firmware/edd/int13_dev%d/sectors" % biosdev
-        try:
-            file = open(sysfspath, "r")
-            eddsize = file.read()
-            file.close()
-        except (IOError, OSError) as e:
-            eddsize = None
-
-        found = []
-        for dev in devices:
-            try:
-                fd = os.open(dev.path, os.O_RDONLY)
-                os.lseek(fd, 440, 0) 
-                mbrsig = struct.unpack('I', os.read(fd, 4))
-                os.close(fd)
-            except OSError as e:
-                log.warning("Error reading mbrsig from disk %s: %s" %
-                            (dev.name, str(e)))
-                continue
-
-            mbrsigStr = "0x%08x\n" % mbrsig
-            if mbrsigStr == eddsig:
-                if eddsize:
-                    sysfspath = "/sys%s/size" % dev.sysfsPath
-                    try:
-                        file = open(sysfspath, "r")
-                        size = file.read()
-                        file.close()
-                    except (IOError, OSError) as e:
-                        log.warning("Error getting size for: %s" % dev.name)
-                        continue
-                    if eddsize != size:
-                        continue
-                found.append(dev.name)
-
-        if not found:
-            log.error("No matching mbr signature found for biosdev %d" %
-                      biosdev)
-        elif len(found) > 1:
-            log.error("Multiple signature matches found for biosdev %d: %s" %
-                      (biosdev, str(found)))
+        mbrsig_str = "0x%08x" % mbrsig
+        # sanity check
+        if mbrsig_str == '0x00000000':
+            log.info("edd: MBR signature on %s is zero. new disk image?" % dev.name)
+            continue
         else:
-            log.info("Found %s for biosdev %d" %(found[0], biosdev))
-            edd_dict[found[0]] = biosdev
+            for (dev_name, mbrsig_str_old) in mbr_dict.items():
+                if mbrsig_str_old == mbrsig_str:
+                    log.error("edd: dupicite MBR signature %s for %s and %s" %
+                              (mbrsig_str, dev_name, dev.name))
+                    # this actually makes all the other data useless
+                    return {}
+        # update the dictionary
+        mbr_dict[dev.name] = mbrsig_str
+    log.info("edd: collected mbr signatures: %s" % mbr_dict)
+    return mbr_dict
 
+def get_edd_dict(devices):
+    """ Generates the 'device name' -> 'edd number' mapping.
+
+        Note: The EDD kernel module that exposes /sys/firmware/edd is thoroughly
+        broken, the information there is incomplete and sometimes downright
+        wrong.
+    """
+    mbr_dict = collect_mbrs(devices)
+    edd_entries_dict = collect_edd_data()
+    edd_dict = {}
+    for (edd_number, edd_entry) in edd_entries_dict.items():
+        log.debug("edd: data extracted from 0x%x:\n%s" % (edd_number, edd_entry))
+        matcher = EddMatcher(edd_entry)
+        # first try to match through the pci dev etc.
+        name = matcher.devname_from_pci_dev()
+        # next try to compare mbr signatures
+        if name:
+            log.debug("matched 0x%x to %s using pci_dev" % (edd_number, name))
+        else:
+            name = matcher.match_via_mbrsigs(mbr_dict)
+            if name:
+                log.info("matched 0x%x to %s using MBR sig" % (edd_number, name))
+
+        if name:
+            old_edd_number = edd_dict.get(name)
+            if old_edd_number:
+                log.info("edd: both edd entries 0x%x and 0x%x seem to map to %s" %
+                          old_edd_number, edd_number, name)
+                # this means all the other data can be confused and useless
+                return {}
+            edd_dict[name] = edd_number
+            continue
+        log.error("edd: unable to match edd entry 0x%x" % edd_number)
     return edd_dict
diff --git a/tests/storage/devicelibs/edd_test.py b/tests/storage/devicelibs/edd_test.py
new file mode 100644
index 0000000..ab87307
--- /dev/null
+++ b/tests/storage/devicelibs/edd_test.py
@@ -0,0 +1,133 @@
+import mock
+
+class EddTestCase(mock.TestCase):
+    def setUp(self):
+        self.setupModules(
+            ['_isys', 'logging', 'anaconda_log', 'block'])
+
+    def tearDown(self):
+        from storage.devicelibs import edd
+        self.tearDownModules()
+        mock.DiskIO.restore_module(edd)
+
+    def test_biosdev_to_edd_dir(self):
+        from storage.devicelibs import edd
+        path = edd.biosdev_to_edd_dir(138)
+        self.assertEqual("/sys/firmware/edd/int13_dev8a", path)
+
+    def test_collect_edd_data(self):
+        from storage.devicelibs import edd
+
+        # test with vda, vdb
+        fs = EddTestFS(edd).vda_vdb()
+        edd_dict = edd.collect_edd_data()
+        self.assertEqual(len(edd_dict), 2)
+        self.assertEqual(edd_dict[0x80].type, "SCSI")
+        self.assertEqual(edd_dict[0x80].scsi_id, 0)
+        self.assertEqual(edd_dict[0x80].scsi_lun, 0)
+        self.assertEqual(edd_dict[0x80].pci_dev, "00:05.0")
+        self.assertEqual(edd_dict[0x80].channel, 0)
+        self.assertEqual(edd_dict[0x80].sectors, 16777216)
+        self.assertEqual(edd_dict[0x81].pci_dev, "00:06.0")
+
+        # test with sda, vda
+        fs = EddTestFS(edd).sda_vda()
+        edd_dict = edd.collect_edd_data()
+        self.assertEqual(len(edd_dict), 2)
+        self.assertEqual(edd_dict[0x80].type, "ATA")
+        self.assertEqual(edd_dict[0x80].scsi_id, None)
+        self.assertEqual(edd_dict[0x80].scsi_lun, None)
+        self.assertEqual(edd_dict[0x80].pci_dev, "00:01.1")
+        self.assertEqual(edd_dict[0x80].channel, 0)
+        self.assertEqual(edd_dict[0x80].sectors, 2097152)
+        self.assertEqual(edd_dict[0x80].ata_device, 0)
+        self.assertEqual(edd_dict[0x80].mbr_signature, "0x000ccb01")
+
+    def test_edd_entry_str(self):
+        from storage.devicelibs import edd
+        fs = EddTestFS(edd).sda_vda()
+        edd_dict = edd.collect_edd_data()
+        expected_output = """\ttype: ATA, ata_device: 0
+\tchannel: 0, mbr_signature: 0x000ccb01
+\tpci_dev: 00:01.1, scsi_id: None
+\tscsi_lun: None, sectors: 2097152"""
+        self.assertEqual(str(edd_dict[0x80]), expected_output)
+
+    def test_matcher_device_path(self):
+        from storage.devicelibs import edd
+        fs = EddTestFS(edd).sda_vda()
+        edd_dict = edd.collect_edd_data()
+
+        analyzer = edd.EddMatcher(edd_dict[0x80])
+        path = analyzer.devname_from_pci_dev()
+        self.assertEqual(path, "sda")
+
+        analyzer = edd.EddMatcher(edd_dict[0x81])
+        path = analyzer.devname_from_pci_dev()
+        self.assertEqual(path, "vda")
+
+    def test_get_edd_dict_1(self):
+        """ Test get_edd_dict()'s pci_dev matching. """
+        from storage.devicelibs import edd
+        fs = EddTestFS(edd).sda_vda()
+        self.assertEqual(edd.get_edd_dict([]),
+                         {'sda' : 0x80,
+                          'vda' : 0x81})
+
+    def test_get_edd_dict_2(self):
+        """ Test get_edd_dict()'s pci_dev matching. """
+        from storage.devicelibs import edd
+        edd.collect_mbrs = mock.Mock(return_value = {
+                'sda' : '0x000ccb01',
+                'vda' : '0x0006aef1'})
+        fs = EddTestFS(edd).sda_vda_missing_details()
+        self.assertEqual(edd.get_edd_dict([]),
+                         {'sda' : 0x80,
+                          'vda' : 0x81})
+
+
+
+class EddTestFS(object):
+    def __init__(self, target_module):
+        self.fs = mock.DiskIO()
+        self.fs.take_over_module(target_module)
+
+    def sda_vda_missing_details(self):
+        self.fs["/sys/firmware/edd/int13_dev80"] = self.fs.Dir()
+        self.fs["/sys/firmware/edd/int13_dev80/mbr_signature"] = "0x000ccb01"
+        self.fs["/sys/firmware/edd/int13_dev81"] = self.fs.Dir()
+        self.fs["/sys/firmware/edd/int13_dev81/mbr_signature"] = "0x0006aef1"
+
+    def sda_vda(self):
+        self.fs["/sys/firmware/edd/int13_dev80"] = self.fs.Dir()
+        self.fs["/sys/firmware/edd/int13_dev80/host_bus"] = "PCI 	00:01.1  channel: 0\n"
+        self.fs["/sys/firmware/edd/int13_dev80/interface"] = "ATA     	device: 0\n"
+        self.fs["/sys/firmware/edd/int13_dev80/mbr_signature"] = "0x000ccb01"
+        self.fs["/sys/firmware/edd/int13_dev80/sectors"] = "2097152\n"
+
+        self.fs["/sys/firmware/edd/int13_dev81"] = self.fs.Dir()
+        self.fs["/sys/firmware/edd/int13_dev81/host_bus"] = "PCI 	00:05.0  channel: 0\n"
+        self.fs["/sys/firmware/edd/int13_dev81/interface"] = "SCSI    	id: 0  lun: 0\n"
+        self.fs["/sys/firmware/edd/int13_dev81/mbr_signature"] = "0x0006aef1"
+        self.fs["/sys/firmware/edd/int13_dev81/sectors"] = "16777216\n"
+
+        self.fs["/sys/devices/pci0000:00/0000:00:01.1/host0/target0:0:0/0:0:0:0/block"] = self.fs.Dir()
+        self.fs["/sys/devices/pci0000:00/0000:00:01.1/host0/target0:0:0/0:0:0:0/block/sda"] = self.fs.Dir()
+
+        self.fs["/sys/devices/pci0000:00/0000:00:05.0/virtio2/block"] = self.fs.Dir()
+        self.fs["/sys/devices/pci0000:00/0000:00:05.0/virtio2/block/vda"] = self.fs.Dir()
+
+        return self.fs
+
+    def vda_vdb(self):
+        self.fs["/sys/firmware/edd/int13_dev80"] = self.fs.Dir()
+        self.fs["/sys/firmware/edd/int13_dev80/host_bus"] = "PCI 	00:05.0  channel: 0\n"
+        self.fs["/sys/firmware/edd/int13_dev80/interface"] = "SCSI    	id: 0  lun: 0\n"
+        self.fs["/sys/firmware/edd/int13_dev80/sectors"] = "16777216\n"
+
+        self.fs["/sys/firmware/edd/int13_dev81"] = self.fs.Dir()
+        self.fs["/sys/firmware/edd/int13_dev81/host_bus"] = "PCI 	00:06.0  channel: 0\n"
+        self.fs["/sys/firmware/edd/int13_dev81/interface"] = "SCSI    	id: 0  lun: 0\n"
+        self.fs["/sys/firmware/edd/int13_dev81/sectors"] = "4194304\n"
+
+        return self.fs
-- 
1.7.5.4


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]