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

Commit 81d3cc1

Browse files
authored
Merge pull request #70 from dellgreen/dpg/67/addZFSCompatibilityCheck
zfs file system compatibility checks added. #67
2 parents c42bfd3 + f1cd6ec commit 81d3cc1

File tree

5 files changed

+248
-36
lines changed

5 files changed

+248
-36
lines changed

bmaptools/BmapHelpers.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,16 @@
2020

2121
import os
2222
import struct
23+
import subprocess
2324
from fcntl import ioctl
25+
from subprocess import PIPE
26+
27+
# Path to check for zfs compatibility.
28+
ZFS_COMPAT_PARAM_PATH = '/sys/module/zfs/parameters/zfs_dmu_offset_next_sync'
29+
30+
class Error(Exception):
31+
"""A class for all the other exceptions raised by this module."""
32+
pass
2433

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

8594
return False
95+
96+
def get_file_system_type(path):
97+
"""Return the file system type for 'path'."""
98+
99+
abspath = os.path.realpath(path)
100+
proc = subprocess.Popen(["df", "-T", "--", abspath], stdout=PIPE, stderr=PIPE)
101+
stdout, stderr = proc.communicate()
102+
103+
# Parse the output of subprocess, for example:
104+
# Filesystem Type 1K-blocks Used Available Use% Mounted on
105+
# rpool/USERDATA/foo_5ucog2 zfs 456499712 86956288 369543424 20% /home/foo
106+
ftype = None
107+
if stdout:
108+
lines = stdout.splitlines()
109+
if len(lines) >= 2:
110+
fields = lines[1].split(None, 2)
111+
if len(fields) >= 2:
112+
ftype = fields[1].lower()
113+
114+
if not ftype:
115+
raise Error("failed to find file system type for path at '%s'\n"
116+
"Here is the 'df -T' output\nstdout:\n%s\nstderr:\n%s"
117+
% (path, stdout, stderr))
118+
return ftype
119+
120+
def is_zfs_configuration_compatible():
121+
"""Return if hosts zfs configuration is compatible."""
122+
123+
path = ZFS_COMPAT_PARAM_PATH
124+
if not os.path.isfile(path):
125+
return False
126+
127+
try:
128+
with open(path, "r") as fobj:
129+
return int(fobj.readline()) == 1
130+
except IOError as err:
131+
raise Error("cannot open zfs param path '%s': %s"
132+
% (path, err))
133+
except ValueError as err:
134+
raise Error("invalid value read from param path '%s': %s"
135+
% (path, err))
136+
137+
def is_compatible_file_system(path):
138+
"""Return if paths file system is compatible."""
139+
140+
fstype = get_file_system_type(path)
141+
if fstype == "zfs":
142+
return is_zfs_configuration_compatible()
143+
return True

bmaptools/Filemap.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ def __init__(self, image):
100100
raise Error("cannot synchronize image file '%s': %s "
101101
% (self._image_path, err.strerror))
102102

103+
if not BmapHelpers.is_compatible_file_system(self._image_path):
104+
fstype = BmapHelpers.get_file_system_type(self._image_path)
105+
raise Error("image file on incompatible file system '%s': '%s': see docs for fix"
106+
% (self._image_path, fstype))
107+
103108
_log.debug("opened image \"%s\"" % self._image_path)
104109
_log.debug("block size %d, blocks count %d, image size %d"
105110
% (self.block_size, self.blocks_cnt, self.image_size))

docs/README

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -285,41 +285,42 @@ expand it. Later on, you may reconstruct it using the "bmaptool copy" command.
285285
Project structure
286286
~~~~~~~~~~~~~~~~~
287287

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

324325
How to run unit tests
325326
~~~~~~~~~~~~~~~~~~~~~

requirements-test.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
six
2-
nose
2+
nose
3+
backports.tempfile
4+
mock

