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
39 import Text.JSON (JSObject, JSValue, toJSObject, encodeStrict, decodeStrict)
40 import qualified Text.JSON as J
41 import Text.JSON.Types
43 import qualified Network.Socket as S
45 import Ganeti.HTools.Utils
46 import Ganeti.HTools.Types
48 import Ganeti.Jobs (JobStatus)
50 -- * Utility functions
52 -- | Wrapper over System.Timeout.timeout that fails in the IO monad.
53 withTimeout :: Int -> String -> IO a -> IO a
54 withTimeout secs descr action = do
55 result <- timeout (secs * 1000000) action
57 Nothing -> fail $ "Timeout in " ++ descr
60 -- * Generic protocol functionality
62 -- | Currently supported Luxi operations.
63 data LuxiOp = QueryInstances
69 -- | The serialisation of LuxiOps into strings in messages.
70 strOfOp :: LuxiOp -> String
71 strOfOp QueryNodes = "QueryNodes"
72 strOfOp QueryInstances = "QueryInstances"
73 strOfOp QueryJobs = "QueryJobs"
74 strOfOp QueryClusterInfo = "QueryClusterInfo"
75 strOfOp SubmitManyJobs = "SubmitManyJobs"
77 -- | The end-of-message separator.
81 -- | Valid keys in the requests and responses.
87 -- | The serialisation of MsgKeys into strings in messages.
88 strOfKey :: MsgKeys -> String
89 strOfKey Method = "method"
90 strOfKey Args = "args"
91 strOfKey Success = "success"
92 strOfKey Result = "result"
94 -- | Luxi client encapsulation.
95 data Client = Client { socket :: S.Socket -- ^ The socket of the client
96 , rbuf :: IORef String -- ^ Already received buffer
99 -- | Connects to the master daemon and returns a luxi Client.
100 getClient :: String -> IO Client
102 s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol
103 withTimeout connTimeout "creating luxi connection" $
104 S.connect s (S.SockAddrUnix path)
106 return Client { socket=s, rbuf=rf}
108 -- | Closes the client socket.
109 closeClient :: Client -> IO ()
110 closeClient = S.sClose . socket
112 -- | Sends a message over a luxi transport.
113 sendMsg :: Client -> String -> IO ()
116 sbytes <- withTimeout queryTimeout
117 "sending luxi message" $
118 S.send (socket s) obuf
119 unless (sbytes == length obuf) $ _send (drop sbytes obuf)
120 in _send (buf ++ [eOM])
122 -- | Waits for a message over a luxi transport.
123 recvMsg :: Client -> IO String
126 nbuf <- withTimeout queryTimeout "reading luxi response" $
127 S.recv (socket s) 4096
128 let (msg, remaining) = break ((==) eOM) (obuf ++ nbuf)
131 else return (msg, tail remaining))
132 cbuf <- readIORef $ rbuf s
133 (msg, nbuf) <- _recv cbuf
134 writeIORef (rbuf s) nbuf
137 -- | Serialize a request to String.
138 buildCall :: LuxiOp -- ^ The method
139 -> JSValue -- ^ The arguments
140 -> String -- ^ The serialized form
142 let ja = [(strOfKey Method,
143 JSString $ toJSString $ strOfOp msg::JSValue),
150 -- | Check that luxi responses contain the required keys and that the
151 -- call was successful.
152 validateResult :: String -> Result JSValue
153 validateResult s = do
154 oarr <- fromJResult $ decodeStrict s::Result (JSObject JSValue)
155 let arr = J.fromJSObject oarr
156 status <- fromObj (strOfKey Success) arr::Result Bool
157 let rkey = strOfKey Result
159 then fromObj rkey arr
160 else fromObj rkey arr >>= fail)
162 -- | Generic luxi method call.
163 callMethod :: LuxiOp -> JSValue -> Client -> IO (Result JSValue)
164 callMethod method args s = do
165 sendMsg s $ buildCall method args
167 let rval = validateResult result
170 -- | Specialized submitManyJobs call.
171 submitManyJobs :: Client -> JSValue -> IO (Result [String])
172 submitManyJobs s jobs = do
173 rval <- callMethod SubmitManyJobs jobs s
174 -- map each result (status, payload) pair into a nice Result ADT
175 return $ case rval of
178 mapM (\v -> case v of
179 JSArray [JSBool True, JSString x] ->
181 JSArray [JSBool False, JSString x] ->
183 _ -> Bad "Unknown result from the master daemon"
185 x -> Bad ("Cannot parse response from Ganeti: " ++ show x)
187 -- | Custom queryJobs call.
188 queryJobsStatus :: Client -> [String] -> IO (Result [JobStatus])
189 queryJobsStatus s jids = do
190 rval <- callMethod QueryJobs (J.showJSON (jids, ["status"])) s
191 return $ case rval of
193 Ok y -> case J.readJSON y::(J.Result [[JobStatus]]) of
194 J.Ok vals -> if any null vals
195 then Bad "Missing job status field"
196 else Ok (map head vals)