Skip to content
This repository has been archived by the owner on Oct 16, 2024. It is now read-only.

Commit

Permalink
Merge pull request #70 from dellgreen/dpg/67/addZFSCompatibilityCheck
Browse files Browse the repository at this point in the history
zfs file system compatibility checks added. #67
  • Loading branch information
dedekind authored Sep 29, 2020
2 parents c42bfd3 + f1cd6ec commit 81d3cc1
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 36 deletions.
58 changes: 58 additions & 0 deletions bmaptools/BmapHelpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,16 @@

import os
import struct
import subprocess
from fcntl import ioctl
from subprocess import PIPE

# Path to check for zfs compatibility.
ZFS_COMPAT_PARAM_PATH = '/sys/module/zfs/parameters/zfs_dmu_offset_next_sync'

class Error(Exception):
"""A class for all the other exceptions raised by this module."""
pass

def human_size(size):
"""Transform size in bytes into a human-readable form."""
Expand Down Expand Up @@ -83,3 +92,52 @@ def program_is_available(name):
return True

return False

def get_file_system_type(path):
"""Return the file system type for 'path'."""

abspath = os.path.realpath(path)
proc = subprocess.Popen(["df", "-T", "--", abspath], stdout=PIPE, stderr=PIPE)
stdout, stderr = proc.communicate()

# Parse the output of subprocess, for example:
# Filesystem Type 1K-blocks Used Available Use% Mounted on
# rpool/USERDATA/foo_5ucog2 zfs 456499712 86956288 369543424 20% /home/foo
ftype = None
if stdout:
lines = stdout.splitlines()
if len(lines) >= 2:
fields = lines[1].split(None, 2)
if len(fields) >= 2:
ftype = fields[1].lower()

if not ftype:
raise Error("failed to find file system type for path at '%s'\n"
"Here is the 'df -T' output\nstdout:\n%s\nstderr:\n%s"
% (path, stdout, stderr))
return ftype

def is_zfs_configuration_compatible():
"""Return if hosts zfs configuration is compatible."""

path = ZFS_COMPAT_PARAM_PATH
if not os.path.isfile(path):
return False

try:
with open(path, "r") as fobj:
return int(fobj.readline()) == 1
except IOError as err:
raise Error("cannot open zfs param path '%s': %s"
% (path, err))
except ValueError as err:
raise Error("invalid value read from param path '%s': %s"
% (path, err))

def is_compatible_file_system(path):
"""Return if paths file system is compatible."""

fstype = get_file_system_type(path)
if fstype == "zfs":
return is_zfs_configuration_compatible()
return True
5 changes: 5 additions & 0 deletions bmaptools/Filemap.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ def __init__(self, image):
raise Error("cannot synchronize image file '%s': %s "
% (self._image_path, err.strerror))

if not BmapHelpers.is_compatible_file_system(self._image_path):
fstype = BmapHelpers.get_file_system_type(self._image_path)
raise Error("image file on incompatible file system '%s': '%s': see docs for fix"
% (self._image_path, fstype))

_log.debug("opened image \"%s\"" % self._image_path)
_log.debug("block size %d, blocks count %d, image size %d"
% (self.block_size, self.blocks_cnt, self.image_size))
Expand Down
71 changes: 36 additions & 35 deletions docs/README
Original file line number Diff line number Diff line change
Expand Up @@ -285,41 +285,42 @@ expand it. Later on, you may reconstruct it using the "bmaptool copy" command.
Project structure
~~~~~~~~~~~~~~~~~

