Revision 9b09c0be htools/Ganeti/Rpc.hs

b/htools/Ganeti/Rpc.hs
56 56
  , rpcTimeoutFromRaw -- FIXME: Not used anywhere
57 57
  ) where
58 58

  
59
import Control.Arrow (second)
59 60
import qualified Text.JSON as J
61
import Text.JSON.Pretty (pp_value)
60 62
import Text.JSON (makeObj)
61 63

  
62 64
#ifndef NO_CURL
......
108 110

  
109 111
type ERpcError = Either RpcError
110 112

  
111
rpcErrorJsonReport :: (Monad m) => J.Result a -> m (ERpcError a)
112
rpcErrorJsonReport (J.Error x) = return . Left $ JsonDecodeError x
113
rpcErrorJsonReport (J.Ok x) = return $ Right x
114

  
115 113
-- | Basic timeouts for RPC calls.
116 114
$(declareIADT "RpcTimeout"
117 115
  [ ( "Urgent",    'C.rpcTmoUrgent )
......
133 131
  -- | Whether we accept offline nodes when making a call.
134 132
  rpcCallAcceptOffline :: a -> Bool
135 133

  
136
  rpcCallData _ = J.encode
137

  
138 134
-- | A generic class for RPC results with default implementation.
139 135
class (J.JSON a) => RpcResult a where
140 136
  -- | Create a result based on the received HTTP response.
141
  rpcResultFill :: (Monad m) => String -> m (ERpcError a)
142

  
143
  rpcResultFill res = rpcErrorJsonReport $  J.decode res
137
  rpcResultFill :: (Monad m) => J.JSValue -> m (ERpcError a)
144 138

  
145 139
-- | Generic class that ensures matching RPC call with its respective
146 140
-- result.
......
194 188
                              }
195 189
  | otherwise = Left $ OfflineNodeError node
196 190

  
191
-- | Parse a result based on the received HTTP response.
192
rpcResultParse :: (Monad m, RpcResult a) => String -> m (ERpcError a)
193
rpcResultParse res = do
194
  res' <- fromJResult "Reading JSON response" $ J.decode res
195
  case res' of
196
    (True, res'') ->
197
       rpcResultFill res''
198
    (False, jerr) -> case jerr of
199
       J.JSString msg -> return . Left $ RpcResultError (J.fromJSString msg)
200
       _ -> (return . Left) . JsonDecodeError $ show (pp_value jerr)
201

  
197 202
-- | Parse the response or propagate the error.
198 203
parseHttpResponse :: (Monad m, RpcResult a) => ERpcError String
199 204
                  -> m (ERpcError a)
200 205
parseHttpResponse (Left err) = return $ Left err
201
parseHttpResponse (Right response) = rpcResultFill response
206
parseHttpResponse (Right response) = rpcResultParse response
202 207

  
203 208
-- | Execute RPC call for a sigle node.
204 209
executeSingleRpcCall :: (Rpc a b) => Node -> a -> IO (Node, ERpcError b)
......
214 219
  sequence $ parMap rwhnf (uncurry executeSingleRpcCall)
215 220
               (zip nodes $ repeat call)
216 221

  
222
-- | Helper function that is used to read dictionaries of values.
223
sanitizeDictResults :: [(String, J.Result a)] -> ERpcError [(String, a)]
224
sanitizeDictResults [] = Right []
225
sanitizeDictResults ((_, J.Error err):_) = Left $ JsonDecodeError err
226
sanitizeDictResults ((name, J.Ok val):xs) =
227
  case sanitizeDictResults xs of
228
    Left err -> Left err
229
    Right res' -> Right $ (name, val):res'
230

  
217 231
-- * RPC calls and results
218 232

  
219 233
-- | AllInstancesInfo
220
--   Returns information about all instances on the given nodes
234
--   Returns information about all running instances on the given nodes.
221 235
$(buildObject "RpcCallAllInstancesInfo" "rpcCallAllInstInfo"
222 236
  [ simpleField "hypervisors" [t| [Hypervisor] |] ])
223 237

  
224 238
$(buildObject "InstanceInfo" "instInfo"
225
  [ simpleField "name"   [t| String |]
226
  , simpleField "memory" [t| Int|]
227
  , simpleField "state"  [t| AdminState |]
239
  [ simpleField "memory" [t| Int|]
240
  , simpleField "state"  [t| String |] -- It depends on hypervisor :(
228 241
  , simpleField "vcpus"  [t| Int |]
229 242
  , simpleField "time"   [t| Int |]
230 243
  ])
231 244

  
232 245
$(buildObject "RpcResultAllInstancesInfo" "rpcResAllInstInfo"
233
  [ simpleField "instances" [t| [InstanceInfo] |] ])
246
  [ simpleField "instances" [t| [(String, InstanceInfo)] |] ])
234 247

  
235 248
instance RpcCall RpcCallAllInstancesInfo where
236 249
  rpcCallName _ = "all_instances_info"
237 250
  rpcCallTimeout _ = rpcTimeoutToRaw Urgent
238 251
  rpcCallAcceptOffline _ = False
239

  
240
instance RpcResult RpcResultAllInstancesInfo
252
  rpcCallData _ call = J.encode [rpcCallAllInstInfoHypervisors call]
253

  
254
instance RpcResult RpcResultAllInstancesInfo where
255
  -- FIXME: Is there a simpler way to do it?
256
  rpcResultFill res =
257
    return $ case res of
258
      J.JSObject res' -> do
259
        let res'' = map (second J.readJSON) (J.fromJSObject res')
260
                        :: [(String, J.Result InstanceInfo)]
261
        case sanitizeDictResults res'' of
262
          Left err -> Left err
263
          Right insts -> Right $ RpcResultAllInstancesInfo insts
264
      _ -> Left $ JsonDecodeError
265
           ("Expected JSObject, got " ++ show res)
241 266

  
242 267
instance Rpc RpcCallAllInstancesInfo RpcResultAllInstancesInfo
243 268

  
......
247 272
  [ simpleField "hypervisors" [t| [Hypervisor] |] ])
248 273

  
249 274
$(buildObject "RpcResultInstanceList" "rpcResInstList"
250
  [ simpleField "node"      [t| Node |]
251
  , simpleField "instances" [t| [String] |]
252
  ])
275
  [ simpleField "instances" [t| [String] |] ])
253 276

  
254 277
instance RpcCall RpcCallInstanceList where
255 278
  rpcCallName _ = "instance_list"
256 279
  rpcCallTimeout _ = rpcTimeoutToRaw Urgent
257 280
  rpcCallAcceptOffline _ = False
281
  rpcCallData _ call = J.encode [rpcCallInstListHypervisors call]
258 282

  
259
instance RpcResult RpcResultInstanceList
283
instance RpcResult RpcResultInstanceList where
284
  rpcResultFill res =
285
    return $ case J.readJSON res of
286
      J.Error err -> Left $ JsonDecodeError err
287
      J.Ok insts -> Right $ RpcResultInstanceList insts
260 288

  
261 289
instance Rpc RpcCallInstanceList RpcResultInstanceList
262 290

  
263 291
-- | NodeInfo
264 292
-- Return node information.
265 293
$(buildObject "RpcCallNodeInfo" "rpcCallNodeInfo"
266
  [ simpleField "hypervisors" [t| [Hypervisor] |]
267
  , simpleField "volume_groups" [t| [String] |]
294
  [ simpleField "volume_groups" [t| [String] |]
295
  , simpleField "hypervisors" [t| [Hypervisor] |]
268 296
  ])
269 297

  
270 298
$(buildObject "VgInfo" "vgInfo"
271 299
  [ simpleField "name" [t| String |]
272
  , simpleField "free" [t| Int |]
273
  , simpleField "size" [t| Int |]
300
  , optionalField $ simpleField "vg_free" [t| Int |]
301
  , optionalField $ simpleField "vg_size" [t| Int |]
274 302
  ])
275 303

  
276 304
-- | We only provide common fields as described in hv_base.py.
......
293 321
  rpcCallName _ = "node_info"
294 322
  rpcCallTimeout _ = rpcTimeoutToRaw Urgent
295 323
  rpcCallAcceptOffline _ = False
296

  
297
instance RpcResult RpcResultNodeInfo
324
  rpcCallData _ call = J.encode ( rpcCallNodeInfoVolumeGroups call
325
                                , rpcCallNodeInfoHypervisors call
326
                                )
327

  
328
instance RpcResult RpcResultNodeInfo where
329
  rpcResultFill res =
330
    return $ case J.readJSON res of
331
      J.Error err -> Left $ JsonDecodeError err
332
      J.Ok (boot_id, vg_info, hv_info) ->
333
          Right $ RpcResultNodeInfo boot_id vg_info hv_info
298 334

  
299 335
instance Rpc RpcCallNodeInfo RpcResultNodeInfo

Also available in: Unified diff