root / src / Ganeti / Luxi.hs @ c5da4cf1
History | View | Annotate | Download (16.2 kB)
1 | a0090487 | Agata Murawska | {-# LANGUAGE TemplateHaskell #-} |
---|---|---|---|
2 | a0090487 | Agata Murawska | |
3 | 6583e677 | Iustin Pop | {-| Implementation of the Ganeti LUXI interface. |
4 | 6583e677 | Iustin Pop | |
5 | 6583e677 | Iustin Pop | -} |
6 | 6583e677 | Iustin Pop | |
7 | 6583e677 | Iustin Pop | {- |
8 | 6583e677 | Iustin Pop | |
9 | fae980e5 | Iustin Pop | Copyright (C) 2009, 2010, 2011, 2012 Google Inc. |
10 | 6583e677 | Iustin Pop | |
11 | 6583e677 | Iustin Pop | This program is free software; you can redistribute it and/or modify |
12 | 6583e677 | Iustin Pop | it under the terms of the GNU General Public License as published by |
13 | 6583e677 | Iustin Pop | the Free Software Foundation; either version 2 of the License, or |
14 | 6583e677 | Iustin Pop | (at your option) any later version. |
15 | 6583e677 | Iustin Pop | |
16 | 6583e677 | Iustin Pop | This program is distributed in the hope that it will be useful, but |
17 | 6583e677 | Iustin Pop | WITHOUT ANY WARRANTY; without even the implied warranty of |
18 | 6583e677 | Iustin Pop | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
19 | 6583e677 | Iustin Pop | General Public License for more details. |
20 | 6583e677 | Iustin Pop | |
21 | 6583e677 | Iustin Pop | You should have received a copy of the GNU General Public License |
22 | 6583e677 | Iustin Pop | along with this program; if not, write to the Free Software |
23 | 6583e677 | Iustin Pop | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
24 | 6583e677 | Iustin Pop | 02110-1301, USA. |
25 | 6583e677 | Iustin Pop | |
26 | 6583e677 | Iustin Pop | -} |
27 | 6583e677 | Iustin Pop | |
28 | 6583e677 | Iustin Pop | module Ganeti.Luxi |
29 | ebf38064 | Iustin Pop | ( LuxiOp(..) |
30 | 95d0d502 | Iustin Pop | , LuxiReq(..) |
31 | ebf38064 | Iustin Pop | , Client |
32 | ccc817a2 | Iustin Pop | , JobId |
33 | c48711d5 | Iustin Pop | , fromJobId |
34 | c48711d5 | Iustin Pop | , makeJobId |
35 | 0aff2293 | Iustin Pop | , RecvResult(..) |
36 | 0aff2293 | Iustin Pop | , strOfOp |
37 | ebf38064 | Iustin Pop | , getClient |
38 | 13f2321c | Iustin Pop | , getServer |
39 | 13f2321c | Iustin Pop | , acceptClient |
40 | ebf38064 | Iustin Pop | , closeClient |
41 | 0aff2293 | Iustin Pop | , closeServer |
42 | ebf38064 | Iustin Pop | , callMethod |
43 | ebf38064 | Iustin Pop | , submitManyJobs |
44 | ebf38064 | Iustin Pop | , queryJobsStatus |
45 | cdd495ae | Iustin Pop | , buildCall |
46 | 0aff2293 | Iustin Pop | , buildResponse |
47 | cdd495ae | Iustin Pop | , validateCall |
48 | cdd495ae | Iustin Pop | , decodeCall |
49 | 13f2321c | Iustin Pop | , recvMsg |
50 | 0aff2293 | Iustin Pop | , recvMsgExt |
51 | 13f2321c | Iustin Pop | , sendMsg |
52 | 471b6c46 | Iustin Pop | , allLuxiCalls |
53 | ebf38064 | Iustin Pop | ) where |
54 | 6583e677 | Iustin Pop | |
55 | 0aff2293 | Iustin Pop | import Control.Exception (catch) |
56 | 6583e677 | Iustin Pop | import Data.IORef |
57 | e821050d | Iustin Pop | import qualified Data.ByteString as B |
58 | 62d5242b | Iustin Pop | import qualified Data.ByteString.Lazy as BL |
59 | e821050d | Iustin Pop | import qualified Data.ByteString.UTF8 as UTF8 |
60 | 62d5242b | Iustin Pop | import qualified Data.ByteString.Lazy.UTF8 as UTF8L |
61 | e821050d | Iustin Pop | import Data.Word (Word8) |
62 | 6583e677 | Iustin Pop | import Control.Monad |
63 | 0903280b | Iustin Pop | import Text.JSON (encodeStrict, decodeStrict) |
64 | 6583e677 | Iustin Pop | import qualified Text.JSON as J |
65 | 7adb7dff | Iustin Pop | import Text.JSON.Pretty (pp_value) |
66 | 6583e677 | Iustin Pop | import Text.JSON.Types |
67 | 0aff2293 | Iustin Pop | import System.Directory (removeFile) |
68 | e821050d | Iustin Pop | import System.IO (hClose, hFlush, hWaitForInput, Handle, IOMode(..)) |
69 | 0aff2293 | Iustin Pop | import System.IO.Error (isEOFError) |
70 | 5f5aa745 | Hrvoje Ribicic | import System.Posix.Files |
71 | 6583e677 | Iustin Pop | import System.Timeout |
72 | 6583e677 | Iustin Pop | import qualified Network.Socket as S |
73 | 6583e677 | Iustin Pop | |
74 | 4cd79ca8 | Iustin Pop | import Ganeti.BasicTypes |
75 | 92678b3c | Iustin Pop | import Ganeti.Constants |
76 | 7adb7dff | Iustin Pop | import Ganeti.Errors |
77 | 7adb7dff | Iustin Pop | import Ganeti.JSON |
78 | fa10983e | Iustin Pop | import Ganeti.OpParams (pTagsObject) |
79 | 367c4241 | Dato Simó | import Ganeti.OpCodes |
80 | 4cab6703 | Iustin Pop | import qualified Ganeti.Query.Language as Qlang |
81 | 1c31b263 | Jose A. Lopes | import Ganeti.Runtime (GanetiDaemon(..), MiscGroup(..), GanetiGroup(..)) |
82 | a0090487 | Agata Murawska | import Ganeti.THH |
83 | c48711d5 | Iustin Pop | import Ganeti.Types |
84 | e455a3e8 | Michele Tartara | import Ganeti.Utils |
85 | 9a2ff880 | Iustin Pop | |
86 | 6583e677 | Iustin Pop | -- * Utility functions |
87 | 6583e677 | Iustin Pop | |
88 | 6583e677 | Iustin Pop | -- | Wrapper over System.Timeout.timeout that fails in the IO monad. |
89 | 6583e677 | Iustin Pop | withTimeout :: Int -> String -> IO a -> IO a |
90 | 6583e677 | Iustin Pop | withTimeout secs descr action = do |
91 | ebf38064 | Iustin Pop | result <- timeout (secs * 1000000) action |
92 | 3603605a | Iustin Pop | case result of |
93 | 3603605a | Iustin Pop | Nothing -> fail $ "Timeout in " ++ descr |
94 | 3603605a | Iustin Pop | Just v -> return v |
95 | 6583e677 | Iustin Pop | |
96 | 6583e677 | Iustin Pop | -- * Generic protocol functionality |
97 | 6583e677 | Iustin Pop | |
98 | 0aff2293 | Iustin Pop | -- | Result of receiving a message from the socket. |
99 | 0aff2293 | Iustin Pop | data RecvResult = RecvConnClosed -- ^ Connection closed |
100 | 0aff2293 | Iustin Pop | | RecvError String -- ^ Any other error |
101 | 0aff2293 | Iustin Pop | | RecvOk String -- ^ Successfull receive |
102 | 139c0683 | Iustin Pop | deriving (Show, Eq) |
103 | 0aff2293 | Iustin Pop | |
104 | a0090487 | Agata Murawska | -- | Currently supported Luxi operations and JSON serialization. |
105 | a0090487 | Agata Murawska | $(genLuxiOp "LuxiOp" |
106 | 72295708 | Iustin Pop | [ (luxiReqQuery, |
107 | 88609f00 | Iustin Pop | [ simpleField "what" [t| Qlang.ItemType |] |
108 | 88609f00 | Iustin Pop | , simpleField "fields" [t| [String] |] |
109 | 88609f00 | Iustin Pop | , simpleField "qfilter" [t| Qlang.Filter Qlang.FilterField |] |
110 | ebf38064 | Iustin Pop | ]) |
111 | 72295708 | Iustin Pop | , (luxiReqQueryFields, |
112 | 88609f00 | Iustin Pop | [ simpleField "what" [t| Qlang.ItemType |] |
113 | 88609f00 | Iustin Pop | , simpleField "fields" [t| [String] |] |
114 | 72295708 | Iustin Pop | ]) |
115 | fae980e5 | Iustin Pop | , (luxiReqQueryNodes, |
116 | 88609f00 | Iustin Pop | [ simpleField "names" [t| [String] |] |
117 | 88609f00 | Iustin Pop | , simpleField "fields" [t| [String] |] |
118 | 88609f00 | Iustin Pop | , simpleField "lock" [t| Bool |] |
119 | ebf38064 | Iustin Pop | ]) |
120 | fae980e5 | Iustin Pop | , (luxiReqQueryGroups, |
121 | 88609f00 | Iustin Pop | [ simpleField "names" [t| [String] |] |
122 | 88609f00 | Iustin Pop | , simpleField "fields" [t| [String] |] |
123 | 88609f00 | Iustin Pop | , simpleField "lock" [t| Bool |] |
124 | ebf38064 | Iustin Pop | ]) |
125 | 795d035d | Klaus Aehlig | , (luxiReqQueryNetworks, |
126 | 795d035d | Klaus Aehlig | [ simpleField "names" [t| [String] |] |
127 | 795d035d | Klaus Aehlig | , simpleField "fields" [t| [String] |] |
128 | 795d035d | Klaus Aehlig | , simpleField "lock" [t| Bool |] |
129 | 795d035d | Klaus Aehlig | ]) |
130 | fae980e5 | Iustin Pop | , (luxiReqQueryInstances, |
131 | 88609f00 | Iustin Pop | [ simpleField "names" [t| [String] |] |
132 | 88609f00 | Iustin Pop | , simpleField "fields" [t| [String] |] |
133 | 88609f00 | Iustin Pop | , simpleField "lock" [t| Bool |] |
134 | ebf38064 | Iustin Pop | ]) |
135 | fae980e5 | Iustin Pop | , (luxiReqQueryJobs, |
136 | c48711d5 | Iustin Pop | [ simpleField "ids" [t| [JobId] |] |
137 | 88609f00 | Iustin Pop | , simpleField "fields" [t| [String] |] |
138 | ebf38064 | Iustin Pop | ]) |
139 | fae980e5 | Iustin Pop | , (luxiReqQueryExports, |
140 | 88609f00 | Iustin Pop | [ simpleField "nodes" [t| [String] |] |
141 | 88609f00 | Iustin Pop | , simpleField "lock" [t| Bool |] |
142 | ebf38064 | Iustin Pop | ]) |
143 | fae980e5 | Iustin Pop | , (luxiReqQueryConfigValues, |
144 | 88609f00 | Iustin Pop | [ simpleField "fields" [t| [String] |] ] |
145 | ebf38064 | Iustin Pop | ) |
146 | fae980e5 | Iustin Pop | , (luxiReqQueryClusterInfo, []) |
147 | fae980e5 | Iustin Pop | , (luxiReqQueryTags, |
148 | 34af39e8 | Jose A. Lopes | [ pTagsObject |
149 | 34af39e8 | Jose A. Lopes | , simpleField "name" [t| String |] |
150 | 34af39e8 | Jose A. Lopes | ]) |
151 | fae980e5 | Iustin Pop | , (luxiReqSubmitJob, |
152 | 7e723913 | Iustin Pop | [ simpleField "job" [t| [MetaOpCode] |] ] |
153 | ebf38064 | Iustin Pop | ) |
154 | 346c3037 | Klaus Aehlig | , (luxiReqSubmitJobToDrainedQueue, |
155 | 346c3037 | Klaus Aehlig | [ simpleField "job" [t| [MetaOpCode] |] ] |
156 | 346c3037 | Klaus Aehlig | ) |
157 | fae980e5 | Iustin Pop | , (luxiReqSubmitManyJobs, |
158 | 7e723913 | Iustin Pop | [ simpleField "ops" [t| [[MetaOpCode]] |] ] |
159 | ebf38064 | Iustin Pop | ) |
160 | fae980e5 | Iustin Pop | , (luxiReqWaitForJobChange, |
161 | c48711d5 | Iustin Pop | [ simpleField "job" [t| JobId |] |
162 | 88609f00 | Iustin Pop | , simpleField "fields" [t| [String]|] |
163 | 88609f00 | Iustin Pop | , simpleField "prev_job" [t| JSValue |] |
164 | 88609f00 | Iustin Pop | , simpleField "prev_log" [t| JSValue |] |
165 | 88609f00 | Iustin Pop | , simpleField "tmout" [t| Int |] |
166 | ebf38064 | Iustin Pop | ]) |
167 | fae980e5 | Iustin Pop | , (luxiReqArchiveJob, |
168 | c48711d5 | Iustin Pop | [ simpleField "job" [t| JobId |] ] |
169 | ebf38064 | Iustin Pop | ) |
170 | fae980e5 | Iustin Pop | , (luxiReqAutoArchiveJobs, |
171 | 88609f00 | Iustin Pop | [ simpleField "age" [t| Int |] |
172 | 88609f00 | Iustin Pop | , simpleField "tmout" [t| Int |] |
173 | ebf38064 | Iustin Pop | ]) |
174 | fae980e5 | Iustin Pop | , (luxiReqCancelJob, |
175 | c48711d5 | Iustin Pop | [ simpleField "job" [t| JobId |] ] |
176 | ebf38064 | Iustin Pop | ) |
177 | f63ffb37 | Michael Hanselmann | , (luxiReqChangeJobPriority, |
178 | c48711d5 | Iustin Pop | [ simpleField "job" [t| JobId |] |
179 | f63ffb37 | Michael Hanselmann | , simpleField "priority" [t| Int |] ] |
180 | f63ffb37 | Michael Hanselmann | ) |
181 | fae980e5 | Iustin Pop | , (luxiReqSetDrainFlag, |
182 | 88609f00 | Iustin Pop | [ simpleField "flag" [t| Bool |] ] |
183 | ebf38064 | Iustin Pop | ) |
184 | fae980e5 | Iustin Pop | , (luxiReqSetWatcherPause, |
185 | 88609f00 | Iustin Pop | [ simpleField "duration" [t| Double |] ] |
186 | ebf38064 | Iustin Pop | ) |
187 | a0090487 | Agata Murawska | ]) |
188 | 6583e677 | Iustin Pop | |
189 | 95d0d502 | Iustin Pop | $(makeJSONInstance ''LuxiReq) |
190 | 95d0d502 | Iustin Pop | |
191 | 471b6c46 | Iustin Pop | -- | List of all defined Luxi calls. |
192 | 471b6c46 | Iustin Pop | $(genAllConstr (drop 3) ''LuxiReq "allLuxiCalls") |
193 | 471b6c46 | Iustin Pop | |
194 | 6583e677 | Iustin Pop | -- | The serialisation of LuxiOps into strings in messages. |
195 | a0090487 | Agata Murawska | $(genStrOfOp ''LuxiOp "strOfOp") |
196 | 6583e677 | Iustin Pop | |
197 | cdd495ae | Iustin Pop | -- | Type holding the initial (unparsed) Luxi call. |
198 | cdd495ae | Iustin Pop | data LuxiCall = LuxiCall LuxiReq JSValue |
199 | cdd495ae | Iustin Pop | |
200 | 6583e677 | Iustin Pop | -- | The end-of-message separator. |
201 | e821050d | Iustin Pop | eOM :: Word8 |
202 | e821050d | Iustin Pop | eOM = 3 |
203 | e821050d | Iustin Pop | |
204 | e821050d | Iustin Pop | -- | The end-of-message encoded as a ByteString. |
205 | e821050d | Iustin Pop | bEOM :: B.ByteString |
206 | e821050d | Iustin Pop | bEOM = B.singleton eOM |
207 | 6583e677 | Iustin Pop | |
208 | 6583e677 | Iustin Pop | -- | Valid keys in the requests and responses. |
209 | 6583e677 | Iustin Pop | data MsgKeys = Method |
210 | 6583e677 | Iustin Pop | | Args |
211 | 6583e677 | Iustin Pop | | Success |
212 | 6583e677 | Iustin Pop | | Result |
213 | 6583e677 | Iustin Pop | |
214 | 6583e677 | Iustin Pop | -- | The serialisation of MsgKeys into strings in messages. |
215 | a0090487 | Agata Murawska | $(genStrOfKey ''MsgKeys "strOfKey") |
216 | 6583e677 | Iustin Pop | |
217 | 6583e677 | Iustin Pop | -- | Luxi client encapsulation. |
218 | e821050d | Iustin Pop | data Client = Client { socket :: Handle -- ^ The socket of the client |
219 | e821050d | Iustin Pop | , rbuf :: IORef B.ByteString -- ^ Already received buffer |
220 | 6583e677 | Iustin Pop | } |
221 | 6583e677 | Iustin Pop | |
222 | 6583e677 | Iustin Pop | -- | Connects to the master daemon and returns a luxi Client. |
223 | 6583e677 | Iustin Pop | getClient :: String -> IO Client |
224 | 6583e677 | Iustin Pop | getClient path = do |
225 | ebf38064 | Iustin Pop | s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol |
226 | 4cd79ca8 | Iustin Pop | withTimeout luxiDefCtmo "creating luxi connection" $ |
227 | ebf38064 | Iustin Pop | S.connect s (S.SockAddrUnix path) |
228 | e821050d | Iustin Pop | rf <- newIORef B.empty |
229 | e821050d | Iustin Pop | h <- S.socketToHandle s ReadWriteMode |
230 | e821050d | Iustin Pop | return Client { socket=h, rbuf=rf } |
231 | 6583e677 | Iustin Pop | |
232 | 13f2321c | Iustin Pop | -- | Creates and returns a server endpoint. |
233 | e455a3e8 | Michele Tartara | getServer :: Bool -> FilePath -> IO S.Socket |
234 | e455a3e8 | Michele Tartara | getServer setOwner path = do |
235 | 13f2321c | Iustin Pop | s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol |
236 | 13f2321c | Iustin Pop | S.bindSocket s (S.SockAddrUnix path) |
237 | 5f5aa745 | Hrvoje Ribicic | when setOwner $ do |
238 | 5f5aa745 | Hrvoje Ribicic | setOwnerAndGroupFromNames path GanetiLuxid $ ExtraGroup DaemonsGroup |
239 | 5f5aa745 | Hrvoje Ribicic | setFileMode path $ fromIntegral luxiSocketPerms |
240 | 13f2321c | Iustin Pop | S.listen s 5 -- 5 is the max backlog |
241 | 13f2321c | Iustin Pop | return s |
242 | 13f2321c | Iustin Pop | |
243 | 0aff2293 | Iustin Pop | -- | Closes a server endpoint. |
244 | 0aff2293 | Iustin Pop | -- FIXME: this should be encapsulated into a nicer type. |
245 | 0aff2293 | Iustin Pop | closeServer :: FilePath -> S.Socket -> IO () |
246 | 0aff2293 | Iustin Pop | closeServer path sock = do |
247 | 0aff2293 | Iustin Pop | S.sClose sock |
248 | 0aff2293 | Iustin Pop | removeFile path |
249 | 0aff2293 | Iustin Pop | |
250 | 13f2321c | Iustin Pop | -- | Accepts a client |
251 | 13f2321c | Iustin Pop | acceptClient :: S.Socket -> IO Client |
252 | 13f2321c | Iustin Pop | acceptClient s = do |
253 | 13f2321c | Iustin Pop | -- second return is the address of the client, which we ignore here |
254 | 13f2321c | Iustin Pop | (client_socket, _) <- S.accept s |
255 | 13f2321c | Iustin Pop | new_buffer <- newIORef B.empty |
256 | 13f2321c | Iustin Pop | handle <- S.socketToHandle client_socket ReadWriteMode |
257 | 13f2321c | Iustin Pop | return Client { socket=handle, rbuf=new_buffer } |
258 | 13f2321c | Iustin Pop | |
259 | 6583e677 | Iustin Pop | -- | Closes the client socket. |
260 | 6583e677 | Iustin Pop | closeClient :: Client -> IO () |
261 | e821050d | Iustin Pop | closeClient = hClose . socket |
262 | 6583e677 | Iustin Pop | |
263 | 6583e677 | Iustin Pop | -- | Sends a message over a luxi transport. |
264 | 6583e677 | Iustin Pop | sendMsg :: Client -> String -> IO () |
265 | 4cd79ca8 | Iustin Pop | sendMsg s buf = withTimeout luxiDefRwto "sending luxi message" $ do |
266 | 62d5242b | Iustin Pop | let encoded = UTF8L.fromString buf |
267 | e821050d | Iustin Pop | handle = socket s |
268 | 62d5242b | Iustin Pop | BL.hPut handle encoded |
269 | e821050d | Iustin Pop | B.hPut handle bEOM |
270 | e821050d | Iustin Pop | hFlush handle |
271 | e821050d | Iustin Pop | |
272 | e821050d | Iustin Pop | -- | Given a current buffer and the handle, it will read from the |
273 | e821050d | Iustin Pop | -- network until we get a full message, and it will return that |
274 | e821050d | Iustin Pop | -- message and the leftover buffer contents. |
275 | e821050d | Iustin Pop | recvUpdate :: Handle -> B.ByteString -> IO (B.ByteString, B.ByteString) |
276 | e821050d | Iustin Pop | recvUpdate handle obuf = do |
277 | 4cd79ca8 | Iustin Pop | nbuf <- withTimeout luxiDefRwto "reading luxi response" $ do |
278 | e821050d | Iustin Pop | _ <- hWaitForInput handle (-1) |
279 | e821050d | Iustin Pop | B.hGetNonBlocking handle 4096 |
280 | e821050d | Iustin Pop | let (msg, remaining) = B.break (eOM ==) nbuf |
281 | e821050d | Iustin Pop | newbuf = B.append obuf msg |
282 | e821050d | Iustin Pop | if B.null remaining |
283 | e821050d | Iustin Pop | then recvUpdate handle newbuf |
284 | e821050d | Iustin Pop | else return (newbuf, B.tail remaining) |
285 | 6583e677 | Iustin Pop | |
286 | 6583e677 | Iustin Pop | -- | Waits for a message over a luxi transport. |
287 | 6583e677 | Iustin Pop | recvMsg :: Client -> IO String |
288 | 6583e677 | Iustin Pop | recvMsg s = do |
289 | 6583e677 | Iustin Pop | cbuf <- readIORef $ rbuf s |
290 | e821050d | Iustin Pop | let (imsg, ibuf) = B.break (eOM ==) cbuf |
291 | 95f490de | Iustin Pop | (msg, nbuf) <- |
292 | e821050d | Iustin Pop | if B.null ibuf -- if old buffer didn't contain a full message |
293 | e821050d | Iustin Pop | then recvUpdate (socket s) cbuf -- then we read from network |
294 | e821050d | Iustin Pop | else return (imsg, B.tail ibuf) -- else we return data from our buffer |
295 | 6583e677 | Iustin Pop | writeIORef (rbuf s) nbuf |
296 | e821050d | Iustin Pop | return $ UTF8.toString msg |
297 | 6583e677 | Iustin Pop | |
298 | 0aff2293 | Iustin Pop | -- | Extended wrapper over recvMsg. |
299 | 0aff2293 | Iustin Pop | recvMsgExt :: Client -> IO RecvResult |
300 | 0aff2293 | Iustin Pop | recvMsgExt s = |
301 | 7ae5d703 | Iustin Pop | Control.Exception.catch (liftM RecvOk (recvMsg s)) $ \e -> |
302 | 66ad857a | Iustin Pop | return $ if isEOFError e |
303 | 66ad857a | Iustin Pop | then RecvConnClosed |
304 | 66ad857a | Iustin Pop | else RecvError (show e) |
305 | 0aff2293 | Iustin Pop | |
306 | 6583e677 | Iustin Pop | -- | Serialize a request to String. |
307 | 6583e677 | Iustin Pop | buildCall :: LuxiOp -- ^ The method |
308 | 6583e677 | Iustin Pop | -> String -- ^ The serialized form |
309 | 683b1ca7 | Iustin Pop | buildCall lo = |
310 | 2cdaf225 | Iustin Pop | let ja = [ (strOfKey Method, J.showJSON $ strOfOp lo) |
311 | 2cdaf225 | Iustin Pop | , (strOfKey Args, opToArgs lo) |
312 | ebf38064 | Iustin Pop | ] |
313 | ebf38064 | Iustin Pop | jo = toJSObject ja |
314 | ebf38064 | Iustin Pop | in encodeStrict jo |
315 | 6583e677 | Iustin Pop | |
316 | 0aff2293 | Iustin Pop | -- | Serialize the response to String. |
317 | 0aff2293 | Iustin Pop | buildResponse :: Bool -- ^ Success |
318 | 0aff2293 | Iustin Pop | -> JSValue -- ^ The arguments |
319 | 0aff2293 | Iustin Pop | -> String -- ^ The serialized form |
320 | 0aff2293 | Iustin Pop | buildResponse success args = |
321 | 0aff2293 | Iustin Pop | let ja = [ (strOfKey Success, JSBool success) |
322 | 0aff2293 | Iustin Pop | , (strOfKey Result, args)] |
323 | 0aff2293 | Iustin Pop | jo = toJSObject ja |
324 | 0aff2293 | Iustin Pop | in encodeStrict jo |
325 | 0aff2293 | Iustin Pop | |
326 | cdd495ae | Iustin Pop | -- | Check that luxi request contains the required keys and parse it. |
327 | cdd495ae | Iustin Pop | validateCall :: String -> Result LuxiCall |
328 | cdd495ae | Iustin Pop | validateCall s = do |
329 | 0aff2293 | Iustin Pop | arr <- fromJResult "parsing top-level luxi message" $ |
330 | 0aff2293 | Iustin Pop | decodeStrict s::Result (JSObject JSValue) |
331 | cdd495ae | Iustin Pop | let aobj = fromJSObject arr |
332 | cdd495ae | Iustin Pop | call <- fromObj aobj (strOfKey Method)::Result LuxiReq |
333 | cdd495ae | Iustin Pop | args <- fromObj aobj (strOfKey Args) |
334 | cdd495ae | Iustin Pop | return (LuxiCall call args) |
335 | cdd495ae | Iustin Pop | |
336 | cdd495ae | Iustin Pop | -- | Converts Luxi call arguments into a 'LuxiOp' data structure. |
337 | cdd495ae | Iustin Pop | -- |
338 | cdd495ae | Iustin Pop | -- This is currently hand-coded until we make it more uniform so that |
339 | cdd495ae | Iustin Pop | -- it can be generated using TH. |
340 | cdd495ae | Iustin Pop | decodeCall :: LuxiCall -> Result LuxiOp |
341 | cdd495ae | Iustin Pop | decodeCall (LuxiCall call args) = |
342 | cdd495ae | Iustin Pop | case call of |
343 | cdd495ae | Iustin Pop | ReqQueryJobs -> do |
344 | c48711d5 | Iustin Pop | (jids, jargs) <- fromJVal args |
345 | d2970809 | Iustin Pop | jids' <- case jids of |
346 | d2970809 | Iustin Pop | JSNull -> return [] |
347 | d2970809 | Iustin Pop | _ -> fromJVal jids |
348 | d2970809 | Iustin Pop | return $ QueryJobs jids' jargs |
349 | cdd495ae | Iustin Pop | ReqQueryInstances -> do |
350 | cdd495ae | Iustin Pop | (names, fields, locking) <- fromJVal args |
351 | cdd495ae | Iustin Pop | return $ QueryInstances names fields locking |
352 | cdd495ae | Iustin Pop | ReqQueryNodes -> do |
353 | cdd495ae | Iustin Pop | (names, fields, locking) <- fromJVal args |
354 | cdd495ae | Iustin Pop | return $ QueryNodes names fields locking |
355 | cdd495ae | Iustin Pop | ReqQueryGroups -> do |
356 | cdd495ae | Iustin Pop | (names, fields, locking) <- fromJVal args |
357 | cdd495ae | Iustin Pop | return $ QueryGroups names fields locking |
358 | 5b11f8db | Iustin Pop | ReqQueryClusterInfo -> |
359 | cdd495ae | Iustin Pop | return QueryClusterInfo |
360 | 795d035d | Klaus Aehlig | ReqQueryNetworks -> do |
361 | 795d035d | Klaus Aehlig | (names, fields, locking) <- fromJVal args |
362 | 795d035d | Klaus Aehlig | return $ QueryNetworks names fields locking |
363 | cdd495ae | Iustin Pop | ReqQuery -> do |
364 | 9a94c848 | Iustin Pop | (what, fields, qfilter) <- fromJVal args |
365 | 9a94c848 | Iustin Pop | return $ Query what fields qfilter |
366 | 72295708 | Iustin Pop | ReqQueryFields -> do |
367 | 72295708 | Iustin Pop | (what, fields) <- fromJVal args |
368 | 72295708 | Iustin Pop | fields' <- case fields of |
369 | 72295708 | Iustin Pop | JSNull -> return [] |
370 | 72295708 | Iustin Pop | _ -> fromJVal fields |
371 | 72295708 | Iustin Pop | return $ QueryFields what fields' |
372 | cdd495ae | Iustin Pop | ReqSubmitJob -> do |
373 | cdd495ae | Iustin Pop | [ops1] <- fromJVal args |
374 | cdd495ae | Iustin Pop | ops2 <- mapM (fromJResult (luxiReqToRaw call) . J.readJSON) ops1 |
375 | cdd495ae | Iustin Pop | return $ SubmitJob ops2 |
376 | 346c3037 | Klaus Aehlig | ReqSubmitJobToDrainedQueue -> do |
377 | 346c3037 | Klaus Aehlig | [ops1] <- fromJVal args |
378 | 346c3037 | Klaus Aehlig | ops2 <- mapM (fromJResult (luxiReqToRaw call) . J.readJSON) ops1 |
379 | 346c3037 | Klaus Aehlig | return $ SubmitJobToDrainedQueue ops2 |
380 | cdd495ae | Iustin Pop | ReqSubmitManyJobs -> do |
381 | cdd495ae | Iustin Pop | [ops1] <- fromJVal args |
382 | cdd495ae | Iustin Pop | ops2 <- mapM (fromJResult (luxiReqToRaw call) . J.readJSON) ops1 |
383 | cdd495ae | Iustin Pop | return $ SubmitManyJobs ops2 |
384 | cdd495ae | Iustin Pop | ReqWaitForJobChange -> do |
385 | cdd495ae | Iustin Pop | (jid, fields, pinfo, pidx, wtmout) <- |
386 | cdd495ae | Iustin Pop | -- No instance for 5-tuple, code copied from the |
387 | cdd495ae | Iustin Pop | -- json sources and adapted |
388 | cdd495ae | Iustin Pop | fromJResult "Parsing WaitForJobChange message" $ |
389 | cdd495ae | Iustin Pop | case args of |
390 | cdd495ae | Iustin Pop | JSArray [a, b, c, d, e] -> |
391 | cdd495ae | Iustin Pop | (,,,,) `fmap` |
392 | cdd495ae | Iustin Pop | J.readJSON a `ap` |
393 | cdd495ae | Iustin Pop | J.readJSON b `ap` |
394 | cdd495ae | Iustin Pop | J.readJSON c `ap` |
395 | cdd495ae | Iustin Pop | J.readJSON d `ap` |
396 | cdd495ae | Iustin Pop | J.readJSON e |
397 | cdd495ae | Iustin Pop | _ -> J.Error "Not enough values" |
398 | c48711d5 | Iustin Pop | return $ WaitForJobChange jid fields pinfo pidx wtmout |
399 | cdd495ae | Iustin Pop | ReqArchiveJob -> do |
400 | cdd495ae | Iustin Pop | [jid] <- fromJVal args |
401 | c48711d5 | Iustin Pop | return $ ArchiveJob jid |
402 | cdd495ae | Iustin Pop | ReqAutoArchiveJobs -> do |
403 | cdd495ae | Iustin Pop | (age, tmout) <- fromJVal args |
404 | cdd495ae | Iustin Pop | return $ AutoArchiveJobs age tmout |
405 | cdd495ae | Iustin Pop | ReqQueryExports -> do |
406 | cdd495ae | Iustin Pop | (nodes, lock) <- fromJVal args |
407 | cdd495ae | Iustin Pop | return $ QueryExports nodes lock |
408 | cdd495ae | Iustin Pop | ReqQueryConfigValues -> do |
409 | cdd495ae | Iustin Pop | [fields] <- fromJVal args |
410 | cdd495ae | Iustin Pop | return $ QueryConfigValues fields |
411 | cdd495ae | Iustin Pop | ReqQueryTags -> do |
412 | cdd495ae | Iustin Pop | (kind, name) <- fromJVal args |
413 | 34af39e8 | Jose A. Lopes | return $ QueryTags kind name |
414 | cdd495ae | Iustin Pop | ReqCancelJob -> do |
415 | c48711d5 | Iustin Pop | [jid] <- fromJVal args |
416 | c48711d5 | Iustin Pop | return $ CancelJob jid |
417 | f63ffb37 | Michael Hanselmann | ReqChangeJobPriority -> do |
418 | c48711d5 | Iustin Pop | (jid, priority) <- fromJVal args |
419 | c48711d5 | Iustin Pop | return $ ChangeJobPriority jid priority |
420 | cdd495ae | Iustin Pop | ReqSetDrainFlag -> do |
421 | cdd495ae | Iustin Pop | [flag] <- fromJVal args |
422 | cdd495ae | Iustin Pop | return $ SetDrainFlag flag |
423 | cdd495ae | Iustin Pop | ReqSetWatcherPause -> do |
424 | cdd495ae | Iustin Pop | [duration] <- fromJVal args |
425 | cdd495ae | Iustin Pop | return $ SetWatcherPause duration |
426 | cdd495ae | Iustin Pop | |
427 | 6583e677 | Iustin Pop | -- | Check that luxi responses contain the required keys and that the |
428 | 6583e677 | Iustin Pop | -- call was successful. |
429 | 7adb7dff | Iustin Pop | validateResult :: String -> ErrorResult JSValue |
430 | 6583e677 | Iustin Pop | validateResult s = do |
431 | e821050d | Iustin Pop | when (UTF8.replacement_char `elem` s) $ |
432 | e821050d | Iustin Pop | fail "Failed to decode UTF-8, detected replacement char after decoding" |
433 | 7adb7dff | Iustin Pop | oarr <- fromJResult "Parsing LUXI response" (decodeStrict s) |
434 | 262f3e6c | Iustin Pop | let arr = J.fromJSObject oarr |
435 | 7adb7dff | Iustin Pop | status <- fromObj arr (strOfKey Success) |
436 | 7adb7dff | Iustin Pop | result <- fromObj arr (strOfKey Result) |
437 | 3603605a | Iustin Pop | if status |
438 | 7adb7dff | Iustin Pop | then return result |
439 | 7adb7dff | Iustin Pop | else decodeError result |
440 | 7adb7dff | Iustin Pop | |
441 | 7adb7dff | Iustin Pop | -- | Try to decode an error from the server response. This function |
442 | 7adb7dff | Iustin Pop | -- will always fail, since it's called only on the error path (when |
443 | 7adb7dff | Iustin Pop | -- status is False). |
444 | 7adb7dff | Iustin Pop | decodeError :: JSValue -> ErrorResult JSValue |
445 | 7adb7dff | Iustin Pop | decodeError val = |
446 | 7adb7dff | Iustin Pop | case fromJVal val of |
447 | 7adb7dff | Iustin Pop | Ok e -> Bad e |
448 | 7adb7dff | Iustin Pop | Bad msg -> Bad $ GenericError msg |
449 | 6583e677 | Iustin Pop | |
450 | 6583e677 | Iustin Pop | -- | Generic luxi method call. |
451 | 7adb7dff | Iustin Pop | callMethod :: LuxiOp -> Client -> IO (ErrorResult JSValue) |
452 | 683b1ca7 | Iustin Pop | callMethod method s = do |
453 | 683b1ca7 | Iustin Pop | sendMsg s $ buildCall method |
454 | 6583e677 | Iustin Pop | result <- recvMsg s |
455 | 6583e677 | Iustin Pop | let rval = validateResult result |
456 | 6583e677 | Iustin Pop | return rval |
457 | 9a2ff880 | Iustin Pop | |
458 | 619e89c8 | Iustin Pop | -- | Parse job submission result. |
459 | 7adb7dff | Iustin Pop | parseSubmitJobResult :: JSValue -> ErrorResult JobId |
460 | 7adb7dff | Iustin Pop | parseSubmitJobResult (JSArray [JSBool True, v]) = |
461 | c48711d5 | Iustin Pop | case J.readJSON v of |
462 | c48711d5 | Iustin Pop | J.Error msg -> Bad $ LuxiError msg |
463 | c48711d5 | Iustin Pop | J.Ok v' -> Ok v' |
464 | 619e89c8 | Iustin Pop | parseSubmitJobResult (JSArray [JSBool False, JSString x]) = |
465 | 7adb7dff | Iustin Pop | Bad . LuxiError $ fromJSString x |
466 | 7adb7dff | Iustin Pop | parseSubmitJobResult v = |
467 | 7adb7dff | Iustin Pop | Bad . LuxiError $ "Unknown result from the master daemon: " ++ |
468 | 7adb7dff | Iustin Pop | show (pp_value v) |
469 | 619e89c8 | Iustin Pop | |
470 | 9a2ff880 | Iustin Pop | -- | Specialized submitManyJobs call. |
471 | 7e723913 | Iustin Pop | submitManyJobs :: Client -> [[MetaOpCode]] -> IO (ErrorResult [JobId]) |
472 | 9a2ff880 | Iustin Pop | submitManyJobs s jobs = do |
473 | 683b1ca7 | Iustin Pop | rval <- callMethod (SubmitManyJobs jobs) s |
474 | 9a2ff880 | Iustin Pop | -- map each result (status, payload) pair into a nice Result ADT |
475 | 9a2ff880 | Iustin Pop | return $ case rval of |
476 | 9a2ff880 | Iustin Pop | Bad x -> Bad x |
477 | 619e89c8 | Iustin Pop | Ok (JSArray r) -> mapM parseSubmitJobResult r |
478 | 7adb7dff | Iustin Pop | x -> Bad . LuxiError $ |
479 | 7adb7dff | Iustin Pop | "Cannot parse response from Ganeti: " ++ show x |
480 | 9a2ff880 | Iustin Pop | |
481 | 9a2ff880 | Iustin Pop | -- | Custom queryJobs call. |
482 | 7adb7dff | Iustin Pop | queryJobsStatus :: Client -> [JobId] -> IO (ErrorResult [JobStatus]) |
483 | 9a2ff880 | Iustin Pop | queryJobsStatus s jids = do |
484 | 76b62028 | Iustin Pop | rval <- callMethod (QueryJobs jids ["status"]) s |
485 | 9a2ff880 | Iustin Pop | return $ case rval of |
486 | 9a2ff880 | Iustin Pop | Bad x -> Bad x |
487 | 9a2ff880 | Iustin Pop | Ok y -> case J.readJSON y::(J.Result [[JobStatus]]) of |
488 | 9a2ff880 | Iustin Pop | J.Ok vals -> if any null vals |
489 | 7adb7dff | Iustin Pop | then Bad $ |
490 | 7adb7dff | Iustin Pop | LuxiError "Missing job status field" |
491 | 9a2ff880 | Iustin Pop | else Ok (map head vals) |
492 | 7adb7dff | Iustin Pop | J.Error x -> Bad $ LuxiError x |