--------------------------------------------------------------------------------
| - bmaptool | A tools to create bmap and copy with bmap. Based |
| | on the 'BmapCreate.py' and 'BmapCopy.py' modules. |
| - setup.py | A script to turn the entire bmap-tools project |
| | into a python egg. |
| - setup.cfg | contains a piece of nose tests configuration |
| - .coveragerc | lists files to include into test coverage report |
| - TODO | Just a list of things to be done for the project. |
| - make_a_release.sh | Most people may ignore this script. It is used by |
| | maintainer when creating a new release. |
| - tests/ | Contains the project unit-tests. |
| | - test_api_base.py | Tests the base API modules: 'BmapCreate.py' and |
| | | 'BmapCopy.py'. |
| | - test_filemap.py | Tests the 'Filemap.py' module. |
| | - test_compat.py | Tests that new BmapCopy implementations support old |
| | | bmap formats, and old BmapCopy implementations |
| | | support new compatible bmap fomrats. |
| | - helpers.py | Helper functions shared between the unit-tests. |
| | - test-data/ | Data files for the unit-tests |
| | - oldcodebase/ | Copies of old BmapCopy implementations for bmap |
| | | format forward-compatibility verification. |
| - bmaptools/ | The API modules which implement all the bmap |
| | | functionality. |
| | - BmapCreate.py | Creates a bmap for a given file. |
| | - BmapCopy.py | Implements copying of an image using its bmap. |
| | - Filemap.py | Allows for reading files' block map. |
| | - BmapHelpers.py | Just helper functions used all over the project. |
| | - TransRead.py | Provides a transparent way to read various kind of |
| | | files (compressed, etc) |
| - debian/* | Debian packaging for the project. |
| - doc/* | Project documentation. |
| - packaging/* | RPM packaging (Fedora & OpenSuse) for the project. |
| - contrib/* | Various contributions that may be useful, but |
| | project maintainers do not really test or maintain. |
--------------------------------------------------------------------------------
------------------------------------------------------------------------------------
| - bmaptool | A tools to create bmap and copy with bmap. Based |
| | on the 'BmapCreate.py' and 'BmapCopy.py' modules. |
| - setup.py | A script to turn the entire bmap-tools project |
| | into a python egg. |
| - setup.cfg | contains a piece of nose tests configuration |
| - .coveragerc | lists files to include into test coverage report |
| - TODO | Just a list of things to be done for the project. |
| - make_a_release.sh | Most people may ignore this script. It is used by |
| | maintainer when creating a new release. |
| - tests/ | Contains the project unit-tests. |
| | - test_api_base.py | Tests the base API modules: 'BmapCreate.py' and |
| | | 'BmapCopy.py'. |
| | - test_filemap.py | Tests the 'Filemap.py' module. |
| | - test_compat.py | Tests that new BmapCopy implementations support old |
| | | bmap formats, and old BmapCopy implementations |
| | | support new compatible bmap fomrats. |
| | - test_bmap_helpers.py | Tests the 'BmapHelpers.py' module. |
| | - helpers.py | Helper functions shared between the unit-tests. |
| | - test-data/ | Data files for the unit-tests |
| | - oldcodebase/ | Copies of old BmapCopy implementations for bmap |
| | | format forward-compatibility verification. |
| - bmaptools/ | The API modules which implement all the bmap |
| | | functionality. |
| | - BmapCreate.py | Creates a bmap for a given file. |
| | - BmapCopy.py | Implements copying of an image using its bmap. |
| | - Filemap.py | Allows for reading files' block map. |
| | - BmapHelpers.py | Just helper functions used all over the project. |
| | - TransRead.py | Provides a transparent way to read various kind of |
| | | files (compressed, etc) |
| - debian/* | Debian packaging for the project. |
| - doc/* | Project documentation. |
| - packaging/* | RPM packaging (Fedora & OpenSuse) for the project. |
| - contrib/* | Various contributions that may be useful, but |
| | project maintainers do not really test or maintain. |
------------------------------------------------------------------------------------

How to run unit tests
~~~~~~~~~~~~~~~~~~~~~
Expand Down
4 changes: 3 additions & 1 deletion requirements-test.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
six
nose
nose
backports.tempfile
mock
146 changes: 146 additions & 0 deletions tests/test_bmap_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
# vim: ts=4 sw=4 et ai si
#
# Copyright (c) 2012-2014 Intel, Inc.
# License: GPLv2
# Author: Artem Bityutskiy <[email protected]>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2,
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.

"""
This test verifies 'BmapHelpers' module functionality.
"""

import os
import sys
import tempfile
from mock import patch, mock
from backports import tempfile as btempfile
from bmaptools import BmapHelpers


# This is a work-around for Centos 6
try:
import unittest2 as unittest # pylint: disable=F0401
except ImportError:
import unittest


class TestBmapHelpers(unittest.TestCase):
"""The test class for these unit tests."""

def test_get_file_system_type(self):
"""Check a file system type is returned when used with a file"""

with tempfile.NamedTemporaryFile("r", prefix="testfile_",
delete=True, dir=".", suffix=".img") as fobj:
fstype = BmapHelpers.get_file_system_type(fobj.name)
self.assertTrue(fstype)

def test_get_file_system_type_no_fstype_found(self):
"""Check error raised when supplied file doesnt exist"""

directory = os.path.dirname(__file__)
fobj = os.path.join(directory, "BmapHelpers/file/does/not/exist")
with self.assertRaises(BmapHelpers.Error):
BmapHelpers.get_file_system_type(fobj)

def test_get_file_system_type_symlink(self):
"""Check a file system type is returned when used with a symlink"""

with btempfile.TemporaryDirectory(prefix="testdir_", dir=".") as directory:
fobj = tempfile.NamedTemporaryFile("r", prefix="testfile_", delete=False,
dir=directory, suffix=".img")
lnk = os.path.join(directory, "test_symlink")
os.symlink(fobj.name, lnk)
fstype = BmapHelpers.get_file_system_type(lnk)
self.assertTrue(fstype)

def test_is_zfs_configuration_compatible_enabled(self):
"""Check compatiblilty check is true when zfs param is set correctly"""

with tempfile.NamedTemporaryFile("w+", prefix="testfile_",
delete=True, dir=".", suffix=".txt") as fobj:
fobj.write("1")
fobj.flush()
mockobj = mock.patch.object(BmapHelpers, "ZFS_COMPAT_PARAM_PATH", fobj.name)
with mockobj:
self.assertTrue(BmapHelpers.is_zfs_configuration_compatible())


def test_is_zfs_configuration_compatible_disabled(self):
"""Check compatiblilty check is false when zfs param is set incorrectly"""

with tempfile.NamedTemporaryFile("w+", prefix="testfile_",
delete=True, dir=".", suffix=".txt") as fobj:
fobj.write("0")
fobj.flush()
mockobj = mock.patch.object(BmapHelpers, "ZFS_COMPAT_PARAM_PATH", fobj.name)
with mockobj:
self.assertFalse(BmapHelpers.is_zfs_configuration_compatible())

def test_is_zfs_configuration_compatible_invalid_read_value(self):
"""Check error raised if any content of zfs config file invalid"""

with tempfile.NamedTemporaryFile("a", prefix="testfile_",
delete=True, dir=".", suffix=".txt") as fobj:
mockobj = mock.patch.object(BmapHelpers, "ZFS_COMPAT_PARAM_PATH", fobj.name)
with self.assertRaises(BmapHelpers.Error):
with mockobj:
BmapHelpers.is_zfs_configuration_compatible()

@patch("builtins.open" if sys.version_info[0] >= 3 else "__builtin__.open")
def test_is_zfs_configuration_compatible_unreadable_file(self, mock_open):
"""Check error raised if any IO errors when checking zfs config file"""

mock_open.side_effect = IOError
with self.assertRaises(BmapHelpers.Error):
BmapHelpers.is_zfs_configuration_compatible()

def test_is_zfs_configuration_compatible_notinstalled(self):
"""Check compatiblilty check passes when zfs not installed"""

directory = os.path.dirname(__file__)
filepath = os.path.join(directory, "BmapHelpers/file/does/not/exist")
mockobj = mock.patch.object(BmapHelpers, "ZFS_COMPAT_PARAM_PATH", filepath)
with mockobj:
self.assertFalse(BmapHelpers.is_zfs_configuration_compatible())

@patch.object(BmapHelpers, "get_file_system_type", return_value="zfs")
def test_is_compatible_file_system_zfs_valid(self, mock_get_fs_type): #pylint: disable=unused-argument
"""Check compatiblilty check passes when zfs param is set correctly"""

with tempfile.NamedTemporaryFile("w+", prefix="testfile_",
delete=True, dir=".", suffix=".img") as fobj:
fobj.write("1")
fobj.flush()
mockobj = mock.patch.object(BmapHelpers, "ZFS_COMPAT_PARAM_PATH", fobj.name)
with mockobj:
self.assertTrue(BmapHelpers.is_compatible_file_system(fobj.name))

@patch.object(BmapHelpers, "get_file_system_type", return_value="zfs")
def test_is_compatible_file_system_zfs_invalid(self, mock_get_fs_type): #pylint: disable=unused-argument
"""Check compatiblilty check fails when zfs param is set incorrectly"""

with tempfile.NamedTemporaryFile("w+", prefix="testfile_",
delete=True, dir=".", suffix=".img") as fobj:
fobj.write("0")
fobj.flush()
mockobj = mock.patch.object(BmapHelpers, "ZFS_COMPAT_PARAM_PATH", fobj.name)
with mockobj:
self.assertFalse(BmapHelpers.is_compatible_file_system(fobj.name))

@patch.object(BmapHelpers, "get_file_system_type", return_value="ext4")
def test_is_compatible_file_system_ext4(self, mock_get_fs_type): #pylint: disable=unused-argument
"""Check non-zfs file systems pass compatiblilty checks"""

with tempfile.NamedTemporaryFile("w+", prefix="testfile_",
delete=True, dir=".", suffix=".img") as fobj:
self.assertTrue(BmapHelpers.is_compatible_file_system(fobj.name))

0 comments on commit 81d3cc1

Please sign in to comment.