From ae9e161509267716dedeeb98a5ac7104033f810a Mon Sep 17 00:00:00 2001
From: mulhern <amulhern@redhat.com>
Date: Thu, 18 Sep 2014 08:44:15 -0400
Subject: [PATCH] Check the minimum member size for BtrfsVolumeDevices.

This change forces devices tests to be slightly more realistic in order to
pass, so update them as well.

Also make failing test that relied on literal constant value that was
supposed to be minimum member size pass again and add a few tests to
bracket the minimum member size in case it changes again.

Signed-off-by: mulhern <amulhern@redhat.com>
---
 blivet/devicelibs/btrfs.py          |  4 ++++
 blivet/devices/btrfs.py             |  4 ++++
 tests/devicelibs_test/btrfs_test.py | 22 ++++++++++++++++++++--
 tests/devices_test.py               | 29 +++++++++++++++++++++--------
 4 files changed, 49 insertions(+), 10 deletions(-)

diff --git a/blivet/devicelibs/btrfs.py b/blivet/devicelibs/btrfs.py
index 3e411c8..bead746 100644
--- a/blivet/devicelibs/btrfs.py
+++ b/blivet/devicelibs/btrfs.py
@@ -26,6 +26,7 @@ import re
 from . import raid
 from .. import util
 from ..errors import BTRFSError, BTRFSValueError
+from ..size import Size
 
 import logging
 log = logging.getLogger("blivet")
@@ -33,6 +34,9 @@ log = logging.getLogger("blivet")
 # this is the volume id btrfs always assigns to the top-level volume/tree
 MAIN_VOLUME_ID = 5
 
+# if any component device is less than this size, mkfs.btrfs will fail
+MIN_MEMBER_SIZE = Size("16 MiB")
+
 RAID_levels = raid.RAIDLevels(["raid0", "raid1", "raid10", "single"])
 
 metadata_levels = raid.RAIDLevels(["raid0", "raid1", "raid10", "single", "dup"])
diff --git a/blivet/devices/btrfs.py b/blivet/devices/btrfs.py
index f3e5970..137d785 100644
--- a/blivet/devices/btrfs.py
+++ b/blivet/devices/btrfs.py
@@ -383,6 +383,10 @@ class BTRFSVolumeDevice(BTRFSDevice, ContainerDevice, RaidDevice):
 
         return default
 
