On Tue, Sep 25, 2012 at 06:43:45PM +0200, Agata Murawska wrote:
> 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: Agata Murawska <[email protected]>
> ---
>  htools/Ganeti/Rpc.hs |   90 
> +++++++++++++++++++++++++++++++++++---------------
>  1 files changed, 63 insertions(+), 27 deletions(-)
> 
> diff --git a/htools/Ganeti/Rpc.hs b/htools/Ganeti/Rpc.hs
> index 377800c..d21b834 100644
> --- a/htools/Ganeti/Rpc.hs
> +++ b/htools/Ganeti/Rpc.hs
> @@ -56,6 +56,7 @@ module Ganeti.Rpc
>    , rpcTimeoutFromRaw -- FIXME: Not used anywhere
>    ) where
>  
> +import Control.Arrow (second)
>  import qualified Text.JSON as J
>  import Text.JSON (makeObj)
>  
> @@ -108,10 +109,6 @@ instance Show RpcError where
>  
>  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.
>  $(declareIADT "RpcTimeout"
>    [ ( "Urgent",    'C.rpcTmoUrgent )
> @@ -133,14 +130,10 @@ class (J.JSON a) => RpcCall a where
>    -- | Whether we accept offline nodes when making a call.
>    rpcCallAcceptOffline :: a -> Bool
>  
> -  rpcCallData _ = J.encode
> -
>  -- | A generic class for RPC results with default implementation.
>  class (J.JSON a) => RpcResult a where
>    -- | Create a result based on the received HTTP response.
> -  rpcResultFill :: (Monad m) => String -> m (ERpcError a)
> -
> -  rpcResultFill res = rpcErrorJsonReport $  J.decode res
> +  rpcResultFill :: (Monad m) => J.JSValue -> m (ERpcError a)
>  
>  -- | Generic class that ensures matching RPC call with its respective
>  -- result.
> @@ -194,11 +187,22 @@ prepareHttpRequest node call
>                                }
>    | 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 jerr

"show jerr" is an ugly way to format JSON objects; please use pp_value, per
commit c12a68e2.

> +
>  -- | Parse the response or propagate the error.
>  parseHttpResponse :: (Monad m, RpcResult a) => ERpcError String
>                    -> m (ERpcError a)
>  parseHttpResponse (Left err) = return $ Left err
> -parseHttpResponse (Right response) = rpcResultFill response
> +parseHttpResponse (Right response) = rpcResultParse response
>  
>  -- | Execute RPC call for a sigle node.
>  executeSingleRpcCall :: (Rpc a b) => Node -> a -> IO (Node, ERpcError b)
> @@ -214,30 +218,51 @@ executeRpcCall nodes call =
>    sequence $ parMap rwhnf (uncurry executeSingleRpcCall)
>                 (zip nodes $ repeat call)
>  
> +-- | Helper function that is used to read dictionaries of values.
> +sanitizeDictResults :: [(String, J.Result a)] ->
> +                       ERpcError [(String, a)]

Style issue: '->' go always at the beginning of the line, not at the
end.

> +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'

Mmm… this smells so much of foldM…

>  -- * RPC calls and results
>  
>  -- | AllInstancesInfo
> ---   Returns information about all instances on the given nodes
> +--   Returns information about all running instances on the given nodes.
>  $(buildObject "RpcCallAllInstancesInfo" "rpcCallAllInstInfo"
>    [ simpleField "hypervisors" [t| [Hypervisor] |] ])
>  
>  $(buildObject "InstanceInfo" "instInfo"
> -  [ simpleField "name"   [t| String |]
> -  , simpleField "memory" [t| Int|]
> -  , simpleField "state"  [t| AdminState |]
> +  [ simpleField "memory" [t| Int|]
> +  , simpleField "state"  [t| String |] -- It depends on hypervisor :(
>    , simpleField "vcpus"  [t| Int |]
>    , simpleField "time"   [t| Int |]
>    ])
>  
>  $(buildObject "RpcResultAllInstancesInfo" "rpcResAllInstInfo"
> -  [ simpleField "instances" [t| [InstanceInfo] |] ])
> +  [ simpleField "instances" [t| [(String, InstanceInfo)] |] ])
>  
>  instance RpcCall RpcCallAllInstancesInfo where
>    rpcCallName _ = "all_instances_info"
>    rpcCallTimeout _ = rpcTimeoutToRaw Urgent
>    rpcCallAcceptOffline _ = False
> -
> -instance RpcResult RpcResultAllInstancesInfo
> +  rpcCallData _ call = J.encode [rpcCallAllInstInfoHypervisors call]
> +
> +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)

Hmm, not nice indeed. Let's go with this version and see later if we can
improve it. It's most likely doable with a newtype+custom JSON
instance.

>  instance Rpc RpcCallAllInstancesInfo RpcResultAllInstancesInfo
>  
> @@ -247,30 +272,33 @@ $(buildObject "RpcCallInstanceList" "rpcCallInstList"
>    [ simpleField "hypervisors" [t| [Hypervisor] |] ])
>  
>  $(buildObject "RpcResultInstanceList" "rpcResInstList"
> -  [ simpleField "node"      [t| Node |]
> -  , simpleField "instances" [t| [String] |]
> -  ])
> +  [ simpleField "instances" [t| [String] |] ])
>  
>  instance RpcCall RpcCallInstanceList where
>    rpcCallName _ = "instance_list"
>    rpcCallTimeout _ = rpcTimeoutToRaw Urgent
>    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
>  
>  -- | NodeInfo
>  -- Return node information.
>  $(buildObject "RpcCallNodeInfo" "rpcCallNodeInfo"
> -  [ simpleField "hypervisors" [t| [Hypervisor] |]
> -  , simpleField "volume_groups" [t| [String] |]
> +  [ simpleField "volume_groups" [t| [String] |]
> +  , simpleField "hypervisors" [t| [Hypervisor] |]
>    ])

Is there a reason for this or is it just cleanup?

>  $(buildObject "VgInfo" "vgInfo"
>    [ simpleField "name" [t| String |]
> -  , simpleField "free" [t| Int |]
> -  , simpleField "size" [t| Int |]
> +  , optionalField $ simpleField "vg_free" [t| Int |]
> +  , optionalField $ simpleField "vg_size" [t| Int |]
>    ])
>  
>  -- | We only provide common fields as described in hv_base.py.
> @@ -293,7 +321,15 @@ instance RpcCall RpcCallNodeInfo where
>    rpcCallName _ = "node_info"
>    rpcCallTimeout _ = rpcTimeoutToRaw Urgent
>    rpcCallAcceptOffline _ = False
> -
> -instance RpcResult RpcResultNodeInfo
> +  rpcCallData _ call = J.encode ( rpcCallNodeInfoVolumeGroups call
> +                                , 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

Rest LGTM, thanks.

iustin

Reply via email to