1 {-# LANGUAGE TemplateHaskell #-}
3 {-| Implementation of the Ganeti LUXI interface.
9 Copyright (C) 2009, 2010, 2011 Google Inc.
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.
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.
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
41 import Text.JSON (encodeStrict, decodeStrict)
42 import qualified Text.JSON as J
43 import Text.JSON.Types
45 import qualified Network.Socket as S
47 import Ganeti.HTools.Utils
48 import Ganeti.HTools.Types
50 import Ganeti.Constants
51 import Ganeti.Jobs (JobStatus)
52 import Ganeti.OpCodes (OpCode)
55 -- * Utility functions
57 -- | Wrapper over System.Timeout.timeout that fails in the IO monad.
58 withTimeout :: Int -> String -> IO a -> IO a
59 withTimeout secs descr action = do
60 result <- timeout (secs * 1000000) action
62 Nothing -> fail $ "Timeout in " ++ descr
65 -- * Generic protocol functionality
67 $(declareSADT "QrViaLuxi"
69 , ("QRInstance", 'qrInstance)
71 , ("QRGroup", 'qrGroup)
74 $(makeJSONInstance ''QrViaLuxi)
76 -- | Currently supported Luxi operations and JSON serialization.
79 [ ("what", [t| QrViaLuxi |], [| id |])
80 , ("fields", [t| [String] |], [| id |])
81 , ("qfilter", [t| () |], [| const JSNull |])
84 [ ("names", [t| [String] |], [| id |])
85 , ("fields", [t| [String] |], [| id |])
86 , ("lock", [t| Bool |], [| id |])
89 [ ("names", [t| [String] |], [| id |])
90 , ("fields", [t| [String] |], [| id |])
91 , ("lock", [t| Bool |], [| id |])
94 [ ("names", [t| [String] |], [| id |])
95 , ("fields", [t| [String] |], [| id |])
96 , ("lock", [t| Bool |], [| id |])
99 [ ("ids", [t| [Int] |], [| map show |])
100 , ("fields", [t| [String] |], [| id |])
103 [ ("nodes", [t| [String] |], [| id |])
104 , ("lock", [t| Bool |], [| id |])
106 , ("QueryConfigValues",
107 [ ("fields", [t| [String] |], [| id |]) ]
109 , ("QueryClusterInfo", [])
111 [ ("kind", [t| String |], [| id |])
112 , ("name", [t| String |], [| id |])
115 [ ("job", [t| [OpCode] |], [| id |]) ]
118 [ ("ops", [t| [[OpCode]] |], [| id |]) ]
120 , ("WaitForJobChange",
121 [ ("job", [t| Int |], [| id |])
122 , ("fields", [t| [String]|], [| id |])
123 , ("prev_job", [t| JSValue |], [| id |])
124 , ("prev_log", [t| JSValue |], [| id |])
125 , ("tmout", [t| Int |], [| id |])
128 [ ("job", [t| Int |], [| show |]) ]
130 , ("AutoArchiveJobs",
131 [ ("age", [t| Int |], [| id |])
132 , ("tmout", [t| Int |], [| id |])
135 [ ("job", [t| Int |], [| show |]) ]
138 [ ("flag", [t| Bool |], [| id |]) ]
140 , ("SetWatcherPause",
141 [ ("duration", [t| Double |], [| id |]) ]
145 -- | The serialisation of LuxiOps into strings in messages.
146 $(genStrOfOp ''LuxiOp "strOfOp")
148 -- | The end-of-message separator.
152 -- | Valid keys in the requests and responses.
153 data MsgKeys = Method
158 -- | The serialisation of MsgKeys into strings in messages.
159 $(genStrOfKey ''MsgKeys "strOfKey")
161 -- | Luxi client encapsulation.
162 data Client = Client { socket :: S.Socket -- ^ The socket of the client
163 , rbuf :: IORef String -- ^ Already received buffer
166 -- | Connects to the master daemon and returns a luxi Client.
167 getClient :: String -> IO Client
169 s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol
170 withTimeout connTimeout "creating luxi connection" $
171 S.connect s (S.SockAddrUnix path)
173 return Client { socket=s, rbuf=rf}
175 -- | Closes the client socket.
176 closeClient :: Client -> IO ()
177 closeClient = S.sClose . socket
179 -- | Sends a message over a luxi transport.
180 sendMsg :: Client -> String -> IO ()
183 sbytes <- withTimeout queryTimeout
184 "sending luxi message" $
185 S.send (socket s) obuf
186 unless (sbytes == length obuf) $ _send (drop sbytes obuf)
187 in _send (buf ++ [eOM])
189 -- | Waits for a message over a luxi transport.
190 recvMsg :: Client -> IO String
193 nbuf <- withTimeout queryTimeout "reading luxi response" $
194 S.recv (socket s) 4096
195 let (msg, remaining) = break (eOM ==) nbuf
197 then _recv (obuf ++ msg)
198 else return (obuf ++ msg, tail remaining))
199 cbuf <- readIORef $ rbuf s
200 let (imsg, ibuf) = break (eOM ==) cbuf
202 (if null ibuf -- if old buffer didn't contain a full message
203 then _recv cbuf -- then we read from network
204 else return (imsg, tail ibuf)) -- else we return data from our buffer
205 writeIORef (rbuf s) nbuf
208 -- | Serialize a request to String.
209 buildCall :: LuxiOp -- ^ The method
210 -> String -- ^ The serialized form
212 let ja = [ (strOfKey Method, JSString $ toJSString $ strOfOp lo::JSValue)
213 , (strOfKey Args, opToArgs lo::JSValue)
218 -- | Check that luxi responses contain the required keys and that the
219 -- call was successful.
220 validateResult :: String -> Result JSValue
221 validateResult s = do
222 oarr <- fromJResult "Parsing LUXI response"
223 (decodeStrict s)::Result (JSObject JSValue)
224 let arr = J.fromJSObject oarr
225 status <- fromObj arr (strOfKey Success)::Result Bool
226 let rkey = strOfKey Result
228 then fromObj arr rkey
229 else fromObj arr rkey >>= fail)
231 -- | Generic luxi method call.
232 callMethod :: LuxiOp -> Client -> IO (Result JSValue)
233 callMethod method s = do
234 sendMsg s $ buildCall method
236 let rval = validateResult result
239 -- | Specialized submitManyJobs call.
240 submitManyJobs :: Client -> [[OpCode]] -> IO (Result [String])
241 submitManyJobs s jobs = do
242 rval <- callMethod (SubmitManyJobs jobs) s
243 -- map each result (status, payload) pair into a nice Result ADT
244 return $ case rval of
247 mapM (\v -> case v of
248 JSArray [JSBool True, JSString x] ->
250 JSArray [JSBool False, JSString x] ->
252 _ -> Bad "Unknown result from the master daemon"
254 x -> Bad ("Cannot parse response from Ganeti: " ++ show x)
256 -- | Custom queryJobs call.
257 queryJobsStatus :: Client -> [String] -> IO (Result [JobStatus])
258 queryJobsStatus s jids = do
259 rval <- callMethod (QueryJobs (map read jids) ["status"]) s
260 return $ case rval of
262 Ok y -> case J.readJSON y::(J.Result [[JobStatus]]) of
263 J.Ok vals -> if any null vals
264 then Bad "Missing job status field"
265 else Ok (map head vals)