tests/test_bmap_helpers.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# -*- coding: utf-8 -*-
2+
# vim: ts=4 sw=4 et ai si
3+
#
4+
# Copyright (c) 2012-2014 Intel, Inc.
5+
# License: GPLv2
6+
# Author: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>
7+
#
8+
# This program is free software; you can redistribute it and/or modify
9+
# it under the terms of the GNU General Public License, version 2,
10+
# as published by the Free Software Foundation.
11+
#
12+
# This program is distributed in the hope that it will be useful, but
13+
# WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
# General Public License for more details.
16+
17+
"""
18+
This test verifies 'BmapHelpers' module functionality.
19+
"""
20+
21+
import os
22+
import sys
23+
import tempfile
24+
from mock import patch, mock
25+
from backports import tempfile as btempfile
26+
from bmaptools import BmapHelpers
27+
28+
29+
# This is a work-around for Centos 6
30+
try:
31+
import unittest2 as unittest # pylint: disable=F0401
32+
except ImportError:
33+
import unittest
34+
35+
36+
class TestBmapHelpers(unittest.TestCase):
37+
"""The test class for these unit tests."""
38+
39+
def test_get_file_system_type(self):
40+
"""Check a file system type is returned when used with a file"""
41+
42+
with tempfile.NamedTemporaryFile("r", prefix="testfile_",
43+
delete=True, dir=".", suffix=".img") as fobj:
44+
fstype = BmapHelpers.get_file_system_type(fobj.name)
45+
self.assertTrue(fstype)
46+
47+
def test_get_file_system_type_no_fstype_found(self):
48+
"""Check error raised when supplied file doesnt exist"""
49+
50+
directory = os.path.dirname(__file__)
51+
fobj = os.path.join(directory, "BmapHelpers/file/does/not/exist")
52+
with self.assertRaises(BmapHelpers.Error):
53+
BmapHelpers.get_file_system_type(fobj)
54+
55+
def test_get_file_system_type_symlink(self):
56+
"""Check a file system type is returned when used with a symlink"""
57+
58+
with btempfile.TemporaryDirectory(prefix="testdir_", dir=".") as directory:
59+
fobj = tempfile.NamedTemporaryFile("r", prefix="testfile_", delete=False,
60+
dir=directory, suffix=".img")
61+
lnk = os.path.join(directory, "test_symlink")
62+
os.symlink(fobj.name, lnk)
63+
fstype = BmapHelpers.get_file_system_type(lnk)
64+
self.assertTrue(fstype)
65+
66+
def test_is_zfs_configuration_compatible_enabled(self):
67+
"""Check compatiblilty check is true when zfs param is set correctly"""
68+
69+
with tempfile.NamedTemporaryFile("w+", prefix="testfile_",
70+
delete=True, dir=".", suffix=".txt") as fobj:
71+
fobj.write("1")
72+
fobj.flush()
73+
mockobj = mock.patch.object(BmapHelpers, "ZFS_COMPAT_PARAM_PATH", fobj.name)
74+
with mockobj:
75+
self.assertTrue(BmapHelpers.is_zfs_configuration_compatible())
76+
77+
78+
def test_is_zfs_configuration_compatible_disabled(self):
79+
"""Check compatiblilty check is false when zfs param is set incorrectly"""
80+
81+
with tempfile.NamedTemporaryFile("w+", prefix="testfile_",
82+
delete=True, dir=".", suffix=".txt") as fobj:
83+
fobj.write("0")
84+
fobj.flush()
85+
mockobj = mock.patch.object(BmapHelpers, "ZFS_COMPAT_PARAM_PATH", fobj.name)
86+
with mockobj:
87+
self.assertFalse(BmapHelpers.is_zfs_configuration_compatible())
88+
89+
def test_is_zfs_configuration_compatible_invalid_read_value(self):
90+
"""Check error raised if any content of zfs config file invalid"""
91+
92+
with tempfile.NamedTemporaryFile("a", prefix="testfile_",
93+
delete=True, dir=".", suffix=".txt") as fobj:
94+
mockobj = mock.patch.object(BmapHelpers, "ZFS_COMPAT_PARAM_PATH", fobj.name)
95+
with self.assertRaises(BmapHelpers.Error):
96+
with mockobj:
97+
BmapHelpers.is_zfs_configuration_compatible()
98+
99+
@patch("builtins.open" if sys.version_info[0] >= 3 else "__builtin__.open")
100+
def test_is_zfs_configuration_compatible_unreadable_file(self, mock_open):
101+
"""Check error raised if any IO errors when checking zfs config file"""
102+
103+
mock_open.side_effect = IOError
104+
with self.assertRaises(BmapHelpers.Error):
105+
BmapHelpers.is_zfs_configuration_compatible()
106+
107+
def test_is_zfs_configuration_compatible_notinstalled(self):
108+
"""Check compatiblilty check passes when zfs not installed"""
109+
110+
directory = os.path.dirname(__file__)
111+
filepath = os.path.join(directory, "BmapHelpers/file/does/not/exist")
112+
mockobj = mock.patch.object(BmapHelpers, "ZFS_COMPAT_PARAM_PATH", filepath)
113+
with mockobj:
114+
self.assertFalse(BmapHelpers.is_zfs_configuration_compatible())
115+
116+
@patch.object(BmapHelpers, "get_file_system_type", return_value="zfs")
117+
def test_is_compatible_file_system_zfs_valid(self, mock_get_fs_type): #pylint: disable=unused-argument
118+
"""Check compatiblilty check passes when zfs param is set correctly"""
119+
120+
with tempfile.NamedTemporaryFile("w+", prefix="testfile_",
121+
delete=True, dir=".", suffix=".img") as fobj:
122+
fobj.write("1")
123+
fobj.flush()
124+
mockobj = mock.patch.object(BmapHelpers, "ZFS_COMPAT_PARAM_PATH", fobj.name)
125+
with mockobj:
126+
self.assertTrue(BmapHelpers.is_compatible_file_system(fobj.name))
127+
128+
@patch.object(BmapHelpers, "get_file_system_type", return_value="zfs")
129+
def test_is_compatible_file_system_zfs_invalid(self, mock_get_fs_type): #pylint: disable=unused-argument
130+
"""Check compatiblilty check fails when zfs param is set incorrectly"""
131+
132+
with tempfile.NamedTemporaryFile("w+", prefix="testfile_",
133+
delete=True, dir=".", suffix=".img") as fobj:
134+
fobj.write("0")
135+
fobj.flush()
136+
mockobj = mock.patch.object(BmapHelpers, "ZFS_COMPAT_PARAM_PATH", fobj.name)
137+
with mockobj:
138+
self.assertFalse(BmapHelpers.is_compatible_file_system(fobj.name))
139+
140+
@patch.object(BmapHelpers, "get_file_system_type", return_value="ext4")
141+
def test_is_compatible_file_system_ext4(self, mock_get_fs_type): #pylint: disable=unused-argument
142+
"""Check non-zfs file systems pass compatiblilty checks"""
143+
144+
with tempfile.NamedTemporaryFile("w+", prefix="testfile_",
145+
delete=True, dir=".", suffix=".img") as fobj:
146+
self.assertTrue(BmapHelpers.is_compatible_file_system(fobj.name))

0 commit comments

Comments
 (0)