+    def _preCreate(self):
+        if any(p.size < btrfs.MIN_MEMBER_SIZE for p in self.parents):
+            raise errors.DeviceCreateError("All BTRFS member devices must have size at least %s." % btrfs.MIN_MEMBER_SIZE)
+
     def _create(self):
         log_method_call(self, self.name, status=self.status)
         btrfs.create_volume(devices=[d.path for d in self.parents],
diff --git a/tests/devicelibs_test/btrfs_test.py b/tests/devicelibs_test/btrfs_test.py
index 91950d2..e3f658e 100755
--- a/tests/devicelibs_test/btrfs_test.py
+++ b/tests/devicelibs_test/btrfs_test.py
@@ -7,7 +7,6 @@ import unittest
 
 import blivet.devicelibs.btrfs as btrfs
 from blivet.errors import BTRFSError
-from blivet.size import Size
 
 from tests import loopbackedtestcase
 
@@ -165,7 +164,7 @@ class BTRFSAsRootTestCase2(BTRFSMountDevice):
 class BTRFSAsRootTestCase3(loopbackedtestcase.LoopBackedTestCase):
 
     def __init__(self, methodName='runTest'):
-        super(BTRFSAsRootTestCase3, self).__init__(methodName=methodName, deviceSpec=[Size("8192 KiB")])
+        super(BTRFSAsRootTestCase3, self).__init__(methodName=methodName, deviceSpec=[btrfs.MIN_MEMBER_SIZE, btrfs.MIN_MEMBER_SIZE])
 
     def testSmallDevice(self):
         """ Creation of a smallish device will result in an error if the
@@ -176,5 +175,24 @@ class BTRFSAsRootTestCase3(loopbackedtestcase.LoopBackedTestCase):
             btrfs.create_volume(self.loopDevices, data="single", metadata="dup")
         self.assertEqual(btrfs.create_volume(self.loopDevices), 0)
 
+class BTRFSTooSmallDeviceTestCase(loopbackedtestcase.LoopBackedTestCase):
+
+    def __init__(self, methodName='runTest'):
+        super(BTRFSTooSmallDeviceTestCase, self).__init__(methodName=methodName, deviceSpec=[btrfs.MIN_MEMBER_SIZE / 2, btrfs.MIN_MEMBER_SIZE])
+
+    def testTooSmallDevice(self):
+        """ If just one device is too small mkfs.btrfs will fail. """
+        with self.assertRaises(BTRFSError):
+            btrfs.create_volume(self.loopDevices)
+
+class BTRFSBigEnoughDeviceTestCase(loopbackedtestcase.LoopBackedTestCase):
+
+    def __init__(self, methodName='runTest'):
+        super(BTRFSBigEnoughDeviceTestCase, self).__init__(methodName=methodName, deviceSpec=[btrfs.MIN_MEMBER_SIZE])
+
+    def testBigEnoughDevice(self):
+        """ If all devices are large enough, mkfs.btrfs will succeed. """
+        self.assertEqual(btrfs.create_volume(self.loopDevices), 0)
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/devices_test.py b/tests/devices_test.py
index 78cdc0f..9c4555e 100644
--- a/tests/devices_test.py
+++ b/tests/devices_test.py
@@ -540,8 +540,9 @@ class BTRFSDeviceTestCase(DeviceStateTestCase):
 
     def setUp(self):
         self.dev1 = BTRFSVolumeDevice("dev1",
-           parents=[OpticalDevice("deva",
-              fmt=blivet.formats.getFormat("btrfs"))])
+           parents=[StorageDevice("deva",
+              fmt=blivet.formats.getFormat("btrfs"),
+              size=btrfs.MIN_MEMBER_SIZE)])
 
         self.dev2 = BTRFSSubVolumeDevice("dev2",
            parents=[self.dev1],
@@ -560,14 +561,20 @@ class BTRFSDeviceTestCase(DeviceStateTestCase):
         """
 
         self.stateCheck(self.dev1,
+           currentSize=xform(lambda x, m: self.assertEqual(x, btrfs.MIN_MEMBER_SIZE, m)),
            dataLevel=xform(self.assertIsNone),
            isleaf=xform(self.assertFalse),
+           maxSize=xform(lambda x, m: self.assertEqual(x, btrfs.MIN_MEMBER_SIZE, m)),
            metaDataLevel=xform(self.assertIsNone),
            parents=xform(lambda x, m: self.assertEqual(len(x), 1, m)),
+           size=xform(lambda x, m: self.assertEqual(x, btrfs.MIN_MEMBER_SIZE, m)),
            type=xform(lambda x, m: self.assertEqual(x, "btrfs volume", m)))
 
         self.stateCheck(self.dev2,
+           currentSize=xform(lambda x, m: self.assertEqual(x, btrfs.MIN_MEMBER_SIZE, m)),
+           maxSize=xform(lambda x, m: self.assertEqual(x, btrfs.MIN_MEMBER_SIZE, m)),
            parents=xform(lambda x, m: self.assertEqual(len(x), 1, m)),
+           size=xform(lambda x, m: self.assertEqual(x, btrfs.MIN_MEMBER_SIZE, m)),
            type=xform(lambda x, m: self.assertEqual(x, "btrfs subvolume", m)),
            vol_id=xform(self.assertIsNone))
 
@@ -584,21 +591,21 @@ class BTRFSDeviceTestCase(DeviceStateTestCase):
             BTRFSVolumeDevice("dev")
 
         with self.assertRaisesRegexp(ValueError, "member has wrong format"):
-            BTRFSVolumeDevice("dev", parents=[OpticalDevice("deva")])
+            BTRFSVolumeDevice("dev", parents=[StorageDevice("deva", size=btrfs.MIN_MEMBER_SIZE)])
 
         with self.assertRaisesRegexp(DeviceError, "btrfs subvolume.*must be a btrfs volume"):
             fmt = blivet.formats.getFormat("btrfs")
-            device = OpticalDevice("deva", fmt=fmt)
+            device = StorageDevice("deva", fmt=fmt, size=btrfs.MIN_MEMBER_SIZE)
             BTRFSSubVolumeDevice("dev1", parents=[device])
 
         deva = OpticalDevice("deva", fmt=blivet.formats.getFormat("btrfs"))
         with self.assertRaisesRegexp(BTRFSValueError, "at least"):
             BTRFSVolumeDevice("dev1", dataLevel="raid1", parents=[deva])
 
-        deva = OpticalDevice("deva", fmt=blivet.formats.getFormat("btrfs"))
+        deva = StorageDevice("deva", fmt=blivet.formats.getFormat("btrfs"), size=btrfs.MIN_MEMBER_SIZE)
         self.assertIsNotNone(BTRFSVolumeDevice("dev1", metaDataLevel="dup", parents=[deva]))
 
-        deva = OpticalDevice("deva", fmt=blivet.formats.getFormat("btrfs"))
+        deva = StorageDevice("deva", fmt=blivet.formats.getFormat("btrfs"), size=btrfs.MIN_MEMBER_SIZE)
         with self.assertRaisesRegexp(BTRFSValueError, "invalid"):
             BTRFSVolumeDevice("dev1", dataLevel="dup", parents=[deva])
 
@@ -628,7 +635,7 @@ class BTRFSDeviceTestCase(DeviceStateTestCase):
             self.dev1.size = 32
 
     def testBTRFSSnapShotDeviceInit(self):
-        parents = [StorageDevice("p1", fmt=blivet.formats.getFormat("btrfs"))]
+        parents = [StorageDevice("p1", fmt=blivet.formats.getFormat("btrfs"), size=btrfs.MIN_MEMBER_SIZE)]
         vol = BTRFSVolumeDevice("test", parents=parents)
         with self.assertRaisesRegexp(ValueError, "non-existent btrfs snapshots must have a source"):
             BTRFSSnapShotDevice("snap1", parents=[vol])
@@ -639,7 +646,7 @@ class BTRFSDeviceTestCase(DeviceStateTestCase):
         with self.assertRaisesRegexp(ValueError, "btrfs snapshot source must be a btrfs subvolume"):
             BTRFSSnapShotDevice("snap1", parents=[vol], source=parents[0])
 
-        parents2 = [StorageDevice("p1", fmt=blivet.formats.getFormat("btrfs"))]
+        parents2 = [StorageDevice("p1", fmt=blivet.formats.getFormat("btrfs"), size=btrfs.MIN_MEMBER_SIZE)]
         vol2 = BTRFSVolumeDevice("test2", parents=parents2, exists=True)
         with self.assertRaisesRegexp(ValueError, ".*snapshot and source must be in the same volume"):
             BTRFSSnapShotDevice("snap1", parents=[vol], source=vol2)
@@ -650,15 +657,21 @@ class BTRFSDeviceTestCase(DeviceStateTestCase):
            parents=[vol],
            source=vol)
         self.stateCheck(snap,
+           currentSize=xform(lambda x, m: self.assertEqual(x, btrfs.MIN_MEMBER_SIZE, m)),
+           maxSize=xform(lambda x, m: self.assertEqual(x, btrfs.MIN_MEMBER_SIZE, m)),
            parents=xform(lambda x, m: self.assertEqual(len(x), 1, m)),
+           size=xform(lambda x, m: self.assertEqual(x, btrfs.MIN_MEMBER_SIZE, m)),
            type=xform(lambda x, m: self.assertEqual(x, "btrfs snapshot", m)),
            vol_id=xform(self.assertIsNone))
         self.stateCheck(vol,
+           currentSize=xform(lambda x, m: self.assertEqual(x, btrfs.MIN_MEMBER_SIZE, m)),
            dataLevel=xform(self.assertIsNone),
            exists=xform(self.assertTrue),
            isleaf=xform(self.assertFalse),
+           maxSize=xform(lambda x, m: self.assertEqual(x, btrfs.MIN_MEMBER_SIZE, m)),
            metaDataLevel=xform(self.assertIsNone),
            parents=xform(lambda x, m: self.assertEqual(len(x), 1, m)),
+           size=xform(lambda x, m: self.assertEqual(x, btrfs.MIN_MEMBER_SIZE, m)),
            type=xform(lambda x, m: self.assertEqual(x, "btrfs volume", m)))
 
         self.assertEqual(snap.isleaf, True)
-- 
1.9.3

