root / src / Ganeti / UDSServer.hs @ c7003a76
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 () |