From 4a1dc2bf30073cc265af09e46e5929ed536dcb04 Mon Sep 17 00:00:00 2001 From: Iustin Pop <iustin@google.com> Date: Mon, 20 Aug 2012 19:13:15 +0200 Subject: [PATCH] Create a custom type for disk indices While (again) trying to test Python/Haskell encoding interoperability, I found another bug: the disk index is declared in Python as ht.TPositiveInt, but in Haskell just as Int, so it can take negative values too. Clearly we can do better, so let's add a wrapper type that protects creation of invalid indices via a smart constructor (http://www.haskell.org/haskellwiki/Smart_constructors, the runtime checking variant). This means that outside of OpCodes.hs, it's not possible to load invalid values, and this also applies to de-serialisation from JSON data. Signed-off-by: Iustin Pop <iustin@google.com> Reviewed-by: Agata Murawska <agatamurawska@google.com> --- htools/Ganeti/HTools/QC.hs | 3 +++ htools/Ganeti/OpCodes.hs | 21 ++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/htools/Ganeti/HTools/QC.hs b/htools/Ganeti/HTools/QC.hs index 6df169231..a440e15d5 100644 --- a/htools/Ganeti/HTools/QC.hs +++ b/htools/Ganeti/HTools/QC.hs @@ -461,6 +461,9 @@ instance Arbitrary Node.Node where instance Arbitrary OpCodes.ReplaceDisksMode where arbitrary = elements [minBound..maxBound] +instance Arbitrary OpCodes.DiskIndex where + arbitrary = choose (0, C.maxDisks - 1) >>= OpCodes.mkDiskIndex + instance Arbitrary OpCodes.OpCode where arbitrary = do op_id <- elements OpCodes.allOpIDs diff --git a/htools/Ganeti/OpCodes.hs b/htools/Ganeti/OpCodes.hs index bda66a17e..6c6e45e36 100644 --- a/htools/Ganeti/OpCodes.hs +++ b/htools/Ganeti/OpCodes.hs @@ -28,6 +28,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA module Ganeti.OpCodes ( OpCode(..) , ReplaceDisksMode(..) + , DiskIndex + , mkDiskIndex + , unDiskIndex , opID , allOpIDs ) where @@ -48,6 +51,22 @@ $(declareSADT "ReplaceDisksMode" ]) $(makeJSONInstance ''ReplaceDisksMode) +-- | Disk index type (embedding constraints on the index value via a +-- smart constructor). +newtype DiskIndex = DiskIndex { unDiskIndex :: Int } + deriving (Show, Read, Eq, Ord) + +-- | Smart constructor for 'DiskIndex'. +mkDiskIndex :: (Monad m) => Int -> m DiskIndex +mkDiskIndex i | i >= 0 && i < C.maxDisks = return (DiskIndex i) + | otherwise = fail $ "Invalid value for disk index '" ++ + show i ++ "', required between 0 and " ++ + show C.maxDisks + +instance JSON DiskIndex where + readJSON v = readJSON v >>= mkDiskIndex + showJSON = showJSON . unDiskIndex + -- | OpCode representation. -- -- We only implement a subset of Ganeti opcodes, but only what we @@ -62,7 +81,7 @@ $(genOpCode "OpCode" [ simpleField "instance_name" [t| String |] , optionalField $ simpleField "remote_node" [t| String |] , simpleField "mode" [t| ReplaceDisksMode |] - , simpleField "disks" [t| [Int] |] + , simpleField "disks" [t| [DiskIndex] |] , optionalField $ simpleField "iallocator" [t| String |] ]) , ("OpInstanceFailover", -- GitLab