Revision d79a6502

b/src/Ganeti/Luxi.hs
45 45
  , queryJobsStatus
46 46
  , buildCall
47 47
  , buildResponse
48
  , validateCall
49
  , decodeCall
48
  , decodeLuxiCall
50 49
  , recvMsg
51 50
  , recvMsgExt
52 51
  , sendMsg
......
55 54

  
56 55
import Control.Monad
57 56
import qualified Data.ByteString.UTF8 as UTF8
58
import Data.Functor ((<$>))
59 57
import Text.JSON (encodeStrict, decodeStrict)
60 58
import qualified Text.JSON as J
61 59
import Text.JSON.Pretty (pp_value)
......
170 168
-- | The serialisation of LuxiOps into strings in messages.
171 169
$(genStrOfOp ''LuxiOp "strOfOp")
172 170

  
173
-- | Type holding the initial (unparsed) Luxi call.
174
data LuxiCall = LuxiCall LuxiReq JSValue
175 171

  
176 172
luxiConnectConfig :: ConnectConfig
177 173
luxiConnectConfig = ConnectConfig { connDaemon = GanetiLuxid
......
198 194
      jo = toJSObject ja
199 195
  in encodeStrict jo
200 196

  
201
-- | Check that luxi request contains the required keys and parse it.
202
validateCall :: String -> Result LuxiCall
203
validateCall s = uncurry LuxiCall <$> parseCall s
204 197

  
205 198
-- | Converts Luxi call arguments into a 'LuxiOp' data structure.
199
-- This is used for building a Luxi 'Handler'.
206 200
--
207 201
-- This is currently hand-coded until we make it more uniform so that
208 202
-- it can be generated using TH.
209
decodeCall :: LuxiCall -> Result LuxiOp
210
decodeCall (LuxiCall call args) =
203
decodeLuxiCall :: JSValue -> JSValue -> Result LuxiOp
204
decodeLuxiCall method args = do
205
  call <- fromJResult "Unable to parse LUXI request method" $ J.readJSON method
211 206
  case call of
212 207
    ReqQueryJobs -> do
213 208
              (jids, jargs) <- fromJVal args
b/src/Ganeti/Query/Server.hs
1
{-# LANGUAGE BangPatterns #-}
2

  
3 1
{-| Implementation of the Ganeti Query2 server.
4 2

  
5 3
-}
......
61 59
import Ganeti.Query.Query
62 60
import Ganeti.Query.Filter (makeSimpleFilter)
63 61
import Ganeti.Types
62
import qualified Ganeti.UDSServer as U
64 63
import Ganeti.Utils (lockFile, exitIfBad, watchFile)
65 64
import qualified Ganeti.Version as Version
66 65

  
......
297 296
  logDebug $ "Updates for job " ++ sjid ++ " are " ++ encode (rfields, rlogs)
298 297
  return (JSArray rfields, rlogs)
299 298

  
300
-- | Given a decoded luxi request, executes it and sends the luxi
301
-- response back to the client.
302
handleClientMsg :: MVar () -> JQStatus -> Client -> ConfigReader
303
                   -> LuxiOp -> IO Bool
304
handleClientMsg qlock qstat client creader args = do
299

  
300
type LuxiConfig = (MVar (), JQStatus, ConfigReader)
301

  
302
luxiExec
303
    :: LuxiConfig
304
    -> LuxiOp
305
    -> IO (Bool, GenericResult GanetiException JSValue)
306
luxiExec (qlock, qstat, creader) args = do
305 307
  cfg <- creader
306
  logDebug $ "Request: " ++ show args
307
  call_result <- handleCallWrapper qlock qstat cfg args
308
  (!status, !rval) <-
309
    case call_result of
310
      Bad err -> do
311
        logWarning $ "Failed to execute request " ++ show args ++ ": "
312
                     ++ show err
313
        return (False, showJSON err)
314
      Ok result -> do
315
        -- only log the first 2,000 chars of the result
316
        logDebug $ "Result (truncated): " ++ take 2000 (J.encode result)
317
        logInfo $ "Successfully handled " ++ strOfOp args
318
        return (True, result)
319
  sendMsg client $ buildResponse status rval
320
  return True
321

  
322
-- | Handles one iteration of the client protocol: receives message,
323
-- checks it for validity and decodes it, returns response.
324
handleClient :: MVar () -> JQStatus -> Client -> ConfigReader -> IO Bool
325
handleClient qlock qstat client creader = do
326
  !msg <- recvMsgExt client
308
  result <- handleCallWrapper qlock qstat cfg args
309
  return (True, result)
310

  
311
luxiHandler :: LuxiConfig -> U.Handler LuxiOp JSValue
312
luxiHandler cfg = U.Handler { U.hParse         = decodeLuxiCall
313
                            , U.hInputLogShort = strOfOp
314
                            , U.hInputLogLong  = show
315
                            , U.hExec          = luxiExec cfg
316
                            }
317

  
318

  
319
-- | Logs an outgoing message.
320
logMsg
321
    :: (Show e, J.JSON e, MonadLog m)
322
    => U.Handler i o
323
    -> i                          -- ^ the received request (used for logging)
324
    -> GenericResult e J.JSValue  -- ^ A message to be sent
325
    -> m ()
326
logMsg handler req (Bad err) =
327
  logWarning $ "Failed to execute request "
328
               ++ U.hInputLogLong handler req ++ ": "
329
               ++ show err
330
logMsg handler req (Ok result) = do
331
  -- only log the first 2,000 chars of the result
332
  logDebug $ "Result (truncated): " ++ take 2000 (J.encode result)
333
  logInfo $ "Successfully handled " ++ U.hInputLogShort handler req
334

  
335
-- | Prepares an outgoing message.
336
prepareMsg
337
    :: (J.JSON e)
338
    => GenericResult e J.JSValue  -- ^ A message to be sent
339
    -> (Bool, J.JSValue)
340
prepareMsg (Bad err)   = (False, J.showJSON err)
341
prepareMsg (Ok result) = (True, result)
342

  
343
handleJsonMessage
344
    :: (J.JSON o)
345
    => U.Handler i o              -- ^ handler
346
    -> i                        -- ^ parsed input
347
    -> U.HandlerResult J.JSValue
348
handleJsonMessage handler req = do
349
  (close, call_result) <- U.hExec handler req
350
  return (close, fmap J.showJSON call_result)
351

  
352
-- | Takes a request as a 'String', parses it, passes it to a handler and
353
-- formats its response.
354
handleRawMessage
355
    :: (J.JSON o)
356
    => U.Handler i o              -- ^ handler
357
    -> String                   -- ^ raw unparsed input
358
    -> IO (Bool, String)
359
handleRawMessage handler payload =
360
  case U.parseCall payload >>= uncurry (U.hParse handler) of
361
    Bad err -> do
362
         let errmsg = "Failed to parse request: " ++ err
363
         logWarning errmsg
364
         return (False, buildResponse False (J.showJSON errmsg))
365
    Ok req -> do
366
        logDebug $ "Request: " ++ U.hInputLogLong handler req
367
        (close, call_result_json) <- handleJsonMessage handler req
368
        logMsg handler req call_result_json
369
        let (status, response) = prepareMsg call_result_json
370
        return (close, buildResponse status response)
371

  
372
-- | Reads a request, passes it to a handler and sends a response back to the
373
-- client.
374
handleClient
375
    :: (J.JSON o)
376
    => U.Handler i o
377
    -> Client
378
    -> IO Bool
379
handleClient handler client = do
380
  msg <- recvMsgExt client
327 381
  logDebug $ "Received message: " ++ show msg
328 382
  case msg of
329
    RecvConnClosed -> logDebug "Connection closed" >> return False
383
    RecvConnClosed -> logDebug "Connection closed" >>
384
                      return False
330 385
    RecvError err -> logWarning ("Error during message receiving: " ++ err) >>
331 386
                     return False
332
    RecvOk payload ->
333
      case validateCall payload >>= decodeCall of
334
        Bad err -> do
335
             let errmsg = "Failed to parse request: " ++ err
336
             logWarning errmsg
337
             sendMsg client $ buildResponse False (showJSON errmsg)
338
             return False
339
        Ok args -> handleClientMsg qlock qstat client creader args
387
    RecvOk payload -> do
388
      (close, outMsg) <- handleRawMessage handler payload
389
      sendMsg client outMsg
390
      return close
340 391

  
341 392
-- | Main client loop: runs one loop of 'handleClient', and if that
342 393
-- doesn't report a finished (closed) connection, restarts itself.
343
clientLoop :: MVar () -> JQStatus -> Client -> ConfigReader -> IO ()
344
clientLoop qlock qstat client creader = do
345
  result <- handleClient qlock qstat client creader
394
clientLoop
395
    :: (J.JSON o)
396
    => U.Handler i o
397
    -> Client
398
    -> IO ()
399
clientLoop handler client = do
400
  result <- handleClient handler client
346 401
  if result
347
    then clientLoop qlock qstat client creader
402
    then clientLoop handler client
348 403
    else closeClient client
349 404

  
350 405
-- | Main listener loop: accepts clients, forks an I/O thread to handle
351 406
-- that client.
352
listener :: MVar () -> JQStatus -> ConfigReader -> Server -> IO ()
353
listener qlock qstat creader socket = do
354
  client <- acceptClient socket
355
  _ <- forkIO $ clientLoop qlock qstat client creader
407
listener
408
    :: (J.JSON o)
409
    => U.Handler i o
410
    -> Server
411
    -> IO ()
412
listener handler server = do
413
  client <- acceptClient server
414
  _ <- forkIO $ clientLoop handler client
356 415
  return ()
357 416

  
417

  
358 418
-- | Type alias for prepMain results
359 419
type PrepResult = (Server, IORef (Result ConfigData), JQStatus)
360 420

  
......
385 445
  qlock <- newMVar ()
386 446

  
387 447
  finally
388
    (forever $ listener qlock jq creader server)
448
    (forever $ listener (luxiHandler (qlock, jq, creader)) server)
389 449
    (closeServer server)
b/src/Ganeti/UDSServer.hs
42 42
  , recvMsg
43 43
  , recvMsgExt
44 44
  , sendMsg
45
  -- * Client handler
46
  , Handler(..)
47
  , HandlerResult
45 48
  ) where
46 49

  
47 50
import Control.Applicative
......
63 66
import Text.JSON.Types
64 67

  
65 68
import Ganeti.BasicTypes
69
import Ganeti.Errors (GanetiException)
66 70
import Ganeti.JSON
67 71
import Ganeti.Runtime (GanetiDaemon(..), MiscGroup(..), GanetiGroup(..))
68 72
import Ganeti.THH
......
220 224

  
221 225

  
222 226
-- | Parse the required keys out of a call.
223
parseCall :: (J.JSON mth) => String -> Result (mth, JSValue)
227
parseCall :: (J.JSON mth, J.JSON args) => String -> Result (mth, args)
224 228
parseCall s = do
225 229
  arr <- fromJResult "parsing top-level JSON message" $
226 230
           decodeStrict s :: Result (JSObject JSValue)
......
238 242
           , (strOfKey Result, args)]
