Statistics
| Branch: | Tag: | Revision:

root / htools / Ganeti / Luxi.hs @ 9d4cc8ed

History | View | Annotate | Download (14.5 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 0aff2293 Iustin Pop
  , RecvResult(..)
34 be747966 Iustin Pop
  , TagObject(..)
35 0aff2293 Iustin Pop
  , strOfOp
36 ebf38064 Iustin Pop
  , getClient
37 13f2321c Iustin Pop
  , getServer
38 13f2321c Iustin Pop
  , acceptClient
39 ebf38064 Iustin Pop
  , closeClient
40 0aff2293 Iustin Pop
  , closeServer
41 ebf38064 Iustin Pop
  , callMethod
42 ebf38064 Iustin Pop
  , submitManyJobs
43 ebf38064 Iustin Pop
  , queryJobsStatus
44 cdd495ae Iustin Pop
  , buildCall
45 0aff2293 Iustin Pop
  , buildResponse
46 cdd495ae Iustin Pop
  , validateCall
47 cdd495ae Iustin Pop
  , decodeCall
48 13f2321c Iustin Pop
  , recvMsg
49 0aff2293 Iustin Pop
  , recvMsgExt
50 13f2321c Iustin Pop
  , sendMsg
51 ebf38064 Iustin Pop
  ) where
52 6583e677 Iustin Pop
53 0aff2293 Iustin Pop
import Control.Exception (catch)
54 6583e677 Iustin Pop
import Data.IORef
55 76b62028 Iustin Pop
import Data.Ratio (numerator, denominator)
56 e821050d Iustin Pop
import qualified Data.ByteString as B
57 e821050d Iustin Pop
import qualified Data.ByteString.UTF8 as UTF8
58 e821050d Iustin Pop
import Data.Word (Word8)
59 6583e677 Iustin Pop
import Control.Monad
60 0aff2293 Iustin Pop
import Prelude hiding (catch)
61 0903280b Iustin Pop
import Text.JSON (encodeStrict, decodeStrict)
62 6583e677 Iustin Pop
import qualified Text.JSON as J
63 6583e677 Iustin Pop
import Text.JSON.Types
64 0aff2293 Iustin Pop
import System.Directory (removeFile)
65 e821050d Iustin Pop
import System.IO (hClose, hFlush, hWaitForInput, Handle, IOMode(..))
66 0aff2293 Iustin Pop
import System.IO.Error (isEOFError)
67 6583e677 Iustin Pop
import System.Timeout
68 6583e677 Iustin Pop
import qualified Network.Socket as S
69 6583e677 Iustin Pop
70 b69be409 Iustin Pop
import Ganeti.HTools.JSON
71 6583e677 Iustin Pop
import Ganeti.HTools.Types
72 cdd495ae Iustin Pop
import Ganeti.HTools.Utils
73 6583e677 Iustin Pop
74 92678b3c Iustin Pop
import Ganeti.Constants
75 9a2ff880 Iustin Pop
import Ganeti.Jobs (JobStatus)
76 683b1ca7 Iustin Pop
import Ganeti.OpCodes (OpCode)
77 dc6a0f82 Iustin Pop
import qualified Ganeti.Qlang as Qlang
78 a0090487 Agata Murawska
import Ganeti.THH
79 9a2ff880 Iustin Pop
80 6583e677 Iustin Pop
-- * Utility functions
81 6583e677 Iustin Pop
82 6583e677 Iustin Pop
-- | Wrapper over System.Timeout.timeout that fails in the IO monad.
83 6583e677 Iustin Pop
withTimeout :: Int -> String -> IO a -> IO a
84 6583e677 Iustin Pop
withTimeout secs descr action = do
85 ebf38064 Iustin Pop
  result <- timeout (secs * 1000000) action
86 3603605a Iustin Pop
  case result of
87 3603605a Iustin Pop
    Nothing -> fail $ "Timeout in " ++ descr
88 3603605a Iustin Pop
    Just v -> return v
