Skip to content
Snippets Groups Projects
Commit 9b09c0be authored by Agata Murawska's avatar Agata Murawska
Browse files

Fix RPC call and result (de)serialization


Previous version of RPC calls implementation in Haskell did not take
into account that the actual result type for queries is a list, not
a dictionary.

This patch aims at fixing the problem "for now" - it is not a pretty
solution, but it does work. Note that parsing of the result is now
split into two parts - first, we check if server's aswer is positive,
then if it is, we procede with decoding the actual result.

Values and order of some fields in the result type were changed to
reflect actual order of arguments from server responses.

AllInstancesInfo call was particularly tricky, because it returns a
dictionary where keys are instance names - and the response from
a given node is correct if all the instances were deserialized, not
just some.

Signed-off-by: default avatarAgata Murawska <agatamurawska@google.com>
Reviewed-by: default avatarIustin Pop <iustin@google.com>
parent 599239ad
No related branches found
No related tags found
No related merge requests found
...@@ -56,7 +56,9 @@ module Ganeti.Rpc ...@@ -56,7 +56,9 @@ module Ganeti.Rpc
, rpcTimeoutFromRaw -- FIXME: Not used anywhere , rpcTimeoutFromRaw -- FIXME: Not used anywhere
) where ) where
import Control.Arrow (second)
import qualified Text.JSON as J import qualified Text.JSON as J
import Text.JSON.Pretty (pp_value)
import Text.JSON (makeObj) import Text.JSON (makeObj)
#ifndef NO_CURL #ifndef NO_CURL
...@@ -108,10 +110,6 @@ instance Show RpcError where ...@@ -108,10 +110,6 @@ instance Show RpcError where
type ERpcError = Either RpcError type ERpcError = Either RpcError
rpcErrorJsonReport :: (Monad m) => J.Result a -> m (ERpcError a)
rpcErrorJsonReport (J.Error x) = return . Left $ JsonDecodeError x
rpcErrorJsonReport (J.Ok x) = return $ Right x
-- | Basic timeouts for RPC calls. -- | Basic timeouts for RPC calls.
$(declareIADT "RpcTimeout" $(declareIADT "RpcTimeout"
[ ( "Urgent", 'C.rpcTmoUrgent ) [ ( "Urgent", 'C.rpcTmoUrgent )
...@@ -133,14 +131,10 @@ class (J.JSON a) => RpcCall a where ...@@ -133,14 +131,10 @@ class (J.JSON a) => RpcCall a where
-- | Whether we accept offline nodes when making a call. -- | Whether we accept offline nodes when making a call.
rpcCallAcceptOffline :: a -> Bool rpcCallAcceptOffline :: a -> Bool
rpcCallData _ = J.encode
-- | A generic class for RPC results with default implementation. -- | A generic class for RPC results with default implementation.
class (J.JSON a) => RpcResult a where class (J.JSON a) => RpcResult a where
-- | Create a result based on the received HTTP response. -- | Create a result based on the received HTTP response.
rpcResultFill :: (Monad m) => String -> m (ERpcError a) rpcResultFill :: (Monad m) => J.JSValue -> m (ERpcError a)
rpcResultFill res = rpcErrorJsonReport $ J.decode res
-- | Generic class that ensures matching RPC call with its respective -- | Generic class that ensures matching RPC call with its respective
-- result. -- result.
...@@ -194,11 +188,22 @@ prepareHttpRequest node call ...@@ -194,11 +188,22 @@ prepareHttpRequest node call
} }
| otherwise = Left $ OfflineNodeError node | otherwise = Left $ OfflineNodeError node
-- | Parse a result based on the received HTTP response.
rpcResultParse :: (Monad m, RpcResult a) => String -> m (ERpcError a)
rpcResultParse res = do
res' <- fromJResult "Reading JSON response" $ J.decode res
case res' of
(True, res'') ->
rpcResultFill res''
(False, jerr) -> case jerr of
J.JSString msg -> return . Left $ RpcResultError (J.fromJSString msg)
_ -> (return . Left) . JsonDecodeError $ show (pp_value jerr)
-- | Parse the response or propagate the error. -- | Parse the response or propagate the error.
parseHttpResponse :: (Monad m, RpcResult a) => ERpcError String parseHttpResponse :: (Monad m, RpcResult a) => ERpcError String
-> m (ERpcError a) -> m (ERpcError a)
parseHttpResponse (Left err) = return $ Left err parseHttpResponse (Left err) = return $ Left err
parseHttpResponse (Right response) = rpcResultFill response parseHttpResponse (Right response) = rpcResultParse response
-- | Execute RPC call for a sigle node. -- | Execute RPC call for a sigle node.
executeSingleRpcCall :: (Rpc a b) => Node -> a -> IO (Node, ERpcError b) executeSingleRpcCall :: (Rpc a b) => Node -> a -> IO (Node, ERpcError b)
...@@ -214,30 +219,50 @@ executeRpcCall nodes call = ...@@ -214,30 +219,50 @@ executeRpcCall nodes call =
sequence $ parMap rwhnf (uncurry executeSingleRpcCall) sequence $ parMap rwhnf (uncurry executeSingleRpcCall)
(zip nodes $ repeat call) (zip nodes $ repeat call)
-- | Helper function that is used to read dictionaries of values.
sanitizeDictResults :: [(String, J.Result a)] -> ERpcError [(String, a)]
sanitizeDictResults [] = Right []
sanitizeDictResults ((_, J.Error err):_) = Left $ JsonDecodeError err
sanitizeDictResults ((name, J.Ok val):xs) =
case sanitizeDictResults xs of
Left err -> Left err
Right res' -> Right $ (name, val):res'
-- * RPC calls and results -- * RPC calls and results
-- | AllInstancesInfo -- | AllInstancesInfo
-- Returns information about all instances on the given nodes -- Returns information about all running instances on the given nodes.
$(buildObject "RpcCallAllInstancesInfo" "rpcCallAllInstInfo" $(buildObject "RpcCallAllInstancesInfo" "rpcCallAllInstInfo"
[ simpleField "hypervisors" [t| [Hypervisor] |] ]) [ simpleField "hypervisors" [t| [Hypervisor] |] ])
$(buildObject "InstanceInfo" "instInfo" $(buildObject "InstanceInfo" "instInfo"
[ simpleField "name" [t| String |] [ simpleField "memory" [t| Int|]
, simpleField "memory" [t| Int|] , simpleField "state" [t| String |] -- It depends on hypervisor :(
, simpleField "state" [t| AdminState |]
, simpleField "vcpus" [t| Int |] , simpleField "vcpus" [t| Int |]
, simpleField "time" [t| Int |] , simpleField "time" [t| Int |]
]) ])
$(buildObject "RpcResultAllInstancesInfo" "rpcResAllInstInfo" $(buildObject "RpcResultAllInstancesInfo" "rpcResAllInstInfo"
[ simpleField "instances" [t| [InstanceInfo] |] ]) [ simpleField "instances" [t| [(String, InstanceInfo)] |] ])
instance RpcCall RpcCallAllInstancesInfo where instance RpcCall RpcCallAllInstancesInfo where
rpcCallName _ = "all_instances_info" rpcCallName _ = "all_instances_info"
rpcCallTimeout _ = rpcTimeoutToRaw Urgent rpcCallTimeout _ = rpcTimeoutToRaw Urgent
rpcCallAcceptOffline _ = False rpcCallAcceptOffline _ = False
rpcCallData _ call = J.encode [rpcCallAllInstInfoHypervisors call]
instance RpcResult RpcResultAllInstancesInfo
instance RpcResult RpcResultAllInstancesInfo where
-- FIXME: Is there a simpler way to do it?
rpcResultFill res =
return $ case res of
J.JSObject res' -> do
let res'' = map (second J.readJSON) (J.fromJSObject res')
:: [(String, J.Result InstanceInfo)]
case sanitizeDictResults res'' of
Left err -> Left err
Right insts -> Right $ RpcResultAllInstancesInfo insts
_ -> Left $ JsonDecodeError
("Expected JSObject, got " ++ show res)
instance Rpc RpcCallAllInstancesInfo RpcResultAllInstancesInfo instance Rpc RpcCallAllInstancesInfo RpcResultAllInstancesInfo
...@@ -247,30 +272,33 @@ $(buildObject "RpcCallInstanceList" "rpcCallInstList" ...@@ -247,30 +272,33 @@ $(buildObject "RpcCallInstanceList" "rpcCallInstList"
[ simpleField "hypervisors" [t| [Hypervisor] |] ]) [ simpleField "hypervisors" [t| [Hypervisor] |] ])
$(buildObject "RpcResultInstanceList" "rpcResInstList" $(buildObject "RpcResultInstanceList" "rpcResInstList"
[ simpleField "node" [t| Node |] [ simpleField "instances" [t| [String] |] ])
, simpleField "instances" [t| [String] |]
])
instance RpcCall RpcCallInstanceList where instance RpcCall RpcCallInstanceList where
rpcCallName _ = "instance_list" rpcCallName _ = "instance_list"
rpcCallTimeout _ = rpcTimeoutToRaw Urgent rpcCallTimeout _ = rpcTimeoutToRaw Urgent
rpcCallAcceptOffline _ = False rpcCallAcceptOffline _ = False
rpcCallData _ call = J.encode [rpcCallInstListHypervisors call]
instance RpcResult RpcResultInstanceList instance RpcResult RpcResultInstanceList where
rpcResultFill res =
return $ case J.readJSON res of
J.Error err -> Left $ JsonDecodeError err
J.Ok insts -> Right $ RpcResultInstanceList insts
instance Rpc RpcCallInstanceList RpcResultInstanceList instance Rpc RpcCallInstanceList RpcResultInstanceList
-- | NodeInfo -- | NodeInfo
-- Return node information. -- Return node information.
$(buildObject "RpcCallNodeInfo" "rpcCallNodeInfo" $(buildObject "RpcCallNodeInfo" "rpcCallNodeInfo"
[ simpleField "hypervisors" [t| [Hypervisor] |] [ simpleField "volume_groups" [t| [String] |]
, simpleField "volume_groups" [t| [String] |] , simpleField "hypervisors" [t| [Hypervisor] |]
]) ])
$(buildObject "VgInfo" "vgInfo" $(buildObject "VgInfo" "vgInfo"
[ simpleField "name" [t| String |] [ simpleField "name" [t| String |]
, simpleField "free" [t| Int |] , optionalField $ simpleField "vg_free" [t| Int |]
, simpleField "size" [t| Int |] , optionalField $ simpleField "vg_size" [t| Int |]
]) ])
-- | We only provide common fields as described in hv_base.py. -- | We only provide common fields as described in hv_base.py.
...@@ -293,7 +321,15 @@ instance RpcCall RpcCallNodeInfo where ...@@ -293,7 +321,15 @@ instance RpcCall RpcCallNodeInfo where
rpcCallName _ = "node_info" rpcCallName _ = "node_info"
rpcCallTimeout _ = rpcTimeoutToRaw Urgent rpcCallTimeout _ = rpcTimeoutToRaw Urgent
rpcCallAcceptOffline _ = False rpcCallAcceptOffline _ = False
rpcCallData _ call = J.encode ( rpcCallNodeInfoVolumeGroups call
instance RpcResult RpcResultNodeInfo , rpcCallNodeInfoHypervisors call
)
instance RpcResult RpcResultNodeInfo where
rpcResultFill res =
return $ case J.readJSON res of
J.Error err -> Left $ JsonDecodeError err
J.Ok (boot_id, vg_info, hv_info) ->
Right $ RpcResultNodeInfo boot_id vg_info hv_info
instance Rpc RpcCallNodeInfo RpcResultNodeInfo instance Rpc RpcCallNodeInfo RpcResultNodeInfo
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment