diff --git a/htools/Ganeti/HTools/Luxi.hs b/htools/Ganeti/HTools/Luxi.hs index 1249e22bb1d7c74cf3a2526b428d2883c718227a..4baa98b3b9a0ffd831f559f7c34016cb3aeed9bd 100644 --- a/htools/Ganeti/HTools/Luxi.hs +++ b/htools/Ganeti/HTools/Luxi.hs @@ -69,22 +69,28 @@ parseQueryResult o = fail $ "Invalid query result, expected array but got " ++ show o -- | Prepare resulting output as parsers expect it. -extractArray :: (Monad m) => JSValue -> m [JSValue] +extractArray :: (Monad m) => JSValue -> m [[(JSValue, JSValue)]] extractArray v = - getData v >>= parseQueryResult >>= (return . map (JSArray . map snd)) + getData v >>= parseQueryResult + +-- | Testing result status for more verbose error message. +fromJValWithStatus :: (Text.JSON.JSON a, Monad m) => (JSValue, JSValue) -> m a +fromJValWithStatus (st, v) = do + st' <- fromJVal st + L.checkRS st' v >>= fromJVal -- | Annotate errors when converting values with owner/attribute for -- better debugging. genericConvert :: (Text.JSON.JSON a) => - String -- ^ The object type - -> String -- ^ The object name - -> String -- ^ The attribute we're trying to convert - -> JSValue -- ^ The value we try to convert - -> Result a -- ^ The annotated result + String -- ^ The object type + -> String -- ^ The object name + -> String -- ^ The attribute we're trying to convert + -> (JSValue, JSValue) -- ^ The value we're trying to convert + -> Result a -- ^ The annotated result genericConvert otype oname oattr = annotateResult (otype ++ " '" ++ oname ++ "', error while reading attribute '" ++ - oattr ++ "'") . fromJVal + oattr ++ "'") . fromJValWithStatus -- * Data querying functionality @@ -135,16 +141,16 @@ getInstances ktn arr = extractArray arr >>= mapM (parseInstance ktn) -- | Construct an instance from a JSON object. parseInstance :: NameAssoc - -> JSValue + -> [(JSValue, JSValue)] -> Result (String, Instance.Instance) -parseInstance ktn (JSArray [ name, disk, mem, vcpus - , status, pnode, snodes, tags, oram - , auto_balance, disk_template ]) = do - xname <- annotateResult "Parsing new instance" (fromJVal name) +parseInstance ktn [ name, disk, mem, vcpus + , status, pnode, snodes, tags, oram + , auto_balance, disk_template ] = do + xname <- annotateResult "Parsing new instance" (fromJValWithStatus name) let convert a = genericConvert "Instance" xname a xdisk <- convert "disk_usage" disk - xmem <- (case oram of - JSRational _ _ -> convert "oper_ram" oram + xmem <- (case oram of -- FIXME: remove the "guessing" + (_, JSRational _ _) -> convert "oper_ram" oram _ -> convert "be/memory" mem) xvcpus <- convert "be/vcpus" vcpus xpnode <- convert "pnode" pnode >>= lookupNode ktn xname @@ -166,11 +172,11 @@ getNodes :: NameAssoc -> JSValue -> Result [(String, Node.Node)] getNodes ktg arr = extractArray arr >>= mapM (parseNode ktg) -- | Construct a node from a JSON object. -parseNode :: NameAssoc -> JSValue -> Result (String, Node.Node) -parseNode ktg (JSArray [ name, mtotal, mnode, mfree, dtotal, dfree - , ctotal, offline, drained, vm_capable, g_uuid ]) +parseNode :: NameAssoc -> [(JSValue, JSValue)] -> Result (String, Node.Node) +parseNode ktg [ name, mtotal, mnode, mfree, dtotal, dfree + , ctotal, offline, drained, vm_capable, g_uuid ] = do - xname <- annotateResult "Parsing new node" (fromJVal name) + xname <- annotateResult "Parsing new node" (fromJValWithStatus name) let convert a = genericConvert "Node" xname a xoffline <- convert "offline" offline xdrained <- convert "drained" drained @@ -203,9 +209,9 @@ getGroups :: JSValue -> Result [(String, Group.Group)] getGroups jsv = extractArray jsv >>= mapM parseGroup -- | Parses a given group information. -parseGroup :: JSValue -> Result (String, Group.Group) -parseGroup (JSArray [uuid, name, apol]) = do - xname <- annotateResult "Parsing new group" (fromJVal name) +parseGroup :: [(JSValue, JSValue)] -> Result (String, Group.Group) +parseGroup [uuid, name, apol] = do + xname <- annotateResult "Parsing new group" (fromJValWithStatus name) let convert a = genericConvert "Group" xname a xuuid <- convert "uuid" uuid xapol <- convert "alloc_policy" apol diff --git a/htools/Ganeti/Luxi.hs b/htools/Ganeti/Luxi.hs index 20c0b141cbf16c726dbc8090c1fb4982716901eb..ade6c229b08828b111b2fcee6e607f91a2d246a1 100644 --- a/htools/Ganeti/Luxi.hs +++ b/htools/Ganeti/Luxi.hs @@ -28,7 +28,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA module Ganeti.Luxi ( LuxiOp(..) , QrViaLuxi(..) + , ResultStatus(..) , Client + , checkRS , getClient , closeClient , callMethod @@ -145,6 +147,23 @@ $(genLuxiOp "LuxiOp" -- | The serialisation of LuxiOps into strings in messages. $(genStrOfOp ''LuxiOp "strOfOp") +$(declareIADT "ResultStatus" + [ ("RSNormal", 'rsNormal) + , ("RSUnknown", 'rsUnknown) + , ("RSNoData", 'rsNodata) + , ("RSUnavailable", 'rsUnavail) + , ("RSOffline", 'rsOffline) + ]) +$(makeJSONInstanceInt ''ResultStatus) + +-- | Check that ResultStatus is success or fail with descriptive message. +checkRS :: (Monad m) => ResultStatus -> a -> m a +checkRS RSNormal val = return val +checkRS RSUnknown _ = fail "Unknown field" +checkRS RSNoData _ = fail "No data for a field" +checkRS RSUnavailable _ = fail "Ganeti reports unavailable data" +checkRS RSOffline _ = fail "Ganeti reports resource as offline" + -- | The end-of-message separator. eOM :: Char eOM = '\3' diff --git a/htools/Ganeti/THH.hs b/htools/Ganeti/THH.hs index 6bb377e62baf407d9e109bdc35c0b6f10d8e4cd1..e3c1110a864a671ebc934ac0a4859a4690ae98b5 100644 --- a/htools/Ganeti/THH.hs +++ b/htools/Ganeti/THH.hs @@ -30,7 +30,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA -} module Ganeti.THH ( declareSADT + , declareIADT , makeJSONInstance + , makeJSONInstanceInt , genOpID , genOpCode , noDefault @@ -68,10 +70,18 @@ showJSONE = varNameE "showJSON" toStrName :: String -> Name toStrName = mkName . (++ "ToString") . ensureLower +-- | ToInt function name. +toIntName :: String -> Name +toIntName= mkName . (++ "ToInt") . ensureLower + -- | FromString function name. fromStrName :: String -> Name fromStrName = mkName . (++ "FromString") . ensureLower +-- | FromInt function name. +fromIntName:: String -> Name +fromIntName = mkName . (++ "FromInt") . ensureLower + -- | Converts a name to it's varE/litE representations. -- reprE :: Either String Name -> Q Exp @@ -85,6 +95,77 @@ appFn :: Exp -> Exp -> Exp appFn f x | f == VarE 'id = x | otherwise = AppE f x +-- * Template code for simple integer-equivalent ADTs + +-- | Generates a data type declaration. +-- +-- The type will have a fixed list of instances. +intADTDecl :: Name -> [String] -> Dec +intADTDecl name constructors = + DataD [] name [] + (map (flip NormalC [] . mkName) constructors) + [''Show] + +-- | Generates a toInt function. +genToInt :: Name -> Name -> [(String, Name)] -> Q [Dec] +genToInt fname tname constructors = do + sigt <- [t| $(conT tname) -> Int |] + clauses <- mapM (\(c, v) -> clause [recP (mkName c) []] + (normalB (varE v)) []) constructors + return [SigD fname sigt, FunD fname clauses] + +-- | Generates a fromInt function. +genFromInt :: Name -> Name -> [(String, Name)] -> Q [Dec] +genFromInt fname tname constructors = do + sigt <- [t| (Monad m) => Int-> m $(conT tname) |] + let varp = mkName "s" + varpe = varE varp + clauses <- mapM (\(c, v) -> do + g <- normalG [| $varpe == $(varE v) |] + r <- [| return $(conE (mkName c)) |] + return (g, r)) constructors + oth_clause <- do + g <- normalG [| otherwise |] + r <- [|fail ("Invalid int value for type " ++ + $(litE (stringL (nameBase tname))) ++ ": " ++ show $varpe) |] + return (g, r) + let fun = FunD fname [Clause [VarP varp] + (GuardedB (clauses++[oth_clause])) []] + return [SigD fname sigt, fun] + +-- | Generates a data type from a given string format. +declareIADT:: String -> [(String, Name)] -> Q [Dec] +declareIADT sname cons = do + let name = mkName sname + ddecl = intADTDecl name (map fst cons) + tostr <- genToInt (toIntName sname) name cons + fromstr <- genFromInt (fromIntName sname) name cons + return $ ddecl:tostr ++ fromstr + +-- | Creates the showJSON member of a JSON instance declaration. +genShowJSONInt :: String -> Q [Dec] +genShowJSONInt name = [d| showJSON = JSON.showJSON . $(varE (toIntName name)) |] + +-- | Creates the readJSON member of a JSON instance declaration. +genReadJSONInt :: String -> Q Dec +genReadJSONInt name = do + let s = mkName "s" + body <- [| case JSON.readJSON $(varE s) of + JSON.Ok s' -> $(varE (fromIntName name)) s' + JSON.Error e -> + JSON.Error $ "Can't parse int value for type " ++ + $(stringE name) ++ ": " ++ e + |] + return $ FunD (mkName "readJSON") [Clause [VarP s] (NormalB body) []] + +-- | Generates a JSON instance for a given type. +makeJSONInstanceInt :: Name -> Q [Dec] +makeJSONInstanceInt name = do + let base = nameBase name + showJ <- genShowJSONInt base + readJ <- genReadJSONInt base + return [InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) (readJ:showJ)] + -- * Template code for simple string-equivalent ADTs -- | Generates a data type declaration.