From f817b0451dc5ba69ee1eed04099bfcbba98aedc8 Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Tue, 29 Aug 2017 23:10:27 +0200 Subject: Lvm: create, resize, format and remove logical volumes --- src/Propellor/Property/Lvm.hs | 170 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 src/Propellor/Property/Lvm.hs diff --git a/src/Propellor/Property/Lvm.hs b/src/Propellor/Property/Lvm.hs new file mode 100644 index 00000000..43bbe0ee --- /dev/null +++ b/src/Propellor/Property/Lvm.hs @@ -0,0 +1,170 @@ +-- | Maintainer: Nicolas Schodet +-- +-- Support for LVM logical volumes. + +module Propellor.Property.Lvm ( + lvFormatted, + Eep(..), + installed, +) where + +import Propellor +import Propellor.Base +import Utility.DataUnits +import qualified Propellor.Property.Apt as Apt +import qualified Propellor.Property.Mount as Mount +import qualified Propellor.Property.Partition as Partition + +data Eep = YesReallyFormatLogicalVolume + +type DataSize = String + +type VolumeGroup = String +type LogicalVolume = String + +-- | Create or resize a logical volume, and make sure it is formatted. When +-- reverted, remove the logical volume. +-- +-- Example use: +-- +-- > import qualified Propellor.Property.Lvm as Lvm +-- > import qualified Propellor.Property.Partition as Partition +-- > Lvm.lvFormatted Lvm.YesReallyFormatLogicalVolume "vg0" "test" "16m" +-- > Partition.EXT4 +-- +-- If size and filesystem match, nothing is done. +-- +-- Volume group must have been created yet. +lvFormatted + :: Eep + -> VolumeGroup + -> LogicalVolume + -> DataSize + -> Partition.Fs + -> RevertableProperty DebianLike UnixLike +lvFormatted YesReallyFormatLogicalVolume vg lv sz fs = + setup cleanup + where + setup :: Property DebianLike + setup = property' ("formatted logical volume " ++ lv) $ \w -> do + es <- liftIO $ vgExtentSize vg + case es of + Nothing -> errorMessage + $ "can not get extent size, does volume group " + ++ vg ++ " exists?" + Just extentSize -> do + case parseSize extentSize of + Nothing -> errorMessage + $ "can not parse volume group size" + Just size -> do + state <- liftIO $ lvState vg lv + ensureProperty w + $ setupprop size state + + cleanup :: Property UnixLike + cleanup = property' ("removed logical volume " ++ lv) $ \w -> do + exists <- liftIO $ lvExist vg lv + ensureProperty w $ if exists + then removedprop + else doNothing + + -- Parse size and round to next extent size multiple. + parseSize :: Integer -> Maybe Integer + parseSize extentSize = do + s <- readSize dataUnits sz + return $ (s + extentSize - 1) `div` extentSize * extentSize + + -- Dispatch to the right props. + setupprop :: Integer -> (Maybe LvState) -> Property DebianLike + setupprop size Nothing = createdprop size `before` formatprop + setupprop size (Just (LvState csize cfs)) + | size == csize && fsMatch fs cfs = doNothing + | size == csize = formatprop + | fsMatch fs cfs = tightenTargets $ resizedprop size True + | otherwise = resizedprop size False `before` formatprop + + createdprop :: Integer -> Property UnixLike + createdprop size = + cmdProperty "lvcreate" + (bytes size $ [ "-n", lv, "--yes", vg ]) + `assume` MadeChange + + resizedprop :: Integer -> Bool -> Property UnixLike + resizedprop size rfs = + cmdProperty "lvresize" + (resizeFs rfs $ bytes size $ [ vg lv ]) + `assume` MadeChange + where + resizeFs True l = "-r" : l + resizeFs False l = l + + removedprop :: Property UnixLike + removedprop = cmdProperty "lvremove" [ "-f", vg lv ] + `assume` MadeChange + + formatprop :: Property DebianLike + formatprop = Partition.formatted Partition.YesReallyFormatPartition fs path + + path = "/dev" vg lv + + fsMatch :: Partition.Fs -> Maybe String -> Bool + fsMatch Partition.EXT2 (Just "ext2") = True + fsMatch Partition.EXT3 (Just "ext3") = True + fsMatch Partition.EXT4 (Just "ext4") = True + fsMatch Partition.BTRFS (Just "btrfs") = True + fsMatch Partition.REISERFS (Just "reiserfs") = True + fsMatch Partition.XFS (Just "xfs") = True + fsMatch Partition.FAT (Just "fat") = True + fsMatch Partition.VFAT (Just "vfat") = True + fsMatch Partition.NTFS (Just "ntfs") = True + fsMatch Partition.LinuxSwap (Just "swap") = True + fsMatch _ _ = False + + bytes size l = "-L" : ((show size) ++ "b") : l + +-- | Make sure needed tools are installed. +installed :: RevertableProperty DebianLike DebianLike +installed = install remove + where + install = Apt.installed ["lvm2"] + remove = Apt.removed ["lvm2"] + +data LvState = LvState Integer (Maybe String) + +-- Check for logical volume existance. +lvExist :: VolumeGroup -> LogicalVolume -> IO Bool +lvExist vg lv = + doesFileExist path + where + path = "/dev" vg lv + +-- Return Nothing if logical volume does not exists (or error), else return +-- its size and maybe file system. +lvState :: VolumeGroup -> LogicalVolume -> IO (Maybe LvState) +lvState vg lv = do + exists <- lvExist vg lv + if not exists + then return Nothing + else do + s <- readLvSize + fs <- readFs + return $ do + size <- s + return $ LvState size $ takeWhile (/= '\n') <$> fs + where + path = "/dev" vg lv + readLvSize = catchDefaultIO Nothing + $ readish + <$> readProcess "lvs" [ "-o", "size", "--noheadings", + "--nosuffix", "--units", "b", path ] + readFs = Mount.blkidTag "TYPE" path + +-- Read extent size (or Nothing on error). +vgExtentSize :: VolumeGroup -> IO (Maybe Integer) +vgExtentSize vg = + catchDefaultIO Nothing + $ readish + <$> readProcess "vgs" [ "-o", "vg_extent_size", + "--noheadings", "--nosuffix", "--units", "b", path ] + where + path = "/dev" vg -- cgit v1.2.3 From ae8b74d826afa6ebf8cc0557d9f69e8a40e01f3c Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Thu, 31 Aug 2017 23:01:02 +0200 Subject: Add Lvm to propellor.cabal --- propellor.cabal | 1 + 1 file changed, 1 insertion(+) diff --git a/propellor.cabal b/propellor.cabal index 1b5c46d6..e5f8b63b 100644 --- a/propellor.cabal +++ b/propellor.cabal @@ -122,6 +122,7 @@ Library Propellor.Property.LightDM Propellor.Property.Locale Propellor.Property.Logcheck + Propellor.Property.Lvm Propellor.Property.Mount Propellor.Property.Network Propellor.Property.Nginx -- cgit v1.2.3 From c382567f952f4a4968a6bb1bfeff76ed3f83d3f1 Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Fri, 1 Sep 2017 22:58:02 +0200 Subject: Lvm: use better types for LogicalVolume and VolumeGroup --- src/Propellor/Property/Lvm.hs | 70 ++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/src/Propellor/Property/Lvm.hs b/src/Propellor/Property/Lvm.hs index 43bbe0ee..53ca7198 100644 --- a/src/Propellor/Property/Lvm.hs +++ b/src/Propellor/Property/Lvm.hs @@ -4,8 +4,10 @@ module Propellor.Property.Lvm ( lvFormatted, - Eep(..), installed, + Eep(..), + VolumeGroup(..), + LogicalVolume(..), ) where import Propellor @@ -19,8 +21,8 @@ data Eep = YesReallyFormatLogicalVolume type DataSize = String -type VolumeGroup = String -type LogicalVolume = String +newtype VolumeGroup = VolumeGroup String +data LogicalVolume = LogicalVolume String VolumeGroup -- | Create or resize a logical volume, and make sure it is formatted. When -- reverted, remove the logical volume. @@ -29,7 +31,8 @@ type LogicalVolume = String -- -- > import qualified Propellor.Property.Lvm as Lvm -- > import qualified Propellor.Property.Partition as Partition --- > Lvm.lvFormatted Lvm.YesReallyFormatLogicalVolume "vg0" "test" "16m" +-- > Lvm.lvFormatted Lvm.YesReallyFormatLogicalVolume +-- > (Lvm.LogicalVolume "test" (Lvm.VolumeGroup "vg0")) "16m" -- > Partition.EXT4 -- -- If size and filesystem match, nothing is done. @@ -37,33 +40,32 @@ type LogicalVolume = String -- Volume group must have been created yet. lvFormatted :: Eep - -> VolumeGroup -> LogicalVolume -> DataSize -> Partition.Fs -> RevertableProperty DebianLike UnixLike -lvFormatted YesReallyFormatLogicalVolume vg lv sz fs = +lvFormatted YesReallyFormatLogicalVolume lv sz fs = setup cleanup where setup :: Property DebianLike - setup = property' ("formatted logical volume " ++ lv) $ \w -> do + setup = property' ("formatted logical volume " ++ (vglv lv)) $ \w -> do es <- liftIO $ vgExtentSize vg case es of Nothing -> errorMessage $ "can not get extent size, does volume group " - ++ vg ++ " exists?" + ++ vgname ++ " exists?" Just extentSize -> do case parseSize extentSize of Nothing -> errorMessage $ "can not parse volume group size" Just size -> do - state <- liftIO $ lvState vg lv + state <- liftIO $ lvState lv ensureProperty w $ setupprop size state cleanup :: Property UnixLike - cleanup = property' ("removed logical volume " ++ lv) $ \w -> do - exists <- liftIO $ lvExist vg lv + cleanup = property' ("removed logical volume " ++ (vglv lv)) $ \w -> do + exists <- liftIO $ lvExist lv ensureProperty w $ if exists then removedprop else doNothing @@ -86,26 +88,25 @@ lvFormatted YesReallyFormatLogicalVolume vg lv sz fs = createdprop :: Integer -> Property UnixLike createdprop size = cmdProperty "lvcreate" - (bytes size $ [ "-n", lv, "--yes", vg ]) + (bytes size $ [ "-n", lvname, "--yes", vgname ]) `assume` MadeChange resizedprop :: Integer -> Bool -> Property UnixLike resizedprop size rfs = cmdProperty "lvresize" - (resizeFs rfs $ bytes size $ [ vg lv ]) + (resizeFs rfs $ bytes size $ [ vglv lv ]) `assume` MadeChange where resizeFs True l = "-r" : l resizeFs False l = l removedprop :: Property UnixLike - removedprop = cmdProperty "lvremove" [ "-f", vg lv ] + removedprop = cmdProperty "lvremove" [ "-f", vglv lv ] `assume` MadeChange formatprop :: Property DebianLike - formatprop = Partition.formatted Partition.YesReallyFormatPartition fs path - - path = "/dev" vg lv + formatprop = Partition.formatted Partition.YesReallyFormatPartition + fs (path lv) fsMatch :: Partition.Fs -> Maybe String -> Bool fsMatch Partition.EXT2 (Just "ext2") = True @@ -122,6 +123,8 @@ lvFormatted YesReallyFormatLogicalVolume vg lv sz fs = bytes size l = "-L" : ((show size) ++ "b") : l + (LogicalVolume lvname vg@(VolumeGroup vgname)) = lv + -- | Make sure needed tools are installed. installed :: RevertableProperty DebianLike DebianLike installed = install remove @@ -132,17 +135,14 @@ installed = install remove data LvState = LvState Integer (Maybe String) -- Check for logical volume existance. -lvExist :: VolumeGroup -> LogicalVolume -> IO Bool -lvExist vg lv = - doesFileExist path - where - path = "/dev" vg lv +lvExist :: LogicalVolume -> IO Bool +lvExist lv = doesFileExist (path lv) -- Return Nothing if logical volume does not exists (or error), else return -- its size and maybe file system. -lvState :: VolumeGroup -> LogicalVolume -> IO (Maybe LvState) -lvState vg lv = do - exists <- lvExist vg lv +lvState :: LogicalVolume -> IO (Maybe LvState) +lvState lv = do + exists <- lvExist lv if not exists then return Nothing else do @@ -152,19 +152,27 @@ lvState vg lv = do size <- s return $ LvState size $ takeWhile (/= '\n') <$> fs where - path = "/dev" vg lv readLvSize = catchDefaultIO Nothing $ readish <$> readProcess "lvs" [ "-o", "size", "--noheadings", - "--nosuffix", "--units", "b", path ] - readFs = Mount.blkidTag "TYPE" path + "--nosuffix", "--units", "b", vglv lv ] + readFs = Mount.blkidTag "TYPE" (path lv) -- Read extent size (or Nothing on error). vgExtentSize :: VolumeGroup -> IO (Maybe Integer) -vgExtentSize vg = +vgExtentSize (VolumeGroup vgname) = catchDefaultIO Nothing $ readish <$> readProcess "vgs" [ "-o", "vg_extent_size", - "--noheadings", "--nosuffix", "--units", "b", path ] + "--noheadings", "--nosuffix", "--units", "b", vgname ] + +-- Give "vgname/lvname" for a LogicalVolume. +vglv :: LogicalVolume -> String +vglv lv = + vgname lvname where - path = "/dev" vg + (LogicalVolume lvname (VolumeGroup vgname)) = lv + +-- Give device path. +path :: LogicalVolume -> String +path lv = "/dev" (vglv lv) -- cgit v1.2.3 From 1b5c0b6c95283f73f13da36e578e96721d61da38 Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Fri, 1 Sep 2017 23:26:13 +0200 Subject: Lvm: use Partition.Fs in LvState --- src/Propellor/Property/Lvm.hs | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/Propellor/Property/Lvm.hs b/src/Propellor/Property/Lvm.hs index 53ca7198..e48b206a 100644 --- a/src/Propellor/Property/Lvm.hs +++ b/src/Propellor/Property/Lvm.hs @@ -108,17 +108,8 @@ lvFormatted YesReallyFormatLogicalVolume lv sz fs = formatprop = Partition.formatted Partition.YesReallyFormatPartition fs (path lv) - fsMatch :: Partition.Fs -> Maybe String -> Bool - fsMatch Partition.EXT2 (Just "ext2") = True - fsMatch Partition.EXT3 (Just "ext3") = True - fsMatch Partition.EXT4 (Just "ext4") = True - fsMatch Partition.BTRFS (Just "btrfs") = True - fsMatch Partition.REISERFS (Just "reiserfs") = True - fsMatch Partition.XFS (Just "xfs") = True - fsMatch Partition.FAT (Just "fat") = True - fsMatch Partition.VFAT (Just "vfat") = True - fsMatch Partition.NTFS (Just "ntfs") = True - fsMatch Partition.LinuxSwap (Just "swap") = True + fsMatch :: Partition.Fs -> Maybe Partition.Fs -> Bool + fsMatch a (Just b) = a == b fsMatch _ _ = False bytes size l = "-L" : ((show size) ++ "b") : l @@ -132,7 +123,7 @@ installed = install remove install = Apt.installed ["lvm2"] remove = Apt.removed ["lvm2"] -data LvState = LvState Integer (Maybe String) +data LvState = LvState Integer (Maybe Partition.Fs) -- Check for logical volume existance. lvExist :: LogicalVolume -> IO Bool @@ -150,13 +141,25 @@ lvState lv = do fs <- readFs return $ do size <- s - return $ LvState size $ takeWhile (/= '\n') <$> fs + return $ LvState size $ parseFs + $ takeWhile (/= '\n') <$> fs where readLvSize = catchDefaultIO Nothing $ readish <$> readProcess "lvs" [ "-o", "size", "--noheadings", "--nosuffix", "--units", "b", vglv lv ] readFs = Mount.blkidTag "TYPE" (path lv) + parseFs (Just "ext2") = Just Partition.EXT2 + parseFs (Just "ext3") = Just Partition.EXT3 + parseFs (Just "ext4") = Just Partition.EXT4 + parseFs (Just "btrfs") = Just Partition.BTRFS + parseFs (Just "reiserfs") = Just Partition.REISERFS + parseFs (Just "xfs") = Just Partition.XFS + parseFs (Just "fat") = Just Partition.FAT + parseFs (Just "vfat") = Just Partition.VFAT + parseFs (Just "ntfs") = Just Partition.NTFS + parseFs (Just "swap") = Just Partition.LinuxSwap + parseFs _ = Nothing -- Read extent size (or Nothing on error). vgExtentSize :: VolumeGroup -> IO (Maybe Integer) -- cgit v1.2.3 From 2ecd58717ff1ab145b3ec6abea0513b283990734 Mon Sep 17 00:00:00 2001 From: Nicolas Schodet Date: Fri, 1 Sep 2017 23:32:36 +0200 Subject: Lvm: split size parsing and rounding --- src/Propellor/Property/Lvm.hs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Propellor/Property/Lvm.hs b/src/Propellor/Property/Lvm.hs index e48b206a..360595c7 100644 --- a/src/Propellor/Property/Lvm.hs +++ b/src/Propellor/Property/Lvm.hs @@ -55,13 +55,14 @@ lvFormatted YesReallyFormatLogicalVolume lv sz fs = $ "can not get extent size, does volume group " ++ vgname ++ " exists?" Just extentSize -> do - case parseSize extentSize of + case parseSize of Nothing -> errorMessage $ "can not parse volume group size" Just size -> do state <- liftIO $ lvState lv + let rsize = roundSize extentSize size ensureProperty w - $ setupprop size state + $ setupprop rsize state cleanup :: Property UnixLike cleanup = property' ("removed logical volume " ++ (vglv lv)) $ \w -> do @@ -70,11 +71,14 @@ lvFormatted YesReallyFormatLogicalVolume lv sz fs = then removedprop else doNothing - -- Parse size and round to next extent size multiple. - parseSize :: Integer -> Maybe Integer - parseSize extentSize = do - s <- readSize dataUnits sz - return $ (s + extentSize - 1) `div` extentSize * extentSize + -- Parse size. + parseSize :: Maybe Integer + parseSize = readSize dataUnits sz + + -- Round size to next extent size multiple. + roundSize :: Integer -> Integer -> Integer + roundSize extentSize s = + (s + extentSize - 1) `div` extentSize * extentSize -- Dispatch to the right props. setupprop :: Integer -> (Maybe LvState) -> Property DebianLike -- cgit v1.2.3