Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / UDSServer.hs @ ea174b21

History | View | Annotate | Download (11.3 kB)

1
{-# LANGUAGE TemplateHaskell #-}
2

    
3
{-| Implementation of the Ganeti Unix Domain Socket JSON server interface.
4

    
5
-}
6

    
7
{-
8

    
9
Copyright (C) 2013 Google Inc.
10

    
11
This program is free software; you can redistribute it and/or modify
12
it under the terms of the GNU General Public License as published by
13
the Free Software Foundation; either version 2 of the License, or
14
(at your option) any later version.
15

    
16
This program is distributed in the hope that it will be useful, but
17
WITHOUT ANY WARRANTY; without even the implied warranty of
18
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19
General Public License for more details.
20

    
21
You should have received a copy of the GNU General Public License
22
along with this program; if not, write to the Free Software
23
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24
02110-1301, USA.
25

    
26
-}
27

    
28
module Ganeti.UDSServer
29
  ( ConnectConfig(..)
30
  , Client
31
  , Server
32
  , RecvResult(..)
33
  , MsgKeys(..)
34
  , strOfKey
35
  , connectClient
36
  , connectServer
37
  , acceptClient
38
  , closeClient
39
  , closeServer
40
  , buildResponse
41
  , parseCall
42
  , recvMsg
43
  , recvMsgExt
44
  , sendMsg
45
  -- * Client handler
46
  , Handler(..)
47
  , HandlerResult
48
  , listener
49
  ) where
50

    
51
import Control.Applicative
52
import Control.Concurrent (forkIO)
53
import Control.Exception (catch)
54
import Data.IORef
55
import qualified Data.ByteString as B
56
import qualified Data.ByteString.Lazy as BL
57
import qualified Data.ByteString.UTF8 as UTF8
58
import qualified Data.ByteString.Lazy.UTF8 as UTF8L
59
import Data.Word (Word8)
60
import Control.Monad
61
import qualified Network.Socket as S
62
import System.Directory (removeFile)
63
import System.IO (hClose, hFlush, hWaitForInput, Handle, IOMode(..))
64
import System.IO.Error (isEOFError)
65
import System.Timeout
66
import Text.JSON (encodeStrict, decodeStrict)
67
import qualified Text.JSON as J
68
import Text.JSON.Types
69

    
70
import Ganeti.BasicTypes
71
import Ganeti.Errors (GanetiException)
72
import Ganeti.JSON
73
import Ganeti.Logging
74
import Ganeti.Runtime (GanetiDaemon(..), MiscGroup(..), GanetiGroup(..))
75
import Ganeti.THH
76
import Ganeti.Utils
77

    
78

    
79
-- * Utility functions
80

    
81
-- | Wrapper over System.Timeout.timeout that fails in the IO monad.
82
withTimeout :: Int -> String -> IO a -> IO a
83
withTimeout secs descr action = do
84
  result <- timeout (secs * 1000000) action
85
  case result of
86
    Nothing -> fail $ "Timeout in " ++ descr
87
    Just v -> return v
88

    
89

    
90
-- * Generic protocol functionality
91

    
92
-- | Result of receiving a message from the socket.
93
data RecvResult = RecvConnClosed    -- ^ Connection closed
94
                | RecvError String  -- ^ Any other error
95
                | RecvOk String     -- ^ Successfull receive
96
                  deriving (Show, Eq)
97

    
98

    
99
-- | The end-of-message separator.
100
eOM :: Word8
101
eOM = 3
102

    
103
-- | The end-of-message encoded as a ByteString.
104
bEOM :: B.ByteString
105
bEOM = B.singleton eOM
106

    
107
-- | Valid keys in the requests and responses.
108
data MsgKeys = Method
109
             | Args
110
             | Success
111
             | Result
112

    
113
-- | The serialisation of MsgKeys into strings in messages.
114
$(genStrOfKey ''MsgKeys "strOfKey")
115

    
116

    
117
data ConnectConfig = ConnectConfig
118
                     { connDaemon :: GanetiDaemon
119
                     , recvTmo :: Int
120
                     , sendTmo :: Int
121
                     }
122

    
123
-- | A client encapsulation.
124
data Client = Client { socket :: Handle           -- ^ The socket of the client
125
                     , rbuf :: IORef B.ByteString -- ^ Already received buffer
126
                     , clientConfig :: ConnectConfig
127
                     }
128

    
129
-- | A server encapsulation.
130
data Server = Server { sSocket :: S.Socket        -- ^ The bound server socket
131
                     , sPath :: FilePath          -- ^ The scoket's path
132
                     , serverConfig :: ConnectConfig
133
                     }
134

    
135

    
136
-- | Connects to the master daemon and returns a Client.
137
connectClient
138
  :: ConnectConfig    -- ^ configuration for the client
139
  -> Int              -- ^ connection timeout
140
  -> FilePath         -- ^ socket path
141
  -> IO Client
142
connectClient conf tmo path = do
143
  s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol
144
  withTimeout tmo "creating a connection" $
145
              S.connect s (S.SockAddrUnix path)
146
  rf <- newIORef B.empty
147
  h <- S.socketToHandle s ReadWriteMode
148
  return Client { socket=h, rbuf=rf, clientConfig=conf }
149

    
150
-- | Creates and returns a server endpoint.
151
connectServer :: ConnectConfig -> Bool -> FilePath -> IO Server
152
connectServer conf setOwner path = do
153
  s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol
154
  S.bindSocket s (S.SockAddrUnix path)
155
  when setOwner . setOwnerAndGroupFromNames path (connDaemon conf) $
156
    ExtraGroup DaemonsGroup
157
  S.listen s 5 -- 5 is the max backlog
158
  return Server { sSocket=s, sPath=path, serverConfig=conf }
159

    
160
-- | Closes a server endpoint.
161
-- FIXME: this should be encapsulated into a nicer type.
162
closeServer :: Server -> IO ()
163
closeServer server = do
164
  S.sClose (sSocket server)
165
  removeFile (sPath server)
166

    
167
-- | Accepts a client
168
acceptClient :: Server -> IO Client
169
acceptClient s = do
170
  -- second return is the address of the client, which we ignore here
171
  (client_socket, _) <- S.accept (sSocket s)
172
  new_buffer <- newIORef B.empty
173
  handle <- S.socketToHandle client_socket ReadWriteMode
174
  return Client { socket=handle
175
                , rbuf=new_buffer
176
                , clientConfig=serverConfig s
177
                }
178

    
179
-- | Closes the client socket.
180
closeClient :: Client -> IO ()
181
closeClient = hClose . socket
182

    
183
-- | Sends a message over a transport.
184
sendMsg :: Client -> String -> IO ()
185
sendMsg s buf = withTimeout (sendTmo $ clientConfig s) "sending a message" $ do
186
  let encoded = UTF8L.fromString buf
187
      handle = socket s
188
  BL.hPut handle encoded
189
  B.hPut handle bEOM
190
  hFlush handle
191

    
192
-- | Given a current buffer and the handle, it will read from the
193
-- network until we get a full message, and it will return that
194
-- message and the leftover buffer contents.
195
recvUpdate :: ConnectConfig -> Handle -> B.ByteString
196
           -> IO (B.ByteString, B.ByteString)
197
recvUpdate conf handle obuf = do
198
  nbuf <- withTimeout (recvTmo conf) "reading a response" $ do
199
            _ <- hWaitForInput handle (-1)
200
            B.hGetNonBlocking handle 4096
201
  let (msg, remaining) = B.break (eOM ==) nbuf
202
      newbuf = B.append obuf msg
203
  if B.null remaining
204
    then recvUpdate conf handle newbuf
205
    else return (newbuf, B.tail remaining)
206

    
207
-- | Waits for a message over a transport.
208
recvMsg :: Client -> IO String
209
recvMsg s = do
210
  cbuf <- readIORef $ rbuf s
211
  let (imsg, ibuf) = B.break (eOM ==) cbuf
212
  (msg, nbuf) <-
213
    if B.null ibuf      -- if old buffer didn't contain a full message
214
                        -- then we read from network:
215
      then recvUpdate (clientConfig s) (socket s) cbuf
216
      else return (imsg, B.tail ibuf)   -- else we return data from our buffer
217
  writeIORef (rbuf s) nbuf
218
  return $ UTF8.toString msg
219

    
220
-- | Extended wrapper over recvMsg.
221
recvMsgExt :: Client -> IO RecvResult
222
recvMsgExt s =
223
  Control.Exception.catch (liftM RecvOk (recvMsg s)) $ \e ->
224
    return $ if isEOFError e
225
               then RecvConnClosed
226
               else RecvError (show e)
227

    
228

    
229
-- | Parse the required keys out of a call.
230
parseCall :: (J.JSON mth, J.JSON args) => String -> Result (mth, args)
231
parseCall s = do
232
  arr <- fromJResult "parsing top-level JSON message" $
233
           decodeStrict s :: Result (JSObject JSValue)
234
  let keyFromObj :: (J.JSON a) => MsgKeys -> Result a
235
      keyFromObj = fromObj (fromJSObject arr) . strOfKey
236
  (,) <$> keyFromObj Method <*> keyFromObj Args
237

    
238

    
239
-- | Serialize the response to String.
240
buildResponse :: Bool    -- ^ Success
241
              -> JSValue -- ^ The arguments
242
              -> String  -- ^ The serialized form
243
buildResponse success args =
244
  let ja = [ (strOfKey Success, JSBool success)
245
           , (strOfKey Result, args)]
246
      jo = toJSObject ja
247
  in encodeStrict jo
248

    
249
-- | Logs an outgoing message.
250
logMsg
251
    :: (Show e, J.JSON e, MonadLog m)
252
    => Handler i o
253
    -> i                          -- ^ the received request (used for logging)
254
    -> GenericResult e J.JSValue  -- ^ A message to be sent
255
    -> m ()
256
logMsg handler req (Bad err) =
257
  logWarning $ "Failed to execute request " ++ hInputLogLong handler req ++ ": "
258
               ++ show err
259
logMsg handler req (Ok result) = do
260
  -- only log the first 2,000 chars of the result
261
  logDebug $ "Result (truncated): " ++ take 2000 (J.encode result)
262
  logInfo $ "Successfully handled " ++ hInputLogShort handler req
263

    
264
-- | Prepares an outgoing message.
265
prepareMsg
266
    :: (J.JSON e)
267
    => GenericResult e J.JSValue  -- ^ A message to be sent
268
    -> (Bool, J.JSValue)
269
prepareMsg (Bad err)   = (False, J.showJSON err)
270
prepareMsg (Ok result) = (True, result)
271

    
272

    
273
-- * Processing client requests
274

    
275
type HandlerResult o = IO (Bool, GenericResult GanetiException o)
276

    
277
data Handler i o = Handler
278
  { hParse         :: J.JSValue -> J.JSValue -> Result i
279
    -- ^ parses method and its arguments into the input type
280
  , hInputLogShort :: i -> String
281
    -- ^ short description of an input, for the INFO logging level
282
  , hInputLogLong  :: i -> String
283
    -- ^ long description of an input, for the DEBUG logging level
284
  , hExec          :: i -> HandlerResult o
285
    -- ^ executes the handler on an input
286
  }
287

    
288

    
289
handleJsonMessage
290
    :: (J.JSON o)
291
    => Handler i o              -- ^ handler
292
    -> i                        -- ^ parsed input
293
    -> HandlerResult J.JSValue
294
handleJsonMessage handler req = do
295
  (close, call_result) <- hExec handler req
296
  return (close, fmap J.showJSON call_result)
297

    
298
-- | Takes a request as a 'String', parses it, passes it to a handler and
299
-- formats its response.
300
handleRawMessage
301
    :: (J.JSON o)
302
    => Handler i o              -- ^ handler
303
    -> String                   -- ^ raw unparsed input
304
    -> IO (Bool, String)
305
handleRawMessage handler payload =
306
  case parseCall payload >>= uncurry (hParse handler) of
307
    Bad err -> do
308
         let errmsg = "Failed to parse request: " ++ err
309
         logWarning errmsg
310
         return (False, buildResponse False (J.showJSON errmsg))
311
    Ok req -> do
312
        logDebug $ "Request: " ++ hInputLogLong handler req
313
        (close, call_result_json) <- handleJsonMessage handler req
314
        logMsg handler req call_result_json
315
        let (status, response) = prepareMsg call_result_json
316
        return (close, buildResponse status response)
317

    
318
-- | Reads a request, passes it to a handler and sends a response back to the
319
-- client.
320
handleClient
321
    :: (J.JSON o)
322
    => Handler i o
323
    -> Client
324
    -> IO Bool
325
handleClient handler client = do
326
  msg <- recvMsgExt client
327
  logDebug $ "Received message: " ++ show msg
328
  case msg of
329
    RecvConnClosed -> logDebug "Connection closed" >>
330
                      return False
331
    RecvError err -> logWarning ("Error during message receiving: " ++ err) >>
332
                     return False
333
    RecvOk payload -> do
334
      (close, outMsg) <- handleRawMessage handler payload
335
      sendMsg client outMsg
336
      return close
337

    
338
-- | Main client loop: runs one loop of 'handleClient', and if that
339
-- doesn't report a finished (closed) connection, restarts itself.
340
clientLoop
341
    :: (J.JSON o)
342
    => Handler i o
343
    -> Client
344
    -> IO ()
345
clientLoop handler client = do
346
  result <- handleClient handler client
347
  if result
348
    then clientLoop handler client
349
    else closeClient client
350

    
351
-- | Main listener loop: accepts clients, forks an I/O thread to handle
352
-- that client.
353
listener
354
    :: (J.JSON o)
355
    => Handler i o
356
    -> Server
357
    -> IO ()
358
listener handler server = do
359
  client <- acceptClient server
360
  _ <- forkIO $ clientLoop handler client
361
  return ()