239 243
      jo = toJSObject ja
240 244
  in encodeStrict jo
245

  
246

  
247

  
248
-- * Processing client requests
249

  
250
type HandlerResult o = IO (Bool, GenericResult GanetiException o)
251

  
252
data Handler i o = Handler
253
  { hParse         :: J.JSValue -> J.JSValue -> Result i
254
    -- ^ parses method and its arguments into the input type
255
  , hInputLogShort :: i -> String
256
    -- ^ short description of an input, for the INFO logging level
257
  , hInputLogLong  :: i -> String
258
    -- ^ long description of an input, for the DEBUG logging level
259
  , hExec          :: i -> HandlerResult o
260
    -- ^ executes the handler on an input
261
  }
b/test/hs/Test/Ganeti/Luxi.hs
47 47

  
48 48
import Ganeti.BasicTypes
49 49
import qualified Ganeti.Luxi as Luxi
50
import qualified Ganeti.UDSServer as US
50 51

  
51 52
{-# ANN module "HLint: ignore Use camelCase" #-}
52 53

  
......
97 98
-- | Simple check that encoding/decoding of LuxiOp works.
98 99
prop_CallEncoding :: Luxi.LuxiOp -> Property
99 100
prop_CallEncoding op =
100
  (Luxi.validateCall (Luxi.buildCall op) >>= Luxi.decodeCall) ==? Ok op
101
  (US.parseCall (Luxi.buildCall op) >>= uncurry Luxi.decodeLuxiCall) ==? Ok op
101 102

  
102 103
-- | Helper to a get a temporary file name.
103 104
getTempFileName :: IO FilePath

Also available in: Unified diff