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
43 import Text.JSON (encodeStrict, decodeStrict)
44 import qualified Text.JSON as J
45 import Text.JSON.Types
47 import qualified Network.Socket as S
49 import Ganeti.HTools.JSON
50 import Ganeti.HTools.Types
52 import Ganeti.Constants
53 import Ganeti.Jobs (JobStatus)
54 import Ganeti.OpCodes (OpCode)
57 -- * Utility functions
59 -- | Wrapper over System.Timeout.timeout that fails in the IO monad.
60 withTimeout :: Int -> String -> IO a -> IO a
61 withTimeout secs descr action = do
62 result <- timeout (secs * 1000000) action
64 Nothing -> fail $ "Timeout in " ++ descr
67 -- * Generic protocol functionality
69 $(declareSADT "QrViaLuxi"
71 , ("QRInstance", 'qrInstance)
73 , ("QRGroup", 'qrGroup)
76 $(makeJSONInstance ''QrViaLuxi)
78 -- | Currently supported Luxi operations and JSON serialization.
81 [ ("what", [t| QrViaLuxi |], [| id |])
82 , ("fields", [t| [String] |], [| id |])
83 , ("qfilter", [t| () |], [| const JSNull |])
86 [ ("names", [t| [String] |], [| id |])
87 , ("fields", [t| [String] |], [| id |])
88 , ("lock", [t| Bool |], [| id |])
91 [ ("names", [t| [String] |], [| id |])
92 , ("fields", [t| [String] |], [| id |])
93 , ("lock", [t| Bool |], [| id |])
96 [ ("names", [t| [String] |], [| id |])
97 , ("fields", [t| [String] |], [| id |])
98 , ("lock", [t| Bool |], [| id |])
101 [ ("ids", [t| [Int] |], [| map show |])
102 , ("fields", [t| [String] |], [| id |])
105 [ ("nodes", [t| [String] |], [| id |])
106 , ("lock", [t| Bool |], [| id |])
108 , ("QueryConfigValues",
109 [ ("fields", [t| [String] |], [| id |]) ]
111 , ("QueryClusterInfo", [])
113 [ ("kind", [t| String |], [| id |])
114 , ("name", [t| String |], [| id |])
117 [ ("job", [t| [OpCode] |], [| id |]) ]
120 [ ("ops", [t| [[OpCode]] |], [| id |]) ]
122 , ("WaitForJobChange",
123 [ ("job", [t| Int |], [| id |])
124 , ("fields", [t| [String]|], [| id |])
125 , ("prev_job", [t| JSValue |], [| id |])
126 , ("prev_log", [t| JSValue |], [| id |])
127 , ("tmout", [t| Int |], [| id |])
130 [ ("job", [t| Int |], [| show |]) ]
132 , ("AutoArchiveJobs",
133 [ ("age", [t| Int |], [| id |])
134 , ("tmout", [t| Int |], [| id |])
137 [ ("job", [t| Int |], [| show |]) ]
140 [ ("flag", [t| Bool |], [| id |]) ]
142 , ("SetWatcherPause",
143 [ ("duration", [t| Double |], [| id |]) ]
147 -- | The serialisation of LuxiOps into strings in messages.
148 $(genStrOfOp ''LuxiOp "strOfOp")
150 $(declareIADT "ResultStatus"
151 [ ("RSNormal", 'rsNormal)
152 , ("RSUnknown", 'rsUnknown)
153 , ("RSNoData", 'rsNodata)
154 , ("RSUnavailable", 'rsUnavail)
155 , ("RSOffline", 'rsOffline)
158 $(makeJSONInstance ''ResultStatus)
160 -- | Check that ResultStatus is success or fail with descriptive message.
161 checkRS :: (Monad m) => ResultStatus -> a -> m a
162 checkRS RSNormal val = return val
163 checkRS RSUnknown _ = fail "Unknown field"
164 checkRS RSNoData _ = fail "No data for a field"
165 checkRS RSUnavailable _ = fail "Ganeti reports unavailable data"
166 checkRS RSOffline _ = fail "Ganeti reports resource as offline"
168 -- | The end-of-message separator.
172 -- | Valid keys in the requests and responses.
173 data MsgKeys = Method
178 -- | The serialisation of MsgKeys into strings in messages.
179 $(genStrOfKey ''MsgKeys "strOfKey")
181 -- | Luxi client encapsulation.
182 data Client = Client { socket :: S.Socket -- ^ The socket of the client
183 , rbuf :: IORef String -- ^ Already received buffer
186 -- | Connects to the master daemon and returns a luxi Client.
187 getClient :: String -> IO Client
189 s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol
190 withTimeout connTimeout "creating luxi connection" $
191 S.connect s (S.SockAddrUnix path)
193 return Client { socket=s, rbuf=rf}
195 -- | Closes the client socket.
196 closeClient :: Client -> IO ()
197 closeClient = S.sClose . socket
199 -- | Sends a message over a luxi transport.
200 sendMsg :: Client -> String -> IO ()
203 sbytes <- withTimeout queryTimeout
204 "sending luxi message" $
205 S.send (socket s) obuf
206 unless (sbytes == length obuf) $ _send (drop sbytes obuf)
207 in _send (buf ++ [eOM])
209 -- | Waits for a message over a luxi transport.
210 recvMsg :: Client -> IO String
213 nbuf <- withTimeout queryTimeout "reading luxi response" $
214 S.recv (socket s) 4096
215 let (msg, remaining) = break (eOM ==) nbuf
217 then _recv (obuf ++ msg)
218 else return (obuf ++ msg, tail remaining)
219 cbuf <- readIORef $ rbuf s
220 let (imsg, ibuf) = break (eOM ==) cbuf
222 if null ibuf -- if old buffer didn't contain a full message
223 then _recv cbuf -- then we read from network
224 else return (imsg, tail ibuf) -- else we return data from our buffer
225 writeIORef (rbuf s) nbuf
228 -- | Serialize a request to String.
229 buildCall :: LuxiOp -- ^ The method
230 -> String -- ^ The serialized form
232 let ja = [ (strOfKey Method, JSString $ toJSString $ strOfOp lo::JSValue)
233 , (strOfKey Args, opToArgs lo::JSValue)
238 -- | Check that luxi responses contain the required keys and that the
239 -- call was successful.
240 validateResult :: String -> Result JSValue
241 validateResult s = do
242 oarr <- fromJResult "Parsing LUXI response"
243 (decodeStrict s)::Result (JSObject JSValue)
244 let arr = J.fromJSObject oarr
245 status <- fromObj arr (strOfKey Success)::Result Bool
246 let rkey = strOfKey Result
248 then fromObj arr rkey
249 else fromObj arr rkey >>= fail
251 -- | Generic luxi method call.
252 callMethod :: LuxiOp -> Client -> IO (Result JSValue)
253 callMethod method s = do
254 sendMsg s $ buildCall method
256 let rval = validateResult result
259 -- | Specialized submitManyJobs call.
260 submitManyJobs :: Client -> [[OpCode]] -> IO (Result [String])
261 submitManyJobs s jobs = do
262 rval <- callMethod (SubmitManyJobs jobs) s
263 -- map each result (status, payload) pair into a nice Result ADT
264 return $ case rval of
267 mapM (\v -> case v of
268 JSArray [JSBool True, JSString x] ->
270 JSArray [JSBool False, JSString x] ->
272 _ -> Bad "Unknown result from the master daemon"
274 x -> Bad ("Cannot parse response from Ganeti: " ++ show x)
276 -- | Custom queryJobs call.
277 queryJobsStatus :: Client -> [String] -> IO (Result [JobStatus])
278 queryJobsStatus s jids = do
279 rval <- callMethod (QueryJobs (map read jids) ["status"]) s
280 return $ case rval of
282 Ok y -> case J.readJSON y::(J.Result [[JobStatus]]) of
283 J.Ok vals -> if any null vals
284 then Bad "Missing job status field"
285 else Ok (map head vals)