89 6583e677 Iustin Pop
90 6583e677 Iustin Pop
-- * Generic protocol functionality
91 6583e677 Iustin Pop
92 0aff2293 Iustin Pop
-- | Result of receiving a message from the socket.
93 0aff2293 Iustin Pop
data RecvResult = RecvConnClosed    -- ^ Connection closed
94 0aff2293 Iustin Pop
                | RecvError String  -- ^ Any other error
95 0aff2293 Iustin Pop
                | RecvOk String     -- ^ Successfull receive
96 0aff2293 Iustin Pop
                  deriving (Show, Read, Eq)
97 0aff2293 Iustin Pop
98 ccc817a2 Iustin Pop
-- | The Ganeti job type.
99 76b62028 Iustin Pop
type JobId = Int
100 ccc817a2 Iustin Pop
101 be747966 Iustin Pop
-- | Data type representing what items do the tag operations apply to.
102 be747966 Iustin Pop
$(declareSADT "TagObject"
103 be747966 Iustin Pop
  [ ("TagInstance", 'tagInstance)
104 be747966 Iustin Pop
  , ("TagNode",     'tagNode)
105 be747966 Iustin Pop
  , ("TagGroup",    'tagNodegroup)
106 be747966 Iustin Pop
  , ("TagCluster",  'tagCluster)
107 be747966 Iustin Pop
  ])
108 be747966 Iustin Pop
$(makeJSONInstance ''TagObject)
109 be747966 Iustin Pop
110 a0090487 Agata Murawska
-- | Currently supported Luxi operations and JSON serialization.
111 a0090487 Agata Murawska
$(genLuxiOp "LuxiOp"
112 fae980e5 Iustin Pop
  [(luxiReqQuery,
113 8a9ee1e9 Iustin Pop
    [ ("what",    [t| Qlang.ItemType |])
114 4b71f30c Iustin Pop
    , ("fields",  [t| [String]  |])
115 4b71f30c Iustin Pop
    , ("qfilter", [t| Qlang.Filter |])
116 ebf38064 Iustin Pop
    ])
117 fae980e5 Iustin Pop
  , (luxiReqQueryNodes,
118 4b71f30c Iustin Pop
     [ ("names",  [t| [String] |])
119 4b71f30c Iustin Pop
     , ("fields", [t| [String] |])
120 4b71f30c Iustin Pop
     , ("lock",   [t| Bool     |])
121 ebf38064 Iustin Pop
     ])
122 fae980e5 Iustin Pop
  , (luxiReqQueryGroups,
123 4b71f30c Iustin Pop
     [ ("names",  [t| [String] |])
124 4b71f30c Iustin Pop
     , ("fields", [t| [String] |])
125 4b71f30c Iustin Pop
     , ("lock",   [t| Bool     |])
126 ebf38064 Iustin Pop
     ])
127 fae980e5 Iustin Pop
  , (luxiReqQueryInstances,
128 4b71f30c Iustin Pop
     [ ("names",  [t| [String] |])
129 4b71f30c Iustin Pop
     , ("fields", [t| [String] |])
130 4b71f30c Iustin Pop
     , ("lock",   [t| Bool     |])
131 ebf38064 Iustin Pop
     ])
132 fae980e5 Iustin Pop
  , (luxiReqQueryJobs,
133 4b71f30c Iustin Pop
     [ ("ids",    [t| [Int]    |])
134 4b71f30c Iustin Pop
     , ("fields", [t| [String] |])
135 ebf38064 Iustin Pop
     ])
136 fae980e5 Iustin Pop
  , (luxiReqQueryExports,
137 4b71f30c Iustin Pop
     [ ("nodes", [t| [String] |])
138 4b71f30c Iustin Pop
     , ("lock",  [t| Bool     |])
139 ebf38064 Iustin Pop
     ])
140 fae980e5 Iustin Pop
  , (luxiReqQueryConfigValues,
141 4b71f30c Iustin Pop
     [ ("fields", [t| [String] |]) ]
142 ebf38064 Iustin Pop
    )
143 fae980e5 Iustin Pop
  , (luxiReqQueryClusterInfo, [])
144 fae980e5 Iustin Pop
  , (luxiReqQueryTags,
145 be747966 Iustin Pop
     [ ("kind", [t| TagObject |])
146 4b71f30c Iustin Pop
     , ("name", [t| String |])
147 ebf38064 Iustin Pop
     ])
148 fae980e5 Iustin Pop
  , (luxiReqSubmitJob,
149 4b71f30c Iustin Pop
     [ ("job", [t| [OpCode] |]) ]
150 ebf38064 Iustin Pop
    )
151 fae980e5 Iustin Pop
  , (luxiReqSubmitManyJobs,
152 4b71f30c Iustin Pop
     [ ("ops", [t| [[OpCode]] |]) ]
153 ebf38064 Iustin Pop
    )
154 fae980e5 Iustin Pop
  , (luxiReqWaitForJobChange,
155 4b71f30c Iustin Pop
     [ ("job",      [t| Int     |])
156 4b71f30c Iustin Pop
     , ("fields",   [t| [String]|])
157 4b71f30c Iustin Pop
     , ("prev_job", [t| JSValue |])
158 4b71f30c Iustin Pop
     , ("prev_log", [t| JSValue |])
159 4b71f30c Iustin Pop
     , ("tmout",    [t| Int     |])
160 ebf38064 Iustin Pop
     ])
161 fae980e5 Iustin Pop
  , (luxiReqArchiveJob,
162 4b71f30c Iustin Pop
     [ ("job", [t| Int |]) ]
163 ebf38064 Iustin Pop
    )
164 fae980e5 Iustin Pop
  , (luxiReqAutoArchiveJobs,
165 4b71f30c Iustin Pop
     [ ("age",   [t| Int |])
166 4b71f30c Iustin Pop
     , ("tmout", [t| Int |])
167 ebf38064 Iustin Pop
     ])
168 fae980e5 Iustin Pop
  , (luxiReqCancelJob,
169 4b71f30c Iustin Pop
     [ ("job", [t| Int |]) ]
170 ebf38064 Iustin Pop
    )
171 fae980e5 Iustin Pop
  , (luxiReqSetDrainFlag,
172 4b71f30c Iustin Pop
     [ ("flag", [t| Bool |]) ]
173 ebf38064 Iustin Pop
    )
174 fae980e5 Iustin Pop
  , (luxiReqSetWatcherPause,
175 4b71f30c Iustin Pop
     [ ("duration", [t| Double |]) ]
176 ebf38064 Iustin Pop
    )
177 a0090487 Agata Murawska
  ])
178 6583e677 Iustin Pop
179 95d0d502 Iustin Pop
$(makeJSONInstance ''LuxiReq)
180 95d0d502 Iustin Pop
181 6583e677 Iustin Pop
-- | The serialisation of LuxiOps into strings in messages.
182 a0090487 Agata Murawska
$(genStrOfOp ''LuxiOp "strOfOp")
183 6583e677 Iustin Pop
184 cdd495ae Iustin Pop
-- | Type holding the initial (unparsed) Luxi call.
185 cdd495ae Iustin Pop
data LuxiCall = LuxiCall LuxiReq JSValue
186 cdd495ae Iustin Pop
187 6583e677 Iustin Pop
-- | The end-of-message separator.
188 e821050d Iustin Pop
eOM :: Word8
189 e821050d Iustin Pop
eOM = 3
190 e821050d Iustin Pop
191 e821050d Iustin Pop
-- | The end-of-message encoded as a ByteString.
192 e821050d Iustin Pop
bEOM :: B.ByteString
193 e821050d Iustin Pop
bEOM = B.singleton eOM
194 6583e677 Iustin Pop
195 6583e677 Iustin Pop
-- | Valid keys in the requests and responses.
196 6583e677 Iustin Pop
data MsgKeys = Method
197 6583e677 Iustin Pop
             | Args
198 6583e677 Iustin Pop
             | Success
199 6583e677 Iustin Pop
             | Result
200 6583e677 Iustin Pop
201 6583e677 Iustin Pop
-- | The serialisation of MsgKeys into strings in messages.
202 a0090487 Agata Murawska
$(genStrOfKey ''MsgKeys "strOfKey")
203 6583e677 Iustin Pop
204 6583e677 Iustin Pop
-- | Luxi client encapsulation.
205 e821050d Iustin Pop
data Client = Client { socket :: Handle           -- ^ The socket of the client
206 e821050d Iustin Pop
                     , rbuf :: IORef B.ByteString -- ^ Already received buffer
207 6583e677 Iustin Pop
                     }
208 6583e677 Iustin Pop
209 6583e677 Iustin Pop
-- | Connects to the master daemon and returns a luxi Client.
210 6583e677 Iustin Pop
getClient :: String -> IO Client
211 6583e677 Iustin Pop
getClient path = do
212 ebf38064 Iustin Pop
  s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol
213 ebf38064 Iustin Pop
  withTimeout connTimeout "creating luxi connection" $
214 ebf38064 Iustin Pop
              S.connect s (S.SockAddrUnix path)
215 e821050d Iustin Pop
  rf <- newIORef B.empty
216 e821050d Iustin Pop
  h <- S.socketToHandle s ReadWriteMode
217 e821050d Iustin Pop
  return Client { socket=h, rbuf=rf }
218 6583e677 Iustin Pop
219 13f2321c Iustin Pop
-- | Creates and returns a server endpoint.
220 13f2321c Iustin Pop
getServer :: FilePath -> IO S.Socket
221 13f2321c Iustin Pop
getServer path = do
222 13f2321c Iustin Pop
  s <- S.socket S.AF_UNIX S.Stream S.defaultProtocol
223 13f2321c Iustin Pop
  S.bindSocket s (S.SockAddrUnix path)
224 13f2321c Iustin Pop
  S.listen s 5 -- 5 is the max backlog
225 13f2321c Iustin Pop
  return s
226 13f2321c Iustin Pop
227 0aff2293 Iustin Pop
-- | Closes a server endpoint.
228 0aff2293 Iustin Pop
-- FIXME: this should be encapsulated into a nicer type.
229 0aff2293 Iustin Pop
closeServer :: FilePath -> S.Socket -> IO ()
230 0aff2293 Iustin Pop
closeServer path sock = do
231 0aff2293 Iustin Pop
  S.sClose sock
232 0aff2293 Iustin Pop
  removeFile path
233 0aff2293 Iustin Pop
234 13f2321c Iustin Pop
-- | Accepts a client
235 13f2321c Iustin Pop
acceptClient :: S.Socket -> IO Client
236 13f2321c Iustin Pop
acceptClient s = do
237 13f2321c Iustin Pop
  -- second return is the address of the client, which we ignore here
238 13f2321c Iustin Pop
  (client_socket, _) <- S.accept s
239 13f2321c Iustin Pop
  new_buffer <- newIORef B.empty
240 13f2321c Iustin Pop
  handle <- S.socketToHandle client_socket ReadWriteMode
241 13f2321c Iustin Pop
  return Client { socket=handle, rbuf=new_buffer }
242 13f2321c Iustin Pop
243 6583e677 Iustin Pop
-- | Closes the client socket.
244 6583e677 Iustin Pop
closeClient :: Client -> IO ()
245 e821050d Iustin Pop
closeClient = hClose . socket
246 6583e677 Iustin Pop
247 6583e677 Iustin Pop
-- | Sends a message over a luxi transport.
248 6583e677 Iustin Pop
sendMsg :: Client -> String -> IO ()
249 e821050d Iustin Pop
sendMsg s buf = withTimeout queryTimeout "sending luxi message" $ do
250 e821050d Iustin Pop
  let encoded = UTF8.fromString buf
251 e821050d Iustin Pop
      handle = socket s
252 e821050d Iustin Pop
  B.hPut handle encoded
253 e821050d Iustin Pop
  B.hPut handle bEOM
254 e821050d Iustin Pop
  hFlush handle
255 e821050d Iustin Pop
256 e821050d Iustin Pop
-- | Given a current buffer and the handle, it will read from the
257 e821050d Iustin Pop
-- network until we get a full message, and it will return that
258 e821050d Iustin Pop
-- message and the leftover buffer contents.
259 e821050d Iustin Pop
recvUpdate :: Handle -> B.ByteString -> IO (B.ByteString, B.ByteString)
260 e821050d Iustin Pop
recvUpdate handle obuf = do
261 e821050d Iustin Pop
  nbuf <- withTimeout queryTimeout "reading luxi response" $ do
262 e821050d Iustin Pop
            _ <- hWaitForInput handle (-1)
263 e821050d Iustin Pop
            B.hGetNonBlocking handle 4096
264 e821050d Iustin Pop
  let (msg, remaining) = B.break (eOM ==) nbuf
265 e821050d Iustin Pop
      newbuf = B.append obuf msg
266 e821050d Iustin Pop
  if B.null remaining
267 e821050d Iustin Pop
    then recvUpdate handle newbuf
268 e821050d Iustin Pop
    else return (newbuf, B.tail remaining)
269 6583e677 Iustin Pop
270 6583e677 Iustin Pop
-- | Waits for a message over a luxi transport.
271 6583e677 Iustin Pop
recvMsg :: Client -> IO String
272 6583e677 Iustin Pop
recvMsg s = do
273 6583e677 Iustin Pop
  cbuf <- readIORef $ rbuf s
274 e821050d Iustin Pop
  let (imsg, ibuf) = B.break (eOM ==) cbuf
275 95f490de Iustin Pop
  (msg, nbuf) <-
276 e821050d Iustin Pop
    if B.null ibuf      -- if old buffer didn't contain a full message
277 e821050d Iustin Pop
      then recvUpdate (socket s) cbuf   -- then we read from network
278 e821050d Iustin Pop
      else return (imsg, B.tail ibuf)   -- else we return data from our buffer
279 6583e677 Iustin Pop
  writeIORef (rbuf s) nbuf
280 e821050d Iustin Pop
  return $ UTF8.toString msg
281 6583e677 Iustin Pop
282 0aff2293 Iustin Pop
-- | Extended wrapper over recvMsg.
283 0aff2293 Iustin Pop
recvMsgExt :: Client -> IO RecvResult
284 0aff2293 Iustin Pop
recvMsgExt s =
285 0aff2293 Iustin Pop
  catch (liftM RecvOk (recvMsg s)) $ \e ->
286 0aff2293 Iustin Pop
    if isEOFError e
287 0aff2293 Iustin Pop
      then return RecvConnClosed
288 0aff2293 Iustin Pop
      else return $ RecvError (show e)
289 0aff2293 Iustin Pop
290 6583e677 Iustin Pop
-- | Serialize a request to String.
291 6583e677 Iustin Pop
buildCall :: LuxiOp  -- ^ The method
292 6583e677 Iustin Pop
          -> String  -- ^ The serialized form
293 683b1ca7 Iustin Pop
buildCall lo =
294 2cdaf225 Iustin Pop
  let ja = [ (strOfKey Method, J.showJSON $ strOfOp lo)
295 2cdaf225 Iustin Pop
           , (strOfKey Args, opToArgs lo)
296 ebf38064 Iustin Pop
           ]
297 ebf38064 Iustin Pop
      jo = toJSObject ja
298 ebf38064 Iustin Pop
  in encodeStrict jo
299 6583e677 Iustin Pop
300 0aff2293 Iustin Pop
-- | Serialize the response to String.
301 0aff2293 Iustin Pop
buildResponse :: Bool    -- ^ Success
302 0aff2293 Iustin Pop
              -> JSValue -- ^ The arguments
303 0aff2293 Iustin Pop
              -> String  -- ^ The serialized form
304 0aff2293 Iustin Pop
buildResponse success args =
305 0aff2293 Iustin Pop
  let ja = [ (strOfKey Success, JSBool success)
306 0aff2293 Iustin Pop
           , (strOfKey Result, args)]
307 0aff2293 Iustin Pop
      jo = toJSObject ja
308 0aff2293 Iustin Pop
  in encodeStrict jo
309 0aff2293 Iustin Pop
310 cdd495ae Iustin Pop
-- | Check that luxi request contains the required keys and parse it.
311 cdd495ae Iustin Pop
validateCall :: String -> Result LuxiCall
312 cdd495ae Iustin Pop
validateCall s = do
313 0aff2293 Iustin Pop
  arr <- fromJResult "parsing top-level luxi message" $
314 0aff2293 Iustin Pop
         decodeStrict s::Result (JSObject JSValue)
315 cdd495ae Iustin Pop
  let aobj = fromJSObject arr
316 cdd495ae Iustin Pop
  call <- fromObj aobj (strOfKey Method)::Result LuxiReq
317 cdd495ae Iustin Pop
  args <- fromObj aobj (strOfKey Args)
318 cdd495ae Iustin Pop
  return (LuxiCall call args)
319 cdd495ae Iustin Pop
320 cdd495ae Iustin Pop
-- | Converts Luxi call arguments into a 'LuxiOp' data structure.
321 cdd495ae Iustin Pop
--
322 cdd495ae Iustin Pop
-- This is currently hand-coded until we make it more uniform so that
323 cdd495ae Iustin Pop
-- it can be generated using TH.
324 cdd495ae Iustin Pop
decodeCall :: LuxiCall -> Result LuxiOp
325 cdd495ae Iustin Pop
decodeCall (LuxiCall call args) =
326 cdd495ae Iustin Pop
  case call of
327 cdd495ae Iustin Pop
    ReqQueryJobs -> do
328 cdd495ae Iustin Pop
              (jid, jargs) <- fromJVal args
329 76b62028 Iustin Pop
              rid <- mapM parseJobId jid
330 cdd495ae Iustin Pop
              let rargs = map fromJSString jargs
331 cdd495ae Iustin Pop
              return $ QueryJobs rid rargs
332 cdd495ae Iustin Pop
    ReqQueryInstances -> do
333 cdd495ae Iustin Pop
              (names, fields, locking) <- fromJVal args
334 cdd495ae Iustin Pop
              return $ QueryInstances names fields locking
335 cdd495ae Iustin Pop
    ReqQueryNodes -> do
336 cdd495ae Iustin Pop
              (names, fields, locking) <- fromJVal args
337 cdd495ae Iustin Pop
              return $ QueryNodes names fields locking
338 cdd495ae Iustin Pop
    ReqQueryGroups -> do
339 cdd495ae Iustin Pop
              (names, fields, locking) <- fromJVal args
340 cdd495ae Iustin Pop
              return $ QueryGroups names fields locking
341 cdd495ae Iustin Pop
    ReqQueryClusterInfo -> do
342 cdd495ae Iustin Pop
              return QueryClusterInfo
343 cdd495ae Iustin Pop
    ReqQuery -> do
344 9a94c848 Iustin Pop
              (what, fields, qfilter) <- fromJVal args
345 9a94c848 Iustin Pop
              return $ Query what fields qfilter
346 cdd495ae Iustin Pop
    ReqSubmitJob -> do
347 cdd495ae Iustin Pop
              [ops1] <- fromJVal args
348 cdd495ae Iustin Pop
              ops2 <- mapM (fromJResult (luxiReqToRaw call) . J.readJSON) ops1
349 cdd495ae Iustin Pop
              return $ SubmitJob ops2
350 cdd495ae Iustin Pop
    ReqSubmitManyJobs -> do
351 cdd495ae Iustin Pop
              [ops1] <- fromJVal args
352 cdd495ae Iustin Pop
              ops2 <- mapM (fromJResult (luxiReqToRaw call) . J.readJSON) ops1
353 cdd495ae Iustin Pop
              return $ SubmitManyJobs ops2
354 cdd495ae Iustin Pop
    ReqWaitForJobChange -> do
355 cdd495ae Iustin Pop
              (jid, fields, pinfo, pidx, wtmout) <-
356 cdd495ae Iustin Pop
                -- No instance for 5-tuple, code copied from the
357 cdd495ae Iustin Pop
                -- json sources and adapted
358 cdd495ae Iustin Pop
                fromJResult "Parsing WaitForJobChange message" $
359 cdd495ae Iustin Pop
                case args of
360 cdd495ae Iustin Pop
                  JSArray [a, b, c, d, e] ->
361 cdd495ae Iustin Pop
                    (,,,,) `fmap`
362 cdd495ae Iustin Pop
                    J.readJSON a `ap`
363 cdd495ae Iustin Pop
                    J.readJSON b `ap`
364 cdd495ae Iustin Pop
                    J.readJSON c `ap`
365 cdd495ae Iustin Pop
                    J.readJSON d `ap`
366 cdd495ae Iustin Pop
                    J.readJSON e
367 cdd495ae Iustin Pop
                  _ -> J.Error "Not enough values"
368 76b62028 Iustin Pop
              rid <- parseJobId jid
369 cdd495ae Iustin Pop
              return $ WaitForJobChange rid fields pinfo pidx wtmout
370 cdd495ae Iustin Pop
    ReqArchiveJob -> do
371 cdd495ae Iustin Pop
              [jid] <- fromJVal args
372 76b62028 Iustin Pop
              rid <- parseJobId jid
373 cdd495ae Iustin Pop
              return $ ArchiveJob rid
374 cdd495ae Iustin Pop
    ReqAutoArchiveJobs -> do
375 cdd495ae Iustin Pop
              (age, tmout) <- fromJVal args
376 cdd495ae Iustin Pop
              return $ AutoArchiveJobs age tmout
377 cdd495ae Iustin Pop
    ReqQueryExports -> do
378 cdd495ae Iustin Pop
              (nodes, lock) <- fromJVal args
379 cdd495ae Iustin Pop
              return $ QueryExports nodes lock
380 cdd495ae Iustin Pop
    ReqQueryConfigValues -> do
381 cdd495ae Iustin Pop
              [fields] <- fromJVal args
382 cdd495ae Iustin Pop
              return $ QueryConfigValues fields
383 cdd495ae Iustin Pop
    ReqQueryTags -> do
384 cdd495ae Iustin Pop
              (kind, name) <- fromJVal args
385 cdd495ae Iustin Pop
              return $ QueryTags kind name
386 cdd495ae Iustin Pop
    ReqCancelJob -> do
387 cdd495ae Iustin Pop
              [job] <- fromJVal args
388 76b62028 Iustin Pop
              rid <- parseJobId job
389 cdd495ae Iustin Pop
              return $ CancelJob rid
390 cdd495ae Iustin Pop
    ReqSetDrainFlag -> do
391 cdd495ae Iustin Pop
              [flag] <- fromJVal args
392 cdd495ae Iustin Pop
              return $ SetDrainFlag flag
393 cdd495ae Iustin Pop
    ReqSetWatcherPause -> do
394 cdd495ae Iustin Pop
              [duration] <- fromJVal args
395 cdd495ae Iustin Pop
              return $ SetWatcherPause duration
396 cdd495ae Iustin Pop
397 6583e677 Iustin Pop
-- | Check that luxi responses contain the required keys and that the
398 6583e677 Iustin Pop
-- call was successful.
399 6583e677 Iustin Pop
validateResult :: String -> Result JSValue
400 6583e677 Iustin Pop
validateResult s = do
401 e821050d Iustin Pop
  when (UTF8.replacement_char `elem` s) $
402 e821050d Iustin Pop
       fail "Failed to decode UTF-8, detected replacement char after decoding"
403 c96d44df Iustin Pop
  oarr <- fromJResult "Parsing LUXI response"
404 c96d44df Iustin Pop
          (decodeStrict s)::Result (JSObject JSValue)
405 262f3e6c Iustin Pop
  let arr = J.fromJSObject oarr
406 e8230242 Iustin Pop
  status <- fromObj arr (strOfKey Success)::Result Bool
407 6583e677 Iustin Pop
  let rkey = strOfKey Result
408 3603605a Iustin Pop
  if status
409 3603605a Iustin Pop
    then fromObj arr rkey
410 3603605a Iustin Pop
    else fromObj arr rkey >>= fail
411 6583e677 Iustin Pop
412 6583e677 Iustin Pop
-- | Generic luxi method call.
413 683b1ca7 Iustin Pop
callMethod :: LuxiOp -> Client -> IO (Result JSValue)
414 683b1ca7 Iustin Pop
callMethod method s = do
415 683b1ca7 Iustin Pop
  sendMsg s $ buildCall method
416 6583e677 Iustin Pop
  result <- recvMsg s
417 6583e677 Iustin Pop
  let rval = validateResult result
418 6583e677 Iustin Pop
  return rval
419 9a2ff880 Iustin Pop
420 619e89c8 Iustin Pop
-- | Parses a job ID.
421 619e89c8 Iustin Pop
parseJobId :: JSValue -> Result JobId
422 76b62028 Iustin Pop
parseJobId (JSString x) = tryRead "parsing job id" . fromJSString $ x
423 76b62028 Iustin Pop
parseJobId (JSRational _ x) =
424 76b62028 Iustin Pop
  if denominator x /= 1
425 76b62028 Iustin Pop
    then Bad $ "Got fractional job ID from master daemon?! Value:" ++ show x
426 76b62028 Iustin Pop
    -- FIXME: potential integer overflow here on 32-bit platforms
427 76b62028 Iustin Pop
    else Ok . fromIntegral . numerator $ x
428 619e89c8 Iustin Pop
parseJobId x = Bad $ "Wrong type/value for job id: " ++ show x
429 619e89c8 Iustin Pop
430 619e89c8 Iustin Pop
-- | Parse job submission result.
431 619e89c8 Iustin Pop
parseSubmitJobResult :: JSValue -> Result JobId
432 619e89c8 Iustin Pop
parseSubmitJobResult (JSArray [JSBool True, v]) = parseJobId v
433 619e89c8 Iustin Pop
parseSubmitJobResult (JSArray [JSBool False, JSString x]) =
434 619e89c8 Iustin Pop
  Bad (fromJSString x)
435 619e89c8 Iustin Pop
parseSubmitJobResult v = Bad $ "Unknown result from the master daemon" ++
436 619e89c8 Iustin Pop
                         show v
437 619e89c8 Iustin Pop
438 9a2ff880 Iustin Pop
-- | Specialized submitManyJobs call.
439 ccc817a2 Iustin Pop
submitManyJobs :: Client -> [[OpCode]] -> IO (Result [JobId])
440 9a2ff880 Iustin Pop
submitManyJobs s jobs = do
441 683b1ca7 Iustin Pop
  rval <- callMethod (SubmitManyJobs jobs) s
442 9a2ff880 Iustin Pop
  -- map each result (status, payload) pair into a nice Result ADT
443 9a2ff880 Iustin Pop
  return $ case rval of
444 9a2ff880 Iustin Pop
             Bad x -> Bad x
445 619e89c8 Iustin Pop
             Ok (JSArray r) -> mapM parseSubmitJobResult r
446 9a2ff880 Iustin Pop
             x -> Bad ("Cannot parse response from Ganeti: " ++ show x)
447 9a2ff880 Iustin Pop
448 9a2ff880 Iustin Pop
-- | Custom queryJobs call.
449 ccc817a2 Iustin Pop
queryJobsStatus :: Client -> [JobId] -> IO (Result [JobStatus])
450 9a2ff880 Iustin Pop
queryJobsStatus s jids = do
451 76b62028 Iustin Pop
  rval <- callMethod (QueryJobs jids ["status"]) s
452 9a2ff880 Iustin Pop
  return $ case rval of
453 9a2ff880 Iustin Pop
             Bad x -> Bad x
454 9a2ff880 Iustin Pop
             Ok y -> case J.readJSON y::(J.Result [[JobStatus]]) of
455 9a2ff880 Iustin Pop
                       J.Ok vals -> if any null vals
456 9a2ff880 Iustin Pop
                                    then Bad "Missing job status field"
457 9a2ff880 Iustin Pop
                                    else Ok (map head vals)
458 9a2ff880 Iustin Pop
                       J.Error x -> Bad x