1 {-| Implementation of the Ganeti LUXI interface.
7 Copyright (C) 2009 Google Inc.
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
37 import Text.JSON (JSObject, JSValue, toJSObject, encodeStrict, decodeStrict)
38 import qualified Text.JSON as J
39 import Text.JSON.Types
41 import qualified Network.Socket as S
43 import Ganeti.HTools.Utils
44 import Ganeti.HTools.Types
46 -- * Utility functions
48 -- | Wrapper over System.Timeout.timeout that fails in the IO monad.
49 withTimeout :: Int -> String -> IO a -> IO a
50 withTimeout secs descr action = do
51 result <- timeout (secs * 1000000) action
53 Nothing -> fail $ "Timeout in " ++ descr
56 -- * Generic protocol functionality
58 -- | Currently supported Luxi operations.
59 data LuxiOp = QueryInstances
62 -- | The serialisation of LuxiOps into strings in messages.
63 strOfOp :: LuxiOp -> String
64 strOfOp QueryNodes = "QueryNodes"
65 strOfOp QueryInstances = "QueryInstances"
67 -- | The end-of-message separator.
71 -- | Valid keys in the requests and responses.
77 -- | The serialisation of MsgKeys into strings in messages.
78 strOfKey :: MsgKeys -> String
79 strOfKey Method = "method"
80 strOfKey Args = "args"
81 strOfKey Success = "success"
82 strOfKey Result = "result"
84 -- | Luxi client encapsulation.
85 data Client = Client { socket :: S.Socket -- ^ The socket of the client
86 , rbuf :: IORef String -- ^ Already received buffer
89 -- | Connects to the master daemon and returns a luxi Client.
90 getClient :: String -> IO Client
92 s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol
93 withTimeout connTimeout "creating luxi connection" $
94 S.connect s (S.SockAddrUnix path)
96 return Client { socket=s, rbuf=rf}
98 -- | Closes the client socket.
99 closeClient :: Client -> IO ()
100 closeClient = S.sClose . socket
102 -- | Sends a message over a luxi transport.
103 sendMsg :: Client -> String -> IO ()
106 sbytes <- withTimeout queryTimeout
107 "sending luxi message" $
108 S.send (socket s) obuf
109 (if sbytes == length obuf
111 else _send (drop sbytes obuf))
112 in _send (buf ++ [eOM])
114 -- | Waits for a message over a luxi transport.
115 recvMsg :: Client -> IO String
118 nbuf <- withTimeout queryTimeout "reading luxi response" $
119 S.recv (socket s) 4096
120 let (msg, remaining) = break ((==) eOM) (obuf ++ nbuf)
123 else return (msg, tail remaining))
124 cbuf <- readIORef $ rbuf s
125 (msg, nbuf) <- _recv cbuf
126 writeIORef (rbuf s) nbuf
129 -- | Serialize a request to String.
130 buildCall :: LuxiOp -- ^ The method
131 -> JSValue -- ^ The arguments
132 -> String -- ^ The serialized form
134 let ja = [(strOfKey Method,
135 JSString $ toJSString $ strOfOp msg::JSValue),
142 -- | Check that luxi responses contain the required keys and that the
143 -- call was successful.
144 validateResult :: String -> Result JSValue
145 validateResult s = do
146 arr <- fromJResult $ decodeStrict s::Result (JSObject JSValue)
147 status <- fromObj (strOfKey Success) arr::Result Bool
148 let rkey = strOfKey Result
150 then fromObj rkey arr
151 else fromObj rkey arr >>= fail)
153 -- | Generic luxi method call.
154 callMethod :: LuxiOp -> JSValue -> Client -> IO (Result JSValue)
155 callMethod method args s = do
156 sendMsg s $ buildCall method args
158 let rval = validateResult result